By Edgar Hassler
On June 8th, 2008
The Interweb is a wonderful place to host complex web applications as long as you’re tethered to it by Cat5 and an ISP that has your back (Communism works out in this scenario as well). But that’s not the world we live in, and in all other cases using web applications from mobile platforms can be a new circle of Dante’s Inferno below that ice one.
The trouble with web applications on mobile platforms is two fold. First, displaying data for mobile devices is a problem in and of itself, for which I will say nothing more about in this article. Imagine users with laptops and tablet-PCs. The second and more interesting problem revolves around maintaining state and managing client server interactions in an environment where spotty network coverage and ambient radio noise are totally messing with your stuff.
In general developers seem to take for granted the following:
- The user is always connected to the Internet
- The user never switches between browser windows during a stateful web process
This list is certainly not exhaustive, but it’s enough for me to fulfill my weekly blog post requirement. The second point is the most difficult for new developers, so lets start there.
On processes that span several pages – imagine something like a long form spanning three pages taking billing information, delivery information, then payment information – it is tempting to simply slap in a session entry to keep your data between page loads and decide when the process is finished. Most multi-page ordering systems work this way, and it’s often not a problem for their users, but imagine this contrived counter-example:
Edgar W. Developer goes to popularOnlineRetailer.com to buy speakers for his office. He’s stolen the company credit card, has put the speakers in his web shopping cart and has started the checkout process. Then Edgar decides that good deal on hard drives he saw is too good to pass up and wants to order one to his home with his non-stolen credit card. He opens a new window (in the same browser), adds the hard drive to his cart, checks out, notices the speakers, removes them from his cart, completes the process, then closes the window. Now he finishes his original order of the speakers, but when he clicks “submit” something goes terribly awry. His order doesn’t exist anymore.
Edgar would clearly just start over with his criminal intentions on speakers, and most of us would brush this off as a minor inconvenience, but if your users have to order something on the order of one hundred speakers a day through an interface requiring similarly long information requirements and you don’t address this issue, then you may face an angry mob of data entry operators whose manager sold you out and gave them your address. It happens.
What happens, specifically, is that the session identifier is attached to the user’s browser as a cookie, and cookies are sent by every instance of that browser which accesses the issuing domain. Thus, by using multiple instances, the user has wandered into a use case that was unanticipated. There is no way to keep track of instances of the browser strictly from the header information – not even if you allow an infinite list of pages visited, the arguments submitted to those pages and an authoritative map of page relations. The proof of this is left as exercise.
If developers are aware of this problem from planning or testing they might make the web form store it’s entire state at each step. This is a good solution in most cases, and it’s something I’ve done many times in the past. You can even keep all of the form’s values in the session in an array of data arrays and just pass the key to your data in the web form itself. But sometimes this is not enough – either because your data is large or difficult to validate, or because there is so much a variety of such forms that lazy developers like myself just don’t want to do this each time. In fact, our ability to tie session data to a browser instance may make or break our application as its complexity grows.
Handling Per-Instance State
window.open. Guy has to be tricky.
onclick event to links. When a user right-clicks and opens a link into a new tab or window the anchor element’s
onclick event does not fire. Thus we can add behavior to this event that will tell the server that this is a specific pre-existing instance of the browser. We can pass an instance id to the server that links our request to a specific place in the session.
How we pass this id is not trivial. We can append the instance id to the URL as an anchor, such as
index.php?arg1=val1&arg2=val2#instance_id=123456, then, on the receiving page, we can change the location of the browser to not include the instance id. Since it is a local anchor the page will not reload and bookmarks and links will not be relative to the local anchor. But there is a slim window of time between page load and script execution when bookmarks could happen. Instead of appending the local anchor to our URL, we could have the
onclick event create a form which targets the link location and passes via
onclick event, then the whole environment is reinitialized in the new instance (with a new instance id). This is a good solution for processes that are tightly bound together. Google’s gmail is a good example of this type of solution.
Similar to our method of passing the entire state of the form from page to page above, we can store the state of our more dynamic pages in the URL of the browser instance so that bookmarking and opening links into new windows will carry their necessary stateful data with them. Take a look at YUI’s Browser History Manager.
The YUI Browser History Manager is a utility designed to facilitate the creation of web applications in which the navigation buttons are fully functional and in which broad aspects of an application’s state — what panels are open, what tabs are active, etc. — can be bookmarked.
You can read more about YUI’s Browser History Manager at YUI’s development site.
Handling Flaky AJAX Connections
The AJAX approaches above are related to my first point – that web developers don’t consider connection failures when they design web applications. In order to gracefully handle these issues you need to structure and organize your pages’ interaction with the server via AJAX, and this is almost always a queue or a stack of your AJAX calls. Without loss of generality, assume it’s a queue. We want the queue to recover from a connection failure by notifying subscribed elements that a connection failure has occurred, retry the connection after some interval, and then notify subscribed elements when the connection has been re-established. Normally, you would pass a function to handle failure of your request, but in this design we do not accept failure – we assume that it’s only a matter of time before we achieve success. If the user leaves the page before our queue resolves then their data will be lost.
The second iteration of our implementation had to address the maniacal impulse of our users to close the browser window anyway. In this situation we had to find a place to store data on the user’s machine, and there are three ways of going about this.
If cookies are not enough, there are ways of storing state with Adobe Flash and with Google Gears. Both Flash and Gears are proprietary products that must be installed on the user’s system, but 90% of systems have Flash already, and Google Gears provides a richer API with fewer restrictions for storing data than does the Flash solution. Both methods are supported by the Dojo project’s Dojox Offline toolkit. Rather than plagiarizing their documentation, let me simply encourage you to read it.
The Flash and cookie solutions are unable to restore the user’s access while they’re offline. Keeping the window open allows them continual access, and it is a feature of Gears that you can stash your pages in it for offline access – allowing the user to re-open the application while offline. Adobe and Microsoft have products on the shelf to accomplish similar functionality, but they require more installation, and at that point why not just use Java web launch which is free and gives you more power anyway.