Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Scott Stroz and Vicky Ryder and Ray Camden

ColdFusion & AJAX: Converting ColdFusion Objects to Javascript Objects

By Ben Nadel on

As I talked about earlier, I am starting to get into AJAX and, as a hands-on kind of guy, I do my best learning from building. So, now that I have my XmlHttpRequest wrapper, I have created some functions for the server side of things that convert ColdFusion objects into Javascript objects that are going to be passed back to the browser.

If you remember from before, my AJAX wrapper is designed to evaluate the data returned from the server via the XmlHttpRequest (ex. eval( objConnection.responseText )). Javascript is a wicked-sweet interpreted language and I have found it very convenient to return Javascript code that will be evaluated into objects that most closely resemble their ColdFusion counterparts. The following methods are used to convert ColdFusion queries and structs to Javascript objects.

This method takes a ColdFusion query and return the Javascript code that would be used to model it via an array of objects. It returns a string in the form of: new Array(new Object(), new Object(), new Object()):

  • <cffunction name="QueryToArray" access="public" returntype="string" output="false"
  • hint="Converts a query into Javascript code for an array of structures. Will return strings in the form of 'new Array(new Object(), new Object()....);'">

  • <!--- Define arguments. --->
  • <cfargument name="Data" type="query" required="true" />

  • <cfscript>

  • // Define the local scope.
  • var LOCAL = StructNew();

  • // Convert the column list to an array for faster parsing.
  • LOCAL.ColumnList = ListToArray( ARGUMENTS.Data.ColumnList );

  • // Create the string buffer for creating the response string.
  • LOCAL.ResponseBuffer = CreateObject( "java", "java.lang.StringBuffer" );

  • // Start the array. This array will be an array of objects.
  • LOCAL.ResponseBuffer.Append( "new Array(" );

  • // Loop over the query and create an object for each for.
  • for (LOCAL.Row = 1 ; LOCAL.Row LTE ARGUMENTS.Data.RecordCount ; LOCAL.Row = (LOCAL.Row + 1)){

  • // Create the row object.
  • LOCAL.ResponseBuffer.Append( "{" );

  • // Loop over the columns to create the object values.
  • for (LOCAL.Column = 1 ; LOCAL.Column LTE ArrayLen( LOCAL.ColumnList ) ; LOCAL.Column = (LOCAL.Column + 1)){

  • // Get the key.
  • LOCAL.Key = LOCAL.ColumnList[ LOCAL.Column ];

  • // Get the value.
  • LOCAL.Value = ARGUMENTS.Data[ LOCAL.Key ][ LOCAL.Row ];

  • // Add the pair. Escape the value so that is doesn't break the string. This requires the
  • // use of the "\" before single quotes, double quotes, and backward slashes (which
  • // ordinarily would be special characters in Javascript).
  • LOCAL.ResponseBuffer.Append( LCase( LOCAL.Key ) & ":""" & REReplace( LOCAL.Value, "(""|\\)", "\\\1", "ALL" ) & """" );

  • // Check to see if we need to add the comma.
  • if (LOCAL.Column LT ListLen( ARGUMENTS.Data.ColumnList )){
  • LOCAL.ResponseBuffer.Append( "," );
  • }

  • }

  • // Close the row object.
  • LOCAL.ResponseBuffer.Append( "}" );

  • // Check to see if we will have more objects to add.
  • if (LOCAL.Row LT ARGUMENTS.Data.RecordCount){
  • LOCAL.ResponseBuffer.Append( "," );
  • }

  • }

  • // End the array.
  • LOCAL.ResponseBuffer.Append( ")" );

  • // Return the string.
  • return( LOCAL.ResponseBuffer.ToString() );

  • </cfscript>
  • </cffunction>

On the Javascript side, you could use this evaluated object in the same manner you would a query:

  • // Loop over the rows in the "query" which is now an array.
  • for (var intRow = 0 ; intRow < arrResults.length ; intRow++){
  •  
  • // Loop over each column and alert.
  • for (var strColumn in arrResults[ intRow ]){
  • alert( strColumn + " : " + arrResults[ intRow ][ strColumn ] );
  • }
  •  
  • // Or, you can call columns directly.
  • alert( arrResults[ intRow ].id );
  • alert( arrResults[ intRow ].name );
  •  
  • }

The following method takes ColdFusion structures and creates the Javascript code for creating an object in the form of: new Object({ a:3, b:7, ....}). As I stated before, I am not thinking about nested objects just yet. I will cross that bridge shortly. For the moment, while I am learning about AJAX, I am just going to work with structures containing simple values:

  • <cffunction name="StructToObject" access="public" returntype="string" output="false"
  • hint="Converts a ColdFusion struct of SIMPLE values into a Javascript object. Will return strings in the form of: new Object({})">

  • <!--- Define arguments. --->
  • <cfargument name="Data" type="struct" required="true" />

  • <cfscript>

  • // Define the local scope.
  • var LOCAL = StructNew();

  • // Create the string buffer for creating the response string.
  • LOCAL.ResponseBuffer = CreateObject( "java", "java.lang.StringBuffer" );

  • // Start the object.
  • LOCAL.ResponseBuffer.Append( "{" );

  • // Get the key count.
  • LOCAL.KeyCount = StructCount( ARGUMENTS.Data );

  • // Get an index for keeping track of the key usage.
  • LOCAL.KeyIndex = 1;

  • // Loop over the keys to create the object values.
  • for (LOCAL.Key in ARGUMENTS.Data){

  • // Get the value.
  • LOCAL.Value = ARGUMENTS.Data[ LOCAL.Key ];

  • // Add the pair. Escape the value so that is doesn't break the string. This requires the
  • // use of the "\" before single quotes, double quotes, and backward slashes (which
  • // ordinarily would be special characters in Javascript).
  • LOCAL.ResponseBuffer.Append( LCase( LOCAL.Key ) & ":""" & REReplace( LOCAL.Value, "(""|\\)", "\\\1", "ALL" ) & """" );

  • // Check to see if we need to add the comma.
  • if (LOCAL.KeyIndex LT LOCAL.KeyCount){
  • LOCAL.ResponseBuffer.Append( "," );
  • }

  • // Add one to the key index.
  • LOCAL.KeyIndex = (LOCAL.KeyIndex + 1);

  • }

  • // End the object.
  • LOCAL.ResponseBuffer.Append( "}" );

  • // Return the string.
  • return( LOCAL.ResponseBuffer.ToString() );

  • </cfscript>
  • </cffunction>

On the Javascript side of things, you can reference the structure keys just as you would in ColdFusion:

  • alert( objData.id );
  • alert( objData.name );

Next step is to create a recursive function that can handle nested data types.

Now, you might be thinking, why not just use CFWDDX to convert the ColdFusion objects to Javascript objects? One, cause I am learning more by making my own conversion. Two, CFWDDX has to create a top-level Javascript variable to hold the data, which is completely unnecessary in AJAX. And Three, CFWDDX is just not that efficient (in terms of code size). Take for instance trying to convert the following ColdFusion struct to a Javascript object:

  • <cfset objTest = StructNew() />
  • <cfset objTest.id = 1 />
  • <cfset objTest.name = "ben nadel" />
  • <cfset objTest.rich = false />
  • <cfset objTest.smart = true />
  • <cfset objTest.girlfriend = "molly" />
  • <cfset objTest.mood = "freakin' sweet" />

Using the CFWDDX - CFML2JS, we would get Javascript code like:

  • objData = new Object();
  • objData["mood"] = "freakin\' sweet";
  • objData["girlfriend"] = "molly";
  • objData["name"] = "ben nadel";
  • objData["id"] = "1";
  • objData["rich"] = "false";
  • objData["smart"] = "true";

That's 193 characters to return. Using my method, we would get Javascript code like:

  • new Object({
  • mood:"freakin\' sweet",
  • girlfriend:"molly",
  • name:"ben nadel",
  • id:"1",
  • rich:"false",
  • smart:"true"
  • })

That's 106 characters to return. That's a difference of about a half the characters that the server will have to return to the browser. Currently, my methods are not the best at projecting crazy keys like the CFWDDX is, but that will change shortly.

What's frustrating is that in the QueryToArray() method, when I am creating an object, I can use just { } notation since it is being evaluated as part of a parent array; however, I can't just return "{}" in the StructToObject() method as it doesn't know to evaluate directly to an Object. Hence, the use of "new Object()" in the latter method.



Reader Comments

@Nikos,

This is a rather old post. If you are using CF8 or greater, you can now use serializeJSON() and deserializeJSON() to perform the same things.

Very nice work here. Almost helped me with returning a CF structure to javascript. Almost!

Here is my code snippet:
--------------------
<cfset jsobjects = #StructToObject(row)# />
<tr><td>#SpecialtyName#</td><td>[LINK]="javascript:addedit('e','#jsobjects#')">Edit[LINK]</td>
-------------------

Sorry for the LINK in the code; this post does not allow the proper html link.
This is all inside a cfquery loop/output. The sturcture 'row' is created by another custom function.
What I need to do is to pass on the jsobjects variable to a javascript function. But...the StructToObject function puts double quotes around values of each key and so I get an unterminated string javscript error when clicking on the Edit link in above code.

Any idea?

Thanks!
Meengla

@Meengla,

Try using the jsStringFormat() function. It helps to escape strings that are used inside of a Javascript context.

@Ben,

Thanks Ben!
<cfset row = #rowCopy(qryAllSpecialties,currentRow)#> <!---Call function above to return row--->
<cfset jsobjects = #SerializeJSON(row)# />
<tr><td>#SpecialtyName#</td><td><LINK="javascript:addedit(#jsStringFormat(jsobjects)#)">Edit<LINK></td>

now get a javascript error:
'Invalid property id' when click on the edit link.

Looking at the source code, I see:
-----------------

<LINK="javascript:addedit({\"SPECIALTYNAME\":\"TEMP Specialty 1. \",\"SPECIALTYID\":1})">Edit <LINK
-------------------------

In case you are wondering, the idea is to pass the query's output with both column-names and values as an associative array to a javascript function and then use that function to populate a form in a CFWindow. But I am unable to make it work as of now and so am directing to a new page for edit functionality using url.specialtyid.

Meengla