Ajax Themes

ajax-feat

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 $15 dollar download. The javascript code is the same as posted in this tutorial- just conveniently bundled into a theme version.


(And thanks for supporting the site!)

47 Responses

      1. I am trying to load the ‘About Us’ page using ajax in my theme. The page actually uses your thematic plugin to get custom user data. Will it be possible to load the about us content frame using of_get_option(‘about_us_content’) function in this ajax script?

        It will really help if you can make your ajax twentyeleven theme example available for download.

  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?

    1. 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.

      1. Joshua Michaels

        This issue just came up for me while using WP Super Cache but relying on dynamic body classes to show/hide certain protected content based on user role, logged-in/logged-out and a few IE related fixes.

        For a couple other sections on my site I was using Ajaxize to load some custom wp_query loops to keep them updated with the newest posts. That plugin takes any function (even custom functions) and then adds an ajaxified wrapper to it as a div which you can then put into your template.

        But you can’t wrap the element with ajax as then the whole page is ajaxified and caching becomes irrelevant. In addition, I tried creating a custom function that groups all my body class filters together and ajaxizing them but then the normal WP body class filters are ignored (like post type, page id, etc). So, no bueno.

        This same issue came up with this plugin: Awebsome! Browser Selector. And then someone contributed this little ajax helper plugin that works with it: Awebsome Browser Selector for CACHING. This combo works beautifully at least for adding browser-based body classes.

  2. Kristof

    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!

    1. Kristof

      Oh and I just found a minor problem:
      - With on() everything works, except when I click a link in the content part.
      - With live() this problem doesnt happen.
      - With delegate() neither, but then it won’t work in IE.

      You don’t happen to know why on() causes problems when triggered inside the target div?

      1. Not really sure. Does it fail on the initial load, or only after you’ve loaded new content? Are you using the most recent version of jQuery?

  3. Chris

    Impressive guide to adding ajax navigation to any theme.
    However I did have to make some minor adjustments.
    Namely due to the fact that once it’s loaded once via ajax, the page you click won’t for the next, since all the links have returned to normal.
    So remember to change the code so that it adds the push state again.
    So the code that does that I turned that into a function instead and called it once on first load, then later inside load_site_ajax().

    Kudos to you Devin, it certainly was the easiest to implement out there that did what I wanted with this one client that wanted a custom WP theme.

    1. Yeah, I actually came across this the other day when working on a different site. All the click bindings can be added to a function and re-applied after an ajax request if you need it to apply to those new elements too. I’ll put it on the list to update…

    2. Can you share details on the adjustments you made so push state is always active? I’m working through this atm but haven’t quite got it right yet.

      @Devin – Looking forward to the updates :)

      Thanks

      1. Sorry, yes that’s what I meant. I’ve tried to follow the gist of what you and Chris talked about but haven’t found success in rebinding the clicks after an ajax request yet.

  4. emi

    hi! first of all thanks for the great post here. I’m testing your code and it works perfectly for any kind of post content but the problem comes when I load a page with javascripts (lightbox gallery or jw player), they don’t work.do you know the cause of this behavior?(I’m not much into ajax stuff..)
    thanks

    1. WordPress often includes scripts just on the pages/posts that need them. If you’re loading in content via ajax it won’t necessarily bring in the required scripts for items like that.

  5. dimaima

    hi! i thank you first the great post which helped me to implement limk change on adress bar while using ajax.
    I only have one remark, why the content on the page does’t change when clicking on “go back” and “go forward” buttons on web broweser.

    Again thanks.

  6. chrisrub

    This is a great tutorial. I have everything working except for the disqus commenting system. When I load a single post, it no longer loads Disqus. I found a reset function that they provide, but that did not work. Any insight that you can provide?

    Thanks!

    1. The Disqus scripts are likely only loaded on single posts. So if you start on an archive page, and then move to a single post, it hasn’t loaded the script it needs in the head. You can try to set it up so the Disqus scripts are loaded on every page (and use a callback to reinit), or have it try to smartly load the scripts when it needs them.

  7. Jessie

    I am having a lot of trouble with scripts working after the ajax is called – any jquery running on content that I am replacing, stops working, even if it was previously loaded on the page. For example, I am loading a new product into the content area, and the pinterest button that runs on a javascript stops working once the ajax is triggered. My slider stops working as well, and so does my add to cart button… any advice?

      1. Jessie

        I did look at the comments above, but they don’t make very much sense to me – how do i rebind multiple separate .js files ? Sorry for not knowing what that means.

  8. Has anyone had any success adding this to a underscores (_S) based theme? I added it to twentyeleven with no problem but couldn’t get it work with my theme which is based on _S or with an unmodified version of _S.

    I’m not sure what went wrong. I don’t get any console errors…

  9. Thought I’d share two issues I ran into that I was able to fix pretty easily.

    1) When I loaded a page my sidebar (#secondary) was showing up under the main content area (#primary).
    I updated line 32 of ajax_demo_init.js to
    $("#main").load(State.url + ' #primary ', function(data) {
    Works fine.
    2) Also I have a slider on the home page of the theme I’m using this in. If I went from the home page, to another page and back to the home page the slider wouldn’t initialize. So I re-initialized it in the callback that starts on line 34 and it worked again.

  10. Julio C

    Hi, is it possible to control which pages are ajaxfied? I need to omit 1 or 2 pages from AJAX. Noticed your first nav link (ajax demo) does not load the sidebar. I’m looking for this but to apply to any page. Thanks.

    1. It is possible to control which links will load via ajax (if they’re on the same domain). That’s near the top of the js file. Currently set to $(‘#access a’).

  11. Devin

    Hey there,

    Is there a way to make all the links in the theme load like this?

    I’ve tried playing with $(‘#access a’), but sometimes links will load through ajax, and sometimes they just refresh the page.

    1. Yes, re-read the bit under “Pushing the Title and URL”. It links to a snippet that shows you how to detect external versus internal links. You’ll want to use something like that rather than #access a.

  12. I’m not sure if anyone ran into this problem but I noticed it’s still present on the demo page where the ajaxified page load only occurs on one click instance after an actual page load. I solvedthat problem by changing $(‘#access a’).on(‘click’, function(e) .. to $(document).on(‘click’,’#access a’, function(e)…

  13. Stephen

    Hi, how does go about getting the js to load in after the ajax is called. I can’t seem to get my slider to work on one of the pages when its loaded via ajax. Thanks

    1. Look for the comment “/* After the content loads you can make additional callbacks*/”. This is where you would make a callback, like initing a slider.

Leave a Reply

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>