CFC-based Custom Tags by Example - Part 3
Sorry for the delay I had little time the last days for writing this blog entry. But in the end, here it is.
Let me begin with a short review of Part 1 and 2.
Review
Railo 3.1 allows you to write Custom Tags based on Components. You can define "listener" functions/methods that are invoked while CFML walks through the tag.- init(Component parent,boolean hasEndTag):void
this function is called when the Tag is initialized. The argument "parent" contains (if present) the parent CFC based component. The argument "hasEntTag" defines whether the tag has a end tag or not. - onStartTag(Struct attributes,Struct caller):boolean
this function is called before the body is executed. the argument "attributes" holds the attributes defined in the start tag and the argument "caller" represents the callers variables scope. With the help of the return value you can define whether the body will be executed or not. - onEndTag(Struct attributes,Struct caller, String generatedContent):boolean
this function is called after the body is executed. The first and second argument of this function have the same meaning as the same arguments in the "onStartTag" function. This function has a third argument called "generatedContent". This argument contains the content generated between start and end tag, it is up to this function what is happening with this content. with help of the return value you can define if the body should be re executed again or not. - onError(Struct cfcatch,String source):boolean
this function is called when an exception is thrown inside start/end tag or the component body. The argument "cfcatch" holds the information about the exception and "source" defines where the exception was thrown (start, end, body). With the help of the return value you can define whether the exception will be rethrown or not. - onFinally():void
this function is called in any case after the tag has been executed, this means that even after an uncaugth excpetion is thrown before, this function is executed.
Minor Changes
We had a lot of input for this functionality lately. Because of this that we have improved some things which could have an impact on existing code, sorry for that I advance. At least our examples in Part 1 and 2 will no longer work without some minor changes (check out the changed examples below). We changed the "listener" function call from unamed function arguments to named function arguments.What does this mean?
In prior releases only the order of the arguments was important, the name of the argument was up to you. So you could have written the following method:
...
}
...
}
...
}
Requirments
When you have seen my examples in the first 2 parts, you have seen that I have done a customized attribute checking and that attribute checking was very poorly implemented.Example:
<cfsavecontent susi="1" variable="a">Susi</cfsavecontent>
<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 var keys=structKeyList(attributes)>
<cfset var index=ListFindNoCase(keys,'variable')>
<cfset keys=ListDeleteAt(keys,index)>
<cfif ListLen(keys) GT 0>
<cfthrow message="the following attributes are not supported
for tag cf_savecontent [#keys#]">
</cfif>
<cfset caller[attributes.variable]=output>
</cffunction>
I will again take the savecontent exampe to demonstrate this. Here is the call of the tag, like before not really spectacular:
<cfoutput>#urs#</cfoutput>
<cfset this.metadata.attributetype="fixed">
<cfset this.metadata.attributes.variable={required:true,type:"string"}>
<cffunction name="onEndTag" output="no" returntype="boolean">
<cfargument name="attributes" type="struct" required="yes">
<cfargument name="caller" type="struct" required="yes">
<cfargument name="generatedContent" type="string" required="yes">
<cfset caller[attributes.variable]=generatedContent>
<cfreturn false>
</cffunction>
</cfcomponent>
Ursula
metadata.attributetype
Like you can see we define some "metadata" to the tag. At first we define the attribute type ("attributetype"). Here you can chose between 2 different attribute types- dynamic (default)
when you define this type there are no restrictions regarding the number attributes. This is how custom tags have handled attributes in past - fixed
when you define the type fixed, only the attributes you define in the metadata are allowed, nothing else.
metadata.attributes
Like you can see in the example above you can create one entry in the "attributes" struct for every attribute. The name of the key in the struct entry is corresponding to the name of the attributes. Then you can use the following keys for a single entry:- required
is this attribute required or not - type
the type of the attribute - default
a default value for the attribute - hint (not supported yet)
a description for the tag attribute, you can see this information with help of the build in tag "getFunctionData" as well as in the Railo admin documentation
metadata.parsebody (not supported yet)
This is an option we plan for the near future. Let me take again the "savecontent" tag as an example. When you make the following call of the tag:<cf_savecontent variable="urs">my name is #name#</cf_savecontent>
<cfoutput>#urs#</cfoutput>
my name is #name#
The expression inside ## is not parsed because there is no <cfoutput> surrounding it. When you now set the following inside the tag:
<cfset this.metadata.parsebody=true>
...
</cfcomponent>
This means that you do not longer need the <cfoutput> inside the tag. This feature will follow in the near future. CFQUERY would be a perfect example for a tag that has the "parsebody" functionality in it set to true.
metadata.hint (not supported yet)
Like for a single attribute you can define a "hint" for the tag. This is the description, you will be able to read with the help of the build in function "getFunctionData" and inside the Railo administrator.Some more test cases
After we have had a look into details of the metadata structs we test our example a little bit. First we call it without the attribute "variable":<cfoutput>#urs#</cfoutput>
attribute [variable] is required for tag [Savecontent]
Now we will define an attribute that is not supported by our tag:
<cfoutput>#urs#</cfoutput>
attribute [susi] is not supported for tag [Savecontent]
Last but not least we will pass the wrong value type for the attribute:
<cfoutput>#urs#</cfoutput>
can't cast Object type [Array] to a value of type [string]
As you can see the "metadata" can help you a lot with your attribute checkings.
Build in Tags
This point not only works CFC based custom tags, but as well with CFML based custom tags. Because Railo is an Open Source project, we are interested to have the community participate in the project. It's the community that uses Railo, why should not the community be able to extend Railo as well?We have had many people in the past asking for different features and they would even also be interested in implementing them, but due to the lack of Java knowledge it is not possible to them.
Because of that Railo now supports extending its functionality by simply copying a custom tag file into "{railo-configuration-directory}/library/tag". Then after a restart this tag is available as a build in tag, you have even the possibility of overwriting existing tags.
For future releases we plan to bundle some tags built in this way with Railo. Here a little collection of possible tags written as CFC based custom tags:
- form
- input
- select
- table
- tree
- dump
- slider
- cfdiv
- cfwindow
- ajaxproxy – this one is already in the making by Andrea Campolonghi
When you copy the tag to "{webroot}WEB-INF/railo/library/tag", then it will only be available for the current webapp/web context (local). If you on the other hand copy the tag to "{install}lib/railo-server/context/library/tag", then the tag is available for all webapps/web contexts (global).
Here's a little neat trick
For performance reasons the build in tags are trusted. But you can test your implementation if you define a custom tag path that points to your build in tag directory. Then you can test your tags as custom tags and build in tag at the same time.Future?
Like I have written before there will follow some addional metadata properties soon. At the moment the requirements are checked at runtime, but in future we will migrate this to compilation time, which will execution much faster.At this time I am sure that you think: "If I can build internal Railo Tags with CFML will they you support build in functions as well?"
Well the answer is simple: YES, with version 3.1.0.016 build in functions are supported as well. But more on this will follow in a separate blog entry.
Have fun using Railo!



If I use this syntax:
<cfset this.metadata.attributes={
id:{required:false,type:"string",default:""}
}/>
I expect that attributes.id exists as empty string.
Cause Looks like it does not exists at all.
Andrea
you have to set <cfset this.metadata.attributetype="fixed"> as well, type "dynamic" (default) ignore the "attributes" definition
/micha