Building a Better Custom Tag
Building a Better Custom Tag

We've come a long way in the past couple of years. Not that long ago I was teaching how to write simple custom tags and encouraging developers to experiment with them.

Now Allaire's Developers Exchange has thousands of custom tags listed, and I see developers using them as part of everyday development. Considering that it's been only a few years since Jeremy Allaire asked me to write a few tags so his new Tag Gallery would contain some initial content, we've made real progress.

So now I'd like to up the ante a bit and challenge developers to build a better mousetrap (so to speak). Most of the newly available tags are new twists on old ideas, often the same old way of doing things. I'd like to see developers get creative with tag designs, making them as flexible and as reusable as possible. The better the tag design, the better the abstraction, the better the encapsulation, the better the interface - the more uses you'll find for it. That's really what custom tags are all about.

Building Basic Tags
The best way to explain what I mean is with an example, so here goes.

I've been doing lots of WAP work recently (as I know many of you have). When generating WAP content you've probably discovered that $ (the dollar sign) is a special character in WML - it's used to prefix variables. Any time $ is used in text that's not a variable, it has to be escaped as $$ (kind of like # is escaped as ## in CFML). I found myself using code like this throughout my application:

#Replace(variable, "$", "$$", "ALL")#

This Replace() function simply replaces all "$" with "$$". Now, when CF5 ships I'll create a user-defined function called WAPSafe() that will likely look something like this:

<CFSCRIPT>
function WAPSafe(string)
{
return Replace(string, "$", "$$", "ALL");
} </CFSCRIPT>
I'd then be able to simply format text as follows:
#WAPSafe(var)#
But user-defined functions are not available yet, so I created a simple custom tag using the code shown below:
<!--- Initialize defaults --->
<CFPARAM NAME="ATTRIBUTES.text" DEFAULT="">

<!--- Process text --->
<CFOUTPUT>#Replace(ATTRIBUTES.text, "$", "$$", "ALL")#</CFOUTPUT>

This tag, named <CF_WAPSafe>, takes a single attribute that contains the text to be processed. The code within the tag simply displays the processed text. To call this tag I'd do something like this: <CF_WAPSafe TEXT="#var#"> So far so good.

Dual Purpose Tags
Custom tags that process data shouldn't arbitrarily write output text. For maximum control, custom tags should return results to the caller page and let the caller code do whatever it needs with the data.

Here's the revised <CF_WAP-Safe> tag:

<!--- Initialize defaults --->
<CFPARAM NAME="ATTRIBUTES.text" DEFAULT="">
<CFPARAM NAME="ATTRIBUTES.variable" DEFAULT="WAPSAFE">

<!--- Process text --->
<CFSET output=Replace(ATTRIBUTES.text, "$", "$$", "ALL")>

<!--- Save to CALLER variable --->
<CFSET "CALLER.#ATTRIBUTES.variable#"=output>

The tag takes two attributes: the text to be processed and the name of a variable to be created in the CALLER scope (arbitrarily naming variables is bad form; the caller should always be able to specify the name of the variable to be created). The custom tag processes the data and then saves the converted text into a variable. To use this tag I'd do the following:

<CF_WAPSafe TEXT="#var#" VARIABLE="wap_var">
<CFOUTPUT>#wap_var#</CFOUTPUT>
There are now two versions of the same tag: one that saves converted text to a variable and one that displays it. Even though best practices dictate that saving the results to a variable is preferred, there may be occasions when I'd want to have the processed text dumped to the output. So why not support both modes of operation as follows?
<!--- Initialize defaults --->
<CFPARAM NAME="ATTRIBUTES.text" DEFAULT="">
<CFPARAM NAME="ATTRIBUTES.variable" DEFAULT="">

<!--- Process text --->
<CFSET output=Replace(ATTRIBUTES.text, "$", "$$", "ALL")>

<!--- Check if VARIABLE passed --->
<CFIF ATTRIBUTES.variable IS "">
<!--- Display it --->
<CFOUTPUT>#output#</CFOUTPUT>
<CFELSE>
<!--- Save to CALLER variable --->
<CFSET "CALLER.#ATTRIBUTES.variable#"=output>
</CFIF>

This custom tag is the same as the previous version except that the final <CFIF> statement checks if the VARIABLE attribute was specified. If it was, the processed data is saved to the specified variable; otherwise it's displayed using a regular <CFOUTPUT> block.

With minimal extra work the custom tag is now a bit more useful.

The Tag Pair Solution
There's another version of this custom tag that would be useful. Instead of passing the text to be processed as a variable, it would be nice to be able to simply enclose it within tags, such as:

<CF_WAPSafe>
..
</CF_WAPSafe>
This way I could use all sorts of functions, expressions, tags, whatever I needed, and be sure that the generated output was safely formatted.

ColdFusion makes writing this type of custom tag very easy; here's the code for the new version:

<!--- Only process in End mode --->
<CFIF ThisTag.ExecutionMode IS "End">
<!--- Process text --->
<CFSET output=Replace(ThisTag. GeneratedContent, "$", "$$", "ALL")>
<!--- Update content --->
<CFSET ThisTag.GeneratedContent=output>
</CFIF>

This is a great example of using ColdFusion tag pairs. The tag itself is called twice, once when <CF_WAPSafe> (the start tag) is processed and again when </CF_WAPSafe> (the end tag) is processed. When the start tag is processed, the tag is called and ThisTag.ExecutionMode will be "Start". When the end tag is processed, the tag will be called again and ThisTag.ExecutionMode will be "End". Obviously, the custom tag needs to process the text when the end tag is reached (there would be nothing to process when the start tag is invoked) and so the entire code block is wrapped within a <CFIF> statement that checks that This-Tag.Execution-Mode is "End".

The code itself is simple. This-Tag.GeneratedContent contains all the final postprocessing content present between the start and end tags, so Replace() processes that variable. Then ThisTag.Generated-Content is overwritten with the processed text.

Clean and simple.

Putting It All Together
I now have two versions of the tag: one that's a single tag that takes passed data and one that's used as a tag pair to process the enclosed content. Can these two tags be merged? Look at this code:

<!--- Only process if stand alone or end of pair --->
<CFIF (NOT ThisTag.HasEndTag) OR (ThisTag.ExecutionMode IS "End")>

<!--- Initialize defaults --->
<CFPARAM NAME="ATTRIBUTES.text" DEFAULT="">
<CFPARAM NAME="ATTRIBUTES.variable" DEFAULT="">

<!--- Get input --->
<CFIF ThisTag.HasEndTag AND ThisTag.ExecutionMode IS "End">
<!--- If tag pair get text between tags --->
<CFSET input=ThisTag.GeneratedContent>
<CFELSE>
<!--- Otherwise use passed attribute --->
<CFSET input=ATTRIBUTES.text>
</CFIF>

<!--- Process output text --->
<CFSET output=Replace(input, "$", "$$", "ALL")>

<!--- Check if VARIABLE passed --->
<CFIF ATTRIBUTES.variable IS "">
<!--- No variable passed --->
<CFIF ThisTag.HasEndTag>
<!--- If tag pair update text between tags --->
<CFSET ThisTag.GeneratedContent=output>
<CFELSE>
<!--- Otherwise display output --->
<CFOUTPUT>#output#</CFOUTPUT>
</CFIF>
<CFELSE>
<!--- Save to CALLER variable --->
<CFSET "CALLER.#ATTRIBUTES.variable#"=output>
<!--- If tag pair prevent display --->
<CFIF ThisTag.HasEndTag>
<CFSET ThisTag.GeneratedContent="">
</CFIF>
</CFIF>
</CFIF>

The custom tag needs to be executed if the tag is called as a simple tag (not part of a tag pair) or as the end tag of a tag pair. The first <CFIF> statement tests for both of these conditions by checking This-Tag.Has-EndTag and ThisTag.Execution Mode. Next comes variable initialization. The text to be processed is retrieved from either the passed attribute or ThisTag.Generated-Content. Then the Replace() operation occurs. Once the text is processed, a determination is made as to whether the data is to be written out or saved to a variable. If the former, the data will be written to either ThisTag.Generated-Content or displayed as is. If the latter, This-Tag.GeneratedContent is flushed if needed.

The result? A single tag that can be used in four different ways. As a simple inline tag:

<CF_WAPSafe TEXT="#var#">
As a simple tag saving output to a specified variable:
<CF_WAPSafe TEXT="#var#" VARIABLE="output">

As a tag pair replacing content automatically:

<CF_WAPSafe>
..
</CF_WAPSafe>
And one last option (that comes for free), to suppress output and write converted content to a variable:
<CF_WAPSafe VARIABLE="output">
..
</CF_WAPSafe>
I'll be the first to admit that this simple example probably didn't warrant such sophistication. The truth is, it's probably overkill in this situation. I used this example because it's a simple tag to understand (I didn't want to dedicate pages to explaining the tag being built). The concepts and techniques used here are sound, and the more complex and sophisticated a custom tag is, the more it can benefit from this type of design.

Where to Go from Here
I've only scratched the surface here. Tag families (parent-child tags) present even more opportunities. For example, consider a custom tag with syntax such as:

<CF_Chart NAME="chart.jpg" QUERY="data" X="1000" Y="250" Z="350" HEIGHT="240" WIDTH="360" COLOR="white" TEXTCOLOR="black">
There's nothing wrong with the syntax, but what if you were also to support the following:
<CF_Chart NAME="chart.jpg" QUERY="data">
<CF_ChartParam NAME="X" VALUE="1000">
<CF_ChartParam NAME="Y" VALUE="250">
<CF_ChartParam NAME="Z" VALUE="350">
<CF_ChartParam NAME="HEIGHT" VALUE="240">
<CF_ChartParam NAME="WIDTH" VALUE="360">
<CF_ChartParam NAME="COLOR" VALUE="white">
<CF_ChartParam NAME="TEXTCOLOR" VALUE="black">
</CF_Chart>

There are advantages and disadvantages to each syntax. The former is simpler to write, works well with Tag Editors, and is what new users will feel most comfortable with. The latter is cleaner, supports the conditional inclusion of attributes, and makes working with long lists of attributes much easier.

Which do you support? Why not both? The underlying tag code is the same in both cases; all that differs is how data is passed to the tag and any subsequent initialization code. If designed properly, every attribute should be supported both ways (of course you'll need to find a way to handle conflicting values, but you should be handling that already even for simple tags).

Summary
As you can see, there's a lot more to custom tags than a simple attribute passing. And there's lots of room for you to be creative when designing your own custom tags. Custom tags are all about encapsulation, black-boxing, and reuse. Well-designed custom tags are powerful, flexible, intuitive, and highly usable. And most important, well-designed tags are used over and over. Are you up to the challenge?

About Ben Forta
Ben Forta is Adobe's Senior Technical Evangelist. In that capacity he spends a considerable amount of time talking and writing about Adobe products (with an emphasis on ColdFusion and Flex), and providing feedback to help shape the future direction of the products. By the way, if you are not yet a ColdFusion user, you should be. It is an incredible product, and is truly deserving of all the praise it has been receiving. In a prior life he was a ColdFusion customer (he wrote one of the first large high visibility web sites using the product) and was so impressed he ended up working for the company that created it (Allaire). Ben is also the author of books on ColdFusion, SQL, Windows 2000, JSP, WAP, Regular Expressions, and more. Before joining Adobe (well, Allaire actually, and then Macromedia and Allaire merged, and then Adobe bought Macromedia) he helped found a company called Car.com which provides automotive services (buy a car, sell a car, etc) over the Web. Car.com (including Stoneage) is one of the largest automotive web sites out there, was written entirely in ColdFusion, and is now owned by Auto-By-Tel.

In order to post a comment you need to be registered and logged in.

Register | Sign-in

Reader Feedback: Page 1 of 1

I m trying to develop a complete accounts system for my compnay. Right now I m facing problems abt whcih code should go in to the CFCs and which not. For example if I have a database table named Accounts, whcih conatins complete information about my clients, and I want to make forms that displays all records, edit particular record, enter new record and search a record. Kindly tell me the best possible design to implement it. Also knldy help me in deciding what components should I make and how to divide my logic in to different pages.

I will be grateful to u for this act of kindness

Thanks & Regards,
S.Kamran Asghar

I want to know more details about custom tag