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 the jQuery Conference 2010 (Boston, MA) with: Seth Johnson

Generic Invocation Wrapper For Core ColdFusion Functions

By Ben Nadel on
Tags: ColdFusion

I happen to love ColdFusion's CFInvoke and CFInvokeArgument tags. They allow user defined functions (UDF) and ColdFusion component functions to be dynamically invoked using variable values. They don't need to be used all that often; but, when the time comes, these two tags can really make life easy. When it comes to core ColdFusion functions, however, these two tags can't help us. Right now, if you want to execute core ColdFusion functions dynamically, you have to fall back on the Evaluate() method. Now, there's nothing wrong with Evaluate(); but, it is neither as easy to use nor is it as flexible as CFInvoke. As such, I wanted to create a component-based proxy to allow CFInvoke-based execution of core ColdFusion functions.

 
 
 
 
 
 
 
 
 
 

The idea is straightforward: it's still powered by the traditional Evaluate()-based approach; but, it is made generic through the use of ColdFusion's onMissingMethod() function. I have a single component, FunctionProxy.cfc, that has a single method, onMissingMethod(). The onMissingMethod() method contains the code that invokes the requested method using Evaluate(). And since onMissingMethod() will accept any method name thrown at it, it allows all core ColdFusion functions to be invoked by-proxy.

FunctionProxy.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a generic ColdFusion function wrapper for use with reflection-type invokation.">
  •  
  •  
  • <cffunction
  • name="onMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I provide ordered-argument-based invocation of core ColdFusion method.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="missingMethodName"
  • type="string"
  • required="true"
  • hint="I am the name of the core ColdFusion method."
  • />
  •  
  • <cfargument
  • name="missingMethodArguments"
  • type="any"
  • required="true"
  • hint="I am the arguments collection used to invoke the missing method."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Create an array into which we can store the collection
  • of index-based argument references.
  • --->
  • <cfset local.orderedArguments = [] />
  •  
  • <!--- Build up the argument references. --->
  • <cfloop
  • index="local.argumentIndex"
  • from="1"
  • to="#arrayLen( arguments )#"
  • step="1">
  •  
  • <cfset local.orderedArguments[ local.argumentIndex ] = "arguments.missingMethodArguments[ #local.argumentIndex# ]" />
  •  
  • </cfloop>
  •  
  • <!---
  • Evaluate and return the method execution. We are
  • getting the page-based function reference since it
  • appears that not all core ColdFusion methods can be
  • invoked directly with Evaluate().
  • --->
  • <cfreturn Evaluate(
  • "GetPageContext().GetPage().#arguments.missingMethodName#" &
  • "( "
  • & arrayToList( local.orderedArguments, ", " ) &
  • " )"
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, the onMissingMethod() method is programmatically building a snippet of code in the form of:

Evaluate( "function( x, y, z, .... )" )

You'll notice, however, that I am not simply using the name of the target function; rather, I am using the function reference made available within the current page context. I am only doing this because I found out a while back that Image-based functions don't play well with Evaluate(). That might be an old bug, since fixed; but for now, until I re-test, I figured I'd rely on the more universally proven approach.

With this FunctionProxy.cfc ColdFusion component in place, we can now use CFInvoke and CFInvokeArgument to dynamically execute core ColdFusion functions. In the following demo, I am using three different proxy-approaches:

  • <!--- Create an instance of the ColdFusion proxy. --->
  • <cfset proxy = createObject( "component", "FunctionProxy" ) />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Invoke the RandRange() function with CFInvoke. --->
  • <cfinvoke
  • returnvariable="value"
  • component="#proxy#"
  • method="randRange">
  •  
  • <cfinvokeargument name="1" value="10" />
  • <cfinvokeargument name="2" value="20" />
  • </cfinvoke>
  •  
  • <!--- Output the random value. --->
  • <cfoutput>
  •  
  • Random Value: #value#
  •  
  • </cfoutput>
  •  
  •  
  • <br>
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br>
  •  
  •  
  • <!--- Create a struct for use with argumentCollection. --->
  • <cfset args = {} />
  • <cfset args[ "1" ] = "10" />
  • <cfset args[ "2" ] = "20" />
  •  
  • <!---
  • Invoke the RandRange() function with CFInvoke. Notice that
  • this time, we are using argumentCollection instead of
  • multiple CFInvokeArgument.
  • --->
  • <cfinvoke
  • returnvariable="value"
  • component="#proxy#"
  • method="randRange"
  • argumentCollection="#args#"
  • />
  •  
  • <!--- Output the random value. --->
  • <cfoutput>
  •  
  • Random Value: #value#
  •  
  • </cfoutput>
  •  
  •  
  • <br>
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br>
  •  
  •  
  • <!---
  • This time, I'm just going to invoke it without andy fancy
  • reflection (as I might a standard method).
  • --->
  • <cfset value = proxy.randRange( 10, 20 ) />
  •  
  • <!--- Output the random value. --->
  • <cfoutput>
  •  
  • Random Value: #value#
  •  
  • </cfoutput>

As you can see, FunctionProxy.cfc allows us to dynamically invoke core functions using both individually defined arguments (CFInvokeArgument) and a collection of arguments (ArgumentCollection). Because the core ColdFusion functions all rely on ordered arguments, you have to name the arguments using their invocation index. When we run the above code, we get the following output:

Random Value: 16

Random Value: 18

Random Value: 10

If you think the use cases for CFInvoke are few and far between, the use cases for dynamically executing core ColdFusion functions are even more rare. But, if you do need to use a more reflection-based approach to core function invocation, the ability to leverage CFInvoke on top of the core functions can be super useful. Ideally, what I'd like to see down the road is CFInvoke's functionality extended to include core ColdFusion functions.




Reader Comments

@Rick,

Now I just have to come up with a reason to use it :) I know I've wanted it in the past, but it's always so hard to remember for what. Yesterday, I only wanted it for some R&D in CF9... then I remembered that my CF9 is bugging on out on me.

Reply to this Comment

Hey Ben

I'm kind of a fan of that last way of getting the result: ie.
<cfset value = proxy.randRange( 10, 20 ) />
instead of using cfinvoke. It seems cleaner and simpler to me.

Is there any down side of using that.

Cheers
Marty

Reply to this Comment

@Martin,

The point wasn't just to simply invoke the method. After all, if you just want to call the method using it's pre-defined name, then you could simply call it directly:

randRange( 10, 20 )

The point of the proxy is to *allow* you to be able to invoke the method using CFInvoke if you wanted to.

Reply to this Comment

Jeebus ColdFusion is cool. I was going to do this when you tweeted about it @Ben but I was too busy working ;)

@Martin - the problem with proxy.randrange() style is that you can't do proxy.#whateverCFfunctionIwant#() for syntax reasons. Say for instance at runtime you want to use either ArrayMax or ArrayMin depending on criteria X - with cfinvoke you can skip a cfif block or even encapsulate the logic with a function, like cfinvoke method="#MinOrMax()#"

Reply to this Comment

@Darren,

Yeah, CF is super awesome :)

I like your ArrayMax/ArrayMin example. Thinking back, I think one of the places I really wanted this was allowing someone to toggle between the "Case" and "NoCase" functions (ex. reReplace() / reReplaceNoCase()).

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.