ColdFusion Muse

Sneaky Coldfusion - Creating CF Tags Out of HTML Tags

This is quite possibly the neatest trick ever invented for Coldfusion - so hang on to your Cf_hat. I had nearly forgotten about it until someone on CF-Talk mentioned a problem they were having. It seems they were struggling with a CMS system where users were entering hyperlinks that were incorrectly formatted (they lacked URL encoding). The dilemma was how to fix it without requiring action from the user.

The choices were slim and none of them seamless.

  • On submitting a new page, parse out the HREF's and encode the values.
  • Provide tools an instructions on encoding along with a table of html encode values.
  • Teach users how to "wrap" the HREF query string in the #urlencodedformat( )# function
Only the first option provides a solution that is seamless to the user, but it comes with it's own set of problems. There is however, a third option. It used to be called "adaptive tags". When I did a search for "cfimport" and "adaptive tags" I came back with my own previous blog post referencing adaptive tags that I learned from Tim Buntel, and an article by Charlie Arehart in sys-con from 2002. That was pretty much all I found. I wondered if the technique would even work on a CFMX 7 server, but when I tested it, it worked just dandy.

Adaptive Tags Can Render HTML Tags as Coldfusion

Here's the scoop. You probably already know that you can use CFIMPORT to create a library of custom tags (JSP or CF). You do this by designating a folder containing all the tags and a prefix which subsequently becomes a part of your call to the tag. For example, if I had a directory called "foo" with a custom tag called "bar.cfm" in it, I could do the following:

<!--- import all the tags in foo --->
<cfimport taglib="foo" prefix="callme"/>
<!--- call bar.cfm using the prefix --->
<callme:bar attribute1="foo.bar has been called"/>

So far so good. We actually end up with a tried and true JSP type syntax. But what happens if we forget the prefix? Can we still call "bar.cfm"?

<!--- import all the tags in foo --->
<cfimport taglib="foo" prefix=""/>
<!--- call bar.cfm using the prefix --->
<bar attribute1="foo.bar has been called"/>

As it turns out, this works! Now take a moment and let that sink in. How can we use this new found knowledge to fix the problem above? Simple! We create a directory called "htm" (or whatever) and place a tag called "a.cfm" in it. Then we import "htm" without a prefix. When the scripting engine comes across <a it's going to call "A.cfm". All the attributes that exist in the anchor tag are going to be passed to the custom tag in the "attribute" scope. So the following code:

<cfimport taglib="htm" prefix=""/>

<a href="http://blah.cfm?name=Joe J. Smith">Joe J. Smith</a>
...now outputs the following link....
<a HREF="http://blah.cfm?nameJoe%20J%2E%20Smith">Joe J. Smith</a>

Here's a Sample A.cfm Tag

In my test I did something else. I checked for the existence of a "style" attribute. If I didn't find one, I set the style to font-size: 14pt. Here's the sample tag.

<cfsetting enablecfoutputonly="Yes">
<cfif thistag.executionmode IS 'Start'>
<!---enforce a style attribute --->
<cfset isStyle = false/>
<!--- begin the tag --->
<cfoutput><a </cfoutput>
<Cfloop collection="#attributes#" item="aItem">
   <!--- check for style --->
   <cfif aItem IS 'style'>
      <cfset isStyle = true/>
   </cfif>
   <!--- encode the values of the href attr. --->
   <cfif aItem IS 'href' AND listlen(attributes[aItem],'?') IS 2>
      <!--- front part --->
      <cfset qString = listlast(attributes[aItem],'?') />
      <!--- string to hold new qstring --->
      <cfset newQString = "">
      <!--- loop through the qString and encode JUST the values --->
      <cfloop list="#qString#" index="vItem" delimiters="&">
         <cfset newQstring = ListAppend(newQString,listFirst(vItem,'=') & urlencodedformat(listlast(vItem,'=')),'&')/>
      </cfloop>      
      <!--- reset the value of this attr. --->
      <cfset attributes[aItem] = listfirst(attributes[aItem],'?') & '?' & newQstring/>
   </cfif>
   <!--- output the attribute to the <a> tag --->
   <cfoutput>#aItem#="#attributes[aItem]#" </cfoutput>
   
</CFLOOP>
<!--- if no style exists set one --->
<cfif NOT isStyle>
   <cfoutput>style="font-size: 14pt;" </cfoutput>
</cfif>

<cfoutput> ></cfoutput>

</cfif>
<!--- tack on the end tag --->
<cfif thistag.executionmode IS 'End'>
   <cfoutput></a></cfoutput>
</cfif>


<cfsetting enablecfoutputonly="no">

If you want to test it, simply create a directory called "htm" in the same folder as your test script and run this code:

<cfimport taglib="htm" prefix=""/>

<a href="http://blah.cfm?name=Joe J. Smith">Joe J. Smith</a>

Other Possibilities

Obviously you could use this technique to great effect. You could enforce styles and classes. You could create a click-handler that all URLs would be forwarded through. You could rewrite boldface, italics, font tags - virtually any tag with attribute type syntax. Obviously some tags would be problematic (like the body tag). Before you go hog-wild you should consider whether this technique is actually necessary. I suspect in many or most cases it is not. Don't use it just because it's neat. It definitely results in something of a management problem with your code. Now, when you look at a page, you can no longer separate out what belongs to CF and what doesn't. That makes it difficult to maintain code using this technique. Still, it might save the day in a few cases.

Additional Resources on Adaptive Tags in Coldfusion

Muse's Old Blog (Oct. 2002)
Charlie Arehart's "hidden gems" article (Aug. 2002)

Comments
SiamesePurr771's Gravatar Instead of using the no-prefix a tags, it might be wiser to code two different types of a tags. Those that contain hrefs which are known to be syntactically correct (i.e. you wrote in source) and those from untrusted origin.

For those of trusted origin, you use regular <a href="http://someplace.com">;.

For those of untrusted origin, you use <cfhtml:a href="#output#">.

Not using a prefix on import is a pretty scary thing.

-Purr
# Posted By SiamesePurr771 | 5/10/06 1:01 PM
mkruger's Gravatar Purr... I believe I made this exact point in my last paragraph :) Just because something CAN be done does not mean that it SHOULD be done. That being said, there is "never" a time to say "never". Someone will always find a use for something that works for them.
# Posted By mkruger | 5/10/06 1:03 PM
Ryan Guill's Gravatar wow, this is quite cool... im still just starting to realize what you could do with this. The cookieless sessions make a whole lot of sense (from the previous blog post) and im sure there are other things as well. I also didnt know cfimport was for custom tags, ive only used it for jsp...

Cool stuff!
# Posted By Ryan Guill | 5/10/06 1:03 PM
Sami Hoda's Gravatar Very nice!
# Posted By Sami Hoda | 5/10/06 2:49 PM
Fernando Trevisan's Gravatar Great to know this! As other people said, one could think cfimport won't work with CFM...
Pretty cool!
# Posted By Fernando Trevisan | 5/10/06 4:59 PM
John Farrar's Gravatar This was mentioned in O'Reilley's book on CF. To bad the book doesn't sell more. I keep Ben's book on the shelf. Yet, in honest review the O'Reilley book is still better. (Sorry Ben... your books are only great. LOL )

John
# Posted By John Farrar | 5/11/06 10:30 AM
Ben Davies's Gravatar wow... we've just done a style check of at work and found that style compliance was pretty poor (or to be fair on the guys, not perfect). This is a powerful technique indeed.

Of course I could throw another couple of downsides into the pot: Firstly, the next updater for Coldfusion might remove the ability to get away without a prefix. And then suddenly you have a WHOLE lot of maintenance to do in order to enjoy new Adobe fruit.

Secondly, since its being used almost like a rendering technology, relying on this technique doesn't fix your datastore and leaves other sorts of data clients (flex apps, webservices, another CF instance, etc) with the same issue. Better to clean data on the way in than persist bad data.

And like you said the support issues would be insane. I can imagine much head-scratching and ultimately head-thumping going on by the poor support developer.

Is there a way to use cfimport and then disable the library later on the page?
# Posted By Ben Davies | 5/15/06 8:02 AM
mkruger's Gravatar Ben - thanks for the comments. The general consensus is that it's a "neat" technique fraught with peril. As for disabling, I think that the granular nature of the CFIMPORT scope might work to your advantage. I'll test and see.
# Posted By mkruger | 5/15/06 8:08 AM
Phillip Senn's Gravatar At first I thought: "Genius!", but then after reading Purr's commented, I had to agree with Purr.
Use a custom tag instead of "faking" the ColdFusion interpreter into substituting a custom tag for an hmtl tag.

But then I thought of a problem we have at work. We've styled all our <cfinput>s to look like hyperlinks. The only problem is that when we do a <cfdocument format="pdf"> command it ignores CSS.

So let's say we had <cfif> that says something like this:
<cfif this is a pdf form creation>
<cfimport taglib="htm" prefix=""/>
</cfif>
Then you could change all your <input>s to be hyperlinks!
To redefine what <cfinput> means depending upon the context.
I like it.
# Posted By Phillip Senn | 9/4/06 5:51 PM
mkruger's Gravatar Phillip,

Unfortunately this won't work. You cannot overwrite a CF tag. Remember that this technique works in conjuction with CF rendering - not before or after the fact. Consequently, "CFINPUT" is already a tag that CF knows about. You could rewrite the "input" tag in this way - but not "cfinput".

-Mark
# Posted By mkruger | 9/5/06 7:24 AM
Phillip Senn's Gravatar I see.
# Posted By Phillip Senn | 9/5/06 7:51 AM
# Posted By Phillip Senn | 4/11/07 2:05 PM
Tommy's Gravatar I get duplicated content when I try to use this.

Example:
<!--- test_tags.cfm --->
<cfimport taglib="cftags" />
<test />

<!--- test.cfm --->
test data
--------------------
So test_tags.cfm imports test.cfm but instead of just seeing "test data" I see "test data test data". I can't for the life of me figure out why...please help!
# Posted By Tommy | 2/11/08 11:01 AM
John Farrar's Gravatar Yes, when you put an end tag on a tag it gets called again.
<test />
is the same as
<test></test>

Note Ray's example...
<cfif thistag.executionmode IS 'End'>
<cfoutput></a></cfoutput>
</cfif>

modes are start and end.
# Posted By John Farrar | 2/11/08 1:59 PM



Blog provided and hosted by CF Webtools. Blog Sofware by Ray Camden.