JavaScript and the DOM: a page that responds
Lesson 7 of 8
7JavaScript and the DOM: a page that responds
The big pictureThe browser turns HTML into a tree of objects called the DOM. JavaScript reads and edits that tree, so the page changes before your eyes with no reload. This is where everything you learnt meets: HTML builds, CSS dresses, and JavaScript moves.
What the picture shows: The loop of every interactive UI, from a to-do list to React: event, state update, render. Frameworks later only automate the render step.
Map of this part
The concepts in order, click any one to jump to it:
Two ways into the same idea
The page is a notice board. HTML pinned the sheets; JavaScript is your hand: you can take one down, write on another, pin a new one. An event is a bell that rings when a visitor presses or types something, so you know when to move your hand. And the clever idea: instead of scribbling on the board all over the place, keep a notebook with the list of sheets (state), and whenever the notebook changes, rearrange the board from it (render).
The DOM is an object interface (Node, Element, Document) over the document tree. querySelector/querySelectorAll take any CSS selector and return an element or a static NodeList. Setting textContent is safe; innerHTML with user input opens an XSS (script injection) hole. Events propagate in three phases (capture, target, bubble), so delegation puts one listener on the parent and finds the target with event.target.closest(). preventDefault stops the default behaviour and stopPropagation stops propagation. Every tree change may cost a style and layout recalculation (reflow), so batch changes with replaceChildren or a DocumentFragment. Scripts load with defer or at the end of body so the elements exist. localStorage is synchronous text storage per origin, limited to a few megabytes.
Before You Start
🧰Functions, arrays and objects
You know arrow functions, map, filter and spread.
🧱Semantic HTML
You know forms, buttons and semantic elements.
🎨CSS for layout
You know classes and how they change looks.
The Core Idea
1The DOM tree: the page as objects
When the browser reads HTML, it builds a tree from it: document at the root, html under it, head and body under that, and so on. Every element in the tree is a JavaScript object with properties you read and change (textContent, id, className) and methods you call (append, remove). Change the object and the page changes at once.
Read the tree and change it
Notice the heading changed even though the HTML says "Hello": JavaScript edited the tree after the browser drew it. Add a third paragraph with the note class and watch the count.
document.body.style.background = "pink" and press Enter. You changed someone else's page (on your machine only; it goes back on refresh). That is the power of the DOM.2Selecting and changing elements
document.querySelector(".total") gives you the first element matching the selector (the same selectors as CSS), or null if none. querySelectorAll gives you all of them in a list you walk with forEach. Then: textContent for text, classList.add/remove/toggle for classes (with the looks coming from CSS), setAttribute or the property directly (img.src) for attributes, dataset for data-* attributes, and style for inline styles.
Select and change text and classes
Press the button. Then change items[2] to items[0] to make the latte sold out. Notice all the looks are in CSS; JavaScript only added a class.
document.querySelector(".totl") with a typo returns null, and then null.textContent = ... stops the program with "Cannot set properties of null". If you see that message, the selector found nothing: check the name, or whether the script runs before the element exists.3Creating and removing elements
document.createElement("li") makes a new element in memory; you give it text and classes, then put it on the page with parent.append(el) (at the end) or prepend (at the start). el.remove() deletes it. parent.replaceChildren(...list) replaces all the children with a new list in one go, ideal for redrawing a list.
Add and remove elements
Add three drinks and delete one with ✕. Then try typing <b>bold</b> as a name: it shows as plain text because we used textContent.
4Events: listen, then act
el.addEventListener("click", fn) ties a function to an event: "when this happens, do that". The common events: click for presses, input whenever a field's text changes, change when the change is committed, submit for sending a form, and keydown for a key press. The function receives the event object event with the details: event.target is the element it happened on, and event.key the key pressed.
A character counter and a like button
Type in the box and watch the counter change with every letter, turning red after 120.
Forms: listen for submit on the form itself, not click on the button, so it covers Enter too and keeps built-in validation. Call event.preventDefault() on the first line so the page does not reload. form.elements.name.value reads the value of the field with name="name".
5Event delegation: one listener for a whole list
A list with a hundred delete buttons? Do not add a hundred listeners. Events bubble from the element that was pressed up to its parent, then grandparent, up to document. So put one listener on the list, and event.target.closest("li") tells you which item was pressed. It even works for items added later, because the listener sits on the parent, which stays.
// simulating bubbling without a browser: each node knows its parent
const tree = { name: "ul#list", parent: null };
const li = { name: "li[data-id=2]", parent: tree };
const button = { name: "button.delete", parent: li };
function dispatch(target) {
const path = [];
for (let node = target; node; node = node.parent) path.push(node.name);
return path;
}
console.log("click travels:", dispatch(button).join(" -> "));
console.log("so a single listener on", tree.name, "hears clicks from every item");6State first, then render
The biggest idea in this lesson: instead of editing the page in ten places (add here, delete there, update the counter), keep the data in one variable (the state) and write one render() function that draws the whole page from it. Every event changes the state, then calls render. The page never disagrees with the data, and bugs drop sharply. This is exactly the idea React is built on.
// the logic part runs anywhere; the page part needs a browser
let todos = [{ id: 1, text: "buy beans", done: false }];
function add(text) {
todos = [...todos, { id: todos.length + 1, text, done: false }];
}
function toggle(id) {
todos = todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t));
}
function render() { // in the browser this builds <li> elements
return todos.map((t) => `${t.done ? "[x]" : "[ ]"} ${t.text}`).join("\n");
}
add("grind coffee");
toggle(1);
console.log(render());
console.log("left:", todos.filter((t) => !t.done).length);7localStorage: memory that survives a refresh
Every JavaScript variable is wiped when the page refreshes. localStorage is a small store in the browser that survives refreshes and closing the browser: setItem(key, text) saves and getItem(key) reads. It stores text only, so you turn the array into text with JSON.stringify before saving and back with JSON.parse after reading.
// in the browser
localStorage.setItem("todos", JSON.stringify(todos)); // save
const saved = JSON.parse(localStorage.getItem("todos") ?? "[]"); // load (or empty list)8Security: textContent, not innerHTML
innerHTML interprets text as HTML. If you put something a user typed into it, and they typed <img src=x onerror="stealSession()">, their code runs in your page on the devices of everyone who sees their comment. That is an XSS (cross-site scripting) attack, one of the most common web vulnerabilities. The rule: any text from a user goes in with textContent, and elements are built with createElement.
See XSS with your own eyes (in a sealed sandbox)
Press the first button: the page turned red, meaning the "user's" code ran. A real attacker would steal your session instead of changing a colour. Then press the second: the text shows as it is and nothing runs. The preview is fully sealed off from the lesson page.
Worked example
A complete to-do list, running here now: state in an array, render draws everything from it, one listener for the form and one delegated listener for the list, and localStorage to keep it. Add a task, click a task's text to strike it through, and delete another. Then read the code top to bottom: every line in it was explained in this lesson.
The to-do list
Once you understand it, add a feature: a "clear done" button that removes every struck-through task with a single filter line.
Myth vs Reality
| Myth | Reality |
|---|---|
| You need React for any interactive page. | Plain JavaScript is enough for many pages, and understanding it makes React much easier. |
| innerHTML is faster, so always use it. | With user input it is a security hole. textContent and createElement are safe. |
| Changing the page needs a reload. | The DOM changes directly, which is what makes web apps feel like phone apps. |
Where you meet it at work
🛒A shop cart
An "add" button updates the state and the counter without a reload.
🔎Instant search
An input event filters results with each letter.
📝Smart forms
Showing a field based on an earlier choice, and custom error messages.
Test your understanding
A form reloads the page on submit. What is the fix?
preventDefault stops the default submit behaviour while the form stays usable with Enter and assistive tech.
Why is event delegation useful for a changing list?
The event bubbles from the item to the parent, so new items need no wiring.
Which line is safe for showing a name a user typed?
textContent treats input as plain text, so no tag runs.
The message "Cannot set properties of null". What is the likely cause?
The selector is wrong or the script runs before the element exists, so it returned null.
Why turn the array into JSON.stringify before localStorage?
Without converting, "[object Object]" is saved and the data is lost.
Quick review cards
Check Your Understanding
What does "state first, then render" mean?
The data in one variable is the truth; every change updates it and redraws the page from it instead of editing elements by hand in many places.
Why listen for submit rather than click on the submit button?
Because submit covers both the click and Enter and respects built-in validation before reaching your code.
What is the difference between querySelector and querySelectorAll?
The first returns the first match or null; the second a list of all matches.
Where do you put the script tag so it finds the elements?
At the end of body, or in head with the defer attribute, so it runs after the tree is built.
Resources for this lesson
Grouped by type. Start with the official docs for precision, or an article or video for the explanation.
Official docs2
Books1
Done with this lesson?
Sign in to save your progress.