javascript-today

Advanced DOM Manipulation

Creating and modifying DOM elements dynamically is essential for building interactive web applications. Let’s explore the powerful techniques for manipulating the Document Object Model.

Creating Elements

createElement()

Create new HTML elements:

// Create a new div element
const div = document.createElement('div');

// Create other elements
const paragraph = document.createElement('p');
const button = document.createElement('button');
const link = document.createElement('a');
const image = document.createElement('img');

Adding Content

const heading = document.createElement('h2');

// Set text content (safe - escapes HTML)
heading.textContent = 'Hello World';

// Or set inner text
heading.innerText = 'Hello World';

Adding to the Page

const div = document.createElement('div');
div.textContent = 'New content';

// Add to end of body
document.body.appendChild(div);

// Add to specific element
const container = document.querySelector('#container');
container.appendChild(div);

appendChild vs append

Modern append() is more flexible:

const container = document.querySelector('#container');

// Old way: appendChild (only 1 element, returns the node)
const div1 = document.createElement('div');
container.appendChild(div1);

// New way: append (multiple items, no return value)
const div2 = document.createElement('div');
container.append(div2, 'Some text', div3);

// append() accepts strings and multiple elements
container.append('Text before', div2, ' text after');

Key differences:

  • appendChild(): Only one Node, returns the node
  • append(): Multiple items (Nodes or strings), no return value

Inserting at Specific Positions

insertBefore()

const list = document.querySelector('ul');
const newItem = document.createElement('li');
newItem.textContent = 'New Item';

// Insert before first child
const firstChild = list.firstChild;
list.insertBefore(newItem, firstChild);

// Insert before specific element
const thirdItem = list.children[2];
list.insertBefore(newItem, thirdItem);

Modern Insertion Methods

const reference = document.querySelector('#reference');
const newElement = document.createElement('div');

// Insert before reference
reference.before(newElement);

// Insert after reference
reference.after(newElement);

// Insert as first child
reference.prepend(newElement);

// Insert as last child (same as appendChild)
reference.append(newElement);

Visual example:

const container = document.querySelector('#container');
const newDiv = document.createElement('div');

container.prepend(newDiv);  // Adds at start
container.append(newDiv);   // Adds at end
container.before(newDiv);   // Adds before container
container.after(newDiv);    // Adds after container

Removing Elements

remove()

Simplest way to remove an element:

const element = document.querySelector('#old-element');
element.remove();

removeChild()

Remove child from parent:

const parent = document.querySelector('#parent');
const child = document.querySelector('#child');

parent.removeChild(child);

Removing All Children

const container = document.querySelector('#container');

// Method 1: innerHTML
container.innerHTML = '';

// Method 2: Loop removal
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

// Method 3: replaceChildren (modern)
container.replaceChildren();

innerHTML vs createElement

Using innerHTML

Fast for simple cases:

const container = document.querySelector('#container');

// Set HTML content
container.innerHTML = `
  <h2>Title</h2>
  <p>Some text</p>
  <button>Click me</button>
`;

// Add to existing content
container.innerHTML += '<p>Another paragraph</p>';

Pros:

  • Fast to write
  • Good for static content

Cons:

  • Destroys existing event listeners
  • Security risk (XSS attacks if using user input)
  • Re-renders all content

Using createElement

Safer and more flexible:

const container = document.querySelector('#container');

// Create elements
const heading = document.createElement('h2');
heading.textContent = 'Title';

const paragraph = document.createElement('p');
paragraph.textContent = 'Some text';

const button = document.createElement('button');
button.textContent = 'Click me';
button.addEventListener('click', () => alert('Clicked!'));

// Add to page
container.append(heading, paragraph, button);

Pros:

  • Preserves event listeners
  • Better performance for small changes
  • Safer (no HTML injection)
  • More control

Cons:

  • More verbose
  • Slower for large amounts of content

When to Use Each

// ✅ innerHTML: Static content, no events
sidebar.innerHTML = '<h3>Categories</h3><ul>...</ul>';

// ✅ createElement: Dynamic content, event listeners
const button = document.createElement('button');
button.textContent = 'Delete';
button.addEventListener('click', handleDelete);
container.appendChild(button);

// ❌ DANGEROUS: Never use innerHTML with user input
const userInput = prompt('Enter name');
div.innerHTML = `<p>Hello ${userInput}</p>`; // XSS vulnerability!

// ✅ SAFE: Use textContent with user input
const userInput = prompt('Enter name');
const p = document.createElement('p');
p.textContent = `Hello ${userInput}`;
div.appendChild(p);

Cloning Elements

cloneNode()

Duplicate existing elements:

const original = document.querySelector('#template');

// Shallow clone (element only, no children)
const shallowClone = original.cloneNode(false);

// Deep clone (element + all children)
const deepClone = original.cloneNode(true);

// Add clone to page
document.body.appendChild(deepClone);

Practical Example: Cloning Templates

<template id="card-template">
  <div class="card">
    <h3 class="card-title"></h3>
    <p class="card-description"></p>
    <button class="card-button">Learn More</button>
  </div>
</template>

<div id="card-container"></div>
const template = document.querySelector('#card-template');
const container = document.querySelector('#card-container');

const users = [
  { name: 'Alice', bio: 'Developer' },
  { name: 'Bob', bio: 'Designer' }
];

users.forEach(user => {
  // Clone template content
  const card = template.content.cloneNode(true);
  
  // Customize clone
  card.querySelector('.card-title').textContent = user.name;
  card.querySelector('.card-description').textContent = user.bio;
  
  // Add to page
  container.appendChild(card);
});

Replacing Elements

replaceWith()

Replace element with new content:

const oldElement = document.querySelector('#old');
const newElement = document.createElement('div');
newElement.textContent = 'New content';

oldElement.replaceWith(newElement);

// Can replace with multiple items
oldElement.replaceWith('Text', newElement, 'More text');

replaceChild()

Replace child element:

const parent = document.querySelector('#parent');
const oldChild = document.querySelector('#old-child');
const newChild = document.createElement('div');

parent.replaceChild(newChild, oldChild);

Practical Examples

Building a Todo List

const todoList = document.querySelector('#todo-list');
const input = document.querySelector('#todo-input');
const addButton = document.querySelector('#add-button');

addButton.addEventListener('click', () => {
  const text = input.value.trim();
  
  if (!text) return;
  
  // Create list item
  const li = document.createElement('li');
  
  // Create text span
  const span = document.createElement('span');
  span.textContent = text;
  
  // Create delete button
  const deleteBtn = document.createElement('button');
  deleteBtn.textContent = 'Delete';
  deleteBtn.addEventListener('click', () => {
    li.remove();
  });
  
  // Assemble and add to list
  li.append(span, deleteBtn);
  todoList.appendChild(li);
  
  // Clear input
  input.value = '';
});

Creating a Card Grid

const products = [
  { name: 'Laptop', price: 999, image: 'laptop.jpg' },
  { name: 'Phone', price: 599, image: 'phone.jpg' },
  { name: 'Tablet', price: 399, image: 'tablet.jpg' }
];

const grid = document.querySelector('#product-grid');

products.forEach(product => {
  // Create card
  const card = document.createElement('div');
  card.className = 'product-card';
  
  // Create image
  const img = document.createElement('img');
  img.src = product.image;
  img.alt = product.name;
  
  // Create title
  const title = document.createElement('h3');
  title.textContent = product.name;
  
  // Create price
  const price = document.createElement('p');
  price.textContent = `$${product.price}`;
  price.className = 'price';
  
  // Create button
  const button = document.createElement('button');
  button.textContent = 'Add to Cart';
  button.addEventListener('click', () => {
    console.log(`Added ${product.name} to cart`);
  });
  
  // Assemble card
  card.append(img, title, price, button);
  
  // Add to grid
  grid.appendChild(card);
});

Dynamic Form Builder

function createFormField(label, type, name) {
  const wrapper = document.createElement('div');
  wrapper.className = 'form-field';
  
  const labelElement = document.createElement('label');
  labelElement.textContent = label;
  
  const input = document.createElement('input');
  input.type = type;
  input.name = name;
  input.required = true;
  
  wrapper.append(labelElement, input);
  return wrapper;
}

const form = document.querySelector('#dynamic-form');

// Add fields
form.appendChild(createFormField('Name', 'text', 'name'));
form.appendChild(createFormField('Email', 'email', 'email'));
form.appendChild(createFormField('Password', 'password', 'password'));

// Add submit button
const submitBtn = document.createElement('button');
submitBtn.type = 'submit';
submitBtn.textContent = 'Submit';
form.appendChild(submitBtn);

DocumentFragment for Performance

Use DocumentFragment to batch DOM updates:

// Without fragment: Multiple reflows
const list = document.querySelector('#list');
for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  list.appendChild(li); // Triggers reflow each time
}

// With fragment: Single reflow
const list = document.querySelector('#list');
const fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li); // No reflow
}

list.appendChild(fragment); // Single reflow

Best Practices

DO:

  • Use textContent for user input (prevents XSS)
  • Use createElement for dynamic content with events
  • Use DocumentFragment for multiple insertions
  • Clone templates for repeated structures
  • Remove event listeners before removing elements

DON’T:

  • Use innerHTML with unsanitized user input
  • Create elements inside loops without fragments
  • Forget to clear references to removed elements
  • Modify DOM in rapid succession (batch updates)

Summary

Task Method Example
Create element createElement() document.createElement('div')
Add to page append() container.append(element)
Remove element remove() element.remove()
Clone element cloneNode(true) element.cloneNode(true)
Replace element replaceWith() old.replaceWith(newEl)
Insert before before() element.before(newEl)
Insert after after() element.after(newEl)
Set text safely textContent el.textContent = 'text'
Batch updates DocumentFragment fragment.appendChild(el)