Section 13.5. Status and People List


13.5. Status and People List

Most instant messaging and conferencing systems provide a way for the user to indicate that he is too busy to respond, has gone out briefly , or is offline although still connected. The Status component described here is designed to give the user a way to indicate his status. In this example, every user's status appears in a PeopleList component.

There are two basic approaches to adding user status input and display. One is to build a PeopleList component that includes a combo box, to let each user select a status. The other is to build two separate components : a Status component that is visually little more than a combo box and a PeopleList that knows how to display each user's status along with his username.

Regardless of the internal implementation, the user interface might be similar. Figure 13-2 shows such a setup with a PeopleList component on the left and a separate Status component on the right. When a user selects a different status, the icon beside his username in the PeopleList changes.

Figure 13-2. A PeopleList component (left) and a separate Status component (right)

In some respects, the first design option (using a PeopleList without a separate Status component) is simpler. A status property can be added to each person's entry in a userList shared object that indicates the user's status. When a user changes his status, the status property of the user's entry is changed to some value such as "Offline", "Busy", or "Away". The change in the user's entry in the shared object results in an onSync( ) call on the shared object. When the PeopleList component detects that a user's entry has changed, it changes the icon beside the user's username to show his new status.

In this first design option, a single shared object includes user and status information. Each slot contains a single object with firstName , lastName , and status properties. Although this design option works and is simple to implement, it has two disadvantages. First, whenever the status property of a shared object slot is updated, the entire object in the slotinstead of just the updated status propertymust be sent to every client. Resending redundant information wastes a small amount of bandwidth (and user information such as the full name and email address are unlikely to change during a session). Second, the status combo box and people list are tied together in a single design. There may be cases in which a status combo box is not needed or a design calls for it to be located away from the people list.

The second design option requires more work and involves both the creation of separate Status and PeopleList components and the maintenance of two separate shared objects: one containing an object for each user and one holding the status of each user. In the second design, the Status component updates the status shared object with the current status of a user. The PeopleList component must be set up to listen for changes in the status shared object as well as for changes in the people shared object (we'll call it the people shared object to distinguish it from the userList shared object used in the SimplePeopleList). When the status of a user changes, the PeopleList component must update the status icon for the user.

There are, of course, still other approaches, such as using SharedObject.send( ) to update clients , as described in Chapter 8. For clarity and simplicity, this chapter demonstrates the second design approach using two shared objects.

Each slot in the people shared object contains an object with firstName and lastName properties. A simple object illustrates how the data is organized:

 people = {blesser: {firstName:"Brian",   lastName:"Lesser"},           peldi:   {firstName:"Giacomo", lastName:"Guilizzoni"},           jwatkins:{firstName:"Justin",  lastName:"Watkins"}}; 

Each slot in the status shared object contains a string indicating a user's status:

 status = {blesser: "Online",           peldi:   "Busy",           jwatkins:"Away"}; 

The Status component is relatively simple to build. It contains only one user interface component, a combo box.

13.5.1. Designing and Building the Status Component

Let's return to the some of the questions we asked when designing the SimplePeopleList:

  • What resources must the component create and manage by itself?

  • What user interface components should the component contain?

  • What information must the component make available to other components or objects?

The Status component described here must manage a status shared object that contains the current status of each user. Each slot in the shared object is named after a user's username and contains a string value describing the user's status. The user interface for the Status component will be a v2 ComboBox that allows each user to select a status and shows the icon that will appear in the PeopleList for each selection. The Status component, therefore, must make the icons available for the PeopleList to use as well, as shown in Figure 13-2. Since the Status component is responsible for connecting to and managing the status shared object, it must also provide a reference to the status shared object for any other component that needs it. Status changes are broadcast as changes in the shared object to any interested component.

In the design implemented here, the Status component has two parts : a client-side component and a server-side class that works in partnership with it. When a user changes the selection in the Status component's ComboBox, the component makes a remote method call to its server-side counterpart , asking it to change the appropriate entry in the status shared object. The server-side Status object makes sure the correct user's entry is set with a valid status string in the shared object. An alternative to this approach is to update the shared object in the client. However, there are advantages to performing updates on the server, such as being able to serialize and securely validate updates. Chapter 15 and Chapter 18 discuss these in detail. Any components listening for changes on the shared object will receive updates when a user's status changes. However, the Status component doesn't need to receive any updates since it is set by the user.

13.5.1.1 Building the client-side Status component

With our design in mind, it is time to start coding. Example 13-4 lists the complete AS 2.0 client-side code for the Status component, while Example 13-6 lists the server-side Status class. Example 13-4 is quite long. You may want to skip over the get column( ) method for now and return to it when you reach the section on the PeopleGrid component.

Example 13-4. The client-side Status component
 import mx.controls.gridclasses.DataGridColumn; class Status extends mx.core.UIComponent {   // Connect component class and symbol.   var className:String = "Status";   static var symbolName:String = "Status";   static var symbolOwner:Object = Status;   // Default path and name.   var __name:String = "main";   var path:String = "pfcs/Status/main/";   // Subcomponents and movie clips.   var combo:mx.controls.ComboBox;   var boundingBox_mc:MovieClip;   // Externally supplied NetConnection--should already be connected.   var __nc:NetConnection;   // Internally aquired remote SharedObject, if needed by another component.   var __status_so:SharedObject = null;   // Constructor function calls UIComponent's constructor.   function Status( ) {   }   //   init( )   is called before   createChildren( )   and before   onLoad( )   .   function init( ) {     super.init( );     boundingBox_mc._visible = false;     boundingBox_mc._width = boundingBox_mc._height=0;   }   // No drawing is required as the combo box is the entire GUI.   function draw( ) {     size( );   }   // Resize the list to take up the entire area of the component.   function size( ) {     super.size( );     combo.setSize(width, height);   }   // Attach the combo box to this component.   function createChildren( ) {     var depth = 1;     createObject("ComboBox", "combo", depth++, {_x:0, _y:0});     combo.addItem({label:"Online", icon:"Status_Person_Online"});     combo.addItem({label:"Busy", icon:"Status_Person_Busy"});     combo.addItem({label:"Away", icon:"Status_Person_Away"});     combo.addItem({label:"Offline", icon:"Status_Person_Offline"});     combo.dropdown.iconField = "icon";     combo.addEventListener("change", this);   }   /*   change( )   is a callback invoked when the combo box selection changes.    * It calls the remote method   setStatus( )   to notify the server-side    * Status object that the status for this user has changed.    */   function change (ev) {     var status = combo.selectedItem.label;     __nc.call(path + "setStatus", null, status);   }   //   set name   changes the path and name for the component.   // The default name is "main".   function set name(name:String) {     if (!name) name = "main";     __name = name;     path = "pfcs/" + className + "/" + __name + "/";   }   // getter method for the name. Could use a get path method too.   function get name( ) {     return __name;   }   // The Status.nc setter method.   public function set nc (nc:NetConnection) {     __nc = nc;   }   /*   get column   creates what might be called a column transfer object.    * It is designed to work with the PeopleGrid component.    * It returns an object that contains the following properties:    *   name:    *     The name of the column.    *   so:    *     The SO that contains the data that must be displayed in the column.    *   dgColumn:    *     A DataGridColumn for insertion into a DataGrid.    *   propertyName:    *     The property name to add into each item in the DataGrid's data provider.    *   getPropertyValue:    *     A method to extract, from an SO slot, the data that must be added    *     into an item in the DataGrid's dataProvider. (i.e., if the slot contains    *     a record, the getPropertyValue( ) method will extract the right    *     information to put into each item in the DataGrid).    */   public function get column( ) {     if (!__status_so && __nc) {       __status_so = SharedObjectFactory.getRemote(path + "status",__nc.uri);       __status_so.connect(__nc);     }     var col = {       name: path,       so: __status_so,       propertyName: "status",       dgColumn: new DataGridColumn("status"),       target: this     }     col.dgColumn.cellRenderer = "StatusCellRenderer",     col.dgColumn.headerRenderer = "StatusHeaderRenderer";     col.dgColumn.width = 24;     col.getPropertyValue = function (slot) {       return slot;     };     return col;   }   /*   get status_so   uses   _  _nc   to get a reference to a status    * temporary remote shared object. The shared object's relative URI    * depends on the path of this component. By default, it is found    * at   pfcs/Status/main/status   but could be elsewhere. For example:    *   pfcs/Status/breakoutRoom_3/status   .    */   public function get status_so ( ) {     if (!__nc) {       return null;     }     if (!__status_so) {       __status_so = SharedObjectFactory.getRemote(path + "status",__nc.uri);       __status_so.connect(__nc);     }     return __status_so;   }   // Pass through listener requests to the ComboBox.   function addEventListener(type, dest) {     combo.addEventListener(type, dest);   }   function removeEventListener(type, dest) {     combo.removeEventListener(type, dest);   }   // Call   close( )   to make sure the component is cleaned up.   function onUnload( ) {     close( );   }   // Clean up.   function close( ) {     combo.removeEventListener("change", this);   } } 

Much of the code for the Status component is similar to the SimplePeopleList component. The UIComponent class is used as a base class, the ComboBox is attached to the component in the createChildren( ) method, the path is set as the resourcePath was earlier, and the __nc NetConnection object is set in much the same way. One difference is that the options for the combo box are set by adding item objects to the combo instance:

 combo.addItem({label:"Online", icon:"Status_Person_Online"}); 

The string "Status_Person_Online" is the linkage identifier of a movie clip in the Assets layer of the Status component. The timeline and Stage for the Status movie clip symbol are shown in Figure 13-3. Every icon needed by the Status component has been placed on the second frame of the Assets layer in order to guarantee that they will travel with the Status component into the Library of the Flash movie in which they are used. The icon property identifies which icon to display for each item.

Figure 13-3. The timeline and Stage of the Status movie clip symbol; the Stage contains a ComboBox and four icons

The drop-down list of the ComboBox is a List component, so the following statement is all that is needed to make the icons show up in the drop-down list:

 combo.dropdown.iconField = "icon"; 

See the help entry for the dropdown property of the ComboBox component and the iconField enTRy of the List component in the Flash MX 2004 Help system for more information.

The Status component also sets itself up as a listener on the combo subcomponent so that it knows when the combo instance has been changed:

 combo.addEventListener("change", this); 

When the change( ) method is called, it notifies the server-side Status object that it should make a change using a remote method call:

 function change (ev) {   var status = combo.selectedItem.label;   __nc.call(path + "setStatus", null, status); } 

The change( ) method gets the label string from the combo box such as "Offline" or "Away" and calls a remote method using the __nc NetConnection object. The remote method name is a concatenation of the path and the method name, setStatus( ) . If the path is "pfcs/Status/main/", the remote method being called will be named pfcs/Status/main/setStatus . When the remote call arrives at the server, the Client object that represents the Flash movie will have the pfcs/Status/main/setStatus ( ) method invoked on itif it has one. For example, if the status string passed to it is "Away", it will be as though the following method call was made:

 client.pfcs.Status.main.setStatus("Away"); 

Before moving on to describe how the server-side Status object is set up to receive and handle a setStatus( ) call, let's cover one other detail about the client-side Status component.

The Status component is responsible for managing the status shared object. If another component needs access to the status shared object, the Status component must supply it. The getter method get status_so( ) uses the NetConnection object to connect to the shared object and return a reference to it:

 public function get status_so (  ) {   if (!__nc) {     return null;   }   if (!__status_so) {     __status_so = SharedObjectFactory.getRemote(path + "status",__nc.uri);     __status_so.connect(__nc);   }   return __status_so; } 

We're done building the client-side Status class, so let's move on to the server-side Status class and the main server-side application that loads it.

13.5.1.2 Building the server-side Status object

Whenever a client connects to the server, any methods that will be called remotely must be attached to the Client object. Potentially every component may have to attach remote methods to the Client object. To avoid writing long and complicated application.onConnect( ) methods, a good practice is to call a method of a server-side component whenever a client connects. That way, the component can set up the client to receive any remote method calls and can add information into a shared object about the client if it needs to. When the client disconnects, the component should also get a chance to recover resources or remove an entry from a shared object. The main.asc file in Example 13-5 loads a separate .asc file for a Status and PeopleList component. Each file contains a class definition for the server-side part of a component. Within the onAppStart( ) method, instances of the Status and PeopleList classes are created. Whenever a client connects, the Client object is passed to the addClient( ) method of both components, and when a client disconnects, each component's removeClient( ) method is called.

Knowing how to divide your code between the client side and server side, or even following an example of how it's done, can all be a bit confusing. When an event occurs on the server, such as a client connecting or disconnecting, any shared object updates should be handled primarily on the server side. Reacting to client-side events, such as button clicks or text messages, are more complicatedshared objects can be updated on the client or on the server. We'll see several variations throughout this chapter and describe best practices in Chapter 15. Figure 13-4 shows an interaction diagram that illustrates how the client- and server-side parts of the Status component work together to update the status shared object. When addClient( ) or removeClient( ) is called on the server, the status is updated. When the client-side Status component calls its server-side counterpart, the server-side Status object updates the shared object. In the next section, we'll build a PeopleList component that will receive onSync( ) calls whenever the status changes.

Figure 13-4. An overview of how the Status component updates the PeopleList component

Example 13-5 shows the code for the main.asc file that that loads the Status.asc and PeopleList.asc files, initiates the server-side application, and hands off most of the work to the status and peopleList objects. Some functions and error checking have been omitted.

Example 13-5. A main.asc file that loads the Status.asc and PeopleList.asc files
 load("Status.asc"); load("PeopleList.asc"); application.onAppStart = function ( ) {   userList_so = SharedObject.get("private/userList");   status      = new Status( );   peopleList  = new PeopleList( ); }; application.onConnect = function (client, user) {   var tempUser = userList_so.getProperty(user.userName);   if (tempUser) {     application.rejectConnection(client, {msg: "The user name " +                                  user.userName + " is already in use."});      return false;   }   client.user = user;   client.user.arrival  = new Date( );   peopleList.addClient(client);   status.addClient(client);   userList_so.setProperty(client.user.userName, client);   return true; }; application.onDisconnect = function (client) {   status.removeClient(client);   peopleList.removeClient(client);   userList_so.setProperty(client.user.userName, null); }; 

A good deal of error checking and other code has been removed from Example 13-5. The complete source is available in the PeopleList.zip file on the book's web site.

Example 13-6 is the complete listing of the server-side Status class contained in the Status.asc file. The class uses a scheme similar to the client-side Status class to allow it to address different resource paths in order to keep resources for different components separate.

Example 13-6. The server-side Status class definition from the Status.asc file
 function Status(name) {   if (!name) name = "main";   this.name = name;   this.path = "pfcs/" + this.className + "/" + name;   this.so = SharedObject.get(this.path + "/status"); } Status.validStates = {   Online: true,   Offline: true,   Away: true,   Busy: true } Status.prototype.className = "Status"; /* Adds an object path to a client object.  * This effectively parses the   path   , such as "client/pfcs/Status/main"  * to create a hiearchy of properties that match the namespace,  * such as   client.pfcs.Status.main   .  * Each time through the   for   loop, a new object, such as  *   client   ,   client.pfcs   ,   client.pfcs.Status   , and   client.pfcs.Status.main   * is created, if it does not exist already.  */ Status.prototype.addObjectPath = function (client) {   var pathArray = this.path.split("/");   var obj = client;   var name;   for (var i = 0; i < pathArray.length; i++) {     name = pathArray[i];     if (!obj[name]) {       obj[name] = {};     }     obj = obj[name];   } }; Status.prototype.addClient = function (client) {   this.addObjectPath(client);   var thisComponent = this;   client.pfcs[this.className][this.name].setStatus = function (status) {     // Pass the   client   object and   status   to the Status     // component's   setStatus( )   method.     thisComponent.setStatus(client, status);   };   this.so.setProperty(client.pfcs.user.userName, "Online"); }; Status.prototype.removeClient = function (client) {   this.so.setProperty(client.pfcs.user.userName, null);   if (client.pfcs &&       client.pfcs[this.className] &&       client.pfcs[this.className][this.name])     delete client.pfcs[this.className][this.name]; }; Status.prototype.setStatus = function (client, status) {   if (typeof status != "string") return;   if (!Status.validStates[status]) return;   this.so.setProperty(client.pfcs.user.userName, status);   //trace("setStatus> " + client.pfcs.user.userName + ", " + status); }; Status.prototype.close = function ( ) { }; 

Whenever a client connects, its Client object is passed to the component's server-side addClient( ) method. The addClient( ) method has to attach a setStatus( ) method to the server-side client object so the client-side Status component can invoke setStatus( ) on the server. The setStatus( ) method could be attached directly to Client.prototype , so as to be available to all clients as discussed under "Using the Client.prototype object" in Chapter 4. But we chose the more flexible approach of customizing each client, to allow us to differentiate between them. Chapter 18 makes good use of this to give users different privileges based on their roles.

To distinguish the remote methods belonging to each component, the addClient( ) method has to do more than add a setStatus( ) method directly to the client object. Just as each client's streams and shared objects are named in a way to avoid one component overwriting the streams or shared objects belonging to another component, remote methods are named using the same convention:

 pfcs/   ComponentClassName   /   componentInstanceName   /   remoteMethodName   

When a remote method is called, the same format is used. So a client-side Status component instance named main will call the server-side setStatus( ) method this way:

 __nc.call(path + "setStatus", null, status); 

If we evaluate the path variable, we can see this is the same as calling:

 __nc.call("pfcs/Status/main/setStatus", null, status); 

This will call a method on the server-side client object just as though we executed a server-side statement that looks like this:

 client.pfcs.Status.main.setStatus(status); 

Let's look at this method call in detail. The setStatus( ) method is being called on the main object. The main object is a property of the Status object, which is a property of the pfcs object, which is a property of the client object. Despite their names , all of these objects, except the client , are just generic objects that are created simply to have a place to put methods like setStatus( ) . The name of each object conforms to the naming convention used throughout the remainder of this book. Later we'll see other examples of calling remote methods using the same naming conventionfor example, when a TextChat component calls its sendMessage( ) method on the server:

 client.pfcs.TextChat.main.sendMessage(...) 

and when the SharedText component calls a server-side method:

 client.pfcs.SharedText.main.edit(...) 

For each of these remote method calls to work, nested generic objects following the pfcs/ComponentClassName/ComponentInstanceName naming convention must be added to the client object.

Returning to the Status component: in order to call the setStatus( ) method, a number of generic objects must already exist in the client instance. A pfcs object must already be a property of the client object. In turn , a Status object must be a property of the pfcs object, and a main object must be a property of the Status object. When all these object properties are in place, a setStatus( ) method must be attached as a property of the main object. The work of creating the pfcs , Status , and main object properties within the client object is done by the addObjectPath( ) method. Note that the Status component's path property already contains the string "pfcs/Status/main" when addClient( ) calls addObjectPath( ) :

 this.addObjectPath(client); 

When addObjectPath( ) returns, the client.pfcs.Status.main object exists, and the setStatus( ) method can be added to it. The following short snippet of code adds the setStatus( ) method:

 var thisComponent = this; client.pfcs[this.className][this.name].setStatus = function (status) {   thisComponent.setStatus(client, status); }; 

When client.pfcs.Status.main.setStatus( ) is called, it does little more than call the Status component's own setStatus( ) method. In other words, the client.pfcs.Status.main object is really just a stand-in (proxy) for the actual Status component and merely passes on requests to the Status component itself.

In order to call the Status component's setStatus( ) method, some way is needed to refer to the actual Status component from within the main object's setStatus( ) method. At first glance, you might think you could just use this to refer to the Status component. However, when client.pfcs.Status.main.setStatus( ) is called, this refers to the main object. The solution is to use another variable. In this case, the thisComponent variable is passed a reference to this inside the addClient( ) method. When the client.pfcs.Status.main.setStatus( ) method is called, thisComponent still refers to the Status component and can be used to call the component's setStatus( ) method.

This is a nice feature of JavaScript (and Server-Side ActionScript which is JavaScript 1.5). When the client.pfcs.Status.main.setStatus( ) method is defined inside the Status class's addClient( ) method, any variable defined inside addClient( ) and its current value, can be used inside the client.pfcs.Status.main.setStatus( ) method. Behind the scenes, JavaScript creates a special closure object in order to store the variable thisComponent and the value it contained when the client.pfcs.Status.main.setStatus( ) was defined. The same thing happens with the client variable. When the client.pfcs.Status.main.setStatus( ) is defined, the client variable refers to the client on which it is being defined on. So, the client variable will always refer to the Client it is called on. If that seems a little esoteric, don't worry; it is. The main thing is that you can use variables defined inside the addClient( ) method inside the client.pfcs.Status.main.setStatus( ) method, and they will retain the values they had when addClient( ) was called.

When client.pfcs.Status.main.setStatus( ) is called, this method is called by it:

 Status.prototype.setStatus = function (client, status) {   if (typeof status != "string") return;   if (!Status.validStates[status]) return;   this.so.setProperty(client.user.userName, status); }; 

The method is passed a reference to the client object it was called from and so can get the userName associated with the client variable and set the new status in the status shared object:

 this.so.setProperty(client.user.userName, status); 

The Flash movie doesn't need to pass the username to the server. Note that the user object, which contains the userName property, was attached to the client property in the main.asc script when the client connected, as a simple way to associate user information with each client object. Other more complex designs are possible that do not require adding user data directly to the client object and are usually part of a more complex server-side component framework.

Finally, the Status component's addClient( ) method sets the initial connection status for the client in the status shared object:

 this.so.setProperty(client.user.userName, "Online"); 

When the client disconnects, the server-side Status.removeClient( ) method is called. It removes the entry for the client from the status shared object:

 this.so.setProperty(client.user.userName, null); 

That was a fair bit to follow, and it's easy to lose track of the big picture. In summary, the Status component's addClient( ) method sets up each server-side client object so it can receive setStatus( ) calls from the client-side part of the Status component. The addClient( ) method also updates the status shared object so that the user's initial status is "Online". From then on, whenever the user changes his status, the series of events in Figure 13-4 will occur. Have a look at Figure 13-4 from top to bottom to see how one event leads to another.

That's ityou've seen all the pieces that make the Status component work. It's time to turn to designing and building the PeopleList component that will listen to the status shared object and display the right icon for each user.

13.5.2. Designing and Building a PeopleList Component

The SimplePeopleList component introduced earlier was designed to be as simple as possible in order to introduce the basic requirements of building a v2 Flash component. It was not responsible for adding data to the userList shared object and was not capable of showing a user's status. The PeopleList component addresses both shortcomings. A server-side PeopleList object is used to maintain a people shared object to which the client-side PeopleList object connects (this replaces the userList used in the SimplePeopleList component). Furthermore, if a Status component passes a reference to the status shared object to the PeopleList component, the latter also displays status icons; otherwise , PeopleList works correctly without displaying any icons.

Fortunately, the code for the PeopleList.asc file, which implements the server-side PeopleList class is short and relatively simple, as shown in Example 13-7.

Example 13-7. The server-side PeopleList class definition from the PeopleList.asc file
 function PeopleList(name) {   if (!name) name = "main";   this.name = name;   this.path = "pfcs/" + this.className + "/" + name;   this.so = SharedObject.get(this.path + "/people"); } PeopleList.prototype.className = "PeopleList"; PeopleList.prototype.addClient = function (client) {   this.so.setProperty(client.pfcs.user.userName, client.pfcs.user); }; PeopleList.prototype.removeClient = function (client) {   this.so.setProperty(client.pfcs.user.userName, null); }; PeopleList.prototype.close = function ( ) { }; 

The server-side PeopleList class does two things. It adds an entry for each user into the people shared object when the client connects, and it removes the entry when the client disconnects. The entry it adds is an object that was passed into the application.onConnect( ) method. (See the main.asc file listing in Example 13-5.) Creating a separate shared object for the PeopleList component makes it possible to separate the public from the private information passed in by the client and reduce shared object updates to those needed by just the PeopleList component. In this example, the user object is just added to the PeopleList's people shared object. In later chapters, only public information is added.

The PeopleList component does not receive remote method calls from its client-side counterpart the way the Status component does. The shared object path is in the now-familiar path of pfcs/PeopleList/main/people .

The client-side PeopleList component contains the same asset as the SimplePeopleList componenta List component. Much of the code in the client-side PeopleList component is the same as the code in the SimplePeopleList component, so the entire class definition is not reproduced here. See the PeopleList.as file in the PeopleList.zip archive on the book's web site for the complete listing. The setter method that is passed a NetConnection object is almost identical. The only difference is that it gets a shared object at the path path + "people " instead of the path resourcePath + " userList " used by the SimplePeopleList component. In either case, the client-side PeopleList component uses the NetConnection object to get a reference to the remote shared object, sets itself as listener on it, and connects it. The PeopleList class has an additional setter method that can be used to pass a reference to a status_so shared object to it, as shown in the client-side code in Example 13-8.

Example 13-8. Passing in a status shared object to the PeopleList component
 public function set status_so (so:SharedObject) {   if (so instanceof SharedObject) {     __status_so = so;     __status_so.addEventListener("onSync", this);     list.iconFunction = function (item) {       var status = item.data.status;       if (status) {         return "Status_Person_" + status;       }     };     if (__status_so.isConnected) {       onSync({target:__status_so, type:"onSync", list:[]});     }   } } 

The method makes the PeopleList component instance a listener of the status shared object so that the PeopleList.onSync( ) method will receive updates from both the people and status shared objects. It also adds an iconFunction( ) method to the List object.

The method is called whenever a list item needs to be redrawn inside the List. It finds the value of the list item's data.status property and returns the linkage identifier of a status icon to display beside the item's text label. Finally, if the status shared object has already been connected by the Status component, its isConnected property will be true . In that case, the set status_so( ) method calls onSync( ) once itself to make sure the list is redrawn based on any status information that is already available.

Before moving on to describe the onSync( ) method, where most of the interesting work of the client-side PeopleList component takes place, one more setter methodthe one to hide offline usersneeds to be described:

 public function set hideOfflineUsers(hideOfflineUsers:Boolean) {   __hideOfflineUsers = hideOfflineUsers; } 

When a user wants to appear offline, he can select the offline option in the status combo box. In some applications, his name will be completely removed from everyone's PeopleList. In others, it may be more appropriate to show an offline icon. Setting hideOfflineUsers to true ensures that an offline user's icon and username will disappear from the PeopleList display.

Finally, Example 13-9 shows the source code for the PeopleList.onSync( ) method from the PeopleList.as class file.

Example 13-9. The PeopleList onSync( ) method that handles both PeopleList and Status onSync( ) calls
 function onSync (ev) {   // Get the list's   dataProvider   property and truncate it.   var dp = list.dataProvider;   dp.length = 0;   // Fill the   dataProvider   property using the   Array.push( )   method.   for (var p in __people_so.data) {     var obj = clone(__people_so.data[p]);     var status = __status_so.data[p];     if (status) {       obj.status = status;       // To make a person disappear from the PeopleList when       // he or she is offline, skip this record if necessary:       if (__hideOfflineUsers && obj.status == "Offline") continue;     }     dp.push({label:p, data:obj});   }   dp.dispatchEvent({target:dp, type:"modelChanged"}); } 

The username of each person connected to an application is used to name each slot in the people and status shared objects.

The onSync( ) method may be called when either the people or status shared object is updated. In either case, it does the same thing. It replaces all the items in the list with new ones based on the contents of the people and status shared objects. If no status list exists, no status information is included in the list. As shown in Example 13-9, the onSync( ) method gets the object stored in each slot of the people shared object, makes a copy of it using the clone( ) method, and adds a status property to it if there is one.

This is necessary because a property cannot be added to a people shared object slot without forcing another shared object update. So instead, a copy is made and updated.

If the status of an object is "Offline" and hideOfflineUsers is true, an item is not added into the list for it (the continue keyword ensures that the loop skips over the subsequent statement that pushes the obj onto the dataProvider ):

 if (__hideOfflineUsers && obj.status == "Offline") continue; 

Example 13-10 shows the code for the clone( ) method from the PeopleList.as file.

Example 13-10. The clone( ) method returns a shallow copy of an object
 function clone (orig) {   var copy = {};   for (var p in orig) {     copy[p] = orig[p];   }   return copy; } 

Figure 13-5 shows a simple interaction diagram for the PeopleList component. It shows how, when a client connects or disconnects from an application instance, the people shared object is updated on the server, which leads eventually to the PeopleList component receiving an onSync( ) method call.

Figure 13-5. How the server-side PeopleList component updates its client-side partner

If you compare Figure 13-4 with Figure 13-5, you'll see that the PeopleList component receives an onSync( ) method call when one of five events occurs. The onSync( ) method in Example 13-9 updates the list based on the current data in both shared objects by deleting the contents of the list and rewriting the entire list. For lists of fewer than 50 items, the code in Example 13-9 works well. For larger lists with hundreds or thousands of items, a more efficient approach is required. See Chapter 15 for more information on coding efficient onSync( ) methods.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net