Skip to content

JavaScript DOM Manipulation

Mattscreative edited this page Feb 21, 2026 · 1 revision

JavaScript DOM Manipulation Guide - Complete Reference

Table of Contents


Introduction

The Document Object Model (DOM) is a programming interface for web documents. It represents the page so that programs can change the document structure, style, and content. JavaScript interacts with the DOM to create dynamic web pages.


Understanding the DOM

// The DOM tree structure
// document (root)
//   └── html
//        ├── head
//        │    ├── title
//        │    └── meta
//        └── body
//             ├── header
//             ├── main
//             │    ├── section
//             │    └── article
//             └── footer

// Every part of the page is a "node" in this tree
console.log(document);  // The entire document
console.log(document.body);  // The body element
console.log(document.head);  // The head element
console.log(document.documentElement);  // The html element

Selecting Elements

By ID

// Get element by ID - returns single element or null
const header = document.getElementById('header');
const main = document.getElementById('main-content');
const nonExistent = document.getElementById('does-not-exist');

console.log(header);  // <header id="header">...</header>
console.log(nonExistent);  // null

By Class or Tag

// Get elements by class name - returns HTMLCollection (live)
const buttons = document.getElementsByClassName('btn');
console.log(buttons.length);  // Number of elements with class "btn"

// Get elements by tag name - returns HTMLCollection (live)
const paragraphs = document.getElementsByTagName('p');
const divs = document.getElementsByTagName('div');

console.log(paragraphs.length);  // Number of <p> elements

By CSS Selector

// querySelector - returns first match or null
const firstButton = document.querySelector('.btn');
const firstInput = document.querySelector('input[type="email"]');
const navItem = document.querySelector('#nav li.active');

// querySelectorAll - returns NodeList of all matches
const allButtons = document.querySelectorAll('.btn');
const allSections = document.querySelectorAll('section');
const allItems = document.querySelectorAll('#nav li');

// NodeList is NOT live - it's a snapshot
console.log(allButtons.length);  // Number of buttons

// Loop through NodeList
allButtons.forEach(btn => {
    console.log(btn.textContent);
});

// Convert to array
const buttonsArray = Array.from(allButtons);

Other Selection Methods

// Get first child element (skipping text nodes)
const firstChild = element.firstElementChild;

// Get last child element
const lastChild = element.lastElementChild;

// Get parent element
const parent = element.parentElement;

// Get siblings
const nextSibling = element.nextElementSibling;
const prevSibling = element.previousElementSibling;

// Get children
const children = element.children;  // HTMLCollection

// Closest ancestor matching selector
const ancestor = element.closest('.container');

Working with Element Content

Text Content

// textContent - gets/sets ALL text content (including hidden)
const element = document.getElementById('myElement');
console.log(element.textContent);  // Get all text
element.textContent = 'New text content';  // Set text (escapes HTML)

// innerText - gets/sets VISIBLE text (respects CSS)
console.log(element.innerText);  // Get visible text
element.innerText = 'Visible text';  // Set visible text

// Difference:
const hidden = document.createElement('div');
hidden.style.display = 'none';
hidden.textContent = 'Hidden text';
document.body.appendChild(hidden);

console.log(hidden.textContent);    // "Hidden text"
console.log(hidden.innerText);      // "" (empty - not visible)

HTML Content

// innerHTML - gets/sets HTML content (parses as HTML)
const container = document.getElementById('container');
console.log(container.innerHTML);  // Get HTML
container.innerHTML = '<p>New paragraph</p>';  // Set HTML

// Be careful with innerHTML - can overwrite event listeners
// This removes all existing children and their handlers

// insertAdjacentHTML - insert HTML at specific position
element.insertAdjacentHTML('beforebegin', '<p>Before element</p>');
element.insertAdjacentHTML('afterbegin', '<p>First child</p>');
element.insertAdjacentHTML('beforeend', '<p>Last child</p>');
element.insertAdjacentHTML('afterend', '<p>After element</p>');

// outerHTML - gets/sets entire element including the element itself
console.log(element.outerHTML);  // Get element as HTML string
element.outerHTML = '<div>New element</div>';  // Replace entire element

Working with Attributes

Getting and Setting Attributes

const input = document.querySelector('input[type="text"]');

// getAttribute - get attribute value
console.log(input.getAttribute('id'));
console.log(input.getAttribute('placeholder'));

// setAttribute - set attribute value
input.setAttribute('placeholder', 'Enter your name');
input.setAttribute('disabled', true);  // Add boolean attribute

// hasAttribute - check if attribute exists
if (input.hasAttribute('required')) {
    console.log('Input is required');
}

// removeAttribute - remove attribute
input.removeAttribute('disabled');

// Direct property access for common attributes
input.value = 'Hello';  // Get/set value
input.disabled = true;  // Get/set disabled
input.checked = true;  // Get/set checked (checkboxes)
input.id = 'myInput';  // Get/set id
input.className = 'btn primary';  // Get/set class

Working with Data Attributes

const element = document.querySelector('.user');

// data-* attributes become dataset properties
element.dataset.userId = '12345';
element.dataset.role = 'admin';
element.dataset['customField'] = 'value';

// Remove data attribute
delete element.dataset.userId;

// Access all data attributes
console.log(element.dataset);  // DOMStringMap object

// HTML: <div data-user-id="123" data-name="Alice"></div>
// JS: element.dataset.userId → "123"
// JS: element.dataset.name → "Alice"

Working with CSS Styles

Inline Styles

const element = document.getElementById('myElement');

// Set individual styles
element.style.color = 'red';
element.style.backgroundColor = '#f0f0f0';
element.style.fontSize = '16px';
element.style.display = 'none';
element.style.border = '1px solid black';

// CSS properties with hyphens become camelCase
element.style.borderRadius = '5px';
element.style.backgroundImage = 'url("image.jpg")';

// Get inline style
console.log(element.style.color);  // Only returns inline styles

// Set multiple styles at once using CSSText
element.style.cssText = 'color: red; background: blue; padding: 10px;';

// Or use setProperty
element.style.setProperty('color', 'blue');
element.style.setProperty('--custom-color', 'green');  // CSS variables

Computed Styles

const element = document.getElementById('myElement');

// getComputedStyle - get computed styles (from all sources)
const styles = getComputedStyle(element);

console.log(styles.color);           // RGB color
console.log(styles.fontSize);       // Computed font size
console.log(styles.display);         // Display property
console.log(styles.backgroundColor); // Background color

// Get specific property
const width = getComputedStyle(element).width;
const height = getComputedStyle(element).height;

// Get CSS variable
const customColor = getComputedStyle(element).getPropertyValue('--custom-color');

// Get pseudo-element style
const beforeStyle = getComputedStyle(element, ':before');
console.log(beforeStyle.content);

Working with Classes

const element = document.getElementById('myElement');

// classList - provides methods for working with classes
element.classList.add('active');           // Add class
element.classList.remove('hidden');       // Remove class
element.classList.toggle('selected');      // Toggle class
const hasClass = element.classList.contains('active');  // Check class

// Add multiple classes
element.classList.add('btn', 'btn-primary', 'large');

// Remove multiple classes
element.classList.remove('btn', 'btn-small');

// Replace class
element.classList.replace('old-class', 'new-class');

// Get class count
console.log(element.classList.length);

// Access class by index
console.log(element.classList[0]);

// className - get/set entire class attribute
console.log(element.className);  // Get all classes
element.className = 'new-classes';  // Replaces all classes

Creating and Removing Elements

// Create new element
const newDiv = document.createElement('div');
newDiv.id = 'new-div';
newDiv.classList.add('container');
newDiv.textContent = 'Hello World';

// Add element to DOM
document.body.appendChild(newDiv);  // Add as last child

// Or insert at specific position
const container = document.getElementById('container');
container.appendChild(newDiv);

// insertBefore - insert before a reference node
container.insertBefore(newDiv, container.firstChild);

// Modern insertion methods
container.append(newDiv, 'Text node');  // Multiple items
container.prepend(newDiv);  // Insert as first child
container.before(newDiv);   // Insert before element
container.after(newDiv);    // Insert after element

// Clone element
const clone = originalElement.cloneNode(true);  // Deep clone (with children)
const shallowClone = originalElement.cloneNode(false);  // Just the element

// Remove element
newDiv.remove();  // Modern method
container.removeChild(newDiv);  // Old method

// Replace element
container.replaceChild(newElement, oldElement);
container.replaceWith(newElement);  // Modern method

// Creating document fragments (for performance)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const item = document.createElement('div');
    item.textContent = `Item ${i}`;
    fragment.appendChild(item);
}
container.appendChild(fragment);  // Single reflow

Event Handling

Adding Event Listeners

const button = document.getElementById('myButton');

// addEventListener - recommended way to add events
function handleClick(event) {
    console.log('Button clicked!');
}

button.addEventListener('click', handleClick);

// Add multiple event listeners
button.addEventListener('click', () => console.log('First handler'));
button.addEventListener('click', () => console.log('Second handler'));

// Remove event listener (needs named function)
button.removeEventListener('click', handleClick);

// Options for addEventListener
button.addEventListener('click', handler, {
    capture: false,    // Use capture phase
    once: true,       // Remove after first trigger
    passive: true     // Promise not to call preventDefault
});

// One-time event listener
button.addEventListener('click', () => console.log('Only once'), { once: true });

Event Object

element.addEventListener('click', function(event) {
    // event.type - type of event
    console.log(event.type);  // "click"
    
    // event.target - element that triggered event
    console.log(event.target);  // The clicked element
    
    // event.currentTarget - element with listener
    console.log(event.currentTarget);  // The element with addEventListener
    
    // event.preventDefault() - prevent default behavior
    event.preventDefault();
    
    // event.stopPropagation() - stop event from bubbling
    event.stopPropagation();
    
    // Mouse event properties
    console.log(event.clientX, event.clientY);  // Mouse position
    console.log(event.pageX, event.pageY);     // Position relative to document
    
    // Keyboard event properties
    console.log(event.key);       // Key pressed
    console.log(event.code);      // Physical key code
    console.log(event.shiftKey);  // Was shift held?
});

// Different events
element.addEventListener('click', handler);       // Mouse click
element.addEventListener('dblclick', handler);   // Double click
element.addEventListener('mouseenter', handler);  // Mouse enters
element.addEventListener('mouseleave', handler);  // Mouse leaves
element.addEventListener('mouseover', handler);  // Mouse enters (bubbles)
element.addEventListener('mouseout', handler);    // Mouse leaves (bubbles)

input.addEventListener('input', handler);    // Value changes
input.addEventListener('change', handler);   // Value changes (on blur)
input.addEventListener('focus', handler);   // Gets focus
input.addEventListener('blur', handler);    // Loses focus

document.addEventListener('keydown', handler);  // Key pressed
document.addEventListener('keyup', handler);    // Key released

form.addEventListener('submit', handler);   // Form submitted

Event Propagation

// Event phases: capture → target → bubble
// By default, listeners are in bubble phase

const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
const grandchild = document.querySelector('.grandchild');

// Stop propagation - prevent event from reaching other elements
grandchild.addEventListener('click', function(event) {
    event.stopPropagation();  // Parent won't hear the click
    console.log('Grandchild clicked');
});

parent.addEventListener('click', function(event) {
    console.log('Parent clicked');
});

// Stop immediate propagation - stop others on same element
grandchild.addEventListener('click', function(event) {
    event.stopImmediatePropagation();
    console.log('First handler');
});

grandchild.addEventListener('click', function(event) {
    console.log('Second handler - won\'t run');
});

// Capture phase - event goes from root down to target
parent.addEventListener('click', function(event) {
    console.log('Capture phase');
}, { capture: true });

// Bubble phase - event goes from target up to root (default)
parent.addEventListener('click', function(event) {
    console.log('Bubble phase');
});

Event Delegation

// Instead of adding listeners to each item, add to parent
const list = document.getElementById('todo-list');

// Add single listener to parent
list.addEventListener('click', function(event) {
    // Check if clicked element is an item
    if (event.target.matches('.todo-item')) {
        // Handle the item
        event.target.classList.toggle('completed');
    }
    
    // Handle delete button within item
    if (event.target.matches('.delete-btn')) {
        event.target.parentElement.remove();
    }
});

// Dynamically added items automatically work
const newItem = document.createElement('li');
newItem.className = 'todo-item';
newItem.textContent = 'New task';
list.appendChild(newItem);  // Works without adding new listener

Working with Forms

const form = document.getElementById('myForm');

// Get form data
const formData = new FormData(form);
const data = Object.fromEntries(formData);

// Or manually
const name = form.name.value;
const email = form.email.value;
const password = form.password.value;

// Checkbox
const subscribe = form.subscribe.checked;

// Radio buttons
const gender = form.gender.value;  // Selected radio's value

// Select dropdown
const country = form.country.value;  // Selected option value
const selectedOption = form.country.options[form.country.selectedIndex];

// Validate form
function validateForm(form) {
    const errors = [];
    
    if (!form.name.value.trim()) {
        errors.push('Name is required');
    }
    
    if (!form.email.value.includes('@')) {
        errors.push('Valid email is required');
    }
    
    if (form.password.value.length < 8) {
        errors.push('Password must be at least 8 characters');
    }
    
    return errors;
}

// Handle form submission
form.addEventListener('submit', function(event) {
    event.preventDefault();  // Prevent page reload
    
    const errors = validateForm(form);
    if (errors.length > 0) {
        console.log('Errors:', errors);
        return;
    }
    
    // Submit form data
    console.log('Form is valid!');
    // form.submit();  // Actually submit (if not using fetch)
});

// Reset form
form.reset();

Practical Examples

Todo List Application

class TodoList {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.todos = JSON.parse(localStorage.getItem('todos')) || [];
        this.init();
    }
    
    init() {
        this.render();
        this.setupEventListeners();
    }
    
    render() {
        this.container.innerHTML = this.todos.map((todo, index) => `
            <li class="todo-item ${todo.completed ? 'completed' : ''}">
                <input type="checkbox" ${todo.completed ? 'checked' : ''} data-index="${index}">
                <span>${todo.text}</span>
                <button class="delete-btn" data-index="${index}">Delete</button>
            </li>
        `).join('');
    }
    
    add(text) {
        this.todos.push({ text, completed: false });
        this.save();
        this.render();
    }
    
    toggle(index) {
        this.todos[index].completed = !this.todos[index].completed;
        this.save();
        this.render();
    }
    
    delete(index) {
        this.todos.splice(index, 1);
        this.save();
        this.render();
    }
    
    save() {
        localStorage.setItem('todos', JSON.stringify(this.todos));
    }
    
    setupEventListeners() {
        // Event delegation for dynamic elements
        this.container.addEventListener('click', (event) => {
            if (event.target.matches('input[type="checkbox"]')) {
                this.toggle(parseInt(event.target.dataset.index));
            }
            if (event.target.matches('.delete-btn')) {
                this.delete(parseInt(event.target.dataset.index));
            }
        });
        
        // Add todo form
        const form = document.getElementById('add-todo-form');
        form.addEventListener('submit', (event) => {
            event.preventDefault();
            const input = form.querySelector('input');
            if (input.value.trim()) {
                this.add(input.value.trim());
                input.value = '';
            }
        });
    }
}

// Usage
const todoList = new TodoList('todo-container');

Modal Dialog

class Modal {
    constructor(options = {}) {
        this.title = options.title || 'Modal';
        this.content = options.content || '';
        this.onClose = options.onClose || (() => {});
        this.create();
    }
    
    create() {
        // Create modal elements
        this.overlay = document.createElement('div');
        this.overlay.className = 'modal-overlay';
        
        this.modal = document.createElement('div');
        this.modal.className = 'modal';
        
        this.modal.innerHTML = `
            <div class="modal-header">
                <h2>${this.title}</h2>
                <button class="modal-close">&times;</button>
            </div>
            <div class="modal-content">${this.content}</div>
        `;
        
        this.overlay.appendChild(this.modal);
        document.body.appendChild(this.overlay);
        
        this.setupEventListeners();
    }
    
    setupEventListeners() {
        // Close button
        this.modal.querySelector('.modal-close').addEventListener('click', () => {
            this.close();
        });
        
        // Click outside to close
        this.overlay.addEventListener('click', (event) => {
            if (event.target === this.overlay) {
                this.close();
            }
        });
        
        // Escape key
        document.addEventListener('keydown', (event) => {
            if (event.key === 'Escape' && this.overlay.style.display !== 'none') {
                this.close();
            }
        });
    }
    
    open() {
        this.overlay.style.display = 'flex';
    }
    
    close() {
        this.overlay.style.display = 'none';
        this.onClose();
    }
    
    setContent(content) {
        this.modal.querySelector('.modal-content').innerHTML = content;
    }
}

// Usage
const modal = new Modal({
    title: 'Confirm Action',
    content: '<p>Are you sure you want to proceed?</p><button id="confirm-btn">Confirm</button>',
    onClose: () => console.log('Modal closed')
});

document.getElementById('open-modal-btn').addEventListener('click', () => {
    modal.open();
});

Tabs Component

class Tabs {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.tabs = this.container.querySelectorAll('.tab');
        this.panels = this.container.querySelectorAll('.tab-panel');
        this.activeTab = null;
        this.init();
    }
    
    init() {
        this.tabs.forEach(tab => {
            tab.addEventListener('click', () => {
                this.activate(tab.dataset.tab);
            });
        });
        
        // Activate first tab by default
        if (this.tabs.length > 0) {
            this.activate(this.tabs[0].dataset.tab);
        }
    }
    
    activate(tabId) {
        // Update tabs
        this.tabs.forEach(tab => {
            tab.classList.toggle('active', tab.dataset.tab === tabId);
        });
        
        // Update panels
        this.panels.forEach(panel => {
            panel.classList.toggle('active', panel.id === tabId);
        });
        
        this.activeTab = tabId;
    }
    
    getActiveTab() {
        return this.activeTab;
    }
}

// HTML structure expected:
// <div id="tabs">
//   <div class="tab-list">
//     <button class="tab" data-tab="tab1">Tab 1</button>
//     <button class="tab" data-tab="tab2">Tab 2</button>
//   </div>
//   <div id="tab1" class="tab-panel">Content 1</div>
//   <div id="tab2" class="tab-panel">Content 2</div>
// </div>

Quick Reference

// Selection
document.getElementById('id');
document.querySelector('.class');
document.querySelectorAll('.class');

// Content
element.textContent = 'text';
element.innerHTML = '<p>HTML</p>';

// Attributes
element.getAttribute('attr');
element.setAttribute('attr', 'value');
element.dataset.name = 'value';

// Styles
element.style.color = 'red';
getComputedStyle(element).color;

// Classes
element.classList.add('class');
element.classList.remove('class');
element.classList.toggle('class');

// Events
element.addEventListener('click', handler);
element.removeEventListener('click', handler);

// DOM
document.createElement('div');
parent.appendChild(child);
element.remove();

Clone this wiki locally