A key custom tag feature is the ability of collaborating custom tags to exchange complex data without user intervention and without violating the encapsulation of a tag's implementation outside the circle of its collaborating tags. The following issues need to be addressed:
To enable developers to obtain maximum productivity in an environment with few restrictions, CFML custom tags can expose all their data to collaborating tags.
When you develop custom tags, you should document all variables that collaborating tags can access and/or modify. When your custom tags collaborate with other custom tags, you should make sure that they do not modify any undocumented data.
To preserve encapsulation, put all tag data access and modification operations into custom tags. For example, rather than simply documenting that the variable MyQueryResults in a tag's implementation holds an important query result set and expecting users of the custom tag to manipulate MyQueryResults directly, create another nested custom tag that manipulates MyQueryResult. This protects the users of the custom tag from changes in the tag's implementation.
Two custom tags can be related in a variety of ways in a page. Ancestor and descendant relationships are important because they relate to the order of tag nesting.
A tag's descendants are inactive while the page is executed, that is, they have no instance data. The tag's data access is therefore restricted to ancestors only. Ancestor data will be available from the current page and from the whole runtime tag context stack. The tag context stack is the path from the current tag element back up the hierarchy of nested tags, including those in included pages and custom tag references, to the start of the base page for the request. CFINCLUDE tags and custom tags will appear on the tag context stack.
There are many cases in which descendant tags are used only as a means for data validation and exchange with an ancestor tag, such as CFHTTP/CFHTTPPARAM and CFTREE/CFTREEITEM. You can use the CFASSOCIATE tag to encapsulate this processing.
When CFASSOCIATE is encountered in a sub tag, the sub tag's attributes are automatically saved in the base tag. The attributes are in a structure appended to the end of an array whose name is `ThisTag.collection_name'. The default value for the DataCollection attribute is `AssocAttribs'. This attribute should be used only in cases where the base tag can have more than one type of sub tag. It is convenient for keeping separate collections of attributes, one per tag type.
CFASSOCIATE performs the following operations:
<!--- Get base tag instance data ---> <CFSET data = getBaseTagData(baseTag).thisTag> <!--- Create a string with the attribute collection name ---> <CFSET collectionName = 'data.#dataCollection#"'> <!--- Create the attribute collection, if necessary ---> <CFIF not isDefined(collectionName)> <CFSET "#collectionName#" = arrayNew(1)> </CFIF> <!--- Append the current attributes to the array ---> <CFSET temp=arrayAppend(evaluate(collectionName), attributes)>
The CFML code accessing sub-tag attributes in the base tag could look like the following:
<!--- Protect against no sub-tags ---> <CFPARAM Name='thisTag.assocAttribs' default=#arrayNew(1)#> <!--- Loop over the attribute sets of all sub tags ---> <CFLOOP index=i from=1 to=#arrayLen(thisTag.assocAttribs)#> <!--- Get the attributes structure ---> <CFSET subAttribs = thisTag.assocAttribs[i]> <!--- Perform other operations ---> </CFLOOP>
The ancestor's data is represented by a structure object that contains all the ancestor's data.
The following set of functions provide access to ancestral data:
GetBaseTagList()
-- Returns a comma-delimited list of uppercased ancestor tag names. An empty string is returned if this is a top-level tag. The first element of a non-empty list is the parent tag.
GetBaseTagData(TagName, InstanceNumber=1)
-- Returns an object that contains all the variables, scopes, etc. of the nth ancestor with a given name. By default, the closest ancestor is returned. If there is no ancestor by the given name or if the ancestor does not expose any data (such as CFIF), an exception is thrown.
This example was snipped from a custom tag.
<CFIF thisTag.executionMode is 'start'> <!--- Get the tag context stack The list will look something like "CFIF,MYTAGNAME..." ---> <CFSET ancestorList = getBaseTagList()> <!--- Output your own name because CFIF is the first element of the tag context stack ---> <CFOUTPUT> I'm custom tag #ListGetAt(ancestorlist,2)#<P> </CFOUTPUT> <!--- Determine whether you're nested inside a loop ---> <CFSET inLoop = ListFindNoCase(ancestorList,'CFLOOP')> <CFIF inLoop neq 0> I'm running in the context of a CFLOOP tag.<P> </CFIF> <!--- Determine whether you are nested inside a custom tag. Skip the first two elements of the ancestor list, i.e., CFIF and the name of the custom tag I'm in ---> <CFSET inCustomTag = ''> <CFLOOP index=elem list=#ListRest(ListRest(ancestorList))#> <CFIF (Left(elem, 3) eq 'CF_')> <CFSET inCustomTag = elem> <CFBREAK> </CFIF> </CFLOOP> <CFIF inCustomTag neq ''> <!--- Say you are there ---> <CFOUTPUT> I'm running in the context of a custom tag named #inCustomTag#.<P> </CFOUTPUT> <!--- Get the tag instance data ---> <CFSET tagData = getBaseTagData(inCustomTag)> <!--- Find out the tag's execution mode ---> I'm located inside the <CFIF tagData.thisTag.executionMode neq 'inactive'> template because the tag is in its start or end execution mode. <CFELSE> body </CFIF> <P> <CFELSE> <!--- Say you are lonely ---> I'm not nested inside any custom tags. :^( <P> </CFIF> </CFIF>
Attributes can be passed to custom tags via the reserved attribute ATTRIBUTECOLLECTION. ATTRIBUTECOLLECTION must reference a structure.
CFMODULE
<CFMODULE TEMPLATE=template OTHERATTR1=value ATTRIBUTECOLLECTION=structure OTHERATTR2=value>
Shorthand
<CF_MYCUSTOMTAG OTHERATTR1=value ATTRIBUTECOLLECTION=structure OTHERATTR2=value>
The key/value pairs contained within the structure specified by ATTRIBUTECOLLECTION will be copied into the ATTRIBUTES scope. This has essentially the same effect as specifying these attributes in the custom tag's attribute list.
ATTRIBUTECOLLECTION may be freely mixed with other attributes within the custom tag's attribute list.
Custom tag processing reserves ATTRIBUTECOLLECTION to refer to the structure holding a collection of custom tag attributes. If ATTRIBUTECOLLECTION does not refer to such a collection, the custom tag processor will raise a TEMPLATE exception.
A custom tag invoked by the two examples above may refer to #attributes.x# and #attributes.y# to access the attributes passed via structure.
If the called custom tag uses a CFASSOCIATE tag to save its attributes in the base tag, the attributes passed via structure will be saved as independent attribute values, with no indication that they were aggregated into a structure by the custom tag's caller.
![]() |
Via CFMODULE |
<CFSET zort=StructNew()> <CFSET zort.X = "-X-"> <CFSET zort.Y = "-Y-"> <CFMODULE TEMPLATE="testtwo.cfm" a="blab" attributecollection=#zort# foo="16">
![]() |
Via shorthand |
<CFSET zort=StructNew()> <CFSET zort.X = "-X-"> <CFSET zort.Y = "-Y-"> <CF_TESTTWO a="blab" attributecollection=#zort# foo="16">
If testtwo.cfm
contains this CFML:
---custom tag ---<br> <CFOUTPUT>#attributes.a# #attributes.x# #attributes.y# #attributes.foo#</cfoutput> <BR>--- end custom tag ---
Its output will be:
---custom tag --- blab -X- 12 16 --- end custom tag ---