Infrequently Noted

Alex Russell on browsers, standards, and the process of progress.

in defense of the "back" button

The back button is one of those things that until recently I've considered an unfortunate casualty of the move from web pages to DHTML/CADEX applications. location.replace() and the history-less behavior of XMLHTTP have elicited foot-shuffling, eye-averting mumbling from people building responsive UIs when asked "what about the back button?". That the back button nukes all of a user's progress through an application has been a small point of collective shame. Wouldn't it be great if there were some way to cause the back-button to fire off some sort of callback within the UI instead of obliterating it?

Lo-and-behold, we have a solution! ()

Before I get into the nitty gritty of how this all works, let me provide a demonstration of the way you'd invoke this capability in Dojo. A normal call to get some data in Dojo often looks like:

dojo.io.bind({
url: "sampleData.txt",
load: function(type, data, evt){ alert(data); },
error: function(type, errObj){ alert(errObj.message); }
});


dojo.io.bind() takes an anonymous object consisting of properties required for making an asynchronous request as well as functions that are useful for reporting back success or failure thereof. In normal operation within a browser, this call won't ever add anything to the history, so when the user hits the back button, away we go to wherever the current application was linked from. Here's the same request, but with a callback registered that will be called when the user hits "back", instead of the previous "nuking" behavior:

dojo.io.bind({
url: "sampleData.txt",
load: function(type, data, evt){ alert(data); },
error: function(type, errObj){ alert(errObj.message); },
backButton: function(){ alert('the back button was intercepted, the document should not unload'); }
});


How it works:

After discussing the problem with Aaron, I thought I'd try a quick, naive implementation that just allowed me to intercept the back button. Who cares if actually does anything for the purposes of a prototype, right? Boy was I in for a ride.

Unbeknownst to me, browsers seem to keep two sets of history list: one that's the visible set of top-level pages visited, and another set that includes any transitions between iframes. The first set is exposed via drop-down lists from the back button and the like. The second set, while not exposed to the user, is actually what one cycles through when moving back and forth with forward and backward buttons. But not if the iframe that's doing the navigating was created after the page calls onload. This "property" alone took hours to debug, but once Aaron sent me a minimal static HTML page that added an entry to the forward-back buttons by changing an iframe, I had one of those slap-your-forehead moments and it all made sense.

Turns out that there's NO way to create an iframe after onload that will affect the forward-back history as far as I can tell. Unlike a lot of other dynamically created iframe bugs (and lordy, are there a lot of 'em), this seems to be true across IE and Firefox. As a result, Dojo now does a document.write() to create an invisible iframe when the required bootstrap files are being loaded, since they are the only files that are guaranteed to be pulled in with a <script> tag.

Once that is done, we continually load the same file in this iframe (blank.html) but provide it with an ever-changing query string (keyed on millisecond time). This file calls back up to the parent environment whenever it is loaded in the iframe, and so we can determine when a new document has been loaded and based on whether or not we requested a new document, whether or not this was due to a press of the back button. Aaron has suggested that I post a stand-alone package of this code, but I don't know that breaking it out from Dojo is really that useful. If you want this feature in a stand alone package, comment here or send me mail.

Despite this advancement in the state of usability, my current code doesn't correctly handle subsequent forward button requests or the bookmarking of particular states in an application (think "checkpointing"). Aaron and I have discussed possible solutions, but until I have working code I'm reluctant to post descriptions of how it might be done. The good news for everyone is that these problems are looking more and more solveable for the great majority of browsers. The back button no longer need be a tactial nuke that gets dropped into our apps.

We can finally make peace with our inner usability demons.

The technique presented here only works on IE and Mozilla/Firefox as of this writing. I'm still looking for ways to cajole Safari into a similar behavior, and Opera is untested to date.