Macromedia Coldfusion MX 7 Web Application Construction Kit

Now that you understand how to use existing custom tags, it's time to write your own. This section introduces you to the basic concepts involved in creating a custom tag. As you will soon see, it's an easy and productive way to write your code.

Writing Your First Custom Tag

It's traditional to illustrate a new language or technique with a "Hello, World" example. Listing 23.3 shows a custom tag that outputs a "Hello, World" message in the current Web page, formatted with ordinary HTML table syntax.

Save this listing as HelloWorld.cfm in either the special CustomTags folder or the folder you've been using as you follow along in this chapter.

Listing 23.3. HelloWorld.cfmA Simple Custom Tag Template

<!--- Filename: HelloWorld.cfm Author: Nate Weiss (NMW) Purpose: Creates the <CF_HelloWorld> custom tag example ---> <table border="5" cellPadding="5"> <tr><th bgcolor="yellow"> <b>Hello, World, from Orange Whip Studios.</b><br> </th></tr> <tr><td bgcolor="orange"> Orange whip... two orange whips... three orange whips!<br> </td></tr> </table>

Now you can use the custom tag just by adding a cf_ prefix to the tag's filename (without the .cfm part). This means you have just created a custom tag called <cf_HelloWorld>, which you can use in code as shown in Listing 23.4.

Listing 23.4. UsingHelloWorld.cfmTesting the <CF_HelloWorld> Custom Tag

<!--- Filename: UsingHelloWorld.cfm Author: Nate Weiss (NMW) Purpose: Shows how <CF_HelloWorld> can be used in a ColdFusion page ---> <html> <head><title>Testing &lt;CF_HelloWorld&gt;</title></head> <body> <!--- Display Hello World Message, via Custom Tag ---> <cf_HelloWorld> </body> </html>

It's a start, but this custom tag isn't terribly exciting, since it will always output exactly the same thing. In fact, at this point, you could just replace the reference to the custom tag in Listing 23.4 with an ordinary <cfinclude> tag and the results would be the same:

<!--- Display Hello World Message, via Custom Tag ---> <cfinclude template="HelloWorld.cfm">

Things get a lot more interesting after you start making custom tags that accept attributes, just like ColdFusion's built-in tags.

Introducing the attributes Scope

To make your own custom tags really useful, you want them to accept tag attributes, just as normal CFML and HTML tags do. ColdFusion makes this very easy by defining a special attributes scope for use within your custom tag templates.

The attributes scope is a ColdFusion structure that is automatically populated with any attributes provided to the custom tag when it is actually used in code. For instance, if an attribute called message is provided to a tag, as in <cf_HelloWorld message="Country and Western">, then the special attributes scope will contain a message value, set to Country and Western. You could output this value to the page by referring to #ATTRIBUTES.message# between <cfoutput> tags within the custom tag template.

Outputting Attribute Values

Listing 23.5 shows another custom tag called <cf_HelloWorldMessage>, which is almost the same as <cf_HelloWorld> from Listing 23.3. The difference is the fact that this tag accepts an attribute called message, which gets displayed as part of the "Hello, World" message (Figure 23.4).

Listing 23.5. HelloWorldMessage.cfmDefining Attributes for Your Custom Tags

<!--- Filename: HelloWorldMessage.cfm Author: Nate Weiss (NMW) Purpose: Creates a custom tag that accepts attributes ---> <!--- Tag Attributes ---> <cfparam name="ATTRIBUTES.message" type="string"> <!--- Output message in HTML table format ---> <cfoutput> <table border="5" cellPadding="5"> <tr><th bgcolor="yellow"> <b>Hello, World, from Orange Whip Studios.</b><br> </th></tr> <tr><td bgcolor="orange"> #ATTRIBUTES.message#<br> </td></tr> </table> </cfoutput>

Figure 23.4. The <CF_HelloWorld Message> custom tag displays any message in a consistent manner.

The <cfparam> tag at the top of Listing 23.5 makes it clear that a message parameter is expected to be provided to the tag and that it is expected to be a string value. The <cfoutput> block near the end outputs the value of the message parameter provided to the tag, as shown in Figure 23.4. Listing 23.6 shows how to supply the message parameter that the tag now expects.

NOTE

To make this listing work, you must save the previous listing (Listing 23.5) as HelloWorldMessage.cfm, either in the same folder as Listing 23.6 or in the special CustomTags folder.

Listing 23.6. UsingHelloWorldMessage.cfmSupplying Attributes to Your Custom Tags

<!--- Filename: UsingHelloWorldMessage.cfm Author: Nate Weiss (NMW) Purpose: Shows how to use the <CF_HelloWorldMessage> custom tag ---> <html> <head><title>Testing &lt;CF_HelloWorldMessage&gt;</title></head> <body> <!--- Display Hello World Message, via Custom Tag ---> <cf_HelloWorldMessage message="We're getting the band back together!"> </body> </html>

NOTE

Attribute names are not case sensitive. There is no way to determine whether a parameter was passed to a tag with code such as Message="Hello" or MESSAGE="Hello". Of course, the case of each attribute's value is preserved, so there is a difference between message="Hello" and message="HELLO".

Using <cfparam> to Declare Attributes

You don't have to include the <cfparam> tag in Listing 23.5. As long as the message attribute is actually provided when the tag is used, and as long as the parameter is a string value, the <cfparam> tag doesn't do anything. It only has an effect if the attribute is omitted (or provided with a value that can't be converted to a string), in which case it displays an error message.

However, I strongly suggest that you declare each of a custom tag's attributes with a <cfparam> tag at the top of the tag's template, for the following reasons:

  • Always having your custom tag's attributes formally listed as <cfparam> tags at the top of your templates makes your custom tag code clearer and more self-documenting.

  • Specifying the expected data type with <cfparam>'s type attribute acts as a convenient sanity check in case someone tries to use your tag in an unexpected way.

  • If you declare all your tag's attributes using <cfparam> tags at the top of a tag's template, you know that the rest of the template will never run if the attributes aren't provided properly when the tag is actually used. This prevents problems or data inconsistencies that could arise from partially executed code.

  • As discussed in the next section, you can easily make any of your tag's attributes optional by simply adding a default attribute for the corresponding <cfparam> tag.

See Chapter 9, "CFML Basics," for more information about the <cfparam> tag.

NOTE

You can use the <cftry> and <cfcatch> tags to provide friendly error messages when the attributes passed to a custom tag do not comply with the rules imposed by the custom tag's <cfparam> tags. See the version of the <cf_PlaceOrder> custom tag presented in Chapter 32, "Error Handling," for an example.

Making Attributes Optional or Required

When you start working on a new custom tag, one of the most important things to consider is which attributes your new tag will take. You want to ensure that the attribute names are as clear, intuitive, and self-describing as possible.

Often, you will want to make certain attributes optional, so they can be omitted when the tag is actually used. That way, you can provide lots of attributes for your tags (and thus flexibility and customizability), without overburdening users of your tags with a lot of unnecessary typing if they just want a tag's normal behavior.

Using <cfparam> to Establish Default Values

The most straightforward way to declare an optional attribute for a custom tag is to provide a default attribute to the corresponding <cfparam> tag at the top of the tag's template.

Look at the version of the <cf_HelloWorldMessage> tag shown in Listing 23.7. This version is the same as the previous one (shown in Listing 23.5), except that it defines five new attributes: topMessage, topColor, bottomColor, tableBorder, and tablePadding. The values are given sensible default values using the default attribute.

Listing 23.7. HelloWorldMessage2.cfmMaking Certain Attributes Optional

<!--- Filename: HelloWorldMessage2.cfm Author: Nate Weiss (NMW) Purpose: Creates a custom tag that accepts attributes ---> <!--- Tag Attributes ---> <cfparam name="ATTRIBUTES.message" type="string"> <cfparam name="ATTRIBUTES.topMessage" type="string" default="Hello, World, from Orange Whip Studios."> <cfparam name="ATTRIBUTES.topColor" type="string" default="yellow"> <cfparam name="ATTRIBUTES.bottomColor" type="string" default="orange"> <cfparam name="ATTRIBUTES.tableBorder" type="numeric" default="5"> <cfparam name="ATTRIBUTES.tablePadding" type="numeric" default="5"> <!--- Output message in HTML table format ---> <cfoutput> <table border="#ATTRIBUTES.tableBorder#" cellPadding="#ATTRIBUTES.tablePadding#"> <tr><th bgcolor="#ATTRIBUTES.topColor#"> <b>#ATTRIBUTES.topMessage#</b><br> </th></tr> <tr><td bgcolor="#ATTRIBUTES.bottomColor#"> #ATTRIBUTES.message#<br> </td></tr> </table> </cfoutput>

If the tag is explicitly provided with a topColor value when it is used, that value will be available as ATTRIBUTES.topColor. If not, the default attribute of the <cfparam> tag kicks in and provides the default value of Yellow. The same goes for the other new attributes: if values are supplied at run time, the supplied values are used; if not, the default values kick in.

NOTE

There's generally no harm in defining more attributes than you think people will need, as long as you supply default values for them. As a rule of thumb, try to provide attributes for just about every string or number your tag uses, rather than hard-coding them. This is what Listing 23.7 does.

Assuming you save Listing 23.7 as a custom tag template called HelloWorldMessage.cfm, you can now use any of the following in your application templates:

<cf_HelloWorldMessage message="We're getting the band back together!"> <cf_HelloWorldMessage topMessage="Message of the Day" message="We're getting the band back together!"> <cf_HelloWorldMessage message="We're getting the band back together!" topColor="Beige" bottomColor="##FFFFFF" tableBorder="0">

Using Functions to Test for Attributes

Instead of using the <cfparam> tag, you can use the isDefined() function to test for the existence of tag attributes. This is largely a matter of personal preference. For instance, instead of this:

<cfparam name="ATTRIBUTES.message" type="string">

you could use this:

<cfif not isDefined("ATTRIBUTES.message") > <cfabort showError="You must provide a Message attribute"> </cfif>

Or instead of this:

<cfparam name="ATTRIBUTES.topColor" type="string" default="Yellow">

you could use this:

<cfif not isDefined("ATTRIBUTES.topColor")> <cfset ATTRIBUTES.topColor="Yellow"> </cfif>

NOTE

Since the ATTRIBUTES scope is implemented as a ColdFusion structure, you can also use CFML's various structure functions to test for the existence of tag attributes. For instance, instead of isDefined("ATTRIBUTES.topColor")shown in the previous code snippetyou could use structKeyExists(ATTRIBUTES, "topColor") to get the same effect.

NOTE

Because the special ATTRIBUTES scope exists only when a template is being called as a custom tag, you can use is Defined("ATTRIBUTES") if you want to be able to detect whether the template is being visited on its own or included via a regular <cfinclude> tag.

Who Are You Developing For?

Before you get started on a new custom tag, think about who its audience will be. Keep that audience in mind as you think about the tag's functionality and what its attributes and default behavior should be. Custom tags generally fall into one of these two groups:

  • Application-Specific Tags. These display something or perform an action that makes sense only within your application (or within your company). These tags generally either relate to your application's specific database schema or are in charge of maintaining or participating in business rules or processes specific to your company. This type of tag extends the CFML language to the exclusive benefit of your application, creating a kind of tool set for your code's internal use.

  • General-Purpose Tags. These don't have anything specific to do with your application; instead, they provide some functionality you might need in a variety of scenarios. Rather than being of interest mainly to you or your programming team, these tags are of interest to the ColdFusion developer community at large. This type of tag extends the CFML language for all ColdFusion programmers who download or buy it.

The type of code you use to write the two types of tags isn't categorically different, but it is still helpful to keep the tag's audience in mind as you workwhether that audience is just you, or ColdFusion developers all over the world. If you are creating an application-specific tag, think about the people who might need to look at the code in the future. If you're creating a general- purpose tag, imagine fellow developers using your tag in various contexts.

Then ask yourself these questions:

  • How can I name the tag so that its purpose is self-explanatory? In general, the longer the tag name, the better. Also, the tag name should hint not only at what the tag does, but also at what it acts on. Something such as <cf_DisplayMovie> or <cf_ShowMovieCallout> is better than just <cf_Movie> or <cf_Display>, even if the shorter names are easier to type or seem obvious to you.

  • How can I name the attributes so that they are also self-explanatory? Again, there's little harm in using long attribute names. Long names make the tagsand the code that uses themmore self-documenting.

  • Which attributes will the audience need, and which should be optional versus required? A good rule of thumb is that the tag's optional attributes should have sensible enough default values so that the tag works in a useful way with only the required attributes. The optional attributes should be gravy.

NOTE

Try to make your tag's name and attribute names come together in such a way that the tag's use in code reads almost like a sentence. It's really great when you can understand a tag's purpose just by looking at its usage in actual code templates.

Querying and Displaying Output

Now that you know how to create a custom tag that accepts a few attributes to control its behavior, let's make a tag that really does something useful. This section demonstrates how easy it is to create tags that look up and display information. You can then reuse them throughout your application.

Running Queries in Custom Tags

You can use any tag in the CFML language within a custom tag template, including <cfquery>, <cfoutput>, and <cfset>. Listing 23.8 turns the movie-display code from the FeaturedMovie.cfm template in Chapter 19, "Introducing the Web Application Framework," into a custom tag called <cf_ShowMovieCallout>.

The first half of the FeaturedMovie.cfm example randomly determines which of the available movies to show. The second half queries the database for the selected movie and displays its title, description, and other information. This custom tag does the work of the second half of that example (that is, it only shows a movie's information, without the randomizing aspect). It takes just one required attribute, a numeric attribute called filmID. Within the custom tag, you can use the ATTRIBUTES.filmID value in the criteria for a <cfquery> to retrieve the appropriate film information.

At its simplest, this tag can be used as followsa neat, tidy, helpful abstraction of the CFML, HTML, and CSS code that the tag generates:

<!--- Show movie number five, formatted nicely ---> <cf_ShowMovieCallout filmID=5">

Listing 23.8. ShowMovieCallout.cfmQuery/Display a Particular Film Record

<!--- <CF_ShowMovieCallout> Custom Tag Retrieves and displays the given film Example of Use: <cf_ShowMovieCallout Film> ---> <!--- Tag Attributes ---> <!--- FilmID Attribute is Required ---> <cfparam name="ATTRIBUTES.filmID" type="numeric"> <!--- Whether to reveal cost/release dates (optional) ---> <cfparam name="ATTRIBUTES.showCost" type="boolean" default="yes"> <cfparam name="ATTRIBUTES.showReleaseDate" type="boolean" default="Yes"> <!--- Optional formatting and placement options ---> <cfparam name="ATTRIBUTES.tableAlign" type="string" default="right"> <cfparam name="ATTRIBUTES.tableWidth" type="string" default="150"> <cfparam name="ATTRIBUTES.caption" type="string" default="Featured Film"> <!--- Use "ows" datasource by default ---> <cfparam name="ATTRIBUTES.DataSource" type="string" default="ows"> <!--- Get important info about film from database ---> <cfquery name="getFilm" datasource="#ATTRIBUTES.dataSource#"> SELECT MovieTitle, Summary, AmountBudgeted, DateInTheaters FROM Films WHERE FilmID = #ATTRIBUTES.filmID# </cfquery> <!--- Display error message if record not fetched ---> <cfif getFilm.recordCount neq 1> <cfthrow message="Invalid FilmID Attribute" detail="Film #ATTRIBUTES.filmID# doesn't exist!"> </cfif> <!--- Format a few queried values in local variables ---> <cfset productCost = ceiling(val(getFilm.AmountBudgeted) / 1000000)> <cfset releaseDate = dateFormat(getFilm.DateInTheaters, "mmmm d")> <!--- Now Display The Specified Movie ---> <cfoutput> <!--- Define formatting for film display ---> <style type="text/css"> th.fm {background:RoyalBlue;color:white;text-align:left; font-family:sans-serif;font-size:10px} td.fm {background:LightSteelBlue; font-family:sans-serif;font-size:12px} </style> <!--- Show info about featured movie in HTML Table ---> <table width="#ATTRIBUTES.tableWidth#" align="#ATTRIBUTES.tableAlign#" border="0" cellSpacing="0"> <tr><th >#ATTRIBUTES.caption#</th></tr> <!--- Movie Title, Summary, Rating ---> <tr><td > <b>#getFilm.MovieTitle#</b><br> #getFilm.Summary#<br> </td></tr> <!--- Cost (rounded to millions), release date ---> <cfif ATTRIBUTES.showCost or ATTRIBUTES.showReleaseDate> <tr><th > <!--- Show Cost, if called for ---> <cfif ATTRIBUTES.showCost> Production Cost $#ProductCost# Million<br> </cfif> <!--- Show release date, if called for ---> <cfif ATTRIBUTES.showReleaseDate> In Theaters #ReleaseDate#<br> </cfif> </th></tr> </cfif> </table> <br clear="all"> </cfoutput>

TIP

It's often helpful to put an Example of Use comment at the top of your custom tag as shown here, even if you provide better documentation elsewhere. If nothing else, the hint will serve as a quick reminder to you if you need to revise the tag later.

At the top of this listing, a number of <cfparam> tags make clear what the tag's required and optional parameters will be. Only the filmID attribute is required; because its <cfparam> tag doesn't have a default attribute, ColdFusion will throw an error message if a filmID isn't provided at run time. The showCost and showReleaseDate attributes are Boolean values, meaning that either Yes or No (or an expression that evaluates to true or False) can be supplied when the tag is actually used; the default for each is defined to be Yes. The tableAlign, tableWidth, caption, and dataSource attributes are also given sensible default values so they can be omitted when the tag is used.

Next, the <cfquery> named getFilm retrieves information about the appropriate film, using the value of ATTRIBUTES.filmID in the WHERE clause. Then two local variables called productionCost and releaseDate are set to formatted versions of the AmountBudgeted and DateInTheaters columns returned by the query.

NOTE

These two variables are referred to as local because they exist only in the context of the custom tag template itself, not in the calling template where the tag is used. The getFilm query is also a local variable. See the section "Local Variables in Custom Tags," later in this chapter, for more information.

Custom tags should be able to deal gracefully with unexpected situations. For that reason, a <cfif> block is used right after the <cfquery> to ensure that the query retrieved one record as expected. If not, a <cfthrow> tag halts all processing with a customized, diagnostic error message. For more information about <cfthrow>, see Chapter 32.

NOTE

The error message generated by the <cfthrow> tag will be displayed using the appropriate look-and-feel template if the <cferror> tag or onError() method is used in Application.cfc, as discussed in Chapter 19.

The rest of the template is essentially unchanged from the FeaturedMovie.cfm template as it originally appeared in Chapter 19. The film's title, summary, and other information are shown in an attractive table format. The <cfif> logic at the end of the template enables the display of the Production Cost and Release Date to be turned off by setting the showCost or showReleaseDate attributes of the tag to No.

After you've saved Listing 23.8 as a custom tag template called ShowMovieCallout.cfm (in the special CustomTags folder or in the same folder as the templates in which you want to use the tag), it is ready for use. Listing 23.9 shows how easily you can use the tag in your application's templates. The results are shown in Figure 23.5.

Listing 23.9. UsingShowMovieCallout.cfmUsing the <cf_ShowMovieCallout> Custom Tag

<!--- Filename: UsingShowMovieCallout.cfm Author: Nate Weiss (NMW) Purpose: Demonstrates how to use the <CF_ShowMovieCallout> custom tag ---> <html> <head><title>Movie Display</title></head> <body> <!--- Page Title and Text Message ---> <h2>Movie Display Demonstration</h2> <p>Any movie can be displayed at any time by using the <b>&lt;cf_ShowMovieCallout&gt;</b> tag. All you need to do is to pass the appropriate FilmID to the tag. If the formatting needs to be changed in the future, only the Custom Tag's template will need to be edited.<br> <!--- Display Film info as "callout", via Custom Tag ---> <cf_ShowMovieCallout film> </body> </html>

Figure 23.5. Using the <CF_ShowMovie Callout> tag, you can display any film with just one line of code.

Local Variables in Custom Tags

The previous section pointed out that the getFilm, productCost, and releaseDate variables are local variables, meaning they exist only within the scope of the custom tag template itself.

This is an important aspect of ColdFusion's custom tag functionality. Whenever a custom tag is executed, it gets a private area in the server's memory to store its own variables. Unless you use a scope prefix (such as ATTRIBUTES, APPLICATION, or SESSION), all references to variables in the custom tag template refer only to this private area. The result is what some other programming languages call a name spacean interim memory space for the tag to do its work, without regard to how it will affect the template in which it is used.

For instance, if you attempt to display the value of the productCost variable in Listing 23.9, after the <cf_ShowMovieCallout> tag, you would get an error message saying that the variable doesn't existbecause the variable exists only within the custom tag's template itself. After the tag finishes its work, all its variables are discarded and are no longer available.

This is what enables custom tags to be so modular and independent. Because they don't affect the variables in the templates in which they run, they are free to create variables and run queries in any way they need to. All kinds of problems would arise if this weren't the case.

For instance, what if Listing 23.9 needs to run its own query named getFilms before it uses the <cf_ShowMovieCallout> tag? If custom tags didn't have their own local variables, the <cfquery> inside the custom tag template would overwrite any variable called getFilms that was set before the tag was called. This could lead to all sorts of strange behavior, especially if you were using a custom tag that you didn't write, because you would need to know all the variable names the custom tag uses internally and to avoid using them in your templates.

Remember these important points:

  • Variables in custom tags are always local, unless you specify a special scope name (discussed shortly).

  • Variables set before the tag is used aren't available within the custom tag itself.

  • Similarly, variables set in the custom tag's template aren't available as normal variables in code that uses the tag.

  • The ATTRIBUTES scope enables you to pass specific values into the tag.

  • The special CALLER scope, which you will learn about shortly, lets you access or set specific variables in the calling template.

Custom Tags Versus <cfinclude>

Earlier in this book, you learned about the <cfinclude> tag, which lets you put ordinary CFML code in a separate template and include it anywhere else you need to use it. You might be thinking that including a template with the <cfinclude> tag is pretty similar to calling a custom tag. That's true, but custom tags are more sophisticated because they have their own variable name spaces, as explained above.

Although you can often get the same results using <cfinclude> instead of creating a custom tag, your code will usually be harder to maintain and debug because variables set in the calling template might interfere with the way the included template behaves, and vice versa.

Категории