DataLens

The DOM: selecting and changing elements

Find elements with querySelector, change their text, classes and attributes, create and remove elements, and render lists of data safely without innerHTML pitfalls.

Level: Intermediate · 8 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

When the browser loads a page, it turns the HTML into a tree of objects called the DOM (Document Object Model). JavaScript can read and change that tree, and the page updates instantly. That's how a cart counter goes up, a list filters as you type, or an error message appears under a form field. Everything interactive in the browser comes down to "find an element, change it".

The examples in this module build their own small page first with document.body.innerHTML, so each one runs on its own.

Selecting elements

Before you can change anything on a page, you have to find it. Here's a tiny product list. The first line builds the page (in a real site it would already be in the HTML), and the rest finds elements in it using the same CSS selectors you already know:

document.body.innerHTML = `
  <h1 id="title">Toko Kita</h1>
  <ul class="products">
    <li class="product" data-id="1">Arabica Gayo</li>
    <li class="product sold-out" data-id="2">Toraja Kalosi</li>
    <li class="product" data-id="3">Kintamani</li>
  </ul>`;

const title = document.querySelector("#title");
const first = document.querySelector(".product");
const all = document.querySelectorAll(".product");
const missing = document.querySelector(".does-not-exist");

console.log(title.textContent);
console.log(first.textContent, all.length);
console.log(missing);
Toko Kita
Arabica Gayo 3
null

A null result is the classic DOM bug: document.querySelector("#tilte").textContent crashes with "Cannot read properties of null". Check your selector when you see that error.

Changing text, classes and attributes

Once you have an element, you can change it. When a product sells out, its label should say "Sold out" and look greyed out. When you add something to the cart, the little counter in the corner should go up. Each of those is a small change to an element's text, its classes or its attributes:

document.body.innerHTML = `<p class="stock">In stock</p><a class="link" href="/old">Details</a><span id="cart-count">0</span>`;

const stock = document.querySelector(".stock");
stock.textContent = "Sold out";
stock.classList.add("sold-out");
stock.classList.remove("stock");
console.log(stock.className, stock.classList.contains("sold-out"));

const link = document.querySelector(".link");
link.setAttribute("href", "/product/2");
console.log(link.getAttribute("href"));

const counter = document.querySelector("#cart-count");
counter.textContent = Number(counter.textContent) + 1;
console.log(counter.textContent);
sold-out true
/product/2
1

data attributes

data-* attributes store your own information on elements, readable through element.dataset:

document.body.innerHTML = `<li class="product" data-id="7" data-price="95000">Arabica Gayo</li>`;
const item = document.querySelector(".product");
console.log(item.dataset.id, item.dataset.price);
item.dataset.inCart = "true";
console.log(item.outerHTML);
7 95000
<li class="product" data-id="7" data-price="95000" data-in-cart="true">Arabica Gayo</li>

data-in-cart in HTML becomes dataset.inCart in JavaScript (dashes become camelCase).

Creating, adding and removing elements

Sometimes the element doesn't exist yet. When a customer adds products, the cart list needs a new line for each one, and when they remove something, its line has to go. You build elements from data and add them to the page:

document.body.innerHTML = `<ul id="cart"></ul>`;
const list = document.querySelector("#cart");

const items = [{ name: "Arabica Gayo", qty: 2 }, { name: "French Press", qty: 1 }];
for (const item of items) {
  const li = document.createElement("li");
  li.textContent = item.name + " × " + item.qty;
  li.classList.add("cart-item");
  list.append(li);
}
console.log(list.children.length, list.lastElementChild.textContent);

list.firstElementChild.remove();
console.log(list.children.length);
2 French Press × 1
1

createElement makes a new element, append adds it at the end of a parent (prepend for the start), and remove() takes an element out of the page.

innerHTML: convenient and dangerous

element.innerHTML = "..." replaces an element's contents with HTML parsed from a string. It's handy for your own fixed markup, as in the examples above. Never use it with data from users or APIs:

document.body.innerHTML = `<div id="review"></div>`;
const review = document.querySelector("#review");
const userText = "<img src=x onerror=alert('hacked')>";

review.textContent = userText;                     // safe: shown as text
console.log(review.innerHTML);
&lt;img src=x onerror=alert('hacked')&gt;

With innerHTML = userText, the browser would create that image and run the attacker's code: a cross-site scripting (XSS) attack. With textContent, the same string is displayed as harmless text. Build elements with createElement + textContent when any part of the content comes from outside your code.

Key idea

querySelector/querySelectorAll to find elements (null means no match); textContent, classList and setAttribute/dataset to change them; createElement + append to add, remove() to delete. Use textContent, never innerHTML, for data that isn't yours.

Why this matters for clean code

Keep DOM code thin: compute data in plain functions, then have one small function that renders that data into the page. Toggle classes rather than set inline styles, so the look lives in CSS. And select elements once, near the top, with clear names (const cartList = ...), rather than repeating selectors throughout the code.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Events and event listeners →