Copyright © 2003 O'Reilly Media, Inc. All Rights Reserved.
Flash Remoting: The Definitive Guide
By Tom Muck
September 2003
ISBN: 0-596-00401-X
http://www.oreilly.com/catalog/flashremoting/
Available from booksellers or direct from O'Reilly Media, www.oreilly.com.

Cover image
This content is excerpted from the above-named O'Reilly publication, with permission, by agreement with ActionScript.org.

A broadcaster is based on the Observer pattern, another standard design pattern in programming. A broadcaster is implemented in ActionScript using the undocumented ASBroadcaster class. With this class, you can create objects that broadcast custom events inside your movie. After an event is broadcast, a listener that is listening for that particular event will respond.

WARNING ASBroadcaster is an undocumented class, and, as such, it may not remain in the language forever. You can implement the example here using ASBroadcaster or one of the numerous substitute broadcasters freely available on the Web.

Broadcasters fit right into the Flash Remoting framework because of the asynchronous nature of the technology. When you call a remote service, you don't wait for the response. The remote service method eventually returns a result to the responder function in the Flash client. The remote service is essentially a broadcaster, and your responder object is essentially a listener. This does not provide enough flexibility in handling results, however, so it makes sense to set up a custom broadcaster to convey the remote response to the part of your Flash movie that will benefit from it.

You can set up a broadcaster inside of your responder to broadcast a custom event to the movie. The advantage of this approach is that, once the event is broadcast, you can have one or more listeners acting on the remote response. To create a broadcaster, pass an instance of the generic Object class to the static ASBroadcaster.initialize( ) method:

var myBroadcaster = new Object( );
ASBroadcaster.initialize(myBroadcaster);

This converts myBroadcaster into an ASBroadcaster object capable of broadcasting. Specify the custom event to broadcast using the broadcastMessage( ) method:

myBroadcaster.broadcastMessage("onMyCustomEvent", "Hello there");

Finally, set up a listener object to listen for the custom event. Here, we create an object, myListener, with an anonymous function assigned to the onMyCustomEvent property:

myListener = {
 onMyCustomEvent:function(message) {
 trace(message);
 }
}

Finally, add the listener to the object to myBroadcaster using the addListener( ) method:

myBroadcaster.addListener(myListener);

Example 12-4 utilizes a broadcaster to broadcast the onResult event from the server, rather than using a callback function. It uses the same HelloUserClass class as shown earlier in HelloUserOOP.fla, with no changes. The only changes are in the ActionScript code in the movie, as well as the two classes that were set up.

Create a copy of the User.as file and rename it UserBroadcaster.as. Change the constructor and the sayHello( ) method as show in Example 12-4 (changes shown in bold).

Example 12-4. UserBroadcaster class

/*
User class

* public UserBroadcaster
constructor:
new UserBroadcaster( ); // Default user with no arguments
new UserBroadcaster(name); // Set a default name property
arguments:
name: string
properties:
service: the remote service with which the user interacts
name: the name of the user
methods:
getName: retrieve name property
setName: set name property
arguments:
name: string
getService: retrieve service object
setService: set the remote service for the object
arguments:
connection: a NetConnection object
servicePath: a path to a remote service
sayHello:interface to remote method, sayHello( )
arguments:
none
Dependencies:
com.oreilly.frdg.BroadcasterResponder
*/

#include "com/oreilly/frdg/BroadcasterResponder.as"

function UserBroadcaster (name) {
if (arguments)
this.name = name;
// Set this class up as a broadcaster
ASBroadcaster.initialize(this);

}

UserBroadcaster.prototype.getName = function ( ) {
return this.name;
};

// Set the name property only if the argument exists and is not blank
UserBroadcaster.prototype.setName = function (name) {
if (name != "" && name != undefined)
this.name = name;
};

UserBroadcaster.prototype.getService = function ( ) {
return this.service;
};

// Create remote service object as a property of User
UserBroadcaster.prototype.setService = function (connection, servicePath) {
this.service = connection.getService(servicePath);
};

UserBroadcaster.prototype.sayHello = function ( ) {
this.getService( ).sayHello(new BroadcasterResponder("onSayHello", this),
this.name);
};

Let's compare the UserBroadcaster class in Example 12-4 with the User class from the earlier callback implementation. The main differences are the initialization of the class as an ASBroadcaster in the constructor and the fact that the sayHello( ) method now uses a different responder object: BroadcasterResponder. You pass a custom event ("onSayHello") and the broadcaster object (this) to the responder function. The responder object notifies any listeners. The BroadcasterResponder responder function's definition is shown here:

/*
public BroadcasterResponder
 constructor:
 new BroadcasterResponder(event);
 arguments:
 event: the event that will be broadcast
 properties:
 none
 methods:
 onResult: method to handle remote results
 arguments:
 event: the event that will be broadcast
 onStatus: method to handle remote errors
 arguments:
 event: the event that the error occurred in
Dependencies:
 none
*/


function BroadcasterResponder (event, broadcaster) {
 this.event = event;
 this.broadcaster = broadcaster;
}
// Set up <em>onResult( )</em> and <em>onStatus( )</em> handlers as
// methods of the <em>BroadcasterResponder</em> class
BroadcasterResponder.prototype.onResult = function (myResults) {
 this.broadcaster.broadcastMessage(this.event, myResults);
};
BroadcasterResponder.prototype.onStatus = function (myError) {
 this.broadcaster.broadcastMessage(this.event + 'Error', myError);
};
System.onStatus = BroadcasterResponder.prototype.onStatus;

The BroadcasterResponder function accepts two arguments: the custom event that will fire when this responder is called, and the broadcaster that will broadcast the message (the UserBroadcaster object instance, in this case). The implementation is simple: when a successful result is returned from the server, the onResult( ) method is called and the broadcaster broadcasts the event ("onSayHello" in this case) and the actual results from the remote call to the movie. If an error is received by the onStatus( ) event handler, the name of the event becomes event+ "Error", or "onSayHelloError" in this case. Next, listeners need to be set up in the main movie:

#include "com/oreilly/frdg/UserBroadcaster.as"

if (initialized == undefined) {
 initialized = true;
 _global.app = new HelloUserClass("http://localhost/flashservices/gateway");
 var servicePath = "com.oreilly.frdg.HelloUser";
 app.myUser = new UserBroadcaster("User");
 app.myUser.setService(app._conn, servicePath);
}

submit_pb.onRelease = function ( ) {
 app.myUser.setName(userName_txt.text);
 app.myUser.sayHello( );
};

// Listener object for the <em>onSayHello</em> event
results_txt.onSayHello = function (message) {
 this.text = message;
};

// Listener object for errors in <em>onSayHello</em>
results_txt.onSayHelloError = function (message) {
 this.text = message.description;
};

app.myUser.addListener(results_txt);

The listener object is the results_txt TextField. Any object can serve as a listener, but the object must have a function set up to respond to your custom event. We simply create the necessary event handlers on the object (by setting the onSayHello and onSayHelloError properties to anonymous functions) and then add it as a listener to receive events fired off by the UserBroadcaster instance (app.myUser).

Again, this technique is well suited to Flash Remoting. The Macromedia Pet Market blueprint application also uses custom broadcasters. One advantage, as mentioned earlier, is that you can add multiple listeners to the event. For example, you can add this code to create a built-in debugging listener:

var debug = true;
// var debug = false; // Uncomment this line to turn off debugging
var debugListener = {onSayHello:function(message) {
 trace("User name: " + app.myUser.getName( ));
 trace("Results from server: " + message);
}}
if (debug) app.myUser.addListener(debugListener);

The listener is "turned on" when the debug flag is set to true. Doing this, you can add listeners to all of your remote calls without having to dig into your code to make changes and put trace( ) statements all over the place. It can all be done from one place, because your listener is listening for the event.

Mixing Procedural and Object-Oriented Code

Another common way to build an application is to mix procedural style with some OOP concepts. ActionScript 1.0 makes it easy to program in this way by not forcing the rules of OOP on you, as some other languages, such as ActionScript 2.0, require. The procedural example shown earlier could easily benefit from some of the techniques shown in the sections about OOP. For example, the code could implement callback functions in a custom responder object or a broadcaster. Chapter 14 shows a complete Flash MX application that is built procedurally using OOP concepts.