The page as a tree
Finding elements on a page and changing them: querySelector, textContent, and building new elements to append.
The document is a tree of elements
When the browser reads your HTML it builds a tree: the body contains a heading and a list, the list contains items. JavaScript can walk that tree — and the tree is live. Change it and the screen changes with it.
The object that represents the page is called the DOM, and one function finds almost anything in it: document.querySelector("…") takes any CSS selector and returns the first match. querySelectorAll returns all of them.
The tree is not the file. The file is a sequence of characters; the tree is what the browser built from them, and it can differ: the browser closes tags you left open, moves stray elements into the body, and adds a head and body if you did not write them. Querying the tree is therefore more reliable than reasoning about the source.
Finding, reading, writing
const heading = document.querySelector("h1");
console.log(heading.textContent);
heading.textContent = "Changed from JavaScript";
const list = document.querySelector(".loaves");
const item = document.createElement("li");
item.textContent = "spelt";
list.append(item);Reading and writing the same property
textContent is the text inside an element: read it to see what a heading says, assign to it to change what the page shows. classList.add("done") adds a class, so the CSS you already wrote can react to the state.
Assigning to textContent replaces everything inside the element with plain text, which is exactly what you want for anything a visitor typed — the browser treats it as text, never as markup.
Making new elements
document.createElement("li") makes an element that is not on the page yet; it becomes visible when something appends it. list.append(item) is that moment — and the browser lays the page out again on its own.
That is the whole loop of a dynamic page: find something, make something, put it where it belongs. The next chapter adds the part that decides when.
An element made with createElement has no parent until it is appended, and it is not drawn while it is detached. That is useful: fill it, set its classes, attach listeners, and only then put it on the page, so the visitor never sees a half-built row.
The tree is live
The DOM is not a photograph of the HTML file; it is the tree the browser currently has. Change an element and the change is real immediately, and an element reference you saved earlier still points at the same element even after it moves or gets new children.
Assigning to textContent replaces everything inside an element — children included — with one piece of text. That is fine for a heading or a status line, but it is not how you add an item to a list, because the old items would go with it.
Text is text; markup is markup
textContent always treats its value as text. innerHTML parses its value as HTML, which is useful when you genuinely have markup and dangerous when the value came from a visitor: a string containing a tag becomes a real element, and a string containing a script can become a real script.
The rule is simple: use textContent for anything a person typed, and build elements with createElement when you need structure. innerHTML also throws away the existing children and their listeners every time it is assigned, which is the second reason to avoid it inside an update.
A worked update, in order
Suppose the script runs after the page is parsed. document.querySelector("h1") walks the tree and returns the first heading, or null if there is none. Reading .textContent gives the words currently inside it. Assigning a new string marks the page for an update, and the heading shows the new words when the browser next paints.
Then document.querySelector(".loaves") finds the list, createElement("li") builds a detached item, and setting its text fills it. list.append(item) puts it at the end of the list and makes it part of the document. At no point did the HTML file change; the tree the browser holds did.
Three failures to know: a selector that matches nothing returns null, so the next .textContent throws — log the element before changing it; querySelectorAll returns a NodeList, which has forEach but not map; and assigning visitor text through innerHTML lets their markup run, while textContent keeps it as text.
Try these next
- Change the last list item, using
querySelectorAlland its final index. - Add a class to the heading with
classList.addand style that class in the page. - Log
document.querySelectorAll("li").lengthbefore and after appending an item.
Change the page from the script
Find the heading and change its text, then find the list and append a new item to it.
3 left to do
- An element is found with querySelector
- Its text is changed with textContent
- A new element is made and appended
document.querySelector("h1").textContent = "…" — then createElement("li"), set its text, and append it to the list.
Build a list from data
The page has an empty description list. For each fact, create a `dt` for the term and a `dd` for the detail, and append both to the list.
3 left to do
- The facts are walked one by one
- Elements are made with createElement
- They are appended to the list
Walk the facts with forEach, make each element with document.createElement, set its textContent, and append it to the <dl>.
Log the element you just found before changing anything. console.log on a freshly-selected element shows whether the selector matched what you thought.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does document.querySelector(".card") return?
