Section 4 · JavaScript in the Browser
The DOM: Select, Create, Update
18 min3 min readCode playground
Lesson at a glance
Understand the DOM as the browser’s live tree of the page, select elements with CSS selectors, change their text, classes and attributes, and render a list from data.
- 1The DOM: the page as objects
- 2Selecting elements
- 3Changing what is there
- 4Creating elements and rendering a list
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Everything Pocket has done so far happened in the console. A real user never opens the console; they see a page. The rest of this section moves Pocket onto that page, and the first step is learning how JavaScript sees a web page at all.
The DOM: the page as objects
When a browser loads HTML, it does not keep it as text. It builds a tree of objects, one for every element, and draws the page from that tree. The tree is the DOM (Document Object Model), and the global document object is its root.
<body>
<h1>Pocket</h1>
<p id="total">Total: $0.00</p>
<ul id="expense-list"></ul>
<script src="app.js" defer></script>
</body>The DOM is live. Change an object in the tree and the browser redraws that part of the page straight away. That is all “updating the page” means.
The defer attribute tells the browser to run app.js after it has built the whole tree. Without it, a script in the <head> runs before the body exists and every lookup finds nothing. Scripts loaded with type="module", which you will meet in section 5, are deferred automatically.
Selecting elements
document.querySelector(selector) takes a CSS selector, the same syntax you write in a stylesheet, and returns the first matching element. querySelectorAll returns all matches.
const totalEl = document.querySelector("#total"); // by id
const list = document.querySelector("#expense-list");
const firstItem = document.querySelector("#expense-list li");
const allItems = document.querySelectorAll("li.big"); // every matchquerySelectorAll returns a NodeList. It has forEach and works with for...of; spread it, [...allItems], when you want array methods such as map and filter.
When nothing matches, querySelector returns null, and the next line that uses the result throws TypeError: Cannot set properties of null. That error almost always means a typo in the selector or a script that ran too early.
Changing what is there
Once you have an element, its properties are the levers:
totalEl.textContent = "Total: $30.20"; // replace the text
totalEl.classList.add("over-budget"); // add a CSS class
totalEl.classList.toggle("highlight"); // add if absent, remove if present
totalEl.hidden = false; // show or hide
list.dataset.month = "2026-10"; // sets data-month="2026-10"textContent sets plain text. Prefer classes over setting style directly: the CSS file decides what .over-budget looks like, and JavaScript only decides whether the element has it. Designers can then change the look without touching your logic.
Creating elements and rendering a list
To add new things to the page, create elements, fill them in, and attach them:
const li = document.createElement("li");
li.textContent = "Coffee: $4.50";
li.dataset.id = "exp-1";
list.append(li);createElement makes an element that is not on the page yet. append attaches it as the last child of list, and only then does it appear. element.remove() takes an element off the page.
Pocket has an array of expenses and needs one <li> per expense. The cleanest way to keep the page and the data in agreement is a render function: it clears the container, then rebuilds it from the data, every time the data changes.
function renderExpenses(expenses) {
list.replaceChildren(); // remove the old items
for (const expense of expenses) {
const li = document.createElement("li");
li.textContent = `${expense.label}: ${formatMoney(expense.amount)}`;
li.dataset.id = expense.id;
if (expense.amount >= 10000) {
li.classList.add("big");
}
list.append(li);
}
const total = expenses.reduce((sum, e) => sum + e.amount, 0);
totalEl.textContent = `Total: ${formatMoney(total)}`;
}Rebuilding everything sounds wasteful, and for thousands of items it would be. For the dozens of items in a personal app, it is fast and, more importantly, simple: there is exactly one function that decides what the list looks like, and the page can never drift out of step with the data. This idea, the screen as a function of the data, is the core of frameworks like React, which do the same thing more cleverly.
The rule that makes it work is discipline about direction. Data flows one way: change the array, then call renderExpenses. Never fix up a single <li> by hand “just this once”, and never read the expenses back out of the page to calculate something; the page is a picture of the data, not the data itself. The moment two places can change what is on screen, they will eventually disagree.
The page now shows Pocket’s data, but it cannot react to the user yet. Next lesson: events.
Key takeaways
- The browser turns your HTML into the DOM, a tree of objects; changing those objects with JavaScript changes the page immediately.
querySelectorreturns the first element matching a CSS selector, ornull;querySelectorAllreturns all matches.- Set text with
textContent, notinnerHTML, whenever the content comes from data or users, so it can never run as HTML. - Change appearance through
classListand let CSS decide what the classes look like. - A
renderfunction that clears a container and rebuilds it from your data keeps the page and the data in sync.