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:
function onStartTag(struct a,struct b){
...
}
This has worked as long there were 2 structs in the arguments definition. With the new release (3.1.0.016) not the position of an argument is important, but the name is now decisive. So could write:
function onStartTag(struct attributes,struct caller){
...
}
or even
function onStartTag(struct caller,struct attributes){
...
}
Both function definitions are valid, since not the position but the name of the arguments is important. The first example however (onStartTag(a,b)) will no longer work. It is the same way Application.cfc works. There as well the order of the arguments in not important. Only the names are. But let us now have a look at what other opportunities CFC based custom tags offer in Railo.
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:
<cfparam name="attributes.variable">
I have made a custom implementation of the tag "cfsavecontent". But there is one difference when you do something like this:
<cf_savecontent susi="1" variable="a">Susi</cf_savecontent>
<cfsavecontent susi="1" variable="a">Susi</cfsavecontent>
In this example my custom tag would work with no error, but the build in tag on the second line would fail, because the attribute "susi" is of course not supported for the tag <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 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>
But attribute checkings are mostly the same for all tags. Because of that we have made a generic solution for attribute checking. You still can make your custom checking, for some special constelations but for the common stuff, you can simpy define requirments.I will again take the savecontent exampe to demonstrate this. Here is the call of the tag, like before not really spectacular:
<cf_savecontent variable="urs">Ursula</cf_savecontent>
<cfoutput>#urs#</cfoutput>
and here is the implementation of it:
<cfcomponent output="no">
<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>
and the output: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:<cfset name="Michael">
<cf_savecontent variable="urs">my name is #name#</cf_savecontent>
<cfoutput>#urs#</cfoutput>
You will get the following output: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:
<cfcomponent output="no">
<cfset this.metadata.parsebody=true>
...
</cfcomponent>
You get the following output:
my name is MichaelThis 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":<cf_savecontent>Ursula</cf_savecontent>
<cfoutput>#urs#</cfoutput>
The we should get the following error message:attribute [variable] is required for tag [Savecontent]
Now we will define an attribute that is not supported by our tag:
<cf_savecontent variable="urs" susi="1">Ursula</cf_savecontent>
<cfoutput>#urs#</cfoutput>
So we are supposed to get the error message:attribute [susi] is not supported for tag [Savecontent]
Last but not least we will pass the wrong value type for the attribute:
<cf_savecontent variable="#ListToArray('susi,urs')#">Ursula</cf_savecontent>
<cfoutput>#urs#</cfoutput>
We now get the error message: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!
2 responses so far ↓
1 andrea // Jun 17, 2009 at 9:55 PM
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
2 Michael Offner-Streit // Jun 18, 2009 at 11:20 AM
you have to set <cfset this.metadata.attributetype="fixed"> as well, type "dynamic" (default) ignore the "attributes" definition
/micha
Leave a Comment