Andrew's Widgets | Developing Dashboard Widgets | Debugging Widget JavaScript

Apple Dashboard Developing Dashboard Widgets

A brief introduction to building widgets for Apple’s Dashboard environment.

Andrew's Widgets
Jump to:
Note: I have begun writing a series on some advanced widget development topics The Loop, my company’s blog. The series is based on things I learned while developing Sundial. Check it out!

What the heck is a widget?

According to Apple, widgets are “mini-applications that let you perform common tasks and provide you with fast access to information.” The cool thing for web developers and designers is that widgets are at their heart just web pages, bundled in a specific way. So, if you can make a useful web page, you can probably make a useful widget! All you need is a little information about their structure.


My desktop with Dashboard enabled

Widgets are simply bundles of text and image files. By adding the “.wdgt” extension to a folder, you turn the folder into a bundle which is treated by Mac OS X as one file. Conversely, you can explore the code and graphics for any widget by either removing the “.wdgt” extension or just control-clicking on the widget and choosing “Show Package Contents”.

Hello World

When learning a new programming language, the first task is typically to write a trivial program to print “Hello World” on the screen. We will now take a look at the Dashboard equivalent!

Apple has been kind enough to post a “Hello World” widget on their Sample Dashboard Code page. I have mirrored it on my server in case they move or delete it for some reason. Here is a breakdown of the parts that make up HelloWorld.wdgt:

HelloWorld.html

For this example, Apple keeps it really simple! Here’s the code:

<html>
<head>
   <style>
   
   body {
	  margin: 0;
   }
   
   .helloText {
	  font: 24px "Lucida Grande";
	  font-weight: bold;
	  color: white;
	  text-align: center;
	  position: absolute;
	  top: 41px;
	  left: 18px;
	  width: 180px;
   }

   </style>
</head>
<body>
   <img src="Default.png">
   <div class="helloText">Hello, World!</div>
</body>
</html>

Note: You can name the file anything you want. It just needs to match up with the corresponding value in your Info.plist file.

Default.png

This image is what the user will see when s/he loads the widget, but is also used in this case as our backdrop. The image dimensions for your Default.png typically correspond to the size of your widget as specified in your Info.plist file.

Icon.png

This image is what the user will see when s/he opens the widget bar. It is analogous to a desktop application’s icon. According to Apple’s widget design guidelines, this image should be 85 x 85 pixels with the “content” of the image taking up 74 x 74 pixels, at most.

Info.plist

This XML file is used by Mac OS X. It describes the widget and controls whether the widget has access to particular system resources (internet connectivity, command-line applications, the filesystem, etc.). The plist (short for property list) for “Hello World” contains mostly just the essential items.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
   "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>CFBundleDisplayName</key>
   <string>Hello World</string>
   <key>CFBundleIdentifier</key>
   <string>com.apple.widget.helloworld</string>
   <key>CFBundleVersion</key>
   <string>1.0</string>
   <key>CloseBoxInsetX</key>
   <integer>16</integer>
   <key>CloseBoxInsetY</key>
   <integer>14</integer>
   <key>MainHTML</key>
   <string>HelloWorld.html</string>
</dict>
</plist>

Let’s look at each of the elements and what they do:

There are several other possible plist keys you can include. See the Dashboard Reference for the full list of plist keys.

Tip: A common mistake is not to include a needed security-related key.

Inexplicably, Apple left “CFBundleName” out of it’s example plist file, despite the documentation saying it is required.

Hello World Screenshot

The following is a screenshot of our Hello World widget in action:

Nifty Stuff You Can Do

Now, making a widget that just sits there isn’t very useful. What makes a useful web page (and, by extension, a useful widget!) is interactivity. Widgets can do anything a web page can do, but they can also do some pretty nifty things a web page cannot.

System Access

Within the Dashboard environment, JavaScript has access to a special object called “widget”. This allows Apple to open up a host of functionality, including the ability to access the Mac OS X command-line within your widgets. Pretty much anything you can do from the command-line, you can do within a widget. Wow! How’s that for possibilities? I’ve used this feature to do the following in my widgets:

Here are a couple of examples:

// get photos from the photos directory
var photos = widget.system("/usr/bin/php php/readphotos.php",null).outputString;
// copy password to the pasteboard
widget.system("/bin/echo '"+password+"' | /usr/bin/pbcopy", null);

Save & Retrieve Preferences

The widget object can also be used to save and retrieve preferences unique to a widget, or even a particular instance of a widget. These are similar to cookies within a regular web browser. This allows you to maintain state across sessions and otherwise track a user’s settings. Here is an example from Make-A-Pass:

// default settings
var prefKeys = new Array(
   "passlen",
   "clearpass",
   "fips181",
   "lowercase",
   "uppercase",
   "digits",
   "punctuation",
   "nosims"
);
var prefDefaults = new Array(8,1,false,true,true,true,true,true);

// save settings
function saveSettings() {
   if (window.widget) {
      // set password length and clear pass separately
      widget.setPreferenceForKey(slider.curval,"passlen");
      widget.setPreferenceForKey(clearpassObj.selectedIndex,"clearpass");
      
      // save other keys
      for (var i = prefKeys.length - 1; i > 1; i--) {
         tempObj = document.getElementById(prefKeys[i]);
         widget.setPreferenceForKey(tempObj.checked,prefKeys[i]);
      }
      
      // save the current time so we know when to clear the password
      saveTimeCreated();
   }
}

// get settings
function getSettings() {
   if (window.widget) {
      var prefs = new Array();
      for (var i = prefKeys.length - 1; i > -1; i--) {
         if (!(widget.preferenceForKey(prefKeys[i]) === undefined)) {
            prefs.push(widget.preferenceForKey(prefKeys[i]));
         }
      }
      if (prefs.length !== prefKeys.length) prefs = false;
   } else {
      prefs = false;
   }
   return prefs;
}

Asynchronous HTTP Requests

There has been much hype lately about Ajax. Ajax is the name given to the technique of tapping into modern web browsers’ ability to make and handle asynchronous HTTP requests. If that last sentence makes you go “Huh?” I’ll explain. Let’s take a look at the canonical example: Google Maps. No, really, take a look. Follow the link, zoom in, pan around. Get some driving directions. Pretty slick, eh? I won’t go into detail about how they do it (others already have), except for this: every time you change what you’re looking at, your web browser makes a request to Google’s servers and parses the XML response, all using some pretty clever JavaScript.

Well, guess what? Widgets can do the same thing! A common use of this capability is to grab RSS feeds and parse them. I did this in my Today in New Mexico widget. Here is the relevant code:

function loadXML(url) {
   xmlRequest = new XMLHttpRequest();
   xmlRequest.setRequestHeader("Cache-Control", "no-cache");
   xmlRequest.onreadystatechange = processRequestChange;
   xmlRequest.open("GET",url,true);
   xmlRequest.send(null);
}

function processRequestChange() {   
   if (null == xmlRequest.readyState) return;
   if (xmlRequest.readyState == 4) {
      if (xmlRequest.status == 200) {      
         displayMsg("loaded");
         parseRSS();
      } else {
         if (null == xmlRequest.status) return;
         displayMsg("noconnection");
      }
   }
}

function parseRSS() {
   if (null == xmlRequest.responseXML) {
      displayMsg("emptyfeed");
   } else {
      xml = xmlRequest.responseXML;
      dateObj.innerHTML =
         formatDate(xml.getElementsByTagName("pubDate")[0].firstChild.nodeValue);
      itemsHTML = "";
      items = xml.getElementsByTagName("item");
      for (var i = 0; i < items.length; i++) {
         itemsHTML += addItem(items[i]);
      }
      eventsObj.innerHTML = itemsHTML;
      setScroller();
   }
}

Quartz Methods

Quartz is Apple’s name for its graphics rendering framework. Desktop apps written in Java, Objective-C, REALbasic, etc. make calls to the Quartz API to draw stuff on the screen. Well, Apple has integrated into Safari the ability to access Quartz methods directly using JavaScript. This gives developers some awesome possibilities! I tapped some Quartz methods in my SlideShow widget to show the current slide reversed as the backside of the widget. Here is the relevant code:

// draw the current photo on the backside of the widget, flipped horizontally
function changeBackside() {
   // get the source image
   var img = new Image(400,300);
   img.src = photoObjs[imgIdx].src;
   
   // due to a bug in Safari, this needs to be here or this function won't work
   // (within a widget, alert messages are written to the Console)
   alert("Disregard");
   
   // get the canvas object & context objects
   canvas = document.getElementById("canvas");
   context = canvas.getContext("2d");
   
   // set the drawing context
   context.clearRect(0,0,400,300); // erase the rectangle
   
   // save the context for later restoration
   context.save();
   
   context.scale(-1,1); // flip horizontal
   context.translate(-400,0); // move the canvas over to compensate for the flip
   
   // draw the image
   context.drawImage(img,0,0);
   
   // restore the context so we start with a fresh slate next time
   context.restore();
}

As you can see, the potential capabilities of widgets are virtually limitless!

Resources

Recommended Reading

Any or all of the following will help advance your widget building skills.

Dashboard Widgets for Mac OS X Tiger: Visual QuickStart Guide – by Dori Smith

Mac OS X Technology Guide to Dashboard – by Danny Goodman

JavaScript: The Definitive Guide – by David Flanagan

DHTML: The Definitive Reference – by Danny Goodman

Cascading Style Sheets: The Definitive Guide – by Eric A. Meyer

HTML & XHTML: The Definitive Guide – by Chuck Musciano and Bill Kennedy

Beginning AppleScript (Programmer to Programmer) – by Stephen G. Kochan

Cocoa® Programming for Mac® OS X – by Aaron Hillegass

The following are some resources to help you get started building your own widgets.