Paged Loading with XML-based Recordsets
One of the nicest things about Flash Remoting in Actionscript 2 is the pageable recordset functionality. This allows you to split the results of a large data set into chunks, or pages, that can be downloaded on an as-needed basis. This is a demonstration of how to use this paging functionality with XML-based web services. Specifically, I've created a sample application that uses the Flickr REST API (XML) to do a photo search.
Demo: Flickr Tag Search
Source: FlickrDemo.zip
Enter a tag in the search box, and hit 'go'. The results are loaded in batches of 100, and they fill in as you scroll. As you can see, we know how many records are available even before they are all loaded. We are
I've used this feature on a number of projects that included Flash remoting, and it is truly amazing. When in paged loading mode, a recordset instance is like an array that doesn't have all it's slots filled in. If I am a movie clip representing a datagrid row that needs record at slot 537 and that record is not loaded yet, the recordset gives me a little message, "in progress", instead of the actual data. The mx.remoting.Recordset class and it's friends take care of managing the server requests needed to load the missing data. Then, I simply listen for model change events until I get the data I need. As the definition above states, the List-based v2 UI components all understand this data provider API and can auto-fill themselves on an as-needed basis. This makes it possible to create things like scrolling lists that have thousands of records.
There are times when I've needed this same kind of paged loading functionality, but I didn't have access to Flash remoting. Instead, I needed to use an XML-based service that returned results based on GET method parameters that would either specify or range of records to return or simply a page number and the number of items per page. So, I have built a couple of classes that fetch data using XML and then use the built-in paging functionality provided with the mx.remoting code. This system has been set up in such a way that you can simply extend my XmlRecordsetService base class, implement a few key functions, and your result will be an instance of a class that extends mx.remoting.RecordSet. This recordset can be plugged in to any of the list-based components that come with Flash including the DataGrid that I have used in my demo.
Now, on to the code...
When you load a Flash Remoting service that returns a recordset, the recordset maintains a reference to the service class, and requests data when needed. I have created a class that extends the RecordSet class which allows me to do a couple of essential things. One is that I can reate a recordset from an array of objects using the static 'create' method. The second is that this class overrides a couple of methods that interface with the service layer.
com.bumpslide.services.CustomRecordset.as
I've done my best to implement the existing remoting service interface, but in the mx remoting classes, there is no formal interface here, and on top of that, there are a couple of bits of code in the original RecordSet class that needed fixing. So, this 'Custom' recordset class fixes those things.
Now, the other side of this recordset/service partnership is of course the service loading code. For this, I have an abstract class I call XmlRecordSetService. This class actually inherits functionality from the XmlService class which is itself an implementation of my Bumpslide service interface as defined in the abstract class com.bumpslide.services.Service. Now, this code is meant to be compiled, and not read, so we'll skip an analysis and jump into an implementation.
This is my Flickr Photo service class:
-
import com.bumpslide.util.Debug;
-
import com.bumpslide.util.Sprintf;
-
-
/**
-
* XmlRecordSetservice to retrieve flickr photos
-
*
-
* This class is an example of how to use the xml recordset functionality.
-
* Flickr photo search is a perfect example of an XML based service that
-
* returns data in pages.
-
*
-
* We just need to tell our service how to interpret and fetch these pages,
-
* and it will return an mx.remoting.Recordset class with full paged
-
* data loading and model updates.
-
*
-
* @author David Knape
-
*
-
* Copyright © 2006 David Knape (http://bumpslide.com)
-
* Released under the open-source MIT license.
-
* http://www.opensource.org/licenses/mit-license.php
-
* See LICENSE.txt for full license terms.
-
*/
-
class com.bumpslide.services.FlickrPhotoService extends com.bumpslide.services.XmlRecordSetService
-
{
-
//
-
var FLICKR_REST_ENDPOINT : String = "http://api.flickr.com/services/rest/";
-
var FLICKR_API_KEY : String = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
-
-
// auto read ahead...
-
var prefetchPages : Number = 2;
-
var pageSize : Number = 100;
-
-
// pass 'onServiceBusy' events without any delay
-
// so we can display messages related to behind the scenes loading
-
// of page requests
-
var busyMs = 1;
-
-
// show debug messages
-
var debug = false;
-
-
/**
-
* STEP 1 - Parse arguments into named variables using getters
-
*/
-
function get tags () : String {
-
return args[0];
-
}
-
-
/**
-
* STEP 2 - Build the URL
-
*/
-
function buildUrl() {
-
-
var url:String = FLICKR_REST_ENDPOINT + '?';
-
var params:Object = getUrlParameters();
-
params['api_key'] = FLICKR_API_KEY;
-
for(var p in params) {
-
if(params[p]!=undefined) {
-
url += Sprintf.format( '%s=%s&', p, params[p] );
-
}
-
}
-
return url;
-
}
-
-
/**
-
* a little helper for our flickr service
-
*
-
* (we can easily override this later in a subclass if necessary)
-
*/
-
private function getUrlParameters () {
-
return {
-
method: 'flickr.photos.search',
-
per_page: currentRequest.pageSize,
-
page: currentRequest.pageNum,
-
tags: tags
-
}
-
}
-
-
/**
-
* Step 3 - Get record total from XML result
-
*
-
* Recordset Services need to know how many records are in the recordset.
-
* The server-side code should include this somewhere, and here we
-
* determine where that is.
-
*/
-
function getTotalCount() {
-
return parseInt( xml.firstChild.firstChild.attributes.total );
-
}
-
-
/**
-
* Step 4 - Get page data
-
*
-
* Once XML Loads, this should return an array of objects corresponding to
-
* the current page request.
-
*/
-
function getPageData() {
-
-
var data:Array = nodesToObjects( xml.firstChild.firstChild.childNodes );
-
-
// we could just return data directly, but now that we have it,
-
// let's be nice, build the url's we need
-
for( var n in data ) {
-
var p = data[n];
-
var photoUrl = "http://farm1.static.flickr.com/"+p.server+"/"+p.id+"_"+p.secret;
-
data[n].urlPhotoPage = "http://www.flickr.com/photos/"+p.owner+"/"+p.id+"/";
-
data[n].urlThumb = photoUrl + '_s.jpg';
-
data[n].urlSmall = photoUrl + '_m.jpg';
-
data[n].urlMedium = photoUrl + '.jpg';
-
data[n].urlLarge = photoUrl + '_b.jpg';
-
}
-
return data;
-
}
-
-
/**
-
* Step 5 - handle result
-
*
-
* At this point, we should have a full-featured recordset.
-
* Although, it may not be entirely loaded.
-
*/
-
function handleResult( rs ) {
-
-
// you may want to save data to model locator here
-
Debug.warn( "RecordsetLoaded:");
-
Debug.warn( rs.items );
-
-
}
-
}
To explore this code in more detail, you may download the source. This an other examples can also be found in the Bumpslide SVN repository on SourceForge.