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.
Comments on: "My Custom Tag Tips and Tricks" (8)
Believe it or not, I learned about looping tags back when I worked on Spectra.
LikeLike
Hi Doug
Great tips. I knew about the first one from before, but the second one was totally new to me.
Anyway, just wanted to say that you have a typo. Before the cfimport example code you say:
Here’s what that code above would look like when using cfmodule:
cfmodule should be cfimport I guess 🙂
LikeLike
Doug,
I agree completely on cfimport. I resisted it for a long time, but when I finally tried it I really liked it.
Thanks for mentioning . It seems like I have heard of that one before and forgotten it. I will try to remember it this time.
LikeLike
Another benefit is if you are running multiple versions of the site on the same CF server.
Say you have a version 1 and a version 2 (upgrade) and you need to keep them both going as you upgrade your clients.
If you have a custom tag called and in the new version you upgraded you can’t maintain 2 custome tag folders with same name.
Going with the method you can help yourself.
LikeLike
Brian, you can do this with cfmodule too. 🙂
LikeLike
This is outstanding information. Thanks for taking the time to post this as I was looking for a simular solution to a problem that we were face with in building a Portal application using AJAX.
LikeLike
WHAT! I have never seen the CFExit tag with LOOP value. That is very cool. Uggg! Why am I always behind 🙂
LikeLike
Ditto. Didn’t know about the iterator in custom tags. Thanks for the tip!
LikeLike