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-114
$eventId
ID of event: wwdc2005
$eventContentId
ID of session without event part: 114
$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 114

Dashboard Widgets

Application Technologies • 59:15

This hands-on session will provide you with the techniques you need to make a great Dashboard Widget on Mac OS X Tiger. We'll discuss Widget UI design and explain, using sample code, how to implement scrollers and other common Widget interface elements.

Speaker: Christian Wagner

Unlisted on Apple Developer site

Transcript

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

And then we're going to finally wrap it up with some widget events that you'll probably help you make a widget from scratch that integrates well into Dashboard, feels like it belongs, So what's in a Widget? There's at least four things inside of every Widget. And if you were here at the last session, you saw these. There's at least an HTML file. This is where your Widget's implementation is. Here you use CSS, HTML, and JavaScript to introduce interactivity, your design, and your structure for your Widget.

From there, there's also an Info.plist file. And for those of you not familiar, the Info.plist is used for all of the information that Dashboard and Mac OS X needs about your Widget, all the metadata, its name, some little bits about where to find the HTML, and stuff like that. Beyond that, there's two image files, a default.png and an icon.png.

These are files that are used by the Dashboard to represent your Widget, either while it's loading or in the Widget bar. And these are put inside of a Widget bundle. A Widget bundle is a file that's used to represent your Widget. A widget to a user is a single icon which they can click on and just represents a Widget. But inside, it's really a folder which contains all of the resources that your Widget needs. And you can put anything you need in here, images and CSS and JavaScript files.

So to that end, a lot of Widgets you'll see will have things like JavaScript files in there, CSS files. You'll see these things called Lproj. These are language projects. They're used for localizing your Widget. And finally, you'll also see things like Widget plugins sometimes. And these are native code plugins which you can use to bridge the WebKit.

So you can use these to build your own Widget. So you can use these to build your own framework that's used inside of a Widget and Objective-C runtime. So we're going to jump right into it and build your first Widget. This Widget has four things in it like I mentioned before. It has an Info.plist, it has an HTML file, a default.png, and it has an icon.png.

So I should mention really quick that all of the sample code we're using in this session is available from the WWDC 2005 site. This is the sample code example called Hello World. So if you've downloaded it, feel free to follow along. If not, just look on the screen here, and we'll take you through the whole process. It's very simple, and will only take about a minute or two.

And just so you know, I'm going to be using a typing assistant, which we call Demo Monkey. And the reason for that is because there's nothing more boring than watching me type code. So I'm just going to be dragging the bits of code that I need into the files, and I'll talk to them as I drag them in.

So to start off with, if we take a look at the Widget, it's actually very straightforward. It just has a graphic in the background, then it has the text, "Hello, world." The text is positioned and stylized. And then it also has a couple of other nice things with regards to where the close button is located, and we do that in our Info.plist key. We always recommend that you place the close button over the top left corner of your Widget. We'll get into that more into detail later and all that stuff. So let's go in and actually make this Widget.

The first thing we'll do here, and we're going to use Xcode to do this, is create a new file. And this is going to be our HTML file. The first bit we're going to put in here is our body, and it uses, you know, standard HTML. So we have opening and closing HTML tags.

And then in the body, we have two things. We have an image, which is our background. We happen to be reusing our default.png here, but you can use any image you like. And then we have the text, "Hello, world." And then we wrap it in a div. So a div is just an HTML layer.

And we style it. We're using a class, and we call that class "Hello, text." So let me bring in that style information. And that goes in the head of the file. It's in between the HTML and the body. And in it, we just have two bits, the first bit being "Hello, text." Again, this just applies some font information, bolds it, places it, and things to that effect. And then the other style which we use here is a body, and we set our margin to zero.

So the reason you do this is because you don't want to use margins in a Widget. Sometimes margins change between WebKit versions, and it leads to unpredictable layout. If you get rid of that, wherever you position something is where it'll end up. So we strongly recommend you use "Set your body to zero." It's a very simple thing. So that's our HTML file. It's actually pretty straightforward. So I'm just going to save this. And what I'm going to do is, on the desktop, I am going to make a new folder. And I'm going to call this one Hello World.

And in it, I'm going to put this file, hello-world.html. So that's it. I can take this file if I go on my desktop, and I can run it inside Safari. This is one of the great things about Widgets is that, ooh, you'll notice that it doesn't work in Safari. And the reason for that is because I have to include the images.

So if I go here into the project that I've already set aside, and you have this all in your sample code, there's the two files that I need, default and icon.png. So let's take a quick peek at The default icon is what's-- the default-- I'm sorry, the default image is what's shown on the Dashboard as your Widget's loading. And like I said, we're also using it as the background for our Widget. And then our icon.png is what's shown in the Widget bar to represent your Widget. Users can click on this or drag it out to make the actual Widget.

So now that they're in the same folder as our HTML file, I can show this in Safari, and sure enough, there we go. It has our text and our image. Everything's placed. So it's a great web page, but it's not a Widget yet. And the thing we have to add to make it a Widget is an Info.plist file. So the Info.plist file is all the information that Mac OS X, the Finder, and Dashboard need in order to make this particular bundle a Widget. It provides some metadata that's important. It must be named Info.plist. This is a requirement of Mac OS X.

So we're going to go into Xcode here and make a new file. So every Info.plist file starts off with just some information about the file, what version of XML we're using, and which DTD it conforms to. Do yourself a favor and just grab this from existing files. It just makes life a lot easier. But we've included it here.

From there, we have a couple of keys that are required by the OS. The first one is CFBundleDisplayName. This is the name of the Widget as it will be localized. And this is then used in the Widget Bar and in the Finder if you provide localized equivalents. There's a CFBundle identifier. This is a unique string for your class of Widget. You have to make this unique for any Widget. And this helps Dashboard separate-- helps it-- how should I say-- differentiate different Widgets.

From there, we have a CFBundle name. This is what's used by other utilities within the system to recognize this Widget, like the activity monitor and things to that effect. And then finally, we have a CFBundle version. We recommend you provide this, even though it's not strictly necessary, because it helps for conflict resolution. If a user has multiple versions of your Widget installed, Dashboard is smart enough to look at this key and load the most recent version.

From there, we're going to load in some Dashboard-specific keys. The first one we're going to put in here is main HTML. And this is just a string which points to the HTML which contains the implementation of our Widget. Remember a minute ago, I saved hello world dot HTML. Well, here's where I specify it. And when Dashboard loads this Widget, it's going to say, OK, this is the HTML file I need, and it's going to load that.

We're also going to put in a close box. And like I said before, by default, a close box, if you don't specify the close box inset X and Y keys, it just goes in the top left corner, which is not that big of a deal. But if your Widget has alpha or transparency around its edges, you need to move the close box so that it's over the top left corner of the Widget. It just helps people associate that the close box belongs to a Widget a lot easier.

And then the final thing we need to do is we need to bring in just some closing tags to close out the dictionary that's the plist and close out the plist altogether. So we're going to save this. And again, we'll save it in the same folder we saved all our other files. We name it Info.plist.

Now, I just built this all manually using Xcode, and that's okay, but we provide on the system a great-- with the developer tools, actually-- the Property List Editor application, which we strongly recommend you use. It just helps prevent typos, and it gives it a nice graphical interface for all the different keys.

So if I look here, all the keys that I have already specified in Xcode, they're all listed here. Their types are also noted here. This is especially useful for, like, the closed-box inset X and Y. These aren't strings. These are numbers. Things like that are very helpful. So I strongly recommend in the future you use that. So we have a folder. We have our four files in it. Let's make it a Widget.

I'm just going to close it. And all that you have to do to make something a Widget is to select its name and then provide the .wgt file extension. The finder's going to ask, are you sure you want to do this? I'm going to say yes. Go ahead and do it. And then it's a Widget. It's replaced with the standard Widget icon. And all I have to do is double-click on it. And sure enough, there's my Widget. Back to slides, please.

So now that we've assembled your first widget, well, OK, it's a toy. It doesn't really do much of anything. It's time to actually make your widget do something. And so you need to introduce interactivity in your widget. And you do this using JavaScript. You can use the widget technologies, which we'll discuss, and which were discussed in the previous session. You can also leverage some of the great new features in WebKit. But first, you really should think about your widget's design.

Design is what makes your widgets stand apart and will make people recognize it when Dashboard is shown. So there's a lot of things you should take into account. And you should do it first before you actually implement your widget in order to put an emphasis, the proper emphasis, on the design.

So the first thing we're going to talk about is try not to make your widget the jack of all trades. Make your widget very succinct. And have it focus on a primary purpose. And do that purpose phenomenally well. So instead of doing one widget that has a clock, some weather, some calendar information, do just a clock, just a weather widget, and just a calendar.

And that way, a user can put as many of each instance of widget they want on their screen at any time. I have family in Europe, so I want to keep track of their time versus my time. But I only care about the weather in San Francisco, so I can only have one weather widget. And you get the idea.

Scott Forstall in the State of the Union said, "Don't Bogart the Dashboard." What that means is make your Widget as small as effectively possible. Try not to make huge Widgets that have banner ads and tons of article information and things like that in there because it's just stealing space. Some of our most popular computers are 12-inch iBook and our 12-inch PowerBook, which have 10 by 7 screens.

And a Widget like the one you're seeing on the left is just going to totally hog the Dashboard space and people aren't going to keep it around. If you're going to try to provide this type of information, aggregate it into something like headlines and then from there hand it off to another application like Safari instead of just having it all on the Dashboard.

The next thing you should take into account is use color to your advantage. One of the nice things about Widgets is that we don't constrain you nearly as much when it comes to UI design. You can do whatever you want. So color is a great way to brand your Widget.

For instance, of the nine Widgets up there, you can pick out the calendar really easily. It sticks out versus the other ones. If everyone makes a Widget that looks like the world clock, you're not going to differentiate your Widget. This red one really pops out. And so when I'm using Dashboard, I know, OK, I need to use the calendar. I'm going to hit F12, and then I'm going to look for the red thing, the calendar in this case. And that's what's going to jump out, and that's what I'm going to use.

Next thing to consider is be very, very careful with branding. Try not to waste space on the front of your Widget with one-time information or advertisements and things like that. If you do need to provide some information about branding and where the Widget data came from, do it on the back of the Widget. Provide a URL, maybe hand them off to a website, something to that effect. Try not to put banner ads on the front. Again, people will just be annoyed by it and they're going to get rid of your Widget.

This is another one that's a little bit confusing. Try not to use Aqua controls on the front of your Widget. Use integrated controls and design custom controls that look like their real-world counterparts and behave like them, but also integrate really well into your design. If you look at the Widget on the left, the Aqua controls just kind of seem disruptive. They don't feel quite right in place with this nice blue, clean design.

So the Widget version on the right has this nice little pop-up menu that just looks nice, but notice that it still looks like a pop-up menu. It has a clear outline. It has the arrows on the side. It functions just like a pop-up menu, too, but it integrates better into the design of the Widget.

This is a personal pet peeve of mine: closed boxes. Make sure to take the time to add those two Info.plist keys, close_box_inset_x and -y, to move your closed box over the top left corner of the Widget. If you do this, people will know right away which Widget owns that closed box.

If you don't do this and someone has a really cluttered Dashboard, there's going to be closed boxes all over the place and they're going to get really confused really quickly. So this shows a clear ownership, and I really recommend you do it. It's just two keys. It's really simple to do.

Next thing you should take into account, use the Apple supplied artwork. You probably don't know this, but if you look inside of System Library Widget Resources, we provide you with a whole bunch of artwork and other little tools. So for instance, you'll notice that a lot of Widgets that use the flip transition to flip to their back uses this info button artwork.

Well, that's all available for you in System Library Widget Resources. Same thing for standard buttons, these glass style buttons that we use, like for done and things to that effect. You should really use those and instead of copying them into your Widget, just link to them directly in System Library Widget Resources. That way if we ever update them, you'll get them for free.

And finally, look around. There's some great Widgets out there that are just a good source of inspiration. See what they've done. Take a look and see if their ideas can work for you. A lot of our Widgets provide a really good experience for summarizing data, like the Weather Widget. You know, it shows by default just the current weather, but then you can show more to look at the forecast. It's a really good way of working, and we recommend you do the same.

So now that we've talked about design, we're going to spend just a few minutes talking about localization. And we bring it up really early, because it tends to affect how you lay out all of the files within your Widget and how you wind up working with some of the resources inside of your Widget. So it's important that we bring it up now so that you can take into account when you're building your first Widget.

So if you look inside a localized Widget, you'll see a lot of times these things called L-proj's. These are language project directories. And this is a standard system that OS X uses to localize resources. You'll notice that they have usually two character codes as their names. So de.lproj is for German, en is for English, es is for Spanish, and so forth and so forth. And if you look at our documentation, there's tons of information about all the different codes you can use for localization. Inside of these folders, you'll find all the localized resources for that widget.

What that means is inside here, you can put any images you want to use for specific localization, any style sheets, so if you have to take into account a different design, localized strings, you might want to include them in here, and any Info.plist keys that you want to localize. So for instance, if you'd like to have your Widget have a different name and a different language, you can do that in an Info.plist.strings file.

The nice thing about this system, by the way, is that it's a standard OS X system. Whenever you ask for any one of these resources in your HTML, JavaScript, or CSS, it will automatically try to just go into the localized folder that corresponds to the user's current localization setting. You get this for free.

You don't have to specify every directory or anything like that. So if I were to, in this case, just ask for localized strings.js and I were running in German, it would just automatically just fetch the German version of that. I don't have to do anything for it. So it's a really handy little system.

First one of these files I'd like to talk about is the Info.plist.strings. There's only really one line in this file. That's the CFBundle display name. And here you would provide a foreign language equivalent for the name of your Widget. And this is the string that's used in the finder when the user sees it. It's also used in the Widget bar. So for any keys you want to provide an equivalent for, you just do an equal statement. So in this case, we're going to use CFBundle display name. We're going to set equal to Hola Mundo for our Spanish translation.

Now we're going to get into localized strings, and this is a little bit more of a convoluted topic. So I've got some good graphics here. We hope to work through this all. So the first bit of this all is that in your language project, for each language, you should have something to the effect of a localizedstrings.js file. This contains all of the strings in your Widget that need to be localized. And it looks something like this. It has an array in it. We call it localized string. You can call it whatever you want.

And in it, we have each index corresponds to a localized version of that string. So we provide the index of hello world. That's our English equivalent, which we're programming in. But then we provide the Spanish equivalent, hola mundo, there. So that way, later on, when I query for a string, I can just ask for what I know the string to be and automatically get the localized version back.

So the first thing that happens here--the next thing that happens, once you've provided the localized strings is in your HTML file, you just need to include the file itself. Don't include a path to it. Just ask for localizedstrings.js. And you'll automatically get the localized version. And all this stuff is going to be in the localize string. So you can see that the string is going to be in the localize string.

And you can also include your JavaScript file. Because in your JavaScript file, we are going to call a setup function. This is called when your widget first loads, and it knows about all of the things that need to be localized inside your widget. And it's going to pull all of those localized strings in and dump them inside of the widget.

So that setup function looks like this. It has getElementById, and it just knows the elements that need to be localized. And it's going to call this getLocalizedString function. And that works like this. Get localized string, since we've included the localizedstrings.js file inside of the project, will automatically be available to the JavaScript file.

And so we're going to just pass in the Hello World key to that. And then it's going to just get supplied back in and then returned back to the function that we called in our setup function that asks for the string. And so in here, when we've asked for get localized string Hello World, it's gone to the JavaScript file, gotten the localized string out. And then it's replaced it and sent it back to our HTML file where it's localized. So we've done a lot there. We're going to go into demo and we'll explain that a lot more clearly there.

And then also, some of the things you might want to consider localizing, style sheets. So if you need to do different string placement based on different languages, colors that mean different things in different cultures, and finally, any images, if you want to include any flags or any other culture-specific images, you can include them in a localization language project. So let's go to demo two, please.

So in this example, we're going to be following the Hello VELT example, which is inside of the sample code that you could have gotten. Please feel free to take a look or follow along on screen at this point. So again, I'm going to be using a script helper here that just provides all the code that I need.

So the first thing which we need to do here-- and let's actually take a look at what the widget looks like beforehand. So I'm going to just go here, and I'm going to call this hello-welt.wdgt. We're going to add the file extension to that, and we're going to run it. So right now, we are running in the German localization, so it is localized.

So if we take a look in here, we have a German localization, and in it are a couple of files. The first thing is the info.plist-- Info.plist.strings. And again, like I said before, we're just providing an equivalent to any of the strings that we want to localize inside of our Info.plist. So in this case, we just want to localize the CFBundle display name, and we want it in German to be Hallo Welt, which is the German equivalent of Hello World. Also in this language project file, we have a localized strings.js file.

And in it, we have all of the localized strings that we're going to be using inside of this Widget. So it's actually pretty simple. We have hello, world. That's the index that we're going to query for. And we're setting that equal to hello, welt, which is our German thing. So when we use our localized string function, it's going to know about this array. It's going to provide the English, and it's going to get the German version back and provide it back for the body of the Widget.

And then we're going to construct really quickly our JavaScript file for this. So the first thing which we're going to do is we're going to provide a setup function. And this setup function, like I said before, knows what elements inside the Widget need to be localized. So in this case, the Widget has a part of text called hello text. And we're going to set its inner text to be the result of this getLocalizedString function. This getLocalizedString function is going to-- I'm going to show you that right now. Oops, wrong bit. There we go.

It's going to try to get inside the localized strings array and take whatever key is provided, so in this case, hello world, and it's going to try to pull that out of the array. And once it has, it's going to return it back into the setup function, which will then place it inside of the widget. So let's save this. So if I save it on the desktop here, I'm just going to put it-- actually, I need to unmake my widget. Here we go.

Okay, let's save that now. And we're going to call this hello-velt.js. And then it saves it right there. And the last thing we need to do is, inside of our HTML file, we'll need to include the two different resources, JavaScript resources that we need to have. Our localized strings.js, again, it'll load in whatever the localized version is, and we need to include the actual JavaScript file, which is going to have all the functions we need to actually get the localized string. And then we provide an onload handler for our body, which calls the setup function.

All right, so we're going to go ahead and make this a widget again. at .wdgt on there. So I'm going to run this one as German and as English, just to show you how that all works. So if we go to internationalization, right now we are running in German-- or right now, we're running in English.

OK. We are-- running in German now, and it's running in German, and it should be running in English. When we set that up there-- well, it doesn't seem to be working for me today. But if you look at the sample code, I know that it works. I can verify it because I've tried it many times. So we'll go there. Back to slides, please.

So now that we've talked about localization, we're going to spend a good amount of time the next 20 minutes talking about setting up a Widget back and then saving preferences. This is a really big deal, because pretty much any Widget that you download nowadays has a back to it, and it usually needs to save some type of preferences, just if it's color or language or anything to that effect.

So the first thing you need to do is inside of your HTML, you need to set up two layers or two sides. So in this case, we recommend that you use just two divs. And set one to be the front div, and you put your Widget's front side there. And then you use another div, and you set that to be the Widget's back. And you just put whatever-- in between these tags, you put whatever images and strings you need to make that Widget to have a front and a back.

And then in your CSS file, you're going to need to do two things. You need to set the display style for the front to block. So in this case, it means show. Block means show in CSS. And then for the back, you need to set the back display to none, so tell it to hide, not to show.

Another thing you need to do on the front is you need to use the info button. Now, unfortunately, in previous and prereleased versions of OS X Tiger, we included a different graphic, and some people are still using that, and I want to discourage that, because we really want people to use this info button graphic.

People know when they expect -- they expect a certain behavior when they see it. They know that it's going to take them to a widget backside. It's a standard control, if you will. There's two parts to this icon. There's the eye icon, actually, and then there's the background circle, which fades on -- or which actually flashes on when you mouse over the eye. And there's a bunch of JavaScript we'll go into to handle these rollovers and fades.

Again, these images are available for you in system library widget resources. We suggest you link to them there, and that you use black or white versions. We provide them both, whichever works best for your design. And again, we recommend that you place this at the bottom right-hand corner of your widget whenever possible. It's just the usual place where people expect to see this thing. It's okay to put it in the bottom left or the top right, but really strive as much as possible to put it in the bottom right. That's where they expect to see it.

And at this point, I just want to address the topic of control regions really quick. Because the info button is a button, it's a control region. You would not expect someone to mouse over that and be able to grab the widget from the info button. And so you need to set a region around it so that Dashboard knows that, hey, this is a button. I don't want people to be able to drag from here. So that's done as a CSS property.

It's called Apple Dashboard Region. And it takes, as its argument, one function called Dashboard Region. Its first parameter is always control. There's no other type. But from there, we do either circles or squares -- or rectangles, sorry. And then you can optionally provide offsets from the edge of the wrapped element.

So now that we've done CSS, in JavaScript, you'll need to do a couple of things. Once the Info button is clicked upon, you need to use Widget.PrepareForTransition. And then you pass it to the parameter toBack. And what this does is it freezes your Widgets UI so that any changes you do from this point on won't be shown to the user.

It just freezes it, and it will stay that way for about three to four seconds. If you don't do the following steps, though, crazy stuff will start happening. So you need to follow the next steps. From there, we usually take the front div that we set up before, and we set its display to none. So in other words, don't show it. And then we set the back style display to block. That means now bring it forward. And then finally, you call Widget.PerformTransition.

This is the function which provides that great little flip transition which you've all seen, which flips a Widget from the right to the left. And for what it's worth, the front of the Widget then will be that frozen thing, which we froze with PrepareForTransition. The back will be whatever we've changed the Widget UI to. to be.

So, okay, now you're on the back. What do you do here? Here is a good place for branding and copyright information. So if you have any URLs you want to point to for data streams or if you want to provide some information about your own website, some help information, feel free to put that back here.

If you're going to provide preferences, we actually do actually use Aqua Controls here, though. And the reason for that is that people don't visit backsides very often, and so they need to be able to know what's going on. So Aqua Controls are much more natural to them, and they'll just be able to use them and be done with them.

And besides, the back of the Widget is also kind of a more utility feel to it, and Aqua has that feel going for it. And then when you're done, use a Done button in the bottom right-hand corner that shows the changes have taken effect. If someone clicks on this, that's going to run the transition and flip it back over to the front. Don't reuse the Info button here. Use a Done button. It shows that the changes have happened, and it separates. It makes the Info button a little bit more special.

And then flipping back to the front follows a similar set of processes as flipping to the back. Here we just use prepare for transition, but instead of passing into back, we pass into front. We set the back's display style to none, so it doesn't show. And then we set the front's display style to block, so it doesn't do anything. And then finally, perform transition. This flips the Widget from the left to the right in the opposite direction as our first one. So let me show you how this all works.

So we're going to be following the Goodbye World example for the rest of today's demos. If you take a look in the Goodbye World sample code, you'll notice that it's milestone. So we're going to go through milestone one and two right now, just starting off, and then actually adding all these different features on there, adding the flip transition. So again, I've got a script for this guy, so I have my code easy to pull off.

And we're going to show you the Widget as it is beforehand really quick. So I'm just going to take this guy. I'm going to name it Goodbye. HelloWorld.wdgt. And that's going to make this a Widget. I'm going to double-click it and run it here in Dashboard. And you'll see that it basically doesn't do anything.

It's just like the Hello World we first ran about 15 minutes ago. So we're going to add the back to this Widget. So let's, in case you didn't know, you can always Control-click on a Widget and click Show Package Contents. And this will show you what's actually inside of it. This way you don't always have to remove the .wdgt extension to show the contents of a bundle.

So the first thing we're going to do is take a look at our HTML file here in Xcode. And you'll see it's just like the Hello World that we made before. It just has two things. It has an image and the text Hello World on it and not much else. So we're going to, first off, add the front div tag that I spoke about before.

We set its ID to front. And the reason we're using ID here is because we're going to be later using this widget in--this div in JavaScript. So if you ever want to access anything through the DOM, you need to use an ID statement instead of a class statement. Class is used for styles.

And then we also need to provide a close div tag there, just to close off the back to signal that we're done. And then we're going to be adding the back div. And the back div, all that it's doing is it has its own background image. It's a little bit darker than the front one. It's a good idea to do on the back of a Widget. And then it just has a done button, like I said you should be using before.

So that's basically just setting it up to have a front and a back. And now we're going to be adding the info button there. Again, the info button has two parts. So let me prepare this for that to come in. The first thing it has is the actual eye.

So again, I've applied a class flip. And that means that in my JavaScript, I'm going to have a flip class, which positions this. And then I have an ID flip. That's going to actually have the URL to the image. I'm going to do that all in my JavaScript. That does the eye.

And then we need to do the background circle as well. So I'm going to drag that text in here. And this one also has a class flip. So it means it's going to be positioned in the same place as the eye icon is. But its ID is flip-a-rolly. So that means that it's going to have a different ID and a different image associated with it. So that's our HTML file. Let's go into our CSS file.

Again, it's a pretty basic file. It contains some information about how the text should look, setting the body margins to zero, and then also setting up where the background image is placed. We're going to add two bits right now. We're going to add the front CSS statement. So again, this makes sure that the front, when the Widget is first load, is shown.

And then we're going to add the style for the back. And its display has been set to none, so it hides. And I've also slipped in a little bit of CSS information about the Done button. It just positions it so that it's in the middle of the back.

And then the final thing which we're going to throw in here is the CSS information for the info button. So remember when I said they both had class flip? So that's this particular class, and a class flip is denoted with a period in front of it. And we position it absolutely.

With regards to the bottom right-hand corner, we set its width and height, and then we provide it with a control region. Like I mentioned before, you don't want people to be able to drag from on top of a button. So we've specified an Apple Dashboard region here. And then now for Flip, the Flip ID and the FlipRoly ID, notice these are marked off with a hash in front of them. We've specified their initial opacity and what file we're using for them. So the info button goes to System Library Widget Resources, and it's pulling out the white info button.

And we've also set its z-index to be 8,000. So the reason we do that is we want it to be way up there on top. We don't ever want anything to cover this over. 8,000 seems like a large enough number. It's pretty arbitrary, but we chose 8,000. And then for the circle that shows up underneath, we've specified in System Library Widgets to use the white rolly image. And we set its z-index to be 7,999. That ensures it's just underneath the eye.

And that's pretty much everything we need to do in CSS. So now you'll notice that this particular Widget does not have a JavaScript file, because it doesn't do anything in JavaScript. So we're going to add now the functions that get called when the pref should be shown and hidden. So in Xcode, I'm going to create a new file, and the first function I'm going to add here is showback. And this follows a pretty simple idea.

What it does is it tries to obtain from the DOM the front and the back divs. It just assigns them to local variables for use later on in the function. And then we call Widget.prepareForTransitionToBack. Okay? This is going to prepare the Widget. It's going to freeze its UI and then flip it over to the back. Notice that we wrapped it inside of an if statement. The reason being is if I wanted to debug this Widget inside of Safari, Widget.prepareForTransition would just choke Safari. And so we need to make sure we're running in Dashboard before we try to call this.

Then we set the front style display to none, so we're hiding the front, and then we're showing the back using a back style display equals block. And then finally, once we've done everything, we set its timeout to--we set the widget.perform transition to occur, and this actually will do the actual flip transition. Now notice that we wrap this inside of a set timeout.

That's a little idiosyncrasy of WebKit. We're forcing--by doing this, we're forcing widget.perform transition to happen on the next render loop. This makes sure that everything that we've done beforehand in this function has happened, has drawn, and then will perform the actual transition. So there's no drawing happening while someone's flipping to the back. That can get kind of ugly. And then finally, we're just doing some cleanup here with regards to the info button.

So that's the show function. Let me show you the hide function. It actually does just about the same thing as the show function. The only difference here is that we are calling prepareForTransition to the front, and then we set our display for the back to none and our front to block. So it's pretty much just a flip of the show press function.

And then the last thing we need to do is we need to include a whole bunch of fade information for the info button. So this is quite a block of code here. And I'm not going to go into it very in depth right now. You can get this from the documentation. It's included inside of the Dashboard Programming Guide.

And what this chunk of code does is it does two things. It fades the info eye icon on when a person mouses over the widget, and fades it out when they mouse out. And then when a person rolls over that eye icon, it brings its background circle up and then gets rid of it when a person mouses away from it. So the functions that you should be aware of right here are mouse move. This is what's called when somebody mouses over the Widget. Mouse exit. This is what's called when somebody mouses out of the Widget.

And then down here at the bottom, we have Enter Flip. So this is when somebody mouses-- rolls over the eye icon. And we have Exit Flip when somebody mouses out of the icon. So we're going to save this file. And again, we're going to save it inside of our widget, which I need to unbundle at the moment. So now we'll save it.

So inside of our Widget, we now have this goodbyeworld.js. But there's a problem, and that is that we haven't told our HTML file to include it. So let's open there and add all those event handlers that I talked about for fading and things to that effect. We go back into our HTML file.

First thing we're going to do is we're going to do our JavaScript include right up here. And again, we're just including goodbye-world.js, so that way our Widget can run all the JavaScript functions we added. From there, in our Info button, I'm going to include the Show Prefs. So when our Info button is clicked, this way the prefs will be shown.

For our Done button on the back, I'm going to include is an on-click handler for hide prefs. What that does is that calls the hide prefs function that we just wrote a minute ago. And then two more things we have to add. We have to add the handlers for when we mouse over the Widget and the i button. So mousing over the Widget, we're going to do on the front div, we're going to provide onMouseMove and onMouseOut handlers and provide them to the mouseMove and mouseExit functions that I described to you a minute ago.

And then on the info button itself, we're going to have also onMouseMove and onMouseOver handlers. And these are called when somebody mouses over the info button itself and brings up the background circle underneath it. So we're going to save this, and believe it or not, we now are going to have a backside on the Widget. It's a lot of work, but it's worth it. So we're going to throw this guy, and we're going to add in a .wdgt. Whoops, forgot the period there.

Go ahead and add that. And when I run this in Dashboard, you'll notice when I mouse over, the info button now fades on nice and gracefully. And then when I mouse over it, the circle pops underneath it. When I click on it, we get the flip to the back. Here I click on the done, it flips back forward, and we're set.

So can I go back to slides, please? So now we've shown you how to do the flip, but it's kind of useless. It's a toy right now. So this really works best when you're dealing with preferences. You need to provide a menu or text fields or any way of providing preferences. And then you'll need to save them, too.

So Dashboard has available to it all of the preference system of Mac OS X. It's the same system. We've provided JavaScript wrappers for that. So to save a preference, we provide you the set preference for key method. And that just takes two arguments, a preference and then a key that you want to save it under.

And you should definitely save this preference the moment that it's set. And the reason for that is that widgets tend to be, unfortunately, a little bit transitory. Someone can log out at any time. They could log out while your widget was on its back. And so you probably want to save the preference that they just made.

And it's also good to think about preferences even if you don't think you need to use preferences because of the fact that sometimes installers will quit the dock to try to put their icon into it. And all of a sudden, your widget will lose whatever it was doing. And so it's a good idea to save any information that you'd like to persist across restarts, across logins and logouts, and things like that. And beyond that, you also want to retrieve preferences. And a good place to do this is on your onload handler.

So when your widget first loads, it can try to access whatever preferences are available and then try to adjust the widget accordingly. And to do that, we provide the preference for key method. And that just takes in whatever key that you know about that you've saved. And we'll try to retrieve that key. Now, if no preference exists, you'll just be getting null back. So that's something to know, too. So let's go in the demo and show you how to do that.

So we're just going to keep building on the Widget that we just used. I'm going to unbundle it first this time. makes life a little bit easier. So the first thing we're going to do is, in our HTML file, we're just going to add a menu. We're going to open it in Xcode, and then we're going to add a menu in there. And the reason is because if we're going to save preferences, we might as well give the user something worth saving.

And so that menu, we're going to put it on the back of the Widget, and we are going to place it right here in between the background image and the done. And it has just two options in it, hello world and goodbye world, so a person can choose whichever string they prefer. And notice that we gave it an ID of world pop-up. We're going to need that in our JavaScript to access this particular menu settings. settings.

So that's all we have to do here in our HTML right now. And our CSS file, really quick. Open that up in Xcode. We need to provide a style for the menu. Again, nothing too crazy, just some absolute positioning so that it is center-aligned in the middle of the back.

Whoops. There we go. Save that. And then, in JavaScript, We're going to need to provide two things. We're going to need to provide a facility for when the menu is changed, so that way when the menu changes, we can say, OK, we need to update the text on the front of the Widget, and then also save that off on the disk. So I have a function written up that does that, and it's just called menuChanged.

And what it takes in is, it takes in the element, so that's the select itself. And based on that element, its selected index, if the first thing is chosen, it's going to change the text of the Widget to be "Hello World." And then if we're running within a Widget, it's going to save that preference out.

So we save preference for key, and we pass it the string "Hello World." That's the preference that we're saving. And we're going to give it some arbitrary key. In this case, I've chosen "World String." And we do the same thing if someone chooses Goodbye World on the back of the Widget. We're just going to say the string Goodbye World, and again, we use the key World String for that.

So that's saving a preference. What if you want to retrieve a preference? So I'm going to add an onload handler called setup that's called when the Widget is first loaded. And it checks to make sure we're running inside of a Widget, and if we are, it's going to try to get that preference from the system. Widget.preferenceForKey, and we pass it the key that we know about, world string, and it's going to then provide whatever preference is stored for my Widget back.

We check to see if it's null. If it's set to null, that means that there was no preference, and we can just skip over this, run it as if it was run for the first time. But if it happens to have a preference, we're going to obtain the inner text from the DOM and set it to the saved preference, and then we do also some cleanup work with regards to the select on the back. If someone has Goodbye World as their preference, they would expect that the menu on the back reflected that, so we just do some selected index trickery here.

So that's our JavaScript. All we have to do now is we go back into our HTML file, and then we add two handlers. First, for when the menu changes, we need to actually say, OK, if the menu changes, call that change world function that we just wrote. And then on the body, we need to include a setup handler.

That's called when the widget is first loaded. So we use the onLoad handler, set it equal to the setup function. And that means when the widget is first loaded, it's going to call this and try to do the preference retrieval. So let's run this and see how it works.

So we're going to add the .WDGT. That's a W, OK? And we'll add that. So, I'm going to flip it to the back. We now have this menu that says Goodbye World. I'm going to choose that. When we flip it over, it says Goodbye World. And you'll notice that if I close this and open it up again, it remembered that Goodbye World is my preference. So, if we could go back to slides, please.

So now that we've saved preferences, I should probably tell you about one of the bigger caveats about using preferences. By default, whenever you save a preference, it'll be available to any Widget. So in a sense, it's a global preference that any Widget can access. And sometimes that's just not the right thing to do. You need to be aware of the fact that sometimes, like the Weather Widget, you want to have multiple instances of a Widget open, and each one remembers its own preferences.

To that end, we provide you with the Widget.Identifier property. So what this is, it's a unique identifier per class of Widget. So every World Clock Widget will have a unique identifier of its own type, versus all the other World Clock Widgets. We recommend that you integrate it into your save key, the portion where you save and retrieve a key. So we use a function like this a lot of times called makeKey, which just appends the identifier to the key you're saving.

And when it saves it then out to the Info.plist file, it's going to actually save it with the identifier and the key. So that way, when you save and retrieve it, that preference will be available to you. And that preference will be unique for that instance of Widget. So again, we call preferenceForKey. And then we use makeKey. It makes this key and spits it back in, as if we were just calling Widget.Identifier plus key as the second parameter in setPreferenceForKey. So let's show you how this works. Demo two, please.

Okay. So for this modification, everything happens in JavaScript. And we're actually just going to provide this new make key method function, and then we're going to just change all of our calls to preference for key and set preference for key. So if we open our Widget up again here... We're going to open up our JavaScript file again. And the first thing that we're going to add is just this unique function here, which is called make key. And again, all it does is it makes a unique key based on the identifier of your Widget.

And then I'm going to look for set preference for key. And right now, like I said before, it's just doing it globally. We're going to provide a new version, which uses the make key function that I talked about before. And that's going to use make key instead of just actually providing the string. Get rid of the old one. And we'll do the same thing for if goodbye is saved.

I don't have Steve Mangling going on here, but we'll just get rid of this. And then the final little bit of this is that when we set up the function, we also needed to retrieve the unique Widgets for this instance. So in the setup function, when we do our preference for key up here, we need to provide a different version, which does preference for key using our make key function. So just going to save this and make this a Widget again.

So I've got one Widget here, and I'm going to set it to be Goodbye World. And then I'm going to open another instance up here and have it to be Hello World. And then I'm going to just pretend that somebody logged in and logged out. I'm really just going to kill the doc, which is what's hosting the Dashboard, using Kill All Doc. This will simulate a login and logout. Then, I'm going to show Dashboard. Sure enough, each remembered what string they had on them.

Okay, back to slides, please. So now that we've talked about pretty much all the different caveats of working with preferences, you should also be aware of some event handlers which can provide extra value to your Widget. The first, and one thing I should note about all of these, is that you should always wrap them inside of an if window.widget statement. You provide this in your JavaScript file, and the idea is to make sure that these are only used when you're running within Dashboard. Unfortunately, Safari doesn't support the Widget object, so most of these will not work in Safari.

So it's just a nice little bit of advice for you. The first thing we're going to talk about is the Widget activation events. These are Widget onShow and onHide. These are handlers for those events, and you provide your functions that would be called when these events happen. So what's onShow? onShow is called when your Widget, when Dashboard as a whole is first shown.

And the idea behind this is that you might want to turn on some timers, start drawing, do processor-intensive tasks. What's onShow? onShow is called when your Widget, when Dashboard as a whole is first shown. And the idea behind this is that you might want to turn on some timers, start drawing, do processor-intensive tasks.

What's onShow? onShow is called when your Widget, when Dashboard as a whole is first shown. And the idea behind this is that you might want to turn on some timers, start drawing, do processor-intensive tasks. While your Widget's showing. Or update stuff from the Internet. Anything like that. And then conversely, when a Widget, when Dashboard is hidden, you want to be available, you want to know about onHide, because when the Dashboard's not shown, you don't want to be using up CPU or resources unnecessarily. And so you set a function to be equal to the onHide, an onHide property.

In addition to that, we also provide the Widget Focus events, Window on Focus and Window on Blur. And these are useful for knowing when your Widget has key focus. A good example of this is our Calculator Widget. The Calculator's screen lights up when you click on it, and that kind of shows that, okay, whatever you type at this point is going to get sent to the Calculator, and then its screen, it goes gray whenever it loses the focus of the screen-- of the key.

And then finally, there's Widget on Remove. And at this point, I have to confess a deep, dirty secret. Our Widget at this point, our Goodbye World Widget, has been polluting our hard disk. It's been saving a whole bunch of prefs without cleaning up after itself after I close them.

So the Widget on Remove handler is good for clearing out preferences that you've saved that you need to get rid of after you're dismissed. So to wipe out a preference, you set preference for key. And for the value you're saving, you just pass it null. And Dashboard will know that means, OK, get rid of the preference.

And two other things you should be aware of are some Widget methods. We provide two methods, Widget.OpenApplication. And what this does is you provide a bundle ID here, and any bundle ID that you provide here, it will try to open that application if it's available on the system. And when it does, it gets rid of the Dashboard and brings up that application.

So calling OpenApplication.com.apple.iTunes will bring up iTunes and dismiss the Dashboard. And if you need to open a web page, for instance, on the back, if you have any branding information and you want to point people to wherever your feed came from, use Widget.OpenURL, and you provide the URL here.

You can just provide it as a string or build it however you see fit. And again, this will open that web page in whatever the user's default browser is. So let's demo this really quick for you. So we are going to remove the dot widget extension from Goodbye World here again so that we can work with it.

And what we're going to do is we're going to add a couple of handlers to our Goodbye World. We're going to have its text change color whenever it gains and loses focus. And we're also going to take care of removing preferences, cleaning up after ourselves. So this all happens again in our JavaScript file, so I'm going to open that up here in Xcode.

So the first thing I'm going to do is register for some events. And again, I've wrapped these inside of an if window.widget. That just makes sure that we're running within Dashboard, that we're not running in a browser like Safari. And we're registering for three events. First off, widget on remove.

This is called when the Widget is removed from the Dashboard. And we're going to provide a function called remove to do that. We're going to handle window on focus. So this is when the Widget gets key focus. We're going to do something different there, and we're going to provide the focused function for that. And then similarly, we're going to use window on blur, which we'll call a function called blur.

And blur will-- blurred, sorry-- will change the Widget text color to a different color just to show that the Widget doesn't have focus anymore. The first one of these is removed. And like I said before, it calls set preference for key, sets the preference to be null. And it's already using our make key function, so it'll only do this per instance, not globally.

Our focused function sets the world text color to white, so that means when the Widget has focus, it's going to be white. And we have it that when the Widget loses focus, when it's blurred, it changes the text color to gray. And that's it. So, first I'm going to show you the focus changes and I'm going to show you the effect on preferences.

So I have my Widget here. I'm just going to bring out another Widget. I'm going to bring out the Calculator one. And you'll notice that the moment the Calculator lights up, my Widget goes gray. That's because I've signed up for the onFocus and onBlur events. So we can just kind of do dueling Widgets here back and forth.

So that's not that special, but let me go into the user's library preferences folder. And I'm going to show you that, sure enough, I've polluted my preferences folder here, and I have this plist here, even though I haven't saved a preference for my current instance of Widget. So I'm going to delete this.

I'm going to bring up the Widget, flip it over to the back, and change its preference. And the moment that happened, you'll notice right here in the background that all of a sudden there's now a plist for my Widget. And that's where the preference has been saved. So I click on Done, and sure enough, it's there. But if I close this Widget, the moment I close it, you'll notice that that preference disappeared. This is just good housekeeping. You don't want to pollute people's hard drive. Make sure you clean up your preferences.

Okay, can I go back to slides, please? So now that you've made this phenomenal Widget, and you have the foundations for making a great Widget, how are you going to get it out there? Well, we recommend that you archive it using the finder's archive command. From there, you put it on your server, and then you submit it to our download site at apple.com/downloads/macos10dashboard.

We have over 400 Widgets up there made by you guys, so thanks very much. You guys have already been a pretty active little community, and we're looking forward to seeing that grow and become just a bigger and bigger community. In fact, I'd like to switch demo right now and show you some of the great Widgets that people have made.

So one of the more popular Widgets that I've seen is the Business Week RSS Widget. And this one uses RSS and network access. We'll be talking about that more in our advanced session tomorrow. But you'll see that it just puts a whole bunch of headlines on here and things like that. Another really popular one which you might have seen is the Hulu Girl Widget. So we can have Homer on our Dashboard Hulu-ing. Not terribly useful, but kind of fun.

Another one which was out here is there's a WWDC Schedule Widget. So you can find out what's happening next. At 2:00, there's a .mac SDK session. You should visit that. I have a friend in .mac. It's good stuff. And you can find out all about the conference for that. And just to show you how easy it is to make one of these, over the weekend, in four hours, I made this Widget, which I call Mirror.

And what this does is it accesses an eyesight and puts yourself on the Dashboard. And it mirrors it. So that way, I can see if someone's looking over my shoulder. It's like the ultimate boss key. So that way, if I have games on my Dashboard or something like that, I can say, oh, shoot, someone's there and get rid of it.

Or let's say I'm getting ready to talk to Steve Jobs or somebody like that. I can make sure my hair is in good shape. So it's a handy little Widget. And as an incentive to show up to our lab this afternoon, if you come, I'll show you how I made that. I used Quartz Compositor and a couple other little things. And we'll show you how I did that there. So back to slides, please.

So today we've covered the basic Widget. The four things that go in every Widget are an Info.plist file, an HTML file, and the two images, the default and icon.png. From there, we covered some design tips which help you make a Widget that looks like it belongs on Dashboard and uses conventions that people are used to seeing.

Then we spent a good amount of time talking about localization and what it takes to localize your Widget strings and its design. Then we talked about preferences and Widget backs, how to add a back and how to access it and how to save preferences and retrieve them. And finally, we talked about some useful Widget events which help your Widget act like it belongs on the Dashboard.

For this, all the information I covered in the session, take a look at our documentation. It's available on the WWDC site. There's two books there right now. There's the Dashboard Programming Guide, which covers different topics and task-based information about adding things to widgets. And there's the Reference, which covers all the Infoplist keys and all the other little things that the widget object can do.

And there's a ton of other resources there, like sample code. There's great sample code on how to resize and stretch and add scrollers to your widget. These are things that are not necessarily widget specific. They're DHTML. But the sample code makes it really easy to do these things.

Your roadmap for the rest of this week, tomorrow at 2:00, I believe in the North Beach room, we're going to be doing advanced Dashboard Widgets. That's where we cover how to use command line utilities inside of a Widget, how to access the network through HTTP requests. We also cover Widget and WebKit plugins in there. So if you ever need to interact with Cocoa or anything to that effect, or need to draw special 3D graphics or anything like that, you'll want to use a WebKit plugin or a Widget plugin.

At the same time, unfortunately, there is Safari for Web Designers, which covers Canvas and a couple of other things. So if you're interested in more web design, we suggest you go to that session and then view our session when the sessions come online. Tomorrow morning is Advanced WebKit Development, and that is a great session about making a WebKit plugin and all of the associated in and outs with that.

We'll be covering it in an advanced session, but if you want to know all the nuts and bolts, we suggest you go to that session tomorrow morning. And then finally this afternoon at 3:30 in Applications Technology Lab 1 on Level 2, we have our Development Lab. So if you have any questions about Widget implementations, design, come talk to us. We'll have the Dashboard Engineers and WebKit Engineers there to give you advice.

If you have any questions, feel free to contact our evangelist. His name is Alan Samuel. His email address is [email protected]. And he'll be up here in a moment, too. And there's also a great Widget community starting up at lists.apple.com. The Dashboard Dev Mailing List is there for you to ask your questions. It's manned by Apple engineers and by community people, third-party developers, just making Widgets on their own.