2.5 Making a Remote Call

Now that we have some preliminaries out of the way, it's time to do some Remoting! The steps for a successful Flash Remoting call to a remote service are as follows:

  1. Include the NetServices.as library.

  2. Optionally, initialize variables to hold the URL of the gateway path and the name of the remote service.

  3. Create a connection object that initializes a path to the Flash Remoting gateway.

  4. Create a service object, which will be used to invoke remote services and dispatch results and error events.

  5. Create onResult( ) and/or onStatus( ) callback methods to handle the onResult and onStatus events. These can be contained in a responder object or on the current timeline.

  6. Call the remote service as a method of the service object. The Flash movie continues to execute while waiting for the response, so your code must not assume that the results are returned immediately.

  7. When an onResult or onStatus event is returned, the appropriate callback function is triggered, and the movie reacts as programmed.

The following sections describe the preceding steps in greater detail by going through a slightly more advanced version of the HelloWorld service, called HelloUser. The HelloUser service collects a user-supplied parameter, checks the time on the server, concatenates a string, and passes the whole thing back as a result to the Flash movie. The responder object processes the results of the remote call and displays the string onscreen. This example demonstrates the following concepts not shown in the HelloWorld example:

  • The setDefaultGateway( ) method of the NetConnection object is utilized.

  • A custom class is created to serve as a responder object.

  • The remote call is triggered by the user.

  • An argument is passed to the remote service method.

  • The server does some processing before returning a string.

This example and others in the book can be downloaded from the online Code Depot at http://www.flash-remoting.com.

Example 2-1 shows the complete code for the client-side movie. The movie has three text fields?an unnamed label, userName_txt (the input element), and results_txt (to contain the result). In addition, a PushButton component named submit_pb triggers the call to the remote service. The interface is shown in Figure 2-10.

Figure 2-10. Simple interface for the HelloUser movie
figs/frdg_0210.gif

The user fills in his name, clicks the Submit button, and is greeted with "Hello Tom. It is 11:02:32 PM" or something similar. Review the code briefly, and we'll discuss portions of it in subsequent sections.

Example 2-1. HelloUser.fla ActionScript code
#include "NetServices.as"

// Set up variables for the URL and service paths
var myURL = "http://localhost/flashservices/gateway";
var servicePath = "com.oreilly.frdg.HelloUser";

// Define the custom SimpleResult class
function SimpleResult ( ) { 
  // Set up onResult( ) and onStatus( ) handlers as methods of the SimpleResult class
  this.onResult = function (myResults) {
    results_txt.text = myResults;
  };

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

  // Set the system status to be handled by the 
  // responder object's onStatus( ) handler as well
  System.onStatus = this.onStatus;
}

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

// Set up the callback function to handle mouseclicks
submit_pb.setClickHandler("callSayHello");

// Call the service when the user clicks the Submit button.
function callSayHello ( ) {
  service.sayHello(userName_txt.text);
}

2.5.1 Initialize Objects as Needed

Before making a connection, you should initialize necessary variables. Don't forget to include the NetServices.as file:

#include "NetServices.as"

// Set up variables for the URL and service paths
var myURL = "http://localhost/flashservices/gateway";
var servicePath = "com.oreilly.frdg.HelloUser";

As should be familiar by now, the myURL variable holds the Flash Remoting gateway URL (you should specify the URL of your own Flash Remoting gateway). The servicePath variable, following the preferred namespace conventions, specifies the name of the remote service.

Next, Example 2-1 defines a new class, SimpleResult, an instance of which is used as a responder object to handle the results of the remote service call. This differs from the HelloWorld example, which used a generic object of the Object class. When movies are complex, it is easier to manage the code if your responder objects are instances of self-contained classes.

Each remote service call triggers either an onResult or onStatus event. The SimpleResult class handles these events with two class methods: onResult( ) and onStatus( ). These methods simply set the text in the results_txt field:

function SimpleResult ( ) { 
  // Set up result and status handlers as methods of the SimpleResult class
  this.onResult = function (myResults) {
    results_txt.text = myResults;
  };

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

  // Set the system status to be handled by the result status handler as well
  System.onStatus = this.onStatus;
}

Subsequent chapters cover other ways to set up callback functions using methodname_Result( ) and methodname_Status( ) functions, as shown in some of the Macromedia documentation.

2.5.2 Create a Connection and Service Object

The path to the Flash Remoting gateway (the remote server) is set once on the main timeline. Generally, it is a good idea to keep all of your initialization scripting in the first frame (following any preloader) of your main actions layer. Our initialization script creates the connection and service objects.

Create a connection to the Flash Remoting services. Since this has to be done only once, we check for a variable flag that we create, named initialized. If it doesn't exist, we know that this is the first time our script has executed. The first time through, we set this variable so that we won't execute this section of code again on subsequent passes:

if (initialized == null) {
  initialized = true;

Next, set a default gateway URL using the NetServices.setDefaultGatewayURL( ) method:

  NetServices.setDefaultGatewayURL(myURL);
  var myConnection_conn = NetServices.createGatewayConnection( );

This approach is slightly different from the one used in the previous example, HelloWorld.fla. Since we use the setDefaultGateway( ) method here, we don't have to specify the gateway in the call to createGatewayConnection( ), as we did in HelloWorld.fla. Chapter 4 shows how to pass the URL to the movie dynamically to avoid hardcoding the URL within the movie. The createGatewayConnection( ) method returns a NetConnection object, which is stored in myConnection_conn.

At this point, the connection object to the server is simply created; no connection to the server has been attempted yet. The initialized flag is set to true to say "we have set up the connection URL." Don't confuse this with checking whether a connection attempt was successful, which we'll do later.

2.5.3 Create a Service Object

Next, Example 2-1 creates the ActionScript service object, which is a reference to the server-side service that you are going to create. Creating a service object lets us call methods of the service later.

  var service = myConnection_conn.getService(servicePath, new SimpleResult( ));
}

The second parameter, the responder object, is created as a new instance of the SimpleResult class. When a remote method is called and the service returns a result, the onResult( ) method of the responder object is triggered. Again, at this point we still have not contacted the remote service.

2.5.4 Create Callback Methods or Functions

The initialization code defines callback methods?onResult( ) and onStatus( )?for the SimpleResult class that we created.

The PushButton instance named submit_pb also needs a callback function, which we've named callSayHello( ), attached to it. The callSayHello( ) function uses the service object that was set up previously (i.e., service) to call the remote sayHello( ) method. It passes the text of the username_txt field as an argument:

submit_pb.setClickHandler("callSayHello");
// Call the service when the user clicks the Submit button.
function callSayHello ( ) {
  service.sayHello(userName_txt.text);
}

2.5.5 Call the Remote Service

Clicking the Submit button triggers the call to the remote service, as is typical of most Flash Remoting movies. You can extrapolate this concept to more complex interfaces, where each user interface element triggers a different remote method. For example, in a database results page, you could have Update, Insert, and Delete buttons that allow changes to your remote database. Each button could call a different remote method.

2.5.6 Wait for the Response

Because Flash Remoting is asynchronous, the Flash movie continues to execute while it awaits a response. In a simple movie such as this, the playhead is presumably paused in a frame and the movie simply displays the results when they are received. In a more complex movie, your ActionScript code must deal gracefully with extended delays that may be associated with calling a complex remote method.

When the response occurs, the onResult( ) handler of the responder object is called. This is one of the key concepts of event-driven programming. The Flash movie is continuously executing, but when the remote method returns the result, an event occurs (onStatus or onResult) and the appropriate method within the responder object is called.

2.5.7 Act on the Response or Error

In this example, the response from the remote method is a string, and our responder object is an instance of the SimpleResult class. Regardless, the response is passed as a parameter to the responder object's onResult( ) method, which sets the text of the field named results_txt to the incoming myResult variable:

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

If an error occurs, the onStatus( ) method is triggered instead of onResult( ). Our onStatus( ) method sets the text of the results_txt field to the description property of the incoming status object (i.e., an object indicating the type of error), myError:

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

The datatype of the parameter passed to the onResult( ) handler is dictated by the return value of the remote service. The return value is specified using the <CFRETURN /> tag in CFML; the return statement in Server-Side ActionScript, C#, VB, CFScript, or Java; or the Flash.Result object in ASP.NET or ColdFusion pages. The parameter passed to the onStatus( ) function is always an error object, with predefined properties including description, as described in Chapter 4.

2.5.8 The Server-Side Code

The following sections describe the server-side counterparts to the client-side ActionScript from Example 2-1. The different server-side examples are written in CFML, Server-Side ActionScript, Java, ASP.NET, and PHP.

2.5.8.1 ColdFusion MX

Example 2-2 shows a ColdFusion Component named HelloUser.cfc, containing the CFML that implements the remote service. It is stored in the webroot\com\oreilly\frdg\ directory. This CFC is based on the HelloWorld code from Example 1-2. The changes are noted in bold.

Example 2-2. ColdFusion MX Component HelloUser.cfc
<cfcomponent>
   <cffunction name="sayHello" returntype="string" access="remote">
      <cfargument name="username" type="string" default="">
      <cfreturn "Hello #username#. It is #TimeFormat(now( ),"H:MM:SS TT")#" />
   </cffunction>
</cfcomponent>

The only differences from the HelloWorld CFC in Chapter 1 are the addition of an argument and the processing of the time on the server. Passing an argument to a CFC is simply a matter of including a <cfargument> tag inside of the <cffunction> tag.

2.5.8.2 Server-Side ActionScript

Example 2-3, which is an enhanced version of Example 1-3, shows the Server-Side ActionScript to implement the remote service. It is stored in a file named HelloUser.asr and saved in the webroot\com\oreilly\frdg\ directory. If you created the ColdFusion CFC named HelloUser.cfc earlier, you should rename it to SomethingElse.cfc to prevent it from being called instead of the SSAS file.

Example 2-3. Server-Side ActionScript file name HelloUser.asr
function getTime ( ) {
  var d = new Date( );
  // Format the time with leading zeroes for the seconds and minutes
  var seconds = d.getSeconds( ) < 10 ? "0" + d.getSeconds( ) : d.getSeconds( );
  var minutes = d.getMinutes( ) < 10 ? "0" + d.getMinutes( ) : d.getMinutes( );
  // Format the hours with a 12-hour clock and AM/PM
  var hours = d.getHours( );
  var am_pm = hours > 12 ? " PM" : " AM";
  hours = hours > 12 ? hours-12 : hours;
  // Return the time in hh:mm:ss AM/PM format
  return hours + ":" + minutes + ":" + seconds + am_pm;
}

function sayHello (username) {
   return "Hello " + username + ". It is " + getTime( );
}

The custom getTime( ) function creates the current time string using a Date object in SSAS. The username argument from the Flash movie is concatenated with some literal text and the current server time to form the return string. As you can see, working with dates in SSAS is similar to working with dates in client-side ActionScript.

2.5.8.3 J2EE

The J2EE HelloWorld in Example 1-4 used a Java class. A Java class is stateless?each call to the class creates a new instance of the class. Example 2-4 shows how a JavaBean can be used instead, with the added advantage that a JavaBean can be stateful. The user's JSESSIONID is appended to the AMF packet by both the Flash Remoting server and the Flash movie. Flash Remoting automatically maintains the JavaBean state in the user session, provided the JavaBean implements the java.io.Serializable interface, as shown in Example 2-4. If not, the Flash Remoting adapter on the server will not store an instance of the class in the session and the service acts as a regular stateless Java class.

The steps to compile the JavaBean service vary from server to server. Typically you can run the javac compiler on the source file from a command prompt and place the resulting .class file in the classpath of your web application, using the folder structure outlined earlier (com\oreilly\frdg):

c:\>javac HelloUser.java

The source code for the JavaBean service is shown in Example 2-4.

Example 2-4. JavaBean HelloUser.java
package com.oreilly.frdg;

import java.util.*;
import java.text.SimpleDateFormat;
import java.io.Serializable;

public class HelloUser implements Serializable { 
  public HelloUser ( ) { // constructor
    message="";
  }
  private String message;
  
  public String getMessage ( ) {
    this.setMessage( );
    return this.message;
  }

  public void setUsername (String username) {
    this.username = username;
  }
  
  public void setMessage ( ) {
    this.message = "Hello " + this.username + ". It is " + getTime( );
  }

  public String getTime ( ) {
    SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss a");
    Date d = new Date( );
    return formatter.format(d);
  }

  public String sayHello(String username) {
    this.setUsername(username);
    return this.getMessage( );
  }
}

Example 2-4 has five methods: getMessage( ), setUsername( ), setMessage( ), getTime( ), and sayHello( ). The sayHello( ) method is called by Flash Remoting, which returns the message. Again, the username argument is concatenated to the greeting, which consists of some literal text and the current time on the server, to form the return string.

2.5.8.4 ASP.NET

As mentioned earlier, ASP.NET remote methods can take different forms, including .aspx pages. Example 2-5 shows server-side ASP.NET implemented as an .aspx page named sayHello.aspx. The code, written in C#, that implements the remote service is stored in the webroot/com/oreilly/frdg/HelloUser directory.

Example 2-5. ASP.NET code implemented in C# as an .aspx page
<%@ Page Language="C#"%>
<%@ Register TagPrefix="MyTag" Namespace="FlashGateway" Assembly="flashgateway" %>
<MyTag:Flash ID="Flash" Runat="Server" />
<%
if (Flash.Params.Count > 0) {
  String username = Flash.Params[0].ToString( );
  String currentTime = DateTime.Now.ToLongTimeString( );
  Flash.Result = "Hello " + username + ". It is " + currentTime;
}
%>

In this ASP.NET example, the page name of the .aspx page becomes the method name in the Flash movie. When using .aspx pages in this way, you have to register the tag prefix "MyTag", as shown, and create the namespace FlashGateway, along with the assembly (flashgateway.dll) that creates the functionality of the flashgateway control. Using ASP.NET pages has a few other caveats as well:

  • Arguments passed to the movie must be accessed as properties of the Flash.Params object.

  • Results must be returned to the Flash movie with the Flash.Result object.

Given these Flash-specific requirements, it is easy to see why the preferred method is to create a .NET DLL, as shown in Example 1-5, rather than use an .aspx page. Using a DLL allows you a cleaner separation between client- and server-side code.

2.5.8.5 PHP

An AMFPHP implementation of this service utilizes a HelloUser class. The following PHP code implements the HelloUser service and should be placed in the file webroot/flashservices/services/com/oreilly/frdg/HelloUser.php, assuming a standard installation of AMFPHP:

<?php
class HelloUser {
  function HelloUser ( ) { /* constructor */
    $this->methodTable = array(
      'sayHello' => array(
        'description' => 'Says a friendly hello to the user.',
        'access' => 'remote',
        'arguments' => array('username')
      )
    );
  }

  function sayHello ($username) {
    return "Hello $username. It is " . date('H:M:s', time( ));
  }
}
?>

In this example, we use PHP's built-in date( ) and time( ) functions to get the date and time, which we concatenate with the text greeting. Again, we must define the method table for AMFPHP to function properly and to allow our code to have web documentation.



    Part III: Advanced Flash Remoting