DISSECTING FLASHTONE


FlashTone is an application written in about 90% Flash and 10% JScript, which both stores and dials phone numbers. Much like a cell phone, numbers are stored in association with the numbers in the number pad (see Figure 8.2). In other words, you might assign your home number to the 1 key, your office number to the 2 key, your spouse's cell phone number to the 3 key, and so on. To save a number that you have already entered, simply tap on the Save button. The number pad reveals all the numbers you have saved so far and which slots you have empty (see Figure 8.2). You can then tap on either an empty slot or an existing number to overwrite it. To dial a saved number, tap on the Load button. Again, all the numbers that you have previously stored are revealed through the number pad. Just tap on the number you want to recall, then tap Dial.

Figure 8.2. FlashTone running on a Pocket PC device.

graphics/08fig02.gif

Figure 8.3. Notice that previously saved numbers are revealed when the user taps Load or Save.

graphics/08fig03.gif

Touchtone Pads

Touchtone phone number pads are typically arranged in three columns and four rows. Each row and column has a unique tone, which sounds at a unique frequency. When you press a number, the tone you hear is actually a combination of the row frequency and the column frequency. Tones are combinations of two frequencies rather than simply a single frequency in order to reduce the chance that ambient noise might, by chance, match the frequency of a particular number and cause you to misdial your phone. We see, therefore, that the sounds we hear when dialing a number are the actual sounds that place a call. All you have to do to dial a phone, therefore, is to make the correct sounds. In fact, it is merely a convenience that phones have number pads built in at all because all a number pad really is a very precise noisemaker. FlashTone is now exposed for what it really is: nothing more than a Flash movie that plays back WAV files that happen to be recordings of the right combinations of frequencies needed to dial a touchtone phone.

As you can see, dialing phone numbers turns out to be the easy part; it's storing them and retrieving them that requires explanation. Let's examine FlashTone at the beginning of the data flow process as we use JScript to load saved data.

Using JScript to Load Saved Phone Numbers

JScript is typically executed in response to an event. For instance, a developer might use JScript to validate information typed into a web form at the time the form is submitted. Or he might validate each field in the form at the time the field loses focus (a field is said to lose focus when the cursor leaves the field). It is not required that JScript be executed in response to a user event, however. As with ActionScript, any JScript code existing outside of the scope of a function will execute from top to bottom as the code is loaded. Let's start, therefore, by looking at flashTone.html from the top down.

 <html>  <head>   <title>FlashTone</title>   <script language="JScript">      // Constants.      var COOKIE_NAME = "_tones"; 

The first thing we do (other than open the HTML document with an <html> tag and open the head of the document) is open a script tag and define a single constant, COOKIE_NAME.

COOKIE_NAME is the name of the cookie in which FlashTone's ten-number memory is stored. We then define the functions getCookieValue() , setCookie() , and saveNumbers() (which we will discuss momentarily) before actually calling the function getCookieValue() with the following piece of code:

 var tones = getCookieValue(COOKIE_NAME); 

graphics/01icon13.gif

Note that the function getCookieValue() must be defined prior to attempting to call it. Remember, code outside the scope of functions executes as the page loads, so code that executes is unaware of functions that have not yet been loaded.


To see what the value of tones will be, let's examine the function getCookieValue() :

 function getCookieValue(name) {       // Retrieve saved numbers.       var allCookies = document.cookie.split(";");       for (i = 0; i < allCookies.length; ++i) {          if (allCookies[i].split("=")[0] == COOKIE_NAME) {              return unescape(allCookies[i].split("=")[1]);          }       }       return null;  } 

getCookieValue() starts out by retrieving a list of cookies from the document object. The browser only returns cookies from its cookie database that were set from the same domain from which the current page was served. Because the page was loaded locally rather than served from a server, all cookies that were set locally whether they were set by FlashTone or some other application will be returned in one long semicolon delimited list. The first thing we must do, therefore, is split them and sort through the result looking for the exact cookie we are interested in, which is the cookie named _tones as defined as the value of constant COOKIE_NAME . After we find the correct cookie, we pass the value through the native JScript unescape() function, which URL decodes it, and we pass it back to the caller where it is assigned to the variable tones. Notice how if the cookie does not exist (as in the case where the user has either never saved any numbers or is loading the FlashTone application for the first time), null is returned rather than a string. This is by no means a problem or an error condition, and you will see how we deal with it appropriately when the time comes.

As you will see when we go over the process of saving data to the browser's cookie database, all 10 of the phone numbers are stored as a single colon (: ) delimited string to save us from having to store and retrieve 10 different cookies. Because 4K is plenty of space (the minimum amount of space a browser must allocate for a single cookie), it is much more efficient to store them together rather than take up more entries in the cookie database by saving them individually.

Now that we have extracted our saved phone numbers from the browser's cookie database, it is time to pass them into the FlashTones Flash movie. The best and easiest way to pass data into a Flash movie from the HTML page in which the movie is embedded is as query string parameters at the time the movie is loaded, like this:

 <object ...>    <param name="movie" value="someMovie.swf?name=this+is+a+value">  </object> 

From the time the movie loads, you will have access to an ActionScript variable called name with a value of this is a value from the main timeline (_level0). The only remaining problem we must solve is that an object tag is HTML, but all the saved data is in the form of a JScript variable.

Passing the Phone Numbers from JScript to Flash

The only way to pass data from JScript to HTML and then into Flash at the time the Flash movie is loading is to use JScript to generate the HTML tags that load the movie, like this:

 <body bgcolor="#ffffff" topmargin="0" leftmargin="0">   <script language="JScript">    document.write('<object class');    document.write('');  document.write('codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/ graphics/ccc.gifswflash.cab#version=5,0,0,0"');    document.write('width="240"');    document.write('height="240">');    document.write('<param name=movie value="flashTones.swf?'+COOKIE_NAME+  graphics/ccc.gif'='+escape(tones)+'">');    document.write('<param name=quality value=high>');    document.write('</object>');   </script>  </body> 

By using JScript to write out the HTML, we can easily append our saved numbers to the movie's URL, and use JScript's native escape() function to URL encode the data. We are then guaran-teed that Flash will have access to all the phone numbers on the main timeline in the form of an ActionScript variable from the time the movie starts playing.

Now that our movie has loaded, let's see how the FlashTone movie handles the data we just passed in.

Inside the FlashTone Movie

FlashTone is not particularly complex as far as Flash applications go (see Figure 8.4). It consists of twelve layers over only four frames and does little more than respond to buttons getting tapped and play WAV files. The application does contain a fair amount of ActionScript, however, which, while not overly complicated, does merit detailed explanation.

Figure 8.4. FlashTone in the Flash authoring environment.

graphics/08fig04.gif

graphics/01icon13.gif

At one time, FlashTone was actually much more complex than it is today as I started out building the number pad programmatically as opposed to simply arranging movieclips on the stage. In other words, I used ActionScript to create instances of movieclips and, in a for loop, placed them at specific x-and y-coordinates on the stage to form a number pad. The advantage of building the interface programmatically as opposed to simply placing 12 movie clips on the stage is flexibility I was able to change their position and distance from each other just by changing the values of a few ActionScript variables. Additionally, I could change the look of all the buttons just by redesigning the one button I used to duplicate all 12 of the others. Unfortunately, when I moved the application over to my Pocket PC device for testing, I discovered that it took between 5 and 20 seconds to actually render, depending on how many other Pocket PC applications I had open. It turned out that the work of building the interface programmatically was too much for my device's processor. So after all that work, I had to refactor the FlashTone movie and place the buttons on the stage the old-fashioned way. I learned the hard way that one must sometimes sacrifice elegance for efficiency when designing for mobile platforms.


Most of the ActionScript in FlashTone is contained on the top-most layer labeled "control." I like to keep as much ActionScript together in one place as I can to simplify development (no searching through your movie looking for one particular function or variable declaration), and because I believe it simplifies and promotes code reuse (using functions more than once from different places in the code).

As with JScript, when ActionScript exists outside the scope of a function, it executes from the top down as it loads. The first thing that happens in the FlashTones movie, therefore, is a call to the native ActionScript action stop() ; because our movie is more of an application that responds to user actions than an animation, we want to carefully control the flow of the movie rather than simply let it play through from start to finish. We then define a few constants and other global variables before reaching the point where we deal with the string of saved numbers that was passed in from the HTML file as the movie was loaded.

 // Array that holds the stored numbers.  var savedNumbers = new Array(10);  if (_tones != null && _tones != "null") {       var allTones = String(_tones).split(":");       for (var i = 0; i < allTones.length; ++i) {            savedNumbers[i] = allTones[i];       }  } 

We create a new array containing 10 elements (for our 10 phone number memory) and assign it to a global variable called savedNumbers . We then check to see if the variable _tones has been defined before trying to operate on it (recall that _tones is the name of the query string parameter that we appended to the name of the movie while loading FlashTone it translates directly into the name of an ActionScript variable). The only case in which _tones would not be defined is if the user had never saved any phone numbers before, in which case all 10 elements of savedNumbers would remain null (a situation we will deal with shortly). Assuming some numbers had been saved in the past, we split the numbers on the colon character into an array called allTones (remember that the phone numbers were passed in as a single colon-delimited list), and iterate through them, adding them in their appropriate places to the savedNumbers array. At this point, we have successfully retrieved data from the browser's cookie database, passed it into a Flash movie, and made the data available in a useful data structure to the rest of our application. Quite an accomplishment.

Although there is no more code that relates to data persistence until we get to the point in the data flow of wanting to save phone numbers, I will continue to describe the ActionScript in the control layer to make the inner-workings of the FlashTone application clearer.

 // Create all the sound objects for playing tones.  var tones = new Array();  for (var i = 0; i < TOTAL_NUMBERS; ++i) {       tones[i] = new Sound();       tones[i].attachSound("tone_"+i);  } 

This is the first time we deal with the tones that are eventually used to dial phone numbers. We create an array, assign it to a variable called tones, and then populate the array with new ActionScript Sound objects. Calling the function attachSound() attaches a sound from the library to the Sound object from which you call the function. Note that for this technique to work, the symbol in the library has to be specially linked. To link a symbol in the library, right-click the symbol and select Linkage. In the resulting box, give the symbol a name and select Export This Symbol. The symbol will then be exported into the SWF file and available for dynamic loading.

The next piece of code completes FlashTone's initialization sequence (as defined by all the code outside of functions that is executed as the movie loads) and is responsible for attaching the saved phone numbers to their appropriate numbers on the number pad.

 // Populate the number pad with saved numbers.  for (var i = 0; i < BUTTON_EFFECT_NUM; ++i) {       eval("numberPad.button_"+i).savedNumber =           (savedNumbers[i] == null ||           savedNumbers[i] == "")?"empty":savedNumbers[i];  } 

The main timeline contains a movie clip called "numberPad." The numberPad movie clip contains 12 buttons, one for every button on a touchtone phone. Numbers 0 9 contain dynamic text boxes called "savedNumber" that get assigned to their associated elements in the savedNumbers array. As you can see, if an element is null or blank (meaning no number has been saved in that slot), the string "empty" is assigned instead.

The rest of the code in "control" exists inside of functions and only executes in response to user input. Before discussing the processes of entering and saving phone numbers, however, it's important that you understand FlashTone's concept of "modes."

At any given time, FlashTone can be in one of three different modes: "enter," "load," or "save." The mode is simply dictated by the value of the global variable "mode," the default mode being "enter" (as you can see from the way in which the mode variable is initialized).

 var mode = "enter"; 

The application's mode dictates the behavior of certain buttons. For example, tapping on the 1 key in enter mode simply sounds the proper tone and appends a one to the value in the numberInput text box. In load mode, however, tapping on the 1 changes the value of the numberInput text box to the stored phone number associated with the 1 key. Finally, in save mode, tapping on the 1 key will save whatever number is in the numberInput box in position 1 of the array of saved phone numbers. (All these process we will look at in more detail shortly.)

Modes are changed from "enter" to either "load" or "save" by tapping on the "load" or "save" buttons, which calls the function handleAction() , passing either the string "load" or "save" depending on which button was tapped.

 // Handles actions like load and save by delegating to the appropriate  // function.  function handleAction(action) {     // Must be in "enter" mode to use an action. If not, bail out.     if (mode != "enter") {         mode = "enter";         buttonEffectOff();         return;     }     if (action == "save") {         saveMode();     } else if (action == "load") {         loadMode();     } else {         help();     }  } 

Essentially all the handleAction() function does is delegate to saveMode() , loadMode() , or help() , depending on which button initiated the call. saveMode() and loadMode() do little more than set the value of the "mode" variable to either "save" or "load," then call buttonEffectOn() (which causes the buttons in the number pad to reveal the saved numbers they have associated with them). The help() function simply makes the help movieclip (where all the help copy is stored) visible by setting its "_visible" property to true.

The beginning of the handleAction() function is interesting, though. As you can see, you are required to be in "enter" mode before switching into another mode; otherwise, the mode is set to "enter" and the function returns. This is to allow users to go from "load" or "save" mode by tapping on the appropriate button back to "enter" mode by tapping on the button again, essentially accomplishing a toggle effect.

So, now that we know how modes are changed, let's explore how being in different modes effects the application. Tapping on any other button in the number pad besides "load," "save," or "help" calls the buttonPressed() function.

 // Handle the event of a button being pushed.  function buttonPressed(input) {     if (mode == "enter") {         // User is simply entering a phone number.         if (numberInput.length < MAX_INPUT_SIZE) {             soundTone(input);             numberInput = numberInput + String(input);         }      } else {         buttonEffectOff();         // Illegal operation.         if (isNaN(input)) {             mode = "enter";             return;         }         if (mode == "save") {             // User wants to save a phone number.             saveNumber(input);         } else {             // User wants to load a phone number.             loadNumber(input);         }         mode = "enter";      }  } 

The first thing buttonPressed() does is check the application's mode. This is how we know to give the keypad different behaviors depending on which mode the application is in. For example, in "enter" mode, tapping on the 1 key calls the soundTone() function (which essentially just calls start() on the appropriate Sound object so that the tone actually plays), then appends the number 1 to the numberInput text box. If the mode is "save," the process of saving a phone number is initiated (which we will soon explore in detail). If the mode is not "enter" or "save," it is assumed that the mode is "load," in which case the loadNumber() function is called.

 function loadNumber(num)  {     numberInput = savedNumbers[num];  } 

loadNumber() 's only job is to change the value of the numberInput text box to the associated value stored in the savedNumbers array. The effect is that a number was just "loaded" from the application's memory.

There are a few other things going on inside buttonPressed() that are worth mentioning. Let's take a moment to look more closely at the following code:

 buttonEffectOff();  // Illegal operation.  if (isNaN(input)) {      mode = "enter";      return;  } 

The call to buttonEffectOff() restores the number pad to its normal appearance of obscuring the saved phone numbers. The check to be sure that the value of input is actually a number is to handle the "*" and "#" buttons getting tapped in any mode other than "enter." In "enter" mode, tapping on the "*" or "#" is perfectly fine because they are treated just as any other number in the number pad. In "save" or "load" mode, however, they are illegal options because they are not slots in which phone numbers can be saved or from which numbers can be retrieved. If an illegal operation is detected, therefore, the mode is simply set back to "enter" and the function returns without performing any other operations.

Before we talk about the process of saving phone numbers (which is the last step in the data flow process), let's look at the two remaining buttons in the FlashTone interface which we haven't yet explored: "clear" and "dial."

The clear button is easy; tapping on it calls the clearInput() function, which just sets the value of numberInput to an empty string (giving it the appearance of being cleared). Dial is a little more involved, however. Tapping on the dial button calls the following function:

 // Handle the dial event.  function dial(num) {     toDial = new Array();     var numStr = String(num);     var len = numStr.length;     for (var i = 0; i < len; ++i) {        curNum = numStr.charAt(i);        if (curNum == "*" || curNum == "#" || !isNaN(curNum)) {            toDial[toDial.length] = curNum;        }     }     gotoAndPlay(2);  } 

The dial function has two jobs. The first is to break apart the phone number that is to be dialed and assign it to the global array toDial . The second is to use the ActionScript action goToAndPlay() to play the movie starting from the second frame. On the second frame of the dialing layer, the following code actually dials the phone number:

 var nextTone = getNextTone();  if (nextTone == -1)  {      stop();  }  soundTone(nextTone); 

The getNextTone() function is defined in the control layer and does nothing more than iterate through the global toDial array, returning one number at a time, and finally 1 when there are no more numbers to return. The code on the "dialing" layer sends the number it got back from getNextTone() to the soundTone() function in control (which plays the tone), unless 1 is returned, in which case the movie is stopped again. Assuming the movie does not get stopped, it is allowed to continue playing until reaching the fourth frame where there is ActionScript on the "dialing" layer which calls goToAndPlay(2) , causing the movie to continue looping until all the numbers have been accounted for and played. The loop between frame 2 and frame 4 is what causes the necessary pause between tones.

Passing Phone Numbers from Flash to JScript

Let's go back and take a closer look at the process of saving phone numbers since it is the final step in the data flow process. The user tapping on a number while in "save" mode initiates the process. As we discussed before, the buttonPressed() function is called, which delegates to the saveNumber() function as shown here:

 function saveNumber(num)  {     savedNumbers[num] = numberInput;     eval("numberPad.button_"+(num)).savedNumber = (numberInput == null ||            numberInput == "")?"empty":numberInput;     var allNumbers = savedNumbers[0];     for (var i = 1; i < savedNumbers.length; ++i) {        allNumbers += (":"+savedNumbers[i]);     }        // Save the numbers as a cookie.        getURL("javascript:saveNumbers('"+allNumbers+"')");  } 

The saveNumber() function is not actually as daunting as it may appear at first glance. The first thing it does is replace the appropriate element in the savedNumbers array with the number currently in the numberInput text box (the number the user wants to save). The next thing it does is set the number that the user wants to save on the button in the number pad that the user has chosen to associate with the phone number so that the newly saved number can be revealed when the buttonEffectOn() function is called. The third thing the saveNumber() function does is iterate through all the phone numbers in the savedNumbers array and concatenate them all into a single colon (: ) delimited string. Finally, saveNumber() performs the magic of communicating the string of numbers to JScript where they can be written to the browser's cookie database.

As I mentioned earlier, JScript is usually executed in response to an event, and events are usually captured through event handlers. To capture the event of a form being submitted, you use an onSubmit event handler as an attribute of your form. To capture the events of a field either gaining or losing focus, you use an onFocus or onBlur event handler as an attribute of your input field. The most common event to capture is probably the click, which you can do in two ways; as you might expect, you can use an onClick event handler as an attribute of either a button or an anchor tag, or you can use the following syntax:

 <a href="javascript:someFunction('data')">Click here!</a> 

Clicking on the Click here! link calls the function someFunction passing it the string 'data' . The importance of being able to execute JScript in this manner as opposed to strictly using an event handler is that it is exactly like telling your browser to follow a URL, only the URL is a set of JScript instructions rather than an actual URL (which the browser knows by the javascript: prefix. That means any application, process, or plug-in that can tell your browser to follow a URL can also execute JScript, which, using the ActionScript action getURL() , gives Flash exactly the hook we need to pass ActionScript data into JScript.

Look again at the last line of the saveNumbers() function:

 getURL("javascript:saveNumbers('"+allNumbers+"')"); 

We use the ActionScript action getURL() to tell the browser to access a URL; however, prefixing the URL with the keyword javascript: , the browser knows to interpret the rest of the URL not as a URL at all, but as JScript. The result, therefore, is the execution of the JScript function saveNumbers() with our string of phone numbers being passed in as an argument.

Using JScript to Write to the Browser's Cookie Database

The final process in the data flow between ActionScript, JScript, and cookies has us back where we started: between the <script> tags of the HTML document in which the FlashTone movie is embedded. This time, the relevant function is saveNumbers() .

 // Saves a : delimited list of phone numbers for three years.  function saveNumbers(numbers) {     setCookie(COOKIE_NAME,numbers,(365*3));  } 

Remember that this function is now a Jscript function in the HTML page, and not an ActionScript function.

As it turns out, saveNumbers() is a very simple function that simply delegates to a more generic function called setCookie . I decided to put the code, which actually does the work of saving the cookie, in its own generic function to make it more re-usable. In other words, because there is nothing specific to the FlashTone application in setCookie , I can re-use the function for saving cookies on behalf of other applications, as well.

The first thing setCookie does is create a new JScript date object, which it then calls setDate on, passing it a new future date derived from the current date plus 365x3 (for a total of 1,095) days, which was passed in from saveNumbers() . As you will see, the Date object is what we use to generate the date string that gets used as the value of the "expires" attribute of the cookie. By setting it to 1,095 days from the current date, the FlashTone's memory will last for three years from the last time any number was saved. After three years, the browser will remove the cookie from its database and FlashTone's memory will be deleted.

graphics/01icon12.gif

Feel free to redefine 1,095 because it is an arbitrary number. Be sure you include an expiration date, however, as the absence of an expiration date indicates to the browser that the cookie only needs to persist for the life of the current session, which expires the moment the browser is closed.


The last thing setCookie does is set the cookie in the browser's cookie database. JScript handles cookies as properties of the document object; however, setting cookies only overwrites cookies of the same name as existing cookies. The JScript syntax is a little misleading, therefore, because you are actually adding to the document's cookie property as opposed to redefining it.

The value of the cookie is passed through the native JScript function escape() to make sure the entire cookie is properly formatted. escape() returns a URL encoded version of whatever string you pass to it, thus ensuring that there are no quotes or semicolons that will make parsing the cookie again later impossible. Finally, we call toGMTString() on the Date instance which just happens to return the expiration date in the exact format the browsers require, and we have completed the data flow cycle from the browser's cookie database to JScript to Flash, then back to JScript, and finally, back to the browser's cookie database. And the whole process happens instantly and completely transparently to the user.



Macromedia Flash Enabled. Flash Design and Development for Devices
Macromedia Flash Enabled. Flash Design and Development for Devices
ISBN: 735711771
EAN: N/A
Year: 2002
Pages: 178

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