Invoking A ColdFusion Function With A Closure-Like CALLER-Based Variable Binding

Posted January 14, 2010 at 9:58 AM by Ben Nadel

Tags: ColdFusion

The more I work with Javascript and jQuery, the more I love the power of closures. And, the more I love the power of closures, the more I want them in ColdFusion. As such, every time that I learn something new about the internal workings of ColdFusion, I stop and think about how I can leverage it to create closure-like behavior in my ColdFusion code. Recently, when I was playing around with Groovy, I figured out how to invoke ColdFusion component methods from within a Groovy context. Two years ago, the master-blaster, Elliott Sprehn, taught me how to get the PageContext from a ColdFusion custom tag CALLER scope. This morning, I woke up and suddenly wondered if I could combine these two features into a closure-like behavior?

If you look at my exploration of executing ColdFusion components in a Groovy context, you'll see that the key to invoking ColdFusion methods, using the underlying Java, is to have the appropriate Instance on which the method is being invoked as well as the appropriate parent Page to which the method is bound (NOTE: I don't fully understand what I just said - it's simply what I could piece together as an unfrozen caveman programmer). If we think about a ColdFusion custom tag context and its calling page, we can easily get the calling page instance (caller.variables); and, with what Elliott Sprehn has taught us, we can also get the calling page's context. As such, we should have everything we need in order to invoke a method from within a custom tag such that it is dynamically bound to the calling page context.

To demonstrate this, I have created a ColdFusion page with a user defined method, sayHello(). The sayHello() method returns a greeting that references a variables-scoped Name value. To demonstrate the dynamic binding of this UDF, I am going to pass it into a coldFusion custom tag and have the custom tag execute it in the calling context (where it will pick up the proper Name value).

  • <cffunction
  • name="sayHello"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I say hello to the NAME values stored in this template's VARIABLES scope.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="adjective"
  • type="string"
  • required="false"
  • default="studly"
  • hint="I am an adjective to add to the greeting."
  • />
  •  
  • <!--- Say hello to NAME variable. --->
  • <cfreturn "Hello #arguments.adjective# #name#!" />
  • </cffunction>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Define the name value to be used in SayHello() method. Take
  • note of this value as there will be a different NAME value in
  • the following custom tag (for binding demonstartion).
  • --->
  • <cfset name = "Tricia" />
  •  
  • <!---
  • Execute our test custom tag and pass in a reference
  • to the function we want to execut.
  • --->
  • <cf_contexttag
  • greeting="#sayHello#"
  • />

As you can see, the sayHello() method makes uses of the unscoped "Name" variable, which it will look for in the binding page's Variables scope. The SayHello() method reference then gets passed into the ColdFusion custom tag, contexttag.cfm, which will execute the method using both the calling context as well as the tag-local context (for comparison).

ContextTag.cfm

  • <!---
  • Param the greeting attribute. This is the reference to
  • the SayHello() function defined in the calling context.
  • --->
  • <cfparam
  • name="attributes.greeting"
  • type="any"
  • />
  •  
  •  
  • <!---
  • Now that the Greeeting function (SayHello()) is in the
  • context of this custom tag, if we execute it, it will
  • dynamically bind to the custom tag context (includ any
  • VARIABLES references). As such, we need to do some crazy
  • fenagaling (thank you Elliott Sprehn!) to get it to execute
  • in the context of the CALLER.
  • --->
  •  
  •  
  • <!---
  • Get the meta data for the caller page. This will give us the
  • CLASS used for the caller scope; from this, we can use
  • reflection to access the given field (page context) from
  • a given instance of that class (caller).
  • --->
  • <cfset callerMetaData = getMetaData( caller ) />
  •  
  • <!---
  • Get the class field that would hold a reference to the
  • pageContext.
  • --->
  • <cfset contextField = callerMetaData.getDeclaredField( "pageContext" ) />
  •  
  • <!---
  • This is a private field so we have to explicitly change its
  • access (I don't fully understand things at this level - this
  • is just what Elliott had... and, if you don't do it, it tells
  • you that the field is private and throws an error).
  • --->
  • <cfset contextField.setAccessible( true ) />
  •  
  • <!---
  • Now that we have the Field object that represents the
  • PageContext property, use reflection to get that property from
  • the CALLER instance.
  • --->
  • <cfset callerPageContext = contextField.get( caller ) />
  •  
  •  
  • <!---
  • This method will simply echo whatever arguments have
  • been passed to it. This is just an easy way to define an
  • argument collection to be used when invoking the passed-in
  • Greeting method.
  •  
  • NOTE: To be used with NAMED attributes. Positional attributes
  • have be executed using an Object[] array... (I think).
  • --->
  • <cffunction
  • name="getArgumentCollection"
  • access="public"
  • output="false"
  • hint="I echo the arguments collection.">
  •  
  • <cfreturn arguments />
  • </cffunction>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • At this point, we have everything we need to invoke the given
  • method in the context of the calling page:
  •  
  • * Local scope reference (Variables).
  • * Calling page context.
  • * Argument collectoin object.
  • --->
  •  
  •  
  • <!---
  • To demonstrate that the method is executing in the right
  • context, let's create a tag-local NAME variable.
  • --->
  • <cfset name = "Joanna" />
  •  
  •  
  • <!---
  • Execute the gretting method in the context of the calling page
  • and store the result.
  •  
  • NOTE: To invoke the method, we are using the underlying,
  • undocumented Java methods for invoking ColdFusion methods.
  • --->
  • <cfset result = attributes.greeting.invoke(
  • caller.variables,
  • javaCast( "string", getMetaData( attributes.greeting ).name ),
  • callerPageContext.getPage(),
  • getArgumentCollection( adjective = "coolest" )
  • ) />
  •  
  •  
  • <cfoutput>
  •  
  • Caller-Context: #result#<br />
  • <br />
  •  
  • <!---
  • To demonstrate that this is doing some MAGIC, let's also
  • execute the UDF in the context of teh custom tag to see
  • which NAME variable it binds to.
  • --->
  • Tag-Context: #attributes.greeting()#<br />
  •  
  • </cfoutput>
  •  
  •  
  • <!--- Exit out of the custom tag. --->
  • <cfexit method="exittag" />

As you can see in the above code, first we get the calling page's context. Then, we define a local Name variable, "Joanna", and execute the passed-in method, binding it once to the calling page context and then once to the custom tag context. When we run the above code, we get the following output:

Caller-Context: Hello coolest Tricia!

Tag-Context: Hello studly Joanna!

As you can see, when the sayHello() method is invoked on the calling context, it picks up, "Tricia," as the Name variable value; and, when it's invoked on the ColdFusion custom tag context, it picks up, "Joanna," as the Name variable value. Isn't that wild?!? Furthermore, when we invoke the method on the calling context, we also pass in the optional argument, Adjective, "coolest."

ColdFusion custom tags give us the CALLER scope, which is what makes this possible. From everything that I have seen so far, no such calling-page-link exists in any other complex construct within ColdFusion (components, methods) - at least not that I have been able to find in any of the pageContext-accessible data. This whole exercise might just seem like a whole bunch of silliness, but I do have a follow up blog post that might make is seem pretty cool.




Reader Comments

Jan 14, 2010 at 12:20 PM // reply »
14 Comments

I know about as much ColdFusion as a mitochondria, but I wonder that this looks like standard OOP. The scope of your variables remain in effect until and unless you trash them or step outside of the closure. Is this something not normally found in ColdFusion? Did I miss something....?


Jan 14, 2010 at 6:50 PM // reply »
11,246 Comments

@Kristopher,

It doesn't quite work that way with unbound methods (unbound to a ColdFusion component at any rate). The way ColdFusion works is that when you execute an unbound method, it dynamically binds to the page object associated with the execution context; this renders the context in which the method was defined as useless (relatively speaking).

This is great because it allows a high degree of dynamic behavior in the language. But, sometimes, it would be nice to have the define-time binding that closures that offer.

Hopefully, I'll demo this in my next blog post.


Jan 14, 2010 at 7:44 PM // reply »
11,246 Comments

Here is the follow up to this post, which demonstrates how to use this approach with regular expression match replacement:

http://www.bennadel.com/blog/1814-Using-A-ColdFusion-Method-Closure-In-Regular-Expression-Replace-Logic.htm


Jan 15, 2010 at 10:56 AM // reply »
113 Comments

@Ben

Closures either have to be a language feature or have to be implemented in code without tricks. A sample usage of a sample API for closures implemented in code is as follows. The implementation of the API is left as an exercise to the reader.

function f(a, b, c) {
//a, b, and c are in the arguments scope
//d, e, and f are in the variables scope
return a + b + c + d + e + f;
}

//the closure parameter becomes the
// variables scope of the function,
// at the time the function is called
// via the delegate's call method
fd = new Delegate(f, closure = { d = 'x', e = 'y', f = 'z' });

//call the function via the delegate's
// call method, passing in the arguments
// (you can use either array or struct arguments)
//should print out '123xyz'.
writeOutput(fd.call('1', '2', '3'));

Cheers,
Justice


Jan 16, 2010 at 4:12 PM // reply »
11,246 Comments

@Justice,

Right - Unfortunately ColdFusion doesn't have support for Closures, nor does it make it easy to simulate (ala my example). I am not sure how the Delegate would work? It seems like there would be a lot of overhead to making sure that the closure scope creates two-way communication between variable read/creation. Meaning, a closure method can set values INTO the closing scope as well as just reading from them.


Jan 17, 2010 at 4:21 PM // reply »
113 Comments

@Ben,

Right. So you would simply have to implement closures the same way any other language with closures implements them behind the scenes.

Rather than using the caller scope, you can create a closure out of a non-closure function and a struct with the variables to be closed over.

Cheers,
Justice


Jan 24, 2010 at 11:02 PM // reply »
11,246 Comments

@Justice,

Absolutely! And I hope they do (figure out how to do it). This was mostly an experiment in trying to push the language in its current state.


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 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
May 23, 2013 at 4:26 PM
ColdFusion QueryAppend( qOne, qTwo )
@Heather, Glad people are still getting value out of this! ... read »
May 23, 2013 at 3:49 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, I meant the code at the bottom (not the video). I did try to experiment with an intermediary variable, like: value = users.id[ i ]; arrayContains( userIDs, value ); ... but t ... read »
May 23, 2013 at 11:06 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Are you talking about As Number: YES As String: YES As Java: YES? If so, that's with 3 different ways of referencing the constant 1, not users.id[1]. Query object references(*) are what seem ... read »
May 23, 2013 at 9:55 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dan, According to the CF Admin, I'm running Java "1.6.0_45". As far as the DB column, in the database it's an INT. I'll see if I can dig into what CF sees it as. @WebManWalking, But h ... read »
May 23, 2013 at 9:49 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, I think the problem is that we're used to loose typing in ColdFusion, like JavaScript. If a value is a number but it's needed in an expression to be a string, noooo problem. I've encountered ... read »
May 23, 2013 at 9:47 AM
ColdFusion QueryAppend( qOne, qTwo )
You rock! Thank you, thank you, thank you!!! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools