Skip to main content

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"
Browser API

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);
HTMLCollection is Live

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
// 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');
// 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];
Use querySelector

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>';
XSS Risk

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-* for Custom Data

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
Use CSS Classes

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');
Performance
  • 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
Next Steps

In Module 14, you'll learn about Events and Event Handling to make your applications truly interactive.


Practice Exercises

  1. Build a dynamic form with validation and error messages
  2. Create an image gallery with thumbnails and lightbox
  3. Implement a filterable product list
  4. Build a tabbed interface component
  5. Create a drag-and-drop sortable list
  6. Implement a modal/dialog component
  7. Build a breadcrumb navigation dynamically
  8. Create a tree view component for hierarchical data

Additional Resources