Skip to main content

Course Progress

Loading...

DOM Manipulation with jQuery

Duration: 45 minutes
Module 1: DOM Manipulation

Learning Objectives

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

Session Overview

Welcome to our deep dive into DOM manipulation with jQuery! Today's session focuses on how jQuery simplifies interacting with the Document Object Model (DOM). By the end of this session, you'll understand how to efficiently select, traverse, modify, and create HTML elements using jQuery — skills that are essential for WordPress theme and plugin development.

DOM Manipulation with jQuery Selection Traversal Modification Creation Insertion Deletion Basic Selectors Filter Methods Form Selectors Parents & Ancestors Children & Descendants Siblings Filtering Content Manipulation Attribute Manipulation Class Manipulation CSS Manipulation Creating Elements Cloning Elements Inside Existing Elements Outside Existing Elements Removing Elements Emptying Elements

Understanding the DOM

Before we dive into jQuery's DOM manipulation methods, let's refresh our understanding of what the DOM actually is.

What is the DOM?

The Document Object Model (DOM) is a programming interface that represents HTML documents as tree structures. Each element in an HTML document (paragraphs, divs, spans, etc.) becomes a node in this tree. The DOM provides a way for scripts to access and modify the structure, content, and style of a document.

The Family Tree Analogy

Think of the DOM as a family tree. The document is the oldest ancestor, with children (html, head, body) who themselves have children (divs, paragraphs), and so on. Each family member (node) has characteristics (attributes) and can have relationships with other family members (parent-child, siblings). jQuery provides easy ways to navigate this family tree and modify its members.

document html head body title div script p a text

Why Use jQuery for DOM Manipulation?

While modern JavaScript has improved its DOM manipulation capabilities, jQuery still offers significant advantages, especially in WordPress development:

  • Concise syntax: jQuery methods are shorter and more readable than vanilla JavaScript equivalents
  • Cross-browser compatibility: jQuery handles browser differences that might still exist
  • Method chaining: Perform multiple operations on the same selection with a fluent interface
  • WordPress integration: WordPress core uses jQuery extensively, making it a natural fit
  • Plugin ecosystem: Take advantage of thousands of jQuery plugins that extend functionality

Selecting Elements with jQuery

The foundation of DOM manipulation with jQuery is element selection. jQuery makes it easy to find exactly the elements you want to work with.

Basic Selectors

jQuery uses CSS-style selectors to find elements in the DOM:

// Select by ID
$("#header") // Finds the element with id="header"

// Select by class
$(".nav-item") // Finds all elements with class="nav-item"

// Select by tag name
$("p") // Finds all paragraph elements

// Combined selectors
$("ul.menu li") // Finds all list items inside ul with class="menu"

// Multiple selectors (comma-separated)
$("h1, h2, h3") // Finds all h1, h2, and h3 elements

// Attribute selectors
$("a[target='_blank']") // Finds links that open in new tabs
$("input[type='checkbox']") // Finds checkbox inputs
$("img[alt]") // Finds images with alt attributes

// Pseudo-class selectors
$("tr:odd") // Finds odd-numbered table rows
$("li:first-child") // Finds list items that are first children
$("div:visible") // Finds visible divs

Real-World WordPress Example: Customizing Admin Elements

Here's how you might use jQuery selectors to customize the WordPress admin interface:

jQuery(document).ready(function($) {
    // Hide specific metaboxes on the post edit screen
    $("#postexcerpt, #slugdiv").hide();
    
    // Add a class to all required form fields
    $(".form-required").addClass("highlighted-field");
    
    // Find all external links in the dashboard
    $(".wp-admin a[href^='http']:not([href*='"+window.location.hostname+"'])").addClass("external-link");
    
    // Style the first item in each admin menu
    $(".wp-admin-bar-root-default > li:first-child").css("background-color", "#f0f0f0");
});

Form-Specific Selectors

jQuery provides special selectors for working with forms:

// Form element selectors
$(":input") // All form elements (input, select, textarea, button)
$(":text") // Text inputs
$(":password") // Password inputs
$(":radio") // Radio buttons
$(":checkbox") // Checkboxes
$(":submit") // Submit buttons
$(":button") // Button elements
$(":file") // File inputs

// Form state selectors
$(":enabled") // Enabled form elements
$(":disabled") // Disabled form elements
$(":checked") // Checked radio buttons and checkboxes
$(":selected") // Selected options in select elements

Real-World Example: Form Validation in WordPress

Here's how you might use these form selectors in a WordPress form validation scenario:

jQuery(document).ready(function($) {
    // When the form is submitted
    $("#user-profile-form").on("submit", function(e) {
        // Reset previous errors
        $(".error-message").remove();
        
        // Check if required fields are empty
        $(this).find("input.required:text").each(function() {
            if ($(this).val() === "") {
                e.preventDefault(); // Stop form submission
                $(this).after("<span class='error-message'>This field is required</span>");
            }
        });
        
        // Check if at least one checkbox is checked
        if ($(this).find("input:checkbox:checked").length === 0) {
            e.preventDefault();
            $("#checkbox-group").after("<span class='error-message'>Please select at least one option</span>");
        }
        
        // Validate email format
        let $email = $(this).find("input[type='email']");
        let emailPattern = /^[^@]+@[^@]+\.[a-z]{2,}$/i;
        
        if ($email.length && !emailPattern.test($email.val())) {
            e.preventDefault();
            $email.after("<span class='error-message'>Please enter a valid email address</span>");
        }
    });
});

Traversing the DOM

Once you've selected elements, jQuery provides powerful methods to navigate through the DOM tree to find related elements.

Parent and Ancestor Methods

// Get the direct parent
let $parent = $("#child-element").parent();

// Get all ancestors
let $ancestors = $("#child-element").parents();

// Get specific ancestors
let $specificAncestors = $("#child-element").parents("div.container");

// Get ancestors until a specific element
let $limitedAncestors = $("#child-element").parentsUntil(".stop-here");

// Get closest ancestor matching a selector
let $closest = $("#child-element").closest(".wrapper");

Child and Descendant Methods

// Get all children
let $children = $("#parent-element").children();

// Get specific children
let $specificChildren = $("#parent-element").children(".highlighted");

// Get all descendants matching a selector
let $descendants = $("#parent-element").find("a");

// Get contents (including text nodes)
let $contents = $("#parent-element").contents();

Sibling Methods

// Get all siblings
let $siblings = $("#middle-element").siblings();

// Get specific siblings
let $specificSiblings = $("#middle-element").siblings(".important");

// Get next sibling
let $next = $("#first-element").next();

// Get all next siblings
let $allNext = $("#first-element").nextAll();

// Get next siblings until a match
let $nextUntil = $("#first-element").nextUntil(".stop-here");

// Get previous sibling
let $prev = $("#last-element").prev();

// Get all previous siblings
let $allPrev = $("#last-element").prevAll();

// Get previous siblings until a match
let $prevUntil = $("#last-element").prevUntil(".stop-here");

Filtering Methods

// Filter a selection based on a selector
let $filtered = $("li").filter(".active");

// Filter using a function
let $evenItems = $("li").filter(function(index) {
    return index % 2 === 0;
});

// Get first item in a selection
let $first = $("li").first();

// Get last item in a selection
let $last = $("li").last();

// Get item at a specific index
let $third = $("li").eq(2); // Zero-based index (3rd item)

// Get items greater than an index
let $afterThird = $("li").gt(2);

// Get items less than an index
let $beforeFourth = $("li").lt(3);

// Not selector (elements that don't match)
let $notHidden = $("div").not(".hidden");

Real-World Example: WordPress Menu Enhancement

Here's how you might use traversal methods to enhance a WordPress navigation menu:

jQuery(document).ready(function($) {
    // Add a dropdown icon to menu items with children
    $(".menu-item-has-children").each(function() {
        $(this).children("a").first().append("<span class='dropdown-icon'>▼</span>");
    });
    
    // When a parent menu item is clicked
    $(".menu-item-has-children > a").on("click", function(e) {
        e.preventDefault();
        
        // Toggle the submenu
        $(this).siblings(".sub-menu").slideToggle();
        
        // Toggle the dropdown icon
        $(this).find(".dropdown-icon").toggleClass("open");
        
        // Close other open submenus
        $(this).closest("li").siblings().find(".sub-menu").slideUp();
        $(this).closest("li").siblings().find(".dropdown-icon").removeClass("open");
    });
    
    // Add active class to current menu item and its ancestors
    $(".current-menu-item")
        .addClass("highlighted")
        .parents(".menu-item").addClass("ancestor-active");
});

The GPS Navigation Analogy

Think of DOM traversal like using a GPS navigation system. You start at a specific location (your selected element) and can then navigate in different directions: up to parents/ancestors, down to children/descendants, or sideways to siblings. Each traversal method is like inputting a different direction on your GPS, taking you to related elements in the DOM tree.

Current Element .parent() .parents() .children() .find() .prev() .prevAll() .next() .nextAll()

Modifying Elements

jQuery provides a rich set of methods for modifying existing elements in the DOM.

Getting and Setting Content

// Get text content (without HTML)
let headerText = $("#header").text();

// Set text content
$("#header").text("New Header Text");

// Get HTML content (including tags)
let contentHTML = $("#content").html();

// Set HTML content
$("#content").html("<strong>New content</strong> with <em>emphasis</em>");

// Get value of form elements
let username = $("input[name='username']").val();

// Set value of form elements
$("input[name='username']").val("JohnDoe");

Real-World Example: WordPress Post Editor Enhancement

Here's how you might use content manipulation in a WordPress admin customization:

jQuery(document).ready(function($) {
    // Add a character counter below the title field
    $("#title").after("<div id='title-counter'>Characters: 0</div>");
    
    // Update the counter when typing
    $("#title").on("input", function() {
        let count = $(this).val().length;
        $("#title-counter").text("Characters: " + count);
        
        // Add warning class if too long
        if (count > 60) {
            $("#title-counter").addClass("warning");
        } else {
            $("#title-counter").removeClass("warning");
        }
    });
    
    // Add template content to empty editor
    if ($("#content").text() === "") {
        if ($("#post_type").val() === "product") {
            // For product post type
            $("#content").html("<h2>Product Description</h2>\n<p>Enter product details here...</p>\n\n<h2>Features</h2>\n<ul>\n  <li>Feature 1</li>\n  <li>Feature 2</li>\n</ul>");
        } else {
            // For regular posts
            $("#content").html("<p>Enter your content here...</p>");
        }
    }
});

Working with Attributes

// Get an attribute
let linkURL = $("a.main-link").attr("href");

// Set an attribute
$("a.main-link").attr("href", "https://example.com");

// Set multiple attributes
$("img.profile").attr({
    src: "profile.jpg",
    alt: "Profile picture",
    width: 100,
    height: 100
});

// Remove an attribute
$("a.temp-link").removeAttr("href");

// Check if has attribute
if ($("img").is("[alt]")) {
    console.log("Image has alt attribute");
}

// Setting boolean attributes
$("input[type='checkbox']").prop("checked", true); // Check a checkbox
$("select option").prop("selected", false); // Deselect all options
$("button").prop("disabled", true); // Disable a button

// Toggle boolean attributes
$("input").prop("readonly", function(i, currentValue) {
    return !currentValue;
});

Managing CSS Classes

// Add a class
$("nav li").addClass("active");

// Add multiple classes
$("div.content").addClass("highlighted bordered");

// Add classes conditionally
$("tr").addClass(function(index) {
    return index % 2 === 0 ? "even" : "odd";
});

// Remove a class
$("nav li.old-item").removeClass("old-item");

// Remove multiple classes
$("div").removeClass("temporary highlighted");

// Toggle a class (add if not present, remove if present)
$(".accordion-item").toggleClass("expanded");

// Toggle multiple classes
$("p").toggleClass("highlighted bordered");

// Toggle class based on condition
$("li").toggleClass("active", $(this).index() < 3);

// Check if element has a class
if ($("#signup-btn").hasClass("disabled")) {
    // Do something
}

Manipulating CSS Styles

// Get a CSS property
let bgColor = $("#header").css("background-color");

// Set a CSS property
$("#header").css("background-color", "#f0f0f0");

// Set multiple CSS properties
$(".highlight").css({
    backgroundColor: "#ffff00",
    fontWeight: "bold",
    padding: "5px",
    borderRadius: "3px"
});

// Set property with calculation
$("div").css("width", function(index, currentWidth) {
    // Convert currentWidth from "100px" to 100
    return parseInt(currentWidth) + 50 + "px";
});

// Get computed dimensions
let width = $("#element").width(); // Content width
let innerWidth = $("#element").innerWidth(); // Content + padding
let outerWidth = $("#element").outerWidth(); // Content + padding + border
let outerWidthWithMargin = $("#element").outerWidth(true); // Content + padding + border + margin

// Set dimensions
$("#element").width(500);
$("#element").height(300);

Real-World Example: WordPress Theme Customizer Preview

Here's how you might use class and style manipulation for a live theme preview:

// In the WordPress Customizer
jQuery(document).ready(function($) {
    // When the primary color setting changes
    wp.customize('theme_primary_color', function(value) {
        value.bind(function(newColor) {
            // Update colors for various elements
            $(".site-header, .main-navigation").css("background-color", newColor);
            $(".button, .highlight").css("background-color", newColor);
            
            // Update text color based on background brightness
            let brightness = getBrightness(newColor);
            let textColor = brightness > 160 ? "#333333" : "#ffffff";
            
            $(".site-header, .main-navigation").css("color", textColor);
            $(".button, .highlight").css("color", textColor);
        });
    });
    
    // When layout setting changes
    wp.customize('theme_layout', function(value) {
        value.bind(function(newLayout) {
            // Remove all layout classes
            $("body").removeClass("layout-wide layout-boxed layout-narrow");
            
            // Add the new layout class
            $("body").addClass("layout-" + newLayout);
        });
    });
    
    // Helper function to calculate brightness
    function getBrightness(hexColor) {
        // Convert hex to RGB
        hexColor = hexColor.replace('#', '');
        let r = parseInt(hexColor.substr(0, 2), 16);
        let g = parseInt(hexColor.substr(2, 2), 16);
        let b = parseInt(hexColor.substr(4, 2), 16);
        
        // Calculate brightness (YIQ formula)
        return (r * 299 + g * 587 + b * 114) / 1000;
    }
});

Creating and Inserting Elements

jQuery makes it easy to create new HTML elements and insert them into the DOM.

Creating Elements

// Create a simple element
let $newDiv = $("<div></div>");

// Create an element with content
let $newParagraph = $("<p>This is a new paragraph.</p>");

// Create an element with attributes
let $newLink = $("<a href='https://example.com' class='external-link'>Visit Example</a>");

// Alternative syntax with attributes object
let $newImage = $("<img>", {
    src: "image.jpg",
    alt: "Description",
    class: "featured-image",
    width: 300,
    height: 200
});

// Create a complex structure
let $newCard = $("<div class='card'></div>")
    .append("<img src='product.jpg' alt='Product'>")
    .append("<h3>Product Title</h3>")
    .append("<p>Product description goes here.</p>")
    .append("<button>Add to Cart</button>");

Inserting Elements Inside

// Append to the end of an element
$("#container").append($newParagraph);
// Or with HTML string directly
$("#container").append("<p>New paragraph</p>");

// Append multiple elements
$("#container").append($newParagraph, $newDiv, $newLink);

// Prepend to the beginning of an element
$("#container").prepend($newParagraph);
// Or with HTML string directly
$("#container").prepend("<h2>Section Title</h2>");

// Append the same content to multiple elements
$("div.card").append("<span class='badge'>New!</span>");

// Prepend the same content to multiple elements
$("ul.list").prepend("<li class='list-header'>Items:</li>");

// Alternative syntax that reverses target and content
$newParagraph.appendTo("#container");
$newHeading.prependTo("#container");

Inserting Elements Outside

// Insert after an element (as a sibling)
$("#target").after($newDiv);
// Or with HTML string directly
$("#target").after("<div class='separator'></div>");

// Insert before an element (as a sibling)
$("#target").before($newHeading);
// Or with HTML string directly
$("#target").before("<h2>Section Title</h2>");

// Insert after multiple elements
$("p.note").after("<hr>");

// Insert before multiple elements
$("h3").before("<a class='anchor' id='section-{{index}}'></a>");

// Alternative syntax that reverses target and content
$newDiv.insertAfter("#target");
$newHeading.insertBefore("#target");

Wrapping Elements

// Wrap each element in a container
$("p").wrap("<div class='paragraph-wrapper'></div>");

// Wrap all elements together in a single container
$("p").wrapAll("<div class='paragraphs-container'></div>");

// Wrap only the contents of each element
$("div.post").wrapInner("<article></article>");

// Unwrap (remove parent)
$("p").unwrap();

Real-World Example: WordPress Comment Form Enhancement

Here's how you might use element creation and insertion to enhance a WordPress comment form:

jQuery(document).ready(function($) {
    // Add emoji picker to comment form
    $("#comment").after("<div class='emoji-picker'><button type='button' class='toggle-emoji-picker'>😀 Add Emoji</button><div class='emoji-container' style='display:none;'></div></div>");
    
    // Populate emoji container
    const emojis = ["😀", "😂", "😍", "🤔", "😎", "👍", "❤️", "🎉", "🔥", "✨"];
    let emojiHTML = "";
    
    emojis.forEach(function(emoji) {
        emojiHTML += "<span class='emoji-item'>" + emoji + "</span>";
    });
    
    $(".emoji-container").html(emojiHTML);
    
    // Toggle emoji picker
    $(".toggle-emoji-picker").on("click", function() {
        $(".emoji-container").slideToggle(200);
    });
    
    // Insert emoji when clicked
    $(".emoji-item").on("click", function() {
        let emoji = $(this).text();
        let $commentField = $("#comment");
        let currentText = $commentField.val();
        
        // Insert emoji at cursor position or append to end
        let cursorPos = $commentField[0].selectionStart;
        
        if (typeof cursorPos !== 'undefined') {
            let textBefore = currentText.substring(0, cursorPos);
            let textAfter = currentText.substring(cursorPos, currentText.length);
            
            $commentField.val(textBefore + emoji + textAfter);
            
            // Reset cursor position after the inserted emoji
            let newPos = cursorPos + emoji.length;
            $commentField[0].setSelectionRange(newPos, newPos);
        } else {
            // Fallback: just append to the end
            $commentField.val(currentText + emoji);
        }
        
        // Focus the comment field
        $commentField.focus();
    });
    
    // Add character counter below comment form
    $("#comment").after("<div class='comment-char-count'>0 characters</div>");
    
    // Update character count when typing
    $("#comment").on("input", function() {
        let count = $(this).val().length;
        $(".comment-char-count").text(count + " characters");
    });
});

The LEGO Building Analogy

Think of DOM creation and insertion like building with LEGO bricks. First, you create individual pieces (new elements), then you decide how to connect them: stack them inside each other (append/prepend), place them side by side (before/after), or even wrap them in a container piece (wrap). Just like with LEGOs, you can build complex structures piece by piece, or create pre-assembled sections and then place them into your main construction.

LEGO Building Analogy - DOM Creation Flow Create Element $('<div class="card"></div>') Append Inside at end Prepend Inside at beginning After Outside after Before Outside before Wrap Surround with $('#container') .append(newElement) $('#container') .prepend(newElement) $('#ref') .after(newElement) $('#ref') .before(newElement) $('#element') .wrap(newElement) Visual representation of DOM insertion methods Action Code Example

Removing Elements

jQuery provides several methods for removing elements from the DOM.

Basic Removal Methods

// Remove elements and their data/events
$(".temp-message").remove();

// Remove elements that match a selector
$("div").remove(".obsolete, .temporary");

// Remove elements but keep their data/events for later use
let $detached = $(".sidebar-widget").detach();

// Later, you can reattach the elements
$("#new-sidebar").append($detached);

// Remove all children of an element
$("#comments-list").empty();

// Replace elements with new content
$("#old-content").replaceWith("<div id='new-content'>Updated content</div>");

// Replace elements with elements from another selection
$("p.temp").replaceWith($("div.final"));

// Replace all matched elements with the first element
$("div.duplicate").replaceAll("div.original");

Real-World Example: WordPress Admin Cleanup

Here's how you might use removal methods to customize the WordPress admin interface:

jQuery(document).ready(function($) {
    // Remove unnecessary metaboxes for specific user roles
    if (currentUser.role !== 'administrator') {
        $("#dashboard_right_now, #dashboard_activity").remove();
    }
    
    // Empty and repopulate a dashboard widget
    $("#dashboard_quick_press").empty().append("<p>Quick actions have been customized for your role.</p>");
    
    // Dynamically filter and hide table rows based on content
    $("#the-list tr").each(function() {
        if ($(this).find("td.column-author").text().includes("Guest Author")) {
            $(this).addClass("guest-post").hide();
        }
    });
    
    // Add a toggle button
    $("#posts-filter .tablenav.top").append(
        "<button type='button' id='toggle-guest-posts' class='button'>Show/Hide Guest Posts</button>"
    );
    
    // Toggle visibility when button is clicked
    $("#toggle-guest-posts").on("click", function() {
        $(".guest-post").toggle();
    });
    
    // Replace the welcome panel with a custom message
    if ($("#welcome-panel").length) {
        let $customWelcome = $("<div id='custom-welcome-panel' class='welcome-panel'></div>")
            .append("<h2>Welcome to " + siteName + "!</h2>")
            .append("<p>Here are your pending tasks for today:</p>")
            .append("<ul id='daily-tasks'></ul>");
        
        // Fetch tasks via AJAX and populate the list
        $.get(ajaxurl, {
            action: 'get_daily_tasks'
        }, function(response) {
            if (response.success && response.data.tasks) {
                $.each(response.data.tasks, function(i, task) {
                    $("#daily-tasks").append("<li>" + task.title + "</li>");
                });
            }
        });
        
        $("#welcome-panel").replaceWith($customWelcome);
    }
});

Cloning Elements

jQuery's clone method allows you to make copies of existing elements.

Basic Cloning

// Clone an element
let $cloned = $("#original-element").clone();

// Insert the clone somewhere
$("#target-container").append($cloned);

// Clone with data and events (true parameter)
let $clonedWithEvents = $("#original-element").clone(true);

// Clone with data but without events
let $clonedWithData = $("#original-element").clone(true, false);

// Clone, modify, and insert
$("#template-row").clone()
    .attr("id", "new-row-" + rowCount)
    .find(".item-name").text("New Item").end()
    .find(".item-price").text("$0.00").end()
    .appendTo("#items-table tbody");

Real-World Example: WordPress Dynamic Form Fields

Here's how you might use cloning to create repeatable form fields in a WordPress plugin:

jQuery(document).ready(function($) {
    // Set up a counter for unique IDs
    let fieldCounter = $(".repeatable-field-group").length;
    
    // Add button to create a new field group
    $("#repeatable-fields").after("<button type='button' id='add-field-group' class='button button-secondary'>Add Another Item</button>");
    
    // When the add button is clicked
    $("#add-field-group").on("click", function() {
        // Clone the template
        let $template = $(".repeatable-field-group:first").clone(true);
        
        // Update IDs and names
        fieldCounter++;
        
        $template.attr("id", "field-group-" + fieldCounter);
        
        // Update input fields
        $template.find("input, select, textarea").each(function() {
            let $input = $(this);
            let name = $input.attr("name");
            
            // Update the array index in the name attribute
            name = name.replace(/\[(\d+)\]/, "[" + fieldCounter + "]");
            $input.attr("name", name);
            
            // Clear the value
            $input.val("");
            
            // Update id attribute if present
            let id = $input.attr("id");
            if (id) {
                $input.attr("id", id.replace(/-(\d+)$/, "-" + fieldCounter));
            }
        });
        
        // Update labels
        $template.find("label").each(function() {
            let $label = $(this);
            let forAttr = $label.attr("for");
            
            if (forAttr) {
                $label.attr("for", forAttr.replace(/-(\d+)$/, "-" + fieldCounter));
            }
        });
        
        // Add delete button if it doesn't exist
        if ($template.find(".remove-field-group").length === 0) {
            $template.append("<button type='button' class='button remove-field-group'>Remove</button>");
        }
        
        // Add the cloned and modified template to the form
        $("#repeatable-fields").append($template);
    });
    
    // Handle removal of field groups (using event delegation)
    $("#repeatable-fields").on("click", ".remove-field-group", function() {
        // Don't remove if it's the last one
        if ($(".repeatable-field-group").length > 1) {
            $(this).closest(".repeatable-field-group").slideUp(300, function() {
                $(this).remove();
            });
        } else {
            // Just clear the values if it's the last one
            let $group = $(this).closest(".repeatable-field-group");
            $group.find("input, select, textarea").val("");
            
            alert("You need to keep at least one item.");
        }
    });
});

Performance Optimization

When manipulating the DOM, performance considerations become important, especially for complex WordPress sites.

Best Practices for DOM Manipulation

  • Cache jQuery objects: Store frequently used selections in variables
  • Minimize DOM traversals: Find elements efficiently with specific selectors
  • Use document fragments: Build complex structures before inserting into the DOM
  • Limit reflows and repaints: Batch DOM operations when possible
  • Use CSS classes for styling: Modify classes instead of individual style properties
  • Detach elements for bulk changes: Remove from DOM, modify, then reinsert
  • Use event delegation: Attach events to parent elements instead of multiple children

Bad Performance:

// Multiple DOM queries for the same element
$("#header").css("background-color", "#f0f0f0");
$("#header").addClass("sticky");
$("#header").find("h1").text("New Title");

// Adding multiple elements one by one
for (let i = 0; i < 100; i++) {
    $("#list").append("<li>Item " + i + "</li>");
}

// Inefficient style changes causing reflows
$(".box").css("width", "200px");
$(".box").css("height", "150px");
$(".box").css("background-color", "#f0f0f0");
$(".box").css("border-radius", "5px");

Good Performance:

// Cache jQuery objects
let $header = $("#header");
$header.css("background-color", "#f0f0f0")
       .addClass("sticky")
       .find("h1").text("New Title");

// Build HTML string before appending
let items = [];
for (let i = 0; i < 100; i++) {
    items.push("<li>Item " + i + "</li>");
}
$("#list").append(items.join(""));

// Or use document fragment for complex structures
let fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    let li = document.createElement("li");
    li.textContent = "Item " + i;
    fragment.appendChild(li);
}
$("#list")[0].appendChild(fragment);

// Batch style changes
$(".box").css({
    width: "200px",
    height: "150px",
    backgroundColor: "#f0f0f0",
    borderRadius: "5px"
});

// Or better, add a class with all styles
$(".box").addClass("featured-box");

Real-World Example: WordPress Image Gallery Optimization

Here's how you might optimize the creation of a large image gallery:

jQuery(document).ready(function($) {
    // Fetch gallery images via AJAX
    $.getJSON(ajaxurl, {
        action: 'get_gallery_images',
        gallery_id: currentGalleryId
    }, function(response) {
        if (response.success && response.data.images) {
            renderGallery(response.data.images);
        }
    });
    
    function renderGallery(images) {
        let $gallery = $("#image-gallery");
        
        // Detach the gallery while we work on it
        $gallery.detach();
        
        // Clear any existing content
        $gallery.empty();
        
        // Build all items at once
        let galleryHTML = "";
        
        $.each(images, function(i, image) {
            galleryHTML += 
                "<div class='gallery-item' data-id='" + image.id + "'>" +
                    "<img src='" + image.thumb + "' alt='" + image.title + "'>" +
                    "<div class='item-overlay'>" +
                        "<h4>" + image.title + "</h4>" +
                        "<div class='item-actions'>" +
                            "<button class='view-full'>View</button>" +
                            "<button class='edit-image'>Edit</button>" +
                        "</div>" +
                    "</div>" +
                "</div>";
        });
        
        // Add all items at once
        $gallery.html(galleryHTML);
        
        // Reattach to the DOM
        $("#gallery-container").append($gallery);
        
        // Initialize masonry layout after images load
        $gallery.imagesLoaded(function() {
            $gallery.masonry({
                itemSelector: '.gallery-item',
                columnWidth: '.gallery-item',
                percentPosition: true
            });
        });
        
        // Use event delegation for all gallery interactions
        $gallery.on("click", ".view-full", function() {
            let imageId = $(this).closest(".gallery-item").data("id");
            openLightbox(imageId);
        });
        
        $gallery.on("click", ".edit-image", function() {
            let imageId = $(this).closest(".gallery-item").data("id");
            openImageEditor(imageId);
        });
    }
});

Practical WordPress Examples

Example 1: Dynamic Admin Meta Box

Let's create a dynamic meta box for the WordPress post editor that uses jQuery DOM manipulation:

// PHP part (functions.php or plugin file)
function add_dynamic_related_posts_meta_box() {
    add_meta_box(
        'dynamic_related_posts',
        'Related Posts',
        'render_related_posts_meta_box',
        'post',
        'side',
        'default'
    );
}
add_action('add_meta_boxes', 'add_dynamic_related_posts_meta_box');

function render_related_posts_meta_box($post) {
    // Add nonce for security
    wp_nonce_field('related_posts_nonce', 'related_posts_nonce');
    
    // Get saved related posts
    $related_posts = get_post_meta($post->ID, '_related_posts', true);
    
    // Output the meta box HTML
    ?>
    <div id="related-posts-container">
        <p>
            <label for="related-posts-search">Search posts:</label>
            <input type="text" id="related-posts-search" class="widefat" placeholder="Type to search...">
        </p>
        
        <div id="search-results" class="hidden">
            <ul></ul>
        </div>
        
        <div id="selected-related-posts">
            <h4>Selected Related Posts:</h4>
            <ul id="related-posts-list">
                <?php
                if (!empty($related_posts)) {
                    foreach ($related_posts as $id) {
                        $related_post = get_post($id);
                        if ($related_post) {
                            echo '<li data-id="' . esc_attr($id) . '">';
                            echo esc_html($related_post->post_title);
                            echo '<a href="#" class="remove-related">×</a>';
                            echo '<input type="hidden" name="related_posts[]" value="' . esc_attr($id) . '">';
                            echo '</li>';
                        }
                    }
                }
                ?>
            </ul>
            <p class="howto">Drag to reorder.</p>
        </div>
    </div>
    <?php
    
    // Enqueue the script
    wp_enqueue_script('jquery-ui-sortable');
    wp_enqueue_script(
        'related-posts-meta-box',
        get_template_directory_uri() . '/js/related-posts.js',
        array('jquery', 'jquery-ui-sortable'),
        '1.0',
        true
    );
    
    wp_localize_script('related-posts-meta-box', 'relatedPostsData', array(
        'ajaxurl' => admin_url('admin-ajax.php'),
        'nonce' => wp_create_nonce('search_posts_nonce'),
        'currentPostId' => $post->ID
    ));
}

// Save the meta box data
function save_related_posts_meta($post_id) {
    // Check nonce
    if (!isset($_POST['related_posts_nonce']) || !wp_verify_nonce($_POST['related_posts_nonce'], 'related_posts_nonce')) {
        return;
    }
    
    // Check autosave
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    
    // Check permissions
    if ('post' === $_POST['post_type'] && !current_user_can('edit_post', $post_id)) {
        return;
    }
    
    // Save related posts
    if (isset($_POST['related_posts'])) {
        update_post_meta($post_id, '_related_posts', array_map('intval', $_POST['related_posts']));
    } else {
        delete_post_meta($post_id, '_related_posts');
    }
}
add_action('save_post', 'save_related_posts_meta');

// AJAX handler for post search
function search_posts_ajax() {
    check_ajax_referer('search_posts_nonce', 'nonce');
    
    $search_term = sanitize_text_field($_POST['search_term']);
    $current_post_id = intval($_POST['current_post_id']);
    
    $args = array(
        'post_type' => 'post',
        'post_status' => 'publish',
        'posts_per_page' => 10,
        's' => $search_term,
        'post__not_in' => array($current_post_id),
    );
    
    $query = new WP_Query($args);
    $results = array();
    
    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            $results[] = array(
                'id' => get_the_ID(),
                'title' => get_the_title(),
            );
        }
    }
    
    wp_reset_postdata();
    wp_send_json_success($results);
}
add_action('wp_ajax_search_posts', 'search_posts_ajax');

JavaScript (related-posts.js):

jQuery(document).ready(function($) {
    // Make the related posts list sortable
    $("#related-posts-list").sortable({
        placeholder: "sortable-placeholder",
        update: function(event, ui) {
            // Re-index the hidden inputs if needed
        }
    });
    
    // Search functionality
    let searchTimeout;
    $("#related-posts-search").on("input", function() {
        let searchTerm = $(this).val().trim();
        
        // Clear previous timeout
        clearTimeout(searchTimeout);
        
        // Hide results if search term is too short
        if (searchTerm.length < 3) {
            $("#search-results").addClass("hidden");
            return;
        }
        
        // Set a small timeout to prevent too many AJAX requests while typing
        searchTimeout = setTimeout(function() {
            // Show loading indicator
            $("#search-results").removeClass("hidden").html("<p>Searching...</p>");
            
            // Send AJAX request
            $.ajax({
                url: relatedPostsData.ajaxurl,
                type: "POST",
                data: {
                    action: "search_posts",
                    nonce: relatedPostsData.nonce,
                    search_term: searchTerm,
                    current_post_id: relatedPostsData.currentPostId
                },
                success: function(response) {
                    if (response.success && response.data.length > 0) {
                        // Build results list
                        let resultsHTML = "<ul>";
                        
                        response.data.forEach(function(post) {
                            // Check if this post is already in the related posts list
                            let isSelected = $("#related-posts-list li[data-id='" + post.id + "']").length > 0;
                            let disabledClass = isSelected ? " class='disabled'" : "";
                            
                            resultsHTML += "<li data-id='" + post.id + "'" + disabledClass + ">";
                            resultsHTML += post.title;
                            
                            if (!isSelected) {
                                resultsHTML += "<a href='#' class='add-related'>+</a>";
                            } else {
                                resultsHTML += " (already added)";
                            }
                            
                            resultsHTML += "</li>";
                        });
                        
                        resultsHTML += "</ul>";
                        $("#search-results").html(resultsHTML);
                    } else {
                        // No results
                        $("#search-results").html("<p>No posts found.</p>");
                    }
                },
                error: function() {
                    // Error handling
                    $("#search-results").html("<p>Error searching posts.</p>");
                }
            });
        }, 500);
    });
    
    // Add related post
    $("#search-results").on("click", ".add-related", function(e) {
        e.preventDefault();
        
        let $listItem = $(this).parent();
        let postId = $listItem.data("id");
        let postTitle = $listItem.text().replace("+", "").trim();
        
        // Create the new related post item
        let $newItem = $("<li>").attr("data-id", postId);
        $newItem.text(postTitle);
        $newItem.append("<a href='#' class='remove-related'>×</a>");
        $newItem.append("<input type='hidden' name='related_posts[]' value='" + postId + "'>");
        
        // Add to the related posts list
        $("#related-posts-list").append($newItem);
        
        // Disable in search results
        $listItem.addClass("disabled").find(".add-related").remove().end().append(" (already added)");
        
        // Clear search
        $("#related-posts-search").val("");
        $("#search-results").addClass("hidden");
    });
    
    // Remove related post
    $("#related-posts-list").on("click", ".remove-related", function(e) {
        e.preventDefault();
        
        let $listItem = $(this).parent();
        let postId = $listItem.data("id");
        
        // Remove the item with animation
        $listItem.fadeOut(300, function() {
            $(this).remove();
            
            // Update search results if they're visible
            $("#search-results li[data-id='" + postId + "']").removeClass("disabled")
                .find("span").remove().end()
                .append("<a href='#' class='add-related'>+</a>");
        });
    });
    
    // Hide search results when clicking outside
    $(document).on("click", function(e) {
        if (!$(e.target).closest("#related-posts-container").length) {
            $("#search-results").addClass("hidden");
        }
    });
});

Example 2: Dynamic Theme Customizer Controls

Here's an example of using jQuery DOM manipulation for a custom Theme Customizer experience:

// JavaScript for the WordPress Theme Customizer
jQuery(document).ready(function($) {
    // Listen for changes to the header layout option
    wp.customize('theme_header_layout', function(value) {
        value.bind(function(newLayout) {
            // Update the preview by manipulating the DOM
            let $headerContent = $(".site-header-content");
            
            // Remove existing layout classes
            $headerContent.removeClass("layout-centered layout-split layout-traditional");
            
            // Add the new layout class
            $headerContent.addClass("layout-" + newLayout);
            
            // Rearrange elements based on the selected layout
            switch(newLayout) {
                case 'centered':
                    // Move logo to the top center
                    $(".site-branding").prependTo($headerContent).addClass("centered");
                    
                    // Move navigation below
                    $(".main-navigation").appendTo($headerContent);
                    
                    // Move search and cart icons to the bottom
                    $(".header-icons").appendTo($headerContent);
                    break;
                    
                case 'split':
                    // Center the logo
                    $(".site-branding").appendTo($headerContent.find(".header-middle")).addClass("centered");
                    
                    // Split the navigation into two parts
                    let $nav = $(".main-navigation");
                    let $menuItems = $nav.find(".menu > li");
                    let middlePoint = Math.ceil($menuItems.length / 2);
                    
                    // Create left and right navigation containers if they don't exist
                    if ($(".header-left .left-navigation").length === 0) {
                        $(".header-left").append("<nav class='left-navigation'><ul class='left-menu'></ul></nav>");
                    }
                    
                    if ($(".header-right .right-navigation").length === 0) {
                        $(".header-right").append("<nav class='right-navigation'><ul class='right-menu'></ul></nav>");
                    }
                    
                    // Clear existing items
                    $(".left-menu, .right-menu").empty();
                    
                    // Distribute menu items
                    $menuItems.each(function(index) {
                        if (index < middlePoint) {
                            $(this).clone(true).appendTo(".left-menu");
                        } else {
                            $(this).clone(true).appendTo(".right-menu");
                        }
                    });
                    
                    // Hide the original navigation
                    $nav.hide();
                    
                    // Move search and cart to the right
                    $(".header-icons").appendTo($(".header-right"));
                    break;
                    
                case 'traditional':
                    // Logo on the left
                    $(".site-branding").prependTo($headerContent.find(".header-left")).removeClass("centered");
                    
                    // Show the original navigation and move it to the right
                    $(".main-navigation").show().appendTo($(".header-right"));
                    
                    // Move search and cart next to navigation
                    $(".header-icons").insertAfter($(".main-navigation"));
                    break;
            }
        });
    });
    
    // Listen for changes to the color scheme
    wp.customize('theme_color_scheme', function(value) {
        value.bind(function(newColorScheme) {
            // Remove existing scheme classes
            $("body").removeClass(function(index, className) {
                return (className.match(/(^|\s)color-scheme-\S+/g) || []).join(' ');
            });
            
            // Add new scheme class
            $("body").addClass("color-scheme-" + newColorScheme);
            
            // Update dynamic elements - example for navigation
            if (newColorScheme === "dark") {
                // If a light logo version exists, switch to it
                let $logo = $(".site-logo img");
                let darkLogoSrc = $logo.attr("src");
                let lightLogoSrc = $logo.data("light-logo");
                
                if (lightLogoSrc) {
                    $logo.attr("src", lightLogoSrc).data("dark-logo", darkLogoSrc);
                }
                
                // Add dark mode icon
                if ($(".dark-mode-indicator").length === 0) {
                    $(".header-icons").prepend("<span class='dark-mode-indicator'>🌙</span>");
                }
            } else {
                // Switch back to dark logo if needed
                let $logo = $(".site-logo img");
                let lightLogoSrc = $logo.attr("src");
                let darkLogoSrc = $logo.data("dark-logo");
                
                if (darkLogoSrc) {
                    $logo.attr("src", darkLogoSrc).data("light-logo", lightLogoSrc);
                }
                
                // Remove dark mode icon
                $(".dark-mode-indicator").remove();
            }
        });
    });
    
    // Listen for changes to the typography
    wp.customize('theme_heading_font', function(value) {
        value.bind(function(newFont) {
            // If the font hasn't been loaded yet, load it
            if (newFont !== "default" && $("#font-" + newFont).length === 0) {
                $("head").append("<link id='font-" + newFont + "' href='https://fonts.googleapis.com/css?family=" + newFont + ":400,700&display=swap' rel='stylesheet'>");
            }
            
            // Update CSS Custom Property
            document.documentElement.style.setProperty('--heading-font', "'" + newFont + "', sans-serif");
            
            // Or, apply directly to elements
            $("h1, h2, h3, h4, h5, h6").css("font-family", "'" + newFont + "', sans-serif");
        });
    });
});

Example 3: Interactive Widget with Ajax

Let's create a WordPress widget that uses jQuery DOM manipulation to provide a live search feature:

// PHP Widget Class (in your theme's functions.php or plugin file)
class Live_Search_Widget extends WP_Widget {
    public function __construct() {
        parent::__construct(
            'live_search_widget',
            'Live Search Widget',
            ['description' => 'A widget that shows live search results as users type']
        );
    }
    
    public function widget($args, $instance) {
        echo $args['before_widget'];
        
        if (!empty($instance['title'])) {
            echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
        }
        
        // Widget content
        ?>
        <div class="live-search-widget">
            <div class="search-form-container">
                <input type="text" class="live-search-input" placeholder="">
                <button type="button" class="clear-search" style="display: none;">×</button>
            </div>
            
            <div class="live-search-results">
                <div class="results-message">Type to search...</div>
                <ul class="results-list"></ul>
            </div>
            
            <div class="search-footer">
                <a href="?s=" class="view-all-results" style="display: none;">View all results</a>
            </div>
        </div>
         admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('live_search_nonce'),
            'searching_text' => esc_html($instance['searching_text'] ?: 'Searching...'),
            'no_results_text' => esc_html($instance['no_results_text'] ?: 'No results found'),
            'min_chars' => absint($instance['min_chars'] ?: 3)
        ]);
        
        echo $args['after_widget'];
    }
    
    public function form($instance) {
        $title = !empty($instance['title']) ? $instance['title'] : 'Search';
        $placeholder = !empty($instance['placeholder']) ? $instance['placeholder'] : 'Search...';
        $searching_text = !empty($instance['searching_text']) ? $instance['searching_text'] : 'Searching...';
        $no_results_text = !empty($instance['no_results_text']) ? $instance['no_results_text'] : 'No results found';
        $min_chars = !empty($instance['min_chars']) ? absint($instance['min_chars']) : 3;
        
        ?>
        <p>
            <label for="get_field_id('title')); ?>">Title:</label>
            <input class="widefat" id="get_field_id('title')); ?>" name="get_field_name('title')); ?>" type="text" value="">
        </p>
        
        <p>
            <label for="get_field_id('placeholder')); ?>">Placeholder Text:</label>
            <input class="widefat" id="get_field_id('placeholder')); ?>" name="get_field_name('placeholder')); ?>" type="text" value="">
        </p>
        
        <p>
            <label for="get_field_id('searching_text')); ?>">Searching Message:</label>
            <input class="widefat" id="get_field_id('searching_text')); ?>" name="get_field_name('searching_text')); ?>" type="text" value="">
        </p>
        
        <p>
            <label for="get_field_id('no_results_text')); ?>">No Results Message:</label>
            <input class="widefat" id="get_field_id('no_results_text')); ?>" name="get_field_name('no_results_text')); ?>" type="text" value="">
        </p>
        
        <p>
            <label for="get_field_id('min_chars')); ?>">Minimum Characters:</label>
            <input class="tiny-text" id="get_field_id('min_chars')); ?>" name="get_field_name('min_chars')); ?>" type="number" min="1" max="10" value="">
        </p>
         ['post', 'page', 'product'], // Add your post types
        'post_status' => 'publish',
        'posts_per_page' => 5,
        's' => $search_term,
    ];
    
    $query = new WP_Query($args);
    $results = [];
    
    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            
            // Get featured image if available
            $thumbnail = '';
            if (has_post_thumbnail()) {
                $thumbnail = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
            }
            
            // Get post type label
            $post_type_obj = get_post_type_object(get_post_type());
            $post_type_label = $post_type_obj->labels->singular_name;
            
            $results[] = [
                'id' => get_the_ID(),
                'title' => get_the_title(),
                'excerpt' => wp_trim_words(get_the_excerpt(), 10),
                'permalink' => get_permalink(),
                'thumbnail' => $thumbnail,
                'post_type' => $post_type_label,
                'date' => get_the_date()
            ];
        }
        
        wp_reset_postdata();
        wp_send_json_success($results);
    } else {
        wp_send_json_error('No results found');
    }
}
add_action('wp_ajax_live_search', 'live_search_ajax');
add_action('wp_ajax_nopriv_live_search', 'live_search_ajax');

JavaScript (live-search.js):

jQuery(document).ready(function($) {
    let searchTimeout;
    
    // Cache DOM elements for better performance
    let $searchInput = $(".live-search-input");
    let $clearButton = $(".clear-search");
    let $resultsContainer = $(".live-search-results");
    let $resultsMessage = $(".results-message");
    let $resultsList = $(".results-list");
    let $viewAllLink = $(".view-all-results");
    
    // When user types in the search input
    $searchInput.on("input", function() {
        let searchTerm = $(this).val().trim();
        
        // Clear previous timeout
        clearTimeout(searchTimeout);
        
        // Show/hide clear button
        if (searchTerm.length > 0) {
            $clearButton.show();
        } else {
            $clearButton.hide();
            $resultsMessage.text("Type to search...");
            $resultsList.empty();
            $viewAllLink.hide();
            return;
        }
        
        // Check if search term is long enough
        if (searchTerm.length < liveSearchData.min_chars) {
            $resultsMessage.text("Please enter at least " + liveSearchData.min_chars + " characters...");
            $resultsList.empty();
            $viewAllLink.hide();
            return;
        }
        
        // Set a timeout to prevent too many AJAX requests while typing
        searchTimeout = setTimeout(function() {
            performSearch(searchTerm);
        }, 500);
    });
    
    // Clear search button
    $clearButton.on("click", function() {
        $searchInput.val("").focus();
        $clearButton.hide();
        $resultsMessage.text("Type to search...");
        $resultsList.empty();
        $viewAllLink.hide();
    });
    
    // Handle clicks on search results
    $resultsList.on("click", "li", function() {
        window.location.href = $(this).find("a").attr("href");
    });
    
    // Update the view all results link
    function updateViewAllLink(searchTerm) {
        $viewAllLink.attr("href", $viewAllLink.attr("href") + encodeURIComponent(searchTerm));
        $viewAllLink.show();
    }
    
    // Perform the AJAX search
    function performSearch(searchTerm) {
        // Show searching message
        $resultsMessage.text(liveSearchData.searching_text);
        $resultsList.empty();
        
        // Send AJAX request
        $.ajax({
            url: liveSearchData.ajaxurl,
            type: "POST",
            data: {
                action: "live_search",
                nonce: liveSearchData.nonce,
                search_term: searchTerm
            },
            success: function(response) {
                if (response.success && response.data.length > 0) {
                    // Hide the message and clear the list
                    $resultsMessage.hide();
                    $resultsList.empty();
                    
                    // Add each result to the list
                    $.each(response.data, function(index, result) {
                        let $resultItem = $("<li class='result-item'>").appendTo($resultsList);
                        
                        let $resultLink = $("<a>")
                            .attr("href", result.permalink)
                            .appendTo($resultItem);
                        
                        // If thumbnail exists, add it
                        if (result.thumbnail) {
                            $("<div class='result-thumbnail'>")
                                .append($("<img>").attr({
                                    src: result.thumbnail,
                                    alt: result.title
                                }))
                                .appendTo($resultLink);
                        }
                        
                        let $resultContent = $("<div class='result-content'>").appendTo($resultLink);
                        
                        $("<h4 class='result-title'>").text(result.title).appendTo($resultContent);
                        
                        if (result.excerpt) {
                            $("<div class='result-excerpt'>").text(result.excerpt).appendTo($resultContent);
                        }
                        
                        let $resultMeta = $("<div class='result-meta'>").appendTo($resultContent);
                        
                        $("<span class='result-type'>").text(result.post_type).appendTo($resultMeta);
                        $("<span class='result-date'>").text(result.date).appendTo($resultMeta);
                    });
                    
                    // Update the "View all results" link
                    updateViewAllLink(searchTerm);
                } else {
                    // No results found
                    $resultsMessage.text(liveSearchData.no_results_text).show();
                    $resultsList.empty();
                    $viewAllLink.hide();
                }
            },
            error: function() {
                // Error handling
                $resultsMessage.text("Error performing search. Please try again.").show();
                $resultsList.empty();
                $viewAllLink.hide();
            }
        });
    }
});

Homework Assignment

To practice your jQuery DOM manipulation skills, complete the following tasks:

  1. Task 1: WordPress Comment System Enhancement

    Create a script that enhances the WordPress comment system. Your script should:

    • Add "Reply" links to each comment that auto-scroll to the comment form and pre-fill it with "@username"
    • Add a "Quote" feature that copies the comment text into the comment form with proper formatting
    • Implement an "Edit" timer for your own comments (5 minutes to edit after posting)
    • Create a simple preview feature that shows how your comment will look as you type
  2. Task 2: Dynamic Content Loader

    Create a script that dynamically loads more posts when a user clicks "Load More" or scrolls to the bottom of the page. Your implementation should:

    • Create and inject a "Load More" button at the end of post listings
    • Use AJAX to fetch additional posts without page refreshes
    • Show a loading indicator while fetching
    • Gracefully handle the "no more posts" scenario
  3. Task 3: Interactive Form Builder

    Create a form builder interface that uses jQuery to:

    • Allow users to add different types of form fields (text, email, checkbox, radio, select)
    • Provide options to configure each field (label, placeholder, required status)
    • Allow drag and drop reordering of fields
    • Generate the corresponding HTML/PHP code for a WordPress form

Submit your completed code with comments explaining your DOM manipulation techniques before the next session.

Additional Resources

Documentation and Tutorials

Tools and Plugins

  • Chrome DevTools: Use the Elements panel to inspect and modify the DOM
  • jQuery Cheat Sheet: Quick reference for common DOM manipulation methods
  • jsFiddle: Online playground for testing jQuery code
  • What's Running: Browser extension to detect jQuery versions on websites