// Now append each cell to the row tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3); // and append the row to the table body. $('cities').down('tbody').insert(tr, 'bottom'); Three times as many lines! As is often the case, one approach is easy but sloppy, and the other is harder but more “correct.” Can’t we split the difference? // First, create the row. var tr = new Element('tr', { 'class': 'total' }); // Next, create each cell and append it on the fly. tr.appendChild( new Element('td').update('Total') ); tr.appendChild( new Element('td', { 'class': 'number'}).update(totalPopulation) ); tr.appendChild( new Element('td', { 'class': 'code' }) ); // Now append the row to the table body. $('cities').down('tbody').insert(tr, 'bottom'); We haven’t talked about this technique yet, but it ought to seem familiar anyway. The glue in the middle, the new Element part, takes a tag name as its first argument, creates that element, calls Element.extend on it (to give it the Prototype instance methods), and then returns the element. The rest of it is stuff we’ve already covered: • The optional second argument to Element is an object with the attribute/value pairs the element should have. Element#writeAttribute takes care of that part. • Instead of the annoying document.createTextNode, we can use Prototype’s own Ele- ment#update to set the text content of a new element. • The last step is the same in all cases. We use Element#down to hop from the table to its tbody, and then we use Element#insert to place our new element after all the other rows. CHAPTER 6 ■ WORKING WITH THE DOM134 The wrap Method Let’s take a time-out from the example to talk about Element#wrap. Sometimes you’ll want to create a new element to act as a container for something that’s already on the page. This is one mode of attack for browser bugs—rendering issues, for example, can sometimes be defeated with a “wrapper” element. Prototype’s Element#wrap is shorthand for creating an element and specifying its con- tents all at once. Its arguments are identical to those of the Element constructor: // wrap a TABLE in a DIV var someTable = $('cities'); var wrapper = someTable.wrap('div', { 'class': 'wrapper' }); Here, since the table already exists in the DOM, we don’t have to explicitly append it somewhere. The div is created at the spot occupied by the table; the table then gets added as a child of the div. Wrapping an element that isn’t yet in the document, of course, is a different matter: // add a link to a list var li = new Element('a', { href: "http://google.com" }).update("Google").wrap("li"); $('links').insert(li); Like most methods on Element, wrap can be chained. But, as these examples show, it returns the wrapper, not the original element. Putting It Together Back to the example. Let’s write a function that, when given a table of the sort we’ve writ- ten, will automatically calculate the total and insert a new row at the bottom. Insert this block in the head of the page, right below where Prototype is loaded: <script type="text/javascript"> function computeTotalForTable(table) { // Accept a DOM node or a string. table = $(table); // Grab all the cells with population numbers in them. var populationCells = table.select('td.number'); CHAPTER 6 ■ WORKING WITH THE DOM 135 // Add the rows together. // (Remember the Enumerable methods?) var totalPopulation = populationCells.inject(0, function(memo, cell) { var total = cell.innerHTML; // To add, we'll need to convert the string to a number. return memo + Number(total); }); // We've got the total, so let's build a row to put it in. var tr = new Element('tr', { 'class': 'total' }); tr.insert( new Element('td').update('Total') ); tr.insert( new Element('td', { 'class': 'number' } ).update(totalPopulation) ); // Insert a cell for the airport code, but leave it empty. tr.insert( new Element('td', { 'class': 'code' }) ); table.down('tbody').insert(tr); }</script> This code does a lot of stuff, so let’s look at it piece by piece. First, we use the $ function on the table argument so that we can be sure we’re work- ing with a DOM node. Then we use Element#select to grab all the table cells with a class of number—there will be one per table row. Now that we have all of the table cells that contain population figures, we add them together with Enumerable#inject. We start off with 0, adding the value of each cell to that figure as we loop through the cells. The returned value is the sum of all the numbers con- tained in the cells. Now that we have the number, we need to lay the DOM scaffolding for it. We build a tr with a class of total, and then three tds with contents that correspond to that of the existing cells. Our total population figure gets inserted into the third cell via Element#update. We insert each cell as a child of the tr upon creation; since Element#insert adds new nodes at the end, by default, these elements will appear on the page in the order they’re inserted. All that’s left to do is test it! Open index.html in Firefox, and pass our population table to the computeTotalForTable function (see Figure 6-3): computeTotalForTable('cities'); CHAPTER 6 ■ WORKING WITH THE DOM136 Figure 6-3. JavaScript can add better than I can. Summary There’s a lot of meat to Prototype’s DOM support—primarily because that’s where most of your headaches come from. The DOM’s verbosity and uneven browser support are the proverbial “rock” and “hard place” of JavaScript development. An exploration of the DOM, however, isn’t complete without attention paid to events. The next chapter will exemplify the power of Prototype’s element traversal methods in the context of handling standard (and not-so-standard) browser events. CHAPTER 6 ■ WORKING WITH THE DOM 137 Advanced JavaScript: Functional Programming and Class-Based OOP JavaScript is a multi-paradigm language. No matter how well you think you know how to use it, you’re destined to find some style of wr iting code that confuses the hell out of you. This is good news, if you’ll believe it. It means that there’s often a better, shorter, more secure, or easier-to-understand way of doing something. Earlier chapters introduced you to object-oriented programming and functional programming. So you’re probably familiar with what they are, but may not realize yet why they’re useful. In this chapter, we’ll revisit these techniques, exploring advanced use cases for both. Object-Oriented JavaScript Programming with Prototype The term object-oriented programming (OOP) has become nearly meaningless with over- use, but I’ll try to wring out a few final drops of meaning. JavaScript itself is an object-oriented language, as we’ve discussed, but most of the common OOP concepts— class definitions, clear inheritance chains, and so on—aren’t built into the language. Prototype builds a class-based facade around the prototypal OOP model of JavaScript. In this chapter, you’ll learn how to use classes the Prototype way. Why OOP? The jaded developer might wonder whether I’m espousing OOP as the alpha and omega of programming styles. As Steve Yegge once quipped, advocating “object-oriented 139 CHAPTER 7 programming” is like advocating “pants-oriented clothing”; it elevates one architectural model to an overimportant position. In the DOM scripting world, OOP is often—but not always—the right tool for the job. Its advantages mesh well with the realities of the browser environment. Cleanliness JavaScript 1.x has no explicit namespacing and no package mechanism—no comprehen- sive way to keep different pieces of code from stepping on each other. If your web app needs, say, 50 lines of JavaScript, this isn’t a problem; if it needs three external libraries and a bunch of behavior code, you’ll run into naming problems. Scripts share the global space. In other words, if I have a function named run, I’ll need to make sure that none of the scripts I rely upon defines a run function, or else it will be overwritten. I could rename my function to something unique—like myRun or dupont_Run—but this is just a stall tactic. Ensuring uniqueness of function names becomes exponentially harder as the number of functions increases. (PHP’s standard library is an extreme example of the sort of clutter that can accumulate this way.) OOP helps solve this. It lets me define methods in more appropriate contexts. Instead of a global function called stringReplace, JavaScript defines replace as an instance method on strings. Sharing a scripting environment is like sharing an apartment. If you don’t help keep the place clean, your roommates will think of you as an inconsiderate bastard. Don’t be that guy. If you must be messy, be messy in your own room. Common areas need to remain tidy. Encapsulation OOP helps keep things organized through bundling. An object contains all the methods and properties associated with it, minimizing clutter and affording portability. Information-Hiding Many clocks rely on an elaborate system of gears to tell time, but humans see very little of what goes on inside a clock. We don’t have to figure out the time from the positions of the gears; we can look at the hands on the clock face. In other words, the gears are important to the clock, but they’re not important to us. Most real-world objects have a “public” interface (the way the outside world uses it) and a “private” interface (the way the object does the tasks it needs to do). OOP works the same way, letting a developer produce code that is easier to understand and more rele- vant on the line where it’s used. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP140 . events. The next chapter will exemplify the power of Prototype s element traversal methods in the context of handling standard (and not-so-standard) browser events. CHAPTER 6 ■ WORKING WITH THE. lot of meat to Prototype s DOM support—primarily because that’s where most of your headaches come from. The DOM’s verbosity and uneven browser support are the proverbial “rock” and “hard place”. that part. • Instead of the annoying document.createTextNode, we can use Prototype s own Ele- ment#update to set the text content of a new element. • The last step is the same in all cases. We use