Section 13.9. PeopleGrid

13.9. PeopleGrid

Some application designs require more than can be provided by the PeopleList component presented earlier. You may need to aggregate different information about people into one place, using a number of icons or components to represent the data. For example, you may need icons or components to show the connection status, audio/video capabilities, whether each user has questions about a presentation, or whether each user feels the presentation is moving too quickly or too slowly. When so much information must be aggregated into one place, a DataGrid can be used in place of a List component to present it. The DataGrid component provides a mechanism for creating custom columns that can contain regular text or specialized cell renderers that can display graphics or other components in each cell of the column. Figure 13-13 shows a PeopleGrid component in use; the first column contains status icons and the second column shows the number of questions each user has. The PeopleGrid works much the same way the PeopleList component does but contains a DataGrid instead of a List component.

Figure 13-13. The PeopleGrid component with Status and Questions columns

The PeopleList component discussed earlier had the logic to display an icon from the Status component hardwired into it. However, while it is relatively easy to adapt the PeopleList code to show different icons or to work with another component, it is unrealistic to hardwire the display logic into a DataGrid for any number of components that may need to display information within the grid. Instead, the PeopleGrid provides an addColumn( ) method for other components to define a column within its DataGrid. For example, Status and Questions components may each export a column to the PeopleGrid. The Questions component may also want to know when one of its icons has been clicked on. It can set itself up as a listener of the PeopleGrid component in order to receive custom events sent by its own cell renderer via the grid.

Example 13-39 shows one way a PeopleGrid component placed on the Stage may be initialized and connected with other components. This code would go in the timeline of your Flash client.

Example 13-39. Timeline code to initialize and connect the PeopleGrid component with other components
 peopleGrid.hideOfflineUsers = true; peopleGrid.nc = application.nc; peopleGrid.name = "main"; peopleGrid.addEventListener("change", application); peopleGrid.addColumn(status.column); peopleGrid.addColumn(questions.column); peopleGrid.addEventListener("showQuestions", questions); 

In the following statement, the getter method of the status component generates an object that contains all the information the peopleGrid needs to display and update a column within its DataGrid:

 peopleGrid.addColumn(status.column); 

Example 13-40 shows the Status component's get column( ) method from the Status.as file in the PeopleGrid.zip archive.

Example 13-40. The Status component's get column( ) method
 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; } 

The get column( ) method creates an object that contains a DataGridColumn object that uses the StatusCellRenderer class (see Example 13-43) to show custom icons in the column. It also contains a reference to the Status component's shared object so that it can get status data for the StatusCellRenderer instance to display. The PeopleGrid will use the propertyName property and getPropertyValue( ) method to get the data it needs from an entry in the status shared object and add it to each item in its DataGrid. In this case, the status shared object contains a text value, so it is all that is returned. In other words, a property named status containing a text string like "Online" or "Busy" will be added to each DataGrid item.

Example 13-41 shows the PeopleGrid component's addColumn( ) method from the PeopleGrid.as file in the PeopleGrid.zip archive.

Example 13-41. The PeopleGrid component's addColumn( ) method
 public function addColumn(col) {   // Add this column to a hash (associative array) by component name.   columnObjects[col.name] = col;   grid.addColumnAt(0, col.dgColumn);   col.so.addEventListener("onSync", this);   // Forces the Questions column label to show up.   grid.redraw(true); } 

The PeopleGrid contains an object named columnObjects that stores each column it receives from other components for later use and then adds a column onto its DataGrid. The columnObjects object is used each time an onSync( ) method is received. Example 13-42 shows the onSync( ) method of the PeopleGrid and how the columnObjects object is used to correctly update the item in the DataGrid.

Example 13-42. The PeopleGrid component's onSync( ) method handles events from the people shared object and other components' shared objects
 function onSync (ev) {   // Get the list's   dataProvider   and truncate it.   var dp = grid.dataProvider;   dp.length = 0;   // Fill the   dataProvider   using the   Array.push( )   method.   for (var userName in __people_so.data) {     // Copy a slot in the people shared object.     var item = clone(__people_so.data[userName]);     // Add the   userName   as a property so it will appear in the grid.     item.userName = userName;     // Check all the   columnObjects   and add any properties from the      // shared objects of other components that have columns in the grid.     for (var col in columnObjects) {       var c = columnObjects[col];       // Get the user's slot in the other component's shared object.       var slot   = c.so.data[userName];       // If there is one, get the data from the slot and put it       // in the cloned object.       if (slot) {         item[c.propertyName] = c.getPropertyValue(slot);       }     }     // Skip any users with status "Offline".     if (__hideOfflineUsers && item.status == "Offline") continue;     dp.push(item);   }   dp.dispatchEvent({target:dp, type:"modelChanged", eventName: "updateAll"}); } 

The onSync( ) method makes use of a number of objects so we'll describe it step-by-step. The grid property of the component holds a reference to a DataGrid. In order to reduce the time the grid needs to insert, update, and delete items, the DataGrid methods such as addItem( ) are not used. Instead, grid.dataProvider is retrieved and manipulated directly. The dataProvider object is based on an Array object, so we can manipulate it as an array. First, we get the dataProvider from the grid :

 var dp = grid.dataProvider; 

Then we simply dispose of all the items in the dataProvider by resetting the length property of the array to 0:

 dp.length = 0; 

Now, we create a copy of each entry in the people shared object using the clone( ) method of the PeopleGrid. The copy is necessary because other properties from other shared objects may have to be added to it. If those properties were added to an object in the people shared object, the slot would be updated. Each copy will become an item in the grid :

 for (var userName in __people_so.data) {   // Copy a slot in the people shared object.   var item = clone(__people_so.data[p]);   // Add the   userName   as a property so it will appear in the grid.   item.userName = userName;   //... code omitted here that adds other properties to   item   . } 

Notice that we have to add in a property to the object so the grid will display the username from each entry in the grid 's userName column:

 item.userName = userName; 

Now that we have an item with all the information from the people shared object in it, we have to add information from any component's shared objects that have columns in the PeopleGrid. We have to go through each column object from each component and use it to copy information from the other component's shared object into the item:

 for (var col in columnObjects) {   var c = columnObjects[col];   // Get the user's slot in the other component's shared object.   var slot = c.so.data[userName];   // If there is one, get the data from the slot and put it   // in the cloned object.   if (slot) {     obj[c.propertyName] = c.getPropertyValue(slot);   } } 

For each column, we get the column object:

 var c = columnObjects[col]; 

Now, we use the username in userName to get the slot for the user in the other component's shared object:

 var slot = c.so.data[userName]; 

Finally, we can create a new property in the object this way:

 obj[c.propertyName] = c.getPropertyValue(slot); 

In the example of the Status component, the propertyName will be status and getPropertyValue( ) will return a string like "Online". So, if the item originally contained userName , firstName , and lastName properties, each will now also have a status property. As we loop through each component's column object, other properties will be added to the item. If there is a Questions column, for example, a questions property may be added containing the number of questions a user has.

When each item, with all its new properties, is complete, it is added to the data provider for the grid:

 dp.push(item); 

Finally, when all the items have been added to the data provider, its dispatchEvent( ) method is called to tell the grid to display the new data:

 dp.dispatchEvent({target:dp, type:"modelChanged", eventName: "updateAll"}); 

The updateAll event name is required to make the grid redraw itself completely.

The onSync( ) method, together with the column objects in the columnObjects hash, do the key work of aggregating data from the shared objects owned by other components into items that appear in the grid.

The appearance of each component's column in the grid can be customized by having each component provide a cell renderer. A cell renderer is a component that implements the CellRenderer API. The code for the StatusCellRenderer class is reproduced in Example 13-43. It belongs in a file named StatusCellRender.as .

Example 13-43. The StatusCellRenderer code
 import mx.core.UIComponent class StatusCellRenderer extends UIComponent {   var boundingBox_mc:MovieClip;   function StatusCellRenderer ( ) {   }   function setValue(suggested, item, selected) {    if (!item  !item.status) {       gotoAndStop("Blank");       return;     }     gotoAndStop(item.status);   }   function init ( ) {     super.init( );     boundingBox_mc._visible = false;     boundingBox_mc._width = boundingBox_mc._height=0;   }   function getPreferredHeight ( ) {     return 18;   }   function getPreferredWidth ( ) {     return 18;   } } 

Whenever the grid needs to display a cell or update its visual appearance, the renderer's setValue( ) method is called. In Example 13-43, it sends its playhead to a blank frame if there is no item or item property named status . The Blank frame will display in any empty cells or empty rows in the grid. When the item has a status property, the renderer sends the playhead to a frame of the same name. Figure 13-14 shows the timeline and Stage of the StatusCellRenderer movie clip symbol. You can see from the timeline that the blank frame actually contains a movie clip. It is the boundingBox_mc clip with its _alpha level set to 0. The transparent clip makes sure the grid lays out the cell correctly even when there is nothing to look at.

Figure 13-14. The StatusCellRenderer's timeline; the Blank frame includes a boundingBox_mc clip with _alpha set to 0

Sometimes a cell renderer needs to communicate with its component. For example, if you have a Questions component and the user clicks on the questions icon, you may want the Questions component to display a window with that user's questions in it. When a cell renderer is used inside a DataGrid, a property named listOwner is automatically created for it. The listOwner property can be used to have the DataGrid broadcast a message by calling the grid's dispatchEvent( ) method. For example, if an onRelease event occurs, a custom event named showQuestions can be generated this way:

 function onRelease (  ) {   listOwner.dispatchEvent(     {type:"showQuestions", target:this, userName:userName, questions:questions}   ); } 

We've seen that the Status component is responsible for creating a column object, complete with cell renderer and getProperty( ) method, that provides a view of the Status component within a column of the DataGrid. Some developers would prefer to implement the Status component so that it doesn't need to know anything about DataGridColumn objects. They would argue that unless every possible UI that displays Status would use DataGridColumns , the code for the get column( ) method should be placed in the PeopleGrid or in some other class instead.

The problem with putting the code in the PeopleGrid component is that it cannot possibly know how to present every column. The PeopleGrid should allow other components or objects to set up columns with their own cell and header renderers. For example, if you build a new Questions component, to show which user has a question, after the PeopleGrid is complete, you will write a new renderer for it. The developer of the Question component can create the library symbols and so on to provide what the PeopleGrid needs to display Questions in a column. In a sense, the Questions component knows how to display a view of itself in the PeopleGrid. The idea isn't much different than the way the DataGrid expects an application to create cell renderers and column objects that it will use.

Adding the code to generate a column object within each component such as the Status component does increase its size. If it is not used with a PeopleGrid, the increase in code size is not warranted. A solution to this problem would be to move the column generation code out to a separate StatusColumn component.

The PeopleGrid component puts to good use the DataGrid component's ability to show custom content in columns using cell renderers and to broadcast messages using the dispatchEvent( ) method. There is almost no end to the information other components can pack into a PeopleGrid using the methods described here. As a more complete example, the PeopleGrid.zip file on the book's web site includes a Questions component.



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