Avoid Repeating Yourself With ColdFusion Custom Tags

Who likes building forms? I know I don't. Creating basic forms can really be a chore: repeating the same layout over and over again and checking for errors on every field is not something I like spending time on. Luckily, ColdFusion custom tags can help relieve some of the pain. There are actually some great tools out there (cfUniForm comes to mind) that already leverage custom tags to make building simple forms a(n almost) pleasant task. The purpose of this entry is to help you see the light of custom tags, and show you how you can leverage them too make your life easier.

First, a disclaimer. I am in no way trying to replace any existing libraries out there. I know this isn't going to be a complete solution, and it isn't meant to be. All I want is show you what you can do with custom tags. Now that that's taken care of, let's get started.

Take, for example, a very simple form with a text and a password field:

A simple form

<form name='someform' action='customtags.cfm' method='post'>

    <label for='s_my_first_field'>My First Field</label>
    <input type='text' name='my_first_field' id='s_my_first_field' size='20' />

    <label for='s_my_password_field'>My Password Field</label>
    <input type='password' name='my_password_field' id='s_my_password_field' size='20' />

</form>

When we'll be done, the entire form will be generated by the custom tags. It will also display errors for us next to the invalid fields.

Start by creating a tags folder where your test page is. This folder will contain the custom tags for the example. Having the tags in a central location allows us to import them easily into our template.

Importing our custom tags

<cfimport prefix='f' taglib='tags' />

The prefix is how we'll reference the tags in our template. For example, if we have a form tag, we will call it with "<f:form>". You can choose the prefix that best suits your needs. Now, open up a new file and save it as form.cfm in the tags folder. The purpose of this custom tag will simply be to generate the form and hold the errors structure (more on that later).

A simple form custom tag

<!--- Run this part only for the opening tag. --->
<cfif thisTag.executionMode eq 'start'>

    <!--- Param the error structure, in case it wasn't provided. --->
    <cfparam name='attributes.errors' default='#structNew()#' />

    <!---
        Output the form opening tag to the browser with
        the provided attributes.
     --->

    <cfoutput>
        <form
            name='#attributes.name#'
            action='#attributes.action#'
            method='#attributes.method#'
        >

    </cfoutput>
</cfif>

<!--- This is only for the closing tag. --->
<cfif thisTag.executionMode eq 'end'>
    <cfoutput></form></cfoutput>
</cfif>

Not much is happening here. All we do is take the passed in attributes and apply them to our form tag. We then render it to the browser. Note, however, the conditions on "thisTag.executionMode". This variable tells whether we are opening or closing the tag, and allows us to output different results depending on what was called. Now let's change our example code to use the custom tag.

Applying the form custom tag

<cfoutput>
<f:form name='someform' action='customtags.cfm' method='post'>

    <label for='s_my_first_field'>My First Field</label>
    <input type='text' name='my_first_field' id='s_my_first_field' size='20' />

    <label for='s_my_password_field'>My Password Field</label>
    <input type='password' name='my_password_field' id='s_my_password_field' size='20' />

</f:form>
</cfoutput>

Let's take things a little further. Like I've said previously, custom tags are great for reducing repetitive tasks. What we can see from our example layout is that there is a label tag for each field in the form. The label is basically just a string, so it can't be very hard to integrate into the same custom tag as for the field.

Calling the label and the field with one tag

<cfoutput>
<f:form name='someform' action='customtags.cfm' method='post'>
    
    <f:field label='My First Field' type='text' name='my_first_field' size='20' />
    
    <f:field label='My Password Field' type='password' name='my_password_field' size='20' />

</f:form>
</cfoutput>

Here's what I've got so far for the cf_field custom tag. Create a field.cfm file in the tags folder, and paste this code into it:

Field.cfm contents

<!---    Execute this section only for the opening tag. --->
<cfif thisTag.executionMode eq 'start'>
    
    <!--- Param the size attribute in case it wasn't provided. --->
    <cfparam name='attributes.size' default='' />
    
    <!---
        Retrieve a list of all this tag's parents.
        This allows us to do two things:
            - Make sure the cf_field custom tag (this) is properly nested
             in a cf_form custom tag;
            - Retrieve the parent cf_form custom tag's attributes.
     --->

    <cfset baseTags = listToArray( getBaseTagList() ) />

    <cfif baseTags.indexOf( 'CF_FORM' )>
    
        <!---
            If the tag has a cf_form parent, get it's variables and
            save them in our local formVars variable.
         --->

        <cfset formVars = getBaseTagData( 'cf_form' ) />
        
    <cfelse>
    
        <!---
            If the tag doesn't have a cf_form parent, it isn't properly
            nested. Throw an error.
         --->

        <cfthrow
            type='CF_FIELD.INVALID_NEST'
            message='The cf_field custom tag must be nested within cf_form.'
        />

            
    </cfif>
    
    <!---
        Our field's ID is a concatenation of the first letter of our form's name
        and the name of this field. We retrieve the name of the form through
        the formVars structure we saved earlier.
     --->

    <cfset field_id = left( formVars.attributes.name, 1 ) & '_' & attributes.name />
    
    <!--- Output the label. --->
    <cfoutput><div class='form-field'>
        <label for='#field_id#'>#attributes.label#:</label>
    </cfoutput>
    
    <!---
        Here is the really neat part. Within the switch section, we can have
        a different string format for every field type.
     --->

    <cfswitch expression='#attributes.type#'>

        <!---
            Generally speaking, the format for text and password fields is the same.
         --->

        <cfcase value='text,password'>
            
            <cfsaveContent variable='newField'>
            <input
                type='#attributes.type#'
                name='#attributes.name#'
                id='#field_id#'
                size='#attributes.size#'
            />

            </cfsaveContent>

        </cfcase>

    </cfswitch>
    
    <!--- Output the constructed field to the browser. --->
    <cfoutput>#newField#</cfoutput>

</cfif>

<!---    Execute this section only for the closing tag. --->
<cfif thisTag.executionMode eq 'end'>
    
    <!--- If this field has an error message, display it. --->
    <cfif structKeyExists( formVars.attributes.errors, attributes.name )>
        <cfoutput><div class='form-error'>
            #formVars.attributes.errors[ attributes.name ]#.
        </div></cfoutput>
    </cfif>
    
    <!--- Close the field's containing div. --->
    <cfoutput></div></cfoutput>

</cfif>

It probably looks like alot is going in there, but there really isn't that much to it. I've added alot of comments so you know every step of the way what is happening. I do want to go into more detail with what's happening in lines 14 and 35, though. The first thing we're doing is retrieving a list of all the custom tag's parents with getBaseTagList(). That means that, in our example, we'll have a list that looks a little like "CFOUTPUT,CF_FORM" (there may be other things in there, but CF_FORM is the important one). If we find a CF_FORM parent, we can retrieve it's entire variable collection using getBaseTagData( 'cf_form' ) (variable scope, attributes, methods, etc.).

There's one last thing I want to look at in this code, and that's in the closing tag. You can see that we're looking in the parent's attribute scope, and searching in the errors structure for a key with this field's name. If we find one, we output it's value just before the field's container closes. This means we can now pass in an errors structure to our form and have it dynamically display the messages next to the appropriate fields.

Passing errors to our custom tags

<cfimport prefix='f' taglib='tags' />

<cfset errors = { my_first_field = 'This field is mandatory.' } />

<cfoutput>
<f:form name='someform' action='customtags.cfm' method='post' errors='#errors#'>

    <f:field label='My First Field' type='text' name='my_first_field' size='20' />
    
    <f:field label='My Password Field' type='password' name='my_password_field' size='20' />

</f:form>
</cfoutput>

And that's pretty much all I have for now. I hope you can see that custom tags can make your life easier by removing some of the repetitiveness (is that even a word?) from coding layouts.

TweetBacks
There are no TweetBacks for this entry.
Comments
Sebastiaan's Gravatar What a friggin' huge font-size here in the comments...

Anyways, at my company we have our own CF Webshop product which uses mostly customtags to create the forms. We don't use cfform though, but call the form-controllers (input, button, radio, select, textarea, checkbox, etc.) via customtags. It makes for quick development!

Good piece!
# Posted By Sebastiaan | 10/27/08 8:32 AM
Francois Levesque's Gravatar Hi Sebastiaan,

Yeah, I know about the comments form :P, I guess I haven't had the time to style it appropriately yet.

Glad you liked the post. I can't stand cfform either, that's why I'm happy that there's an alternative ;).
# Posted By Francois Levesque | 10/27/08 8:50 AM
Raul Riera's Gravatar Sorry to post on this old post, but whre can I use the cfimport tag so it loads for the whole app? (I hope not in the onRequestStart hehe
# Posted By Raul Riera | 10/29/08 7:28 PM
Raul Riera's Gravatar Lol i though it was older, nevermind the firrst part of the last comment hehe
# Posted By Raul Riera | 10/29/08 7:29 PM
Francois Levesque's Gravatar Hi Raul,

From what I was able to gather, you can call the cfimport tag in your application.cfc, but it won't carry over into your requested template. It seems you need to use the tag in every page you would want to use it.

Although I haven't tried it, I'm pretty sure though that you could put the custom tags in one of your custom tags directories and use cf_ syntax to access them, application-wide. Let me know if it works out for you.
# Posted By Francois Levesque | 10/29/08 7:43 PM
Raul Riera's Gravatar I guess it will be too overhead to load all the custom tags on every request, I will dig into ColdExt to see how they do that (if they do that)

Thanks!
# Posted By Raul Riera | 10/29/08 8:02 PM
Raul Riera's Gravatar Hmmm yes, ColdExt instructs the user to load the files on the request they want them to be used. Oh well :(
# Posted By Raul Riera | 10/29/08 8:04 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.3.000. Contact Blog Owner