Using jQuery in WordPress: techniques for theme authors

Using jQuery in WordPress: techniques for theme authors

jQuery techniques for working with WordPress themes.

There are many tutorials on jQuery and WordPress but most of them simply explains how to insert jQuery into WordPress. This tutorial, instead, will cover some interesting use cases of jQuery in WordPress.

Summary

Context

Our jQuery code should be executed only when it's necessary and only if certain conditions are met.

For that reason it's important to create a context for our code. This context is related to specific sections of a WordPress site and directly influenced by the presence or absence of specific elements.

Where am I?

As many of you already know, WordPress helps developers with styling their themes by adding several classes to the body element.

Such classes reflect the location of the current section you're visiting. So the home page will have a home class, a page the page class, a single post the single class and so on.

The major problem of using jQuery in WordPress is to establish a context where execute our code. We usually select specific elements and apply actions and routines to them.

However, such elements may be present only in a particular section of a WordPress site and not in another. So the need to create a context is crucial.

We can use the aforementioned CSS classes to narrow down our selection and invoke our code only when it's really needed.

For example, if we want to add our social sharing buttons via jQuery only on single posts ( and not on a category or archive section ), we can write the following code:

if( $( "body" ).hasClass( "single" ) ) {
    // Insert social sharing buttons
}

The use of WordPress classes is often overlooked by theme authors. The result is a massive execution of code even when it's not necessary.

Selecting elements

We work on DOM elements: that's a fact. As said earlier, not all elements are always present in all sections.

For that reason, another key aspect of creating a context for our jQuery code is to select elements that are actually present in the DOM structure of a page.

Elements fall into two main groups: singular elements and sets of elements.

For singular elements we can simply test their length property:

if( $( "#slider" ).length ) {
    $( "#slider" ).flexslider();
}

Sets of elements can be either tested via the length property or selected with an .each() loop so that jQuery won't raise an error if they don't exist:

$( ".slides" ).each(function() {
    // Do something
});

Again, this practice is not always used by theme authors, thus resulting in a redundant and error-prone jQuery code.

Performance and efficient element selection

Selecting elements is not enough: element selection must be as fast as possible.

For that reason, we can take advance of the second parameter of the $() jQuery wrapper, namely the context parameter. When you use this parameter, jQuery will search elements starting from the element provided as the second parameter. jQuery uses document as the default second parameter.

$( ".slides", "#slider" ).each(function() {
    // Do something
});

Other effective ways for selecting elements are provided by jQuery through the .filter() and .children() methods.

The .filter() method also implements contextual selection. Therefore $( "#slider" ).find( ".slides" ) is equivalent to $( ".slides", "#slider" ).

For an even faster selection, you can simply use native DOM methods and then pass the selected elements to the jQuery's $() wrapper which turns them into jQuery objects:

var slides = document.querySelectorAll( "#slider .slides" );
$( slides ).each(function() {
    // Do something
});

DOM manipulation

A significant amount of the time in WordPress is spent with DOM manipulation.

DOM manipulation is useful but not always necessary. For example, when we want to implement an overlay effect we usually need to dynamically inject a DOM structure at the end of the document tree (due to CSS stacking).

Other times we need to fix some layout bugs caused by WordPress, such as the empty paragraphs added by the autopee filter:

$( "p:empty", ".post" ).remove();

The jQuery's website provides an introductory tutorial to this topic here.

Attributes

A significant percentage of DOM manipulation is related to changing HTML attributes on the fly.

Using classes is a very common technique, especially when we're dealing with CSS animations and transitions:

$( "#menu" ).toggleClass( "visible" );

For all the other HTML attributes jQuery provides us with the .attr() method. This method can be used either to read the value of an attribute or to set one or more attributes on a given element.

Here's a simple implementation of a technique used to apply the invalid target attribute to external links:

$( "a[href^='http']" ).each(function() {
    var $a = $( this ),
        href = $a.attr( "href" ),
        currentDomain = "http://" + location.host;
        if( href.indexOf( currentDomain ) == -1 ) {
             $a.attr( "target", "_blank" );
        }
});

As you can see, this method is used here to set an attribute and its value. The counterpart of the .attr() method is .removeAttr() which removes an attribute completely.

For example, if you want to completely reset the font size used in a WordPress tag cloud, you can write the following code:

$( "a", ".wp-tag-cloud" ).removeAttr( "style" );

The tricky part with the .attr() method is to check whether an attribute exists. As of jQuery 1.6, this method returns undefined for attributes that have not been set. So we can test their existence with a single if condition:

if( !$( "img.cover" ).attr( "alt" ) ) {
    // Do something
}

Instead, to retrieve and change DOM properties such as the checked, selected, or disabled state of form elements, use the .prop() method.

A classic use-case of this method is when you want to create a toggle using a checkbox:

var $check = $( "#choice" );
if( $check.prop( "checked" ) ) {
    // Do something
} else {
    // Do something else
}

If you're using WPML (or another WordPress multilingual plugin), you can use attributes to check the current language of a section:

var langAttr = $( "html" ).attr( "lang" );
if( langAttr ) {
    // WPML
    var langAttrParts = langAttr.split( "-" );
    var lang = langAttrParts[0];
    switch( lang ) {
        case "en":
            // Do something for the English language
        break;
        case "de":
            // Do something for the German language
        break;
        //…
        default:
           break;
     }
  }

Attributes can be very useful if we take full advantage of them.

Data attributes

HTML5 introduces data attributes which are a simple way to associate arbitrary data with elements in form of strings.

A typical example are slideshows: we can store additional information on each slide by means of data attributes:

<div class="slide" data-transition="fade" data-speed="1000"></div>

In this case transition and fade can be retrieved with jQuery via the .data() method:

$( ".slide", "#slider" ).each(function() {
    var $slide = $( this ),
        transition = $slide.data( "transition" ),
        speed = parseInt( $slide.data( "speed" ), 10 );
        // Do something
});

Note that the second value has been converted into a number because data attributes values are always stored as strings.

In WordPress you can use data attributes in more advanced ways, for example by associating JSON strings to elements. JSON strings usually store metadata associated to a post or a WordPress object (custom post type, taxonomy etc.) or even to user settings.

Here's a sample custom Loop used to create a slider by making use of custom post types and custom fields:

<?php 
$args = array( 'post_type' => 'slides', 'posts_per_page' => -1);
$loop = new WP_Query( $args );
if( $loop->have_posts() ):
?>
    <div id="slider">
        <?php while( $loop->have_posts() ): 
              $loop->the_post(); 
              $post_id = get_the_ID();
              $transition = get_post_meta( $post_id, 'transition', true );
              $speed = (int) get_post_meta( $post_id, 'speed', true );
              $img_pos_x = (int) get_post_meta( $post_id, 'coord_x', true );
              $img_pos_y = (int) get_post_meta( $post_id, 'coord_y', true );
               $data = array( 'transition' => $transition, 'speed' => $speed, 'x' => $img_pos_x, 'y' => $img_pos_y );
               $json_str = json_encode( $data );
        ?>
            <div class="slide" data-effects='<?php echo $json_str; ?>'></div>
  <?php endwhile; wp_reset_postdata(); ?>
  </div>
<?php endif; ?>

The JSON output could be as follows:

{"transition":"fade","speed":1000,"x":100,"y":50}

Then you can use the JSON string with jQuery:

function applyEffects( currentSlide ) {
    var effects = JSON.parse( currentSlide.data( "effects" ) );
    // Do something
}

As you can see, data attributes can be very handy in many different situations. But what if a browser doesn't support them? In this case you can treat them as normal HTML attributes by using the .attr() method.

Working with elements

jQuery allows us to create, delete, clone and more generally manipulate all the elements contained within the DOM tree.

We've already seen the .remove() method that completely removes an element from the document tree.

Inserting elements, instead, works in two ways:

  1. selecting an element and inserting the new element into the target element as an HTML string
  2. creating the new element as an HTML string and then inserting it into the target element.

The first method works as follows:

// Appends the element to the target element's content
$( "#target" ).append( "<p>Test</p>" );
// Prepends the element to the target element's content
$( "#target" ).prepend( "<p>Test</p>" );
// Inserts the element after the target element
$( "#target" ).after( "<p>Test</p>" );
// Inserts the element before the target element
$( "#target" ).before( "<p>Test</p>" );

The other method, instead, is as follows:

// Appends the element to the target element's content
$( "<p>Test</p>" ).appendTo( "#target" );
// Prepends the element to the target element's content
$( "<p>Test</p>" ).prependTo( "#target" );
// Inserts the element after the target element
$( "<p>Test</p>" ).insertAfter( "#target" );
// Inserts the element before the target element
$( "<p>Test</p>" ).insertBefore( "#target" );

From a pure performance perspective, however, a recommended way to handle complex DOM insertions is to use the innerHTML property or the jQuery's .html() counterpart. What is the best approach in this case?

Both approaches make use of HTML strings but the innerHTML property is considerably faster because in this case browsers use directly their core HTML parser which is blazing fast.

This can be quite surprising, but the old JavaScript and DOM school kicks in when it comes to performance:

var tableBody = document.querySelector( "#data tbody" );
var numRows = 5000;
var html = "";
for( var i = 0; i < numRows; ++i ) {
    var row = "<tr><td>Test</td><td>" + ( i + 1 ) + "</td></tr>";
    html += row;
}
tableBody.innerHTML = html;

DOM methods are faster than jQuery's methods. The reason behind this is that they're directly implemented in the native browser code whereas jQuery's methods are simply a simplified way to use these core methods.

Wrapping and cloning elements are another powerful feature offered by the jQuery's API. Cloning is implemented via the .clone() method:

var $copy = $( "#target" ).clone();
$( $copy ).insertBefore( "#another-target" );

After cloning an element you got a copy of the original element and you can place it in another position within the DOM tree.

Wrapping elements is achieved through three different yet similar methods:

  1. .wrap( element ): wraps an element with another element created as an HTML string
  2. .wrapAll( element ): wraps a set of elements with another element created as an HTML string
  3. .wrapInner( element ): wraps the text contents of an element with an element created as an HTML string.

For example, given the following element:

<div id="target"></div>

We can use .wrap() as follows:

$( "#target" ).wrap( "<div class='wrap'></div>" );

This results in the following new structure:

<div class="wrap">
    <div id="target"></div>
</div>

As said earlier, .wrapAll() works instead on a set of elements:

$( "p", "#target" ).wrapAll( "<div class='wrap'></div>" );

This results in the following new structure:

<div id="target">
    <div class="wrap">
        <p></p>
        <p></p>
        <p></p>
     </div>
</div>

The third method works on the text contents of a given element. For example, given the following element:

<div id="target">Test</div>

We can use .wrapInner() as follows:

$( "#target" ).wrapInner( "<p class='wrap'></p>" );

This results in the following new structure:

<div id="target">
    <p class="wrap">Test</p>
</div>

Turning the WordPress archive widget into an accordion

jQuery: turning the WordPress archive widget into an accordion

Talking to WordPress with AJAX

WordPress provides a specific API to work with AJAX. All AJAX requests are usually routed to the /wp-admin/admin-ajax.php file which processes the request and returns an output to the client.

In WordPress each request is an action. Therefore the action parameter must always be passed along with the other AJAX parameters. This special parameter tells WordPress to execute a function or a class method that actually processes the request.

function my_ajax_call() {
    $string_to_hash = $_GET['str'];
    echo md5( $string_to_hash );
    exit();
}
add_action( 'wp_ajax_my_call', 'my_ajax_call' );
add_action( 'wp_ajax_nopriv_my_call', 'my_ajax_call' );

We're simply passing a GET parameter with a string that will be encoded in MD5 and sent back to the client. Note that after echoing the output the function exits immediately (this is required in WordPress).

There are two prefixes that WordPress uses for AJAX actions:

  1. wp_ajax_: executed only for authenticated users
  2. wp_ajax_nopriv_: executed only for non-authenticated users

After these prefixes comes the name of the action which in our case is my_call.

In jQuery it's pretty easy to perform the AJAX action defined above:

var ajaxURL = "http://" + location.host + "/wp-admin/admin-ajax.php";
var data = {
    action: "my_call",
    str: "Test"
};

$.get( ajaxURL, data, function( str ) {
    console.log( str );
});

You can also use jQuery's Deferred objects for this task:

$.when( $.get( ajaxURL, data ) ).done(function( str ) {
    console.log( str );
});

Let's see a couple of examples where AJAX makes the difference in WordPress.

Creating a rating system for WordPress posts

Creating a rating system simply means using WordPress custom fields together with an AJAX action that will store user's votes for our posts.

For example, we can rate a post as bad/good, boring/interesting and so on. Then we have to count how many times users have chosen an option or another.

WordPress: post rating system with AJAX and WordPress

Loading plugins on the fly

jQuery provides us with the $.getScript() method that can be used to load plugins without manually enqueueing them into WordPress.

function loadPlugin( name, callback ) {
    var scriptsURL = "http://" + location.host + "/wp-content/themes/mytheme/js/";
    var pluginURL = scriptsURL + name;
    $.getScript( pluginURL, callback );
}

Here's how we can use this solution:

loadPlugin( "flexslider.js", function() {
    $( "#slider" ).flexslider();
});

In this case the FlexSlider plugin is available when we run our callback function after the $.getScript() method has fetched and executed it in the local scope.

Conclusion

In this tutorial we've covered some key aspects of the use of jQuery in WordPress that can be further extended and developed in order to achieve advanced results in theme development.