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.

Section · 01

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.

Section · 02

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.

Section · 03

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.)

Section · 04

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)

Section · 05

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)

Section · 06

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();
});

Section · 07

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.