Dynamic DOM Construction
Creating and removing elements dynamically is like being a digital architect - you can build entire structures from scratch, tear them down, and rebuild them differently. This power allows you to create truly dynamic web applications that respond to user actions and data changes.
Creating Elements
JavaScript provides several methods to create new DOM elements programmatically.
Basic Element Creation
// Create a new div element
const div = document.createElement('div');
div.textContent = 'Hello, World!';
div.className = 'message';
div.id = 'welcome-message';
// Create and configure multiple elements
const button = document.createElement('button');
button.textContent = 'Click me';
button.className = 'btn btn-primary';
button.setAttribute('type', 'button');
// Create elements with complex structure
const card = document.createElement('div');
card.className = 'card';
const cardHeader = document.createElement('div');
cardHeader.className = 'card-header';
cardHeader.textContent = 'Card Title';
const cardBody = document.createElement('div');
cardBody.className = 'card-body';
cardBody.textContent = 'Card content goes here';
card.appendChild(cardHeader);
card.appendChild(cardBody);
// Creating text nodes
const textNode = document.createTextNode('This is pure text');
const paragraph = document.createElement('p');
paragraph.appendChild(textNode);
Creating Complex HTML Structures
// Function to create a product card
function createProductCard(product) {
const card = document.createElement('div');
card.className = 'product-card';
card.dataset.productId = product.id;
// Create image
const image = document.createElement('img');
image.src = product.imageUrl;
image.alt = product.name;
image.className = 'product-image';
// Create title
const title = document.createElement('h3');
title.className = 'product-title';
title.textContent = product.name;
// Create price
const price = document.createElement('div');
price.className = 'product-price';
price.textContent = `$${product.price.toFixed(2)}`;
// Create description
const description = document.createElement('p');
description.className = 'product-description';
description.textContent = product.description;
// Create button
const button = document.createElement('button');
button.className = 'add-to-cart';
button.textContent = 'Add to Cart';
button.onclick = () => addToCart(product.id);
// Assemble the card
card.appendChild(image);
card.appendChild(title);
card.appendChild(price);
card.appendChild(description);
card.appendChild(button);
return card;
}
// Usage
const product = {
id: 1,
name: 'Wireless Headphones',
price: 99.99,
description: 'Premium sound quality with noise cancellation',
imageUrl: '/images/headphones.jpg'
};
const productCard = createProductCard(product);
document.querySelector('.products-container').appendChild(productCard);
Inserting Elements into the DOM
There are multiple ways to insert elements into the document, each serving different purposes.
Common Insertion Methods
const container = document.querySelector('.container');
const newElement = document.createElement('div');
newElement.textContent = 'New Element';
// appendChild - Adds to the end of parent
container.appendChild(newElement);
// insertBefore - Inserts before a specific element
const referenceElement = document.querySelector('.reference');
container.insertBefore(newElement, referenceElement);
// insertAdjacentElement - More flexible positioning
// beforebegin: Before the element itself
// afterbegin: Just inside the element, before first child
// beforeend: Just inside the element, after last child
// afterend: After the element itself
const targetElement = document.querySelector('.target');
targetElement.insertAdjacentElement('beforebegin', newElement);
targetElement.insertAdjacentElement('afterbegin', newElement);
targetElement.insertAdjacentElement('beforeend', newElement);
targetElement.insertAdjacentElement('afterend', newElement);
// insertAdjacentHTML - Insert HTML string
targetElement.insertAdjacentHTML('beforeend', '<div>HTML content</div>');
// insertAdjacentText - Insert text
targetElement.insertAdjacentText('beforeend', 'Plain text content');
// Modern methods: prepend, append, before, after
container.prepend(newElement); // Add as first child
container.append(newElement); // Add as last child
targetElement.before(newElement); // Add before element
targetElement.after(newElement); // Add after element
// Replace an existing element
const oldElement = document.querySelector('.old');
const replacementElement = document.createElement('div');
replacementElement.textContent = 'Replacement';
oldElement.replaceWith(replacementElement);
Practical Insertion Examples
// Insert element at specific position
function insertAtPosition(parent, newElement, position) {
const children = parent.children;
if (position >= children.length) {
parent.appendChild(newElement);
} else {
parent.insertBefore(newElement, children[position]);
}
}
// Insert element in sorted order
function insertSorted(container, newItem, compareFunction) {
const items = Array.from(container.children);
const insertIndex = items.findIndex(item =>
compareFunction(newItem, item) < 0
);
if (insertIndex === -1) {
container.appendChild(newItem);
} else {
container.insertBefore(newItem, items[insertIndex]);
}
}
// Usage
const list = document.querySelector('.sorted-list');
const newItem = document.createElement('li');
newItem.textContent = 'Charlie';
newItem.dataset.name = 'Charlie';
insertSorted(list, newItem, (a, b) =>
a.dataset.name.localeCompare(b.dataset.name)
);
Using Document Fragments
Document fragments are lightweight containers that help optimize multiple DOM insertions.
Creating and Using Fragments
// Create a document fragment
const fragment = document.createDocumentFragment();
// Add multiple elements to fragment
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
div.className = 'list-item';
fragment.appendChild(div);
}
// Insert all at once (single reflow)
document.querySelector('.container').appendChild(fragment);
// Practical example: Building a table
function createTable(data, columns) {
const table = document.createElement('table');
const fragment = document.createDocumentFragment();
// Create header
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
columns.forEach(column => {
const th = document.createElement('th');
th.textContent = column.label;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
fragment.appendChild(thead);
// Create body
const tbody = document.createElement('tbody');
data.forEach(row => {
const tr = document.createElement('tr');
columns.forEach(column => {
const td = document.createElement('td');
td.textContent = row[column.field];
tr.appendChild(td);
});
tbody.appendChild(tr);
});
fragment.appendChild(tbody);
table.appendChild(fragment);
return table;
}
// Usage
const data = [
{ id: 1, name: 'John', age: 30 },
{ id: 2, name: 'Jane', age: 25 },
{ id: 3, name: 'Bob', age: 35 }
];
const columns = [
{ field: 'id', label: 'ID' },
{ field: 'name', label: 'Name' },
{ field: 'age', label: 'Age' }
];
const table = createTable(data, columns);
document.body.appendChild(table);
Removing Elements
Elements can be removed from the DOM in several ways.
Basic Removal Methods
// Remove element using remove() method
const element = document.querySelector('.removable');
element.remove(); // Modern approach
// Remove element using removeChild()
const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
parent.removeChild(child); // Traditional approach
// Remove all children
function removeAllChildren(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
}
// Alternative: Set innerHTML (less efficient)
parent.innerHTML = '';
// Remove elements matching condition
function removeElements(selector, condition) {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
if (condition(element)) {
element.remove();
}
});
}
// Usage
removeElements('.item', element =>
element.dataset.expired === 'true'
);
Advanced Removal Patterns
// Remove with animation
function removeWithFade(element, duration = 300) {
element.style.transition = `opacity ${duration}ms`;
element.style.opacity = '0';
setTimeout(() => {
element.remove();
}, duration);
}
// Remove and return element
function detachElement(element) {
const parent = element.parentNode;
if (parent) {
return parent.removeChild(element);
}
return null;
}
// Conditional removal with cleanup
function safeRemove(element) {
// Remove event listeners
const clone = element.cloneNode(true);
element.parentNode.replaceChild(clone, element);
// Clear data
if (element.dataset) {
Object.keys(element.dataset).forEach(key => {
delete element.dataset[key];
});
}
// Remove from DOM
clone.remove();
}
// Batch removal with performance optimization
function batchRemove(elements) {
// Create document fragment to hold removed elements
const fragment = document.createDocumentFragment();
elements.forEach(element => {
if (element.parentNode) {
fragment.appendChild(element);
}
});
// All elements are now removed from DOM
return fragment;
}
Complex Cloning Patterns
// Deep clone with event listener preservation
function cloneWithEvents(element) {
// Note: This is a simplified approach
const clone = element.cloneNode(true);
// Re-attach event listeners (requires tracking)
// In practice, you'd need to maintain a registry of event listeners
return clone;
}
// Clone and update IDs to avoid duplicates
function cloneWithUniqueIds(element, suffix) {
const clone = element.cloneNode(true);
// Update ID of clone
if (clone.id) {
clone.id = `${clone.id}-${suffix}`;
}
// Update IDs of all descendants
const elementsWithId = clone.querySelectorAll('[id]');
elementsWithId.forEach(el => {
el.id = `${el.id}-${suffix}`;
});
// Update references (for, href, etc.)
const labels = clone.querySelectorAll('label[for]');
labels.forEach(label => {
const forAttr = label.getAttribute('for');
label.setAttribute('for', `${forAttr}-${suffix}`);
});
return clone;
}
// Clone with data binding
function createBoundElement(template, data) {
const clone = template.content.cloneNode(true);
// Simple data binding
function bindData(element, data) {
const bindElements = element.querySelectorAll('[data-bind]');
bindElements.forEach(el => {
const bindPath = el.dataset.bind;
const value = bindPath.split('.').reduce((obj, key) => obj[key], data);
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
el.value = value;
} else {
el.textContent = value;
}
});
}
bindData(clone, data);
return clone;
}
Real-World Applications
Dynamic List Manager
class ListManager {
constructor(containerId, itemTemplate) {
this.container = document.getElementById(containerId);
this.template = document.getElementById(itemTemplate);
this.items = new Map();
}
addItem(data) {
const id = data.id || Date.now();
const item = this.createItemElement(data);
this.items.set(id, item);
this.container.appendChild(item);
return id;
}
createItemElement(data) {
const clone = this.template.content.cloneNode(true);
const item = clone.querySelector('.list-item');
// Set data attributes
item.dataset.id = data.id;
// Fill in content
Object.entries(data).forEach(([key, value]) => {
const element = item.querySelector(`[data-field="${key}"]`);
if (element) {
element.textContent = value;
}
});
// Add event listeners
const deleteBtn = item.querySelector('.delete-btn');
if (deleteBtn) {
deleteBtn.addEventListener('click', () => this.removeItem(data.id));
}
return item;
}
removeItem(id) {
const item = this.items.get(id);
if (item) {
item.classList.add('removing');
setTimeout(() => {
item.remove();
this.items.delete(id);
}, 300);
}
}
updateItem(id, newData) {
const item = this.items.get(id);
if (item) {
Object.entries(newData).forEach(([key, value]) => {
const element = item.querySelector(`[data-field="${key}"]`);
if (element) {
element.textContent = value;
}
});
}
}
clear() {
this.items.forEach(item => item.remove());
this.items.clear();
}
sortItems(compareFunction) {
const sortedItems = Array.from(this.items.values()).sort(compareFunction);
sortedItems.forEach(item => {
this.container.appendChild(item);
});
}
}
// Usage
const todoList = new ListManager('todo-list', 'todo-template');
todoList.addItem({
id: 1,
title: 'Complete project',
priority: 'high',
dueDate: '2024-01-20'
});
todoList.addItem({
id: 2,
title: 'Review code',
priority: 'medium',
dueDate: '2024-01-21'
});
Dynamic Form Builder
class DynamicForm {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.form = document.createElement('form');
this.fields = [];
this.container.appendChild(this.form);
}
addField(config) {
const field = this.createField(config);
this.fields.push(field);
this.form.appendChild(field.element);
return field;
}
createField(config) {
const wrapper = document.createElement('div');
wrapper.className = 'form-field';
// Create label
if (config.label) {
const label = document.createElement('label');
label.textContent = config.label;
label.htmlFor = config.id;
wrapper.appendChild(label);
}
// Create input element
let input;
switch (config.type) {
case 'select':
input = this.createSelect(config);
break;
case 'textarea':
input = document.createElement('textarea');
break;
case 'checkbox':
case 'radio':
input = this.createCheckableInput(config);
break;
default:
input = document.createElement('input');
input.type = config.type || 'text';
}
// Set common properties
if (config.id) input.id = config.id;
if (config.name) input.name = config.name;
if (config.placeholder) input.placeholder = config.placeholder;
if (config.required) input.required = true;
if (config.value) input.value = config.value;
wrapper.appendChild(input);
// Add validation message
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
wrapper.appendChild(errorDiv);
return {
element: wrapper,
input: input,
config: config,
validate: () => this.validateField(input, config)
};
}
createSelect(config) {
const select = document.createElement('select');
if (config.options) {
config.options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.label;
if (option.selected) optionElement.selected = true;
select.appendChild(optionElement);
});
}
return select;
}
createCheckableInput(config) {
const wrapper = document.createElement('div');
wrapper.className = `${config.type}-group`;
if (config.options) {
config.options.forEach((option, index) => {
const label = document.createElement('label');
const input = document.createElement('input');
input.type = config.type;
input.name = config.name;
input.value = option.value;
input.id = `${config.id}-${index}`;
if (option.checked) input.checked = true;
label.appendChild(input);
label.appendChild(document.createTextNode(option.label));
wrapper.appendChild(label);
});
}
return wrapper;
}
validateField(input, config) {
const errorDiv = input.parentElement.querySelector('.error-message');
let isValid = true;
let errorMessage = '';
if (config.required && !input.value) {
isValid = false;
errorMessage = 'This field is required';
} else if (config.pattern && !new RegExp(config.pattern).test(input.value)) {
isValid = false;
errorMessage = config.patternMessage || 'Invalid format';
} else if (config.minLength && input.value.length < config.minLength) {
isValid = false;
errorMessage = `Minimum length is ${config.minLength}`;
}
if (errorDiv) {
errorDiv.textContent = errorMessage;
input.classList.toggle('invalid', !isValid);
}
return isValid;
}
validate() {
let isValid = true;
this.fields.forEach(field => {
if (!field.validate()) {
isValid = false;
}
});
return isValid;
}
getData() {
const data = {};
this.fields.forEach(field => {
const input = field.input;
const name = field.config.name;
if (input.type === 'checkbox') {
if (!data[name]) data[name] = [];
if (input.checked) data[name].push(input.value);
} else if (input.type === 'radio') {
if (input.checked) data[name] = input.value;
} else {
data[name] = input.value;
}
});
return data;
}
submit(endpoint) {
if (this.validate()) {
const data = this.getData();
console.log('Submitting:', data);
// Actual submission logic here
}
}
}
// Usage
const form = new DynamicForm('form-container');
form.addField({
type: 'text',
id: 'username',
name: 'username',
label: 'Username',
required: true,
minLength: 3
});
form.addField({
type: 'email',
id: 'email',
name: 'email',
label: 'Email Address',
required: true
});
form.addField({
type: 'select',
id: 'country',
name: 'country',
label: 'Country',
options: [
{ value: '', label: 'Select Country' },
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'uk', label: 'United Kingdom' }
]
});
Notification System
class NotificationManager {
constructor(containerId = 'notifications') {
this.container = document.getElementById(containerId);
if (!this.container) {
this.container = document.createElement('div');
this.container.id = containerId;
this.container.className = 'notification-container';
document.body.appendChild(this.container);
}
this.notifications = new Map();
}
show(message, type = 'info', duration = 3000) {
const notification = this.createNotification(message, type);
const id = Date.now();
this.notifications.set(id, notification);
this.container.appendChild(notification);
// Auto-dismiss if duration is set
if (duration > 0) {
setTimeout(() => this.dismiss(id), duration);
}
return id;
}
createNotification(message, type) {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
// Create message content
const content = document.createElement('div');
content.className = 'notification-content';
content.textContent = message;
// Create close button
const closeBtn = document.createElement('button');
closeBtn.className = 'notification-close';
closeBtn.innerHTML = '×';
closeBtn.onclick = () => this.dismissByElement(notification);
notification.appendChild(content);
notification.appendChild(closeBtn);
// Add entrance animation
requestAnimationFrame(() => {
notification.classList.add('notification-show');
});
return notification;
}
dismiss(id) {
const notification = this.notifications.get(id);
if (notification) {
this.removeNotification(notification, id);
}
}
dismissByElement(element) {
for (const [id, notification] of this.notifications) {
if (notification === element) {
this.removeNotification(notification, id);
break;
}
}
}
removeNotification(notification, id) {
notification.classList.add('notification-hide');
notification.addEventListener('transitionend', () => {
notification.remove();
this.notifications.delete(id);
}, { once: true });
}
success(message, duration) {
return this.show(message, 'success', duration);
}
error(message, duration) {
return this.show(message, 'error', duration);
}
warning(message, duration) {
return this.show(message, 'warning', duration);
}
info(message, duration) {
return this.show(message, 'info', duration);
}
}
// Usage
const notifications = new NotificationManager();
notifications.success('Operation completed successfully!');
notifications.error('An error occurred. Please try again.');
notifications.warning('Your session will expire in 5 minutes.');
notifications.info('New updates are available.');
// With custom duration
notifications.show('This will stay for 10 seconds', 'info', 10000);
// Persistent notification
const persistentId = notifications.show('Click the X to dismiss', 'warning', 0);
// Programmatically dismiss
setTimeout(() => {
notifications.dismiss(persistentId);
}, 5000);
Performance Best Practices
- Use DocumentFragment: For batch insertions
- Clone templates: Instead of creating elements from scratch
- Minimize reflows: Batch DOM modifications
- Use event delegation: For dynamic elements
- Clean up: Remove event listeners before removing elements
// Bad: Multiple reflows
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
document.body.appendChild(div); // Causes reflow each time
}
// Good: Single reflow
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
document.body.appendChild(fragment); // Single reflow
// Bad: Creating elements from scratch
function createCard(data) {
const card = document.createElement('div');
const title = document.createElement('h3');
const content = document.createElement('p');
// ... more element creation
}
// Good: Clone from template
const template = document.getElementById('card-template');
function createCard(data) {
const clone = template.content.cloneNode(true);
// ... just fill in data
}
// Event delegation for dynamic elements
document.addEventListener('click', (e) => {
if (e.target.matches('.dynamic-button')) {
// Handle click for all dynamic buttons
}
});
Practice Exercises
- Create a dynamic shopping cart that adds/removes items
- Build a todo list with categories and filtering
- Implement a comment system with nested replies
- Create a drag-and-drop list reordering system
Exercise Solutions (Try First!)
Click to see solutions
// 1. Dynamic Shopping Cart
class ShoppingCart {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.items = new Map();
this.total = 0;
this.init();
}
init() {
this.cartElement = document.createElement('div');
this.cartElement.className = 'shopping-cart';
this.itemsContainer = document.createElement('div');
this.itemsContainer.className = 'cart-items';
this.totalElement = document.createElement('div');
this.totalElement.className = 'cart-total';
this.cartElement.appendChild(this.itemsContainer);
this.cartElement.appendChild(this.totalElement);
this.container.appendChild(this.cartElement);
this.updateTotal();
}
addItem(product) {
if (this.items.has(product.id)) {
const item = this.items.get(product.id);
item.quantity++;
this.updateItemElement(item);
} else {
const item = {
...product,
quantity: 1,
element: this.createItemElement(product)
};
this.items.set(product.id, item);
this.itemsContainer.appendChild(item.element);
}
this.updateTotal();
}
createItemElement(product) {
const element = document.createElement('div');
element.className = 'cart-item';
element.dataset.id = product.id;
element.innerHTML = `
${product.name}
${product.price.toFixed(2)}
1
`;
// Add event listeners
element.querySelector('.decrease').onclick = () => this.updateQuantity(product.id, -1);
element.querySelector('.increase').onclick = () => this.updateQuantity(product.id, 1);
element.querySelector('.remove').onclick = () => this.removeItem(product.id);
return element;
}
updateItemElement(item) {
const quantityElement = item.element.querySelector('.quantity');
quantityElement.textContent = item.quantity;
}
updateQuantity(id, change) {
const item = this.items.get(id);
if (item) {
item.quantity += change;
if (item.quantity <= 0) {
this.removeItem(id);
} else {
this.updateItemElement(item);
this.updateTotal();
}
}
}
removeItem(id) {
const item = this.items.get(id);
if (item) {
item.element.remove();
this.items.delete(id);
this.updateTotal();
}
}
updateTotal() {
this.total = Array.from(this.items.values()).reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
this.totalElement.innerHTML = `
Total: ${this.total.toFixed(2)}
`;
if (this.items.size === 0) {
this.itemsContainer.innerHTML = 'Your cart is empty';
}
}
}
// 2. Todo List with Categories
class TodoList {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.todos = new Map();
this.categories = new Set(['all']);
this.currentCategory = 'all';
this.init();
}
init() {
this.todoElement = document.createElement('div');
this.todoElement.className = 'todo-list';
// Create category filter
this.filterContainer = document.createElement('div');
this.filterContainer.className = 'todo-filters';
// Create todo input
this.inputContainer = document.createElement('div');
this.inputContainer.className = 'todo-input';
this.inputContainer.innerHTML = `
`;
// Create todos container
this.todosContainer = document.createElement('div');
this.todosContainer.className = 'todos';
this.todoElement.appendChild(this.filterContainer);
this.todoElement.appendChild(this.inputContainer);
this.todoElement.appendChild(this.todosContainer);
this.container.appendChild(this.todoElement);
// Add event listeners
this.setupEventListeners();
this.updateFilters();
}
setupEventListeners() {
const addButton = this.inputContainer.querySelector('.add-todo');
const input = this.inputContainer.querySelector('.todo-text');
const categorySelect = this.inputContainer.querySelector('.todo-category');
addButton.onclick = () => this.addTodo();
input.onkeypress = (e) => {
if (e.key === 'Enter') this.addTodo();
};
// Add new category option
const newCategoryOption = document.createElement('option');
newCategoryOption.value = 'new';
newCategoryOption.textContent = 'New Category...';
categorySelect.appendChild(newCategoryOption);
categorySelect.onchange = (e) => {
if (e.target.value === 'new') {
const newCategory = prompt('Enter new category name:');
if (newCategory) {
this.categories.add(newCategory);
this.updateFilters();
this.updateCategorySelect();
categorySelect.value = newCategory;
}
}
};
}
addTodo() {
const input = this.inputContainer.querySelector('.todo-text');
const categorySelect = this.inputContainer.querySelector('.todo-category');
const text = input.value.trim();
const category = categorySelect.value || 'general';
if (text) {
const todo = {
id: Date.now(),
text,
category,
completed: false,
createdAt: new Date()
};
this.todos.set(todo.id, todo);
this.renderTodo(todo);
input.value = '';
if (!this.categories.has(category)) {
this.categories.add(category);
this.updateFilters();
this.updateCategorySelect();
}
}
}
renderTodo(todo) {
const element = document.createElement('div');
element.className = `todo-item ${todo.completed ? 'completed' : ''}`;
element.dataset.id = todo.id;
element.dataset.category = todo.category;
element.innerHTML = `
${todo.text}
${todo.category}
`;
// Add event listeners
const checkbox = element.querySelector('input[type="checkbox"]');
checkbox.onchange = () => this.toggleTodo(todo.id);
const deleteButton = element.querySelector('.delete-todo');
deleteButton.onclick = () => this.deleteTodo(todo.id);
if (this.currentCategory === 'all' || todo.category === this.currentCategory) {
this.todosContainer.appendChild(element);
}
}
updateFilters() {
this.filterContainer.innerHTML = '';
this.categories.forEach(category => {
const button = document.createElement('button');
button.className = `filter-btn ${category === this.currentCategory ? 'active' : ''}`;
button.textContent = category;
button.onclick = () => this.filterByCategory(category);
this.filterContainer.appendChild(button);
});
}
updateCategorySelect() {
const select = this.inputContainer.querySelector('.todo-category');
select.innerHTML = '';
this.categories.forEach(category => {
if (category !== 'all') {
const option = document.createElement('option');
option.value = category;
option.textContent = category;
select.appendChild(option);
}
});
// Re-add new category option
const newOption = document.createElement('option');
newOption.value = 'new';
newOption.textContent = 'New Category...';
select.appendChild(newOption);
}
filterByCategory(category) {
this.currentCategory = category;
this.updateFilters();
// Re-render todos
this.todosContainer.innerHTML = '';
this.todos.forEach(todo => {
if (category === 'all' || todo.category === category) {
this.renderTodo(todo);
}
});
}
toggleTodo(id) {
const todo = this.todos.get(id);
if (todo) {
todo.completed = !todo.completed;
const element = this.todosContainer.querySelector(`[data-id="${id}"]`);
if (element) {
element.classList.toggle('completed', todo.completed);
element.querySelector('input[type="checkbox"]').checked = todo.completed;
}
}
}
deleteTodo(id) {
const todo = this.todos.get(id);
if (todo) {
this.todos.delete(id);
const element = this.todosContainer.querySelector(`[data-id="${id}"]`);
if (element) {
element.remove();
}
}
}
}
Summary
Creating and removing DOM elements dynamically gives you the power to:
- Build interactive user interfaces that respond to data
- Create reusable components and templates
- Efficiently manage large sets of DOM elements
- Implement complex UI patterns like lists, forms, and notifications
- Optimize performance with fragments and cloning
Key takeaways:
- Use createElement and appendChild for basic element creation
- Leverage template elements and cloneNode for efficiency
- Use DocumentFragment for batch insertions
- Always clean up event listeners before removing elements
- Consider performance implications of DOM manipulation
Master these techniques to create dynamic, responsive web applications!