Both methods are idempotent: calling them more than once in a row has the same effect as calling them just once. So you don’t need to check whether an element is visible before you hide it. Nonetheless, an easy way to figure out the display state of an element is to use Element#visible: foo.hide(); foo.visible(); //-> false foo.show(); foo.visible(); //-> true Element#visible simply reports on the element’s CSS display, returning true if it’s set to none. Now imagine using all three of these methods to write code that will switch an element between visible and hidden: if (element.visible()) element.hide(); else element.show(); You don’t have to imagine it, actually, because Prototype does it for you with a method called Element#toggle: foo.visible(); //-> true foo.toggle(); foo.visible(); //-> false foo.toggle(); foo.visible(); //-> true Element#toggle is a handy abstraction for elements that should have their display state toggled each time an event (e.g., a click) is triggered. // HTML: <div class="news-item" id="item_1"> <h3>Declaration of Independence <span id="toggle_1">Hide/Show</span></h3> <p id="summary_1">When, in the course of human events, it becomes necessary for one people to dissolve the political bonds which have connected them with another, and to assume among the powers of the earth, the separate and equal station to which the laws of nature and of nature's God entitle them, a decent respect to the opinions of mankind requires that they should declare the causes which impel them to the separation.</p> </div> // JavaScript: $('toggle_1').observe('click', function() { $('summary_1').toggle(); }); CHAPTER 6 ■ WORKING WITH THE DOM116 With the preceding markup, this one line of code toggles the visibility of the para- graph of text every time the span is clicked. In other words, items with expanded and collapsed states are perfect for Element#toggle. The addClassName, removeClassName, hasClassName, and toggleClassName Methods Showing and hiding elements is handy. But what if we want to do more with CSS? It’s remarkably easy to style elements directly through JavaScript using the style property that exists on every node. It’s so easy, in fact, that it enables sloppy coding. Consider the following: function highlight(element) { element.style.backgroundColor = 'yellow'; } function unhighlight(element) { element.style.backgroundColor = 'white'; } Imagine these two functions as mouseover/mouseout handlers for a table. When the user moves the pointer over a row, its background color is changed to yellow; when the pointer leaves the row, it goes back to the default white. This code works as intended, but it’s not very maintainable. It’s a bad idea for the same reason that inline CSS in HTML is a bad idea: it mixes different layers and makes future changes more painful. For example, what if you introduce a third background color for table rows (red, perhaps, to denote important items)? Such rows would start out red, turn yellow on mouseover, and turn white on mouseout. You could rewrite the handlers to take this into account, storing the initial color value somewhere so that it can be restored on mouseout, but you’d just be going further down the wrong path. Instead, use CSS class names—they’re well-suited to the task. // CSS: #items tr { background-color: white; } #items tr.important { background-color: red; } CHAPTER 6 ■ WORKING WITH THE DOM 117 #items tr.highlighted { background-color: yellow; } // JavaScript: function highlight(element) { element.addClassName('highlighted'); } function unhighlight(element) { element.removeClassName('highlighted'); } This is a far safer and more elegant way to apply arbitrary styling. It uses Prototype’s Element#addClassName and Element#removeClassName (which do exactly what they say they do) to add and remove the styling. And, unlike the first approach, the style information is in CSS, where it should be. It won’t be forgotten about three years from now when you re-skin your application. This approach is particularly flexible because any element can have any number of class names. The HTML class attribute accepts any number of names separated by spaces ( <tr class="important highlighted">). Since it’s an attribute, we can use the DOM to modify its value. In JavaScript, class is a reserved word, so the relevant property is called className instead. foo.className; //-> ""; foo.className = 'important'; //-> "important" This is readable but foolish. Assigning a new class name directly will step on any others that may have been there already. Likewise, you can remove a class name by giv- ing it an empty value ( ""), but that will remove all of an element’s class names, not just a specific one. In other words, this isn’t a value you change; it’s a value you add to or subtract from. You use addClassName and removeClassName to ensure that these operations have no side effects. If you’ve noticed how addClassName and removeClassName resemble hide and show, then you’ll have predicted our next two methods: toggleClassName and hasClassName. CHAPTER 6 ■ WORKING WITH THE DOM118 foo.className; //-> "important" foo.toggleClassName('highlighted'); foo.hasClassName('highlighted'); //-> true foo.hasClassName('important'); //-> true foo.toggleClassName('highlighted'); foo.className; //-> "important" All four methods expect the class name as an argument; otherwise, they work the same way as hide, show, visible, and toggle. The setStyle and getStyle Methods Despite the warnings I just gave you, sometimes you’ll have no choice but to set style properties directly. Size and position ( width, height, top, and left) can be declared in a style sheet, but complex use cases will require manipulating these properties dynami- cally as a result of user input. The native DOM style API forces you to set these properties one at a time: foo.style.top = '12px'; foo.style.left = '150px'; foo.style.width = '100px'; foo.style.height = '60px'; But Prototype’s Element#setStyle can apply CSS styles in bulk. It accepts an object lit- eral as an argument, directly correlating JavaScript object properties and values with CSS properties and values: foo.setStyle({ top: '12px', left: '150px', width: '100px', height: '60px'; }); Element#setStyle ’s counterpart is named Element#getStyle, appropriately—though it’s not quite a counterpart. It retrieves the given style property on the node, regardless of that style’s origin. CHAPTER 6 ■ WORKING WITH THE DOM 119 // CSS: #foo { font-family: "'Helvetica Neue', Helvetica, Arial, sans-serif" padding: 10px; border: 15px; } // JavaScript: foo.getStyle('font-family'); //-> "'Helvetica Neue', Helvetica, Arial, sans-serif" foo.getStyle('padding-left'); //-> "10px" foo.getStyle('border-top'); //-> "15px" Upon first look, this seems unnecessary—why not just read from foo.style? But the style property doesn’t read from the entire style cascade—just inline styles. // HTML: <div id="foo" style="display: inline;"></div> // JavaScript: foo.style.fontFamily; //-> undefined foo.style.paddingLeft; //-> undefined foo.style.borderTop; //-> undefined 0foo.style.display; //-> 'inline' So the element’s style object may contain what you’re looking for—if a property reports a value, then it’s sure to be the correct one, since inline styles are the most spe- cific. But most of the time it will return undefined, since most style declarations come from a CSS block or an external CSS file. Internet Explorer and the DOM both provide APIs for determining the true style of a certain element. Element#getStyle acts as a wrapper around both. The update, replace, insert, and remove Methods Changing the content of an element is a common task, but it’s not an easy one. Suppose we want to change CHAPTER 6 ■ WORKING WITH THE DOM120 <div id="foo"><p>bar</p></div> to <div id="foo"><span>thud</span></div> There are two ways to do this in modern browsers. One uses DOM Level 1 methods, relying on the hierarchy-of-nodes model: var foo = $('foo'); var span = document.createElement('span'); var text = document.createTextNode('thud'); span.appendChild(text); foo.replaceChild(span, foo.firstChild); The other uses the element’s innerHTML property (first introduced in Internet Explorer, and then implemented by other browsers). It treats an element’s contents as a string: foo.innerHTML; //-> "<p>bar</p>"; foo.innerHTML = "<span>thud</span>"; Which should be used? There isn’t an easy answer. The innerHTML technique has the advantage of simplicity (in most cases) and speed (it can be up to an order of magnitude faster), but the disadvantage of being only a de facto standard, prone to irregular behavior. Meanwhile, the DOM technique is considered “purer” by standardistas—but, just like most other parts of the DOM, it can feel clunky and verbose, with many lines of code needed to do simple tasks. Purity comes at a cost. Those that champion one approach and slander the other are presenting a false dilemma. Sometimes it makes sense to treat markup as a node tree; sometimes it makes sense to treat markup as a string. Both techniques work well in all modern browsers; you don’t have to choose one and stick with it. If embrace of the nonstandard innerHTML property gives you pause, consider that the scope of what we can accomplish with today’s browsers would be severely impeded if we restricted ourselves to that which is defined by a W3C specification. We’d be building sites of interest to academics and power users, but which ignore the real world and the market-leading browser. Writing JavaScript for today’s Web requires balancing the ideal and the practical. As a way forward, we can push for standardization of all the nonstandard APIs we use—“paving the cowpaths,” as it’s called. For instance, the HTML 5 specification aims to prescribe a standard behavior for innerHTML so that it can be used without guilt or caveat. CHAPTER 6 ■ WORKING WITH THE DOM 121 . on mouseover, and turn white on mouseout. You could rewrite the handlers to take this into account, storing the initial color value somewhere so that it can be restored on mouseout, but you’d just. and more elegant way to apply arbitrary styling. It uses Prototype s Element#addClassName and Element#removeClassName (which do exactly what they say they do) to add and remove the styling. And, . and power users, but which ignore the real world and the market-leading browser. Writing JavaScript for today’s Web requires balancing the ideal and the practical. As a way forward, we can push