Hi, Tom. Nice tut. I made a few spelling changes and added a comment in one of the code blocks near the bottom. The code outside the code blocks I usually put in inline code styles like that, not sure if that is a must or just me :~).

ColdFusion custom tags have been overlooked by many ColdFusion developers since the advent of CFCs, however they provide a different benefit and work well in a ColdFusion application, even if it is a CFC-based application. That is because CF custom tags are ideally suited to presentation of html, whereas CFCs are ideally suited to the logic of an application. Using a list of US and Canadian states as a simple example of a ColdFusion custom tag, I'll show how custom tags can be used to enhance an application by building reusable HTML tags enhanced by some server-side logic, taking some of the more code-heavy tags out of the page code flow.

The technique I'll be using for custom tags is to use the <cfimport> tag to specify a tag library for a site. There is another method involving using a <cf_ prefix on the tag, but I prefer to define a library in the beginning of the page to simplify the tag presentation. Another reason I use this method is that my page design is also usually in a custom tag, using the method shown in this series of articles:

Since my pages are already importing a tag library by their very nature, no extra code is necessary.

Also, Neil Giarratana's article on Breathing New Life into Custom Tags is a worthwhile introduction to using <cfimport>.

A Simple Tag

The states tag is a very simple tag. It is basically a select box with all the US states and Canadian provinces listed. There are some people who might put the list of states in the database, but in my estimation this creates too many hits to the database and is unnecessary. The same list can be put in a custom tag where it can be centralized, but also spit out inline. The fastest web operations are plain HTML output with no server processing. A list of states written out in HTML might not look as elegant as a <cfoutput> loop over a query, but it will be faster.

One of the goals is to keep the tag as simple as possible on the calling page. At the most basic, we can simply call the tag like this:

<cmx:states>

Assuming that the tag library prefix will be cmx.

Another goal is to allow attributes like class, size, disabled, etc to be passed through as is. We'll do this with a simple loop.

Finally, we want to be able to choose the selected value easily, by passing it in as an attribute.

The Code

The code for the tag is shown in the following listing. I'll comment individually on the functionality following:

<cfif #thisTAG.executionmode# EQ "start">
<!--- Only execute if it is a start tag --->
<cfsilent>

<cfparam name="attributes.selected" default="">
<cfparam name="attributes.fieldname" default="states">
<cfparam name="attributes.usonly" default="true">
<cfparam name="attributes.label" default="--Choose a state--">

<cfset variables.attributeslist = " ">
<cfloop collection="#attributes#" item="attribute">
  <cfif not ListFindNoCase('selected,fieldname,usonly,label',attribute)>
    <cfset variables.attributeslist = '#variables.attributeslist# #LCase(attribute)#="#attributes[attribute]#"'>
  </cfif>
</cfloop>
</cfsilent><cfoutput><select name="#attributes.fieldname#" id="#attributes.fieldname#"#variables.attributesList#>
<optgroup label="United States">
<option value=""<cfif #attributes.selected# EQ ""> selected="selected"</cfif>>#attributes.label#</option>
<option value="AL"<cfif #attributes.selected# EQ "AL"> selected="selected"</cfif>>Alabama</option>
<option value="AK"<cfif #attributes.selected# EQ "AK"> selected="selected"</cfif>>Alaska</option>
<option value="AZ"<cfif #attributes.selected# EQ "AZ"> selected="selected"</cfif>>Arizona</option>
<option value="AR"<cfif #attributes.selected# EQ "AR"> selected="selected"</cfif>>Arkansas</option>
<option value="CA"<cfif #attributes.selected# EQ "CA"> selected="selected"</cfif>>California</option>
<option value="CO"<cfif #attributes.selected# EQ "CO"> selected="selected"</cfif>>Colorado</option>
<option value="CT"<cfif #attributes.selected# EQ "CT"> selected="selected"</cfif>>Connecticut</option>
<option value="DE"<cfif #attributes.selected# EQ "DE"> selected="selected"</cfif>>Delaware</option>
<option value="DC"<cfif #attributes.selected# EQ "DC"> selected="selected"</cfif>>District of Columbia</option>
<option value="FL"<cfif #attributes.selected# EQ "FL"> selected="selected"</cfif>>Florida</option>
<option value="GA"<cfif #attributes.selected# EQ "GA"> selected="selected"</cfif>>Georgia</option>
<option value="HI"<cfif #attributes.selected# EQ "HI"> selected="selected"</cfif>>Hawaii</option>
<option value="ID"<cfif #attributes.selected# EQ "ID"> selected="selected"</cfif>>Idaho</option>
<option value="IL"<cfif #attributes.selected# EQ "IL"> selected="selected"</cfif>>Illinois</option>
<option value="IN"<cfif #attributes.selected# EQ "IN"> selected="selected"</cfif>>Indiana</option>
<option value="IA"<cfif #attributes.selected# EQ "IA"> selected="selected"</cfif>>Iowa</option>
<option value="KS"<cfif #attributes.selected# EQ "KS"> selected="selected"</cfif>>Kansas</option>
<option value="KY"<cfif #attributes.selected# EQ "KY"> selected="selected"</cfif>>Kentucky</option>
<option value="LA"<cfif #attributes.selected# EQ "LA"> selected="selected"</cfif>>Louisiana</option>
<option value="ME"<cfif #attributes.selected# EQ "ME"> selected="selected"</cfif>>Maine</option>
<option value="MD"<cfif #attributes.selected# EQ "MD"> selected="selected"</cfif>>Maryland</option>
<option value="MA"<cfif #attributes.selected# EQ "MA"> selected="selected"</cfif>>Massachusetts</option>
<option value="MI"<cfif #attributes.selected# EQ "MI"> selected="selected"</cfif>>Michigan</option>
<option value="MN"<cfif #attributes.selected# EQ "MN"> selected="selected"</cfif>>Minnesota</option>
<option value="MS"<cfif #attributes.selected# EQ "MS"> selected="selected"</cfif>>Mississippi</option>
<option value="MO"<cfif #attributes.selected# EQ "MO"> selected="selected"</cfif>>Missouri</option>
<option value="MT"<cfif #attributes.selected# EQ "MT"> selected="selected"</cfif>>Montana</option>
<option value="NE"<cfif #attributes.selected# EQ "NE"> selected="selected"</cfif>>Nebraska</option>
<option value="NV"<cfif #attributes.selected# EQ "NV"> selected="selected"</cfif>>Nevada</option>
<option value="NH"<cfif #attributes.selected# EQ "NH"> selected="selected"</cfif>>New Hampshire</option>
<option value="NJ"<cfif #attributes.selected# EQ "NJ"> selected="selected"</cfif>>New Jersey</option>
<option value="NM"<cfif #attributes.selected# EQ "NM"> selected="selected"</cfif>>New Mexico</option>
<option value="NY"<cfif #attributes.selected# EQ "NY"> selected="selected"</cfif>>New York</option>
<option value="NC"<cfif #attributes.selected# EQ "NC"> selected="selected"</cfif>>North Carolina</option>
<option value="ND"<cfif #attributes.selected# EQ "ND"> selected="selected"</cfif>>North Dakota</option>
<option value="OH"<cfif #attributes.selected# EQ "OH"> selected="selected"</cfif>>Ohio</option>
<option value="OK"<cfif #attributes.selected# EQ "OK"> selected="selected"</cfif>>Oklahoma</option>
<option value="OR"<cfif #attributes.selected# EQ "OR"> selected="selected"</cfif>>Oregon</option>
<option value="PA"<cfif #attributes.selected# EQ "PA"> selected="selected"</cfif>>Pennsylvania</option>
<option value="RI"<cfif #attributes.selected# EQ "RI"> selected="selected"</cfif>>Rhode Island</option>
<option value="SC"<cfif #attributes.selected# EQ "SC"> selected="selected"</cfif>>South Carolina</option>
<option value="SD"<cfif #attributes.selected# EQ "SD"> selected="selected"</cfif>>South Dakota</option>
<option value="TN"<cfif #attributes.selected# EQ "TN"> selected="selected"</cfif>>Tennessee</option>
<option value="TX"<cfif #attributes.selected# EQ "TX"> selected="selected"</cfif>>Texas</option>
<option value="UT"<cfif #attributes.selected# EQ "UT"> selected="selected"</cfif>>Utah</option>
<option value="VT"<cfif #attributes.selected# EQ "VT"> selected="selected"</cfif>>Vermont</option>
<option value="VA"<cfif #attributes.selected# EQ "VA"> selected="selected"</cfif>>Virginia</option>
<option value="WA"<cfif #attributes.selected# EQ "WA"> selected="selected"</cfif>>Washington</option>
<option value="WV"<cfif #attributes.selected# EQ "WV"> selected="selected"</cfif>>West Virginia</option>
<option value="WI"<cfif #attributes.selected# EQ "WI"> selected="selected"</cfif>>Wisconsin</option>
<option value="WY"<cfif #attributes.selected# EQ "WY"> selected="selected"</cfif>>Wyoming</option>
</optgroup>
<cfif attributes.usonly EQ false>
<optgroup label="Canada">
<option value="AB"<cfif #attributes.selected# EQ "AB"> selected="selected"</cfif>>Alberta</option>
<option value="BC"<cfif #attributes.selected# EQ "BC"> selected="selected"</cfif>>British Columbia</option>
<option value="MB"<cfif #attributes.selected# EQ "MB"> selected="selected"</cfif>>Manitoba</option>
<option value="NB"<cfif #attributes.selected# EQ "NB"> selected="selected"</cfif>>New Brunswick</option>
<option value="NL"<cfif #attributes.selected# EQ "NL"> selected="selected"</cfif>>Newfoundland and Labrador</option>
<option value="NT"<cfif #attributes.selected# EQ "NT"> selected="selected"</cfif>>Northwest Territories</option>
<option value="NS"<cfif #attributes.selected# EQ "NS"> selected="selected"</cfif>>Nova Scotia</option>
<option value="NU"<cfif #attributes.selected# EQ "NU"> selected="selected"</cfif>>Nunavut</option>
<option value="ON"<cfif #attributes.selected# EQ "ON"> selected="selected"</cfif>>Ontario</option>
<option value="PE"<cfif #attributes.selected# EQ "PE"> selected="selected"</cfif>>Prince Edward Island</option>
<option value="QC"<cfif #attributes.selected# EQ "QC"> selected="selected"</cfif>>Quebec</option>
<option value="SK"<cfif #attributes.selected# EQ "SK"> selected="selected"</cfif>>Saskatchewan</option>
<option value="YT"<cfif #attributes.selected# EQ "YT"> selected="selected"</cfif>>Yukon</option>
</optgroup>
<optgroup label="Other">
<option value="*none*">*none*</option>
</optgroup></cfif>
</select>
</cfoutput></cfif>

Going through the code, the entire tag is contained within a <cfif> tag:

<cfif #thisTAG.executionmode# EQ "start">

This is a common way to ensure that the tag does not output doubly if it happens to be written with a closing tag:

<cmx:states />

If the tag did not check for execution mode, it would output the contents of the tag twice, because the tag is called once for the opening and once for the closing tag.

The <cfsilent> tag ensures that no whitespace will output.

The <cfparam> block shows all the attributes that are used by the tag:

<cfparam name="attributes.selected" default="">
<cfparam name="attributes.fieldname" default="states">
<cfparam name="attributes.usonly" default="true">
<cfparam name="attributes.label" default="--Choose a state--">

The selected attribute will allow us to set a selected option, as in this HTML:

<select name="blah">
  <option value="1" selected="selected">First option</option>
  <option value="2">Second option</option>
</select>

By passing a value into the tag, the tag will select the appropriate option.

The fieldname attribute allows us to set the field name of the tag, but uses the default of "states" if nothing is passed.

The usonly attribute allows us to construct a tag with only US states OR US states with Canadian provinces.

The label attribute allows us to set a static label for the list, defaulting to "--Choose a state--".

Those are the tag attributes for the CF custom tag, however we also want to be able to pass regular select attributes to the tag. We'll do this by looping through the attributes and making a string:

<cfset variables.attributeslist = " ">
<cfloop collection="#attributes#" item="attribute">
  <cfif not ListFindNoCase('selected,fieldname,usonly,label',attribute)>
    <cfset variables.attributeslist = '#variables.attributeslist# #LCase(attribute)#="#attributes[attribute]#"'>
  </cfif>
</cfloop>

We start with a string with just one space in it. Then we loop through the list of all attributes passed to the tag, and filter out those that are used by the custom tag with a ListFindNoCase function. The string is build up and output in the select tag, along with the tag name and id:

<select name="#attributes.fieldname#" id="#attributes.fieldname#"#variables.attributesList#>

The option tags are output with <cfif> statements to test for the currently selected value (passed into the custom tag via the selected attribute):

<option value="AL"<cfif #attributes.selected# EQ "AL"> selected="selected"</cfif>>Alabama</option>

Putting the whole thing together, we can save the states.cfm file in a directory under our site root. Call the directory cmxtags and save the states.cfm file in the directory. When you need to use the states tag, simply import the directory at the top of the page:

<cfimport taglib="cmxtags" prefix="cmx">

Then use the tag within your form:

<cmx:states>

or

<cmx:states name="statefield" selected="#rsSomeQuery.state#">

or

<cmx:states class="longfield" selected="#form.states#">

Database functionality

Earlier I stated that I don't think pulling the content from the database is a good idea in this case. However, once you have built the tag, you could easily change it to pull content from a database, as in this example:

<cfquery name="rsStates" datasource="somedsn">
SELECT StateCode, State FROM States ORDER BY StateCode
</cfquery>

<select name="#attributes.fieldname#" id="#attributes.fieldname#"#variables.attributesList#>
<cfoutput query="rsstates">
<option value="#rsStates.StateCode#"<cfif #attributes.selected# EQ "">
#rsStates.State#</option>
</cfoutput>
</select>

Your calling pages would never change because they are using the custom tag. Just like CFCs abstract the application logic from your pages allowing freedom for your HTML coders, custom tags give you the same kind of flexibility with presentation.

Conclusion

This has been a simple tutorial showing a basic ColdFusion custom tag. We showed how to use the tag using a <cfimport> tag to create a library, pass attributes to the tag, pass html attributes through the tag, and change the functionality to use a database.