Walking the DOM

The DOM allows us to do anything with elements and their contents, but first we need to reach the corresponding DOM object. All operations on the DOM start with the document object. That’s the main “entry point” to DOM. From it we can access any node. DOM tree refers to the HTML page where all the nodes are objects. There can be 3 main types of nodes in the DOM tree:

  1. text nodes
  2. element nodes
  3. comment nodes
DOM tree

According to the W3C HTML DOM standard, everything in an HTML document is a node:

  1. The entire document is a document node
  2. Every HTML element is an element node
  3. The text inside HTML elements are text nodes
  4. Every HTML attribute is an attribute node (deprecated)
  5. All comments are comment nodes

With the HTML DOM, all nodes in the node tree can be accessed by JavaScript. New nodes can be created, and all nodes can be modified or deleted.

DOM tree

The topmost tree nodes are available directly as document properties:

<html> = document.documentElement
The topmost document node is document.documentElement. That’s the DOM node of the <html> tag.

<body> = document.body
Another widely used DOM node is the <body> element – document.body.

<head> = document.head
The <head> tag is available as document.head.

Note: A text node is always a leaf of the tree.

Note: In the DOM world null means “doesn’t exist” or “no such node”.

<html>
<head>
    <title>hello</title>
</head>
<body>
    ...
</body>
</html>
<script>
    console.log(document.title); // hello
    console.log(document.documentElement);  // html tag
    console.log(document.head); // head tag
    console.log(document.body); // body tag
    console.log(document);  // complete html file
</script>

Auto correction

If an erroneous HTML is encountered by the browser it tends to correct it. For example, if we put something after the body, it is automatically moved inside the body. Another example is <table> tag which must contain <tbody>.

Note: document.body can sometimes be null if the JavaScript is written before the <body> tag.

Children of an element

Direct as well as deeply nested elements of an element are called its children.

Child nodes => Elements that are direct children. For example, <head> and <body> tag are children of <html>.

Descendant nodes => All nested elements, children, their children and so on.

firstChild, lastChild and childNodes

element.firstChild => first child element

element.lastChild => last child element

element.childNodes => all child element

Following is always true:

element.childNodes[0] === element.firstChild

element.childNodes[element.childNodes.length-1] === element.lastChild

There is also a method element.hasChildNodes() to check whether there are any child nodes.

Note: childNodes looks like an array. But its not actually an array but a collection. We can use Array.from(collection) to convert it into an Array.

<body><div>
        <p>this is a paragraph</p>
        <span>Span</span>
    </div>
</body>
<script>
    console.log(document.body.firstChild);  // div tag and inside div contents
    console.log(document.body.lastChild);   // script tag and inside script contents
    console.log(document.body.childNodes);
                    
    let arr = Array.from(document.body.childNodes);
    console.log(arr);
    // (3) [div, text, script]
        // 0: div
        // 1: text
        // 2: script
        //  length: 3
        //  [[Prototype]]: Array(0)
</script>
Notes on DOM collections:
  • they are read-only.
  • they are live element.childNodes variable (reference) will automatically update if childNodes of element is changed.
  • they are iterable using for...of loop.
  • Siblings and the parent

    Siblings are nodes that are children of the same parent. For example, <head> and <body> are siblings. Siblings have same parent. In the above example its <html>.

    <body> is said to be the next or right sibling of <head>.

    <head> is said to be the previous or left sibling of <body>.

    The next sibling is in nextSibling property and the previous one in previousSibling. The parent is available as parentNode.

    <body><div><div> class="first">first</div><div> class="second">second</div></div></body>
    <script>
        console.log(document.body.firstChild);   // div tag
    
        let a = document.body.firstChild;
        console.log(a.parentNode);  // body tag
        console.log(a.parentElement);   // body tag
        console.log(a.firstChild.nextSibling);  // div.second
                    
        console.log(document.documentElement.parentNode);  // document
        console.log(document.documentElement.parentElement);  // null
    </script>

    Element only Navigation

    Sometimes we don't want text or comment nodes. Some links only take element nodes into account. For example,

    document.previousElementSibling => previous sibling which is an element

    document.nextElementSibling => next sibling (element)

    document.firstElementChild => first element child

    document.lastElementChild => last element child

    <body><!-- this is a comment -->
        <nav>
            <ul>
                <li>Home</li>
                <li>About</li>
                <li>Hire Me</li>
            </ul>
        </nav>
    </body>
    <script>
        const b = document.body;
        console.log(b.firstChild);  // comment
        console.log(b.firstElementChild);   // nav tag 
    </script>

    Table links

    certain DOM elements may provide additional properties specific to their type for convenience. Table element supports the following properties:

    table.rows => collection of <tr> elements

    table.caption => reference to <caption>

    table.tHead => reference to <thead>

    table.tFoot => reference to <tfoot>

    table.tBodies => collection of <tbody> elements

    tbody.rows => collection of <tr> inside

    tr.cells => collection of <td> and <th>

    tr.sectionRowIndex => index of <tr> inside enclosing element

    tr.rowIndex => row number starting from 0

    td.cellIndex => number of cells inside enclosing <tr>

    <body>
        <div class="container my-4">
            <table class="table">
                <thead>
                    <tr>
                        <th> scope="col">#</th>
                        <th> scope="col">First</th>
                        <th> scope="col">Last</th>
                        <th> scope="col">Handle</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <th> scope="row">1</th>
                        <td>Mark</td>
                        <td>Otto</td>
                        <t>>@mdo</t>
                    </tr>
                    <tr>
                        <th> scope="row">2</th>
                        <td>Jacob</td>
                        <td>Thornton</td>
                        <td>@fat</td>
                    </tr>
                    <tr>
                        <th> scope="row">3</th>
                        <td> colspan="2">Larry the Bird</td>
                        <td>@twitter</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </body>
    <script>
        const t = document.body.firstElementChild.firstElementChild;
        console.log(t); // table tag
                        
        console.log(t.rows);    // HTMLCollection(4)0: tr1: tr2: tr3: trlength: 4[[Prototype]]: HTMLCollection
        console.log(t.caption); // null
        console.log(t.thead);   // undefined
        console.log(t.tHead.firstElementChild); // tr tag
        console.log(t.tBodies); // HTMLCollection [tbody]0: tbodylength: 1[[Prototype]]: HTMLCollection
        console.log(t.tFoot);   // null
        console.log(t.tbody.rows);
        console.log(t.rows[0].rowIndex);    // 0
                        
        console.log(typeof document);  // object
        console.log(typeof window);  // object
    </script>

    Searching the DOM

    DOM navigation properties are helpful when the elements are close to each other. If they are not close to each other, we have some more methods to search the DOM.

    document.getElementById
    this method is used to get the element with a given id attribute.
    let span = document.getElementById('span');
    span.style.color = 'orange';
    document.querySelectorAll
    returns all elements inside an element matching the given CSS selector.
    document.querySelector
    returns the first element for the given CSS selector. A efficient version of element.querySelectorAll(CSS)[0]
    document.getElementsByTagName
    returns elements with the given tag name
    document.getElementsByClassName
    returns elements that have the given CSS class. Don't forget the s letter
    document.getElementsByName
    searches elements by the name attribute
    <script>
        // change card title to orange
        let cartTitle = document.getElementsByClassName('card-title')[0];
        cartTitle.style.color = "orange";
                        
        let cardText = document.getElementById('cardText');
        cardText.style.color = "blue";
                        
        let query = document.querySelectorAll('.card-title');
        console.log(query);
        query[0].style.color = 'orange';
        query[1].style.color = 'orangered';
        query[2].style.color = 'red';
                        
        document.querySelector('.button').style.color = 'brown';
        document.querySelector('.button').style.fontFamily = 'cursive';
                        
        console.log(document.getElementsByTagName('a'));
        console.log(document.querySelector('.card-title').getElementsByTagName('a'));
        console.log(document.querySelector('.card').getElementsByTagName('a'));
                        
        console.log(document.getElementsByName('search'));
    </script>

    matches, closest and contains methods

    There are three important methods to search the DOM:

    elem.matches(CSS)
    to check if element matches the given CSS selector.
    elem.closest(CSS)
    to look for the nearest ancestor that matches the given CSS selector. The elem itself is also checked.
    elemA.contains(elemB)
    returns true if elemB is inside elemA (a descendant of elemA) or when elemA == elemB
    <body>
        <div class="box" id="id1">this is an element 1.
            <span> id="sp1">this is a span</span>
        </div>                
        <div> class="box" id="id2">this is an element 2.</div>
    </body>
    <script>
        const id = document.getElementById('id1');
        console.log(id);  // div.box#id1
    
        console.log(id.matches('.class'));  // false
        console.log(id.matches('.box'));  // true
    
        const sp = document.getElementById('sp1');
        console.log(sp.closest('.box'));  // div.box#id1
    
        console.log(sp.contains(id));  // false
        console.log(id.contains(sp));  // true
        console.log(sp.contains(sp));  // true
    </script>