The DOM: changing the page with JavaScript

Intermediate

Introduction

Until now, your programs have only printed text. This is the lesson where JavaScript starts to change what people see. When the browser reads an HTML page, it builds a tree of objects from it, one for each element and piece of text. This tree is the DOM (Document Object Model). JavaScript changes the page by changing these objects, and the browser redraws the page to match.

How it works

The whole page is the object document. Every element object has properties and methods:

CodeWhat it does
document.querySelector(".card")the first element the CSS selector matches (or null)
document.querySelectorAll("li")all of them, as a list
el.textContent = "Hi"replaces the element's text
el.classList.add("done")adds a class (also remove, toggle)
el.style.color = "red"sets one inline style
el.setAttribute("href", url)sets an attribute
document.createElement("li")makes a new element, not yet on the page
parent.append(el)puts it on the page, inside parent
el.remove()takes it off the page
el.replaceChildren()empties an element

textContent, not innerHTML, for text. el.innerHTML = text reads text as HTML. If the text came from a user, it can contain tags and scripts that then run in the page: a cross-site scripting (XSS) attack. Use textContent for text, and build elements with createElement.

Scripts need the elements to exist. A browser runs a script when it reaches it, so a script at the top of the page cannot find elements further down. Put your script at the end of the body, or use the defer attribute.

The DOM is a tree. Every node has a parentElement, children and siblings, so code can walk the tree just as the tree lessons do in Data Structures. The traced example below does the same with a small tree of plain objects shaped like the DOM, counting the elements and collecting the text:

// A small tree shaped like the DOM, walked with recursion.
const page = {
  tag: "body",
  children: [
    { tag: "h1", text: "Shop", children: [] },
    {
      tag: "ul",
      children: [
        { tag: "li", text: "Pen", children: [] },
        { tag: "li", text: "Ruler", children: [] },
      ],
    },
  ],
};

function walk(node, depth) {
  console.log("  ".repeat(depth) + node.tag + (node.text ? ": " + node.text : ""));
  let count = 1;
  for (const child of node.children) {
    count += walk(child, depth + 1);
  }
  return count;
}

console.log(walk(page, 0), "elements");
Open in compiler

Syntax

const box = document.querySelector("#box");
box.textContent = "Saved";
box.classList.add("ok");

const item = document.createElement("li");
item.textContent = "New item";
document.querySelector("ul").append(item);

Example

The script below builds a list from an array and highlights the cheapest item. Change the array and run the page again. The console under the page shows what the script printed, and any error.

console.log output from the page shows here.

Line by line

  1. items is the data, and the HTML has only an empty list.
  2. querySelector finds the list, and reduce finds the cheapest item.
  3. The loop makes one li for each item, with createElement.
  4. textContent sets the words, so no text can ever be treated as HTML.
  5. The cheapest item gets the class that the CSS highlights.
  6. append is what actually puts each item on the page.
  7. The last two lines update the summary and write to the console.

Expected result

A heading, then three items, with "Pen: 10 taka" highlighted in green, then "3 items". The console shows Built 3 list items.

In the real world

Each time you add something to a cart and the number by the icon changes, a script has changed the DOM. Frameworks such as React and Vue exist mostly to do these updates for you, but they are making the same calls underneath.

Common mistakes

  • Running a script before the elements exist. Put the script at the end of the body, or use the defer attribute.
  • Using the result of a failed search. If querySelector returned null because the selector is wrong, null.textContent is a TypeError.
  • Forgetting the dot or hash. querySelector("card") looks for a card element, not class="card".
  • Using innerHTML with text from users.

Best practices

  • Keep your data in JavaScript, and build the page from it.
  • Change classes rather than individual styles.
  • Keep references to elements you use often in variables.
  • When re-drawing a list, empty it first, or you will end up with duplicates.

Summary

The DOM is the page as a tree of objects. querySelector finds elements, and textContent, classList and setAttribute change them. createElement and append add new ones. Use textContent for anything that came from a user.

Quick check

1. What is the DOM?
2. What does document.querySelector give when nothing matches?
3. Which is safe for putting a user's comment on the page?
4. Where does a new element made with createElement appear?

Key takeaways

  • Change a page by changing its objects, and the browser redraws it.
  • Use textContent, not innerHTML, for anything a user could have typed.
  • Build lists from data, so that the page always matches the data.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.