My Custom Tag Tips and Tricks
In the spirit of Ray Camden’s recent blog entry about custom tags, I thought I’d add a couple of my own:
Tip One:
Use the cfimport tag. We all know that we can register custom tag paths in the ColdFusion administrator. We all know that we can simply prepend any cfm file’s name with a cf_ to treat that file like a custom tag, if it’s in the same directory as the calling code. We all know that you can use the cfmodule tag to execute any cfm file from any path as a custom tag, if you don’t mind typing lots of extra characters and making hard to read (in my opinion) code.
What most people don’t know is that you can use the cfimport tag to import sets of tags from a relative directory. What’s nice about this is that you can place your custom tags under your source path and use them in a very intuitive way without caring where the code is when you call it.
For example, let’s say that you had a /customTags directory under your webroot. In this directory you had a tag that did something simply amazing like making wrapped text blink!
Here’s our tag:
<cfif "end" IS ThisTag.executionMode> <cfsavecontent variable="ThisTag.generatedContent"> <blink> <cfoutput> #ThisTag.generatedContent# </cfoutput> </blink> </cfsavecontent> </cfif>
To me it’s obvious that only custom tags should be put in the /customTags directory. That means that to call the tag as <cf_blink> we’ll need to register a custom tag path in the ColdFusion administrator and hope that no one else has a blink tag in some other directory that might conflict with our tag.
Honestly, that’s not the most likely problem. The most likely problem is that when you deploy your application to production you’ll forget to setup that custom tag path and every freakin’ page which uses your wonderful blink tag will thow an oh-so-lovely ColdFusion error. Woo hoo!
Ok, so the other standard technique is the cfmodule tag:
<cfmodule template="/customTags/blink.cfm">Foo!!!</cfmodule>
Ok. Now, technically, there’s nothing wrong with this. But, let’s say we’ve got a set of interrelated custom tags that all have to work together. For example, I once created a tag which would paginate over data sets in Reactor Iterator objects. (It’s not pretty and not ready for prime time. But) Here’s what a usage of that might look like when using cfmodule:
<cfmodule direction="#viewstate.getValue("direction", "")#" itemsPerPage="10" iterator="#FooIterator#" page="#viewstate.getValue("page", 1)#" sortBy="#viewstate.getValue("sortBy", "")#" template="/scripts/tags/reactor/paginator.cfm" variableList="fooId,startDate,endDate,name,description,joins,copies"> <cfmodule name="fooId" template="/scripts/tags/reactor/column.cfm"> <cfmodule name="startDate" template="/scripts/tags/reactor/column.cfm"> <cfif IsObject(Record)> <cfoutput> #DateFormat(Record.getStartDate(), "mmm d yyyy")# </cfoutput> </cfif> </cfmodule> <cfmodule name="endDate" template="/scripts/tags/reactor/column.cfm"> <cfif IsObject(Record)> <cfoutput> #DateFormat(Record.getEndDate(), "mmm d yyyy")# </cfoutput> </cfif> </cfmodule> <cfmodule name="name" template="/scripts/tags/reactor/column.cfm"> <cfif IsObject(Record)> <cfoutput> <a href="index.cfm?event=GoToDaisyOverview&daisyId=#Record.getDaisyId()#">#Record.getName()#</a> </cfoutput> </cfif> </cfmodule> <cfmodule name="description" template="/scripts/tags/reactor/column.cfm"> <cfmodule name="joins" template="/scripts/tags/reactor/column.cfm"> <cfmodule name="copies" template="/scripts/tags/reactor/column.cfm"> </cfmodule>
If I could read that, I would respect that! What it comes down to is that it’s a freakin’ mess! It’s what I’d do if there were no other options, but there’s a better option: Use the cfimport tag.
Here’s what that code above would look like when using cfmodule:
<cfimport prefix="reactor" taglib="/scripts/tags/reactor/"/> <reactor:paginator direction="#viewstate.getValue("direction", "")#" itemsPerPage="10" iterator="#FooIterator#" page="#viewstate.getValue("page", 1)#" sortBy="#viewstate.getValue("sortBy", "")#" variableList="fooId,startDate,endDate,name,description,joins,copies"> <reactor:column name="fooId"> <reactor:column name="startDate"> <cfif IsObject(Record)> <cfoutput> #DateFormat(Record.getStartDate(), "mmm d yyyy")# </cfoutput> </cfif> </reactor:column> <reactor:column name="endDate"> <cfif IsObject(Record)> <cfoutput> #DateFormat(Record.getEndDate(), "mmm d yyyy")# </cfoutput> </cfif> </reactor:column> <reactor:column name="name"> <cfif IsObject(Record)> <cfoutput> <a href="index.cfm?event=GoToDaisyOverview&daisyId=#Record.getDaisyId()#">#Record.getName()#</a> </cfoutput> </cfif> </reactor:column> <reactor:column name="description"> <reactor:column name="joins"> <reactor:column name="copies"> </reactor:paginator>
The biggest caveat with cfimport is that you need to call it in every file that uses the tags. That means you can’t just call cfimport in your application’s startup code and call it done.
I used to avoid this technique because I thought it only worked in the enterprise version of ColdFusion. (The documentation is ambiguous as it related to this.) But, I can assure you that this works and I’ve been using this technique now for quite a while on a standard edition of ColdFusion.
Here’s our blink tag:
<cfimport prefix="tags" taglib="/customTags" /> <tags:blink>Foo!!</tags:blink>
Tip Two:
You may have noticed in the reactor paginator example above that it looks as if the paginator tag looks over an iterator object and shows specific sets of rows in it. It does! One of the best kept secrets of custom tags is that they can behave just like the loop tags. (Thanks to Ray Camden from whom I learned this fine code-a-licious treat.)
Here’s some code from the paginator tag (with all the meat stripped out) that loops over a Reactor iterator:
<cfif "start" is thisTag.executionMode> <cfparam name="attributes.Iterator"/> <cfif 0 IS attributes.Iterator.getRecordCount()> <cfexit method="exittag"/> </cfif> <!--- ... do stuff to show before we loop over the records in the iterator ... ---> <cfelse> <cfif attributes.Iterator.hasMore()> <cfset Record=attributes.Iterator.getNext()/> <!--- ... do stuff with the record ... ---> <cfexit method="loop"/> <cfelse> <!--- ... do stuff to show after the loop is complete ----> </cfif> </cfif>
The key line of code in that is:
<cfexit method="loop" />
This line essentially says to start the tag over, but keep its current state. Thus, on the first exectution the Iterator’s index is 0 so hasMore() returns ture and the tag gets the next record in the iterator, incrementing its index. This repeats until the iterator is out of elements and hasMore() returns false which causes the tag’s execution to end.
Admittedly, looping a custom tag isn’t something I’ve needed much in the past decade, but it’s just nifty and under appreciated.