In the past you used to give and receive advice that keeping form state in a session was a valid way to approach the problem of forms that span several pages. It's no longer sound advice, and it hasn't been for a while.
Even before tabs became popular, a few of us were right-clicking links and opening-in-new-windows. It's a nice way to get more things done quicker: loading another page while waiting for the first one to load so that you are doing something all the time, rather than spending a significant amount of time waiting. It even works well in web
applications - not just general surfing.
But back then, and even until relatively recently when tabs were confined to the realm outside of Internet Explorer, the amount of people who used the approach in web applications was small. So small, in fact, we felt we could be lazy and hold what should have been repeated as hidden form fields within sessions instead.
Now Internet Explorer has tabs, and people are starting to use them. That changes things as people start to internalize the concept and use the productivity-boosting feature in ways that break our applications. Now, instead of presenting a list of customers and expecting our user to click one and edit the associated records until he's complete; our users are opening three or four customers (customerA through customerD) at a time in separate tabs. If you had stored form state in the session, when they think they are editing customerA, they will in fact be changing the record for customerD with a form that is pre-filled with values from customerA. Oops.
Luckily, the fix is relatively easy: just start storing the state in the form instead of the session. Of course, that's only "easy" if the path through your forms is linear. What about the case where many different forms can be traversed in just about any order, sometimes they appear based on what other forms said, and some are required while others can be skipped?
It's still easy if you've got automated tests that cover the possible scenarios. Just strip out the session and add the data to the forms until the tests pass. If you don't have tests, and you are not familiar enough with the different paths to create 100% coverage (or its been so long you forgot the paths), it's not looking good for you. Chances are, you don't have tests. This
is a relic from the days
before tabbed browsing, and who was doing automated testing then?
But, there is still one way out for you: inject the needed fields into your form after it's been crested but before it has been sent to the browser. I've not yet come across a situation in Rails or Javaland where I needed to do this, so I haven't investigated how to do it there (and Rails is new in the first place, so its unlikely it would be a problem in any application if you've thought to avoid the practice now that tabbed browsing is popular). But in ColdFusion, it is easy. Since Application.cfm is run before the request, you can check what page is being requested from there and intercept it if the page is one on which you need to do this processing. Wrap a cfinclude of that page in cfsavecontent tags, and now you have a string of what will be sent to the browser. Just find the spot you need to insert your data, insert it, and output the resultant string to the browser.
In my case it was especially easy because fortunately, we were only storing the
id
of the record in the session and I could be sure it needed to be in every form on the page. Thus, my code looked like this (in Application.cfm):
<cfif cgi.script_name is inMyListOfScriptsToIntercept>
<cfsavecontent variable = "toBeOutput"
>
<cfinclude template = "#cgi.script_name#"
>
</cfsavecontent>
<cfset toBeOutput = replaceNoCase(toBeOutput,"</form>
"
,"<input type="
hidden" name="
id" value="
#form.id#" >
</form>
"
,"all"
)>
</cfif>
If you can get at the data before it is output to the browser, this strategy works well. However, I don't like the magic nature of it when you'll be editing the individual files later and wondering why in the world it works. It's only a hack, so expect to
learn the application well enough to test it and put the right fix in later. But, this works well as a temporary solution as you can be more sure it works than you can be sure about the flow of the application when it gets quite complex.
Hey! Why don't you make your life easier and subscribe to the full post
or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate
wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!
Leave a comment
Ack, you're right!
We typically store search parameters in the session so when a user clicks "back to search" link it takes them right back to their last search, back to the last page they were on. I have a really nice custom tag that takes care of all this for me and I don't want to give it up!
Posted by
Joe Zack
on Jul 19, 2007 at 12:00 PM UTC - 6 hrs
Hi Joe - No doubt it is nice and easy to do that for us programmers - but we are programming for the users! I find storing search terms especially troubling as I like to perform different searches at the same time to see what else is available to me.
A couple of classic examples for me:
1) searching for classes to take at school - I might do a search for a specific class I know I want to take, and while that one is working, perform a broader search for Computer Science classes, and also another search for business classes.
2) on various travel sites - I don't yet know where I'm going, but I know it'll be in one of 3 places, so I'll search and browse the results for all 3.
I've been stumped in both examples by holding search terms in the session, so I'm definitely avoiding it in my own code now.
I just got a note from users talking about strange behavior and finally tracked it down to them using tabs when we never considered that case in building the app and stored info in the sessions (easiest route) that should have been replicated into the forms themselves... hence, this post. =)
Posted by
Sam
on Jul 19, 2007 at 12:13 PM UTC - 6 hrs
We give each form a GUID and use that as the session key. Then we only need to propagate the GUID in the hidden form state.
Posted by Jaime Metcher
on Jul 19, 2007 at 05:23 PM UTC - 6 hrs
Jaime - Good idea I hadn't thought about. I see it would work fine if you store every form they visit (and each instance of the same form) in the session, but that seems equal to the trouble of reposting through the form itself.
Why did you decide to do it that way, out of curiosity?
Posted by
Sam
on Jul 20, 2007 at 07:10 AM UTC - 6 hrs
I have done the same thing as Jaime in the past... I never felt very comfortable with it as it can leave a lot of junk in the session till it times out, but it always felt easier than figuring out which form elements to add.
Posted by
Ben Nadel
on Jul 20, 2007 at 12:28 PM UTC - 6 hrs
Leave a comment