Note: The following chapter is an excerpt from O'Reilly's Programming Flash Communication Server by Brian Lesser, Giacomo Guilizzoni, Robert Reinhardt, Joey Lott, and Justin Watkins. Chapter 13 was written by Brian Lesser. You can purchase the book on the O'Reilly website.
In Chapter 13, Brian describes the first major step in building full-fledged applications: designing and building custom communication components. Building communication components makes it possible to partition applications into well-defined building blocks that can be built and tested separately and then assembled to make a variety of applications. This chapter begins by building a simple people list component that shows who is connected to an application instance. Then Brian extends the simple people list to manage its own shared object, work with a separate user status component, and display connection status icons beside each user's name. We've excerpted the first couple sections of the chapter below.
While Flash MX Professional 2004 is not required for this article, it is recommended:
Note: This chapter is excerpted from Programming Flash Communication Server. Copyright ©2005 O'Reilly Media, Inc. All rights reserved.
The preceding chapters have introduced the classes that form the foundation of every communication application. The core communication classes in client-side Flash ActionScript are Video, Camera, Microphone, NetStream, NetConnection, and SharedObject. In FlashCom Server-Side ActionScript, the core classes are Application, Client, SharedObject, Stream, and NetConnection. Describing and demonstrating how to work with each class has taken up more than half of this book for good reason: facility with the core communication objects is a necessity for building applications. The preceding chapters included many small test and demonstration programs and, if you have been reading through them sequentially, you may be wondering how to turn the many examples you've seen into full-featured applications. The remainder of this book is devoted to providing you with the information and examples you need to build useful, robust, secure, and scalable applications.
This chapter describes the first and arguably most important step in building full-fledged applications. It introduces how to design and build custom communication components. Building communication components makes it possible to partition applications into well-defined building blocks that can be built and tested separately and then assembled to make a variety of applications. One advantage of composing an application out of communication components is that many components can manage their own stream and shared object resources without the rest of the application having to manipulate them. Similarly, the code and logic for communication functions such as text chat, video conferencing, shared text areas, and people lists can be encapsulated within each component. In this respect, communication components often resemble miniature applications—each may make use of multiple user interface components, employ client-and server-side code, and manage its own data. Since each communication component is relatively self-contained, adding a component to an application is relatively simple.
If you've looked in Flash's Library panel, in the server's scriptlib folder of Macromedia's communication components and component framework, or at Macromedia's v1 or v2 user interface component sets, you may feel that components are complex and time-consuming to understand and build. In fact, they are not so difficult to make. The communication components in this chapter do not rely on Macromedia's communication component framework or any of Macromedia's communication components. Using, modifying, and creating your own components within Macromedia's framework is described in the next chapter.
This chapter begins by building a simple people list component that shows who is connected to an application instance. It isn't much more than a visible list of users generated from the contents of a shared object. Then, we'll extend the simple people list to manage its own shared object, work with a separate user status component, and display connection status icons beside each user's name. Other components, including a text chat, shared text, video conference, video window, and people grid are also described. Some of the components are designed to work together or be used independently.
The word "component" has many different meanings, so it is important to explain its use in this chapter. In general, a component is software that has been built according to a well-defined standard or component model. The component model may include a set of interfaces, an event model, and a set of base classes from which you can derive your own components. If the component conforms to the component model, it can be incorporated into applications without being modified.
Flash provides a component model (a.k.a. a component framework) and comes with a number of ready-made components. The most often used are the UI components such as the Button, TextInput, and List components. At the most fundamental level, a Flash component is a movie clip that can be imported into a Flash movie's Library and used with little or no configuration. A component movie clip often contains assets such as imported image, video, and sound files; other Flash symbols; and ActionScript code. The Flash component model has evolved from support for so-called Smart Clips in Flash 5, through the v1 component architecture of Flash MX, to the v2 component architecture introduced in Flash MX 2004. Flash components that are imported into a Library can be dragged to the Stage, resized, and customized using the Properties panel, Component Inspector, or ActionScript without having to alter the internal elements of the component itself.
Flash components can be used to build larger and more complex Flash components. For example, a text chat component will often contain TextArea, TextLabel, and Button components. However, a text chat component is also a communication component. It requires a connection to a FlashCom Server and may include server-side scripts as an essential part. This chapter is about building communication components, but for brevity we often refer to communication components simply as components.
Unlike the v1 and v2 component architectures created by Macromedia, there is no special way to package the server-side script (and server-side media files) of a communication component. Installing scripts on the server requires copying ActionScript files to the server and, often, editing the main.asc file. The Macromedia communication components' architecture, described in the next chapter, does provide important elements of a server-side component model.
So, for the purposes of this chapter, communication components are Flash components coupled with server-side scripts that work together. The components presented in this chapter all use the v2 component set and message handling system introduced in Flash MX 2004.
Note: Most of the code examples in the remainder of the book use ActionScript 2.0. In order to compile these examples, set the ActionScript version to ActionScript 2.0 under File > Publish Settings > Flash. Many examples require the components that are included only in Flash Pro and will therefore not compile in the standard edition of Flash MX 2004.
The documentation included with Flash update 7.2 has improved coverage of the v2 component architecture, but it is still incomplete in some respects. This chapter contains some helpful information on building v2 components; however, building components is a large topic so you may wish to refer to some of the following references:
Macromedia's LiveDocs often includes additional comments, notes, and examples; see the section on using components:
livedocs.macromedia.com/flash/mx2004/index.html
The Macromedia Developer Center section on Flash Components page provides articles and sample source code for most components:
www.macromedia.com/devnet/flash/components.html
The source code for the Flash MX 2004 components can be found at a path similar to:
C:\Program Files\Macromedia\Flash MX 2004\en\First Run\Classes\mx
At UltraShock's site, see the Tutorials/Flash MX 2004 section:
Nigel Pegg wrote many of the components and has posted useful information about using and customizing them in his blog at:
See Joey Lott's article on creating components here:
www.person13.com/articles/components/creatingcomponents.html
The components that are described in this chapter are available in one of three sample applications named after the type of component used to show who is online. Each application is contained in one Zip file on the book's website:
Each file contains all the source and .swf files required to install the application as well as rebuild it. Recompiling the source files requires Flash MX Professional 2004.
People list components can be very basic—a simple one can show the username of everyone connected to an application instance and nothing more. As each user connects, his name appears in the list, and as each leaves, it disappears. You can add features from there. People lists normally show the connection status of each person, such as online or away from the keyboard. You may want to show other information, such as whether each person has audio or video resources. In an online course system, you may want to know if someone is waiting to ask a question. People lists often aggregate and display information from other components or from multiple shared objects to present a summary of information about each user. Of all the component types, people lists are ones you may find yourself building and rebuilding. Throughout this chapter, we'll build three people lists. After reading it, you may not have to build so many!
To begin with, let's build a simple people list component that displays the username of each person connected to an application instance and provides additional information such as a user's full name, email address, and connection time whenever a user clicks on someone's name in the list. To keep things manageable, this simple people list does not even manage its own shared object—it uses one managed by the server-side main.asc file. Before we start, let's consider some questions you should ask before you build any component:
In the case of a simple people list, here are some answers:
Components often create and manage their own streams and shared objects. In the case of a people list, a shared object containing information about users may already be provided by a server-side script. In other cases, a server-side people list component may be responsible for providing user information. For the simple people list, we can assume the information is already available in a shared object and that each slot name is a user's username and each slot value is an object containing the user's full name, email address, and connection time. Since the people list component will use a preexisting shared object, it doesn't have to create any other resources.
There are a few contenders for UI components the people list might use. The most common is a List component, which performs much faster than a DataGrid when used properly. However, a DataGrid has the advantage that columns in the grid can contain different icons so that different information about each user can be shown graphically. Finally, a Tree component can be used. Opening and closing person nodes in the tree can show additional information. For the simple people list, a List component is sufficient.
When the user clicks on a username in the people list, any object that needs to know about the user will receive an object containing the username, full name, email address, and connection time for that user. We'll use the v2 component architecture available in Flash MX 2004 to provide this feature with a minimum of fuss.
Our custom SimplePeopleList component is a subclass of the UIComponent class provided as part of the v2 component framework; it contains only two movie clips. One is the BoundingBox movie clip that is normally used to provide an initial size for many components and a List component to display the username of each person. The SimplePeopleList component was created by inserting a new movie clip symbol in a Flash movie and giving it a linkage identifier of SimplePeopleList and an AS 2.0 class name of SimplePeopleList using the Linkage Properties dialog box. (In this chapter, we do not use packages. They are used in Chapter 15 when introducing a simple alternative communication component framework.)
Note: Whenever building a component based on the v2 component set, you should not drag components from the Components panel into your own component. Instead, select File > Import > Open External Library, then open the StandardComponents.fla file from the .../First Run/ComponentFLA directory. When you need assets from the v2 component set, drag them from the external Library into your movie's Library or onto the Stage.
The SimplePeopleList needs the following assets from the StandardComponents.fla file's Library:
The SimplePeopleList component is really just a movie clip symbol in the Library that is associated with the SimplePeopleList.as class file, has been exported for ActionScript, and has the linkage identifier SimplePeopleList. Figure 13-1 shows the two frames that make up the SimplePeopleList movie clip symbol.
Figure 13-1. The timeline of the SimplePeopleList movie clip symbol
The BoundingBox clip has been placed at the origin (0, 0) of the SimplePeopleList component in frame 1 and has been named boundingBox_mc. The action in the Actions layer is a simple stop( ) statement to keep the playhead from moving to the second frame. Finally, the second frame of the Assets layer is where the List component has been placed. The stop( ) statement on the first frame guarantees that the second frame will never be reached. Therefore, assets—such as the List component—that must be in a movie's Library are placed in the second frame. When the component is imported into a movie, the assets will also be imported and appear in the movie's Library. Since the playhead will never reach the second frame, the List clip in the second frame will never be instantiated and is never directly used. With these elements in place, the real work of creating the SimplePeopleList component, including attaching a List component, is done in the SimplePeopleList.as class file. Example 13-1 shows the complete listing of the SimplePeopleList class definition; this client-side ActionScript 2.0 code must be placed in an external SimplePeopleList.as file. However, this listing does not show all the code necessary to create a working example; see the SimplePeopleList.zip file on the book's website for the complete code.
Many of the examples throughout this chapter use getter and setter methods. These are just methods that are called automatically when a corresponding property is set or retrieved. They provide a way to have assignment and retrieval operations for a property managed by a function, while allowing the developer to read or assign the property in the typical way (without having to invoke a function manually). For more information on these methods, see the section "Getter and Setter Methods" in Chapter 4 of ActionScript for Flash MX: The Definitive Guide (O'Reilly).
class SimplePeopleList extends mx.core.UIComponent {
// Connect component class and symbol.
var className:String = "SimplePeopleList";
static var symbolName:String = "SimplePeopleList";
static var symbolOwner:Object = SimplePeopleList;
// Subcomponents and movie clips.
var list:mx.controls.List;
var boundingBox_mc:MovieClip;
// Externally supplied NetConnection.
var __nc: NetConnection;
// Shared object obtained via the NetConnection.
var userList:SharedObject;
// Default path to resources for this component.
var __resourcePath:String = "pfcs/SimplePeopleList/main/";
// Constructor function calls UIComponent's constructor.
function SimplePeopleList () {
}
// init() is called after 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 List subcomponent is the entire GUI. function draw () {
size();
}
// Resize the List to take up the entire area of the compoent.
function size () {
super.size(); list.setSize(width, height);
}
// Attach the List to this component.
function createChildren () {
var depth = 1;
createObject("List", "list", depth++, {_x:0, _y:0});
list.vScrollPolicy = "auto";
}
// The simplePeopleList.resourcePath = "mypath" setter method.
public function set resourcePath(resourcePath:String) {
if (!resourcePath) {
trace("SimplePeopleList Error: Empty or undefined resourcePath.");
return;
}
__resourcePath = resourcePath;
}
// The simplePeopleList.nc = myNC setter method. Assumes resourcePath is correct.
public function set nc (nc:NetConnection) {
if (!nc) {
trace("SimplePeopleList Error: nc was null or undefined.");
return;
}
__nc = nc;
if(!nc.isConnected) {
trace("SimplePeopleList Error: nc must be connected before use.");
return;
}
// Get the temporary shared object.
// SharedObjectFactory is explained shortly.
userList = SharedObjectFactory.getRemote(__resourcePath + "userList", nc.uri);
userList.addEventListener("onSync", this);
// Assume this component "owns" the userList and is responsible for
// connecting to it. userList.connect(nc);
}
// Passes through add listener requests to the List subcomponent.
function addEventListener(type, dest) {
list.addEventListener(type, dest);
}
// Passes through remove listener requests to the List subcomponent.
function removeEventListener(type, dest) {
list.removeEventListener(type, dest);
}
// A simple event handler for the userList shared object that
// manipulates the List's dataProvider directly.
function onSync (ev) {
var dp = list.dataProvider;
dp.splice(0);
for (var p in userList.data) {
dp.push({label:p, data:userList.data[p]});
}
dp.dispatchEvent({target: dp, type:"modelChanged"});
}
// When this component is disposed of, release resources.
function onUnload () {
close(); }
// close() releases resources.
function close () {
userList.removeEventListener("onSync", this);
}
}
The SimplePeopleList component's code can be broken down into two broad categories: the code required to produce the graphical user interface and the part that ties together the userList shared object and the List component. Let's look at each separately.
By extending the UIComponent class and setting the className, symbolName, and symbolOwner variables, the SimplePeopleList class inherits all the properties and methods of the UIComponent class and connects the SimplePeopleList symbol in the Library with the SimplePeopleList class for the v2 component framework. Of particular interest here are the following methods that will be called in order when a SimplePeopleList object is instantiated:
boundingBox_mc clip. The clip already exists because it was placed on the SimplePeopleList component's Stage in frame 1.Both the list instance of the List component and the boundingBox_mc instance of the BoundingBox movie clip must be declared as variables within the class definition to avoid compiler errors.
The SimplePeopleList class has a property named __nc reserved to hold a NetConnection object. For the purposes of designing a simple people list component, we assume that the Flash movie that it is being used connects to a FlashCom application instance and can provide a connected NetConnection object for the SimplePeopleList to use. Rather than providing a method that can be used to pass in a reference to the NetConnection object, the SimplePeopleList component defines a setter method. The setter method will be called when the component's nc property is set this way:
peopleList.nc = nc;
The setter method—with comments and error checking removed—is as follows:
public function set nc (nc:NetConnection) {
__nc = nc;
userList = SharedObjectFactory.getRemote(__resourcePath + "userList", nc.uri);
userList.addEventListener("onSync", this);
userList.connect(nc);
}
The public nc property is saved in the private __nc property for later use. Then the NetConnection object and the SharedObjectFactory class are used to get a reference to the userList shared object. The path to the shared object is composed from two strings:
__resourcePath + "userList"
If the __resourcePath property has not been changed from its default, the path to the userList shared object is:
pfcs/SimplePeopleList/main/userList
In previous chapters, shorter paths have been used. However, in this chapter, a regular naming scheme is used to identify and keep separate the shared object and stream resources each component manages. The userList path has three parts preceding the name of the shared object itself:
The SharedObjectFactory class is a custom class written to return shared objects that can broadcast events. This class makes it possible to call addEventListener( ) on the userList shared object. The SharedObjectFactory class is discussed in detail in the next section.
The nc( ) setter method adds the SimplePeopleList component instance as a listener of the userList for onSync( ) events. Once again, here is the code that gets the shared object and sets up the SimplePeopleList as a listener for onSync events:
userList = SharedObjectFactory.getRemote(__resourcePath + "userList", nc.uri);
userList.addEventListener("onSync", this);
The component's onSync( ) handler is called whenever the shared object's onSync( ) method is called. The addEventListener( ) and removeEventListener( ) methods are the standard methods v2 components provide to add and remove event listeners.
Finally, the nc( ) setter method connects the shared object using the NetConnection object:
userList.connect(nc);
When the connection to the userList shared object occurs, the SimplePeopleList. onSync( ) method is called:
function onSync (ev) {
var dp = list.dataProvider;
dp.splice(0);
for (var p in userList.data) {
dp.push({label:p, data:userList.data[p]});
}
dp.dispatchEvent({target: dp, type:"modelChanged"});
}
When onSync( ) is called, the contents of the List component instance are simply removed and recreated using the shared object's data property. Each list item's label is set to the slot name (username) of each slot in the user list, and the item's data is set to the data in the slot. In this case, the data will be an object containing the full name, email address, and connection time of the user. Since the onSync( ) method must delete the entire contents of the list and rewrite it each time the shared object changes, it manipulates the list's dataProvider property directly to improve performance. The contents of the list are deleted by calling splice( ), and new items are added using push( ). Unlike the List component's removeAll( ) and addItem( ) methods, splice( ) and push( ) do not tell the list to redraw itself. Directly updating the dataProvider avoids redundant update requests to the list as each item is added. Instead, the data provider's dispatchEvent( ) method is called once to ask the list to redraw itself after all the items have been added.
While it is not used in the SimplePeopleList.onSync( ) method, the onSync( ) method is passed an event object. The list of information objects that was passed to the shared object's onSync( ) method is contained within the event object and is available in its ev.list property.
If the nc( ) setter and onSync( ) methods connect to and display the contents of the userList shared object, you may be wondering how the user information gets into the shared object in the first place. In the case of the SimplePeopleList component, the work of adding and removing user information is done by the main.asc script on the server. The complete file is available in the SimplePeopleList.zip file on the book's website. Here is an excerpt from the main.asc file that shows how user information is added and removed from the userList shared object:
application.onAppStart = function () {
userList_so = SharedObject.get("pfcs/SimplePeopleList/main/userList");
};
application.onConnect = function (client, user) {
client.user = user;
client.user.arrival = new Date();
userList_so.setProperty(client.user.userName, client.user);
return true;
};
application.onDisconnect = function (client) {
userList_so.setProperty(client.user.userName, null);
};
The main.asc script is coded on the assumption that the client will pass a second parameter in the NetConnection connect( ) method when it connects. The parameter should contain an object with userName, fullName, and email properties that will be stored in the userList shared object. The user object is also stored as a property of each Client object in this and later examples, so that the server-side part of any component can easily retrieve user information associated with a client.
The SimplePeopleList class is responsible for managing the userList shared object within the client. (Later, we'll create a people list component that also adds users to the shared object.) So it needs to provide a way to retrieve the user object for any user that appears in its list. Like all Flash MX 2004 v2 components that must broadcast events, the List component has addEventListener( ) and removeEventListener( ) methods.
When a user clicks on the List subcomponent within the SimplePeopleList component, a change event is generated. Other components or objects can add themselves as event listeners in order to know when an item has been selected in the list. When they receive a change event, they can use the event's target property to get a reference to the list and use the list's selectedItem property to get the data object from the selected item.
The SimplePeopleList component provides two methods that allow other objects and components to subscribe to list selection events:
function addEventListener (type, dest) {
list.addEventListener(type, dest);
}
function removeEventListener (type, dest) {
list.removeEventListener(type, dest);
}
The methods simply pass through an event listener request to the list component instance. When the user selects an item in the people list, the list component instance broadcasts an event to any listeners that have used the people list's addEventListener( ) method to listen for the event. For example, the following statement adds an application object as a listener for change events:
peopleList.addEventListener("change", application);
Other components will require more complex addEventListener( ) methods and may need to be able to broadcast their own custom events.
Note: In Example 13-1, the target property of each change event will be the List component and not the SimplePeopleList. Instead of passing event listener requests through to the List, SimplePeopleList could add itself as a listener to the List, then redispatch List events to its own listeners. Unfortunately, redispatching events is not enough to emulate the List's functionality. The SimplePeopleList must also provide the standard List methods and properties such as selectedItem and selectedIndex that listeners expect to be able to use on the event target. The effort and increase in code size needed to make the target property point to the List instead of the SimplePeopleList may not be justified.
Now that we've described how the SimplePeopleList component works, how do you use it in a Flash movie? Briefly, you must add it to the Library of a movie and attach it to the Stage manually at authoring time or using ActionScript. Once on the Stage or attached with ActionScript, it must be passed a reference to an already connected NetConnection object. In the example .fla found in the SimplePeopleList.zip file, an application object creates a NetConnection object, and the SimplePeopleList instance (named peopleList) is connected this way:
// First tell the people list about events we want to listen for.
peopleList.addEventListener("change", application);
// If necessary, replace the default resource path (not necessary here).
peopleList.resourcePath = "pfcs/SimplePeopleList/main/";
// Provide an already connected NetConnection to the component.
peopleList.nc = application.nc;
See SimplePeopleList.zip on the book's website for a complete example including both the server-side main.asc script and all the client-side files.
You'll find the SimplePeopleList movie clip symbol in the Library of SimplePeopleList.fla in the Zip archive. The SimplePeopleList could also have been compiled into a SWC file. A SWC file is an archive that contains a catalog.xml file that describes the other contents of the archive, ActionScript files that define component classes, and the SWF files that implement each component. A SWC file may also contain other assets. See Understanding SWC Files in the Flash MX 2004 Help. Converting a movie clip symbol into a compiled SWC is relatively easy. Select the symbol in the Library, right-click (Windows) or Ctrl-click (Macintosh) to open the contextual menu, and select Export SWC File. Then save the SWC file. SWC files are not used in this book—partly because of problems using them as subcomponents within other components. However, they are a good way to package component sets. For more information on creating and compiling components see:
www.person13.com/articles/components/creatingcomponents.html
The SimplePeopleList component was developed to show how a component can manage and coordinate the work of both v2 user interface components and communication resources such as the userList shared object. It is a subclass of the UIComponent class, implements many of the core UIComponent methods, and uses the v2-style setter methods. The SimplePeopleList component has two shortcomings that will be addressed later in this chapter: it doesn't provide a way to show the connection status of users, and it relies on code in the main.asc file to populate a shared object. The PeopleList and Status components developed later provide these features.
Brian Lesser is the lead author of Programming Flash Communication Server. He works in Ryerson University's Computing and Communications Services where he has the ungainly title of Assistant Director, Teaching and Technology Support. Brian has been captivated by Macromedia Flash and Flash Communication Server, and their potential in education, since FCS was first released. Giacomo "Peldi" Guilizzoni, Joey Lott, Robert Reinhardt, and Justin Watkins contributed chapters to the book and are all experienced Flash and Communication Server developers. The authors maintain a support site for the book that includes source code, extra TechNotes, and Flash Communication Server news.