Working with WDDX Packets in JavaScript


In Chapter 16, you learned about serializing and deserializing WDDX packets with the <cfwddx> tag (and with other tools like Java and Active Server Pages). This chapter has also discussed <cfwddx>, but only within the context of generating JavaScript code. What about serializing and deserializing WDDX packets within JavaScript?

As you might expect, support is provided for working with WDDX packets in both directions (serializing into new packets, and deserializing from existing packets). This section will explain how.

Serializing Packets with WddxSerializer

Earlier in this chapter, you learned about the wddx.js file and the WddxRecordset object type defined therein. That same file also defines a WddxSerializer object, which, lets you serialize JavaScript values and variables into WDDX packets. Conceptually, its purpose is to provide the JavaScript equivalent of <cfwddx>'s CFML2WDDX action.

To use the serializer, you follow the following basic steps:

1.

Create the value or variable that you want to serialize.

2.

Create a new instance of the WddxSerializer object.

3.

Call the new instance's serialize() method to serialize your value. The method returns the corresponding WDDX packet.

Table 17.14 shows the methods supported by the WddxSerializer object. In most situations, the only one you need to use is serialize().

Table 17.14. JavaScript WddxSerializer Methods

METHOD

DESCRIPTION

.serialize(value)

Serializes the value and returns the resulting WDDX packet (as a string). The value can be just about any JavaScript value, including dates, strings, numbers, Object instances, custom objects, arrays, and WddxRecordset objects.

Custom serialization methods

For advanced use only. WddxSerializer also supports serializeVariable(), serializeValue(), and .write() methods, which you can use to create custom objects that know how to serialize themselves in some special way. For details, consult the WDDX SDK.


To create an instance of the serializer object, use code like the following:

 var mySer = new WddxSerializer(); 

To serialize a value, just call serialize() like so, assuming that myVar is the value that you want to serialize:

 var wddxPacket = mySer.serialize(myVar); 

Building a Simple Recordset-Editing Interface

Take a look at Listing 17.7. It creates a web Web page with a simple form on it . On the left, a list of all current films are is displayed in a multiline <select> list. When the user selects a film in the list, that film's title, budget, one-liner, and summary are displayed in the editable fields to the right (Figure 17.5). The user can edit the title, budget, or other information, then can press Keep These Edits to store the edits in the browser's copy of the recordset. The Commit Changes to Server button serializes the entire recordset and posts it to the server for processing.

Figure 17.5. Users can scroll through current films and make updates as needed.


NOTE

If the selected film has an image, it will be displayed as well, though this version of the browser provides no method of for uploading a new image (though it would be easy enough to do with <cffile action="Upload">).


Listing 17.7. JSFilmBrowser.cfmSerializing a Recordset Object after It has Been Edited
 <!--- Name:        JSFilmBrowser.cfm Author:      Nate Weiss and Ben Forta Description: Allows the user to edit the              records in a WddxRecordset.              The edited records can be              posted to the server as a WDDX packet. Created:     02/01/05 ---> <!--- Get data about films from database ---> <cfquery name="FilmsQuery"          datasource="ows">   SELECT FilmID, MovieTitle, AmountBudgeted,          PitchText, Summary, ImageName   FROM Films   ORDER BY MovieTitle </cfquery> <!--- Workaround for bug in <cfwddx action="CFML2JS"> for NULL values ---> <!--- (see note in text) ---> <cfloop query="FilmsQuery">   <cfif FilmsQuery.ImageName EQ "">     <cfset FilmsQuery.ImageName="">   </cfif> </cfloop> <html> <head> <title>Film Browser</title> <!--- Include WddxRecordset and WddxSerializer support ---> <script type="text/javascript"         src="/books/2/449/1/html/2/wddx.js"         language="JavaScript"></script> <!--- Custom functions for this page ---> <script type="text/javascript"         language="JavaScript">    <!--- Convert query to JavaScript object named "rsFilms" --->    <cfwddx      action="CFML2JS"      input="#FilmsQuery#"      toplevelvariable="rsFilms">   // Add a column called "wasedited" to the recordset   // A "Yes" in this column means the row was "touched"   rsFilms.addColumn("wasedited");   ////////////////////////////////////////////////////   // This function fills the select list with films   function InitControls() {     with (document.DataForm) {       // Clear any current optionS from the select       FilmID.options.length=0;       // For each film record...       for (var row=0; row < rsFilms.getRowCount(); row++) {         // Create a new option object         var NewOpt=new Option;         NewOpt.value=rsFilms.getField(row, "FilmID");         NewOpt.text=rsFilms.getField(row, "MovieTitle");         // Add the new object to the select list         FilmID.options[FilmID.options.length]=NewOpt;       }     }   }   ////////////////////////////////////////////////   // This function populates other input elements   // when an option in the select box is clicked   function FillControls() {     with (document.DataForm) {       // Get the data row number       var row=FilmID.selectedIndex;       // Populate textboxes with data in that row       AmountBudgeted.value=rsFilms.getField(row, "AmountBudgeted");       MovieTitle.value=rsFilms.getField(row, "MovieTitle");       PitchText.value=rsFilms.getField(row, "PitchText");       Summary.value=rsFilms.getField(row, "Summary");       // Get the name of the image file for this film, if any       var imageName=rsFilms.getField(row, "ImageName");       // Get a reference to the <img> tag on the page       var objImage=document.images["filmImage"];       // If there is no image for this film, make the <img> be invisible       if (imageName == "") {         objImage.style.visibility="hidden";       // If there is an image, show that image in the <img> object,       // and make sure the object is visible       } else {         objImage.src="/books/2/449/1/html/2/images/" + imageName;         objImage.style.visibility="visible";       };     }   }   ////////////////////////////////////////////////   // This function "saves" data from the various   // text boxes into the wddxRecordset object   function KeepChanges() {     with (document.DataForm) {       // Get the data row number       var SelectedFilm=FilmID.selectedIndex;       var row=SelectedFilm;       // Populate JavaScript recordset with data from form fields       rsFilms.setField(row, "MovieTitle", MovieTitle.value);       rsFilms.setField(row, "AmountBudgeted",                        parseInt(AmountBudgeted.value));       rsFilms.setField(row, "PitchText", PitchText.value);       rsFilms.setField(row, "Summary", Summary.value);       rsFilms.setField(row, "wasedited", "Yes");       // Re-initialize the select list       InitControls();       // Re-select the film that was selected before       FilmID.selectedIndex=SelectedFilm;     }   }   ////////////////////////////////////////////////   // This function inserts a new row in the   // wddxRecordset object, ready for editing   function NewRecord() {     with (document.DataForm) {       // Add a new row to the recordset       rsFilms.addRows(1);       var NewRow=rsFilms.getRowCount()-1;       rsFilms.setField(NewRow, "FilmID", "new");       rsFilms.setField(NewRow, "MovieTitle", "(new)");       rsFilms.setField(NewRow, "AmountBudgeted", "");       rsFilms.setField(NewRow, "PitchText", "");       rsFilms.setField(NewRow, "Summary", "");       // Re-initialize the select list       InitControls();       // Re-select the film that was selected before       FilmID.selectedIndex=NewRow;       FillControls();     }   }   ////////////////////////////////////////////////   // This function inserts a new row in the   // wddxRecordset object, ready for editing   function CommitToServer() {     with (document.DataForm) {       // Create new WDDX Serializer object (defined in wddx.js)       var mySer=new WddxSerializer();       // Serialize the "rsFilms" recordset into a WDDX packet       var FilmsAsWDDX=mySer.serialize(rsFilms);       // Place the packet into the "WddxContent" hidden field       WddxContent.value=FilmsAsWDDX;       // Submit the form       submit();     }   } </script> </head> <!--- Run InitControls() function when page first appears ---> <body onload="InitControls();"> <h2>Film Browser</h2> <!--- Ordinary html form for editing the recordset ---> <cfform action="JSFilmBrowserCommit.cfm"         method="Post"         name="DataForm">   <!--- CommitToServer() function gives this a value --->   <cfinput type="Hidden"            name="WddxContent">   <table border cellpadding="10">    <tr valign="TOP">     <td>       <!--- select populated by InitControls() function --->       <!--- When clicked, calls FillControls() function --->       <cfselect name="FilmID"                size="16"                onchange="FillControls()">         <option>============= (loading) =================       </cfselect>     </td>     <td>       <!--- Image placeholder to display film image (when available) --->       <!--- When the page first loads, this image will be hidden --->       <img src="/books/2/449/1/html/2/"            name="filmImage"            border="0"            align="right"            style="visibility:hidden">       <!--- These controls get populated by FillControls() --->       Film Title:<br>       <cfinput name="MovieTitle"                size="40"                maxlength="50"><br>       Amount Budgeted:<br>       <cfinput name="AmountBudgeted"                size="15"                maxlength="50"><br>       One-Liner:<br>       <cfinput name="PitchText"                size="40"                maxlength="50"><br>       Summary:<br>       <cftextarea name="Summary"                   rows="4"                   cols="50" /><br>       <p>       <!--- Button to "keep" edits with KeepChanges() function --->       <cfinput type="button"                name="btn1"                value="Keep These Edits"                onclick="KeepChanges()">       <!--- Button to cancel edits with FillControls() function --->       <cfinput type="button"                name="btn2"                value="Cancel"                onclick="FillControls()">       <!--- Button to insert new film with NewRecord() function --->       <cfinput type="button"                name="btn3"                value="New Record"                onclick="NewRecord()"><br>     </td>    </tr>   </table>   <!--- Button to save to server w/ CommitChanges() function --->   <p align="center">    <cfinput type="button"             name="btn4"             value="Commit Changes To Server"             onclick="CommitToServer()"><br>   </p> </cfform> </body> </html> 

The first few lines are familiar. First, the wddx.js file is included with the src attribute of a <script> tag, so that the page's JavaScript code can refer to WddxRecordset and WddxSerializer objects. For details about this line, refer to the "Including the wddx.js JavaScript File" section, earlier in this chapter.

NOTE

The strange-looking <cfloop> at the top of this listing is a workaround for a small bug in action="CFML2JS" in the version of ColdFusion that I was using when writing this chapter. The effect of the bug is that NULL values returned by database queries may not be converted into JavaScript correctly. This loop was the easiest way to fix the problem in a database-independent way. It is hoped that this bug will have been fixed in an update of some kind by the time you read this book, in which case the <cfloop> can be removed.


The scripting part of the template then goes on to create a user-defined function called InitControls(), which is responsible for populating the <select> box in the simple form at the bottom of the template (shown along the left side of Figure 17.5). First, FilmID.options.length is set to zero to clear any options that may currently be sitting in the <select> box. Then a for loop is used to fill the <select> with options for each film. This code is quite similar to the fillFilms() function in the variations of the cascading select list examples you've seen in this chapter.

The FillControls() function fills the four text boxes with the Title, Budget, and so on for the currently-selected film in the select list. Since the FillControls() function is referred to in the select list's onchange handler, the function will execute whenever the user chooses a different film from the list. The function itself is extremely simpleit just sets a variable called row that represents the currently-selected film. Then the function uses the WddxRecordset getField() method to retrieve each value from the rsFilms recordset, storing it in the value property of the corresponding text box.

NOTE

The FillControls() function also changes the image displayed in the <img> tag named filmImage. If there is an image for the current film, the image object's src attribute is set to display that film's image. This portion of the code also sets the object's style.visibility property to hidden or visible so that the image object is invisible for films that do not have an image. With modern browsers, the style.visibility property can be used in this way to control the visibility of nearly all elements of a page (<div> blocks and so on), not just images. Consult a Dynamic HTML (DHTML) reference for details.


The KeepChanges() function does the opposite of FillControls(). It reads the values from the four text boxes and uses getField() to place their values into the appropriate spots in the rsFilms recordset object. Next, it executes the InitControls() function to "re-draw" the items in the select list; if the movie's title was edited, the new title will now appear in the list. Lastly, it sets the list's selectedIndex back to the choice that was selected before the function was called. The function is assigned to the "Keep These Edits" button by referring to the function's name in the button's onclick handler.

The NewRecord() is responsible for adding a new row to the recordset when the user clicks the New Record button. All it needs to do is to call the addRows() function (see Table 17.13); the new row is added to the bottom of the recordset. Next, it uses the getrowCount() function to set a variable named row, which will hold the row number of the just-added row. Then it uses the setField() function to set each column of the new row to some initial values. Note that the FilmID column is set to the string "new". This will indicate to the server that the record is a new record and thus should be inserted (rather than updated) to the database. Finally, the function redraws the select list with the InitControls() function, sets its selectedIndex so that the new record appears "selected" in the form, and calls the FillControls() function so that the data-entry inputs get filled with the new (mostly blank) values.

The CommitToServer() function is in charge of serializing the recordset into a new WDDX packet, then placing the packet in a hidden field and submitting the form. The serializing part requires only two lines of JavaScript code. First, a new WddxSerializer object called mySer is created, with the help of JavaScript's new keyword. This step is necessary whenever you want to serialize a value from JavaScript. Next, the serialize() method of the mySer object is used to serialize the rsFilms recordset into a WDDX packet, placing the packet into a JavaScript variable called FilmssAsWDDX. The packet (which is a string at this point, in the form of XML), is then placed in the hidden form field called WddxContent. Finally, the function submits the form.

The end result is that the ColdFusion template that this form submits to (JSFilmBrowserCommit.cfm) will be able to refer to a variable called #Form.WddxContent#. The variable will hold the WDDX packet that contains the edited version of the recordset.

Processing the Posted Packet on the Server

The JSBrowserCommit.cfm template that receives the WDDX packet from the Film Browser example (Listing 17.7) is actually quite simple. Since the packet's contents were stored in the hidden field named WddxContent just before the form was submitted, the packet will be available to this template in the #Form.WddxContent# variable. All the template needs to do is use <cfwddx> to deserialize the packet into a query recordset named EditedRecordset. Then it can use a <cfloop> over the query to quickly examine each data row to see if it is a new or changed record.

Listing 17.8 shows the code for the JSFilmBrowserCommit.cfm template.

Listing 17.8. JSFilmBrowserCommit.cfmReceiving and Deserializing a Packet Created by JavaScript
 <!--- Name:        JSFilmBrowserCommit.cfm Author:      Nate Weiss and Ben Forta Description: Receives an edited recordset in              the form of a WDDX packet and              makes changes to the corresponding              database table accordingly. Created:     02/01/05 ---> <!--- We are expecting to receive a form field named WDDXContent ---> <cfparam name="form.WddxContent"          type="string"> <!--- Deserialize the WDDX packet into a native ColdFusion recordset ---> <cfwddx action="WDDX2CFML"         input="#form.WddxContent#"         output="EditedRecordset"> <!--- We'll increment these counters in the loop ---> <cfset InsertCount=0> <cfset UpdateCount=0> <!--- Loop over each of the records in the query ---> <cfloop query="EditedRecordset">   <!--- If it's a new record (the user inserted it) --->   <cfif EditedRecordset.FilmID EQ "new">     <!--- Insert a new record into the database --->     <cfquery datasource="ows">       INSERT INTO Films (MovieTitle, AmountBudgeted, PitchText, Summary)       valueS ('#MovieTitle#', #AmountBudgeted#, '#PitchText#', '#Summary#')     </cfquery>     <!--- Increment the insert counter --->     <cfset InsertCount=InsertCount + 1>   <!--- It's an existing record (user may have edited) --->   <cfelseif EditedRecordset.WasEdited EQ "Yes">     <!--- Updating the existing record --->     <cfquery datasource="ows">       UPDATE Films SET         MovieTitle='#MovieTitle#',         AmountBudgeted=#AmountBudgeted#,         PitchText='#PitchText#',         Summary='#Summary#'       WHERE Filmid=#FilmID#     </cfquery>     <!--- Increment the update counter --->     <cfset UpdateCount=UpdateCount + 1>   </cfif> </cfloop> <html> <head> <title>Committing Changes</title> </head> <body> <h2>Committing Changes</h2> <!--- Display message about what exactly happened ---> <cfoutput>   <p><strong>Changes Committed!</strong>   <ul>     <li>Records Updated: #UpdateCount#     <li>Records Inserted: #InsertCount#   </ul> </cfoutput> </body> </html> 

If the FilmID column of the current record is set to the string "new", then the template knows that the record was inserted by the Film Browser's NewRecord() function. Therefore, it runs a simple INSERT query to insert the new row into the Inventory table.

If the FilmID column of the current record is not set to "new", the template checks to see if the WasEdited column has been set to "Yes". If it has, then the template knows that the record was edited by the Film Browser's KeepChanges() function. Therefore, it runs a simple UPDATE query to update the corresponding row in the Inventory table, using the FilmID column as the primary key.

Finally, the template displays a simple message to let the user know that the records were inserted or updated successfully. A summary is provided that shows the number of inserted records and the number of updated records (see Figure 17.6).

Figure 17.6. When the edited recordset is submitted to the server, the database is updated accordingly.


The WDDX SDK includes several similar examples, written in ColdFusion, ASP, and Perl. Some of the examples show how the recordset can be saved to the browser machine's hard drive using Microsoft's Scripting.FileSystemObject control (the code is IE-specific). This allows the user to save their work locally while they perform their data-entry tasks, possibly over the course of several hours or days, with or without an Internet connection.

Deserializing Packets with WddxDeserializer

As you have learned in this chapter, you can use <cfwddx> to send values to JavaScript in one step, without ever converting the values to XML packets. As I suggested earlier in the "Is This Serializing?" sidebar, you can think of the generated-JavaScript-code phase as the equivalent to the XML-packet-phase that you would normally expect to see in WDDX-powered applications. I know of no easier way to send complex, multifaceted data to JavaScript as a page loads.

That said, there may be times when you would like to deserialize packets within the context of a Web page, without refreshing the entire page. As a rule, the time to consider such a crazy thing is when you want the user to be able to retrieve or scroll through data in real time (like the Film Browser example you just saw), but where the amount of data or some other consideration makes it infeasible to send the entire set of data to the client at once.

Okay, see if you can guess the name of the object you use to deserialize WDDX packets in JavaScript. That's right, it's WddxDeserializer, and it provides a deserialize() method that basically does the inverse of what the WddxSerializer object's serialize() method does. Table 17.15 shows the methods supported by WddxDeserializer.

Table 17.15. JAVASCRIPT WddxDeserializer Methods

METHOD

DESCRIPTION

.deserialize(packet)

Deserializes the WDDX packet (supply the packet as a string). Returns the deserialized value, which could be a native JavaScript object, array, WddxRecordset, string, date, number, and so on.

.deserializeUrl(url)

Fetches and deserializes the WDDX packet at the given URL. You can pass parameters to the URL by adding name/value pairs to the deserializer's special urlData property. For details, consult the WDDX SDK.


In general, you just create a new deserializer object like this:

 var myDes = new WddxDeserializer(); 

Then, assuming you already have a WDDX packet (that is, an XML-formatted string) in a JavaScript variable called myPacket, you can deserialize it like so:

 var myObject = myDes.deserialize(myPacket); 

The JavaScript myObject variable would then contain whatever data was in the packet, so it might be an Array object, a WddxRecordset object, or whatever custom object is appropriate.

Sounds great, right? Sure it is, but there are a few catches:

  • Depending on the browsers you need to support, there isn't necessarily an easy way to fetch a WDDX packet from a Web server using JavaScript. You're fine if you need only support Internet Explorer 5 (or later) for Windows, or any other Mozilla-based browser (including Netscape 6 and Firefox). If you need to support other browsers, you may need to rely on a Java applet to fetch the text over the Internet for you. There are details about this in the WDDX SDK.

  • To keep wddx.js as small as possible, it does not include the WddxDeserializer object. Instead, it is implemented in a separate file called wddxDes.js, which must be included by any page that wants to use the deserializer. There is also a wddxDesIE.js file which is specially optimized for Internet Explorer. These files are not distributed with ColdFusionX. They are, however, freely available as a part of the WDDX SDK and are included in the code listings for this chapter. You'll see how to include the files in the next example listing.

Cascading Selects, Approach #5: Fetching Matching Films in Real Time

Let's take a look at a real-world example. Listing 17.9 creates another solution to the (now age-old) cascading select list problem. This one's pretty interesting. Instead of working with one large list of films that are is passed to the browser when the page first loads, this version contacts the ColdFusion server each time the user selects a different rating. That is, the options to show in the second select list are retrieved in "real time" from the server.

The <cfform> portion of this listing is the same as the previous versions of this example. Much of the script portions have changed.

Listing 17.9. RelatedSelectsVia WDDX.cfmFetching and Deserializing Packet
 <!--- Name:        RelatedSelectsViaWDDX.cfm Author:      Nate Weiss and Ben Forta Description: Demonstrates use of WDDX              deserialization within JavaScript. Created:     02/01/05 ---> <!--- URL that will return WDDX packet containing recordset of film data ---> <cfset FilmComponentURL="http://127.0.0.1:8500/ows_adv/17/FilmsRobot.cfm?"> <!--- Get rating data from database ---> <cfquery name="RatingsQuery"          datasource="ows">   select RatingID, Rating   FROM FilmsRatings   ORDER BY RatingID </cfquery> <html> <head> <title>Relating Select Boxes via WDDX</title> <!--- Pass variables to JavaScript ---> <cfoutput>   <script type="text/javascript"           language="JavaScript">     var FilmComponentURL="#JSStringFormat(FilmComponentURL)#"   </script> </cfoutput> <!--- Include WddxRecordset support ---> <script type="text/javascript"         language="JavaScript"         src="/books/2/449/1/html/2/wddx.js"></script> <!--- Include WddxDeserializer support ---> <!--- (use special file if browser is IE under Windows ---> <cfif (CGI.HTTP_USER_AGENT contains "MSIE")   AND (CGI.HTTP_USER_AGENT contains "Win")>   <script type="text/javascript"           language="JavaScript"           src="/books/2/449/1/html/2/wddxDesIE.js"></script> <cfelse>   <script type="text/javascript"           language="JavaScript"           src="/books/2/449/1/html/2/wddxDes.js"></script> </cfif> <!--- Custom JavaScript functions for this page ---> <script type="text/javascript"         language="JavaScript">   // showFilms() function   // Relates two <select> boxes by fetching a WDDX recordset packet based on   // the first box; the second box is filled with the recordset contents   function fillFilms() {     // Object reference for first select box     var objSel=document.forms[0].RatingID;     // Assuming there is a selection in the first select box     if (objSel.selectedIndex >= 0) {       // Get the value of the current selection in the first select box       var ratingID=objSel[objSel.selectedIndex].value;       // Add the value to the URL       var packetURL=FilmComponentURL + "&Ratingfilmid");         var movieTitle=rsFilms.getField(i, "movietitle");         // Add an option to the second select box         objSel.options[objSel.options.length]=new Option(movieTitle, filmID);       };     }   };   // Utility function to fetch text from a URL   // A wrapper around the appropriate objects exposed by Netscape 6 or IE   function httpGetFromURL(strURL) {     var objHTTP, result;     // For Netscape 6+ browsers (or other browsers that support XMLHttpRequest)     if (window.XMLHttpRequest) {       objHTTP=new XMLHttpRequest();       objHTTP.open("GET", strURL, false);       objHTTP.send(null);       result=objHTTP.responseText;     // For IE browsers under Windows (version 5 and later)     } else if (window.ActiveXObject) {       objHTTP=new ActiveXObject("Microsoft.XMLHTTP");       objHTTP.open("GET", strURL, false);       objHTTP.send(null);       result=objHTTP.responseText;     } else {       alert("Sorry, your browser can't be used for this example.");     }     // Return result     return result;   } </script> </head> <body onload="fillFilms()"> <h2>Relating Select Boxes via WDDX</h2> <!--- Ordinary html form ---> <cfform action="ShowFilm.cfm"         name="FilmForm"         method="Post">   <!--- First select box (displays ratings) --->   <strong>Rating:</strong><br>   <cfselect name="RatingID"             query="RatingsQuery"             value="RatingID"             display="Rating"             onchange="fillFilms()"/><br>   <!--- Second select box (displays films) --->   <p><strong>Film:</strong><br>   <cfselect name="FilmID"             size="5"             style="width:300px"/><br>   <!--- Submit button --->   <cfinput type="Submit"            name="sbmt"> </cfform> </body> </html> 

At the top of this listing, a CFML variable called FilmComponentURL, which contains the URL for a ColdFusion page called FilmsRobot.cfm. This page is a slight variation on the FilmsRobot1.cfm page that was created in Chapter 16, "Using WDDX" (you'll see the code for this robot page in the next listing). The FilmComponentURL variable is then passed to JavaScript using a simple <script> block.

NOTE

You may need to adjust this URL depending on how you installed ColdFusion and the location of the listings for this chapter.


Next, the usual wddx.js file is included, which enables the use of WddxRecordset. Then wddxDes.js or wddxDesIE.js is included, depending on whether the user is using IE for Windows or not. This enables the use of WddxDeserializer.

Within the fillFilms() function, the ratingID variable holds the currently selected rating. Another variable called packetURL is then constructed by adding a URL parameter called RatingID to the FilmComponentURL. This URL that can be used to retrieve the appropriate WDDX packet from the server.

Next, a function called httpGetFromURL() is used to contact the robot page and retrieve the WDDX packet that it responds with. If you take a look at the body of the httpGetFromURL() function, you'll see that it executes slightly different code depending on whether the user is using IE or Netscape/ Mozilla. For now, just accept that this is one relatively straightforward way to retrieve text from an arbitrary URL via JavaScript. (There are other ways, too, such as with the load() method of the respective browser's XML DOM implementations.)

In any case, the XML text of the robot's WDDX packet should be in the myPacket variable, ready for deserialization. The next two lines create an instance of WddxDeserializer called wddxDes and use it to deserialize the packet, returning what is hopefully a WddxRecordset object named rsFilms. It's now a simple matter to iterate through the rows of the recordset, filling the second select list with options on the way.

The result is a page that behaves in the same way as the previous versions, as shown back in Figure 17.4. One of the advantages to this approach is that the browser machine never needs to have the entire list of films in its memory at the same time. Another advantage is that the browser machine gets an up-to-date list every time the user chooses a different rating. If your data changes very frequently, this may be a significant benefit.

Listing 17.10 shows the code for the FilmsRobot.cfm page. This is the robot page that supplies film data to the previous listing, based on the RatingID URL parameter. Please refer to Chapter 16 for a full discussion of this type of page.

Listing 17.10. FilmsRobot.cfmSupplying WDDX Packets to the JavaScript Page from Listing 17.9
 <!--- Name:        FilmsRobot.cfm Author:      Nate Weiss and Ben Forta Description: Creates a back-end web page              that supplies data about films Created:     02/01/05 ---> <!--- URL Parameters to control what film data the page responds with ---> <cfparam name="URL.FilmID"          type="numeric"          default="0"> <cfparam name="URL.RatingID"          type="numeric"          default="0"> <cfparam name="URL.Details"          type="boolean"          default="No"> <cfparam name="URL.Keywords"          type="string"          default=""> <!--- Execute a database query to select film information from database ---> <cfquery name="FilmsQuery"          datasource="ows">   SELECT     <!--- If all information about film(s) is desired --->     <cfif URL.Details>       *     <!--- Otherwise, return the film's ID and title --->     <cfelse>       FilmID, MovieTitle     </cfif>   FROM Films   <!--- If a specific film ID was specified --->   <cfif URL.FilmID GT 0>     WHERE Filmid=#URL.FilmID#   <cfelseif URL.RatingID GT 0>     WHERE Ratingid=#URL.RatingID#   <!--- If keywords were provided to search with --->   <cfelseif URL.Keywords NEQ "">     WHERE MovieTitle LIKE '%#URL.Keywords#%'        OR Summary LIKE '%#URL.Keywords#%'   </cfif>   ORDER BY MovieTitle </cfquery> <!--- Convert the query recordset to a WDDX packet ---> <cfwddx action="CFML2WDDX"         input="#FilmsQuery#"> 

Cascading Selects, Approach #6: Wrapping the WDDX Fetching in a Custom Tag

As an experiment, I created a custom tag version of the JavaScript code that powers the last version of the cascading select list example (Listing 17.9). The custom tag allows you to create any two <select> lists using normal HTML syntax. You then bind the two together using the <CF_Relate TwoSelectLists> custom tag. An example of using the tag is provided in the UseRelateTwoSelectLists .cfm file (included with this chapter's listings). Here's the key portion of that example:

 <!--- Relate the two select lists in real time, using WDDX ---> <CF_RelateTwoSelectLists  WddxRecordsetURL="#FilmComponentURL#"  SelectObject1="document.forms[0].RatingID"  SelectObject2="document.forms[0].FilmID"  ValueColumn="FilmID"  DisplayColumn="MovieTitle"> 

The custom tag itself is implemented in the RelateTwoSelectLists.cfm file (also included with this chapter's listings). You are invited to take a look at the listing to get yourself thinking about ways in which JavaScript functionality can be wrapped up in CFML custom tags for easy reuse.



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