Lesson 14 of 28
Changing the Page
The browser turns your HTML into a live tree of objects. Find elements, rewrite their text, restyle them and build new ones, and watch the page update instantly.
Everything you have written so far has lived in the console. This is where that stops. Once you can reach into the page from JavaScript, your code starts changing something a person can actually see.
The page is a tree of live objects
When the browser loads your HTML, it does not keep the file open and re-read it. It builds a tree of objects in memory, one object per element, nested the same way your tags were nested. That tree is called the DOM — the Document Object Model — and it is what you are looking at on screen.
JavaScript can read that tree and change it, and the screen updates the moment you do.
Here is the part that catches people out. The tree is the page, not the file. Change a heading's text with JavaScript, reload, and the old text is back, because the file on disk never moved. You are editing the browser's live copy.
Everything starts from one object the browser hands you: document.
Finding an element
document.querySelector takes a CSS selector — the same syntax you use in a
stylesheet — and returns the first element that matches.
<h1 id="page-title">Lessons</h1>
<ul class="lesson-list">
<li class="lesson">Values and Variables</li>
<li class="lesson">Arrays and Objects</li>
</ul>const title = document.querySelector("#page-title");
const firstLesson = document.querySelector(".lesson");
const allLessons = document.querySelectorAll(".lesson");
allLessons.length; // 2If nothing matches, querySelector returns null. That is worth remembering,
because the error you get next is a confusing one:
const missing = document.querySelector("#does-not-exist");
missing.textContent; // TypeError: Cannot read properties of nullquerySelectorAll returns every match as a NodeList. It is list-like rather
than a real array: it has length, you can index into it, and forEach and
for...of both work. map and filter do not.
for (const lesson of allLessons) {
console.log(lesson.textContent);
}
const titles = Array.from(allLessons).map((el) => el.textContent);Reading and writing text
.textContent works in both directions. Read it to see what is inside an
element, assign to it to change what is inside.
const title = document.querySelector("#page-title");
title.textContent; // "Lessons"
title.textContent = "Your lessons"; // the page updates nowAssigning replaces everything inside that element, children included.
textContent against innerHTML
.innerHTML looks similar but does something different: it treats the string
as markup and parses it.
box.innerHTML = "<strong>Saved</strong>"; // a real bold element
box.textContent = "<strong>Saved</strong>"; // the tags show up as textThat parsing is the problem. Suppose a student types their display name and you put it on the page:
const name = input.value; // whatever they typed
profile.innerHTML = "Welcome, " + name;If they type a tag, the browser builds it, because it has no way to tell your
markup from theirs. Some tags can run JavaScript, and that code runs as if you
had written it — with access to whatever the signed-in visitor has access to.
Use .textContent for anything a person supplied. Save .innerHTML for
markup you wrote yourself.
Changing how something looks
.style sets inline styles, one property at a time. Property names are
camelCase, so background-color becomes backgroundColor.
card.style.backgroundColor = "gold";
card.style.borderRadius = "8px";It works, but it spreads design decisions through your JavaScript. Changing a class is almost always better:
card.classList.add("completed");
card.classList.remove("completed");
card.classList.toggle("completed"); // add if missing, remove if present
card.classList.contains("completed"); // true or falseOne class name can carry twenty CSS properties, and removing it undoes all
twenty at once — where inline styles have to be unset one by one. Your
JavaScript ends up saying only what state something is in, and the stylesheet
decides what that state looks like. toggle is exactly what you want for
anything being switched on and off.
Attributes
const link = document.querySelector("#buy");
link.getAttribute("href"); // "/checkout"
link.setAttribute("href", "/pro");
link.hasAttribute("target"); // false
link.removeAttribute("target");The common attributes also have direct properties — img.src, input.value,
el.id — and those are nicer to read. For your own data, use a data-
attribute and read it through .dataset:
<button data-lesson-id="12">Start</button>button.dataset.lessonId; // "12" — always a string, never a numberTry it yourself
The playground below has no page attached, only a console. So use it for the half of the job that comes first: turning your data into the exact strings you intend to display. Get the labels right here, and the DOM part afterwards is almost mechanical.
Making and removing elements
document.createElement builds an element in memory. Nothing appears until you
put it somewhere.
const item = document.createElement("li");
item.textContent = "Reacting to Clicks";
item.classList.add("lesson");
const list = document.querySelector(".lesson-list");
list.append(item);Forgetting that second step is the single most common reason a beginner says
"nothing happened". The element exists, it just is not in the tree yet.
append adds it as the last child, prepend as the first.
Getting rid of one is easier:
item.remove();Building a list from an array
Here is the payoff, and it is the shape you will use constantly: data goes in one end, elements come out the other.
<ul id="lesson-list"></ul>const lessons = [
{ title: "Values and Variables", price: 0 },
{ title: "Arrays and Objects", price: 199 },
{ title: "Changing the Page", price: 249 },
];
const list = document.querySelector("#lesson-list");
list.textContent = ""; // clear it first
for (const lesson of lessons) {
const item = document.createElement("li");
item.textContent = lesson.title + " — ₹" + lesson.price;
item.classList.add("lesson");
if (lesson.price === 0) {
item.classList.add("free");
}
list.append(item);
}That clearing line matters. Run this twice without it and you get six items instead of three, which is a bug that looks baffling until you spot it.
Notice the array never changed. It stayed the source of truth, and the page became a picture of it. When the array changes, you run this again. That idea — data first, page second — is the whole basis of React later on.
What to remember
- The DOM is a live tree of objects, and changes to it never reach the file on disk.
querySelectorgives one element ornull;querySelectorAllgives a list you can loop over.- Use
.textContentfor anything a person typed;.innerHTMLbuilds real elements from the string. - Prefer
classListover.styleso the design stays in your CSS. createElementthenappend— an element you never append is invisible.
Check yourself
4 questions · pass 3/4 to unlock Reacting to Clicks
1.What does
document.querySelectorAll(".lesson")give you back?2.Why is
.textContentthe safer default when the text came from a person?3.You need to switch a card between a normal and a highlighted look. Why is
classList.add("highlighted")better than setting.styleproperties one by one?4.You change a heading's text with JavaScript, then reload the page. What do you see?
4 left to answer