3.6. Subclassing the NetConnection Class
You can extend the features of the
NetConnection class
by creating a subclass based on it. Creating subclasses lets you
encapsulate code that operates on the
NetConnection object within the
subclass's methods such as connect(
), onStatus( ), or close(
). In turn, this can lead to more modular and easier to
maintain applications. The key to doing this is to use the
super operator. Example 3-8
shows a very simple example of how to extend the
NetConnection class and then how to use it
within a simple test script.
Example 3-8 uses AS 1.0 syntax so it works in both
Flash MX and Flash MX 2004. This also allows you to compile using
ActionScript 1.0 under
File Publish
Settings ActionScript.
Example 3-10 provides an alternative implementation
that uses AS 2.0 syntax and the
v2 UI component's EventDispatcher
class.
Example 3-8. A simple extension of the NetConnection class
// Constructor function must call super( ).
function NetConnectionSubClass ( ) {
super( );
}
// Subclass NetConnection by assigning an instance of it
// to the prototype object of the new subclass.
NetConnectionSubClass.prototype = new NetConnection( );
// A simple demonstration onStatus( ) event handler that just
// writes out each info.code message.
NetConnectionSubClass.prototype.onStatus = function (info) {
trace("info.code: " + info.code);
};
/* This connect( ) method may be passed any number of arguments.
* The apply( ) method makes sure all arguments are passed into
* the super.connect( ) method when it is called.
*/
NetConnectionSubClass.prototype.connect = function ( ) {
return super.connect.apply(super, arguments);
};
// Create an instance of the NetConnectionSubClass subclass.
lobby_nc = new NetConnectionSubClass( );
// Attempt the connection.
if (lobby_nc.connect("rtmp:/testLobby/", "Guest", "Guest")) {
trace("Attempting connection...");
}
else {
trace("Can't attempt connection. Is the URI correct?");
}
The super operator is used in two different ways
in Example 3-8. First, it must be called within the
constructor function as follows:
super( );
Invoking super( ) in this way calls the
NetConnection constructor (the
superclass's constructor). Second, whenever a
NetConnection method must be called by a
subclass's method, the super
operator can be used to call the superclass's
method. In this example, when the
NetConnectionSubClass.connect( ) method is
called, it in turn uses super to call the
NetConnection.connect( ) method as follows:
return super.connect.apply(super, arguments);
Using the apply( ) method provides a good,
general purpose way to call the superclass's
connect( ) method. Passing
super as a parameter ensures that
connect( ) is called as a method of the
NetConnection superclass and passing
arguments insures that all the parameters passed
to the subclass's connect( )
method are passed on to the superclass's
connect( ) method.
A simpler, but less general purpose, way to call the
NetConnection.connect( ) method is to
specifically pass individual parameters this way:
NetConnectionSubClass.prototype.connect = function (targetURI, userName, password) {
return super.connect(targetURI, userName, password);
};
In cases in which parameter values must be manipulated before being
passed to the superclass's connect(
) method or those in which not all parameters must be
passed on, it is often simpler to avoid using the apply(
) method.
On its own, Example 3-8 shows the basic mechanics of
creating a subclass of the NetConnection class,
but let's see what advantages this offers. Looking
back at Example 3-4 and Example 3-6, you will see that before calling the
connect( ) or close( )
methods, the handleCloseEvents flag had to be set
each time. This is extra work that shouldn't have to
be done in the doConnect( ), doChat(
), and doLobby( ) functions.
In fact, these details can be hidden inside a
NetConnection subclass's
methods as follows:
// close( ) turns off the handleCloseEvents flag before closing the connection.
LobbyChatConnection.prototype.close = function ( ) {
this.handleCloseEvents = false;
super.close( );
};
If we modify the connect( ) method to set the
handleCloseEvents flag as well, the
doLobby( ) function can be greatly simplified:
// Called when the Lobby button in a chat room is clicked.
function doLobby(btn) {
lobbyChat_nc.close( );
lobbyChat_nc.connect("rtmp:/testLobby", handleLobbyConnection,
userName, password);
}
Compare the preceding code to the doLobby( )
function in Example 3-6. You may notice that
something else is missing. In the "Reusing a
NetConnection Object" section
of this chapter, two onStatus( ) event handlers
were written: one for managing lobby connections and the other for
managing chat room connections. The doLobby( )
and doChat( ) functions were responsible for
making sure the right onStatus( ) handler was in
place before calling connect( ). In some cases,
this is necessary. However, when the onStatus( )
handlers are almost identical, there is a more elegant solution. The
main difference between the handlers is the frame to which they move
the playhead when a connection is made. A more general purpose
onStatus( ) handler can call a function when a
connection is established. In Example 3-9, one of
two simple functions is called by the onStatus(
) handler depending on what application it connects to:
// Called when a connection has been established to the testLobby app.
function handleLobbyConnection( ) {
writeln("Success, you are connected to the lobby!");
gotoAndPlay("Lobby");
}
// Called when a connection has been established to a chat room.
function handleChatConnection( ) {
writeln("Success, you are connected to a chat room!");
gotoAndPlay("ChatRoom");
}
How does the onStatus( ) handler know which of
these functions to call? In this example, a reference to one of them
is passed into the connect( ) method and saved
in the connectionHandler property. Example 3-9 is a complete listing of a test script in
which the onStatus( ) method uses the
connectionHandler property to call either
the handleLobbyConnection( ) or
handleChatConnection( ) function.
Example 3-9. NetConnection subclass test script
// writeln( ) writes messages into a text field named trace_txt
// and is a replacement for using trace( ) and the Output panel.
function writeln (msg) {
trace_txt.text += msg + "\n";
trace_txt.scroll = trace_txt.maxscroll;
}
// LobbyChatConnection( ) is a constructor function for a NetConnection subclass.
// It will not work unless super( ) is called within the function.
function LobbyChatConnection ( ) {
super( );
}
LobbyChatConnection.prototype = new NetConnection( ); // Subclass NetConnection.
LobbyChatConnection.prototype.handleCloseEvents = true; // Initial value.
// connect( ) is always passed four parameters, updates the handleCloseEvents flag,
// and calls the superclass's connect( ) method.
LobbyChatConnection.prototype.connect = function (targetURI, connectionHandler,
userName, password) {
this.connectionHandler = connectionHandler;
this.handleCloseEvents = true;
var result = super.connect(targetURI, userName, password);
if (result) {
writeln("Please wait. Attempting connection...");
}
else {
writeln("Can't attempt connection to: " + this.uri);
writeln("Is the URI correct?");
}
return result;
};
// close( ) turns off the handleCloseEvents flag before closing the connection.
LobbyChatConnection.prototype.close = function ( ) {
this.handleCloseEvents = false;
super.close( );
};
/* onStatus( ) calls the current connectionHandler when a successful connection
* is made. It reports closed connections and errors except when closed
* connections are expected. When a connection is closed, the playhead is
* sent to the Login frame.
*/
LobbyChatConnection.prototype.onStatus = function (info) {
// Always deal with successful connections.
if (info.code == "NetConnection.Connect.Success") {
this.handleCloseEvents = true;
this.connectionHandler( );
}
// Handle messages when the connection is closed.
if (!this.isConnected && this.handleCloseEvents) {
// Always handle rejection messages when they occur.
if (info.code == "NetConnection.Connect.Rejected") {
writeln(info.application.Message);
writeln('Did you use the username "Guest" and password "Guest" ?');
}
else {
writeln("Error: Connection Closed.");
}
this.handleCloseEvents = false;
gotoAndPlay("Login");
}
// Handle remote method call errors here if you need to.
};
// Called when a connection has been established to the testLobby app.
function handleLobbyConnection ( ) {
writeln("Success, you are connected to the lobby!");
gotoAndPlay("Lobby");
}
// Called when a connection has been established to a chat room.
function handleChatConnection ( ) {
writeln("Success, you are connected to a chat room!");
gotoAndPlay("ChatRoom");
}
// Second, create a LobbyChatConnection object.
lobbyChat_nc = new LobbyChatConnection( );
// When the Connect button in the Login frames is clicked, this
// function attempts to connect to the testLobby application.
function doConnect ( ) {
// Keep track of the username and password
_global.userName = userName_txt.text;
_global.password = password_txt.text;
// Try to connect to the lobby.
lobbyChat_nc.connect("rtmp:/testLobby", handleLobbyConnection,
userName, password);
}
// Called when the Lobby button in a chat room is clicked.
function doLobby (btn) {
lobbyChat_nc.close( );
lobbyChat_nc.connect("rtmp:/testLobby", handleLobbyConnection,
userName, password);
}
// Called when the Chat button in the lobby is clicked.
function doChat (btn) {
lobbyChat_nc.close( );
lobbyChat_nc.connect("rtmp:/testChat/room1", handleChatConnection,
userName, password);
}
// Called when the Disconnect button is clicked.
function doDisconnect ( ) {
lobbyChat_nc.close( );
gotoAndPlay("Login");
}
gotoAndPlay("Login");
The onStatus( ) handler in Example 3-9 moves the playhead to the
Login frame itself. If necessary, the more general
purpose mechanism of calling a function could be used in the same way
that the connectionHandler( ) function was
called.
Now let's look at how to create a
NetConnection subclass using ActionScript 2.0.
The FCSConnector class defined in Example 3-10 is just one way to do it. Like the previous
examples, it uses an internal handleCloseEvents
property to remember whether it should report a
"NetConnection.Connect.Closed"
code. It is also designed to take advantage of the event-passing
system introduced in the UI components distributed with Flash MX
2004. Whenever onStatus( ) is called, one of
several possible events is dispatched to any objects that have added
themselves as event listeners. Events are dispatched by the
dispatchEvent( ) method. You may notice in the
listing that the dispatchEvent( ) method is
never defined. In fact, it is added to each
FCSConnector object when the
EventDispatcher class initializes the object in
the FCSConnector( ) function.
Example 3-10. An ActionScript 2.0 NetConnection subclass
class com.oreilly.pfcs.FCSConnector extends NetConnection {
// EventDispatcher needs these.
var addEventListener:Function;
var removeEventListener:Function;
var dispatchEvent:Function;
var dispatchQueue:Function;
// Internal data.
var handleCloseEvents:Boolean = true;
function FCSConnector ( ) {
super( );
mx.events.EventDispatcher.initialize(this);
}
function connectionClosed (type, info) {
if (handleCloseEvents) dispatchEvent({target:this, type:type, info:info});
handleCloseEvents = false;
}
function onStatus (info) {
switch (info.code) {
case "NetConnection.Connect.Success":
dispatchEvent({target:this, type:"onConnect", info:info});
break;
case "NetConnection.Connect.Rejected":
connectionClosed("onReject", info);
break;
case "NetConnection.Connect.Closed":
connectionClosed("onClose", info);
break;
case "NetConnection.Connect.Failed":
connectionClosed("onFail", info);
break;
case "NetConnection.Connect.AppShutdown":
connectionClosed("onClose", info);
break;
case "NetConnection.Connect.InvalidApp":
connectionClosed("onReject", info);
break;
case "NetConnection.Call.Failed":
dispatchEvent({target:this, type:"onCall", info:info});
break;
}
}
function connect ( ) {
handleCloseEvents = true;
return super.connect.apply(super, arguments);
}
function close ( ) {
handleCloseEvents = false;
super.close( );
}
}
The FCSConnector class does not simply create an
onStatus event and pass on the information
object as part of the event. Instead its onStatus(
) method looks at the info.code string
it receives and dispatches one of the following events:
onConnect, onReject,
onFail, onClose, or
onCall. Creating separate events has advantages
and disadvantages. It allows someone using the
FCSConnector class to receive only the events he
is interested in and to write separate functions to handle each type
of event. However, in some applications, it may be less work to
handle a single onStatus event. Example 3-11 lists code for a simple demonstration movie
that uses the FCSConnector class. It imports the
FCSConnector class, stores a single instance of it
in the nc variable, and then adds the main
timeline as a listener for all four connection-related events. Each
event is delivered to a function or method of the same name.
Example 3-11. Importing and using the FCSConnector class in the main timeline of a movie
import com.oreilly.pfcs.FCSConnector;
// Create a new FCSConnector.
nc = new FCSConnector( );
// Add the _root timeline as a listener for connection-related events.
// Note: Since this code is on the main timeline this refers to _root.
nc.addEventListener("onReject", this);
nc.addEventListener("onFail", this);
nc.addEventListener("onClose", this);
nc.addEventListener("onConnect", this);
// Handle "NetConnection.Connect.Failed" messages here.
function onFail (ev) {
var info = ev.info;
writeln("Can't reach the server.")
writlen("Please check your network connection and try again.");
writeln("info.code: " + info.code);
connectButton.label = "Connect";
}
// Handle "NetConnection.Connect.Success" messages here.
function onConnect (ev) {
var info = ev.info;
writeln("You are now connected.");
writeln("info.code: " + info.code);
connectButton.label = "Disconnect";
}
// Handle "NetConnection.Connect.Rejected" messages here.
function onReject (ev) {
var info = ev.info;
writeln("Your connection attempt was rejected.");
writeln("info.code: " + info.code);
writeln("info.description: " + info.description);
if (info.application) {
for (var p in info.application) {
writeln("info.application." + p + ": " + info.application[p]);
}
}
connectButton.label = "Connect";
}
// Handle "NetConnection.Connect.Closed" messages here.
function onClose (ev) {
var info = ev.info;
writeln("Your connection was closed.");
writeln("info.code: " + info.code);
connectButton.label = "Connect";
}
connectButton.addEventListener("click", this);
function click (ev) {
var button = ev.target;
if (button.label == "Connect") {
if (nc.connect("rtmp:/testLobby",
userNameTextInput.text,
passwordTextInput.text)) {
button.label = "Wait...";
writeln("Attempting to connect...");
}
else {
writeln("Can't attempt connection. Check the URI.");
}
}
else {
writeln("Connection closed.");
button.label = "Connect";
nc.close( );
}
}
function writeln (msg) {
msgTextArea.text += msg + "\n";
msgTextArea.vPosition = msgTextArea.maxVPosition;
msgTextArea.redraw( );// Fixes scrolling bug in TextArea as of Flash 7.2.
}
All the preceding examples are available in a Zip archive file from
http://flash-communications.net.
The archive includes the file
com/oreilly/pfcs/FCSConnector.as. That file must
be extracted from the archive with its path intact (most unZip
programs have a Maintain Folders option for this purpose) and placed
in a directory in the classpath of Flash MX 2004. See Chapter 9 of Essential ActionScript
2.0 (O'Reilly) for more information on
packages and the classpath.
|