Content and Media • 50:15
Dashboard provides quick and easy access to widgets-lightweight Web 2.0 applications-on Mac OS X. Join industry experts to explore how a few of the best widgets were created, from concept to deployment.
Speakers: Joshua Keay, David Keay, Chris Masto
Unlisted on Apple Developer site
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Welcome to session 609, Widget Design Techniques. And today's session will be led by three gentlemen from Monkey Business Labs, and those gentlemen are Josh Keay, David Keay, and Chris Masto. And with that I will hand it over to Josh.
( Applause )
Good morning. Like I said, Stephen said, my name is Joshua Keay, and I'm from Monkey Business Labs in New York City. And Apple invited us here today to give a presentation about widget design techniques. We've been doing widget development for about two years, and we learned a couple tricks during that period of time, and they said it would be good to share them, so here we are.
Let's go to the starting slide. Like I said, my name is Joshua Keay, I work with my brother, Dave Keay, and Chris Masto. And we are going to be covering the following things here today. We're going to start by this idea of what makes a good widget. You have to ask yourself when you're going to be doing these development things, is this a widget or an application? And it's a tough question. A lot of our stuff actually kind of like bridges that gap between the application and widget.
And we you know, have to make a lot of decisions about what features to include and what not to, so that's a big issue. We're going to give a intro into widget interface design. My background is as a designer, so I'm going to be able to share a little bit of insight into that. And then we're going to take it over to Dave, who's going to talk about doing widget development outside of Dashcode.
Been a lot of sessions here today talking about how you can use this new tool to your advantage, but you know, as we've learned, widgets are made using some basic coding techniques, so you can use the skills from web development to build these things by hand, and there's a lot you can learn from that. We're going to get into this idea of where to get data for your widget. Cause if you're going to build something, you actually need to have a really good data source.
There are a lot of different places you can get that data. You can get it off the web, and we're going to have Chris talk about that. You can also have a Cocoa plug-in. If you've got an existing application you can talk to it using a plug-in. Questionable technique, but we'll talk about that too. And lastly, you can have a self contained widget like stickies, and store the data locally.
After that we're going to get into some advanced widget interface design techniques, and talk about you know, different ways of using resizing if you want really good animated effects, along with some good CSS practices to make your coding process a little bit easier. And lastly, a question and answer. So let's get started. Brief history of the company. I'm the president of the company, and there's a pretty inauspicious beginning.
I was you know, working as a designer in New York, I was working on this book project. It was like two o'clock in the afternoon on a Tuesday. And you know that feeling where you just look around and you think like oh my God, I need a nap.
And I sat down, I was going to take that nap, and I realized I wasn't even tired. I was just sick of this stupid book project. So I started sketching. I think I'd been you know, reading on the web something about Dashboard widget development. I was thinking this is a pretty new technology, but nobody's doing really exciting stuff with it yet, cause this was back before Tiger even shipped. And I said okay, let's see what we can do with that.
So this here is the first shot of that sketchbook. On the left side you see all this weird you know, like book layout stuff, and the right side there's this long list different ideas. And you get a lot of bad concepts in there, but a couple good ones as well. TV Tracker was in there, like to do Tracker, all this other stuff. You can see little tiny like an alarm clock, and TV Tracker, actually tiny little thing right underneath the guy's leg. So that was pretty good. Now since this sketch we've shipped a number of widgets.
On the upper left we've got TV Tracker, which is pretty high profile, you may have played with it. It's a really simple way of finding out what's on television right now. To the right of that, Checkmarker, which is basically a to do list, you can just have a million different things that you're keeping track of. So it's a easy way of you know, keeping your mind on that.
Lower left we've got Picture Framer, which is a pretty simple way of experiencing your photographs. It's a little different from iPhoto. You can think of iPhoto as being like the photo album that you keep on your shelf, and Picture Framer is more like the shelf, the little photo that you put on your desk. You could put a picture of your dog, you could you know, plug-in a Flickr photo screen with like your favorite photos coming in.
Lower right you also may have seen Package Tracker, this is the latest version, Package Tracker 3. It integrates the tracking multiple packages within one widget. You just enter in the package number and it shows you where your package is right now. So it's a pretty interesting story. I mean we started you know, from almost nothing, just like a couple ideas. And we started building things, and as they say, if you build it they will come, and indeed they did.
This was a pretty big day for us. Apple put us on the front page of Apple.com, number one downloaded widget that day, and for a couple days thereafter. It's amazing the amount of publicity you can from something like that. So needless to say, our traffic went a little crazy. But it was a good day, kind of nice little photo for the scrapbook. So now without further ado, we're going to talk about making great widgets.
Now a couple questions for you guys. How many people here have made a widget before? If you'd just raise your hand. Okay, good. Now how many of you have gone to those sessions that we've had in the last couple days talking about Dashcode and other widget development techniques?
Okay, cool. So a decent mix. We're going to be getting into some slightly different techniques than the Dashcode stuff that they were talking about. We're more traditional developers, we started before Dashcode even shipped. So we do most of our stuff you know, by hand. We build the graphics in PhotoShop, we wire it together with code.
But you know, they're definitely good complementary skill sets to have. So we're going to talk about it just starting from scratch, you know, starting with an idea, and how to build it, and then if you've built widgets before, we're going to get into some more advanced techniques of resizing and animation.
So the question, what is a widget? Seems like a pretty simple question, right? But if you think about it, you know, applications are getting bigger and bigger, and more complicated. You know, GarageBand is a great app, but it's got a trillion little controls to it. You have to think that there are you know, applications that are well suited to this. You want to make a DVD, you want to you know, capture photos of your wedding, that's great. But sometimes there are simple little goals that you have, and widgets hopefully will take care of those goals. So first and foremost, widgets should be simple.
I like to think of them as physical objects. You know, basic functionality, you can almost mix and match your appliances. If one doesn't work you throw it out, you know, there's going to be some competitor who makes something that will track TV a little differently. You know, basic, basic levels of modulation. You know, you should be able to adjust things, but not too much.
If it's going to start taking over your life, if it's going to start taking over your screen, maybe you're not talking about a widget any more. But in the event that you are, you're going to need to figure out how to design a good widget interface. And there are some interesting considerations for that.
A lot of people think of widgets, and they think of the way that they look. And you know, I don't blame them. They're, that's definitely one of the most standing out qualities that they have. But there's a lot more to it than just that. You know, if you're going to make something really great, you have to think about it on all different levels, and beyond just the graphics. So we'd start you know, on paper like we saw before. But I really think the key to making good design decisions, like the iPod, you know, figuring out how to make something simple, is knowing what to leave out.
There are lots and lots of different choices. You know, feature requests come in every day, people want you to turn your application into something that you didn't originally conceive it as. Sometimes that's a good thing, more often than not you say hmm, maybe, you know. You put it on your list of things to do, but it's about staying focused, staying true to that original vision.
So I think you know, having those real confident design decisions, and saying you know, that's not what we're about. You know, we're about giving people something simple. We're a toaster, we're going to toast things. You know, there are appliances that people make that you know, toast and make coffee? That's okay, but that's more of an application, so.
We ask ourselves what is the customer's goal? And it's an interesting, tricky question to ask. Because you can say you know, on a really basic level the customer has a goal of like the weather widget, what is the weather? So you have to kind of like burrow down a little deeper than that.
Like it's, you know, I look at a lot of different dashboards, and people use the dashboard for different things, but it seems like everybody has this weather widget. And what's weird is there are people who use the weather widget in two different ways. Like the first level is like the basic one, you know, I have one weather widget open.
And it shows me you know, what the weather is right now in New York, the next five days of it, which is pretty cool. But there's a second level of things that people are curious about. They want to know what the weather is like in Florida right now, you know, or San Francisco.
So they open up multiple weather widgets, which is pretty neat. But you don't need to know what the weather's going to be like in Florida in five days, it's irrelevant. You know, you just are kind of curious right now so you can do side by side comparisons. So they created this really simple interface. You know, it actually doesn't have any buttons or anything. It does expand and collapse, and it will show you the next five days, or it will just show you the simple, straight forward, this is what the weather is right now in Florida.
So it's a pretty neat question. Our process for that, we start on paper, as you saw, then we wireframe, and then we render. So for a case in point I've got TV Tracker on screen right now. Kind of an interesting example. TV Tracker started, we were working with a developer who had actually built his own front end and the engine, and we said you know, this is a great engine, but we could probably upgrade it a little bit.
So this was TV Tracker you know, .01 All the functionality's there, you get the channels over here, you can see the different things on screen, it's got a nice little icon in the background to kind of remind you that it's the TV. But at the end of the day you look at it and you say good functionality, but you don't want to decorate your dashboard with it. So we started sketching, and there's a TV. It's a really obvious idea, but you know, widgets, you can kind of get away with stuff like that, whereas normal application, unless Apple's making you, you know, and it's QuickTime, you can't so much go crazy like that.
So we segued, and you get this wire frame. You know, once we started working out the details, you want to actually map out the pixels, and figure out like how much space do you need for that scroll bar, how much space do you need for everything else. So you kind of get a feel for that.
Really, really basic interface What's interesting about this is, you look at the next slide, this is TV Tracker 1, 2, and 3. And you know, the skins have changed, it's shinier now than it used to be, a little less chrome, but at the end of the day the interface is still exactly the same thing.
If you start with something really basic, really functional, you don't need to change all that much, you don't need to add a trillion features. So if you look at this, you know, the one on the upper left, you get this little diamond button, which was a convention that we borrowed from iCal. You know, we'd, you'd click that and it would center it on the current time and place you at like, click forward till tomorrow. You could just press that and suddenly you'd jump back.
The second version we said okay, let's play with that functionality a little bit. The little nudge arrows are right there next to the time, and there's a little gear menu there that you can use to change some settings. We added one little piece of functionality there. We realized that there was another way that people are using this.
You know, aside from just showing people what was on TV right now, which is very useful. If you're at work, it's three o'clock in the afternoon, you know, what's on TV? Oprah. You know, it's just like you don't really need to know what's on TV, but you might want to find out what's on TV tonight.
You might want to know what you can do, whether you should go to dinner or whether you should stay in. So we added this functionality called prime time mode, which allows you to kind of like alternate between that. You can flip it over, you can choose your own prime time in case you get home from work really late. But it's just a different way of thinking about how people use the widget.
TV Tracker 3 over here. Basically exactly the same thing, we did add a little bit of resizability, we changed the look up too. So that was TV Tracker's evolution in a nutshell, not too much change. So if you start with a good design, you can just run with it.
Next slide, how to look good. And this is you know, a real question. There are some good tools out there. You know, Apple actually ships some little glass buttons that you can use by default. You're not supposed to use aqua, at least on the front of the widget. You can use it on the back, but not on the front. So you could go into Photoshop every time and be like okay, it's going to be purple button with rounded corners and a drop shadow.
Or you could use this glass button, and that works pretty well. You could use Dashcode, which has some great objects built into it, you can you know, get away with some good style in there. You could use rounded rectangles and all that. The sessions the other day covered all that. Beyond that, there's this whole realm of trying to figure out what can you do, what can you do.
At the end of the day, good design is hard. You know, there's a lot of decisions. You can look at different things, you can follow Apple's precedent, which is always a good idea. But if you really want to take it to the next level, you know, I'm assuming a lot of you guys are developers, it's a developer conference. And this idea of division of labor, you know, that you're really good at one thing, it's hard to be good at everything.
So oftentimes we work with designers. I myself am a designer, I don't code, I'm pretty bad at even just like CSS. But I can occasionally make a nice graphic. So I work with developers. For you guys you know, I suggest this idea of hiring a designer. This slide actually should just say work with a designer, because you don't have to hire somebody, there's a lot of really talented people out there, they want to make great applications. You can go on the web, you can look at different sites. There's companies like IconFactory. You know, in the old days you needed a good icon. Rather than drawing your own icon, nowadays there's user interface companies.
There's this website called Programmer Meet Designer. You know, it actually helps connect to people like that, it's almost like a dating service for developers, so.
( Laughter )
It works pretty well, and it's just a really great paradigm, you know, working in teams, like being able to bounce ideas off somebody changes the way that you develop, and you see the program in different ways. Part of the problem, you know, you're under the hood all the time, and just seeing how programs work, oftentimes the developer really you know, is too intimate with this stuff. The end user doesn't necessarily care how the cache is being cleaned up.
So you know, you bring in the outside guy, he'll say you know, we don't need that cache cleanup button, we're just going to kind of like gloss over that feature, and it works just fine. But it's a different way of getting a new perspective on your stuff. So when possible, hire a designer. One of the weird things, and there's a lot of really young designers out there, some twenty five year old guys, you know, some eighteen year old guys, and they make amazing, amazing stuff.
They're not necessarily looking for a lot of money, you know, they don't have a wife and kids to support. So you can really get some great design talent at not a lot of money, and really make an amazing app. So it's a good paradigm for development. In a nutshell, our design process, we always start with a good idea. We have to ask ourselves what does the customer want?
There's this next question that you know, we have to address, is it actually buildable? Like we get a lot of feature requests for things, and they're just impossible. You know, like we'd love to have live streaming TV in TV Tracker, it's not going to happen. You know, the technology's not there.
So the reality is you have to find something that's sustainable, find a really good data source, make sure it's going to be able to last. We sketch out our ideas, we iterate, you need to just keep making a million sketches. Like we showed one TV Tracker sketch up there because they vetoed me showing a hundred.
But you know, I drew that damn thing over and over and over. Every time it gets a little bit better. You know, you might just streamline it, you might just understand it better. But when you're developing the stuff, it's just very, very powerful to just keep working on it.
Beyond that, we do discuss a spec. You know, it's pretty informal, we don't have any printed document or anything like that. It's email, just bouncing around ideas, you know, bouncing features off of existing users is always a good idea. Just try to keep the scope about right. You know, it's important to introduce new features, but not get carried away, so that's what a spec is for. We start building and making our rendered graphics simultaneously. Oftentimes you know, you realize halfway through that this button's not going to work, it's too big, it's too small, whatever it is.
If you have you know, one design and then you pass it off to a developer, or vice versa, if you read all your code and then hired a designer to come in, it's going to be a very strange process, and you're going to actually waste a lot of time. So this idea that you know, you're going to discuss it all along works very well. Lastly we say iterate. You know, you just keep churning. You know, TV Tracker 3 is pretty good, the other ones?
You know, it took time to get there. But you just keep working and keep shipping. If we decided to ship TV Tracker 3 as TV Tracker 1, you know, somebody else would have done it. So just keep churning, keep trying, and it's a lot of fun. So, okay. Now the fun graphicky stuff is over, and we're going to actually get into the real meat of the things, which is how to make a widget.
My brother Dave is going to give a little bit of an introduction on the ways to make a widget without Dashcode, you know, all the ingredients you need, how to mix it together. And I think it's going to be pretty informative, because you know, it's amazing stuff, and it's not too hard. So without further ado, Dave.
Thanks Josh.
( Applause )
I'm Dave. I develop with Monkey Business Labs, and I'm just going to go over the basics of making your own widget without using Dashcode. So first, development tools. I say without using Dashcode, but I actually do use Dashcode. It has a great debugger, it has a great evaluator, so it is very handy for solving problems in your widgets and things like that.
But generally I just use TextMate for all my development. It's pretty much a really good text editor, they won an ADA last year. So I'm sure you've heard of it, but if you haven't, check it out. And then behind the scenes in widgets, it's basically just web technologies that you have to learn. So I'm going to cover everything that happens inside of a really basic widget, and let's look at that.
So widgets are really just folders. There's pretty much five things you have to have in there. There's your info.plist file, that's the widget properties, HTML, your document tree, you just define everything that's on the front and back of your widget, your CSS, that's where you control the layout and styles in your widget, JavaScript is everything dynamic, if you want something cool to happen, that's where you write it, and then images. I think you know what they do, but there's a couple things you have to know about them.
So I'm going to cover a really simple hello world widget. When you click on the widget, it changes and says goodbye world. So first info.plist. It's just key value pairs, it's a list of properties, a lot of Mac applications use them. There are really only three things you have to specify here.
So there's the main HTML file, web pages have index.HTML, widgets don't really have that default HTML file, so you specify that. The CFBundleIdentifier, that's the name that the OS uses to refer to your widget. It's in reverse domain format, so something like com.monkeybusinesslabs.widget.TVtracker. And then there's the CFBundleDisplayName, that's what you see in the finder, you see in the tray in the dashboard, and things like that.
You can also, there are optional keys in this info.plist file, so there's the allow full access, allow network access. And you can control things like writing files, reading things from the network, and sending things out. And you can control the size of your widget, you can see that I control the width and height there, and I have that allow multiple instances key, which you can use to let users run multiple instances of the widget. In your HTML you can import your CSS and JavaScript files, you see them doing that with the import tag and the script tag.
And then underneath that there's the body tag, and that's where you define all those elements in your HTML. So I have one image, which is that background image, default.png, and a span, which is just a container which holds some text. It says hello world. I set IDs for both of those, so I can refer to them in the CSS and JavaScript.
And the only other thing you have to notice is this onClick event that I have in the body tag, and that's a JavaScript function that you'll see in a minute. In the CSS there's just two items. There were two elements in the HTML, so there's that background image. I set the position to be in the upper left corner of the widget.
CSS uses a coordinate system that starts in the upper left at zero zero and moves right or down, depending on those two values, or you can specify where the image would be from the right and bottom of the widget too. And then I do the same for that text. I have it forty one pixels from the top, thirty two from the left. And I set the font, and set it as bold, and white text, and things like that.
In the JavaScript there's that changeText function that I mentioned earlier in the HTML. You can see I used this getElementById method to look at that span, hello text, and see what's going on in it. If it says hello text, I change it to goodbye, or if it says hello world I change it to goodbye world, otherwise I change it back. And I'm also changing the location of that text by that style.top property.
So once you make those files, you just put all those in a folder. There are a couple images you need that I mentioned before. You need that default.png, and that's the image people see when they first open your widget and it's loading. And you need icon.png, which is just the icon that's in the tray at the bottom of the widget. Rename that folder to something.wdgt, and it's a widget.
So if you actually want to go make your own widget, you probably want to have some resources. I use the Apple widget documentation which is at developer.apple.com/macosx/dashboard.html There's the Safari CSS documentation. Widgets just use WebKit to render everything, so if you just use a Safari CSS doc then that'll work out pretty much correctly. And for JavaScript, O'Reilly's JavaScript book is great. Chris and I both use it, so check it out. And now Chris is going to talk about getting some data for your widgets so you can say things other than hello world.
( Applause )
Thanks Dave.
My name's Chris Masto, I am also a widget developer. And one thing that a lot of widgets do is they retrieve data from the internet, they process it a little bit, they display it in a simplified form. The standard way to do that is XMLHTTPRequest. So - ( Laughter ) We'll talk a little bit about that too. We'll talk a little bit about the basics of how XMLHTTPRequest works.
It's fairly straight forward. I'm going to start out here with just one little quick tip, the way I like to do things. Most widgets in my experience, they only retrieve one source of data, and they retrieve it again when a timer goes off, or a user clicks a refresh button or something like that.
So what I like to do is I keep my request object in a global variable, and I just abort any pending request before I do anything else. This is just in case you know, the user's sitting there slamming on the refresh button, but it actually takes a minute to pull the data down. To create your XMLHTTPRequest you just use a standard JavaScript new syntax to get the request object.
And then you're going to set up a couple of properties to make it work. The first one is the onreadystatechange handler. This is a function which we will write, called stateChanged in this example, and this is called when there, as the request goes through its different request cycle process. We then call the open method, which takes three parameters. The first is the, whether the request is a get or a post.
The second is the URL of the data that we're actually getting in this case. And the third is flagged for whether the request is synchronous or asynchronous. It's very important, especially in Leopard as you may have heard already, where all of the widgets in the dashboard run in a single process, to do your request in an asynchronous manner.
If you do a synchronous XMLHTTPRequest, say it takes a few seconds to run, all of the widgets in the dashboard will pause while it's waiting to get your data, and that's obviously not an optimal user experience. So the recommendation is to definitely always set this flag to true, always do asynchronous requests. Then we set a cache-control header here, because we want to make sure that we're always getting fresh data. This also helps to avoid a slight problem, which I'll talk about in a minute.
And then finally we simply call send, that fires off the request asynchronously, and your onreadystatechange handler will be called back as the request processes. There are several properties of the XMLHTTPRequest object that you can examine. There's the readyState, there's a status, and there's the actual data itself. So let's start by talking about the readyState property.
Okay. This is a value which starts out at zero initially, and as the request goes through the different phases of the request cycle it advances. It eventually gets to four, which is the loaded state. Most widgets will ignore everything except the loaded state, because until that point you haven't actually retrieved any data. Well you haven't received all of your data, and there's nothing really to do with it. So let's look at a sample onreadystatechange handler.
So here's our stateChanged function from earlier, and as you can see, as I was just saying, it waits for the readyState to get to four, which means that the data has finally been loaded. It checks the status of the request. If it was successful then we call a processing function, and if it was unsuccessful then we handle the error however we're going to handle it.
I want to focus in a little bit as a tip here on this HTTP status value. You might expect that what you're looking for is the two hundred okay status, and that's true in most cases. But this is where XMLHTTPRequest is badly named, because it's not just for HTTP. It takes a URL.
So you can for example, and I've quite frequently used a file URL to get data from the file system, or you can actually get data that you've bundled inside of your widget. You have to set a flag in your info.plist to allow that for security reasons, but you'll find if you do that, then you'll get back a status value of zero.
It's also been the case, depending on which version of the OS you have, and the phase of the moon and the alignment of Jupiter, and things like that, sometimes if you get back a cache version of a previous request, the status might also be set to zero. So the takeaway tip here is basically just don't rely terribly, terribly much on the status.
So once you've gotten data back, you want to look at it, you want to do something with it. So the way I see it, there are basically three types of data that you might be requesting, excuse me. You might be getting back XML, an XML document, you might be getting back an HTML document, or you might be getting back none of the above, aka plain text.
In the even that you're requesting XML, you can look at the responseXML property, that contains a standard XML document, you can call dom methods on it, you can you know, use it as an XML document, it's fairly straight forward. In the event you're retrieving say some plain text, or as we saw in an earlier session maybe a csv file, you can look at the responseText property, and that simply contains the body of the response, its text string.
The nice thing about this is that you can use this even if you're retrieving XML data as well. So it's kind of a view source function, you know, especially if you're in Dashcode. You can pop into the evaluator, you can print out response text, you can see what that XML document looks like. It might help you to know how you need to parse it.
There is however, the third possibility. Let's say you're just requesting a web page, and you want to look at that data that's on the web page. Well very few HTML pages out there are well formed, valid XML. And if they're not well formed, valid XML, response XML is just going to be null, you can't actually use it. And you could look at response text, but of course that's just a string. So then you'd start parsing it with regular expressions, and you know, nested tags are really hard to read with parts of regular expressions.
Ideally, at least ideally for me, I'd like to have an HTML document that I could you know, I could walk the dom tree on and I could process it that way. So what we want is to turn that text that we've gotten back into an actual HTML document. Fortunately there is createHTMLDocument, which will do that for you. So let's take a quick look at how that works.
Fairly straight forward, there are only a few steps. First thing is that you have to actually dig up the createHTMLDocument function. It's a dom level two function, it lives in the dom implementation, which is, can be accessed from your document. So essentially there's some boiler plate code that you can use to get to that. It takes one parameter, which is a string that represents the title of the document that's going to be created.
CreateHTMLDocument creates a full HTML document, with a head and a title and a body, but we're basically about to throw that away. So we just pass in an empty string and deal with it that way. So here we call document doc.open, which says hey I'm going to write a new document.
We simply write out the document, which is the response text that we received, and then we close it. And then the underlying machinery will churn away and produce a nice HTML document object, which you can then call dom methods on, process it in whatever way is convenient to you. So there is how to use createHTMLDocument. And now Dave is going to talk about using Cocoa plug- ins as another way to get data into your widget.
( applause )
All right.
So I think a lot of you guys are probably Cocoa developers, so what Chris just showed you is a great way to integrate widgets with your web applications, but it's not as good for some of your Cocoa apps. So let's say you have an application that uses core data to store your data, or you just have it stored some way that's not really accessible just in a flat file like that.
Then a Cocoa plug-in could really work well there. Or if you wanted your widget to do something when the user gets email, then you probably aren't going to really detect that from JavaScript, but you might be able to with a plug-in. So you can get to data, you can get to different events.
So for those reasons you think Cocoa plug-ins, it sounds great, but there are some problems. Just like Chris said, once Leopard comes out, all the widgets are going to run in their own process. But once you have a widget plug-in, you're going to have, or your widget will branch off in its own process.
So not only do you have the overhead of a widget plug-in, there's all this extra overhead because it's running in its own process. So we try to avoid them for that reason. And also you might be tempted to actually make your widgets interface in a plug-in, but Apple wants us to just stick to JavaScript and CSS and HTML. So just use Cocoa plug-ins to get data and events.
So just getting right down to how to make your own plug-in, it's really simple. You create it as a Cocoa Bundle in Xcode. You need to use a special initialiser, this initWithWebView method. And that takes a web view, but that just gets called by the dashboard automatically, so you don't have to do anything else with it. Then you have to expose your class to JavaScript, you do that with this WebScriptObjectAvailable method. You see this string demo plug-in that I have here, you could use whatever you want there, and then that's how you're going to refer to your plug-in in JavaScript.
Then you create methods in your Cocoa plug-in of course. So here I just have one that returns a string, you're obviously going to do something much more interesting, but this is a good for a demo. And then any selectors that you make you can actually refer to in JavaScript.
But if you have any characters like colons, underscores, or dollar signs in the selector name, then it could get weird. So I think colons actually turn into underscores when you refer them in JavaScript, underscores become dollar sign underscores, and dollar signs become two dollar signs. So that's where a webScriptNameForSelector comes in. In this you, it's a method which takes a selector and actually returns a string which you can use in JavaScript to refer to it.
So if I had you know, a bunch of dollar signs in that name, then I could just return a really simple string, and use that in JavaScript. There is the equivalent web script name for key which you can use. It works the same way, so you can access all your keys in JavaScript as well.
Then going back to the info.plist file that I talked about before, you just include something, or include this key for the plug-in there. And when you launch your widget, then the dashboard is going to call that initialization method, and you can refer to that plug-in just with that string you wrote, demoPlugin. So here you see this JavaScript, I have this variable, hello text. It's going to get that string, hello world, from my plug-in.
And in your Cocoa you can return NSStrings, NSArrays, NSNumbers, and those all just get converted to data structures that JavaScript can understand automatically, so you don't have to bother with any weird conversion. And it goes the other way as well, JavaScript arrays, numbers, and strings just get transferred, or converted to things which Cocoa can understand.
So that's really it for Cocoa plug-ins. They can get as complicated as you want them to be, but I didn't want to go through an actual data access thing, cause that's not that relevant. So now let's look at what we can actually do to store that data inside of our widgets using preferences.
So widget preferences can be used just for simple things, like the stickies widget remember what color this, the widget is when you restart your computer. So simple settings like that. You could also store more data, like if you grabbed a little document from the web, and you just wanted to cache it so that next time they used the widget, if it's not online, they can still see that document. I think weather does something like that.
And you can also use it for multiple instances. So if you set up your preferences right, then you can avoid overwriting all the data from one preference. So first let's just look at the actual technical parts of it. Reading and writing is really simple. There are two methods you're going to use, widget.setPreferenceForKey, that takes the preference and key. And an example is just setPreferenceForKey red and color. Then later if you call widget.preferenceForKey with color, you would get red. You can store strings in there. I'm actually not sure of the upper limit on the size, but you can store a good amount of data in there.
Then that's it for just storing preferences in all of the instances of your widget. So a preference stored that way gets shared between all the instances of your widget. If you set the color in one, read the color in another, it's going to be the same thing. So that could be a problem if you have two instances that overwrite some data, so that's where widget identifiers come in.
The widget identifier is just a unique string associated with each widget instance, and you refer to it as widget.identifier. You don't lose it when you restart your computer, it only gets lost when you actually close the widget. So it's, you can just use that to store preferences that are unique to that instance, and here are two methods that I use to do that. You can see that I just send a preference and key just like I did before, but then I call that setPreferenceForKey, and I use the widget identifier along with the key that I sent. So going on. You actually, or sorry.
When you're using a few instances like that, then you're going to want to actually use JavaScript events to figure out when you need to read data and when you should save it. And there's the widget.onshow and widget.onfocus events. On how fires whenever someone opens the dashboard, and that's going to fire in all of your instances of your widget at once. On focus happens when you actually click on a widget. So here's how you use them, you just say widget.onshow, and specify a function, just like Chris specified that callback function for XMLHTTPRequest before.
And you know, that's basically when you'd want to read your preferences, if you think another instance would have changed them. The last thing you need to do with multiple instance preferences is actually removing them. When you close your widget, this widget.onremove function gets called. And in that you want to set all the preferences that you would use there to know. That's because that instance of the widget is the only thing which can actually access that widget identifier, so if you don't remove them here, they'll just stay in that preference file forever.
It will get a little bit bigger every time someone launches one of your widgets, and that's not a really good thing. So that covers it for widget preferences. So we're going to leave data and get into interface stuff. And I'm going to start out by talking about resizing widgets.
Josh talked about TV Tracker before, and that's a really good example of how one of our widgets went from being a static size to resizing. The initial version showed one hour of data, and everyone wrote to us and said hey we love your widget, but it's way too small, I can't see anything.
So we said all right, we'll make it show an hour and a half, the next amount of data you can use, and everyone said it was too big. So we said you guys are kind of crazy, so we'll just make it resizable. And that's TV Tracker 3, you can click in the corner and drag and resize it.
So I'll show you how we did that, but first let's talk about a few different kinds of resizing. Josh talked about the weather widget, you click on the top of it and it just grows a little bit with a nice animation. So there's only two states, it's really simple. The stocks widget is a little more complicated than that. If you're looking at two stocks it doesn't have to be very tall, but once you have four stocks like this it has to be a bit taller.
And then TV Tracker, this resizing is really just controlled by the user. We knew there was too much data to show at once, so we said we can't do it, we'll let them decide how much they want to see. I'm actually going to show you how we did that, and you probably won't need to do it in your widgets, but if you can do that, you can do all the other kinds. So let's look at that.
First, to make a widget resize you need some flexible images. The first TV Tracker, it just had one static image as the background. It worked fine at that size, but if you stretched it, it would have looked really bad. Then you need something to trigger the resizing, maybe it's the user clicking on the top of the widget like in weather, maybe it's the data that you want to display changing, like in stocks. Or it could be that click and drag that we have in TV Tracker. And then you need to actually write that JavaScript to make things happen. So here's TV Tracker 1 and 3.
That TV Tracker 3, it's not one background image, it's actually nine. So the four that are in the corners are always static, and they don't change size. But the five of them that are in the middle, in the column and row there, actually just stretch to fit whatever size the widget is. So this is what that looks like, kind of exploded so you can see the nine images. And here's the HTML and CSS that we're using for that.
So the upper left image, grid one, well you can think of this almost as if the images are numbered like the numbers on a phone, so the upper left is one, and the lower right is nine. So grid one is that first image in the upper left. You can see in the CSS I just have that position to be zero zero, so it's in the upper left all the time.
And I specify that width and height there, which is just the resolution of the image. Then we do something weird, which is this grid2Holder. So we do that because you can't just have an image stretch between two points, and continually stretch when you resize the widget. When you change the size of the widget, an image that's stretched like that is just going to stay the same size.
But you can have a div, which is really just like an element which holds other elements in the HTML, and that will resize properly. So here you see that I have this positioned absolutely as well. It's at the top of the widget, so that's zero. Its left value is sixty eight pixels in, because that's the size of that upper left image, and the right is sixteen, or sixty nine pixels from the right, which is the resolution of that image in the upper right. So when I resize the widget, this is going to continue to stretch from that left side to the right side of the widget.
Grid two is inside of that, which is the actual image. And you can see that I have that specified to be always zero pixels from the left, top, bottom and right of that div, and it's width is a hundred percent. If I didn't have it inside of that div, which is smaller than the widget, then it would stretch over the whole thing and cover those images one and three. So now that's really it for the CSS and HTML, so we have to check out the JavaScript for this.
We're, we use mouse input from the user, so that uses three events, mouseDown, mouseMove, and mouseUp. So in our HTML we added this dragThumb, which is a nice little image in the lower right. And we have the onMouseDown event registered there, and that calls a method that we wrote called resizeDown.
So looking at resize down, the first thing I do is just register those other two events, mouseMove and mouseUp. We don't care about those events until now because the user can do whatever they want with the mouse until they're resizing. So we register those here, two more functions that we wrote. And then we just set a variable, this growboxInset, and that's the distance that the user clicked and dragged on the widget from the lower right corner.
You need to know that if you want to figure out where the new value for the lower right corner should be, so we're going to use that later. And in that you can see we use this event.x and event.y, and that's just the values of where the user clicked. The event.stopPropagation and preventDefault are there just to make sure that any other events that could have happened when they clicked the mouse aren't happening, we just want it to resize.
In the mouseMove event we calculate the new value for the lower right corner, that's going to be the event values again where the mouse actually is, plus that distance that they clicked from the lower right. Then we call window.resizeTo, that's something that Apple gives us in the dashboard, and that resizes the widget.
If you had set values in your info.plist file for the width and height, this changes what those are. Once again, you need to stop the event, and then when they let go of the mouse, we just need to unregister those two events, and stop this event as well, and that does it.
There are a few more things that you could add in that JavaScript of course. So if you had a lot of elements in your widget which have to resize when they're clicking and dragging, it might actually slow down the machine a lot, so in that mouseDown event you might hide a lot of elements. If you did that then in mouseUp you'd have to show them again.
Or if you wanted more live feedback of something which doesn't automatically resize, you would do that in mouseMove, and you could just modify all the styles of your elements the same way that we changed the text and location of that hello world text in that first example. So you've seen how we did it in TV Tracker with clicking and dragging, but you say that's not how anything works in the weather widget and in stocks.
So in the weather widget you could use something called AppleAnimator. And that's just a way to make one value, like the height that you want your widget to be change to a different value over time in a set number of intervals that you choose. So that's in all, or that's on all your computers in system library, widget resources, Apple classes. And the way you would use that is just when you call the resize, you do anything that you would have done in that mouseDown event.
When the AppleAnimator actually fires off its callback function that you register, then you do what you would have done in the mouseMove event, and when the animation is finished, exactly what you would have done in mouseUp. So it's really the same as resizing the way that we do it, it's just handled automatically instead of having the user use the mouse.
And stocks is even simpler than that. They don't animate or anything, it's just when you're on the back of the widget you change what data you're looking at, and the widget gets bigger. So that's really it for those three kinds of resizing, they're all pretty much the same, just the input is different. And now Chris is going to come back and tell you about some good CSS practices that he uses. And that's it.
( applause )
Thank you Dave. So I am going to talk about using CSS Introspection to create dynamic documents without using evil magic constants. So let's break that into pieces and see if it actually makes any sense at all. Let's say for example, you have a widget which needs to change size depending on how much data it needs to display. Here is an example of one. In this state it has four rows, you can add another row to it, and it will expand to show that extra information.
Well how does this work? Since widgets are JavaScript, you can actually just take a peek and see how the author implemented it. If you do that, you'll find a function, like this calculateWidgetHeight for example. You see in here that basically what it's doing is it's adding up the heights of the various elements of this widget's graphic design.
So each of these numbers here is some piece of that total display, and this figures out what the new height is going to have to be set to. Now there are a couple other places where numbers are used here like this. There's a, the size of the back for example. We see some other sizes here and here, and so on.
Now this is fine, this is the standard method. We all use it, it's in several things I've written. But we might want to think about why that could be difficult. Every time your graphic designer goes out and changes your graphics, you have to go back to the code, and you have to copy the numbers in, and you have to remember where you put them. So it can get a little tedious over time. Is there a better way that we can do this? Or is there an alternative method where we don't have to do as much copying of constants around?
Let's look at a CSS snippet from this particular widget. Here we can see that the height of this particular row is actually specified there. If we go back and look at the code, we can see that the height of twenty five is already in the CSS. Similarly, if we look at another piece of the CSS here, we can see there's a height of thirty four, it's already in there, and another one, and so on.
They're all in there, they're already in the CSS. Well it turns out that we can use a function called getComputedStyle, which will allow, it's a very simple function. It takes an element, and it takes a string pseudo element, which is one of those colon before colon first line things that you use in really fancy CSS, and you never use in widgets. So we'll just let that be null for our examples for these purposes.
And what it does is it returns a CSSStyleDeclaration back, with all of the CSS values computed, filled out, filled in, their absolute values. You can look at them and see actually how big things are. It's kind of like a measuring device for your current layout. Now a very important point of this is that it's measuring the current computed style.
It's not telling you what it says in the CSS file itself, it's telling you what's in there on the screen or in the widget. This means that it's handy, because you can leave things unspecified, you can let WebKit lay it out for you, and then you can find out how big it turned out to be.
But it's somewhat of a double edged sword, because if you've changed that, if you've animated something, resized it, or perhaps made something invisible, you won't really be able to get the you know, how big does it want to be, you'll just be able to find out how big something currently is. I think the biggest fallout from this is that if you've done the common technique of setting the display property in something CSS to none to get rid of it, it's no longer there, so getComputedStyle will just tell you it's zero by zero.
There's another way of working around this in most cases. You can use the visibility property, set it to hidden. The difference really is that visibility: hidden says just don't draw this. But it still appears in the layout, it still takes up space. If you say display:none, it's kind of like saying well, temporarily pretend this doesn't exist. So how can we use getComputedStyle to rework some of this code in this example? Let's just focus on the top here, and let's say that we want to replace these constants with a function that will determine their heights dynamically.
So we'll write a function called getElementHeight, we'll pass in the HTML ID of the element we want to get the height, of and we'll use get computed style to figure out what it is. So the getElementHeight function would look something like this, fairly straight forward, fairly short. First thing we do is we just use our old friend, the standard dom getElementByID, we dig up that element. We call getComputedStyle on it, and we're passing null in for the, for that pseudo element parameter. Once we've got the style back, we look at its height, and we return it, simple as that.
Now there's a little JavaScript tip here that I just want to throw in quickly. What you get back from getComputedStyle are CSS styles. So that height will be something like thirty seven PX. We're only interested in the integer of thirty seven, so we just use parseInt to get that.
However, if you leave off that second parameter to parsent, which is the base that you're parsing, and your string happens to start with zero or zero X, then it will be interpreted as octal, or hexadecimal, and it's often not what you want. So I've been guilty of that a lot myself. You leave it off and then suddenly you get a strange value. So always supply the base. And there is getComputedStyle.
So in this session here we talked, first Josh explained a little bit about what makes a good widget, how to do good design, how you might want to hire a designer to work with. We went through the basics of what pieces go into making a widget, discussed some of the different ways that you can retrieve data for your widget, whether it's over the internet, from a Cocoa plug-in, or just storing it in preferences.
And we discussed a couple of fancy dancy advanced techniques for resizing, and for dynamically computing the current style of your widget. And here is the how to get more information.
- So if you guys have any questions, or for more information you can contact the evangelist for WebKit, who's Mark Malone, or the 2D and 3D graphics evangelist, Allan Schaffer.