Invoking A ColdFusion Function With A Closure-Like CALLER-Based Variable Binding
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.
Want to use code from this post? Check out the license.
Reader 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....?
@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.
Here is the follow up to this post, which demonstrates how to use this approach with regular expression match replacement:
www.bennadel.com/blog/1814-Using-A-ColdFusion-Method-Closure-In-Regular-Expression-Replace-Logic.htm
@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
@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.
@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
@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.