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 TechCrunch Disrupt (New York, NY) with: Aaron Foss

What If ColdFusion Looked Like jQuery

By Ben Nadel on
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>
Tweet This Interesting post by @BenNadel - What If ColdFusion Looked Like jQuery Thanks my man — you rock the party that rocks the body!


Reader 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']

Reply to this Comment

"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.

Reply to this Comment

@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()#'>

Reply to this Comment

@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/

Reply to this Comment

@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.

Reply to this Comment

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.

Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.