Module 13: DOM Manipulation
The Document Object Model (DOM) is the bridge between JavaScript and HTML. Mastering DOM manipulation is essential for creating dynamic, interactive web applications.
1. Understanding the DOM
1.1 What is the DOM?
The DOM is a programming interface that represents HTML documents as a tree structure of objects that can be manipulated with JavaScript.
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1 id="heading">Hello World</h1>
<p class="text">This is a paragraph</p>
</body>
</html>
DOM Tree:
document
└── html
├── head
│ └── title
│ └── "Page Title"
└── body
├── h1#heading
│ └── "Hello World"
└── p.text
└── "This is a paragraph"
The DOM is not part of JavaScript itself—it's a Web API provided by browsers.
1.2 The document Object
console.log(document.title); // Page title
console.log(document.URL); // Current URL
console.log(document.domain); // Domain name
console.log(document.doctype); // Document type
console.log(document.body); // <body> element
console.log(document.head); // <head> element
2. Selecting Elements
2.1 getElementById
const heading = document.getElementById('heading');
console.log(heading); // <h1 id="heading">Hello World</h1>
// Returns null if not found
const notFound = document.getElementById('nonexistent');
console.log(notFound); // null
2.2 getElementsByClassName
const textElements = document.getElementsByClassName('text');
console.log(textElements); // HTMLCollection [p.text, div.text, ...]
// Returns empty HTMLCollection if not found
console.log(textElements.length); // Number of elements
// Convert to array
const array = Array.from(textElements);
getElementsByClassName returns a live HTMLCollection that updates automatically when the DOM changes.
2.3 getElementsByTagName
const paragraphs = document.getElementsByTagName('p');
const allElements = document.getElementsByTagName('*'); // All elements
console.log(paragraphs[0]); // First paragraph
2.4 querySelector (Modern - Recommended)
// Returns first match
const heading = document.querySelector('#heading');
const firstPara = document.querySelector('.text');
const firstDiv = document.querySelector('div');
// Complex selectors
const nested = document.querySelector('.container .item');
const attribute = document.querySelector('[data-id="123"]');
const pseudo = document.querySelector('li:first-child');
// Returns null if not found
const notFound = document.querySelector('.nonexistent');
2.5 querySelectorAll (Modern - Recommended)
// Returns NodeList (static)
const allText = document.querySelectorAll('.text');
const allDivs = document.querySelectorAll('div');
// Iterate with forEach
allText.forEach(element => {
console.log(element.textContent);
});
// Convert to array
const array = Array.from(allText);
const spread = [...allText];
Prefer querySelector and querySelectorAll for their flexibility and consistency. They support all CSS selectors.
2.6 Comparison
// Older methods
document.getElementById('myId'); // Fast but limited
document.getElementsByClassName('myClass'); // Live collection
document.getElementsByTagName('div'); // Live collection
// Modern methods (preferred)
document.querySelector('#myId'); // Flexible
document.querySelector('.myClass'); // Static NodeList
document.querySelectorAll('div'); // Static NodeList
3. Modifying Content
3.1 textContent
const heading = document.querySelector('#heading');
// Get text
console.log(heading.textContent); // "Hello World"
// Set text (plain text only, no HTML)
heading.textContent = 'New Heading';
heading.textContent = '<strong>Bold</strong>'; // Displays as text, not HTML
3.2 innerHTML
const container = document.querySelector('.container');
// Get HTML
console.log(container.innerHTML);
// Set HTML (parses HTML tags)
container.innerHTML = '<p>New <strong>content</strong></p>';
// Append HTML
container.innerHTML += '<p>Additional content</p>';
Be cautious with innerHTML when inserting user-generated content. It can execute scripts and cause security vulnerabilities.
3.3 innerText vs textContent
const div = document.querySelector('div');
// textContent: Gets all text including hidden
console.log(div.textContent);
// innerText: Gets only visible text (respects CSS)
console.log(div.innerText);
// Setting is similar for both
div.textContent = 'New text';
div.innerText = 'New text';
3.4 outerHTML
const div = document.querySelector('div');
// Get element and its HTML
console.log(div.outerHTML); // <div>Content</div>
// Replace entire element
div.outerHTML = '<section>New element</section>';
// div no longer exists in DOM
4. Modifying Attributes
4.1 getAttribute / setAttribute
const link = document.querySelector('a');
// Get attribute
const href = link.getAttribute('href');
console.log(href); // "https://example.com"
// Set attribute
link.setAttribute('href', 'https://newsite.com');
link.setAttribute('target', '_blank');
// Check if attribute exists
if (link.hasAttribute('target')) {
console.log('Has target attribute');
}
// Remove attribute
link.removeAttribute('target');
4.2 Direct Property Access
const input = document.querySelector('input');
// Common properties
console.log(input.id);
console.log(input.className);
console.log(input.value);
console.log(input.type);
// Set properties
input.id = 'username';
input.className = 'form-input';
input.value = 'John';
input.type = 'email';
4.3 data-* Attributes
// HTML: <div data-user-id="123" data-role="admin">
const div = document.querySelector('div');
// Access via dataset
console.log(div.dataset.userId); // "123"
console.log(div.dataset.role); // "admin"
// Set data attributes
div.dataset.status = 'active';
div.dataset.lastLogin = '2024-03-15';
// Remove
delete div.dataset.role;
Use data-* attributes to store custom data on elements. Access them via the dataset property.
5. Modifying Styles
5.1 Inline Styles
const box = document.querySelector('.box');
// Set individual styles
box.style.color = 'red';
box.style.backgroundColor = 'blue'; // camelCase for CSS properties
box.style.fontSize = '20px';
box.style.marginTop = '10px';
// Get computed style
const computed = window.getComputedStyle(box);
console.log(computed.color);
console.log(computed.fontSize);
5.2 cssText
const box = document.querySelector('.box');
// Set multiple styles at once
box.style.cssText = 'color: red; background: blue; font-size: 20px;';
// Or with template literals
box.style.cssText = `
color: red;
background-color: blue;
padding: 10px;
border-radius: 5px;
`;
5.3 CSS Classes (Preferred)
const element = document.querySelector('.item');
// classList API
element.classList.add('active');
element.classList.remove('hidden');
element.classList.toggle('highlight'); // Add if absent, remove if present
// Check if class exists
if (element.classList.contains('active')) {
console.log('Element is active');
}
// Replace class
element.classList.replace('old-class', 'new-class');
// Add multiple classes
element.classList.add('class1', 'class2', 'class3');
// Get all classes
console.log(element.classList); // DOMTokenList
Prefer manipulating CSS classes over inline styles. It's more maintainable and performant.
6. Creating Elements
6.1 createElement
// Create element
const div = document.createElement('div');
const para = document.createElement('p');
const link = document.createElement('a');
// Set properties
div.id = 'container';
div.className = 'wrapper';
para.textContent = 'This is a paragraph';
link.href = 'https://example.com';
link.textContent = 'Click here';
console.log(div); // <div id="container" class="wrapper"></div>
6.2 createTextNode
const text = document.createTextNode('Hello World');
const para = document.createElement('p');
para.appendChild(text);
console.log(para); // <p>Hello World</p>
6.3 cloneNode
const original = document.querySelector('.item');
// Shallow clone (element only)
const shallowClone = original.cloneNode();
// Deep clone (element and all descendants)
const deepClone = original.cloneNode(true);
document.body.appendChild(deepClone);
7. Adding/Removing Elements
7.1 appendChild
const container = document.querySelector('.container');
const newDiv = document.createElement('div');
newDiv.textContent = 'New element';
// Append as last child
container.appendChild(newDiv);
7.2 insertBefore
const container = document.querySelector('.container');
const newDiv = document.createElement('div');
const reference = container.querySelector('.item');
// Insert before reference element
container.insertBefore(newDiv, reference);
7.3 Modern Methods (Preferred)
const container = document.querySelector('.container');
const element = document.querySelector('.item');
// Prepend (insert at beginning)
container.prepend(newDiv);
// Append (insert at end)
container.append(newDiv);
// Before (insert before element)
element.before(newDiv);
// After (insert after element)
element.after(newDiv);
// Can insert multiple elements
container.append(div1, div2, div3);
container.append('Text', element, 'More text');
7.4 insertAdjacentHTML
const element = document.querySelector('.item');
// beforebegin: Before the element
element.insertAdjacentHTML('beforebegin', '<div>Before</div>');
// afterbegin: Inside element, before first child
element.insertAdjacentHTML('afterbegin', '<div>First child</div>');
// beforeend: Inside element, after last child
element.insertAdjacentHTML('beforeend', '<div>Last child</div>');
// afterend: After the element
element.insertAdjacentHTML('afterend', '<div>After</div>');
7.5 Removing Elements
const element = document.querySelector('.item');
// Modern method (preferred)
element.remove();
// Old method
element.parentNode.removeChild(element);
// Remove all children
const container = document.querySelector('.container');
container.innerHTML = ''; // Quick but not ideal
// Better: Remove children individually
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Or with modern method
container.replaceChildren(); // Removes all children
7.6 Replacing Elements
const oldElement = document.querySelector('.old');
const newElement = document.createElement('div');
newElement.textContent = 'New content';
// Modern method
oldElement.replaceWith(newElement);
// Old method
oldElement.parentNode.replaceChild(newElement, oldElement);
8. Navigating the DOM
8.1 Parent Navigation
const element = document.querySelector('.child');
// Direct parent
const parent = element.parentNode;
const parentElement = element.parentElement; // Usually the same
// Closest ancestor matching selector
const container = element.closest('.container');
const form = element.closest('form');
8.2 Child Navigation
const container = document.querySelector('.container');
// All children
const children = container.children; // HTMLCollection (elements only)
const childNodes = container.childNodes; // NodeList (includes text nodes)
// First/last child
const first = container.firstElementChild;
const last = container.lastElementChild;
// Check if has children
if (container.hasChildNodes()) {
console.log('Has children');
}
8.3 Sibling Navigation
const element = document.querySelector('.item');
// Next sibling
const next = element.nextElementSibling;
// Previous sibling
const prev = element.previousElementSibling;
// All siblings
function getSiblings(element) {
const siblings = [];
let sibling = element.parentNode.firstElementChild;
while (sibling) {
if (sibling !== element) {
siblings.push(sibling);
}
sibling = sibling.nextElementSibling;
}
return siblings;
}
9. Practical Examples
9.1 Building a Todo List
const todoList = document.querySelector('#todo-list');
const input = document.querySelector('#todo-input');
const addBtn = document.querySelector('#add-btn');
function addTodo() {
const text = input.value.trim();
if (!text) return;
// Create elements
const li = document.createElement('li');
li.className = 'todo-item';
const span = document.createElement('span');
span.textContent = text;
const deleteBtn = document.createElement('button');
deleteBtn.textContent = 'Delete';
deleteBtn.onclick = () => li.remove();
// Assemble
li.appendChild(span);
li.appendChild(deleteBtn);
todoList.appendChild(li);
// Clear input
input.value = '';
}
addBtn.addEventListener('click', addTodo);
9.2 Dynamic Table
function createTable(data) {
const table = document.createElement('table');
// Header
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
Object.keys(data[0]).forEach(key => {
const th = document.createElement('th');
th.textContent = key;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Body
const tbody = document.createElement('tbody');
data.forEach(row => {
const tr = document.createElement('tr');
Object.values(row).forEach(value => {
const td = document.createElement('td');
td.textContent = value;
tr.appendChild(td);
});
tbody.appendChild(tr);
});
table.appendChild(tbody);
return table;
}
// Usage
const data = [
{ name: 'John', age: 30, city: 'New York' },
{ name: 'Jane', age: 25, city: 'London' }
];
const table = createTable(data);
document.body.appendChild(table);
9.3 Accordion Component
function createAccordion(items) {
const accordion = document.createElement('div');
accordion.className = 'accordion';
items.forEach((item, index) => {
const section = document.createElement('div');
section.className = 'accordion-section';
const header = document.createElement('button');
header.className = 'accordion-header';
header.textContent = item.title;
const content = document.createElement('div');
content.className = 'accordion-content';
content.textContent = item.content;
content.style.display = 'none';
header.addEventListener('click', () => {
const isOpen = content.style.display === 'block';
// Close all
accordion.querySelectorAll('.accordion-content').forEach(c => {
c.style.display = 'none';
});
// Toggle current
if (!isOpen) {
content.style.display = 'block';
}
});
section.appendChild(header);
section.appendChild(content);
accordion.appendChild(section);
});
return accordion;
}
10. Performance Considerations
10.1 Minimize DOM Access
// ❌ Bad: Multiple DOM access
for (let i = 0; i < 1000; i++) {
document.querySelector('.container').innerHTML += '<div>Item</div>';
}
// ✅ Good: Cache reference
const container = document.querySelector('.container');
let html = '';
for (let i = 0; i < 1000; i++) {
html += '<div>Item</div>';
}
container.innerHTML = html;
10.2 DocumentFragment
// ✅ Best: Use DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
// Single DOM update
container.appendChild(fragment);
10.3 Batch Style Changes
// ❌ Bad: Multiple reflows
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = 'red';
// ✅ Good: Single reflow
element.style.cssText = 'width: 100px; height: 100px; background-color: red;';
// Or use classes
element.classList.add('styled-element');
- Cache DOM references
- Use DocumentFragment for bulk insertions
- Batch style changes
- Minimize reflows and repaints
11. Best Practices
11.1 Semantic HTML
// ❌ Bad
const div = document.createElement('div');
div.textContent = 'Click me';
div.onclick = handleClick;
// ✅ Good
const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', handleClick);
11.2 Avoid innerHTML for User Input
// ❌ Dangerous: XSS vulnerability
const userInput = '<script>alert("XSS")</script>';
element.innerHTML = userInput;
// ✅ Safe: Use textContent
element.textContent = userInput;
// ✅ Safe: Sanitize HTML
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
11.3 Use Event Delegation
// ❌ Bad: Attach listener to each item
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// ✅ Good: Single listener on parent
container.addEventListener('click', (e) => {
if (e.target.matches('.item')) {
handleClick(e);
}
});
Summary
In this module, you learned:
- ✅ Understanding the DOM structure
- ✅ Selecting elements with various methods
- ✅ Modifying content, attributes, and styles
- ✅ Creating and manipulating elements
- ✅ Navigating the DOM tree
- ✅ Building interactive components
- ✅ Performance optimization techniques
- ✅ Best practices and security considerations
In Module 14, you'll learn about Events and Event Handling to make your applications truly interactive.
Practice Exercises
- Build a dynamic form with validation and error messages
- Create an image gallery with thumbnails and lightbox
- Implement a filterable product list
- Build a tabbed interface component
- Create a drag-and-drop sortable list
- Implement a modal/dialog component
- Build a breadcrumb navigation dynamically
- Create a tree view component for hierarchical data