Introduction to JavaScript · Lesson 3 of 5

DOM Manipulation & Events

JavaScript DOM Manipulation

The DOM (Document Object Model) is the browser's representation of your HTML as a tree of objects. JavaScript can read and modify it — that's how web pages become interactive.


Selecting Elements

JavaScript
// By ID (returns single element or null)
const title = document.getElementById("main-title");

// CSS selector — first match
const btn = document.querySelector(".submit-btn");
const nav = document.querySelector("nav > ul > li:first-child");

// CSS selector — all matches (returns NodeList, not Array)
const items = document.querySelectorAll(".card");
const inputs = document.querySelectorAll("input[type='text']");

// Convert NodeList to Array for full array methods
const itemsArray = Array.from(items);
// or: [...items]

// Relative selectors
const parent = element.parentElement;
const children = element.children;           // HTMLCollection
const firstChild = element.firstElementChild;
const next = element.nextElementSibling;
const prev = element.previousElementSibling;
const closest = element.closest(".container"); // walks up the DOM tree

Reading and Modifying Content

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

// Text content
el.textContent;           // get plain text (no HTML)
el.textContent = "Hello"; // set text (escapes HTML — safe)

// HTML content
el.innerHTML;             // get HTML string
el.innerHTML = "<strong>Bold</strong>"; // set HTML (careful: XSS risk if user input)
el.outerHTML;             // includes the element itself

// Form input values
const input = document.querySelector("#username");
input.value;              // get current value
input.value = "Asma";     // set value

Working with Attributes

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

// Standard attributes
link.href;
link.href = "https://example.com";

// Any attribute
link.getAttribute("data-id");           // get
link.setAttribute("data-id", "42");     // set
link.removeAttribute("disabled");       // remove
link.hasAttribute("disabled");          // check

// Data attributes
const card = document.querySelector(".card");
card.dataset.userId;        // reads data-user-id attribute
card.dataset.category = "electronics"; // sets data-category

CSS Classes

JavaScript
const el = document.querySelector(".btn");

// classList API (preferred over className)
el.classList.add("active");            // add
el.classList.remove("disabled");       // remove
el.classList.toggle("open");           // add if absent, remove if present
el.classList.toggle("open", true);     // force add
el.classList.toggle("open", false);    // force remove
el.classList.contains("active");       // check
el.classList.replace("old", "new");    // replace

// Replace whole class string (rarely needed)
el.className = "btn btn-primary";

Inline Styles

JavaScript
const el = document.querySelector(".box");

el.style.backgroundColor = "blue";    // camelCase for hyphenated properties
el.style.fontSize = "18px";
el.style.display = "none";            // hide
el.style.display = "";                // remove inline style (reverts to CSS)

// Reading computed styles (includes CSS file styles)
const styles = window.getComputedStyle(el);
styles.backgroundColor; // actual computed value
styles.fontSize;

Creating and Removing Elements

JavaScript
// Create element
const div = document.createElement("div");
div.className = "card";
div.textContent = "New card";

// Create with innerHTML (faster for complex HTML)
const template = document.createElement("div");
template.innerHTML = `
  <article class="card">
    <h2>Title</h2>
    <p>Description</p>
  </article>
`;
const card = template.firstElementChild;

// Append to DOM
document.body.appendChild(div);          // add as last child
document.body.prepend(div);              // add as first child
parent.insertBefore(div, referenceNode); // insert before specific child

// Modern insert methods
parent.append(div, "text node");   // add multiple, accepts text
parent.before(div);                // insert before parent
parent.after(div);                 // insert after parent
refNode.replaceWith(div);          // replace reference node

// Remove
el.remove();                         // modern, remove self
parent.removeChild(el);              // old way

// Clone
const clone = el.cloneNode(true);    // true = deep clone (includes children)

Event Listeners

JavaScript
const btn = document.querySelector("#submit-btn");

// Add listener
btn.addEventListener("click", function(event) {
  console.log("clicked!", event);
});

// Arrow function (modern)
btn.addEventListener("click", (e) => {
  e.preventDefault();  // stop default behavior (form submit, link navigate)
  console.log("x:", e.clientX, "y:", e.clientY);
});

// Remove listener — must pass the same function reference
function handleClick(e) { console.log("clicked"); }
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);

// Listen once (auto-removes after first trigger)
btn.addEventListener("click", handleClick, { once: true });

Common Events

JavaScript
// Mouse events
el.addEventListener("click", handler);
el.addEventListener("dblclick", handler);
el.addEventListener("mouseenter", handler); // no bubbling
el.addEventListener("mouseleave", handler); // no bubbling
el.addEventListener("mouseover", handler);  // bubbles
el.addEventListener("mouseout", handler);   // bubbles
el.addEventListener("contextmenu", handler); // right click

// Keyboard events
document.addEventListener("keydown", (e) => {
  console.log(e.key, e.code, e.ctrlKey, e.shiftKey);
  if (e.key === "Enter") { /* submit */ }
  if (e.key === "Escape") { /* close modal */ }
  if (e.ctrlKey && e.key === "s") {
    e.preventDefault();
    saveDocument();
  }
});

// Form events
form.addEventListener("submit", (e) => {
  e.preventDefault();  // always prevent default on forms
  const data = new FormData(form);
  data.get("username");
});

input.addEventListener("input", (e) => {
  // fires on every keystroke
  console.log(e.target.value);
});

input.addEventListener("change", (e) => {
  // fires when focus leaves after value changed
  validateInput(e.target.value);
});

// Window/document events
window.addEventListener("load", () => { /* page fully loaded */ });
document.addEventListener("DOMContentLoaded", () => { /* DOM ready */ });
window.addEventListener("resize", () => { console.log(window.innerWidth); });
window.addEventListener("scroll", () => { console.log(window.scrollY); });

Event Delegation

Instead of adding listeners to every item (expensive, doesn't work for dynamic items), add ONE listener to the parent.

JavaScript
// Without delegation — breaks for dynamically added items
document.querySelectorAll(".delete-btn").forEach(btn => {
  btn.addEventListener("click", deleteItem); // won't work for future items
});

// With delegation — one listener handles all current AND future items
document.querySelector(".item-list").addEventListener("click", (e) => {
  const deleteBtn = e.target.closest(".delete-btn");
  if (!deleteBtn) return; // click was elsewhere in the list

  const itemId = deleteBtn.dataset.id;
  deleteItem(itemId);
});

Practical Example: Todo List

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

let todos = [];

form.addEventListener("submit", (e) => {
  e.preventDefault();
  const text = input.value.trim();
  if (!text) return;

  const todo = { id: Date.now(), text, done: false };
  todos.push(todo);
  input.value = "";
  renderTodos();
});

// Event delegation for complete and delete
list.addEventListener("click", (e) => {
  const id = Number(e.target.closest("li")?.dataset.id);
  if (!id) return;

  if (e.target.classList.contains("complete-btn")) {
    todos = todos.map(t => t.id === id ? { ...t, done: !t.done } : t);
  }
  if (e.target.classList.contains("delete-btn")) {
    todos = todos.filter(t => t.id !== id);
  }
  renderTodos();
});

function renderTodos() {
  list.innerHTML = todos.map(todo => `
    <li data-id="${todo.id}" class="${todo.done ? "done" : ""}">
      <span>${todo.text}</span>
      <button class="complete-btn">${todo.done ? "Undo" : "Done"}</button>
      <button class="delete-btn">Delete</button>
    </li>
  `).join("");
}

Key Takeaways

  1. querySelector/querySelectorAll — the modern way to select elements
  2. textContent for setting text (safe), innerHTML for HTML (watch XSS)
  3. classList.add/remove/toggle — cleaner than manipulating className
  4. addEventListener — always use this, not onclick attributes
  5. Event delegation — one parent listener beats many child listeners
  6. e.preventDefault() — stops form submit, link navigation, right-click menu