|
BF on CF Building a Better Custom Tag
Building a Better Custom Tag
By: Ben Forta
Mar. 30, 2001 12:00 AM
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
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>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 ---> 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
Here's the revised <CF_WAP-Safe> tag: <!--- Initialize defaults ---> 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">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 ---> 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
<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 ---> 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
<!--- Only process if stand alone or end of pair --->
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>And one last option (that comes for free), to suppress output and write converted content to a variable: <CF_WAPSafe VARIABLE="output">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
<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"> 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
Reader Feedback: Page 1 of 1
|
|||||||