<charlie griefer>

*tap*tap*tap* is this thing on?

dealing with form and URL variables

may 30, 2008 at 10:00am in ColdFusion

it's not terribly uncommon to have a page that is expecting a particular variable, but the page may not know if that variable will be in the form scope or URL scope. case in point might be a resultset that does pagination. on the first submit, the variables would likely be in the form scope. as the user pages through the results, it's entirely possible that the variables would be in the URL scope.

the most straightforward way to handle this would be:

  1. <cfparam value="myNewVar" default="" />
  2. <cfif structKeyExists(URL, 'myVar')>
  3. <cfset variables.myNewVar = URL.myVar />
  4. </cfif>
  5. <cfif structKeyExists(form, 'myVar')>
  6. <cfset variables.myNewVar = form.myVar />
  7. </cfif>

it checks both scopes for the var, and if it finds the variable in either, it assigns it to a new local variable. it's straightforward, but not particularly "clean".

the first place that i saw this handled more elegantly was in fusebox. fusebox takes both form and URL scopes and places any variables found there into the attributes scope. in the case of a collision (if a variable exists in both scopes), you can specify which one should take precedence. cleaner.

other frameworks take a similar approach. both model-glue and coldbox put form and URL vars into the event "scope".

so that's all well and good... but what if you're not using a framework? the question came up on cf-talk, and my answer was to use a custom tag that mimics the way fusebox does it. then 2 people (aaron rouse and dominic watson) demonstrated the most ridiculously simple/straightforward way to handle this. a definite headslap "duh charlie" moment. here's the code:

  1. <cfset myAttributes = structNew() />
  2. <cfset structAppend(myAttributes, URL) />
  3. <cfset structAppend(myAttributes, form) />

that's it. no conditionals, no custom tags.

so... what's going on here?

as of coldfusion mx (6.0), all scopes are structures. so we first create a new empty structure. think of this as a container that we'll use to hold the data from both the form and URL scopes/structures. using the built in structAppend() function, we're simply adding URL and form variables into that container. now, in much the same way that fusebox uses the 'attributes' scope, and model-glue/coldbox use the 'event' scope, we can refer to the variables in the myAttributes 'scope', as it will contain data from both form and URL.

one thing to point out is that structAppend() takes an optional 3rd argument (overwriteFlag). by default, this is set to 'true'. so similar to way that the frameworks allow you to specify which scope should take precedence in the case that the same variable exists in both, the order in which you write your structAppends() is very important. if you want the URL scope to take priority, append the URL scope last. if you want the form scope to take priority, append the form scope last. whichever scope/struct is appended last will effectively overwrite the existing variable, giving that scope the "higher priority".

comments (Comment Moderation is enabled. Your comment will not appear until approved.)add comment
Matt Quackenbush's Gravatar
Matt Quackenbush
# May 30, 2008 @ 3:39 PM
 
Moral of the story: use a framework! LOL

Seriously, I am definitely an advocate of using frameworks, but there are times when they simply don't fit the bill. In those cases, this is an excellent way to handle your variables "merging".
TJ Downes's Gravatar
TJ Downes
# May 30, 2008 @ 4:19 PM
 
Nice, Charlie. Thanks! I been using the initial hack in most cases simply because it was the fastest thing that came to mind and only had one or two variables which needed to be converted.However, it's still more code than the way you are pointing out, so I'll keep this in mind for future use.
Peter Bell's Gravatar
Peter Bell
# May 31, 2008 @ 11:03 AM
 
Personally I have a simple "Input" business object with a load() method and generic getters and setters. So for me I do something like:

Input = beanFactory.getBean("Input");
Input.load(form);
Input.load(URL);

Benefits of this approach is that if I want to have any logic around URL/form (input) properties, I can encapsulate it in Input.cfc. o for example, if there is no CatID, I might want it to return the default category ID for the application - say category 12. That kind of logic can get encapsulated in Input.cfc.

That said, not quite as easy as your approach which is what I used to use!
Sebastiaan's Gravatar
Sebastiaan
# Jun 3, 2008 @ 6:43 AM
 
Hi Charlie,

no offence, but I've used this since 2001 ;-) In CF 4.5 and 5 as a custom tag (300 lines long or so) and since MX those three lines. I have never looked back!

But better late than never! Good writeup though for the good practice-searching cf-programmer ;-)


 
© 1948-2008, charlie griefer - design based on *Limelight* by www.mitchinson.net
blogCFC was created by raymond camden. this blog is running version 5.9.002.

CSS | XHTML