Menu:

Sponsor

Discover Master of Alchemy, our first iPad/iPhone and iPod touch game!

 

Forum's topics

Latest Files

Archives

Top Rated

Categories

Photo Gallery


AMFPHP - Pageable RecordSet

1. Description

Recordset paging is a feature of the Actionscript mx.remoting.RecordSet class which allows you to divide the result of a query into different pages and decide how to fetch the returned rows. Most of the Flash UIComponents can work with pageable recordsets, including the DataGrid, List, and ComboBox.

2. Fetching recordsets manually

AMFPHP 1.0 has full support for Remoting Pageable Recordsets. Using Pageable Recordsets means that the result of a query can be fetched in several chunks. Instead of retrieving 500 rows from the remote server, say, you may only fetch the first 25, then fetch the rest whenever they are needed. The beauty of pageable recordsets is that Macromedia v2 components know when you should fetch the rest of the query result; it will then automatically ask to the server for the others records when they are needed.

But let's start with a manual approach in order to see how pageables recordset can be emulated using traditional methods. First write a new PHP class called "PageServiceServlet.php" into the "flashservices/services/test/" folder with this code:

define("DB_HOST", "localhost");		# mysql host
define("DB_USER", "admin");		# mysql username
define("DB_PASS", "****");		# mysql password
define("DB_NAME", "database_name");  # db name

class PageServiceServlet
{
	var $countQuery;

	function PageServiceServlet()
	{
		$this->methodTable = array(
			"GetRecords" => array(
				"description" => "Returns some records",
				"access"      => "remote",
				"pagesize"    => "25",
			)
		);
	}

	function GetRecords($offset = 0, $limit = 25)
	{
		if(!$conn_id = PageServiceServlet::Connect()){
			return false;
		}
		$this->countQuery = sprintf("SELECT COUNT(*) AS recordCount FROM click");
		return mysql_query(sprintf("SELECT id as ID, titolo as Title, hits as Hits, type as Type FROM click LIMIT %d, %d", $offset, $limit));
	}


	function GetRecords_count()
	{
		if(!$conn_id = PageServiceServlet::Connect()){
			return false;
		}
		$query = mysql_query($this->countQuery);
		$row   = mysql_fetch_assoc($query);
		return $row['recordCount'];
	}


	/**
	 * Connect to DataBase
	 * @return connection id
	 */
	function Connect(){
		$conn_id = @mysql_connect(DB_HOST, DB_USER, DB_PASS);
		if($conn_id){
			if(@mysql_select_db(DB_NAME)){
				$ip = $_SERVER["REMOTE_ADDR"];
				return $conn_id;
			}
		}
		return false;
	}

}


                

As you can see inside the methodTable GetRecords entry there's the "pagesize" key defined. When AMFPHP executes the GetRecords() method for the first time it will be aware that this method returns a pageable recordset thanks to the "pagesize" key. After calling the method it will automatically call the [functionName]_count function to get the total record count.

Once a RecordSet has been returned from the server it is under the control of the RsDataFetcher. If you don’t set the delivery mode, Flash can nevertheless automatically return records on demand simply by using RecordSet.getItemAt(index). If the index is out of range, that is, the recordset entry exists but has not been fetched, RsDataFetcher will retrieve the recordset row and dispatch a modelChanged event, with eventType updateItem. Note however that in the default mode, RsDataFetcher will attempt to retrieve entries one at a time. Usually you will want to return items in pages, however, hence you will want to use setDeliveryMode with the ‘page’ option (read more about it in the Remoting manual).

According to the above PHP we can write a flash code like this in order to get one page at time:

/* remoting classes */
import mx.remoting.Service;
import mx.rpc.RelayResponder;
import mx.rpc.ResultEvent;
import mx.remoting.PendingCall;


// create service
var serv:Service = new Service("http://localhost:8080/flashservices/gateway.php", null, "test.PageServiceServlet", null, null);
// store the total records retrieved here
var records:Number = 0;
// store the  Recordset here
var dataProvider:Array = new Array();

// First call for getting the first results
callRemoteMethod();


function callRemoteMethod(offset:Number){
	var pc:PendingCall
	if(offset == undefined){
		pc = serv.GetRecords();
	} else {
		pc = serv.GetRecords(offset);
	}
	pc.responder = new RelayResponder(this, "onGetRecordsResult");
}

// default responder
function onGetRecordsResult(rs:ResultEvent):Void {
	// add the current recordset length to the records variable
	records += rs.result.getNumberAvailable()
	trace("records: " + records)
	trace("totals:  " + rs.result.getLength())
	// save the current dataprovider
	dataProvider = dataProvider.concat(rs.result.items)
	// if we need more records.. i.e. there are still records in the table
	if(records < rs.result.getLength()){
		callRemoteMethod(records);
	} else {
		// else, populate the DataGrid
		for(var a in rs.result.getColumnNames().reverse()){
			grid.addColumn(rs.result.getColumnNames()[a])
		}
		dataProvider.sortOn("ID", Array.NUMERIC)
		grid.dataProvider = dataProvider
	}
}

                

As you can see while the "records" variable is less than the RecordSet.getLength() we manually ask to the server for a new "page" of records

3. ondemand, fetchall, page

3.1 ondemand

Now let's take a look at how this can be automated using components and the setDeliveryMode() Recordset method. Put a DataGrid component on the stage and call it simply "grid". Write the as:
/* remoting classes */
import mx.remoting.Service;
import mx.rpc.RelayResponder;
import mx.rpc.ResultEvent;
import mx.remoting.PendingCall;


// create service
var serv:Service = new Service("http://localhost:8080/flashservices/gateway.php", null, "test.PageServiceServlet", null, null);
var pc:PendingCall = serv.GetRecords();
pc.responder = new RelayResponder(this, "onGetRecordsResult");


// default responder
function onGetRecordsResult(rs:ResultEvent):Void {
	grid.dataProvider = rs.result
}

                

Much less code that previous example :) Test your movie and try to scroll the DataGrid. You will see that while scrolling the datagrid is still populating itself. This is because the default deliveryMode for RecordSet is "ondemand". This means that datagrid will ask to the server for a certain item only when it needs to show it. In fact, if you scroll the DataGrid in a position where items are not still retrieved from the server, the DataGrid itself will ask to the server for those items calling the GetRecords remote method and passing the offset of the item to be shown (for example: 34), and the limit of 1 as second arguments. (yes, it asks for just one item every time) This is useful when you have large tables and you don't need to get all the results immediately.

3.2 page

Once the onGetRecordsResult is called the first time you can change the deliverymode of the RecordSet using:
rs.result.setDeliveryMode(mode String, pagesize Number, prefetchPage Number)

  • mode: ondemand, page, fetchall
  • pagesize: how many records will be requested each time (page, fetchall)
  • prefetchPage: when you request a record using the getItemAt() method, the RecordSet object ensures that those pages to be prefetched after the page containing the requested record are either already available in the client or requested from the server. If the value of prefetchPage is zero, only the current page containing the requested record is fetched (page only)
According to this change the onGetRecordsResult code in this way:
function onGetRecordsResult(rs:ResultEvent):Void {
	rs.result.setDeliveryMode("page", 25, 2);
	grid.dataProvider = rs.result
}
				

This will fetch one page at time (page with 25 records) and with a number of prefetched pages of 2 (2x25 records). This mean that once datagrid needs to show the contents from page 2, it will ask also the contents of page 3 and page 4.

3.3 fetchall

In fetchAll mode all the records from the remote server are automatically delivered in a series of requests, also without scrolling down your datagrid. You can supply a pagesize parameter which tells to RecordSet how many records to retrieve for each call.

function onGetRecordsResult(rs:ResultEvent):Void {
	// retrieve all records, 10 items for each request
	rs.result.setDeliveryMode("fetchall", 10);
	grid.dataProvider = rs.result
}
				

4. Listeners: modelChanged

Moreover the Recordset object has an interesting listener which can be registered: "modelChanged". It will be called every time the recordset object is modified in some way. For example, change our code into:
function onGetRecordsResult(rs:ResultEvent):Void {
	rs.result.setDeliveryMode("page", 25, 2);
	grid.dataProvider = rs.result
	rs.result.addEventListener("modelChanged", this)
}


function modelChanged(info)
{
	if(info.eventName == "allRows"){
		trace('DataGrid is fully populated')
	}
}
				

Every time the datagrid dataProvider is modified in some way the modelChanged function is called. In our case the eventName can be:
  • fetchRows: Calling remote server method for fetching records
  • updateItems: A series of items needs refreshing. That is: the items have been retrieved from the server and now will be displayed in the datagrid replacing the temporary items
  • allRows: all the records have been received.

5. Conclusion

Pageable recordsets are indeed useful when you have very large table records and you prefer to make a series of requests rather than fetching all the records in one single request.
For more info: