Using WDDX Packets to Store Information in Files
In the listings you've seen so far in this chapter (especially Listing 16.3), you have learned how easy it is to convert any variable or data structure to XML with the <cfwddx> tag. Because WDDX packets are so easy to create, and contain just about any type of information, and because the packet itself is just simple text (as is any XML document), ColdFusion developers often use WDDX as a way to store complex information in places where it's usually only possible to store text.Tools and Languages Supported by WDDX."
About Storing Packets in Files
You have already seen how you can use <cffile> and <cfwddx> to create WDDX packets, store them in files, and deserialize the packets back into native ColdFusion variables. There are many situations in which you might want to save information in such files.For instance, you might want to build an application whose behavior or appearance can be tweaked with various settings. Let's say you are building an intranet for the fictitious Orange Whip Studios company, and you want certain aspects of the application to be flexible. One setting will be for the background color of the application's home page, another setting will be for the name of the company, and so on. This way, if the desired color or the name of the company changes next month, you simply change the setting. Conceptually, this is equivalent to the Options or Preferences dialog box found in many Windows or Mac applications.
Building a Simple WDDX Function Library
The serialization and deserialization examples you've seen so far in this chapter use the <cfwddx> and <cffile> tags to read or store WDDX packets on the server's drive. As you've seen, it's really easy. We can make it even easier by creating a few simple user-defined functions.The UDF function library called WDDXFunctions.cfm (included with the listings for this chapter) contains a few simple functions for reading and writing WDDX packets on the server's drive. These functions are just shorthand for using the <cfwddx>, <cfhttp>, and <cfwddx> tags. The library also contains similar functions for reading and writing packets in the CLIENT scope, and for reading packets from other Web servers using HTTP.Listings 16.1 and 16.2. Wrapping them into functions makes them even easier to use.
Listing 16.7. WDDXFunctions.cfmA UDF Library for Reading and Writing WDDX Packets
<!---
Name: WDDXFunctions.cfm
Author: Nate Weiss and Ben Forta
Description: A general-purpose UDF library to make using WDDX easier
Created: 02/01/05
--->
<!---
Function to write any value to the
server's drive as a WDDX packet.
--->
<cffunction name="WDDXFileWrite"
returntype="void">
<!--- Required arguments --->
<cfargument name="File"
type="string"
required="Yes">
<cfargument name="Value"
type="any"
required="Yes">
<!--- This variable is for this function's use only --->
<cfset var WddxPacket=">
<!--- Convert the value to a WDDX packet --->
<cfwddx action="CFML2WDDX"
input="#ARGUMENTS.Value#"
output="WddxPacket">
<!--- Save the WDDX packet to the server's drive --->
<cffile action="WRITE"
file="#ARGUMENTS.File#"
output="#WddxPacket#">
</cffunction>
<!---
Function to read a value from a WDDX packet on
the server's drive. Returns the value in the packet,
after deserialization.
--->
<cffunction name="WDDXFileRead"
returntype="any">
<!--- Required argument --->
<cfargument name="File"
type="string"
required="Yes">
<!--- These variables are for this function's use only --->
<cfset var Result=">
<cfset var WddxPacket=">
<!--- Read the WDDX packet from the server's drive --->
<cffile action="READ"
file="#ARGUMENTS.File#"
variable="WddxPacket">
<!--- Deserialize the value in the WDDX packet --->
<cfwddx action="WDDX2CFML"
input="#WddxPacket#"
output="Result">
<!--- Return the result --->
<cfreturn Result>
</cffunction>
<!---
Function to read a value from a WDDX packet
on a Web server. Returns the value in the
packet, after deserialization.
--->
<cffunction name="WDDXHttpGet"
returntype="any">
<!--- Required argument --->
<cfargument name="URL"
type="string"
required="Yes">
<!--- The Result variable is for this function's use only --->
<cfset var Result=">
<!--- Fetch the WDDX packet over the wire --->
<cfhttp method="GET"
url="#ARGUMENTS.URL#">
<!--- Deserialize the value in the WDDX packet --->
<cfwddx action="WDDX2CFML"
input="#CFHTTP.FileContent#"
output="Result">
<!--- Return the result --->
<cfreturn Result>
</cffunction>
<!---
Function to write any value to a client variable
as a WDDX packet.
--->
<cffunction name="WDDXClientWrite"
returntype="void">
<!--- Required arguments --->
<cfargument name="Name"
type="string"
required="Yes">
<cfargument name="Value"
type="any"
required="Yes">
<!--- This variable is for this function's use only --->
<cfset var WddxPacket=">
<!--- Convert the value to a WDDX packet --->
<cfwddx action="CFML2WDDX"
input="#ARGUMENTS.Value#"
output="WddxPacket">
<!--- Save the packet as a CLIENT variable --->
<cfset CLIENT[ARGUMENTS.Name]=WddxPacket>
</cffunction>
<!---
Function to retrieve a value stored with
WDDXClientWrite().
--->
<cffunction name="WDDXClientRead"
returntype="any">
<!--- Required argument --->
<cfargument name="Name"
type="string"
required="Yes">
<!--- These variables are for this function's use only --->
<cfset var Result=">
<cfset var WddxPacket=">
<!--- If the client variable exists and is valid --->
<cfif IsDefined("CLIENT.#ARGUMENTS.Name#")>
<cfif IsWddx(CLIENT[ARGUMENTS.Name])>
<!--- Deserialize the value in the WDDX packet --->
<cfwddx action="WDDX2CFML"
input="#CLIENT[ARGUMENTS.Name]#"
output="Result">
</cfif>
</cfif>
<!--- Return the result --->
<cfreturn Result>
</cffunction>
Storing Application Settings as a WDDX Packet
The new UDF library can be put to work right away. As mentioned, the example for this section will be an application that has a few settings for controlling things like the background color, company name, and so on., and so on.
Listing 16.8. Application.cfmReading Application Settings from a WDDX Packet on Disk
First, the WDDX function library from Figure 16.5).
<!---
Name: Application.cfm
Author: Nate Weiss and Ben Forta
Description: Executes on every request
Created: 02/01/05
--->
<!--- Define the application --->
<cfapplication name="OrangeWhipIntranet"
clientmanagement="Yes">
<!--- Include the WDDXFunctions UDF library --->
<cfinclude template="WDDXFunctions.cfm">
<!--- If the application has not been initialized yet, or if the --->
<!--- user is currently trying to change the application's settings... --->
<cfif (NOT IsDefined("APPLICATION.Initialized"))
OR IsDefined("FORM.IsSavingSettings")>
<!--- Initialize the application --->
<cftry>
<!--- Location of AppSettings.xml file --->
<cfset SettingsFile = GetDirectoryFromPath(GetCurrentTemplatePath())
& "/AppSettings.xml">
<!--- Attempt to initialize application.
If this fails for any reason, --->
<!--- the <cfcatch> block will display the Settings form page. --->
<cfset APPLICATION.AppSettings = WddxFileRead(SettingsFile)>
<!--- Remember that the application has been initialized, so that --->
<!--- this whole section will be skipped until server is restarted --->
<cfset APPLICATION.Initialized = True>
<!--- Display the Settings form page if any exceptions are thrown --->
<cfcatch type="Any">
<cfinclude template="AppSettingsForm.cfm">
<cfabort>
</cfcatch>
</cftry>
</cfif>
Listing 16.9. AppSettingsForm.cfmReading Application Settings from a WDDX Packet on Disk
<!---
Name: AppSettingsForm.cfm
Author: Nate Weiss and Ben Forta
Description: Provides a form for editing this application's settings
Created: 02/01/05
--->
<!--- Location of AppSettings.xml file --->
<cfset ThisFolder=GetDirectoryFromPath(GetCurrentTemplatePath())>
<cfset SettingsFile=ThisFolder & "AppSettings.xml">
<!--- Read time zone recordset from WDDX packet on the server's drive --->
<cfset TimeZones=WDDXFileRead(ThisFolder & "TimeZoneRecordsetPacket.xml")>
<!--- If the form is being submitted --->
<cfif IsDefined("FORM.IsSavingSettings")>
<!--- Make new structure called Settings, which contains data from form --->
<cfset Settings.CompanyName=FORM.CompanyName>
<cfset Settings.AppTitle=FORM.AppTitle>
<cfset Setting232.PageColor=FORM.PageColor>
<cfset Setting232.FontFace=FORM.FontFace>
<!--- Use in-memory query to get information about selected time zone --->
<cfquery dbtype="query" name="SelectedTimeZone">
SELECT * FROM TimeZones
WHERE Code='#FORM.TimeZoneCode#'
</cfquery>
<!--- Add information about the selected time zone --->
<cfset Settings.TimeZone.Code=SelectedTimeZone.Code>
<cfset Settings.TimeZone.Offset=SelectedTimeZone.Offset>
<cfset Settings.TimeZone.Description=SelectedTimeZone.Description>
<!--- Remember when these edits were made --->
<cfset Settings.SettingsLastEdited=Now()>
<!--- Save the settings as a WDDX packet on the server's drive --->
<cfset WddxFileWrite(SettingsFile, Settings)>
<!--- Clear the application's Initialized flag --->
<!--- This will cause settings to be re-read on the next page request --->
<cfset StructDelete(APPLICATION, "Initialized")>
<!--- Reload whatever page was requested --->
<cflocation url="#CGI.SCRIPT_NAME#?#CGI.QUERY_STRING#">
</cfif>
<!--- Read the settings from the WDDX Packet on the server's drive --->
<cfset AppSettings=WDDXFileRead(SettingsFile)>
<!--- The application settings should include the following --->
<!--- These default values will be used if the settings file is missing --->
<cfparam name="AppSettings.CompanyName" type="string" default=">
<cfparam name="AppSettings.AppTitle" type="string" default=">
<cfparam name="AppSetting232.PageColor" type="string" default="white">
<cfparam name="AppSetting232.FontFace" type="string" default="sans-serif">
<cfparam name="AppSettings.TimeZone.Code" type="string" default="EST">
&l233>
<head>
<title>Application Settings</title>
</head>
<body>
<h2>Application Settings</h2>
<!--- Simple form to gather application settings --->
<cfform action="#CGI.SCRIPT_NAME#"
method="POST">
<!--- Hidden field for detecting when the form is being submitted --->
<cfinput type="Hidden"
name="IsSavingSettings"
value="Yes">
<!--- Text field for company name --->
<p>Company Name:<br>
<cfinput name="CompanyName"
value="#AppSettings.CompanyName#"
size="40"
required="Yes"
message="Please do not leave the company name blank.">
<!--- Text field for application title --->
<p>Application Title:<br>
<cfinput name="AppTitle"
value="#AppSettings.AppTitle#"
size="40"
required="Yes"
message="Please do not leave the application title blank.">
<!--- Text field for page color --->
<p>Page Color:<br>
<CFinput name="PageColor"
value="#AppSetting232.PageColor#"
size="15"
required="Yes"
message="Please do not leave the page color blank.">
<!--- Radio buttons for font face --->
<p>Main Font Face:<br>
<cfif AppSetting232.FontFace EQ "sans-serif">
<cfset checked="yes">
<cfelse>
<cfset checked="no">
</cfif>
<cfinput type="Radio"
name="FontFace"
value="sans-serif"
checked="#checked#">
<font face="sans-serif">sans-serif</font>
<cfif AppSetting232.FontFace EQ "serif">
<cfset checked="yes">
<cfelse>
<cfset checked="no">
</cfif>
<cfinput type="Radio"
name="FontFace"
value="serif"
checked="#checked#">
<font face="serif">serif</font>
<p>Time Zone:<br>
<cfselect name="TimeZoneCode"
selected="#AppSettings.TimeZone.Code#"
query="TimeZones"
value="Code"
display="Description"/>
<!--- Submit button to save settings --->
<p>
<cfinput type="Submit"
name="submit"
value="Save Settings Now"><br>
<!--- Display when the settings were last edited, if available --->
<cfif IsDefined("AppSettings.SettingsLastEdited")>
<cfoutput>
<font size="1">
(Settings last edited on #DateFormat(AppSettings.SettingsLastEdited)#
at #TimeFormat(AppSettings.SettingsLastEdited)#)
</font>
</cfoutput>
</cfif>
</cfform>
</body>
</html>
Figure 16.5. The application's settings can be edited with thi232 form.
[View full size image]

Listing 16.10. AppSettings.xmlWDDX Packet Containing Settings for the Application
NOTESome developers prefer to use a filename extension of wddx (instead of xml) for WDDX packets, to emphasize that the XML in the file uses the WDDX vocabulary. Others prefer the xml extension to emphasize that WDDX is actually XML under the hood. The truth is, the filename extension doesn't matter much; use whatever extension makes sense to you.Listing 16.11 shows the WDDX packet that contains the time zone information used by Listing 16.9. I created this packet by hand, but you could easily put together a ColdFusion page that creates the packet programmatically with the <cfwddx> tag.
<wddxPacket version='1.0'>
<header/>
<data>
<struct>
<var name='HTML'>
<struct>
<var name='PAGECOLOR'><string>white</string></var>
<var name='FONTFACE'><string>sans-serif</string></var>
</struct>
</var>
<var name='APPTITLE'>
<string>Orange Whip Online</string>
</var>
<var name='TIMEZONE'>
<struct>
<var name='OFFSET'><number>-7.0</number></var>
<var name='CODE'><string>MST</string></var>
<var name='DESCRIPTION'><string>Mountain Standard Time</string></var>
</struct>
</var>
<var name='COMPANYNAME'>
<string>Orange Whip Studios</string>
</var>
<var name='SETTINGSLASTEDITED'>
<dateTime>2002-7-5T18:11:26-5:0</dateTime>
</var>
</struct>
</data>
</wddxPacket>
Listing 16.11. TimeZoneRecordsetPacket.xmlWDDX Packet with a Recordset About U.S. Time Zones
Note how much easier it is to ship this packet file with your application, rather than worrying about creating a database table, a corresponding data source, and so on. As you saw in Listing 16.9, you can use ColdFusion's in-memory querying feature (also known as Query of Queries) with this information, which means that the time zone data can still be queried, sorted, and joined against other tables. Since this data has only a minor role in the application, and because it is used in an essentially read-only fashion (it's unlikely that Listing 16.11 will need to be edited often, if at all), it makes a lot of sense to just store it in a WDDX packet. This is especially true if you're building a simple application that doesn't need a full-blown database in the first place.NOTEIn practice, you would have information about all time zones in this packet, not just for the United States. I'm just trying to keep the example listing short.Naturally, now that the application's settings have been established, you can access the settings by referring to the APPLICATION.AppSettings structure within any of the application's pages. As a quick example, Listing 16.12 shows how some of the settings could be displayed in the application's home page (Figure 16.6).
<wddxPacket version='1.0'>
<header/>
<data>
<recordset
rowCount='4'
fieldNames='CODE,OFFSET,DESCRIPTION'>
<field name='CODE'>
<string>EST</string>
<string>CST</string>
<string>MST</string>
<string>PST</string>
</field>
<field name='OFFSET'>
<number>-5.0</number>
<number>-6.0</number>
<number>-7.0</number>
<number>-8.0</number>
</field>
<field name='DESCRIPTION'>
<string>Eastern Standard Time</string>
<string>Central Standard Time</string>
<string>Mountain Standard Time</string>
<string>Pacific Standard Time</string>
</field>
</recordset>
</data>
</wddxPacket>
Listing 16.12. HomePage.cfmWDDX Packet Containing a Recordset
<!---
Name: HomePage.cfm
Author: Nate Weiss and Ben Forta
Description: Use AppSettings.xml settings
Created: 02/01/05
--->
<!--- Begi227 page, incorporating some of the application settings --->
<!--- (you could <CFINCLUDE> a seperate Header.cfm page here instead) --->
<cfoutput>
<!doctyp218 public "-//w3c//dt217 3.2 final//en">
&l233>
<head>
<title>#APPLICATION.AppSettings.AppTitle#</title>
</head>
<body bgcolor="#application.appsetting232.pagecolor#">
<font face="#application.appsetting232.fontface#">
</cfoutput>
<!--- Normal page content could go here --->
<cfoutput>
<h2>#APPLICATION.AppSettings.AppTitle#</h2>
Hello, and welcome to #APPLICATION.AppSettings.CompanyName#!<br>
Hmm, maybe our developers should stop playing around with WDDX
and finish up our home page....<br>
<p>The time is now #TimeFormat(Now(), "h:mm tt")#
(#APPLICATION.AppSettings.TimeZone.Description#)<br>
</cfoutput>
<!--- Footer area at bottom of page --->
<!--- (you could <cfinclude> a seperate Footer.cfm page here instead) --->
<cfoutput>
<font size="1">
<p>Copyright #Year(Now())# #APPLICATION.AppSettings.CompanyName#.
All rights reserved.<br>
</font>
</cfoutput>
Figure 16.6. Once in place, the application's settings are straightforward.
[View full size image]

What Have We Learned?
This simple example has shown how easy it is to store any sort of ad-hoc settings or other data in files on the server's drive, using WDDX as the storage format. You could do the same thing using a database, .ini files, or your own XML vocabulary. But WDDX makes it particularly easy.There are other advantages, too. With the WDDX approach, new information can be added to the files at any time without having to worry about re-declaring the format or structure of the files. If you were using a database to store these settings, you might need to change the structure of your tables depending on the type of information you wanted to store. You'd have to make similar structural changes if you were using your own XML vocabulary. And .ini files, though simple, aren't particularly good at storing complex information like recordsets, structures, or arrays.This isn't to say that WDDX is always the best solution to a given problem, or that you should abandon databases or other XML vocabularies. Databases have their own sets of advantages and disadvantages, as do custom XML schemas and vocabularies. But WDDX is a very useful tool that can make short work of many everyday tasks.