Section 14.3. Creating a Simple Component from Scratch: SharedTextInput

14.3. Creating a Simple Component from Scratch: SharedTextInput

The communication components that ship with FlashCom are all complex, high-level components that do much more than a simple UI component such as a Button or a CheckBox. They could be considered small applications that include other UI components to perform client/server operations that were packaged as components because they could be used in many different end user applications.

Their complexity, coupled with the lack of documentation, made it almost impossible for programmers who just started looking at FlashCom to understand how each communication component worked and to learn from its code. To make things clearer, this section creates a communication component derived from a regular component but enhanced to have multiuser functionality through FlashCom.

The base component is a TextInput field, but we want to make its contents (text) shared among all users. We will call the new component SharedTextInput. Whenever a user types in the SharedTextInput component, everybody else connected to the application sees what the user is typing in real time. They can then type over what the original user has typed to change it, and everyone will see the changes.

As you will see, this component is very easy to write and understand. It's also flexible and easy to extend, in the sense that it can be used in a wide variety of applications and even included in other components (we will see an example of such a "container" component in the next section, when we build SharedAddressForm).

14.3.1. The .fla File

Building the .fla for the SharedTextInput component is really easy:

  1. Create a new movie clip.

  2. Set it to Export for ActionScript in the Symbol Properties dialog box.

  3. Specify the AS 2.0 Class name SharedTextInput in the Symbol Properties dialog box.

  4. Create a two-frame timeline for the component, such as you would do in all other components: a stop( ); action and a BoundingBox (call it m_boundingBox ) in frame 1 and an instance of TextInput in frame 2. If you are not familiar with building components, refer to Chapter 13.

After writing the client-side code, you could turn your component into a SWC, as described in Chapter 13, or leave it as a movie clip to use in your applications.

14.3.2. The Client-Side Class File

For the sake of code readability and simplicity, we will write the SharedTextInput class in ActionScript 2.0 but make it extend MovieClip instead of UIComponent . This is intended to ease the transition for developers still learning the UI components v2 architecture. If you understand the v2 architecture, it should be a breeze for you to make SharedTextInput extend UIComponent or even TextInput itself.

Example 14-1 shows the code for SharedTextInput.as . It's followed by a detailed description of each section.

Example 14-1. The client-side code for the SharedTextInput class
 import mx.controls.TextInput; class SharedTextInput extends MovieClip {   private var m_boundingBox:MovieClip;  // On the Stage   private var m_text:TextInput;         // Attached at _level0   private var m_name:String;   private var m_prefix:String;   private var m_nc:NetConnection;   private var m_so:SharedObject;   function SharedTextInput (Void) {     m_name = _name;     if ((m_name == null)  (m_name == ""))       m_name = "_DEFAULT_";     m_prefix = "SharedTextInput." + m_name + ".";     m_boundingBox.removeMovieClip( ); //Hide the bounding box     // Declare a change listener for the TextInput field     var l = new Object( );     l.mc = this;     l.change = function (p_eventObj) {       this.mc.m_so.data.text = this.mc.m_text.text;     };     this.attachMovie("TextInput", "m_text", 0);     m_text.setSize(100, 22);     m_text.addEventListener("change", l);   }   function onUnload (Void):Void {     this.close( );   }   function connect (p_nc:NetConnection):Void {     // Save a reference to the NetConnection passed in (for convenience).     m_nc = p_nc;     // Attach a reference to ourselves to the NetConnection      // so that server-to-client calls can be routed to this instance      // using the same naming scheme we used for   m_prefix   .     if (m_nc.SharedTextInput == null)       m_nc.SharedTextInput = new Object( );     m_nc.SharedTextInput[m_name] = this;     // Get the shared object that contains the text     m_so = SharedObject.getRemote(m_prefix + "text", m_nc.uri, false);     m_so.mc = this;     m_so.onSync = function (p_list) {       for (var i = 0; i < p_list.length; i++) {         // Update the UI only if the change came from someone else         if ((p_list[i].name == "text") && (p_list[i].code != "success")) {           this.mc.m_text.text = this.data.text;           break;         }       }     };     m_so.connect(m_nc);    // Call the server side of SharedTextInput     m_nc.call(m_prefix + "connect", null);   }   function close (Void):Void {     // Clean up     m_nc.call(m_prefix + "close", null);     m_nc.SharedTextInput[m_name] = null;     m_nc = null;   }   function setSize (p_w:Number, p_h:Number):Void {     m_text.setSize(p_w, p_h);   } } 

The SharedTextInput class code imports TextInput because we are going to embed an instance of it in our component. The class then defines some private variables needed. The class constructor first creates the m_name and m_prefix variables, inferring them from the instance name ( _name ) of the SharedTextInput component, which is chosen by the developer who uses this component. The m_prefix has to follow the naming conventions of the framework:

   ClassName   + "." +   instanceName   + "." 

As you will see, this prefix is used for remote method invocations and resources namespacing (this prevents conflicts if two SharedTextInput components share the Stage at the same time).

Next, the constructor removes the m_boundingBox clip from the Stage and attaches a TextInput component instance, calling it m_text . The constructor also defines an event listener ( l ), which stores the text from the TextInput component in the m_so shared object (which triggers an onSync event for all the users connected to it, including the client that sets the value).

The class defines an onUnload( ) method that calls close( ) if the SharedTextInput component is removed from the Stage (allowing the component to clean up after itself). The class also defines a connect( ) method, which is required by the framework. This is the function that tells us which NetConnection instance to use for our shared objects and streams; it is called automatically by the SimpleConnect component or manually by each application that wants to use communication components.

In the connect( ) method, we save a reference to the NetConnection passed in (for convenience), and we add a reference to ourselves (the SharedTextInput instance) to it as follows :

 m_nc.SharedTextInput[m_name] = this; 

This is done so that server-to-client calls can be routed to the SharedTextInput instance using the same naming scheme we used for m_prefix , namely:

   ClassName   + "." +   instanceName   + "." +   methodName   

Every component should attach a reference to itself to the NetConnection object at the beginning of its connect( ) function if it wants to receive calls from its server-side code.


The connect( ) method then gets an RSO (notice how we use m_prefix in its name, to avoid collisions) and defines an onSync( ) method on it:

 m_so = SharedObject.getRemote(m_prefix + "text", m_nc.uri, false); m_so.onSync = function (p_list) { ... 

The onSync( ) method should be fairly simple to understand by now. It loops through the updated slots in the shared object; if the value that changed is "text" and it was updated by someone elsethe code is "change" or "delete" but not "success"we update the text in the m_text TextInput area. If you didn't exclude the "success" case, the text field would update twice as you're typing, making the component pretty horrible to type in (try it for yourself).

Finally, the connect( ) method performs a client-to-server call to our server-side connect( ) method (always using the prefix):

 m_nc.call(m_prefix + "connect", null); 

Calling connect( ) on the server-side part of the component is required by the framework (that's how the server-side component instance is created!).


The class's close( ) method simply cleans up by telling the server-side code that the user instance is leaving and freeing memory resources. The close( ) method can be called by the host application or by the onUnload( ) method we defined earlier.

The last method of the class is setSize( ) , which is also required by the framework, and simply resizes the component to the requested dimensions.

This small class contains most of the logic for the component. To reiterate its strategy for multiuser interaction:

  1. Whenever someone types in the m_text TextInput component, a change event is trapped (by l ).

  2. The content of m_text is stored in the "text" slot of a shared object.

  3. Changes trigger an onSync event in all the clients connected.

  4. If the client is not the one that triggered the change, the new value of "text" is copied to the text property of m_text , updating it.

That wasn't so hard, was it? On to the server-side code.

14.3.3. The Server-Side Code

The server-side code lives in the SharedTextInput.asc file, which gets included in applications that use the component.

The server-side code shown in Example 14-2 is even shorter than its client-side counterpart .

Example 14-2. The server-side implementation of the SharedTextInput class
 try {var dummy = SharedTextInput;} catch (e) { // #ifndef SharedTextInput   load("components/component.asc");   SharedTextInput = function (p_name) {     this.init(p_name);     this.so = SharedObject.get(this.prefix + "text", false);     this.so.owner = this;     this.so.onSync = function (p_list) {       // This triggers an   onPropertyChange   event to those who are listening.       this.owner.text = this.getProperty("text");     };   };   // All named instances are held in the   instances   associative array.   // SharedText extends FCComponent.   SharedTextInput.prototype = new FCComponent("SharedTextInput", SharedTextInput);   // The first method called by a client component.   SharedTextInput.prototype.connect = function (p_client) {   };   SharedTextInput.prototype.setText = function (p_text) {     this.so.setProperty("text", p_text);     // This will not trigger an   onSync   event, which is good,     // because, otherwise, we could cause an infinite loop.   };   trace("SharedTextInput loaded successfully."); } // #endif 

After the usual "fake #ifndef " statement (implemented with the try/catch statement), the code defines the class constructor. The constructor calls init(p_name) , which triggers code in FCComponent , the base class that all server-side communication components extend. This runs some initialization code to set up event and method routing. It also defines the prefix property on this class, which we use in the next section. The code gets a reference to the shared object used by this component (the same one that we got on the client-side code) and defines an onSync( ) handler on it:

 this.so = SharedObject.get(this.prefix + "text", false); this.so.onSync = function (p_list) { ... 

The onSync( ) handler merely updates the text property of the component instance with the content of the "text" slot of the shared object. As we will see later, this is all that's needed to send an event to all listening classes. This onSync( ) handler is triggered whenever any client makes a change to the TextInput component.

The code specifies that the SharedTextInput class extends FCComponent (because SSAS doesn't support the extends keyword, we establish inheritance using the prototype property, as was common in client-side ActionScript 1.0 development):

 SharedTextInput.prototype = new FCComponent("SharedTextInput", SharedTextInput); 

The empty connect( ) method allows the lazy instantiation of this component.

Finally, the class defines a setText( ) method, which is a public API for the server-side code of this component (we'll see how it's used in a real-world example in the next section). For now, notice it simply sets the value of the shared object to what's passed in as a parameter. The fact that using setProperty( ) on the server side doesn't trigger server-side onSync events is a lifesaver here; otherwise, we might end up in an infinite loop.

The final trace( ) statement tells the world that the server-side code loaded successfully.

This code is very simple, and yet it throws an event whenever the text is changed by anyone , and it has a public API for setting the text from the server side. As you will see in the next section, this makes the component extremely flexible.

14.3.4. A Sample Application That Uses SharedTextInput

Now that we have the component all ready to goclient-side .fla , client-side .as class code, and server-side .asc codewe need to put it to the test!

To do so, we'll create a simple application that could be used by a math teacher. Figure 14-2 shows what the application is going to look like.

Figure 14-2. A simple application using SharedTextInput

Basically, we have two SharedTextInput components on the Stage, one to hold the value of an angle (alpha), and another to hold the value of the cosine of alpha. Whenever anybody changes the value of alpha, the cosine is updated, and vice versa. Imagine a math teacher changing the value of alpha, knowing that all his students are seeing what he's typing, and allowing his students to test it themselves , with everyone else seeing the changes in real time.

Let's see how to create such an application.

After creating all the text labels needed, drag two instances of SharedTextInput to the Stage, and name them m_alpha and m_cosAlpha using the Properties panel.

Drag a SimpleConnect component to the Stage and connect m_alpha and m_cosAlpha to it by specifying their instance names in SimpleConnect's Communication Components parameter list.

Create a server-side application folder named cosAlphaTest , and write the code in Example 14-3 into a main.asc file placed in that folder.

Example 14-3. The main.asc file for the cosAlphaTest application
 load("components.asc"); load("SharedTextInput.asc"); // This example shows how to create an application that uses // two instances of the SharedTextInput component to do some math. function toDeg (p_rad) { return p_rad*180/Math.PI; } function toRad (p_deg) { return p_deg*Math.PI/180; } l = new Object( ); l.onPropertyChange = function (p_eventSource, p_eventData) {   if (p_eventData.name != "text")     return;   if (p_eventSource.name == "m_alpha") {     var alpha = toRad(p_eventData.newValue/1);     SharedTextInput.instances["m_cosAlpha"].setText(Math.cos(alpha));   }   else {     var cosAlpha = p_eventData.newValue/1;     SharedTextInput.instances["m_alpha"].setText(toDeg(Math.acos(cosAlpha)));   } }; SharedTextInput.addListener("text",l); 

Copy SharedTextInput.asc to the cosAlphaTest application folder or to your scriptlib folder.

Update your SimpleConnect parameters to tell it to connect to the cosAlphaTest application, and you should be ready to test your application.

Let's look at the main.asc code in Example 14-3. First, it loads components.asc to load the framework, including the SimpleConnect component. It then loads SharedTextInput.asc (the code for the server-side SharedInputText class from Example 14-2).

After defining a couple of utility functions to transform radians to degrees, we create a class-wide listener object for the SharedTextInput class. Whenever the text property of any of the instances of SharedTextInput is updated, the framework invokes the onPropertyChange( ) method of the listener. The onPropertyChange( ) method first makes sure that the property that was changed is "text" and ignores changes made to any other property. It then looks at the name ( p_eventSource.name ) of the SharedTextInput instance that triggered the onPropertyChange event. If the change was in the m_alpha SharedTextInput instance, we transform the value to radians and use the public setText( ) method of the m_cosAlpha SharedTextInput instance to set its value to Math.cos(alpha) . This will update the m_text TextInput component of all clients connected to the application, through the onSync event generated by setText( ) with setProperty( ) .

Still with us? Good.

The else branch in the onPropertyChange( ) handler is symmetrical to the preceding if branch. It takes the value typed in m_cosAlpha , calculates its arc cosine, and saves it in m_alpha .

Make sure you have the FlashCom Server 1.5.2 updater . In prior versions, a bug in the event dispatching mechanism of the framework prevented class-wide events from being dispatched. (The author discovered the bug when writing this section of this book, and was able to fix in time for 1.5.2.)


Test the Flash movie. Whenever anybody types in the m_alpha SharedTextInput field, everybody sees the changes to it. Furthermore, because of the "wiring" on the server side, the contents of m_cosAlpha are also updated for all users of the application.

This is a simple application (very little code), but it shows how to use multiple components in the same application and link them together, using the communication component framework's event dispatching system and using components' server-side methods (such as setText( ) ).

The next section shows how to create a container component, which includes multiple copies of SharedTextInput to do its job.



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