It goes without saying that URLs are important. They’re the method of accessing the vast collections of information and resources on the web, and more recently, they’ve begun representing the intended state of a web application. You can copy these URLs and share them with your friends or use them to create links from any HTML document. They’re the veins of the web, and they need to be looked after.
// Check the length of the history stack console.log(history.length); // Send the user agent forward console.log(history.forward()); // Send the user agent back console.log(history.back()); // Send the user agent back (negative) or forward (positive) // by a given number of items console.log(history.go(-3));
With dynamic Ajax web applications, where the browser updates the page in parts instead of changing location entirely, it’s difficult to give the user a URL to bookmark or share the current application state. Fragment identifiers, like those used on this article’s headings via the
id attribute, provide some state information, but they’re entirely dependent on client-side scripts.
The changes to the History API are intended to give developers ways to push history items to the browser so the native back and forward actions can cycle through those items. These history items can also hold data that you can later extract to restore the page state.
Pages can add state objects between their entry in the session history and the next (“forward”) entry. These are then returned to the script when the user (or script) goes back in the history, thus enabling authors to use the “navigation” metaphor even in one-page applications.
If the user copies or bookmarks a stateful URL and visits it later, your back-end can be configured to interpret such a URL and jump the user right to the correct page and/or state.
In this article, I’ll cover the client-side use of the History API, so make sure you set up your server to work with the new URLs. If you’ve already built an accessible website that provide these entry points, you’re laughing!
This works as a method of creating a bookmarkable, shareable URL for a page’s state in the absense of a standard API. While the Twitter implementation accepts both
http://twitter.com/akamike, it has some disadvantages:
The hashbang was never intended to be a long-term solution, so don’t rely on it. If you do use hashbangs, be prepared to deal with the consequences (and possible backlash from web purists).
These examples will build on top of each other. We’ll start with a basic HTML document with some inline styles and scripts for your convenience.
Save this file and open it in your favourite editor. It must be accessed via HTTP, so that means you need either a local server (e.g.
http://localhost/) or an online web server you can upload to. Viewing the file directly using your browser’s Open File function will not work, since it uses the
file:// protocol and not HTTP. Also be sure to update the
href attributes on each of the navigation links to ensure the correct directory structure is used. Personally, I’m viewing the demo locally at
We’ll be working exclusively within the
<script> element at the end of the
<body>. The code includes some simple styles and dynamically changes the content as you click the links. In reality, this could be loaded from your server via
XMLHttpRequest, but for the purposes of this demonstration I’ve bundled it up into a self-contained file. The important part is that we have a quick-and-dirty dynamic page to work with, so let the fun begin!
At the moment there, is no bookmarkable URL for the different states of this page. If you click around the navigation items, then click Back in your browser, you won’t be taken back to the previous state and may even be taken away from the page to whatever you viewed before (depending on your browser). It would be nice if you could share “Socks” with your friends, right? We can do that via
history.pushState() method takes three parameters:
With these parameters, you can define the state of the page, give that state a name, and even provide a bookmarkable address, as if the page had reloaded entirely. Let’s dive right in and add this to the
clickHandler function, right above the
The single line of code we added informs the
history object that:
hrefattribute of that link.
Reload the page in your browser and click a few of the links, keeping an eye on the address bar. Notice how it changes on each click, despite the fact that you aren’t actually navigating away from this page. If you also have a look at your history log, you’ll see a long list of page titles (in this case ”Kittens!” over and over). Provided your server is set up to serve the correct page upon access, the user could copy that URL and paste it into a new browser window to jump straight to that kitten.
At the moment, clicking the back button will pop you through the history items, but the page won’t react to these changes. That’s because so far, we’ve only created the history records. How can we allow active users to return to a previous state? We listen to the
The user agent fires a
popstate event when the user navigates through their history, whether backwards or forwards, provided it isn’t taking the user away from the current page. That is, all those
pushStates we called will keep the user on the current page, so the
popstate event will fire for each history item they pop through.
Before the closing
</script> tag, add a new listener for the
We attach the event listener to the
window, which is responsible for firing the event, and pass this event into our handler. We log a message (so we can see when this event is firing), and then we update the content using the state we saved previously. The state is attached to the
event object via the
Open up the page fresh in your browser, click around like before, and then click back. As before, the URL in the address bar changes as you cycle through states, but now the content is also restored back to what it should be. Click forward, and the content is likewise correctly restored.
If you look at the developer console in Chrome when you load the page for the first time, you’ll see the
popstate event fired immediately, before you’ve even clicked a link. This is because Chrome considers the initial page load to be a change in state, and so it fires the event. In this instance, the
state property is
null, but thankfully the
updateContent function deals with this. Keep this in mind when developing as it could catch you out, especially if other browsers assume this behavior.
Unfortunately, as fantastic as HTML5 is, it doesn’t allow us actual time travel. If it did, I would be going back to my childhood and telling a younger me, “Yes, you should have a slice of cake”. Take that as you will.
The History API does, however, allow us to make amends to our history log items. For example, we could update the current state in response to fresh user input in a form. We can do this with
replaceState works just as
pushState does, with the exact same parameters, except that it updates the current entry instead of adding a new one. I can think of one situation in our demo where this could be used: the initial page load. If you click back for long enough, you’ll find that going back to the original URL doesn’t provide you the original content. Let’s fix that by adding the following to the bottom of our script:
As this runs when the page loads, it saves the initial page state. We can later load this state when the user browses back to this point via the event listener we set up previously. You can try it out by loading up the page, clicking a few links, and then hitting back until you return to the original URL. The initial content has returned!
I’ve set up a demo of our completed code. I’ve also added a little back-end magic to make our
history.pushState URLs work like a real site. Remember that the URLs you push should be live URLs that the user can bookmark and share as real entry points to your site.
Up-to-date copies of Chrome (5+), Safari (5.0+), Firefox (4.0+), and Opera (11.50+) have support for the new History API. Even some mobile browsers work just fine, like Mobile Safari on iOS 4+. Unfortunately, IE 9 and below lack support, but it should work in IE 10 when it arrives.
Safari 5.0 sometimes exhibits one oddity: navigating between states causes the loading spinner to appear and stay even when the state has been loaded. This stops when you navigate away using a link or action that does not involve a state saved by the History API.
A polyfill does exist for the History API. The aptly named History.js uses HTML4’s
hashchange event with document fragment identifiers to mimic the history API in older browsers. If one of the hash URLs is used by a modern browser, it uses
replaceState to quietly correct the URL.
It sounds like magic, but make sure you’re aware of the consequences of using fragment identifiers, as mentioned previously in this article. As such, the author of History.js has put together a guide titled Intelligent State Handling.