Section 8.2. Getting a Shared Object in Flash

8.2. Getting a Shared Object in Flash

One reason shared objects cannot be extended, or subclassed, to create custom classes is that they are created by static methods of the SharedObject class rather than constructed using the new operator. In client-side Flash ActionScript, the two static methods to create shared objects are SharedObject.getLocal( ) and SharedObject.getRemote( ) .

8.2.1. Getting and Using a Local Shared Object

SharedObject.getLocal( ) returns a local shared object (LSO):

 local_so = SharedObject.getLocal("ConfigData"); 

Once returned, a local shared object can be used immediately. Properties and values can be created, stored in, and retrieved from the shared object's data property ( data is a generic Object instance):

 // Set up an   onStatus(  )   handler for this LSO. local_so.onStatus = function (info) {   if (info.code == "SharedObject.Flush.Failed") {     trace("Unable to save user information - space request not granted.");   } }; // Set two properties of the shared object. local_so.data.userName = "blesser"; local_so.data.password = "bigSecret"; // Try to write the data to disk. var result = local_so.flush( ); if (result == false) {   trace("Can't save user information because local storage is set to Never."); } 

The Flash editor provides code hints for shared objects when variable names end in "_so".

Local shared objects are not covered in detail in this book. See ActionScript for Flash MX: The Definitive Guide (O'Reilly) for a complete description of how to work with LSOs. A comparison of local path options for local and remote shared objects is included in "Locally and Remotely Persistent Shared Objects" later in this chapter. However, you may also want to consult the previously referenced book for a description of how to share local objects between movies from the same domain and how to use the local path parameter in getLocal( ) .

8.2.2. Getting and Using a Remote Shared Object

Remote shared objects are created using SharedObject.getRemote( ) but do not dynamically share information until they are connected to the server using a NetConnection object and have been synchronized with a copy of the shared object on the server. Setting up an RSO is a multistep process that begins with creating an unconnected RSO. The RSO must be associated with a relative URI (the shared object's name ) and an RTMP address (the URI of the application instance), both of which must be passed into the getRemote( ) method:

 nc = new NetConnection(  ); nc.connect("rtmp:/courseChat"); remote_so = SharedObject.getRemote("UsersData", nc.uri); 

Normally, a NetConnection object's uri property is passed into the getRemote( ) method as the RTMP address, but a string can be used as well:

 remote_so = SharedObject.getRemote("UsersData", "rtmp:/courseChat"); 

RSOs work across the network, so the next step is to connect the RSO instance in the Flash movie with the server by calling the shared object's connect( ) method and passing it a NetConnection object:

 if (remote_so.connect(nc)) {   trace("Connection object and URIs are OK. Wait for first onSync."); } else {   trace("Can't connect - check URIs and the NetConnection object."); } 

If the connection succeeds, the SharedObject.connect( ) method returns true , but the shared object is still not ready for use. The server may have to create its own copy of the shared object or copy the contents of an already existing shared object to the local copy that has just connected. When the server has completely synchronized its own copy of the shared object with the movie's copy, the onSync( ) method of the movie's shared object is called. The onSync( ) method should be defined before connect( ) is called to ensure that onSync( ) calls are not missed. The onSync( ) method is passed an array of information objects when it is called. After the initial call to onSync( ) indicates that synchronization has taken place, each information object will normally contain a message about a slot in the shared object. Example 8-2 shows a simple default onSync( ) handler that outputs all the information it receives, which is often useful during testing. This code belongs in the client-side Flash movie. The writeIn( ) function is shown in Example 5-1.

Example 8-2. A default onSync( ) handler
 SharedObject.prototype.onSync = function (list) {   writeln("=====SharedObject.prototype.onSync=====");   if (!this.synchronized) {     writeln("This shared object has been synchronized.");     this.synchronized = true;   }   for (var i in list) {          // Loop through all the objects in the array.     writeln("------------------");     var info = list[i];          // Get the object at position   i   in the array.     for (var p in info) {        // Loop through all the properties of the object.       writeln("i: " + i + ", " + p + ": " + info[p]);       if (p == "oldValue") {     // If a value changed, show the new one too.         writeln("newValue: " + this.data[info.name]);       }     }   } }; 

Once onSync( ) has been called at least once, the shared object is ready for use.

In client-side ActionScript, all data exchange is performed through the data property of a shared object. (Properties assigned directly to the shared object are ignored.) To write data to a slot, assign a value to a property of the shared object's data object. To read data from a slot, read a property of the shared object's data object.


This example shows how to write properties to the data property of the shared object:

 // Assign numbers or other primitive types using the dot operator. remote_so.data.ball_1_x = 0; // Assign a number. remote_so.data.ball_1_y = 0; // Assign a number. // Create an array. var shapeList = new Array( ); shapeList[0] = "lineStyle"; shapeList[1] = 2; shapelist[2] = "moveTo"; shapeList[3] = 0; shapeList[4] = 0; shapeList[5] = "lineTo"; shapeList[6] = 125; shapeList[7] = 0; shapeList[8] = "Z"; // Sentinel value to force drawing of the last command // Assign an array to a slot, in this example using the [] operator. var shapeName = "line_20"; remote_so.data[shapeName] = shapeList; // Assign an object, in this example using an object literal and the [] operator. var userName = "blesser"; remote_so.data[userName] = {fullName: "Brian Lesser", status: "Offline"}; 

Similarly, reading data from a shared object requires accessing the properties of the data object:

 // Move a movie clip to the coordinates stored in a shared object. ball_1_mc._x = remote_so.data.ball_1_x; ball_1_mc._y = remote_so.data.ball_1_y; // Create a new movie clip and draw within it using an array in a shared object. var sList = remote_so.data["line_20"]; var mc = this.createEmptyMovieClip("line_20_mc", 100); startData = -1; endData = -1; for (var i = 0; i < sList.length; i++) {   var token = sList[i];   if (typeof token == "string") {     if (lastCommand != undefined) {       mc[lastCommand].apply(mc, sList.slice(startData, endData));     }     lastCommand = token;     startData = i + 1;   } else {     endData = i + 1;   } } // Fill in the fields of a form using an object in a shared object. fullName_txt.text = remote_so.data[userName].fullName; status_txt.text = remote_so.data[userName].status; 

Movie clips should not be stored in a shared object slot; their properties, such as _x and _alpha , will not be sent to the server and other clients :

 remote_so.data.ball_1 = ball_1_mc; // Error: mc properties cannot be shared! 

A shared object cannot send movie clip properties to the server because the properties cannot be accessed by a for-in loop. Similarly, an object's properties that have been hidden using ASSetPropFlags( ) will not be sent to the server and other clients. Individual properties, such as MovieClip._x , can be assigned to a shared object slot.

Deleting a slot in a shared object is done using the delete operator:

 // Delete properties of the shared object. delete remote_so.data.ball_1_x; delete remote_so.data.ball_1_y; var shapeName = "line_20"; delete remote_so.data[shapeName]; var userName = "blesser"; delete remote_so.data[userName]; 

To delete all the properties of a shared object, you can loop through all the properties of the data object using a for-in loop and delete each one:

 for (var p in so.data) {   delete so.data[p]; } 

However, depending on how your application is designed, another client can add a new property at the same time you are trying to clear the shared object of all its properties. A shared object can be reliably cleared using Server-Side ActionScript (SSAS).

You cannot replace the data object with your own object by assigning it to the data property of a shared object. If you try to assign something to the data object itself, nothing happens:

 remote_so.data = new Object(  ); // Illegal (does nothing) 

Instead, assign the value to a property of the data object, as follows :

 remote_so.data.someObj = new Object(  );  // This works 

8.2.2.1 Private versus shared data

Whenever a slot in the data object is created, updated, or deleted, the corresponding slot on every other instance of the RSO is updated accordingly . However, properties that are added directly to the shared object itself remain private to the instance and do not cause a remote update:

 // Add a new property to the shared object that will not be copied // to remote instances. remote_so.synchronized = false; 

If you intended to create a shared property, use this instead:

 remote_so.data.synchronized = false; 

When you call SharedObject.getRemote( ) to get a reference to a shared object, it always returns the same object (if it is successful), regardless of how many times you call it. Consider the following short script:

 r1_so = SharedObject.getRemote("UsersData", nc.uri); r2_so = SharedObject.getRemote("UsersData", nc.uri); r1_so.synchronized = false; trace(r2_so.synchronized); // false 

Both r1_so and r2_so refer to the same remote shared object instance and have identical properties.


8.2.3. Updating Objects and Arrays in Shared Object Slots

When a shared object's slot contains an object or array, modifying an element in the object or array causes the entire contents of the shared object's slot to be sent to FlashCom and copied to all the remote instances of the shared object. For example, if a property of a shared object named line_20 contains an array, the following statement causes the entire array to be copied to every remote copy of the shared object:

 remote_so.data.line_20[4] = 3; 

This may sound very inefficient, and at times it is.

It is often necessary to design shared objects differently than you might design local objects in order to avoid large updates caused by small changes in data.


However, shared objects are not updated as soon as a change occurs. They are updated once within a set time interval and only if a change has occurred. Often this reduces the amount of redundant data being sent when many changes are made on an object or array in a slot. Update intervals are described in "Updates and Frame Rates" later in this chapter.

Reassigning the same value to an existing shared object slot will not cause an update. However, assigning new arrays or objects containing identical values will cause an update:

 // Changing the value in a shared object slot causes an update. remote_so.data.ball_1_x = 2; // Assigning the same value again will not cause an update. remote_so.data.ball_1_x = 2; // Changing the array in a slot causes an update. remote_so.data.line_20[4] = 4; // Assigning the same value that is already there does not. remote_so.data.line_20[4] = 4; var userName = "peldi"; var user1 = {fullName: "Giacomo Guilizzoni", status: "Online"}; var user2 = {fullName: "Giacomo Guilizzoni", status: "Online"}; // When adding a new property, the slot will be created on all copies. remote_so.data[userName] = user1; // Assigning a different object with identical contents will force an update. remote_so.data[userName] = user2; 

When an object or array in a slot is updated, a new copy of that object or array is created in all the remote shared objects (except in the movie where the slot was updated). The old object or array is replaced with a new one. This can produce surprising results if you are using a reference to an object or array in a shared object slot. For example, if one movie creates a reference to an object in a shared object slot this way:

 // Movie #1 gets a reference to an object in a slot. var peldi = this.data.peldi; 

then another movies updates the same slot in its copy this way:

 // Movie #2 changes the   status   property of the object in the same slot. this.data.peldi.status = "Offline"; 

When the update is complete, the peldi object will not refer to the same object as the one stored in remote_so.data["peldi"] . After the update, the following test will produce the results listed after each statement:

 // Movie #1 now has two separate objects with a different   peldi   property. trace(peldi == this.data.peldi);  // false trace(peldi.status);              // Online trace(this.data.peldi.status);    // Offline 

To fix this problem, the old reference should be overwritten with a reference to the new object once the change has occurred (see the next section for how to detect a slot change):

 // Movie #1 retrieves the most recent object in the slot. peldi = this.data.peldi; 

The ActionScript garbage collection process will dispose of the old object.

8.2.4. onSync( ) and Managing Change

Whenever the content of a shared object slot is created, updated, or deleted in one movie, the changes are sent to the server. If the update is successful, every movie connected to the same shared object will eventually have its copy of the slot updated. Updates occur at set intervals (and only if changes were made), so several slots may be updated in one batch. When the updates for one interval are complete, the onSync( ) method of the shared object is called and passed an array of information objects. Each information object contains a code property that indicates what happened (usually either a slot has changed or all slots have been deleted). Information objects may also have a name property, which contains the slot name, and an oldValue property, which contains the slot's value just before the slot was updated. When a movie attempts to add a new slot to a shared object or update an existing slot, the change will be either accepted or rejected by the server. The supported code values are described in Table 8-1.

Table 8-1. Properties of information objects passed to onSync( )

Code

Description

Name

oldValue

"success"

Indicates a slot change by the current movie was accepted. Other movies receive a "change" event for this slot.

Name of the new or updated slot

None

"change"

A slot was changed or added by another movie or by a server-side script. The first time onSync( ) is called after connecting to the shared object, existing values on the server are copied to the connecting movie's shared object.

Name of the new or updated slot

Indicates previous value (or none if a new slot)

"delete"

The local movie or a remote movie deleted a slot (sent to all movies connected to the shared object).

Name of the deleted slot

None

"reject"

Server rejected a movie's attempt to change a shared object slot. This can happen when there is contention between movies to update a shared object or when a locally and remotely persistent shared object connects.

Name of the rejected slot

Indicates previous value

"clear"

Indicates the shared object has been clearedthat is, it had no slots before the current set of updates. Occurs when onSync( ) is called for the first time after synchronizing with a temporary shared object or when a persistent shared object has gotten so out of sync with the server's copy of the shared object that it has to be clearedall its slots were deleted.

None

None


Even though the array passed into onSync( ) contains information objects that may contain any of the codes in Table 8-1, some or even all of them can often be ignored. Example 8-3 contains the definition of a short onSync( ) method that simply deletes the contents of a ListBox component and then repopulates it using shared object data.

Example 8-3. Tying together a shared object and a listbox
 Chat.prototype.init = function (nc) {   this.userList_so = SharedObject.getRemote("UserList", nc.uri);   this.userList_so.owner = this;   this.userList_so.onSync = function (list) {     this.owner.peopleList_lb.removeAll( );     for (var p in this.data) {       this.owner.peopleList_lb.addItem(p, this.data[p]);     }   };   this.userList_so.connect(nc) }; 

One reason for this simple approach is that the ListBox component does not provide a way to efficiently search for, update, and delete elements in the data provider array in which it stores list items. So, we call its removeAll( ) method to delete all the items and then use a for-in loop to repopulate the listbox using the addItem( ) method. In Example 8-3, the name and value of each shared object property are passed into addItem( ) as the label string and data parameters. See Example 13-1 and Chapter 15 for more efficient ways to update ListBox and DataGrid components with shared object data. Creating and managing shared objects within another object, such as the Chat object, is a common practice. In Example 8-3, this statement adds a reference in the shared object to the Chat object for later use:

 this.userList_so.owner = this; 

Since the owner property is not assigned to the data property of the shared object, it is simply a private property of the shared object and will not cause a shared object update. Within the shared object's onSync( ) method, it is used to get the Chat object and its properties:

 this.userList_so.onSync = function (list) {   this.owner.peopleList_lb.removeAll(  );   for (var p in this.data) {     this.owner.peopleList_lb.addItem(p, this.data[p]);   } }; 

Within onSync( ) , the keyword this refers to the shared object and not the Chat object. So this.owner refers to the Chat object and this.data refers to the shared object's data object. Example 8-3 shows only how to repopulate a listbox after a shared object's data has been updated. You may be wondering how the user-list data gets populated in the first place. The code that adds and deletes the properties of the user list is shown in Example 8-5. In that example, the user list shared object's properties are set using Server-Side ActionScript as clients connect and disconnect from an application instance. Server-side scripting of shared objects is described under Section 8.4 a little later in this chapter.

8.2.5. Yet Another Shared Ball Example

A shared ball sample application in which users drag around a movie clip so that other users see it move, has become the FlashCom equivalent of "hello world" for introducing shared objects. Macromedia's version of this application can be found in the server's samples/tutorial_sharedball directory. It displays real-time shared object updates as the ball is dragged around on the Stage, including responding to those updates when someone else drags the ball around.

Example 8-4 is a slightly more involved version of the same basic idea. A shared object, named BallPosition , contains the x and y coordinates of a shared ball movie clip. The slot names are named after the movie clip. If the shared ball movie clip is named ball_1_mc , the x and y coordinate slots are named ball_1_mc_x and ball_1_mc_y . When a copy of the shared ball movie first starts, the ball is colored red to indicate that its shared object is not synchronized. It can still be moved around on stage without any effect on other movies. After the movie connects and the shared object is synchronized for the first time, the ball turns green. If one or more movies are already connected and their movie clips of the shared ball are already synchronized, the most recently connected ball jumps to the latest position defined in the shared object. If no other movies are connected, the ball's current position is saved in the shared object. From then on, when the movie clip is dragged around the Stage, the following events occur:

  1. The global x and y coordinates of the mouse are retrieved and clipped to the rectangular area the ball is allowed to move within.

  2. The shared ball movie clip is moved to the x and y coordinates.

  3. The BallPosition shared object's slots are updated with the new x and y coordinates of the movie clip, and the changes are sent to other connected movies.

  4. The data objects of all movies are updated, after which the onSync( ) method of every copy of each movie's shared object is called. The onSync( ) method in the movie that changed the ball position is passed a list of slots with information objects (normally one for the x position and one for the y position) with a code value of "success." The other movies receive a code value of "change".

  5. The movies that receive a "change" notification move the ball movie clip to the coordinates in the shared object.

In summary, if one movie changes the x and y coordinates in the shared object, all the movies are notified of the change. The movie that made the change is notified that the change was successful, and the movies that did not make the change are notified that the data in their copy of the BallPosition shared object has changed. In response to the notification, the movie that made the change does not have to do anything. It has already placed the movie clip of the ball in the correct place. Every movie that receives a "change" notification must move the ball movie clip to its new location on stage.

What happens if two or more movies try to move the ball at the same time? One movie will be successful, and all the other's changes to the shared object will be rejected. Instead of a "success" code , a value of "reject" is received in the information objects in the list; the movie receiving the rejection must update the clip position to match the location stored in the shared object from the last movie that moved the ball.

Like Example 8-3, Example 8-4 places the code that gets and manipulates the shared object within another object. In this case, the SharedBall class defines the behavior of a shared ball movie clip and contains all the shared object code. The code in Example 8-4 is the complete listing for the SharedBall class, but the listing does not show the complete working example, which is available on the book's web site. When a ball is created in the movie, it is passed a reference to the NetConnection object that has just connected to the server. The SharedBall class takes it from there. Note that Example 8-4 uses some AS 2.0 syntax. The book's web site also contains an AS 1.0 version of this example.

Example 8-4. A SharedBall class containing a shared object
 class SharedBall extends MovieClip {   var xSlot:String; // Slot holding _x location of the shared ball   var ySlot:String; // Slot holding _y location of the shared ball   var bounding_mc;  // Initially, the name of the parent clip's bounding area   var left:Number, right:Number, top:Number, bottom:Number; // Travel limits   var so;  // SharedObject, but not typed, so we can add the   synchronized   property   var color:Color;   var ballColor_mc:MovieClip;   /* SharedBall constructor function. The   bounding_mc   property may be set    * in the property dialog box or within the init object passed to attachMovie( )    * when this clip is created.    */   function SharedBall ( ) {     // Save the name of the slots this shared ball will use in the shared object,     // such as   ball_1_mc_x   and   ball_1_mc_y   xSlot = _name + "_x";     ySlot = _name + "_y";     // Set the left, top, right, and bottom properties that act as movement limits.     setTravelLimits( );     // Get a color object for the movie clip that gives the overall clip its color.     color = new Color(ballColor_mc);     color.setRGB(0xff0000);   }   /* The registration point for this clip is the center of the ball.    * The radius is, therefore, half the width or half the height.    * Keeping the center one radius length from the bounding box's edge    * keeps the ball entirely within the bounding box.    * If the   bounding_mc   property is the name of a clip in the parent,    * use it as the bounding box; otherwise use the Stage as the bounding box.    */   function setTravelLimits ( ) {     bounding_mc = _parent[bounding_mc];     if (!bounding_mc) {       bounding_mc = {_width: Stage.width, _height: Stage.height};     }     var radius  = _width / 2;     left   = bounding_mc._x + radius;     right  = bounding_mc._x + bounding_mc._width - radius;     top    = bounding_mc._y + radius;     bottom = bounding_mc._y + bounding_mc._height - radius;   }   /*   onConnect( )   is called after the movie has connected to the application    * instance. It is passed a reference to the connected NetConnection object.    * It sets up the   BallPosition   shared object.    */   function onConnect (nc) {     // Get the RSO named   BallPosition   so = SharedObject.getRemote("BallPosition", nc.uri);     // Create a private flag property named   synchronized   so.synchronized = false;     // Create a private reference, named   owner   , to the Ball object.     so.owner = this;     // Define the   onSync( )   handler.     so.onSync = function(list) {       // If   synchronized   is not set, this is the first call.       if (!this.synchronized) {         this.synchronized = true;         // Tell the ball clip we have just synchronized.         this.owner.onSynchronized( );       }       /* If any movies have changed the ball's position, tell the        * ball clip that a change has occured. We do not need to watch        * for "success" codes here, as the ball has already been moved        * by this movie when a "success" code is received. If an update        * is rejected, move back.        */       for (var i in list) {         if (list[i].code == "change"  list[i].code == "reject") {           this.owner.onChange( );           break;         }       }     }     // Don't forget to connect to the shared object!     so.connect(nc);   }   // Color the ball green when the ball is synchronized.   function onSynchronized( ) {     // Turn green to show the ball is synchronized to the shared object.     color.setRGB(0x00ff00);     // If there are no _x and _y values in the shared object     // for this ball, set them.     if (typeof so.data[xSlot] == "undefined") {        so.data[xSlot] = _x;        so.data[ySlot] = _y;     }   }   // Change the ball position--see   so.onSync( )   .   function onChange( ) {     _x = so.data[xSlot];     _y = so.data[ySlot];   }   // Color the ball red when the network connection is closed.   function onDisconnect ( ) {     color.setRGB(0xff0000);   }   /* When the user clicks on the ball movie clip, define an   onMouseMove(   ) method    * to move the ball within the travel limits defined by the left, top, right,    * and bottom properties. Then update the shared object with the new position.    */   function onPress ( ) {     onMouseMove = function( ) {       // Get the mouse position.       var x = _root._xmouse;       var y = _root._ymouse;       // Make sure x and y are within their travel limits.       if (x < left) {         x = left;       } else if (x > right) {         x = right;       }       if (y < top) {         y = top;       } else if (y > bottom) {         y = bottom;       }       // Move the clip to (x, y), and store the coordinates in the shared object.       so.data[xSlot] = _x = x;       so.data[ySlot] = _y = y;     }   }   // Stop dragging when the mouse is released over the clip.   function onRelease ( ) {     delete onMouseMove;   }   // Stop dragging when the mouse is released outside the clip.   function onReleaseOutside ( ) {     delete onMouseMove;   } } 

Inside the SharedBall.onMouseMove( ) method are two statements that attempt to both set both the position of the shared ball movie clip and update the shared object:

 so.data[xSlot] = _x = x; so.data[ySlot] = _y = y; 

In the code snippet, the x and y variables contain the coordinates to which to move the ball. Sometime after these statements are executed, the onSync( ) method of the SharedBall clip is called and this short for-in loop checks each information object in the list:

 for (var i in list) {   if (list[i].code == "change"  list[i].code == "reject") {     this.owner.onChange(  );     break;   } } 

Inside the onSync( ) method, this refers to the shared object. The code property of each information object in the list is checked for the value "change" or "reject". If either value occurs, the SharedBall clip is told to change its position to the coordinates currently in the shared object. The onSync( ) method notifies the SharedBall clip that a change has occurred by calling a method of its owner object, which is in fact the SharedBall clipsee the SharedBall.onConnect( ) method, which sets the shared object's owner property to refer to the SharedBall clip. Here is the SharedBall.onChange( ) method that sets the position of the ball:

 function onChange (  ) {   _x = so.data[xSlot];   _y = so.data[ySlot]; } 

In this example, the SharedBall class contains the shared object and is completely responsible for creating, updating, and responding to it.

Example 8-4 also fixes a small bug in the shared ball sample that ships with FlashCom versions 1.0 and 1.5. The onMouseMove( ) method in the Macromedia example sets the location of the movie clip in the shared object before checking to see if the mouse cursor has moved off stage. You can therefore drag and release the ball off stage and it will sometimes stay completely out of view where you can no longer click on it to drag it around. If you quickly drag the ball off stage and release it, the shared object position is set to the offstage position, but the movie clip's position is set back to being on stage. However, when onSync( ) is called, it moves the clip to whatever coordinates are in the shared object. To correct the bug, make the shared object update the last statements in the onMouseMove( ) method as shown in Example 8-4.



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