Document Object Model (DOM)
When using JavaScript in the browser, the "document" object gives us access to the page elements and allows us to change or create anything on the page. For example, you might want to change the background color of the body tag to green using JavaScript:
<!DOCTYPE html>
<html>
<head>
<title>Sytech Tutorials</title>
</head>
<body>
<script>
// change the background color of the body to green via the document object
document.body.style.background = "green";
</script>
</body>
</html>
There is a reason why we put the script in the body instead of the head, we will talk about that later.
The DOM tree
The fundamental aspect of the Document Object Model (DOM) is that every HTML tag is an object. Nested tags are called "children" of the enclosing tag, whilst the enclosing tag itself is called the "parent". The "children" and "parent" tags altogether make up the DOM tree in which all these objects are accessible via JavaScript. Surprising as it may seem, even the text inside a tag is also an object.
Now let us look at an example of a DOM tree:
<!DOCTYPE Html>
<html>
<head>
<title>Example of the DOM</title>
</head>
<body>
<p>
<h1>This is a heading inside a paragraph</h1>
<h2>Sub heading here</h2>
</p>
<ol>
<li>one</li>
</ol>
</body>
I'm out
</html>
The HTML tags are called elements, short for element nodes. Now, as you can see in the code above:
- the
<html>
node is the root of the document - the
<head>
and<body>
nodes are children of the<html>
element. Since they are on the same level, they are siblings to each other. - the
<head>
node is the parent to the<title>
node. - the
<title>
node is a child of the<head>
element. - a text node contains only a string and cannot have children. It is therefore always a leaf to the DOM.
Surprisingly, if we add some text after the closing </body>
tag, that text is automatically moved into the <body>
, at the end. You might have seen that on the above example we added the "I’m out" string outside the body. Run the code and you will see that the "I’m out" text is also displayed, as if its in the body. This is inline with the HTML spec which requires that all content must be inside the <body>
.
The diagram below is a graphical representation of the DOM (sibling nodes are joined in dashed line).
Traversing the DOM
In order to be able to manipulate the elements and contents of the DOM we need to be able to access the corresponding DOM object, assign it to a variable, and then modify it through the variable. Every DOM object node is accessed through the document
object.
The top tree nodes are accessed directly as document
properties:
- the
<html>
node is accessed asdocument.documentElement
- the
<body>
node is accessed asdocument.body
- the
<head>
node is accessed asdocument.head
A script cannot access an element that still doesn’t exist at its moment of execution. Which means if a script is inside <head>
, then document.body
is unavailable, because the browser starts by reading the <head>
and executing the script inside before reading the <body>
.
Lets show this with an example:
<html>
<head>
<script>
document.write( "<br>From the HEAD : " + document.body );
</script>
</head>
<body>
<script>
document.write( "<br>From the BODY : " + document.body );
</script>
</body>
</html>
As you can see from the example above, trying to access the document.body
from the <head>
returned null
but trying to access it from the <body>
returned [object HTMLBodyElement]. A null
value means "the node doesn’t exist" or as in this case "the node doesn’t exist yet".
Child nodes and descendant nodes
Child nodes are elements that are the first direct descendants of a referred node. For as we already mentioned above, <head>
and <body>
are children of <html>
element.
Descendants are all elements that are nested in the given element, i.e children, their children and so on. For example, the <body>
in the earlier DOM diagram has descendants <p>
, <h1>
, <h2>
, <ol>
, <li>
and text nodes.
childNodes
We can access all the child nodes through the childNodes
collection.
<!DOCTYPE Html>
<html>
<head>
<title>Example of the DOM</title>
</head>
<body>
<p>
<h1>This is a heading inside a paragraph</h1>
<h2>Sub heading here</h2>
</p>
<ol>
<li>one</li>
</ol>
<script>
for (var i = 0; i < document.body.childNodes.length; i++) {
alert( document.body.childNodes[i]);
}
</script>
<ol>
<li>list</li>
<li>after</li>
<li>script</li>
</ol>
</body>
</html>
document.body.childNodes
returns an "array-like" collection of children in the <body>
tag in which the first child is referenced at document.body.childNodes[0]
, the second child at document.body.childNodes[1]
and so on up to document.body.childNodes[n-1]
whereby n is the number of children in the <body>
tag.
In the example above we used a for
loop to loop through the children, showing each through an alert. The index of the loop limits it at the document.body.childNodes.length
property which is the length of the childNodes
array.
<script>
is inside the <body>
tag with some content before and after it. However it interesting to note that if you run the example above the last element to be shown will be the <script>
element even though there is a <ol>
tag below the script. This is because at the moment of the script execution the browser had not yet read the content below the script.
One of the most important things to note about childNodes
is that it returns what looks like an array. However it’s not an array, but a collection, which is an array-like iterable object.
Since its an iterable object we can use a for...of
loop to iterate through it as follows
<!DOCTYPE Html>
<html>
<head>
<title>Example of the DOM</title>
</head>
<body>
<p>
<h1>This is a heading inside a paragraph</h1>
<h2>Sub heading here</h2>
</p>
<ol>
<li>one</li>
</ol>
<script>
for (let node of document.body.childNodes) {
alert(node);
}
</script>
<ol>
<li>list</li>
<li>after</li>
<li>script</li>
</ol>
</body>
</html>
However the drawback is that array methods won’t work with childNodes
because it’s not an array.
firstChild and lastChild
The firstChild
and lastChild
properties give access to the first child and last child of the given element respectively.
However, firstChild
is just a shorthand for childNote[0]
and lastChild
for childNodes[n - 1]
whereby n is the number of children of the chosen element.
Text nodes
Text nodes are formed by pieces of text inside element nodes and they contain only strings. Text nodes cannot have any children. Spaces and newlines are also valid text nodes and part of the DOM. For example if the <head>
tag contains some spaces or newlines before the <title>
tag, those characters become text nodes.
However, spaces and newlines before
do not form text nodes, but any text, spaces or newlines after automatically become part of the body. Lets see some examples on text nodes.Example 1
<!DOCTYPE Html>
<html>
<head>
</head>
<body>
<p>
<script>
alert(document.body.firstChild);
</script>
</body>
</html>
Example 2
<!DOCTYPE Html>
<html>
<head>
</head>
<body><p>
<script>
alert(document.body.firstChild);
</script>
</body>
</html>
Those two pieces of code look identical, right? But if you run them you are going to be shocked.
Output of Example 1
Output of Example 2
In the first example, the first child of <body>
is a text node caused by the fact that there is a newline between the body opening tag and the paragraph opening tag. In the second example, the first child is a paragraph tag since there is no newline. That’s how they differ.
Comment nodes
The browser might not render comments but however, comment nodes are still part of the DOM. For example:
<!DOCTYPE Html>
<html>
<head>
</head>
<body><!--Here is how comments are written in Html-->
<p>
<script>
alert(document.body.firstChild);
</script>
</body>
</html>
Siblings and the parent
Siblings are nodes that are children of the same parent. For example, <head>
and <body>
are siblings whereby <body>
is the "next" or "right" sibling of <head>
and <head>
is the "previous" or "left" sibling of <body>
. The parent is accessed through parentNode
.
The next node by the same parent is accessed through nextSibling
, and the previous node through previousSibling
.
In the next example we are going to "inline" all tags above the script to prevent blank text nodes from creeping in.
<html><head></head><body><script>
alert( document.body.parentNode === document.documentElement ); // true because parent of <body> is <html>
// next or right sibling of <head>
alert( document.head.nextSibling ); // HTMLBodyElement
// previous or left sibling of <body>
alert( document.body.previousSibling ); // HTMLHeadElement
</script></body></html>
Element navigation
All the navigation properties we have been talking about so far refer to all DOM nodes including text nodes and even comment nodes. However usually when dealing with the DOM we don’t want text or comment nodes creeping in between the element nodes. Element navigation is useful in cases where we want to manipulate the structure of a page by manipulating the element nodes that represent respective tags. Element properties are similar to node properties, just with Element
word inside the properties.
children
gives us access only to those children that are element nodes.firstElementChild
,lastElementChild
give us access to first and last element children.previousElementSibling
,nextElementSibling
give us access to sibling elements.parentElement
give us access to the parent element.
However sometimes the parent can not be an element and parentElement
returns null
. This happens with document.documentElement
. As we said earlier, document.documentElement
refers to documentElement
which is the <html>
element. documentElement
is access through document
which is its formal parent but however document
is not an element. This means that: document.documentElement.parentNode
returns document
but document.documentElement.parentElement returns null
.