custom events from anywhere in your code; you can also listen for custom events with the same API that you’d use to listen for native browser events. The First Custom Event Prototype itself fires a custom event called dom:loaded. It fires at a specific time in the page’s life cycle: after the page’s DOM tree is fully accessible to scripts, but before the win- dow’s load event, which doesn’t fire until all external assets (e.g., images) have been fully downloaded. Use dom:loaded when you want to work with the DOM in that narrow window of time before the page appears on the screen fully rendered. In nearly all cases, it’s better to assign to dom:loaded than load—unless your handler depends upon having every- thing downloaded and rendered. This is also a good time to talk about the naming scheme for custom events. You’ve probably noticed that dom:loaded, unlike native events, contains a colon. This is by design— all custom events must contain a colon in their names. Since custom events are handled differently under the hood, Prototype needs a way to distinguish them from native browser events (which number in the hundreds if all major browsers are consid- ered). Embrace the convention. Broadcasting Scores The data stream we built in Chapter 4 will power a large portion of our fantasy football site. It would be wasteful and silly for each JavaScript component to make its own Ajax requests, so let’s write some general code with the specific task of “asking” for scores from the server, and then “announcing” these scores through some sort of public address system. Create a new file called score_broadcaster.js and place this code inside: var ScoreBroadcaster = { setup: function() { this.executer = new PeriodicalExecuter(this.update.bind(this), 30); this.update(); }, update: function() { this.request = new Ajax.Request("scores.php", { onSuccess: this.success.bind(this) }); }, CHAPTER 5 ■ EVENTS 109 success: function(request) { document.fire("score:updated", request.responseJSON); } }; document.observe("dom:loaded", function() { ScoreBroadcaster.setup(); }); First, notice the design pattern—we’re creating a ScoreBroadcaster object to act as our namespace. Next, jump to the bottom—we’ve hooked up ScoreBroadcaster.setup to run as soon as the DOM is ready. This function schedules a new Ajax request every 30 seconds; successful requests will call another function that will fire a custom event with our data. Now look in the middle—we call document.fire with two arguments. This method fires custom events, naturally, and exists on all elements ( Element#fire) and on the document object, too. You’ve just learned two things about this method: • The first argument is the name of the event to be fired. As we discussed, the name needs to have a colon in it, so let’s call it score:updated. The noun:verbed naming scheme is just a convention, but it’s a useful one. • The second argument is an object that contains any custom properties for attach- ing to the event object. Just like native browser events, custom events pass an event object as the first argument to any handler. Alongside familiar properties like target, custom events have a memo property on their event objects. The second argument of Element#fire gets assigned to this property. In short, we’re attaching the score information so that handlers can read it. As we covered in Chapter 4, we’re using Prototype’s special responseJSON property on the Ajax response object—useful because it automatically unserializes the JSON payload. Using the application/json MIME type gets us this property for free. That’s one fewer link in the chain we have to worry about. When we write compo- nents, we won’t have to deal with the boring details of getting the data. Score updates will be dropped upon them as though they were manna from heaven. Listening for Scores To illustrate this point, let’s write some quick test code to make sure the custom event is working right. Add this to the bottom of score_broadcaster.js: CHAPTER 5 ■ EVENTS110 document.observe("dom:loaded", function() { document.observe("score:updated", function(event) { console.log("received data: ", event.memo); }); }); We listen for a custom event the same way we listen to a native event: using Event.observe. Custom events behave much the same way as native events: they bubble up the DOM tree, and they have their own event objects that implement all the proper- ties and methods we’ve already covered. Here we listen for our custom score:updated event and log to the Firebug console whenever it fires. Include this script on an HTML page and observe the result. Every 30 seconds, one of the lines shown in Figure 5-12 should appear in your Firebug console. Figure 5-12. This line should appear in your Firebug console twice a minute. In subsequent chapters, we’ll write code that hooks these stats up to the interface. Summary To revisit the theme of the chapter, events should make simple things simple and com- plex things possible. Prototype’s event system doesn’t make everything simple, but it does manage the unnecessary complexities of modern browser events. The watchword for this chapter has been normalization: making different things behave uniformly. Prototype makes two different event systems (Internet Explorer’s and the W3C’s) behave uniformly; it also makes native events and custom events behave uniformly. Keep this concept in mind while we look at DOM traversal in the next chapter. CHAPTER 5 ■ EVENTS 111 Working with the DOM Now that you’ve got the foundation you need to explore advanced concepts, it’s time to learn about Prototype’s powerful helpers for working with the DOM. About the DOM API As we discussed in the last chapter, the DOM is specified in a series of documents released by the W3C. DOM Level 1 outlines the basics you’re probably used to. Levels 2 and 3 specify a series of enhancements and expansions to the DOM API, such as events, node traversal, and style sheets. Level 1 enjoys support in all modern browsers, but the other levels can- not be counted on. Despite its obvious power and utility, at times the DOM API just doesn’t feel very JavaScript-y. Methods have annoyingly long names. Some methods take a lot of argu- ments, and some methods expect their arguments in an unintuitive order. This is an unfortunate but necessary result of the DOM’s language independence. Though the most famous implementation of the DOM is JavaScript’s, the APIs are designed to be implemented in nearly any modern programming language. This approach has its drawbacks (the DOM can’t leverage any of JavaScript’s dynamism, since it has to work in more static languages like Java), but it also has the advantage that the DOM works the same way in any language: learn once, write anywhere. Still, we’re writing our code in JavaScript, so let’s make the most of it. Prototype con- tains a large number of extensions to the browser’s DOM environment, so developers can have their DOM and eat it too. Node Genealogy The strange world of the DOM is filled with jargon and terms of art. In order to minimize confusion, let’s look at a few of them up front. 113 CHAPTER 6 Think of the DOM as an unseralizer. It takes a linear stream of HTML, parses it into different types of nodes, and arranges those nodes in a tree according to their relationships. That may not have been too helpful, so I’ll let the code talk (see Figure 6-1): <p><u>Colorless</u> <i>green <u>ideas</u></i> sleep <b>furiously</b>.</p> Figure 6-1. The DOM translates a stream of HTML into a tree of nodes. This paragraph has both element nodes and text nodes as descendants. The resemblance of a tree like this to a family tree is convenient—it lets us borrow the jargon of genealogy. Take the p tag at the top. It has five children: u, i, "sleep", b, and ".". The two in quo- tation marks are text nodes. The other three are element nodes, and those elements have children of their own. And "ideas" is p’s great-grandchild, so to speak; it’s in the third level of descendants from p. The distinction is useful, then, between children and descendants, and between parent and ancestor. Children are all elements that are exactly one level descended from CHAPTER 6 ■ WORKING WITH THE DOM114 a certain node. Descendants are all elements that a node contains—the sum of all the node’s children, its children’s children, and so on. Likewise, a node can have only one parent, but can have many ancestors. Prototype’s DOM Extensions The DOM is broad, sterile, and built by committee. In the interest of creating a “safe” API that can be used by many different languages, it maintains a cordial distance from the features of JavaScript. Its API chooses verbose method names like getElementById and getAttributeNode—as with natural language, the cost of eliminating all ambiguity is to double the number of words. Prototype establishes a bridge between the DOM and the commonest use cases of the typical developer. As a result, the code you write will be shorter and far more readable. Prototype’s DOM API is broad, so let’s divide it into rough categories based on task: modifying, traversing, collecting, and creating. Modifying These methods modify properties of a DOM node or report information about a node for later modification. The hide, show, visible, and toggle Methods These are the most commonly used element methods. They control whether the element is visible or hidden (whether its CSS display property is set to none, thereby hiding it from view). Controlling element display is a staple of web applications. Think of a message that should disappear after the user dismisses it. Think of a listing of items, each with a sum- mary that should only be shown when the user mouses over that item. Think of a view stack—a group of elements that should occupy the same area of the page, with only one visible at a time. Element#hide and Element#show control element display: var foo = $('foo'); foo.hide(); foo.style.display; //-> 'none'; foo.show(); foo.style.display; //-> 'block'; CHAPTER 6 ■ WORKING WITH THE DOM 115 . custom events from anywhere in your code; you can also listen for custom events with the same API that you’d use to listen for native browser events. The First Custom Event Prototype. contains a colon. This is by design— all custom events must contain a colon in their names. Since custom events are handled differently under the hood, Prototype needs a way to distinguish them. is just a convention, but it’s a useful one. • The second argument is an object that contains any custom properties for attach- ing to the event object. Just like native browser events, custom