How to traverse the DOM in JavaScript
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 children
node 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!