Thursday, April 03, 2003

Using CustomTags in ColdFusion

Using custom tags in the presentation layer can greatly organize your code. Here's a quick start guide that I wrote up. Building a simple ColdFusion custom tag is as easy as:

A simple custom tag

create a file called todaysdate.cfm:

<cfoutput>#DateFormat(Now(), "mm/dd/yyyy")#</cfoutput>

Then place todaysdate.cfm in ColdFusion server's CustomTags directory, or in the same directory that you will call it from. To call it simply create a page with the following:

<cf_todaysdate>

Using attributes

To pass an attribute into a tag, is also fairly easy.
greet.cfm:
<cfparam name="attributes.name" default="Dude" type="string">
<cfoutput>Hello #attributes.name#!</cfoutput>

Now to call it:

<cf_greet name="Pete">

In the greet tag there is an attribute called "name" which we passed the value "Pete" to in our example. We can access attributes using the attributes scope, attributes.attributename. In this tag we used the <cfparam> tag to set the default value of the name attribute to "Dude".

Using start and end tags

Another thing you can do with custom tags is to use start and end tags. Something like this:

<cf_myTag> This is some content </cf_myTag>

To access the content inside the tag we use another scope called thisTag. The thisTag scope contains variables that hold information about the tag that is being executed, such as the content between the start and end tags. Such content may be accessed with the variable thisTag.generatedContent.

Because you may want to do some processing when the start tag is invoked, and then some more when the end tag is invoked, your custom tag will file will be executed twice when you use an end tag. Luckily you can use the variable thisTag.executionMode to determine if your currently executing the start tag or the end tag. Lets consider a highly trivial example, of creating a ColdFusion tag to make text bold, there are several ways we can do this. Here is the simplest:

bold.cfm:
<cfif thisTag.executionMode IS "start"><b><cfelse></b></cfif>

The above tag will output a <b> tag when the start tag executes, and a </b> tag when the end tag executes.

Another way of solving this problem is to use the thisTag.generatedContent variable:

bold.cfm:
<cfif thisTag.executionMode IS "end">
	<cfset thisTag.generatedContent = 
		"<b>" & thisTag.generatedContent & "</b>">
</cfif>

In this case we are resetting the value of thisTag.generatedContent to include the <b> tags. When the end tag is finished executing ColdFusion will output the contents of thisTag.generatedContent.

Note that the value of thisTag.generatedContent is not set until the end tag begins executing. Setting the value of it in the start tag execution mode will have no effect on the output.

A third way to solve this problem is similar to the last, but in many cases this will be the way to go:

bold.cfm:
<cfif thisTag.executionMode IS "end">
	<cfoutput><b>#thisTag.generatedContent#</b></cfoutput>
	<cfset thisTag.generatedContent = "">
</cfif>

In this example your handling the output yourself, and setting the value of thisTag.generatedContent to an empty string. This is the way to go if your using a lot of conditionals to produce your output.

A few more tricks we can use to ensure that our tag is being called properly include:

To ensure that the end tag is present:

<cfif thisTag.executionMode IS "end">
	Do something.
<cfelseif NOT thisTag.hasEndTag>
	<cfthrow message="Missing end tag.">
</cfif>

To ensure that the file is only called as a custom tag:

<cfif NOT IsDefined("thisTag.executionMode")>
	Must be called as customtag.<cfabort>
</cfif>
<cfif thisTag.executionMode IS "end">
	Do something.

<cfelseif NOT thisTag.hasEndTag>
	<cfthrow message="Missing end tag.">
</cfif>

Update 4/4/03:Ray Camden also suggested this handy code snippet:

To ignore an end tag:

<cfif thisTag.executionMode is "end">
    <cfexit>
</cfif>

That code simply ignores the end tag. This is useful if your trying to use valid XML within your CFML, such as <cf_mytag />. Many people have gotten in the habit of doing that now, but keep in mind that CFML is not valid XML, mainly CFIF statements tend to break the rules.