Skip to main content

Course Progress

Loading...

💲 Working with jQuery in WordPress

Master jQuery integration in WordPress themes

Learn no-conflict mode, best practices, and common jQuery patterns

Learning Objectives

  • Understand jQuery in WordPress context
  • Master jQuery no-conflict mode
  • Use WordPress's bundled jQuery properly
  • Create jQuery plugins for WordPress
  • Handle jQuery events and DOM manipulation
  • Implement jQuery AJAX in WordPress
  • Migrate from jQuery to vanilla JavaScript
  • Debug jQuery issues in WordPress

jQuery in WordPress

WordPress includes jQuery by default and many themes and plugins depend on it. Understanding how to work with jQuery in WordPress's no-conflict mode is essential for theme development.

💡
Key Concept
WordPress runs jQuery in no-conflict mode, meaning the $ shortcut is not available by default. You must use jQuery or wrap your code properly.

WordPress jQuery Version

WordPress includes jQuery but may not always have the latest version due to compatibility concerns. As of WordPress 5.9+:

  • jQuery 3.6.0+ is included
  • jQuery Migrate is included for backward compatibility
  • jQuery UI components are available
  • No-conflict mode is enabled by default

Understanding No-Conflict Mode

Why No-Conflict Mode?

// This will NOT work in WordPress by default
$(document).ready(function() {
    $('.button').click(function() {
        // $ is undefined
    });
});

// This WILL work
jQuery(document).ready(function() {
    jQuery('.button').click(function() {
        // jQuery is available
    });
});

Common No-Conflict Patterns

// Pattern 1: IIFE with $ parameter
(function($) {
    'use strict';
    
    // $ is available inside this function
    $(document).ready(function() {
        $('.button').click(function() {
            console.log('Button clicked!');
        });
    });
    
})(jQuery);

// Pattern 2: jQuery ready with $ parameter
jQuery(document).ready(function($) {
    // $ is available inside this function
    $('.button').click(function() {
        console.log('Button clicked!');
    });
});

// Pattern 3: Shorter syntax
jQuery(function($) {
    // $ is available inside this function
    $('.button').click(function() {
        console.log('Button clicked!');
    });
});

// Pattern 4: Using const/let
const $ = jQuery.noConflict();
$(document).ready(function() {
    // $ is now available
    $('.button').click(function() {
        console.log('Button clicked!');
    });
});

Properly Enqueuing jQuery

Using WordPress's jQuery

<?php
// functions.php

// Correct way - Use WordPress's jQuery
function mytheme_enqueue_scripts() {
    // jQuery is included as a dependency
    wp_enqueue_script(
        'mytheme-script',
        get_template_directory_uri() . '/assets/js/main.js',
        array('jquery'), // jQuery dependency
        '1.0.0',
        true
    );
}
add_action('wp_enqueue_scripts', 'mytheme_enqueue_scripts');

// Using specific jQuery UI components
function mytheme_enqueue_jquery_ui() {
    // Enqueue jQuery UI components
    wp_enqueue_script('jquery-ui-core');
    wp_enqueue_script('jquery-ui-datepicker');
    wp_enqueue_script('jquery-ui-accordion');
    wp_enqueue_script('jquery-ui-tabs');
    wp_enqueue_script('jquery-ui-sortable');
    
    // Enqueue jQuery UI CSS
    wp_enqueue_style(
        'jquery-ui',
        'https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css',
        array(),
        '1.12.1'
    );
}
add_action('wp_enqueue_scripts', 'mytheme_enqueue_jquery_ui');

// Replace WordPress jQuery with custom version (NOT RECOMMENDED)
function mytheme_replace_jquery() {
    if (!is_admin()) {
        wp_deregister_script('jquery');
        wp_register_script(
            'jquery',
            'https://code.jquery.com/jquery-3.6.0.min.js',
            array(),
            '3.6.0',
            true
        );
        wp_enqueue_script('jquery');
    }
}
// add_action('wp_enqueue_scripts', 'mytheme_replace_jquery'); // Uncomment only if necessary
Replacing WordPress's jQuery with a custom version can break plugins and themes that depend on the bundled version. Only do this if absolutely necessary and test thoroughly.

jQuery DOM Manipulation in WordPress

Common jQuery Operations

(function($) {
    'use strict';
    
    $(document).ready(function() {
        
        // Selecting elements
        const $header = $('.site-header');
        const $navigation = $('#site-navigation');
        const $buttons = $('button.primary');
        
        // Class manipulation
        $header.addClass('is-sticky');
        $navigation.removeClass('hidden');
        $buttons.toggleClass('active');
        
        // Attribute manipulation
        $('img').attr('loading', 'lazy');
        $('a[target="_blank"]').attr('rel', 'noopener noreferrer');
        $('.form-input').prop('disabled', false);
        
        // Content manipulation
        $('.site-title').text('New Title');
        $('.content').html('<p>New content</p>');
        $('.input').val('New value');
        
        // DOM traversal
        $('.menu-item').parent().addClass('has-children');
        $('.widget').children('h3').addClass('widget-title');
        $('.current').siblings().removeClass('active');
        $('.post').find('.entry-title').addClass('highlighted');
        
        // Creating elements
        const $newButton = $('<button>', {
            class: 'btn btn-primary',
            text: 'Click me',
            click: function() {
                alert('Button clicked!');
            }
        });
        
        // Inserting elements
        $('.content').append($newButton);
        $('.header').prepend('<div class="announcement">Welcome!</div>');
        $('.sidebar').after('<aside class="secondary"></aside>');
        $('.footer').before('<section class="cta"></section>');
        
        // Removing elements
        $('.temporary').remove();
        $('.content').empty();
        $('.wrapper').unwrap();
        
        // Chaining
        $('.post')
            .find('.entry-content')
            .addClass('formatted')
            .find('img')
            .wrap('<figure class="image-wrapper"></figure>')
            .parent()
            .append('<figcaption>Image caption</figcaption>');
    });
    
})(jQuery);

jQuery Event Handling

Event Binding and Delegation

(function($) {
    'use strict';
    
    $(document).ready(function() {
        
        // Direct event binding
        $('.button').click(function(e) {
            e.preventDefault();
            console.log('Button clicked');
        });
        
        // Multiple events
        $('.input').on('focus blur', function(e) {
            $(this).toggleClass('active');
        });
        
        // Event delegation (for dynamic content)
        $(document).on('click', '.dynamic-button', function(e) {
            e.preventDefault();
            console.log('Dynamic button clicked');
        });
        
        // Custom events
        $('.widget').on('widget:refresh', function() {
            $(this).find('.content').load('/api/widget-content');
        });
        
        // Trigger custom event
        $('.widget').trigger('widget:refresh');
        
        // Common WordPress events
        
        // Mobile menu toggle
        $('.menu-toggle').on('click', function() {
            const $menu = $('.nav-menu');
            const isOpen = $menu.hasClass('is-open');
            
            $menu.toggleClass('is-open');
            $(this).attr('aria-expanded', !isOpen);
            
            // Trigger custom event
            $(document).trigger('menu:toggled', [!isOpen]);
        });
        
        // Smooth scrolling
        $('a[href^="#"]').on('click', function(e) {
            const hash = this.hash;
            
            if (hash) {
                e.preventDefault();
                
                $('html, body').animate({
                    scrollTop: $(hash).offset().top - 100
                }, 800, function() {
                    window.location.hash = hash;
                });
            }
        });
        
        // Form validation
        $('#contact-form').on('submit', function(e) {
            const $form = $(this);
            const $errors = $form.find('.error');
            
            // Clear previous errors
            $errors.removeClass('error');
            
            // Validate required fields
            $form.find('[required]').each(function() {
                if (!$(this).val()) {
                    $(this).addClass('error');
                    e.preventDefault();
                }
            });
            
            // Validate email
            const $email = $form.find('input[type="email"]');
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            
            if ($email.length && !emailRegex.test($email.val())) {
                $email.addClass('error');
                e.preventDefault();
            }
        });
        
        // Infinite scroll
        let loading = false;
        $(window).on('scroll', function() {
            if (loading) return;
            
            const scrollHeight = $(document).height();
            const scrollPosition = $(window).height() + $(window).scrollTop();
            
            if ((scrollHeight - scrollPosition) < 200) {
                loading = true;
                loadMorePosts();
            }
        });
        
        // Debounced resize
        let resizeTimer;
        $(window).on('resize', function() {
            clearTimeout(resizeTimer);
            resizeTimer = setTimeout(function() {
                // Handle resize
                adjustLayout();
            }, 250);
        });
        
        // Keyboard navigation
        $(document).on('keydown', function(e) {
            // Escape key
            if (e.keyCode === 27) {
                $('.modal').removeClass('is-open');
                $('.dropdown').removeClass('is-open');
            }
            
            // Arrow keys for gallery
            if ($('.gallery').hasClass('is-open')) {
                if (e.keyCode === 37) { // Left arrow
                    navigateGallery('prev');
                } else if (e.keyCode === 39) { // Right arrow
                    navigateGallery('next');
                }
            }
        });
    });
    
})(jQuery);

jQuery AJAX in WordPress

Complete jQuery AJAX Implementation

(function($) {
    'use strict';
    
    $(document).ready(function() {
        
        // Load more posts with AJAX
        $('#load-more').on('click', function() {
            const $button = $(this);
            const page = $button.data('page') || 1;
            const category = $button.data('category');
            
            // Disable button and show loading
            $button.prop('disabled', true).text('Loading...');
            
            $.ajax({
                url: wp_ajax.ajax_url,
                type: 'POST',
                dataType: 'json',
                data: {
                    action: 'load_more_posts',
                    nonce: wp_ajax.nonce,
                    page: page + 1,
                    category: category
                },
                success: function(response) {
                    if (response.success) {
                        // Append new posts
                        $('#posts-container').append(response.data.html);
                        
                        // Update page number
                        $button.data('page', page + 1);
                        
                        // Check if more posts available
                        if (page + 1 >= response.data.max_pages) {
                            $button.hide();
                        } else {
                            $button.prop('disabled', false).text('Load More');
                        }
                        
                        // Trigger event for other scripts
                        $(document).trigger('posts:loaded', [response.data]);
                    } else {
                        alert('Error: ' + response.data);
                    }
                },
                error: function(xhr, status, error) {
                    console.error('AJAX Error:', error);
                    $button.prop('disabled', false).text('Try Again');
                },
                complete: function() {
                    // Always executed
                }
            });
        });
        
        // Like post functionality
        $('.like-button').on('click', function() {
            const $button = $(this);
            const postId = $button.data('post-id');
            const $count = $button.find('.like-count');
            
            // Optimistic UI update
            $button.toggleClass('liked');
            
            $.post(wp_ajax.ajax_url, {
                action: 'like_post',
                nonce: wp_ajax.nonce,
                post_id: postId
            })
            .done(function(response) {
                if (response.success) {
                    $count.text(response.data.likes);
                } else {
                    // Revert on error
                    $button.toggleClass('liked');
                    alert(response.data.message);
                }
            })
            .fail(function() {
                // Revert on error
                $button.toggleClass('liked');
                alert('An error occurred');
            });
        });
        
        // Live search
        let searchTimer;
        $('#search-input').on('keyup', function() {
            const query = $(this).val();
            const $results = $('#search-results');
            
            // Clear previous timer
            clearTimeout(searchTimer);
            
            if (query.length < 3) {
                $results.empty().hide();
                return;
            }
            
            // Debounce search
            searchTimer = setTimeout(function() {
                $.ajax({
                    url: wp_ajax.ajax_url,
                    type: 'GET',
                    data: {
                        action: 'live_search',
                        nonce: wp_ajax.nonce,
                        query: query
                    },
                    beforeSend: function() {
                        $results.html('<div class="loading">Searching...</div>').show();
                    },
                    success: function(response) {
                        if (response.success && response.data.results.length > 0) {
                            let html = '<ul class="search-results-list">';
                            
                            $.each(response.data.results, function(index, item) {
                                html += `<li>
                                    <a href="${item.url}">
                                        <h4>${item.title}</h4>
                                        <p>${item.excerpt}</p>
                                    </a>
                                </li>`;
                            });
                            
                            html += '</ul>';
                            $results.html(html);
                        } else {
                            $results.html('<p>No results found</p>');
                        }
                    },
                    error: function() {
                        $results.html('<p>Search error</p>');
                    }
                });
            }, 300);
        });
        
        // File upload with progress
        $('#file-upload').on('change', function() {
            const file = this.files[0];
            if (!file) return;
            
            const formData = new FormData();
            formData.append('action', 'upload_file');
            formData.append('nonce', wp_ajax.nonce);
            formData.append('file', file);
            
            $.ajax({
                url: wp_ajax.ajax_url,
                type: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                xhr: function() {
                    const xhr = new XMLHttpRequest();
                    
                    // Upload progress
                    xhr.upload.addEventListener('progress', function(e) {
                        if (e.lengthComputable) {
                            const percentComplete = (e.loaded / e.total) * 100;
                            $('#upload-progress').css('width', percentComplete + '%');
                        }
                    }, false);
                    
                    return xhr;
                },
                success: function(response) {
                    if (response.success) {
                        $('#upload-result').html(
                            `<img src="${response.data.url}" alt="Uploaded">`
                        );
                    }
                },
                error: function() {
                    alert('Upload failed');
                }
            });
        });
    });
    
})(jQuery);

Creating jQuery Plugins for WordPress

jQuery Plugin Pattern

(function($) {
    'use strict';
    
    // Plugin definition
    $.fn.wpAccordion = function(options) {
        
        // Default settings
        const settings = $.extend({
            speed: 300,
            closeOthers: true,
            activeClass: 'is-active',
            contentClass: 'accordion-content',
            triggerClass: 'accordion-trigger',
            startClosed: true,
            callback: null
        }, options);
        
        // Plugin logic for each element
        return this.each(function() {
            const $accordion = $(this);
            const $triggers = $accordion.find('.' + settings.triggerClass);
            const $contents = $accordion.find('.' + settings.contentClass);
            
            // Initialize
            if (settings.startClosed) {
                $contents.hide();
            } else {
                $contents.first().show();
                $triggers.first().addClass(settings.activeClass);
            }
            
            // Bind events
            $triggers.on('click', function(e) {
                e.preventDefault();
                
                const $trigger = $(this);
                const $content = $trigger.next('.' + settings.contentClass);
                const isOpen = $trigger.hasClass(settings.activeClass);
                
                if (settings.closeOthers && !isOpen) {
                    $triggers.removeClass(settings.activeClass);
                    $contents.slideUp(settings.speed);
                }
                
                $trigger.toggleClass(settings.activeClass);
                $content.slideToggle(settings.speed, function() {
                    // Callback after animation
                    if (typeof settings.callback === 'function') {
                        settings.callback.call(this, $trigger, $content);
                    }
                });
            });
            
            // Public methods
            $accordion.data('wpAccordion', {
                open: function(index) {
                    $triggers.eq(index).trigger('click');
                },
                close: function(index) {
                    const $trigger = $triggers.eq(index);
                    if ($trigger.hasClass(settings.activeClass)) {
                        $trigger.trigger('click');
                    }
                },
                closeAll: function() {
                    $triggers.removeClass(settings.activeClass);
                    $contents.slideUp(settings.speed);
                },
                openAll: function() {
                    $triggers.addClass(settings.activeClass);
                    $contents.slideDown(settings.speed);
                },
                destroy: function() {
                    $triggers.off('click');
                    $accordion.removeData('wpAccordion');
                }
            });
        });
    };
    
    // Auto-initialize
    $(document).ready(function() {
        $('.wp-accordion').wpAccordion();
    });
    
    // Another plugin example: Tabs
    $.fn.wpTabs = function(options) {
        const settings = $.extend({
            activeClass: 'is-active',
            animation: 'fade', // fade, slide, none
            speed: 300,
            responsive: true,
            breakpoint: 768
        }, options);
        
        return this.each(function() {
            const $tabs = $(this);
            const $nav = $tabs.find('.tabs-nav');
            const $navItems = $nav.find('a');
            const $panels = $tabs.find('.tab-panel');
            
            // Initialize
            $panels.hide();
            $panels.first().show();
            $navItems.first().addClass(settings.activeClass);
            
            // Tab switching
            $navItems.on('click', function(e) {
                e.preventDefault();
                
                const $link = $(this);
                const target = $link.attr('href');
                const $target = $(target);
                
                if ($link.hasClass(settings.activeClass)) return;
                
                $navItems.removeClass(settings.activeClass);
                $link.addClass(settings.activeClass);
                
                if (settings.animation === 'fade') {
                    $panels.fadeOut(settings.speed);
                    $target.fadeIn(settings.speed);
                } else if (settings.animation === 'slide') {
                    $panels.slideUp(settings.speed);
                    $target.slideDown(settings.speed);
                } else {
                    $panels.hide();
                    $target.show();
                }
            });
            
            // Responsive behavior
            if (settings.responsive) {
                $(window).on('resize', function() {
                    if ($(window).width() <= settings.breakpoint) {
                        $tabs.addClass('tabs-accordion');
                    } else {
                        $tabs.removeClass('tabs-accordion');
                    }
                }).trigger('resize');
            }
        });
    };
    
})(jQuery);

Using the Plugins

// Initialize with options
$('.my-accordion').wpAccordion({
    speed: 500,
    closeOthers: false,
    callback: function($trigger, $content) {
        console.log('Accordion toggled');
    }
});

// Access plugin methods
const accordion = $('.my-accordion').data('wpAccordion');
accordion.open(2); // Open third item
accordion.closeAll(); // Close all items

// Initialize tabs
$('.my-tabs').wpTabs({
    animation: 'slide',
    speed: 400,
    breakpoint: 640
});

Migrating from jQuery to Vanilla JavaScript

jQuery Vanilla JavaScript
$(selector) document.querySelector(selector)
$(selector).each() document.querySelectorAll(selector).forEach()
$(el).addClass('class') el.classList.add('class')
$(el).removeClass('class') el.classList.remove('class')
$(el).toggleClass('class') el.classList.toggle('class')
$(el).hasClass('class') el.classList.contains('class')
$(el).attr('attr', 'value') el.setAttribute('attr', 'value')
$(el).text('text') el.textContent = 'text'
$(el).html('html') el.innerHTML = 'html'
$(el).on('click', fn) el.addEventListener('click', fn)
$(el).show() el.style.display = 'block'
$(el).hide() el.style.display = 'none'
$.ajax() fetch()
$(document).ready() DOMContentLoaded event

Migration Example

// jQuery Version
$(document).ready(function() {
    $('.button').on('click', function() {
        $(this).toggleClass('active');
        $('.content').slideToggle();
    });
    
    $.ajax({
        url: '/api/data',
        method: 'GET',
        success: function(data) {
            $('#result').html(data);
        }
    });
});

// Vanilla JavaScript Version
document.addEventListener('DOMContentLoaded', function() {
    const buttons = document.querySelectorAll('.button');
    
    buttons.forEach(button => {
        button.addEventListener('click', function() {
            this.classList.toggle('active');
            
            const content = document.querySelector('.content');
            // For slide toggle, use CSS transitions
            content.classList.toggle('is-visible');
        });
    });
    
    fetch('/api/data')
        .then(response => response.text())
        .then(data => {
            document.getElementById('result').innerHTML = data;
        })
        .catch(error => console.error('Error:', error));
});

Common jQuery Issues in WordPress

Troubleshooting jQuery Problems

// Issue 1: $ is not defined
// Solution: Use proper no-conflict wrapper
(function($) {
    // Your code here
})(jQuery);

// Issue 2: Code runs before DOM is ready
// Solution: Use document ready
jQuery(document).ready(function($) {
    // DOM is ready
});

// Issue 3: Events not working on dynamic content
// Solution: Use event delegation
$(document).on('click', '.dynamic-element', function() {
    // This works for elements added later
});

// Issue 4: jQuery version conflicts
// Solution: Check version and use appropriate methods
if (typeof jQuery !== 'undefined') {
    console.log('jQuery version:', jQuery.fn.jquery);
    
    // Check for specific version
    if (jQuery.fn.jquery.match(/^3\./)) {
        // jQuery 3.x specific code
    }
}

// Issue 5: Multiple jQuery versions loaded
// Solution: Use specific version namespace
const $j = jQuery.noConflict();
$j(document).ready(function() {
    // Use $j instead of $ or jQuery
});

// Debug helper
window.jQueryDebug = function() {
    console.log('jQuery loaded:', typeof jQuery !== 'undefined');
    console.log('$ defined:', typeof $ !== 'undefined');
    if (typeof jQuery !== 'undefined') {
        console.log('jQuery version:', jQuery.fn.jquery);
        console.log('jQuery plugins:', Object.keys(jQuery.fn).length);
    }
};
Use browser console to check if jQuery is loaded: console.log(jQuery.fn.jquery) will show the version if jQuery is available.

Best Practices

jQuery Best Practices in WordPress

  • Always use no-conflict mode: Wrap code in proper closure
  • Use WordPress's jQuery: Don't load external versions unless necessary
  • Cache jQuery objects: Store selections in variables
  • Chain methods: Take advantage of jQuery's chaining
  • Use event delegation: For dynamic content
  • Minimize DOM manipulation: Batch operations when possible
  • Check element existence: Before applying operations
  • Use data attributes: For storing data on elements
  • Consider vanilla JS: For simple operations
  • Test across versions: Ensure compatibility
jQuery may be removed from WordPress core in the future. Consider using vanilla JavaScript for new projects and gradually migrating existing jQuery code.

Practice Exercise

💻
Build jQuery Components

Create jQuery-powered theme features:

  1. Implement jQuery in no-conflict mode
  2. Create a custom accordion plugin
  3. Build AJAX comment submission
  4. Implement smooth scrolling navigation
  5. Create image gallery with lightbox
  6. Build form validation system
  7. Add infinite scroll functionality
  8. Create custom dropdown menus
  9. Implement sticky header on scroll
  10. Migrate one component to vanilla JS

Additional Resources