Use dojo.hash instead of dojo.back

In Dojo 1.4, the Dojo Toolkit team introduced a new “dojo.hash” library for managing the back button in AJAX applications. It’s a replacement for “dojo.back,” which was available in Dojo 1.0. If you’re deciding whether to use dojo.hash vs. dojo.back for your next web application, you should use dojo.hash.

Background: Back Button in AJAX

AJAX applications can update a page inline without navigating to a new page. This makes the page much more responsive, but since everything happens in one page, it makes the back button much less useful.

Way back in 2005, someone figured out a clever trick that would allow AJAX applications to support the back button. If a page at http://www.example.com/#beds=4 changed the URL to http://www.example.com#beds=3, the “#” in the URL would guarantee that the page wouldn’t reload, but the user could still click the back button to go back to “#beds=4″. The user could then click the forward button to go forward to “#beds=3″, navigating back and forth through the browser’s history.

In general, modifying the URL of a page would add an entry to the browser’s history, even if the change was in the URL’s “hash fragment”, the part of the URL that appears after the “#” sign. Since hash fragment changes don’t cause the page to reload, AJAX applications could use the hash fragment to add browser history entries dynamically.

To detect these changes, the page would use a “setInterval” timer to automatically poll the URL for updates every 100ms or so. (In IE8, Microsoft introduced the “onhashchange” event, eliminating the need to poll for changes in the hash fragment; all modern browsers now support “onhashchange”.)

It wasn’t quite that easy, of course. On some browsers, the page must use a hidden iframe to add entries to the browser’s history. In 2007, Brad Neuberg worked out most of the thorny details and wrote the Really Simple History library, which is now in wide use across many JavaScript libraries. His insights were incorporated into the dojo.back library, which was released with Dojo v1.0.

What’s Wrong with dojo.back

There are two problems with dojo.back: first, dojo.back loses history information when the user refreshes the page, and second, dojo.back uses document.write, which makes it difficult to use dojo.back correctly.

1) Refreshing the Page and Bookmarking the URL

dojo.back allows us to pass it a JavaScript object, storing the object in a hashmap in memory. dojo.back modifies the URL to include a random unique string. When the user clicks the back button, dojo.back fires a “back” event; when the user clicks the “forward” button, dojo.back fires a “forward” event.

So, if a user navigates to our site at http://www.example.com/ and performs some action that the user should be allowed to undo, we can pass a memento to dojo.back, e.g. {beds: 4, baths: 2}. dojo.back will modify the URL to http://www.example.com/#1288732596876 and keep a record in memory that “#1288732596876″ corresponds to {beds: 4, baths: 2}. If the user clicks the back button, the URL will revert to http://www.example.com/, and dojo will notify our code.

But that introduces a problem: what if the user refreshes the page? The hashmap in memory is then erased, and all of those entries in the browser’s history are now useless.

A similar problem occurs if the user wants to bookmark your URL for later, or share the URL with another user over email. Since the URL doesn’t contain the data we need to re-create the original object, the data is lost when the current window closes.

There seems to be an obvious fix for this problem: couldn’t we just use “#beds=4&baths=2″ in the URL instead of a random string? Instead of purely unique hash values, we could use a hash value that has meaning, i.e. a “semantically-named” hash value.

That is the correct fix, but there’s no way to do this correctly with dojo.back. dojo.back allows us to configure the “unique” string of the hash value, but we can’t instruct dojo.back to reconstruct mementos from the hash. If the user refreshes the page, dojo.back will erase its in-memory hashmap; it’s not smart enough to read “#beds=4&baths=2″ and re-inflate that into {beds: 4, baths: 2}.

Worse, if we use “#beds=4&baths=2″ as our semantically-named hash value, there’s a good chance that it won’t be unique. So if the user starts at “#beds=4&baths=2″ and then goes on to “#beds=4&baths=1″ and then finally to “#beds=4&baths=2″, dojo.back has no way to know whether the user went FORWARD to “baths=2″ or BACKWARD to “baths=2″

For this reason, dojo.back includes this cryptic warning at the top of its documentation:

NOTE: There are problems with using dojo.back with semantically-named fragment identifiers (“hash values” on an URL). In most browsers it will be hard for dojo.back to know distinguish a back from a forward event in those cases. For back/forward support to work best, the fragment ID should always be a unique value (something using new Date().getTime() for example). If you want to detect hash changes using semantic fragment IDs, then consider using dojo.hash instead (in Dojo 1.4+).

In other words, if we use dojo.back, our hash values need to be dates, e.g. “#1288732596876″, not meaningful strings like “#beds=4&baths=2″, or Dojo will confuse “back” navigation with “forward” navigation.

So, if you want your back button to keep working after refresh, or if you want users to share your URLs with each other, you should use dojo.hash instead of dojo.back.

2) dojo.back is harder to deploy because it uses document.write

Most of what we now know about back-button support was figured out in 2005, when the web was younger. At that time, no browser supported the onhashchange event; the only way to get AJAX back button to work properly in some browsers was to include the embedded iframe before the onload event.

Brad Neuberg figured out that the magic trick was to add the iframe using document.write; this technique was incorporated into dojo.back. document.write is a very dangerous technique on modern browsers, because the specified behavior is for document.write to modify the page if it’s called before the onload event, or to completely erase the entire page if it’s called after the onload event.

As a result, dojo.back has another cryptic note in its documentation:

WARNING: dojo.back.init() must be called before the page’s DOM is finished loading. Otherwise it will not work. Be careful with xdomain loading or djConfig.debugAtAllCosts scenarios, in order for this method to work, dojo.back will need to be part of a build layer.

Fortunately, this document.write hack is not required in any of Dojo’s currently supported browsers; notably, it’s not required in IE6+ or Safari 3+. In fact, if you’re reading this blog post in IE, you’re probably using IE8 or IE9, which has “onhashchange” built-in and requires no iframe at all.

dojo.hash uses an iframe, but does not attempt to create it using document.write; as a result, it’s a lot safer to use than dojo.back.

dojo.hash Is the Way of the Future

dojo.hash is a very simple library compared to dojo.back. In fact, if the browser supports the “onhashchange” event, then it does almost nothing more than attach the “onhashchange” event to the “/dojo/hashchange” topic. (You can also use dojo.hash to get/set the hash fragment in a convenient way.)

dojo.hash makes no attempt to store state objects in memory; instead, anyone who uses dojo.hash must serialize their memento into a hash string before passing it to dojo.hash. (dojo.objectToQuery is particularly useful for for this serialization.) When subscribing to the “/dojo/hashchange” topic, Dojo will invoke a callback function, passing it the current hash fragment.

We think you will find dojo.hash more useful than dojo.back for almost all situations. If dojo.back works for you today, you may not need to upgrade, but if you’re building something new and deciding which library to use, we strongly recommend dojo.hash.

Discussion