Section 13.7. Shared Text

13.7. Shared Text

A shared text area acts much like a whiteboard. Any user can add to or edit text so that everyone can see it. However, allowing everyone to make changes at once can lead to anarchy and frustration. So a mechanism is required to control who can add or edit text at one time. The SharedText component described here uses an Edit button to allow a user to take control of the SharedText component. When a user clicks the button, the button is toggled into the on state for the user who clicked it and disabled for everyone else. The text area of the SharedText component becomes editable for only the one user. When the user has finished making changes, he clicks the Send button and everyone sees the update. Figure 13-8 shows the user interface for the SharedText component in its default state. The text field cannot be edited, and the only button enabled is the Edit button. In the default mode, the version stepper control can be used to page through all the previous versions of the text area. Each version is saved in a separate shared object slot so that previous versions of the text can be reviewed.

Figure 13-8. SharedText component in its default state

Figure 13-9 shows the SharedText component after the Edit button has been clicked and control of the text area has been granted. The Edit button's label is now Cancel and all the buttons are toggled on. Clicking the Delete Version button deletes the version of the text that is currently displayed and releases control of the shared text area. Dragging the mouse over some text to select it and clicking the Select button displays the same version with selected text for everyone and also releases control of the component. Making changes to the text and clicking the Send button creates a new version of the text and stores it in another slot of the shared object before releasing control.

Figure 13-9. SharedText component during editing

The SharedText component manages one resource: a shared object named messages . The shared object contains a variety of slots that are used for different purposes.

The currentUser slot is used as part of the locking mechanism that allows only one user to update the shared text component at a time. If the slot contains an empty string, the shared text component is not being updated by anyone and is therefore available for use. If the currentUser slot contains a username, then only that user has permission to update the shared text component.

There are a length slot and a sequence of numbered slots. The slot name of each numbered slot is actually a string representation of a number. Slot 1, for example, is really named slot "1". The full text of each version of shared text is stored in each numbered slot and the length slot contains the number of text slots. For example, if there is only one version of text, in slot "1", then length will be 1.

Finally, there are slots that, when updated, force each client to display one of the versions of text, scroll to a position within it, and select some of the text. The slot names are: selectCount , selectScroll , selectVersion , endIndex , and startIndex . The selectCount slot represents the number of selections that have been made during the history of the component. When it changes, each client knows to display the text in the slot indicated by the selectVersion property, scroll to the selectScroll position in the TextArea, and bold the text between the startIndex and endIndex characters .

The locking mechanism used in the SharedText component is a cooperative one, because it relies on each client responding to changes in the currentUser slot. When a user clicks the Edit button, a remote method named edit( ) is called on the server. If the currentUser slot is empty when the call is executed, the client's username is placed in the currentUser slot. The change in the currentUser slot results in an onSync( ) call on each client, in response to which each client checks whether its username is in the currentUser slot. If so, it activates all the editing buttons and allows the user to update the shared text area. If someone else's username is in the currentUser slot, all the buttons are disabled.

The client that has control can now safely update the other properties of the shared object to add another version of text, select some text, or delete a version of text. When the client updates the shared object, it is detected on the server and the currentUser slot is immediately cleared. In turn , onSync( ) is called, and each client puts the buttons back into the default state. The initial sequence of events is illustrated in Figure 13-10.

Figure 13-10. The sequence of events required to set the currentUser property and notify each client (two clients are shown)

Example 13-22 lists part of the server-side SharedText class definition, which should be placed in a SharedText.asc file. Two methods common to other components addObjectPath( ) and removeClient( ) have been omitted to reduce the size of the listing. They are included in the version on the book's web site.

Example 13-22. The server-side SharedText class
 /* SharedText constructor is passed its name. The default is "main".  * All resources exist in the namespace:   pfcs/SharedText/name/.   * Gets and initializes the   pfcs/SharedText/name/messages   shared object.  */ function SharedText (name) {   this.name = name;   if (!name) this.name = "main";   this.path = "pfcs/" + this.className + "/" + name;   this.so = SharedObject.get(this.path + "/messages", true);   this.so.ready = false;   this.so.onSync = function ( ) {     /* If any change is made in the SO, the client is done,      * so the current user is set to an empty string.      * Note: server-side changes always succeed so they      * do not result in an   onSync( )   call.     */     var currentUser = this.getProperty("currentUser");     if (currentUser != "") this.setProperty("currentUser", "");   };   this.so.setProperty("currentUser", "");   if (typeof this.so.getProperty("length") != "number") {     this.so.setProperty("length", 0);   } } SharedText.prototype.className = "SharedText"; //   addClient( )   is called whenever a client needs to connect // to the SharedText component. SharedText.prototype.addClient = function (client) {   this.addObjectPath(client);   var thisComponent = this;   client.pfcs[this.className][this.name].edit = function ( ) {     thisComponent.edit(client.pfcs.user.userName);   }; }; /*   edit( )   is called by a client to get or release control  * of the SharedText component. The   messages   shared object's  *   currentUser   slot may have one of three values. It can be  * empty, contain someone else's username, or contain  * the   userName   property of the person calling this method.  */ SharedText.prototype.edit = function (userName) {   var currentUser = this.so.getProperty("currentUser");   if (userName == currentUser)     this.so.setProperty("currentUser", "");   else if (currentUser != "")     return;   else     this.so.setProperty("currentUser", userName); }; SharedText.prototype.close = function ( ) { }; 

When the client requests control of the SharedText component, it calls the server-side edit( ) method on the client object. In turn, the client's edit( ) method calls the component's edit( ) method and passes the user's userName to it. If no one else is actively using the component, the messages shared object's currentUser property will be an empty string and the method will set it to the userName of the user making the request. If the user cancels editing the text area, edit( ) is called again and the currentUser property is set back to an empty string. If another user attempts to get access to the component while it is in use, the function will simply return without doing anything.

The shared object being used here is a persistent one. If the program crashes or the instance unloads without a user releasing control, the currentUser property may have a username in it from a previous session. So to start the session off correctly, the currentUser property is always set to an empty string. If the session is the first ever, the length property is set to contain a 0:

 this.so.setProperty("currentUser", ""); if (typeof this.so.getProperty("length") != "number") {   this.so.setProperty("length", 0); } 

Control of the shared text area is determined using the string in the currentUser property of the messages shared object. Now, all that is necessary to make the control of the component work properly is to have the client-side SharedText component send edit requests and respond correctly to changes in the shared object. Example 13-23 shows the client-side onSync( ) method from SharedText.as that responds to shared object changes.

Example 13-23. The client-side SharedText.onSync( ) method
 function onSync (ev) {   if (ev.list.length == 0 && messages.data.length > 0) {     replaceSharedText( );     updateButtons( );     return;   }   var info;   for (var i in ev.list) {     info = ev.list[i];     if (info.name == "length") {       replaceSharedText( );     } else if (info.name == "currentUser") {       updateButtons( );     } else if (info.name == "selectCount") {       showSelection( );     }   } } 

For brevity, the initialization code for the client-side component that connects to the messages shared object is not shown. The complete code is available on the book's web site. The messages shared object is persistent and therefore requires some special handling for the case in which a movie connects, disconnects, and then reconnects to the shared object. If the shared object has not changed between the time the movie disconnects and reconnects, the list passed to onSync( ) will be empty because the local copy of the shared object will already contain the same data that is in the remote shared object. To handle the situation, the onSync( ) method checks whether the list is empty but there is already data in the shared object. If so, the component is updated to correctly reflect the state of the shared object. Otherwise, the onSync( ) method calls replaceSharedText( ) if the number of entries in the shared object has changed, calls updateButtons( ) if the currentUser has changed, and calls showSelection( ) if someone has selected text in the text area. The three methods called by onSync( ) are listed in Example 13-24, Example 13-25, and Example 13-26.

The updateButtons( ) method listed in Example 13-24 is perhaps the simplest. It enables or disables each button, selects (toggles on) or deselects (toggles off) each button, and changes the label of the Edit button to Edit or Cancel as needed by calling one of three methods. For example, enableButtons( ) enables each button and highlights them all by setting selected to TRue . (The disableButtons( ) and defaultButtons( ) methods are not shown.) The updateButtons( ) method checks the currentUser property of the shared object against its own __user property that contains the username used when the person logged in. The method makes sure only the person who has control of the shared text area can edit it.

Example 13-24. The enableButtons( ) and updateButtons( ) methods
 function enableButtons (  ) {   editButton.enabled = true;   editButton.selected = true;   editButton.label = "Cancel";   sendButton.enabled = true;   sendButton.selected = true;   selectButton.enabled = true;   selectButton.selected = true;   deleteButton.enabled = true;   deleteButton.selected = true;   sharedTextArea.editable = true; } /* Whenever the   currentUser   property in the shared object  * changes, the buttons must be updated to correctly reflect  * this user's permission to edit the TextArea.  */ function updateButtons( ) {   var currentUser = messages.data.currentUser;   if (currentUser == "" && __user) {     defaultButtons( );   } else if (currentUser == __user.userName && __user) {     enableButtons( );   } else {     disableButtons( );   } } 

Whenever new text is added to the shared object, its length property is incremented and the replaceSharedText( ) method, shown in Example 13-25, is called. The method displays the text in the most recently added shared object slot and updates the versionStepper NumericStepper subcomponent to show the current version and to reset the stepper's limits.

Example 13-25. The replaceSharedText( ) method
 function replaceSharedText (ev) {   var len = messages.data.length;   if (len == 0) {     sharedTextArea.text = "";     versionStepper.minimum = 0;     versionStepper.maximum = 0;     versionStepper.value = 0;     return;   }   sharedTextArea.text = messages.data[len];   versionStepper.minimum = 1;   versionStepper.maximum = len;   versionStepper.value = len; } 

The showSelection( ) method, shown in Example 13-26, displays the version of text the user has selected and highlights a selected region using the <b></b> HTML tag.

Example 13-26. The showSelection( ) method
 function showSelection (  ) {   versionStepper.value = messages.data.selectVersion;   sharedTextArea.html = true;   // Wrap it in a bold tag.   var start = messages.data.startIndex;   var end  = messages.data.endIndex;   var slot = messages.data.selectVersion;   var msg = messages.data[slot].substring(0, start) + "<b>";   msg += messages.data[slot].substring(start, end) + "</b>";   msg += messages.data[slot].substring(end)   sharedTextArea.text = msg;   sharedTextArea.vPosition = messages.data.selectScroll;   sharedTextArea.redraw( );   sharedTextArea.html = false; } 

Unlike the TextChat component, the SharedText component updates the shared object directly.

Allowing a client to directly update a shared object is less secure than having the client request a change via a remote method call and then having your own server-side code accept or reject the request. However, updating shared objects directly can be more efficient because server-side code does not have to run and therefore tie up the single ActionScript thread for the instance on the server.


The SharedText component described here was originally written for an in-house project that was used by a small number of authenticated users. Improving the security of the component is discussed in Chapter 18.

The shareText( ) method, shown in Example 13-27, is called when the user clicks the Send button in order to update the shared text area. It creates a new slot in the shared object based on the number of numbered text slots, copies text in the TextArea subcomponent into it, and increases the count ( length ) of the number of text slots.

Example 13-27. The shareText( ) method adds new text to the messages shared object
 function shareText (ev) {   var len = messages.data.length;   if (messages.data[len] == sharedTextArea.text) {     return;   }   len++;   messages.setFPS(0);   messages.data.length = len;   messages.data[len] = sharedTextArea.text;   messages.setFPS(12);   disableButtons( ); } 

Similarly, the selectText( ) method, shown in Example 13-28, is called when the Select button is clicked; it selects the text version to display, and any text within it to bold, by directly setting properties of the messages shared object.

Example 13-28. The selectText( ) method
 function selectText (ev) {   messages.setFPS(0);   messages.data.startIndex = startIndex;   messages.data.endIndex = endIndex;   messages.data.selectVersion = versionStepper.value;   messages.data.selectScroll = sharedTextArea.vPosition;   if (!messages.data.selectCount) messages.data.selectCount = 1;   else messages.data.selectCount++;   messages.setFPS(12);   disableButtons( ); } 

Finally, the deleteVersion( ) method, shown in Example 13-29, is called when the Delete Version button is clicked and the user has control of the shared text area. It deletes the text in the version slot the user is currently viewing. Then it adjusts the slot numbers and length property so the version numbers run continuously from beginning to end without interruption.

Example 13-29. The deleteVersion( ) method
 function deleteVersion (ev) {   messages.setFPS(0);   var len = messages.data.length;   if (len < 1) {     return;   }   for (var i = versionStepper.value; i < len; i++) {     messages.data[i] = messages.data[i+1];   }   delete messages[len];   messages.data.length = len-1;   messages.setFPS(12);   disableButtons( ); } 

The SharedText component shown here is quite simple. Many more features, such as text formatting and text archiving, could be added. However, this simple component has proven one of the most successful for making online meetings usefulespecially the versioning feature. The shared area can be used to keep track of evolving task lists; to share, explain, and change source code; and to edit short memos or notices in advance of publication.

Now that we have the TextChat and SharedText components under our belts, let's look at a more complex video conferencing 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