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:
<!--- Impor the tag libraries. ---> <cfimport taglib="./" prefix="tag" /> <tag:parent> <tag:child name="Libby" /> <tag:child name="Andrea" /> <tag:child name="Kate" /> <tag:child name="Sam" /> <tag:child name="Sarah" /> </tag:parent>
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):
<!--- Check to see which execution mode the tag is running in. We won't have access to the child tag data until we are in the End tag mode. ---> <cfswitch expression="#THISTAG.ExecutionMode#"> <cfcase value="Start"> <!--- Define the tag-based functions. ---> <cffunction name="AddName" access="public" returntype="void" output="false" hint="Adds a name to this tag's names array."> <!--- Define arguments. ---> <cfargument name="Name" type="string" required="true" hint="A nested tag name attribute to add." /> <!--- Add this name to our internal names array that we defined below. ---> <cfset ArrayAppend( THISTAG.Names, ARGUMENTS.Name ) /> <!--- Return out. ---> <cfreturn /> </cffunction> <!--- Define the array or names that will house the Name attribute values our child (nested) tags. ---> <cfset THISTAG.Names = ArrayNew( 1 ) /> </cfcase> <cfcase value="End"> <!--- Output the names that we have collected from the child tags. ---> <p> Names: </p> <!--- Loop over our names array. ---> <cfloop index="intName" from="1" to="#ArrayLen( THISTAG.Names )#" step="1"> <p> #intName#) #THISTAG.Names[ intName ]# </p> </cfloop> </cfcase> </cfswitch>
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:
<!--- Check to see which execution mode the tag is running in. We won't have access to the child tag data until we are in the End tag mode. ---> <cfswitch expression="#THISTAG.ExecutionMode#"> <cfcase value="Start"> <!--- Get the base tag. ---> <cfset THISTAG.Parent = GetBaseTagData( "cf_parent" ) /> <!--- Define the tag attributes. Our only attribute is the Name attribute. Since we are not defining a default value, this is a REQUIRED attribute. ---> <cfparam name="ATTRIBUTES.Name" type="string" /> <!--- Now that we have the parent tag (base tag) pointer, let's add this tag's Name attribute value to the parent's name array. ---> <cfset THISTAG.Parent.AddName( ATTRIBUTES.Name ) /> </cfcase> </cfswitch>
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:
<!--- Dump out the THISTAG scope so that we can get a better of sense of where we are. ---> <cfdump var="#THISTAG#" label="THISTAG From Within AddName() UDF." top="2" /> <!--- Abort page processing so the previous error event doesn't fire. ---> <cfabort />
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.
Want to use code from this post? Check out the license.