What If ColdFusion Looked Like jQuery

Posted March 28, 2007 at 7:40 AM by Ben Nadel

Tags: ColdFusion

I am a Huge fan of jQuery. I love the method chaining and the ability to apply methods to each element in a jQuery collection object. I was wondering if I would want ColdFusion to be able to do that. When I started to experiment, I realized that ColdFusion has two limitations that stop it from being like jQuery:

  1. The biggest limitation is simply the ability to create on-the-fly, nameless functions. It can be done, but it's hard and not very elegant. These anonymous functions are half of what make jQuery (and Javascript in general) so amazingly powerful.
  2. ColdFusion does not have a very solid structure like the browser's Document Object Model (DOM) over which actions can be taken. This is not a limitation of ColdFusion itself, but merely a fact of the sever-side world.

But, I figured there could be jQuery-like structures for ColdFusion. These turned out to be more like data type wrappers, but still, I thought they were very interesting. Here is a demo:

  • <!---
  • Create a strcut with value keys. This is to
  • demonstrate that our data wrapper allows the
  • adding of STRUCTs to a simple list.
  • --->
  • <cfset objValues = StructNew() />
  • <cfset objValues[ "testa" ] = "likes" />
  • <cfset objValues[ "testb" ] = "Nancy." />
  •  
  •  
  • <!---
  • Create an array of values. This is to
  • demonstrate that our data wrapper allows the
  • adding of ARRAYs to a simple list.
  • --->
  • <cfset arrValues = ListToArray( "Nancy,is" ) />
  •  
  •  
  • <!---
  • Create a jQuery-like wrapper for a ColdFusion
  • list value. I am using the $ signe just to drive
  • home that this is modelled on jQuery.
  • --->
  • <cfset $List = CreateObject( "component", "$List" ).Init() />
  •  
  •  
  • <!---
  • Now that we have our jQuery-like data wrapper,
  • let's start manipulating the list using chained
  • methods and an Each method call.
  • --->
  • #$List.Prepend( "Ben" )
  • .Append( "naughty" )
  • .InsertAt( 2, objValues )
  • .InsertAt( 4, arrValues )
  • .Each( EachHandler )
  • .ToString()#
  •  
  •  
  • <!--- This defines the Each Handler for the calls above. --->
  • <cffunction
  • name="EachHandler"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="Handles manipulation of each list item. Must return the item.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • />
  •  
  • <!--- Return cammel-case value. --->
  • <cfreturn REReplace(
  • ARGUMENTS.Value,
  • "^([a-z])",
  • "\U\1",
  • "ONE"
  • ) />
  • </cffunction>

The 6 chained method calls on $List result in the following output:

Ben,Likes,Nancy.,Nancy,Is,Naughty

This demonstrates an Each method that takes a list value and returns an updated value. However, just as in jQuery / Javascript, there is no requirement for the Each method to actually update the list. In this example, we can use the Each method as an iterator that populates a global, REQUEST-scoped value array:

  • <!--- Create out global values array. --->
  • <cfset REQUEST.Values = ArrayNew( 1 ) />
  •  
  • <!---
  • Build the global array using the Each
  • method of the list data wrapper.
  • --->
  • <cfset $List.Each( EachHandler2 ) />
  •  
  •  
  • <!--- Dump out the global array. --->
  • <cfdump
  • var="#REQUEST.Values#"
  • label="Global Value Array"
  • />
  •  
  •  
  • <!--- This defines the Each method handler for use above. --->
  • <cffunction
  • name="EachHandler2"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Takes each value and adds it to a REQUEST-scope array.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • />
  •  
  • <!--- Append this value to our global array. --->
  • <cfset ArrayAppend( REQUEST.Values, ARGUMENTS.Value ) />
  •  
  • <!--- Return void. --->
  • <cfreturn />
  • </cffunction>

CFDumping out the REQUEST.Values array, we get:


 
 
 

 
ColdFusion As jQuery Demo  
 
 
 

I like the idea. I think it has some merit, I am just not sure that ColdFusion is the proper place to apply this type of methodology. Does anyone have any thoughts on this type of thing?

Here is the code that created the $List.cfc ColdFusion component / jQuery-like data wrapper:

  • <cfcomponent
  • output="false"
  • hint="Creates functionality around a list.">
  •  
  •  
  • <!---
  • Create an instance struct to hold
  • instance specific data.
  • --->
  • <cfset VARIABLES.Instance = StructNew() />
  •  
  • <!--- Store the list internally as an array. --->
  • <cfset VARIABLES.Instance.Values = ArrayNew( 1 ) />
  •  
  • <!--- Set the default delimiter. --->
  • <cfset VARIABLES.Instance.Delimiter = "," />
  •  
  •  
  • <!--- This is a hack to get better naming for functions. --->
  • <cfset THIS.ToString = THIS.$ToString />
  •  
  •  
  •  
  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized list object.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!--- Set up the default value array. --->
  • <cfset VARIABLES.Instance.Values = ArrayNew( 1 ) />
  •  
  • <!--- Set up the default delimiter. --->
  • <cfset VARIABLES.Instance.Delimiter = "," />
  •  
  •  
  • <!---
  • Check to see if a second argument, the delimiter,
  • has been passed in.
  • --->
  • <cfif (
  • StructKeyExists( ARGUMENTS, "2" ) AND
  • Len( ARGUMENTS[ 2 ] )
  • )>
  •  
  • <!--- Store the passed in delimiter. --->
  • <cfset VARIABLES.Instance.Delimiter = ARGUMENTS[ 2 ] />
  •  
  • </cfif>
  •  
  •  
  • <!--- Check to see if we had a values set passed in. --->
  • <cfif StructKeyExists( ARGUMENTS, "1" )>
  •  
  • <!--- Append the values to our list. --->
  • <cfset THIS.Append(
  • Values = ARGUMENTS[ 1 ],
  • Delimiters = VARIABLES.Instance.Delimiter
  • ) />
  •  
  • </cfif>
  •  
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Append"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Appends list items to the list.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Values"
  • type="any"
  • required="true"
  • hint="These are the values that you are appending. Can be list, array, struct."
  • />
  •  
  • <cfargument
  • name="Delimiters"
  • type="string"
  • required="false"
  • default="#VARIABLES.Instance.Delimiter#"
  • hint="The characters use for list delimiters."
  • />
  •  
  •  
  • <!---
  • Append the new values to the end of our
  • ineternal value array.
  • --->
  • <cfset VARIABLES.Instance.Values.AddAll(
  • THIS.ToValueArray(
  • ArgumentCollection = ARGUMENTS
  • )
  • ) />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Delete"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="This takes values (list, array, struct) and deletes them from the list.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Values"
  • type="any"
  • required="true"
  • hint="These are the values that you are deleting. Can be list, array, struct."
  • />
  •  
  • <cfargument
  • name="Delimiters"
  • type="string"
  • required="false"
  • default="#VARIABLES.Instance.Delimiter#"
  • hint="The characters use for list delimiters."
  • />
  •  
  •  
  • <!---
  • Create an array of the values and then remove
  • them all from our internal list.
  • --->
  • <cfset VARIABLES.Instance.Values.RemoveAll(
  • THIS.ToValueArray(
  • ArgumentCollection = ARGUMENTS
  • )
  • ) />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Each"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Applies the given function to each list item.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Method"
  • type="any"
  • required="true"
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!--- Loop over list items to apply method. --->
  • <cfloop
  • index="LOCAL.Index"
  • from="1"
  • to="#VARIABLES.Instance.Values.Size()#"
  • step="1">
  •  
  • <!--- Get an updated value. --->
  • <cfset LOCAL.Value = ARGUMENTS.Method(
  • VARIABLES.Instance.Values[ LOCAL.Index ]
  • ) />
  •  
  • <!---
  • Theoretically, each of the methods *should*
  • return a value. However, this function might
  • NOT be returning a value. Therefore, check to
  • see if value exists locally before saving it
  • back into our list.
  • --->
  • <cfif StructKeyExists( LOCAL, "Value" )>
  •  
  • <!--- We have a valid value. --->
  • <cfset VARIABLES.Instance.Values[ LOCAL.Index ] = LOCAL.Value />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="InsertAt"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Inserts the given values at the given index.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Index"
  • type="numeric"
  • required="true"
  • hint="The index at which to insert the values. LTE 1 will prepend."
  • />
  •  
  • <cfargument
  • name="Values"
  • type="any"
  • required="true"
  • hint="These are the values that you are inserting. Can be list, array, struct."
  • />
  •  
  • <cfargument
  • name="Delimiters"
  • type="string"
  • required="false"
  • default="#VARIABLES.Instance.Delimiter#"
  • hint="The characters use for list delimiters."
  • />
  •  
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  •  
  • <!--- Create a local values array. --->
  • <cfset LOCAL.Values = THIS.ToValueArray(
  • ArgumentCollection = ARGUMENTS
  • ) />
  •  
  •  
  • <!--- Check the index to see if we are prepending. --->
  • <cfif (ARGUMENTS.Index LTE 1)>
  •  
  • <!--- Return prepending result. --->
  • <cfreturn THIS.Prepend(
  • ArgumentCollection = ARGUMENTS
  • ) />
  •  
  • <cfelseif (ARGUMENTS.Index GT VARIABLES.Instance.Values.Size())>
  •  
  • <!--- Return appending result. --->
  • <cfreturn THIS.Append(
  • ArgumentCollection = ARGUMENTS
  • ) />
  •  
  • <cfelse>
  •  
  • <!---
  • We actually need to insert the values. In order
  • to do that, lets get local copies of the array
  • to mess with.
  • --->
  • <cfset LOCAL.PreValues = VARIABLES.Instance.Values />
  • <cfset LOCAL.PostValues = VARIABLES.Instance.Values />
  •  
  •  
  • <!--- Trim the pre-value list. --->
  • <cfloop
  • index="LOCAL.Index"
  • from="#VARIABLES.Instance.Values.Size()#"
  • to="#ARGUMENTS.Index#"
  • step="-1">
  •  
  • <!--- Delete the post non-pre values. --->
  • <cfset ArrayDeleteAt(
  • LOCAL.PreValues,
  • LOCAL.Index
  • ) />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Trim the post-value list. --->
  • <cfloop
  • index="LOCAL.Index"
  • from="#(ARGUMENTS.Index - 1)#"
  • to="1"
  • step="-1">
  •  
  • <!--- Delete the pre non-post values. --->
  • <cfset ArrayDeleteAt(
  • LOCAL.PostValues,
  • LOCAL.Index
  • ) />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Add the new value to the pre values. --->
  • <cfset LOCAL.PreValues.AddAll(
  • LOCAL.Values
  • ) />
  •  
  • <!--- Add the post values to that. --->
  • <cfset LOCAL.PreValues.AddAll(
  • LOCAL.PostValues
  • ) />
  •  
  • <!--- Store the new list back into the instance. --->
  • <cfset VARIABLES.Instance.Values = LOCAL.PreValues />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  •  
  • </cfif>
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Prepend"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Prepends list items to the list.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Values"
  • type="any"
  • required="true"
  • hint="These are the values that you are prepending. Can be list, array, struct."
  • />
  •  
  • <cfargument
  • name="Delimiters"
  • type="string"
  • required="false"
  • default="#VARIABLES.Instance.Delimiter#"
  • hint="The characters use for list delimiters."
  • />
  •  
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  •  
  • <!--- Create a local values array. --->
  • <cfset LOCAL.Values = THIS.ToValueArray(
  • ArgumentCollection = ARGUMENTS
  • ) />
  •  
  • <!---
  • At this point, we should have an array of values
  • that we need to prepend. To accomplish this, we
  • are going to append the exiting list to this new
  • list and then save the resultant back into the
  • list object.
  • --->
  • <cfset LOCAL.Values.AddAll( VARIABLES.Instance.Values ) />
  •  
  • <!--- Store the new list back into our list. --->
  • <cfset VARIABLES.Instance.Values = LOCAL.Values />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="$ToString"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="Returns a string representation of the list.">
  •  
  • <!--- Join all values. --->
  • <cfreturn ArrayToList(
  • VARIABLES.Instance.Values,
  • VARIABLES.Instance.Delimiter
  • ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="ToValueArray"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="Takes some sort of value (list, array, struct), and returns an array of values.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Values"
  • type="any"
  • required="true"
  • hint="These are the values that are being converted to an array. Can be list, array, struct."
  • />
  •  
  • <cfargument
  • name="Delimiters"
  • type="string"
  • required="false"
  • default="#VARIABLES.Instance.Delimiter#"
  • hint="The characters use for list delimiters."
  • />
  •  
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  •  
  • <!---
  • Check to see which type of data is being prepended
  • to the list. We can take a string, an array,
  • or a struct (from which we will take the values).
  • --->
  • <cfif IsSimpleValue( ARGUMENTS.Values )>
  •  
  • <!--- Create a local copy of the new values. --->
  • <cfset LOCAL.Values = ListToArray(
  • ARGUMENTS.Values,
  • ARGUMENTS.Delimiters
  • ) />
  •  
  • <cfelseif IsArray( ARGUMENTS.Values )>
  •  
  • <!---
  • The passed in value is an array. Just store it
  • locally for now.
  • --->
  • <cfset LOCAL.Values = ARGUMENTS.Values />
  •  
  • <cfelseif IsStruct( ARGUMENTS.Values )>
  •  
  • <!--- Create a local values array. --->
  • <cfset LOCAL.Values = ArrayNew( 1 ) />
  •  
  • <!---
  • The passed in value is a struct. We need
  • to get the values out of it. Loop over the
  • struct to get all the simple values for
  • our internal array.
  • --->
  • <cfloop
  • item="LOCAL.Key"
  • collection="#ARGUMENTS.Values#">
  •  
  • <!---
  • Check to see if this value is simple.
  • We only want simple values since our
  • eventual list can only be a string.
  • --->
  • <cfif IsSimpleValue( ARGUMENTS.Values[ LOCAL.Key ] )>
  •  
  • <!--- Add to array. --->
  • <cfset ArrayAppend(
  • LOCAL.Values,
  • ARGUMENTS.Values[ LOCAL.Key ]
  • ) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <cfelse>
  •  
  • <!---
  • The values passed in did not match an valid data
  • type. Just create an empty array to pass back.
  • --->
  • <cfset LOCAL.Values = ArrayNew( 1 ) />
  •  
  • </cfif>
  •  
  •  
  • <!--- Return the values array. --->
  • <cfreturn LOCAL.Values />
  • </cffunction>
  •  
  • </cfcomponent>


Reader Comments

Mar 28, 2007 at 9:00 AM // reply »
35 Comments

JQuery is fun, no doubt. Certainly returning the jQuery object allows some great chaining and very concise syntax.

Your experiment with CF in a jQuery style is interesting though to be true, I'd be happy if I could define arrays and structs using a native notation rather than a series of tags.

Ex.

aStruct{ key1: 'value1', key2: 'value2' }

anArray[ 'one', 'and', 'uh', 'two', 'and', 'uh']


Mar 28, 2007 at 9:09 AM // reply »
42 Comments

"The 6 chained method calls on $List result in"
code that is impossible to read.

Sorry, but I just don't like the syntax, and can't see the benefit.

Yes, having a 'for each' loop construct would be nice, but no more than that.


Mar 28, 2007 at 9:27 AM // reply »
2 Comments

Ben--you are a madman and I mean this as a compliment.


Mar 28, 2007 at 10:39 AM // reply »
170 Comments

@Dan:

Here are 2 functions that give you almost what you want:

<cffunction name="arrayCreate" returntype="array" output="false">
<cfset var aReturn = arrayNew(1) />
<cfset var i = 0 />
<cfloop index="i" from="1" to="#arrayLen(arguments)#">
<!---//
you might want to using duplicate(arguments[i])
if you plan on using complex array items
//--->
<cfset arrayAppend(aReturn, duplicate(arguments[i])) />
</cfloop>
<cfreturn aReturn />
</cffunction>

<cfdump var='#arrayCreate("one", "two", "three", "four")#'>
<cfdump var='#arrayCreate()#'>

<cffunction name="structCreate" returntype="struct" output="false">
<cfset var stReturn = structNew() />
<cfset var sKey = 0 />
<cfloop item="sKey" collection="#arguments#">
<cfset stReturn[sKey] = duplicate(arguments[sKey]) />
</cfloop>
<cfreturn stReturn />
</cffunction>

<cfdump var='#structCreate(key1="value1", key2="value2")#'>
<cfdump var='#structCreate()#'>


Mar 28, 2007 at 1:49 PM // reply »
23 Comments

@Dan,

I'm with you. I hate typing in TONS of tags to programmatically build a structure or array.

You could easily define you're arrays and structures in JSON format and avoid all the tags by using the JSON encode/decode library for CF to convert the JSON notation to a native CF data structure.

I've thought about doing this for a project I'm working on. I haven't done it yet, but there's on reason it shouldn't work.

CFJSON
http://www.epiphantastic.com/cfjson/


Mar 28, 2007 at 5:21 PM // reply »
170 Comments

@Kurt:

I'd certainly stay away from using a string to define native CF variables. String parsing is notoriously slow in Java/CF.


Mar 28, 2007 at 6:02 PM // reply »
11,238 Comments

@Tom,

I agree with you. At first the jQuery method chaining is awesome. But then, I quickly realize that I am not sure how to best format it.... and to me, when I don't know how to best format the code itself, that is red flag that it needs to be much more simple.

In jQuery, I tend to create the jQuery object and keep a reference to it. Then, I just make multiple calls on it:

o = $( "..." );
o.attr();
o.attr();
o.each();

I find not only is this easier to read, it actually pays off in the end because in the Each() methods, I now have a reference to stuff that might be needed later.

@Rob,

:) The mad scientist is always hard at work.

@Dan G.,

Good stuff. I would like to experiment with that.

@Kurt,

I am with Dan on this one. Plus, by using a string, you are forced to only use data structures that can fit into a string value.


Mar 28, 2007 at 6:02 PM // reply »
11,238 Comments

Mostly, I just want the EACH() ability. But since ColdFusion is compiled, I guess this is not possible - seems to only be possible in interpreted scripting languages.


Apr 1, 2007 at 11:44 PM // reply »
23 Comments

@Dan and @Ben,

My point was simply that sometimes there's a need to build a data structure within CF for whatever reason and that as long as all you're storing at the very end nodes of the data structure is simple data such as strings, JSON is a very clean and simple way to do it. It requires MUCH less typing than a bunch of CFSETs and is MUCH easier to understand.

Yes, string parsing is slow, but I'm guessing that most places this technique would be used is to convert a JSON string that never changes into a native CF value. In this case you could simply cache the data structure after it was initially parsed and decoded.

Sorry I wasn't clearer earlier.


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