There is more than one way to skin a cat. In our last tutorial I showed you an old, tried-but-true method for including page headers and footers. But, every ColdFusion developer knows that there are many different ways. In this tutorial we'll go over creating the same header and footer using custom tags.

Custom Tags provided a huge level of power to ColdFusion Markup Language, because it gave us the ability to write simple and elaborate code snippets that could be placed in a centralized repository for consistent reuse. One way to think of them is like an include, but with expanded functionality. Let's get started.

We're going to create a new folder under our root directory titled cftags, and within this a subfolder titled ui. Within the ui folder, create a new template called dspMainTemplate.cfm, and copy the contents of your old incHeader.cfm into the file.

The first thing you want to do is make sure that the file is only called as a Custom Tag. To do this, just verify that the template has an executionMode, as only Custom Tags do. If it isn't called as a Custom Tag, then you'll want to stop all processing and tell someone they've made a mistake.


view plain print about
1<cfif NOT IsDefined("thisTag.executionMode")>
2    <cfoutput>
3        Must be called as customtag.
4    </cfoutput>
5    <cfabort />

Next you'll want to make sure that a closing tag was included in the calling template. If one isn't present then you'll want to throw an appropriate error message.


view plain print about
1<cfif NOT thisTag.hasEndTag>
2    <cfthrow message="Missing end tag." />

You may remember that our header had cfparams for the many different dynamic variables we had defined as necessary for our header and footer. Custom Tags use the attributes scope for any variables that are to be passed in.


view plain print about
1<cfparam name="attributes.pageTitle" default="My Site" />
2<cfparam name="attributes.pageKeywords" default="Page specific keywords for SEO" />
3<cfparam name="attributes.pageDescription" default="Page specific description for SEO" />
4<cfparam name="attributes.pageMetaTags" default="" />
5<cfparam name="attributes.additionalStylesheets" default="" />
6<cfparam name="attributes.additionalScripts" default="" />
7<!--- Navigation Menu variables are paramed to prevent errors --->
8<cfparam name="attributes.mainMenu" default="" />
9<cfparam name="attributes.sideNav" default="" />
10<!--- Used by navigation menus --->
11<cfparam name="attributes.siteSection" default="" />

Now, down to the nitty gritty. You know from above that we are making a Custom Tag that will require an ending tag. This is because your primary page content will go between the opening and closing tags, so you're going to test for the 'start' of your tag call. That's what executionMode is for.


view plain print about
1<cfif thisTag.executionMode IS "start">

Next you'll have your actual header content, replacing the calls to your dynamic variables, so that they will now reference the attributes scope, instead of the variables scope.


view plain print about
2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
3<html xmlns="">
4    <head>
5        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
6        <!--- Dynamic Page Title --->
7        <title>#attributes.pageTitle#</title>
8        <!--- Dynamic MetaTags --->
9        <meta name="description" content="#attributes.pageDescription#" />
10        <meta name="keywords" content="#attributes.pageKeywords#" />
11        <cfif len(trim(attributes.pageMetaTags))>
12            #attributes.pageMetaTags#
13        </cfif>
14        <!--- Default, site-wide style sheet --->
15        <link href="css/default.css" rel="stylesheet" type="text/css" media="screen" lang="en" title="Default Site Stylesheet" />
16        <!---
17        //    Loop through additional stylesheets (passed as a list).
18        //    Note the lack of 'media', 'lang', or 'title' attributes.
19        //    To include those attributes will cause Firefox to not load the stylesheet
20        // (as of 1.5, I haven't tested this lack of functionality in 2.0 yet)
21         --->

22        <cfif len(trim(attributes.additionalStylesheets))>
23            <cfloop list="#attributes.additionalStylesheets#" index="variables.stylesheetName">
24                <link href="css/#variables.stylesheetName#" rel="stylesheet" type="text/css" />
25            </cfloop>
26        </cfif>
27        <!--- Global functions javascript file --->
28        <script src="scripts/glFunctions.js" language="javascript" type="text/javascript"></script>
29        <!--- Loop through a list of file names to include additional javascript files --->
30        <cfif len(trim(attributes.additionalScripts))>
31            <cfloop list="#attributes.additionalScripts#" index="variables.scriptName">
32                <script src="scripts/#variables.scriptName#" language="javascript" type="text/javascript"></script>
33            </cfloop>
34        </cfif>
35    </head>
36    <body>
37        <!--- div containing the entire body of the document --->
38        <div id="totalBody">
39         <!--- div containing the entire header --->
40         <div id="headerBlock">
41         <div id="headerText">
42         <h2>This is my header block</h2>
43         </div>
44         <div id="headerMenu">
45         #attributes.mainMenu#
46         </div>
47         </div>
48         <div id="mainContent">
49                 <div id="navMenu">
50                    #attributes.sideNav#
51                </div>

Wow! Can't get much easier than this. Next we need to check for the 'end' of the executionMode and apply our footer code.


view plain print about
1<cfelseif thisTag.executionMode IS "end">
2    <cfoutput>
3                </div>
4            </div>
6        </body>
7    </html>
8    </cfoutput>

It's that simple. But wait! There's more! Lucky contestants will also learn how to call the custom template! First we need to import the Custom Tag for use. By using cfimport you are able to import all of the tags within a specific directory as well as set a unique identifier for referring to those tags.


view plain print about
1<cfimport prefix="ui" taglib="/blogproject/cftags/ui">

One of the first things you'll notice in the following code is that I am still writing many of my dynamic variables to the variables scope initially. This is for convenience, because many of these variables could be quite large, so it is easier to 'set' them first and then pass them to the Custom Tag's attributes.


view plain print about
2    variables.pageTitle = "My Site - Home Page";
3    variables.pageKeywords = "My Site, Home Page, coldfusion, programming, XHTML, javascript, css";
4    variables.pageDescription = "The My Site Home Page is the gateway into My Site, where many interesting things are bound to be discovered.";
6<cfsavecontent variable="variables.pageMetaTags">
7    <cfoutput>
8        <meta name="robots" content="index,follow" />
9        <meta name="revised" content="Cutter, 11/9/06" />
10    </cfoutput>
13    variables.additionalStylesheets = "home.css";
14    variables.additionalScripts = "myajax.js,calcfunct.js";
15    variables.siteSection = "Home";

Next we'll create our 'start' tag, passing in all of the attributes needed by the tag. We'll follow that with some content, and then write our closing tag.


view plain print about
1<ui:dspMainTemplate pageTitle="#variables.pageTitle#"
2        pageKeywords="#variables.pageKeywords#"
3        pageDescription="#variables.pageDescription#"
4        pageMetaTags="#variables.pageMetaTags#"
5        additionalStylesheets="#variables.additionalStylesheets#"
6        additionalScripts="#variables.additionalScripts#"
7        siteSection="#variables.siteSection#">

9<!--- Here we will now include page content --->

Voila! It's that easy. Sure, you can make it as complicated as you wish, but we just want to cover some basics in these tutorials. Using the same template file you could also use the the old >cf_myTag< syntax as well, without importing all of the tags in a folder, but I tend to organize my tags in such a way that all of the ones I might need for certain tasks are located in one folder and only called when they are needed.

Anyway, that's it for this go around. Next time out maybe we'll start exploring our base template from within a framework? What do ya'll think? Any suggestions?