Home » Document Object Model Tutorial » Document Object Model (DOM)

Document Object Model (DOM)

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>

Green background



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).

Dom tree

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 as document.documentElement
  • the <body> node is accessed as document.body
  • the <head> node is accessed as document.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>

Output



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

Output of Example 2

Ouput

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>

Output


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.