Coding a jQuery AJAX site that degrades gracefully
Posted on 03. Mar, 2010 by Guillermo in Development
Over the course of this tutorial I’ll show how to create a simple way (Ajax powered) to refresh the content of the pages while degrading gracefully when there’s no javascript. Added bonus: its “crawler friendly”.

The idea
The site we are building for this tutorial will have a normal navigation, consisting of plain old links to the different pages. If javascript is enabled it will replace the default action of these links (for example going to a new page) with a new behavior: load the content using Ajax and refreshing the page without going to a new URL.
We’ll use the jQuery load function. This function will fetch the new page. It has a very useful feature (essential in our case) that lets you load a page fragment and not the whole page as right now we’re only interested in the main content div.
The HTML
<ul id="menu"> <li><a href="index.html">Home</a></li> <li><a href="link2.html">Link 2</a></li> <li><a href="contact.html">Contact</a></li> </ul>
Here we have the main navigation. It doesn’t have anything special, we only need the id of the navigation element containing the links we want to add this special behavior
<div id="ajax-wrap"> <div id="text"> </div> </div>

Another important part is to create a div that wraps the content; this will be the div whose content will change when we move from page to page. Note we have two elements, the wrapper div and the actual element that has the content in it (in this case the div with the “text” id).
Also note that all the content pages are complete html pages by themselves. We load them with ajax and only use the content portion. If someone actually goes to the URL the link is pointing to, they should see a full page and not just the content portion. (This is key for the site to work without javascript, like a regular site).
Javascript
I’ve created a small javascript object that handles everything, here’s the code:
var AjaxContent = function(){
var container_div = '';
var content_div = '';
return {
getContent : function(url){
$(container_div).animate({opacity:0}, //Turn the opacity to 0
function(){ // the callback, loads the content with ajax
$(container_div).load(url+" "+content_div, //only loads the selected portion
function(){
$(container_div).animate({opacity:1}); //and finally bring back the opacity back to 1
}
);
});
},
ajaxify_links: function(elements){
$(elements).click(function(){
AjaxContent.getContent(this.href);
return false; //prevents the link from beign followed
});
},
init: function(params){ //sets the initial parameters
container_div = params.containerDiv;
content_div = params.contentDiv;
return this; //returns the object in order to make it chainable
}
}
}();
We include this script on the pages (along with jQuery) and we’re ready to rock and roll. I’ll explain it little by little.
When the page loads, we have to identify the links and assign them some new events. Here’s the part where we initialize and set up the special nav.
$(function(){
AjaxContent.init({containerDiv:"#ajax-wrap",contentDiv:"#text"}).ajaxify_links("#menu a");
});
This call initializes the AjaxContent object with the ids of the wrapper div and the content div (note the jquery [selector] notation). The second chained call tells which links we want to add this special behavior, in this case the links inside the element with the “menu” id (“#menu a”). This is it! It should work by now although let’s explain what’s going on inside.
ajaxify_links: function(elements){
$(elements).click(function(){
AjaxContent.getContent(this.href);
return false;
});
}
This is the method we called before. It selects all the elements passed using our jQuery selector string, in this case it takes all the links. For each one of them, it assigns a function to the click event. This function calls the getContent method and prevents the link from being followed by the browser (with the “return false” sentence).
getContent : function(url){
$(container_div).animate({opacity:0}, //first, turn the opacity to 0
function(){ //on the callback, load the content with ajax
$(container_div).load(url+" "+content_div,
function(){
$(container_div).animate({opacity:1}); //and finally bring back the opacity back to 1
}
);
});
}
This method fades out the container div. It then calls the load method and only retrieves the part that we want, in this case we’ve told the script to get the div with the “text” id (jQuery actually loads the whole HTML and then parses it, returning only the part we’ve asked for). When the content is loaded, it fades in the content again.
Conclusion
As you could see, thanks to the power of jQuery, this is really easy to do. We now have a fancy way to retrieve the content and keep the site looking like a normal non-ajax site to the ones who don’t use javascript. The site also remains both SEO friendly and fully accessible to the crawlers because the links are normal <a> elements with the href property.
Thanks to the guys on Solucija for the free template I’ve used for this example.
Happy coding!
Source files:
and Demo
Related posts:
Spanish





+54 261 4340244
Jeremy
Mar 29th, 2010
Hi,
This AJAX tool works great, and I love that it’s SEO friendly. Do you have any plans to make the back button work?
Thanks!
Jeremy
Guillermo
Mar 30th, 2010
Thanks Jeremy!
It’s true, back button doesn’t work with this approach. This could be solved appending a hashtag on the url every time that the content changes and then using that hashtag to reload the content again when the user clicks back or forward.
Right now adding that feature is not on the plans, the article was only to show how to load AJAX content in a crawler friendy way, but we’ll let you know if we make any changes
Brent
Jun 23rd, 2010
cool, I was seeing some other tutorials but none of them degraded gracefully. It’d be awesome if you could work in the back button fix using URL hash at some point, if you ever do please email me – brent@mimoymima.com
thanks!
b r e n t
Peter
Aug 1st, 2010
I am implementing your code to my web, everything work perfect besides Back button. The page url change on back button but content does not update. I was trying to plug in BBQ or history.js but nothing works. Could you help me with this.
Leon
Oct 26th, 2010
A couple of quick questions if you’re still around.
How can I specify more than one ID in ajaxify_links(“#nav a”)
Why might my jQuery Cycle plugin not work any more for pages loaded via your solution?
Thanx..
Guillermo
Dec 15th, 2010
Hi Leon! Sorry for the delay
The function ajaxify_links passes a string to select one (or more) elements using Jquery Selector’s format. In your case you can separate several ids with commas.
I don’t know how you’re using the cycle plugin. I’ll have to guess that you’re attaching the cycle pugin to some elements inside a “$(document).ready” call. When you load a new page with my solution, new content is inserted on the page. Elements inside this content don’t have the cycle event attached to them (because the plugin was called when this elements were not loaded). You can re-attach the cycle plugin each time you refresh the content.
Hope this answer your questions
Thanks!
modnarte
May 24th, 2011
Hi Guillermo,
I have a similar issue like Leon’s. My ImageFlow was working perfectly until I added your AJAX solution. I love the way your solution works on my pages but as a newbie, I need more information on re-attaching Imageflow. Can you be specific about the code I need and where to insert it?
This is the ImageFlow init code:
var domReady = function(handler) { domReadyEvent.add(handler); };
domReadyEvent.init();
domReady(function()
{
var instanceOne = new ImageFlow();
instanceOne.init({ ImageFlowID: ‘myImageFlow’ });
});
Thanks!
modnarte
May 30th, 2011
Hi, I have resolved the Imageflow issue, somewhat, by adding this to nav.js:
$.noConflict(true);
$(document).ready(function () {
/* $(“p”).text(“The DOM is now loaded and can be manipulated.”);*/
/* This will be called AFTER the Ajax thing has been done */
function initImageflow () {
// Destroy the ImageFlow Instance
instanceOne = null;
// Build a shiny new one
var instanceTwo = new ImageFlow();
// Initiate with your options
instanceTwo.init({ ImageFlowID:’myImageFlow’ }); }
});
Works in Safari, Chrome, Opera but Firefox seems to be flickering more than before adding your AJAX!
Any suggestions?
toohot
Sep 18th, 2011
Hi Guillermo! Good turtorial, all works fine. Could You help with Back button function please