In the example code, I used an input type=”text” to store the state value. In order to hide this control, you should set the style to “display:none” instead of using an input type=”hidden” form control. Browser implementations are more likely to ensure that input type=”text” values are restored, and Safari 5 for example, decided that type=”hidden” values should not necessarily be preserved (Webkit Bugzilla). Using input type=”text” with style=”display:none” will work more reliably across browsers.
On the other hand, if the “back” and “forward” buttons do not work as expected, you are surely going to make more than a few users quite infuriated. The typical AJAX application behavior when you push the “back” button is to completely erase everything that the user did, and send them to the fresh page as it was when they first visited it. Essentially, you are sending the users back to the entrance door. Sounds like you won’t be seeing them again any time soon.
Luckily, I managed to come up with a solution that works with browsers as erratic as IE6, does not require loading a library, and should only require a small amount of additional code. Although it only solves a small part of the browser history and “back” button problem, I think it solve the most irritating issues quite sufficiently, such that the user won’t feel one bit the urge to slam shut his laptop and pour himself a strong cup of coffee.
What is the “Back” button problem?
I probably don’t have to teach this to people who have done a fair amount of AJAX programming, but I will discuss it to clarify what my solution does and doesn’t solve.
AJAX is a technique that enables the browser to update only a part of a page, and is very useful for improving the perceived responsiveness of your web site. However, AJAX updates generally do not rewrite the location in the browser location bar. Since it is this location value that the browsers use to maintain their histories and to enable the back and forward buttons, AJAX renders these buttons useless. These buttons will typically send you to the first state that you saw when you first visited the web page, before any of the AJAX updates took place. In effect, all the work that you did since you visited the page will be lost (unless of course, the AJAX updates themselves rewrote the information on the server).
For example, imagine that you are visiting a web catalog site that lists electronic products, and uses AJAX to make browsing the product categories nice and quick. Imagine that the the user decided to do a text search after reaching a category deep in the hierarchy (“mac” in the illustration below). Also let’s assume that this product search fires a regular HTTP request (non-AJAX). Now after viewing the results, what would the user do if he decides to take another look at the “mac” category? Instinctively, he would push the “back” button on his browser, sending him unwittingly to the root category of the product catalog. You can almost see the cold sweat on his face as he realizes that he might have lost any way to get back to the “mac” category, save for going through the whole product hierarchy again. Pushing the “forward” button wouldn’t help either, since he would be taken straight to the search results page, right where his woes began.
We can easily see why the AJAX “back” button issue can cause such anger and frustration. Despite all your AJAX efforts to make navigation and data entry nice and quick, the “back” button issue will strip you of all the goodwill that was earned. Most likely, the users will even leave with a worse feeling than before they first visited.
The normal solution
Although there are many posts on the web and several libraries to solve the “back” button problem, all of the solutions that I could find on Google were basically variations of the methods used in RSH and YUI History. Namely, to use the hash (“#”) at the end of the location to store the state information in the browser’s history.
However, since this is not the intended usage of the hash, browsers deviate in how this is handled. Changing the location does not trigger IE browsers to store the changed location in the browser’s history, and early versions of Safari did not provide a way to read the hash portion after it was changed. To support IE, both RSH and YUI store browser history in an iframe and send an otherwise unnecessary request to the server, an unfortunate hack. An even more troublesome hack is that RSH and YUI use polling to read the hash portion, even with the more recent browsers. Polling is the action of executing a method in regular intervals, even when the user is not interacting with the browser. With RSH, the polling frequency is an alarming 0.5 seconds. This can cause the whole computer to slow down and will cause laptops to consume more battery charge. Polling is generally a despised hack that most programmers try hard to avoid. (As mentioned above, HTML5 provides the window.onhashchange event so that you don’t have to rely on polling, but this is only supported on the most recent of browsers.)
Going through all these loops and potentially causing a slow down on the user’s computer sounds simply too much to solve the “back” button problem. HTML5 will hopefully make things much easier, but as things stand today, I think we need a different solution.
A solution using form elements
The issue with the “back” button boils down to how we can store AJAX state information. As described above, I think that using the hash (“#”) in the location is much too troublesome. In the method below, I used regular form elements.
Form elements do preserve their contents between “back” button clicks. Remember when you were editing a form on a web site, and you realized that you had made a mistake after hitting the submit button. If you go back to the data entry page by clicking the “back” button, you will see all the form elements containing the same information they had just before you submitted it. Most browsers (even the older ones), automatically restore this information, even if they are different from the default values in the HTML. In my method, I use this feature to store the AJAX state. (better description here)
Browsers store this information inside their caches. If this solution doesn’t work on your web app, try adding the HTTP header “Cache-control: private” in your response, which should tell browsers that they can cache content locally.
I’m not sure if the above behavior is dictated by a standard, or is just how the browsers have opted to behave. I do think that this feature is central to the end-users’ browsing experience, and this is probably why this solution seems to work on all browsers without any hacking.
With the form solution, we do not create a history entry, and as a result, we can only solve a small portion of what RSH and YUI History provides. This small part however, is in my opinion, the most frequent source of end-user headaches, and is by far the most important problem. Being able to solve this with a simple solution should really help.
This demo works as follows.
- When you click the link “blue”, the content of the text field and its background color are changed to blue.
- Then go to another web site by clicking on the “go to Google” link that I provided.
- When you return to the demo web page by clicking the “back” button, the text field will contain the word “blue” and the background-color will also be blue. Just the way it was before going to Google. Compare this to another demo page with the 17th line commented out.
A description of the source follows.
- Lines 11-13 are a form to preserve the state (the state form). When the “back” button on the browser is pressed, the state values are restored from the browser cache.
- Line 14 is the link “blue”. When this is clicked, the value of “blue” is written to the state form with “$(‘field’).setValue(‘blue’)”. The next “setColor();” command changes the background color to synchronize with the value in the state form.
- Lines 17 is executed when the page is first loaded, and after the page is redrawn after coming back with the “back” button. The “setColor();” command will ensure that the background color is synchronized with the value in the state form. Safari and FF work fine without the setTimeout wrapper, but I found that it is necessary to get it working on IE when you come back with the “back” button.
- I have checked that this demo works on FireFox3.6, Safari 4 and IE6-8.
In this simple example, I only changed the background color. With an AJAX web app, you would save the AJAX request parameters in the state form, either before or after you made the AJAX request. You would also provide a command that sends the AJAX request after coming back with the “back” button, so that the DOM state will be synchronized with the state form values, which have been automatically restored from the browser hash.
What you can and what you cannot do with this method.
With this method, you can use the “back” button to return to your prior state after being sent to a new URL. You will be returned to the very last state. You cannot return to any intermediate AJAX state.
Of course, this is not a 100% solution covering bookmarking and stuff. I do believe that it does however cover more than 80% of the end-user needs. With a regular AJAX page, the end-user will lose all of their efforts; every click and every form entry. You are essentially showing the end-user the exit, and telling him that he doesn’t have to bother coming back. With the method that I described, the end-user will be sent back to his final state and he can simply modify things without going through the same procedure from the start. The equivalent of politely asking “Pardon. Could you please repeat that final request, Sir?”.