Build a Dashboard Widget
Pages: 1, 2
The Default Image
As I said in the "DefaultImage" section of the Info.plist overview, widgets need to have a default background image. This image is displayed while Dashboard loads a widget. For a simple widget, the easiest thing to do is to have this image be the same as the background image that you will have for the widget once it is loaded. Apple's documentation specifies that this image needs to be a PNG file. PNG files can be created with Photoshop, Fireworks, GIMP, or just about any other image-editing software.
To keep things simple, I have named the file for the ManPage project Default.png. Here is what it looks like:

While this file can be as complex or as simple as a developer would like, it is important to remember that the main purpose of this file is to display an image while the widget is loading. This allows the user to see information about the widget, the default footprint of the widget, and other visual information that the developer wants the user to see. To keep things simple for the ManPage widget, this image is the same as the as background for the widget.
The MainHTML File
MainHTML is the file that provides the basic user interface and JavaScript functions for the widget. While complex widgets could include multiple HTML, CSS and .js files, for simplicity's sake, the ManPage widget consolidates all of this into one file.
While MainHTML files are basically HTML files, similar to those for regular web pages, there are some things to keep in mind while developing them. First and foremost, there is no default background for a widget. In order for the widget to have a canvas to draw on, you need to specify a background image. (I am not sure if invisible widgets would work, but it would be cool to try out.) This can be done using a div tag, like in the ManPage HTML section below.
The second thing to keep in mind is that Dashboard conforms to CSS, JavaScript, and DOM web standards. While this article is not intended to be an overview of developing CSS-based HTML pages, I will say that since Dashboard is basically a conforming browser, it is much easier to develop and debug widgets using web standards.
The MainHTML for the ManPage widget is below:
<html>
<head>
<title>Dashboard Man Page</title>
<meta http-equiv="Content-Type"
content="text/html;charset=utf-8" >
<style type="text/css">
<!--
.backStyle {
position:absolute;
width:400px;
height:400px;
left:0px;
top:0px;
z-index:0;
visibility: visible;
}
.textStyle {
font-family: Lucida Grande, Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
position:absolute;
width:111px;
height:22px;
z-index:1;
left: 128px;
top: 26px;
}
.inputStyle{
position:absolute;
width:123px;
height:23px;
z-index:1;
left: 246px;
top: 24px;
visibility: visible;
}
.outputStyle{
position:absolute;
width:324px;
height:400px;
z-index:1;
left: 33px;
top: 72px;
visibility: visible;
}
-->
</style>
<script type='text/javascript'>
<!--
// get man page
function getManPage()
{
if (document.getElementById('programName').value != null)
{
var commandLine =
'groff -mandoc -Tascii -P-b -P-c `/usr/bin/man -w "'
+ document.getElementById('programName').value+'"`'
+ ' | /bin/cat';
var output = widget.system(commandLine, null);
document.getElementById('outputArea').value = output.outputString;
}
}
-->
</script>
</head>
<body>
<div id="BackgroundLayer" class="backStyle">
<img src="Default.png" width="400" height="600">
<div id="TextLayer" class="textStyle">
<div align="right"> Get More Info On:</div>
</div>
</div>
<div id="InputLayer" class="inputStyle">
<input id="programName" type="text"
size="14" border="0" height="23px" width="123px"
onchange='getManPage();'>
</input>
</div>
<div id="OutputLayer" class="outputStyle">
<textarea id="outputArea" cols="42"
rows="25" readonly>
</textarea>
</div>
</body>
</html>
This is about as simple an HTML file as you can get: there is some header information, one JavaScript function, one style, and then some HTML markup. The HTML markup defines three layers: one for the background, one for a box where a user can input the name of the *nix command that they want a man page for, and one for the output of the man page. To trigger the retrieval of the man page, when the value in the programName input box has changed, it calls the getManPage() function via the onchange attribute.
The getManPage() function is where all the heavy lifting of this widget happens. Here is what happens, line by line:
if (document.getElementById('programName').value != null){This checks to make sure the
programNameinput element actually contains text. We need to do this becauseonchangewill trigger thegetManPagemethod any time the text changes, including if it is all deleted.-
var commandLine = 'groff -mandoc -Tascii -P-b -P-c `/usr/bin/man -w "' + document.getElementById('programName').value+'"`' + ' | /bin/cat';This defines the shell command that we will call to get the
manpage. The command looks pretty complex, but like most shell commands, it is actually pretty easy once it is broken down. The first thing to look at is the middle section, because this is the first thing the shell will process.`man -w "' + document.getElementById('programName').value + '`We are building a string here that gets the
manpage for theprogramNamethat we entered. If we inputls, then this command would be:`man -w ls `The back ticks (
`s) that surround the command tell the shell to replace the back-ticked expression with the output of the expression before running the rest of the command line.The output from the
mancommand, once it is replaced on the command line, is sent to thegroffutility via:"groff -mandoc -Tascii -P-b -P-c" +This utility is used to format text for output to various devices. We need to call this command because the output from
manis formatted to be displayed on a terminal screen, including bold and underline control characters. If we did not strip these out, the output would look very strange in the HTML text area in which we are placing it. The parameters togroffare set to interpret the input as output from themancommand and the output as ASCII.The last part of the command line is:
"| cat "This forces the output of the whole command to go to
stdout. Anyone who has used any *nix shell will tell you that this is a weird command to include, and I agree. I needed to include it because of the way that Dashboard's JavaScriptwidget.systemcommand handles output tostdout(we will go overwidget.systemin the next section). var obj = widget.system(commandLine ,null);This command is used to run the command that we defined before in a *nix shell. The first argument is the
commandLinestring that we built before. The second argument is a mystery. Apple only provides one or two samples of this command in any of the documentation that comes with the pre-release version of Tiger and on the developer site. I have Googled the Web and searched all of Apple's documentation to see what exactly thenullspecifies, but I have found nothing. Both of Apple's examples call it with a null value, so I do the same.-
document.getElementById('outputArea').value = obj.outputString;This command takes the
outputStringfrom thewidget.systemcall and puts the value in theoutputAreaelement.
More to Come
While this is not the most complex or useful widget in the world, it does show off some of the capabilities of what Dashboard can do. Since Dashboard gives full access to the *nix shell, it is easy to imagine a widget calling a Perl script that makes database calls or interacts with a local CVS server. This means that, beyond the obvious uses of creating end-user widgets, Dashboard provides developers with a rapid prototype environment where they can try out new ideas with only a little bit of coding required.
The next article will include information on debugging and testing widgets, links to some sites that have sprouted up with widgets to download and bulletin boards for widget developers, and a how-to on writing a widget that uses JavaScript's object to retrieve information from remote web XML servers.
If you are looking for more information on Dashboard widget development, check out Apple's Developing Dashboard Widgets page. The page is kind of sparse, but gives some insight to developing widgets
Andrew Anderson is a software developer and consultant in Chicago who's been using and programming on the Mac as long as he can remember.
Return to MacDevCenter.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 24 of 24.
-
Thanks for Hanging in There with the Adventure
2005-05-09 13:42:02 Derrick Story |
[Reply | View]
For those of you that gave this code a whirl on Friday and early Saturday (and thereby helped us discover a glitch), I thank you. As you read by the author's comments, he made the mistake of not retesting his code with the released version of Tiger. When I saw the first comment about a problem on Friday night, I let Andrew know right away, and he was able to get things straightened up by mid day Saturday. The code in the article is now clean and functioning.
My apologies to those of you who served as unknowing beta testers. We strive to prevent this. But thanks to you, we got it cleaned up quickly.
-
What the 'null' means
2005-05-09 01:34:19 MWright [Reply | View]
Just in case you haven't found this reference yet, here is a description of what the the second argument in widget.system() is and what it does. (synchronous vs asynchronous mode). -
What the 'null' means
2005-05-09 01:39:02 MWright [Reply | View]
Here's a better link: page. -
What the 'null' means
2005-05-09 03:40:31 Andrew Anderson | [Reply | View]
Thanks for the link, I too found this over the weekend. Looks like Apple posted this right after they released Tiger. It includes quite a bit of good information.
Asyncrounous mode versus Synchornous mode and the ability to control System in and looks like an interesting way to wrap an interactive script or program via Dashaboard.
-
Widget does not work, I am working on a fix
2005-05-07 18:18:59 Andrew Anderson | [Reply | View]
I apologize but there is a problem with the Widget in the article. I originally wrote it and the article in a pre-release version of Tiger before Tiger was released. Unfortunately
I submitted the article before the release and never had the chance to test it in the final build.
The technical reason for the problem is that it looks like Apple changed the way that Dashboard handles the Widget.system method in the final release that makes the Widget not work.
I am sorry about this and understand everyone's frustration. I know people come to O'Reilly and MacDevCenter for reliable information and that this obviously is not up to par with what you expect. I am actively working on getting a fix together and I will post it as soon as possible.
yours,
Andrew -
Here is the fix!!
2005-05-07 21:01:16 Andrew Anderson | [Reply | View]
After messing with the code for a while, I determine the fix. The new code section is posted at the end of this message and I am working on getting it changed in the actual text of the article.
The problem with the Widget is the way that the widget.system method in Javascript handles the path. In the pre-release versions included a certain set of default values for the path (i am unsure of the exact list), while the release version does not. This new version of the code handles the path and also insulates the values in the command via quoting to prevent any problems passing values.
Thanks for everyones patience....
Andrew
Here is the complte code listing:
<html>
<head>
<title>Dashboard Man Page</title>
<meta http-equiv="Content-Type"
content="text/html;charset=utf-8" >
<style type="text/css">
<!--
.backStyle {
position:absolute;
width:400px;
height:400px;
left:0px;
top:0px;
z-index:0;
visibility: visible;
}
.textStyle {
font-family: Lucida Grande, Arial, Helvetica, sans-serif;
font-size: 12px;
color: #000000;
position:absolute;
width:111px;
height:22px;
z-index:1;
left: 128px;
top: 26px;
}
.inputStyle{
position:absolute;
width:123px;
height:23px;
z-index:1;
left: 246px;
top: 24px;
visibility: visible;
}
.outputStyle{
position:absolute;
width:324px;
height:400px;
z-index:1;
left: 33px;
top: 72px;
visibility: visible;
}
-->
</style>
<script type='text/javascript'>
<!--
// get man page
function getManPage()
{
if (document.getElementById('programName').value != null)
{
var commandLine =
'groff -mandoc -Tascii -P-b -P-c `/usr/bin/man -w "'
+ document.getElementById('programName').value+'"`'
+ ' | /bin/cat';
var output = widget.system(commandLine, null);
document.getElementById('outputArea').value = output.outputString;
}
}
-->
</script>
</head>
<body>
<div id="BackgroundLayer" class="backStyle">
<img src="Default.png" width="400" height="600">
<div id="TextLayer" class="textStyle">
<div align="right"> Get More Info On:</div>
</div>
</div>
<div id="InputLayer" class="inputStyle">
<input id="programName" type="text"
size="14" border="0" height="23px" width="123px"
onchange='getManPage();'>
</input>
</div>
<div id="OutputLayer" class="outputStyle">
<textarea id="outputArea" cols="42"
rows="25" readonly>
</textarea>
</div>
</body>
</html>
-
Example Doesn't Work
2005-05-07 13:36:45 joshuawait [Reply | View]
I copied the example from this article exactly and it doesn't work. I tried cleaning it up and looking for a variety of problems.
I gave up.
I tried Apple's article
http://developer.apple.com/macosx/dashboard.html
and it worked great. I successfully created my first Dashboard widget. It's a Backup ROI calculator that I use to help clients figure out how much money they save by having a reliable backup.
Dashboard is great, but I shouldn't have to debug an O'Reilly article to figure it out.
-
I tried it as well and couldn't get it to work.
2005-05-07 12:53:06 maddmike [Reply | View]
I didn't see anything on the console either.
-
Isn't that a bit dangerous?
2005-05-07 01:55:30 KerryB [Reply | View]
var commandLine =
"groff -mandoc -Tascii -P-b -P-c `man -w " +
document.getElementById('programName').value + "`| cat ";
I realise this isn't production code, and I may be missing something, but what happens if the user types something likefoo;rm -rf $HOMEinto the command field? -
Isn't that a bit dangerous?
2005-05-09 09:32:39 heyotwell [Reply | View]
"I realise this isn't production code, and I may be missing something, but what happens if the user types something like foo;rm -rf $HOME into the command field?"
More importantly, what prevents a *widget* from simply running that code on its own?
-
It doesn't work
2005-05-06 18:16:36 kevincranford [Reply | View]
I must be doing something wrong because I copied and pasted eveything in this article and I cannot get it to work. -
It doesn't work
2005-06-14 11:33:30 TimGrant [Reply | View]
What you're describing happened to me too. I made some changes to the JavaScript and now it works a little bit better:
function getManPage()
{
if (document.getElementById('programName').value != null)
{
var commandLine = 'groff -mandoc -Tascii -P-b -P-c `/usr/bin/man -w "' + document.getElementById('programName').value+'"`' + ' | col -b '
document.getElementById('outputArea').value = "Processing..."
var output = widget.system(commandLine, myEventHandler);
}
}
function myEventHandler(cmd)
{
document.getElementById('outputArea').value=cmd.outputString;
cmd.close
}
It still only works on some terms, though, e.g., "chown" but not "chmod". -
It doesn't work
2007-03-23 20:01:10 jeffclough [Reply | View]
Some trial and error shows that the output control can only hold up to 366 lines of text. This seems like an odd number, so maybe it's different from system to system (or version to version or whatever). I just used the head command to truncate the output:
if (document.getElementById('programName').value != null) {
var commandLine =
'groff -mandoc -Tascii -P-b -P-c `/usr/bin/man -w "'
+ document.getElementById('programName').value+'"`'
+ ' | head -366 | /bin/cat';
var output = widget.system(commandLine, null);
document.getElementById('outputArea').value = output.outputString;
} -
The Working JavaScript Function and Explanation of Error in Article
2005-06-30 00:59:51 maymay [Reply | View]
The reason the above code doesn't work (at least not on 10.4.1) is because the Widget will freeze, waiting for output, due to the call fromwidget.system. By specifying a handler, you're changing from synchronous operation to asynchronous operation.
These are the offending lines that need to change in the article:
var output = widget.system(commandLine, null);
document.getElementById('outputArea').value = output.outputString;
What's happening here is that the call towidget.system()is performed synchronously and thus makes the Widget wait for the output from the call to complete before continuing its own execution. Unfortunately, when this method is called synchronously (that is, when its second argument isnull), one needs to specify a property of the method to capture, as explained in the Dashboard Programming Guide in the section on Command-Line Access, under Synchronous Operation.
As cited in the Guide: Runningwidget.system(commandLine, null)as shown above executes the comman, but any output is lost since you dont specify that you want that information. To get its output, specify theoutputStringproperty and save it in a variable:
var output = widget.system(/usr/bin/id -un, null).outputString;
So learning from this example, the getManPage() JavaScript function's lines mentioned above should be changed to this and it will work:
var output = widget.system(commandLine, null).outputString;
document.getElementById('outputArea').value = output;
Additionally, it should be noted that according to Apple's introduction to developing a Dashboard widget, theInfo.plistfile is required to have aCFBundleDisplayNamekey as well as aCFBundleVersionkey, but is not required to have aWidthor aHeight.
Best regards, and best of luck,
-Meitar Moscovitz
http://www.maymay.net/ (Personal site) -
It doesn't work
2005-05-07 05:18:46 Andrew Anderson | [Reply | View]
Hi.
What does not work for you ?
When you load Dashboard does it load in the window ?
or
Does the functionality not work when you try to run it ?
The first one is probably XML driven, the second HTML driven.
One way to debug Widgets, is to actually use the "Console" application, in the Applicatiosn/Utilities folder. All Javascript errors/messages will go here. Load that up and it should give you a pointer to any problems.
-
It doesn't work
2005-05-23 11:14:49 andyinindy [Reply | View]
Here's one that works great; maybe we can figure out how he did it!
http://www.interdimensionmedia.com/downloads/NIXmanual.zip -
re:It doesn't work
2005-05-23 16:11:43 Andrew Anderson | [Reply | View]
AndyInIndy,
Thanks for the post. I looked at the widget briefly and it looks like they have some complex javascript doing the work. I will take a closer look and post something if I can get it together better.
thanks again,
Andrew -
...doesn't work...
2005-05-11 09:18:20 andyinindy [Reply | View]
Same here; the widget freezes, no man page displayed, no helpful output in console.
I am convinced that widget.system is hopelessly broken. Maybe 10.4.1 will fix it. Here's someone who also hates widget.system:
http://www.mikeash.com/blog/pivot/entry.php?id=6 -
...doesn't work...
2005-05-11 10:58:08 Andrew Anderson | [Reply | View]
it definately behaves differently between the pre-release versions of Tiger and the final release build.
what was most frustrating when I was debugging the problem in the release build is that Dashboard would crash, wouldn't pick up new versions of the Widget as they were created and just suffered from general weird behavoir.
the only thing that even approximates a debug environment is the mix of the console and using javascript alert statements, which is not an easy way to debug anything.
I confirmed that the latest version of the code, works in the release build of Tiger, but as you said widget.system seems to be hopeless broken.
Thanks for the feedback though, I think part two of the article will focus more on how to setup some semblance of a development/debug environment and using the other features of Dashboard. -
It doesn't work
2005-05-07 16:26:30 kevincranford [Reply | View]
The Dashboard Widget loads but when I enter something to get the man page on (like ls) it just sits there and shows nothing. It also wouldn't let me enter anything else into the search field. I checked the console and there is nothing related to this logged there either.






http://iseowidget.brand-zen.com/
And mayby you know how to build such one. is there any features?