Working with Complex Data in Flash MX 2004
Although computers are great at gathering, manipulating, and calculating raw data, humans prefer their data presented in an orderly fashion. The same way all the world events can be organized into a newspaper by editors, rich Internet applications (RIAs) should present just the important data. If you're buying a house in a new town, for instance, you don't need to know all the prices ever paid for houses, but you may want to know the median price paid in the past 90 days. Unfortunately, as the programmer, you have to sift through all the data gathered to extract just the important partsand present it in a visually interesting way.
This chapter covers different ways to store, manipulate, and generally manage data. You'll see how storing all data in an easily accessible way makes your job easier when it comes to extracting and presenting just the meaningful parts. The orderly data you gather in this chapter leads straight into Chapter 5, "Presenting Data," where you'll see ways to present the data.
I keep saying "all this data" as if all the world's information is at your disposal. (Actually it is, as you'll see during the discussion about linking to web services in Chapter 7, "Exchanging Data with Outside Services.") But from where does the data come? In fact, the really cool applications you build will involve importing data from outside sources and then presenting that data inside Macromedia Flash MX 2004. There are lots of sources for outside data, including databases, web services, and other connected users. This chapter concentrates on working with data after it's inside Flash. Realize the simplest source of data is from you, the authorjust type it in. For this chapter's apps, that's the source of the data.
Of course, everything you learn in this chapter also applies to manipulating data gathered from outside sources. This apparent non sequitur of learning to manipulate data before you learn how to gather it actually makes sense. You'll be better equipped to decide how the data should be gathered after you can clarify your preference for how it's organized inside Flash. For example, only after you measure and decide which windows to dress can you order the curtains. In any event, there's lots of interesting information about manipulating data inside Flash.
Specifically, this chapter covers the following topics:
-
Identifying the appropriate data type for variables
-
Comparing homemade (or "generic") objects to arrays
-
Sorting arrays and parse objects
-
Structuring data for easy access and manipulation
Structuring Data
You can't control the weather, but if you're gathering weather information, you can certainly control how the data is organized. That's what this section is about: deciding how data is stored so that it's easy to access just the parts you need. You'll see that structuring data is totally up to you. The goal is to make the data organization easier for later when you analyze and display the data.
This section first examines selecting the data's form (its data type), then compares two particularly useful data types (arrays and objects), and thenfinallyshows you how to figure out how to get at the specific values contained.
NOTE
Comparing Arrays and Objects Both arrays and objects are discussed as comparable ways to store data (each with its respective benefits). In fact, arrays are a type of object. As such, objects offer much more than arrays, so I don't want you to see arrays as an equal. When it comes to how you structure data, however, they are definitely comparable.
Data Types
Variables are nothing more than containers for data. You make up a variable name, set its value, and then you can access the data contained. The type of data is up to you. Strings, numbers, arrays, and objects are all examples of data types. Only if your variable contains a number can you do "number things" to itsuch as mathematical operations like divide and subtract. If your variable contains a string, you can do "string things" such as capitalize or extract specific characters. How you treat or what you store in a variable depends on its data type.
NOTE
Flash MX 2004's Strong Data Typing ActionScript 1 is an "untyped" language, which means you can store one data type in a particular variable (say, a string) and then later store a different data type (say, a number) in that same variable and the contents will always be treated appropriately. You can still do that. If you use ActionScript 2.0 (AS2), howevera Publish Setting optionyou can take advantage of strong data typing. This just means that for the first use of any named variable, you can specify it will always be treated as a particular data typesay, String. Then, later, if you mistakenly use that variable where a number is required, or otherwise treat it as anything but a string, your movie will not compile. That is, you'll see a Type Mismatch error in the Output panel when you test or publish the movie. The advantage is that Flash can perform slightly faster because it doesn't have to constantly check what data type a particular variable contains. Additionally, it can help make your code clearer. Perhaps the best side effect is that because Flash will know what data type your variable contains, it will trigger code hints when you later type the variable name followed by a period. The downside is if you don't follow the rules, it becomes a real hassle because your code won't compile.
The way you cast a variable as a particular data type is with the casting operator (:). After a variable's first declaration, type : followed by the data type you want to use. It looks like this:
var myName:String="Phillip"
Notice that :String gets inserted into an otherwise perfectly legitimate line of code. Also, you may see casting for parameters. For example, the following code says the myFunction() accepts a number only:
function myFunction (someParam:Number){}
Use this when you feel ready. In the meantime, just realize what the colon is used for, because you'll likely see strong typing in example files (but not much in this book).
Although there are many data types, they are always treated one of two ways: as primitive (also called value) and by reference. The difference arises when you copy a variable. Primitive variables (which include numbers and strings) are copied "by value," whereas reference types (including arrays, objects, and clip instances) are copied "by reference." Copying a primitive variable really copies the variable's current value, whereas copying a reference variable just creates a pointer to the original variable. This can mess you up if you copy a variable and then change the original and see that the copy also changes. Take this example:
1 myOriginal=[1,2,3]; 2 myCopy=myOriginal; 3 myOriginal[0]="one"; 4 trace(myCopy);
Line 3 changes the myOriginal array, but that also changes myCopy because it isn't a copy of the value; instead, it's just a reference to the original.
The way you can copy just the contents of the myOriginal array is by replacing line 2 with this code:
myCopy=myOriginal.slice();
The slice() method can also copy a portion of your array; when you forgo any parameters, however, it copies each element. Note that this solution only works when the array is one level deep (that is, when you don't have any nested arrays).
A great analogy is how copying a file on your computer is like copying by value, whereas making a shortcut (or alias) to that file is copying by reference. If you change the original reference variable type, all references to it change as well.
The following list categorizes typical data types as primitive or reference.
Primitive (or Value) |
Reference |
Number |
array |
String |
object |
Boolean |
function |
Undefined |
movieclip |
null |
|
By the way, you can always see the type of a particular variable by doing trace(typeof someVariable);. Just replace someVariable with the variable in question. In addition, there's an operator called instanceof. With this, you can check whether a variable contains a particular data type. For example, trace(_root instanceof MovieClip); should display true in the Output window.
The difference between primitive and reference type variables is important, but above all you need to keep track of the data type you're storing in each variable. By following a logical and consistent way of naming each variable, you're making a good start. Also, documenting how each variable is used when commenting is another good habit. Actually, using AS2's strict data type casting is a way to force you. However you do it, know what type of data is contained in a variable before you go accessing them.
Homemade Objects Versus Arrays
If you ever felt limited by the idea that each named variable can contain only one value at a time, surely you've discovered arrays. I like the analogy that an array is like a line-ruled sheet of paper where you can write something different on each line (or row). A sheet of graph paper is like a multidimensional array because you can store different values in each cell, based on row and column. The many benefits of arrays come down to convenience and expandability. It's more convenient to store a bunch of phone numbers in a single named array variable instead of coming up with a new name for each one. Also, an array is expandable if, say, you want to let users add as many contacts as they want to an address book. You'll see how easy arrays are managed in the "Sorting Data" section later in this chapter.
Making Generic Objects
In a similar way, generic objects enable you to store lots of values in a single variable (also called associative arrays, or short objects, or just objects). The major benefit generic objects offer is that each value in the object has a name. You should think of the values as properties because that's how they're accessed. Just like how a clip has an _x property and an _alpha property, your generic objects can have any properties you name. To reference the phone property, you could use myObject.phone, provided your object name is myObject.
NOTE
Associative Arrays Aren't Arrays Personally, I don't like the term associative array because generic objects don't support any of the cool array methods such as sorting.
What follows is a quick rundown on how you formulate your own generic objects.
Use new Object() to initialize:
myObject=new Object();
Then, start creating and populating properties using standard dot syntax:
myObject.someProperty="a value"; myObject.anotherProperty="another value";
Alternatively, you can create and populate in one line (in what's called a literal way) by surrounding with curly braces, placing properties followed by a colon and its value, and separating properties with commas, as follows:
myObject={someProp:"a value", otherProp:"val 2"};
I generally prefer the first approach (creating the object and then adding properties). However, the literal one-line way is convenient because when you don't really need a variable, you're passing just the value of an object.
In either case, to access an object's various properties' values, use the standard dot syntax:
trace("someProperty is "+myObject.someProperty);
(As discussed later in this chapter, you also can access properties using a string inside brackets, with no dot: myObject["someProperty"].)
NOTE
Nesting Data Types Arrays and objects break the concept of name/value pairs. Whereas a plain variable containing a string does just have a name and a value, an array has one name and several values. In the case of an object, the object has a name, but contained within the objects' value is a collection of named propertieseach with its own value. When you start nesting data types, it can get even more complex. For example, you could have an array full of objects. Or, the value of one property in an object could be an object itself. Keep in mind that you can usually design the data structure to fit your needs so that instead of "deciphering," you'll just be drilling down to access the data you need.
This discussion would be rather academic without application examples. Remember, you decide how to structure the data. Your data structure is completely subjective, but you should strive to make things easy for yourselfeasy to access and change data as well as easy to adapt to unforeseen changes. Consider a contact management applicationa rolodex if you will. Listing 4.1 shows one way to structure the data:
Listing 4.1 Array of Contact Objects
1 contacts=[]; 2 contact1=new Object(); 3 contact1.first="Phillip"; 4 contact1.last="Kerman"; 5 contact1.phone="503-555-1212"; 6 contacts.push(contact1); 7 contacts.push({first:"George", 8 last:"Bush", 9 phone:"800-555-1212"});
In the end, the contacts array has a generic object in each slot (or index). Each object represents a different person and contains the three properties first, last, and phone. Lines 7 through 9 demonstrate how you can create a literal object (in this case, immediately pushing it into the array); lines 2 through 6 do effectively the same thing, but create an unnecessary variable (contact1).
Although creating the contacts array in the preceding example may seem like a bit of a chore, the benefits of this structure are apparent when you begin accessing the data. For example, you can reference the phone number of the first contact with the expression contacts[0].phone. Only because you happen to have the variable contact1 can you similarly say contact1.phonebut you don't have to. The expression contacts[0] returns the entire object in the first slot; so if you just add .phone, you'll be grabbing the phone property of the object in the array.
Listing 4.2 provides a quick example of the sorts of gymnastics possible with the data structure for contacts:
Listing 4.2 Extracting Contact Object Data
1 myField_txt.text=""; 2 for(var i=0;i<contacts.length;i++){ 3 var thisOne=contacts[i]; 4 myField_txt.text+= Number(i+1)+": "; 5 myField_txt.text+= thisOne.first + " "; 6 myField_txt.text+= thisOne.last + ", "; 7 myField_txt.text+= thisOne.phone + "\r"; 8 }
The loop started in line 2 repeats while the variable i is less than the length of the contacts array. For each iteration, line 3 conveniently stores an entire object (in the current index). It turns out the difference in saying thisOne or contacts[i] isn't muchbut when you need to dig even deeper into an array or object, such temporary variables prove really helpfulif for nothing else, to reduce your typing. Anyway, lines 4 through 7 continue to add formatted text to the myField_txt's text property. Notice that to control the output, at the end of each line I concatenate a colon, space, comma, or return character ("\r").
NOTE
Start Counting at Zero Remember, in Flash arrays (and strings) start counting with zero for the first index. Just to make things interesting, ColdFusion starts counting with one.
The output looks like this:
1: Phillip Kerman, 503-555-1212 2: George Bush, 800-555-1212
Benefits of Arrays
The beauty of this structure is that you can add as many contacts as you want. Finally, remember that this is just one way to structure the data. Another design might involve an array full of arrays. So where you were pushing an object before, you could push an array instead:
contacts.push(["Phillip", "Kerman", "503-555-1212"]);
The one downside to this structure is that you must remember "first name is first, last name is second, and phone number comes third." I can think of two reasons why a generic object is better in this case:
The data for each person is known. (That is, you always need those three attributes.)
Giving names to those three properties makes them easier to access (as opposed to memorizing the order). This also puts your data in context, which can help you later or others reading your code.
Generally, try to use generic objects if the data has much more meaning when named and use arrays if the quantity of data is unknown or you expect it to grow and shrink. The following sorts of data are best suited for an array or for object:
-
Arrays
-
Objects
Several values to be used in statistical analysis, such as test score averages
A collection of bookmarks the user saves
A variety of color values to be selected randomly
User profiles such as name, rank, number
Current user preferences such as music on or off, background color, and language
An individual bookmarkwhich can be saved in an array with other bookmarks, but the individual bookmark can have properties for page number and the given name for the page
To drill home the idea that you can nest arrays and objects as deeply as you want, here's one more variation to the contacts array (presumably to make things more convenient, not just convoluted). Suppose you want to include each contact's children's names. You could start over and take the approach in Listing 4.3.
Listing 4.3 Objects Containing Arrays Nested in an Array
1 contacts=[]; 2 contacts.push({first:"Phillip", 3 last:"Kerman", 4 phone:"503-555-1212", 5 kids:["Savannah"]}); 6 contacts.push({first:"George", 7 last:"Bush", 8 phone:"800-555-1212", 9 kids:["Jenna", "Barbara"]});
Notice in lines 5 and 9 that way down inside the kids property of each nested object is an array (containing one or more strings).
This way, you could later determine how many children a contact had by using the following:
contacts[i].kids.length;
Notice that this example uses an array as the value for each contact's kids property. This makes sense because you don't know how many children someone may have, and there's really no need to identify each child with property names such as child1, child2, and so on.
By now you should be getting a better sense how arrays and objects differ in their usefulness. Keep in mind that the general rule (that arrays are better for unlimited and unclassified data) can be broken. And, you can always change your mind later. However, designing the data structure is an architectural decision best suited for early in a production.
Using Objects Like Arrays
The biggest limit of objects is they lack all the cool array methods (such as sort(), push(), and pop() to name a few). You'll learn how to manipulate arrays using methods in the next section. For now, however, you need to understand that although the standard for-loop (as used earlier) won't let you loop through properties in an object, the for-in loop will! Listing 4.4 provides a template that you can use with any generic object.
Listing 4.4 Looping Through an Object
1 myObject={prop:"val",prop2:"val2"}; 2 for (p in myObject){ 3 var thisProperty=p; 4 var thisValue= myObject[p] 5 trace(thisProperty+":"+thisValue); 6 }
Basically, the for-in loop (when used with an object) will set the iterant variable (p in this case) to a string version of the actual property name. Notice in line 4 that you can ascertain the value of the property by placing the string property name inside brackets (instead of following a period in as in the standard dot syntax).
Dynamically Referencing Properties
The fact you can grab properties' values by using either object.property or object["property"] may seem insignificant, but this brings out a very important point. Using the "string in brackets" access is the only possible way when you fashion a property name dynamically. This can certainly happen if you use an object for data that might have been more convenient as an array. Consider this simple object:
dogs={dog_1:"max", dog_2:"hank", dog_3:"skippy"};
Suppose you have another variable currentDog and its value is 2. To access "hank", you could use the following expression:
dogs["dog_"+currentDog];
That is, "dog_"+currentDog equals "dog_2"; but because that's a string, you need to surround it with brackets. I promise this technique will come up time and again. You also should know this technique works with clip instance names. That's because movie clip instance names are like properties of the timeline where they reside. The syntax looks the same as object.property after all:
_root.box_1._x=100;
Following the same rule of string names in brackets, you could use the following:
_root["box_"+1]._x=100;
Naturally, the 1 could be a variable. Notice that after _root there's no dot. Notice, too, that although you can normally leave off _root (as in box_1._x=100) when using this bracket reference, you must precede it with the path to the movie clip.
If this section wasn't totally clear, consider rereading it because you'll see many instances of nested values referenced this way.
Associating Functions with Event Properties
It's actually possible to store references to functions inside properties of an object. And as it turns out, I can't think of many times when it's anything more than a convenience. (Perhaps you plan to trigger one of several different functions and decide to store which one inside an object.) The reason I mentioned this, however, is that so many advanced features use that model. Consider this example:
my_btn.onPress=function(){play();};
It looks like the my_btn button instance has a property called onPress into which a literal function is stored. That's exactly what it is! The only thing special is that onPress is built in...and Flash knows when to trigger it. You can store functions in a ton of other expected property names, and they'll trigger at the appropriate times. You'll learn all the important properties that work this way in time, but for now look at these example fragments in Listing 4.5 to see how familiar they are knowing what you know now.
Listing 4.5 Examples of Storing Functions in Properties That Match Flash Events
1 my_sound.onComplete=function(){ trace("sound done");}; 2 myResponder.onData=function(data){ 3 trace("the data " + data + "was received"); 4 } 5 my_connection.onStatus=function(info){ 6 if(info.code=="NetConnection.Connect.Success"){ 7 play(); 8 } 9 }
The basic idea here is associating functions with known Flash events (in the form of properties). Line 1 defines a trace() command when the my_sound instance of the Sound object completes playing a sound. Line 2 differs slightly because the onData event (triggered when outside data is received) includes an argument (called data in this case) that contains the actual data received. Finally, Line 5 shows that when the onStatus event triggers, the argument received is actually an object itself that may contain a property called code. Receiving an object as the argument is a convenient way to stuff lots of named data (as properties) instead of having multiple argumentsand it happens to be the standard way a lot of the advanced events work. (Incidentally, onStatus is the event triggered when data returns from the server. You'll see it when using the Flash Communication Server (in Chapter 8, "Foundation Communication Server," and Chapter 9, "Advanced Communication Server"), although it also pops up a few other places.
Generic objects are more than just a great way to store complex data (although, that's the main reason you're studying them now). The investment learning is paid back time and again throughout ActionScript. In addition to how many events are triggered (as you just saw), Flash functions commonly return or accept single-object parameters chocked full of named properties. This way, a ton of data can be compacted into a single variable name. Here are just a few methods that accept or return generic objects: setTransform(), getBounds(), getTextExtent().
For an example that's new to Flash MX 2004, check out all the details you can find out about how text will display using getTextExtent(). The properties height, width, ascent, descent, and textFieldRequiredHeight are all crammed into one generic object. Here's an example:
my_fmt=new TextFormat(); my_fmt.font="Arial"; my_fmt.size=32; details=my_fmt.getTextExtent("Phillip"); trace("height is "+details.height); trace("width is "+details.width); trace("ascent is "+details.ascent); trace("descent is "+details.descent);
You'll learn about the TextFormat object (the first 3 lines) in the next couple of chapters. For now, notice that getTextExtent() returns, into the variable details, an object with several properties that you can then display in the Output window. You may not need everything in that details variable, but there's only one methodit just returns several values inside the properties of a generic object.
The bulk of work managing complex data involves first designing the structure, then importing it (or populating inside Flash), and then accessing individual elements as needed. To filter out just the parts you need, often you'll want to leave the original data untouched as you copy elements. However, there are plenty of times when the original data isn't so sacredyou'll want it permanently modified. This chapter deals with actually changing the data, whereas the next chapter on presenting data covers how to filter.
Comparison to DataProvider Object
Despite having said the data structure is up to you, the developer, I have been gravitating toward arrays containing generic objects. This just happens to match the format of columns and rows (rows being the slots of the array, and columns being the names of properties contained in each object). So, it's a good format to use when appropriate.
On top of the good reasons to use this format, it just so happens it matches the general structure of the DataProvider object. (Yes, another object type.) It's so cool how DataProviders work with components that if I try to explain it here, the book may catch fire. It's covered in detail in Chapter 12. If you just want to see something cool, drag a List component on stage (orbetter yetif you have Flash Pro, drag a DataGrid) and name the instance my_component. Copy the code from Listing 4.3, where the contacts array was populated, and add the following code after it:
my_component.dataProvider=contacts;
Test the movie and you'll see that the component automatically fills with the data (see Figure 4.1). What's really wild is that if the contacts array changes later (using the DataProvider's addItem() method in place of how you might expect to use push()), the component will update and stay in sync. I know that we're not really talking about presenting data yet, and certainly not covering components yet, but this was so cool I had to mention it. The good news is that DataProviders are like arrays full of generic objects but more.
Figure 4.1 You can link your data to a component by assigning a DataProvider.
Sorting Data
One of the best ways to help users analyze complex data is to sort it in some logical manner. Consider the contacts array from earlier in this chapter: a user could quickly find a particular contact if the array were sorted alphabetically; or, maybe, the user wants to list all the phone numbers from a particular area code. By sorting, you simplify large quantities of complex data.
Using the sort() Method
The good news/bad news is that Flash has several built-in sorting routines, but they often don't work the way you expect or need. Consider this simple array:
prices=[230,345,179,365,300,159];
Suppose these are house prices (in thousands of dollars, naturally) and you plan to plot them on a graph. This is a perfect case for the Array object's sort() method. The array will actually change order when you execute the following code:
prices.sort();
Note that unlike some methods that just return a value, sort() permanently changes the array.
I don't know whether it was evil of me to show this example first, because it was almost too easy. Now try this one:
people=["van Hoff", "Smith", "Adams", "Zwebber"]; people.sort(); trace(people); //outputs: Adams,Smith,Zwebber,van Hoff
The problem is that although Flash can sort numbers fine, it considers all uppercase letters to come before any lowercase. Naturally, it's totally conceivable to force everyone's name to start with an uppercase letter, but that's not ideal. It turns out that Flash MX 2004 adds a few useful sorting options that can help you avoid difficult workarounds. For example, you can sort without regard to case using myArray.sort(Array.CASEINSENSITIVE). (Just type Array. and you'll see a code hint with the rest of the sort variations.) Finally, you can combine sort options by separating them with |. Suppose you want to sort descending and not case sensitivejust pass Array.CASEINSENSITIVE | Array.DESCENDING.
Here's an adjusted version of the preceding example:
people=["van Hoff", "Smith", "Adams", "Zwebber"]; people.sort(Array.CASEINSENSITIVE); trace(people); //outputs: Adams,Smith,van Hoff,Zwebber
Using the sortOn() Method
The sort() method definitely only works on arrays. Even when your arrays contain generic objects in each slot, however, you can take control of the sorting. The sortOn() method is especially designed for sorting arrays full of objects. Listing 4.6 some starter data that is used for the next few examples:
Listing 4.6 songs Array Containing Objects
songs=[]; songs.push({artist:"Fugazi", songtitle:"Waiting Room", time:"02:53"}); songs.push({artist:"Beastie Boys", songtitle:"Alive", time:"04:48"}); songs.push({artist:"Radiohead", songtitle:"Creep", time:"03:56"});
Even a moderately complex array (such as this one that contains objects) requires a different tack when sorting.
At this point, you can't use songs.sort() because Flash can't put objects in order. Because you also can't do trace(songs) (try it if you want), I've come up with the following function in Listing 4.7 for testing purposes.
Listing 4.7 Utility Function to Display the songs Arrays Contents
1 function traceAll(theArray) { 2 var total = theArray.length; 3 for (var i = 0; i<total; i++) { 4 for (var p in theArray[i]) { 5 trace(p+"="+ theArray[i][p]); 6 } 7 trace("----"); 9 } 9 }
Most of this should be a review, but do notice the temporary variable total. Line 2 stores the length of the array so that line 3 doesn't repeatedly (on every loop) recalculate the array's length. This gets around a slight quirk in Flash's efficiency.
To use this function, just pass an array. Keep in mind that this only goes one level deep into the array's contained objects. You can download a loop that will dig as deep as necessary from http://www.phillipkerman.com/rias.
At this point, you can use sortOn() to specify any property name on which to perform a sort. For example, you can sort by artist name:
1 trace("before:"); 2 traceAll(songs); 3 songs.sortOn("artist"); 4 trace("after:"); 5 traceAll(songs);
Basically, line 3 is the only one doing any work; the rest is to see the results. The sortOn() method just accepts a string name for the property on which you want to sort the array's objects.
This data can be sorted by "artist", "title", or "time", and it works in all cases. However, sortOn() has the same limits as sort(). Namely, to sort without regard to case (or any other variation), you need to either write your own compare function or use one of the built-in sorting options (such as Array. CASEINSENSTIVE). (For example, try changing "Beastie Boys" to "beastie boys" and sortOn() renders unfavorable results.) It's pretty simple: Just pass your compare function or built-in option as the second parameter in sortOn(). Say, songs.sortOn("artist",Array.CASEINSENSITIVE).
Finally, you can pass an array of property names and sortOn() will have a logic to follow when there are matches. For example, you want to sort by artist, but if you have a bunch of Beastie Boys you want those sorted by song title. Just use songs.sortOn(["artist", "songtitle"]). (Notice, that's one array in the first parameteryou can still use the second parameter for a sorting rule.)
Sorting is one way to change an array's contents, if only the order of its contents. When it comes to displaying the contents of an array, having it sorted can certainly help the user. (Do realize, however, that displaying a portion of or a sorted view of an array doesn't require that you physically change the data.) Just remember that sorting permanently changes an array. Also, after you do add to an array, it will need to be re-sorted to stay in order. The next example shows how the user can populate an array as the movie playsso the order is determined by the user and doesn't need to change.
Building a Slide-Show Maker
This discussion moves to presenting data next chapter, but first consider the following example, which will give you a good idea how to build something the user gets to populate. That is, you're spending a lot of time structuring and presenting relatively static data that the user accepts because it's interesting. Here you'll see the user creating the data as he adds pictures to create his own slide show.
You need to first structure the data despite the fact you don't know the exact images the user will select. Basically, you need to make a place to hold the data. In the end, you need an array full of slides. An array is a good choice because the total number is unknown. Into each slot of the array, you can have the picture's title and, perhaps, the frame number where it resides. But an array may not be enough for all the data. It may be nice to have room for a few more variables that track general information such as a name for the particular show and the date created. There's no rule against maintaining separate variables: one for the array of slides, one for the show name, and one for date savedbut I still say we stuff all this data into one generic object. Later, when you actually save this show you'll see why one variable is useful.
To look mainly at code and the Flash stage, consider this bare-bones collection of interface elements and their respective instance names (shown in Figure 4.2):
-
Button components
-
Input text fields
-
List component
add_pb, delete_pb, next_pb, previous_pb, save_pb
title_txt, show_txt
show_lb
In addition to the items listed above, you need a movie clip with the instance name content. Inside this clip, select the first several frames, press F6 to make keyframes, and then put a different picture on each frame.
Figure 4.2 The slide-show example includes several components, some text, and a movie clip. Note that the button labels shown match the instance names.
Glance at all this code in Listing 4.8, and then read the explanation that follows about the important lines.
Listing 4.8 Complete Slide-Show Code
1 //=========== 2 // SET UP BUTTONS 3 add_pb.setLabel("add"); 4 add_pb.addEventListener("click", doAdd); 5 delete_pb.setLabel("delete"); 6 delete_pb.addEventListener("click", doDelete); 7 delete_pb.enabled=false; 8 save_pb.setLabel("save"); 9 save_pb.addEventListener("click", doSave); 10 save_pb.enabled=false; 11 next_pb.setLabel("Next"); 12 next_pb.addEventListener("click", doPager); 13 previous_pb.setLabel("Previous"); 14 previous_pb.addEventListener("click", doPager); 15 previous_pb.enabled=false; 16 //=========== 17 // BUILD DEFAULT TITLES ARRAY 18 titles = []; 19 titles.push("page one"); 20 titles.push("page two"); 21 titles.push("page three"); 22 titles.push("page four"); 23 titles.push("page five"); 24 titles.push("page six"); 25 //=========== 26 // STOP THINGS ON PAGE 1 27 content.stop(); 28 title_txt.text = titles[content._currentframe-1]; 29 show_txt.text=""; 30 //=========== 31 // INTIALIZE THE LIST BOX 32 show_lb.removeAll(); 33 show_lb.addEventListener("change", clickLine); 34 // INTIALIZE THE SHOW OBJECT 35 // show.pages will contain an array of objects: 36 // {title:"string", frame:1 } 37 // show.time is the last date saved 38 // show.showname is the title for the whole show 39 //=========== 40 show ={pages:[], 41 time:new Date(), 42 showname:show_txt.text}; 43 //=========== 44 // DO PAGER (next_pb's and previous_pb's click handler) 45 function doPager(whoSaid) { 46 //figure out direction 47 var direction; 48 switch (whoSaid.target) { 49 case next_pb : 50 direction = 1; 51 break; 52 case previous_pb : 53 direction = -1; 54 break; 55 } 56 //jump to new frame 57 content.gotoAndStop(content._currentframe+direction); 58 //set title text 59 title_txt.text = titles[content._currentframe-1]; 60 //check buttons 61 next_pb.enabled=true; 62 previous_pb.enabled=true; 63 if (content._currentframe == content._totalframes) { 64 next_pb.enabled=false; 65 } 66 if (content._currentframe == 1) { 67 previous_pb.enabled=false; 68 } 69 } 70 //=========== 71 // DO ADD (add_pb's click handler) 72 function doAdd() { 73 //push both their title, and the current page 74 show.pages.push({title:title_txt.text, 75 frame:content._currentframe}); 76 show.time=new Date(); 77 show.title=show_txt.text; 78 refreshList(); 79 } 80 //=========== 81 // DO DELETE (delete_pb's click handler) 82 function doDelete() { 83 var currentLine = show_lb.getSelectedIndex(); 84 oldShow = show.pages; 85 total = show.pages.length; 86 show.pages = []; 87 for (var i = 0; i<total; i++) { 88 if (i != currentLine) { 89 show.pages.push(oldShow[i]); 90 } 91 } 92 refreshList(); 93 show_lb.selectedIndex=Math.min(currentLine, 94 show.pages.length-1); 95 delete_pb.enabled=(show.pages.length>0); 96 } 97 //=========== 98 // CLICK LINE (show_lb's change handler) 99 function clickLine() { 100 content.gotoAndStop(show_lb.selectedItem.data); 101 title_txt.text =show_lb.selectedItem.label; 102 delete_pb.enabled=(show.pages.length>0); 103 } 104 //=========== 105 // REFRESH LIST (utility function) 106 function refreshList() { 107 show_lb.removeAll(); 108 var total = show.pages.length; 109 for (var i = 0; i<total; i++) { 110 show_lb.addItem(show.pages[i].title, 111 show.pages[i].frame); 112 } 113 }
More than half this code just monitors and maintains the interface state. Lines 1 through 15 set the labels, click handlers, and enable properties of the buttons. Notice that on lines 34 through 42, I document and create the show object. The comments include how you can access the different elements (such as show.pages for the array of saved pages).
Check out the doAdd function (lines 71 through 79) where I set the three properties of the show object. In the case of the pages property, I actually push a generic object (with properties title and frame) onto the end of the array. If you ever need to jump to a page already saved, you can extract a single item in the array and use its title property (for display text) and frame property (in a gotoAndStop() command). It turns out I didn't quite do it that way. When adding items to a List component, you may include both a value for the label and a value for the data. Only the label is visible to the user, but the Flash programmer can access either the label or data property for a given item in the list. On line 100, I just use the data of the selected item in the list. And on line 101, I use the label property. When adding items to the list, I added both title and frame properties as label and data respectivelyshown in lines 110 and 111.
Although you'll learn more in Chapter 12, there's one more feature I want to highlight. The doPager function on line 45 is sneaky because either button (next_pb or previous_pb) can trigger this functionI'm just taking advantage of the fact that most components pass as the only parameter an object full of details about itself, so the switch case that follows uses the target property received to figure out in which direction to move.
Although this example shows a completed project, you can adapt several concepts to other applications. For example, you can make a "back button" or history feature for your application by automatically adding each visited frame to an array. Also, the natural next step to this slide-show maker is to make a slide-show player mode. That is, a modified interface that only lets the user step through previously saved slides. Actually, in Chapter 6, "Basic Data Exchange," you'll see how to use the local shared object to save the slide shows you create.
It was difficult to study how to manipulate and change data without displaying it too, because that's the easiest way to judge the results. In any event, presenting data is covered next. Just remember that the data is yours to fashion as needed.