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​