3.7. Communication Components Without SimpleConnect
Chapter 2 showed how applications can be created using Macromedia's communication components. Macromedia supplies the SimpleConnect component to manage a network connection and connect all the other components to it. SimpleConnect allows users to log in using any name when they connecteven the same name someone else is using. If you need to develop an application that manages user identities differently but want to use Macromedia's communication components, there are two options. One is to write your own connection component. Chapter 13 through Chapter 15 describe how to build custom components. The other option is not to use a connection component at all, as illustrated in the final example in Chapter 2. The following example uses a little server-side scripting, a NetConnection subclass, and the communication components to demonstrate creating a basic chat room application with separate login and chat screens. The application enforces unique usernames, doesn't allow name changes while connected, and does not permit lurking. It is not designed to provide a lobby and multiple chat rooms. These and other enhancements are added in later chapters.
To use the communication components without SimpleConnect, an application's main.asc file must load the component framework and store a username for every client that connects within the framework. A minimal main.asc file is shown in Example 3-12.
load("components.asc"); application.onConnect = function (client, userName) { gFrameworkFC.getClientGlobals(client).username = userName; application.acceptConnection(client); };
Example 3-12 allows anyone to connect with any username. To reject connections in which the username is blank or already in use requires a little more work. Example 3-13 shows a listing of another main.asc file. The script uses an object named users to keep track of the userName associated with each Flash movie. (An object such as users , in which the item name is used to access array elements, is known as an associative array , hash table , or simply hash .) The trim( ) function is used to preprocess each userName before checking whether it is null, an empty string, or already in the users object.
load("components.asc"); // Trim any whitespace from before or after the userName. // SSAS supports regular expressions, but client-side ActionScript does not. function trim (str) { if (typeof str != "string") return ""; // Make sure str is a string. str = str.replace(/^\s*/, ""); // Trim leading spaces. str = str.replace(/\s*$/, ""); // Trim trailing spaces. str = str.replace(/\n/g, ""); // Remove new lines. str = str.replace(/\r/g, ""); // Remove carriage returns. str = str.replace(/\t/g, ""); // Remove tabs. return str; } // Hash of client objects using the userName as a property name. users = {}; // The onConnect( ) method rejects connection attempts // where userName is invalid text or is already in use. application.onConnect = function (client, userName) { userName = trim(userName); // Remove leading and trailing whitespace. if (userName.length == 0) { // If it is empty, reject it. application.rejectConnection(client, {msg: "Empty username."}); return; } if (users[userName]) { // If it is in use already, reject it. application.rejectConnection(client, {msg: 'The username "' + userName + '" is already in use.'}); return; } // Store a reference to the client in the users hash. users[userName] = client; gFrameworkFC.getClientGlobals(client).username = userName; application.acceptConnection(client); }; // When a client disconnects, remove the username from the // users hash so someone can use it again. application.onDisconnect = function (client) { var userName = gFrameworkFC.getClientGlobals(client).username; delete users[userName]; };
Placing the main.asc file from Example 3-13 into an applications subdirectory named netConnectChat allows this server-side main.asc script to control the netConnectChat application. If you were developing the main.asc script, you could always test it with a test client but let's push on to create the client-side part of the netConnectChat application in the next section.
Let's build a Flash movie to connect to our brand new netConnectChat application. For now, we'll stick with ActionScript 1.0 and the v1 UI components on which Macromedia's communication components rely. In Chapter 13, when we develop our own custom communication components, we'll switch to ActionScript 2.0 and the v2 UI components.
Using Macromedia's communication components without SimpleConnect is a two-step process: establish a network connection to the application instance and then pass the NetConnection object to each component's connect( ) method. The netConnectChat application uses the PeopleList, Chat, and UserColor components to create a simple text chat interface. The interface can be created with everything on a single frame, as is popular when using the SimpleConnect component, or the different states of the movie can be spread over the timeline. The book's web site includes an example of a single-frame movie. In this example, we'll build the client-side movie for this application using the timeline. Figure 3-4 shows the timeline and Stage when the movie is on the Chat frame.
The Chat frame includes the following movie clips (components) and text field:
A Chat component instance
A PeopleList component instance
A UserColor component instance
The text field at the bottom of the Stage
The Connect button at the bottom of the Stage
The Login frame contains only the userName_txt field and connect_btn button. If we were using the SimpleConnect component to log in the user, any other communication component would have to exist throughout the same timeline frames as SimpleConnect. In this example, the communication components do not need to be on the same frames as the username field or the Connect button. The components need only a NetConnection to function, so the username field and Connect button don't have to be included in the Chat frame either. However, in the Chat state, we change the Connect button label to Disconnect; the button logs out the user when clicked. The username text field is kept on the Stage to show the current user's name, but it is disabled during the chat.
The Scripts layer contains all but two lines of the script for the movie. The three important frames are the Init frame, which contains most of the script, and the Login and Chat frames. When the script on the Init frame has completed executing, the playhead moves to the Login frame. There is only one line of code on the Login frame of the Scripts layers :
startLoginState( );
This statement calls the startLoginState( ) function declared in the first frame of the timeline to initialize the elements on the Stage:
function startLoginState ( ) { connect_btn.setLabel("Connect"); userName_txt.selectable = true; }
Similarly, there is only a single function call on the Chat frame:
startChatState( );
The startChatState( ) function, also declared on the first frame, initializes the elements in the Chat frame:
function startChatState ( ) { connect_btn.setLabel("Disconnect"); userName_txt.selectable = false; peopleList_mc.connect(nc); chat_mc.connect(nc); userColor_mc.connect(nc); }
The label on the Connect button is reset as necessary by both functions, and the selectable property of the username field is used to disable and enable the userName_txt field.
When the playhead moves to the Chat frame, the communication components are created because that is the first frame in which they exist. But they also need to be connected to a NetConnection object or a NetConnection subclass to operate . The startChatState( ) function passes the global nc object to the connect( ) method of each component for this purpose.
The remaining scripts on the timeline (frames 4, 14, and 24) contain only a stop( ) function call. In addition to the components and movie clips placed directly on the Stage, the Library also contains an AlertBox movie clip symbol. It contains a MessageBox component named alert_mc from the Flash UI components set 2 and provides a way to display pop-up messages. The AlertBox movie clip's timeline contains code to set the title of the MessageBox and set its message text:
alert_mc.setTitle("Alert"); alert_mc.setMessage(message);
Example 3-14 shows the script from the main timeline (see timelineComponentConnect.as on the book's web site):.
// A simple alert function and variables to pop a message dialog box on stage. alertLevel = 10; // Level on which to display the alert dialog box. upperLeft = 10; // _x and _y position of the alert dialog box. // alert( ) displays msg in a pop-up alert dialog box. function alert (msg) { attachMovie("AlertBox", "abox" + alertLevel, alertLevel, {_x: upperLeft, _y: upperLeft, message: msg} ); // Increment the alertLevel and upperLeft position. alertLevel += 1; upperLeft += 10; if (upperLeft > 150) upperLeft = 10; } /* ChatConnection is a subclass of NetConnection designed to move the playhead * to the Chat frame when a connection is made and move it back to the Login * frame when it is lost. */ function ChatConnection ( ) { super( ); } ChatConnection.prototype = new NetConnection( ); // Subclass NetConnection . ChatConnection.prototype.handleCloseEvents = true; // Initial value. // connect( ) turns on the handleCloseEvents flag before calling super.connect( ) . ChatConnection.prototype.connect = function ( ) { this.handleCloseEvents = true; var result = super.connect.apply(super, arguments); if (!result) { alert("Invalid target URI: " + this.uri); } return result; }; // close( ) turns off the handleCloseEvents flag before closing the connection. ChatConnection.prototype.close = function ( ) { this.handleCloseEvents = false; super.close( ); }; /* onStatus( ) reports closed connections and errors except when closed * connections are expected. When a connection is closed, the playhead is * sent to the Login frame. When it is opened, it is sent to the Chat frame. */ ChatConnection.prototype.onStatus = function (info) { if (info.code == "NetConnection.Connect.Success") { gotoAndPlay("Chat"); } else if (!this.isConnected) { if (this.handleCloseEvents) { var msg; if (info.code == "NetConnection.Connect.Rejected") { msg = 'Connection Rejected!'; if (info.application) { msg += '\n' + info.application.msg; } } else { msg = 'Error: Connection ' + info.code.split(".").pop( ); } alert(msg); this.handleCloseEvents = false; gotoAndPlay("Login"); } } }; // Main timeline button handler functions. /* doConnect( ) is called whenever connect_btn is clicked. If the button label * is Connect, the NetConnection.connect( ) method is called. If the label * is Disconnect, the close( ) method is called. */ function doConnect (btn) { if (btn.getLabel( ) == "Connect") { if (!nc.isConnected) { nc.connect("rtmp:/netConnectChat", userName_txt.text); btn.setLabel("Waiting..."); } } else if (btn.getLabel( ) == "Disconnect") { if (nc.isConnected) { userColor_mc.close( ); peopleList_mc.close( ); chat_mc.close( ); nc.close( ); gotoAndPlay("Login"); } } } // Main timeline state change functions. // Called after the Chat frame is entered to connect communication components // and change the appearance or behavior of other Flash components. function startChatState ( ) { connect_btn.setLabel("Disconnect"); userName_txt.selectable = false; peopleList_mc.connect(nc); chat_mc.connect(nc); userColor_mc.connect(nc); } // Called after the Login frame is entered to reset login components. function startLoginState ( ) { connect_btn.setLabel("Connect"); userName_txt.selectable = true; } // Create the chat connection object. _global.nc = new ChatConnection( ); gotoAndPlay("Login");
If you test the movie in multiple browser windows , you'll see that you can log in only if the specified username is unique.
Other than that the fact the alert( ) method has replaced writeln ( ) and that state change functions have been added, there should be little here that isn't familiar from the previous examples in this chapter. One difference is in the onStatus( ) handler, which reports some errors a little differently:
msg = 'Error: Connection ' + info.code.split(".").pop( );
The info.code property always contains a dot-delimited string. The preceding statement uses the split( ) method to split the string into an array and then the array pop( ) method to return the last part of the array. If info.code returns a message such as "NetConnection.Connect.Failed", the preceding statement appends the string "Failed" to "Error: Connection". When a code property is provided, the first term in the string is always the class of object with which the event is associated. The second term is usually the name of the action attempted, and the third is the result. Table 3-2 shows all the NetConnection information object code and level values as of FlashCom 1.5.
Code | Level | Meaning |
---|---|---|
NetConnection.Call.Failed | Error | A remote method call was attempted on this connection and failed. The info.description property contains more details including the name of the method. Remote method calls are discussed in Chapter 9. |
NetConnection.Connect.AppShutdown | Error | This message is supposed to be returned when an application instance is forced to shut downfor example when it is out of memory. However, as of FlashCom 1.5, this message is never received; "NetConnection.Connect.Closed" is received instead. |
NetConnection.Connect.Closed | Status | Connection was closed by the server or the client. As of FlashCom 1.5, despite the level value of "status", it may indicate an error such as an application instance shutting down because it is using too much memory. |
NetConnection.Connect.Failed | Error | The connection attempt failed. The server may not have been reachable or may be down. |
NetConnection.Connect.InvalidApp | Error | This message is supposed to be returned when an application name is not registered on the server. However, as of FlashCom 1.5, this message is never received; "NetConnection.Connect.Closed" is received instead. |
NetConnection.Connect.Rejected | Error | The attempt to connect was rejected by the server or by the application on the server. The reason the client was rejected depends on several factors. For example, the application may reject the connection because the user's credentials are invalid. The application may return an application object as a property of the information object. The info.description property will contain useful information if the application name was not found, if the resource limits of the application were exceeded, or if the server license does not allow more connections or the usage of more bandwidth. See Table 3-3. |
NetConnection.Connect.Success | Status | A connection has been established. |
The info.description property is often empty but for some messages contains important additional information. When the server returns a "NetConnection.Connect.Rejected" message, either the server or the application has rejected the connection. When an application rejects a connection, the server-side script writer has the option to return an application object to explain why. When the server rejects a connection, it returns information in the description property that explains why. A rejected description message looks like this:
[ Server.Reject ] : (_defaultRoot_, _defaultVHost_) : Application (appName) is not defined.
Table 3-3 lists the three types of server rejection messages included in the description property.
Message | Meaning |
---|---|
Server.Reject | The server rejected the connection because the application did not exist, because of a configuration error, or because of some other problem such as bad network data. |
Resource.Limit.Exceeded | The resource limits for the server, virtual host, or application have been exceeded. For example, the maximum allowed number of instances or shared objects has been exceeded. |
License.Limit.Exceeded | The number of simultaneous client connections or bandwidth limit has been exceeded. See the Preface for FlashCom licensing information. |