Unit 3 · Scripting & Storage

Lesson · Unit 3 · 9 min read

The DOM & events, how JavaScript actually changes a page.

When a browser parses HTML, it builds an in-memory tree of objects — the DOM. Every script that does anything visible reaches into that tree. Events are how the tree talks back.

What the DOM is

The DOM (Document Object Model) is the browser’s internal representation of your HTML, as a tree of objects. Every HTML element becomes a JavaScript object you can read and mutate.

<body>
  <header>
    <h1>Hi</h1>
  </header>
  <main>
    <p class="lead">Welcome.</p>
  </main>
</body>

DOM tree:
  document
    └─ html
        └─ body
            ├─ header
            │   └─ h1 "Hi"
            └─ main
                └─ p.lead "Welcome."

Everything JS does to a page — show a modal, validate a form, load more results, animate a button — is some combination of finding nodes in this tree and changing them.

Querying the DOM

document.getElementById("hero")            // single element by ID
document.querySelector(".btn")             // first match by CSS selector
document.querySelectorAll("p")             // NodeList of all matches
document.querySelectorAll("article p.lead") // combinators work too

const main = document.querySelector("main");
main.querySelector("p")                    // scoped query inside main

You’ll use querySelector and querySelectorAll for almost everything — they take any CSS selector and reuse the same knowledge from Unit 2 lesson 5.

Reading and changing elements

const btn = document.querySelector(".btn");

// Read
btn.textContent           // the text inside (safe)
btn.innerHTML             // the HTML inside (NOT safe with user input — XSS!)
btn.value                 // form field value
btn.getAttribute("href")  // any attribute
btn.classList             // class manipulation API

// Change
btn.textContent = "Saving...";
btn.classList.add("loading");
btn.classList.remove("idle");
btn.classList.toggle("active");
btn.setAttribute("disabled", "true");
btn.style.color = "red";    // works but prefer adding a class

textContent vs innerHTML

innerHTML parses the assigned string as HTML — so if you pass user-supplied input, you just executed any <script> tag they sent. That’s XSS. Use textContent for plain text and treat innerHTML like a sharp knife. (More in the security lesson.)

Creating and removing elements

// Create
const li = document.createElement("li");
li.textContent = "New item";
li.classList.add("todo");

// Insert
const list = document.querySelector("#todos");
list.appendChild(li);              // at the end
list.prepend(li);                  // at the start
list.insertBefore(li, list.firstChild);  // before another element

// Remove
li.remove();                       // remove this element
list.innerHTML = "";               // remove all children (cheap reset)

Events — making the page react

const btn = document.querySelector("#save");

btn.addEventListener("click", (event) => {
  console.log("clicked!", event);
  event.preventDefault();  // stop the default behavior (form submit, link nav)
});

The events you’ll use 90% of the time

click          — mouse click or tap
input          — input/textarea value changed (every keystroke)
change         — form field committed (blur after change)
submit         — form submitted
keydown        — key pressed
keyup          — key released
focus / blur   — element gained / lost focus
load           — image/page finished loading
scroll         — user scrolled
resize         — window resized
DOMContentLoaded — HTML parsed (don't query before this)

The event object

Every listener gets an event object with info about what happened.

form.addEventListener("submit", (event) => {
  event.preventDefault();             // stop the page from reloading
  const formData = new FormData(event.target);
  const email = formData.get("email");
  console.log(email);
});

input.addEventListener("input", (event) => {
  console.log(event.target.value);    // the current text
});

document.addEventListener("keydown", (event) => {
  if (event.key === "Escape") closeModal();
  if (event.ctrlKey && event.key === "s") savePage();
});

Event delegation — one listener, many elements

Instead of attaching a listener to each item in a list, attach one to the parent and check what was clicked. Events bubble up the tree by default.

document.querySelector("#todo-list").addEventListener("click", (event) => {
  if (event.target.matches(".delete-btn")) {
    const item = event.target.closest("li");
    item.remove();
  }
});

Delegation works for elements that don’t exist yet — you can add new items to the list later and they’ll still fire the handler. This is why frameworks like React get away with re-rendering without re-binding listeners on every node. Next lesson: talking to the network — fetch, async, and the shape of APIs.