Ajax Themes

Ajax is a great method for loading new content onto a web page without having to do an entire page refresh. It’s sometimes used in WordPress themes for paging, loading in full content after an excerpt, or dynamically displaying new comments as they are posted.

Ajax can also be used to load more significant amounts of content, but it gets more complicated. URLs don’t update by default, which is important if someone wants to bookmark that particular post or share it. Browser navigation buttons for “Back” and “Forward” can also be an issue.

Thankfully there are HTML5 methods for dealing with browser history and states, and an awesome jquery plugin that provides some fallback for older browsers. This tutorial will walk you through the code required for a theme that makes extensive use of ajax and also point you to some live demos.

When to Use Ajax

Sites that use ajax well generally have a good reason for it. One of my favorite ajax sites is Designers MX, which hosts music mixes. By using ajax the site is able to keep the music playing uninterrupted while users navigate through the site.

When animations could be interrupted by a fresh page load, ajax is also extremely helpful. I developed a site for Byron Reece that features a large slider at the top for selecting posts. If the user had to load a new page after clicking on an item on the slider the site interaction would not have worked.

Sites that have heavy initial page weight but then just need to load in small snippets can benefit enormously from ajax. An example might be a gallery site that displays a huge amount of thumbnails initially, but then just needs a single request for a larger image when the user selects it.

How URL Pushstates Work

You’ll notice on the Byron Reece site that when you click on an a different item in the slider the URL updates even though a fresh page load is not happening. In modern browsers that support HTML5 push states, this url looks like any other you would normally use on the web. In legacy browsers, like IE8, it reverts to the hash url structure (#!).

Most of this URL magic is handled by history.js. All you do is send a title and url for the new browser state when the ajax event happens.

A Simple Example

Ajax Demo Theme

I coded out a much simpler example of an AJAX driven site using the TwentyEleven theme as a base for an example. Any links clicked in the top navigation menu will load the new content from that link into the #primary and #secondary divs.

I wouldn’t suggest building a site like this, as you don’t get a whole lot of upside for using ajax here, but it works well for demonstration purposes.

Enqueue the Required Javascript

For the pushstates to work as I used them, you’ll need to load history.js. There is a compressed version for use with jquery that can be downloaded here. I also load another javascript file that contains the custom code, which I saved in ajax_demo_init.js.

/*
 * Loads the scripts that are required for push_states to work.
 *
 * https://github.com/balupton/History.js
 *
 */
function ajax_demo_init() {
	if ( !is_admin() ) {
		wp_deregister_script('historyjs');
		wp_register_script( 'historyjs', get_bloginfo( 'stylesheet_directory' ) . '/js/jquery.history.js', array( 'jquery' ), '1.7.1' );
		wp_enqueue_script( 'historyjs' );
		wp_register_script( 'ajax_demo_init', get_bloginfo( 'stylesheet_directory' ) . '/js/ajax_demo_init.js', array( 'historyjs' ), false, true );
		wp_enqueue_script( 'ajax_demo_init' );
	}
}
add_action( 'wp_enqueue_scripts','ajax_demo_init' );

ajax_demo_init

This is the custom javascript that enables history.js, tells the site which link targets to load via ajax, and pushes the new browser states. I’ll post it as a block here, and then explain in more detail below:

jQuery(document).ready(function($) {
	// Establish Variables
	var
		History = window.History, // Note: Using a capital H instead of a lower h
		State = History.getState(),
		$log = $('#log');
	// If the link goes to somewhere else within the same domain, trigger the pushstate
	$('#access a').on('click', function(e) {
		e.preventDefault();
		var path = $(this).attr('href');
		var title = $(this).text();
		History.pushState('ajax',title,path);
	});
	// Bind to state change
	// When the statechange happens, load the appropriate url via ajax
	History.Adapter.bind(window,'statechange',function() { // Note: Using statechange instead of popstate
		load_site_ajax();
	});
	// Load Ajax
	function load_site_ajax() {
		State = History.getState(); // Note: Using History.getState() instead of event.state
		// History.log('statechange:', State.data, State.title, State.url);
		//console.log(event);
		$("#primary").prepend('<div id="ajax-loader"><h4>Loading...</h4></div>');
		$("#ajax-loader").fadeIn();
		$('#site-description').fadeTo(200,0);
		$('#content').fadeTo(200,.3);
		$("#main").load(State.url + ' #primary, #secondary', function(data) {
			/* After the content loads you can make additional callbacks*/
			$('#site-description').text('Ajax loaded: ' + State.url);
			$('#site-description').fadeTo(200,1);
			$('#content').fadeTo(200,1);
			// Updates the menu
			var request = $(data);
			$('#access').replaceWith($('#access', request));
		});
	}
});

Pushing the Title and URL

This snippet tells the site which links should be loaded by ajax (‘#access a’). To fully ajaxify a site you’d probably want it to detect external vs internal links, and load all the internal ones with ajax.

$('#access a').live('click', function(e) {
	e.preventDefault();
	var path = $(this).attr('href');
	var title = $(this).text();
	History.pushState('ajax',title,path);
});

The path variable is what sets the new url. The title is what will be at the top of the browser window and show up when bookmarked.

You might need to be a little careful with the title. You’ll notice in my Ajax Demo the full title is there on a fresh page load, but on an ajax load it drops site name from the title. To get it to match perfectly you’d have to tweak the title variable slightly.

Binding the Listener on Statechange

This idea took a little while for me to understand. When someone clicks on a link that you want to load via ajax, you don’t actually trigger any of the ajax on that click function. Instead, you have the click trigger a statechange event and pass it the needed variables for url and title.

The real work is done in the statechange listener. Here’s a real simple example with all the console log events uncommented so you can see what’s going on:

// Bind to state change
// When the statechange happens, load the appropriate url via ajax
History.Adapter.bind(window,'statechange',function() { // Note: Using statechange instead of popstate
     State = History.getState(); // Note: Using History.getState() instead of event.state
     History.log('statechange:', State.data, State.title, State.url);
     console.log(event);
});

(The above doesn’t actually load content, it just posts to the browser console.)

In the Ajax Demo, a click on “Example One” will display the following in the browser console:

statechange: [
Object
, "Example One", "http://themes.wptheming.com/ajax-demo/example-one/"]
MouseEvent

You can see the event was triggered by a mouseclick, and that “Example One” is the new title, “http://themes.wptheming.com/ajax-demo/example-one” is the URL that will be loaded.

Any functions in the statechange listener will also be fired when someone clicks the “back” or “forward” buttons in their browser.

Loading in the New Content via Ajax

The final piece is to actually load in the new content using ajax. If you’re unfamiliar with how this works, I’d also read the jQuery documentation the load function. For the TwentyEleven ajax example, we replace the #primary and #secondary divs with content from the new URL. This all happens inside the load_site_ajax function:

$("#main").load(State.url + ' #primary, #secondary', function(data) {
     /* After the content loads you can make additional callbacks*/
});

If you look at the original code, you’ll also notice that a couple other things I do to let the user know that new content is being loaded.

As soon as the ajax link is clicked, a loader gif is shown:

$("#primary").prepend('<div id="ajax-loader"><h4>Loading...</h4></div>');
$("#ajax-loader").fadeIn();

I also dim the content, so that the user is aware that it will change shortly:

$('#content').fadeTo(200,.3);

Once the content has loaded, the ajax loader disappears because the new content doesn’t have the ajax loader in #primary (remember, it was prepended using javascript). So, all that’s left to do is undim the new content:

$('#content').fadeTo(200,1);

One final issue I had, is that the menu items weren’t highlighting correctly because WordPress applies certain classes to them depending on which page it has loaded. When you load new content into #primary or #secondary and change the URL, the menu will keep the same classes it had on the initial page load. One way around that is to also reload the menu:

// Updates the menu
var request = $(data);
$('#access').replaceWith($('#access', request));

A Note on Browser Compatibility

Benjamin (the creator of history.js) wrote a great post about supporting url states in legacy browsers. By legacy, I really mean Internet Explorer 9 and below (IE10 is slated to support it).

There’s a couple issues with older browsers, but I think the main one is bookmarking. If a user visits the home page in IE8, then loads a secondary page via ajax, the URL will look something like this:

http://themes.wptheming.com/ajax-demo/#example-one/?&_suid=132434056033304790466820008113

However, if you visit that URL directly- it will only load the home page, not the page you thought you bookmarked (http://themes.wptheming.com/ajax-demo/example-one).

I worked out a method for detecting a hash and loading the correct content after the initial load, but it doesn’t work in modern browsers- and sort of defeats the point of using ajax since it is now loading the initial page content and then new page content:

// For legacy browser support
// Only checks for hashes, so more robust support might be needed if you use anchors
if (window.location.hash) {
	load_site_ajax();
}

I’m sure someone has worked out a better way to do this! Please share it in the comments.

Additional Thing to Watch Out For

If you use social buttons like Google+ or Twitter, don’t expect them to automatically update with the new URL. I’ll write a follow-up post about how to get those working.

A lot of WordPress themes use body classes for styling. These won’t update on an ajax load.

Did I Miss Something?

If you see anywhere I can improve the code, please share it in the comments- especially about legacy browser support.

Download the AJAX Demo Code

If you’d like to get the Ajax Demo theme code, it’s available as a $5 download. The javascript code is the same as posted in this tutorial- just conveniently bundled into a theme version.

Purchase the “Ajax Demo” Theme for $5
(And thanks for supporting the site!)

About Devin

I'm a WordPress developer based in Austin, Texas. Follow my projects on GitHub, or more general WordPress ramblings as @devinsays on twitter.

13 comments on “Ajax Themes

  1. This is looking great – well done!

    Suggest using get_stylesheet_directory_uri() instead of get_bloginfo( ‘stylesheet_directory’ ) in your function demo_init() call.

    Is it possible to swap out and replace the body classes (generated by body_class() ) with each new post/page load?

    • Thanks, I’ll make that update.

      I tried swapping out the body classes using several different methods but was never able to make it work. One of the more hackish methods was to save the body classes into a hidden div inside #content, and then try to replace the page’s body classes after the ajax call using jquery- but no luck. I’d be interested to see if someone could make it work.

  2. Great stuff man! I’m currently working on an ajaxed wordpress site with soundcloud integration for a record label, and as I had little experience with AJAX I went through numerous approaches, rewriting and trying loads of stuff.

    I stumbled upon your post after finishing a “works okay in most browsers” /#/-scumbag-script, and decided to give your method a shot: Instant success, now it works cross browser, better than before, and code is sexy and simple.

    So thank you handsomely for this, and grats – this has been by far not only the best, but also simpliest and cleanest approach I could find in all the last weeks!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>