I have been developing a sub-site on an existing WordPress installation. This includes creating templates based on designs from our art department and developing a plugin to display recipes. After I got the design and code working, I began entering all the recipes by hand. Then I passed the test site URL along to my team members to let them do some quality control. I then received an email saying that I had to-do items on Basecamp. Some of the to do items were hard to track down because my coworkers simply put in a title. We have ~50 recipes and so I had to scroll through the recipes to find the offending one.
So I started thinking. What if there were a way to streamline the entering of to-do items and add the URL as well? Originally, I was going to use a page with frames but I couldn’t keep track of the URL after navigating away from the source page. Then I started thinking about the Evernote bookmarklet. It can copy a URL and even an entire web page. So I did a few searches on “bookmarklets” and came across an article by John Resig about using a bookmarklet to insert jQuery and then modify Digg.com. So I took the idea and began to got a basic jQuery dialog box loading into my page through a bookmark. Then I wrote a separate page to pull the relevant info from Basecamp and then populate some dropdown menus.
So let’s take a look at the bookmark. I can write my javascript as I normally would but it will be encapsulated in the href of an anchor tag. You have to use the `document.CreateElement` to get jQuery loaded and then you can use the jQuery syntax. I also learned about the `$.getScript()` function in jQuery. So I could save several lines of code per js include. In the next phase I want to put the loading of files and DOM creation in a separate file to streamline what’s in the bookmark and make it easier to edit.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<!—- bookmark.htm —->
<a href="
javascript:var%20c=document.createElement('link');c.href = '/wp-content/plugins/treu-quality-control/css/ui-lightness/jquery-ui-1.8.17.custom.css';
c.type = 'text/css';
c.rel = 'stylesheet';
c.media = 'screen';
document.body.appendChild(c);
var%20c2=document.createElement('link');c2.href = '/wp-content/plugins/treu-quality-control/css/quality_control.css';
c2.type = 'text/css';
c2.rel = 'stylesheet';
c2.media = 'screen';
document.body.appendChild(c2);
var%20s=document.createElement('script');s.setAttribute('src',%20'/wp-content/plugins/treu-quality-control/js/jquery-1.7.1.min.js');
s.type = 'text/javascript';
document.body.appendChild(s);
s.onload=function(){
$.getScript('/wp-content/plugins/treu-quality-control/js/jquery-ui-1.8.17.min.js', function(){
var%20d=document.createElement('div');
d.setAttribute('id',%20'wd_dialog_modal');
document.body.appendChild(d);
$('#qc_dialog_modal').dialog({
height: 350,
width: 400,
title: 'Create%20To%20Do%20Item%20on%20BaseCamp',
buttons: {
'Submit': function() {
$.post('/wp-content/plugins/treu-quality-control/ajax.php?action=createToDoItem',
{
url: $('#qc_url').val(),
comment: $('#qc_comment').val(),
responsible_party: $('#qc_username').val(),
ToDoListId: $('#qc_todolist').val()
},
function(data) {
$( this ).dialog( 'close' );
}
);
$( this ).dialog( 'close' );
},
Cancel: function() {
$( this ).dialog( 'close' );
}
},
});
var%20input = document.createElement('input');
input.type = 'hidden';
input.id = 'wd_url';
input.value = window.location;
d.appendChild(input);
var%20para = document.createElement('p');
d.appendChild(para);
var%20newtext = document.createTextNode('Add%20your%20To%20Do%20Item');
para.appendChild(newtext);
var%20input = document.createElement('textarea');
input.id = 'wd_comment';
para.appendChild(input);
var%20para2 = document.createElement('p');
d.appendChild(para2);
var%20newtext = document.createTextNode('Select%20Project:');
para2.appendChild(newtext);
var%20input = document.createElement('select');
input.id = 'wd_project';
para2.appendChild(input);
$.get('/wp-content/plugins/treu-quality-control/ajax.php?action=getProjects', function(data) {
$('#qc_project').html(data);
});
$('#qc_project').change(function() {
$.get('/wp-content/plugins/treu-quality-control/ajax.php?action=getProjectToDoLists&projectid='+$('#qc_project').val(), function(data) {
$('#qc_todolist').html(data);
});
});
var%20para3 = document.createElement('p');
d.appendChild(para3);
var%20newtext = document.createTextNode('Select%20To%20Do%20List:');
para3.appendChild(newtext);
var%20input = document.createElement('select');
input.id = 'wd_todolist';
para3.appendChild(input);
$.get('/wp-content/plugins/treu-quality-control/ajax.php?action=getProjectToDoLists', function(data) {
$('#qc_todolist').html(data);
});
var%20para4 = document.createElement('p');
d.appendChild(para4);
var%20newtext = document.createTextNode('Select%20User:');
para4.appendChild(newtext);
var%20input = document.createElement('select');
input.id = 'wd_username';
para4.appendChild(input);
$.get('/wp-content/plugins/treu-quality-control/ajax.php?action=getPersons', function(data) {
$('#qc_username').html(data);
});
});
};void(s);
">Bookmark</a>
</body>
</html>
I load bookmark.htm and drag the link to my bookmark bar. Now I just need to find a page that is in need of correcting. I click on the bookmark and a dialog will appear. I can enter my to-do item and choose the appropriate project, to-do list and the person responsible for fixing the issue.

Now let’s go over to Basecamp. As we can see, my to-do item was added to my list and the URL to the page was appended to the item.

I made this to scratch my own itch and I’m hoping that it’s easy enough to use that my coworkers will start using it as well. I also added a settings menu in the administration panel to enter the Basecamp API key, the Basecamp subdomain and the company id.

As a footnote, as I worked on the functions to pull the XML, I came across an issue using usort on SimpleXML objects. I came across an answer on StackOverflow. The author’s code sample got me on the right track.
I created a generic class and stored all the elements in it. However, after I did this I still couldn’t sort the new array of generic objects. The properties were retaining their simpleXML DNA. After some more research I learned I needed to cast each property as a string. Then I combined the first name and last name into a new property so I could sort the names.
function getPersons()
{
/* ... */
/* Get the XML from Basecamp */
$people = connectBasecamp($url);
/* Combine the first and last names into a new full_name property so I can sort them. */
foreach($people as $key => $r) {
// I had problems with dashes in the property names so changed them to underscores when creating my new object and then combine them here
$people[$key]->full_name = $people[$key]->last_name . ', ' . $people[$key]->first_name ;
}
/* Sort using my new function I created */
usort($people, "sortLastFirst");
/* ... */
}
function connectBasecamp($url=false, $data=false, $args=array()) {
/* ........ */
$xml = new SimpleXMLElement($page);
$i = 0;
foreach($xml as $key => $r) {
$array[$i] = new StdClass;
foreach($r as $k => $col) {
$k = str_replace('-','_',$k); /* Also learned that PHP doesn't like hypens in property names when you type them out. So just replaced them with underscores. */
$array[$i]->$k = (string)$col; /* Cast the property to a string and it removes the SimpleXML DNA and now I can sort */
}
$i++;
}
/* ... */
}
function sortLastFirst($a, $b) {
if ($a->full_name == $b->full_name) {
return 0;
}
return ($a->full_name < $b->full_name) ? -1 : 1;
}
I’m including a zip file with all the files if you would like to install it on one of your WordPress sites. My next idea is to figure out a way to use this concept to generate a “screenshot” that could be uploaded to Basecamp.