5.5 Loading Query Data Incrementally

You should never make your user wait longer than necessary. And if the wait is unavoidable, at least make it seem shorter. For example, since browsers are usually accessing remote resources over which they have very little control, they are designed to display partial content as it becomes available. Modern browsers improve performance where it is under their control and give the illusion of performance when issues are beyond their control (such as connection speed, site design, server traffic, and so on).

Browsers use sophisticated caching and rendering algorithms to decrease the amount of work that has to be done between a request and a fully rendered response. Where performance is out of their hands, they employ user feedback to ease the pain of waiting as much as possible. For example, browsers use loader bars to indicate the page's load progress and render available content before the remainder is fully loaded. Most browsers use an animated icon in the upper-right corner to imply that the browser is making steady progress even though the animation has nothing to do with what the browser is loading.

One of the most appealing aspects of using Flash to develop web-based applications is that a well-designed UI can perform very well. Not only does Flash content stream, but once the page is fully loaded a well-designed Flash application requires fewer round trips to the server than traditional HTML-based applications. And when it is necessary to retrieve data from the server through Flash Remoting, incremental or pageable recordsets can be used to get data in front of users as quickly as possible.

5.5.1 Implementing Pageable Recordsets on the Server

A pageable recordset is returned to the client over the course of more than one request, allowing the application to start rendering results sooner than if all the results were returned at once. By default, when a recordset is returned to Flash, the entire recordset is returned in a single response. If you are not returning very many records, or if everyone using the application has a high-speed connection, the default behavior is probably fine; however, when returning large numbers of records over slower connections, it is more practical to return them incrementally.

A page is a subset of a recordset, and page size refers to the number of records in a given page. The default page size is the size of the entire recordset, which means that if the page size isn't explicitly set on the server, all records are returned in a single page. To change the page size, simply assign a value to the Flash.Pagesize variable before returning your Query object, as shown in Example 5-9. Although setting the Flash.Pagesize variable might appear to make our CFC function Flash-specific, it is ignored by non-Flash clients. See the full discussion under Section 5.5.4 later in this chapter.

Example 5-9. Customers.cfc
<cfcomponent>
  <cffunction name="getCustomers"
   access="remote"
   returnType="query">
    <cfquery name="rsCustomers" datasource="Northwind">
     SELECT ContactName FROM Customers
    </cfquery>
    <cfset Flash.Pagesize = 10 />
    <cfreturn #rsCustomers# />
  </cffunction>
</cfcomponent>

Example 5-9 can be saved as Customers.cfc in the webroot\com\oreilly\frdg directory. Notice how we set the Flash.Pagesize to 10 before returning the Query object. The server returns 10 records for each client request, but the server cannot push data to the client without the client asking for it. Setting the Flash.Pagesize variable to 10 means that the first 10 records are returned initially and the rest of the records are pageable. The server returns the remaining records when requested; however, the server does not limit responses to 10 records at a time. After the initial 10 records are returned, it is up to the client to decide how and when the rest of the data is loaded.

The Flash paging implementation is different than the type of recordset paging that uses the maxrows attribute of the <cfquery> tag, which might be more familiar to ColdFusion programmers. Once the CFML code sets the Flash.Pagesize variable, the server takes care of the details of paging when the client requests another page.

5.5.2 Implementing Pageable Recordsets on the Client

Since the server doesn't limit the number of records returned to the client after the initial records have been returned, it is up to the client to establish a delivery mode for incrementally loading the remaining data. A delivery mode is essentially a policy for how and when pageable data is retrieved from the server. To set a delivery mode, you use the setDeliveryMode( ) method on the RecordSet instance returned from the server, passing in the appropriate arguments. Table 5-6 describes each of the three delivery modes and how to use them.

Table 5-6. Delivery modes for pageable recordsets

Delivery mode

Description

Usage

"ondemand"

Loads each record individually each time a record is requested through the RecordSet.getItemAt( ) function on the object. This is the default behavior for pageable recordsets.

rs.setDeliveryMode("ondemand");

"fetchall"

Loads all the records in the recordset unconditionally, but tells the server to return them in pages, or sets, as opposed to one at a time.

rs.setDeliveryMode("fetchall", 10);

The second argument (10) indicates the number of records per page, meaning the number of records to be returned from the server with each response. It makes sense for this number to be the same as the Flash.Pagesize variable you set on the server, but it does not have to be.

"page"

When an individual record is retrieved from a particular page, not only are all the other records from that page returned, but also some specified number of subsequent pages are returned to the client if they have not already been retrieved.

rs.setDeliveryMode("page", 10, 2);

The second argument (10) indicates the page size while the third argument (2) indicates the number of subsequent pages to be retrieved and cached. If the third argument is 0, only the current page is returned.

Here is a description of when to use each delivery mode:

"ondemand"

Use "ondemand" mode when each record is needed individually and at different times. Do not use this mode if you are iterating through the entire RecordSet at once, because it forces the client to make a separate request for each record, which is very inefficient. This mode is efficient only if you won't eventually load all the records and you want to limit network traffic to only those records that must be loaded.

"fetchall"

Use "fetchall" mode when you know that you are going to load all the data but would like to start displaying the data incrementally rather than having to wait for it all to load. For example, if you know you have 300 records to load, it makes sense to load them over the course of 10 requests, 30 records at a time, so that you can start displaying data as soon as possible.

"page"

The "page" mode lies somewhere between "ondemand" and "fetchall". Use "page" mode when you don't expect to need all the data in the recordset, but you don't want the overhead of loading each record individually. For example, you don't want to make the user wait for 10 pages of search results to load, because she will most likely find what she needs in the first two or three pages. Therefore, load the first two or three pages initially, and then load the other pages as they are needed.

If you are handing off your recordset to a Flash UI component through the component's setDataProvider( ) method, set the delivery mode on the recordset before passing it into the component. The component will automatically load the data as specified in the call to setDeliveryMode( ). Since the best reason to load data incrementally is to display it to users, you may never need to load data incrementally outside of the context of a Flash UI component. If you need to handle or process incrementally loaded data yourself, however, the next section explains how ActionScript implements recordset paging.

5.5.3 Managing Incrementally Loaded Data by Hand

Remember that if the Flash.Pagesize variable is set on the server, the server returns only the specified number of records in the initial query. Unless the client requests the remainder of the data, it is never sent.

The RecordSet.addView( ) method is the key to handling pageable records. Any object that is passed into the addView( ) function should contain a function called modelChanged( ). The RecordSet object calls the modelChanged( ) function whenever its state changes in any way, passing into the function an object with the following properties:

event

Describes the type of event that occurred. Possible values for the event property are as follows:

"addRows"

Indicates that rows have been added to the recordset. Use the firstRow and lastRow properties to determine which rows were affected.

"allRows"

The recordset is completely populated, meaning all rows have been returned from the server. The firstRow and lastRow properties will not have values.

"fetchRows"

Rows have been requested from the server, but a response containing the data has not been received. Use the firstRow and lastRow properties to determine which rows were affected.

"sort"

Indicates that the records in the recordset have been sorted. The firstRow and lastRow properties will not have values.

"updateAll"

Indicates that the recordset has changed. This event occurs when a new view is added to the recordset.

"updateRows"

Indicates that rows have been changed. Use the firstRow and lastRow properties to determine which rows were affected.

firstRow

The index of the first row that was affected by the event.

lastRow

The index of the last row that was affected by the event.

Example 5-10 shows code for a simple ActionScript example that handles a pageable recordset manually (as opposed to allowing a Flash UI component to handle retrieving and displaying the data). The example simply retrieves the pageable data associated with a remote CFC function call and displays it in the Flash Output window. Assume that the remote service?com.oreilly.frdg.Customers?sets the Flash.Pagesize variable to 10 just before returning a RecordSet object of unknown length, containing a column called ContactName.

Example 5-10. Customers1.fla
#include "NetServices.as"

// Establish the connection to the service.
NetServices.setDefaultGatewayURL("http://localhost/flashservices/gateway");
var my_conn = NetServices.createGatewayConnection( );
var customerService = my_conn.getService("com.oreilly.frdg.Customers", this);
// Invoke the getCustomers( ) function within the .cfc file.
customerService.getCustomers( );

// Declare the IncomingDataHandler class's constructor.
function IncomingDataHandler (rs) {
  this.rs = rs;
}

// Get the rows that the server sent back immediately. This should be
// equal to the Flash.Pagesize variable set on the server (10 in this case).
IncomingDataHandler.prototype.getData = function ( ) {
  for (var i = 0; i < this.rs.getNumberAvailable( ); ++i) {
    trace(this.rs.getItemAt(i)["ContactName"]);
  }
};

// This function is called automatically as data is returned from the server.
IncomingDataHandler.prototype.modelChanged = function (info) {
  if (info.event == "updateRows") {
    for (var i = info.firstRow; i <= info.lastRow; ++i) {
      trace(this.rs.getItemAt(i)["ContactName"]);
    }
  }
};

function onResult (result_rs) {
  // Fetch all records, but only 10 at a time.
  result_rs.setDeliveryMode("fetchall", 10);
  var dataHandler = new IncomingDataHandler(result_rs);
  result_rs.addView(dataHandler);
  dataHandler.getData( );
}

function onStatus (error) {
  trace("error: " + error.description);
}

Let's start analyzing Example 5-10 at the beginning of the onResult( ) function, which is automatically called when the remote getCustomers( ) function returns a result. The first thing we do is set the delivery mode to "fetchall" for the RecordSet object that has been passed into onResult( ). This mode indicates we want all the results to be returned as soon as they are available. However, we also specify a page size of 10, so that the data is returned 10 records at a time over the course of as many requests and responses between the client and the server as are necessary to deliver all the data.

Although we are not responsible for writing the code that requests the data, we must implement an object that knows what to do with the data once it is returned from the server, so let us digress momentarily to discuss the IncomingDataHandler class. As you can see in Example 5-10, the IncomingDataHandler class defines a modelChanged( ) function, which is used to listen for records arriving on the client from the server, as follows.

The onResult( ) handler instantiates an IncomingDataHandler object, passing in the result_rs RecordSet object as an argument. It then passes the IncomingDataHandler instance named dataHandler to the addView( ) function of the result_rs recordset. Therefore, dataHandler's modelChanged( ) function is called whenever the state of the result_rs recordset changes. The final line of code in the onResult( ) function calls the getData( ) method on dataHandler, which manages the flow of data between the client and the server.

The getData( ) method merely iterates through the records that were already returned, which is determined by the value of the Flash.Pagesize variable set on the server and accessible through the recordset's getNumberAvailable( ) method. The rest of the paging is up to the modelChanged( ) function, which the recordset calls automatically as the data arrives from the server (10 records at a time). When the modelChanged( ) function receives an "updateRows" event, it extracts the data from the rows that were most recently loaded.

If, in the onResult( ) function, we had set the delivery mode to "ondemand" rather than "fetchall", we would have seen very different results. Recall that setting the delivery mode to "fetchall" causes all the data from the server to be returned to the client as quickly as possible, but the data is divided into pages over several requests and responses. When the delivery mode is set to "ondemand", however, no additional records beyond the initial page are returned until they are requested by the client through the RecordSet.getItemAt( ) method. Therefore, in "ondemand" mode, we would have seen the initial data sent from the server written to the Flash Output window in the IncomingDataHandler.getData( ) function, but until we tried to access records that hadn't been loaded yet, the IncomingDataHandler.modelChanged( ) method would never be called. It is important to note that once a record has been loaded, whether it was loaded on demand or fetched automatically, it gets cached on the client, so there is no need to make additional trips to the server when accessing data that has been loaded previously.

5.5.4 Passing the Page Size from Flash Dynamically

You may recall that one advantage of CFCs is that they tend to be more generic and reusable, since there is generally no need for a CFC function to contain Flash-specific code. They generally do not use the Flash variable scope to access arguments, and they use the <cfreturn> tag instead of the Flash.Result variable to return data. This concept seems to break down the moment you define the Flash.Pagesize variable in your ColdFusion Component; however, setting the Flash.Pagesize variable does not prevent other types of clients from invoking your ColdFusion Component functions. If the Flash variable scope is irrelevant in the context of the current request, setting the Flash.Pagesize variable has no effect whatsoever.

However, for the purists who simply cannot stand the sight of client-specific code in their components, there is actually a way to be rid of the tag entirely and set the page size from the client. Consider the following client-side ActionScript excerpt:

NetServices.setDefaultGatewayURL("http://localhost/flashservices/gateway");
var my_conn = NetServices.createGatewayConnection( );
var customerService = my_conn.getService("com.oreilly.frdg.Customers", this);
customerService.getCustomers2({pagesize:10});

When the preceding ActionScript code invokes the remote getCustomers2( ) ColdFusion function, it passes in an object containing a pagesize property. The ColdFusion Server understands the argument to mean that the query returned by the function should be pageable, sparing your ColdFusion Component function from unsightly client-specific code. The getCustomers2( ) function is listed in Example 5-11.

Example 5-11. The getCustomers2( ) function in the Customers.cfc file
<cffunction name="getCustomers2"
 access="remote"
 returnType="query">
  <cfquery name="rsCustomers" datasource="Northwind">
   SELECT ContactName FROM Customers
  </cfquery>
  <cfreturn #rsCustomers# />
</cffunction>

The only difference from the previous getCustomers( ) function in Example 5-9 is that the Flash-specific line, <cfset Flash.Pagesize = 10 />, has been removed from getCustomers2( ). The full code can be found at the online Code Depot as Customers2.fla and Customers.cfc.



    Part III: Advanced Flash Remoting
     
    ASPTreeView.com
     
    Evaluation has Е¶јґЅБИФµОexpired.
    Info...