Using Shared Objects

An SWF file can save data (variables as well as array, XML, and other data objects) to a user's hard drive using shared objectssimilar to but more powerful than the cookies used by Web browsers. You can use shared objects to store information generated by the user while viewing your movie (name, last frame visited, music preference, and so on). Shared objects can be used by movies played in a Web browser as well as those turned into stand-alone projectors.

You can use shared objects with any of the following (for example):

  • XML.load

  • XML.sendAndLoad

  • LoadVars.load

  • LoadVars.sendAndLoad

  • LoadVariables

  • LoadVariablesNum

  • XMLSocket.connect

  • Importing a shared library

The following is an example of a script you might use to create a shared object:


var myObject:SharedObject = SharedObject.getLocal("stuff_I_saved");


If the shared object stuff_I_saved already exists on the user's hard drive, its data is loaded instantly into myObject. If stuff_I_saved does not yet exist, it's created and still referenced by myObject. In the latter case, myObject would be emptythat is, it would contain no data.

NOTE

If used as just mentioned, the getLocal() method will create a shared object if none exists, or will retrieve data from an existing shared object.


graphics/11inf11.gif

As you can see from the previous syntax, the shared object's name is actually "stuff_I_saved"; however, in ActionScript you can't reference the shared object directly using that name. Therefore, a reference to the shared object is created using myObject. This means that whenever you reference myObject in a script, you're actually referencing the shared object named "stuff_I_saved"a tricky concept but essential to understanding how ActionScript deals with shared objects.

Data is saved to a shared object using the data property. Take a look at the following example:


myObject.data.userName = userName_txt.text;


This would save the userName variable (and its value, the text in the userName_txt text field) in the shared object. You can save entire objects as well. For example, if you wanted to save an array contained by your project, you would use the following syntax:


myObject.data.savedArray = nameOfArray;


A single shared object can contain multiple bits of data simultaneously:


myObject.data.savedArray = nameOfArrayObject;

myObject.data.savedXML = nameOfXMLObject;

myObject.data.userName = userName_txt.text;


A particular piece of data can be erased from a shared object using null, as in the following example:


myObject.data.userName = null;


If userName were a piece of data in the shared object, the preceding script would delete it.

You can delete an entire shared object by using the clear() method of the SharedObject class:


myObject.clear();


Extracting data from a shared object is similar to creating data in one:


userName_txt.text = myObject.data.userName;


In the userName_txt text field, the preceding script will display the value of userName in the shared object. If this variable doesn't exist in the shared object, the value displayed in the text field will be undefined.

When the SWF session ends (that is, the movie is closed or exited), all the information under the data property of your shared object is automatically written to the shared object file, ready to be retrieved using the getLocal() method described earlier. You can force a shared object to be written and saved at any time by using the flush() method. For example:


myObject.flush();


This line of ActionScript forces your shared object and all the data it contains to be saved. Because myObject references the shared object named "stuff_I_saved", this is the object that will actually be saved.

Flash stores all shared objects in a central location on the user's hard drivethe exact location depends on where the movie resides that created the shared objects.

On Windows XP, all shared objects are stored in the following general directory:

Documents and Settings\<username>\Application Data\Macromedia\Flash Player\

where <username> is the name of the user who was logged on when the shared object was created.

On a Mac, the location is as follows:

System Folder\Preferences\Macromedia\Flash Player\

TIP

Depending on the version of your operating system, the location of shared object files may vary somewhat. To locate shared object files on your machine, search for files with an .sol extension.


These are both general pathsthat is, when a movie creates a shared object, a new subdirectory is created at one of the previously mentioned locations. For example, if you were to view a movie at the following URL:

http://www.electrotank.com/fun/games/MiniGolf.swf

any shared object created by this movie would, by default, be saved at the following path on a Windows machine:

Documents and Settings\<username>\Application Data\Macromedia\Flash Player\electrotank.com\fun\games\MiniGolf

Notice how this subdirectory's path structure matches that of the URL.

graphics/11inf12.gif

Because a movie played locally (such as a projector) doesn't exist at a URL, Flash will save shared objects that it creates to a localhost directory:

Documents and Settings\<username>\Application Data\Macromedia\Flash Player\localhost

All these directory paths are default paths where shared object data is stored. You actually have a lot of latitude as to where a shared object is stored or retrieved from within the general directory. Using the previous example, imagine playing a movie at the following URL:

http://www.electrotank.com/fun/games/MiniGolf.swf

This movie has the following shared object:


myScores = SharedObject.getLocal("scoreData");


This shared object is saved to the following path in Windows XP:

Documents and Settings\<username>\Application Data\Macromedia\Flash Player\electrotank.com\fun\games\MiniGolf\scoreData.sol

Flash will look for this same location again when the movie is played from that URL; however, the getLocal() method lets you add an optional directory path where the shared object should be saved. Assuming the movie at the aforementioned URL has this shared object declaration:


var myScores:SharedObject = SharedObject.getLocal("scoreData", "/fun");


the shared object would be saved to the following path:

Documents and Settings\<username>\Application Data\Macromedia\Flash Player\electrotank.com\fun\scoreData.sol

Armed with this knowledge, you can create movies at different locations that use the same shared objectuseful if you want all the movies on your site to reference a "master" shared object containing information about the user. Simply save a shared object in the main (/) directory.

Be careful when using a single shared object across movies. Any one of the shared objects has the potential of overwriting the data it contains with new data.

A single movie can create, save, and load multiple shared objects simultaneously.

TIP

You can configure the amount of data that a given URL can store by using the Flash player. If you right-click the window of an open SWF and select Settings, you'll see the Local Storage controls. You can block any site from storing information on your machine.


graphics/11inf13.gif

In this exercise, you'll create a journal that saves text entries in an array as a shared object.

  1. Open journal1.fla in the Lesson11/Assets folder.

    You will notice one frame with four layers, named according to their contents. The stage contains two text fields that will be used to display information. The large text field in the center, journalBody_txt, will be used for journal entries. The smaller text field at the bottom of the screen, entryNumber_txt, will be used to display the current journal entry number. The Buttons layer contains the Prev, Next, New, and Save buttons, which have instance names of previous_btn, next_btn, new_btn, and save_btn, respectively.

    graphics/11inf14.jpg

    This application will allow you to start a new journal entry, save it, and browse through the entries you've created.

  2. With the Actions panel open, select Frame 1 in the Actions layer and then add the following script:

    
    var myJournal:SharedObject = SharedObject.getLocal("JournalObject");
    
    

    This line of ActionScript creates a reference to the shared object JournalObject. This object can be read and modified using the myJournal reference set up here. When using myJournal in the following scripts, we're actually working with the shared object named JournalObject.

  3. Add the following conditional statement just below the line of script you added in Step 2:

    
    if (myJournal.data.journal == undefined) {
    
      myJournal.data.journal = [];
    
    }
    
    

    This statement looks in the shared object for an array named journal. If it doesn't find one (undefined), the action within the statement creates the journal array.

    NOTE

    If an array is created, it automatically becomes part of the shared object when the movie is exited or the shared object is saved using the flush() method.

    The journal array will appear undefined the first time the movie is played. Each subsequent time the movie is played, the array will exist and this action will be ignored.

    TIP

    It's a good idea to check for undefined data values in a shared object. This allows you to assign default values the first time a movie is played by the user.

  4. Add the following function definition at the end of the current script:

    
    function displayEntry(num:Number) {
    
      var entry:String = myJournal.data.journal[num - 1];
    
      if (entry != undefined) {
    
        entryNumber_txt.text = num;
    
        journalBody_txt.text = entry;
    
      }
    
    }
    
    

    This function does two things: it sets the value of two text fields on the stageentryNumber_txt and journalBody_txtbased on the value of num. Then, a conditional (if) statement is used to specify what should occur if the user has saved an entry in the journal.

    As shown in Step 3, when the application is first used (as opposed to reopening it after adding an entry), an array named journal is created on the shared object. By default, a new array object always has a length of 1, indicating that it contains a single value at index 0, which is initially a value of undefined. The first time the application is used, myJournal.data.journal[0] contains a value of undefined. This value doesn't change until the user deliberately saves an entry into that index number.

    In Step 5, we will script a call to this function:

    
    displayEntry(myJournal.data.journal.length);
    
    

    The first time the application is used, the length of the journal array will be 1; therefore, the function call will look like this:

    
    displayEntry(1);
    
    

    The first line in the displayEntry() function that we just defined uses the parameter value passed to it (in this case, 1) to set the value of entry. That line of script gets evaluated this way:

    
    entry = myJournal.data.journal[1 - 1]
    
    

    or, broken down further:

    
    entry = myJournal.data.journal[0]
    
    

    As already mentioned, if the user has never saved a journal entry at index 0 (such as the first time the application is used), entry is assigned a value of undefined; otherwise, it will contain the text of the first entry.

    The conditional (if) statement looks at the value of entry, and performs an action only if the value of entry is not undefined. If entry has a value of undefined, the function does nothing and the entryNumber_txt and journalBody_txt text fields will be empty. This only occurs when the user has never saved a journal entry. After the user has saved at least one entry in the journal, the actions in the conditional statement are executed.

    Assume that the user has saved nine entries and then reopens the application. In this circumstance, the displayEntry() function we just defined will be called and passed a parameter value of 9:

    
    displayEntry(9)
    
    

    NOTE

    This function call is added and explained a bit more in the next step.

    As a result, the value of entry, within the function, is assigned a value representing the text of the ninth entry in the journal (which is actually stored in index 8 of the journal array, as explained later in this lesson). This will cause the actions in the conditional statement to execute because entry is no longer undefined. The first action displays the value of the number passed to the function in the entryNumber_txt text field, which in this case is 9. The second action displays the value of entry in the journalBody_txt text field.

    The reason for subtracting 1 from the value of num, as shown in the first line of the function definition, is that the index (within the journal array) where each entry is saved is always one less than its actual entry number. Therefore, the fifth entry is saved in index 4, the sixth entry in index 5, and so on. The reason is that array indexes begin at 0, but we want our entry numbers to begin with 1; therefore, this conversion keeps them in sync. Several of the scripts that follow employ similar logic.

    This script is probably the trickiest to understand of the entire project. Be sure to review it several times until you feel comfortable with how it works.

  5. Add the following function call to the end of the current script:

    
    displayEntry(myJournal.data.journal.length);
    
    

    Because this function call exists on Frame 1, it's executed as soon as the movie plays. The displayEntry() function (which was defined in Step 4) is called and passed a value based on the length value of the journal array in the shared object. This will display the final entry that the user made before exiting the movie. For example, if the journal array has three entries, the displayEntry() function is passed a value of 3 and the third journal entry is displayed. If the journal array has just been created (as described in Step 3), it will contain a single, empty element; therefore, a length of 1 gets sent to the function.

  6. Add the following function definition to handle saving data:

    
    function save() {
    
      var num:Number = Number(entryNumber_txt.text) - 1;
    
      myJournal.data.journal[num] = journalBody_txt.text;
    
      myJournal.data.flush();
    
    }
    
    

    As mentioned earlier in this lesson, data is automatically saved to a shared object when a movie is exited. By using the flush() method, as shown here, you can save data at any time while the movie is playing. This function will be called when the Save button is clicked (see Step 11). Let's take a look at how this function works.

    The first line in the function creates a variable named num. The value of this variable is set to the current value displayed in the entryNumber_txt text field, minus 1. The Number() function is used to make sure num contains a numerical value. The num value is used in the next line of the function to reference the appropriate array index of the current journal entry as it relates to the current entry number. As mentioned in Step 4, the number displayed in the entryNumber_txt text field is actually one more than the associated array index it references, which is why 1 is subtracted from the current entry value in the first line of script. (Keep reading. This will make more sense in a moment.)

    The next line in this function definition uses the value of num to update the journal array with the text displayed in the entryNumber_txt text field. As always, the best way to understand this is by using a sample scenario. Imagine that the current entry number displayed in the entryNumber_txt text field is 9. When this function is called, num would be set to a value of 8 (9 - 1). The second line in the function would be evaluated as follows:

    
    myJournal.data.journal[8] = journalBody_txt.text;
    
    

    This will place the text in the journalBody_txt text field into index 8 of the journal array. Note again that the current entry number is 9, but the currently referenced index number of the array is 8. (see Step 4 for more on this topic.) This line of script can affect the data in the array in two ways: if index 8 was previously empty (undefined), it will now contain text; if it previously included text, that text will be overwritten.

    graphics/11inf15.jpg

    The last action in the function uses the flush() method to force the data and shared object to be saved to the user's hard drive. For our project, that will include all of the entries that exist in the journal array.

  7. Add the following function definition to create a new journal entry:

    
    function newEntry() {
    
      entryNumber_txt.text = myJournal.data.journal.length + 1;
    
      journalBody_txt.text = "";
    
      Selection.setFocus("journalBody_txt");
    
    }
    
    

    The first action in this function sets the current journal entry number (entryNumber_txt text field) to the length of the journal array, plus 1. If the journal array has two entries, it has a length of 2. Adding 1 will cause 3 to appear in the entryNumber_txt text field. This action causes all new entries to be inserted at the end of the array. The last two actions in this definition are used to empty the journalBody_txt text field and then to give it focus so that the user can immediately begin typing his or her entry. To better understand this, let's take a look at how this function works in harmony with the save() function discussed in the Step 6.

    Assume that there are two entries in the journal array. This means that the array has entries at index positions 0 and 1 (important to remember), and that it has a length of 2. When the function in this step is executed, the entryNumber_txt text field displays 3 (the length of the journal array plus 1) and the journalBody_txt text field will be emptied. The user now types text into this text field and clicks the Save button, which calls the save() function defined in Step 6. At that point, the save() function subtracts 1 from whatever is displayed in the entryNumber_txt text field, which in turn saves the current text in the journalBody_txt text field to index position 2 of the journal array. The journal array now contains three entries at index positions 0, 1, and 2, and its length is 3. If the function is called again, the process begins again.

  8. Add the following function definition, which will be used to display the next entry in the journal:

    
    function nextEntry() {
    
      var num:Number = Number(entryNumber_txt.text) + 1;
    
      if (num > myJournal.data.journal.length) {
    
        num = myJournal.data.journal.length;
    
      }
    
      displayEntry(num);
    
    }
    
    

    When executed, this function displays the next journal entry in the array. It does this by assigning a value to num based on the current numerical value displayed in the entryNumber_txt text field, plus 1. This value represents the next journal entry to be displayed. To prevent our application from displaying an entry that doesn't exist, the value of num is compared against the total number of entries in the journal array (the length property of journal). If the value of num is greater (as the if statement determines), you're attempting to display a nonexistent entry. In that case, the action within the if statement resets the value of num to the length property value of the journal array, in effect causing the last entry in the array to be displayed instead. The final action in this function calls the displayEntry() function and passes it the value of num, enabling it to display the appropriate journal entry.

  9. Create the following function to display previous journal entries:

    
    function previousEntry() {
    
      var num:Number = Number(entryNumber_txt.text) - 1;
    
      if (num < 1) {
    
        num = 1;
    
      }
    
      displayEntry(num);
    
    }
    
    

    This function works similarly to the function described in Step 8. num is given a value representing the current entry number minus 1. The if statement prevents the application from displaying anything beyond journal entry 1. Here's how it works.

    Suppose the user is currently viewing entry 6. When this function is called, num is assigned a value of 5 (6 - 1) and that value is checked to make sure it's not less than 1. Because it's more than 1, the action within the if statement is ignored, and the displayEntry() function is called and passed a value of 5, displaying journal entry 5.

    If the user is viewing entry 1 when this function is called, num would initially be assigned a value of 0. The if statement would determine that this value is indeed less than 1 and change its value to 1. The displayEntry() function would then be passed a value of 1. Because entry 1 is already being displayed, it will appear as if nothing has changed onscreen. As mentioned, this mechanism prevents browsing past entry 1 because no entries exist at entry 0 or less.

  10. Add the following onRelease event handler for the new_btn instance:

    
    new_btn.onRelease = function() {
    
      newEntry();
    
    };
    
    

    When the user clicks the new_btn button instance, the newEntry() function is called, advancing the current entry number by 1 and clearing the journalBody_txt field so that new text can be entered.

  11. Add the following onRelease event handler for the save_btn instance:

    
    save_btn.onRelease = function() {
    
      save();
    
    };
    
    

    When the user clicks the save_btn button instance, the save() function is executed, at which point the current text in the journalBody_txt field either replaces an existing entry or is added as a new entry in the journal array (as described in Step 6).

  12. Add the following onRelease event handler for the previous_btn instance:

    
    previous_btn.onRelease = function() {
    
      previousEntry();
    
    };
    
    

    The call to the previousEntry() function changes the display to show the journal entry created before the current one that's displayed.

  13. Finally, add the following onRelease event handler for the next_btn button instance:

    
    next_btn.onRelease = function() {
    
      nextEntry();
    
    };
    
    

    graphics/11inf16.gif

    This ActionScript simply calls the nextEntry() function when the button is clicked. The screen is then updated to display the next entry in the list of journal entries.

  14. Choose Control > Test Movie to test your work. Enter some text as a journal entry. Click the Save button to save the entry and then the New button to create a new journal entry. Click the Save button, and restart the movie.

    When you restart your movie, the shared object will be loaded as described in Steps 23, and any data previously saved can be browsed using the Prev and Next buttons.

  15. Close the test movie and save your work as journal2.fla.

    Thus far in this lesson you have learned the basics of creating, retrieving, and saving shared objects.

    You can also use shared objects to save any of the following (for example):

    • User's name

    • Last frame visited

    • User's music preference

    • Date of user's last visit

    • User's ordering preferences

    • Scores (for games)

    • Appointments, addresses, lists

    • Property values (x, y, alpha, rotation, and so on) of elements