Building Custom OS X Dashboard Widgets
This guide is meant to supplement the comprehensive (although limited) Apple Dashboard Reference page, which provides a lot of technical details, but lacks some of the important “between the lines” stuff. This guide will cover some tools to use, shortcomings, gotcha's, you name it. I've picked up these tips while developing the NeoInvoice Time Tracking Widget, along with development of some other smaller widgets.
Tip #1: Developing in Browser
While developing your widget, you will want to build it in a web browser. Since the widget will be rendered using WebKit, you will want to use Google Chrome or Apple Safari to develop. Don't bother using Firefox, or trying to make the HTML render the same in multiple browsers. The built-in web developer tools (Cmd + Opt + I) will help you out a lot. Apple creates an object available to your Widget, appropriately named “widget“, which allows you to do some magic you can't normally do in a browser (e.g. execute commands, rotate widget animations, run an app or open a website, and save preferences [an alternative to cookies]).
This widget object, of course, is NOT available to your browser. You will need to keep this in mind while developing your Widget. You can use the following line of code to determine if you are in the browser or Widget mode, and either do or do not run functionality:
var isWidget = (typeof(widget) != "undefined");
Tip #2: Getting around Cross Site Request restrictions in your browser
A feature of modern browsers is that AJAX (XHR) requests are not able to access data in a separate domain. This prevents all sorts of bad things from happening. A Widget, however, doesn't have a domain and (if it's AJAX powered) will need to talk to a web service to get updates and send new data for a website. Since an author has a bit more control of a Widget than a website, it makes sense that the Widget doesn't require the same restrictions that a browser places on a website. Luckily for us, WebKit based browsers have a command line argument you can pass in to disable this feature.
Run one of the following commands in your Terminal (depending on which browser you prefer to develop on). Keep in mind that if you run other websites in the browser while developing, there are security risks involved.
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome --disable-web-security /Applications/Safari.app/Contents/MacOS/Safari --disable-web-security
Tip #3: Info.plist caching
The XML document which controls a bunch of your Widget's attributes
Info.plist, is cached rather aggressively by OS X. Or more specifically, certain pieces of it are (e.g. security settings). If you run your Widget once, and make a change to the plist file, it will remain in memory until a reboot (there should be a cached file to delete or a Terminal command to run to fix this, but I couldn't find it). So, try to set your options right the first time or be prepared for some hair pulling slowness.
Settings other than the security items would properly be reloaded each time a Widget was removed from the Dashboard and added again, such as the X and Y position of the close icon.
Tip #4: Running Command Line Scripts
test.php won't necessarily get run by the PHP interpreter, even if you prefix it with the proper script shebang. For this reason, you would either want to distribute the Widget as a .tar.gz file (which does store permissions), or better yet, execute the full command required to execute the file, such as
/usr/bin/php -q test.php.
Tip #5: Using the Widget Transition animation
Widgets have access to the flip animation (made popular by iOS), which allows you to show a
front side of your Widget. This is great for
flipping the Widget over when you want to show the config side of the screen, and flipping it back when you want to show the user the normal interface again. There are two functions involved when doing this. The first is widget.prepareForTransition(
ToBack). What this function does is take a screenshot of the Widget, and pause any updates being made. Once you run this function, you will want to change the DOM around (hiding the front screen items and showing the back screen items). Once you've done that, you are ready to show the other side by running widget.performTransition(). This function takes a screenshot of the new DOM, calculates and displays the animation to go between the two sides, and un-freezes the DOM.
if (isWidget) setTimeout("widget.performTransition()", 10);
Widgets run a lot like standard HTML documents, but there are a few pitfalls. Keep these tips in mind, and memorize the Apple Dashboard guide, and you will be on your way to creating an awesome web app experience in no time.