Passing Data to JavaScript Using cfwddx


Passing Data to JavaScript Using <cfwddx>

So far in this chapter, you have seen how to pass variables from ColdFusion to JavaScript by generating the appropriate script code on the fly, using <cfoutput> and other basic CFML tags. The listings have worked out just fine; there is nothing wrong with this approach. For lack of a better term, I'm going to call this the roll-your-own approach to passing variables to JavaScript.

ColdFusion provides a higher-level tool for passing variables to JavaScript: the CFML2JS action of the <cfwddx> tag. You were introduced to <cfwddx> in Chapter 16, "Using WDDX." That chapter explained how to use the tag to serialize and deserialize data in WDDX packets (a form of XML).

This section will focus on a completely different use for <cfwddx>, which is passing variables to JavaScript. This use of <cfwddx> has nothing directly to do with WDDX packets or XML. While it is consistent with WDDX's overall mission (to make it really easy to transfer data from place to place), it is not really about the WDDX format per se.

Instead of producing an XML version of a ColdFusion variable or value, this form of <cfwddx> produces a chunk of JavaScript code that, when executed by the browser's interpreter, re-creates the variable or value in the browser's memory. In other words, instead of converting your data into a WDDX packet, this method converts your data to a bunch of JavaScript code.

Using this Form of <cfwddx>

Table 17.12 shows the <cfwddx> syntax for sending data to JavaScript. As you can see, most of the attributes are common to the way the tag is used to produce WDDX packets (see Chapter 16).

Table 17.12. <cfwddx> Syntax for Passing Values to JavaScript

ATTRIBUTE

DESCRIPTION

action

Required. Set this attribute to CFML2JS to pass CFML variables to JavaScript. The other actions are for working with WDDX (XML) packets, which is a whole different topic (see Chapter 16, "Using WDDX", for details).

input

Required. The CFML value that you want to pass to JavaScript, surrounded by # signs. Can be just about any data variable, including arrays, recordsets, and structures.

toplevelvariable

Required. The name for the value after it is passed to JavaScript. You will use this name in your client-side script code to refer to the passed-in data. Remember that JavaScript is case-sensitive, so your script code needs to use the same exact name that you provide here.

output

Optional. If you provide this attribute, then a new CFML variable will be created which contains the generated JavaScript code (as a string). It is then your responsibility to output the value of this string within a <script> block so that it gets to the browser. If you omit this attribute, the generated JavaScript code is inserted into the current document, right where the <cfwddx> tag appears (it is assumed that the <cfwddx> tag is already positioned with a <script> block).


So, to transfer a ColdFusion variable called MyValue to JavaScript, you use code similar to the following:

 <script language="JavaScript">  <cfwddx action="CFML2JS"         input="#MyValue#"         toplevelvariable="myValueFromCF"> </script> 

If the value of MyValue is a string, the code received by the browser will be similar to the following:

 <script language="JavaScript">  myValueFromCF = "Hello, World!"; </script> 

If MyValue holds an array, the code received by the browser might be similar to this (depending on the actual data in the array, of course):

 <script language="JavaScript">  myValueFromCF = new Array();  myValueFromCF[0] = "Belinda";  myValueFromCF[1] = "Ben";  myValueFromCF[0] = "Nate"; </script> 

Most commonly, the <cfwddx> tag is placed between <script> tags (as shown in the first snippet). Sometimes you may prefer to store the JavaScript code in a string variable, outputting it within a <script> tag later in your ColdFusion page. Just use the output attribute to hold the generated code, like so:

 <!--- Convert MyValue to JavaScript code ---> <cfwddx action="CFML2JS"         input="#MyValue#"         output="GeneratedJS"         toplevelvariable="myValueFromCF"> <!--- Output generated JavaScript code ---> <script language="JavaScript">  <cfoutput>#GeneratedJS#</cfoutput> </script> 

NOTE

If you were to output the value of GeneratedJS without surrounding it with <script> tags, the browser would just display the JavaScript code as text, rather than parsing and understanding the code.


In any case, the value you supply to input can be arbitrarily complex; it could be a structure that contains numerous arrays, each of which holds an arbitrary number of smaller structures. In fact, you'll see that happen in the next section.

NOTE

If the value you supply to input contains a query recordset object (or objects), you must use an additional <script> tag to include the wddx.js file. The file teaches JavaScript how to deal with recordsets, something it doesn't understand out of the box. For details, please see the "Working with WddxRecordset Objects" section later in this chapter.


Is This Serialization?

I find it interesting to note that both types of output (the WDDX packet and the JavaScript code) are text-only representations of the original data. As such, both operations could be said to serialize the data, and the JavaScript interpreter on the browser machine could be said to deserialize the data.

Think about the steps in a "normal" WDDX scenario, as you learned in Chapter 16:

1.

Data is serialized into a WDDX packet, using <cfwddx action="CFML2WDDX">.

2.

The packet is passed to another application, environment, or process.

3.

The packet is deserialized by <cfwddx> or some other WDDX-aware application, effectively transferring the data to the new location.

Now think about the steps when using <cfwddx> in this new form:

1.

Data is converted into JavaScript code, using <cfwddx action="CFML2JS">.

2.

The JavaScript code is sent to the browser as part of a Web page.

3.

The code is executed by the browser's JavaScript interpreter, effectively transferring the data to the browser's memory.

The steps are pretty similar conceptually, apart form from the way the data looks while in its serialized state. The first scenario uses XML as the serialization format, and the second uses JavaScript code. Of course, the XML form has more uses, since it can be unpacked by any WDDX-aware application (or even just an XML-aware one).

Just something to think about!

Cascading Selects, Approach #3: Passing the Data Via <cfwddx>

Listing 17.5 demonstrates a third approach to the cascading select boxes problem. This approach assembles a CFML structure of ratings and their corresponding films (the structure is assembled on the server). Then the <cfwddx> tag is used to "pass" the structure to JavaScript in a single step. The JavaScript code uses this data to populate the second select list. The end-user experience is once again the same (as shown in Figure 17.4 above).

Listing 17.5. JSRelatedSelects3.cfmUsing <cfwddx> to Supply Data for the Second Select List
 <!--- Name:        JSRelatedSelects3.cfm Author:      Nate Weiss and Ben Forta Description: Demonstrates one approach to relating              select lists via JavaScript, using              film and rating data from the ows              example database. Created:     02/01/05 ---> <!--- Get film data from database ---> <cfquery name="FilmsQuery"          datasource="ows">   select FilmID, MovieTitle, RatingID   FROM Films   WHERE RatingID IS NOT NULL   ORDER BY RatingID, MovieTitle </cfquery> <!--- Get rating data from database ---> <cfquery name="RatingsQuery"          datasource="ows">   select RatingID, Rating   FROM FilmsRatings   ORDER BY RatingID </cfquery> <!--- Create a new structure to hold the ratings ---> <cfset RatingsStruct=StructNew()> <!--- For each rating returned by the database... ---> <cfloop query="RatingsQuery">   <!--- We are currently working with this film rating --->   <cfset ThisRatingid=RatingsQuery.RatingID>   <!--- Create a new value in the structure, which is an array of films --->   <cfset RatingsStruct[RatingID]=ArrayNew(1)>   <!--- Use an in-memory-query to get the films with this rating --->   <cfquery dbtype="query"            name="MatchingFilms">     select * FROM FilmsQuery     WHERE Ratingid=#ThisRatingID#   </cfquery>   <!--- For each matching film... --->   <cfloop query="MatchingFilms">     <!--- Create a new structure to hold the film's ID and title --->     <cfset FilmStruct=StructNew()>     <cfset FilmStruct.filmid=MatchingFilms.FilmID>     <cfset FilmStruct.movietitle=MatchingFilms.MovieTitle>     <!--- Append the structure to the array for this rating --->     <cfset ArrayAppend(RatingsStruct[RatingID], FilmStruct)>   </cfloop> </cfloop> <html>   <head> <title>Cascading Select Lists</title> <!--- Custom JavaScript code ---> <script type="text/javascript"         language="JavaScript">   <!--- Output the JavaScript code needed to create a JavaScript object --->   <!--- called objRatings that holds the same data as RatingsStruct --->   <cfwddx action="CFML2JS"           input="#RatingsStruct#"           toplevelvariable="objRatings">   // This function fills the second select box based on the first box's value   function fillFilms() {     // Get the currently selected rating ID from the first select box     with (document.forms[0].RatingID) {       // Stop here if there is no selected rating       if (selectedIndex == -1) {         return;       }       var ratingID=options[selectedIndex].value;     }     // Remove all options from the second select box     document.FilmForm.FilmID.options.length=0;     // Grab the appropriate array of films from the objRatings object     var arFilms=objRatings[ratingID];     // For each item in the films array...     for (var i=0; i < arFilms.length; i++) {       // Place a new option in the second select box       with (document.FilmForm.FilmID) {         options[options.length]=           new Option(arFilms[i].movietitle, arFilms[i].filmid);       }     };   }; </script> </head> <!--- Call the fillFilms() function when the page first appears ---> <body onload="fillFilms()"> <!--- 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> 

After the queries at the top of the page, a new CFML structure called RatingStruct is created. Then, within a <cfloop> that loops over each rating in the database, a new array is created with ArrayNew() and stored in RatingsStruct (using the rating ID as the name). Next, an in-memory-query is used to get the films for the current rating, and an inner <cfloop> is used to loop over each of the films. Within this inner loop, a structure is created to represent the film, holding the film's ID number and title. This film structure is then appended to the end of the array for the current rating.

NOTE

In other words, when the nested loops have finished executing, RatingStruct will contain an array of films for each rating. The arrays will be filled with smaller structures that contain the ID and title for each film. You could access the title of the second film with a rating of 5 using RatingStruct[5][2].MovieTitle.


With the RatingsStruct structure in place, it can be passed to JavaScript in one step using the <cfwddx> tag. In this listing, the toplevelvariable attribute establishes that the data shall be known to JavaScript as an object named objRatings. If you use your browser's View Source command, you can see the JavaScript code that <cfwddx> generates to re-create the structure on the client. Here's an excerpt:

 objRatings = new Object(); objRatings["1"] = new Array(); objRatings["1"][0] = new Object(); objRatings["1"][0]["movietitle"] = "Charlie's Devils"; objRatings["1"][0]["filmid"] = 2; objRatings["1"][1] = new Object(); objRatings["1"][1]["movietitle"] = "Four Bar-Mitzvahs and a Circumcision"; objRatings["1"][1]["filmid"] = 4; ... objRatings["4"] = new Array(); objRatings["4"][0] = new Object(); objRatings["4"][0]["movietitle"] = "Ground Hog Day"; objRatings["4"][0]["filmid"] = 7; objRatings["4"][1] = new Object(); objRatings["4"][1]["movietitle"] = "Raiders of the Lost Aardvark"; objRatings["4"][1]["filmid"] = 17; objRatings["4"][2] = new Object(); objRatings["4"][2]["movietitle"] = "The Sixth Nonsense"; objRatings["4"][2]["filmid"] = 15; 

As you can see, there's no magic here. This JavaScript isn't any different conceptually from the JavaScript that the previous versions of this example generated. What's cool is that <cfwddx> did it all for us, automatically!

The remainder of Listing 17.5 is very similar to the previous versions of this example (Listing 17.4 and Listing 17.3). Within this version's fillFilms() function, the JavaScript code is able to obtain an array of films for a particular rating using objRatings[ratingID]. This returns the JavaScript equivalent of the array that was created for the rating in the CFML portion of the listing. Because each element of the array contains an object that in turns holds filmid and movietitle properties.

NOTE

Because ColdFusion is not case-sensitive (but JavaScript is), structure key names are always re-created in JavaScript using lower case. That's why the JavaScript portion of the code must refer to arFilms[i].movietitle instead of arFilms[i]. MovieTitle or arFilms[i].movieTitle. Whenever you use <CFWDDX> to pass structures to JavaScript, the resulting JavaScript objects will use lower case for the property names. For this reason, I recommend that you use lowercase names when building the corresponding CFML structures (like this listing does when assigning values to FilmStruct). Otherwise, it can be confusing to use one spelling in the CFML portion of your code and the all-lowercase spelling in the JavaScript portion.


Working with WddxRecordset Objects

Most ColdFusion data types correspond to JavaScript data types rather neatly (strings become strings in JavaScript, dates become Date objects, arrays become Array objects, and so on). However, the ColdFusion notion of a recordset (or query object) has no obvious counterpart in JavaScript. For this reason, a simple, lightweight implementation of a JavaScript-based recordset object is included with ColdFusion. The object is called WddxRecordset. Whenever you use <cfwddx> to pass a query object to JavaScript, the generated JavaScript code will construct a new instance of this object.

Including the wddx.js JavaScript File

The WddxRecordset object is implemented in a file called wddx.js, which is automatically installed when you install ColdFusion. You must include the file with a set of <script> tags as discussed in this section. Otherwise, the browser will not understand what a WddxRecordset is, and you will likely see an error message reporting that "WddxRecordset is undefined" or something similar.

By default, the file is located in the CFIDE/scripts folder within your Web server's document root. This means that you can use a line like the following to properly include the wddx.js file:

 <!--- Include wddx.js, located in the /CFIDE/scripts folder ---> <script language="JavaScript" src="/books/2/449/1/html/2//CFIDE/scripts/wddx.js"></script> 

If the wddx.js file has been moved or deleted, or if you are using a virtual Web server instance that has a different document root, that relative URL may not be valid. If so, one option would be to configure your Web server such that the /CFIDE/scripts prefix maps to the folder that actually contains wddx.js. Or, more simply, you can just copy the wddx.js file to the same folder as the ColdFusion pages you need to use it in, adjusting the relative path accordingly, like so:

 <!--- Include wddx.js, located in this folder ---> <script language="JavaScript" src="/books/2/449/1/html/2/wddx.js"></script> 

Simply to ensure that all the examples work correctly without adjustments, the example listings for this chapter use this method. Just do whatever makes sense for your situation.

NOTE

Conceptually, this line means the same thing to the browser as that a <cfinclude> tag means to ColdFusion. The browser will fetch the file over the Internet and execute its code inline, as if it appeared between the <script> tags.


Using WddxRecordset Methods

The WddxRecordset object supports a number of methods for getting data in and out of the recordset, as listed in Table 17.13. If you read Chapter 16, you may notice that the nature and scope of these methods are similar to the ones provided for the COM and Java implementations of WDDX that were discussed in that chapter. The theory in all these cases is the same: to provide a basic notion of a recordset that includes just enough methods to make the recordset useful, while keeping it easy to use, understand, and support.

Table 17.13. JavaScript WddxRecordset Methods

METHOD

DESCRIPTION

.addColumn(name)

Adds a column to the recordset. Specify the new column's name as a string.

.addRows(num)

Adds the specified number of rows to the recordset. Specify the number of rows as an integer.

.isColumn(name)

Determines whether the name you specify is a column of the recordset. Returns a boolean (true/false) value.

.getField(row, col)

Returns the data in the recordset at the row and column position you specify. Specify row as an integer (the first row is 0, the second row is 1, and so on). Specify col as a string.

.geTRowCount()

Returns the number of rows in the recordset, as an integer. Similar conceptually to the automatic RecordCount property for query objects in CFML.

.setField(row, col, value)

Places value into the recordset at the row and column position you specify. Specify row as an integer. Specify col as a string.

.dump(escape)

For debugging purposes. Conceptually, this method is the equivalent of <cfdump> in CFML. To dump the contents of the recordset to the screen, you could use document.write (rs.dump()), where rs is an instance of the WddxRecordset object. The optional escape argument determines whether characters that are special to HTML are escaped (in the fashion of HTMLEditFormat() in CFML). The default is false; in general, you should use .dump(true).

.wddxSerialize()

Used internally by the WddxSerializer object. This method is not meant to be called on its own, but you might take a look at this portion of the wddx.js file to see how you can create a custom JavaScript object that serializes itself in some kind of special way. For details, consult the WDDX SDK.


NOTE

Complete reference information and further examples are provided in the WDDX Software Development Kit (SDK), which is available from http://www.openwddx.org.


NOTE

For all methods that take a col argument, the column name is not case sensitive (the column names are stored internally in lowercase to achieve the case-insensitivity). The only exception is if you create a new recordset from scratch on the client as discussed in the Creating Recordsets from Scratch sidebar for details, in which case you can specify that the case of the column names are preserved (details in the WDDX SDK). Otherwise, the capitalization of recordset column names is not considered to be important.


You'll see the getrowCount() and getField() methods used in the next code listing.

Cascading Selects, Approach #4: Using a WddxRecordset Object

As an example of how to use WddxRecordset in an actual Web page, let's return to the cascading select problem. Listing 17.6 shows a fourth solution to the problem, this time using <cfwddx> to send the FilmsQuery recordset to the browser as a variable called rsFilms. Once interpreted by the browser's script engine, the rsFilms object will be an instance of WddxRecordset, meaning that any of the methods listed in Table 17.13 can be used to retrieve (or change) the data it contains.

Listing 17.6. JSRelatedSelects4.cfmPassing Recordset Data to Relate the Two Select Lists
 <!--- Name:        JSRelatedSelects4.cfm Author:      Nate Weiss and Ben Forta Description: Demonstrates one approach to relating              select lists via JavaScript, using              film and rating data from the ows              example database. Created:     02/01/05 ---> <!--- Get film data from database ---> <cfquery name="FilmsQuery"          datasource="ows">   select FilmID, MovieTitle, RatingID   FROM Films   WHERE RatingID IS NOT NULL   ORDER BY RatingID, MovieTitle </cfquery> <!--- Get rating data from database ---> <cfquery name="RatingsQuery"          datasource="ows">   select RatingID, Rating   FROM FilmsRatings   ORDER BY RatingID </cfquery> <html> <head> <title>Cascading Select Lists</title> <!--- Include the wddx.js file (in same folder as this ColdFusion page) ---> <!--- This allows us to receive and work with WddxRecordset objects ---> <script type="text/javascript"         src="/books/2/449/1/html/2/wddx.js"         language="JavaScript"></script> <!--- Custom JavaScript code ---> <script type="text/javascript"         language="JavaScript">   <!--- Output the JavaScript code needed to create a JavaScript object --->   <!--- called objRatings that holds the same data as RatingsStruct --->   <cfwddx action="CFML2JS"           input="#FilmsQuery#"           toplevelvariable="rsFilms">   // This function fills the second select box based on the first box's value   function fillFilms() {     // Get the currently selected rating ID from the first select box     with (document.forms[0].RatingID) {       // Stop here if there is no selected rating       if (selectedIndex == -1) {         return;       }       var ratingID=options[selectedIndex].value;     }     // Remove all options from the second select box     document.FilmForm.FilmID.options.length=0;     // For each item in the films array...     for (var row=0; row < rsFilms.getRowCount(); row++) {       // If this is a matching film       if ( rsFilms.getField(row, "RatingID") == ratingID ) {         // Place a new option in the second select box         with (document.FilmForm.FilmID) {           options[options.length]=new Option(             rsFilms.getField(row, "MovieTitle"),             rsFilms.getField(row, "FilmID"));         }       };     };   }; </script> </head> <!--- Call the fillFilms() function when the page first appears ---> <body onload="fillFilms()"> <!--- 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> 

If you use the number of lines of code as a measure of simplicity, this is the simplest approach yet. Whether it actually feels simpler to you as a developer is a matter of perspective and personal preference.

As you can see, this listing is structurally similar to the versions that came before it. The important differences introduced in this version are as follows:

  • A <script> block is used with src="/books/2/449/1/html/2/wddx.js" to include support for WddxRecordset objects.

  • <cfwddx> is used with action="CFML2JS" to generate the JavaScript code needed to create a WddxRecordset that contains the same data as the ColdFusion FilmsQuery object.

  • The number of rows in the recordset is obtained using rsFilms.getRowCount(). This is used to create a for loop that loops over each row, where the current row is available as the integer row.

  • The getField() method is used to compare the rating of each film to the user's current selection, and to retrieve each matching film's ID number and title.

Creating Recordsets from Scratch

Most of the time, you will receive a WddxRecordset from ColdFusion as a result of the <cfwddx> tag, or by deserializing a WDDX packet that contains a <recordset> block. That said, you may occasionally need to create a WddxRecordset from scratch in your JavaScript code. Just create a new recordset with new WddxRecordset(), then use the addColumn(), addRows(), and setField() methods from Table 17.12. For instance, you could create a new recordset with JavaScript code like the following:

 rs = WddxRecordset() rs.addColumn("firstname") rs.addColumn("lastname") rs.addRows(2); rs.setField(0, "firstname", "Nate"); rs.setField(0, "lastname", "Weiss"); rs.setField(1, "firstname", "Winona"); rs.setField(1, "lastname", "Ryder"); 

Conceptually, the steps are similar to the QueryNew(), QueryAddRow(), QueryAddColumn(), and QuerySetCell() functions in CFML. You can also pass the initial column names and number of rows to the WddxRecordset() constructor; see the WDDX SDK for details.



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