4.3 Creating Responder Functions

You've seen different ways of creating a responder function for a remote service call. There are two broad categories of responder functions:

  • The responder functions can be methods named onResult( ) and onStatus( ) (or, more accurately, they are functions assigned to the onResult and onStatus properties of a responder object).

  • Responder functions can also be named functions, in which case the function name must match the name of the remote method followed by _Result or _Status, such as methodname_Result( ) and methodname_Status( ). This approach is used in some of the Macromedia documentation and in Example 3-8.

Now that you understand the basics, here is the twist. A responder object can be passed to getService( ), in which case the same responder object is used for all future method invocations on that service. Alternatively, a responder object can be passed separately each time a method is invoked on the service, in which case a responder object should not be passed in the initial call to getService( ).

4.3.1 Using onResult( ) and onStatus( ) Responder Functions

Let's first look at passing a responder object to getService( ). Recall the syntax for calling the getService( ) method, where myConnection_conn is a NetConnection object returned by an earlier call to createGatewayConnection( ):

myService = myConnection_conn.getService(serviceName[, responderObject]);

The first parameter, serviceName, is a service name such as com.oreilly.frdg.HelloWorld. The optional second parameter, responderObject, is any object that defines onResult( ) and onStatus( ) methods, which will handle responses from future calls to methods of the remote service. The Macromedia documentation sometimes refers to responderObject as a result-handler callback object, we use the term responder object.

The responderObject argument does not receive the result of the getService( ) call itself, which we stored in myService. Instead, responderObject is used to obtain the results from subsequent calls to methods of the service. The myService object is simply a proxy through which you can make calls to methods of the remote service.

Many examples from Macromedia and elsewhere use the keyword this as the responder object, which causes Flash to look for callback functions defined on the object from which the getService( ) method is invoked. The only requirements for a responder object is that it defines an onResult( ) and onStatus( ) method, or uses named callback functions as described later in this section.

The responder object can take different forms. In this excerpt from Example 1-1, a generic instance of the Object class was created to handle the response from remote method calls:

myResult = new Object( );

myResult.onResult = function (data) {
  trace("Data received from Server : " + data);
};

myResult.onStatus = function (info) {
  trace("An error occurred : " + info.description);
};

// ...other code omitted
var myService = myServer.getService(myServicePath, myResult);

Note how the myResult object is passed as the responderObject parameter of the getService( ) method. After a remote method call on the service completes, the onResult( ) method of the myResult object will receive the results (unless an error occurs, in which case the onStatus( ) method will be invoked instead).

The following example defines the onResult( ) and onStatus( ) handlers on the current Flash object, as specified by the keyword this, and passes this as the responder object. This technique is also commonly seen throughout this book and in Macromedia's documentation:

this.onResult = function (myResult) {
    results_txt.text = myResult;
};

this.onStatus = function (myError) {
    results_txt.text = myError.description;
};
// Setting up of myConnection_conn and servicePath variables are not shown
var myService = myConnection_conn.getService(servicePath, this);

Recall that you can invoke any service function as a method of the service object returned by getService( ):

// Call a service function named functionA( ) on myService with no parameters.
myService.functionA( );

If the service function expects parameters, you can pass the parameters to it just as with any other method invocation:

// Call a service function named functionB( ) on myService with two parameters.
myService.functionB("sample1", "sample2");

If you passed a responderObject parameter when calling getService( ) earlier, the response from each remote method call is passed to the responder object's onResult( ) handler.

However, if you didn't specify a responder object when calling the getService( ) method, you can specify named callback functions for each remote method called (as discussed later).

First, let's discuss another option: passing a responder object as the first argument when invoking a remote function on the service. If the first argument is an object defining an onResult( ) method, the NetServiceProxyResponder object strips it from the argument list passed to the remote service function and uses it as a responder object instead (the responder object parameter is not sent to the service function).

For example:

// Create the service object without specifying a responder object.
myService = myConnection.getService("serviceName");

// Call functionA( ), specifying that myResponseObjectA should handle the 
// results, but without passing any additional parameters. 
// The definition of myResponseObjectA is not shown.
// myResponseObjectA is not sent to the service function.
myService.functionA(myResponseObjectA);

// Call functionB( ), specifying that myResponseObjectB should handle 
// the results, and pass two additional parameters. myResponseObjectB is not 
// sent to the service function, but the two string parameters are sent.
// The definition of myResponseObjectB is not shown.
myService.functionB(myResponseObjectB, "sample1", "sample2");

Specifying the responder object when invoking a remote method on the service lets you specify different responder objects for each remote method call, as shown in the preceding example. You don't have this flexibility if the responder object is set when calling getService( ). If you set a responder object via getService( ) and attempt to specify another responder object when invoking a remote function, it won't work. The responder object will be passed as a parameter to the remote function instead of being stripped out of the argument list.

4.3.2 Using Named Responder Functions

An alternative to using onResult( ) and onStatus( ) responder functions is to use named responder functions that match the name of the method. For example, here we define two named responder functions for the sayHello( ) method:

function sayHello_Result (myResult) {
    results_txt.text = myResult;
}

function sayHello_Status (myError) {
    results_txt.text = myError.description;
}

When a remote service call returns a result, the NetServiceProxyResponder object, which handles the result from the remote call, looks for a function that follows the methodName_Result( ) naming convention. Thus, onResult events generated by the sayHello( ) function cause Flash Remoting to invoke the sayHello_Result( ) function. Similarly, error events generated by the sayHello( ) function cause Flash Remoting to invoke the sayHello_Status( ) function

Using named functions in this way keeps the result and status callback functions separate for each remote method call. Contrast this with the approach in which the onResult( ) and onStatus( ) handlers of a responder object passed to getService( ) handle the results of all remote method calls on that service.

4.3.3 Response Dispatch Hierarchy

Now that we know about the various ways that responder objects and functions can be specified, how does Flash Remoting decide which responder function to invoke when results are returned from a remote method call?

We saw earlier that when a service is established via getService( ), Flash generates a NetServiceProxyResponder object. When a remote method call returns a result, a corresponding onResult (or onStatus) event is serialized by the Flash Remoting gateway as part of the AMF packet that is sent back to your Flash movie.

The NetServiceProxyResponder object dispatches the onResult event from a remote call in this order:

  1. First, it looks for a function that is named using the methodname_Result( ) convention. If it finds one, results are sent to that function. This function can be defined on the responder object or the current timeline.

  2. If the methodname_Result( ) function isn't found and a responder object with an onResult( ) method was specified in the call to getService( ), results are sent to that responder object's onResult( ) method.

  3. If a responder object wasn't specified in the call to getService( ) and the first argument passed to the remote method invocation is an object that defines an onResult( ) method, the first argument is assumed to be a responder object and results are sent to its onResult( ) method.

  4. If no responder object is specified (or if the specified responder object lacks an onResult( ) method), the NetServiceProxyResponder object sends the results to the Output window if the movie is playing in the authoring environment. Otherwise, the results are lost.

The NetServiceProxyResponder object also handles the onStatus event of the remote service in this order:

  1. First, it looks for a function that is named using the methodname_Status( ) convention. If it finds one, status errors are sent to that function.

  2. If the methodname_Status( ) function isn't found and a responder object with an onStatus( ) method was specified, results are sent to the responder object's onStatus( ) method.

  3. If no responder object is specified (or if the specified responder object lacks an onStatus( ) method), the _root level is checked for an onStatus( ) method. If it is found, it is used.

  4. If that is not found, the _global.System.onStatus( ) method, if any, is used.

  5. Finally, if none of the preceding handlers are found, the NetServiceProxyResponder object sends the status to the Output window in the authoring environment. Otherwise, the status is lost.

In the authoring environment, if you don't specify responders, the results are displayed in the Output window. This can be handy when testing applications.

4.3.4 Choosing the Appropriate Type of Responder Function

Now that you understand your options, which type of responder function should you use? The answer depends on your application's structure and requirements.

Named result functions are typically used when you have specified a default responder object for a service object (that is, when you've passed a responder object to the getService( ) method). This technique allows a single responder object to define separate responder functions for each remote service function (because of the naming convention used).

You should use onResult( ) and onStatus( ) responder functions when you are passing a responder object as the first parameter to each service function invocation. This technique is quite flexible: you can define different responder objects for each service function invocation, or you can share a single responder object among multiple service function invocations.

Provided you understand the mechanisms, you can mix and match the techniques to suit your situation. Now we will we explore various possible situations and solutions.

By passing a responder object to getService( ), you can use one event handler to handle all the results or errors for multiple remote method calls, if appropriate. For example, if you have a service that accessed a company employees database, you might have various methods like this:

myService.addEmployee(name);
myService.deleteEmployee(ID);
myService.updateEmployee(ID, record);

Each method can return true if it is a successful database transaction. If you use named functions to handle the results, each of these remote method calls needs its own set of responder functions, as in this code snippet:

updateEmployee_Result (result) {
  if (result != true) results_txt.text = "There was an error.";
}
deleteEmployee_Result (result) {
  if (result != true) results_txt.text = "There was an error.";
}
addEmployee_Result (result) {
  if (result != true) results_txt.text = "There was an error.";
}
updateEmployee_Status (status) {
  results_txt.text = status.description;
}
deleteEmployee_Status (status) {
  results_txt.text = status.description;
}
addEmployee_Status (status) {
  results_txt.text = status.description;
}

Using the responder object approach, this example could be written using one onResult( ) handler and one onStatus( ) handler attached to a generic object:

Responder = new Object( );
Responder.onResult = function (result) {
  if (result != true) results_txt.text = "There was an error.";
};

Responder.onStatus = function (status) (
  results_txt.text = status.description;
};

Or, if you pass this (i.e., the current object) as the responder object, you can simply write:

onResult = function (result) {
  if (result != true) results_txt.text = "There was an error.";
};
onStatus = function (status) (
  results_txt.text = status.description;
};

Using a responder object is more concise in this particular case. In addition, it is in keeping with object-oriented design. The named handler functions are easy to comprehend and use, but they are more typical of procedural programming.

However, you may need to process the results of each remote method call differently. For example, suppose the addEmployee( ), deleteEmployee( ), and updateEmployee( ) methods each require special handling. In such cases, you can pass a responder object as the first argument in the remote method call, as described earlier under Section 4.3.1.

Applying this technique to the hypothetical addEmployee( ), deleteEmployee( ), updateEmployee( ) methods, the resulting ActionScript might look like Example 4-1.

Example 4-1. SampleDatabaseMethods.fla
#include "NetServices.as"
// Set up variables for the URL and service paths.
var myURL = "http://localhost/flashservices/gateway";
var servicePath = "com.oreilly.frdg.SampleDatabaseMethods";

// Define the custom responder class for the remote updateEmployee( ) method.
function UpdateResult ( ) { }

// Define a custom onResult( ) handler for the UpdateResult class.
UpdateResult.prototype.onResult = function (myResults) {
  results_txt.text = "Update employee successful";
  // Do some housekeeping after updating an employee
};

UpdateResult.prototype.onStatus = errorHandler;

// Define the custom responder class for the remote addEmployee( ) method.
function AddResult ( ) { }

// Define a custom onResult( ) handler for the AddResult class.
AddResult.prototype.onResult = function (myResults) {
  results_txt.text = "Add employee successful";
  // Do some housekeeping after adding an employee
};

// AddResult and subsequent classes all share a single error handler.
AddResult.prototype.onStatus = errorHandler;

// Define the custom responder class for the remote deleteEmployee( ) method.
function DeleteResult ( ) { }

// Define a custom onResult( ) handler for the DeleteResult class.
DeleteResult.prototype.onResult = function (myResults) {
  results_txt.text = "Delete employee successful";
  // Do some housekeeping after deleting an employee 
};

DeleteResult.prototype.onStatus = errorHandler;

System.onStatus = errorHandler;

function errorHandler (myError) {
  results_txt.text = myError.description;
}

// Connection hasn't been initialized; create connection and service objects.
if (initialized == null) {
  initialized = true;
  NetServices.setDefaultGatewayURL(myURL);
  var myConnection_conn = NetServices.createGatewayConnection( );
  var myService = myConnection_conn.getService(servicePath);
}

// Set up the callback functions to handle mouseclicks.
add_pb.setClickHandler("callAdd");
update_pb.setClickHandler("callUpdate");
delete_pb.setClickHandler("callDelete");

// Call the remote service when the user clicks the buttons.
function callAdd ( ) {
  myService.addEmployee(new AddResult( ), "Jack O'Lantern");
}
function callUpdate ( ) {
  myService.updateEmployee(new UpdateResult( ), myRecordNum, myRecord);
}
function callDelete ( ) {
  myService.deleteEmployee(new DeleteResult( ), myRecordNum);
}

Each remote method call has a corresponding responder object that defines a custom onResult( ) handler. Notice, however, that all responder objects share a common error handler function. This allows you to process the results of each remote method differently while economizing with a single error handler.

We've seen how to invoke different responder functions for different remote methods, but you may want to distinguish between multiple calls to the same remote method. Remember that remote method invocations are asynchronous, and you cannot rely on results being returned to Flash in the same order in which the functions are invoked. Therefore, if you are using the same responder function for multiple calls to the same remote service function, you can't tell which service function invocation returned a particular result. To distinguish between the results from multiple calls to the same remote method, you can use a separate instance of a custom class for each responder object. Attach a custom property to each responder object instance and check its value when the result is returned to the responder function.

This solution adds an id parameter to the AddResult class constructor. You can create multiple instances of the AddResult class?one for each function invocation?and assign each one a unique id. Then, you can distinguish between results using the id property of the responder object. Replace the following functions in Example 4-1 with these new versions:

// Define the custom responder class for the remote addEmployee( ) method.
// Assign an id property to each instance.
function AddResult (id) {
  this.id = id;
}

// Define a custom onResult( ) handler for the AddResult class.
AddResult.prototype.onResult = function (myResults) {
  // Process the result differently, depending on the value of the id property.
  results_txt.text = "Employee " + this.id + " added successfully";
};

Now you can invoke the same service function multiple times. In each case, use an instance of the AddResult class as the responder object, but assign each instance an id corresponding to the employee name so that you can distinguish between them when the results are returned.

myService.addEmployee(new AddResult("Jack Sprat"),     "Jack Sprat");
myService.addEmployee(new AddResult("Jack Beanstalk"), "Jack Beanstalk");
myService.addEmployee(new AddResult("Jack O'Lantern"), "Jack O'Lantern");

Let's look at one more scenario for creating and managing responder objects. You can create a common responder class (named BaseResult in the following code snippet) and then create new responder objects that inherit from the base class for each remote method:

// Define a BaseResult class. This class is never called directly,
// but it acts as a base class for responder objects.
function BaseResult ( ) { }

BaseResult.prototype.onResult = function (myResults) {
  trace("success");
};

BaseResult.prototype.onStatus = function (myError) {
  results_txt.text = myError.description;
};

system.onStatus = BaseResult.prototype.onStatus;

UpdateResult.prototype = new BaseResult( );

function UpdateResult( ) { // Empty constructor
}

UpdateResult.prototype.onResult = function (myResults) {
  results_txt.text = "Update employee successful";
  // Do some housekeeping after updating an employee.
};
// etc.

In this scenario, the onStatus( ) handler is defined in the parent class (BaseResult) and is available to all of the classes that inherit from it. Each class that is created implements its own onResult( ) method. The full code listing for this example is available at the online Code Depot as SampleDatabaseMethods2.fla.

You can also establish a hierarchy of result handlers in your Flash movie. You can do this if you have several methods that can share a result handler but some methods that need special handling. For example, the following script defines two dedicated result handlers and one generic handler that will handle all other remote method calls:

function myMethod1_Result (result) {
  // Do some stuff for myMethod1( )
}
function myMethod2_Result result) {
  // Do some stuff for myMethod2( )
}
function onResult (result) {
  // Do some generic stuff for all other methods
}

The first two functions correspond to method names, and the third function simply acts as a generic method that all other remote method calls will use as a result handler.

Of course, the bottom line is that these techniques are all available and you should use what you feel comfortable with or what the situation demands. Section 12.7.2.4 shows other techniques for responder objects that rely on callback functions or broadcasters to handle results more elegantly.



    Part III: Advanced Flash Remoting