27.4. Manipulating Documents with the DOMThe majority of your DOM Scripting work will likely center around reading from and writing to the document. But before you can do that, you need to understand how to traverse the node tree. 27.4.1. Finding Your Way AroundThe DOM offers many ways to move around and find what you want. As any HTML or XML document is essentially a collection of elements arranged in a particular hierarchy, we traverse the DOM using the elements as markers . In fact, a lot of what we do in the DOM is akin to wayfinding: using elements (especially uniquely id-ed ones) as signposts, which let us know where we are in our documents and help us get deeper and deeper into our documents without losing our way. Let's take the following snippet of (X)HTML, for example: <div > <h1>This is a heading</h1> <p>This is a paragraph.</p> <h2>This is another heading</h2> <p>This is another paragraph.</p> <p>Yet another paragraph.</p> </div> If you wanted to find the H2 in this snippet, you would need to use two of JavaScript's interfaces to the DOM, getElementById( ) and getElementsByTagName( ): var the_div = document.getElementById( 'content' ); var h2s = the_div.getElementsByTagName( 'h2' ) var the_h2 = h2s[0]; In the first line, you use the DOM to find an element on the page with an id equal to content and assign it to the variable the_div.
Once you have your container div assigned to the_div, you can proceed to find the h2 you want, using getElementsByTagName( ) (line 2). That method returns an array of elements (H2s), referred to as a collection. Finally, you know that the desired h2 is the first one in that collection and, as Chapter 26 showed, the first element in an array has an index of 0, therefore, the h2 we want is h2s[0]. There's a shorter way to write all of this, too: var the_h2 = document.getElementById( 'content' ).getElementsByTagName( 'h2' )[0]; On a side note, if you ever want a collection of all of the elements in a given document, you can use the universal selector (*) in combination with the getElementsByTagName( ) method: var everything = document.getElementsByTagName( '*' ); Although not a horribly efficient means of collecting information from a page, this approach can be useful in certain instances. The methods getElementById( ) and getElementsByTagName( ) are both quite useful, but sometimes you need to be able to move around without knowing the id, or even the type of the element you are accessing. For this reason, there are numerous properties available to move about the document easily: parentNode, firstChild, lastChild, nextSibling, and previousSibling. Each does exactly what you would expect: allows you to access any element filling the specified role. To access the first paragraph after the h2, for instance, you could write: var the_el = document.getElementById( 'content' ).getElementsByTagName( 'h2' )[0].nextSibling; Another property available to you is childNodes, which is a collection of the element's children (element and text nodes). In certain instances, it may also be useful to test for child nodes before attempting to access them programmatically, using the hasChildNodes( ) method. If you are moving around in the DOM using parentNode, nextSibling, and the like and want to know more information about the element you are targeting, there are a few properties available to you to provide more information. The first is nodeType, which, as you would expect, returns the type of node you are targeting. There are three commonly used nodeTypes, which are numbered:
You can also find the name of the node using the property nodeName. In the case of element and attribute nodes , nodeName is the element name or attribute name, respectively. Even if you are using XHTML, which requires lowercase tag and attribute names, the value returned by nodeName may be in uppercase, so it is considered a best practice to consistently convert the returned value to either upper- or lowercase when using it in a comparison. Using the (X)HTML example from earlier: <div > <h1>This is a heading</h1> <p>This is a paragraph.</p> <h2>This is another heading</h2> <p>This is another paragraph.</p> <p>Yet another paragraph.</p> </div> we could trigger an alert( ) using nodeType and nodeName whenever an element node is encountered, letting us know what it is: var the_div, children, node, name; the_div = document.getElementById( 'content' ); if( the_div.hasChildNodes( ) ){ children = the_div.childNodes; for( node in children ){ if( children[node].nodeType == 1 ){ name = children[node].nodeName.toLowerCase( ); alert( 'Found an element named "'+name+'"' ); } } } else { return; } As you can see, there are many ways to "walk" the DOM: the direct route of getElementById( ), the meandering path of getElementsByTagName( ), and the step-by-step method of parent, child, sibling traversal. There are also numerous tools at your disposal to aid in orienting yourself, such as id attributes, but also node types and names. 27.4.2. Reading and Manipulating Document StructureOnce you can find your way around the DOM, it is easy to collect and alter the content of elements on the page. The content you collect can be in the form of other elements, attribute values, and even text content. The primary means of doing this is by using what are sometimes referred to as the "getters and setters" of the DOM: innerHTML, nodeValue, getAttribute( ), and setAttribute( ). 27.4.2.1. innerHTMLWhen compared to the surgical precision of other DOM methods and properties, innerHTML has all the subtlety of a sledgehammer. Originally part of the Internet Explorer DOM (i.e., not part of the W3C DOM), but now widely supported, this element property can be used to get and set all of the markup and content within the targeted element. The main problem with using innerHTML to get content is that the collected content is treated as though it is a string, so it's pretty much only good for moving large amounts of content from one place to another. Using the example above, you could collect all of the contents of the content div by writing: var contents = document.getElementById( 'content' ).innerHTML; Similarly, you could replace contents of the div by setting its innerHTML equal to a string of text that includes HTML: var contents = 'This is a <em>new</em> sentence.'; document.getElementById( 'content' ).innerHTML = contents; It is also possible to append content to an element using innerHTML: var div = document.getElementById( 'content' ).innerHTML; div.innerHTML += '<p>This is a paragraph added using innerHTML.</p>'; 27.4.2.2. nodeValueAnother property you can use to get and set the content of your document is nodeValue. The nodeValue property is just what it sounds like: the value of an attribute or text node. Assuming the following (X)HTML snippet: <a href="http://www.easy-designs.net">Easy Designs</a> you could use nodeValue to get the value of the text node in the link and assign it to a variable named text: var text = document.getElementById( 'easy' ).firstChild.nodeValue; This property works in the other direction as well: document.getElementById( 'easy' ).firstChild.nodeValue = 'Easy Designs, LLC'; In the above example, we set the text of the link equal to Easy Designs, LLC, but we could just as easily have used concatenation to add the , LLC to the text: document.getElementById( 'easy' ).firstChild.nodeValue += ', LLC'; 27.4.2.3. getAttribute()/setAttribute( )You can collect the value of an element's attributes using the getAttribute( ) method. Assuming the same (X)HTML as the example above, you could use getAttribute( ) to collect the value of the anchor's href attribute and place it in a variable called href: var href = document.getElementById( 'easy' ).getAttribute( 'href' ); The value returned by getAttribute( ) is the nodeValue of the attribute named as the argument. Similarly, you can add new attribute values or change existing ones using the setAttribute( ) method. If you want to set the href value of a specific page on easy-designs.net, you could do so using setAttribute( ): var link = document.getElementById( 'easy' ); link.setAttribute( 'href', 'http://www.easy-designs.net/index.php' ); You could also add a title to the link using setAttribute( ): link.setAttribute( 'title', 'The Easy Designs, LLC homepage' ); This brings us to our next topic: creating document structure using the DOM.
27.4.3. Creating Document StructureJavaScript has a host of methods available for creating markup on the fly. We've already seen that setAttribute( ) can be used to add new attributes, in addition to modifying existing ones, but by using createElement( ) and createTextNode( ) methods, we can do so much more. 27.4.3.1. createElement( )As you'd expect, the createElement( ) method (which is used on the document object) creates and returns a new element. To build a div for example, do the following: var new_div = document.createElement( 'div' ); This assigns your newly created element to the variable new_div. To actually see the newly created div on the page, we should probably put some content in it.
27.4.3.2. createTextNode( )Using the createTextNode( ) method (also on the document object), we can generate a text node to attach to our newly created div. Let's assign the new text node to the variable text: var text = document.createTextNode( 'This is a new div' ); We now have two newly created nodes, but they aren't connected. To do that, we need to use the DOM to make the text node a child of the div. We can accomplish this in a number of ways. 27.4.3.3. appendChild( )The most common means of making one node a child of another is to use the appendChild( ) method. new_div.appendChild( text ); appendChild( ) is a method available to any element node, and it takes only a single argument: the node you want to insert. With appendChild( ), you can also skip the intermediate step of assigning the text node to a variable, and directly append the new text node to the div: new_div.appendChild( document.createTextNode( 'This is a new div' ) ); Of course, this only puts those two nodes together, so we still need to put our div into the body of the document to have it show up in the browser. Using appendChild( ), we can add the div to the body of the page, but appendChild( ) simply does what it says: appends the argument to the target element. The div would become the last element in the body. What if we wanted our new div to be the first element in the body?
27.4.3.4. insertBefore( )Well, in that case, we can use the insertBefore( ) method. var body = document.getElementsByTagName( 'body' )[0]; body.insertBefore( new_div, body.firstChild ); insertBefore( ) takes two arguments: the first is the node you want to insert, and the second is the node you want to insert it in front of. In our example, we are inserting the new div in front of the firstChild of the body element. 27.4.3.5. replaceChild( )Let's suppose that instead of inserting our new div before the firstChild of the body element, we wanted it to replace the firstChild. To do so, we could use the replaceChild( ) method: body.replaceChild( new_div, body.firstChild ); Like insertBefore( ), replaceChild( ) takes two arguments: The first argument is the node you want inserted in the place of the node that is the second argument. 27.4.3.6. removeChild( )Because we're on the topic of node manipulation, we should take a look at the removeChild( ) method as well. Using removeChild( ), we can remove a single node or an entire node tree from the document. This method takes a single argument: the node you want to remove. Suppose we wanted to remove the text node from our div. We could accomplish that easily using removeChild( ): div.removeChild( div.firstChild ); We could even use removeChild( ) to delete the entire body from the page, which would not be a good thing, but demonstrates its power: body.parentNode.removeChild( body ); 27.4.3.7. cloneNode( )One final method available to you when working with DOM nodes is cloneNode( ). Using this powerful method, you can replicate an individual node (by supplying the method with an argument of false) or the node and all of its descendant nodes (by supplying it with an argument of TRue). Here is an example of cloneNode( ) in use: var ul = document.createElement( 'ul' ); var li = document.createElement( 'li' ); li.className = 'check'; for( var i=0; i < 5; i++ ){ var new_li = li.cloneNode( true ); new_li.appendChild( document.createTextNode( 'list item ' + ( i + 1 ) ) ); ul.appendChild( new_li ); } The benefits may not seem immediately apparent by looking at this example, but there is a major benefit in performance: cloning a node is a much faster process than building a new one from scratch. |