Unique URLs - Ajax Patterns

Unique URLs

From Ajax Patterns

Address, Bookmarks, Cut-And-Paste, Distinct, Favourites, Hyperlink, Location, Link, Mail, Share, Unique, URL

Contents

Goal Story

Sasha is exploring a recent news event by browsing a map of the area. There's a Unique URL for each location, so she's able to link to it in her blog and add it to her social bookmarking account.

Problem

How can you assign distinct URLs to different parts of your application?

Forces

  • Expressive URLs are vital. The ability to link from one site to another is arguably what made the web successful. Furthermore, web surfers like to bookmark pages for later on. And these bookmarks are now a social phenomenon, with the advent of "linkblogs" and social bookmarking software like del.icio.us.
  • Browsers retain URLs in their history, and the Back Button will only work if URLs vary. The Back Button is a big habit for many users. It can mean "Go back to where I was a minute ago". For many users, it can also mean "Undo my last action", and that mental model must be respected even though some experts deem it to be technically incorrect.
  • A single address for your whole site is not enough - you need to support deep linking for links to be effective. That is, each distinct component of a website should have its own URL.
  • In conventional websites, the URL changes when the page is refreshed, due to submitting a form or following a link. But in Ajax Apps, the page never refreshes, so the browser won't change the URL.
  • From a security point of view, it is not even accepted to manipulate the URL. It would be a perfect solution for the phishing web-sites, who wants to trick the users to steal their vital information. Just imagine a fake website like tvviter.com, with the fake URL of twitter.com, who asks you to sign in to your account!

Solution

Provide Unique URLs for significant application states. Each time a significant state change occurs - e.g. the user opens a new product in an E-Commerce system - the application's URL changes accordingly. The objective is straightforward, but as discussed below, the implementation requires a few unusual tricks. This Solution is fairly complex, because it delves into the details of building Unique URLs. Increasingly, though, reusable libraries will save you the effort, so be sure to check out what's available before directly applying any of the techniques here. The Real-World Examples identifies a couple of resources and there will be doubtless be others by the time you read this.

In Ajax, most server communication occurs with XMLHttpRequest. Since “XMLHttpRequest Call”s don't affect the page URL, the URL is by default permanently stuck on the same URL that was used to launch the Ajax App, no matter how many transfers occur. The only option you have to manipulate the URL is with Javascript. And in fact, it's very easy to do that with the window.location.href property:

   window.location.href = newURL; // Caution - Read on before using this.

Very easy, but one big problem: under normal circumstances, the browser will automatically clear the page and load the new URL, which kind of defeats the purpose of using Ajax in the first place. Fortunately, there's a cunning workaround here. It was originally conceived in the Flash world and has more recently been translated to an Ajax context. Mike Stenhouse's demo and article provides a good summary of the approach, and the Solution here is heavily based on his work.

The cunning workaround relies on fragment identifiers, those optional components of URLs that come after the hash character (#). In http://ajaxpatterns.org/Main_Page#Background, for example, the fragment identifier is Background. The point of fragment identifiers has traditionally been to get browsers scrolling to different points of a page, which is why they are sometimes called "in-page links" or "named links". If you're looking at the Table of Contents on AjaxPatterns.org and click on the Background link - http://ajaxpatterns.org/#Background - the address bar will update and the browser will scroll to the Background section. Here's the point: no reload actually occurred. And yet, the browser behaves as if you'd clicked on a standard link: the URL in the address bar changes and the Back button will send you back from whence you came (at least for some browsers; more on portability later).

You can probably see where this is going. We want Ajax Apps to change the page URL without forcing a reload, and it turns out that changes to fragment identifiers do exactly that. The solution, then, is for our script to change only the fragment component. We could do this by munging the window.location.href property, but there's actually a more direct route: window.location.hash. So when a state change is significant enough, just adjust the URL property:

   window.location.hash = summary;

Throughout this pattern, "summary" refers to a string representation of the current browser state, at least that portion of the state that's worth keeping. For example, you have an Ajax interface containing several tabs: Books, Songs, Movies. You want to assign a Unique URL to each tab, so the "summary" will be the name of the tab. If there were other significant variables, they would be integrated into the summary string. To keep the fragment synchronised with the current state, you need to identify when state changes occur and update the fragment accordingly. In the tab example, you can do this by making the onclick handler set the hash property in addition to changing the actual content:

   songTab.onclick = function() {
     window.location.hash = "#songs".
     // ... now open up the tab
   }

So now the URL will change from http://ajax.shop/ to http://ajax.shop/#songs. Easy enough, but what happens when you mail the URL to a friend? Your buddy's browser will open up http://ajax.shop and assume songs is an innocent, old-fashioned, in-page link. It will try to scroll to a "songs" element and won't find one, but it won't complain about it either, so it will just show the main page as before. We've made the URL change after a state change, but so far we haven't built in any logic to make the state change after a URL change.

State and URL need to be in sync. If one changes, the other must change as well. Effectively, there's a mini-protocol, specific to your application, that defines how they map to each other. In our example above, the protocol says "The fragment always represents the currently open tab". Setting the fragment is one side of the protocol; the other side is reading it. After your buddy opens up http://ajax.shop/#songs, and after his browser has unsuccessfully tried to scroll to the non-existent songs element, we need to read the fragment and update the interface accordingly:

   window.onload = function() {
     initialiseStateFromURL();
   }
  
   function initialiseStateFromURL() {
     var initialTab = window.location.hash;
     openTab(initialTab);
   }

Good. Now you can mail the link, bookmark it, link to it from another page. In some browsers at least, it will also go into your history, which means the back button works as expected. As a simple implementation, that's good enough.

However, the solution's still not ideal, because what will happen if the application's already loaded? The most likely situation is in clicking the back button to revert to a previous state. Or another example would be retrieving a bookmark while already on the same application. In both cases, the URL will change in the address bar, but the application will do absolutely nothing!

Remember, we're making the application manipulate fragments precisely because the browser doesn't reload the page when they change. When it comes to Back buttons and bookmarks and the like, we're talking about changes that are initiated by the user, but the browser treats those with exactly the same inaction as when our script initiates them. So our onload won't be called and the application won't be affected in anyway.

To make Unique URLs work properly, we have to resort to a somewhat cheap trick: continuously polling the fragment property. Whenever the fragment happens to change, the browser script will notice a short time later and update state accordingly. In this example, we could schedule a check every second using setInterval():

   window.onload = function() {
     initialiseStateFromURL();
     setInterval(initialiseStateFromURL, 1000);
   }

In fact, we should only perform some action if the hash has recently changed. So we perform a check in initialiseStateFromURL() to see if the hash has recently changed:

   var recentHash = "";
   function pollHash() {
  
     if (window.location.hash==recentHash) {
       return; // Nothing's changed since last polled.
     }
     recentHash = window.location.hash;
  
     // URL has changed, update the UI accordingly.
     openTab(initialTab);
  
   }

The polling adds some performance overhead, but we now have a URL setup that feels like the real thing.

One last thing on this technique. I said above that you manually set window.location.hash after state changes. Now that we've considered the polling technique, you can see it's also possible to do the reverse: change the URL in response to user events, then let the polling mechanism pick it up and change state accordingly. That adds a delay, but a big benefit is you can easily add a hyperlink on the page to affect the system state.

Here's a quick summary of the technique just described, which I'll call the "page-URL" technique:

  1. Ensure window.location.hash always reflects application state.
  2. Create a function, e.g. initialiseStateFromURL() to set application state from window.location.hash.
  3. Run initialiseStateFromURL on startup and on periodic intervals.

But wait, there's more! I mentioned above that each URL goes into history, but only "for some browsers". IE's one of the outliers here, so the above technique will fail IE's history mechanism and the back button won't work as expected. The good news is there's a workaround for it. The bad news is that it's a different technique and won't work properly on Firefox. So if you can live with everything but IE history working, go with the hash-rewrite technique. Otherwise, read on ...

The IE-specific solution relies on IFrames and their source URLs, so I'll call it the "IFrame-URL" technique. While IE won't attribute a fragment change as historically significant, it will recognise changes to the source URL of an embedded IFrame. Each time you change the source URL of an IFrame, the page overall will be added to the history. Think of each page in the history as not just the main URL, but the combination of main URL and URLs of any IFrames. Its the combination that must vary in order to create a new history entry.

So to get IE history working, we can embed a dummy IFrame and keep changing its source URL to reflect the current state. When the user hits Back, the source will change to its previous state. In theory, the browser could keep polling the source URL to know when it's updated, but for some reason reverted IFrames don't seem to provide the correct source URL. So a workaround is to make the actual IFrame body contain the summary of browser state, i.e. that string which was in the fragment identifier for the previous algorithm. Then, the polling mechanism looks at the IFrame body rather than its source URL.

For example, if the user clicks on the Songs tab, the IFrame source will change to dummy.phtml?summary=Songs. dummy.phtml will be made to output this argument verbatim, i.e. the body of the IFrame will be "Songs". When the user later clicks somewhere else and then the Back button, the browser will re-set the IFrame's URL to dummy.phtml?summary=Songs and the body will once again be "Songs". The polling mechanism will soon detect the change, and open up the Songs tab.

Now we have history, but we haven't done anything about the URLs. So when the user clicks on the Songs tab, we not only change the IFrame source, but also the main document URL, using a fragment identifier like before. We also have to make sure the polling mechanism looks at the URL as well as the IFrame source. If either has changed since last poll, the application state must be updated. In addition, the one that hasn't changed must also be updated, i.e. if the IFrame source has changed, the URL must be updated and vice-versa.

A quick summary of the IFrame-URL technique

  • Embed an IFrame into your document.
  • On each significant state change, update the IFrame's source URL using a query string (dummy.phtml?summary=Songs), and the main page URL using a fragment identifier (http://ajax.shop/#Songs).
  • Continuously poll the IFrame source URL and the main page URL. If either has changed, update the other one and update the application state according to the new value.

Note this won't work on Firefox, because Firefox will include URL changes as well as IFrame changes in the history. Thus, each state change will actually add two entries to Firefox's history. As mentioned above, you'll probably need to implement both algorithms in order to fully support both browsers, or a modified version of the second technique will do it too. Also, be careful with both approaches because there's a risk your polling mechanism will revert states too eagerly. If the user has begun changing a value and the polling mechanism kicks in, it's possible the value will change back. If you separate input and output carefully enough, that can be avoided. Or a workaround is to suspend the polling mechanism at certain times. None of this is ideal, but a Unique URL mechanism is worth fighting for.

As a variant on changing the IFrame's source, Brad Neuberg (based on a hack by Danny Goodman) has pointed out that browsers will retain form field data. Instead of changing the IFrame source, you set the value of a hidden text field in the IFrame. That will solve the history problem, and you'll still need to do something about the main page URL to give the page a Unique URL.

To summarize all this, here are all the things we'd want from Unique URLs, and an explanation of how they can be achieved in Ajax.

Bookmarkable, Linkable, and Type-In-Able

window.location.hash makes the URL unique, so a user can bookmark it, link to it, and type it directly into their browser. window.onload ensures the bookmark is used to set state. Polling or IFrame ensure this will work even when the base URL (before the hash) is the same.

History and Back Button

window.location.hash will change and polling it will pick that up in most browsers. A hidden IFrame is also required for portability and can also be polled.

Back to Ajax App

After leaving an Ajax App, the user should be able to click the Back Button and return to the final state just prior to leaving. The techniques in the previous pattern will support this behaviour.

URL handling is one of the greatest complaints with Ajax, with myths abounding about how "Ajax breaks the back button" and "You can't bookmark Ajax Apps". That an Ajax App can include full page refreshes is enough to debunk these myths. But this pattern shows that even without page refreshes, Unique URLs are still do-able, if somewhat complicated. This pattern identifies some of the current thinking on Unique URLs, but the problem has not yet been solved in a satisfactory manner. The best advice is to watch for new ideas and delegate to a good library where applicable.

Decisions

How will you support search engine indexing?

Search engines point "robot" scripts to a website and have them accumulate a collection of pages. The robot works by scooting through the website, finding standard links to standard URLs and following them. It won't click on buttons or type in values like a user would, and it probably won't distinguish among fragment identifiers either. So if it sees links to http://ajax.shop/#Songs and http://ajax.shop/#Movies, it will follow one or the other, but not both. That's a big problem, because it means an entire Ajax App will only have one searchlink associated with it, and you'll miss out on a lot of potential visits.

The simplest approach is to live with a single page and do whatever you can with the initial HTML. Ensure it contains all info required for indexing, focusing on meta tags, headings, and initial content.

A more sophisticated technique is to provide a Site Map page, linked from the main page, that links to all URLs you want indexed with the link text containing suitable descriptions. One catch here: you can't link to URLs with fragment identifiers, so you'll need to come up with a way to present search engines with standard URLs, even though your application would normally present those using fragment identifiers. For example, have the Site Map link to http://ajax.shop/Movies and configure your server to redirect to http://ajax.shop/#Movies. It's probably reasonable to explicitly check if a robot is performing the search, and preserve the URL if that's the case - i.e. when the robot requests http://ajax.shop/Movies, simply output the same contents as the user would see on http://ajax.shop/#Movies. Thus, the search engine will index http://ajax.shop/Movies with the correct content, and when the user clicks on a search result, the server will know (because the client is not a bobot) to redirect to http://ajax.shop/#Movies.

Search engine strategies for Ajax Apps has been discussed in a detailed paper by Jeremy Hartlet of Backbase. See that paper for more details, though note that some advice is Backbase-specific.

What will be the polling interval?

The solutions here involve polling either the main page URL or the IFrame URL or both. What sort of polling interval is appropriate? Anything more than a few hundred milliseconds will cause a noticeable delay (though that's not a showstopper because users are used to delays in loading pages anyway). From a responsiveness perspective, the delay would ideally be as short as possible, but there are two forces in the opposite direction:

Performance overhead

Continuous polling will have an impact on overall application performance, so consider 10 milliseconds the bare minimum. Somewhere between 100 milliseconds is probably the best trade-off.

Over-eager synchronisation

This is only a problem for the IFrame-URL approach and not the Page-URL approach. The IFrame-URL approach, as described, keeps Page URL, IFrame URL, and application state synchronised. Thus, there's the possibility of beginning to change one of those things and having it "snap back" during a synchronisation checkpoint. For example, you might begin changing application state and suddenly see it "snap back" because it's different to URL state. There are various pragmatic ways to avoid this sort of thing, but as long as you have to rely on polling, the problem might still arise for certain combinations of events. While far from ideal, a longer polling period will at least reduce the problem.

What state differences warrant Unique URLs?

This pattern discusses "state changes" abstractly. When we say a "state change" should result in a new URL and a new entry in the browser's history, what kind of state change are we discussing? In an Ajax App, some changes will be significant enough to warrant a new URL, and some won't. Some guidelines:

  • User preferences shouldn't change the URL - if a user opts for yellow background, that shouldn't be in the URL.
  • When there's a particular thing the user's looking at, that's a logical candidate for a Unique URL. For example, a product in an E-Commerce catalogue, or a general category.
  • When it's possible to open several things at once, you'll probably need to capture the entire collection.

What will the URLs look like?

Unique URLs requires you to make like an information architect and do some URL design work. Possibly, you'll only be controlling the fragment identifier rather than the entire URL, but even the fragment identifier has usability implications. Some guidelines:

  • Users sometimes hack URLs, which suggests that in some cases, you might want to make the variable explicit. So instead of /#Movies, consider #category=Movies if you want to make the URLs more hack-friendly. It's also more like a CGI-style URL, which may be more familiar to users. The downside is a little more coding to parse such URLs.
  • As mentioned in a Decision above, search engine indexing is also a consideration, and the fragment identifier will sometimes be mapped to a search-engine-friendly URL. Therefore, ensure the fragment identifier is as descriptive as possible in order to attract searchers. For example, favour a product name over a product ID, since the name is what users are likely to search for.

Real-World Examples

History.js

History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState. Supports jQuery, MooTools and Prototype. For HTML5 browsers this means that you can modify the URL directly, without needing to use hashes anymore. For HTML4 browsers it will revert back to using the old onhashchange functionality.

https://github.com/balupton/history.js


PairStairs

Nat Pryce's PairStairs (http://nat.truemesh.com/stairmaster.html) is a Extreme Programming tool that accepts a list of programmer initials and outputs a corresponding matrix (Figure 1.98, “PairStairs”). The URL stays synchronised with the initials being entered, e.g. if you enter "ap ek kc mm", the URL will update so as to end with #ap_ek_kc_mm.

Figure 1.98. PairStairs PairStairs

Dojo Binding Library

Dojo Toolkit's dojo.io.bind lets you specify (or auto-generate) a fragment-based URL as part of a web remoting call. After the response arrives, the application's URL will switch accordingly.

Really Simple History Library

Really Simple History is a framework that lets you explicitly manage browser history (Figure 1.99, “Really Simple History Demo”). A call to dhtmlHistory.add(fragment, state) will set the URL's fragment identifier to fragment and set a state variable to state. A callback function can be registered to determine when the history has changed (e.g. back button was pressed), and it will receive the corresponding location and state. The library has now been incorporated into the Dojo project.

Figure 1.99. Really Simple History Demo Really Simple History Demo

Code Refactoring [AjaxPatterns Unique URL Sum]

This example refactors the Basic Sum Demo using both the Page-URL and IFrame-URL technique. There are actually four implementations here:

  • Unique URL Sum Demo uses the Page-URL technique so that when state changes, the URL will change.
  • Polling URL Sum Demo enhances the Unique URL Sum Demo to add a polling mechanism, so that when the URL changes, the state will also change. Thus, the URL-state synchronisation occurs in both directions.
  • IFrame URL Sum Demo alters the Unique URL Sum Demo to use the IFrame-URL technique. In this version, the history will work correctly because the IFrame source is changed, but the main page URL is not actually updated.
  • Full IFrame URL Sum Demo completes the IFrame-URL implementation by making the main page URL unique.

Unique URL Sum Demo

This first refactoring changes the fragment identifier (hash) when a sum is submitted. The fragment identifier always contains the main page state, which in this case is a comma-separated list of the three sum figures (e.g. #3,5,10):

   function submitSum() {
     definedFigures = {
       figure1: $("figure1").value,
       figure2: $("figure2").value,
       figure3: $("figure3").value
     }
     hash =     "#" + definedFigures.figure1 + "," + definedFigures.figure2
              + "," +definedFigures.figure3;
     window.location.hash = hash;
     ajaxCaller.get("sum.phtml", definedFigures, onSumResponse, false, null);
   }

This is only useful if the startup routine actually takes the fragment identifier into account. Thus, a new function, setInitialFigures(), is called on initialisation. It inspects the fragment identifier, sets the parameters, and calls submitSum() to update the sum result:

   function setInitialFigures() {
     figuresRE = /#([-]*[0-9]*),([-]*[0-9]*),([-]*[0-9]*)/;
     figuresSpec = window.location.hash;
     if (!figuresRE.test(figuresSpec)) {
       return; // ignore url if invalid
     }
     $("figure1").value = figuresSpec.replace(figuresRE, "$1");
     $("figure2").value = figuresSpec.replace(figuresRE, "$2");
     $("figure3").value = figuresSpec.replace(figuresRE, "$3");
     submitSum();
   }

Polling URL Sum Demo

The previous version will update the URL upon state change, but not vice-versa. This demo rectifies the problem by polling the URL and updating state if the URL changes. The functionality for updating state from URL is already present in the setInitialFigures() function of the previous version. We just need to keep running it instead of just calling it on startup. So, setInitialFigures() has been renamed to pollHash(), and is run periodically:

   window.onload = function() {
     ...
     setInterval(pollHash, 1000);
   }

The function is the same as before, but with just one change: a recentHash variable is maintained to ensure that we only change state if the hash has actually changed. We don't want to change state more times than necessary:

   var recentHash = "";
  
   function pollHash() {
     if (window.location.hash==recentHash) {
       return; // Nothing's changed since last polled.
     }
     recentHash = window.location.hash;
  
     // ... Same code as in previous setInitialFigures() function ...
   }

IFrame Sum Demo

The previous versions won't work on IE, because a fragment identifier change is not significant enough to get into IE's history. So here, we'll introduce an IFrame and change its source URL whenever the document changes.

The initial HTML includes an IFrame. This is the IFrame whose source URL we'll manipulate in order to add entries to browser history. It's backed by a php file that simply mimics the summary argument - the body of the IFrame always matches the source URL, so we can find the summary by inspecting the body. In fact, we should just be able to inspect the source URL directly, but there seems to be some bug in IE that fails to update the source property when you go back to a previous IFrame (even though it actually fetches the content according to that URL). Incidentally, the IFrame would normally be hidden via CSS, but is kept visible for demonstration purposes.

   <iframe id='iFrame' name='iFrame' src='summary.phtml?summary='></iframe>

Each time a sum is submitted, the IFrame's source is updated:

   function submitSum() {
     ...
     $("iFrame").src = "summary.phtml?summary=" + summary;
   }

Instead of polling the URL, we're now polling the IFrame's body, which contains the state summary. Remember that in this version, the main page URL is fixed - we're only making IE history work, and not yet making the application bookmarkable. Whenever we notice the IFrame source is change, we assume that's because the back button has been clicked. Thus, we pull out the summary from the IFrame body and adjust the application state accordingly:

   window.onload = function() {
     ...
     setInterval(pollSummary, 1000);
   }
  
   function pollSummary() {
  
     summary = window["iFrame"].document.body.innerHTML;
     if (summary==recentSummary) {
       return; // Nothing's changed since last polled.
     }
     recentSummary = summary;
  
     // Set figures according to summary string and call submitSum()
     // to set the result ...
  
   }

The Back Button will now work as expected on both Firefox and IE.

Full IFrame Sum Demo

This final version builds on the previous version to include Unique URLs for the main page, so that you can bookmark a particular combination of figures. Note that this is a demo of "IFrame URL" as discussed in the Solution, and won't work properly on Firefox for reasons explained there.

The page contains an IFrame as before, and since we're now synchronising the URL as well, there are actually three things to keep in sync:

  1. The application state, i.e. the three sum figures.
  2. The page URL
  3. The IFrame URL

The algorithm is this: when one of these changes, change the other two and update the sum result. To facilitate this, some convenience functions exist to read and write these properties:

   function getURLSummary() {
     var url = window.location.href;
     return URL_RE.test(url) ? url.replace(/.*#(.*)/, "$1") : "";
   }
  
   function getIFrameSummary() {
     return util.trim(window["iFrame"].document.body.innerHTML);
   }
  
   function getInputsSummary() {
     return $("figure1").value +","+ $("figure2").value +","+ $("figure3").value;
   }
  
   function setURL(summaryString) {
     window.location.hash = "#" + summaryString;
     document.title = "Unique URL Sum: " + summaryString;
   }
  
   function setIFrame(summaryString) {
     $("iFrame").src = "summary.phtml?summary=" + summaryString;
   }

The updateSumResult() is also straightforward:

 function updateSumResult() {

   definedFigures = {
     figure1: $("figure1").value,
     figure2: $("figure2").value,
     figure3: $("figure3").value
   }
   ajaxCaller.get("sum.phtml", definedFigures, onSumResponse, false, null);

 }

 function onSumResponse(text, headers, callingContext) {
   self.$("sum").innerHTML = text;
 }

Now for the actual synchronisation logic. The first thing that might change is the application state itself. This occurs when the submit button is clicked, and will cause the IFrame and page URL to update:

   window.onload = function() {
     self.$("addButton").onclick = function() {
       onSubmitted();
     }
     ...
   }
  
   function onSubmitted() {
     var inputsSummary = getInputsSummary();
     setIFrame(inputsSummary);
     setURL(inputsSummary);
     updateSumResult();
   }

The other two things that can change are the IFrame source (e.g. if the Back Button is pushed) or the URL (e.g. if a Bookmark is selected). There's no way to notice these directly, so, as before, we poll for them. A single polling function suffices for both:

   window.onload = function() {
     ...
     pollTimer = setInterval(pollSummary, POLL_INTERVAL);
   }
  
   function pollSummary() {
  
     var iframeSummary = getIFrameSummary();
     var urlSummary = getURLSummary();
     var inputsSummary = getInputsSummary();
  
     if (urlSummary != inputsSummary) { // URL changed, e.g. Bookmark
       setInputs(urlSummary);
       setIFrame(urlSummary);
       updateSumResult();
     } else if (iframeSummary != inputsSummary) { //IFrame changed,e.g. Back Button
       setInputs(iframeSummary);
       setURL(iframeSummary);
       updateSumResult();
     }
  
   }

One final point: the timer is suspended while the user edits a field, to prevent the URL or IFrame source from reverting the application state[20]:

   window.onload = function() {
     ...
     // Prevent iframe from triggering URL change during editing. The URL
     // change would in turn trigger changing of the values currently under edit,
     // which is why it needs to be stopped.
     for (var i=1; i<=3; i++) {
       $("figure"+i).onfocus = function() {
         clearInterval(pollTimer);
       }
       $("figure"+i).onblur  = function() {
         pollTimer = setInterval(pollSummary, POLL_INTERVAL);
       }
     }
     ...
   }

The application is now bookmarkable, as well as having a working history. It's still not ideal, because if you unfocus an edit field, the figures will revert to a previous state. Also, there seems to be a browser issue (in both IE and Firefox) which means that the document URL isn't updated after you manually edit it. Thus, you can change the URL using a bookmark, but if you change it manually, it will not change again in response to an update of application state.

Alternatives

Occasional Refresh

Some Ajax Apps perform page refreshes when a major state change occurs.For example, A9 uses a lot of “Display Morphing” and ???, but nevertheless performs a standard submission for searches, leading to clean URLs like http://a9.com/ajax. Usually, that's enough granularity, so no special URL manipulation need occur.

"Here" Link

Some early Ajax offerings, like Google Maps and MSN Earth, keep the same URL throughout, but offer a dynamic link within the page in case the user needs it. In some cases, there's a standard link to "the current page"; in other cases, the link is generated on demand. Various text is used, such as "Bookmark this Page" or "Link to this page". It's very easy to implement this, but unfortunately breaks the URL model that users are familiar with, so that users might not be able to use it. Time will tell whether users will adapt to this new style, or whether it will fade away as Ajax developers learn how to deal with Unique URLs.

Want to Know More?

  • Uniform Resource Identifier (URI) Generic Syntax by Tim Berners-Lee, Roy Fielding, Larry Masinter for the Internet Engineering Task Force.
  • Designing RIAs For Search Engine Accessibility by Jeremy Hartley of Backbase.
  • Bookmarks and the back button in AJAX applications by Laurens Holst of Backbase.
  • Ajax History Libraries by Brad Neuberg.
  • Fixing the Back Button and Enabling Bookmarking for Ajax Apps by Mike Stenhouse.

Acknowledgements

The solution is based heavily on Mike Stenhouse's article.

[20] It's probably feasible to do something more sophisticated by tracking the history of URL, IFrame, and application state independently.