I find that the DOM approach works best when creating HTML programmatically— when the structure or content of the inserted markup isn’t the same every time—because building long strings in JavaScript isn’t my idea of a fun afternoon. For simpler cases, innerHTML is easier and faster. Figure out the balance you’re comfortable with. The first three methods we’ll deal with— update, replace, and insert—don’t force you to pick one approach or the other. All three can accept either a markup string or a node. Using update Element#update changes the contents of an element. Think of it as a thin wrapper around innerHTML—just as assigning to the innerHTML property will erase whatever was there before, update will discard the original contents of the element, replacing it with what you’ve given. // HTML (before): <p id="foo"><b>narf</b></p> // JavaScript: $('foo').update('<span>thud</span>'); // HTML (after): <p id="foo"><span>thud</span></p> It boasts several advantages over innerHTML: • As explained, it can take a DOM node or a string. • The convenient “automatic script evaluation” you were introduced to in Chapter 4 also applies to Element#update. Any script elements in the inserted markup will be removed; the code inside them will be extracted and evaluated after the element has been updated. • It gracefully handles some special cases where Internet Explorer tends to choke. For instance, most table elements have read-only innerHTML properties, as do odd- balls like col and select. Let’s try a DOM node instead of an HTML string: var span = document.createElement('span'); span.appendChild(document.createTextNode('thud')); $('foo').update(span); $('foo').innerHTML; //-> " <span>thud</span>" CHAPTER 6 ■ WORKING WITH THE DOM122 Using replace Element#replace is nearly identical to its brother update, but can be used to replace an ele- ment (and all its descendants) instead of just changing its contents. CHAPTER 6 ■ WORKING WITH THE DOM 123 CHAINING Prototype’s augmentation of DOM node instance methods opens the door to method chaining: a syntac- tic shortcut that makes lines of code read like sentences. Many of the methods in this chapter—specifically those that do not need to return other values— will return the elements themselves. Consider this code: $('foo').addClassName('inactive'); $('foo').hide(); Because both addClassName and update return the element itself, this code can be simplified: $('foo').addClassName('active').hide(); Chaining method calls like this—joining them in a line, each acting upon the return value of the last—can increase code clarity when used judiciously. In this example, we’ve also optimized the code, removing a redundant call to $ to re-fetch the element. Look out for methods that do not return the original element. Consider Element#wrap, which returns the new created parent node: $('foo').wrap('div'); $('foo').addClassName('moved'); // wrong: $('foo').wrap('div').addClassName('moved'); We’ve changed the meaning of the code by accident: instead of adding a class name to the ele- ment with an ID of foo, we’re now adding it to the div that was created and returned by wrap. Reversing the order of the method calls preserves our intent: // right: $('foo').addClassName('moved').wrap('div'); Similarly, note that Element#replace will return the original element, but that element has been replaced and is no longer a part of the DOM tree. If you want to work with the content that has replaced it, you’ll need to obtain that reference some other way. // HTML (before): <div id="foo"><span>thud</span></div> // JavaScript: $('foo').replace('<p id='foo'><b>narf</b></p>'); // HTML (after): <p id="foo"><b>narf</b></p> The new content occupies the same position in the document as its predecessor. So replace is a way to remove an element, keep a finger on its spot in the DOM tree, and then insert something else at that spot. Using insert Element#insert appends content to an element without removing what was there before. We flirted with insert in Chapter 4, so the syntax will be familiar to you: // HTML (before): <div id="foo"><span>thud</span></div> // JavaScript: $('foo').insert("<span>honk</span>", 'top'); // HTML (after): <div id="foo"><span>honk</span><span>thud</span></div> The second argument is the position of insertion—either before, after, top, or bottom. This argument will default to bottom if omitted. // equivalent in meaning: $('foo').insert("<span>honk</span>", 'bottom'); $('foo').insert("<span>honk</span>"); A more robust syntax can be used to insert several things at once. Instead of a string, pass an object as the first argument—the keys are insertion positions and the values are HTML strings. CHAPTER 6 ■ WORKING WITH THE DOM124 // HTML (before): <div id="foo"><span>thud</span></div> // JavaScript: $('foo').insert({ top: "<span>honk</span>", bottom: "<span>narf</span>" }); // HTML (after): <div id="foo"><span>honk</span><span>thud</span><span>narf</span></div> The positions before and after are similar to top and bottom, respectively, but you insert the new elements outside the boundaries of the given element—as siblings, rather than children. // HTML (before): <div id="foo"><span>thud</span></div> // JavaScript: $('foo').insert({ before: "<span>honk</span>", after: "<span>narf</span>" }); // HTML (after): <span>honk</span><div id="foo"><span>thud</span></div><span>narf</span> Using remove In the DOM API, you must remove an element by calling the removeChild method on its parent: // to remove "foo" $('foo').parentNode.removeChild($('foo')); Prototype adds Element#remove in order to circumvent this annoyance: $('foo').remove(); Note that removing an element from the document doesn’t make it vanish; it can be reappended somewhere else, or even modified while detached. But a detached node won’t respond to calls to $ or document.getElementById (since the node is no longer in the document), so make sure you preserve a reference to the node by assigning it to a variable. CHAPTER 6 ■ WORKING WITH THE DOM 125 // to remove "foo" and append it somewhere else var foo = $('foo'); foo.remove(); $('new_container').appendChild(foo); The readAttribute and writeAttribute Methods Prototype’s readAttribute and writeAttribute methods are used to get and set attributes on elements. “Aren’t these superfluous?” you ask. “Doesn’t the DOM give us getAttribute and setAttribute?” Yes, it does, and browsers also expose attributes as properties of their object representations—a holdover from the pre-DOM days. So, for instance, the href attribute of a link can be fetched with $('foo').getAttribute('href') or even $('foo').href. But these approaches have compatibility problems. Internet Explorer, in particular, exhibits a host of bugs in this area, thwarting our attempts to get identical behavior from all major browsers. Element#readAttribute and Element#writeAttribute are wrappers that ensure identical behavior. Using readAttribute Let’s look at some examples of surprising getAttribute behavior in Internet Explorer: // HTML: <label id="username_label" class="required" for="username"> <input type="text" id="username" name="username" disabled="disabled" /> </label> <a id="guidelines" href="/guidelines.html">Username guidelines</a> // JavaScript: var label = $('username_label'); label.getAttribute('class'); //-> null label.getAttribute('for'); //-> null var input = $('username'); input.getAttribute('disabled'); //-> true var link = $('guidelines'); link.getAttribute('href'); //-> "http://www.example.com/guidelines.html" CHAPTER 6 ■ WORKING WITH THE DOM126 The label element has class and for attributes set, but Internet Explorer returns null for both. The input tag has a disabled attribute with a value of “disabled” (in accordance with the XHTML spec), but Internet Explorer doesn’t return the literal value—it returns a Boolean. And the a element points to an absolute URL on the same server, but Internet Explorer gives us the “resolved” version when we ask for its href. In all three cases, we’re expecting the literal value that was set in the markup—that’s how getAttribute is supposed to work, and that’s how the other browsers do it. When it was released, Internet Explorer 6 had the best DOM support of any browser, incomplete as it was; now, six years later, bugs like these make Internet Explorer the slowpoke of the bunch. In nearly all cases, the value we want is hidden somewhere—we’ve just got to find it. Prototype does the heavy lifting for you. var label = $('username_label'); label.readAttribute('class'); //-> "required" label.readAttribute('for'); //-> "username" var input = $('username'); input.readAttribute('disabled'); //-> "disabled" var link = $('guidelines'); link.readAttribute('href'); //-> "/guidelines.html"; Use readAttribute anywhere you’d use getAttribute. It’s safer. Using writeAttribute As you may expect, writeAttribute lets you set attribute values safely in all browsers, suc- ceeding where Internet Explorer’s setAttribute fails: label.setAttribute('class', 'optional'); // fails label.writeAttribute('class', 'optional'); // succeeds But that’s not all. It adds a major syntactic time-saver for writing multiple attributes at once—simply pass an object literal full of attribute names and values. input.writeAttribute('id', 'user_name'); label.writeAttribute({ title: 'Please choose a username.', 'class': 'optional', 'for': 'user_name' }); CHAPTER 6 ■ WORKING WITH THE DOM 127 . readAttribute and writeAttribute methods are used to get and set attributes on elements. “Aren’t these superfluous?” you ask. “Doesn’t the DOM give us getAttribute and setAttribute?” Yes, it does, and. WORKING WITH THE DOM122 Using replace Element#replace is nearly identical to its brother update, but can be used to replace an ele- ment (and all its descendants) instead of just changing its contents. CHAPTER. "foo" and append it somewhere else var foo = $('foo'); foo.remove(); $('new_container').appendChild(foo); The readAttribute and writeAttribute Methods Prototype s readAttribute