The Theo Spears Blog

Blogging Considered Harmful (Considered Harmful)?

Storylines: Seperating flow pages

First posted 2006-06-30 00:00:00.000004+00:00

With this many tabs where do you start?

If you are presented with too many options at once it is very hard to pick the one you want (tab bars are especially bad in this respect, see Interface hall of shame for why). This is why Apple tries to have as few toolbar options as possible and Microsoft redesigned their start menu for windows XP. Whilst we can sometimes avoid this complexity often applications do need lots of information from the user. A popular way to collect this information is using wizards.

Wizards allow the user to provide a few bits of information at a time (often only a single choice between two options). In this respect wizard interfaces are rather similar to pre-GUI interfaces which prompted for a responses one by one. More relevantly however, the wizard approach (though not the metaphor) has been adopted for the web, with complicated web forms (particularly web site registrations or shopping carts) using such a sequential approach.

The topic I wish to address here is how such wizard-like web forms can be implemented cleanly in conjunction with MVC style web frameworks such as Ruby On Rails, cakePHP, and Footpath (my own framework, under heavy development, and the reason for these blogs). The problems to address are

Keeping track of information from all stages

One of the "features" of http that makes creating web applications challenging is that it is stateless (though AJAX is beginning to change that). There are a few ways that can be used to pass state between requests:

  1. Cookies (and sessions)

  2. Hidden fields

  3. URL encoded data

Before considering which of these is best let's make explicit a few of the constraints of using an MVC style design. (These are not absolute constraints, but solutions which break them are likely to be ugly. at least with current frameworks).

  1. A specific url corresponds to a specific controller and view.
    For example the url /user/personaldetails could be a form requesting a user's contact details and age. Allowing a url like /user/register to correspond to one of a number of possible forms (e.g. username/password or address) typically requires some rather ugly code in the view template.

  2. The same url as shows a form is used to process that form.
    This is a result of the above. If the user has entered invalid data the form will need to be shown to them again, i.e. they need to be looking at the same url. Also from a design point of view it makes sense for there to be one controller action responsible for any view, i.e. for set up and processing. It would be possible to have two actions for any view, e.g. register_prepare and register_submit, but if there are errors the submit function is often involved in telling the view to display them.

This means after a successful form submission we need to forward the user on to a new URL so a new form can be displayed. For example consider the sequence below

In theory it would be possible remove stages 4 & 5, making the redirect a purely server-side operation. Whilst from a technical perspective this is better, it would be potentially very confusing for the user, particularly if the form and the page were very different, imagine being asked to fill in your credit card details on a page called /forum/publicfeedback!

Returning to the problem of keeping track of information, in any situation where the user submits a form this is easy, we just include hidden fields (which we can validate using the techniques outlined in a previous post) for the values and pass them on each time. However we cannot do this with stages 4 & 5 as there is only a get request. We could urlencode the information, however this is a) ugly and b) doesn't work with very much data. We also cannot use simple sessions/cookies as the user may have multiple browser windows open, thus may potentially be working through the wizard multiple times at once.

The only option this really leaves (short of really ugly hacks with javascript) is an identifier in the url that maps to a collection of data on the server. As with any such mapping this raises the question of lifetime - if the user runs away half way through how long should we give them to come back? That depends on whether we are just using this map, or also using hidden fields. If we only use the server side map when doing a redirect and use hidden form fields the rest of the time then we can throw away the information as soon as the redirect is completed. Well almost - if we throw away the information the user will no longer be able to refresh the page, so we might want to keep it for an hour or so. We can definitely throw it away if the user finishes filling out the form before this time though.

Now, quick readers will have picked up on an inconsistency in what I am saying. (I'm kidding myself, quick readers will probably have noticed several.) Under MVC the controller is meant to have processed the data coming from the view, so how is it that this data still needs to be passed on? Who is going to process this data at the end of the day? The answer here is that actions associated with a single controller action (e.g. /user/personaldetails) are correct and fine for modifying a single object (or object type) or a collection of closely related objects. However when the interactions between several objects need to be regulated we need a higher level controller, a metacontroller if you like. This fits in neatly with our second stage.

Controller re-use

There are many controllers we can imagine fitting into any number of wizard-like html page sequences. For example logging in/registering is a needed step for almost any operation on most sites, it would be nice to be able to seemlessly integrate these (i.e. register for an account half way through your checkout without having to restart the checkout) with any number of wizard-like sequences (hereafter storyboard). This means a particular controller shouldn't know where to redirect to after it has finished its work.

So who should know?

To combine individual controllers into a storyboard we can introduce a new class, a metacontroller or storyboard class. Each storyboard will have a url like a standard controller action, however rather than displaying content it will specify a sequence of actions. It will also be responsible after each action has completed to specify what information should be passed on to future steps. The framework should provide facilities to make these easy to do. Following the principle of encapsulation, each controller should define what information is available for the storyboard to retain.

What about passing information the other way though? For example a storyboard might involve editting a certain object part way through, and the relevant controller will need to know which object to edit. Here the simplest answer is for the storyboard to make use of the existing input parameters each controller defines, specifying them through the url. Obviously the framework should provide helper functions to make this easier, so the storyboard author does not have to manually create urls.

Conclusion

Although there is no perfect solution to the problem of retaining data for wizard-like or multipage forms using a combination of hidden form fields and session values a reasonably good solution can be found. Such wizard-like interfaces cannot be defined simply through the existing MVC elements provided by existing frameworks, but could be implemented in a more simple fashion using the idea of a coordinator or "storyboard" class.