![]() ![]() ![]() |
This release of ColdFusion adds significant new features to CFML custom tags. These changes are part of overall architectural enhancements designed to address requests for greater power and flexibility in custom tags.
In CFML 4.0, any tag with an end tag present can be an ancestor to another tag.
For developers seeking to encapsulate complex functionality or data operations, nesting custom tags can be a productive mechanism. The logic of nesting tags is based on the relationship you establish between elements within a custom tag. Generally, ancestor/descendant and parent/child terminology is used to describe nested hierarchies, as it provides an easily recognizable frame of reference. A more generic terminology uses the idea of base tags and the sub-tags they contain. These relationships can be framed according to individual preferences and the exact nature of a given hierarchy.
While the ability to create nested custom tags is a tremendous productivity gain, keeping track of complex nested tag hierarchies can become a chore. A simple mechanism, the CFASSOCIATE tag, lets the parent know what the children are up to. By adding this tag to a sub-tag, you enable communication of its attributes to the base tag.
See High-level data exchange for details.
During the execution of a custom tag template ColdFusion keeps some amount of data related to the tag instance. The ThisTag scope is used to preserve this data with a unique identifier. The behavior is similar to the File scope.
The following variables are generated by the ThisTag scope:
The same CFML template may be executed for both the start and end tag of a custom tag.
A custom tag template may be invoked in either of two modes:
If an end tag is not explicitly provided and shorthand empty element syntax (<TagName .../>) is not used, then the custom tag template will be invoked only once in start tag mode. If a tag must have an end tag provided, use ThisTag.HasEndTag during start tag execution to validate this.
A variable with the reserved name ThisTag.ExecutionMode will specify the mode of invocation of a custom tag template. The variable will have one of the following values:
During the execution of the body of the custom tag, the value of the ExecutionMode variable is going to be inactive. In this framework, the template of a custom tag that wants to perform some processing in both modes may look something like the following:
<CFIF ThisTag.ExecutionMode is 'start'> <!--- Start tag processing ---> <CFELSE> <!--- End tag processing ---> </CFIF>
CFSWITCH can also be used:
<CFSWITCH expression=#ThisTag.ExecutionMode#> <CFCASE value='start'> <!--- Start tag processing ---> </CFCASE> <CFCASE value='end'> <!--- End tag processing ---> </CFCASE> </CFSWITCH>
CFEXIT terminates execution of a custom tag. In ColdFusion 4.0, CFEXIT has been extended with a METHOD attribute that specifies where execution continues. With the introduction of start and end tags for custom tags, CFEXIT can specify that processing continues from the first child of the tag or continues immediately after the end tag marker.
The METHOD attribute can also be used to specify that the tag body should be executed again. This enables custom tags to act as high-level iterators, emulating CFLOOP behavior.
The following table summarizes CFEXIT behavior:
CFEXIT Behavior in a Custom Tag | ||
---|---|---|
METHOD Attribute Value |
Location of CFExit Call |
Behavior |
ExitTag (default) |
Base template |
Acts like CFABORT |
|
ExecutionMode=start |
Continue after end tag |
|
ExecutionMode=end |
Continue after end tag |
ExitTemplate |
Base template |
Acts like CFABORT |
|
ExecutionMode=start |
Continue from first child in body |
|
ExecutionMode=end |
Continue after end tag |
Loop |
Base template |
Error |
|
ExecutionMode=start |
Error |
|
ExecutionMode=end |
Continue from first child in body |
Custom tags can access and modify the generated content of any of its instances using the ThisTag.GeneratedContent variable. In this context, the term generated content means the portion of the results that is generated by the body of a given tag. This includes all results generated by descendant tags, too. Any changes to the value of this variable will result in changes to the generated content.
ThisTag.GeneratedContent is always empty during the processing of a start tag. Any output generated during start tag processing is not considered part of the tag's generated content.
As an example, consider a tag that comments out the HTML generated by its descendants. Its implementation could look something like this:
<CFIF ThisTag.ExecutionMode is 'end'> <CFSET ThisTag.GeneratedContent = '<!--#ThisTag.GeneratedContent#-->'> </CFIF>
A key custom tag feature for CFML 4.0 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 4.0 custom tags can expose all their data to collaborating tags.
Custom tag developers should document all variables that collaborating tags can access and/or modify. Developers of custom tags that collaborate with other custom tags should make sure that they do not modify any undocumented data.
We highly recommend that developers preserve encapsulation by putting all tag data access and modification operations into custom tags. For example, rather than documenting that the variable Q in a tag's implementation holds an important query result set and expecting users of the custom tag to manipulate Q directly, the developer should create another nested custom tag that manipulates Q. 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 template. One can be a sibling, parent, ancestor, child, or descendant of the other. For most practical purposes, sibling and cousin relationships rarely matter. Ancestor and descendant relationships do matter because they relate to the order of tag nesting.
A tag's descendants are inactive while the template 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 template 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.
To avoid ambiguity, the following naming convention should be observed for CFML tags:
Note that since this naming convention makes the name of a custom tag template the same as the name of the tag, name collisions are possible. However, locality of reference suggests that tags with a close ancestral relationship are likely to be related. It is unlikely that unrelated tags with the same template name have a close ancestral relationship.
The ancestor's data is represented by a structure object that contains all the ancestor's data, in much the same way that a COM object in CFML contains properties.
The following set of functions provide access to ancestral data:
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 are 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>
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.
The tag syntax is:
<CFASSOCIATE BaseTag=base_tag_name DataCollection=collection_name>
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>
![]() ![]() ![]() |
AllaireDoc@allaire.com
Copyright © 1998, Allaire Corporation. All rights reserved.