Over the past decade, web applications have evolved to make heavy use of JavaScript in order to dynamically load content into web pages. One of the big challenges that developers face when creating these applications is preserving an accurate and useful browser history. Without this many of a browsers most used features, like the back and forward buttons, will not work as expected.
Browsers traditionally create history entries when a new page is visited but with a lot of applications now using AJAX to dynamically load content this method of tracking the session history is no longer sufficient. The History API gives developers the ability to navigate and manipulate the history so that an accurate representation of the user’s browsing pattern can be stored.
Contents
In this first section you are going to learn about the methods used to navigate between history entries. Some of these methods mimic the functionality of the browser’s native controls.
All of the following methods and properties are part of the History
object that can be accessed using window.history
.
Note: The history interface is scoped to the current tab or frame so you can only navigate or manipulate the history associated with the current session. You cannot use the History API to access the global history for the browser as a whole.
The back() Method
Calling the back()
method will cause the browser to navigate back to the previous entry in the session history. This mimics the behaviour of the browsers native back button.
window.history.back();
The forward() Method
The forward()
method will cause the browser to navigate one place forward in the browser history.
window.history.forward();
The go(n) Method
The go(n)
method allows you to navigate back or forward n
number of places in the session history. To navigate backwards n
should be a negative number.
// Go back two entries. window.history.go(-2); // Go forward 3 entries. window.history.go(3);
The length Property
The history objects length
property tells you how many entries are in the session history. This can be useful when used in conjunction with the go()
method.
// Go back to the first page. // (Assuming the you are starting on the last page.) var moves = window.history.length - 1; window.history.go(-moves);
Manipulating The Session History
Now that you know how to navigate the browser history lets take a look at the methods used to create and manipulate history entries.
The pushState() Method
The pushState()
method is used to create a new history entry. This method has three parameters:
stateObj
– The state object is used to store data that is associated the new history entry. This could include the page title, a URL to load via AJAX or even the page content itself.title
– Thetitle
parameter should act as a description for the history entry.URL
– (optional) This is the URL that will be associated with the history entry. The browser won’t load this URL whenpushState()
is called, but will display it in the address bar. It’s worth noting that this URL may be loaded if the user decides to refresh the page or restarts the browser.
// Creates a new history entry. window.history.pushState(stateObj, title, URL);
The replaceState() Method
The replaceState()
method is similar to pushState()
in that it takes the same three parameters. However, rather than creating a new history entry, replaceState()
updates the current history entry. This can be useful if you want to add some data to your state object after pushState()
has been called.
// Updates the current history entry. window.history.replaceState(stateObj, title, URL);
Note: The pushState()
and replaceState()
methods will not cause a hashchange
event to be fired.
The popstate Event
The popstate
event is fired on window
when the active history entry changes. Most commonly when the browsers back or forward buttons are clicked (or a call to back()
, forward()
or go()
is executed).
The event passed into the listener callback contains a state
property that is used to retrieve the state object that is associated with the history entry.
window.addEventListener('popstate', function(event) { var state = event.state; });
It’s worth noting that calls to pushState()
and replaceState()
will not trigger a popstate
event.
Chrome and Safari will fire a popstate
event when the page loads but Firefox doesn’t.
The state Property
To retrieve the state object for the current history entry you can examine the state
property on the window.history
object. This is useful if you need to read the state object when a popstate
event has not been fired.
window.history.state;
Demo: Using the History API in The Wild
Now that you understand how the History API works I’m going to guide you through how to build a simple web application that makes use of the API.
In this section you are going to build an application that consists of a number of pages that are loaded from a JavaScript object. You will use the History API to create history entries for each of the pages as they are displayed.
See the Demo Download The Code Resources
Creating the HTML and CSS
To begin with you will need to create an HTML file that contains some navigation links, a <h1>
for the page title and a <div>
for the content.
The style.css
file used in this demo can be found in the code resources.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>History API Demo</title> <link rel="stylesheet" href="style.css"> </head> <body> <div id="page-wrapper"> <nav> <ul> <li><a href="index.html" class="load-content">Home</a></li> <li><a href="about.html" class="load-content">About</a></li> <li><a href="products.html" class="load-content">Products</a></li> <li><a href="contact.html" class="load-content">Contact</a></li> </ul> </nav> <h1 id="title"></h1> <div id="content"></div> </div> <script src="history.js"></script> </body> </html>
Setting Up Your JavaScript
Now that you’ve got your HTML and CSS setup you need to create a file that will contain your JavaScript code.
Create a new file called history.js
and add to it the following code.
window.onload = function() { // Content for the pages. // Note: You would probably want to load the page content using // AJAX in a real application. var pages = { index: { title: "Home Page", content: "This is the home page." }, about: { title: "About", content: "Some content about the business." }, products: { title: "Products", content: "Buy some of our great products!" }, contact: { title: "Contact", content: "Say hello! We love to chat." } } // Put the rest of the code in this tutorial here. }
Here you have created a JavaScript object called pages
that contains four pages. Each of the pages has a title and some content.
Note: The code download includes a second example that demonstrates how to use AJAX to load the page content. Check out the history-ajax.js
file.
Next you need to get references to some of the key elements on the page. The ones we are most interested in are the navigation links, the <h1>
for the title and the <div>
for the content.
Add the following to your JavaScript file.
// Get references to the page elements. var navLinks = document.querySelectorAll('.load-content'); var titleElement = document.getElementById('title'); var contentElement = document.getElementById('content');
Updating the Page Content
Next up you need to write a function that will update the content displayed on the page from the state object that is retrieved when popstate
is fired. Lets call this function updateContent()
and give it one parameter, stateObj
.
Here you first want to use the stateObj.title
property to update the document.title
and innerHTML
property of the titleElement
. Then use the stateObj.content
property to update contentElement.innerHTML
.
We also add an if
statement here to make sure that the state object is not null.
// Update the page content. var updateContent = function(stateObj) { // Check to make sure that this state object is not null. if (stateObj) { document.title = stateObj.title; titleElement.innerHTML = stateObj.title; contentElement.innerHTML = stateObj.content; } };
Now we need to setup some event listeners that will fire when the user clicks on one of the navigation buttons. We use a for
loop here to cycle through each of the navLinks
and apply the listener for the click
event.
Inside the callback of the event listener a call to e.preventDefault()
should stop the browser from attempting to load the link.
We then need to fetch the page data from the pages
array. We do this by first getting the value of the href
attribute on the link that was clicked. This is then split at the .
producing an array like ["index", "html"]
. Selecting the first element in this array gives us the key needed to access the appropriate data in the pages
array.
A call to updateContent()
ensures that the fetched data is displayed on the screen. Here we pass in the pageData
we just retrieved. This is the same object that will be used later as the state object in our pushState()
call.
Finally we need to create a new history element so we issue a call to pushState()
, passing in pageData
as the state object, pageData.title
as the title and pageURL
as the URL.
// Attach click listeners for each of the nav links. for (var i = 0; i < navLinks.length; i++) { navLinks[i].addEventListener('click', function(e) { e.preventDefault(); // Fetch the page data using the URL in the link. var pageURL = this.attributes['href'].value; var pageData = pages[pageURL.split('.')[0]]; // Update the title and content. updateContent(pageData); // Create a new history item. history.pushState(pageData, pageData.title, pageURL); }); }
Listening For The popstate Event
Next we need to setup an event listener for the popstate
event. This should simply call the updateContent()
function and pass in the state object associated with the event.
Remember, the popstate
event is called whenever the user clicks the back or forward buttons in the browser, or after calls to back()
, forward()
or go()
.
// Update the page content when the popstate event is called. window.addEventListener('popstate', function(event) { updateContent(event.state) });
Loading The Initial Content
The final task is to make sure that the data for the home page is displayed when the page first loads. To do this we need to call the updateContent()
function and pass in the pages.index
object.
We also need to update the current history object so that the state object matches the data for the home page, otherwise there will be no data to display if the user navigates back to this history entry. To do this we can use the replaceState()
method. Pass in pages.index
as the state object, pages.index.title
as the title and an empty string (''
) as the URL.
// Load initial content. updateContent(pages.index); // Update this history event so that the state object contains // the data for the homepage. history.replaceState(pages.index, pages.index.title, '');
That’s it! You’ve now created a simple web application that uses the History API to manage the session history.
See the Demo Download The Code Resources
Browser Support
Browser support for the History API is very good. All five major desktop browsers have support for the API.
IE | Firefox | Chrome | Safari | Opera |
---|---|---|---|---|
10+ | 4.0+ | 5.0+ | 5.0+ | 11.5+ |
All major mobile browsers also include support for the History API.
iOS Safari | Android Browser | Chrome for Android | Firefox for Android | Opera Mobile | IE Mobile | Blackberry |
---|---|---|---|---|---|---|
4.2+ | 2.2, 2.3, 4.2+ | 29.0+ | 23.0+ | 11.1+ | 10.0+ | 7.0+ |
Source: http://caniuse.com/#feat=history
Final Thoughts
In this blog post you have learned how to use the History API to navigate and manipulate the session history.
There is no question that the History API has a place in modern web applications. As we continue to develop increasingly complex client-side applications we need to make sure that we don’t break the native functionality of the browser. The History API provides a simple solution to this problem. With great browser support there really is no reason why you can’t start using this today.
How do you plan to use the History API in your projects? Share your thoughts in the comments.
Thanks for letting me know! That’s now fixed. 🙂
Pretty! This has been an incredibly wonderful post. Thanks for providing this info.
Great tutorial.
I have a question, though. When I try to run this site locally, I get the error “Uncaught SecurityError: Failed to execute ‘replaceState’ on ‘History’: A history state object with URL ‘file:///C:/Users/x/Desktop/history-api/index.html’ cannot be created in a document with origin ‘null’.”
Could you explain why I that error when I run the site locally, and I don’t get the error when I run the site on a server?
Hello,
Above tuitorial is very good.It will help me to implement brower history.Could I implement single page browser history with this code?And when I am trying to download the code,it redirect to http://www.cl.ly/223v1N3e211k and show one error “SERVER NOT FOUND”.Please provide me specific link to download your code and please Let me know about my question.
Thank you in advance.
Hi Matt,
Thanks for your article!
I came across it while searching for solution to my problem… See even in your example you have the same problem – when reloading the page it gives 404 and no back/forward buttons help…
Any ideas how to make this work even on the refresh?
I’m not sure whether its still not known staff for you. The problem with 404 when refreshing page is related to how hashless routing works.
If you start from ‘home’ page, this page actually physically exists on server. So your index.html as well as js files (router as well) could be downloaded. From that moment router handle all other pages loading.
But when you refresh page for example ‘example.com/about’ your browser tries to fetch that resource from server. Of course you don’t have it and that’s why you see 404 not found.
To have that working you should add handling that to your server.
See http://ericduran.io/2013/05/31/angular-html5Mode-with-yeoman/
I’m having an issue with my app which lead me to your page and I’ve noticed the same issue in your demo. Launch the demo then click “Home” then “About” then “Products” then “Contact”. If you then click and hold the back button so you get the history drop-down and then click “Products”, you will navigate to “About”. If you click “About” from this menu, it will navigate to “Home”. I have tried this in the latest versions of Chrome and IE and see the same behavior in both. I must admit I’m stumped. Any ideas?
This is very very imaginative and creative approach, nice buddy. Very nice such a helpful
Thanks for reading Louanne
I am trying to use HTML page navigation with window.location.back(-1) but I alwys get the error that the object of back is undefined. NOTHING is usable. So this means hat I am not able to access my own history and I am not in the right scope. Any guidance to what I am missing here?
FYI I use it on a page script.google.com and everything works just fine until I come to this command. In W3schools all works as advertised, no problem at all to load another URL or go back when I Fiddle.
Hi Leo,
The History API only applies to the history within the current window/tab. You cannot access your entire global browser history.
`window.history.back()` will return `undefined` if there is no previous page to navigate back to.
Hey Matt, this post is good to know the history of API. I like the demo, too. Keep it up..
http://www.infobizzs.com
This is brilliant Matt, I can’t wait to start using History API!
Thanks Andrew,
Glad that you enjoyed it 🙂