How to traverse the DOM in Javascript

“pink sakura tree at day time” by Faye Cornish on Unsplash

The Document Object Model, or shortly the DOM, serves as a reference for the browser when placing elements on the web page. The locations where the elements are placed in the DOM are called Nodes, and on the web page, it’s not that only HTML elements get their node, but also the attributes of the HTML elements have their nodes (attribute nodes), every piece of text has its node (text nodes), and there are many other node types. The structural relation of these nodes reflects the structure of the HTML document. Because of that, we can define the relations between the elements on the page as the relations between their nodes in the DOM.

When we are manipulating the elements on the web page with a programming language, such as JavaScript, we are doing that through their DOM nodes. By accessing a DOM node of a given element we can manipulate its properties, such as position, appearance, content, behavior, etc. Often we want to perform actions on the elements that have some kind of relation between them, known as related nodes. In order to do that, we must have a way of moving from one node to the other nodes, that is, the way of traversing the DOM.

Having the access to a certain node in the DOM, there are ways to traverse through the DOM using its related nodes. We can move up or down the DOM tree, or we can move sideways staying at the same DOM level. In this article, we will take a look how can we access the related DOM nodes using the JavaScript programming language.

Relations between DOM nodes

First, let’s see what relations between elements exist so that we can later better understand the techniques used to access them. HTML elements are nested within each other creating a tree like structure. There may be many levels of nesting elements and all of that reflects in the DOM tree of element nodes.

Descendant and ancestor elements

This relation is of no practical use, but it will help us with expressing some element relations clearer.

One element may have many levels of other elements nested under it, and all of those nested elements in all of the nesting levels are called the descendant elements of our starting element. For example, let’s have a main element for the main content of the page with the following content:

At the first level of nesting there are <h1> element and two <article> elements. Then, at the second nesting level we have <h2> and <section> elements, and finally in the third level there are <p> elements inside <section> elements. All of these elements are descendants of the <main> element.

Given that said, the <main> element is their ancestor element, the element that they belong to in the DOM tree.

But descendant/ancestor relationship can be seen between other elements in this example too. For instance, the <article> elements are ancestors for their nested <h2>, <section> and <p> elements, and those elements are, in turn, its descendant elements. The same relation applies to the<section> and <p> elements.

Here, we have to point out one important thing. For example, the heading “How to grow Bonsai” is not a descendant of the <article id=”article-1">, neither that article is the ancestor of the mentioned heading. The reason for that is because the “How to grow Bonsai” heading is not nested within the first article, but rather under the second article. Therefore, they do not have descendant/ancestor relationship between them. The same applies to <section> and <p> elements, they are descendants of the article element under which they are nested, and that article element is their ancestor.

Parent and Child elements

The special, and very useful, descendant/ancestor relationship is the case where the elements are direct descendants or ancestors of the given node. The ‘direct’, means that they are just one nesting level away from the given node.

The parent node (element) is the closest ancestor element to the given element. If we select heading “How to grow Bonsai”, the first ancestor element (one level up) is the <article id=”article-2"> element, and we call it a parent of the given heading. The <article> elements share the same parent, they are both nested under the <main> element which is their parent. Note that <h1> has the <main> element as its parent too. The paragraph “What do we do now!?” has as its parent the <section> element under the first article element.

Opposite to the parent is a child element, but while the element can have only one parent element, it can have many child elements under it. The child elements are all the direct descendant elements (one level down) of the given element. Children of the <main> element are <h1> and both <article> elements and no other elements. Child elements of the second article are “How to grow Bonsai” heading and the <section> nested under it. The paragraph in that section is not a child element of the second article element, it is a child of the <section> element.

Another important thing to note here is that every bit of text in HTML is presented with a text node in the DOM. Given that said, the heading in the first article has one text node as its child, a node containing the text “First Contact with Alien Beings in History of Mankind”, and the heading element is the parent of that text node, and the heading in the second article is the parent of the child text node with text “How to grow Bonsai”.

Sibling elements

Two or more elements are siblings if they have the same element as their parent. In our example, <h1> and both <article> elements are siblings because they have the same parent, the <main> element. The <p> elements in the first article are siblings because their parent is the <section> element in the first article. But the <p> element in the second article is not a sibling of the <p> elements in the first article because they do not all share the same parent even though they are at the same level of nesting.

Traversing the DOM via the related nodes

Finally, we will see how we can use the relations between the nodes to traverse through the DOM tree. A node in the DOM tree is represented with a Node object, and the Node object has properties that allow us to get to the related nodes of a given node.

We will add some id’s and classes in our example HTML so we can better access elements in the DOM tree:

Finding the parent node of a given node

If we have a Node object that is a reference to a node in the DOM, to get its parent node we can use the parentNode property on the node. Since node is an object and parentNode is a property, we can use the ‘dot’ notation to access the parent element of the node like this:

const parent = node.parentNode;

Let’s find the parent of the first article node in our example HTML.

Now, let’s find the parent of the heading “How to grow Bonsai”.

We can use the parent node of a given node to get all the ancestors of the given node up in the DOM tree. For example:

The grandParent node could also be obtained by chaining the parentNode property on the bonsai node:

const grandParent = bonsai.parentNode.parentNode;

The parentNode is often used to remove a given node from the DOM. Say we want to remove the first article from the document since it’s too distressing. We would do it like this:

The parent of a HTML element can be an Element node, a Document node or a DocumentFragment node. The parentNode property of a node can return null in cases where it is applied to the Document and DocumentFragment nodes because they can never have parent nodes. If a node is just created but it is not attached to the DOM, applying the parenNode on it will also return null.

One more thing to notice is that the parentNode property is a read-only property, meaning that it is not possible to do something like this:

Finding the child nodes of a given node

To get all child nodes of a node we can use its childNodes property. For example:

const children = node.childNodes;

The result is a node list, the list of objects that each represent one child node of our node. The node list is similar to the arrays in a way that it is possible to loop through the list using array indexes. For example, let’s print the nodeName property of all the children in a node list.

Let’s use a different HTML to explain some important implications of using childNodes property:

If we apply the previous javascript code to print out the node names of child nodes from the <ul> element, we will get this result:

Not quite as one could expect. You could think that there will only be printed out six li element names, but instead we get seven more text nodes.

The reason for this is that the node list also contains text nodes and comment nodes so we must take care when using this property. This is happening because in HTML a new line is treated as whitespace, i.e. the text node. So, the line breaks and white space between HTML tags add the text nodes to the node list of children.

To avoid this we could reorganize our HTML structure like this:

Now we get the expected output:

The node list is a live list of nodes. That means that adding or removing child elements to the node will update the list, we don’t have to fetch it again. That also means that if there are changes to the number of elements in the node list, the for loop used like this will fail:

This code will result in an error when it comes to the sixth iteration: TypeError: children[i] is undefined. It is because the childrennode list has changed with removing the third item. To avoid this, we should update the len variable in every iteration, or better, use children.length in the loop conditions instead of len as we did.

If we want to get only HTML elements as children of a node we can use the children property instead of childNodes property:

const childElements = list.children;

The ‘special’ children

Well, often in families there are favorites among the children, the ‘special’ ones. The same is with the node family, there are children that are special. What children are those? — you may ask. And you probably guessed, those are the first one and the last one. :)

This analogy is not a pointless joke. In the DOM tree, a node with child nodes has defined properties firstChild and lastChild. They are used to quickly find the first and the last child node under the given node. In our example the first child will correspond to the first list item, and the last child will correspond to the sixth list item. Here is how we can obtain those elements:

It is important to note that the firstChild and lastChild also treat the line breaks as text nodes and in the case of our first list (one with the line breaks) they would result in the text nodes rather than the list items.

Finding siblings of a node

When we have the access to a node, we can access its sibling nodes using the nextSibling and previousSibling properties.

Property nextSibling will get the sibling node that immediately follows the given node. The syntax is the following:

const next = node.nextSibling;

Let’s find the next sibling of the item with the id=”three”:

We can now proceed to go through the siblings that follow, step by step:

When we get to the last sibling in the parent node, using the nextSibling will return null because there are no more siblings after the last child node:

Property previousSibling will get the sibling node that immediately precedes the given node. The syntax is analogous to the syntax for nextSibling:

const previous = node.previousSibling;

Let’s find the previous sibling of the item with the id=”three”:

We can now proceed to go through other previous siblings, step by step:

When we get to the first sibling in the parent node, using the previousSibling will return null because there are no siblings before the first child node:

Final example

Now, let’s do one example that requires more complex traversal than we have shown so far in our previous examples. Let’s again use the first HTML example structure:

Say we have the access to the heading in the first article (<h2 class=”sensations”>) and we want to read the heading text from the next article.

Oops, wrong node! Remember that the line breaks are treated as white space and that white space is presented with a text node. So, how can we avoid this?

We could use a loop that goes through the next siblings and that checks if the node is an element node rather than a text or any other node. To check if a node is an element node we can check a node property called nodeType. The nodeType property for an element node will have the value ELEMENT_NODE which is called a constant or it can have numeric value of 1, you may check for either one. To see all other node types and their values you can check out this article.

It is best to use while loop for this task. Let’s change the previous code and add a while loop and the check for the node type.

Now we have found the next article. Finally, we want to get the heading under it. Since we know that the heading is the first element node under the article node we could think of using the firstChild property on the nextNode node, but the problem with the text nodes will come up again. To avoid this, we can use the loop again to check the type of the node, or we can use querySelector on nextNode to get the first h2 element, and, probably, this second approach might be the safest to use. But, for the sake of practice, let’s use the children property of the node object that we briefly mentioned earlier.

The children property will return the list child element nodes, and then we can just select the first one from the list. Here is the complete code:

Conclusion

Taking a closer look at the last example, one may ask why bother with all this traversing trough the related nodes when we can just do a simple query like this:

const secondH2 = document.querySelector('.horticulture');

While that is true in this simple case, remember that we explicitly wanted to get to the next article’s heading, and that in a real website the page can be much larger, and the content can be nested much deeper. Also, the content could be generated automatically and we do not know what classes will be available to us. So, we would have to do a full query of the DOM for every next heading. But by using the related siblings we are able to be around the nodes that are of interest to us and not to query the whole DOM tree every time, therefore increasing performance when we have a very large web page.

To use this effectively on the large web page with many articles, we can wrap it all into a function that accepts the current node as the parameter:

Notice that we added one more check nextNode.nodeName === ‘ARTICLE’ to the while loop condition, just to make sure we are getting the right type of the element, because it might happen that there are other siblings to the article elements which are not articles.

Now, we can call the function nextHeading on the second heading to try get the heading from the next article:

There are many use cases when you could use the related nodes to traverse the DOM. The method that you’ll use depend on the HTML structure and on your imagination. So, get to know the node family and their relations well, and they will help you a lot with DOM related tasks.

Thank you for reading and have fun finding your way through the DOM!