Saturday, February 13, 2010

Using Custom Tags to Render HTML Elements

I'm a big fan of using custom tags to render form fields. This isn't a particularly new idea, as Grails, Ruby on Rails, CFWheels, and Mach-II (to name a few) all have similar features. However, each has their own slightly different syntax for how the tags are called. Depending on the language/framework, you could use <g:textField />, <%= text_field %>, text_field(), textField(), or <form:input />.

So which would I choose? Personally I prefer keeping the tags as tags, so text_field() and textField() are out. And since we're using ColdFusion, we'll need to throw out the Rails syntax too. That leaves us with <g:textField /> and <form:input />. I don't know about you, but I like to write as little code as possible. So if given the choice, I would probably combine the two and use <g:input />.

While the g prefix works well for Grails, it doesn't really apply to ColdFusion. So naturally I tried using a cf prefix.


<cfimport prefix="cf" taglib="tags/" />

<cf:input type="text" name="firstName" value="Tony" />


ColdFusion didn't like this very much. Here's the ever-so-friendly error message: A tag starting with 'CF' has been detected. This tag is not supported by this version of ColdFusion. Please verify your typo and try again. Unknown tag: cfmodule.

So I decided to look up the valid naming rules for the cfimport tag. While doing so, I stumbled upon this gem: If you import a CFML custom tag directory and specify an empty value, "", for this attribute, you can call the custom tags without using a prefix. Intrigued, I tested it out.


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

<input type="text" name="firstName" value="Tony" />


Sure enough, ColdFusion sent the call to /tags/input.cfm, which looks like this:


<cfoutput>
<cfif thisTag.executionMode eq "start">
<cfset html = [] />
<cfset html.add("input") />
<cfloop collection="#attributes#" item="attribute">
<cfset html.add('#lcase(attribute)#="#attributes[attribute]#"') />
</cfloop>
<cfelse>
<#arrayToList(html, " ")# />
</cfif>
</cfoutput>


I'll admit this isn't the most intuitive solution, since it appears as if you're rendering a normal HTML element. However, if used correctly, this could be extremely powerful. For starters, you could automatically wrap all dynamic values in htmlEditFormat(). You could also set the "id" attribute to the "name" attribute if it wasn't specified, set a default "title" attribute, wrap all the fields with an appropriate label, or even add in your own custom attributes. Better yet, you could create your own "HTML" elements with their own custom behavior. Take the following hypothetical form for example:


<form controller="users" action="update" bind="user">
<hidden name="id" />
<input name="firstName" />
<input name="lastName" />
<email name="email" />
<date name="birthDate" />
<phone name="phoneNumber" />
<address name="homeAddress" />
<radio name="gender" options="Male,Female" />
<checkbox name="subscribeToNewsletter" />
<submit label="Update Account" />
</form>


Yeah it might take some time getting used to, but I think the power might outweigh the initial learning curve in the long run.

7 comments:

  1. I guess I never had tried that before, really cool, I wish I would have known that when I was working on poi stuff then I could have just shifted the tag library when a user is click for the export to excel. hmmm maybe I will try that still.

    ReplyDelete
  2. Yeah, it's a cool trick. cfSpec uses it to allow you to write readable test expectations. http://www.adelphus.com/2009/1/6/getting-started-with-cfspec-1

    ReplyDelete
  3. I love using custom tags, but a non-prefix approach feels a bit odd; especially when you are overloading a common HTML element. I think the prefix approach adds nice readability.

    ReplyDelete
  4. @Ben,

    Yeah I agree not using a prefix might be a little odd, especially for new developers. However, it could also be a really graceful way of adding new functionality without "hacking" together a site using an assortment of HTML tags, ColdFusion tags, and JavaScript. By not using a prefix, you don't have to decide between using an HTML tag or a ColdFusion tag - it's completely transparent to the developer.

    On top of that, it could also be an easy way of guaranteeing cross-browser support for new HTML5 elements, such as the video and audio tags.

    ReplyDelete
  5. @Ben:

    I'd highly recommend against using this technique. It's confusing to someone new looking at the code (because it's not at all intuitive) and can lead to lots of confusion when debugging.

    For example, if someone adds a new tag called "div", now all of your existing code could break.

    You will also run into problems finding instances of the tag within your code. If you ever need to search for all your input elements (because you've made a change in your code,) then you'll end up getting a mix of HTML & CF elements with the same name--but have different meanings.

    My personal preference is to give a meaningful prefix to my tags. I use things like gui:menu, form:input, page:layout, page:header, etc.

    Not only does it make the code extremely readable, it gives me great categorizing of related functionality. I know immediately the generic type of functionality the tag is providing.

    Also, if I'm making large structual changes to the code, I can now just search for things like "<gui:" or "<page:" to find all the related tags.

    ReplyDelete
  6. @Tony/@Ben:

    That last comment should have been directed to Tony, not Ben. My apologies!

    ReplyDelete
  7. @Dan,

    Yeah I don't know if I'm sold on fully embracing this technique yet in any of my current projects, since there could be some pretty severe drawbacks, but I still think it could be useful given the right circumstances (project size/scope/architecture, developer skill level, etc...).

    ReplyDelete