Learn JavaScript - The DOM API and Element Manipulation
Episode 13 of 23

Learn JavaScript - The DOM API and Element Manipulation

This episode opens phase three: JavaScript in the browser. You learn to select elements with selectors, read and change content, create new elements, and manage classes and attributes through the DOM API. You build pages that change dynamically.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

Up to episode 12, all your code ran in Node.js — a server world with no knowledge of web pages. Phase three changes everything: your JavaScript enters the browser and interacts with the page. The gateway is the DOM (Document Object Model) — a representation of the HTML structure as objects that can be read and modified via JavaScript.

Episode 13 covers the four most basic DOM operations: selecting elements, changing their content, creating new elements, and managing classes and attributes. No frontend framework involved — just the pure DOM API, so the mechanics are crystal clear before you ever meet React or Vue. Run all the examples via an HTML file opened in the browser, or directly in the DevTools Console.

Selecting Elements with Selectors

document.querySelector

document.querySelector selects the first single element that matches a CSS selector:

JSSelecting elements with querySelector
const judul = document.querySelector("#judul");
const tombol = document.querySelector(".btn-utama");
const pertama = document.querySelector("li");

document.querySelector("#judul") selects the element with id judul, .btn-utama selects the first class match, and li selects the first li element. querySelector accepts any CSS selector — id, class, tag, even combinations. If nothing matches, the result is null.

document.querySelectorAll and getElementById

To select many elements, use querySelectorAll, which returns an array-like NodeList:

JSSelecting many elements
const item = document.querySelectorAll(".menu li");
const kotak = document.getElementById("hero");
 
console.log(item.length);
console.log(kotak);

document.querySelectorAll(".menu li") returns all li elements inside an element with class menu. getElementById is the classic fast way to select by id. To iterate over querySelectorAll results, use for...of or forEach — patterns you already mastered in episodes 3 and 9.

Reading and Changing Content

textContent and innerHTML

textContent changes an element's raw text without processing markup, while innerHTML processes a string as HTML:

JSChanging element content
const judul = document.querySelector("#judul");
 
judul.textContent = "Halo dari JavaScript!";
console.log(judul.textContent);
 
const daftar = document.querySelector("#daftar");
daftar.innerHTML = "<li>Beras</li><li>Minyak</li>";

judul.textContent = "Halo dari JavaScript!" replaces the displayed text. daftar.innerHTML = "<li>Beras</li>" inserts raw HTML. This difference matters for security: textContent is always safe, while innerHTML is dangerous if filled with user data — more on that in episode 22.

Warning

Never put user input into innerHTML without a sanitization step. Putting user text in innerHTML opens an XSS attack vector. Use textContent for plain text, and innerHTML only for markup you fully control.

Creating and Adding New Elements

createElement and append

document.createElement creates an element in memory, then append or appendChild puts it into the document:

JSCreating and adding elements
const daftar = document.querySelector("#daftar");
 
const itemBaru = document.createElement("li");
itemBaru.textContent = "Telur";
 
daftar.append(itemBaru);
console.log(daftar.children.length);

document.createElement("li") creates a new element that isn't visible yet. After filling its textContent, daftar.append(itemBaru) places it as daftar's last child. append accepts multiple arguments at once and also accepts strings — appendChild takes only one node.

Building Multi-Level Structures

Elements can be built step by step, then mounted all at once:

JSBuilding a dynamic card
const kontainer = document.querySelector("#kontainer");
 
const kartu = document.createElement("article");
kartu.classList.add("kartu");
 
const judulKartu = document.createElement("h3");
judulKartu.textContent = "Judul Kartu";
 
const isi = document.createElement("p");
isi.textContent = "Isi kartu dibuat dari JavaScript.";
 
kartu.append(judulKartu, isi);
kontainer.append(kartu);

kartu.append(judulKartu, isi) adds two children at once, and kontainer.append(kartu) mounts the entire structure in one step. This pattern is the standard way to build dynamic interfaces — each component is built in memory, then mounted into the document.

Managing Classes and Attributes

classList: Add, Remove, and Toggle

classList provides a convenient API for managing an element's classes:

JSManaging classes with classList
const tombol = document.querySelector("#tombol");
 
tombol.classList.add("aktif");
tombol.classList.remove("lama");
tombol.classList.toggle("disembunyikan");
console.log(tombol.classList.contains("aktif"));

tombol.classList.add("aktif") adds a class, remove deletes it, toggle adds it if absent and removes it if present, and contains checks whether a class exists. toggle is the most-used pattern for states like open and closed menus.

Managing Attributes and Styles

Other attributes are accessed via setAttribute, getAttribute, and removeAttribute, while inline styles are changed through the style property:

JSChanging attributes and styles
const gambar = document.querySelector("#profil");
const kotak = document.querySelector("#kotak");
 
gambar.setAttribute("src", "/images/baru.png");
gambar.setAttribute("alt", "Foto profil terbaru");
 
kotak.style.backgroundColor = "tomato";
kotak.style.padding = "16px";

gambar.setAttribute("src", "/images/baru.png") changes an attribute, and kotak.style.backgroundColor = "tomato" changes an inline style. CSS property names are written in camelCase: background-color becomes backgroundColor. For more complex styling, prefer managing classes with classList over stacking inline styles.

Applying This to a Real Page

An HTML Skeleton and Execution Flow

All the code above needs a page to run on. Its minimal structure:

JSindex.html with a script
<!DOCTYPE html>
<html>
  <body>
    <div id="daftar"></div>
    <script src="main.js"></script>
  </body>
</html>

<script src="main.js"></script> loads the JavaScript after the div element has been read. This order matters: a script placed at the end of body guarantees the target element already exists in the DOM when JavaScript runs. The modern alternative is the defer attribute on a script in the head.

Running a Local Server

To test a page safely, run a local server — rather than opening the file directly:

Running a local server
npx serve .

npx serve . serves the project directory at http://localhost:3000. Using a local server makes fetch and modules work normally — two topics we'll cover in episodes 10 and 15.

Wrap-Up

Episode 13 opened the door to the interactive web: selecting elements with querySelector, changing content with textContent and innerHTML, building new elements with createElement and append, and managing classes, attributes, and styles through the DOM API.

Key takeaways:

  • querySelector selects one element; querySelectorAll selects many.
  • textContent is safe for text; innerHTML is dangerous for user input.
  • createElement then append builds dynamic elements step by step.
  • classList provides add, remove, toggle, and contains.
  • Style property names are written in camelCase.
  • Put a script at the end of body or use defer.

In the next episode 14 we'll cover event handling and event delegation — responding to clicks, input, and keyboard with addEventListener, understanding event propagation, and the efficient delegation pattern for dynamic elements. This is how pages truly respond to users.

Learn JavaScript - The DOM API and Element Manipulation | Learn JavaScript