EduFlow
JavaScript Fundamentals
0%

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.

  1. 1The DOM: the page as objects
  2. 2Selecting elements
  3. 3Changing what is there
  4. 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.

index.html
<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 tree for the Pocket page: document contains body, and body contains an h1, a p with id total, and a ul with id expense-list. JavaScript reads and changes these objects, and the browser redraws the page. document body h1 p#total ul#expense-list JavaScript changes these
The browser turns HTML into a tree of element objects

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.

app.js
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 match

querySelectorAll 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:

app.js
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:

app.js
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.

app.js
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.
  • querySelector returns the first element matching a CSS selector, or null; querySelectorAll returns all matches.
  • Set text with textContent, not innerHTML, whenever the content comes from data or users, so it can never run as HTML.
  • Change appearance through classList and let CSS decide what the classes look like.
  • A render function that clears a container and rebuilds it from your data keeps the page and the data in sync.

Further reading

Check your understanding

3 questions

Question 1 of 3

document.querySelector("#totl").textContent = "$0.00"; throws TypeError: Cannot set properties of null. What is the most likely cause?

Question 2 of 3

A user types <img src=x onerror=alert(1)> as an expense label. Which line displays it safely?

Question 3 of 3

Your render function appends one <li> per expense. After adding a fourth expense and calling render again, the list shows seven items. What is missing?

Practice

Code playground

Render Pocket's list

Write the body of renderExpenses(expenses) so it rebuilds the page from the data:

  1. Clear #expense-list.
  2. Append one <li> per expense with the text Coffee: $4.50 (label, colon, formatted amount), its data-id set to the expense’s id, and the class big when the amount is 10000 cents or more.
  3. Set #total to Total: followed by the formatted sum.

Calling renderExpenses again must replace the list, not add to it, and labels must be shown as plain text.

<h1>Pocket</h1>
<p id="total">Total: $0.00</p>
<ul id="expense-list"></ul>
Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

Preview

Console

(no output)

Checks

  • One
  • per expense, like “Coffee: $4.50”
  • Each item has its expense id in data-id
  • Only expenses of 10000 cents or more get the class “big”
  • #total shows “Total: $207.00”
  • Rendering again replaces the list and shows labels as plain text