Accessibility
 
Home / Developer Center / ColdFusion MX Application Developer Center /

ColdFusion Article

Icon or Spacer Icon or Spacer Icon or Spacer
Rob Brooks-Bilson
Rob Brooks-Bilson

www.amkor.com

Harnessing XML with custom tags or ColdFusion Components

Not long ago, Macromedia began syndicating their DevNet News through a publicly accessible XML feed. This XML file contains information about all the latest articles and resources on DevNet and is updated whenever new content is added to the site.

As a happy coincidence, this XML feed provides a perfect excuse for checking out some of the new features in Macromedia ColdFusion MX, such as its XML parsing capabilities and ColdFusion Components (CFCs). At the same time, you'll see how you can extend the capabilities of custom tags, with which you're already familiar, by using the new CFCs instead.

Custom tags to the rescue
Let's start by creating a custom tag that grabs the XML feed and transforms that XML into a format more useful to ColdFusion developers. As it happens, ColdFusion MX comes with several new tags and functions for working with XML.

Below I show you a simple custom tag you can create for grabbing the feed and storing the information as a query object. I use a query object instead of other ColdFusion data types, such as a structure or Web Development Data Exchange (WDDX) packet, because of ColdFusion's ability to perform in-memory queries. This makes it easy to filter and sort the data pulled from the feed before returning it from the custom tag. Storing the data in a query object also allows both novice ColdFusion programmers and gurus alike to use the feed without having to worry about dealing with more complex data structures such as nested structures or arrays. In short, I'm keeping things as simple as possible.

The following steps outline the tag's functionality:

1.

Use the cfhttp tag to grab the XML feed from Macromedia's site.

2.

Convert the feed (stored in CFHTTP.FileContent) to a complex structure using ColdFusion MX's new XmlParse() function.

3.

Create a query object, loop through the complex structure containing the data from the XML feed, and populate the appropriate columns and rows of the query.

4.

Use ColdFusion's in-memory query feature (query of a query) to filter the data according to the attributes passed to the tag. You should be able to filter by product, author or resource type (article, column, tip, and so on). Optionally, sort the results based on the sort criteria passed to the tag.

5.

Return the results to the calling template using the (optional) query name supplied to the tag.


Before you actually start coding, you need to get an idea of what the XML feed actually looks like:

<resource type="">
<title></title>
<author></author>
<url></url>
<product name="" />
</resource>

Rather than reinvent the wheel and explain everything here, I'll refer you to the Macromedia DevNet XML Feed page for a complete breakdown of the feed, its structure, and the XML Document Type Definition (DTD) that it uses.

Once you have the structure of the XML, writing the custom tag becomes easy.  Download the sample files and open Listing 1:GetDevNetFeed.cfm below .

Download the sample file getdevnetfeed.zip (5K)


The first thing the tag does is check to see whether any attributes were passed to it. If so, a flag (Raw) is set to True. Otherwise, it's set to False, allowing the tag to avoid a lot of additional processing when it comes to filtering the query results.

Next, default values are assigned for each of the tag's attributes. The tag works by accepting a number of optional parameters. You may pass a name for the query object returned by the tag (Name). If you pass no name, the default (MyQuery) is assigned. Other optional attributes are used to filter the results pulled from the feed: Product, Author and Resource. An optional sort criteria and order can be passed using the SortOrder attribute.

Once all attribute assignments are complete, the cfhttp tag grabs the XML feed from Macromedia's site and stores it in the CFHTTP.FileContent variable. The XmlParse() function converts the feed into a complex structure (see Figure 1 ).


CFComponent Structure

Figure 1 : View full screen image (40k)


The next section of the tag performs the bulk of the work. An empty query object is created and a loop is used to iterate over the structure containing the content from the feed. During each iteration of the loop, the relevant information from the feed is pulled out and placed in a corresponding column in the query object. Each iteration represents one item in the news feed and translates to one row or record in the query.

Once all of the data has been extracted from the structure, the tag checks to see whether any filters were passed by evaluating the Raw flag. It it's True, the query object is returned to the calling template. If it's false—meaning that there were attributes passed into the tag—an in-memory query is performed against the existing query object and filters are applied in the WHERE clause of the SQL statement. This allows the original query object to be filtered by product, resource type or author. It also allows the query object to be resorted. Once the in-memory query is complete, the resulting query is returned to the calling template.

To see the tag in action, consider the following example in which I call the tag, passing no attributes, and dump the contents of the result using the cfdump tag:

<CF_getdevnetfeed>

<CFDUMP VAR="#MyQuery#">

The output looks something like what's shown in Figure 2.


CFDump output for DevNet feed

Figure 2 : View full screen image (48k)


Suppose you're interested in learning more about Macromedia Flash. It would make sense to bring back items only pertaining to Macromedia Flash, sorted by title. You can accomplish this easily by passing a few additional attributes to the tag. I've added some formatting for the output as well:

<!--- call the feed.  Name the return query 'GetTheFeed'. --->
<CF_getdevnetfeed NAME="GetTheFeed"
                  PRODUCT="Flash"
                  SORTORDER="Title">

<HTML>
<HEAD>
     <TITLE>News From The Macromedia DevNet</TITLE>
  <STYLE TYPE="text/css">
  <!--
       TH.tableHead  {
            background-color: #888888;
            font-family: Arial;
            
            font-weight: bold;
            text-align: center;
            color: #000000
       }
    
       TD.tableCellLeftA  {
            background-color: #D3D3D3;
            font-family: Arial;
            
            text-align: left;    
            color: 000000
       }
    
       TD.tableCellLeftB  {
            background-color: #C0C0C0;
            font-family: Arial;
            
            text-align: left;    
            color: 000000
       }    
  -->
  </STYLE>
</HEAD>

<BODY>                  
                  
<TABLE BORDER="0" CELLPADDING="3" CELLSPACING="1">
<TR>
  <TH CLASS="tableHead">Author</TH>
  <TH CLASS="tableHead">Title</TH>
</TR>  
<CFOUTPUT QUERY="GetTheFeed">
<TR>
  <TD CLASS="#IIF(GetTheFeed.currentrow MOD 2, DE('tableCellLeftA'), 
         DE('tableCellLeftB'))#">#Author#</TD>
  <TD CLASS="#IIF(GetTheFeed.currentrow MOD 2, DE('tableCellLeftA'), 
         DE('tableCellLeftB'))#"><A HREF="#URL#">#Title#</A></TD>
</TR>
</CFOUTPUT>
</TABLE>

</BODY>
</HTML>

Running this example results in output similar to Figure 3.


Running the DevNet custom tag with a table format

Figure 3 : View full screen image (32k)


Note that in this example, you also provide a name for the query object returned by the tag. This allows you to decide on the variable name you use for the query object, avoiding any potential variable naming conflicts that could arise by letting the tag use the default "MyQuery."

From custom tag to ColdFusion Component
You may not realize it, but this is a great tag to convert into a ColdFusion Component (CFC). CFCs offer an additional way to abstract and encapsulate code besides the methods that most ColdFusion developers are already used to, such as custom tags, includes and user-defined functions (UDFs). CFCs offer a whole range of features that make their use compelling, such as the ability to have multiple CFCs in a single template, inheritance, introspection (CFCs are self-documenting), remote invocation and instant availability as SOAP-based web services.

Actually converting the CF_GetDevNetFeed custom tag to a CFC is a straightforward process. CFCs are defined using the cfcomponent tag. Within a component, you can have any number of methods, each defined using a pair of cffunction tags. Each method can accept zero or more arguments (defined with cfargument tags). If a CFC returns any data, it does so with the cfreturn tag.

Look at Listing 2, GetDevNetFeed.cfc, which shows you what the tag looks like rewritten as a CFC. 


The first thing you'll probably notice is that the filename now ends with the extension .cfc instead of .cfm. CFCs have their own file extension so ColdFusion can differentiate them from regular CFML templates. In order to test this CFC, simply save it in any directory off of your web root.

Instead of using attributes like the custom tag did, CFCs use arguments. These are defined using the cfargument tag. Arguments can be required or optional, and can have default data types and values assigned to them (just like the cfparam tag). In this case, all of the arguments are optional and set to accept only string values.

In this example, I have also added exception handling via cftry/cfcatch. This allows you to do things like handle problems grabbing the feed, dealing with an empty feed, etc. If any exceptions arise, custom error messages are thrown via the cfthrow tag.

The "guts" of the CFC work the same way as the CF_GetDevNetFeed custom tag. The cfhttp tag grabs the XML feed and then the results are converted to a structure. A blank query is created and then populated by looping over the contents of the structure.

There is one additional feature present here that wasn't in the custom tag version. This time, the Product attribute is designed to allow you to specify a single product, or a comma-delimited list of products by which to filter the feed . This is accomplished by looking at the Product parameter as a list, checking its length and, if it's greater than one, looping over each product in the list and pulling the appropriate data.

The component wraps up with a cfreturn tag that returns the raw or filtered query to the entity that called it. I say "entity" here instead of "template" because this CFC can be called in several different ways and from entities other than ColdFusion. Let me show you how.

One way to call a CFC is to use the new cfinvoke tag, which allows you to specify the name of the component to invoke, a method in the component, and a return variable used to hold any data returned by the component. Save the following code in the same directory as the component you just created:

<!--- minimalist call with CFINVOKE --->
<cfinvoke component="GetDevNetFeed"
   method="GetFeed" 
   returnvariable="MyQuery1">

<cfdump var="#MyQuery1#">

If you run the code, you should get output that looks something like Figure 4.


Output from invoked CFC

Figure 4 : View full screen image (48k)


Parameters can be passed by including them directly in the cfinvoke tag:
<!--- call with CFINVOKE, filter on multiple products, sort it, and return as 
      a query  object --->
<cfinvoke component="GetDevNetFeed2" 
  method="GetFeed" 
  product="ColdFusion,Macromedia_Flash" 
 <!--- no space between items --->
  sortOrder="Title DESC"
  returnVariable="MyQuery2">
  
  <cfdump var="#MyQuery2#">

Besides calling with the cfinvoke tag, the CFC can also be called via a URL, the cfobject tag, or the CreateObject() function, as well as from a form, from Flash Remoting, or as a SOAP-based web service. This means you can call it from a web page using various methods, or from any other language or device capable of using the GET or POST method. This is possible because I specified "Remote" for the ACCESS type in the cffunction tag.

Here's an example of how to call the component via a URL with cfhttp:

<!--- call via CFHTTP. --->
<!--- Return as WDDX encoded query, filter on Product --->
<cfhttp method="Get" 
  url="http://127.0.0.1:8500/test/getdevnetfeed2.cfc?method=getfeed&product=Flash">

<cfdump var="#CFHTTP.FileContent#">

Running this code results in the output shown in Figure 5.


Output when you call the CFC with CFHTTP

Figure 5 : View full screen image (44k)


Note that the dump shows the object returned by the call is a WDDX-encoded query object. Calling the CFC via a URL causes the output to be returned automatically as WDDX-encoded text; in this case, it's a WDDX-encoded query object. This allows both ColdFusion and non-ColdFusion programs to call the component. It's up to the calling entity to deal with the returned data.

If you were to look at the actual WDDX, it would look like Figure 6.


WDDX output

Figure 6 : View full screen image (40k)


Using components as a web service
One of the great features of CFCs is their ability to turn almost any CFC into a SOAP-based web service—instantly. This makes it possible to expose your CFC to any client or program that's capable of interacting with web services. In the case of DevNet XML feed, making it available as a web service means it can now be called by Macromedia Flash programs, programs written for the Microsoft .NET platform, and Java 2 Enterprise Edition (J2EE) applications. All you need to do to expose a method in your CFC to the world as a web service is to set the ACCESS function of the method's cffunction tag to "Remote." That's it! It's that simple—and if you'll notice, the GetDevNetFeed method in our CFC is already set to Remote. This is something that just can't be done using the custom tag you created earlier.

To call the web service from a ColdFusion template, you use the cfinvoke tag:

<cfinvoke webservice="http://127.0.0.1:8500/test/getdevnetfeed.cfc?wsdl"
method="GetFeed"
returnVariable="MyQuery4">
<cfinvokeargument name="product" value="ColdFusion" />
<cfinvokeargument name="sortOrder" value="Title" />
</cfinvoke>

<CFDUMP VAR="#MyQuery4#">

Notice the "?wsdl" after the .cfc extension. This tells ColdFusion that you are invoking the CFC as a web service and causes ColdFusion to automatically generate the Web Services Description Language (WSDL) interface necessary for the interaction. You can see the actual WSDL generated by entering the URL to the CFC in your browser and appending the WSDL to the query string (see Figure 7).


Generated WSDL
Figure 7 : View full screen image (40k)

The syntax for invoking the CFC as a web service from within ColdFusion is familiar. It uses the same cfinvoke tag we used earlier. Note that this time, however, you should use the WEBSERVICE attribute of the cfinvoke tag to specify the service you want to call. Parameters are passed to the service via cfinvokeargument tags.

Where to go from here
There's a lot more to ColdFusion Components than I could possibly discuss in this article. For a complete discussion of CFCs, I urge you to check out the ColdFusion documentation.

Additionally, you can easily enhance both the custom tag and the CFC that I describe in this article with any number of additional features. Here are some suggestions for you to consider:


Instead of returning just a query object, give the caller the option to return the results in other formats such as an array of structures or a WDDX packet.

Each time the tag or component is called, a roundtrip to the Macromedia server is required. Because the content probably doesn't change throughout the day, you could build in a caching mechanism for the feed to minimize network traffic and improve processing time.


About the author
Rob Brooks-Bilson is the author of the O'Reilly book Programming ColdFusion as well as the Web technology manager at Amkor Technology, where he has worked since 1996. Rob's involvement with ColdFusion goes all the way back to version 1.5 and includes several large-scale projects, the creation of numerous open source custom tags, and more recently, the open source Common Function Library Project, where he coordinates several libraries of freely available functions. Rob is a member of Team Macromedia (formerly Team Allaire) and is a frequent speaker at ColdFusion user groups and conferences. Rob also has his CF certification as a Macromedia Certified Advanced ColdFusion 5 Developer. He has written several articles on ColdFusion for Intranet Design Magazine,, CNET'sBuilder.com, and Oreilly.com.