Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Rich Toomsen
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Rich Toomsen

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

By on
Tags:

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

17 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....?

15,674 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.

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

15,674 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.

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

15,674 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.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel