The Javascript history is one of the coolest new features in HTML5. It lets us push things onto the history stack without loading a new page, and without using weird, hacky hash-bangs (#!, like Twitter) to keep our content crawlable.
It’s crucial in the Fireside web app because you want your podcast to continue playing while you browse through other shows and lists. I could probably accomplish the same general idea with a frame, iframe, or with URL fragments (the part the URL following a hash). However, frames are a hack, and break things like command-clicking to open in a new window, and page fragments break the web (which I plan on writing more about later).
How to use the API
Using the API is relatively simple. I’m using jQuery, because I’m not a masochist. First, you want to bind an action to the links that you want to intercept and load with Javascript. I made a CSS class for this, so that I can quickly add this to any link, but, of course, you can bind this to any selector. We pass the event e
to our function, because we’ll be needing it. We will use .live()
instead of .ready()
because if we add more elements to the document later, we want them to also be bound to this function, and .ready()
will not do that.
$(".load_ajaxy").live('click', function(e) {
Next, we grab the href
attribute, and we check if the event has a metaKey being held down. If the user is holding down the Command key on a Mac or the Control key on Windows, we don’t want to execute any more code.
var thePath = $(this).attr("href");
if(!e.metaKey) {
We check to see if the browser supports the History API, and actually push the URL onto the stack. The URL can be relative or absolute. This API detection code is lifted from Mark Pilgrim’s now-defunct Dive into HTML5.
if(!!(window.history && history.pushState)) {
history.pushState(null, null, thePath);
}
Next, we stop the default action of the link (i.e., sending us to the page it links to), and perform an AJAX request to load the content from our server. I built a special PHP page that will parse the url, and return just the content area.
e.preventDefault();
$.ajax({
url: '/content/',
data: 'withPath='+thePath,
type: "post",
success: function(data) {
//load downloaded content and insert into document
$('.content').html(data);
}
});
You can also add fancy things like an indeterminate progress indicator or a semi-transparent cover to indicate to the user that the page is loading.
Complete Code
$(".load_ajaxy").live('click', function(e) {
var thePath = $(this).attr("href");
if(!e.metaKey) {
if(!!(window.history && history.pushState)) {
history.pushState(null, null, thePath);
}
e.preventDefault();
$.ajax({
url: '/content/',
data: 'withPath='+thePath,
type: "post",
success: function(data) {
//load downloaded content and insert into document
$('.content').html(data);
});
}
}
});
When the back button is clicked
The other component is what happens when the back button is clicked. We need to monitor the popState
event, so we bind a function to it.
$(window).bind("popstate", function() {
var thePath = (location.href).replace('http://domain.com','');
$.ajax({
url: '/content/',
data: 'withPath='+thePath,
type: "post",
success: function(data) {
//load downloaded content and insert into document
$('.content').html(data);
}
});
});
One small note: the popState
event gets fired in Chrome and Firefox (but not Safari) when the page first loads, so you have to deal with this. I use a bit that’s flipped that tells me that the page is fully loaded, which I check in the popState
function. You can also handle this by not loading content in the version of the page that is served, but rather loading it with javascript in this popState function.
Browser Support
The History API is supported in IE 10, Firefox 6.0+, Chrome 13.0+, Safari 5.0+, and Mobile Safari 5+. If the browser doesn’t support it, my implementation still stops the default action and performs the AJAX request, it just doesn’t push anything onto the history stack.