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.
Let's think of an analogy
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.
TRAVERSING THE DOM
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:
- the
document
is theroot node
and it has two children: theDOCTYPE declaration
and thehtml
- the
html
is the parent ofhead
andbody
- the
body
is the parent ofh2
,p
,h2
andul
- the
ul
is parent to theli
s
Also, let's remember that everything in an HTML document is considered a node and we have:
- the Document, which in itself is
a node
- HTML elements, which are
Element Nodes
- the text inside the HTML elements, which are
Text Nodes
- comments, which are
Comment Nodes
TRAVERSING BASED ON THE PARENT NODE RELATION
A 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: parentNode
and 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")[0];
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")[0];
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 <body></body>
.
TRAVERSING BASED ON THE CHILDREN NODES RELATION
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: childNodes
, firstChild
, lastChild
, children
, firstElementChild
and lastElementChild
.
An interesting situation
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
document
,element
andtext
. So, elements are just nodes of the typeelement
. 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 children
, firstElementChild
and lastElementChild
properties. So, to rewrite our example:
const bodyChildren = document.body.children;
console.log(bodyChildren);
► (4) [h2, p, h2, ul]
TRAVERSING BASED ON THE SIBLING NODES RELATION
A 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: previousSibling
, nextSibling
, previousElementSibling
and nextElementSibling
. Keep in mind that, just like in the case of child nodes, previousSibling
and nextSibling
select siblings of any type, whether previousElementSibling
and 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!
Header source image: Paul Esch-Laurent on Unsplash
Refrence articles:
Top comments (1)
Looking forward to the next part. thanks so much :D