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 mainYou’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 classtextContent 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.