Creating and Removing Elements

Dynamic DOM Manipulation: Building and Destroying Elements

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.

graph TB A[Document] --> B[createElement] B --> C[Configure Element] C --> D[appendChild/insertBefore] A --> E[createTextNode] E --> D A --> F[createDocumentFragment] F --> G[Add Multiple Elements] G --> D D --> H[Live DOM] H --> I[removeChild/remove] style A fill:#f9f,stroke:#333,stroke-width:4px style H fill:#bfb,stroke:#333,stroke-width:2px

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

// 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

  1. Create a dynamic shopping cart that adds/removes items
  2. Build a todo list with categories and filtering
  3. Implement a comment system with nested replies
  4. 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.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:

Key takeaways:

Master these techniques to create dynamic, responsive web applications!