ColdFusion Custom Tags Cannot Act As ColdFusion Component Mixins

Posted January 4, 2011 at 10:28 AM by Ben Nadel

Tags: ColdFusion

The other night, I was laying in bed, thinking about ColdFusion code (as I often times find myself doing), and it suddenly occurred to me that I wasn't sure of the expected behavior of ColdFusion custom tags that execute within a ColdFusion component context. Anyone who has created a user defined function (UDF) library or used free-standing functions knows that UDFs get bound to the calling page, not to the defining page. Unless, of course, the UDF is being called as a class method on a ColdFusion component; in that case, the UDF is bound to the component instance, not to the calling context. But what happens when you execute a ColdFusion custom tag within a ColdFusion component? What is the CALLER scope bound to? And, how do method invocations via the CALLER scope work?

To test this, I created a simple ColdFusion component that had a public method, a private method, and private variable:

Greeting.cfc

  • <cfcomponent
  • output="true"
  • hint="I am here to test the binding of CALLER to a ColdFusion component.">
  •  
  •  
  • <!--- Set a local variable to the component. --->
  • <cfset variables.name = "Sarah" />
  •  
  •  
  • <cffunction
  • name="sayHello"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I say hello.">
  •  
  • <!--- Say hello. Use the loacl NAME variable. --->
  • Hello, my name is #variables.name#!
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="privateMethod"
  • access="private"
  • returntype="void"
  • output="false"
  • hint="I am just a private method for CFDump.">
  •  
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <!---
  • Now, execute a custom tag that will invoke the sayHello()
  • method in the CALLER context.
  • --->
  • <cf_sayhello />
  •  
  • </cfcomponent>

As you can see, this is very simple; the public method, sayHello(), makes use of the private variable, name, in order to output a greeting. As part of the pseudo constructor, we are then executing a ColdFusion custom tag (sayhello.cfm) which will invoke the sayHello() method. The private method is there just to help us figure out which scopes are being bound (a private method will not show up on a public-scope binding).

The sayhello.cfm ColdFusion custom tag then turns around and invokes the sayHello() method on its Caller scope (and various combinations of caller-based paths):

sayhello.cfm (ColdFusion Custom Tag)

  • <!---
  • Define a local variable to the custom tag. This will not collide
  • with the caller's variables scope, but since it is named the same
  • as a variable in the caller context, it will allow us to figure
  • out where the method invocation is being bound.
  • --->
  • <cfset variables.name = "customTag" />
  •  
  • <!---
  • Invoke the sayHello() method on the CALLER scope. If the caller
  • scope were just a standard CFM template, then the function would
  • execute with a local, custom-tag context. However, in this case,
  • the caller context is a CFC - where will the function bind??
  • --->
  • <cfset caller.sayHello() />
  •  
  • <br />
  • <br />
  •  
  • <!---
  • Now, try to invoking the method by explicitly calling the
  • variables scope associated with the caller context.
  • --->
  • <cfset caller.variables.sayHello() />
  •  
  • <br />
  • <br />
  •  
  • <!---
  • Now, try invoking the method by explicitly calling the
  • this scope associated with the caller context.
  • --->
  • <cfset caller.this.sayHello() />
  •  
  •  
  • <br />
  • <br />
  •  
  •  
  • <!---
  • Now, let's CFDump out the caller scope to see what the actual
  • binding is.
  • --->
  • <cfdump
  • var="#caller#"
  • label="CALLER Scope"
  • />
  •  
  • <br />
  •  
  • <!---
  • And, let's output the caller-bound varaibles scope to see what
  • the actual binding is.
  • --->
  • <cfdump
  • var="#caller.variables#"
  • label="CALLER.Variables Scope"
  • />
  •  
  • <br />
  •  
  • <!---
  • And, let's output the caller-bound this scope to see what
  • the actual binding is.
  • --->
  • <cfdump
  • var="#caller.this#"
  • label="CALLER.This Scope"
  • />
  •  
  •  
  • <!--- Exit out of the tag. --->
  • <cfexit method="exitTag" />

As you can see, the first thing we are doing is defining a custom-tag-local variable, name, with the value "customTag". This will help us determine where the sayHello() method is being bound. Then, we try to execute the sayHello() method on various caller-based objects: caller, caller.variables, caller.this. When we instantiate the Greeting.cfc ColdFusion component, thereby running the above ColdFusion custom tag, we get the following page output:

 
 
 
 
 
 
ColdFusion Caller Scope Does Not Bind To A ColdFusion Compoennt When Exexcuting Within A ColdFusion Component. 
 
 
 

Now this is some very interesting stuff! The first thing we can see is that some of our class method invocations actually acted like unbound user-defined functions; that is, their execution was bound to their calling context (the custom tag), and not to the ColdFusion component instance as one might expect:

caller.sayHello() :: NOT bound to CFC.

caller.variables.sayHello() :: NOT bound to CFC.

caller.this.sayHello() :: Bound to CFC.

Furthermore, from our CFDump output, we can see that there is a clear distinction between the CALLER scope the ColdFusion component itself. The Caller scope is mysterious and powerful beast. I have blogged before about the special way in which the Caller scope treats keys for getting and setting values. As such, it's not too surprising that the caller scope doesn't map to a ColdFusion component even when the target context is a ColdFusion component.

When you look at the CFDump, you'll notice that the ColdFusion custom tag has access to the private method - privateMethod() - of the ColdFusion component when it outputs the variables scope. However, variables-scope-based invocations do not bind to the CFC. The only way that we could get the method to bind to the CFC is when we invoke it via "caller.this"; unfortunately, when we do that, as you can see in our last CFDump, we no longer have access to the private method.

From this, I can see two important take-aways: 1) a ColdFusion custom tag, when executed from within a ColdFusion component, can gain access to both the public and private methods of the given component, and 2) although the custom tag can access both the public and private methods of the CFC, it cannot act as though it were a ColdFusion component mixin. In short, a custom tag acts like a completely separate, encapsulated object which happens to have the ability to access unbound private methods.

I suppose it would help to starting thinking about the Caller scope more like a "bridge" to another context and less like a reference to an existing object or scope.




Reader Comments

Jan 4, 2011 at 11:37 AM // reply »
49 Comments

Our experience is use Custom Tags for content handlers, view tools and not for logic. With that concept in mind we would not be against your use case but that level of content handling might work better from a component to start with.

Why were you trying to use a custom tag inside a mixin component to start with... that might shed some light on the subject. Was this something from a takeover project. (It is where the frustration hits me more often for things like this.)


Jan 4, 2011 at 2:36 PM // reply »
11,314 Comments

@John,

I can't think of a great use-case for in-CFC custom tags. This was more to explore the behavior of the Caller scope within a CFC context. I just wanted to know how it works!


Jan 4, 2011 at 2:49 PM // reply »
49 Comments

@Ben,

Functional exploration has a value in the Mix. Thanks for the exploration of one of my favorite topics... 'how ColdFusion thinks'.


Jan 4, 2011 at 3:22 PM // reply »
11,314 Comments

@John,

Agreed - understanding the way things works is always quality.


Jan 4, 2011 at 4:13 PM // reply »
28 Comments

@Ben,

When I run your code and dump caller.this, I see both sayHello() and privateMethod() inside the dump. What version of CF are you running? I'm on 9,0,1,274733.


Jan 4, 2011 at 4:38 PM // reply »
11,314 Comments

@Tony,

Uh oh :) I ran this in ColdFusion 8.1 (or whatever the latest CF8 version is. Furthermore, when I try to access the privateMethod():

<cfset caller.this.privateMethod() />

... I get:

The method privateMethod was not found in component /Sites/bennadel.com/testing/caller_cfc_method/Greeting.cfc. Ensure that the method is defined, and that it is spelled correctly.

It works if I call:

<cfset caller.variables.privateMethod() />

... but, of course, outputs nothing.

Can you actually invoke the private method without error?


Jan 4, 2011 at 4:56 PM // reply »
28 Comments

@Ben,

No it still errors out. Must just be an update to cfdump that's causing the difference in results.


Jan 4, 2011 at 5:00 PM // reply »
11,314 Comments

@Tony,

Ha ha, I can't tell which is worse : a change in behavior of the caller scope across versions... or a completely misleading CFDump :)


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
Jun 20, 2013 at 3:15 AM
A Billion Wicked Thoughts By Ogi Ogas And Sai Gaddam
nice post i love it thanks 4 u :) ... read »
seb
Jun 20, 2013 at 2:32 AM
Working With Inherited Collections In AngularJS
@mike, @ben, The best article about scope and prototypal prototypical inheritance in angularjs is http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical- ... read »
Jun 20, 2013 at 2:17 AM
ColdFusion NumberFormat() Exploration
Nice read thanks Ben, Is there a way to mask a negative number? Long story short in the finance sector when you go 'short' on a stock you want the price to fall this is a good thing because you are ... read »
Jun 20, 2013 at 1:09 AM
The Beauty Of The jQuery Each() Method
my html code : <html> <head> <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="nss.js"> ... read »
Jun 19, 2013 at 11:31 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Ben, bunch to learn indeed, but thats fun part : ) ... read »
Jun 19, 2013 at 10:41 PM
Referencing ColdFusion Query Columns In A Loop Using Both Array And Dot Notation
Burdock-roots Are you going fat day by day? You need to be good for your family and make some money too. So we bring for you a best product that helps you to be more energetic every day. You will b ... read »
Jun 19, 2013 at 9:52 PM
Working With Inherited Collections In AngularJS
I recognize the applicability of your solution, and how easy it makes to share data across multiple views or even "submodules" of rather simple application. But it seems to me that it creat ... read »
Jun 19, 2013 at 9:38 PM
Directive Link, $observe, And $watch Functions Execute Inside An AngularJS Context
@Alesei, Glad you like it. Even after working with AngularJS for months, I still get a bunch of unexpected, "$digest is already in progress". So hard to debug sometimes! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools