-
-
Notifications
You must be signed in to change notification settings - Fork 2
JavaScript DOM Manipulation
Mattscreative edited this page Feb 21, 2026
·
1 revision
- Introduction
- Understanding the DOM
- Selecting Elements
- Working with Element Content
- Working with Attributes
- Working with CSS Styles
- Working with Classes
- Creating and Removing Elements
- Event Handling
- Working with Forms
- Practical Examples
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.
// 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// 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// 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// 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);// 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');// 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)// 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 elementconst 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 classconst 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"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 variablesconst 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);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// 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 reflowconst 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 });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 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');
});// 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 listenerconst 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();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');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">×</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();
});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>// 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();