3-2:CFC_based_Custom_Tags_Examples
Part 1
This entry shows you how to use CFC-based Custom Tags through examples. In Railo 3.1, we have introduced Custom Tags based on CFML Components. If you are not familiar with Custom Tags at all, please first consult other resources to learn in detail about Custom Tags. 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)
Part 2
In the first Part we have looked at the major functions for CFC based Custom Tags, and how you can manipulate the environment and control the flow. In this entry we will look into exception handling.Let me start with the first example, which is catching an exception. For this we take the example "savecontent" from first part.
<cf_savecontent variable="urs" catch="yes"><cfthrow message="Hello Urs"></cf_savecontent> <cfoutput>#urs#</cfoutput>
And the implementation of the CFC custom tag. <cfcomponent>
<cffunction name="onStartTag" output="no"> <cfargument name="attributes" type="struct" required="yes"> <cfargument name="caller" type="struct" required="yes"> <cfset this.attributes=attributes> <cfset this.caller=caller> </cffunction> <cffunction name="onEndTag" output="no"> <cfargument name="attributes" type="struct" required="yes"> <cfargument name="caller" type="struct" required="yes"> <cfargument name="output" type="string" required="yes"> <cfparam name="attributes.variable"> <cfset caller[attributes.variable]=output> </cffunction> <cffunction name="onError" output="no" returntype="boolean" hint="invoked when a failure occurs while executing the tag inside a function or the body. The return value defines if the exception should be caught or not"> <cfargument name="err" type="struct" required="yes" hint="exception information as a struct. The same one you get as inside a cfcatch block"> <cfargument name="source" type="string" required="yes" hint="Define where the failure has occurred, possible values are [start,body,end]"> <cfparam name="this.attributes.catch" type="boolean" default="#false#"> <cfset this.caller[this.attributes.variable]="error:"&err.message> <cfreturn not this.attributes.catch> </cffunction> </cfcomponent>
the output:
error:Hello Urs
As you can see we have introduced a new method here called "onError". You can compare this function with the "onError" method of Applcation.cfc. But with the "onError" of the tag you catch only exceptions that are thrown in the body, the method "onStartTag" or the method "onEndTag" of the tag. The first argument "err" gives you the cfcatch struct of the error whereas the second argument informs you where the exception was thrown, in the start-, endtag or in the body. With the return value you define whether the exception should be ended here or rethrown. In this case you can define with the help of the attribute "catch" whether the exception should be rethrown or not.
Ok, now we have a method that is invoked when a failure occurs. As you can see in the example above, the methods are never invoked both. If a failure occurs in the body, the method "onEndTag" will not be executed. If no failure occurs the method "onError" will not be invoked either. In some cases it would be good to have a method that is invoked no matter whether an exception has occurred or not. Let me make an example for this. We will use the savecontent custom tag again for this.
<cftry> <cf_savecontent2 variable="urs"> <cfthrow message="Hello Urs"> </cf_savecontent2> <cfcatch></cfcatch> </cftry> <cfoutput>#urs#</cfoutput><br /> <cf_savecontent2 variable="urs">Hello Urs</cf_savecontent2> <cfoutput>#urs#</cfoutput>
and the implementation of the tag <cfcomponent>
<cffunction name="onStartTag" output="no"> <cfargument name="attributes" type="struct" required="yes"> <cfargument name="caller" type="struct" required="yes"> <cfset this.caller=caller> <cfparam name="attributes.variable"> <cfset this.name=attributes.variable> <cfset this.value="empty"> </cffunction> <cffunction name="onEndTag" output="no"> <cfargument name="attributes" type="struct" required="yes"> <cfargument name="caller" type="struct" required="yes"> <cfargument name="output" type="string" required="yes"> <cfset this.value=output> </cffunction> <cffunction name="onFinally" output="no" returntype="void" hint="invoked after the tag is executed in any case, even the tag throws a non caught exception"> <cfset this.caller[this.name]=this.value> </cffunction> </cfcomponent>
the output: empty
Hello Urs Here we now have the last method supported by CFC based Custom Tags: "onFinally". This method is executed after all other methods, in any case, even if an exception has occurred in the body or in one of the functions executed before. This can be really helpful for example to close streams (ftp,file,...) opened in one of the functions before or doing something that has to be done in every case. Ok, this was exception handling with CFC based Custom Tags. In the last part we will look how you can use CFC based Custom Tags as Build in Tags and how you can define requirements for custom tags.
SideBar
User Login