back to uSimpleChat tutorial home  back to  
unify the web

unity 2 uSimpleChat tutorial, actionscript 1.0 version

PART 4

The fourth version of uSimpleChat will show how to:

In the first three versions of uSimpleChat, we created our chat room in the udefault namespace. In this version, we'll give the chat room its very own namespace. By providing our application with its own namespace, we ensure that no other application will interfere with our chat room. This allows multiple projects to be developed and deployed on the same Unity server. It also provides a way to group rooms logically, so that they can be manipulated en masse. For example, if a chat application keeps all its sports-related rooms in a namespace called "moockchat.sports", then an administration tool can easily be written to send a message to all rooms related to sports ("ALERT: Wayne Gretzky's chatting in the Hockey room right now!!").

The addition of a custom namespace changes our application logic somewhat. In uSimpleChat version 4, the client-side application logic is:

  1. Connect to server.
  2. When connection succeeds, create namespace usimplechat.
  3. When client-side NameSpace object is created for simplechat:
    • Register to listen to its events.
    • Create room simplechat.chat.
  4. When simplechat NameSpace object reports new chat room:
    • Register to listen to chat room's events.
    • Join chat room.
  5. When chat room reports that it was joined, display chat interface.

In addition to the above revised logic, this version adds new features such as a client list and user names. Those features do not affect the basic logic involved in connecting to the server, creating a room, and joining it. In fact, even very complex Unity applications generally do not deviate much from the basic architecture of uSimpleChat version 4. This version of our chat application makes an excellent boilerplate for many kinds of multiuser apps.

Creating the simplechat Namespace
Unlike the previous two versions of uSimpleChat, version 4's USimpleChat.onClientReady() method does not tell Unity to create the chat room. Instead, it only tells Unity to create the simplechat namespace, and then waits to be notified when the corresponding client-side NameSpace object is created by the RoomManager. We use the RoomManager.createNamespaceOnServer() method to create the namespace. Here's the code for USimpleChat.onClientReady(), version 4:

org.moock.unity.simplechat.v4.USimpleChat.prototype.onClientReady 
                                                            = function () {
  // Make a new namespace, "simplechat", and start observing it.
  this.getRoomManager().createNamespaceOnServer("simplechat", true);

  // Tell the user what's happening.
  this.getTargetMC().status_txt.text 
                         = "Connected to Unity. Joining simplechat room...";
}

In the call to createNamespaceOnServer(), the second argument (true) specifies that the client wants to "observe" the namespace. When a namespace is observed by a client, Unity automatically notifies that client of changes to the namespace (i.e., additions or removals of rooms or subnamespaces). For example, later, when we're informed that the simplechat NameSpace has been created, we'll add the chat room to it. In response, Unity will notify us that the room was added. The RoomManager will automatically create the corresponding client-side URoom instance, and the simplechat NameSpace instance will invoke onAddRoom() on all its listeners (in our case, a SimpleChatNamespaceView instance).

Notice that we don't bother implementing the RoomManagerListener.onCreateNamespaceResults() method. That method tells us whether the namespace creation was successful or not. However, UClient already implements onCreateNamespaceResults(), and outputs the results to the client log. (We'll learn how to enable the client log later in this tutorial.) For our purposes, logging is all we need. If there's a problem creating the namespace, we can check the log and attempt to debug. Of course, if the namespace could not be created, it would also be nice to display a generic error message to the user, but that's left as an exercise for the reader. Hint: implement USimpleChat.onCreateNamespaceResults(), and move the main timeline to an error frame in the event of a failed namespace creation attempt.

Creating the simplechat.chat Room
When our client-side simplechat NameSpace instance is created, USimpleChat.onAddNamespace() executes. From that method, we'll register an event listener for the NameSpace, just like we did version 3. This time, Our listener will be an instance of the SimpleChatNamespaceView class, which bears much in common with version 3's UdefaultNamespaceView class. In addition to registering a NameSpace listener, the version 4 USimpleChat.onAddNamespace() will create our chat room in the new namespace.

Here's the code for USimpleChat.onAddNamespace():

org.moock.unity.simplechat.v4.USimpleChat.prototype.onAddNamespace 
                                                            = function (e) {
  // Invoke superclass's version of this method to preserve logging.
  super.onAddNamespace(e);

  // Retrieve a reference to the new NameSpace object.
  var ns = this.getRoomManager().getNamespace(e.getNamespaceID());

  if (e.getNamespaceID() == "simplechat") {
    // Register the "simplechat" NameSpace listener. Nothing new here.
    ns.addNamespaceListener(
             new org.moock.unity.simplechat.v4.SimpleChatNamespaceView(ns));

    // This is new to version 4: create a room in the new namespace.
    this.getRoomManager().createRoomOnServer("chat", "simplechat",
                                             true, true, 50, null, null);
  } else {
    this.log.warn("Unexpected namespace added to chat application.");
  }
}
Notice that the arguments to our createRoomOnServer() method have changed since version 3. This time, the third and fourth arguments are true instead of false, indicating that:

We'll respond to clients joining and leaving the room from our ChatRoomView class, which will implement the URoomListener.onAddClient() and URoomListener.onRemoveClient() methods.

Joining the simplechat.chat Room
If the server successfully creates the simplechat.chat room, it will notify all clients observing the simplechat namespace that the room was added. We respond to that notification from the SimpleChatNamespaceView.onJoin() method (exactly like we did in version 3 with UdefaultNamespaceView.onJoin()). The onJoin() method has three tasks: 1) create a new ChatRoomView instance, 2) registers the ChatRoomView instance to receive events from the chat room, 3) join the chat room.

Here's the code for SimpleChatNamespaceView:

// CLASS SimpleChatNamespaceView EXTENDS NamespaceView
org.moock.unity.simplechat.v4.SimpleChatNamespaceView 
                                            = function (namespace) {
  super(namespace);
}

// EXTEND SUPERCLASS
org.moock.unity.simplechat.v4.SimpleChatNamespaceView.prototype
  = new org.moock.unity.NamespaceView();

// METHODS
  /**
   * NamespaceListener event handler
   */
org.moock.unity.simplechat.v4.SimpleChatNamespaceView.prototype.onAddRoom
                                                              = function (e) {
    // When a new room is added to this namespace...

    // If it's the chat room...
    if (e.getRoomID() == "chat") {
      // First get a reference to the room object.
      var room = this.ns.getRoom(e.getRoomID());

      // Then create a view for the room and register it to 
      // receive the room's events.
      room.addURoomListener(
                        new org.moock.unity.simplechat.v4.ChatRoomView(room));

      // Finally, join the room.
      room.join();
    }
  }

Displaying the Chat Interface
We respond to the joining of the simplechat.chat room via ChatRoomView.onJoin(). That method has two responsibilities: 1) move the main timeline's playhead to the frame labeled "simpleChatInterface", and 2) create the ListBox component instance that will display the user list for the room. Note that we're forced to create the ListBox programmatically rather than placing it on the Stage manually (with the rest of the chat interface). If we were to place the component on the Stage manually, its methods wouldn't be initialized until a frame had passed, which would prevent us from adding user names to it for the duration of one frame.

Here's the code for onJoin():

org.moock.unity.simplechat.v4.ChatRoomView.prototype.onJoin = function (e) {
    // If the room join attempt was successful...
    if (e.getStatus() == "ROOM_JOINED") {
      // Display the interface frame.
      this.client.getTargetMC().gotoAndStop("simpleChatInterface");

      // Attach the users listbox. Have to do this at runtime so the listbox
      // methods are available immediately. Otherwise, we'd have to wait a 
      // frame for the listbox to initialize itself.
      this.users_lb = this.client.getTargetMC().attachMovie("FListBoxSymbol",
                                                            "users_lb",
                                             this.client.getNewTargetDepth());

      this.users_lb._x = 409;
      this.users_lb._y = 163;
      this.users_lb.setSize(117, 156);

      // Tell the user the application is ready to use.
      this.client.getTargetMC().status_txt.text 
                                      = "Chat now active. Send your message.";
    } else {
      this.client.getTargetMC().status_txt.text 
                       = "Could not join chat room. Status: " + e.getStatus();
    }
  }

Displaying the User List
Whenever a client joins the chat room, ChatRoomView.onAddClient() executes. Whenever a client leaves the chat room, ChatRoomView.onRemoveClient() executes. Those two methods let us populate the user list for the room. Immediately after we join a room, onAddClient() executes once for each client in the room at the time we joined it.

Like all other event methods in the uClientCore API, the onAddClient() and onRemoveClient() methods are passed an event object, e, that provides information about the event. The event object passed to onAddClient() and onRemoveClient() contains the id of the client that was added or removed. In the simplest case, we could add that ID to, and remove it from our ListBox as follows:

// Add.
this.users_lb.addItem(e.getClientID());

// Remove.
var clientID:String = e.getClientID();
for (var i:Number = this.users_lb.getLength(); --i >= 0;) {
  if (this.users_lb.getItemAt(i).label == clientID) {
    this.users_lb.removeItemAt(i);
    return;
  }
}

However, the above code would result in a user list containing only numeric user ids. Not very much fun. Our application allows users to set their name as a "shared client attribute". Client attributes for a particular client are retrieved via the RemoteClient.getAttribute() method. For example, the following code retrieves a global shared client attribute named "username" for a RemoteClient instance (the null argument indicates that the scope of the client attribute is global):

theRemoteClient.getAttribute(null, "username");

RemoteClient instances themselves are retrieved via the RemoteClientManager.getClient() method, which takes the client ID for the RemoteClient to retrieve as its only argument. The RemoteClientManager for an application is accessed via the UClient.getRemoteClientManager() method. Hence, to retrieve a remote client with ID 45, we use:

theClient.getRemoteClientManager().getClient(45);

Here, then, is the ChatRoomView.onAddClient() code that retrieves the username attribute for a client that has been added to the room:

var remoteuser = this.client.getRemoteClientManager().getClient(e.getClientID());
var username = remoteuser.getAttribute(null, "username");

Once the username has been retrieved, it is displayed in the users_lb ListBox, via the addItem() method. The label for the ListBox item is the username, and the data for the ListBox item is the client id. This allows us to operate on items in the list by client id, which is guaranteed to be unique.

Here's the complete code for the onAddClient() method:

org.moock.unity.simplechat.v4.ChatRoomView.prototype.onAddClient 
                                                            = function (e) {
  // Retrieve the username for the client that just joined.
  var remoteuser = this.client.getRemoteClientManager().getClient(e.getClientID());
  var username = remoteuser.getAttribute(null, "username");

  // Use the client id as a user name if the user hasn't set a name.
  if (username == undefined) {
    username = "User" + e.getClientID();
  }

  // Add the new user to the list box.
  this.users_lb.addItem(username, e.getClientID());
  this.users_lb.sortItemsBy("label", "asc");
}

When a client leaves the room, we must remove it from the ListBox. That's a little bit tricker. The ListBox component does not provide a means of removing an item according to a specific data value. Instead, we have to manually find the data value we're looking for, determine its index in the list, and remove it. The onRemoveClient() method, shown next, does just that: it removes an item for a particular client id from the users_lb ListBox.

org.moock.unity.simplechat.v4.ChatRoomView.prototype.onRemoveClient 
                                            = function (e) {
  var clientID:String = e.getClientID();
  for (var i:Number = this.users_lb.getLength(); --i >= 0;) {
    if (this.users_lb.getItemAt(i).data == clientID) {
      this.users_lb.removeItemAt(i);
      return;
    }
  }
}

Setting a User Name
Our application allows each client to set a shared client attribute, username, that stores the client's chosen user name. When the attribute is defined or changed, all clients in room at the time will be notified of the new value. We'll define the username attribute as a global attribute, which means that if the client is in multiple rooms, all clients in all those rooms will be notified when the attribute changes. A client attribute can also be scoped to a particular room, so that only clients in that room are notified when it changes. For details, see UClient.setClientAttribute().

To allow end-users to set a user name, we'll add a new text field (nameInput_txt) and a new button (setname_pb) to our uSimpleChat.fla file. We'll use the method UClient.setClientAttribute() to set the username attribute, but we'll wrap that method in a USimpleChat method called setName(). To wire setname_pb button clicks to the USimpleChat.setName() method, we'll add the following code to our .fla, at the frame labeled "simpleChatInterface":

setname_pb.setClickHandler("setName", sc);
Here's the setName() method:
org.moock.unity.simplechat.v4.USimpleChat.prototype.setName 
                                                             = function () {
  if (this.getTargetMC().nameInput_txt.text.length > 0) {
    this.setClientAttribute("username", 
                            this.getTargetMC().nameInput_txt.text, 
                            null, 
                            true, 
                            false, 
                            false);
    this.getTargetMC().nameInput_txt.text = "";
  }
}
The arguments to setClientAttribute() have the following meaning:

When the username attribute is set, ChatRoomView.onUpdateClientAttribute() automatically fires on all clients in the simplechat.chat room at the time. That method is responsible for updating the user name displayed in the users_lb list box.

Here's the code for ChatRoomView.onUpdateClientAttribute(). Notice that it subcontracts the work of updating the users_lb list box to another method, renameUser() (shown following).

org.moock.unity.simplechat.v4.ChatRoomView.prototype.onUpdateClientAttribute 
                                                            = function (e) {
  // URoomEvent.getChangedAttr() returns an object whose properties
  // specify the changed attribute's name (attrName) and value (attrVal).
  var attrName = e.getChangedAttr().attrName;
  var attrVal  = e.getChangedAttr().attrVal;

  // If the username attribute was changed, display it on screen.
  if (attrName == "username") {
    this.renameUser(e.getClientID(), attrVal); 
  }
}

org.moock.unity.simplechat.v4.ChatRoomView.prototype.renameUser 
                                            = function (clientID, newName) {
  for (var i = this.users_lb.getLength(); --i >= 0;) {
    if (this.users_lb.getItemAt(i).data == clientID) {
      this.users_lb.replaceItemAt(i, newName, clientID);
      this.users_lb.sortItemsBy("label", "asc");
    }
  }
}

With the ability to set a user name in place, the uSimpleChat version 4 application is functionally complete. However, as a final bit of polish for our application, we'll add code to handle a connection failure and to enable the application log.

Handling Connection Failure
When a connection failure occurs, the application's SocketManager automatically invokes one of three methods on USimpleChat:

Here's the implementation for those methods in USimpleChat, version 4.

org.moock.unity.simplechat.v4.USimpleChat.prototype.onConnectFailure
                                                            = function (e) {
  // Invoke superclass's version of this method to preserve logging.
  super.onConnectFailure(e);
  this.getTargetMC().status_txt.text = "Connection to Unity failed.";
  this.getTargetMC().gotoAndStop("disabled");
}

org.moock.unity.simplechat.v4.USimpleChat.prototype.onServerKillConnect 
                                                            = function (e) {
  // Invoke superclass's version of this method to preserve logging.
  super.onServerKillConnect(e);
  this.getTargetMC().status_txt.text = "Unity has closed the connection.";
  this.getTargetMC().gotoAndStop("disabled");
}

org.moock.unity.simplechat.v4.USimpleChat.prototype.onClientKillConnect 
                                                            = function (e) {
  // Invoke superclass's version of this method to preserve logging.
  super.onClientKillConnect(e);
  this.getTargetMC().status_txt.text = "Connection to Unity closed.";
  this.getTargetMC().gotoAndStop("disabled");
}

In each of the three disconnection scenarios, we'll display a frame labeled "disabled", which contains a button the user can use to reconnect.

Here's the disabled frame in uSimpleChat.fla:

To make the reconnect_pb button attempt to reconnect to Unity when clicked, we add the following code to the "scripts" layer of the "disabled" frame:

reconnect_pb.setClickHandler("connect", sc);

Cleaning Up After the Party
When a connection is lost, all NameSpace and URoom instances are automatically removed from the client. Immediately before each URoom is deleted, it fires onLeave() on its listeners. From that method, each room view object is expected to clean up any resources it may have created. In the case of our ChatRoomView class, we must remove the users_lb we created when the chat room was joined. Here's the ChatRoomView.onLeave() method, which does just that.

org.moock.unity.simplechat.v4.ChatRoomView.prototype.onLeave = function () {
  this.users_lb.removeMovieClip();
}

We didn't create any resources in the SimpleChatNamespaceView class, so we don't need to do anything special when the simplechat namespace is deleted. If we had needed to clean up after that namespace, we'd have implemented the SimpleChatNamespaceView.onDie() method.

Enabling the Client Log
As a final feature in our application, we'll add an application log, which will display lots of information about what's happening internally in the uClientCore classes. The log is enabled in the call to the USimpleChat constructor (on the frame labeled "main"). The last argument of the constructor call is set to false, meaning "don't disable the log", as follows:

var sc = new org.moock.unity.simplechat.v4.USimpleChat(this,
                                                       null,
                                                       null,
                                                       "uSimpleChatConfig.xml",
                                                       false);

Within the constructor function itself, the log display is customized with the following code:

this.logPanel.setPosition(17, 376);
this.logPanel.setSize(511, 250);
this.logPanel.minimize();

The above code controls the logPanel movie clip instance, which is dynamically created automatically by the UClient class when the log is enabled.

Mission Complete
Over four stages, we've created a solid, reasonably sophisticated multiuser application. The listings for the final application classes are provided below. This is the end of the tutorial but it should only be the beginning of your own exploration. If you create something interesting with Unity, let us know at unity@moock.org! We may include your project in the Unity Application Showcase.




Code Listing for USimpleChat Class, Version 4

// PACKAGE
org.moock.unity.simplechat = new Object();
org.moock.unity.simplechat.v4 = new Object();

// CLASS USimpleChat EXTENDS UClient
org.moock.unity.simplechat.v4.USimpleChat = function (target, 
                          host, 
                          port, 
                          configURL, 
                          disableLog) {
  // Invoke UClient constructor.
  super(target, host, port, configURL, disableLog);

  // Adjust the display of the log panel.
  this.logPanel.setPosition(17, 376);
  this.logPanel.setSize(511, 250);
  this.logPanel.minimize();

  // Listen for key presses so we can submit messages
  // when the user presses ENTER.
  Key.addListener(this);

  // Tell the user we're connecting.
  this.getTargetMC().status_txt.text = "Connecting to Unity...";
}

// EXTEND SUPERCLASS
org.moock.unity.simplechat.v4.USimpleChat.prototype 
                                             = new org.moock.unity.UClient();

// METHODS
  /**
   * UClient event handler
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.onClientReady 
                                                              = function () {
    // Make a new namespace, "simplechat", and start observing it.
    this.getRoomManager().createNamespaceOnServer("simplechat", true);

    // Tell the user what's happening.
    this.getTargetMC().status_txt.text 
                           = "Connected to Unity. Joining simplechat room...";
  }

  /**
   * SocketListener event handler
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.onConnectFailure
                                                              = function (e) {
    // Invoke superclass's version of this method to preserve logging.
    super.onConnectFailure(e);
    this.getTargetMC().status_txt.text = "Connection to Unity failed.";
    this.getTargetMC().gotoAndStop("disabled");
  }

  /**
   * SocketListener event handler
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.onServerKillConnect 
                                                              = function (e) {
    // Invoke superclass's version of this method to preserve logging.
    super.onServerKillConnect(e);
    this.getTargetMC().status_txt.text = "Unity has closed the connection.";
    this.getTargetMC().gotoAndStop("disabled");
  }

  /**
   * SocketListener event handler
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.onClientKillConnect 
                                                              = function (e) {
    // Invoke superclass's version of this method to preserve logging.
    super.onClientKillConnect(e);
    this.getTargetMC().status_txt.text = "Connection to Unity closed.";
    this.getTargetMC().gotoAndStop("disabled");
  }

  /**
   * RoomManager event handler
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.onAddNamespace 
                                                              = function (e) {
    // Invoke superclass's version of this method to preserve logging.
    super.onAddNamespace(e);

    // Retrieve a reference to the new NameSpace object.
    var ns = this.getRoomManager().getNamespace(e.getNamespaceID());

    if (e.getNamespaceID() == "simplechat") {
      ns.addNamespaceListener(
               new org.moock.unity.simplechat.v4.SimpleChatNamespaceView(ns));
      this.getRoomManager().createRoomOnServer("chat", "simplechat",
                                               true, true, 50, null, null);
    } else {
      this.log.warn("Unexpected namespace added to chat application.");
    }
  }

  /**
   * Takes user input from outgoing_txt and sends it to all clients
   * in the room "udefault.chat".
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.sendMessage 
                                                               = function () {
    // Only send the message if there's text
    // in the outgoing_txt text field.
    if (this.getTargetMC().outgoing_txt.text.length > 0) {
      // The message typed by the user.
      var msg = this.getTargetMC().outgoing_txt.text;

      // The message we'll send to the server.
      var safeMsg = '<![CDATA[' + msg + ']]>';

      // Send the message to the server.
      this.invokeOnRoom("displayMessage", "simplechat.chat", true, safeMsg);

      // Clear the user input text field.
      this.getTargetMC().outgoing_txt.text = "";
    }
  }

  /**
   * Displays text sent by a client.
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.displayMessage 
                                                  = function (clientID, msg) {
    // Retrieve the username for the client that sent the message.
    var remoteuser = this.getRemoteClientManager().getClient(clientID);
    var username = remoteuser.getAttribute(null, "username");

    // Use the client id as a user name if the user hasn't set a name.
    if (username == undefined) {
      username = "User" + clientID;
    }

    // Display the message on screen.
    this.getTargetMC().incoming_txt.text += username + ": " + msg + "\n";

    // Scroll the incoming_txt text field to the bottom.
    this.getTargetMC().incoming_txt.scroll = this.getTargetMC().incoming_txt.maxscroll;
  }

  /**
   * Sets the username attribute for this client.
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.setName 
                                                               = function () {
    if (this.getTargetMC().nameInput_txt.text.length > 0) {
      this.setClientAttribute("username", 
                              this.getTargetMC().nameInput_txt.text, 
                              null, 
                              true, 
                              false, 
                              false);
      this.getTargetMC().nameInput_txt.text = "";
    }
  }


  /**
   * Key event handler.
   * Checks for ENTER key. Sends a message if the outgoing text field is focused.
   */
  org.moock.unity.simplechat.v4.USimpleChat.prototype.onKeyDown
                                                               = function () {
    if (Key.getCode() == Key.ENTER) {
      if (Selection.getFocus()
          == targetPath(this.getTargetMC().outgoing_txt)) {
        this.sendMessage();
      } else if (Selection.getFocus() 
                 == targetPath(this.getTargetMC().nameInput_txt)) {
        this.setName();
      }
    }
  }

Code Listing for SimpleChatNamespaceView Class, Version 4

// CLASS SimpleChatNamespaceView EXTENDS NamespaceView
org.moock.unity.simplechat.v4.SimpleChatNamespaceView 
                                            = function (namespace) {
  super(namespace);
}

// EXTEND SUPERCLASS
org.moock.unity.simplechat.v4.SimpleChatNamespaceView.prototype
  = new org.moock.unity.NamespaceView();

// METHODS
  /**
   * NamespaceListener event handler
   */
org.moock.unity.simplechat.v4.SimpleChatNamespaceView.prototype.onAddRoom
                                                              = function (e) {
    // When a new room is added to this namespace...

    // If it's the chat room...
    if (e.getRoomID() == "chat") {
      // First get a reference to the room object.
      var room = this.ns.getRoom(e.getRoomID());

      // Then create a view for the room and register it to 
      // receive the room's events.
      room.addURoomListener(
                        new org.moock.unity.simplechat.v4.ChatRoomView(room));

      // Finally, join the room.
      room.join();
    }
  }

Code Listing for ChatRoomView Class, Version 4

// CLASS ChatRoomView EXTENDS URoomView
org.moock.unity.simplechat.v4.ChatRoomView = function (room) {
  super(room);
}

// EXTEND SUPERCLASS
org.moock.unity.simplechat.v4.ChatRoomView.prototype 
  = new org.moock.unity.URoomView();

// PROPERTIES
org.moock.unity.simplechat.v4.ChatRoomView.prototype.users_lb = null;

// METHODS
  /**
   * URoomListener event handler
   */
org.moock.unity.simplechat.v4.ChatRoomView.prototype.onJoin = function (e) {
    // If the room join attempt was successful...
    if (e.getStatus() == "ROOM_JOINED") {
      // Display the interface frame.
      this.client.getTargetMC().gotoAndStop("simpleChatInterface");

      // Attach the users listbox. Have to do this at runtime so the listbox
      // methods are available immediately. Otherwise, we'd have to wait a 
      // frame for the listbox to initialize itself.
      this.users_lb = this.client.getTargetMC().attachMovie("FListBoxSymbol",
                                                            "users_lb",
                                             this.client.getNewTargetDepth());

      this.users_lb._x = 409;
      this.users_lb._y = 163;
      this.users_lb.setSize(117, 156);

      // Tell the user the application is ready to use.
      this.client.getTargetMC().status_txt.text 
                                      = "Chat now active. Send your message.";
    } else {
      this.client.getTargetMC().status_txt.text 
                       = "Could not join chat room. Status: " + e.getStatus();
    }
  }

  /**
   * URoomListener event handler
   */
  org.moock.unity.simplechat.v4.ChatRoomView.prototype.onAddClient 
                                                              = function (e) {
    // Retrieve the username for the client that just joined.
    var remoteuser = this.client.getRemoteClientManager().getClient(e.getClientID());
    var username = remoteuser.getAttribute(null, "username");

    // Use the client id as a user name if the user hasn't set a name.
    if (username == undefined) {
      username = "User" + e.getClientID();
    }

    // Add the new user to the list box.
    this.users_lb.addItem(username, e.getClientID());
    this.users_lb.sortItemsBy("label", "asc");
  }

  /**
   * URoomListener event handler
   */
  org.moock.unity.simplechat.v4.ChatRoomView.prototype.onRemoveClient 
                                              = function (e) {
    var clientID:String = e.getClientID();
    for (var i:Number = this.users_lb.getLength(); --i >= 0;) {
      if (this.users_lb.getItemAt(i).data == clientID) {
        this.users_lb.removeItemAt(i);
        return;
      }
    }
  }

  /**
   * URoomListener event handler
   */
  org.moock.unity.simplechat.v4.ChatRoomView.prototype.onUpdateClientAttribute 
                                                              = function (e) {
    var attrName = e.getChangedAttr().attrName;
    var attrVal  = e.getChangedAttr().attrVal;

    // If the username attribute was changed, display it on screen.
    if (attrName == "username") {
      this.renameUser(e.getClientID(), attrVal); 
    }
  }

  /**
   * Clean up UI when the client leaves the room or the server
   * connection is lost. (When the server connection is lost,
   * all rooms and namespaces are removed, and onLeave() fires 
   * for every removed room.)
   */
  org.moock.unity.simplechat.v4.ChatRoomView.prototype.onLeave = function () {
    this.users_lb.removeMovieClip();
  }

  /**
   * Changes a user's name in the user list.
   */
  org.moock.unity.simplechat.v4.ChatRoomView.prototype.renameUser 
                                              = function (clientID, newName) {
    for (var i = this.users_lb.getLength(); --i >= 0;) {
      if (this.users_lb.getItemAt(i).data == clientID) {
        this.users_lb.replaceItemAt(i, newName, clientID);
        this.users_lb.sortItemsBy("label", "asc");
      }
    }
  }


Documentation Version

1.0.3