Saving Application State in History

Posted on May 10, 2010

Sergio Cinos Frontend Engineer

A lot of web applications are presented to the user as a single page with a single URL (without taking queryString nor hash parts into account) and the content changing behind the scenes. As the user interacts with our application using the UI, it passes through different ‘pages’ or states (Home page, Photo page, settings page…). The main problem with this approach is that the ‘Back’ button loses all of its meaning, because clicking it will take the user to the previous real page he was in. The linking/bookmarking is also broken and all of this results in poor user experience.

The solution to this problem is to save the application state (example: the page name and the message id the user is reading) into URL. Say we have a string that represents our application state (maybe a simple list of key-value pairs, maybe a JSON string encoded in base64…). Our main goal is to inject this string into the URL so the user can copy/bookmark it. Also, we need this changed URL to be saved on the browser’s History so the user can use the back button (and forward, of course) using the proper browser buttons. As an additional requirement, we want to minimize the number of server requests to avoid extra network and servers load. As the web application is probably using a lot of JavaScript to manage UI, we want to save application state using JavaScript as well.

For Firefox, Safari and Chrome we can just save our state-string into URL hash part (the part after #). Changing the hash (progamatically, by hand, through a link…) does not create a new server request, and the former URL is saved into the history, which meets all of our needs. To load the state, you need to check for URL changes using timers as there is no event-related solution implemented in any of the main-stream browsers. If you only want to target those browsers, you don’t need to read more, because from now we will just focus on previous versions of Internet Explorer.

However, in Internet Explorer 6 and 7 (from now on, IE for readability) this just doesn’t work. If you want the solution for IE, just skip to the end of the article. History/URL management in IE is really weird, and we are going to explore and explain non-working systems in order to discover and learn about all related problems. This is the only way to really understand both problem and solution, so we can adapt it to our needs.

In IE, if the hash is changed (due to a script execution, as a result of a link or the user manually changing it), the former URL is saved in History only if there is an element with the ‘id’ attribute equal to the new hash or an ‘A’ element whose ‘name’ attribute is equal to the new hash. So if we want to save our application state string we need to create those elements dynamically (it would be insane to encode those elements in our HTML for every potential application state representation). If you choose the A+name approach, that element cannot be created using DOM methods (i.e.: appendChild), it needs to be inserted through ‘innerHTML’ as text into the page. This is our first solution to the main problem, but as it turns out, it is a non-working one.

We find the first bug: in IE, if you go back one page in the history, and that ‘previous’ page’s URL only differs with the ‘current’ URL in the hash part, that change is not communicated to the document. To put it in simple words, the user sees the ‘previous’ URL in the Location Bar, but if you access ‘document.location’ it contains the ‘current’ address (the URL before the user pressed the Back button). Your page and your code will be unable to notice this change.

As a second approach, we will try to use the iframes. How does History work when using iframes? Each visited page (in the main ‘view’ or inside an iframe) is saved in the same History stack. When the user goes back to any URL in that list, it is loaded in its original source (main view or iframe). We can use this to save our state: we create an iframe with ’src’ equal to a URL with our state saved in the hash each time the user changes to another state, so the History is filled with URLs requested from the iframe. As the user goes back, those URLs are loaded into the iframe without changing our main page. As we learned before, we can’t just change the hash part, because when going back your code can’t detect the changes to those addresses. We need to change any other part.

The problem is that changing any part other than the hash will fire a server request. We want to minimize this, and making a extra request for each application state is unacceptable. There is an obscure way to prevent this. Just forget about iframe’s src (don’t set it on creation) and use this code snippet to save the state:

//We are saving ‘newHash’ into the iframe referenced by ‘historyIframe’
var iDocument = historyIframe.contentWindow.document;
iDocument.open();
iDocument.close();
iDocument.location.hash = newHash;

That code causes IE to think that there is a new document on the window, so it saves the previous URL in the history, but it doesn’t perform an extra request. This can be used to build a full and cross-browser system to save application state into URLs and keep History and Bookmarks working:

  • To save any state, encode it as a string and save it into main location hash, iframe hash and a JavaScript variable (called ‘control’ in this explanation). To load a state, periodically check for differences between main hash and iframe hash. You need to use a short interval to avoid interface delays, somewhere around 100ms.
  • If the iframe hash is different from the one previously saved in the control variable, then the user has changed the page using History. Load the state saved into the iframe and update document.location and control variable.
  • If the main location hash is different from the values saved in iframe/control, the user has loaded a new URL using a link or by hand. Load the state saved in main location and update iframe and control variable.

This can be used to build a fully functional state system that doesn’t break History nor Bookmarks, except if you are loading pages from different domains using iframes or popups. In that case you are using ‘document.domain’ to bypass cross-browser security and this solution doesn’t work for you. We will discuss this issue in the next article.

2 Responses to “Saving Application State in History”

  • Cody Poskus
    December 9, 2010 at 6:00 am

    good post. thank you for sharing!

  • Cristin Zelek
    February 25, 2011 at 7:45 pm

    I’ve been here a few times and it appears like your articles get much more informative each time. Maintain it up I appreciate reading them.

Leave a Reply

  • (required)
  • will not be published (required)