Workshop Chapter 15. Creating a Dynamic Slide Presentation

CONTENTS

Workshop Chapter 15. Creating a Dynamic Slide Presentation

    In this workshop chapter, we'll explore three advanced topics: loading structured data in XML format, loading JPEG images (using loadMovie()), and creating a dynamic screen layout on-the-fly. The project is to create a slide presentation that includes text and bullet points. Each "slide" is based on the same template, but the content of each will vary. In this project, every slide will have a title; a photograph; a variable number of bullets; some "permanent content" (such as the title), which stays on the screen while the slide is visible and the bullet points change; and some "post content," which appears after the last bullet point (see Figure W15.1).

    Figure W15.1. In this workshop chapter, we'll make a dynamic slide show that reads structured data from a file.

    graphics/31fig01.gif

    Structuring the data in XML format is only part of the task. We have to import that data, parse it, and then use it in an interactive Flash movie. When we're finished, the Flash movie will adapt itself to any XML file that has the format we design. With this approach, you can update the presentation just by editing the XML file and replacing JPEG images. This workshop chapter isn't terribly intense, but surprisingly, designing the XML structure is probably the easiest part.

    1. First, let's structure the XML. For each slide, we need a title, a photo, permanent content, post content, and a variable number of bullets. So actually, the bullets node will include child nodes. Here's the starter format to consider:

      <SLIDE>  <TITLE>title goes here</TITLE>  <PHOTO>somephoto.jpg</PHOTO>    <BULLETS>      <Bull>first bullet</Bull>      <Bull>second bullet</Bull>      <Bull>third bullet</Bull>    </BULLETS>  <POST_CONTENT>this appears at the end</POST_CONTENT>  </SLIDE>

      Notice that the "values" for the first child of TITLE, PHOTO, and POST_CONTENT are all strings. The value for the first child of BULLETS, however, is a series of BULL children, which each have a value.

    2. Use a text editor to create a file that contains several slides, such as the one in Step 1, but start the file with the following text:

      <?xml version="1.0"?>  <SHOW>

      This code declares the file as XML and then starts the first tag SHOW. Each of our SLIDEs will be children of SHOW. After this line, place several SLIDEs that follow the form shown in Step 1. For the PHOTO tag, specify the file-names for a few JPEG files you have handy.

    3. After the last </SLIDE>, insert a line that reads </SHOW>. This tag closes the <SHOW> tag that we opened in the second line.

    4. Save the XML file as slidedata.xml. You can find details about how to create and format XML files in Chapter 16, "Interfacing with External Data." Be sure to place your sample JPEG files in the same location as the XML file.

      Loading the XML data into Flash is easy enough, but we'll parse it once it's loaded. Although it's possible to load XML data and just leave it in its native XML format, we will store the data in arrays designed for easy access within Flash. We will modify the parsing script developed in Chapter 16, so the parsing won't be a ton of work. Besides, I want to remove all extraneous null nodes caused by white space in the XML file.

    5. Open Flash and immediately save the default Flash file right next to your slidedata.xml file. Place some text onscreen that says "Loading...". In the first keyframe, place the following script:

      stop();  my_xml=new XML();  my_xml.ignoreWhite=true;  my_xml.onLoad=parse;  my_xml.load("slidedata.xml");

      The first line stops the timeline from proceeding. Then we create an XML object, set the ignoreWhite property to true, identify that the parse() function will trigger when the XML data is fully loaded (onLoad), and finally, commence loading in the last line. Next, we'll need to define the parse() function.

    6. The parse() function is a monster. It's actually a modified version of the script that's fully explained in Chapter 16. As we progress, I'll point out some of the differences between this version and the original. For now, this script goes below what you've already typed into the first frame of the movie:

       1 function parse(success){  2   if (!success){  3     trace("problem");   4     return;   5   }   6   allSlides=new Array();   7   var slides=this.childNodes[0].childNodes;   8   for (var s=0; s<slides.length; s++){  9     var thisData= new Object();  10     var nodesInSlide=slides[s].childNodes.length;  11     for (var n=0; n< nodesInSlide;n++){ 12       var thisNodeName=slides[s].childNodes[n].nodeName.toUpperCase();  13       if(thisNodeName=="BULLETS"){ 14         var theVal=new Array();  15         totalBullets=slides[s].childNodes[n].childNodes.length;  16         for (var b=0;b<totalBullets;b++){ 17           theVal.push(slides[s].childNodes[n].childNodes[b].firstChild.nodeValue);  18         }// for "b"  19       }else{ 20         var theVal=slides[s].childNodes[n].firstChild.nodeValue;  21       }//if  22       thisData[slides[s].childNodes[n].nodeName]=theVal;  23     }//for "n"  24     allSlides[s]=thisData;  25   }//for "s"  26   _root.nextFrame();  27 }

      This function differs from the parse() function in Chapter 16 in two ways: The hierarchy in this version is one step deeper in the case of the BULLETS node. The second difference is that when all the loops are finished, we just jump to the next frame (where the slide show will begin). Just because this was covered earlier doesn't mean it's crystal clear. Let me describe the various loops and point out specific differences.

      The error check at the beginning is really just for our testing. You can replace the trace() command (in line 3) to take users to another frame that contains a warning. Before we step through the rest, it might help to understand the structure we're going to end up with. In the end, the allSlides array will contain values for each slide. The value in each index will be a generic object with properties named the same as each of the nodes (TITLE, PHOTO, BULLETS, and POST_CONTENT). The value for each property will be a string, except for BULLETS, which will have an array for a value. Figure W15.2 can help you visualize how the allSlides array gets populated:

      Figure W15.2 Visualizing the structure of the allSlides array will help when we need to extract portions of the array.
      allSlides =[  { title:"txt",  photo:"a.jpg", post:"txt",  bullets: ["a","b","c"] },   { title:"txt2", photo:"b.jpg", post:"txt2", bullets: ["x","y","z"] },   { title:"txt3", photo:"c.jpg", post:"txt3", bullets: ["a","b","c"] },  ]

      The reason this structure will be so nice is that later we'll be able to, say, find the name of the photo for the first slide with an expression such as allSlides[0].photo.

      The loops work as follows: Line 7 stores all the slides by first taking the first child of the XML file this.childNodes[0] (that's the same as the SHOW node) and then taking the childNodes of that. If you notice, slides shows up many times later, so line 7 mainly saves typing. Line 8 starts a loop that goes through all the slides. Then line 9 initializes a temporary variable that will store all the data for one slide at a time. After gathering everything for the first slide, thisData is copied into the allSlides array (line 24) and then is used again. Line 10 just makes line 11 more readable as we begin to loop through all the nodes within the current slide. In the old version, we then just created a new property for the thisData variable (like the start of line 22) and simply assigned its value like line 20. This version, however, needs to account for BULLETS, which contains nested BULLs. If it weren't for this fact, we could replace lines 12 through 22 with this one line:

      thisData[slides[s].childNodes[n].nodeName]=slides[s].childNodes[n].firstChild.nodeValue;

      Basically, that's the first part of line 22 followed by the second part of line 20; again, it sets a property (matching the nodeName) to equal the nodeValue. Because each slide is not just a series of names with matching values, we need to treat BULLETS uniquely. Line 12 stores an uppercase version of the current node's name, and then line 13 checks whether it matches "BULLETS". If not, lines 20 and 22 execute as described earlier (and like in Chapter 16). If the current node is a bullet, lines 14 18 create a new array in the variable theVal and loop through all the BULLs to push() each value onto the end of the array variable theVal. Notice that line 22 executes in all cases; it's just how the value of theVal gets populated that varies. Finally, in line 24 (after all the nodes in the current slide are processed), the temp variable thisData is pushed onto the end of allSlides.

      All this work to structure and parse the XML data is worth it. For one thing, you can use this slide presentation even if the data changes. As you're about to see, it's easy to do the rest of the work in Flash because we have a very nice array: allSlides.

    7. Insert a blank keyframe into frame 2 by pressing F7.

    8. Just for a test, type this code into the frame script for frame 2 (where we'll put all of the rest of the code for this workshop):

      trace(allSlides[0].title);  trace(allSlides[0].photo);  trace(allSlides[0].bullets[0]);  trace(allSlides[0].post_content);

      You should see the values from the various nodes (including just the first Bull). If you don't, you can try to find the problem. (You can also just download my file that's online at www.phillipkerman.com/actionscripting, of course.) Generally, you want to do a test like the trace() commands every step along the way to confirm that your code is working.

    9. Now that we know the data has loaded alright, we can move on to displaying it. Start by placing two Dynamic Text fields on the Stage in frame 2 one with the instance name of title_txt, positioned at the top of the Stage and another with the instance name of post_txt at the bottom of the Stage. Leave room for the bullets and the photo in the middle of the screen. (We'll get to those in a minute.)

    10. To let the user step through the different slides, you can make arrow buttons. Place two instances on the Stage and rotate one of them 180 degrees. Give the two buttons instance names of back_btn and forward_btn, respectively.

    11. Replace the script in frame 2 with the following:

      back_btn.onPress=function(){ goPage(-1)}  forward_btn.onPress=function(){ goPage(1)}  currentPage=-1;  goPage(1);

      We'll write the goPage() function next, but these 4 lines first assign callbacks to the two buttons (so that they both trigger goPage() but with different parameters). Then, currentPage is initialized to -1 so that when goPage(1) executes (page forward) the first time, it lands on the first page. (Remember, arrays start counting with zero).

    12. Here's the starter version for the goPage() function that you can type into the frame script:

      function goPage(direction){   currentPage+=direction;    title_txt.text=allSlides[currentPage].title;    post_txt.text=allSlides[currentPage].post_content;  }

      Test it out; it's pretty amazing how easy it is. All we do is increment or decrement currentPage and then set the text property appropriately for the text fields. We'll add a few more lines to this function.

    13. We can easily make the photos show up by first making a clip into which the photos will load. So, draw a box that matches the approximate size of your photos, select it, and then convert to a Movie Clip but be sure to select the default top-left registration point option (see Figure W15.3). Name the instance of your clip thePhoto.

      Figure W15.3. The clip's registration point establishes where the top-left corner of a loaded JPEG will appear the loadMovie() method is invoked.

      graphics/31fig03.gif

    14. Next, add the following line of code somewhere inside the goPage() function, but after where currentPage is assigned:

      thePhoto.loadMovie(allSlides[currentPage].photo);

      This extracts the photo property of the current item and uses it as the parameter for loadMovie(). In the downloadable version of this project, I've added a feature that will display a progress bar while the JPEG loads.

      To add the bullet functionality, we need a couple of more pieces. Because we don't know how many bullets might appear on a slide, we'll make one Movie Clip and use the attachMovie() method to create as many instances as necessary. Alternatively (and perhaps as a good exercise), we could use createEmptyMovieClip(), draw a bullet (using the drawing methods), and then create a TextField object all with code. Instead, we'll manually create a clip containing a hand-drawn bullet and text formatted by hand.

      After the text and bullet graphic are created, we'll let the user click anywhere onscreen to see the next bullet. That is, clicking the arrows will jump to the next slide (as it does now), but the way to advance through the bullets is just to click anywhere onscreen.

    15. Inside the goPage() function, add these two lines of code:

      bullNum=-1;  nextBullet();

      Basically, every time a new page is displayed, bullNum is set to -1 and the nextBullet() function gets invoked. (We'll write that function in a minute.)

    16. Start by creating a wide Dynamic Text field using left-justified formatting. Put some sample text that represents a typical bullet point but make sure the margins are set wide enough to accommodate the longest bullet. Name the instance bullet_txt. Draw a box or circle as your bullet-point graphic and place it to the left of the bullet_txt instance.

    17. Select both the bullet-point graphic and the bullet_txt instance and convert them to a Movie Clip named "Animated." (Be sure to keep the top-left registration option). Note the symbol name.

    18. Actually, we'll remove every instance of the Animated symbol, but before that, we can do some offline animating. Position this symbol in its destination position (assuming it's the first bullet to appear).

    19. Copy the instance, and then select Edit, Paste in Place. Press the down arrow on your keyboard until the space between the two bullets looks good. Use the Info panel to note both the y coordinate of the first bullet and the difference between the two bullets. If the top bullet's y is 66 and the second bullet's y is 106, you know the separation is 40 pixels. Based on this information, let's initialize some variables. Create the start for the nextBullet() function with the following code, placed below everything else in the second keyframe:

      function nextBullet(){   var topY=66    var spacing=40;  }

      This code will come in handy in a minute when we position each new bullet.

    20. I'd like to see the bullets animate onscreen, but really snappy. So, use the left and right arrow buttons to position the top bullet all the way off the left side of the Stage. (You can hold down the Shift key to make it jump in bigger steps.) Note the x coordinate. Move it to the right so that it's about halfway to its destination, and then note the x location again. Continue by gathering coordinates for the text, as follows: when moved 10 or 20 pixels to the right of the destination, when moved to the left of the destination about 5 10 pixels, and, finally, in its destination (which, I suppose, you could have gathered earlier but the idea of this step is that you're "animating" by just gathering coordinates). The final effect I'm thinking of will zoom the text onscreen (actually a bit too far), and then it will bounce back and finally settle in place. Now enter the following line to place those values in an array that gets assigned inside the nextBullet() function:

      var steps=[-400, -163, 27, 1, 7];
    21. Delete both instances of the Animated symbol on the Stage. Select the symbol in the Library and set its linkage to animated. Now we can use the attachMovie() method.

    22. Finish the nextBullet() function so that it appears as follows:

       1 function nextBullet(){  2   var topY=66;   3   var spacing=40;   4   var steps=[-400, -163, 27, 1, 7];   5   bullNum++;   6   _root.attachMovie("animated","bull_"+bullNum,bullNum);   7   var thisOne=_root["bull_"+bullNum];   8   thisOne._x=-9000;   9   thisOne._y=topY+( bullNum*spacing );  10   thisOne.bullet_txt.text=allSlides[currentPage].bullets[bullNum];  11   thisOne.steps=steps;  12   thisOne.curStep=0;  13   thisOne.onEnterFrame=function(){ 14     this._x=this.steps[this.curStep];  15     this.curStep++;  16     if(this.curStep>=this.steps.length){ 17       this.onEnterFrame=null;  18     }  19   }  20 }

      After the three lines of variables, we increment bullNum. Line 6 creates a new instance of the Animated symbol, gives it an instance name of bull_x (where x is bullNum), and puts it in the level number one greater than bullNum. (We don't want to use level_0 because it would wipe out our main movie.) Line 7 just stores the newly created instance in a variable to save typing. Line 8 moves it way off to the left. Line 9 basically sets the _y location to match topY but adds a space for every bullNum greater than 0 (that is, multiplying the spacing by bullNum results in 0 when bullNum is zero). Line 10 is a bit uglier than the rest, but all it's doing is setting the text property of the field bullet_txt (inside the new instance) equal to the value of the first bullet (just like we did with the trace() command in step 8).

      Lines 11 19 get a bit hairy. To set up the animation, the new clip is given its own copy of the steps array and has a new variable stepNum (which gets incremented in line 15) initialized to 0. Line 13 defines an onEnterFrame callback for the new clip (thisOne). Line 14 sets the clip's _x property to a value in the appropriate index of steps. The part that's a bit wild is inside the if statement (which is checking to see whether stepNum has reached the end of the steps array). The new clip's onEnterFrame callback is cleared on line 17.

    23. Now we can let the user trigger more bullets. (Currently, only the first one shows up when each new slide appears.) Add this simple callback outside of any other functions:

      _root.onMouseDown=function(){nextBullet()};

      This just triggers the nextBullet() function whenever the user clicks anywhere. Now when the program runs, there seems to be a million problems: two bullets animating at a time, bullets not disappearing, and more! We can attempt to fix only one thing at a time.

      The first step to fixing this is to determine exactly what's happening. The main issue is that even when pressing a button, the user is also triggering the onMouseDown callback. By placing two separate trace() commands in the two functions (goPage() and nextBullet()), I determined that nextBullet() is executing first or more specifically, that onMouseDown occurs before onPress. An amazingly simple solution is to just remove all bullet instances on the Stage when the goPage() function runs. A for in loop can go through all the clips on the Stage, and if we just check that their name starts with "bull_", we know it's a bullet and can remove it.

    24. To remove all the instances of the Animated symbol, add this function to the frame script:

      function removeAll(){   for (i in _root){     if(i._name.substr(0,5).toUpperCase()=="BULL_"){       this[i].removeMovieClip();      }    }  }

      This function just checks each object in the main timeline; if the first five characters match "BULL_", it gets removed. (I like to use the toUpperCase() method as a safety precaution whenever checking strings for exact matches because "bull_"=="BULL_", for example, is false.)

    25. Now just trigger the removeAll() function from within the goPage() function by adding

      removeAll();

      This works pretty well for fixing most of the problems. It's not totally elegant because every time the user clicks the page forward button, a new bullet starts to animate but then gets wiped away instantly. It works, but if you have a better idea, feel free to use it.

      There are only two tasks remaining: to stop the bullets from appearing once the last one is displayed, and not to reveal the post_content until the last bullet displays.

    26. To stop the bullets from appearing, place the following code right beneath the line that reads bullNum++ inside the nextBullet() function:

      if(bullNum>=allSlides[currentPage].bullets.length){   return;  }

      This code bypasses the rest of the nextBullet() function, provided that bullNum has reached or exceeded the length of the bullets property (an array) in the current slide's data.

    27. It turns out that the last step will help us with our final objective: to display the post_content, only once all the bullets have displayed. In the goPage() function, find the line that sets the post_txt field's text (it reads: post_txt.text=allSlides[currentPage].post_content;). First, copy the line (because we're going to paste it somewhere else), and then change it to read

      post_txt.text="";

      This clears the post_txt field any time goPage() is triggered.

    28. Finally, add the following code to the nextBullet() function (somewhere below the if statement you just created):

      if(bullNum>=allSlides[currentPage].bullets.length-1){   post_txt.text=allSlides[currentPage].post_content;  }

      Notice that the third line is the same code you copied from the goPage() function.

    Now you should be able to run the presentation. After you put the final touches on some graphics, you can export a .swf and never need to touch Flash again. That is, you can make new slide presentations just by editing the XML file and replacing the JPEG images. You might consider adding a feature to disable the back button when on the first slide or the forward button when on the last slide.

    This workshop was a lot of work, but realize that you can use the results of it over and over. For example, you can make a giant XML file full of whatever data or bullet points you want. You can also add other elements to this presentation, such as pictures and sounds. Generally, the work to make this project even better lies in good template design: You want something that's usable, expandable, and visually appealing.

    CONTENTS


    ActionScripting in MacromediaR FlashT MX
    ActionScripting in MacromediaR FlashT MX
    ISBN: N/A
    EAN: N/A
    Year: 2001
    Pages: 41

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