DOM Manipulation
Creating and Modifying Elements
Explanation
What is DOM Manipulation?
DOM manipulation is the process of using JavaScript to modify the document structure, content, or styling. It's the foundation of dynamic web pages.
Key Operations
| Operation | Methods | |-----------|---------| | Create | createElement, createTextNode | | Insert | appendChild, insertBefore, append | | Remove | remove, removeChild | | Modify | textContent, innerHTML, attributes | | Clone | cloneNode |
Demonstration
Example 1: Creating Elements
// Create element
const div = document.createElement('div');
div.className = 'card';
div.id = 'my-card';
// Set content
div.textContent = 'Hello World'; // Safe, escapes HTML
div.innerHTML = '<strong>Hello</strong> World'; // Parses HTML
// Set attributes
div.setAttribute('data-id', '123');
div.dataset.category = 'featured'; // data-category="featured"
div.setAttribute('aria-label', 'Card description');
// Set styles
div.style.backgroundColor = 'blue';
div.style.padding = '20px';
div.style.cssText = 'background: blue; padding: 20px;';
// Create complex structure
const card = document.createElement('div');
card.className = 'card';
const header = document.createElement('header');
header.textContent = 'Card Title';
const body = document.createElement('div');
body.className = 'card-body';
body.textContent = 'Card content here';
card.appendChild(header);
card.appendChild(body);
// Using template literals (careful with XSS!)
function createCard(data) {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = `
<header>${escapeHtml(data.title)}</header>
<div class="card-body">${escapeHtml(data.content)}</div>
`;
return div;
}
// HTML escaping utility
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
Example 2: Inserting Elements
const container = document.getElementById('container');
const newElement = document.createElement('div');
// appendChild - add as last child
container.appendChild(newElement);
// insertBefore - insert before reference node
const reference = container.firstChild;
container.insertBefore(newElement, reference);
// Modern insertion methods (more flexible)
container.append(element1, element2, 'text'); // Adds at end
container.prepend(element); // Adds at beginning
container.after(element); // Insert after container
container.before(element); // Insert before container
// Replace element
const oldElement = document.getElementById('old');
const newEl = document.createElement('div');
oldElement.replaceWith(newEl);
// Or: oldElement.parentNode.replaceChild(newEl, oldElement);
// Insert at specific position
container.insertAdjacentHTML('beforebegin', '<div>Before</div>');
container.insertAdjacentHTML('afterbegin', '<div>First child</div>');
container.insertAdjacentHTML('beforeend', '<div>Last child</div>');
container.insertAdjacentHTML('afterend', '<div>After</div>');
// insertAdjacentElement (safer than innerHTML)
const el = document.createElement('div');
container.insertAdjacentElement('beforeend', el);
// Insert text
container.insertAdjacentText('beforeend', 'Some text');
// Batch insertion with DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
Example 3: Removing and Cloning
// Remove element
const element = document.getElementById('to-remove');
element.remove(); // Modern way
// Or from parent
element.parentNode.removeChild(element);
// Remove all children
const container = document.getElementById('container');
// Method 1: innerHTML (fast but loses event listeners)
container.innerHTML = '';
// Method 2: Loop removal
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Method 3: replaceChildren (modern)
container.replaceChildren(); // Empty
container.replaceChildren(newChild1, newChild2); // Replace with new
// Clone element
const original = document.getElementById('template');
const shallowClone = original.cloneNode(false); // Element only
const deepClone = original.cloneNode(true); // With children
// Clone with event listeners (use delegation instead)
// Event listeners are NOT cloned
// Using template element
const template = document.getElementById('card-template');
const clone = template.content.cloneNode(true);
document.body.appendChild(clone);
Example 4: Modifying Attributes
const element = document.querySelector('.my-element');
// Standard attributes
element.id = 'new-id';
element.className = 'class1 class2';
// classList API
element.classList.add('active');
element.classList.remove('inactive');
element.classList.toggle('visible');
element.classList.toggle('active', shouldBeActive); // Force add/remove
element.classList.replace('old-class', 'new-class');
element.classList.contains('active'); // Check
// Multiple classes
element.classList.add('class1', 'class2', 'class3');
// Generic attribute methods
element.getAttribute('data-id');
element.setAttribute('data-id', '123');
element.removeAttribute('data-id');
element.hasAttribute('data-id');
// Data attributes
element.dataset.userId = '123'; // Sets data-user-id
console.log(element.dataset.userId); // Gets data-user-id
delete element.dataset.userId; // Removes
// Form element properties
const input = document.querySelector('input');
input.value = 'Hello';
input.checked = true;
input.disabled = false;
input.required = true;
// Boolean attributes
const checkbox = document.querySelector('[type="checkbox"]');
checkbox.checked = true; // Check
checkbox.checked = false; // Uncheck
// Custom properties (not reflected in HTML)
element.customData = { foo: 'bar' };
Example 5: Modifying Styles
const element = document.querySelector('.box');
// Inline styles
element.style.backgroundColor = 'red';
element.style.marginTop = '20px';
element.style.setProperty('--custom-color', 'blue');
element.style.cssText = 'background: red; padding: 10px;';
// Get computed style
const styles = getComputedStyle(element);
console.log(styles.backgroundColor);
console.log(styles.getPropertyValue('margin-top'));
// Toggle class for styling (preferred)
element.classList.toggle('highlighted');
// Animate with styles
async function fadeOut(element) {
element.style.transition = 'opacity 0.5s';
element.style.opacity = '0';
await new Promise(resolve =>
element.addEventListener('transitionend', resolve, { once: true })
);
element.remove();
}
// CSS custom properties
document.documentElement.style.setProperty('--primary-color', '#007bff');
// Style multiple elements
document.querySelectorAll('.card').forEach(card => {
card.style.border = '1px solid gray';
});
// Add stylesheet dynamically
const style = document.createElement('style');
style.textContent = `
.dynamic-class {
color: red;
font-weight: bold;
}
`;
document.head.appendChild(style);
Example 6: Working with Templates
<!-- HTML Template -->
<template id="card-template">
<div class="card">
<h2 class="card-title"></h2>
<p class="card-body"></p>
<button class="card-btn">Learn More</button>
</div>
</template>
// Using the template
function createCardFromTemplate(data) {
const template = document.getElementById('card-template');
const clone = template.content.cloneNode(true);
clone.querySelector('.card-title').textContent = data.title;
clone.querySelector('.card-body').textContent = data.body;
clone.querySelector('.card-btn').addEventListener('click', () => {
console.log('Clicked:', data.title);
});
return clone;
}
// Render multiple cards
const cards = [
{ title: 'Card 1', body: 'Content 1' },
{ title: 'Card 2', body: 'Content 2' },
];
const container = document.getElementById('cards');
const fragment = document.createDocumentFragment();
cards.forEach(card => {
fragment.appendChild(createCardFromTemplate(card));
});
container.appendChild(fragment);
// Component pattern
class Card {
constructor(data) {
this.data = data;
this.element = this.render();
}
render() {
const template = document.getElementById('card-template');
const clone = template.content.cloneNode(true);
clone.querySelector('.card-title').textContent = this.data.title;
clone.querySelector('.card-body').textContent = this.data.body;
// Store reference for updates
this.titleEl = clone.querySelector('.card-title');
this.bodyEl = clone.querySelector('.card-body');
return clone.firstElementChild;
}
update(data) {
this.data = { ...this.data, ...data };
this.titleEl.textContent = this.data.title;
this.bodyEl.textContent = this.data.body;
}
mount(parent) {
parent.appendChild(this.element);
return this;
}
unmount() {
this.element.remove();
}
}
const card = new Card({ title: 'Hello', body: 'World' });
card.mount(document.body);
card.update({ title: 'Updated Title' });
Key Takeaways:
- createElement for safe element creation
- textContent over innerHTML for security
- Use DocumentFragment for batch operations
- classList for managing classes
- Templates for reusable structures
Imitation
Challenge 1: Build a Todo List
Task: Create a todo list with add, remove, and toggle functionality.
Solution
class TodoList {
constructor(container) {
this.container = container;
this.todos = [];
this.render();
}
render() {
this.container.innerHTML = `
<form class="todo-form">
<input type="text" placeholder="Add todo..." required>
<button type="submit">Add</button>
</form>
<ul class="todo-list"></ul>
`;
this.form = this.container.querySelector('.todo-form');
this.input = this.container.querySelector('input');
this.list = this.container.querySelector('.todo-list');
this.form.addEventListener('submit', (e) => {
e.preventDefault();
this.addTodo(this.input.value);
this.input.value = '';
});
this.list.addEventListener('click', (e) => {
const li = e.target.closest('li');
if (!li) return;
if (e.target.matches('.delete-btn')) {
this.removeTodo(li.dataset.id);
} else {
this.toggleTodo(li.dataset.id);
}
});
}
addTodo(text) {
const todo = {
id: Date.now().toString(),
text,
completed: false
};
this.todos.push(todo);
this.renderTodo(todo);
}
renderTodo(todo) {
const li = document.createElement('li');
li.dataset.id = todo.id;
li.className = todo.completed ? 'completed' : '';
li.innerHTML = `
<span>${escapeHtml(todo.text)}</span>
<button class="delete-btn">×</button>
`;
this.list.appendChild(li);
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
const li = this.list.querySelector(`[data-id="${id}"]`);
li.classList.toggle('completed');
}
}
removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
const li = this.list.querySelector(`[data-id="${id}"]`);
li.remove();
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
new TodoList(document.getElementById('app'));
Practice
Exercise 1: Dynamic Table
Difficulty: Beginner
Create a function that generates a table from an array of objects.
Exercise 2: Modal Component
Difficulty: Intermediate
Build a reusable modal component with open/close functionality.
Summary
What you learned:
- Creating elements
- Inserting and removing
- Modifying attributes and styles
- Working with templates
- Component patterns
Next Steps:
- Read: DOM Events
- Practice: Build UI components
- Explore: Virtual DOM concepts
