Skip to main content

Course Progress

Loading...

Selecting and Manipulating DOM Elements

Duration: 45 minutes
Module 1: DOM Manipulation

Learning Objectives

  • Understand JavaScript fundamentals
  • Add interactivity to web pages
  • Manipulate page elements dynamically
  • Handle user interactions

The Power of DOM Selection and Manipulation

The ability to select and manipulate DOM elements is what makes JavaScript the powerhouse of web interactivity. This skill allows you to create dynamic, responsive web applications that react to user input and update in real-time without requiring page refreshes.

DOM Manipulation as Puppetry

Think of DOM manipulation like puppetry. Your HTML creates the puppets (elements), CSS designs their appearance, and JavaScript is the puppeteer that brings them to life by moving strings (selecting elements) and making them dance (manipulating their properties and content).

                    graph LR
                    A[HTML] -->|Creates Structure| D[DOM Tree]
                    B[CSS] -->|Styles| D
                    C[JavaScript] -->|"Selects & Manipulates"| D
                    D -->|Renders| E[Web Page]
                    style A fill:#f8d7da,stroke:#333,stroke-width:1px
                    style B fill:#d1ecf1,stroke:#333,stroke-width:1px
                    style C fill:#fff3cd,stroke:#333,stroke-width:1px
                    style D fill:#d4edda,stroke:#333,stroke-width:1px
                    style E fill:#e2e3e5,stroke:#333,stroke-width:1px
                

Selecting DOM Elements

Before you can manipulate elements, you need to select them. JavaScript provides multiple ways to find and select elements in the DOM. Let's explore them from most specific to most general.

document.getElementById('unique-id') document.getElementsByClassName('some-class') document.getElementsByTagName('div') document.querySelector('.class') / document.querySelectorAll('div > p') Returns: Single Element Returns: HTMLCollection (live) Returns: HTMLCollection (live) Returns: Element / NodeList (static) Most Specific Most Flexible

Selection Methods in Detail

Selection by ID

The most direct way to select a single element with a unique identifier.

// HTML
<div id="main-content">This is the main content area</div>

// JavaScript
const mainContent = document.getElementById('main-content');
console.log(mainContent); // Returns the div element

Real-world use: Use this when you need to select a specific, unique element - like a form, a main container, or a special button.

Performance: This is the fastest selection method since IDs must be unique in a document.

Selection by Class Name

Used to select multiple elements that share the same class.

// HTML
<div class="product">Product 1</div>
<div class="product">Product 2</div>
<div class="product featured">Product 3</div>

// JavaScript
const products = document.getElementsByClassName('product');
console.log(products); // HTMLCollection of all divs with class "product"
console.log(products.length); // 3
console.log(products[0]); // First product div

Real-world use: Ideal for working with groups of related elements such as navigation items, product cards, or form fields.

Note: Returns a live HTMLCollection which updates automatically if elements are added or removed.

Selection by Tag Name

Selects all elements of a specific HTML tag type.

// JavaScript
const paragraphs = document.getElementsByTagName('p');
console.log(paragraphs); // HTMLCollection of all paragraph elements
console.log(paragraphs.length); // Number of paragraphs in the document

// You can also search within a specific context
const container = document.getElementById('container');
const spanInsideContainer = container.getElementsByTagName('span');

Real-world use: When you need to work with all elements of a specific type, such as modifying all images, links, or list items.

Practical example: Adding target="_blank" to all external links.

Selection with CSS Selectors

The most flexible and powerful selection methods, using the same syntax as CSS selectors.

// querySelector - returns first matching element
const firstButton = document.querySelector('button');
const submitButton = document.querySelector('.submit-btn');
const navigationLink = document.querySelector('nav > ul > li:first-child > a');

// querySelectorAll - returns all matching elements as a NodeList
const allButtons = document.querySelectorAll('button');
const productItems = document.querySelectorAll('.product-item');
const oddListItems = document.querySelectorAll('li:nth-child(odd)');

Real-world use: Ideal for complex selections using CSS selector combinations. Particularly useful for modern web applications with complex DOM structures.

Note: querySelector is slower than getElementById but offers much more flexibility.

Working with Collections

Many selection methods return collections of elements rather than a single element:

Collection Type Returned By Characteristics
HTMLCollection getElementsByClassName
getElementsByTagName
  • Live collection (auto-updates)
  • Array-like but not an array
  • Access items with bracket notation: collection[0]
  • No forEach, map, filter, etc. methods
NodeList querySelectorAll
  • Static collection (does not auto-update)
  • Array-like but not an array
  • Has forEach method
  • No map, filter, etc. methods

Converting Collections to Arrays

To use array methods on these collections, convert them to arrays:

// Using Array.from()
const buttons = document.querySelectorAll('button');
const buttonArray = Array.from(buttons);
buttonArray.forEach(button => {
  console.log(button.textContent);
});

// Using spread operator (...)
const paragraphs = document.getElementsByTagName('p');
const paragraphArray = [...paragraphs];
paragraphArray.map(p => p.textContent);

DOM Selection as Fishing

Different selection methods are like different fishing techniques:

  • getElementById is like spearfishing - targeting one specific fish (element) you can see clearly.
  • getElementsByClassName is like net fishing for a particular species - you'll catch all fish (elements) of that type.
  • getElementsByTagName is like casting a wide net for a general category of fish.
  • querySelector/querySelectorAll is like sport fishing with customizable equipment - you can create complex rules to catch exactly the fish you want, but it takes more effort to set up.

Manipulating DOM Elements

Once you've selected elements, the real power of JavaScript becomes apparent - you can modify content, attributes, styles, and more to create dynamic interactions.

Changing Text and HTML Content

const heading = document.querySelector('h1');

// Changing text content (safer when dealing with user input)
heading.textContent = 'Updated Heading';

// Changing HTML content (can insert HTML elements)
heading.innerHTML = 'Updated <span>Heading</span> with Emphasis';

// innerText vs textContent
// textContent gets all content including text in <script> and <style> elements
// innerText is aware of styling and won't return hidden text
console.log(element.textContent); // Gets all text content
console.log(element.innerText); // Gets visible text content

Real-World Example: Dynamic Content Updates

Consider a word counter for a text area:

// HTML
<textarea id="message" placeholder="Type your message here..."></textarea>
<div id="counter">0 characters</div>

// JavaScript
const textarea = document.getElementById('message');
const counter = document.getElementById('counter');

textarea.addEventListener('input', function() {
  const count = textarea.value.length;
  counter.textContent = `${count} character${count !== 1 ? 's' : ''}`;
  
  // Add visual feedback
  if (count > 200) {
    counter.style.color = 'red';
  } else {
    counter.style.color = 'black';
  }
});

Working with Element Attributes

const link = document.querySelector('a');

// Getting attribute values
const href = link.getAttribute('href');
console.log(href); // e.g., "https://example.com"

// Setting attribute values
link.setAttribute('href', 'https://newdomain.com');
link.setAttribute('target', '_blank');
link.setAttribute('data-category', 'external');

// Checking if an attribute exists
if (link.hasAttribute('target')) {
  console.log('This link opens in a new tab');
}

// Removing attributes
link.removeAttribute('target');

// Direct property access for common attributes
link.href = 'https://anotherdomain.com'; // Same as setAttribute for common properties
link.id = 'main-link';
console.log(link.href); // Same as getAttribute for common properties

HTML5 Custom Data Attributes

HTML5 introduced custom data attributes that let you store extra information on elements:

// HTML
<div id="product" data-id="1234" data-category="electronics" data-price="499.99">
  Smartphone X
</div>

// JavaScript
const product = document.getElementById('product');

// Access using getAttribute
const productId = product.getAttribute('data-id');

// Access using dataset property (modern approach)
console.log(product.dataset.id); // "1234"
console.log(product.dataset.category); // "electronics"

// Modify using dataset
product.dataset.price = "449.99";
product.dataset.inStock = "true"; // Adds a new data-in-stock attribute

Managing CSS Classes

One of the most common DOM manipulations is changing CSS classes to alter appearance and behavior:

const card = document.querySelector('.card');

// Old approach (still works)
// Reading the className property returns a space-separated string
console.log(card.className); // e.g., "card product featured"

// Setting completely replaces all classes
card.className = 'card product';

// Modern approach: classList API
// Check if an element has a class
if (card.classList.contains('featured')) {
  console.log('This is a featured card');
}

// Add classes (can add multiple)
card.classList.add('active');
card.classList.add('highlighted', 'selected');

// Remove classes
card.classList.remove('featured');
card.classList.remove('highlighted', 'selected');

// Toggle a class (add if missing, remove if present)
card.classList.toggle('expanded'); // Returns true if added, false if removed

// Replace one class with another
card.classList.replace('inactive', 'active');

Real-World Example: Interactive Accordion

Using classList to create an interactive accordion component:

// HTML
<div class="accordion">
  <div class="accordion-item">
    <div class="accordion-header">Section 1</div>
    <div class="accordion-content">Content for section 1...</div>
  </div>
  <div class="accordion-item">
    <div class="accordion-header">Section 2</div>
    <div class="accordion-content">Content for section 2...</div>
  </div>
</div>

// CSS (simplified)
.accordion-content {
  display: none;
  padding: 10px;
}
.accordion-item.active .accordion-content {
  display: block;
}
.accordion-header {
  cursor: pointer;
  padding: 10px;
  background-color: #f0f0f0;
}
.accordion-item.active .accordion-header {
  background-color: #e0e0e0;
}

// JavaScript
const accordionHeaders = document.querySelectorAll('.accordion-header');

accordionHeaders.forEach(header => {
  header.addEventListener('click', function() {
    // Toggle the active class on the parent item
    const item = this.parentElement;
    item.classList.toggle('active');
    
    // Optional: Close other items when opening one
    const allItems = document.querySelectorAll('.accordion-item');
    allItems.forEach(otherItem => {
      if (otherItem !== item) {
        otherItem.classList.remove('active');
      }
    });
  });
});

Manipulating Inline Styles

You can directly modify the inline styles of elements using the style property:

const box = document.querySelector('.box');

// Setting individual style properties
box.style.backgroundColor = 'skyblue';
box.style.width = '200px';
box.style.height = '100px';
box.style.border = '2px solid navy';
box.style.borderRadius = '10px';
box.style.marginTop = '20px';

// CSS properties with hyphens must be camelCased in JavaScript
// CSS: background-color → JS: backgroundColor
// CSS: border-radius → JS: borderRadius

// Reading computed styles (what's actually applied after all CSS)
const computedStyle = window.getComputedStyle(box);
console.log(computedStyle.backgroundColor);
console.log(computedStyle.width);

// Setting multiple styles at once
Object.assign(box.style, {
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  color: 'white',
  fontWeight: 'bold'
});

Best Practice: Prefer Classes Over Inline Styles

While direct style manipulation is convenient, using CSS classes is generally better practice:

  • Better separation of concerns (HTML/JS for structure/behavior, CSS for appearance)
  • More maintainable and reusable styling
  • Better performance for multiple style changes
  • Easier to adapt for responsive design

Use direct style manipulation for:

  • Dynamic values (e.g., positions based on calculations)
  • Temporary state changes (e.g., animations)
  • When adding a class would be overkill

Manipulating Element Properties

DOM elements have many properties beyond attributes and styles:

// Form element properties
const nameInput = document.getElementById('name');
nameInput.value = 'John Doe'; // Set input value
nameInput.disabled = true; // Disable input
nameInput.readOnly = true; // Make input read-only

// Checkbox/radio properties
const checkbox = document.getElementById('terms');
checkbox.checked = true; // Check the box
console.log(checkbox.checked); // true

// Image properties
const logo = document.getElementById('logo');
logo.src = '/images/new-logo.png';
logo.alt = 'Company Logo';

// Accessibility properties
const menuButton = document.getElementById('menu-button');
menuButton.ariaExpanded = 'false';
menuButton.ariaLabel = 'Open navigation menu';

Creating and Managing DOM Elements

Beyond manipulating existing elements, JavaScript allows you to create, insert, and remove elements entirely.

Creating New Elements

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

// Add content and attributes to it
newParagraph.textContent = 'This is a dynamically created paragraph.';
newParagraph.className = 'dynamic-content';
newParagraph.id = 'intro-text';

// Create more complex elements
const newLink = document.createElement('a');
newLink.href = 'https://example.com';
newLink.textContent = 'Visit Example';
newLink.target = '_blank';

// Append the link to the paragraph
newParagraph.appendChild(newLink);

Inserting Elements into the DOM

                graph TD
                parent[Parent Element]
                firstChild[First Child]
                middleChild[Middle Child]
                lastChild[Last Child]
                newElement[New Element]
                
                parent --> firstChild
                parent --> middleChild
                parent --> lastChild
                
                parent -.appendChild.-> newElement
                parent -.insertBefore.-> newElement
                firstChild -.before.-> newElement
                middleChild -.after.-> newElement
                parent -.prepend.-> newElement
                
                style newElement fill:#ffcccc,stroke:#ff0000,stroke-width:2px
            
const container = document.querySelector('.container');
const referenceElement = document.querySelector('.existing-element');

// Old methods
// Append as the last child
container.appendChild(newParagraph);

// Insert before a specific element
container.insertBefore(newParagraph, referenceElement);

// Modern methods
// Append one or more elements as the last children
container.append(newParagraph, newLink); // Can append multiple elements

// Prepend as the first child(ren)
container.prepend(newParagraph);

// Insert before a specific element
referenceElement.before(newParagraph);

// Insert after a specific element
referenceElement.after(newParagraph);

Removing Elements

// Modern method
element.remove();

// Old method (still works everywhere)
element.parentNode.removeChild(element);

// Removing all children from an element
// Method 1: Clear innerHTML
container.innerHTML = '';

// Method 2: Remove children in a loop
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

// Method 3: Modern approach
container.replaceChildren(); // Removes all children

Cloning Elements

// Clone an element (false means don't clone children)
const shallowCopy = element.cloneNode(false);

// Clone an element and all its descendants (deep copy)
const deepCopy = element.cloneNode(true);

// Clone, modify, and insert
const template = document.querySelector('.card-template');
const newCard = template.cloneNode(true);
newCard.querySelector('.card-title').textContent = 'New Product';
newCard.querySelector('.card-price').textContent = '$49.99';
container.appendChild(newCard);

Real-World Example: Dynamic List Generation

Creating a list of items from data:

// Data from an API or other source
const products = [
  { id: 1, name: 'Smartphone', price: 699, inStock: true },
  { id: 2, name: 'Laptop', price: 1299, inStock: false },
  { id: 3, name: 'Headphones', price: 199, inStock: true },
  { id: 4, name: 'Tablet', price: 499, inStock: true }
];

// Get container element
const productList = document.getElementById('product-list');

// Clear existing content
productList.innerHTML = '';

// Create and append product elements
products.forEach(product => {
  // Create elements
  const productCard = document.createElement('div');
  const productName = document.createElement('h3');
  const productPrice = document.createElement('p');
  const productStatus = document.createElement('span');
  const addButton = document.createElement('button');
  
  // Add classes and content
  productCard.className = 'product-card';
  productCard.dataset.productId = product.id;
  
  productName.textContent = product.name;
  productName.className = 'product-name';
  
  productPrice.textContent = `$${product.price}`;
  productPrice.className = 'product-price';
  
  productStatus.textContent = product.inStock ? 'In Stock' : 'Out of Stock';
  productStatus.className = product.inStock ? 'in-stock' : 'out-of-stock';
  
  addButton.textContent = 'Add to Cart';
  addButton.className = 'add-to-cart';
  addButton.disabled = !product.inStock;
  
  // Add event listener to button
  addButton.addEventListener('click', function() {
    console.log(`Added ${product.name} to cart`);
    // Add to cart functionality would go here
  });
  
  // Assemble the card
  productCard.appendChild(productName);
  productCard.appendChild(productPrice);
  productCard.appendChild(productStatus);
  productCard.appendChild(addButton);
  
  // Add to the list
  productList.appendChild(productCard);
});

Efficiency and Performance Considerations

DOM manipulation can be expensive in terms of performance. Here are best practices to keep your web applications fast and responsive:

Document Fragments

When adding multiple elements, use a DocumentFragment to batch changes and reduce reflows:

// Inefficient way (causes multiple reflows)
const list = document.getElementById('large-list');
for (let i = 0; i < 100; i++) {
  const item = document.createElement('li');
  item.textContent = `Item ${i}`;
  list.appendChild(item); // Browser reflows each time!
}

// Efficient way using DocumentFragment
const list = document.getElementById('large-list');
const fragment = document.createDocumentFragment(); // Create a fragment

for (let i = 0; i < 100; i++) {
  const item = document.createElement('li');
  item.textContent = `Item ${i}`;
  fragment.appendChild(item); // Append to fragment (no reflow)
}

list.appendChild(fragment); // Single DOM update and reflow

Document Fragment as a Shopping List

Think of DOM manipulation without fragments like making many trips to the grocery store, buying one item at a time. Using DocumentFragment is like making a complete shopping list first, then making a single trip to buy everything at once.

Batch DOM Operations

Group your DOM operations to minimize style recalculations and layout reflows:

// Inefficient pattern
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = 'red';
element.style.fontSize = '14px';
element.style.border = '1px solid black';

// More efficient pattern
// Option 1: Use cssText
element.style.cssText = 'width: 100px; height: 100px; background-color: red; font-size: 14px; border: 1px solid black;';

// Option 2: Use a class instead of inline styles
element.className = 'styled-box';

// Option 3: Batch operations with Object.assign
Object.assign(element.style, {
  width: '100px',
  height: '100px',
  backgroundColor: 'red',
  fontSize: '14px',
  border: '1px solid black'
});

Read Properties First, Then Write

Grouping read operations and write operations can prevent forced synchronous layouts:

// Inefficient pattern (forces layout recalculation)
for (let i = 0; i < elements.length; i++) {
  const height = elements[i].offsetHeight; // Read
  elements[i].style.height = (height * 2) + 'px'; // Write
  // Browser recalculates layout before reading next height
}

// More efficient pattern
const heights = [];
// Read phase
for (let i = 0; i < elements.length; i++) {
  heights.push(elements[i].offsetHeight);
}
// Write phase
for (let i = 0; i < elements.length; i++) {
  elements[i].style.height = (heights[i] * 2) + 'px';
}

Avoid Frequent DOM Updates

Minimize updates, especially in loops or frequently firing events like scroll:

// Poor performance with many updates
window.addEventListener('scroll', function() {
  element.style.opacity = getOpacityValue();
  element.style.transform = getTransformValue();
});

// Better performance with throttling
let ticking = false;

window.addEventListener('scroll', function() {
  if (!ticking) {
    window.requestAnimationFrame(function() {
      element.style.opacity = getOpacityValue();
      element.style.transform = getTransformValue();
      ticking = false;
    });
    ticking = true;
  }
});

Practical Examples

Image Gallery with Filtering

Create an interactive image gallery with category filtering:

// HTML structure
<div class="gallery-filters">
  <button class="filter-btn active" data-filter="all">All</button>
  <button class="filter-btn" data-filter="nature">Nature</button>
  <button class="filter-btn" data-filter="architecture">Architecture</button>
  <button class="filter-btn" data-filter="people">People</button>
</div>

<div class="gallery">
  <div class="gallery-item" data-category="nature">
    <img src="nature1.jpg" alt="Forest">
    <h3>Forest</h3>
  </div>
  <div class="gallery-item" data-category="architecture">
    <img src="building1.jpg" alt="Modern Building">
    <h3>Modern Building</h3>
  </div>
  <!-- More gallery items... -->
</div>

// JavaScript
document.addEventListener('DOMContentLoaded', function() {
  const filterButtons = document.querySelectorAll('.filter-btn');
  const galleryItems = document.querySelectorAll('.gallery-item');
  
  // Add click event to filter buttons
  filterButtons.forEach(button => {
    button.addEventListener('click', function() {
      // Remove active class from all buttons
      filterButtons.forEach(btn => btn.classList.remove('active'));
      
      // Add active class to clicked button
      this.classList.add('active');
      
      // Get filter value
      const filterValue = this.getAttribute('data-filter');
      
      // Filter gallery items
      galleryItems.forEach(item => {
        if (filterValue === 'all' || item.getAttribute('data-category') === filterValue) {
          item.style.display = 'block';
        } else {
          item.style.display = 'none';
        }
      });
    });
  });
});

Dynamic Form Fields

Adding and removing form fields dynamically:

// HTML
<form id="dynamic-form">
  <div id="fields-container">
    <div class="form-field">
      <input type="text" name="item[]" placeholder="Enter item">
      <button type="button" class="remove-field" disabled>Remove</button>
    </div>
  </div>
  <button type="button" id="add-field-btn">Add Another Item</button>
  <button type="submit">Submit</button>
</form>

// JavaScript
document.addEventListener('DOMContentLoaded', function() {
  const form = document.getElementById('dynamic-form');
  const fieldsContainer = document.getElementById('fields-container');
  const addButton = document.getElementById('add-field-btn');
  
  // Add new field when button is clicked
  addButton.addEventListener('click', function() {
    // Create new field
    const newField = document.createElement('div');
    newField.className = 'form-field';
    
    const newInput = document.createElement('input');
    newInput.type = 'text';
    newInput.name = 'item[]';
    newInput.placeholder = 'Enter item';
    
    const removeButton = document.createElement('button');
    removeButton.type = 'button';
    removeButton.className = 'remove-field';
    removeButton.textContent = 'Remove';
    
    // Add event listener to remove button
    removeButton.addEventListener('click', function() {
      fieldsContainer.removeChild(newField);
      updateRemoveButtons();
    });
    
    // Assemble and append the new field
    newField.appendChild(newInput);
    newField.appendChild(removeButton);
    fieldsContainer.appendChild(newField);
    
    // Update remove buttons
    updateRemoveButtons();
  });
  
  // Function to update remove buttons state
  function updateRemoveButtons() {
    const fields = fieldsContainer.querySelectorAll('.form-field');
    const removeButtons = fieldsContainer.querySelectorAll('.remove-field');
    
    // Disable remove buttons if only one field
    if (fields.length === 1) {
      removeButtons[0].disabled = true;
    } else {
      // Enable all remove buttons
      removeButtons.forEach(button => {
        button.disabled = false;
      });
    }
  }
  
  // Form submission
  form.addEventListener('submit', function(event) {
    event.preventDefault();
    
    // Get all input values
    const inputs = form.querySelectorAll('input[name="item[]"]');
    const values = Array.from(inputs).map(input => input.value);
    
    console.log('Submitted values:', values);
    // Submit to server would go here
  });
});

Interactive Data Table

Creating a sortable, filterable data table:

// HTML
<div class="table-controls">
  <input type="text" id="table-search" placeholder="Search...">
</div>

<table id="data-table">
  <thead>
    <tr>
      <th data-sort="name">Name</th>
      <th data-sort="age">Age</th>
      <th data-sort="city">City</th>
    </tr>
  </thead>
  <tbody>
    <!-- Data rows will be inserted here -->
  </tbody>
</table>

// JavaScript
document.addEventListener('DOMContentLoaded', function() {
  // Sample data
  const people = [
    { id: 1, name: 'John Smith', age: 32, city: 'New York' },
    { id: 2, name: 'Jane Doe', age: 28, city: 'Los Angeles' },
    { id: 3, name: 'Bob Johnson', age: 45, city: 'Chicago' },
    { id: 4, name: 'Alice Williams', age: 24, city: 'Miami' }
    // More data...
  ];
  
  const table = document.getElementById('data-table');
  const tableBody = table.querySelector('tbody');
  const searchInput = document.getElementById('table-search');
  const sortHeaders = table.querySelectorAll('th[data-sort]');
  
  // Variables to track sort state
  let sortColumn = 'name';
  let sortDirection = 'asc';
  
  // Initial render
  renderTable(people);
  
  // Add search functionality
  searchInput.addEventListener('input', function() {
    const searchTerm = this.value.toLowerCase();
    const filteredData = people.filter(person => {
      return (
        person.name.toLowerCase().includes(searchTerm) ||
        person.city.toLowerCase().includes(searchTerm) ||
        person.age.toString().includes(searchTerm)
      );
    });
    renderTable(filteredData);
  });
  
  // Add sorting functionality
  sortHeaders.forEach(header => {
    header.addEventListener('click', function() {
      const column = this.getAttribute('data-sort');
      
      // Update sort direction
      if (sortColumn === column) {
        sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
      } else {
        sortColumn = column;
        sortDirection = 'asc';
      }
      
      // Update UI to show sort direction
      sortHeaders.forEach(h => h.classList.remove('sort-asc', 'sort-desc'));
      this.classList.add(`sort-${sortDirection}`);
      
      // Sort and render data
      const sortedData = [...people].sort((a, b) => {
        if (sortDirection === 'asc') {
          return a[column] > b[column] ? 1 : -1;
        } else {
          return a[column] < b[column] ? 1 : -1;
        }
      });
      
      renderTable(sortedData);
    });
  });
  
  // Function to render table
  function renderTable(data) {
    // Clear existing rows
    tableBody.innerHTML = '';
    
    // Create document fragment for better performance
    const fragment = document.createDocumentFragment();
    
    // Add data rows
    data.forEach(person => {
      const row = document.createElement('tr');
      
      const nameCell = document.createElement('td');
      nameCell.textContent = person.name;
      
      const ageCell = document.createElement('td');
      ageCell.textContent = person.age;
      
      const cityCell = document.createElement('td');
      cityCell.textContent = person.city;
      
      row.appendChild(nameCell);
      row.appendChild(ageCell);
      row.appendChild(cityCell);
      
      fragment.appendChild(row);
    });
    
    // Add to table in one operation
    tableBody.appendChild(fragment);
  }
});

Advanced DOM Manipulation Techniques

Template Elements

Using the HTML5 <template> element for reusable DOM fragments:

// HTML
<template id="user-card-template">
  <div class="user-card">
    <img class="user-avatar" src="" alt="User">
    <h3 class="user-name"></h3>
    <p class="user-email"></p>
    <button class="contact-btn">Contact</button>
  </div>
</template>

<div id="user-container"></div>

// JavaScript
const template = document.getElementById('user-card-template');
const container = document.getElementById('user-container');

// Sample user data
const users = [
  { id: 1, name: 'Alex Johnson', email: 'alex@example.com', avatar: 'avatar1.jpg' },
  { id: 2, name: 'Sarah Williams', email: 'sarah@example.com', avatar: 'avatar2.jpg' }
];

users.forEach(user => {
  // Clone the template content
  const userCard = template.content.cloneNode(true);
  
  // Update content
  userCard.querySelector('.user-name').textContent = user.name;
  userCard.querySelector('.user-email').textContent = user.email;
  userCard.querySelector('.user-avatar').src = user.avatar;
  
  // Add event listener
  userCard.querySelector('.contact-btn').addEventListener('click', function() {
    console.log(`Contact ${user.name}`);
  });
  
  // Add to the container
  container.appendChild(userCard);
});

DOM Range API

Working with text selections and ranges:

// Create a range
const range = document.createRange();

// Select content between nodes
const startNode = document.getElementById('start-content');
const endNode = document.getElementById('end-content');
range.setStartAfter(startNode);
range.setEndBefore(endNode);

// Extract content
const documentFragment = range.extractContents();
container.appendChild(documentFragment);

// Delete content
range.deleteContents();

// Insert content
const newNode = document.createElement('p');
newNode.textContent = 'New content';
range.insertNode(newNode);

DOM Mutation Observer

Watching for changes in the DOM:

// Select the node to observe
const targetNode = document.getElementById('watched-content');

// Configure the observer
const config = { 
  attributes: true, 
  childList: true, 
  subtree: true,
  attributeOldValue: true
};

// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
  for (const mutation of mutationsList) {
    if (mutation.type === 'childList') {
      console.log('A child node has been added or removed');
      console.log('Added nodes:', mutation.addedNodes);
      console.log('Removed nodes:', mutation.removedNodes);
    }
    else if (mutation.type === 'attributes') {
      console.log(`The ${mutation.attributeName} attribute was modified`);
      console.log('Old value:', mutation.oldValue);
      console.log('New value:', mutation.target.getAttribute(mutation.attributeName));
    }
  }
};

// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);

// Start observing the target node
observer.observe(targetNode, config);

// Later, you can stop observing
// observer.disconnect();

Browser Compatibility Considerations

While modern browsers mostly implement DOM APIs consistently, some differences remain:

Feature Detection

Always check if a feature is available before using it:

// Check if a method exists
if (element.classList) {
  element.classList.add('active');
} else {
  // Fallback for older browsers
  const classes = element.className.split(' ');
  if (classes.indexOf('active') === -1) {
    element.className += ' active';
  }
}

// Check for modern DOM methods
if ('querySelector' in document) {
  // Use querySelector
} else {
  // Use older methods like getElementById
}

// Check for template element support
const supportsTemplate = 'content' in document.createElement('template');
if (supportsTemplate) {
  // Use template element
} else {
  // Fallback implementation
}

Browser Vendor Prefixes

Some CSS properties in JavaScript still need vendor prefixes:

// Apply transform with vendor prefixes
function setTransform(element, transformValue) {
  element.style.webkitTransform = transformValue;
  element.style.mozTransform = transformValue;
  element.style.msTransform = transformValue;
  element.style.oTransform = transformValue;
  element.style.transform = transformValue;
}

Key Takeaways

  • Selection Methods: Choose the right method based on specificity and flexibility needs (getElementById, querySelector, etc.)
  • Content Manipulation: Use textContent for text-only changes (safer) and innerHTML when HTML structure changes are needed
  • Class Manipulation: Use classList API for modern, clean class manipulation
  • Attributes & Properties: Understand the difference between attributes and properties
  • Element Creation: Use createElement for dynamic content generation
  • Performance: Batch DOM operations and use DocumentFragment for better performance
  • Best Practices: Prefer class manipulation over direct style manipulation when possible

Practice Exercises

  1. Tabbed Interface: Create a tabbed interface where clicking tab headers shows different content panels.
  2. Dynamic Shopping List: Build a shopping list that allows adding, editing, and removing items with local storage persistence.
  3. Image Carousel: Create an image carousel/slider that cycles through images with next/previous buttons.
  4. Form Validation: Build a registration form with real-time validation and error messages.
  5. DOM Tree Visualizer: Create a tool that displays a visual representation of a simple DOM structure.

Further Resources