As I am learning more about ColdFusion custom tags, I wanted to play around with defining user defined functions within a ColdFusion custom tag. To test this out, I set up a parent tag that has a function called AddName(). This function is invoked by each child tag (where by the child tag stores one of its attributes into the parent tag's data scopes).
Here is the main page (index.cfm) that defines the custom tags:
Launch code in new window » Download code as text file »
As you can see, each child tag has a single attribute, Name. This is the attribute value that we will store into the parent. Here is the parent tag (parent.cfm):
Launch code in new window » Download code as text file »
As you can see, in the Start mode of the parent tag, I am defining a user defined function named AddName() and an array named "Names". The AddNames() UDF takes a name argument and appends it to the Name array that we defined within the THISTAG scope. Then, in the End mode of the tag, we are simply looping over the names.
Here is the child tag (child.cfm) that invokes the parent's method:
Launch code in new window » Download code as text file »
As you can see, the child tag gets a pointer to the parent tag using the GetBaseTagData() method. It then defines the required Name attribute and invokes the parent tag's AddName() method using the Parent tag pointer.
This should work fine, but when I run the index page above, I get this ColdFusion error:
Element NAMES is undefined in THISTAG.
What is going on? Well, we know that this error is definitely happening inside of the UDF AddName() as this is the only place that reference this NAMES element. But how could this be? We are explicitly defining the Names array within the THISTAG scope - why wouldn't it exist?
To investigate what was going on, I added these lines of code to the AddName() UDF right before the line that was causing the error:
Launch code in new window » Download code as text file »
After adding that code and then rerunning the index page again, we get the following CFDump output:
| | | | ||
| | ![]() | | ||
| | | |
As you can see, this CFDump contains a structure called "Parent". Well, who has this structure? Not the Parent tag. The only ColdFusion custom tag that has this Parent structure defined is the Child tag.
So what does this mean about the UDF AddName()? It means that when it was invoked, the VARIABLES scope of the UDF binded to the VARIABLES scope of the invoking custom tag at run time, not at compile time. This means that while the UDF was defined within in the context of the Parent tag, since it was invoked by the Child tag, the UDF acted as if it were part of the Child tag and not the Parent tag.
This is a little strange, but when I think about ColdFusion components, it kind of makes sense and kind of doesn't make sense. If you have two ColdFusion components and one has a pointer to a method of the other one and then invokes that method, the method acts as if it were defined within the invoking component, not within the defining component. In that case, the VARIABLES and THIS scopes of the ColdFusion component bind dynamically at invocation time, not at compile time.
For this custom tag example, it is not quite like that. In this case, the invoking tag is not treating the UDF as if it were its own; in fact, it is passing the Name attribute value to the method with an explicit scoping to the Parent object. But, even though it is not quite a parallel situation, perhaps there is something like that going on at the ColdFusion custom tag level as well.
Download Code Snippet ZIP File
Comments (8) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Anonymous And Hilarious Photo Submission (Saucy)
Storing Child ColdFusion Custom Tag Data Within A Parent Using GetBaseTagData()
Once again a very interesting post. Thanks for writing this stuff down and providing such clear explanations.
Posted by Jeff Borisch on Apr 21, 2007 at 12:11 PM
No problem. Just learning this stuff as I go.
Posted by Ben Nadel on Apr 21, 2007 at 12:15 PM
Whilst this might initially make you go "huh?" it ought to be expected behavior - functions are evaluated in the calling context in all cases and variable name binding occurs in that context.
Consider this code (I don't know how it'll look in comments):
parent.cfm:
<cfset foo = "parent">
<cffunction name="getFoo">
<cfreturn foo>
</cffunction>
<cf_child fn="#getFoo#">
<cfdump label="variables" var="#variables#" />
child.cfm:
<cfset foo = "child">
<cfset caller.result = attributes.fn()>
What will result be in the dump? Since foo inside getFoo is not bound by the definition - it is bound by the calling context - getFoo must return the foo in the child context.
Posted by Sean Corfield on Apr 21, 2007 at 2:52 PM
@Sean,
The difference in our two examples is that your example passes the Function object to the child then invokes, while mine invokes the method as an attribute of the parent tag.
The funny thing is that BOTH of our examples lead to the same outcome. However, if you look at ColdFusion components, A and B.... A.Foo() called from within the context of B will bind to A (think getters and setters) but Foo passed from A to B and then invoked in B will bind to B.
Obviously ColdFusion components and ColdFusion custom tags are two different beasts, but both have their own memory scopes and their own rules of visibility... but until it was explored, I think it would not be obvious which scope binds when/where.
Posted by Ben Nadel on Apr 22, 2007 at 10:14 AM
Like I say, it still seems obvious to me. A component method invocation binds in the dynamic context of the component because, well, that's how objects work. Custom tags are just regular pages so they're not going to work that way.
That's why you can copy function references in and out of components and have them bind <tt>variables</tt> scope to that calling context regardless. Components are objects, everything else is a page.
Now, of course, technically components are pages too and functions all compile out to separate objects but the object-style binding only occurs for components...
My Closures library would allow you to "fix" the custom tag behavior. You could create a closure in your parent tag and bind <tt>variables</tt> scope into it (under an alias) and then invoke the closure from the child tag and have it "correctly" reference the parent's <tt>variables</tt> scope (via the bound alias).
Posted by Sean Corfield on Apr 22, 2007 at 1:45 PM
Hmm, your blog doesn't recognize <tt>..</tt> as "teletype" markup to create monospaced text! :)
Posted by Sean Corfield on Apr 22, 2007 at 1:46 PM
@Sean,
I see what you are saying, and I agree. I guess since ColdFusion custom tags behave slightly differently than a standard page template, I just thought maybe the properties of it might be different. But you are quite right - it is just page like anything else.
Posted by Ben Nadel on Apr 22, 2007 at 2:51 PM
Oh yeah, I have also been lazy about what the comments on my blog recognize. I will update one of these days.
Also, this is all just for experimentation's sake. What was not obvious to me before will now be obvious to me in the future :)
... wearing dark glasses, getting good grades, the future's so bright, I gotta wear shades :)
Posted by Ben Nadel on Apr 22, 2007 at 2:53 PM