Skip to main content

DOM Manipulation

The Document Object Model (DOM) is the browser's representation of your HTML page as a tree of objects. JavaScript can read and modify this tree, which is how you make web pages interactive.

Selecting Elements

getElementById

Select a single element by its id attribute:

const header = document.getElementById("main-header");
console.log(header.textContent);

querySelector and querySelectorAll

Select elements using CSS selectors — the most versatile approach:

// Single element (first match)
const button = document.querySelector(".submit-btn");
const nav = document.querySelector("nav");
const input = document.querySelector('input[type="email"]');

// Multiple elements (NodeList)
const items = document.querySelectorAll(".list-item");
items.forEach((item) => {
  console.log(item.textContent);
});

Comparison of Selection Methods

MethodReturnsUse When
getElementByIdElementYou have a unique ID
querySelectorElementYou need the first CSS match
querySelectorAllNodeListYou need all matching elements
getElementsByClassNameHTMLCollectionLegacy code (prefer querySelector)

Reading and Modifying Content

textContent vs innerHTML

<div id="output">
  <strong>Hello</strong> World
</div>
const el = document.querySelector("#output");

console.log(el.textContent); // "Hello World" (plain text)
console.log(el.innerHTML);   // "<strong>Hello</strong> World" (HTML string)

// Set content
el.textContent = "New plain text";  // safe — escapes HTML
el.innerHTML = "<em>New HTML</em>"; // renders HTML — be careful with user input

Always use textContent when inserting user-generated content to prevent cross-site scripting (XSS) attacks.

Modifying Styles and Classes

Working with Classes

const card = document.querySelector(".card");

card.classList.add("active");
card.classList.remove("hidden");
card.classList.toggle("expanded");
console.log(card.classList.contains("active")); // true

Inline Styles

const box = document.querySelector(".box");
box.style.backgroundColor = "#E21B1B";
box.style.padding = "1rem";
box.style.display = "none";

Prefer toggling CSS classes over setting inline styles — it keeps your JavaScript clean and your styles in CSS where they belong.

Modifying Attributes

const link = document.querySelector("a");

// Read
console.log(link.getAttribute("href"));

// Set
link.setAttribute("href", "https://sabaoon.dev");
link.setAttribute("target", "_blank");

// Remove
link.removeAttribute("title");

// Data attributes
const card = document.querySelector(".card");
card.dataset.userId = "123";       // sets data-user-id="123"
console.log(card.dataset.userId);  // "123"

Creating and Inserting Elements

createElement

const list = document.querySelector("#todo-list");

const newItem = document.createElement("li");
newItem.textContent = "Learn DOM manipulation";
newItem.classList.add("todo-item");

list.appendChild(newItem);

Building Multiple Elements

const fruits = ["Apple", "Banana", "Cherry"];
const ul = document.querySelector("#fruit-list");

const fragment = document.createDocumentFragment();

fruits.forEach((fruit) => {
  const li = document.createElement("li");
  li.textContent = fruit;
  fragment.appendChild(li);
});

ul.appendChild(fragment);

Using a DocumentFragment is more efficient than appending elements one by one because it causes only a single reflow.

insertAdjacentHTML

A convenient way to insert HTML at specific positions:

const container = document.querySelector(".container");

container.insertAdjacentHTML("beforebegin", "<p>Before the container</p>");
container.insertAdjacentHTML("afterbegin", "<p>First child</p>");
container.insertAdjacentHTML("beforeend", "<p>Last child</p>");
container.insertAdjacentHTML("afterend", "<p>After the container</p>");

Removing Elements

const item = document.querySelector(".remove-me");
item.remove();

// Remove a child from its parent
const parent = document.querySelector(".list");
const child = parent.querySelector(".old-item");
parent.removeChild(child);

Event Handling

Events are how you respond to user interactions.

addEventListener

const button = document.querySelector("#submit");

button.addEventListener("click", (event) => {
  console.log("Button clicked!");
  console.log("Target:", event.target);
});

Common Events

EventFires When
clickElement is clicked
inputInput value changes
submitForm is submitted
keydownKey is pressed
mouseoverCursor enters an element
focusElement gains focus
blurElement loses focus
loadPage or resource finishes loading

Event Delegation

Instead of adding listeners to many child elements, add one listener to a parent:

const list = document.querySelector("#task-list");

list.addEventListener("click", (event) => {
  if (event.target.matches(".delete-btn")) {
    const taskItem = event.target.closest(".task-item");
    taskItem.remove();
  }
});

Event delegation is more memory-efficient and works for dynamically added elements.

Preventing Default Behavior

const form = document.querySelector("#login-form");

form.addEventListener("submit", (event) => {
  event.preventDefault(); // stop the page from reloading

  const formData = new FormData(form);
  const email = formData.get("email");
  const password = formData.get("password");

  console.log("Logging in with:", email);
});

Practical Exercise

Build an interactive to-do list:

<input id="todo-input" type="text" placeholder="Add a task..." />
<button id="add-btn">Add</button>
<ul id="todo-list"></ul>
const input = document.querySelector("#todo-input");
const addBtn = document.querySelector("#add-btn");
const list = document.querySelector("#todo-list");

function addTask() {
  const text = input.value.trim();
  if (!text) return;

  const li = document.createElement("li");
  li.innerHTML = `
    <span clas="task-text">${text}</span>
    <button clas="delete-btn">Delete</button>
  `;

  li.querySelector(".task-text").addEventListener("click", () => {
    li.classList.toggle("completed");
  });

  list.appendChild(li);
  input.value = "";
  input.focus();
}

addBtn.addEventListener("click", addTask);
input.addEventListener("keydown", (e) => {
  if (e.key === "Enter") addTask();
});

// Event delegation for delete buttons
list.addEventListener("click", (event) => {
  if (event.target.matches(".delete-btn")) {
    event.target.parentElement.remove();
  }
});

Key Takeaways

  • Use querySelector and querySelectorAll for selecting elements — they accept any CSS selector.
  • Prefer textContent over innerHTML when inserting user-generated content to prevent XSS.
  • Toggle CSS classes instead of setting inline styles for cleaner separation of concerns.
  • Use DocumentFragment when inserting many elements to minimize reflows.
  • Event delegation on a parent element is more efficient than individual listeners on each child.