Academic Block

DOM Manipulation Get Certified

The Document Object Model (DOM) is the browser’s live object representation of an HTML page. JavaScript can read and change that structure — selecting elements, updating text and styles, creating or removing nodes, and responding to user actions.

In this lesson you will learn the core methods for selecting, modifying, creating, and removing elements, plus the basics of listening for events. The interactive examples use small simulated DOM objects so they can run in the code editor.

Selecting Elements

The most common ways to find elements are getElementById(), querySelector(), and querySelectorAll().

What this code does: Simulate finding an element by ID and by CSS selector. In a real page these methods return actual DOM nodes.

function makeElement(tag, id) {
    return { tag: tag, id: id, textContent: "" };
}

const page = {
    elements: {
        "main-title": makeElement("h1", "main-title")
    },
    getElementById(id) {
        return this.elements[id] || null;
    },
    querySelector(selector) {
        if (selector.startsWith("#")) {
            return this.getElementById(selector.slice(1));
        }
        return null;
    }
};

const title = page.getElementById("main-title");
title.textContent = "Welcome";
console.log(title.textContent);

const sameTitle = page.querySelector("#main-title");
console.log(sameTitle === title);

Changing Text Content

textContent gets or sets the plain text inside an element. Prefer it over innerHTML when you only need text.

What this code does: Reading and writing textContent updates the visible text without interpreting HTML markup.

function makeElement(tag) {
    return { tag: tag, textContent: "" };
}

const heading = makeElement("h2");
heading.textContent = "Original title";
console.log(heading.textContent);

heading.textContent = "Updated title";
console.log(heading.textContent);

innerHTML vs textContent

innerHTML parses the string as HTML, while textContent treats everything as plain text. Using untrusted input with innerHTML is a security risk.

What this code does: Compare the two properties. innerHTML would create real tags on a real page; here we just store the string.

function makeElement(tag) {
    return {
        tag: tag,
        textContent: "",
        innerHTML: ""
    };
}

const box = makeElement("div");
box.textContent = "<strong>Bold</strong>";
box.innerHTML = "<strong>Bold</strong>";
console.log("textContent:", box.textContent);
console.log("innerHTML:", box.innerHTML);

Changing Styles

The style property lets you set individual CSS properties from JavaScript (using camelCase names).

What this code does: Setting properties on element.style changes appearance. In a real browser the changes would be visible immediately.

function makeElement(tag) {
    return {
        tag: tag,
        style: {}
    };
}

const box = makeElement("div");
box.style.backgroundColor = "navy";
box.style.color = "white";
box.style.padding = "12px";
console.log(box.style);

Working With Classes

classList provides clean methods to add, remove, toggle, and check CSS classes.

What this code does: classList.add, remove, and toggle manage class names without string concatenation.

function makeElement(tag) {
    const classes = new Set();
    return {
        tag: tag,
        classList: {
            add(name) { classes.add(name); },
            remove(name) { classes.delete(name); },
            toggle(name) {
                if (classes.has(name)) classes.delete(name);
                else classes.add(name);
            },
            contains(name) { return classes.has(name); },
            toString() { return [...classes].join(" "); }
        }
    };
}

const card = makeElement("div");
card.classList.add("card");
card.classList.add("highlighted");
console.log(card.classList.toString());
card.classList.toggle("highlighted");
console.log(card.classList.contains("highlighted"));

Creating New Elements

document.createElement() creates a brand-new element, which can then be inserted into the page with appendChild().

What this code does: createElement builds a new element in memory; appendChild inserts it into the page (or parent). Nothing shows until it is appended.

function makeElement(tag) {
    return {
        tag: tag,
        textContent: "",
        children: [],
        appendChild(child) { this.children.push(child); }
    };
}

const document = { createElement: makeElement };
const list = makeElement("ul");
const item = document.createElement("li");
item.textContent = "Buy groceries";
list.appendChild(item);
console.log(list.children);

Creating an element with createElement() alone doesn’t add it to the visible page — it must also be appended to an existing element that’s already part of the document.

Removing Elements

A real element can remove itself from the page directly by calling element.remove().

What this code does: remove() takes an element out of its parent. After removal, it is no longer in the children list.

function makeElement(tag) {
    const el = {
        tag: tag,
        textContent: "",
        children: [],
        parent: null,
        appendChild(child) {
            child.parent = this;
            this.children.push(child);
        },
        remove() {
            if (this.parent) {
                const index = this.parent.children.indexOf(this);
                this.parent.children.splice(index, 1);
                this.parent = null;
            }
        }
    };
    return el;
}

const list = makeElement("ul");
const completedItem = makeElement("li");
completedItem.textContent = "Finished task";
list.appendChild(completedItem);
console.log("Before removal:", list.children.length);
completedItem.remove();
console.log("After removal:", list.children.length);

Working With Attributes

setAttribute() and getAttribute() let you read and write any HTML attribute on an element, such as src, href, or custom data-* attributes.

What this code does: setAttribute and getAttribute read and write HTML attributes such as src or custom data-* values.

function makeElement(tag) {
    const attributes = {};
    return {
        tag: tag,
        setAttribute(name, value) { attributes[name] = value; },
        getAttribute(name) { return attributes[name]; }
    };
}

const profileImage = makeElement("img");
profileImage.setAttribute("src", "profile.jpg");
profileImage.setAttribute("data-user-id", "8842");
console.log(profileImage.getAttribute("src"));
console.log(profileImage.getAttribute("data-user-id"));

Responding to Events With addEventListener()

addEventListener() registers a function to run whenever a specific event, like a click, happens on an element. On a real page, the browser calls this function automatically when the user interacts with the element. Here, we call it ourselves to simulate that interaction.

What this code does: addEventListener registers a handler for an event type. Simulating a click runs the handler and updates the button’s text.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(eventType, handler) {
            listeners[eventType] = handler;
        },
        simulateEvent(eventType, eventObject) {
            if (listeners[eventType]) {
                listeners[eventType](eventObject);
            }
        }
    };
}

const subscribeButton = makeElement("button");
let clickCount = 0;
subscribeButton.addEventListener("click", function () {
    clickCount++;
    console.log("Button clicked! Total clicks:", clickCount);
});

// On a real page, this next line happens automatically when the user clicks.
subscribeButton.simulateEvent("click");
subscribeButton.simulateEvent("click");

The Event Object

The function passed to addEventListener() receives an event object containing details about what happened, such as which key was pressed or which element triggered the event.

What this code does: Listen for keyboard events on an input. The handler can react to specific keys (for example, Enter) when the user types.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        addEventListener(eventType, handler) { listeners[eventType] = handler; },
        simulateEvent(eventType, eventObject) {
            if (listeners[eventType]) listeners[eventType](eventObject);
        }
    };
}

const searchInput = makeElement("input");
searchInput.addEventListener("keydown", function (event) {
    console.log("Key pressed:", event.key);
});
searchInput.simulateEvent("keydown", { key: "Enter" });

Practical Example

Let’s put several of these pieces together to build a small to-do list renderer.

What this code does: Create list items from an array and append each one to a container. This is a common pattern for rendering dynamic lists.

function makeElement(tag) {
    return {
        tag: tag,
        textContent: "",
        children: [],
        appendChild(child) { this.children.push(child); }
    };
}

const document = { createElement: makeElement };
const taskList = makeElement("ul");
const tasks = ["Water the plants", "Reply to emails", "Walk the dog"];

function renderTasks(tasks, container) {
    tasks.forEach(function (taskText) {
        const item = document.createElement("li");
        item.textContent = taskText;
        container.appendChild(item);
    });
}

renderTasks(tasks, taskList);
taskList.children.forEach(function (item) {
    console.log("Rendered item:", item.textContent);
});

Common DOM Methods and Properties

Method / Property Purpose
getElementById() / querySelector() Find one element
querySelectorAll() Find all matching elements
textContent Get or set an element’s plain text
innerHTML Get or set an element’s HTML content
style Set individual CSS properties directly
classList Add, remove, or toggle CSS classes
createElement() / appendChild() Create and insert new elements
remove() Remove an element from the page
addEventListener() Respond to user interactions

Common Mistakes With DOM Manipulation

  • Trying to select an element before the page has finished loading, resulting in null — wrapping your code in a DOMContentLoaded listener avoids this.
  • Forgetting that getElementById() and querySelector() return null when no match is found, and then trying to use properties on that null value.
  • Setting innerHTML directly from untrusted user input, which can introduce security vulnerabilities.
  • Creating an element with createElement() but forgetting to actually append it anywhere, so nothing appears on the page.
  • Confusing textContent (plain text) with innerHTML (parsed as markup).

Best Practices for DOM Manipulation

  • Prefer textContent over innerHTML when you’re only working with plain text.
  • Toggle CSS classes with classList instead of setting many individual style properties directly.
  • Always check whether a selected element actually exists before trying to use it.
  • Use addEventListener() instead of inline onclick HTML attributes, to keep behavior separate from markup.
  • Batch multiple DOM changes together when possible, since frequent updates can affect page performance.

Quick Summary

  • The DOM is the browser’s object representation of an HTML page, which JavaScript can read and modify.
  • getElementById(), querySelector(), and querySelectorAll() select elements.
  • textContent, innerHTML, style, and classList change an element’s appearance and content.
  • createElement() and appendChild() add new elements; remove() takes them away.
  • addEventListener() lets your code respond to clicks, key presses, and other user interactions.

Try It Yourself

Modify the code below. Try changing the button’s click behavior, or adding a second click listener.

What this code does: Practice example: a click listener increments a like counter and updates the button label. Try adding another listener or changing the text, then click Test My Code.

function makeElement(tag) {
    const listeners = {};
    return {
        tag: tag,
        textContent: "",
        addEventListener(eventType, handler) { listeners[eventType] = handler; },
        simulateEvent(eventType) { if (listeners[eventType]) listeners[eventType](); }
    };
}

const likeButton = makeElement("button");
let likeCount = 0;
likeButton.addEventListener("click", function () {
    likeCount++;
    likeButton.textContent = "Liked (" + likeCount + ")";
    console.log(likeButton.textContent);
});

likeButton.simulateEvent("click");
likeButton.simulateEvent("click");
likeButton.simulateEvent("click");
Tip: The methods and properties shown here work exactly the same way on a real web page — try copying any of these snippets into your browser’s developer console on an actual page (adjusting the element IDs to match) to see them affect the page for real.

Understanding DOM manipulation is essential before moving on to event delegation, forms, and more advanced browser-based JavaScript concepts.

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close