AniUI Academy

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.

11 min read

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; // 2

If 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 null

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

Assigning 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 text

That 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 false

One 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 number

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

Try it yourself
Loading playground...

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.
  • querySelector gives one element or null; querySelectorAll gives a list you can loop over.
  • Use .textContent for anything a person typed; .innerHTML builds real elements from the string.
  • Prefer classList over .style so the design stays in your CSS.
  • createElement then append — an element you never append is invisible.

Check yourself

4 questions · pass 3/4 to unlock Reacting to Clicks

up to 50
  1. 1.What does document.querySelectorAll(".lesson") give you back?

  2. 2.Why is .textContent the safer default when the text came from a person?

  3. 3.You need to switch a card between a normal and a highlighted look. Why is classList.add("highlighted") better than setting .style properties one by one?

  4. 4.You change a heading's text with JavaScript, then reload the page. What do you see?

4 left to answer