HTML5 introduced pushState to allow you to modify the browser's history....

If you have been reading our web development blog (which I sure hope you have), you may have noticed that we used some sexy CSS3 animations paired with AJAX to navigate between the different months and pages. In this blog I will not go into details of how we acheived this, although everything is available for you to copy from the script.max.js file. Recently I have done a small change on this blog, making it a more complete solution. As already mentioned in a previous blog on progressive enhancement, the blog works perfectly well on browsers which do not support JavaScript by using the query string and MVC3 routing to retrieve the content.

HTML5 pushState #1

In the example above, the MVC3 controller parses the route and knows that the first parameter after "blog" is the year and the second parameter is the month. This way, typing any URL directly will allow you to retrieve the blog entries for that particular year and month, whilst omitting these parameters will retrieve the current month's entries. So far, this is all old school and has been this way since when we launched the website. So if it's working fine without JavaScript, looking gorgeous with JavaScript; what more could I add to make this even better?

And that's where our new friend called pushState comes into the picture. Once again, if you have been reading our blog, you may have or have not noticed that when JavaScript was enabled, the URL was remaining the same. This might seem obvious because you are sending a request through JavaScript, however this small oversee means that you would be unable to save the URL as a favourite or send it to someone.

This resulted in the introduction of location.hash, which concatenates a hash at the end of the URL to represent the current page. This would be something like:

www.incredible-web.com/blog/#2013-3

This system brings with it an entire baggage of problems. Firstly, it is required that your MVC controller is parsing the URL and understanding what comes beyond the hash, meaning you will have to develop the routing mechanism twice. That sucks, but apart from this, you will have two URLs for the same page and hence the same content. This could be interpreted by Google as duplicate content (unless you are using a canonical URL) and your search engine rankings could be penalized. Needless to say, this also means that if you decide to change something in the URL, such as changing the year parameter to a two-digit (13  instead of 2013) you will have to do this change twice and also make sure that any permalinks are updated twice.

Now using HTML5 pushState, we resolve the above issues and we make it look even more awesome. Check it out, if you go to the blog (/blog) and click on a previous month, the URL will be updated immediately with the URL as  it would seem if JavaScript was disabled, hence clicking on APR will show you /blog/2013/4. This is done by pushing the URL of the retrieved content onto the history object, as follows:

window.history.pushState(null, "Incredible Web Blog - April", "/blog/2013/4"); 

The first parameter (null in our case) can be any serializable object, and can be read when we pop the state object (described later). The second parameter is the title and is not used by Chrome or Firefox yet, whilst the third parameter is obviously the new url. 

Apart from pushing onto the history stack, we will then need to read from the history stack. This is done using the window.onpopstate event. This event is fired everytime the history object changes, meaning that it will be fired on every browser back/forward as well as on loading the page the first time. In our case, we do not want to handle the event on the first page load, so we will create a boolean to exempt it. The resultant code is super-straightforward, as you can see below:

$(function () {
  $("ul.archive a").click(function (e) {
    window.history.pushState(null, $(this).attr("title"), $(this).attr("href"));
    e.preventDefault();
  });
  // set the default location.pathname
  var home = location.pathname;
  window.onpopstate = function (e) {
    // check if it's the first page load or through back/forward
    if (home != location.pathname) {
      updateBlogArchive($("ul.archive a[href='" + location.pathname + "']"));
      home = "";
    }
  };
});

The result is a smooth rendition of the blog navigation, which you should know already, considering you are regularly reading our blog. ;)