Use the HTML5 History API with jQuery

Today we will be showing you how to use the HTML5 History API on your website to enable browser history updating when we use AJAX requests

Many great websites these days make use of the HTML5 History API along with AJAX requests in order to enrich their user experience.

AJAX

AJAX is a method of sending requests to your web server, ultimately allowing you to update parts of your page without refreshing the entire web page, this can be very handy for websites which need to keep certain content on the page, whilst updating other areas. Examples of this are you seeing new comments instantly on your Facebook page or watching videos on YouTube while clicking the 'Load More' option to see what other suggested videos YouTube has for you - without AJAX these functions would require the page to completely refresh with the new data displayed, this can be a bit annoying if you're in the middle of reading what your friends are up to or in the middle of a video!

HTML5 History API

The only issue with AJAX requests is that there isn't really a way to make use of the back and forward buttons on the web browser, these requests do not update the browser history. This can make bookmarking a specific page on a website that makes heavy use of AJAX almost impossible! This is where the HTML5 History API comes in. Using the HTML5 History API we can make an AJAX request and then tell the browser to update its history, allowing the user to use the back and forward buttons and also bookmark the page they are on!

Using AJAX and the HTML5 History API we can create a rich experience for the end user, it can also speed up the loading times of each page because we are only taking specific parts of the content from the pages the user is trying to view.

Getting Started

We will go through everything step by step from the HTML page creation all the way to AJAXing the page and making it use the HTML5 History API whilst gracefully degrading it for browsers that do not support these functions.

Step 1

Start with a basic HTML page

<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <title>Demo Site</title>
  </head>
  <body>
    <div id="wrapper">
      <!-- our content is going here -->
    </div>
  </body>
</html>

Step 2

Add the necessary jQuery (you will see I'm loading jQuery from Google's Hosted Libraries) and and the HTML5 History script.

<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <title id="title">Demo Site</title>
  </head>
  <body>
    <div id="wrapper">
      <div id="main-content">
        <h1>
          Some Dynamic Content
        </h1>
        <p>
          This content will be replaced with AJAX
        </p>
      </div>
    </div>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
    <script src="html5histjq.js"></script>
  </body>
</html>

You could put the scripts inside the <head> element however I like to put them just before the ending </body> tag because it slightly increases page load times.

You will also notice that we have assigned an id to the <title> element, this is to help AJAX find it as I have difficulty in selecting just the <title> element without the use of an ID (the AJAX request can't seem to filter it out so we can use it).

We have also added some extra content inside of the <div id="wrapper"></div> element which we will replace with AJAX.

Step 3

Now we can start working on our html5histjq.js script.

As like with all scripts we need to tell jQuery to execute it while on page load, now while we could use something like this:

$(document).ready(function(){
  // our script
});

But we'll use the shorthand version:

$(function(){
  // our script
});

First we need to test if the browser supports the functions we are going to be using to update the history, if it doesn't then the script will not execute and nor will the website use its AJAX functionality - this is important so we don't get those annoying script error popups on older versions of various browsers.. ahem, Internet Explorer.

Step 4

Add this inside the function mentioned above:

function supportHtmlHist() {
  if (window.history && history.pushState) {
    h5hapi();
  }
  return !!(window.history && history.pushState);
};

You will notice above the h5hapi(); line, that is the name of our function which will control AJAX and the HTML5 History functionality for our website, you can rename this if you wish, but be sure to rename the below to match it.

Step 5

So now we define it on the next line below the above function:

function h5hapi(){
  // HTML5 and AJAX here
};

Step 6

Now inside of this h5hapi() function we need a variable which will hold all of the classes or ids we want to enable the HTML5 History and AJAX functionality on, write this inside of the h5hapi() function:

var ilinks = ".internal-link";

You can change the ilinks name if you wish however make sure to update the rest of the script that references it as you go. Remember to replace .internal-link for whatever selector(s) you want to use, to specify multiple selectors, seperate them with commas; for example:

var ilinks = ".internal-link, .news-link, #navigation li a";

Now we have to add some more variables for our script, we need to decide what elements we want to update on our page, so you can append a comma to the end of the previous line before the semi-colon, and on the next line define our element variable, make the above line look like this:

var ilinks     = "internal-link",
    dynamic    = "#main-content",
    dynamic_el = $(dynamic);

We do this as we will be referencing these multiple times as we write the script and this is a more optimised way of doing it.

Step 7

We will be defining the function which will update our site content using AJAX, so below the above var lines, write the following:

function updateContent(loc){
  // AJAX functionality
};

You will notice loc in the above function, that is an argument we will pass the href attribute of our clicked links to, it will allow us to assign more than just one link to be used in the function.

Step 8

Now inside of the updateContent() function we get to define our AJAX call, we do this by writing the following:

$.get(loc, function(data){
  dynamic_content = $(data).find(dynamic).children();
  dynamic_el.html(dynamic_content);
});

But we need to add a callback function for when the content has been updated, this is where we will update the browser title, so update the above so it looks like this:

$.get(loc, function(data){
  dynamic_content = $(data).find(dynamic).children();
  dynamic_el.html(dynamic_content);
}).done(function(data){
  document.title = $(data).filter('#title').text();
});

To explain the above line, we use the $.get function which we pass two arguments into, one is loc which we are passing the link of the clicked element in to (you will see how we do this later on). We then pass in a second function, which is what we want to do after $.get has obtained the information from the other page.

We then use the .done function to update the title of the page after it is finished processing the previous request (I personally find this works best, although there may be alternative ways of doing it).

Step 9

Now we need to set up a function which will handle passing information to the updateContent() function, this function will take the location of the href attribute on clicked links and pass it to updateContent(), we will tell this function to use the ilinks variable we defined before to 'watch' for clicks on any of those selectors.

Below the line above, on a new line write this:

function navigationClickHandler(ilinks){
  // click handler functionality
};

Step 10

Now we can write the function itself, write this inside of the navigationClickHandler() function above:

$(ilinks).on('click', function(e){
  e.preventDefault();
  history.pushState(null, this.title, this.href);
  updateContent(this.href);
});

We use preventDefault() to stop the web browser just refreshing and going to those links, history.pushState() tells the browser to put the previous page into the history stack. updateContent() is the function we defined earlier that takes the href attribute, as you can see, this is where we are passing that attribute to the function.

Step 11

We need to enable support for the browser back and forward buttons, currently as the script stands, they will cycle through the history stack but the page will not refresh to show the page the user wants, so they will only see the URL updating in their browsers URL bar. To fix this we write the following on a new line, after the previous script segment, we actually make use of another AJAX request for this too, so once again on a new line write the following:

$(window).on('popstate', function(e){
  e.preventDefault();
  updateContent(document.location);
};

Popstate is a function that detects when the browsers changed it's state based on the history stack, so we say whenever a user is in a different place on their history (clicking the back and forward buttons) to call our previously defined updateContent() function and update the page content.

Step 12

Almost there now, we need to call the navigationClickHandler() function and tell it to watch the elements we have assigned to the ilinks variable, so on the next line write the following:

navigationClickHandler(links);

Step 13

Finally we need to call the function that checks if the browser supports the HTML5 History API, if it does it will execute our h5hapi() function as we previously discussed. Do this by writing the following outside of the h5hapi() function, but still inside of the main $(function(){}); function:

supportHtmlHist();

Completion

So now your script should look like the following (you can copy and paste this, just make sure to update the ilinks and dynamic variables inside of the h5hapi() function):

$(function(){
  function supportHtmlHist(){
    if (window.history && history.pushState) {
      h5hapi();
    }
    return !!(window.history && history.pushState);
  };
  function h5hapi(){
    var ilinks     = ".internal-link",
        dynamic    = "#main-content",
        dynamic_el = $(dynamic);
    function updateContent(loc){
      $.get(loc, function(data){
        dynamic_content = $(data).find(dynamic).children();
        dynamic_el.html(dynamic_content);
      }).done(function(data){
        document.title = $(data).filter('#title').text();
      });
    };
    function navigationClickHandler(ilinks){
      $(ilinks).on('click', function(e){
        e.preventDefault();
        history.pushState(null, this.title, this.href);
        updateContent(this.href);
      });
    };
    $(window).on('popstate', function(e){
      e.preventDefault();
      updateContent(document.location);
    });
    navigationClickHandler(ilinks);
  };
  supportHtmlHist();
});