In the last article we learned what the DOM is and that it can be manipulated (change the nodes inside it). We also learned about one way we can target DOM nodes using different selectors (if you didn't read the first article of the series, I recommend doing so before going any further). We basically learned how to make use of the built-in methods of the document object to access HTML elements by
class, id, tage name or query selectors.
Another way of targeting nodes is by traversing the DOM. Traversing simply means to
move throught the DOM using the relation between nodes (when having access to a certain DOM node, we can reach its related nodes). This means we can stay on the same DOM level (branch) while moving up, down or even sideways.
But why would we need to do that? We would think that using
document.querySelector() would be enough for every situation we encounter when trying to manipulate the DOM. Well, yes and no.
Imagine you are inside of your favorite book store, where the books in a series are sorted in ascending order, from left to right. You are looking at the first Harry Potter volume that's sitting on a shelf. You're missing the first two volumes and you would like to buy them, so after you found the first one, you know the second should be next to it. Now you have two possibilities to get the second volume:
- You get it directly from the shelf, since you can move a bit to the right and find it right away.
- You go to the cash register and ask for the person working there to look for the book in their system, tell you where in the store it's located and then go get it.
Which approach would be faster? The first one, of course. We found the second volume based on the position (relation) to the first one, instead of interogating the system (do a search by name). Based on this analogy, it will always be easier to move from one node to the other than doing a full search.
Before moving forward, let's write some simple HTML code so we can visualize our examples better:
<!DOCTYPE html> <html> <head> <title>Traversing the DOM is fun!</title> </head> <body> <h2>How to</h2> <p>If you want to learn how to traverse the DOM, you should continue reading this tutorial.</p> <h2>We can traverse the DOM based on the relation between:</h2> <ul id="nodesList"> <li>Parent nodes</li> <li>Children nodes</li> <li>Siblings nodes</li> </ul> </body> </html>
A real representation of the DOM based on the above HTML looks like bellow (if you want to generate real DOM trees, you can use this tool)
We can traverse the DOM based on the relation between
parent, children and sibling nodes. We have the first node in the tree which is called the
root node. This is the only node that doesn't have a parent since it's positioned on the highest level. Every other node has exactly one parent and can have any number of children. We call nodes with the same parent sibling nodes.
In our example above, the simplest parent-child relationship between nodes is:
root nodeand it has two children: the
DOCTYPE declarationand the
htmlis the parent of
bodyis the parent of
ulis parent to the
Also, let's remember that everything in an HTML document is considered a node and we have:
- the Document, which in itself is
- HTML elements, which are
- the text inside the HTML elements, which are
- comments, which are
parent node is any node that is one level above another node, or closer to the document node in the DOM hierarchy. There are two ways to get the parent of a node:
parentElement. So, in our example, if we would want to get the parent node of the first
h2, we would say:
// first we target the h2 tag const headerTwo = document.getElementsByTagName("h2"); const parent = headerTwo.parentNode; console.log(parent); ► (1) [body]
What we are going to get is the
<body></body> tag, since it's one level above the
<h2></h2> tag. If we want to go even one more level up, we can chain multiple
parentNode properties, like so:
const headerTwo = document.getElementsByTagName("h2"); const parent = headerTwo.parentNode.parentNode; console.log(parent); ► (1) [html]
Now we 'll be targeting the
<html></html> tag since it's one level above the
The children of a node are considered to be all nodes that are positioned one level bellow that node (that is one level of nesting).
The properties that help us target children nodes are:
If we would want to target the children of the
<body></body> element in our example, we would probably say:
const bodyChildren = document.body.childNodes; console.log(bodyChildren);
I assume you would expect to see a list of four items printed to the console.
► (4) [h2, p, h2, ul]
Instead you will see this:
► (9) [text, h2, text, p, text, h2, tex, ul, text]
This is happening because the
childNodes property is targeting all nodes, including text nodes. In our example, the indentation between the HTML lines of code are interpreted as text.
I think now is the perfect time to clarify what
nodes (which were targeted by the childNodes property) and
elements (h2, p, h2, ul, which we were expecting to see in the console) are.
A node is any object represented in the DOM. Nodes can be of multiple types but the ones we are going to encounter most often are
text. So, elements are just nodes of the type
element. A complete list of all node types can be found here.
Coming back to out example, where instead of four nodes we got back nine, this is because
childNodes selects all node types (including text), not only the
element ones, which we were interested in.
If we want to select only the children of the type
node element, we can use the
lastElementChild properties. So, to rewrite our example:
const bodyChildren = document.body.children; console.log(bodyChildren); ► (4) [h2, p, h2, ul]
sibling node is any node that is on the same DOM level with any other node. In our example,
h2, p, h2 and ul are all siblings since they are on the same DOM level. The property we can use to select sibling nodes are:
nextElementSibling. Keep in mind that, just like in the case of child nodes,
nextSibling select siblings of any type, whether
nextElementSibling will only select nodes of type element.
So, if we want to select the second
<li> element in the
<ul>, we need to write:
// we target the list based on its id const list = document.getElementById("nodesList"); // we target the first node of element type const firstElement = list.firstElementChild; // we use nextElementSibling to get to the second list item const thirdElement = firstElement.nextElementSibling; console.log(thirdElement ) ► (1) [li]
If instead we would have used the
nextSibling property, the return value would have been a
text node since the indentation in the HTML is considered text, so we would have targeted white space (a node of type text) instead of the next list item element.
const thirdElement = firstElement.nextSibling; console.log(thirdElement ) ► (1) [text]
Now that we know how to traverse the DOM, in the next article we will focus on how we can actually change things in the DOM, with examples. See you next time!