 |
 |
 |
 |
|
|
|
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: |
| |
Use the cfhttp
tag to grab the XML feed from Macromedia's
site. |
| |
Convert the
feed (stored in CFHTTP.FileContent) to a
complex structure using ColdFusion MX's
new XmlParse() function. |
| |
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. |
| |
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. |
| |
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 . |
| |
|
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 ). |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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). |
|
| 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.
|
|
|
|
|