I am going to be doing some work with a rather complex ColdFusion custom tag system and I wanted to get a bit more comfortable with how nesting ColdFusion custom tags works. The first thing I looked into was the ColdFusion CALLER scope. What is the CALLER scope? What does it refer to? Logically, it would seem that the CALLER scope refers to the scope that called the current custom tag. This is very obvious to me when I am using one custom tag - then, the CALLER scope clearly refers to the primary page. However, what happens when I have nested custom tags? Does the CALLER mean different things to the different tags?
To test this, I ran ColdFusion nested tags in two different ways. In the first experiment, both the parent tag (parent.cfm) and the child tag (child.cfm) are defined in the ColdFusion markup of the primary page (index.cfm). Then, in the second experiment, my primary page only defines the parent tag (parent2.cfm). Then, that parent tag explicitly defines the child tag within its own code.
The child custom tag is the same for both experiments and just sets a variable into its CALLER scope and then exits out:
<!--- Store a random value in the CALLER scope of this tag (the child tag). ---> <cfset CALLER.Name = "Libby" /> <!--- We only care about doing this once for the tag, so just exit out (in case there is an end tag). ---> <cfexit method="EXITTAG" />
The index page and the parent custom tag code change slightly for the two different experiments below.
Experiment One: Root Level Child Invocation
For this first experiment, both the parent custom tag and the child custom tag are invoked in the ColdFusion markup of the index.cfm page:
<!--- Import tag libraries. ---> <cfimport taglib="./" prefix="tag" /> <!--- Execute the nested tags. ---> <tag:parent> <tag:child /> </tag:parent> <!--- Dump out the local variables scope to see how it has been affected by the ColdFusion custom tag execution. ---> <cfdump var="#VARIABLES#" label="Top Level VARIABLES Scope (post tag execution)" />
As you can clearly see, this page defines both sets of custom tags. The parent.cfm ColdFusion custom tag is defined as:
<!--- Check to see which mode the tag is running in. ---> <cfswitch expression="#THISTAG.ExecutionMode#"> <cfcase value="End"> <!--- Dump out the contents of this tag's THISTAG and the VARIABLES scope. ---> <cfdump var="#THISTAG#" label="THISTAG Execution Mode: END" /> <cfdump var="#VARIABLES#" label="VARIABLES Execution Mode: END" /> </cfcase> </cfswitch>
In its end tag execution mode, it simply dumps out its local scopes. This way, we can see how the local scope of the parent tag might be affected. In addition, once the custom tags have executed, the index page CFDumps out its local variables. Running the above setup, we get the following CFDumps:
As you can see from the second CFDump (the VARIABLES scope of the parent tag), the value-setting of the CALLER scope from the child tag updated the CALLER scope being used by the parent tag. Then, from the third CFDump (the VARIABLES scope of the root index page), you can see that the value "Libby" from the child tag was stored into the root level VARIABLES scope.
This shows us that the CALLER scope in this experiment are the same for both the parent and child ColdFusion custom tags.
Experiment Two: Parent Level Child Invocation
For this experiment, we are going to let the parent tag explicitly define and invoke the child custom tag. The index page has been modified slightly to only define the parent tag:
<!--- Import tag libraries. ---> <cfimport taglib="./" prefix="tag" /> <!--- Execute the nested tags. This time, however, unlike our last experiment, we are going to let the parent tag explicitly call the child tag to see how that works. ---> <tag:parent2 /> <!--- Dump out the local variables scope to see how it has been affected by the ColdFusion custom tag execution. ---> <cfdump var="#VARIABLES#" label="Top Level VARIABLES Scope (post tag execution)" />
Now that the child tag is no longer part of the root index page, our parent ColdFusion custom tag code has been modified to define it:
<!--- Check to see which mode the tag is running in. ---> <cfswitch expression="#THISTAG.ExecutionMode#"> <cfcase value="Start"> <!--- Now, we are going to explicitly call the child tag (rather than rely oun our calling page to include it in the markup). ---> <cf_child /> </cfcase> <cfcase value="End"> <!--- Dump out the contents of this tag's THISTAG and the VARIABLES scope. ---> <cfdump var="#THISTAG#" label="THISTAG Execution Mode: END" /> <cfdump var="#VARIABLES#" label="VARIABLES Execution Mode: END" /> </cfcase> </cfswitch>
As you can see, in the Start mode of the parent tag, we are defining and invoking the child tag. Then, when we are in the End mode of the parent tag (as in the first experiment), we are CFDumping out the local scopes. Running the above code, we get the following CFDumps:
The noticeable change here is that the value "Libby", which was set into the CALLER scope via the child ColdFusion custom tag, is now showing up in the VARIABLES scope of the parent ColdFusion custom tag. In this case, the CALLER scope from within the child tag referred to the VARIABLES scope of the parent tag. Clearly, the CALLER scope was not the same for both tags.
This clearly shows us that CALLER scope points to the invoking page and is not related to the logical nesting of the CFML. If you look at this from a semantic standpoint, it makes sense. What is the CALLER scope? It is the local scope of the page which "called" the current custom tag. In the first experiment, even though the child was nested within the parent, it was in fact the root index page that was "calling" the child tag. Then, in the second experiment, the child tag was being "called" by the parent tag itself. Do not mix up the "parent" from the "caller"; these are two very different things depending on your markup.
Want to use code from this post? Check out the license.
nice write-up! Good reminder for all of us, and probably taught many folks a thing or two. Have you got into using cfassociate, getBaseTagData, and assocAttribs? CustomTags can be very powerful and if used properly will make you UI and "widget" development go much better: http://livedocs.adobe.com/coldfusion/7/htmldocs/wwhelp/wwhimpl/js/html/wwhelp.htm?href=00001086.htm
if folks are interested, I could post some examples on cfZen soon...
Thanks. I have done a ton with single ColdFusion custom tags in my applications, but have never really used nested tags in a production application. I have tinkered around with nested tags on my blog:
... and I think they are very cool. But, I want to do a fairly complex little set of tags and I really need to bone up on how they all relate. I am not a huge fan of how CFAssociate works. I really wish that in addition to THISTAG scope, there was also a PARENTTAG scope (or something to that affect). I feel that the way it works right now, the child tag is too closely tied to the parent tag (which can be good, can be bad).
With the use of a PARENTTAG scope, you could easily utilize the nested markup rather than having to rely on know the parent tags name or hack something to get with GetBaseTagList().
But anyway, I am on the hunt now :) Plan on seeing some more stuff.
@Ben - is this what you're looking for?
Good example... see my comments.
I haven't gotten very crazy with nesting custom tags but I've only gone as far as one level if I've done any nesting at all. For UI I have 3 major custom tags I use called table, column and paging. The table and column are used together to render an XHTML table with the fields you specify. The paging custom tag is separate as you might not always want to paginate tabular data although that is where its the most common. I love custom tags but besides those 3 I haven't found anything else I do regularly that would go well in a custom tag. A great post Ben, definitely something I'll bookmark if I need to do some advanced custom tag stuff.
Maybe I'm missing something in your thought processes but this behavior is exactly what I'd expect and "blindingly obvious" to me.
I think what I'd find enlightening is hearing folks who did *not* expect this behavior explain what they expected to happen and why. Anyone want to bare their soul on that?
I think where my thought process was going was that I had equated PARENT and CALLER. Once I messed that one up, it was no longer obvious if the CALLER would be the parent tag or the parent page.
In retrospect, it does make a lot more sense.
@Ben, interesting line of thought.
In your example:
the thing to remember is that both parent and child tags are executed by this page. The child tag is not executed by the parent tag. So caller is the same for both tags.
Reading your blog entries is proving fascinating for me because I have a very different computing background so it sometimes never occurs to me that certain things might behave differently. It's really helping me understand where a lot of CFers are coming from because some of the things you are highlighting are clearly common thinking for many people. Keep it up - I love your blog!
Glad you love the blog :) Hearing stuff like that always makes me feel good. As far as computing background, I was trained in Computer Science, but frankly, I was never all that good at it ;) I really started concentrating on web development early on and never really mastered the real comp sciences. I think that is why OOP and MVC is proving so difficult for me to understand.
But now that I see how ColdFusion custom tags work, it does make more sense.
What would you think about a PARENT pointer for custom tags? Something like THISTAG.Parent or THISTAG.ParentTag. I think that would be a cool idea, and would allow a more open-ended tag to easily be associated with multiple types of parent tags.
Is that a good idea? Or am I missing something obvious that would make this a silly idea?
@Ben, I haven't really thought about it too much because I never use custom tags - except in a few very rare cases where I want to specifically capture some layout widget behavior, in which case I've never needed anything complicated. I uses CFCs for all my encapsulation.
Having said that, Fusebox 5's custom lexicons do have a parent key - if the tag is nested - so you can walk up the lexical tree of tag invocations. However, custom lexicons all essentially share one big variables scope - in common with the way all Fusebox verbs operate.
Yeah, I hear you. I use custom tags for UI stuff, which really never gets all that complicated. But, I want to see what these babies can do :)