Creating a Dashboard Widget for Basecamp with Dashcode
Before I picked up a new MacBook Pro, I knew I wanted to write a Dashboard Widget for Basecamp. What I didn’t know was that I would also get to try out Apple’s yet to be officially released Dashcode. A couple of months ago, after the MacBook Pro’s started shipping, news surfaced that this widget development environment was accidentally included on the DVD’s that shipped with the laptops.
At around the same time the Dashcode news was wandering around the Mac-centric blogs, 37signals released an API for Basecamp. I have been using Basecamp to keep track of our home remodeling project. I decided to take a crack at writing a Dashboard Widget that leveraged the Basecamp API and RSS feed. What follows is a summary of my experiences with Dashcode and with the Basecamp API while creating Telescope–a Dashboard Widget for Basecamp.
Having never written a widget before, I wasn’t quite sure what was in store. The first screen that appears when launching Dashcode gives an option to start a new project from a predefined template. The three that are included in the MacBook Pro version are Custom, Countdown, and Blog Viewer (really just an RSS reader). As I knew I would be leveraging the Basecamp RSS feed, I chose the Blog Viewer template. Having been through the process once now, I would probably choose to start from the Custom template. I ended up spending a fair amount of time understanding the generated code and ultimately stripped out a bunch of code that I didn’t need for Telescope. One annoying bug in the generated source was one that made me think the whole thing wasn’t working and almost pushed me to ditch the idea of using Dashcode for Telescope. It turned out to be a bug in the template code that was attempting to filter the RSS feed to the last n days, causing me to think that the RSS viewer was broken altogether. After fixing that, things looked good to go.
Working with the layout, I encountered my first few bugs with Dashcode. I found that working with the graphical layout tool was a sometimes unpredictable experience–crashing Dashcode four or five times. At first I thought it was going to be a fruitless exercise due to the bugs. However, after deciding to give it a second shot after a frustrating initial attempt, I was able to avoid the bugs and make some real progress. The bug that was the most annoying was one that crashed Dashcode when editing text label in the layout. Avoiding complete edits of the labels seemed to minimize the crashes.
With the initial layout complete, the crashing was much less frequent. The editor worked well, providing syntax highlighting for the CSS, Javascript, and HTML files, block indenting, keyword completion, line numbers, the ability to jump to functions, and set and clear breakpoints. It’s not as complete as TextMate, but the integration with the debug environment makes it very useful.
The debugging capability was probably the most useful part of Dashcode. The key features that helped here were breakpoints, quick start/stop of widgets, the stack frame, and the Javascript evaluator. Building Telescope was the first time I had worked with XMLHttpRequest so the stack frame and evaluator were especially helpful in understanding the results I was getting back from the Basecamp API. It was pretty handy to be able to type something like this into the evaluator and see the result:
milestones[0].getElementsByTagName("milestone")[i].getElementsByTagName("completed")[0].firstChild.nodeValue
Then you can drop the snippet into the code and quickly restart the widget and see it in action. Much faster and more flexible than putting in alert statements or using the stack frame.
After using the evaluator to become familiar with navigating the XML results, I will probably use Prototype next time. It was a good learning experience, but I think using Prototype would have been quicker and easier.
One area that took some trial and error was the handling of HTTP authentication. XMLHttpRequest.open supports two ways of doing this. One with the username and password passed as independent parameters to open:
url = "http://hostname.clientsection.com/project/list";
projectMilestones.open("GET", url, true, basecampUsername, basecampPassword);
and the other with the username and password encoded into the URL string as in
url = "http://" + basecampUsername + ":" + basecampPassword + "@hostname.clientsection.com/project/list";
projectMilestones.open("GET", url, true);
I was only successful with the version with the username and password encoded into the URL.
One thing I was grateful for was the simplicity of the Basecamp API. The basic function of Telescope is to give a quick snapshot of current project status. The Basecamp RSS feed give the last 25 changes made. Whether this is for a specific project or all projects, depends on whether you use the project specific feed or the general account feed. Generally, Telescope is more useful when using the general account feed because it allows you to filter the events based on project. The project list comes from the Basecamp API–giving the user the ability to see updates for any active project.
In addition to pulling the list of active projects through the API, Telescope also retrieves the milestones for the selected project and displays the next milestone (or the earliest overdue milestone).
With the milestone API you can either pull all milestones for a project or milestones in a specific state. Unfortunately, I wanted both upcoming and overdue. Not wanting to load the server unnecessarily, I asked Jamis (37signals’ Basecamp API wizard) whether more small requests or fewer larger requests were preferable. He indicated a preference for fewer requests so I retrieved all milestones and filtered out the completed ones within Telescope.
This leads me to another topic. I wanted to allow the server to distinguish Telescope from other API requests so 37signals could let me know if Telescope was causing undue load on the servers. I have read a number of threads from server administrators complaining about widgets that refresh too often or otherwise load the servers with no perceived benefit to the widget user. I highly encourage widget creators to be sensitive to those running the applications on which the widgets depend.
One solution I came up with for this still hasn’t worked. It seemed like the perfect approach would be to set the User-Agent header to Telescope so that it appeared as a unique application in the server logs. In theory this should work, but for some reason, the User-Agent string was not getting updated. Here is the snippet that I will eventually try from another environment to determine if this is an issue with the Dashboard Engine:
projectMilestones.open("GET", milestoneURL, true);
projectMilestones.setRequestHeader('User-Agent', userAgent);
I will leave the code in on the hope that in some Dashboard Engine update it starts working. In the mean time, I have added a custom header (X-User-Agent) to distinguish Telescope. Unfortunately, most analysis tools won’t be looking for that so it will require manual analysis to determine what sort of load Telescope is putting on the applications.
Overall, I have had a very positive experience on all fronts with these new toys: MacBook Pro, Basecamp API, and Dashcode. Hopefully folks will find my exercise useful; either through using Telescope or for the background on the Basecamp API or Dashcode. I hope to extend Telescope in the future. Let me know how you are using it and what you think it should do.
Happy Basecamping!
Comments are closed
Comments are currently closed on this entry.