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().
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 serialize a value, just call serialize() like so, assuming that myVar is the value that you want to serialize:
var mySer = new WddxSerializer();
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.
[View full size image]

Listing 17.7. JSFilmBrowser.cfmSerializing a Recordset Object after It has Been Edited
Including the wddx.js JavaScript File" section, earlier in this chapter.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.
<!---
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>
&l233>
<head>
<title>Film Browser</title>
<!--- Include WddxRecordset and WddxSerializer support --->
<script type="text/javascript"
src="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="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>
<!--- Ordinar238 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="
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>
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
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).
<!---
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>
&l233>
<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>
Figure 17.6. When the edited recordset is submitted to the server, the database is updated accordingly.
[View full size image]

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.
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. |
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 myDes = new WddxDeserializer();
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:
var myObject = myDes.deserialize(myPacket);
- 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
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.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.Chapter 16 for a full discussion of this type of page.
<!---
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>
&l233>
<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="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="wddxDesIE.js"></script>
<cfelse>
<script type="text/javascript"
language="JavaScript"
src="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 + "&Ratingid=" + ratingID;
// Fetch the WDDX packet from the URL
var packet=httpGetFromURL(packetURL);
// Deserialize the packet
// The result is a WddxRecordset object called rsFilms
var wddxDes=new WddxDeserializer;
var rsFilms=wddxDes.deserialize(packet);
// Object reference for the second select box
objSel=document.forms[0].FilmID;
// Remove all items from the second select box
objSel.length=0;
// For each row in the recordset...
for (var i=0; i < rsFilms.getRowCount(); i++) {
// Grab the ID and title for the current row of the recordset
var filmID=rsFilms.getField(i, "filmid");
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>
<!--- Ordinar238 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>
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 (<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:
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.
<!--- 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">