ENUMs, User Preferences, and the MySQL SET Datatype
Enforce Coding Standards with PHP_CodeSniffer and Eclipse IDE on Ubuntu Linux
Symfony 2 Crash Course
Getting Set up with Ogre 3D on Ubuntu
Using PHP pspell Spell Check Functions with a Custom Dictionary
JQuery Venetian Blinds Transition Effect

Going Back in History with AJAX and HTML5

Tuesday, 13 September 11, 10:33 am
Web pages have become increasingly dynamic in recent years, with more and more content being fetched, manipulated and saved in the background with AJAX. This breaks the traditional connection between the URL and the displayed document: no longer does a URL refer uniquely to one single document. This causes problems for users, because browsers use the URL when storing pages in history. Browser history is implemented as a stack, a last-in, first-out (FIFO) structure. As you navigate through the world wide web, the browser pushes the URLs that you visit on to the history stack. When you click back, it pops URLs off this stack, and pushes URLs onto the 'forward' stack.

With AJAX however, it's common to offer a range of functionality on a single page, and oftentimes that functionality can dramatically affect what content is shown on a specific URL. For instance, an AJAX-driven email page might initially show a list of items in the user's inbox. As they click links or buttons on that page, the content is updated by AJAX to e.g. show pages of older messages, or show the text of an individual email. As the URL is not changing, nothing is being pushed on to the history stack, with the result that if the user does click 'Back', they can lose the current page's state as a subsequent 'Forward' click will not return them to where they were.

HTML 5 offers a solution to this problem, by allowing JavaScript to push items onto the history stack, and replace existing items. Each item can be saved with an object storing state as properties. This object will be available as history.state, and you can basically store whatever you like in it as custom properties.

When the user navigates through their browser history using Back and Forward, the popstate event is fired, and you can run code for this event by defining a function for onpopstate().

Putting this Together to Make a Navigable AJAX Web App

The basic principle is that whenever the content of the current page is changed using AJAX methods, we push the new state onto the history stack along with an object that will let you regenerate that state.

This does mean you need to code your application with popstate in mind. A good way is to write a function which creates the current view when passed a state object, and then use this function when the user interacts with the page AND when the back/forward buttons are pressed.

Your createView() function may look something like this:
<script type="text/javascript"> function createView(stateObject, pushHistory) { document.getElementById('contentBox').innerHTML = '<h1>'+stateObject.title+'</h1>'+boxcontent[stateObject.contentId]; currentPage = stateObject.contentId;   // Save state on history stack // - First argument is any object that will let you restore state // - Second argument is a title (not the page title, and not currently used) // - Third argument is the URL - this will appear in the browser address bar if (pushHistory) history.pushState(stateObject, stateObject.title, '?page='+stateObject.contentId); } </script>
Notice the pushHistory argument to the createView() function. This lets us tell the function whether or not to save the current state on the stack - we'll basically always want to do this except when regenerating the view after 'Back' or 'Forward' has been clicked i.e. in the onpopstate() function. This function is fired whenever the browser's back/forward buttons are used:
<script type="text/javascript"> window.onpopstate = function(event) { // We use false as the second argument below // - state will already be on the stack when going Back/Forwards createView(event.state, false); }; </script>  
Then when the user interacts with your page in a way that changes the content in a way that they will consider a 'new page', you call the createView() function with true as the second parameter. In this simple demo, we do this via the onlick attribute of the Next Page link:
<div id="contentBox"> <h1>Welcome to Page 1</h1> Content for Page 1. </div> <a href="?page=2" onclick="createView( { contentId: (currentPage<boxcontent.length-1? currentPage+1 : 1), title: 'Welcome to Page '+(currentPage<boxcontent.length-1? currentPage+1 : 1) }, true );return false;">Next Page</a>

Welcome to Page 1

Content for Page 1.
Next Page
Click Activate Demo above to try out the code. After you've gone forwards with the Next Page link that appears, use of the 'Back' button will show the previous page, using the browser popstate. 'Forward' works in a similar way.

The last thing we're using in this simple demo is the following array of content:
<script type="text/javascript"> var boxcontent = new Array(); boxcontent[1] = 'Content for Page 1 is content.'; boxcontent[2] = 'This exciting stuff is content for Page 2.'; boxcontent[3] = 'Page 3 is probably the best page, in content terms.'; boxcontent[4] = 'Wow, you\'re still reading this crap?';   // We use a global variable to hold the current page number var currentPage = 1;   // We also have to push the initial state onto the history stack history.pushState( { contentId: 1, title: 'Welcome to Page 1' }, 'Welcome to Page 1', '?page=1'); </script>

Please enter your comment in the box below. Comments will be moderated before going live. Thanks for your feedback!

Cancel Post

/xkcd/ The Maritime Approximation