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.

The next sections discuss storing WDDX packets in text files, client variables, and string columns in database tables. These are just examples; you can apply the basic idea in other ways as well. Anytime you want to store any kind of information in a place that normally can store only text, consider using WDDX to get the data into a simple text format. It's fast, simple, proven, and lightweight. Best of all, it's supported not only by ColdFusion but by ASP, .NET, Java, PHP, Perl, and all the other environments listed in the earlier section "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.

Table 16.4 shows the functions provided by this simple library (you'll see the code to create the functions in a moment).

Table 16.4. Functions in the WDDXFunctions.cfm UDF Library

FUNCTION

DESCRIPTION

WDDXFileWrite(file, value)

Stores the value as a WDDX packet at the location on the server's drive indicated by file. The value can be any structure, recordset, or other serializable value.

WDDXFileRead(file)

Reads the WDDX packet at the location on the server's drive indicated by file, deserializes the packet, and returns the deserialized data.

WDDXHttpGet(url)

Similar to WDDXFileRead(), except that the WDDX packet is read from another Web server using HTTP.

WDDXClientWrite(name, value)

Like WDDXFileWrite(), except that the WDDX packet is stored as a client variable with the name specified by the name argument.

WDDXClientRead(name)

Like WDDXFileRead(), except that the WDDX packet is read from the CLIENT scope. If the variable does not exist or does not contain a valid packet, the function returns an empty string.


Listing 16.7 shows the code used to create the functions listed in Table 16.4. As you can see, the code in each of the individual <cffunction> blocks is fairly simple, and very similar to the code used earlier in 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.

Listing 16.8 is a simple Application.cfm file, which you can modify to suit your needs. The idea is to check and see if the application is being accessed for the first time (that is, since the ColdFusion server has been restarted). If so, the application's settings are read in from a file called AppSettings.xml and stored as an application variable called APPLICATION.AppSettings. After that's done, any of the information in the packet can be referred to as APPLICATION.AppSettings.AppTitle, APPLICATION .AppSettings.HTML.PageColor, and so on.

Listing 16.8. Application.cfmReading Application Settings from a WDDX Packet on Disk
 <!--- 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> 

First, the WDDX function library from Listing 16.7 is included using the <cfinclude> tag. Then a simple <cfif> test is used to check to see if the application's settings have already been read from the server's drive. If they have not, a variable called SettingsFile is created that holds the location of the AppSettings.xml file (the GetdirectoryFromPath() and GetCurrentTemplatePath() functions are used to indicate that the file should be stored in the same folder as the Application.cfm file itself).

Next, the WddxFileRead() function from Listing 16.7 is used to read the WDDX packet stored in the AppSettings.xml file, deserialize it, and save the data from the packet in the APPLICATION.AppSettings variable. Finally, the APPLICATION.Initialized variable is set to true to indicate that the application has been initialized. This step will cause the entire <cfif> block in this listing to be skipped for all subsequent page executions (until the server is restarted), which means that this code adds virtually no overhead to the application as a whole.

If, for some reason, there is a problem reading and deserializing the application settings (perhaps the AppSettings.xml file does not exist, or someone has edited it in such a way that it is no longer valid), the <cfcatch> block will execute. The <cfcatch> block includes a file called AppSettingsForm.cfm, which displays a form for creating the application's settings, and then halts further execution. In other words, if the settings file is missing or invalid, the application will force the user to provide new application settings before any pages can be accessed.

NOTE

You could ship or deploy your ColdFusion application with the AppSettings.xml file deliberately missing. The first time the application is used, it will demand that the first user (presumably the person installing or deploying the application) provide the correct settings.


Listing 16.9 shows the code to create the HTML form for editing the application's settings (see Figure 16.5).

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 Settings.HTML.PageColor=FORM.PageColor>   <cfset Settings.HTML.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="AppSettings.HTML.PageColor" type="string" default="white"> <cfparam name="AppSettings.HTML.FontFace" type="string" default="sans-serif"> <cfparam name="AppSettings.TimeZone.Code" type="string" default="EST"> <html> <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="#AppSettings.HTML.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 AppSettings.HTML.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 AppSettings.HTML.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 this HTML form.


First, the WDDXFunction.cfm library is included, and the location of the AppSettings.xml file is determined (similar to Listing 16.8). Next, information about time zones is read in from a different WDDX packet and stored in the TimeZoneRecordsetPacket.xml file (skip ahead to Listing 16.11 if you want to have a look at this file). The packet contains a recordset, which means that after this line executes, the TimeZones variable can be used like the results of a <CFQUERY> tag.

The bulk of the work is done in the large <cfif> block that follows, which executes when the user submits the form (refer to Figure 16.5). Inside the <cfif>, a new structure called Settings is created and filled with the information being submitted by the user, by referring to various FORM variables.

Note that three pieces of information are stored with respect to the chosen time zone (a three-letter code, the numeric offset from Greenwich Mean Time, and a description). Only the code for the time zone is submitted by the form, so an in-memory query is used to get the corresponding offset and description for the selected time zone. It's pretty neat that the TimeZones recordset can be queried directly like this, even though the recordset came from a WDDX packet on the server's drive rather than from a database.

Once the Settings structure has been filled with the appropriate information, the WddxFileWrite() function from Listing 16.7 is used to save the structure to disk as a WDDX packet in the AppSettings .xml file. Then the StructKeyDelete() function is used to remove the Initialized flag (if it exists) from the APPLICATION scope. This will cause the application to no longer consider itself initialized, which in turn means that the settings will be read in afresh from disk the next time one of the application's pages is visited.

Listing 16.10 shows the AppSettings.xml file created when the form shown earlier in Figure 16.5 is submitted. Again, I have added some indention and whitespace to make the packet easier for us humans to read, but this doesn't affect the validity of the packet.

Listing 16.10. AppSettings.xmlWDDX Packet Containing Settings for the Application
 <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> 

NOTE

Some 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.

Listing 16.11. TimeZoneRecordsetPacket.xmlWDDX Packet with a Recordset About U.S. Time Zones
 <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>  

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.

NOTE

In 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).

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 ---> <!--- Begin HTML page, incorporating some of the application settings ---> <!--- (you could <CFINCLUDE> a seperate Header.cfm page here instead) ---> <cfoutput>   <!doctype html public "-//w3c//dtd html 3.2 final//en">   <html>   <head>   <title>#APPLICATION.AppSettings.AppTitle#</title>   </head>   <body bgcolor="#application.appsettings.html.pagecolor#">   <font face="#application.appsettings.html.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.


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.



Advanced Macromedia ColdFusion MX 7 Application Development
Advanced Macromedia ColdFusion MX 7 Application Development
ISBN: 0321292693
EAN: 2147483647
Year: 2006
Pages: 240
Authors: Ben Forta, et al

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net