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.
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.
// 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) { trace(theError.description); }; if (initialized == null) { initialized = true; NetServices.setDefaultGatewayUrl("http://localhost/flashservices/gateway"); 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.
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.
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" class="flashgateway.io.ASObject" name="myObject" action="create" /> <cfset myInstance = myObject.init( )> <cfset myInstance.setType("MyFlashObject") /> <cfset myInstance.put("inited", 1) /> <cfreturn myInstance /> </cffunction>
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.
<cfcomponent> <cffunction name="computeTimeDifference" access="remote"> <!--- Create the ActionScript object ---> <cfobject type="java" class="flashgateway.io.ASObject" name="myObject" action="create"> <!--- 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> </cfif> <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 /> </cffunction> </cfcomponent>
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.
<cfcomponent> <cffunction name="computeTimeDifference" access="remote"> <cfscript> myObject = CreateObject("java", "flashgateway.io.ASObject"); t = myObject.init( ); t.setType("TimeDifferenceClass"); 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; </cfscript> </cffunction> </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.
// 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”.
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.
// 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; } } }
PHP
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.
<?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; } } ?>
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.
Get Flash Remoting: The Definitive Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.