Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2005-134
$eventId
ID of event: wwdc2005
$eventContentId
ID of session without event part: 134
$eventShortId
Shortened ID of event: wwdc05
$year
Year of session: 2005
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC05 • Session 134

Advanced Dashboard Widgets

Application Technologies • 58:06

Bring your laptop for this hands-on session focussed on extending the capabilities of your Widget beyond what is possible with simple HTML, JavaScript, and CSS. You will learn to use Cocoa to access the powerful core technologies in Mac OS X from your Widget.

Speakers: Christian Wagner, Matt Drance

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Good afternoon and welcome to session 134, Advanced Dashboard Widgets. I have one favor to ask of you before we start. If you have seats in between you and you're in the middle, try to move in and leave aisle seats open so people who are coming in, there's already people sitting out along the side, so we can get everybody a seat. It's just a nice thing to do.

My name is Christian Wagner. I'm a technical writer with TechPubs at Apple. I've been involved with the Dashboard project for a couple of months now. I've been writing some of the documentation. We're here to tell you today about some of the advanced technologies that you can integrate into your Widget to help take it into the next level, to help really make it more interactive, and to leverage all the different technologies that you might ever want to put into a Widget.

Really quick, at our sessions yesterday we covered how to make the basic Widget, so just info, plist, keys, and what's required in a file, images, and things to that effect. We also talked about some design tips, how to localize your Widget, working with Widget backs and preferences, and finally some of the Widget events that you might want to take advantage of in your Widget. Beyond that, or actually today, we're going to assemble a Widget from scratch right in the beginning just in case you weren't there, so we'll try to bring you up to speed.

And today we're going to focus on a few more higher level functions: command line utilities and integrating them in scripts into your Widget, HTTP requests, network access, pulling data from the Internet. After that we'll be going into Widget plugins and WebKit plugins, and finally we'll round it out with some useful little bits, tidbits that you might want to be aware of when you're making your Widget. Just little niceties and other things you might want to use.

So, one thing which I want to dive into before we talk about anything else, and it's this idea of access keys. There are these info-plist keys that you need to set if you're going to do accessing networking resources, if you're going to use a system utility, if you're going to wind up using a plugin.

And you need to set these, because a lot of the things that are in this presentation don't work unless you use these. And I'll point out, when we go to demo, where you can find this in the documentation. So, on to our first topic. We're going to talk about command line access.

And the first thing you should be aware of is that you need to set a key in your info-plist, and that's the allow system key. And it should be a Boolean value. You should set it to true, or to yes. You use widget.system, and that lets you run a command line utility. There's two parameters at play here.

The first one is the command. So, whatever command you're going to run, whether it be a script or a full Unix command line utility. And you should definitely specify the entire path to that. Not just the command name itself, but you should specify if it's in user bin.

A lot of times it won't work unless you do that. And then the second thing, which you might or might not specify, is a handler. And this is what toggles the two modes that widget.system runs in. It runs in either synchronous or asynchronous mode. So synchronous means that it's going to block your widget, and that's the first thing we're going to talk about today.

And you use this mode by passing the handler a value of no. So your entire widget will block while the command executes, and will only continue once it's done. And there's three properties that you can use to do this. There's three properties available here. So I should backtrack really quick.

When you call widget.system, you can either just append one of these properties to the call itself, and then you'll get the output string, which is what's written to standard output, the error string, which is what's written to standard error, or you'll get the status, which is the status code the command ends with.

And you use this by either, like I said, just appending that property to the end of the call itself, or you call the command and assign it to a variable, and then on that variable, you ask for the output string, or you ask for the error string, the status, anything like that. So we're going to go to demo here. If I could have demo one, please.

and what do you know, there's a documentation update available. So I'm not going to do it now, but you should always have the latest documentation. So we're going to make a Widget. It's very simple and it's a slightly modified of Widget versus the one we saw in the overview yesterday. It's called Uptime and all it does is it calls the Uptime command. It's pretty straightforward, pretty simple. We're going to show you how to make it and we're going to make it from scratch.

We're literally just going to make a new folder here on the desktop and we're going to call it "uptime". And the first things I'm just going to drop in here right away are the images required by a Widget. For review, there's two images required in every Widget. There's "default.ping" which is the background pattern used by the Widget while it's loading.

And then we're also going to use it as the background pattern for our Widget. You don't have to do this, but we do it. And then there's also just an icon, it's generic in this case, just a simple icon. And that's used in the Widget bar to represent your Widget, by the way. Sorry, I didn't quite hear that.

Well, let's turn on Zoom then. So I can show you those. Nope, not VoiceOver, Zoom, Command, Option. Okay, so let's do that. Here's the first one. Zoom in on that. And again, this is a PNG. It has to be a PNG. It's the only file format that Dashboard uses for these things.

And the nice thing about using PNG is you get a nice modern graphic that is 16-bit support and it supports alpha, transparency. It's just a great format to work with. And then our icon.png Nothing too special going on there either. It's just a 75 by 75 pixel image. And again, these must be named, let's zoom out again, must be named default and icon.png respectively in order for Dashboard to find them.

So now that we have the basics in place here, let's go into the actual code that we need to write. There's not much. So just so you know, the whole time I'm going to be using this program called Demo Monkey, and this lets me write code without having to write code. I'm just going to be dragging and dropping all the code that I need here, because watching me type is pretty boring.

But what I'm going to do here is I'm going to go into Xcode, and I'm going to make an HTML file. And all it's going to have in it is just the usual HTML tags, opening and closing HTML tags. It's going to have a body, and in the body we're going to have a background image. In this case, we're just going to use the default.png. And then we're going to have some text, some placeholder text that lets our users know that, hey, we're going to retrieve the uptime. But this is a placeholder that's first showed while the... Retrieval happens.

So we're going to save this. Actually, hold on. I'll do it with Command-S. And I'm going to save it on my desktop inside the Widget. And I'm just going to call this one uptime.html. And that's pretty much it for now. We're also going to use a CSS file in here. And what goes on in here isn't too terribly important. We're just doing some placement. So we're placing the text, we're giving it a font style, we're setting it to bold, we're making it white, and we're just positioning it absolutely within the Widget.

This is standard CSS, nothing crazy going on here. And the body, we're setting its margins to zero. And the reason you do that is because you don't want to, these are web apps, you don't want to have to rely on margins. Web apps usually don't have margins. So we're going to save this one. And we're going to just call this uptime.css.

Okay, this has all been pretty fun and nice, but let's get into the JavaScript. Let's get into the good stuff. So the first thing we're going to do is we're going to sign up for two notifications. And we covered these yesterday, but as a quick recap, you can be notified when the Dashboard is shown and hidden. And you do this using handlers, WidgetOnShow and WidgetOnHide.

And you just assign functions that are called whenever these events happen. So I have two, both of these used here. I use WidgetOnShow and I set it to the function Uptime. So let me pull the Uptime function in here. And I'm going to set it to a little bit convoluted, but not too bad.

So what I do here is... The first thing I do is grab the widget text that I have inside the DOM. This lets me work with it later on. Then I get to the good stuff. I call the command. Here I'm using widget.system. The first parameter I'm passing in is userbinuptime. The second thing I'm passing in is no. This will run it synchronously.

The widget will stall on the execution of this command. But that's okay. It's a very quick command. It puts back one string. We'll get away with it this time. And then I'm going to place the inner text inside the HTML of the element that's going to hold our output to the result of the command. I'm going to do its output string.

So that means... We're going to pass it the output string. So this is whatever is put to standard output is going to be then put back into the Widget itself. And then, you'll notice I signed up for another handler. This is Widget on Hide, and I set it to this function I called reset.

So let's bring that in. Reset just takes the uptime that's in the Widget and wipes it out, puts a placeholder thing on there. The reason being is when a Widget shows up on the Dashboard, it might take it a minute to run, or not a minute, but a few seconds to run the uptime command and actually put it in there.

We don't want them to see the previous uptime because that might be really misleading. So we're just going to put some placeholder text whenever it's hidden, in this case, this retrieving text. And then we're going to save this and put it in our Widget. We're going to call this uptime.js.

So we have a lot of the nuts and bolts in place. The last thing which we need to do though is go back into our HTML and we need to reference the CSS and the JavaScript file that we just made. So I'm going to just grab that right here. It's pretty straightforward. In our head, I need to add an opening head statement here.

and I will introduce the style and import the uptime.css that I created a minute ago and I also then import the string, the JavaScript that I wrote that has the Widget.System call. And that is basically it. Now, I've done all of the Widget work. I still need to do one last thing, and that is I need to make an Info.plist. This is the file that Mac OS X and Dashboard and the Finder use to supply work with the Widget. It sets up all the things that the system needs to know about this Widget. So I'm going to go in Xcode here and really quick assemble that.

Every Info.plist file starts off with just some XML descriptions and some doc types, DTD setup, and things to that effect. So that goes first. Then we bring in the OS required keys. We have things like a CFBundle Display Name. This is the name of the Widget that's used in the Widget Bar. We have the CFBundle Identifier. This is a unique string across all Widgets that identifies this Widget to Dashboard and Mac OS X. We have the Bundle Name. This is different than the Display Name because this is what a lot of monitoring utilities like Activity Monitor uses.

And then finally we have a CFBundle Version. This is used for version conflicts. So if you release multiple versions of your Widget, you want to increment this by some value so that Dashboard always knows which is the most recent, which one to use. So those are the ones required by OS X. And now there's the Dashboard specific ones. First one is the main HTML. And here I just specify uptime.html. This is the file that contains pretty much my implementation of my Widget. We're going to change where the close box is.

By the default, a Widget's close box is over the top left corner of its underlying window. But if you use a Widget that has some type of alpha around it, some type of transparency, you should really move that close box in so that's over the top left corner of the actual graphic.

And the final thing which we're going to use here is Allow System. So this is the Info.plist key I told you about before. And this will turn on command line access for my Widget. And you'll notice that it has a Boolean value and I set it to true.

So we're going to close that off. And that is that. So we're going to save it. We're going to name it Info.plist. And again, that's a required name, required spelling. And that is our Widget. So fingers crossed. We're going to throw in a .wdgt extension here. And then it asks, do you want to make this a Widget? Yes, we do. And sure enough, there we go. Handy dandy little Widget.

Okay, can I go back to slides please? So now that we've shown you how to use Widget.Systems synchronously, we're going to show you how to use it asynchronously. And this is usually what we recommend that you do. Asynchronous operation means that the Widget executes concurrently to your Widget.

So the Widget won't get held up in case you're running a longer command or you're running a script that has continuous output or something to that effect. And you use it, you toggle on this asynchronous mode by supplying a handler. So in this example, we're supplying a function called endHandler. And that function needs to accept one argument.

That argument will be an object that you can then call either outputString, errorString, or statusOn to get the final information about the Widget after it's done executing. In addition, we have a couple of handlers you should be aware of. There's onReadOutput and onReadError. And so these are called whenever the command actually writes something to standard output and standard error.

And whatever handlers you provide here need to accept one argument, and that's going to be the string that was written to the appropriate output. So in this example, we call just some script, some Perl script. And then in the endHandler, we just take whatever the output was and just dump it into our Widget.

In addition to the properties, there's also some methods in case you need to interact with your command. So for instance, if you're taking a Perl script and you keep feeding it information or pulling information out of it, there's cancel, which just quits it straight out. There's writeString, which just writes to standard input.

And then there's close, which sends standard input in EOF, an end of five. So it's for gracefully quitting your command versus cancel, which just kills it on the spot. And again, it works like this. You just call whatever command you're using. And we're just writing some string. And then when we're done with it, we send it closed. So it's pretty straightforward. And let's go to demo here.

So if you downloaded the source code ahead of time for this session, I suggest you pull up the Voices example. And let me pull out the... There's two versions... Hi. So there's two versions of this Widget in this project. There's a synchronous and asynchronous version, and we're going to start with the synchronous one. And all this Widget does, as you already heard, it uses Macintok to take whatever string I type in here, I can select a voice, and then I can spit that string back out. So I'm just going to say hi to my mom.

She's very proud of me today, so I think she owes that. And sure enough, it just says that. It says "Hi," I can choose a voice here. But there's a... Howdy. So, this can get really annoying though. Let's say I paste "Hi Mom" in here a couple of times.

All of a sudden, there's no way for me to stop it. Short of muting it. So, this is not a solution. This is not how you should be doing your Widget. So we're going to take this Widget and we're going to make it asynchronous. And I think we can safely turn the sound back on.

I'm going to take the synchronous version and bring it on my desktop. In case you didn't know, you can take any Widget and pop it open. If you command-click on it and choose "show package contents" that opens up the Widget bundle in the Finder. I'm going to be using Demo Monkey to provide my code for me.

All we're going to be doing this time is we're just going to be modifying the JavaScript code. Feel free to try to follow along here. One thing which I would recommend you do is in the project which I submitted for you guys to have, just take the two JavaScript files and use our file merge utility. Put the one on the left and the other on the right and just follow along as I change things.

So basically the JavaScript in here is pretty simple. It has some comments. Not enough, but enough. And the one we're going to look at here is called Speak. This is what's actually called when you click on that Speak button. And it does a whole bunch of regular expressions to make sure we have the right characters in the string and all that other stuff. But what's really the good stuff is right here. We have if window.widget.

Again, we wrap our widget.system calls inside of this window.widget call. And the reason for that is because if you were to run this widget in Safari for debug purposes, you wouldn't want this called because execution would just halt on the spot and it wouldn't be pretty. And I have this global variable called currentlyBeingSpoken. So whatever the current widget.system command executing is, I just put it inside of this variable. And I'm going to be using that later on. That's why I pointed it out now.

So right now we're just calling widget.system and we're using osascript to say, to pass a say command. You can also use the say one. I just used osascript because that's how I felt. And right now we're using null here, but I'm going to replace this with a version that has a handler. So I'm going to comment this out.

You'll notice that this one has "done" on it as a handler instead of "no". So I have to supply a "done" button. We'll get there in a second. I'm also going to add in here the capability of having a "stop" button. Because if I want to be able to have this Widget stop talking, I'm going to want to have a "done" button. So inside of the HTML there's the "speak" button text. And in the case that we're speaking, I'm having it set its text to "stop".

And then I also have the button itself, I use the DOM to change its handler. Because right now if someone clicks on that button, they're going to get the "speak" one. I'm going to provide a "cancel" function which is going to get called. And that's going to be able to cancel this straight out if someone hits the "stop" button.

So the first one of those, the cancel function, just does this right here. If it makes sure we're running within a Widget again, if window.widget, it takes currently being spoken, whatever's being spoken right now, and cancels it. And then it does some clean-up work with regards to the UI. But then it calls the done function, and that's also what gets called when our command is just run on its own naturally, and it's run its course.

It just does some cleanup. That's basically all it does. It just cleans up the UI and changes the button text back to speak. It doesn't actually do anything with the Widget.SystemCall at all. It just sets it to null for bookkeeping purposes. So let's go ahead and save this.

[Transcript missing]

Hi Mum, Hi. And enough of that. So if I have it speak again though, you'll notice that when I close it, it's still talking and that's not a good behavior either. So we're going to mute that in the meantime and we're going to go back to our JavaScript file and we're going to add a remove handler. This is another one of the events we talked about yesterday that you can sign up for. You can be notified for when a Widget is removed.

So we just have this function that I'm going to have called whenever the Widget is removed called removed. And that function contains I think a whopping three lines of text. It has, it just checks if something is being spoken and if so, it cancels it. So we'll save that. Load the Widget again.

or you know what, we'll go all out. I'm amazed that I can type right now, all right. I sure like being inside this fancy computer. I hope you all know where that's from, but if not, I'll tell you after the session. So if I have it speaking again, and I get rid of it, it stops talking. So this is kind of how Widgets should behave in this case.

So that's it for demos. Can we go back to slides, please? So, I've talked to you about using Widget.Systems synchronously and asynchronously. I'm now going to bring up Matt Drance, one of our DTS engineers, and he's going to talk to you about network resource access inside of your Widget. Thanks.

Good, much better. Okay, so, as Christian already introduced me, I'm Matt Drance, and I am the Dashboard Rep in DTS. So, should you guys run into any problems with your Widget development, or questions, or concerns, I'm one of the guys you'd want to talk to. Of course, the goal of this session is for you to not have to do that. We'll see how well it goes.

So, I'm going to be talking to you about network access, specifically web-based content like XML, HTML, other feeds that you might have access to. Along Christian's theme of using keys, the first thing you want to do when you're starting to access the outside network is set the allow network access key in your Info.plist. Then there's this magical JavaScript object called the XMLHTTP request. A lot of you probably know about this already. If you've been fishing around in our own Widgets that come with the system, you see this in Proxmox.

It's a pretty extensive use all over the place. And what it does is it basically is like a headless web connection, and it goes ahead and does a fetch just like a browser would, but gives you access to all of that content rather than just rendering it on screen. So, using an XMLHTTP request is as simple as instantiating the object in JavaScript, setting handlers for when the request is finished or in progress, and we'll show you that in a second. You open the HTTP connection with a destination URL.

You send the request, and then in the handler function that you said earlier, you go ahead and get the content and work with it in whatever fashion that you want it to. So, the first half, setting up the request. Like I just said before, you set a handler function. It could be one of two handlers that we make available to you. One of them is onReadyStateChange, and this is an incremental handler.

During the process of the request, it goes through a number of values starting with zero and going through to four when it's complete. Alternatively, if you don't really care about the incremental function, you can set up a function called the "Handler" function. So, if you want to make progress of the request, you can just set an onLoadHandler, which will be called at the very end when everything's done loading.

You go ahead and set your request type, like a GET or a POST, and specify a URL using the open method. And we usually recommend, much like the system command, that you pass "true" for asynchronous mode, especially if you're in the case of a bogged down server or a slow network connection, you'll be blocking your widget's execution for the whole time that that request is waiting. So, it's best to do it in synchronous mode. So, you can set up a request header, and you can set up a request header in the same way that you would in an asynchronous mode, and put up some kind of progress bar, loading indicator, something like that.

And you can optionally set request headers, if you know what kind of request you need to send, if there's any additional data that needs to come in with the request. And then finally, you send it. So, here's an example of that code. This is really as simple as it gets.

Create the XML HTTP request. Set your, in this case we're doing onReadyStateChange, and process rec change is a function that's declared somewhere else in the JavaScript file. And that will be called as the request state changes. And we call, in this case we're doing a get to a URL that's been specified somewhere else. We pass in true for asynchronous mode, and then we finally send the request.

So when the request is done loading and you get your response back, you need to handle it somehow. Well, that handler that we set earlier is going to be called and at that point, depending on whether you're using onLoad or onReadyStateChange, in the case of onReadyStateChange, you want to check for the right state. In other words, a state of four indicates that the request is complete.

At that point, you can go ahead and try to inspect the server response. You can do things like get a header. You can get all of the headers and work with them later. And then the real thing you want to work with is the request text or the request XML properties. And we'll go into the difference of those in just a second.

So here's some code that handles a response and again this is that process rec change function that we assigned as the handler a couple slides ago. We check for a ready state of 4 and a status of 200 which is a complete OK status from the server. If the XML exists, we go ahead and we process the response XML in a dynamic HTML sort of fashion. Otherwise, we go ahead and send out some kind of error message.

So what are these two properties? Response XML versus Response Text? Well, the Response XML property is the one that you really want to use if you can. It's really, really useful. It's a fully functional DOM tree object in JavaScript. It allows you to do all of the kind of functions that you're used to doing in a normal DOM tree, like get element by tag name, get element by ID, so on and so forth. It's a lot easier to work with. The alternative would be the Response Text. Is that Response HTML? No.

That's my fault. That's supposed to say Response Text, and the text is basically just a huge string representing the source of the document. And of course, the disadvantage of this, you can work with it as much as you want, but now you're going to have to do manual string parsing, regular expression stuff, all that kind of thing. So if you have the possibility and the opportunity, you really want to work with it. So if you have the possibility and the opportunity, you really want to work with it. with the response XML.

The Response XML property. Now in order to use Response XML, you need to have a MIME type of text XML coming back from the server. If you don't, or if you don't know if you will, you can always make the override MIME type call on the request before sending it out. And then that will at least get that hurdle out of the way, and assuming the XML you get back is well formed, you will get a proper DOM tree back.

Okay, and now we're going to show you what I've just been talking about. Can we go to demo two, please? Wonderful. So how many of you took a look at the sample RSS example that came down with this, that was associated with this talk? How many of you got it to work? I didn't think so. Okay, so, and this actually goes back to one of the questions that was asked at one of the earlier sessions. Let's go ahead and open this guy.

And what this is supposed to do is show a bunch of downloads from the Apple Dashboard site. Unfortunately, it's not there. And going back to the question that was asked in the initial talk, the problem here is we finished this sample two or three weeks ago. Unfortunately, on Monday, we changed the URL to the download feed.

So those of you wondering about legal issues and whatnot when using somebody else's feed, there's that, but there's also the fact that you don't control it, and they can change it out from under you whenever you want. Whenever they want, not when you want. If it was what I want, it wouldn't have changed. Let's see what we can do about this. For those of you who have the project open, I have the answer, and it's wonderfully simple. We're just going to open up the sample RSS JavaScript file.

Down at the beginning of this file, this is the URL that we're going to be using. And what we need to do is just take out the Mac OS X and put in a home. Let's not do it in caps. Okay, so while we're in here, let's just have a look at what's happening. Well, let me show you how it works so you don't fall asleep.

All right, and there it is, finally. So what this is is just a chronological list of the Widgets that are available on the Mac OS X download page. As opposed to the Safari site with the same URL, which I would love to show you right now. Let's not do that one.

So here's the HTML version. And as you can see, if we were going to be working with this, it would probably be a lot of work to go ahead and parse all this HTML and get out what we wanted. So instead, we're going to go get the RSS feed. And I just showed you that a second ago. And let's take a quick look at that.

This is a little overwhelming at first. This is really just some raw XML that gets brought down with the feed. You can start to see these things like Oregon Coastal Webcam Widget. I saved this earlier. This may not be the newest Widget anymore. But we basically have a bunch of items here defined with an item tag, and we have a title, and a link, and description, which I don't think we're going to be using, and a publish date. You can see that in this case we're just displaying the title and the publish date. When we click on one of these we use the widget.openurl command to go ahead and open it in Safari.

So as you can see, or as I'll show you in a second, let's go on down to the... XML loaded function. And this is going to be, this is what we're setting here using the onLoad handler and the slides we're using on ready state change. Here we're using onLoad. And this is going to get called when the feed is finally loaded.

Then we go ahead and we start working with the XML. If the response XML property exists, which in this case it does, we go ahead and we get the contents. We get the RSS block. And then we start to actually walk down the tree. For all of the items down here.

And this find child function is actually really simple and I want to show it to you. We basically just walk through the DOM tree from that XML that we got. And we basically look for matches with the name that we requested. And it's basically a much simpler implementation of doing something like get element by ID or get elements by tag name, which is going to give you everything by just brute forcing the entire thing.

So by walking down the tree, we can get this done a lot cleaner and a lot quicker. And I hope you can see the difference here. If we didn't have this response XML element, we'd basically be scraping through this whole thing, either using a bunch of, you know, index of and substring calls or a big fat regular expression. So this is a lot cleaner. And, you know, we go ahead and we get the title and the link and the publish date, like I pointed out to you in the RSS. And we see our widget. So I think that's about it.

Can we go back to slides, please? Now let's talk a little bit about plugins. What is a plugin? There are a couple of plugins you can use in Dashboard. The first one is a Widget plugin. A Widget plugin is something that will give you JavaScript access to non-visual native services, data kind of stuff, APIs that are available to the system but not necessarily available to web applications.

An example of this is the Address Book widget. That widget does all of its display using standard web stuff, HTML, JavaScript, CSS, but it actually needs system resources to go get those names out of the Address Book. It uses a Widget plugin to do that behind the scenes.

Now the difference between this and an Internet plugin, an Internet plugin is the more traditional plugin that most of you are probably used to. It's a view-based graphical addition to the actual layout, to the actual web content. You use it and you embed it using the Internet. You can use the embed tag just like you always would with old school plugins.

You could use our new WebKit API or you could use the old Netscape API. An example of this is the Phone Book widget which actually implants a special drop-down to do that searching that it does. That's not a normal web control. It's something that we wrote as an Internet plugin.

Widget Plugins are non-graphical. To use them in a Widget, you set the plugin Info.plist key. The value of that key is going to be the full name of the Widget plugin with a .widgetplugin extension. You start as a foundation bundle as opposed to an app kit bundle because it's non-graphical. You need to implement a few methods.

The most important is initWithWebView, which is a standard Objective-C initialization method. This is required for Widget plugins. This method is going to be called when the plugin loads, and that's actually before any of your web content is loaded. The plugin is loaded very early in the whole Widget loading process. So if you fail to implement this method, if you throw an exception or crash in it, you're not going to see anything in your Widget.

There are some other methods that you need to implement that are part of the informal web scripting protocol. Windows Script Object Available is selector excluded from web script, is key excluded from web script. I'll explain to you specifically what those do in a second. In it with WebView.

Like I said, it's required for all plugins and it's a simple initialization. Whatever you need to do to get your plugin started. If you need to go talk to some native service, you might want to hook into it at that point so you don't have to bog yourself down later when you get your first real request. The other thing is Windows Script Object Available.

This is called shortly after the initialization. You're passed a web script object. I think you can see up here what the example of this is. The highlighted part there is where we set ourselves, in this case the plugin object instance, for a key. Where is this key going? This web script object that you're getting passed here is a Cocoa wrapper to the JavaScript window object.

All those calls you made to window.widget and onShow and things like that. That is the window object. By doing this set value for key, we're going to allow your plugin to be referenced implicitly in JavaScript using, in this case, my plugin. It's now part of the window object on the JavaScript side.

The other optional method implementations. We say optional because your plugin is not going to break if you don't implement them. However, it's going to be fairly useless. They're optional, but if you want to get anything done, you should probably write them. The most important one is "webscript name for selector". This is how you expose specific methods in your plugin to JavaScript. You receive a selector, in this case, basically the method that is going to be called at some point. You return an NSString that is then translated into the JavaScript equivalent of the function that you want to call.

You don't need to implement this method, but we really recommend you do because Objective-C methods have colons in them for parameter definition. Those names tend to get mangled when they go across the bridge, for lack of a better word, to JavaScript. Override all of your selector names with a more JavaScript-friendly name, the kind of syntax you would expect to use in JavaScript.

Then there's "isSelectorExcludedFromWebScript". This either says yes or no as far as barring access to the method in the first place. It's kind of a double negative thing. If you don't want the method excluded, you return no. If you do want it excluded, or you don't want it exposed... You return no if you want to see it, and you return yes if you don't. The default behavior is to return yes for everything.

and likewise there is "is_key" excluded from Web Script and this is literal direct key access to member variables inside the plugin object. We usually recommend you just return "yes" in any case for this and if you have something that you want JavaScript to access, go ahead and write an access or function in good Cocoa programming style.

Okay, now I'm going to show you Widget Plugins. Can we go back to demo two please? Okay, so again, one of the things you should have found in your accompanying DMG for this session is the Fortune Widget, which I believe is also up on our website and part of the SDK in Developer Examples Dashboard. So you can roll a die and find out where you want to get it from.

So this is a very simple Widget that demonstrates the use of a plugin. This particular plugin just returns a random set of strings to JavaScript to be displayed in this little Fortune cookie. And I, that's not the one I was looking for, but... Things are going so well right now. I don't know. At least that one's a little better.

So what this is doing is every time I click on the fortune cookie, we go ahead and we ask the Widget plugin for a new fortune. The Widget plugin returns it as an NSString, which is then translated into a JavaScript string that we can then just insert right into the DOM. So how do we do this? Well, let's go ahead and open up the Widget for starters.

And again, this is a Widget plugin. It's not a web plugin. Once Xcode comes up for us, I can show you what I mean. Let's have a look at the HTML real quick. The HTML is wonderfully simple. Basically, we just have our fortune cookie as the background, and then we have a quote, which is just going to be a text string that we throw up based on what we get back from the plugin.

So, the first thing I want to show you is this if fortune plugin. And fortune plugin is the name of the Widget plugin in this case. And this name is set using that Windows Script object available method when the plugin is loaded. I'll show you that in a second.

But we're going to go ahead and reference it using the same string that the plugin says you should use. And then we just start making calls directly to it as if it were a regular JavaScript function. And, In this case we have one function called logMessage and we just go ahead and we say log this for me and then we go ahead and we get our fortune back from the plugin. And that's just going to return to us as a normal JavaScript var and we go ahead and we set the quotes in our HTML to that return value. So, pretty simple. So let's go ahead and take a look at the actual plugin project.

So here's our plugin. And like I said, the most important thing here is the init with web view. And we're going to go ahead and randomize, we're going to set up a random number generator in our initializer, which is going to go ahead and work through all of our quotes. Our quotes here is just set up as a normal string array.

And we have our Windows Script object available. So this is just like in the slides, set value self for key Fortune Plugin. And again, Fortune Plugin is how we were referencing it in JavaScript. So this is setting Fortune Plugin as a property of the Window object in the Widget.

Because this is a simple plugin with just two methods, here's our web script name for selector method. If we're requesting Get Fortune, this is how JavaScript should call it. For Log Message, this is how Log Message should call it. So here's the difference. So you notice that Log Message in the Cocoa world is going to have a colon because that represents the parameter that gets sent in.

We're going to return this back to JavaScript as just Log Message, and then JavaScript will call that using the open and close paren like it does. So there's a little syntactic difference here, and you want to just basically close those gaps by using this method. And then isSelector excluded from Web Script? If it's the two methods we want to expose, which in this case is all of them, we return no, otherwise return yes. And as I said in the slides, we always want to bar key access.

So, now granted this is admittedly something of a contrived example, you know, there's no reason we couldn't just randomize the strings in JavaScript and call alert instead of log message. But I think this demonstrates real simply, you know, what it takes to get a Widget plugin up and running. You know, Address Book, for example, uses a plugin that goes ahead and calls the Address Book API, gets people's names and addresses, and it basically has the same skeleton around it.

and Get Fortune goes ahead and gets a random number and gives us an index from the Fortune array and returns that over. Now you notice there's no special conversion or preparation required. You just return an NSString and the web scripting protocol implementation goes ahead and transfers it as needed for you. So that's about it for Widget Plugins. Go back to the slides please.

So now let's talk about WebKit plugins. Well, unlike the Widget plugins which used a plugin key, and that's talking about Widget plugins, the WebKit plugins require a key called Allow Internet Plugins. In other words, anything that would be maybe in library Internet plugins off the home directory or something specifically in the widget.

And instead of using, instead of explicitly calling out the plugin like we did with the Widget plugin, we were just calling it using Fortune plugin, we need to use an embed tag to display it. And then when we're talking to it later in JavaScript, we have to get it either by the element ID that we assign it or we use the document embeds array that's part of normal JavaScript.

And we're going to create this one as a Cocoa bundle. As opposed to the foundation bundle. And then we're going to create this one as a Cocoa bundle. As opposed to the foundation bundle. Because our goal is for this thing to be graphical. Otherwise, we'd just be writing a Widget plugin. So, and then there are the typical web scripting APIs that we saw earlier. You need to implement those just like in the Widget plugin to get scripting access.

But there's also web plugin methods that you're going to need to implement. The most important one is plugin view with arguments. And this is your initializer function just like in it with web view. And then you go ahead and do object for web script, which is basically, usually, you're going to have a function called object for web script. And then you go ahead and do object for web script, which is basically, usually, you're going to have a function called object for web script. And then you usually just return self.

And your web plugin initialize and destroy, which is going to be when the plugin first gets loaded, and then closed, usually in the case of a browser when the window is closed. And we have web plugin start and stop. And web plugin set is selected for if you want to do something in case the user actually selects. It's really more useful in Safari than in Dashboard. So the plugin view with arguments method. This is required by the web plugin protocol. You initialize the view. And you receive an arguments dictionary.

Which contains a lot of things. But what might be useful to you is this is how you would get into the attributes that you might want to set in your embed tag. Things like source or whatever else you want to do to customize the behavior of the plugin from the start. And you go ahead and you grab that. It's not the dictionary that you're passed. But it's a member of that dictionary that's identified by the web plugin attributes key. And this is going to be all your attributes from the embed tag.

So, when you're working with WebKit plugins, it's a little different from working in Safari. Something that's very important is you're going to want to implement the WebPluginStart and WebPluginStop methods. And then you can, by exposing those to JavaScript using the same stuff that we did with Widget plugins, you want to tie those into the onShow and onHide events. And the reason you want to do this is because, presumably, you have a visual plugin, and it's probably doing some kind of work, some kind of animation.

And you don't want that plugin to consume cycles while the dashboard is hidden. So you can, by hooking into WebPluginStop using onHide, we can go ahead and shut the thing off for now and then restart it when the dashboard is shown again. So I'm going to show you a quick demo of this. Can we go back to demo two? Okay.

So I'm going to open up this Picture Frame Widget, which I believe they may have demoed this morning at the advanced WebKit development session. And this Widget basically hooks into Core Image and does a couple of transitions through a series of pictures. And I think the theme here is Matisse paintings, and it basically randomizes the transforms that are done and the transitions and This rollover control here is actually an HTML element, not part of the plugin, something that we just bring on when the user mouses into the Widget. And then by pressing start and stop we have on-click handlers here.

and then communicate through to the plugin using the web scripting protocol, same way we did with the Widget plugin. So, how is this implemented? Well, let's go ahead and start by looking at the familiar stuff. And this is our web scripting implementation here where we have a selector excluded from web script, web script named for selector. This is the stuff that we already did in the Widget plugin. We have a few more selectors in this case, but the implementation is more or less the same. Now, I also want to have a look at the Webkit plugin implementation.

So here's our plugin view with arguments call and this is where we set everything up with the plugin. All the core image things that we need to set up and we go ahead and we get the width and height things in our HTML file. Let me show you.

In the case of the Widget plugin, we didn't have to do anything in the HTML layout because this was just a data service that was going to be available in JavaScript. But because this WebKit plugin is doing some displayable content, we actually show it here in the HTML using the embed tag. We set an ID here.

Here is our width and height. And that width and height is what we're grabbing over here when we go ahead and we get the WebKit plugin attributes. And that allows us to set the height and width of the control, as well as the source attribute, which tells us which picture to start with.

and our initialized method, we really did everything we needed to in plugin view with arguments so that we leave that empty. And here's Web Plugin Start and Web Plugin Stop. This is basically very simple. We go ahead and we start the transition timer and stop it on stop. Let's show you the importance of these functions right now. I'm going to actually bring up Activity Monitor.

You can see here, there's our picture frame client. And, um, well it's not playing. So let's play it. And you can see it's taking up a decent amount of the CPU, you know, periodically when it actually has to perform one of the transitions. So, let's get rid of the, uh... I'm going to hide this. In a perfect world, this would go down quite a bit. Unfortunately, it isn't. What we need to do is be a much better dashboard citizen and... hook our on-hide and on-show handlers in to go ahead and stop and start the plugin when Dashboard is shown and hidden.

We have an onHide method, but I have simply commented out the code that assigns it. So, simple as that. and our onHide method. Notice that it goes and gets the, oh, I'm sorry, we call stopPlayingImages, which is going to be, and we basically just clear the timeout that goes ahead and does the stop and start there.

By assigning that handler, kill this one, bring activity monitor back to the front, Now we can see it's doing some work. And now if I did these things right, it should go ahead and bring itself back down. And now we're down to zero. And it should stay there. I'm really taking a big risk by waiting this long. I should quit while I'm ahead. Alright, so now that's a good Dashboard Citizen. and I'm pretty sure that's all I got to show you. Can we go back to slides? I think it's time for Christian to come back.

So, now that we've told you about some great things you can do with plugins and command line and network access, we're going to just touch on a couple of other useful bits, and I'm going to go through a mea culpa as well. So, first and foremost, there's some great new WebKit technologies you might want to take advantage of, and you'll find documentation on these. There's the Canvas, which lets you do arbitrary drawing in a 2D space. There was a demo of this during our overview yesterday.

So, if you ever have to do any drawing, instead of using a WebKit plugin like Matt just showed you, try to use the JavaScript Canvas. It's just a lot cleaner, a lot more lightweight. It's really easy to use. It uses core graphics like calls. I strongly recommend you look at it. Our Widgets use it, so if you ever look at the Flight Tracker Widget or a Stocks Widget, they both use it to do a lot of their drawing.

The Graph in Stocks uses this, and the Airplane Arc in the Flight Tracker does as well. Next thing you should look at using is the Search field. If you ever need to do any searching inside of your Widget, you look at the Dictionary Widget or the Address Book Widget. They both have that little round search field, which you see in so many of our applications.

So, that's a new input type that is supported in Tiger. It's called InputType=Search. So, you might want to consider using that, too. There's Drag and Drop. You probably don't know this, but if you were at the Apple Design Awards, you do know this. The best Tiger program was Transmit by Panic, and they have a widget that accompanies their application that does Drag and Drop.

If you ever need to FTP a file, you can put their widget on your dashboard, hook it up to one of your FTP servers, and whenever you need to send a file there, you can just begin a drag, show dashboard, and drop it on this widget, and it'll just automatically send it to that. So, that's a great use of Drag and Drop, and it's one of the little hidden gems of Dashboard, the fact that you can do that. The Tile Game Widget also lets you do this.

The final thing which you might want to take advantage of is Pasteboard Operations. So, copy, cut, and paste. If you look at our Calculator Widget, it uses this, and the reason for that is because there's no clear text field on the calculator, right? But people would probably expect to be able to copy a value out of it, or paste one in there. So, there's some handlers in the body tag there which lets you handle that.

There are also DHTML effects that you should be aware of. These aren't Widget-specific, really. They're just DHTML tricks that work with timers and resizing and things like that. There are scroll bars and scrollers. There's a great bit of sample code which Matt wrote called "scroller" and it shows you how to very easily integrate a scrollable region inside of there.

In 10.4.1, there are also new handlers that let you do scroll wheel support. We hope to have some documentation out on that, too. You can also do scroll wheel support inside of a Widget. There's stretching. That's what our Weather Widget does. You'll notice when you click on it, it stretches down. It's basically for toggling between two sizes. Matt wrote a great bit of sample code that's called "stretcher" which teaches you how to do that nice and clean and easy.

There's live resizing. There's a sample code called "grid" which lets you do live resizing. You shouldn't use that all the time, but sometimes it's necessary for things like the dictionary if you have a ton of information to show and people want to view it all in one shot. And then finally, there's fading.

So, if you're using a lot of different things, if you ever need to fade text in and out, for instance, you saw that the Fortune Widget, you know, fades those fortunes in and out instead of just, you know, abruptly changing them. It's a nice effect. Matt's Fader Widget does all that, too.

So, if I could switch to demo one really quick. One of the things that people called me on yesterday in the lab was the fact that I told you to use the glass done button which we provide for you in System Library Widget Resources. And I didn't give you much more than that.

So, I'm going to fix that now since I have a few minutes. So, what I'm going to do is I'm going to start back or I'm going to finish off today and the whole WWDC for dashboard with the same place we started and that's with the Hello World Widget. And what we're going to do is we're going to add this done button to it. So, I'm going to show you how we did that.

It's a little bit tricky but it's not too hard. So, here's the widget running, you know, without the done button, just the Hello World Widget. And what we're going to do is I'm going to pop this open here. And of course, I do have a script ready for this guy. Let's bring him up.

You just got to do a couple of things that aren't too hard and that will let you use the standard glass buttons for free. It will size them automatically. You will get all the behaviors. It's really simple. That way if we ever change the artwork or anything like that, you will get it for free.

So I'm going to take this and I'm going to open it up here. We've got all of our beautiful disclaimers. Okay, so the first bit we are going to do is we are going to add a new div to our body. And the div is actually going to be empty. It's just going to be done. It's going to be called done. And we give it an ID because we are going to reference this in JavaScript. The command that we use in JavaScript, by the way, will provide all the artwork. That's why I'm not referencing it here.

So in CSS, then, I'm also going to position it. This is just some general purpose cleanup work here. Again, we are just going to position it absolutely and then put it roughly in the middle, a little bit down on our Widget. That's not too important. Where it gets good is inside of our JavaScript, inside of the JavaScript portion of the head portion of the HTML file, we are going to include as our source system, library, Widget resources, button, generic button, dot, generic button, dot, js.

So there are some functions in here that auto-generate the buttons for you. We are going to use one of them. So just remember, look in system, library, Widget resources if you want this stuff. The file is right there in the button folder. So we just have to include it here. And then once we do, we are going to use an onLoad handler.

[Transcript missing]

DoneClicked obtains the DOM text and changes its text to "Goodbye" In our body, I need to put an onLoad handler that calls the loaded function I put in here a minute ago. I'm going to save this, close it, and open it. And sure enough, I've got this glassy nice little done button. Click on it and it says goodbye. And that is my last demo. Slides please.

So, today we have covered a couple of really important things. Technologies that probably, that you are going to want to leverage if you want to take your Widget to the next level. These are great technologies which open up a whole world of usage for your Widget. You can use command line utilities and any type of, anything that you can do with Unix you can do here. Scripts or command lines, things like uptime and stuff like that.

We have given you access to HTTP requests and XML feeds so that you can do anything with network resources. Pull images off, anything like that. We have given you Widget plugins that let you access native APIs so you can integrate with your own classes, our frameworks, anything in Objective C. And finally, we have given you some great WebKit, the capability to do WebKit plugins and that lets you do custom drawing, OpenGL, menus, anything like that from within your Widget. So, at this point I would like to point you to the documentation. There are two bits there.

There is the dashboard programming guide and the dashboard reference which both cover everything that we have talked about today and more in depth. There are also some great JavaScript documents about Safari and JavaScript so you might want to look at those as well. There is also the sample code I mentioned before. That is there as well. If you have any questions, feel free to contact Alan Samuel.

He is our evangelist. You can get a hold of him at [email protected]. And feel free to join our mailing list. We have a dashboard developer community at lists.apple.com. You can browse the archives there and see what has been discussed already. And then feel free to join and ask questions about your Widgets. That is [email protected].