Passing ColdFusion Structs Through Form Submissions

Posted July 2, 2007 at 7:30 AM by Ben Nadel

Tags: ColdFusion

During Selene Bainum's CFUNITED presentation on using Structs and CFCs, she discussed the fact that while you can have structs in ColdFusion, you cannot submit structural data via a form submission. While this is technically true, it occurred to me that this is something that would be way too easy to "fake." Below, I will create a Contact structure, submit it through the Form scope, and get it back as a full structure.

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <cfparam
  • name="FORM.Contact"
  • type="struct"
  • default="#StructNew()#"
  • />
  •  
  • <cfparam
  • name="FORM.Contact.Phone1.Type"
  • type="string"
  • default=""
  • />
  •  
  • <cfparam
  • name="FORM.Contact.Phone1.Number"
  • type="string"
  • default=""
  • />
  •  
  • <cfparam
  • name="FORM.Contact.Phone2.Type"
  • type="string"
  • default=""
  • />
  •  
  • <cfparam
  • name="FORM.Contact.Phone2.Number"
  • type="string"
  • default=""
  • />
  •  
  • </cfsilent>
  •  
  • <cfoutput>
  •  
  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>
  • <head>
  • <title>ColdFusion Form Deserialization</title>
  • </head>
  • <body>
  •  
  • <form
  • action="#CGI.script_name#"
  • method="post">
  •  
  • <!-- Phone 1 is the home phone. --->
  • <input
  • type="hidden"
  • name="contact.phone1.type"
  • value="Home"
  • />
  •  
  •  
  • <label for="contact.phone1.number">
  • Home Phone:
  • </label>
  •  
  • <input
  • type="text"
  • name="contact.phone1.number"
  • id="contact.phone1.number"
  • value="#FORM.contact.Phone1.Number#"
  • />
  •  
  • <br />
  •  
  • <!-- Phone 2 is the cell phone. -->
  • <input
  • type="hidden"
  • name="contact.phone2.type"
  • value="Mobile"
  • />
  •  
  •  
  • <label for="contact.phone2.number">
  • Mobile Phone:
  • </label>
  •  
  • <input
  • type="text"
  • name="contact.phone2.number"
  • id="contact.phone2.number"
  • value="#FORM.contact.Phone2.Number#"
  • />
  •  
  • <br />
  •  
  • <input
  • type="submit"
  • value="Update Phone Numbers"
  • />
  •  
  • </form>
  •  
  • </body>
  • </html>
  •  
  • </cfoutput>

In the above code, notice that I am CFParam'ing the Contact structure within the FORM scope. Then, the really important part is that when I output the form fields, notice that name of the form fields are using the full struct notation of the Contact within the FORM scope. Doing this alone will not cut it. If I submit the form in this style, I will get these very same form fields in the FORM struct:


 
 
 

 
Passing ColdFusion Structs Through Form Submissions  
 
 
 

As you can see, the form field names contain the full struct paths. This doesn't help us as they are still flat text values, not actual ColdFusion structures. The remedy for this is easy. All we have to do is put a little bit of code in our Application.cfc/cfm that checks for FORM scope values and cleans them up:

  • <!---
  • Check to see if the FORM scope is empty. If it is
  • not empty, then we have a form submission that we
  • might have to clean.
  • --->
  • <cfif NOT StructIsEmpty( FORM )>
  •  
  • <!---
  • Loop over the form keys. We are looking for
  • keys that contain a period which would denote
  • an embedded structure.
  • --->
  • <cfloop
  • item="Key"
  • collection="#FORM#">
  •  
  • <!--- Check for struct notation. --->
  • <cfif Find( ".", Key )>
  •  
  • <!--- Get the value of the form field. --->
  • <cfset Value = FORM[ Key ] />
  •  
  • <!---
  • Delete the key from the form. Remember,
  • we don't really want this key, we want
  • the structure that it represents.
  • --->
  • <cfset StructDelete( FORM, Key ) />
  •  
  • <!---
  • Now, we want to param the structure/value
  • within the FORM. Since ColdFusion can store
  • data into dynamic variable names, all we
  • have to do is refer to the same exact value
  • using dot notation rather than struct notation.
  •  
  • For example, the key, "contact.phone1.type",
  • the "FORM.#Key#" would become:
  •  
  • FORM.contact.phone1.type
  •  
  • Since ColdFusoin will create nested structs
  • if they don't exist, this will create both
  • the Contact and Phone1 struct into which it
  • will then store the Type value from the
  • submitted form field.
  • --->
  • <cfset "FORM.#Key#" = Value />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • </cfif>

There's really not a lot going on in this code. If there are any values in the FORM scope, the algorithm loops over them looking for FORM keys that have periods in them. Assuming that these periods stand for ColdFusion structure notation, the algorithm deletes them from the FORM scope as they are, but then saves them right back in using dynamic variable name. The real magic is in this line:

  • <cfset "FORM.#Key#" = Value />

ColdFusion allows for us to use dynamic variable names in such a way that we can use the FORM key:

"CONTACT.PHONE1.NUMBER"

... to create a dynamic variable name:

"FORM.CONTACT.PHONE1.NUMBER"

Into which will store the deleted FORM value. Now, since ColdFusion will create nested structs that do not yet exist (a feature which I usually feel is very bad form to use), it will recreate the submitted Contact struct data into a FORM-scoped Contact struct:


 
 
 

 
Passing ColdFusion Structs Through Form Submissions  
 
 
 

Pretty easy hack. The only thing that is strange is that the FieldNames key that gets passed through with the form submission has all the large field names (removed prior to CFDump for better display). Of course, if you actually use the FieldNames key, you should probably re-think the way you are performing your actions.

At first, I was concerned that the dot notation within the form field names would break things like the Label tag functionality or DOM traversing. However, since pretty much anything in Javascript can use quoted names and IDs, then all you have to do is be a little bit more careful about how you refer to DOM values and everything should work just fine.



Reader Comments

Jul 2, 2007 at 10:26 AM // reply »
111 Comments

Dude - even your banners are X rated . . . "ColdFusion 8 - playing with it feels so good" :->

Nice technique. I'd also run into this issue but hadn't thought to write the code to parse form variables for periods. Nice!


Jul 2, 2007 at 10:57 AM // reply »
11,238 Comments

@Pete,

Glad to help out.

And, try not to read too much into those ads... I am just saying ColdFusion 8 is really awesome, nothing more ;)


Jul 2, 2007 at 11:14 AM // reply »
111 Comments

Well, I didn't think you were starting an escort business, but on the other hand the rather fetching lady in the left hand corner of the ad suggested that the performance boosts provided by CF8 might not go to waste . . .


Jul 20, 2007 at 4:23 PM // reply »
2 Comments

This technique is awesome, for master-details interfaces it simply rocks...

Using a bit of javascript it's really easy to add order rows, affecting the name of the form inputs in it so it matches this naming convention then submit the whole thing just to loop over rows server-side.

BTW, your blog is the best I've ever seen on CF... it's almost a must-read for anyone in the CF business.

...even the ads are cool... ;)


Jul 20, 2007 at 4:44 PM // reply »
11,238 Comments

@Patrick,

Thanks a lot for the very kind words. Always glad to help :)


Jul 23, 2007 at 7:26 PM // reply »
2 Comments

Here is an updated version that returns an array for, say, the order lines of an order.

I basically use your code to get some structures created, then I loop over the FORM collection to check which struct element is a struct itself, then convert it to an array.

We do it 3 levels deep: order.orderlines.(qty,itemID,etc)

I haven't found a way to do it in one cfloop pass... :(

<cfloop item="key" collection="#formData#">
<cfif Find( ".", key )>
<cfset Value = formData[ key ] />
<cfset StructDelete( formData, key ) />
<cfset "formData.#Key#" = Value />
</cfif>
</cfloop>

<cfloop item="key" collection="#formData#">
<cfif isStruct(formData[Key])>
<cfset newArray = StructKeyArray(formData[Key])>
<cfloop from="1" to="#arrayLen(newArray)#" index="i">
<cfset newArray[i] = evaluate("formData[Key].#newArray[i]#")>
</cfloop>
<cfset StructDelete( formData, Key ) />
<cfset "formData.#Key#" = newArray />
</cfif>
</cfloop>


Jul 24, 2007 at 7:50 AM // reply »
11,238 Comments

@Patrick,

That looks cool. Don't worry about trying to do it all in one pass - you need to build up your struct to get the array of keys first, so I don't even think one pass would be possible.


Aug 6, 2007 at 10:15 AM // reply »
2 Comments

Ben,

I've encountered this issue myself and I have tested a solution using wddx packets. I use wddx to transform the struct and then put that into a form variable. Then on the form action page I transform the wddx package back into a struct. This has worked well for multiple nested structs.

Have you seen this? Or encountered any issues with this.

-RRS


Aug 6, 2007 at 1:48 PM // reply »
11,238 Comments

@Rudi,

I have not played around with form-based WDDX at all. Sounds like an interesting approach. My only problem with it is that I am not sure it is as intuitive as just using dot-notation.


Dec 8, 2011 at 9:37 AM // reply »
1 Comments

Awesome! I combined this lesson with the one about using the Cf 9 multi file uploader (http://www.coldfusionjedi.com/index.cfm/2009/11/20/Quick-example-of-jQueryColdFusion-9-multifile-uploader) to create a photo gallery uploader. It was exactly what I needed.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools