“A casual relationship with reality”
Categories: Technical, Web Standards
I've talked about Ajax before, and I'm going to start talking about it more now that I'm working on a personal project revolving around it. So that means I'm going to get pretty technical here, feel free to skip these entries if you're not interested in that.
Anyhow, one of the big "problems" with Ajax is that it somewhat breaks a couple of the standard interaction models that the web is built on. Specifically, Ajax web applications have trouble with the Back button and with bookmarks. Personally I don't think either of these are particularly serious, though they do have to be dealt with and no perfect solution yet exists.
I think I've come up with what would be the correct solution, however it's not something that can be done yet, because it requires the the browser manufacturers (specifically Microsoft, Mozilla, Apple and Opera) to make a change to how the XMLHttpRequest object works.
Let me describe the general problem before I get into the solution. In traditional web applications, you generally know you can hit your back button to return to where you were previously, and you can bookmark your current page to return to it later. We all know this, and we've come to expect it. For instance, at Amazon, if I perform a search, I know I can bookmark the results page and be able to return to it at a later date, even if I've done other searches since then. Similarly, if I click on an item to go to its product details, I know I can hit the Back button and return to my search results.
However, in an Ajax application, things aren't so simple. The reason I can back-button and bookmark to my heart's content is because each of these things are unique pages. In other words, there is a specific URL for this specific search, and there is a specific URL for that specific product. Having a unique URL is what allows me to return at a later date to the same resource regardless of whatever interactions have occurred in the meantime. But the whole point of Ajax is to be able to take actions on a given web page, such as searching or viewing product details, without having to leave the page or reload it. The whole point is to not have a unique URL for any given application state.
So that means no bookmarking and no back-buttoning. For instance, if you perform a search on this page using the search box on the right, you can't bookmark the search results because there is nothing to bookmark. Similarly, if you perform one search and then perform another, hitting your back button won't take you back to the previous search. For some, this is a huge problem, but as I've said, I think it's a minor one that does need to be addressed.
Basically, Ajax works by having Javascript fetch data from a web server without loading a new page. There are actually several methods for doing this, but the most common, and the most forward thinking, is the XMLHttpRequest object. My idea, which I'll call "URL Substitution" focuses solely on this method and utilizes (but does not depend on) one of XMLHttpRequest's most underutilized features, the ability to set a custom HTTP header.
Allow XMLHttpRequest calls to conditionally replace the URL in the user's address bar with the URL being fetched.
Read more about "URL Substitution"
It's really as simple as that. When issuing an XMLHttpRequest, an Ajax application should be able to set an option that says whether or not the URL in the address bar should change to reflect the location of the request. Actually, technically it wouldn't just be replacing the URL in the address bar. Specifically it would be changing what the browser considers to be the current location of the page, as if it were actually going to that location. So, it changes the address, adds the new location to the browsers history, and sets whatever internal state it needs to be able to recognize that location as the bookmarkable URL.
In principle, this isn't that different from one of the currently circulating solutions to this problem, which is to set the page's hash value (the part of a web address following the "#" symbol). This allows a newly bookmarkable (and back-buttonable) state, and since Javascript can read the value of the hash it can perform whatever manipulations are necessary to return the page to the proper state.
The problem with that solution is that it's not that flexible and returning state can only happen after the page has loaded. The inflexibility results in lots of "workaround" code to implement the functionality across the various browsers. The state problem means you can't author the page to allow the server to respond with an initial state based on the hash value. Of course, that's largely outweighed by the big entry in the "pro" column that says "it works now".
That is, of course, the biggest problem with URL Substitution: It doesn't work now. Nor will it until browser manufacturers add this functionality to XMLHttpRequest, which isn't likely to happen unless this post magically gathers some serious mindshare and the idea itself is deemed acceptable by those with the influence to bless it as "not considered harmful".
I mentioned that this requires the use of XMLHttpRequest's ability to set a custom HTTP header. The reason this is important is because simply changing the address is not enough. If you were to manually go to a URL that normally serves as an Ajax-compatible resource, you'll be greeted with an XML page or some Javascript code or a fragment of HTML. You pretty much will never receive an HTML page that can be used on its own. So what we need to do is tell the server whether or not the request is being made as part of an Ajax request or whether a browser is expecting to receive a full-fledged HTML/XHTML page as a result. Frequently this is done by appending a parameter to the querystring, usually something like "&ajax=true". This works fine for the usual purpose, which is to differentiate whether a given request is coming via Ajax or from a traditional form submission. In this instance, we need the exact same URL to behave differently depending on the calling context.
The only way I can see to do that is to set a custom header using the setRequestHeader() function. The beauty of this is that the only way that header can be set in a browser is by Javascript. Going to the same location via a bookmark will send the default headers the browser would ordinarily use. So the code that responds merely has to check for the presence of the custom header and respond accordingly.
Although there is an important subtlety here: When using the Back button, the browser must not send any custom headers even though technically the current state of the page was created by using it. This also means that the common performance optimization of storing previous pages in memory can't be used if a custom header is set, though that's not such a big problem because navigating to a previous page using the back button should trigger a full refresh anyway.
The upside of this technique is that setting custom headers opens up more options in generating responses and can even be used now in place of "&ajax=true", a method I've accepted as often necessary but never really liked.
The final subtlety of this method is to reiterate that URL Substitution should only occur at the discretion of the code making the remote request. This is to ensure that only worthy changes of state become part of the browser history and to prevent applications that make frequent calls (chat applications, for instance) don't fill up the history to the point where it's unusable.
I think this is an elegant solution. One of the biggest advantages of it, should the capability ever exist, is that because the server is doing most of the heavy lifting, it should be easy to incorporate it into browsers that support the feature while letting other browsers operate with regular Ajax functionality minus the URL Substitution.
That being said, I do question whether this is actually necessary. I said earlier that the back-button/bookmark issue needs addressing, and I stand by that, however that doesn't mean we necessarily have to change the behavior or technical methodologies of Ajax pages. I think that as Ajax grows, we're going to see users start to "get" the whole concept in a much more complex manner. More precisely, I think users will (and already are) developing a good mental separation between interactions within the page and interactions between pages. In other words, they will have an instinct as to what is "bookmarkable" and what is not. The reason for this is that they, for the most part, already have a good understanding of this right now.
Returning to Amazon.com as an example, most users know that after adding an item to their cart, they can't bookmark the resulting page and expect normal behavior from it. We all pretty much understand that the result of an action that changes generally static things like shopping carts is not repeatable through a bookmark. We understand that we can bookmark search results but we can't bookmark "Add to Cart", even though in both instances, the server is performing a dynamic action initiated by us. We understand this because through a mix of convention and metaphor, the distinction between permanent and temporary actions is made fairly clear. The technical term here is idempotent, which roughly means "repeatable".
The only time users run into trouble with their mental model of idempotence is when sites operate counter to this expectation. I think that this understanding can extend to Ajax as well and provides excellent guidance in one of the fundamental questions developers must answer when designing the architecture of an Ajax web application: When do we use Ajax and when do we use the traditional request-response loop? If the foundation of that answer is based on whether the action should be bookmarkable or navigable, then it's entirely likely that good architecture can solve the bookmark/Back button problem better than any technological solution.
Additionally, I think bookmarking or re-navigating are actually something that the user will infrequently expect to be able to do in most complex Ajax applications. The reason for this is that Ajax is meant to bring something approaching desktop applications into the webpage model, and desktop apps don't generally have bookmarking or back-buttoning (and when they do, as in the case of PDF viewer or the Mac OS X finder, they're used very infrequently). So again, the mental model most users already have helps us circumvent the desire to bookmark a page except where their instincts "tell" them they should be able to.
But nonetheless, this whole Web 2.0 thing is only at the toddler stage right now, so I think it's reasonable to assume that future web applications will fuse desktop and web interaction models even more than they do now, and if that occurs, we will need an actual solution like URL Substitution.
One final note: This problem has been discussed a lot, and by people who are not only smarter than me, but at the very front lines of Ajax, which I am definitely not. So it's likely that this solution, in whole or in part, has been suggested (and possibly even rejected) already. If I'm taking credit for an idea someone has already kicked around, I apologize in advance, and kudos for being ahead of the curve.
posted by Mark Kawakami at December 13, 2005, 01:12 AM // permalink // (2) Comments