facebook google plus twitter
Webucator's Free ColdFusion MX Tutorial

Lesson: Custom Tags

Welcome to our free ColdFusion MX tutorial. This tutorial is based on Webucator's Comprehensive ColdFusion Training course.

Lesson Goals

  • To create custom tags.
  • To call custom tags.

Calling Custom Tags

To call a custom tag, use the following syntax:

<cf_tagname>

The cf_ is simply a prefix that tells the ColdFusion Application Server that this is a custom tag. The string that follows the cf_ is the name of the custom tag file minus the file extension. So, in the example above, the file would be called tagname.cfm. Any ColdFusion file can be called as a custom tag.

To illustrate, let's assume that we have a page called Foo.cfm, which has nothing more than the word "foo" written on it. Foo.cfm will be our custom tag template. You call it with the <cf_foo> tag, which will simply output the word "foo". The code for the two files is shown below.

Code Sample:

CustomTags/Demos/Foo.cfm
foo

Code Sample:

CustomTags/Demos/FooCaller.cfm
<html>
<head>
<title><cf_foo></title>
</head>
<body>
	<cf_foo>
</body>
</html>

thisTag Structure

The thisTag structure holds data related to the custom tag instance. The table below shows the variables available in the thisTag structure.

Variable Description
ExecutionMode The mode of execution: "start", "end", or "inactive"
HasEndTag true if the tag is called with an end tag; otherwise, false
GeneratedContent The content in the body of the tag (i.e, between the open and close custom tag)
AssocAttribs Contains the attributes of nested custom tags that have been made available through the <cfassociate> tag.

ExecutionMode

Custom tags can be called with end tags, like so: <cf_foo></cf_foo> or <cf_foo/>. This instructs ColdFusion to call the custom tag twice. Open CustomTags/Demos/FooCallerClosed.cfm in your browser and you will see that the word "foo" is output twice.

However, you may not want the code in the tag to be executed twice or you may want the start tag to behave differently from the end tag. This can be handled by checking the ExecutionMode of the ThisTag structure. Take a look at our modified custom tag template.

Code Sample:

CustomTags/Demos/Foo2.cfm
<cfif ThisTag.executionMode EQ "start">
	foo
<cfelse>
	bar
</cfif>

Code Sample:

CustomTags/Demos/Foo2Caller.cfm
<html>
<head>
<title><cf_foo2></title>
</head>
<body>
	<cf_foo2/>
</body>
</html>

As you can see, the word "foo" is output when the tag is opened and the word "bar" is output when it is closed. Now that's useful! But let's take a look at an even more interesting example. The following page creates a custom tag which displays an image from a specified directory. Each time the custom tag is called, it returns a different image from the directory.

Code Sample:

CustomTags/Demos/ImageFlipper.cfm
               <cfif thisTag.executionMode EQ "start">
	<cfdirectory action="list" name="getImages" directory="#ExpandPath(ATTRIBUTES.directory)#">
	
	<cfparam name="SESSION.picture" default="0">
	<cfset SESSION.picture = SESSION.picture + 1>
	
	<cfif SESSION.picture GT getImages.RecordCount>
		<cfset SESSION.picture = 1>
	</cfif>
	
	<cfoutput>	
	<img src="#ATTRIBUTES.directory#/#getImages.name[SESSION.picture]#"
		<cfif isDefined("ATTRIBUTES.height")>
			height="#ATTRIBUTES.height#"
		</cfif>
		<cfif isDefined("ATTRIBUTES.width")>
			width="#ATTRIBUTES.width#"
		</cfif>
		<cfif isDefined("ATTRIBUTES.alt")>
			alt="#ATTRIBUTES.alt#"
		<cfelse>
			alt="#getImages.name[SESSION.picture]#"
		</cfif>>
	</cfoutput>
</cfif>

Code Sample:

CustomTags/Demos/Runners.cfm
<html>
<head>
<title>Flipping Image</title>
</head>

<body>
<cf_ImageFlipper directory="Images/Runners" height="205" width="150">Runners</cf_ImageFlipper>
</body>
</html>

In the code above, you'll notice the ATTRIBUTES scope, which contains any variables passed in as attributes as shown below...

<cf_ImageFlipper directory="Images/Runners" height="205" width="150">

We check for the existence of these attributes using the isDefined() function.

GeneratedContent

The GeneratedContent variable, which is only accessible in the "end" mode, holds all content between the open tag and close tag (e.g., between <cf_foo> and </cf_foo>). Its value can be modified in the custom tag. The following files show how a custom tag can be used to comment out the body based on the value of an attribute.

Code Sample:

CustomTags/Demos/ImageFlipper2.cfm
<cfif thisTag.executionMode EQ "start">
	<cfdirectory action="list" name="getImages" directory="#ExpandPath(ATTRIBUTES.directory)#">
	
	<cfparam name="SESSION.picture" default="0">
	<cfset SESSION.picture = SESSION.picture + 1>
	
	<cfif SESSION.picture GT getImages.RecordCount>
		<cfset SESSION.picture = 1>
	</cfif>
	
	<cfoutput>	
	<img src="#ATTRIBUTES.directory#/#getImages.name[SESSION.picture]#"
		<cfif isDefined("ATTRIBUTES.height")>
			height="#ATTRIBUTES.height#"
		</cfif>
		<cfif isDefined("ATTRIBUTES.width")>
			width="#ATTRIBUTES.width#"
		</cfif>
		<cfif isDefined("ATTRIBUTES.alt")>
			alt="#ATTRIBUTES.alt#"
		<cfelse>
			alt="#getImages.name[SESSION.picture]#"
		</cfif>>
	</cfoutput>
<cfelseif thisTag.executionMode EQ "end">
	<cfif isDefined("ATTRIBUTES.CommentBody") AND ATTRIBUTES.CommentBody EQ "true">
		<cfset thisTag.GeneratedContent = "<!--#thisTag.GeneratedContent#-->">
	</cfif>
</cfif>

Code Sample:

CustomTags/Demos/Runners2.cfm
<html>
<head>
<title>Flipping Image</title>
</head>

<body>
<cf_ImageFlipper2 directory="Images/Runners" height="205" width="150" CommentBody="true">
	<br/>Runners
</cf_ImageFlipper2>
</body>
</html>

Using Custom Tags

Duration: 20 to 30 minutes.

In this exercise, you will create a custom tag that can turn tab-delimited text into an HTML table.

  1. Open CustomTags/Exercises/ProfitLoss.txt in your editor. This is a tab-delimited text file showing a simple profit-loss statement.
  2. Open CustomTags/Exercises/ProfitLoss.cfm in your editor. This file is created for you. It calls the custom tag cf_Text2Table, which you will be working on. Notice that it has both an open and a close tag and that the body of the tag will contain the text from ProfitLoss.txt.
  3. Open CustomTags/Exercises/Text2Table.cfm in your editor. You will write code to turn the body text of the tag (i.e, the text between the open tag and close tag) into an HTML table. Note that you will need to modify the contents of the GeneratedContent variable.
  4. To test your solution, open CustomTags/Exercises/ProfitLoss.cfm in your browser. It should look something like this:

The Text2Table tag requires an end tag. Add code so that the tag exits (use <cfexit>) with an error message if the call does not include an end tag. The resulting page is shown below:

Solution:

CustomTags/Solutions/Text2Table.cfm
<cfparam name="ATTRIBUTES.RowMarker" default="#chr(10)##chr(13)#">
<cfparam name="ATTRIBUTES.CellMarker" default="#chr(9)#">
<cfparam name="ATTRIBUTES.HeaderRow" default="false">

<cfif thisTag.executionMode EQ "end">
	<cfset thisTag.GeneratedContent = TRIM(thisTag.GeneratedContent)>
	<cfsavecontent variable="Table">
	<cfoutput>
	<table border="1">
	<cfset i=1>
	<cfloop list="#thisTag.GeneratedContent#" index="row" delimiters="#ATTRIBUTES.RowMarker#">
		<cfif ATTRIBUTES.HeaderRow AND i EQ 1>
		<tr>
			<cfloop list="#row#" index="cell" delimiters="#ATTRIBUTES.CellMarker#">
				<th>#cell#</th>
			</cfloop>
		</tr>
		<cfelse>
			<tr>
				<cfloop list="#row#" index="cell" delimiters="#ATTRIBUTES.CellMarker#">
					<td>#cell#</td>
				</cfloop>
			</tr>
		</cfif>
		<cfset i = i + 1>
	</cfloop>
	</table>
	</cfoutput>
	</cfsavecontent>
	<cfset thisTag.GeneratedContent = Table>
</cfif>

Challenge Solution:

CustomTags/Solutions/Text2Table-challenge.cfm
<cfparam name="ATTRIBUTES.RowMarker" default="#chr(10)##chr(13)#">
<cfparam name="ATTRIBUTES.CellMarker" default="#chr(9)#">
<cfparam name="ATTRIBUTES.HeaderRow" default="false">

<cfif NOT thisTag.hasEndTag>
	<div style="color:red; font-weight:bold">Warning: Custom Tag Text2Table must have an end tag</div>
	<cfexit>
</cfif>

<cfif thisTag.executionMode EQ "end">
---- C O D E   O M I T T E D ----
</cfif>

Storing Custom Tags

Custom tags can be stored in any of the following locations. ColdFusion will search for them in the order listed.

  1. In the same directory as the calling page - while this is easy for demonstration purposes, it's not very practical, as it means that the custom tag is only available within that directory.
  2. In a directory (or subdirectory of a directory) specified in ColdFusion Administrator under Extensions -> Custom Tag Paths.
  3. In the cfusion/CustomTags directory or one of its subdirectories.

Other Ways of Calling Custom Tags

One problem with calling custom tags using the cf_ syntax we have seen thus far is that there no way to deal with name conflicts. ColdFusion simply uses the first file it finds that matches the tag name specified. For example, if you had a file in the current directory called "FetchRecord.cfm" and a custom tag file in the cfusion/CustomTags directory with the same name. You cannot access the custom tag file in the cfusion/CustomTags directory using the cf_FetchRecord syntax. ColdFusion provides two ways to deal with the naming conflict.

CFMODULE

The <cfmodule> tag allows you to point directly to the custom tag you want to call. It takes the following syntax:

<cfmodule template="path_to_tag_file" attribute_name="custom_tag_attribute_value">

The custom tag's attributes are passed to the custom tag in the same way as they are using the cf_ syntax. The example below illustrates the use of <cfmodule> to call a custom tag.

Code Sample:

CustomTags/Demos/Runners-cfmodule.cfm
<html>
<head>
<title>Flipping Image</title>
</head>

<body>
<cfmodule template="ImageFlipper.cfm" directory="Images/Runners" height="205" width="150">
</body>
</html>

The example above shows how the attributes can be passed in to the custom tags in separate name-value pairs. It is also possible to pass in the attributes as a single structure using <cfmodule>'s attributecollection attribute:

Code Sample:

CustomTags/Demos/Runners-cfmodule2.cfm
<html>
<head>
<title>Flipping Image</title>
</head>

<body>
<cfset Runner.directory = "Images/Runners">
<cfset Runner.height = "205">
<cfset Runner.width = "150">
<cfmodule template="ImageFlipper.cfm" attributecollection="#Runner#">

</body>
</html>

Custom tags with close tags can also be called with <cfmodule> as shown in the example below.

Code Sample:

CustomTags/Demos/ProfitLoss-cfmodule.cfm
<html>
<head>
  <title>Profit Loss</title>
</head>
<body>
<h1>Profit Loss</h1>

<cfset FilePath = ExpandPath("ProfitLoss.txt")>
<cffile action="read" file="#FilePath#" variable="myfile">
<cfmodule template="Text2Table.cfm"><cfoutput>#myfile#</cfoutput></cfmodule>

</body>
</html>

Finally, with <cfmodule>, custom tags can be specified using dot notation. In this case, the name attribute replaces the template attribute. This is shown in the sample below:

Code Sample:

CustomTags/Demos/ProfitLoss-cfmodule-name.cfm
<html>
<head>
  <title>Profit Loss</title>
</head>
<body>
<h1>Profit Loss</h1>

<cfset FilePath = ExpandPath("ProfitLoss.txt")>
<cffile action="read" file="#FilePath#" variable="myfile">
<cfmodule name="Strings.Text2Table"><cfoutput>#myfile#</cfoutput></cfmodule>
<!---This calls the Text2Table.cfm custom tag file in the cfusion/CustomTags/Strings directory.--->

</body>
</html>

CFIMPORT

The <cfimport> tag is used to import a group of custom tags that can be referenced with a given prefix. The syntax is shown below:

<cfimport prefix="prefix" taglib="path_to_tag_directory">

The prefix attribute is used to specifiy a tag qualifier. A custom tag called "bar" in a library qualified with the "foo" prefix would be called as follows:

<foo:bar>

The taglib attribute points to the directory holding the custom tags. The directory path can be relative to:

  1. the current page location
  2. the web root (the page should start with a "/")
  3. a directory specified in the Administrator ColdFusion mappings page (the page should start with a "/")

Using CFIMPORT with Custom Tags

Duration: 10 to 20 minutes.

In this exercise you will import and use a custom tag library.

  1. Create a new directory at the root of your C drive and name it "CustomTags".
  2. In the C:/CustomTags directory, create another directory called "Strings".
  3. Open CustomTags/Solutions/Text2Table.cfm and save it in the C:/CustomTags/Strings directory you just created.
  4. Open ColdFusion Administrator in your browser and add a mapping named "CustomTags" to "C:\CustomTags".
  5. Open CustomTags/Exercises/ProfitLoss.cfm and save it as CustomTags/Exercises/ProfitLoss2.cfm.
  6. Add the following line of code at the very top of the page:
    <cfimport prefix="String" taglib="/CustomTags/Strings">
  7. Modify the rest of the page so that it uses the custom tag from the Strings library.
  8. To test your solution, open CustomTags/Exercises/ProfitLoss2.cfm in your browser.

Solution:

CustomTags/Solutions/ProfitLoss-cfimport.cfm
               <cfimport prefix="String" taglib="/CustomTags/Strings">
<html>
<head>
  <title>Profit Loss</title>
</head>
<body>
<h1>Profit Loss</h1>

<cfset FilePath = ExpandPath("ProfitLoss.txt")>
<cfif FileExists(FilePath)>
	<cffile action="read" file="#FilePath#" variable="myfile">
	<String:Text2Table HeaderRow="true">
		<cfoutput>#myfile#</cfoutput>
	</String:Text2Table>
</cfif>
</body>
</html>