| By Rob Brooks-Bilson | Article Rating: |
|
| September 17, 2003 12:00 AM EDT | Reads: |
3,901 |
Why Allaire ColdFusion® is HOT!
No doubt about it -- ColdFusion is HOT! What makes ColdFusion such an attractive development platform for Web applications is the fact that it is completely extensible. Since version 2.0, ColdFusion has allowed developers to extend the platform's capabilities using custom tags created in Microsoft Visual C++. Although a powerful feature, it requires a decent knowledge of C++ as well as Microsoft's Visual C++ compiler - putting it out of consideration for a lot of developers.
Version 3.0 of ColdFusion introduced CFML-based Custom Tags. These Custom Tags are composed entirely of CFML and can be written by anyone with basic experience in writing ColdFusion applications. Custom Tags allow you to do everything from automating repetitive tasks to building complex intelligent agents, using ColdFusion built in tags and functions. They work by having a ColdFusion template pass them any number of parameters to the tag for processing. The tag receives the parameters, processes them as necessary, performs a specified action, and/or returns output to the calling template.
Basic concepts
While building Custom Tags using CFML is relatively simple, there are a number of basic concepts to bear in mind. Your foray into building Custom Tags should begin with a trip to the ColdFusion Users Guide. Although the documentation on building Custom Tags is thin, it does present just enough to get you started. With that said, let's look at one of the most important and often misunderstood elements in Custom Tag building - passing variables and attributes to and from Custom Tags.
Passing values from your ColdFusion template to a Custom Tag is easy. All that is involved is calling a Custom Tag from your ColdFusion template and passing any appropriate values as attributes. You simply markup the tag with the appropriate parameters and away you go. Below is the syntax for calling a Custom Tag from within a ColdFusion template:
<CF_MyTag
Attribute1="value1"
Attribute2="value2"
AttributeN="valueN">
As you can see from the above example, calling the tag involves nothing more than referencing the tag name along with any required or optional attributes. Attribute values can be alphanumeric strings or ColdFusion variables. This allows you to pass both static and dynamic values to your Custom Tags for processing.
Once values have been passed from the ColdFusion template, they are available for processing within the Custom Tag. ColdFusion introduces two new variable scopes for dealing with values passed to custom tags: attributes and caller.
New attribute scope
The attributes scope allows Custom Tags to identify values that have been passed to them for processing. Because the attributes scope uniquely identifies a value passed to a Custom Tag, it must be referred to explicitly within the calling tag. If you leave off the attributes scope, ColdFusion will assume that you are trying to reference a local variable and throw an error (unless a local variable with the same name as the attribute exists). The following example illustrates the concept of assigning the value of a passed attribute to a local variable within a Custom Tag:
<!--- Begin ColdFusion Template 1 --->
<!--- call custom tag --->
<CF_DisplayText
TextToDisplay="Show this on the screen from within the
Custom Tag.">
<!--- Begin ColdFusion Custom Tag --->
<CFSET LocalTextVariable = attributes.TextToDisplay>
<CFOUTPUT>
#LocalTextVariable#
</CFOUTPUT>
In the above example, all we did was assign the attribute TextToDisplay from the calling ColdFusion template to a local variable inside of the Custom Tag called LocalTextVariable. This gives the advantage of not having to refer to the variable by the attributes scope as well as allowing us to reference the original value of the variable before any processing is applied. This is a technique isn't essential, but I find it useful nonetheless.
The caller scope
Passing variables to ColdFusion Custom Tags is not a one way street. The last example showed how you could output the results of a call to a Custom Tag within the Custom Tag, but what if you want to pass information back to the template that called the tag? Enter the caller variable scope. The caller scope allows you to pass information back to the ColdFusion template that called the Custom Tag. For a better idea of how this works, consider the following modification to our last example in which we pass a text string to a Custom Tag, the Custom Tag converts the string to all uppercase and then passes the converted string back to the calling template:
<!--- Begin ColdFusion Template 1 --->
<!--- call custom tag --->
<CF_DisplayText
TextToDisplay="Show this on the screen from within the
Custom Tag.">
<!--- display results from the custom tag within a CFOUTPUT section,
referencing the variable name from the caller scope within the
Custom Tag--->
<CFOUTPUT>
Output from Custom Tag: #ShowText#
</CFOUTPUT>
<!--- Begin ColdFusion Custom Tag --->
<!--- assign attribute to local variable --->
<CFSET LocalTextVariable = attributes.TextToDisplay>
<!--- convert text string to all uppercase --->
<CFSET UpperCaseText = UCase(LocalTextVariable)>
<!--- pass converted string back to calling template --->
<CFSET Caller.ShowText = UpperCaseText>
As you can see from the simplistic example, all you have to do to return a variable from a Custom Tag is to set the variable equal to an arbitrary variable name with the caller scope. This works great for any scenario where you need to return a single result for any number of variables. The next logical step, then, is to have a Custom Tag return the results of a query to a ColdFusion template. This allows you to write sophisticated Custom Tags that return multiple values for each variable passed. A good example of this is the CF_StockGrabber Custom Tag available from the Allaire Tag Gallery. CF_StockGrabber is a Custom Tag that retrieves stock quotes from Yahoo's quote.yahoo.com site and returns them to the calling ColdFusion template as a query. Rather than break down the entire tag step-by-step, I have chosen to highlight the relevant parts.
StockGrabberLite--the template
The first step is to create a ColdFusion template that calls the tag and subsequently displays the results. This is achieved by creating a ColdFusion template with the following elements:
<!--- Template that calls the CF_StockGrabberLite tag --->
<!--- add yhoo twice because of a bug in CFHTTP --->
<CF_StockGrabberLite
TickerSymbols="yhoo+yhoo+intc+egrp"
QueryName="MyQuery">
<TABLE BORDER=1>
<TR>
<TH BGCOLOR="#FFCC99">Symbol</TH>
<TH BGCOLOR="#FFCC99">Price</TH>
<TH BGCOLOR="#FFCC99">Change</TH>
<TH BGCOLOR="#FFCC99">Time</TH>
<TH BGCOLOR="#FFCC99">Date</TH>
<TH BGCOLOR="#FFCC99">Open</TH>
<TH BGCOLOR="#FFCC99">High</TH>
<TH BGCOLOR="#FFCC99">Low</TH>
<TH BGCOLOR="#FFCC99">Volume</TH>
</TR>
<CFOUTPUT QUERY="MyQuery">
<TR>
<TD BGCOLOR="##EFD6C6">#Symbol#</TD>
<TD BGCOLOR="##EFD6C6">#Last_Traded_Price#</TD>
<TD BGCOLOR="##EFD6C6">#Change#</TD>
<TD BGCOLOR="##EFD6C6">#Last_Traded_Time#</TD>
<TD BGCOLOR="##EFD6C6">#Last_Traded_Date#</TD>
<TD BGCOLOR="##EFD6C6">#Opening_Price#</TD>
<TD BGCOLOR="##EFD6C6">#Days_High#</TD>
<TD BGCOLOR="##EFD6C6">#Days_Low#</TD>
<TD BGCOLOR="##EFD6C6">#Volume#</TD>
</TR>
</CFOUTPUT>
</TABLE>
And now for the main tag :
<!--- This is the CF_StockGrabberLite tag (simplified version) --->
<CFSET Symbol_List = attributes.TickerSymbols>
<CFSET QueryName = attributes.QueryName>
<CFHTTP METHOD="GET"
URL="http://quote.yahoo.com/download/quotes.csv?Symbols=
#Symbol_List#&format=sl1d1t1c1ohgv&ext=.csv"
NAME="caller.#QueryName#"
COLUMNS="Symbol,Last_Traded_Price,Last_Traded_Date,Last_Traded_Time,
Change,Opening_Price,Days_High,Days_Low,Volume"
DELIMITER=","
TEXTQUALIFIER="""">
The above example takes the query that gets generated by CFHTTP and returns it to the calling ColdFusion template with the query name that was passed to the tag as an attribute. This allows users to dynamically name the query that will later be returned by the tag.
This method for returning the output of a Custom Tag works great for queries that are prebuilt by tags such as CFHTTP and CFQUERY. Unfortunately, not all data that you wish to return as a query comes neatly packaged that way. An alternative scenario involves a prebuilt query that needs to have information added or parsed out before being returned to the calling ColdFusion template. To further explain what I mean, let's look at the last example in which we built CF_StockGrabberLite. The tag works fine until you try to pass an invalid ticker symbol through CFHTTP to Yahoo. Doing this causes the tag to crash.
The reason for this is that invalid ticker symbols, index symbols and mutual fund symbols cause the value N/A to appear in the Volume field. In and of itself, this might seem harmless. However, if you consider that CFHTTP has a parameter for text qualification, the potential for problems arises. Yahoo returns the quotes requested by the tag as a comma-delimited list of qualified values. That is, all text in the file is surounded by double quotes. Numbers are left as is. Passing one of the above-mentioned "problem symbols" causes an unqualified N/A to appear in the volume field. Because CFHTTP expects all text to be qualified, it chokes when it encounteres the unqualified N/A.
To get around this, we need to turn off text qualification in CFHTTP by setting the parameter TEXTQUALIFIER="". If we were to leave it at that, we woud quickly notice that all of our query results were surrounded in quotes - not too practical for displaying stock quotes. We could parse out all of the quotes in the calling template after the results are returned from the Custom Tag, but that would be tedious and necessary in every application that uses the tag. So, what we need to do is to parse out the quotes in the Custom Tag before they are returned to the calling ColdFusion template.
There is no easy way to do a search and replace inside of a query before the results are output. One of the better workarounds is to move the query results to an array, do the search and replace, and repopulate the query with the updated data. This may sound like a huge undertaking, but it is pretty easy once you get the hang of it. Going with the same example we have been using, let's modify our CF_StockGrabberLite tag to strip all quotes from the CFHTTP result query before returning them to the Calling ColdFusion template. For this example, no changes need to be made to the ColdFusion template calling the CF_StockGrabberLite tag. Here's the revised tag:
<!--- This is the CF_StockGrabberLite tag (simplified version) --->
<CFSET Symbol_List = attributes.TickerSymbols>
<CFSET QueryName = attributes.QueryName>
<!--- If you want to see the tag break, change the textqualifier
to """" and insert an invalid ticker like xxxx --->
<CFHTTP METHOD="GET"
URL="http://quote.yahoo.com/download/quotes.csv?Symbols=
#Symbol_List#&format=sl1d1t1c1ohgv&ext=.csv"
NAME="#QueryName#"
COLUMNS="Symbol,Last_Traded_Price,Last_Traded_Date,Last_Traded_Time,
Change,Opening_Price,Days_High,Days_Low,Volume"
DELIMITER=","
TEXTQUALIFIER="">
<!--- RECREATE QUERY --->
<CFSET MyArray = ArrayNew(1)>
<CFSET MyQuery = Evaluate("#QueryName#")>
<CFSET NewColumns = "#MyQuery.ColumnList#">
<CFSET NewQuery = QueryNew(NewColumns)>
<!--- ADD ROWNUMBER TO END OF EACH ROW'S VALUE --->
<CFOUTPUT QUERY="MyQuery">
<CFSET MyArray[CurrentRow] = NumberFormat(CurrentRow, "000009")>
<CFSET Temp = QueryAddRow(NewQuery)>
</CFOUTPUT>
<!--- POPULATE THE NEW QUERY WITH THE INFO FROM THE OLD ONE,
BUT WITH ALL QUOTES REMOVED --->
<CFLOOP FROM=1 TO=#MyQuery.RecordCount# INDEX="This">
<CFSET Row = Val(Right(MyArray[This], 6))>
<CFLOOP LIST="#MyQuery.ColumnList#" INDEX="Col">
<CFSET Temp = QuerySetCell(NewQuery, Col, Replace(Evaluate
("MyQuery.#Col#[Row]"),"""","","All"), This)>
</CFLOOP>
</CFLOOP>
<!--- PASS QUERY WITH QUOTATION MARKS REMOVED BACK TO
CALLING TEMPLATE --->
<CFSET "Caller.#QueryName#" = NewQuery>
Armed with the above techniques, it is possible to write Custom Tags that do some pretty amazing things. For more examples of Custom Tag wizardry, visit Allaire's Tag Gallery.
Published September 17, 2003 Reads 3,901
Copyright © 2003 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
























