Macromedia Coldfusion MX 7 Web Application Construction Kit

In this chapter, you have learned quite a bit about how to use and write custom tags and the various roles they can play in your applications. A number of advanced custom tag concepts remain that this book just can't cover completely.

The following topics are covered in its companion volume, Advanced Macromedia ColdFusion MX 7 Application Development.

Paired Custom Tags

You can create paired custom tags that expect opening and closing tags to be placed in the calling template, just as CFML's own <cfoutput> tag expects a matching </cfoutput> tag to be present. ColdFusion provides a special structure called thisTag, which you can use to create such tags.

You can also search for ThisTag in ColdFusion Studio's online documentation.

Associating Nested Custom Tags

You can also create families of custom tags for use together that can access each other's attributes and other information. The tags usually are nestedoften to gather multiple sets of attributes. Many of ColdFusion's native tags have special subtags that gather additional information from you, the coder. For instance, consider the <cfmailparam> tag, which is used only between opening and closing <cfmail> tags (see Chapter 27, "Interacting with Email"), or the <cfcatch> and <cfrethrow> tags, which are used only between <cftry> tags (see Chapter 32).

You generally create such tag families using the <cfassociate> tag, which this book's companion volume explains.

NOTE

A great example of this type of custom tag is the extremely popular <cf_DHTMLMenu> custom tag by Ben Forta. It is included on the CD-ROM for this chapter.

Passing Attributes with attributeCollection

In addition to the special name and template attribute names (discussed in the section "Controlling Template Locations with <cfmodule>," earlier in this chapter), a third attribute name exists: attributeCollection. ColdFusion reserves the attributeCollection attribute name for a special use: if a structure variable is passed to a custom tag as an attribute called attributeCollection, the values in the structure become part of the ATTRIBUTES scope inside the custom tag template.

That is, instead of doing this:

<!--- Display Hello World Message, via Custom Tag ---> <cf_HelloWorldMessage message="We're getting the band back together!" topColor="yellow" bottomColor="orange">

or this:

<!--- Display Hello World Message, via Custom Tag ---> <cfmodule name="HelloWorldMessage" message="We're getting the band back together!" topColor="yellow" bottomColor="orange">

you could do this:

<!--- Delete movie ---> <cfset attribs = structNew()> <cfset attribs.message = "We're getting the band back together!""> <cfset attribs.topColor = "yellow"> <cfset attribs.bottomColor = "orange"> <!--- Display Hello World Message, via Custom Tag ---> <cfmodule name="HelloWorldMessage" attributeCollection="#attribs#">

This is most useful in certain specialized situations, such as when you are calling a custom tag recursively (using the custom tag within the custom tag's own template, so that the tag calls itself repeatedly when used). You would be able to call a tag a second time within itself, passing the second tag the same attributes that were passed to the first, by specifying attributeCollection="#ATTRIBUTES#".

The REQUEST Scope

In addition to the CALLER and ATTRIBUTES scopes, ColdFusion defines a third special variable scope called REQUEST, which is relevant to a discussion of custom tags. Although it isn't specifically part of ColdFusion's custom tag framework, it comes into play only in situations in which you have custom tags that might in turn use other custom tags.

Making Variables Available to All Custom Tags

Consider a situation where you might want to create a custom tag that uses the <cf_ShowMovieCallout> tag from Listing 23.8 internally. The new custom tag defines an optional dataSource attribute, which defaults to ows. That value is then passed to the <cf_ShowMovieCallout> tag from Listing 23.8. Wouldn't it be more convenient to use the dataSource variable defined in the Application.cfc file, in a process similar to that of earlier templates? That way, if your application needed to use a different data source name, you could just make the change in one place, in Application.cfc.

But how could you refer to that dataSource variable? Normally, you would refer to it directly in the dataSource attribute for each <cfquery> tag, as in the datasource="#dataSource#" attribute that has appeared in previous chapters. Of course, in a custom tag template, that variable wouldn't exist because the tag has its own local variable scope. You could access the value using the CALLER scope, as in dataSource="#CALLER.dataSource#", but there's a problem with that, too. When the new tag calls <cf_ShowMovieCallout>, the code template for <cf_ShowMovieCallout> won't be capable of referring to CALLER.dataSource because there is no local variable named DataSource in the calling template. In this situation, <cf_ShowMovieCallout> could refer to CALLER.CALLER.dataSource. That would work, but your code will get messier and messier if you start nesting tags several levels deep.

The answer is the REQUEST scope, a special scope shared among all ColdFusion templates participating in the page requestwhether they are included via <cfinclude>, called as a custom tag, or called via <cfmodule>.

This means you can change these lines in the Application.cfc file:

<!--- Any variables set here can be used by all our pages ---> <cfset dataSource = "ows"> <cfset companyName = "Orange Whip Studios">

to this:

<!--- Any variables set here can be used by all our pages ---> <cfset REQUEST.dataSource = "ows"> <cfset REQUEST.companyName = "Orange Whip Studios">

Then you will be able to provide the REQUEST.dataSource variable to the dataSource attribute of every <cfquery> tag in your application, regardless of whether the query is in a custom tag.

Maintaining Per-Request Flags and Counters

You can also use the REQUEST scope to create custom tags that are aware of how many times they have been included on a page. For instance, if you look at the code for the <cf_ShowMovieCallout> tag in Listing 23.8, you will see that it includes a <style> block defining how the callout box will appear. If you include this custom tag several times on the same page, that <style> block will be included several times in the page's source, which will cause the final page to have a longer download time than it should.

You could use the REQUEST scope to ensure that the <style> block gets included in the page's source code only once, by surrounding the block with a <cfif> test that checks to see whether a variable called REQUEST.calloutStyleIncluded has been set. If not, the <style> block should be included in the page. If it has been set, the tag knows that the block has already been included on the page, presumably because the tag has already been used earlier on the same page.

The <style> block in ShowMovieCallout.cfm would be adjusted to look like this:

<!--- If the <style> isn't included in this page yet ---> <cfif not isDefined("REQUEST.calloutStyleIncluded") > <!--- 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> <!--- Remember that the <STYLE> has been included ---> <cfset REQUEST.calloutStyleIncluded = "Yes"> </cfif>

Listing 23.16 provides the complete code for the revised ShowMovieCallout.cfm template.

Listing 23.16. ShowMovieCallout2.cfmTRacking Variables Between Tag Invocations

<!--- <cf_ShowMovieCallout2> 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> <!--- If the <STYLE> not included in this page yet ---> <cfif not isDefined("REQUEST.calloutStyleIncluded")> <!--- 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> <!--- Remember that the <STYLE> has been included ---> <cfset REQUEST.calloutStyleIncluded = "Yes"> </cfif> <!--- 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>

NOTE

Because the REQUEST scope isn't shared between page requests, you don't need to use the <cflock> tag when setting or accessing REQUEST variables.

Категории