CodingBison

DOM provides a tree-like abstraction for representing HTML elements. Therefore, to assist navigation of nodes on a tree, DOM provides a set of properties or handles.

Let us begin with a handful of DOM properties that we can use for traversing DOM tree. First, we provide the signature of these APIs:

 // Get the parent node. 
 var elems = currentNode.parentNode;    

 // Get an array that holds child nodes (but not grand-child nodes).
 var elems = currentNode.childNodes;     

 // Check if a node has child nodes. 
 var hasChildNodes = currentNode.hasChildNodes();

 // Get next/previous sibling nodes. 
 var elem = currentNode.nextSibling;     
 var elem = currentNode.previousSibling; 

 // Get first/last child nodes. 
 var elem = currentNode.firstChild;
 var elem = currentNode.lastChild;

The above properties start at a given node (we name it currentNode below) and allow us to get handle of various neighboring nodes. We can get a handle of the parent node (as currentNode.parentNode), or to get a handle (array) of all the child nodes (as currentNode.childNodes). The childNodes carry only the immediate children of the currentNode; thus, if a child of the currentNode were to have its own child nodes, then calling currentNode.childNodes would not return those grand child nodes. Lastly, if needed, we can use hasChildNodes() method to check if the current node has any children or not.

For traversal, DOM provides properties to get handle of the first child node (currentNode.firstChild) or the last child node (currentNode.lastChild). Equally important, DOM provides handles to go to the next sibling node (currentNode.nextSibling) or to the previous sibling node (currentNode.previousSibling); siblings are nodes that sit at the same level.

Let us get started with examples that throw light on these traversal-based DOM properties. For that, we start with a trivial HTML page that contains a paragraph node (tag "p"), an ordered list (tag "ol" for the entire list and tag "li" for the child items), a script node (tag "script"). The script element holds a source file "./dom_traversal.js" that contains the JavaScript code for traversing DOM elements. Here is the HTML page.

 <!doctype html>
 <html>
 <body>
 <!-- This is a simple HTML page --> 

 <p> A wizard's To-Do list: </p>

 <ol>
 <li>Practice Spells</li>
 <li>Visit Bilbo Baggins</li>
 <li>Repair broken staff</li>
 </ol> 

 <div id="idDiv"></div>

 <script type="text/javascript" src="dom1_traversal.js"> </script>
 <body>
 </html>

Next, we provide the contents of the "./dom_traversal.js" file.

This file (provided below) uses rootNode as the root of the HTML page, which for JavaScript is the document object itself; the HTML page or the htmlNode is a child node of this rootNode. Next, we retrieve the childNodes for the htmlNode; it has two children: the head node and the body node. Note that even if we do not explicitly define the <head> or <body> tags, the DOM would still have these two handles! Once we have handle of these elements, we use a simple function printChildrenInfo() to print the nodeName and nodeType properties of each node. Lastly, we also use the parentNode property of the bodyNode to get a handle of the html node and after that, we reprint the html node.

 // Get a handle of the div element.
 var elem = document.getElementById("idDiv");

 // Print information about all child nodes.
 function printChildrenInfo(obj, description) {
     elem.innerHTML += "<br>Number of children for " + description + ": " + obj.length;
     for (var x in obj) {
         if (obj[x].nodeType == undefined) { continue;}
         elem.innerHTML += "<br>" + obj[x];
         elem.innerHTML += ", nodeName: " + obj[x].nodeName;
         elem.innerHTML += ", nodeType: " + obj[x].nodeType;
     }
     elem.innerHTML += "<br>";
 }

 // Get a handle of the root node.
 rootNode = document;
 printChildrenInfo(rootNode.childNodes, "Root");

 // Get a handle of the html node.
 htmlNode = rootNode.childNodes[0];
 printChildrenInfo(htmlNode.childNodes, "HTML");

 // Get a handle of the head node.
 headNode = htmlNode.childNodes[0];
 printChildrenInfo(headNode.childNodes, "Head");

 // Get a handle of the body node.
 bodyNode = htmlNode.childNodes[1];
 printChildrenInfo(bodyNode.childNodes, "Body");

 // Get a handle of the parent of the body node (which is htmlNode).
 htmlNode = bodyNode.parentNode;
 printChildrenInfo(htmlNode.childNodes, "HTML (using bodyNode.parentNode)");

When we load this page, the output shows various elements of HTML page. The output shows that the HTML page has one root node that, in turn, has one child node: the HTML node. Next, the HTML node has two child nodes: the head node and the body node; as noted earlier, even though we did not add the <head> tag in the HTML page, we do not. And, understandably, the head element has zero child nodes. However, when we look at the bodyNode, we find that it has several elements: text elements, paragraph element, ordered-list element, script element, and the (dynamically added) div element.

 A wizard's To-Do list:
     1. Practice Spells
     2. Visit Bilbo Baggins
     3. Repair broken staff

 Number of children for Root: 1
 [object HTMLHtmlElement], nodeName: HTML, nodeType: 1, hasChildNodes: true

 Number of children for HTML: 2
 [object HTMLHeadElement], nodeName: HEAD, nodeType: 1, hasChildNodes: false
 [object HTMLBodyElement], nodeName: BODY, nodeType: 1, hasChildNodes: true

 Number of children for Head: 0

 Number of children for Body: 10
 [object Text], nodeName: #text, nodeType: 3, hasChildNodes: false
 [object Comment], nodeName: #comment, nodeType: 8, hasChildNodes: false
 [object Text], nodeName: #text, nodeType: 3, hasChildNodes: false
 [object HTMLParagraphElement], nodeName: P, nodeType: 1, hasChildNodes: true
 [object Text], nodeName: #text, nodeType: 3, hasChildNodes: false
 [object HTMLOListElement], nodeName: OL, nodeType: 1, hasChildNodes: true
 [object Text], nodeName: #text, nodeType: 3, hasChildNodes: false
 [object HTMLDivElement], nodeName: DIV, nodeType: 1, hasChildNodes: true
 [object Text], nodeName: #text, nodeType: 3, hasChildNodes: false
 [object HTMLScriptElement], nodeName: SCRIPT, nodeType: 1, hasChildNodes: true

 Number of children for HTML (using bodyNode.parentNode): 2
 [object HTMLHeadElement], nodeName: HEAD, nodeType: 1, hasChildNodes: false
 [object HTMLBodyElement], nodeName: BODY, nodeType: 1, hasChildNodes: true

In the above output, we find that some of the nodes are of type "#text". These nodes are essentially white-space or newlines that are present after various elements. For example, the body node starts with a white-space line; this is shows as the first "#text" node. Next the comment node is followed by another new line; this is again shows as a "#text" node. Same is true for other "#text" child nodes. We can easily modify the above example to skip the "#text" records. For this, the, the for/in loop in the printChildrenInfo() function can add a continue for the "#text" records: 'if (obj[x].nodeName == "#text") { continue;}'.

Now that we have seen how to get handles for parent node and child nodes, let us look at other traversal properties that allow us to walk the DOM tree. For this, we rewrite the "./dom_traversal.js" script and we provide the code below. This time, we focus on the ordered list node of the body node and walk through its child nodes in both forward and reverse directions. For forward direction, we get a handle of the first child node of the ordered list (using the firstChild property) and then use nextSibling to go over all the elements using a while loop. For reverse direction, we get a handle for the last child node of the ordered list (using the lastChild property) and then use previousSibling to go over all the elements till we reach the first element.

 // Get a handle of the div element.
 var elem = document.getElementById("idDiv");

 // Print node information. 
 function printNodeInfo(node) {
     elem.innerHTML += node;
     elem.innerHTML += ", nodeName: " + node.nodeName;
     elem.innerHTML += ", nodeType: " + node.nodeType;
     elem.innerHTML += ", innerHTML: " + node.innerHTML;
     elem.innerHTML += "<br>";
 }

 // Get a handle of the ordered-list node.
 orderedlistNode = document.getElementsByTagName("ol")[0];

 // Get a handle of the first child of the ordered-list node.
 node = orderedlistNode.firstChild;
 while (node) {
     elem.innerHTML += "[Loop (forward)] ";
     printNodeInfo(node);
     node = node.nextSibling;
 }
 elem.innerHTML += "<br>";

 // Get a handle of the last child of the ordered-list node.
 node = orderedlistNode.lastChild;
 while (node) {
     elem.innerHTML += "[Loop (reverse)] ";
     printNodeInfo(node);
     node = node.previousSibling;
 }

When we load the page, we find that when using the forward loop, elements of ordered list are printed in the order in which they appear on the page. On the other hand, when we use the reverse loop, elements are printed in the reverse order.

 A wizard's To-Do list:
     1. Practice Spells
     2. Visit Bilbo Baggins
     3. Repair broken staff

 [Loop (forward)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined
 [Loop (forward)] [object HTMLLIElement], nodeName: LI, nodeType: 1, innerHTML: Practice Spells
 [Loop (forward)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined
 [Loop (forward)] [object HTMLLIElement], nodeName: LI, nodeType: 1, innerHTML: Visit Bilbo Baggins
 [Loop (forward)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined
 [Loop (forward)] [object HTMLLIElement], nodeName: LI, nodeType: 1, innerHTML: Repair broken staff
 [Loop (forward)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined

 [Loop (reverse)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined
 [Loop (reverse)] [object HTMLLIElement], nodeName: LI, nodeType: 1, innerHTML: Repair broken staff
 [Loop (reverse)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined
 [Loop (reverse)] [object HTMLLIElement], nodeName: LI, nodeType: 1, innerHTML: Visit Bilbo Baggins
 [Loop (reverse)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined
 [Loop (reverse)] [object HTMLLIElement], nodeName: LI, nodeType: 1, innerHTML: Practice Spells
 [Loop (reverse)] [object Text], nodeName: #text, nodeType: 3, innerHTML: undefined

Note that innerHTML attribute is for text-based attributes. This attribute is not valid for most of the above elements (image, checkbox, and input buttons).

In the above code, we use a while-loop to traverse all the child nodes; in general, a tree structure is a convenient structure for using loops to traverse elements. As an additional example, we could use a while-loop that allows us to traverse upwards from a node to all of its parents; the chain of parents begins with the immediate parent, followed by the parent of the parent, and so on. This can be written as follows where we start with the first child of the ordered-list and then navigate upwards till we reach the root of the tree!

 // Get a handle of the first child of the ordered-list node.
 node = orderedlistNode.firstChild;
 while (node) {

     // Code to do something useful!

     // Now, make node point to its parent node. 
     node = node.parentNode;
 }




comments powered by Disqus