I was wondering is there any way to pass an entire structure or array via a url parameter? I think I remember reading that you can but when I try to pass all form fields like this http://www.mysite.com?MyForm=#Form# (I understand that #form# would be the entire form scope structure) it does not work. Am I doing something wrong?
URL values are just strings; they may represent dates and numbers and strings, but the values are actually just strings. As such, you cannot pass ColdFusion structs and arrays in the URL as they are complex values and ColdFusion does not have a built-in way to convert them to simple (string) values. But, of course, there are ways to manually pass ColdFusion structs and arrays in the URL. We have a few options:
We can loop over the struct and add each key-value pair as a name-value pair to the URL query string.
We can serialize the struct / array and add it to the URL query string as single name-value pair.
The first option, doesn't really work for two reasons: One, we might have keys within the structure that conflict with query string parameters already in the url. And two, what happens if the structure has nested structures or arrays - how do we represent that.
Our best bet here is serialize the ColdFusion struct or array for the URL. This will keep all of the struct data contained to a single URL variable and it will inherently take care of any nested arrays or structures.
When passing the ColdFusion struct or array in the URL, we are going to have to serialize it in WDDX data. In order to make this as simple as possible, we are going to wrap this functionality up into a ColdFusion user defined function, SerializeURLData():
<cffunction name="SerializeURLData" access="public" returntype="string" output="false" hint="Serializes the given data using WDDX. Optionally encodes for URL."> <!--- Define arguments. ---> <cfargument name="Data" type="any" required="true" hint="ColdFusion struct or array data." /> <cfargument name="Encode" type="boolean" required="false" default="true" hint="Flag for URL encoded format." /> <!--- Create local scope. ---> <cfset var LOCAL = StructNew() /> <!--- Serialize the data using WDDX. This will convert the ColdFusion data into WDDX standards XML data. ---> <cfwddx action="CFML2WDDX" input="#ARGUMENTS.Data#" output="LOCAL.WDDXData" usetimezoneinfo="false" /> <!--- Check to see if we are encoding the data for URL. If do this here, then the user has to be carful NOT to run URLEncodedFormat() on the returned data (that would be like double-escaping it). ---> <cfif ARGUMENTS.Encode> <!--- Return the encoded data. ---> <cfreturn URLEncodedFormat( LOCAL.WDDXData ) /> <cfelse> <!--- Return the data as-is. ---> <cfreturn LOCAL.WDDXData /> </cfif> </cffunction>
This function takes two arguments: the data and a flag for encoding. By default, the WDDX data will be URL encoded before it is returned. This is important to understand because it means you should NOT manually encode the URL after this data has been included. Doing so would lead to doubly-escaped URL characters which will not decode as expected. Also realize that since we are encapsulating the serialization / deserialization method, we could easily swap this out with a JSON implementation (encapsulation is a wonderful thing).
Once this data is passed to a page via the URL (of FORM scope), we are going to need a way to deserialize it. In order to make this as simple as possible we are going to wrap this functionality up into a ColdFusion user defined function, DeserializeURLData():
<cffunction name="DeserializeURLData" access="public" returntype="any" output="false" hint="Converts the URL WDDX data back into ColdFusion data objects."> <!--- Define arguments. ---> <cfargument name="Data" type="string" required="true" hint="WDDX data (can be URL encoded)." /> <!--- Define the local scope. ---> <cfset var LOCAL = StructNew() /> <!--- When it comes to converting the data from WDDX back into ColdFusion, we have to make sure that it is not URL encoded. If it is NOT URL encoded, then our first character will be "<". If not, then the data is URL encoded and we must first decode it. ---> <cfif (Left( ARGUMENTS.Data, 1 ) NEQ "<")> <!--- Decode the data. ---> <cfset ARGUMENTS.Data = URLDecode( ARGUMENTS.Data ) /> </cfif> <!--- ASSERT: At this point, no matter how the data was passed to us, it is not in true WDDX format. ---> <!--- Convert the WDDX back to ColdFusion. ---> <cfwddx action="WDDX2CFML" input="#ARGUMENTS.Data#" output="LOCAL.Data" /> <!--- Return the ColdFusion data. ---> <cfreturn LOCAL.Data /> </cffunction>
Notice that this data will check to see if the WDDX data is URL encoded. If it is, the UDF will decode the passed in data (back into WDDX) before it deserializes it back into ColdFusion data.
Ok, so let's see this in action. To demo this URL-passing functionality, we are going to create a simple page that passes a serialized struct back to itself via the URL. On the first load of the page, we will create a struct, dump it out, and provide the link. Once the link has been clicked, will deserialize the data and dump it out.
<!--- Kill extra output. ---> <cfsilent> <!--- Param the URL values. ---> <cfparam name="URL.actress" type="string" default="" /> </cfsilent> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>Passing ColdFusion Structs In URL</title> </head> <body> <!--- Check to see if we have our URL data. ---> <cfif Len( URL.actress )> <!--- We have the URL data. Grab the actress data our of the URL and deserialize it so that we can use it in ColdFusion. ---> <cfset objActress = DeserializeURLData( URL.actress ) /> <h4> Recieved Struct: </h4> <!--- Dump out the struct so see can see what we just grabbed out the URL. ---> <cfdump var="#objActress#" label="Actress From URL" /> <cfelse> <!--- We don't have anything in our URL just yet. Let's build a struct which we will then pass through the URL in a link back to this page. ---> <cfset objActress = StructNew() /> <!--- Populate the struct with some data. We are gonig to intentionally include some complex data types to demonstrate that this can handle intricate data sets. ---> <cfset objActress.Name = "Maria Bello" /> <cfset objActress.Birthday = "04/18/1967" /> <cfset objActress.HomeTown = "Norristown, PA" /> <!--- Create an array for some of her movies. ---> <cfset arrMovies = ArrayNew( 1 ) /> <cfset arrMovies[ 1 ] = "A History of Violence" /> <cfset arrMovies[ 2 ] = "Thank You for Smoking" /> <cfset arrMovies[ 3 ] = "The Cooler" /> <cfset arrMovies[ 4 ] = "Coyote Ugly" /> <!--- Store the movies in the actress. ---> <cfset objActress.Movies = arrMovies /> <h4> Pass Struct: </h4> <!--- Dump out the struct so see can see what we are about to pass via the URL. ---> <cfdump var="#objActress#" label="Actress To Be Passed" /> <!--- Create the URL. ---> <cfset strURL = ( CGI.script_name & "?actress=" & SerializeURLData( objActress ) ) /> <p> <!--- Output the link. ---> <a href="#strURL#">Pass the Struct!</a> </p> </cfif> </body> </html>
When we run the page the first time, we get this CFDump output:
... and when we click the link and deserialized the URL-passed struct, we get this CFDump output:
This seems to work nicely. The one issue that we might need to consider with this technique is that the URL has potential to become very long. WDDX is not exactly a space-efficient serialization technique. The XML standard that is uses is quite verbose and when it is URL encoded, it becomes even longer. Using JSON would cut down on this tremendously.
Want to use code from this post? Check out the license.