4.7 Registering Objects

Flash makes it easy to create code that follows OOP principles. OOP is not the only methodology for writing code, but it fits well with the framework of Flash Remoting. We've been using OOP techniques for many of the ActionScript examples in the book, but up to now we've used ActionScript objects only. What if we could instantiate an object on the client, pass it to the server, manipulate it in some way on the server, and pass it back to the client? This is possible with Flash Remoting.

4.7.1 Using Object.registerClass( )

If you've written Flash applications that use shared objects or extend the MovieClip class, you've probably used the Object.registerClass( ) method. The method allows you to register a specific class by name with ActionScript so that you can utilize the class in your movie simply by using its name:

Object.registerClass("MyObjectClass", MyObject);

The first argument is the name that you want to associate with the class, and the second argument is the actual class constructor. For the previous example to work, you must first define a class constructor:

function MyObject ( ) {
  // Some class initialization code

This technique is typically used when creating UI components or other objects that inherit from the MovieClip class. However, when used with Flash Remoting, Object.registerClass( ) associates a Flash object in a movie with an object that is returned from the server. This ensures that the return object is deserialized into your Flash movie as an instance of the custom class that we set up.

When you instantiate a class, the various properties and methods of the class are known to the Flash movie and can be used in your ActionScript code. When you pass this object to a remote service, the properties remain intact but the methods of the original class are stripped off. Likewise, a return object is not associated with a custom class by default. Even if the return object contains the same properties as the original object, Flash treats it as a generic object of the Object class. The custom methods of the original class are no longer available to the object.

Using Object.registerClass( ) allows Flash to assign a class identifier (the arbitrary name that we pass to the Object.registerClass( ) method) to the instance of the class. This identifier is passed along with the object to Flash Remoting and is returned along with any results to the responder's onResult( ) method. The return object is associated with the class once again when it is deserialized in the Flash movie, thus reinstating the object's methods before being passed to the onResult( ) method.

This is extremely simple to do using Server-Side ActionScript for your remote methods. You can merely pass the ActionScript object to the remote method, and the return object is automatically recreated as the ActionScript object that originated from your movie. For example, suppose you have a remote method named computeTimeDifference( ) and a client-side ActionScript class named TimeDifference. The remote method can compute the difference between the client time and the server time. The TimeDifference object on the client holds the properties date, days, hours, minutes, seconds, and milliseconds and a method named getTimeDifference( ). You might have a client-side ActionScript class constructor like the code in Example 4-6.

Example 4-6. Class constructor for the TimeDifference class
// Class constructor
function TimeDifference( ) { 
  // Initialize the class only if it isn't already initialized
  if (!this.inited) 
    this.init( );

TimeDifference.prototype.init = function ( ) {
  this.date = new Date( );
  this.days = 0;
  this.hours = 0;
  this.minutes = 0;
  this.seconds = 0;
  this.milliseconds = 0;
  this.inited = true; // Instance is initialized

TimeDifference.prototype.getTimeDifference = function ( ) {
  var d = this.days;
  var h = this.hours < 10 ? "0" + this.hours: this.hours;
  var m = this.minutes < 10 ? "0" + this.minutes : this.minutes;
  var s = this.seconds < 10 ? "0" + this.seconds: this.seconds;
  var ms = this.milliseconds < 100 ? "0" + this.milliseconds : this.milliseconds;
  return d + " D " + h + ":" + m + ":" + s + "." + ms;

Object.registerClass("TimeDifferenceClass", TimeDifference);

The last line of Example 4-6 registers the class. This line is key to the serialization and deserialization of objects in Flash Remoting. If the class is registered, the return object will be deserialized into an object of the same type. Take a look at the rest of the client-side ActionScript code for the Flash Remoting application:

var Responder = new Object( ); // Create the responder object

Responder.onResult = function (myResults) {
  trace(myResults.getTimeDifference( ));

Responder.onStatus = function (theError) {

if (initialized == null) {
  initialized = true;
  my_conn = NetServices.createGatewayConnection( );
  myService = my_conn.getService("com.oreilly.frdg.DebugFunctions", Responder);

myService.computeTimeDifference(new TimeDifference( ));

The onResult( ) method here is doing something a little peculiar: it invokes a method on the result from the remote service, as received in the myResults parameter! This is made possible through the registering of the TimeDifference class?the remote service attaches properties to the returned object, and the registered class's methods are reattached by reinstantiating the object on the client side. We can reinstantiate the object without losing any of its properties by using an init( ) method, which is called only if the inited property does not exist, in the constructor:

function TimeDifference( ) {
  // Initialize the class only if it isn't already initialized
  if (!this.inited) 
    this.init( );

Now look at the Server-Side ActionScript in Example 4-7.

Example 4-7. Server-Side ActionScript for the computeTimeDifference( ) method
function computeTimeDifference(t) {
  var d = new Date( );
  var e = new Date(t.get("date"));
  var difference = e.getTime( ) - d.getTime( );
  var days = Math.floor(difference/1000/60/60/24);
  difference -= days*1000*60*60*24
  var hours = Math.floor(difference/1000/60/60);
  difference -= hours*1000*60*60
  var minutes = Math.floor(difference/1000/60);
  difference -= minutes*1000*60
  var seconds = Math.floor(difference/1000);
  difference -= seconds*1000
  var milliseconds = difference;
  t.put("days", days);
  t.put("hours", hours);
  t.put("minutes", minutes);
  t.put("seconds", seconds);
  t.put("milliseconds", milliseconds);
  return t;

The Server-Side ActionScript method takes one argument: a custom object of type TimeDifference, named t, that we pass to the method from the Flash movie. The date property of the object (which holds the current time of the client) is extracted with a get( ) method:

  var e = new Date(t.get("date"));

Then the date is reconstructed as an ActionScript Date object and compared to the server date. The days, hours, minutes, seconds, and milliseconds are computed and packed into the TimeDifference object using the put( ) method. The object is then sent back to the Flash movie.

When you run the movie, you should see a result that shows the difference between your server time and the client time in the Output window. If you are using your local machine as the testing server, this difference may be only milliseconds. The results are traced to the Output window using the TimeDifference.getTimeDifference( ) method:

trace(myResults.getTimeDifference( ));

This tells us that the Flash movie is taking the results from the remote call and placing them back into an instance of our custom TimeDifference class.

To verify that this is happening, try commenting out the last line of the client-side ActionScript by prepending two slashes:

// Object.registerClass("TimeDifferenceClass", TimeDifference);

If you comment out the line, you can still access all of the properties of the myResults parameter, as you can verify by tracing the object's properties in the Output window, but the getTimeDifference( ) method does not work. That is because without the registerClass( ) call, the object is treated as a generic object with simple properties but no methods.

4.7.2 Registering Objects for ColdFusion MX, Java, ASP.NET, and PHP

When you're using Server-Side ActionScript, the passing of an object back and forth from client to server is straightforward. In CFML, Java, and ASP.NET, on the other hand, the object needs to be massaged on the server by creating the object and setting the type manually. This is done using the techniques described in the following sections. ColdFusion MX

Create a serializable object of type flashgateway.io.ASObject (i.e., an ActionScript object) in ColdFusion using a <cfobject> tag in CFML or a CreateObject( ) function within CFScript. The object type should be set to "java" and the class set to "flashgateway.io.ASObject":

<cffunction access="remote" name="myMethod" returntype="any">
  <cfobject type="java" 
   action="create" />
  <cfset myInstance = myObject.init( )>
  <cfset myInstance.setType("MyFlashObject") />
  <cfset myInstance.put("inited", 1) />
  <cfreturn myInstance />

A few things about the ColdFusion MX code need explanation. First, the flashgateway.io.ASObject datatype needs to be created inside of the function with the <cfobject> tag. This allows the creation of a serializable representation of an ActionScript object. Next, an instance of the object is instantiated with:

  <cfset myInstance = myObject.init( )>

The init( ) method is not an internal method of the ASObject class; it is a built-in ColdFusion construct that initiates a call to the constructor of the class. This is a requirement to create an instance of the object. Next is a call to the setType( ) method.

  <cfset myInstance.setType("MyFlashObject") />

This procedure associates the custom client-side ActionScript class specified in the call to Object.registerClass( ) with the server-side ASObject datatype. Next, the inited property is set to 1, ColdFusion's equivalent of the Boolean true. The inited property was the custom property that we set in the client-side ActionScript to trick the class constructor into creating the object without clearing out the properties. We could have also used the inited property of the arguments structure, which will be shown in the next example.

Finally, we return the object to Flash. Let's put the concept to use using the Flash movie that was created in Example 4-6. The ColdFusion MX code is shown in Example 4-8 and is commented inline.

Example 4-8. ColdFusion MX remote service DebugFunctions.cfc
  <cffunction name="computeTimeDifference" access="remote">
<!--- Create the ActionScript object --->
    <cfobject type="java" 
<!--- Create an instance of the object --->
    <cfset t = myObject.init( )>
<!--- Set the type to our custom TimeDifferenceClass for deserialization --->
    <cfset t.setType("TimeDifferenceClass")>
<!--- Do the math for the time difference --->
    <cfset d = now( )>
    <cfset e = createodbcdatetime(arguments.date)>
    <cfset difference = DateDiff("s", d, e)>
    <cfif difference LT 0> 
      <cfset difference = difference = difference * -1>
    <cfset days = int(difference/60/60/24)>
    <cfset difference = difference - days*60*60*24>
    <cfset hours = int(difference/60/60)>
    <cfset difference = difference - hours*60*60>
    <cfset minutes = int(difference/60)>
    <cfset seconds = difference - minutes*60>
<!--- Put the properties into the custom object --->
    <cfset t.put("days", #days#)>
    <cfset t.put("hours", #hours#)>
    <cfset t.put("minutes", #minutes#)>
    <cfset t.put("seconds", #seconds#)>
<!--- Set the inited property to the inited property of the object 
      passed to this method --->
    <cfset t.put("inited", arguments.inited)>
<!--- Finally, return the object --->
    <cfreturn t /> 

You can name this file DebugFunctions.cfc and put it into the webroot\com\oreilly\frdg directory. The Flash movie created earlier in Example 4-6 will work with this service with no change. Notice this line:

<cfset t.setType("TimeDifferenceClass")>

This line sets up the class type so that when it is returned to the Flash movie it will be deserialized into our custom TimeDifference class.

The same service can be written using CFScript, as shown in Example 4-9. The CFScript version uses a CreateObject( ) function rather than a <cfobject> tag.

Example 4-9. The DebugFunctions.cfc file using CFScript instead of CFML
  <cffunction name="computeTimeDifference" access="remote">
    myObject = CreateObject("java", "flashgateway.io.ASObject");
    t = myObject.init( );
    d = now( );
    e = createodbcdatetime(arguments.date);
    difference = DateDiff("s", d, e);
    if (difference LT 0) {difference = difference * -1;}
    days = int(difference/60/60/24);
    difference = difference - days*60*60*24;
    hours = int(difference/60/60);
    difference = difference - hours*60*60;
    minutes = int(difference/60);
    seconds = difference - minutes*60;
    t.put("days", #days#);
    t.put("hours", #hours#);
    t.put("minutes", #minutes#);
    t.put("seconds", #seconds#);
    t.put("inited", arguments.inited);
    return t;
</cfcomponent> Java

You saw in the ColdFusion version of the remote service that we were creating an instance of a Java class that allowed the serialization of the data into a copy of our ActionScript object. The Java class is also used in the Java version of the code, shown in Example 4-10.

Example 4-10. Java version of the service named DebugFunctions.java
// Java Document
package com.oreilly.frdg;
import flashgateway.io.*;
import flashgateway.util.*;
import java.util.*;
import java.lang.*;
import java.io.Serializable;

public class DebugFunctions {
  public DebugFunctions( ) {
  public ASObject computeTimeDifference (ASObject t) {
    Date d = new Date( );
    Date e = (Date)t.get("date");
    double difference = (double)e.getTime( ) - (double)d.getTime( );
    difference = Math.abs(difference);
    int days = (int)(Math.floor(difference/1000/60/60/24));
    difference -= days*1000*60*60*24;
    int hours = (int)(Math.floor(difference/1000/60/60));
    difference -= hours*1000*60*60;
    int minutes = (int)Math.floor(difference/1000/60);
    difference -= minutes*1000*60;
    int seconds = (int)Math.floor(difference/1000);
    difference -= seconds*1000;
    int milliseconds = (int)difference;
    String daysStr = String.valueOf(days);
    String hoursStr = String.valueOf(hours);
    String minutesStr = String.valueOf(minutes);
    String secondsStr = String.valueOf(seconds);
    String millisecondsStr = String.valueOf(milliseconds);
    t.put("days",  daysStr);
    t.put("hours", hoursStr);
    t.put("minutes", minutesStr);
    t.put("seconds", secondsStr);
    t.put("milliseconds", millisecondsStr);
    return t;

The Java code uses the ASObject class, just as the ColdFusion version did. In the computeTimeDifference( ) method, an ASObject (ActionScript object) was passed to the method, and the method returns the same ASObject:

public ASObject computeTimeDifference (ASObject t) {

Again, the methods of the client-side ActionScript object passed to the remote method are not accessible through Java, but the properties can be read with the get( ) method of the ASObject and they can be written using the put( ) method.

The Java class should be compiled and placed in the classpath of your application server. It will be used by the Flash movie created in Example 4-6.

When using the flashgateway.io.ASObject class, you need to put the flashgateway.jar file in your application's classpath; otherwise, you might get an error such as "Service threw an exception during method invocation: flashgateway/io/ASObject".

Even if Flash Remoting is working on your server, your application may not have access to the flashgateway classes unless you explicitly add the path of the flashgateway.jar file to your application. If you are going to be using the ASObject, you need access to these classes. ASP.NET

The ASP.NET version of Flash Remoting also allows the use of the ASObject class from the FlashGateway.IO assembly. Just as in the ColdFusion and Java versions, the TimeDifference object is passed into the method, the time difference is computed, and the properties are packed back into an ActionScript object, which is passed back to the Flash movie. The C# code is listed in Example 4-11.

Example 4-11. C# class for computeTimeDifference( )
// C# Document
using System;
using FlashGateway.IO;

namespace com.oreilly.frdg {
  public class DebugFunctions {
    //protected FlashGateway.Flash Flash;
    public DebugFunctions( ) {
    public ASObject computeTimeDifference (ASObject t) {
      // Set the type of the ActionScript object
      t.ASType = "TimeDifferenceClass";
      DateTime d = DateTime.UtcNow;
      DateTime e = (DateTime)t["date"];
      TimeSpan tsDuration;
      // Use an absolute value for the time difference
      tsDuration = DateTime.Compare(d, e) < 0 ? e - d : d - e;
      t["days"] = tsDuration.Days;
      t["hours"] = tsDuration.Hours;
      t["minutes"] = tsDuration.Minutes;
      t["seconds"] = tsDuration.Seconds;
      t["milliseconds"] = tsDuration.Milliseconds;
      t["serverDate"] = d;
      return t;

The PHP implementation of Flash Remoting (AMFPHP) also contains the functionality required to pass an ActionScript object from the client to the server and back again. Using PHP, you simply set up the name of the custom class in the returns element in the methodTable for the method used, as shown in Example 4-12. The AMFPHP gateway handles the serialization and deserialization of the custom object.

Example 4-12. Utilizing the custom computeTimeDifference( ) method in PHP
class DebugFunctions {
  function DebugFunctions ( ) {
    $this->methodTable = array(
      "computeTimeDifference" => array(
      "description" => "Returns an instance of TimeDifferenceClass (Custom Class)",
      "access" => "remote", // available values are private, public, remote
      "roles" => "role, list", // currently inactive
      "arguments" => array ("t"),
      "returns" => "TimeDifferenceClass" // name of Custom Class
  function computeTimeDifference ($t) {
    $d = time( ); 
    $e = $t["date"] / 1000 // PHP date is in seconds;
    $difference = ($d <= $e) ? ($e - $d) : ($d - $e); 
    $days = floor($difference/60/60/24);
    $difference -= $days*60*60*24;
    $hours = floor($difference/60/60);
    $difference -= $hours*60*60;
    $minutes = floor($difference/60);
    $difference -= $minutes*60;
    $seconds = floor($difference);
    $t["days"] = $days;
    $t["hours"] = $hours;
    $t["minutes"] = $minutes;
    $t["seconds"] = $seconds;
    return $t;

4.7.3 The Real Power of Object.registerClass( )

You've seen ActionScript objects on the client be passed to the server and back again. This should give you a feel for what is possible with Flash Remoting. When you consider that an ActionScript object can be as simple or as complex as you make it, you will start to appreciate the power of this technique. Imagine an initialization script that loads recordset data into 10 drop-down lists in your Flash movie. This can be done with 10 calls to remote methods, or it can be accomplished with one complex ActionScript object where each recordset is a property of the object. That way, you can make just one remote call, as shown in the following imaginary object:

function MyInitObject ( ) {
  if (!this.inited) this.init( );
MyInitObject.prototype.init = function ( ) {
  this.clients = new RecordSet(["ClientName", "ClientID"]);
  this.states = new RecordSet(["State", "StateAbrev"]);
  this.products = new RecordSet(["ProductID", "ProductName", "ProductDesc"]);
  this.categories = new RecordSet(["CatID", "CatDesc"]);
  this.colors = new RecordSet(["ColorID", "Color"]);
  this.shoppingCart = new RecordSet(["ProductID", "Quantity", "UnitPrice"]);
var currentCart = new MyInitObject( );

Application performance can be improved dramatically by caching server-side recordsets and reducing the remote calls using Object.registerClass( ).

The technique is now in your hands. How you use it is up to you.

    Part III: Advanced Flash Remoting