OK let us start with the first example
We have the following call:
<cf_hello><br />
<cf_hello name="Urs">
The implementation looks as follows (hello.cfc):
<cfcomponent>
<cffunction name="onStartTag" output="yes" returntype="boolean"
hint="invoked before the body of the tag is executed, return value define if body should be executed or not (default is true)">
<cfargument name="attributes" type="struct" required="yes"
hint="attribute collection provided by tag caller, same as attribute scope in cfm based custom tags">
<cfargument name="caller" type="struct" required="yes"
hint="variable scope of the caller, same as caller scope in cfm based custom tags">
<cfparam name="attributes.name" default="World">
Hello #attributes.name#
<cfreturn true>
</cffunction>
</cfcomponent>
Output:Hello World
Hello Urs
You see it is not a big deal, you have a function (onStartTag) for the start tag and in this function you get the attribute scope and caller scope (the caller's variable scope) as arguments.
Let us go forward to the next example, we simulate the built-in tag cfsavecontent:
<cf_savecontent variable="urs">Hello Urs</cf_savecontent>
<cfoutput><b>#urs#"</b></cfoutput>
The implementation looks as follows (savecontent.cfc):
<cfcomponent>
<cffunction name="onEndTag" output="no" returntype="boolean"
hint="invoked after the body of the tag is executed, return value define if body should be executed again or not (default is false)">
<cfargument name="attributes" type="struct" required="yes"
hint="attribute collection provided by tag caller, same as attribute scope in cfm based custom tags">
<cfargument name="caller" type="struct" required="yes"
hint="variable scope of the caller, same as caller scope in cfm based custom tags">
<cfargument name="output" type="string" required="yes"
hint="output produced by tag body">
<cfparam name="attributes.variable">
<cfset caller[attributes.variable]=output>
<cfreturn false>
</cffunction>
</cfcomponent>
Output:Hello Urs
In this case we have the counterpart to onStartTag, the onEndTag function is executed when the end tag is executed. The difference here is the 3rd argument "output", this argument contains the body of the tag. It is up to you what happens with this output, you can write it back to response stream, write to a variable or whatever you want.
And now to show a little bit more control over the flow of the tag, we make a simple loop tag, we just define how many time the loop runs:
<cf_repeat count="10">hello,</cf_repeat>
The implementation looks as follows (repeat.cfc):
<cfcomponent output="no">
<cfset this.index=1>
<cffunction name="onStartTag" output="no" returntype="boolean">
<cfargument name="attributes" type="struct" required="yes">
<cfargument name="caller" type="struct" required="yes">
<cfparam name="attributes.count" type="numeric">
<cfreturn true>
</cffunction>
<cfscript>
function onEndTag(struct attributes,struct caller,string output){
writeOutput(output);
return this.index++ LT attributes.count;
}
</cfscript>
</cfcomponent>
Output:hello, hello, hello, hello, hello, hello, hello, hello, hello, hello,
In this case I use a script function for "onEndTag" as this gives me more control over the response stream. You see with the return value I can control if the tag is re-executed or not.
Now we show some interaction between tags. You can use the tag cfassociate and the function GetBaseTagList and GetBaseTagData the same way as for regular CFML based custom tags. These features are not really easy to use, so we looked for another way of interaction between tags:
<cf_parent>
<cf_child name="Urs"/>
<cf_child name="Peter"/>
</cf_parent>
The implementation looks as follows:child.cfc
<cfcomponent>
<cffunction name="init" output="no" returntype="void"
hint="invoked after tag is constructed">
<cfargument name="parent" type="component" required="yes"
hint="the parent cfc custom tag, if there is one">
<cfargument name="hasBody" type="boolean" required="yes"
hint="does the tag has a body or not">
<cfset parent.setChild(this)>
</cffunction>
<cffunction name="onStartTag" output="yes" returntype="boolean">
<cfargument name="attributes" type="struct" required="yes">
<cfargument name="caller" type="struct" required="yes">
<cfset this.name=attributes.name>
<cfreturn false>
</cffunction>
<cffunction name="getName" output="no" returntype="string"
hint="return the name of the child">
<cfreturn this.name>
</cffunction>
</cfcomponent>
parent.cfc
<cfcomponent>
<cfset this.children=[]>
<cffunction name="setChild" output="no" returntype="void">
<cfargument name="child" type="component" required="yes"
hint="child tag">
<cfset ArrayAppend(this.children,child)>
</cffunction>
<cffunction name="onEndTag" output="yes" returntype="boolean">
<cfargument name="attributes" type="struct" required="yes">
<cfargument name="caller" type="struct" required="yes">
<cfargument name="output" type="string" required="yes">
<cfset var child="">
<cfloop array="#this.children#" index="child">
hello #child.getName()#,
</cfloop>
<cfreturn false>
</cffunction>
</cfcomponent>
Output:hello Urs, hello Peter,
You see we have indroduced a new function "init" here, which is called before the tag is executed. In this function you get the parent CFC-based custom tag as an argument (if there is one) and the information if this tag has a body or not. The big plus here is that CFC based custom tags are components and you can use them just like regular components here. This makes interaction between tags much simpler. OK this is the first part of how you can use CFC based custom tags, but there is a much more, stay tuned for Part 2. (you can find all examples attached as zip)
7 responses so far ↓
1 Ben Nadel // Jun 2, 2009 at 4:56 PM
Are the tag scopes properties of the component (THISTAG, ATTRIBUTES, VARIABLES, CALLER)? Meaning, once I have the parent tag, can you do something like:
Parent.THISTAG.HasEndTag
Very interesting stuff.
2 Peter J. Farrell // Jun 2, 2009 at 5:18 PM
3 Gert Franz // Jun 2, 2009 at 6:03 PM
@Peter: Why are we doing this? Well several reasons:
- In part two you will see how easy it is to define requirements for custom tag attributes
- You have much more control over the flow of the customtag. Just tell me for example how you create a loop with a regular customtag?
- In addition, and I consider this one of the most important ones, you can build up a complete hierarchy of tags where one extends another. So you can have a library of tags.
- By placing the tags in the folder /WEB-INF/railo/library/tag they automatically become core tags. This is how we are going to build CF tags that are producing HTML like all the AJAX tags.
Oh and finally, Peter, Michael wrote the blog entry, not me :-)
Gert
4 Gert Franz // Jun 2, 2009 at 6:06 PM
5 Todd Rafferty // Jun 2, 2009 at 7:14 PM
6 Michael Offner-Streit // Jun 4, 2009 at 6:40 PM
the great thing about CFC Based Custom tag is, you are working with components, mean when you get the parent CFC Tag in the init method, you have not only a struct with data, you have the real parent CFC with all possibilities to interact (see my parent child example in detail).
"Meaning, once I have the parent tag, can you do something like:Parent.THISTAG.HasEndTag"
when you want to get data from the parent tag (cfc) you can for example write this data to "this" scope in "onStartTag",
but when you take a look into the parent/child example, in this example i write the complete child tag (cfc) to the parent tag (cfc) with help of the function "addChild", then i have the collected childs in the "onEndTag" of the parent and i can do what i want there.
with cfml based custom tags, you only can see/access data from the parent tags, but with cfc based tags you have a full interaction.
from OO perspective it is not good to change data in parent from child, it is better to access the parent only with help of functions
7 Tim Schottler // Sep 1, 2011 at 10:59 PM
Leave a Comment