Saving Data with the Local Shared Object

[ LiB ]  

The local shared object enables you to save variables in a file on the user 's hard drive. This way, you can restore any variables saved during previous visits to your application. Local shared object files (LSOs, or SOLs as their file extension is .sol) are nearly identical to cookies, but offer one big advantage: Variables containing any Flash data type get restored to the same data type. Because cookies only support string data, storing other data types involves a "to string" conversion when saving and then a "from string" process when restoringwhat a drag! If the variables in your LSO file contain arrays full of objects with nested strings (or anything really), they get restored just as you saved them. Another advantage LSOs have over cookies is your movie doesn't have to be playing in a browser to workFlash projectors can effectively read and write data to disk.

Note

Remote Versus Local

Later you'll see how the Flash Communication Server supports remote shared object files (RSOs). Not only do RSOs remain on your sever, you also can share the contained variables with multiple simultaneous users. Really, however, the two differ in purpose: LSOs are for permanent storage between sessions, and RSOs immediately notify each user when another user makes a change to a variable. The good news, however, is they share similar syntax and data structuresso you'll be able to apply what you learn in this chapter to other topics.


The applications for LSOs storing data for later retrieval are wide reaching. Basically, the idea is to give the users a break by not making them do anything more than once. For example, once they see an intro animation, they shouldn't be forced to see watch it again. Or suppose they enter their name once; the next time they visit you shouldn't ask again. And just like standard HTML, you can display previously visited links in gray. This chapter provides examples such as these, and hopefully you'll think of other scenarios where it would be convenient to store a variable so that it can be read back in later.

Before looking at creative solutions to practical challenges, let's first look at the technical details.

Using the Local Shared Object

The basic syntax for LSOs involves first creating a variable instance and identifying the LSO's filename, and then setting variables (contained in the data property of the variable instance). Listing 6.1 shows the skeleton form.

Listing 6.1. Local Shared Object Skeleton
 1 my_so=SharedObject.getLocal("filename"); 2 my_so.data.someVariableName="a value"; 

The first line does two things: creates an instance of the shared object in the variable my_so and identifies the LSO file that gets saved to. If you're familiar with most objects (for instance, the Sound object or Color object), line 1 breaks the expected form that uses the constructor new (as in my_color=new Color()) . Another weird thing is that if the file "filename" doesn't exist (and it won't the very first time this code is run), it gets created as soon as data is flushed to disk ( specifically , when you close the Flash movie or implicitly call the flush() method). (More about where the LSO gets saved in a minute.) After you have the variable (called my_so here), you can set as many variable's values by storing them as named properties inside the built-in data property. For example:


 my_so.data.oneVar="a"; my_so.data.otherVar="b"; //...and so on 


Note

Code Analogy to Shared Objects

Because the shared object's structure is just an object inside an object, it might help to visualize how you'd have to build it if you had to do it by hand:


 my_so={data: {} } //or: my_so=new Object(); my_so.data=new Object(); 


From this point you can access properties inside data (such as my_so.data.someProp ).

The confusing part is that shared objects handle all of this for you, so you don't actually do it this way.


Seeing Whether a Shared Object is Populated

Before moving on to more details, note that all users will either be returning or they'll be first-time users. For returning users, you may want to restore their settings; for new users, you may want to take them through some initialization process. Shared objects for return users have a data property full of named properties containing values, but a new user's shared object will actually have the data property but without any properties. That is, when checking whether the user is returning or new, you can't just check whether the data property is undefined . This won't do it:


 1 my_so=SharedObject.getLocal("userdata"); 2 if(my_so.data==undefined){ 3 //do initialization stuff 4 }else{ 5 //check values 6 } 


The point is, as soon as you create the shared object variable ( my_so ), it will in fact have a data property. A corrected version of line 2 must involve a specific property name that you set, and therefore a property you expect to find in the shared object of a returning user. This property name will be undefined for new users ( if(my_so.data.expectedVariableName==undefined){ and so on). You'll see this issue arise in all the examples that treat new and returning users differently.

Storing Shared Objects on the User's Machine

The seemingly esoteric topic of exactly where shared object files get saved is worth discussing now. There's an optional second parameter in the getLocal() methodnamely, a way to specify where the physical file will get saved and hence a way to override the default location. You can't just store SOL files anywhere on a user's hard drive. You're given a folder that matches your domain name, which resides inside a special Flash Player folder (which appears in slightly different locations depending on your operating system). By default, SOL files appear in subfolders that exactly match your web site's folder structure, and then inside folders that match the SWF filename, and finally in SOL files that match the name provided in the first parameter in getLocal() . Figure 6.1 shows this visually: id.sol is created by my code getLocal("id") when you visit the machine.swf file at www.phillipkerman.com/machine/.

Figure 6.1. In the Flash Player folder on the user's hard drive, SOL files appear in a specific folder structure.

graphics/06fig01.jpg


The default structure seems convenient enoughand it's certainly clear who created each file. But what if you want access to the variables in a shared object when visiting a different part of a web site? For example, maybe I want to read that ID data (in id.sol ) when a user visits my main web site as well as the machine subfolder. Anyway, all that explanation is to say that to point to an SOL in a location other than the default, just provide a second parameter beginning with the standard HTML conventions for root: / . For example, both of the calls in Listing 6.2 access the same file.

Listing 6.2. Sharing Shared Objects Throughout a Site
 //in phillipkerman.com/machine/machine.swf id_so=SharedObject.getLocal("id","/"); //in phillipkerman.com/site.swf id_so=SharedObject.getLocal("id","/"); 

Alternatively, you can specify subfolders (such as "/all_sos") into which the SOL files get saved. This folder structure doesn't need to match your web site's structure because you're just specifying how the folders get structured on the user's machine.

Flushing Data to Disk

One more tiny technical detail before delving into the examples: The updated values for a shared object are automatically written to disk (that is, "flushed") when the user leaves the particular page. Although waiting until this moment makes me nervous, it actually works fine even if they close the browser. If you plan to store a ton of data (well, anything more than the default 100K per domain), however, the user must first okay it through the Settings dialog box, Local Storage tab (see Figure 6.2).

Figure 6.2. Through the Local Storage tab in the Settings dialog box, users can control how much disk space is given to a shared object file.

graphics/06fig02.jpg


The System Settings dialog box automatically appears ( open to the Local Storage tab) the instant Flash tries to flush data that exceeds the user's limit. When using flush() , Flash checks only the current size of the LSO. If you expect the LSO to grow in size and want to reserve enough space (so that Flash doesn't wait), you can pass the number of bytes you expect to use. It makes sense to check that the user will accept using the disk space, before the user spends time setting a ton of variables.

To reserve 500K, for instance, which is really a lot, you can use the following:


 my_so.flush(500000); 


But it's not that easy. Although the two possible outcomes are simple (success, because they accepted it; or failure, because they didn't), you need to give the users who see the dialog box enough time to answer...and then check their answer. Listing 6.3 provides a universal code sample that does it all.

Listing 6.3. Disk Space Checker
 1 my_so = SharedObject.getLocal("myFile"); 2 my_so.onStatus = function(info) { 3 if (info.code == "SharedObject.Flush.Failed") { 4 enoughSpace = false; 5 //and trigger failure code 6 } 7 if ( info .code == "SharedObject.Flush.Success") { 8 enoughSpace = true; 9 //proceed with application 10 } 11 }; 12 var result=my_so.flush(500000); 13 if (result) { 14 enoughSpace = true; 15 //proceed with application 16 } 17 

Because you have no idea how long the user will take to answer the dialog, you structure a callback for the onStatus event (that gets triggered when the user finally does answer). In line 2, the argument is named info , and we're looking for one of two explicit strings based on the value of the code property inside that parameter. Line 12 initiates the attempt to reserve space. Notice, flush() will return a value (either true or "pending") . If there's already enough space allotted, line 13 sees result is true and proceeds without incident. If not, the only two ways back into the code are accounted for in lines 3 and 7. Realize that if you put any code in line 17, it would execute right away. That is, the flush() won't just hang out and wait for the user to answer the dialogthat's what onStatus is for.

You might think the info object in onStatus is unnecessarily complex. Granted, it'd be way easier if onStatus just passed back true or false (based on success)but that's not how it works. Macromedia instead passes an object with properties so that they can easily expand it later. Basically, it's just a convenient way to stuff a ton of values in a single argument (the same reason anyone uses objects).

Note

Displaying System Settings

You can force the dialog box to appear by using System.showSettings() . In addition, you can optionally pass 0, 1, 2 , or 3 to cause one of the four tabs in the dialog box to appear (Privacy, Local Storage, Microphone, or Camera respectively). Basically, any time that dialog box needs to appear, it will do so automaticallybut now you know how to do it manually.


Practical Examples Involving Local Shared Objects

Now that you understand nearly every detail of local shared object files, you should find the following examples not only useful, but also easy to figure out.

Making a Skip-Intro (Forever) Feature

Without getting into whether you should have an intro animation, quite often you do need to show the user something once (teach them, if you will) but not twice. Check out the simple solution in Listing 6.4.

Listing 6.4. Skip Intro
 1 intro_so=SharedObject.getLocal("intro"); 2 if(intro_so.data.seenIntro){ 3 gotoAndStop("postIntroFrame"); 4 }else{ 5 intro_so.data.seenIntro=true; 6 gotoAndPlay("beginIntro"); 7 } 

Pretty simple, isn't it? Line 2 checks whether the seenIntro value is true . If so, the user jumps to frame "postIntroFrame" . Otherwise, the seenIntro property is set to true and the user goes to the frame where the intro begins. If you want to give the user an option to view the intro (for instance, a See Intro Again button), you can't just jump back to this frameit will take the user directly to "postIntroFrame" . I suppose you could first set intro_so.data.seenIntro to false and then jump back, but I was thinking to replay the intro you just take them back to play the "beginIntro" frame.

Getting a Username Once

Perhaps the most common use for cookies is to save a username. Instead of making users type in their name every time, just store the data on their computer and it will appear as if your site remembers them. We'll start with a simple version in Listing 6.5, and then look at something a bit richer.

Listing 6.5. Saving a Username (Simple)
 1 username_so=SharedObject.getLocal("username"); 2 username_txt.text=username_so.data.username; 3 4 username_so.onChanged=function(){ 5 username_so.data.username=username_txt.text; 6 }; 

Notice that instead of checking whether the LSO had a username property in line 2, I just populated the input text field username_txt . It just doesn't seem to be a problem to populate a field with undefined . Finally, line 5 is the one that resets the value for the username property (inside the data of username_so , naturally). Notice how convenient the text field's onChanged event is. Without it, I'd have to come up with some logical time to go and set the shared object's value (perhaps when the user clicks OK or something). This technique is foolproof, even though it's slightly inefficient how it resets the value after every keystroke the user makes. (By the way, I could have used the onChanged listener variation.)

Refining the Login App

Listing 6.6 adds a password option to the preceding example. The following code won't do any validation on the password. (This is something that needs to be handled on the server side anyway.) All you need is two input text fields (instance names username_txt and password_txt ), a CheckBox component ( remember_ch ), and a Button component ( proceed_pb ), as shown in Figure 6.3.

Figure 6.3. A couple of input text fields and two components are all you need to save a username and password.

graphics/06fig03.jpg


Listing 6.6. Saving a Username and Password
 1 userinfo_so = SharedObject.getLocal("userdata"); 2 if(userinfo_so.data.username==undefined){ 3 username_txt.text = ""; 4 }else{ 5 username_txt.text = userinfo_so.data.username; 6 } 7 password_txt.text = ""; 8 //if remembering... 9 if (userinfo_so.data.remember) { 10 remember_ch.value=true; 11 password_txt.text = userinfo_so.data.password; 12 setRememberMode(); 13 } 14 //always save username 15 username_txt.onChanged = function() { 16 userinfo_so.data.username = username_txt.text; 17 }; 18 //remember checkbox 19 remember_ch.addEventListener("click",setRememberMode); 20 function setRememberMode() { 21 //get and save their preference 22 var whichWay=remember_ch.value; 23 userinfo_so.data.remember=whichWay; 24 if (whichWay == true) { 25 //set password and start monitoring 26 userinfo_so.data.password = password_txt.text; 27 password_txt.onChanged = function() { 28 userinfo_so.data.password = password_txt.text; 29 }; 30 } else { 31 //clear password, and stop monitoring 32 userinfo_so.data.password=null; 33 password_txt.onChanged=null; 34 } 35 } 36 //proceed push button 37 proceed_pb.addEventListener("click", proceed); 38 function proceed(){ 39 trace("username "+username_txt.text); 40 trace("password "+password_txt.text); 41 } 42 //optional: 43 password_txt.password=true; 

The code probably looks worse than it is. Initially, the username_txt field is either cleared (the first time when username is undefined or is filled with the value of username (line 5). By default, the password_txt field is cleared (line 7); if the LSO's remember property is true , however, line 10 sets CheckBox's value to true , line 11 populates the field, and then line 12 triggers the setRememberMode() function (which is mostly redundant here except for lines 27 through 29, where monitoring of password changes commences). Lines 14 through 17 are from the previous code listing. Line 19 triggers the function setRememberMode() any time the user clicks the remember_ch CheckBox. Inside setRememberMode() , line 22 ascertains their preference, and then line 23 sets their preference in the LSO. Finally, provided the user wants to save the password, line 26 sets the password property to the current value in the field and lines 27 through 29 set up the onChanged callback (so it gets resaved as the user types). Notice that lines 32 and 33 both clear the variable and stop the monitoring of the onChanged event.

Finally, starting on line 37 I show how the push button can just trace the user's input username and password. Of course, you must pass those to some authentication routine. (Here I just wanted to show that you cannot just pass the local shared object's password property, because it will have no value when the remember property is false.) Notice, too, that line 43 will make the password_txt field show asterisks no matter what is typed (but makes it harder to test while developing).

Note

Doing What You Say You're Doing

It's interesting to note that it would have been easier to go ahead and save the user's password in the LSO, but just not reveal it unless the user opted to "remember." That's not too cool because the LSO file can be hacked like any cookie (by someone with access to the user's computer). The implication that unchecking the Remember Me option will destroy any record of the password is important enough to abide by it.


Tip

Saving Multiple Usernames

For a challenge, try expanding this feature so that several people can log on via the same computer. That is, save more than one person's username and password, make the usernames appear in a ComboBox component, or automatically fill in the password when a user types a username that matches one saved previously. You can download my solution from www.phillipkerman.com/rias.


Save Feature for the Slide-Show Maker

In Chapter 4, "Working with Complex Data," we built a slide-show maker. It was mainly an exercise in structuring an array full of objects the user could populate. At the time, we included a Save button but didn't program it. Now you'll see in Listing 6.7 how to save all the data (no matter how complex) into an LSO. Refer to the file created in Listing 4.8, "Complete Slide-Show Code" (or download it).

Listing 6.7. Adding a Save Feature to the Slide-Show Maker

Start with Listing 4.8. To enable the Save button, comment out line 10 to read as follows :

 //save_pb.enabled=false; Cut lines 40-43 and in their place type this code: 40 show_so=SharedObject.getLocal("show"); 41 if(show_so.data.show==undefined){ 42 show ={pages:[], 43 time:new Date(), 44 showname:show_txt.text}; 45 }else{ 46 show=show_so.data.show; 47 refreshList(); 48 show_lb.selectedIndex=0; 49 clickLine(); 50 show_txt.text=show.showname; 51 } 52 function doSave(){ 53 show.showname=show_txt.text; 54 show_so.data.show=show; 55 } 

Basically, if the LSO's show property is undefined (line 41), the show variable initializes just as before (lines 42 through 44). Otherwise, line 46 stuffs the show property into the show variable. Incidentally, you could have replaced every instance of show with show_so.data.show , but that would have been more typing. Lines 47 through 49 restore things by triggering old code. Line 50 restores the title of the saved show.

Finally, doSave() (which is the Save button's "clickHandler") saves the title and then stuffs the whole show variable back into the LSO.

In the online version of this application, you'll see that I threw in a few enhancements. For one thing, the preceding code just saves one slide show. You'll find that the code to save multiple shows really isn't too much more workmainly you just have to add interface elements to let users name their shows and the means to select one to load. In addition to the "edit show" mode that you've seen so far, I also added a "play show" feature that removes everything except the content and fancy forward and back buttons . It really is fun adding useful features when you have a good foundation.

The main thing to take away from this exercise is just that any variable can be saved in an LSO no matter how complex.

Limitations of the Local Shared Object

Before moving on to other data exchange mechanisms, you need to understand when LSOs aren't appropriate. The fact that all the data is saved locally means users who log in from different computers will effectively find their data is lost (if they're not at their same computer). For this reason, you want to consider storing just a minimum of data locally and store most of the data remotely (on the host server). Okay, so you may be thinking that I just finished showing you all the cool ways to store a ton of data locally and now I tell you not to. Not really, just realize that other strategies may make more sense than keeping everything local.

LSOs are great for standalone projectors (for example, to store user bookmarks or other preferences). The default location for these SOL files is within a folder called localhost, and then inside a folder structure that matches the hard drive where the projector is run. This means that if you move the projector, a new SOL is created. Just remember you can always provide a second parameter when performing the getLocal() command to override where the SOLs appear. Although the fact new SOL files are created may appear as nothing more than an annoyance to overcome , you can, in fact, take advantage of this. It's a quick-and-dirty way to maintain separate values. For example, just run the slide-show maker from two different locations, and you'll have two independent LSOs.

Finally, you can't really store any data type in an LSO. People have tried without success to store a Sound object or a MovieClip object. It just doesn't work. It would be kind of cool, I suppose, to create an image with the MovieClip's Drawing application programming interface (API) and save the clip containing the image, but it just doesn't work that way. You could, however, very easily store the data necessary to redraw the image when the user returns to the file.

I guess that because the local shared object is so convenient, I thought I'd point out some of the limits for balance.

[ LiB ]  


Macromedia Flash MX 2004 for Rich Internet Applications
Macromedia Flash MX 2004 for Rich Internet Applications
ISBN: 0735713669
EAN: 2147483647
Year: 2002
Pages: 120

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net