JavaScript: The Definitive Guide (4th Ed)

Using the Core DOM API

Now that we've studied the tree structure of documents and seen how the tree is composed of Node objects, we can move on to study the Node object and document trees in more detail. As I noted previously, the core DOM API is not terribly complex. The following sections contain examples that demonstrate how you can use it to accomplish common tasks.

Traversing a Document

As we've already discussed, the DOM represents an HTML document as a tree of Node objects. With any tree structure, one of the most common things to do is traverse the tree, examining each node of the tree in turn. Example 17-1 shows one way to do this. It is a JavaScript function that recursively examines a node and all its children, adding up the number of HTML tags (i.e., Element nodes) it encounters in the course of the traversal. Note the use of the childNodes property of a node. The value of this property is a NodeList object, which behaves (in JavaScript) like an array of Node objects. Thus, the function can enumerate all the children of a given node by looping through the elements of the childNodes[] array. By recursing, the function enumerates not just all children of a given node, but all nodes in the tree of nodes. Note that this function also demonstrates the use of the nodeType property to determine the type of each node.

<head>
<script>
// This function is passed a DOM Node object and checks to see if that node
// represents an HTML tag; i.e., if the node is an Element object. It
// recursively calls itself on each of the children of the node, testing
// them in the same way. It returns the total number of Element objects
// it encounters. If you invoke this function by passing it the
// Document object, it traverses the entire DOM tree.
function countTags(n) { // n is a Node
var numtags = 0; // Initialize the tag counter
if (n.nodeType == 1 /*Node.ELEMENT_NODE*/) // Check if n is an Element
numtags++; // Increment the counter if so
var children = n.childNodes; // Now get all children of n
for(var i=0; i < children.length; i++) { // Loop through the children
numtags += countTags(children[i]); // Recurse on each one
}
return numtags; // Return the total number of tags
}
</script>
</head>
<!-- Here's an example of how the countTags( ) function might be used -->
<body onload="alert('This document has ' + countTags(document) + ' tags')">
This is a <i>sample</i> document.
</body>

Example 17-1: Traversing the nodes of a document

Another point to notice about Example 17-1 is that the countTags( ) function it defines is invoked from the onload event handler, so that it is not called until the document is completely loaded. This is a general requirement when working with the DOM: you cannot traverse or manipulate the document tree until the document has been fully loaded.

In addition to the childNodes property, the Node interface defines a few other useful properties. firstChild and lastChild refer to the first and last children of a node, and nextSibling and previousSibling refer to adjacent siblings of a node. (Two nodes are siblings if they have the same parent node.) These properties provide another way to loop through the children of a node, demonstrated in Example 17-2. This example counts the number of characters in all the Text nodes within the <body> of the document. Notice the way the countCharacters( ) function uses the firstChild and nextSibling properties to loop through the children of a node.

<head>
<script>
// This function is passed a DOM Node object and checks to see if that node
// represents a string of text; i.e., if the node is a Text object. If
// so, it returns the length of the string. If not, it recursively calls
// itself on each of the children of the node and adds up the total length
// of the text it finds. Note that it enumerates the children of a node
// using the firstChild and nextSibling properties. Note also that the
// function does not recurse when it finds a Text node, because Text nodes
// never have children.
function countCharacters(n) { // n is a Node
if (n.nodeType == 3 /*Node.TEXT_NODE*/) // Check if n is a Text object
return n.length; // If so, return its length
// Otherwise, n may have children whose characters we need to count
var numchars = 0; // Used to hold total characters of the children
// Instead of using the childNodes property, this loop examines the
// children of n using the firstChild and nextSibling properties.
for(var m = n.firstChild; m != null; m = m.nextSibling) {
numchars += countCharacters(m); // Add up total characters found
}
return numchars; // Return total characters
}
</script>
</head>
<!--
The onload event handler is an example of how the countCharacters( )
function might be used. Note that we want to count only the characters
in the <body> of the document, so we pass document.body to the function.
-->
<body onload="alert('Document length: ' + countCharacters(document.body))">
This is a sample document.<p>How long is it?
</body>