Application • 47:57
Apple built the Safari web rendering engine using the APIs of Web Kit. You can do the same in your application. In this session, Carbon and Cocoa developers will learn how to use the Web Kit to add powerful HTML presentation, JavaScript processing, and accelerated Internet capabilities to their applications.
Speaker: Richard Williamson
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning. This is session 414. And we'll get the slides in a second. My name is Richard Williamson, and this is Taking Advantage of New Web Kit Features. And I'm going to be telling you about some of the new features in Web Kit that we're introducing this year.
Just over a year ago now, we introduced Safari, and it's a great web browser. Millions of people are using it, and we're pretty proud of it. But the technology behind Safari is not just about web browsing. Already last year, some of the system applications in OS X started to take advantage of the technology behind Safari. And this year, Mail.app is going one step further. Not only will it use the Safari technology to display content, you'll be able to compose rich HTML messages using the Safari technology. We introduced one new app this year, the Calculator. Well, actually, a bunch of apps.
The technology behind Dashboard is the same technology behind Safari. And that technology is Web Kit. So in this session, I'm going to talk about the new features in Web Kit, but also I'm going to talk about what it means to use this technology not just for browsing, but going beyond browsing.
So, quick recap. What is Web Kit? It's a framework that's shipped in OS X, and it allows you to embed web content. Obviously, HTML, images, plug-in data, and it's highly scriptable. And this last topic is something which I'm going to focus on quite a bit in this session.
Behind me is a high-level architecture diagram of Web Kit. The Web Kit framework actually encapsulates the open source KHTML and KJS engines. And it's layered on top of the App Kit for UI controls, core graphics for rendering, and foundation for NSURL loading. And layered above the Web Kit framework is a C API to allow Carbon applications to access this technology. Now, even though it's layered on an open source engine, we encourage you to use the Web Kit API. You can look at the source code in the implementation, but please implement against the Web Kit API.
Okay, what's new? Well, an awful lot is new this year, and I'm going to cover some new API areas and also some new features in Web Kit. One new area we've added this year is support for what we call web archives. And a web archive is simply a way of representing both a page and its associated resources in one package.
Lots of new scripting support. And some of you may have seen some of this already in some of the dashboard sessions. Dashboard relies heavily on the new scripting technology that we have. Editing APIs. This is a big new area in Web Kit, and the APIs we're introducing are pretty comprehensive here to support the DOM, the Document Object Model in Objective-C, and also to support editing in a very Cocoa-style fashion. There's a session on Friday that will drill down into quite some depth in the editing APIs. It was misnamed in the brochure, and the real name should be Web Kit HTML Editing APIs. That's Friday at 10:30, and I strongly encourage you to go attend that session.
Dragging support. We had a lot of requests from you all to override the default dragging behavior of the WebView. You can now do that with the new APIs. Plugins. We've always supported plugins, Netscape-style plugins in Web Kit. But it's always been difficult to write a plugin for Web Kit.
So this year, we're including everything necessary to write a Netscape plugin in the OS. We've also introduced a new form of plugins, Cocoa plugins, that make it vastly simpler to add a plugin for Web Kit. There's a great session later this afternoon, session 413, that will go into some detail about both the Cocoa plugin API and the Netscape API.
and there's many, many other tweaks. We don't really have enough time to describe them all, but a lot of these tweaks were inspired by you all. There's some activity on the Web Kit SDK mailing list, and we went through that list and we gleaned a lot of information, and there's also a lot of features that you all have requested. We pay attention, so if there's a feature you'd like, please let us know.
So in terms of these new APIs, I'm going to focus on just two areas in this presentation, web archives and scripting. But before I drill down into that, I'm going to describe some of the new features that we've added to Web Kit. We now have comprehensive support for CSS 2.1, and we also have support for a lot of advanced new CSS 3 features.
I'm still waking up. There was a session yesterday, Dave Haya gave, 419, that went into a lot of detail about some of the new CSS features that we have. Even though that session has passed, I encourage you to go and review the slides and the DVD material for that presentation.
So I talked about dragging support at the Web Kit framework level. We also added dragging support at the JavaScript level. What this means is that your pages can interact with drag operations. So you can actually have an element participate, an element on a web page participate in a drag operation. This is a very nice feature. And the implementation of this follows the Microsoft dragging support standards, the factor standards. Live Connect. Safari 1.2 introduced Live Connect. That's the ability to interact between Java and JavaScript, bridging those two languages.
We also introduced XML HTTP support in Safari 1.2. This is the ability to initiate an HTTP request from JavaScript. Very useful, for example, if you want to dynamically add stock data from a web service to a web page. Many other applications. There was a great session that's also been given already on Monday, but I refer you to that in the DVD material that discussed these last three things, dragging support, live connect, and XML HTTP request.
And finally, many, many compatibility fixes. This isn't a very glamorous topic, but we spend a lot of time trying to make Web Kit work with the existing web content. And believe me, there's a lot of crazy content out there. So it takes a lot of effort sometimes to figure out what the problem is before you even fix it.
So in addition to pushing supported in Safari, we've added some features that go beyond the standards. Many of these features were inspired by Safari RSS and Dashboard. They're things that we wanted to do that you couldn't quite do with existing standards. One of those things is image compositing. What this will allow you to do is on the image tag on an HTML page, specify a composite operator.
And for those of you that don't know what composite operators are, it's the ability to take advantage of the alpha channel in an image when you blip that image into a surface. So all of the power of the composite operations that are provided by CG core graphics are now available to you in web pages.
We added a few CSS extensions to support the Flexbox model. This is used in Safari RSS. There are also a few other things that we've added that you can use in your web pages. A couple of new input elements that are also in Safari RSS, the slider widget, so you can actually have a slider that will provide range values, and a search field, both very nice features.
And finally, the Canvas element. So we found that we wanted to dynamically draw into content on a web page. You can't do that with anything that's out there that's a standard. So we introduced this new element that will support that. After talking about the new API in depth, I'm going to drill down some more on the Canvas element and show you some examples of that. So let's get into it and talk about the Web Archive API.
Well, a web page is a lot more than just the initial HTML page that you use to lay out your document. It's also the associated resource data, images, JavaScript, and CSS. So Web Archive is all of those things collectively put together. We represent resources by a new class, Web Resource, and the entire thing by a new class, the Web Archive. The on-disk format of a web archive is a single file, a single binary file, which makes it very convenient to move these things around.
There's also an in-memory format, a pasteboard format, that is used to facilitate editing. So, for example, if I have two web views and I make a selection in one web view and drag that to another web view that's editable, the pasteboard representation will be a web archive. So this facilitates editing.
So you typically don't create a web archive directly. Instead, you have an existing web page that you create a web archive from, or alternatively, an existing selection that you create a web archive from. Once you have a web archive, it's really simple to take that archive and load it into a web view or use it to replace a current selection.
So what does the code look like to do this? Very straightforward. For those of you that are familiar with Web Kit, this will look very familiar. We get the mainframe of the WebView, we get the data source associated with the frame, and then we ask it to create a web archive. That's all it takes.
created a web archive from a selection. You get the selected DOM range, and you ask that. Give me a web archive. Now, that second method, that selected DOM range, is a new method that we're introducing as part of the DOM API. And again, I encourage you to attend Friday's session to learn more about that new DOM API.
So once I have a web archive, how do I get it into the page? The existing methods to get content into a web view look like this. Load request, that takes an NSURL request, and we'll go and retrieve the data and put it into the web view. Or load HTML string. This will actually take an HTML snippet and stick it into a web view.
The API looks like this, very much what you would expect to load a web archive. Load archive, and you pass in the archive. Alternatively, you can replace the current selection like this. Replace selection with archive. Okay, to really drive this point home, let me do a quick demo. So there's a new way of saving a page in Safari that takes advantage of the Web Archive feature. I can either save a page as raw source, which I'll do here. Go ahead and save that in my documents folder. All I can save is a web archive.
[Transcript missing]
Let's go ahead and load the Web Archive. Much more like what you would expect. So this is a great way -- This is a great way in your applications if you want to create content that can efficiently be packaged and loaded, you can create web archives and include them in your application bundles. Back to the slides, please.
Okay, how about some stuff that can really help you build a good widget? That's what we're going to talk about now. But first, a little bit of homework. There's always homework. I'm going to talk about scripting in quite some depth here. What do I mean by scripting? We've had JavaScript for a long time. We all know about JavaScript on web pages. What I'm going to focus on in this discussion is scripting in the context of binding JavaScript to native code. So that I can message from JavaScript into native code and in the other direction, message JavaScript from native code.
Specifically, what I mean by native code is a chunk of code like this. This is some Objective-C code that wants to call into JavaScript. It wants to invoke this function set image in JavaScript. Alternatively, I could have a hunk of JavaScript code like this that wants to call into an Objective-C instance. In this case, an address book object. I'm going to get some address book records.
So when we talk about scripting, it's really how do we bind native code to JavaScript in Objective-C. We've also introduced support for binding JavaScript to C. You don't have to implement in Objective-C. You could do this in C, too. And this is really useful for plugins, in particular, existing Netscape plugins that aren't implemented in Cocoa. I'm going to talk about the Objective-C to JavaScript bridging in this session. The plug-in session this afternoon is going to describe in more detail the C bindings to JavaScript.
First, let's talk about calling JavaScript from Objective-C. So JavaScript objects in Objective-C are represented by what we call a WebScript object. And a WebScript object is an Objective-C class that has methods to allow you to call functions and get and set properties on the corresponding JavaScript instance. How do you get to the JavaScript interpreter? What is the root? How do you get that hook into the JavaScript interpreter? Well, it's a JavaScript window object. And the JavaScript window object in Objective-C corresponds to the window object that is the root object on your web pages. And there's a new method on WebView, Windows Script Object, that gets you that initial hook into a web page.
Here's an example of how you could use it. Very simply, to get the location.href property from a web page. So in this case, I'm asking the WebView, give me the Windows script object, and then give me your location property, which is an object, JavaScript, WebScript object again. And then I ask that object, give me the href property. It's as simple as that. There's an alternative method to do the same thing. I can take a little chunk of JavaScript and evaluate that on a particular WebScript object. In this case, I'm evaluating location.href on the window WebScript object. These two mechanisms are equivalent.
How do I invoke that JavaScript function from Objective-C? Well, first, I get the WinDescript object again, then I create an array that represents the arguments that I'm going to pass into JavaScript. In this case, it's just a single element in the array, the name of an image that I'm going to pass to @image.
And then I invoke the JavaScript function by calling call_webscript_method with the name of the function @image and the arguments. Again, alternatively, I can use a snippet of JavaScript to do the same thing. So in this case, add image. Again, these two mechanisms are equivalent. Depending upon your usage patterns, you can use one or the other.
What kinds of things can I pass to and from JavaScript? Well, you can pass any object to JavaScript, any Objective-C object. NSStrings and NSNumbers are treated specially. An NSString is converted to a string in JavaScript. An NSNumber is converted to a number in JavaScript. : Web Script Objects. Again, the Web Script Object represents the JavaScript Object in Objective-C. When you pass one of these back to JavaScript, it becomes unwrapped and you have a reference to the original JavaScript Object. All other kinds of Objective-C instances are represented by a proxy in JavaScript.
Here's a summary of the type matching between JavaScript and Objective-C when calling JavaScript from Objective-C. NSString, String, NSNumbers, mapped to a number. Now, an NSArray is a special case. Arrays in JavaScript are strange, flexible, but strange beasts. You can have sparse arrays, you can have associative arrays, and it's all the same object. NSArrays are much simpler, straightforward arrays, but there isn't a one-to-one correspondence between a JavaScript array and an NSArray. The array you get in JavaScript is read-only and limited. You can't use it like an associative array, but you can access the elements positionally.
A Web Script object, of course, as I've said, gets unwrapped in JavaScript and becomes the original JavaScript object. And any other Objective-C instance is mapped to a proxy in JavaScript. And I'll talk a little bit more about proxies next. First, let's do a demo. And I'll do a quick demo to show you how to write a Web Kit application to interact between Cocoa components, Cocoa widgets, and a web page.
So let's build this from scratch. I've got a fresh Xcode project here. First, let's build an .ib file. : I'm going to take the components from the existing project and copy them in to this new project. So let's get a Nib file. Copy that into the project. And open up this nib file.
So you can see here I've got three controls that are going to specify three different images. And when I click on one of these controls, it's going to set the content of the web view to be the appropriate image for that button. And you'll see that The controller is just going to send -- or the radio matrix is just going to send a message to a controller object to pick an appropriate picture type. So let's go ahead and hide this and pull in the code to do that.
Here we go. So this code is very straightforward. It has the single method -- well, it has two methods. I'll talk about this one first. Picture type clicked. This looks very much like the example I showed you on the slides. We get the Windows script object, which corresponds to the window object of the web page. Then I go ahead and call set image with arguments that I'm constructing to set the appropriate URL for that particular image.
Let me back up a second and show you what we do in our Wake from Nib method. What I'd like to do is load content into that web view that represents the page that I'd like to show. So I do that by extracting a resource out of the bundle that is a string. It's basically just a text file. I convert that to a string and then load it into the web view. So let's take a look at the web page that we're going to use here. Add it as a resource.
Very straightforward page. It has a single JavaScript function. It's going to get the image element by ID, and then it's going to set the source of the image element. And this source is a URL. The final thing I need to do is add some images. Picture of a fire. Let's add that. And a picture of the Grand Canyon and a picture of Napa Valley.
Go ahead and add those. And then let's build this project and run it. Okay, so here's my application. Now, when I click on Napa, what actually happened there is I sent a message from this Objective-C control to my Objective-C controller instance, which in turn sent a message to JavaScript to load the Napa image. Click on the fire button. It's sending the fire URL to JavaScript and Grand Canyon.
Back to the slides. Okay. What about the other direction? I have some JavaScript code that wants to call into Objective-C. Well, this is where proxies come in. If you have an Objective-C object that you want to have appear in JavaScript, we'll represent that object by proxy and we'll automatically reflect the methods and the properties of the Objective-C instance in JavaScript.
And this is an opt-- we have an opt-in policy for the methods that we expose to JavaScript. What that means is you have to tell us, "I want this particular method or this particular property to appear in JavaScript." And the kinds of things that you can expose to JavaScript are any Objective-C object.
You can expose methods that take scalar parameters. Scalars are in some floats, of course, and other kinds of scalar types. You can also expose scalar instance variables. What you can't do is expose structures or pointers to arbitrary memory. You can expose a pointer to an object, but no other kinds of pointer types.
Okay, so again, let's say I have this example class, an address book class, and it has these methods, get a shared instance, get a composite name, the first and the last name, and a way to position the cursor in the address book, position, card, and index. : I want to expose this to JavaScript. How do I do it? Well, I get the instance, the shared instance, and then I simply set the Objective-C object as a property on the Window object. I get the Window script object and call setValueForKey with the name address book.
That name is the name that will appear in your JavaScript code for this particular property. Richard Williamson : So from JavaScript, if I wanted to write something to retrieve the first name in my address book, this is how I do it. Window.addressBook gets me the Window object, and I call positionCardAtIndex to position it at the first record, and then I call compositeName.
Simple as that. For those of you paying attention, that last slide had something that was a little bit strange, that underbar at the end of that method name. What's that all about? It's kind of ugly. Well, by default, we have to map Objective-C names to JavaScript names, and Objective-C names have colons after each or before each parameter. So a colon gets mapped to an underbar.
If you want to override the default name of an Objective-C method that gets mapped to JavaScript, you can implement this method on your class, the class that's exported to JavaScript. Webscript name for selector. In this case, we take a selector in and we return the name that will be exposed to JavaScript. I'm going to use position card at index with no underbar. Now my JavaScript code will look like this.
Position card at index. Okay, so in practice, you typically want to expose functionality into JavaScript very early on in the construction of your page. So you might have JavaScript code in the head section of your page, and that JavaScript code needs to access functionality, native functionality. Richard Williamson So when do you actually publish your objects into JavaScript? Well, you can do that really early on.
We've added a new web frame load delegate method, which is Windows Script Object Available. And this method will be called before we do any processing on the page. So you can insert your functionality into the JavaScript interpreter before we evaluate any JavaScript on the page. So in this case, I'm going to insert the address book instance into the JavaScript namespace very early on. Okay, to summarize the time matching in this direction, a JavaScript string is mapped to an NSString. A JavaScript number is mapped to an NSNumber.
All other kinds of JavaScript objects, including JavaScript arrays, are mapped to WebScript objects, and JavaScript proxies, or an Objective-C proxy in JavaScript, when it's passed back to Objective-C, is represented by the original Objective-C instance. Okay, let's do a demo in this direction. And what I'm going to show you is a demo To dynamically construct a web page using the records from my address book. Let's close this project.
And again, we'll build this from scratch to show you how simple this is. And I've built it already, so it's not really from scratch, but -- So first thing, we'll copy the Nib file again for this project into Xcode. And this Nib file is really straightforward. It's just a single window with a web view.
Now, one thing that we've done here is we've set up the frame load delegate of the web view to be the controller instance. So this is the instance that will receive that message, "Window script object available." So let's go ahead and go into Xcode. Let's take a look at the source code. Well, actually, first let me show you the address book class that we'll be exposing.
So it's a pretty simple object with a shared instance. It implements two methods that we -- or two or three methods that we care about. Composite name. This will return the name of one of my contacts in my address book, a way to position the cursor in the address book, and a way to get the number of cards.
We're actually using the address book framework here to download my entire address book into a simple NSArray. That's how we're going to get the data. There are a few things that are relevant to this demo. Webscript name for selector. This is how we map the name position card at index colon to position card at index.
And this method is the method that we implement to expose the methods. This says, yes, I want these methods available in JavaScript. And you have to implement this method. If you didn't implement this method, or if you returned no to this method, every single method of your Objective-C object would be exposed, including things like Dialog. And it doesn't make a lot of sense to call Dialog from JavaScript. Let's add the controller instance source code.
This looks pretty similar to the previous demo. I'm going to extract some content, HTML content, out of the application bundle. And I'm going to set that content as the HTML of the web frame in my Awake from Nib method. Now, very simply, in the frame load delegate method, I'm going to set the address book as the address book property of the window object. So I need to do one additional thing here, and that is add the address book framework.
One additional thing, too, I have to copy over the source for the page. Let's take a look at that. So this is a pretty simple page. Initially, it has an empty body. There's no content in the page. We're going to dynamically construct the entire page, and that's going to be done in GetContacts on LoadHandler.
And this simply accesses the address book instance that we've exported to JavaScript, gets a number of cards, and then iterates over the cards, getting the name of each record in the address book. And then it creates a div and adds that to the document. Let's go ahead and build and run this. There we go. Let's pull up the address book and make sure that these records match. Whoops. Phone number's in there. All of the records in the address book match the dynamically constructed web page.
So it's really simple to do this stuff. If you have native functionality and you want to get into JavaScript, it's very straightforward. And I think there's going to be a lot of synergy between the Cocoa frameworks and this technology. So, for example, Core Data is going to really allow a lot of interesting applications that use web pages as front end to relational databases.
One last topic on scripting, and that's the DOM. Well, we've introduced a significant new set of Objective-C APIs to expose the DOM in Objective-C. Now, just a quick recap, the DOM is the document object model of a web page, and there's a set of standardized APIs from the W3C that define the DOM API, and we have a mapping of that API in Objective-C.
Now, if I get a DOM object in Objective-C, what does that mean in terms of scripting? Well, all DOM objects inherit from WebScript object, so that means if you have a DOM object, it's inherently scriptable. Let me have another call out to the session of Friday. Again, that's going to be a great session. It talks about the DOM API in detail. But let me show you a quick example of how you would do this.
There's a new method on Webframe to get the DOM document. That's the document object that represents your page. And you can evaluate WebScript on it, just as you can any other WebScript object. In this case, I'm going to call getElementById to get the element with the ID context div. This is equivalent to the Objective-C method, getElementById. getElementById is a new method in the DOM API that we're introducing.
These two mechanisms are equivalent. So why would you use one over the other? Well, it's much more efficient to use the Objective-C API directly. So, for example, if you're doing things that iterate over a tree, it's much more efficient to use the getElementById if you're going to scan the entire document for an element, say. On the other hand, if you're doing stuff dynamically with JavaScript, the first method is appropriate.
So there's a lot of flexibility in the scripting API. We think it's going to give you a lot of new capabilities that are pretty significant. Okay, finally, last feature that we're going to discuss in some detail. The Canvas Element. When we were putting together the dashboard, We came to the conclusion pretty quickly there were some things we wanted to do that we just couldn't do in the dashboard with existing HTML technology. One of those things was doing procedural rendering into a gadget. We introduced the Canvas element to support that. What it allows you to do is to draw into an HTML element on a page.
Specifically, you draw into a page using a context. We've created a 2D context that allows you to do 2D drawing operations into a canvas element. The canvas element is treated very much like the image element. By default, it's a replaced inline element, which means it flows just like an image on a page.
And because the context is backed by Core graphics, you have all of the power of Core graphics in terms of the rendering capabilities into a canvas, which means it's hardware accelerated if you have the appropriate hardware.
[Transcript missing]
So specifically, the drawing operations that we expose are the same drawing operations that you can do with a CG context. You can construct arbitrary paths, including curves. You can fill and stroke those paths. You can composite images. And you have control over the transparency levels and the compositing operation at the global level. So you can actually set a composite operator for drawing other than just image compositing.
This is an example of how you'd specify a canvas element on a web page. It looks very much like the image tag. You specify a width and a height, a composite operator. This is the operator that will be used to composite the resulting image into your web page. And this, by the way, the same attribute name that we've added to the image element.
Okay, so how do I actually do the drawing into the Canvas element? Well, here's an example. In this case, I'm going to draw a line from the top left to the lower right of the Canvas. The first thing we do is we get the Canvas element using the familiar get element by ID, and then we ask that element, give me a context. In this case, a 2D context. Now I begin my drawing.
I'm going to use the clear context. It's going to fill the context with transparency. Construct a path, you know, 00 to the lower left, I mean lower right. Set the stroke color and stroke that path. Pretty straightforward. So I think this stuff is best illustrated with some demos. Let me show you some demos of the canvas.
Let me increase the font size here a bit. Okay. So here's a really simple web page that specifies a Canvas element. Not too exciting yet. Let's open up Safari and load the page. Now let me turn the network back on before I forget to do that. So what does this look like? Well, that's it. A rectangle. Not too exciting. But we're just starting.
So let's go ahead and draw some lines into the canvas. So what I'm going to do is add a little script in here. And this is actually going to draw two lines like this through the page. But we're going to do it in the onload handler of the body element. Okay. Let's go ahead and save this as something different.
These lines in Canvas, ooh. Okay, let's crank this up a bit. Show something a bit more whizzy. Okay, this looks more interesting. Let's hide the other applications. So what I have here is a rectangle. So, we're going to start off with some shapes that we're drawing dynamically frame by frame using a canvas. And we can do things, of course, you know, change the colors. And because the canvas itself is an element on the page like any other element, we can do CSS tricks like positioning. So I'm going to dynamically move the canvas as I render each frame.
continue to do things like change the colors. There's one other thing I want to show you on this page which is kind of interesting, and that's the circle up here. This is another canvas, but it's a canvas used to implement a widget, a new kind of widget on the web page.
And what this widget does is specify the offset and the angle of the shadow in each shape. So you'll notice there's a blurred shadow in each shape. As I move this control, you'll see that shadow change. So this is implemented using standard JavaScript event handling and the canvas to render the widget. Pretty nifty.
Of course, we can change the blur rate with the new slider input element. I can do things like change the background color. So this is pretty cool. Let me show you one other thing that I think is pretty cool. Let's stop this thing moving. There's a new debug menu in JavaScript that I call Dashboard Mode.
And what it does is it takes away all the stuff that Safari adds, and you just have the pure HTML here with no background. So pretty nifty. Let me just show you how powerful this really is. Let's pull up another window. I'm going to load some interesting content here. The Incredibles. This trailer. I love this trailer. Okay. And you can see that it's doing everything you'd expect. This is a web page being rendered transparently over a QuickTime movie. Pretty amazing.
Just a few more slides. So it's not just about web browsing. This Web Kit technology is really powerful, and now with this ability to bind all the Cocoa framework technology into JavaScript, there's a lot of new capabilities that we're providing to you. You can build visually compelling applications really quickly, and you can leverage your existing web design skills. Rapid prototyping. This is an interpreted environment, so you don't have to go through a compile phase. It's great. Simple deployment. These are just web pages.
So I said we'd build a gadget. So let me do that now in the remaining time that we have. This is actually a really simple gadget. It's the clock. It's most of the clock that is included in the dashboard. So the first thing we're going to do is construct a web page. Let's close this. Increase the font size.
Let's save this. Okay, not very interesting. Right now, I'm just creating the face of the clock. It's simply an image. We need to put a... I'm going to load an HTML element around this. I'm being kind of sloppy. I'm not specifying the doc type. Okay. Let's load this into Safari. Very sloppy HTML at this point, but that's the clock face. That's the beginning of the web page. Of the gadget, I should say.
Now what I'd like to do is add some JavaScript code To load in the images that I want to use for the clock hands. So what I'd like to do is have just three images that I use for the hours, minutes, and secondhand, and use the Canvas element to apply transforms to the images as I composite them into the Canvas.
[Transcript missing]
And I have this little function that counts up the images as they're loaded because in this case, the images are all local, but they could be remote. So it may take some time to load the images. And I don't want to start compositing until I have all of the images. Okay, what next? Let's add a load handler.
: So what I've done here is I've added a canvas element that's going to be positioned using CSS right on top of the image element. Initially, it's just transparent, but it's positioned over the image element. This is standard CSS. You can see I'm specifying absolute positioning at 0, 0. So let's take a look at what this looks like now in Safari. That's exactly the same. There's nothing new. OK. Let's add some code to draw the hands.
First, I guess we have to add one more thing. We have to add the code that will figure out the appropriate angle to composite We have to know where to position the second hand, the minute hand, and the hour hand. We do this using the standard JavaScript data object, some simple math. We compute the angles for the second minute and hour hand. Then we go ahead and draw the hands. Let's load this again. Still nothing new. Okay. Let's get to the final piece, actually drawing the hands.
So what we did with the unload handler is we added a JavaScript function that's going to be called every second. And this JavaScript function will go ahead and compute the angles for each of the hands and then call a drawHands method that will do the actual work of compositing the images. And the drawHands method The first thing we do is make sure we have all of the images available. The image count has to be three.
Let's clear the context. And then we call save. Well, what's save all about? Save saves the graphic state of the context that's used to render into the canvas. So after calling save followed by restore, if you've done any transform operations in between a save and restore, they'll be restored as appropriate. So what we actually are doing here is we're translating to the center of the clock face, actually just a little bit above the center of the clock face because that's the center of the circle.
Then we go ahead and call drawHand, which is my JavaScript function up here, to composite that image. Now, we've translated already to the center, but now I need to rotate as appropriate to composite the image hand. So here's a rotate of the angle that we previously computed. And finally, finally, we get to draw the image using drawImage on the context. And then we restore the angle because the next hand is going to be the image hand. And we painted a different rotation. So that's it. Let's go ahead and load it now.
There we have hands working, but they're not quite in the center. What's going on with that? Well, by default, the style of the body element on a web page has a margin that isn't 00. So let's go ahead and add some style to adjust the margin. is the founder of the body element. We're going to set the margins to zero. We learned that. Looks much better. There we have the clock widget. Let's go ahead and turn on dashboard mode. Whoops. Just like it appears in dashboard.
So I want to say a few final words about the availability of these technologies. Of course, everything we've talked about today is going to be available in Safari 2.0 and Tiger. All of the Web Kit features that we've talked about today are also going to be available on Panther in 10.3, and there's already a developer preview of that release that you can download today. Just one final point. There's lots of documentation and sample code available on the connect.apple.com site. I encourage you to check that out.