You have to love a software product that makes developers, even relatively inexperienced ones, productive quickly and empowers them to do seemingly impossible things. The more I work with and learn about ColdFusion the more it seems as if there are no limits to what can be done with it.
I've experienced the "ColdFusion can do anything" effect on a number of occasions after having used it for some programming task that I previously thought was impossible. Just when it seemed I'd hit a dead end and was about to give up, a quick look through the ColdFusion documentation or some other resource produced a tag or function that was just what I needed. And after a few minutes of learning how to leverage my newly discovered tag (or function), the problem was solved and the supposedly impossible task was thrown onto the pile with all the other "impossible" hurdles that I've conquered with ColdFusion. This article will describe how I used ColdFusion to do one such task that, at the time, seemed impossible to me.
Mission: Possible?
As a webmaster for the Air Protection Division at EPA Region 3 I wear many hats. Besides being a ColdFusion developer responsible for keeping our dynamic data-driven intranet applications up and running, I'm also required to develop/maintain a number of static Web sites on which the content doesn't change all that much. By "static" I mean a Web site that resides on a Web server without any ColdFusion (or database) support. I was recently asked by someone in my organization if there was a way to publish data from a large internal database on a static Web site in the form of HTML Web pages. The data for each record in the database would have to be displayed on a single static Web page, so there would be one page for each database record. The data would need to be updated only occasionally, but the site would have to be created in HTML format to ensure maximum accessibility, usability, and consistency with the rest of the site. Oh, and did I mention that the database contained over 700 records? That's over 700 static Web pages that had to be created and then populated with unique content. My initial reaction was "That's impossible" but, as I've come to expect, ColdFusion would prove me wrong and enable me to do the impossible.
Of course, writing and deploying a small ColdFusion application would be the easiest and fastest way for me to address this problem since I am, after all, a ColdFusion developer and this would be a fairly straightforward data drill-down application to write. However, there was a serious problem with the "let's use ColdFusion" approach: there was no ColdFusion (or database) support of any kind on the destination Web server, nor was there likely to be in the future. Therefore, what I needed was a way to create hundreds (or thousands) of static Web pages from the database information automatically and rapidly so that the resulting pages could be served statically. I could use ColdFusion (or any technology) to create the HTML pages that comprise the static Web site, but I couldn't build an actual ColdFusion application because the server lacked ColdFusion support. Now that I think about it, situations like this probably occur all the time because many organizations that, for whatever the reason, don't have ColdFusion support on their Web site still have data that has to be published on those sites. And they can't just upload a large database or spreadsheet file to the site and be done with it because such a "solution" would be inaccessible and pretty much unusable. In my case the final product had to be user-friendly, accessible, easily navigable, and logically organized. In other words, it had to be a standard HTML Web site consisting of several hundred static Web pages.
If You Build It, They Will Come
After a lot of experimentation and a fair amount of trial and error, I discovered that it's actually possible to automatically create an entire static Web site consisting of several hundred unique Web pages using ColdFusion, a database, and a few key CFML tags. Using the technique I developed, static Web pages are constructed "on the fly" from database records, one page per database record, and then written to the ColdFusion server as HTML files. I also realized that I could mimic the functionality of a dynamic data drill-down application in the static Web site by creating a "master" static Web page containing links to the many "details" pages on the site. I call the resulting Web site a "pseudo-dynamic" site because, although it's comprised entirely of static Web pages, the pages are all created (and maintained) automatically with ColdFusion MX 7.
There are two primary ColdFusion tags that enable the creation of documents that can be served statically via the Web: <CFFILE> and <CFDOCUMENT>. The former has been around forever and is extremely versatile. It can be used to create almost any type of text file, including an HTML file. The <CFDOCUMENT> tag is relatively new and is supported only in ColdFusion MX 7; it's used to create PDF and Flashpaper documents. As this article will demonstrate, <CFFILE> and <CFDOCUMENT> can be used to generate any number of unique HTML, PDF, or Flashpaper documents from database information.
ColdFusion File Writing 101
I could write a book about the many and varied uses of the <CFFILE> tag. The <CFFILE> tag has a multitude of capabilities by virtue of its "action" attribute. The action attribute determines how the tag functions and what it can do. When the action attribute is set to "write," the <CFFILE> tag can create a file and write it to a directory on the ColdFusion server. Other valid values of the action attribute include read, copy, move, upload, and append. However, we're only concerned with the tag's "write" attribute here. When writing files to the ColdFusion server with <CFFILE>, the other required attributes of the tag are "file" and "output." As you might expect, the "file" attribute defines the full path and filename of the file you're writing to the server. The content of a file created with <CFFILE> is defined using the "output" attribute of the tag. The value for this attribute can be as simple as a one-word text string or as long and complex as the HTML code for an entire Web page. In other words, by specifying HTML code in its "output" attribute, <CFFILE> can create a static Web page.
The <CFFILE> tag's "output" attribute defines the actual content of the file you're creating. The example code below creates a simple text file named "myCreatedFile.txt," populates it with the customary "Hello World" message, and then writes the file to the ColdFusion server with the filename "myCreatedFile.txt":
<CFFILE ACTION="WRITE" FILE="C:\Inetpub\wwwroot\mySite\myCreatedFile.txt"
OUTPUT="Hello World!">
As shown here, the value of the <CFFILE> tag's "file" attribute consists of the full path to the file you're creating on the server, plus the filename.
Now, let's get back to the task at hand which is, as you'll recall, creating an entire static Web site automatically with ColdFusion. What if we want to use <CFFILE> to create an HTML file instead of a text file? This is not much harder to do. The main difference is that we first use the <CFSAVECONTENT> tag to assign the necessary HTML code to a variable, and then make that variable the value of the <CFFILE> tag's "output" attribute. This sounds a lot more complicated than it really is, so an example should help. Look at the code Listing 1.
Here the <CFSAVECONTENT> tag "stores" the HTML code needed to create our output file in the "myHTMLCode" variable. Then, the <CFFILE> tag writes a file named "myCreatedFile.html" to the server using the code specified in the "myHtmlCode" variable as the file's content. <CFSAVECONTENT> is invaluable in this situation because it lets us save a long complex text string - HTML code in this case - in a variable that can then be used as the value of the "output" attribute in <CFFILE>. This keeps the code neatly organized and readable. Also, unlike the <CFSET> tag, we don't need to escape quotes or worry about other special characters that may be present in the HTML code because the <CFSAVECONTENT> tag automatically takes care of that for us.
We're not limited to storing HTML code in a variable defined by the <CFSAVECONTENT> tag. We can also specify JavaScript and/or Cascading Style Sheet (CSS) syntax in variables defined by <CFSAVECONTENT>. Better yet, we can use CFML to create dynamic content in HTML files that are written to the server with <CFFILE>. Taking our simple example a step further, let's add some CFML to the <CFSAVECONTENT> tag in Listing 1 to display the current date in our output file. This is shown in Listing 2.
Notice how we're able to use both HTML and CFML between the opening and closing <CFSAVECONTENT> tags in Listing 2. ColdFusion will evaluate the CFML between the opening and closing <CFOUTPUT> tags and then treat the resulting code as one long text string. It then assigns this string to the "myHtmlCode" variable. It's like we're "injecting" the HTML and CFML that's needed to create our file into the <CFFILE> tag's "output" attribute. When the <CFFILE> tag executes it will, once again, use the source code that's stored in the "myHtmlCode" variable to create the HTML file and write it to disk. When loaded in a browser this page will display the message "Hello World, the date is [today's date]!" The <CFSAVECONTENT> tag lets us use CFML and achieve precise control over the code that's written to our output file.
There is another important aspect of <CFFILE> that you should know about. When <CFFILE> is instructed to write a file that already exists to the server, the original file is overwritten and replaced with the new one. So be careful when using <CFFILE> because you can overwrite important files on your server. I always make a special directory on the server to hold my ColdFusion-generated files and confine my file writing activities to that directory. That way I can be reasonably sure that nothing really important is being overwritten on the ColdFusion server.
Of course, it's also possible to retrieve data from a database and put it in the <CFSAVECONTENT> tag to include in a file that's subsequently written to the server with <CFFILE>. Conceptually, this isn't much different from the example in Listing 2 since we'd just be substituting query results for the current date and writing a single output file to the server. However, getting back to the task at hand, what if we wanted to generate a hundred or even a thousand static Web pages from information in a database and mimic the functionality of a dynamic data drill-down application using only static Web pages? By now it should come as no surprise that even these tasks aren't too difficult. After all, this is ColdFusion we're using here! The code in Listing 3 does this seemingly impossible task by putting the <CFSAVECONTENT> and <CFFILE> tags in a <CFLOOP> tag.
There's a lot of code in Listing 3, so let's get right into it. The code loops over the "listAllRecords" query executing the <CFSAVECONTENT> and <CFFILE> tags once for each row retrieved by the "listAllRecords" query. With each pass through the loop, the CFML inside the opening and closing <CFOUTPUT> tags is evaluated and the resulting HTML source code that comprises that HTML file is assigned to the "myHtmlCode" variable as a text string. In this case we're outputting, within each HTML file, the Project ID, Name, Description, and Last Modified date values for each record retrieved by the "listAllRecords" query. The myHtmlCode variable, which holds the HTML source code for each file, becomes the value of the "output" attribute in the <CFFILE> tag and the resulting HTML file is written to the "files" directory on the server. Thanks to the <CFLOOP> tag, this process is repeated for every record retrieved by the "listAllRecords" query. Each static Web page written to the server is unique because it's populated with the data from a single row in the database. Notice the code in the "file" attribute in the <CFFILE> tag. This code defines the full path (on the server) and filename of the HTML files that are written to disk. It's dynamic and will change with each pass through the loop. This means that the files (i.e., Web pages) that are written to the "files" directory will be assigned sequential filenames comprised of a "p" concatenated with the "projectid" value and will have an ".html" extension. This will produce filenames such as "p1.html," "p2.html," "p3.html," etc. for the files written to the server. In other words, the filenames are sequential and correspond to the "projectid" values in the database.
Now I'm a Believer
I must admit the first time I
ran the code in Listing 3 in a Web browser I was doubtful it would
work. I fully expected it to crash and give me a dozen error messages
pointing out the flaws in my code and describing why it was impossible
to do what I'd asked it to do. Imagine my surprise when the template,
which I'd aptly named "pageGenerator.cfm," executed on the first
attempt without any errors and sat there waiting for me. At first I
thought that it hadn't done anything much less written hundreds of HTML
files to the server, because it executed so fast. But when I checked
the "files" directory on the server -which served as the repository for
all of my ColdFusion-generated files - I found more than 700 HTML files
there! (See Figure 1.)
Moreover, each file had a unique sequential filename (based on the
"projectid" parameter), syntactically correct HTML source code, and
unique content derived from the "myProjects" database. So, the bottom
line is that ColdFusion enabled me to create, in a matter of seconds,
hundreds of static Web pages populated with data from a database. If
that's not working efficiently and making the best use of my time, then
I don't know what is.
Remember the basic data drill-down application I mentioned earlier? It would now be possible to mimic this functionality - using static pages - by creating a "master" static Web page containing links to each of the "details" pages I'd just created. The master static Web page, which is created with ColdFusion (of course), lists three database column values: Project ID, Project Name, and Last Modified Date. The "Project ID" values serve as links to the corresponding "details" pages. These links are created by concatenating a "p" with the "projectid" value as shown in Listing 4.
When this code is run, a static Web page named "masterpage.html" is created and written to the "files" directory on the server. This page displays the Project ID, Project Name, and Last Modified Date for every record retrieved by the "listAllRecords" query. The Project ID column values are linked to the corresponding "details" pages. Remember how the filenames of the 700-odd "details" pages were derived from the Project ID values? It's the same thing here only this time we're creating a single Web page with links to each of the 700+ "details" pages. There's a direct correlation between the links in the "master" page and the filenames of the details pages, i.e., they are both created from the "projectid" values. So the details of any record can be viewed by clicking on the appropriate "Project ID" link in the master page, as shown in Figure 2. Using just two ColdFusion MX 7 templates (Listings 3 and 4) and a database, we can automatically create a large static Web site - with basic data drill-down functionality - in a matter of seconds. As a final touch and to aid navigation within the static site, a simple HTML link back to the master page can be put on each details page.
There are some significant benefits to using <CFFILE> to create static Web pages using ColdFusion. First, it doesn't matter how many database records there are. Granted, it will take ColdFusion a little longer to process 700 database records (and write the corresponding HTML files) than it will to process just a few, but ColdFusion and the database are still doing all of the work, not me. Plus it's getting done much faster than I could ever do it. Second, by modifying the query parameters it's easy to control what static files are actually generated and written to disk. For example, I could have easily used a WHERE clause in the "listAllRecords" query in Listing 3 to limit the query results to only those records that meet certain criteria. Then only Web pages representing the retrieved records would have actually been generated and written to the server. In fact, this is an excellent way to update and maintain a pseudo-dynamic Web site. For example, assuming your database has a "Last Modified Date" column (indicating the date/time each record was last updated), you can easily limit the query with a WHERE clause to only those records that were modified during the past day, week, month, etc. Then, the only static pages that would be generated by <CFFILE> (and written to the server) would be those that contain data that was modified in the past day, week, or month. You could then overwrite the old outdated static Web pages with the new ones on your server, and your pseudo-dynamic site would be updated with only a minimal effort on your part. Of course, you'll have to upload (FTP) any changed files to the server manually but you'd need to do that anyway with a static Web site.
Another benefit of creating and deploying a pseudo-dynamic Web site is better performance. While the performance of a properly written dynamic ColdFusion application is very good, it's not going to surpass the performance of a static Web site. With this technique ColdFusion is used to create batches of files that are manually uploaded to a Web server and served statically. So the performance penalties normally associated with dynamic Web sites are avoided. In other words, you will always get better performance from a pseudo-dynamic (i.e., static) Web site than you will from a dynamic Web application even if ColdFusion powers that application.
The fourth and arguably greatest benefit of this technique stems from the use of Dreamweaver 8 templates. Being responsible for several static Web sites, I always use Dreamweaver 8's "templates" feature so that whenever I need to make a routine change to a static site, all I need do is change the Dreamweaver template and all of the pages in the site (that use that template) are changed automatically. Therefore, when using <CFFILE> to write static Web pages to the ColdFusion server, I make certain to retain all Dreamweaver template references in the HTML source code. Dreamweaver embeds template references in the HTML source code as HTML comments. Keeping these template references in the HTML source code ensures that my ColdFusion-generated static files will be "linked" to the Dreamweaver template. This means that when (not if) there is a need to revise the Dreamweaver template to accommodate some routine change in the HTML source code, all of the static pages that were generated with <CFFILE> will be changed automatically by Dreamweaver. This is a terrific ancillary benefit because it saves me (or someone else) from having to recreate all of the ColdFusion-generated static pages from scratch just to accommodate routine changes to the HTML source code.
As I mentioned earlier, the <CFDOCUMENT> tag can be used to write PDF and Flashpaper documents to a server. By modifying the <CFFILE> technique, it's possible to create any number of unique PDF or Flashpaper documents from database information in the same way that HTML files are created from database information using <CFFILE>. This time, however, we loop over the <CFDOCUMENT> tag (not <CFFILE>) and specify the filenames via the <CFDOCUMENT> tag's "filename" attribute as shown in Listing 5.
Note that this code omits the <CFSAVECONTENT> and <CFFILE> tags since they are no longer needed. Conceptually this code is the same as Listing 3 because we're still looping over a query and writing a single output file to the ColdFusion server on each pass through the loop. The "filename" attribute of the <CFDOCUMENT> tag is used to specify the full path and filename of the PDF files to be written to the server. As we did before with <CFFILE>, we dynamically construct sequential filenames for the PDF output files and specify "PDF" as the output format in the <CFDOCUMENT> tag. This results in filenames such as "p1.pdf," "p2.pdf," etc. The "overwrite" attribute of the <CFDOCUMENT> tag enables us to overwrite and replace any existing PDF files that may reside in the target directory on the server. The code in Listing 5 will create a unique PDF document for each record retrieved by the "listAllRecords" query; each PDF file will be populated with the data from a single database record and then written to disk.
You may receive the error message "The request has exceeded the allowable time limit" when executing an exceptionally long running template such as the code in Listing 5. To remedy this, you can use the "requesttimeout" attribute of the <CFSETTING> tag to increase the timeout limit for the template and override the ColdFusion Administrator's default timeout setting. Just insert the following code at the top of the template:
<CFSETTING REQUESTTIMEOUT = "360"
ENABLECFOUTPUTONLY = "no">
This will reset the timeout limit for the template to six minutes (i.e., 360 seconds) and preserve the ColdFusion Administrator's default timeout setting for all other templates on the server.
Conclusion
ColdFusion's <CFDOCUMENT> and
<CFFILE> tags enable the rapid creation of static files, and each
file can be populated with unique data from a database. This technique
can be used to create any number of HTML, PDF, or Flashpaper documents
for immediate deployment on a static (non-ColdFusion) Web site. If you
have database information you need to display on a Web site but no
ColdFusion support on the site, you could use this technique to create
as many static files as there are records in your database. You could
then upload those files and serve them statically from your site.
Advanced ColdFusion developers could even implement an Event Gateway or
employ the <CFSCHEDULE> tag to periodically generate a fresh set
of static files whenever the database information changes or according
to some preset schedule. Then it's just a matter of replacing the old
files on the Web server with the new ones. Web servers that have
ColdFusion support may also benefit because files (especially PDF and
Flashpaper documents) could be periodically batch-generated and
uploaded to the server rather than being served dynamically in
real-time. This would lighten the ColdFusion server's processing load
and most likely improve performance.
Needless to say, both the <CFFILE> and <CFDOCUMENT> tags are extremely powerful and important development tools. They come in very handy when you're a webmaster like me and responsible for one or more static Web sites that have no ColdFusion support. In such situations you need to get creative with ColdFusion or you could find yourself hand-coding hundreds (or thousands) of static Web pages. Obviously, this would be a mind-numbing, time-consuming exercise even if you use Dreamweaver's templating feature. Whenever you need to publish static text, HTML, PDF, or Flashpaper documents, the <CFFILE> and <CFDOCUMENT> tags should come to mind. Using them is simplicity itself, and the benefits can be enormous. As for me, I was able to accomplish another "impossible" task with ColdFusion MX 7 and save precious time for other development projects. So all of the harried, overworked ColdFusion developers out there should relax, take a deep breath, and repeat after me: "ColdFusion can do anything."