ActionScript.org Flash, Flex and ActionScript Resources - http://www.actionscript.org/resources
Pagination in Flash
http://www.actionscript.org/resources/articles/1015/1/Pagination-in-Flash-/Page1.html
Chris Bristol
I have been using Flash in my web development since version 4. As ActionScript became a more powerful language, I found myself fascinated by the interaction between it and PHP and MySQL. In recent years, I have worked at eliminating the use of the timeline altogether, building everything in pure AS3, and making my code reusable. The fantastic resources of the vast online Flash community has been a great source of help and inspiration. I hope to be able to give back a little of that. You can reach me via my website: Xty Digital Design
 
By Chris Bristol
Published on June 15, 2010
 
The NextPrev tutorial deals with pagination in Flash, a situation I seem to run into more and more these days. My usual course of action was to serve up the data to a PHP/HTML page and let it deal with paginating the results. But Flash has so much more to offer than the traditional html page, that I decided to recreate the process in Flash, which is a bit ironic in these days of Mr Jobs pushing HTML5 to do everything Flash does, only better. The resulting code was a natural for a Class, or at least it was after I copied and pasted it into a few different projects. This tutorial started out to be of average length, but as I got into it, I realized that there is quite a bit going on in the demo file that really needs to be explained. I am always muttering about the author leaving unexplained the one part I really wanted to know about, so, not wanting to do that, it's a bit longer than usual.  And, if there are things I haven't explained to your personal satisfaction, my inBox is always open!
 

Where would you use pagination in Flash?
Anytime you need to display a lot of results, whether from a database, or, as in this tutorial's demo, from an XML file. In a full Flash site you generally have limited space in which to display data, but even if you are putting the results into a separate Flash movie, your space is still limited. Let's say you have a data set to display which includes images, a block of text, and a couple of buttons. Let's also say each result takes up 250 pixels of height. With the limit on a Flash movie's size being 2880 x 2880, vertically you'd only get 11 results to a single page.

The Demo Movie

The movie below feeds on 2 XML files containing the song data. I wanted the demo to make use of dynamically gathered data, and XML seemed to be the best choice under the circumstances. How you get your data is unimportant to the pagination concept, it's what we do with it afterward that counts. To see the flexibility of the class you can adjust the number of results to display.



The Demo Files

Now, download the nextPrev.zip file and let's dig into the code and see how this works.

The files included in the nextPrev.zip are PagDemo.as and pagDemo.fla. This set of files shows the pagination working with both a top and bottom set of controls, while the PagDemoSingle.as and the pagDemoSingle.fla demonstrates the same class being used with only a single set of controls. The easyMusic.xml and rockMusic.xml files hold the data that we will be reading into the movie. And, you'll find the NextPrev.as class and the CustomEvent.as class files inside the ca.xty.myUtils folder system.

The XML files are very simple examples, and so I hope they don't need any detailed explanation, because I'm not providing any. There are quite a few good tutorials about using Flash and XML and, to do the subject justice, I'd need to make this tutorial even longer than it is. Besides, you will most likely have much more complex data to work with in a real situation, and consequently a much more complex XML file. What I'll mainly be dealing with is gathering the data from the XML file and packaging it up in a way that's easy for us to use.

The pagDemoSingle.as has sections commented out to show you what gets left out when you are using a single set of controls, and beyond that everything else is exactly the same.

There is also a text file called backToTop.txt. This is a tiny bit of JavaScript that we will use together with the ExternalInterface class provided by Flash.

As usual, the fla's are empty, except for a button in the library and the fact that the Document Class property is set to PageDemo or PageDemoSingle.

What's not included in the zip file this time out is an fla for CS3 - but don't despair! Creating one is dead easy. Make a new fla in CS3 with a width of 500 and a height of 600. Drag a Button component into the library and set your Document Class to read either pagDemo or pagDemoSingle - remember, no .as extension, just the class name. Make sure you have all the files included in the zip in the same folder as your CS3 fla and you're good to go.

So let's jump into the pagDemo.as file already!


PagDemo.as
In order to make this work we need to have some data to make a results list from. In this example we are using two XML files to provide that data. Open up the pagDemo.fla and it's Document Class, PagDemo.as. You'll notice in the pageDemo.fla that I haven't lied to you, and there is nothing on the the stage and the only thing in the library is an instance of the Button component. Under the Document Class setting we have declared PagDemo - no .as, just the class name - as the Document Class. Let's switch over to that file.

Starting from the top, we import the necessary classes.
[as]
package{
  import flash.display.*;
  import flash.events.*;
  import flash.geom.*;
  import flash.net.*;
  import flash.text.*;
  import fl.controls.Button;
  import flash.external.ExternalInterface;
  import ca.xty.myUtils.NextPrev;
  import ca.xty.myUtils.CustomEvent;
[/as]

The two custom classes we need are found in the ca.xty.myUtils folder and they are NextPrev.as and CustomEvent.as. We will discuss these, and the ExternalInterface class, in more detail as we go along. Next we set up our variables.
[as]
public class PagDemo extends MovieClip{
       
  //XML variables
  private var dataLoader:URLLoader = new URLLoader();
  private var xmlData:XML = new XML();
  private var musicInfoArray:Array;

  //On screen assets
  private var musicBut1:Button;
  private var musicBut2:Button;
  private var goBtn:Button;
  private var t1:TextField;
  private var ip1:TextField;
   
  //Data display variables
  private var barArray:Array;
  private var titleArray:Array;
  private var composerArray:Array;
  private var descArray:Array;
  private var songLengthArray:Array;
  private var cartArray:Array;
  private var titleHead:TextField;
  private var timeHead:TextField;
  private var composerHead:TextField;
  private var descHead:TextField;
  private var buyHead:TextField;
  private var buyArray:Array;
       
  //Pagination variables
  private var npT:NextPrev;
  private var npB:NextPrev;
  private var useNP:Boolean = false;
  private var maxNum:int = 5;
  private var numSongs:int;
  private var itemStart:int = 0;
  private var itemsToShow:int;
  private var showingItem:int = 1;
  private var numVisible:int;
  private var nBtn:Boolean;
  private var pBtn:Boolean;
  private var firstPage:Boolean = true;
  private var resultsTop:Sprite;
  private var resultsHolder:Sprite;

  // TextFormats
  private var titleFormat:TextFormat;
  private var titleFormatC:TextFormat;
       
  //X and Y positions
  private var xPos:int;
  private var yPos:int;
[/as]

First up, we get our XML ducks in a row. We declare a URLLoader to get the XML files off the server. Then we have a an XML object, and finally an array called musicInfoArray which is an array that we will be dumping all the results from the XML files in.

Our On Screen assets include the buttons to grab the music and another button which allows us to set the number of results we want to show at one time. The TextFields are a label field and an input field to enter in the number of desired results.

The Data Display variables are the things we will be using to show the results. These consist of arrays and TextFields.

The Pagination variables are the things we will be using to keep track of where we are and what is happening within the pagination process. The first two, npT and npB are variables to represent instances of our class NextPrev and stand for NextPrev Top and NextPrev Bottom. The useNP is a Boolean that will tell us whether or not we need to use an instance of the NextPrev class by determining the number of results and balancing that against the next variable maxNum. So, if we have 5 results and our maxNum is 10 then we do not need to use the NextPrev instance. If the opposite is true, then we need it. We set the maxNum to have an initial value of 5, but this can be whatever number of results you want to show. The variable itemStart is set to 0, and showingItem is set to 1. These values are what we need to get started. Then we have a bunch of integer values to pass information back and forth to the NextPrev class, a few more Booleans to let us know where we stand, and finally a couple of Sprites that we will be using as containers to hold our results. Using containers like this will provide us an easy way to remove the results in a single step.

Next up is our constructor.
[as]
public function PagDemo(){
           
  //Set up the TextFormats
  titleFormat = new TextFormat();
  titleFormat.color = 0x000000;
  titleFormat.font = "verdana";
  titleFormat.size = 10;
  titleFormat.align = "left";
           
  titleFormatC = new TextFormat();
  titleFormatC.color = 0x000000;
  titleFormatC.font = "verdana";
  titleFormatC.size = 10;
  titleFormatC.align = "center";
           
  //Give our XML Loader a Complete event listener
  dataLoader.addEventListener(Event.COMPLETE, loadData);
           
  //Put our assets on stage
  musicBut1 = new Button();
  musicBut1.x = 20;
  musicBut1.y = 10;
  musicBut1.width = 120;
  musicBut1.height = 20;
  musicBut1.label = "Rock Music (13)";
  musicBut1.addEventListener(MouseEvent.CLICK, musicButHandler);
  addChild(musicBut1);
           
  musicBut2 = new Button();
  musicBut2.x = 150;
  musicBut2.y = 10;
  musicBut2.width = 120;
  musicBut2.height = 20;
  musicBut2.label = "Easy Listening (4)";
  musicBut2.addEventListener(MouseEvent.CLICK, musicButHandler);
  addChild(musicBut2);
           
  t1 = new TextField();
  t1.x = 280;
  t1.y = 10;
  t1.width = 90;
  t1.height = 20;
  t1.text = "Items to Show:";
  t1.setTextFormat(titleFormat);
  addChild(t1);
           
  ip1 = new TextField();
  ip1.x = 375;
  ip1.y = 10;
  ip1.width = 30;
  ip1.height = 20;
  ip1.type = "input";
  ip1.border = true;
  ip1.defaultTextFormat = titleFormatC;
  ip1.text = String(maxNum);
  addChild(ip1);
           
  goBtn = new Button();
  goBtn.x = 410;
  goBtn.y = 10;
  goBtn.width = 30;
  goBtn.height = 20;
  goBtn.label = "Go";
  goBtn.addEventListener(MouseEvent.CLICK, goHandler);
  addChild(goBtn);
           
}
[/as]

First we give our TextFormats some properties. They are nearly identical, with the only difference being the text align property.

Next we add a COMPLETE Event Listener to our XML dataLoader and direct it at a function called loadData.

Now we stick our stage assets in place. These are the buttons that will allow you to choose between Rock Music and Easy Listening. The numbers in parentheses represent the number of songs available in each category. I have taken a short cut here. In a real application, you would be counting the number of incoming results and appending that number to the button label. The last three assets go to make up our
number-of-results-to-see-at-once feature. A title field with a label of Items to Show, an input field to gather the answer and the goBtn to make it happen. Our goBtn has an event listener for a CLICK event and takes you to a function called goHandler.
[as]
private function goHandler(e:MouseEvent):void{
  //As long as the input field is not empty, we change the variable maxNum to the contents of the ip1 TextField
  if(ip1.text != ""){
    maxNum = Number(ip1.text);
  }
  //If the boolean firstPage is not true we remove the data already displayed on stage and reset our variables to start over
  if(!firstPage){
    removeChild(resultsTop);
    removeChild(resultsHolder);
    if(useNP){
      removeChild(npB);
      useNP = false;
    }
    firstPage = true;
    showingItem = 1;
    itemStart = 0;
    }
    //Check to see if numSongs is greater than the new maxNum and then buildResults again using the the new maxNum
    if(numSongs > maxNum){
      useNP = true;
    }else if(numSongs <= maxNum){
      itemsToShow = numSongs;
    }
  // Now build the results
  buildResults();
}
[/as]

The first thing we check is whether or not our input field, ip1, has anything in it. As long as it is not equal to nothing, we set the variable maxNum to whatever is in the ip1 field. To use this in a project you would want to issue some kind of an alert at this point for those who push buttons first and provide answers later. Next we need to determine if the boolean firstPage is true or not. If it is not true, then that means we already have something on stage that we need to get rid of before proceeding. So we remove our two Sprites, resultsTop and resultsHolder. Now we check the useNP variable to see whether we are using an instance of our NextPrev on the bottom. If it's true, then we remove that instance as well.

Once everything is off stage, we reset the first Page variable to true, the showingItem to 1 and our itemStart to 0. Finally we check to see if numSongs is greater than the new maxNum, and then buildResults() again using the the new maxNum.

Next we'll look at our music button handler.

[as]
private function musicButHandler(e:MouseEvent):void{
  switch(e.currentTarget.label){
    case "Rock Music (13)":
      dataLoader.load(new URLRequest("rockMusic.xml"));
      break;
    case "Easy Listening (4)":
      dataLoader.load(new URLRequest("easyMusic.xml"));
      break;
  }
}
[/as]

We use a switch statement, with the label data as the criteria, and then load the appropriate XML file. You'll remember that we already set up our listener for the dataLoader and declared it's function to be loadData.
[as]
private function loadData(e:Event):void {
  try{
    xmlData = new XML(e.target.data);
    parseData(xmlData);
  } catch (e:TypeError){
    trace("Unable to load XML");
  }
}
[/as]

Here we assign the incoming data property to our XML object called xmlData, and then send that data to a function called parseData.

[as]
private function parseData(dataInput:XML):void {
  musicInfoArray = new Array();
  var infoList:XMLList = dataInput.song.composer;
  numSongs = infoList.length();
  for(var i:int = 0; i < numSongs; i++){
    var comp:String = dataInput.song[i].composer;
    var track:String = dataInput.song[i].title;
    var tl:String = dataInput.song[i].track_length;
    var d:String = dataInput.song[i].description;
    var musicObj:Object = {Composer:comp, Track:track, TrackLength:tl, Desc:d};
    musicInfoArray.push(musicObj);
  }
  //If the boolean firstPage is not true, then we remove the current data displayed and reset our variables to start over
  if(!firstPage){
    removeChild(resultsTop);
    removeChild(resultsHolder);
    if(useNP){
      removeChild(npB);
      useNP = false;
    }
    firstPage = true;
    showingItem = 1;
    itemStart = 0;
  }
  //If numSongs is greater then the maxNum to be shown, then we will need our next/prev feature so set the boolean useNP to true
  //and then buildResults
  if(numSongs > maxNum){
    useNP = true;
  }else if(numSongs <= maxNum){
    itemsToShow = numSongs;
  }
  // Now we build the results
  buildResults();
}
[/as]

We set our musicInfoArray to empty by declaring a new instance of it. This array is going to hold an array of objects, with each object in the array containing all the information we need about one song. Next we declare a temporary variable called infoList that will hold our XMLList data, and we set it to point to the composer block in our XML file. We determine our numSongs variable by giving it the length value of our infoList. Notice here that the length property is declared as a function - length() - not the length property we normally see when getting the length of an array - such as someArray.length.

Now we use a for loop to gather all the available data and package it into an object - musicObj -, which we then push into our musicInfoArray. Once we have all the data stored away, we run through our check to see if this is the firstPage or not, and whether the numSongs is greater than the maxNum we set. This is the same routine as in our goHandler function we discussed above. Once we are ready, we buildResults().

Getting Results
This function is a bit long, so I'm going to break it up with explanations of individual pieces, rather than give you a long block of code and then a long block of explanatory text. Hopefully this won't be confusing and will be easier to follow without making you scroll up and down to match the text with the code.
[as]
private function buildResults():void{
  // If the boolean firstPage is true we start from scratch
  if(firstPage){
    // resultsTop will be a container to hold our next/prev widget and the column titles
    resultsTop = new Sprite();
    resultsTop.x = 0;
    resultsTop.y = 40;
    addChildAt(resultsTop, 0);
               
    xPos = 0;
    yPos = 0;
[/as]

The first thing we do is check our firstPage variable. If it is true, meaning that there is nothing on screen yet, then we create our resultsTop Sprite. This will be the container that will hold all the stuff that makes up our results header. The reason we have two containers - resultsTop and resultsHolder - is because when we go to the second page of a results set we don't need to change the header information. Keeping the two
containers separate gives us this flexibility. We position our resultsTop at an x position of 0 and a y position of 40, and use the addChildAt function to place it on the bottom of the display stack, 0. In this demonstration it is not necessary to use the addChildAt, but, depending on what other things you may be adding later on, it can be handy to have these elements underneath everything else. In any case, it does no harm.

Now we set our variables xPos and yPos to 0. These co-ordinates are now relative to the resultsTop container, not the stage.
[as]
    // If the boolean useNP is true, we need the Next/Prev widget, so build it now
    if(useNP){
      itemsToShow = maxNum;
      npT = new NextPrev(numSongs, maxNum, maxNum, 0x000000, 0x000000);
      npT.x = (stage.stageWidth - (npT.width - 40))/2;
      npT.y = yPos;
      // If you want to show the next/Prev widget on both the top and the bottom of your results give it a name
      npT.name = "NPT";
      npT.addEventListener(CustomEvent.CUSTOM, reBuild, false, 0, true);
      resultsTop.addChild(npT);
           
      yPos += 30;
    }
[/as]

We check our variable useNP, and, if it is true, we set up the variables we need to interact with our NextPrev class. The variable itemsToShow refers to the maximum number of items to show at one time, and so is set to our maxNum variable.

Now we create our top instance of the NextPrev class, called npT, and pass these variables along to the NextPrev's constructor function:
numSongs - the total number of songs in this result set
maxNum - the maximum number of items to show at once
maxNum - represents the items showing, and is the same number at first as the previous variable
0x000000 - this is our text color
0x000000 - this is the text color for the button labels

Then we position our NextPrev instance. The npT's x property is set up to be centered on the page. We take the stage.stageWidth property and subtract the npT's width - 40 and then divide that result by 2. The reason for the -40 from the npT's width is because the last item to be set up in the NextPrev class is a button, and even though the width of that button is declared to be 60, Flash seems to stick with it's default button
width of 100. So, 100 - 60 = 40. Strange, but true. Our y position is just simply our yPos variable. Now, if you are going to be using both a top and a bottom instance of the NextPrev class you will want to give the instance a name. The name value is arbitrary, and I've called it NPT. This is just so we will know which instance - top or bottom - has been clicked. Then we add an event listener using our CustomEvent class, which we will go into in more detail at the end of this function. Finally, we add the npT instance to the resultsTop container, so that it is a child of the resultsTop Sprite, and then we add 30 to our yPos variable.
[as]
    titleHead = new TextField();
    titleHead.x = xPos;
    titleHead.y = yPos;
    titleHead.width = 120;
    titleHead.height = 20;
    titleHead.border = true;
    titleHead.text = "Track Title";
    titleHead.setTextFormat(titleFormat);
    resultsTop.addChild(titleHead);
           
    xPos += 120;
               
    timeHead = new TextField();
    timeHead.x = xPos;
    timeHead.y = yPos;
    timeHead.width = 50;
    timeHead.height = 20;
    timeHead.border = true;
    timeHead.text = "Time";
    timeHead.setTextFormat(titleFormatC);
    resultsTop.addChild(timeHead);
           
    xPos += 50;

    composerHead = new TextField();
    composerHead.x = xPos;
    composerHead.y = yPos;
    composerHead.width = 130;
    composerHead.height = 20;
    composerHead.border = true;
    composerHead.text = "Composer";
    composerHead.setTextFormat(titleFormat);
    resultsTop.addChild(composerHead);
           
    xPos += 130;
           
    descHead = new TextField();
    descHead.x = xPos;
    descHead.y = yPos;
    descHead.width = 160;
    descHead.height = 20;
    descHead.border = true;
    descHead.text = "Description";
    descHead.setTextFormat(titleFormat);
    resultsTop.addChild(descHead);
           
    xPos += 160;
               
    buyHead = new TextField();
    buyHead.x = xPos;
    buyHead.y = yPos;
    buyHead.width = 40;
    buyHead.height = 20;
    buyHead.border = true;
    buyHead.text = "Buy";
    buyHead.setTextFormat(titleFormatC);
    resultsTop.addChild(buyHead);
           
    xPos = 0;
    yPos += 61;
[/as]

Now we begin setting up our headings. First comes the titleHead TextField which holds the words "Track Title". This gets added as a child to our resultsTop container and uses the established xPos and yPos variables to position it. We continue on down the line adding the timeHead TextField next, then the composerHead, descHead,and buyHead Textfields.When we are done all that, we reset our xPos to 0 and add 61 to our yPos.
[as]
    // If the boolean firstPage is not true, then we don't have to add all of the above
    }else if(!firstPage){
      //If the boolean useNP is true, set up our x and y positions to take the next/prev widget into account
      if(useNP){
        xPos = 0;
        yPos = 91;
       //If the boolean useNP is not true, our x and y positions only need to take the column headers into account
      }else{
        xPos = 0;
        yPos = 61;
      }
    }
    // Set up our bar colors and make the current color of our barColor variable equal to the first color
    var bar1Color:Number = 0xFFF7E6;
    var bar2Color:Number = 0xFEE4AB;
    var barColor:Number = bar1Color;
           
    // The resultsHolder will be the container to hold all of our results
    resultsHolder = new Sprite();
    resultsHolder.x = xPos;
    resultsHolder.y = yPos;
    addChildAt(resultsHolder, 0);
   
    yPos = 0;
    // Initialize our arrays for the bars and the column headers
    barArray = new Array();
    titleArray = new Array();
    composerArray = new Array();
    descArray = new Array();
    songLengthArray = new Array();
    buyArray = new Array();
[/as]

If this is not the first page, then we don't need to add all the headings, but we need to know whether useNP is true so that we can take the space it uses into account. If useNP is true, then we reset our xPos to 0 and add 91 to our yPos, otherwise if useNP is false we reset our xPos to 0 and only add 61 to our yPos. In case you're wondering where the number 61 comes from, remember that we have our music buttons and our Item to Show occupying the first 40 pixels, then we have our headings which take up another 20 pixels, and then a 1 pixel space just because it looks better.

Next we give our bar1Color and bar2Color variables some color values. These bars will be Sprites that sit underneath each row and help to separate the results from one another with the use of alternating colors. Then we set our barColor variable to the value of bar1Color.

Now we get onto setting up our results list. We start by creating the resultsHolder Sprite to act as the container for the whole mess. We position it using our xPos and yPos variables, then add it the stage using addChildAt and giving it a spot in the display list of 0. If you remember, we set our resultsTop to this same position. With the resultsHolder now occupying 0, our resultsTop is automatically pushed into position 1.

Again, remember that the xPos and yPos positions will now be relative to our resultsHolder container, not the stage, so we reset our yPos to 0. Our xPos is already 0, so we are good to go there. Next we initialize the arrays we'll be using.
[as]
    // Now we display the results
    for(var i:int = itemStart; i < itemsToShow; i++){
               
      // First we create the bar so that it will sit underneath the text
      barArray[i] = createSprite(barColor, 500, 24, xPos, yPos);
      resultsHolder.addChild(barArray[i]);
      // I want the text to center itself vertically inside the bar, so I add 2 pixels to the y position
      yPos += 2;
      // We make an array of text fields to hold our Song Title
      // Note that we use TextFieldAutoSize.LEFT for the autoSize property. With the multiline and wordWrap properties set to true
      // this will force the text field down to accommodate extra text
      titleArray[i] = new TextField();
      titleArray[i].x = xPos;
      titleArray[i].y = yPos;
      titleArray[i].width = 120;
      titleArray[i].height = 20;
      titleArray[i].multiline = true;
      titleArray[i].wordWrap = true;
      titleArray[i].autoSize = TextFieldAutoSize.LEFT;
      titleArray[i].text = musicInfoArray[i].Track;
      titleArray[i].setTextFormat(titleFormat);
      resultsHolder.addChild(titleArray[i]);
               
      xPos += 120;
      // Next we make an array of text fields to hold our Track Length
      songLengthArray[i] = new TextField();
      songLengthArray[i].x = xPos;
      songLengthArray[i].y = yPos;
      songLengthArray[i].width = 50;
      songLengthArray[i].height = 20;
      songLengthArray[i].text = musicInfoArray[i].TrackLength;
      songLengthArray[i].setTextFormat(titleFormatC);
      resultsHolder.addChild(songLengthArray[i]);
               
      xPos += 50;
      // Next we make an array of text fields to hold our Composer names
      // Note that once again we use TextFieldAutoSize.LEFT for the autoSize property
      composerArray[i] = new TextField();
      composerArray[i].x = xPos;
      composerArray[i].y = yPos;
      composerArray[i].width = 130;
      composerArray[i].height = 20;
      composerArray[i].multiline = true;
      composerArray[i].wordWrap = true;
      composerArray[i].autoSize = TextFieldAutoSize.LEFT;
      composerArray[i].text = musicInfoArray[i].Composer;
      composerArray[i].setTextFormat(titleFormat);
      resultsHolder.addChild(composerArray[i]);
               
      xPos += 130;
      // Next we make an array of text fields to hold our Song Description
      // Note that once again we use TextFieldAutoSize.LEFT for the autoSize property
      descArray[i] = new TextField();
      descArray[i].x = xPos;
      descArray[i].y = yPos;
      descArray[i].width = 160;
      descArray[i].height = 20;
      descArray[i].multiline = true;
      descArray[i].wordWrap = true;
      descArray[i].autoSize = TextFieldAutoSize.LEFT;
      descArray[i].text = musicInfoArray[i].Desc;
      descArray[i].setTextFormat(titleFormat);
      resultsHolder.addChild(descArray[i]);
               
      xPos += 160;
      // Now we make an array of buttons to act as our Buy button
      // We use the name property and assign it a value of i so that we can tell which button has been clicked
      buyArray[i] = new Button();
      buyArray[i].x = xPos + 2.5;
      buyArray[i].y = yPos;
      buyArray[i].width = 35;
      buyArray[i].height = 20;
      buyArray[i].label = "Buy";
      buyArray[i].name = i;
      buyArray[i].useHandCursor = true;
      buyArray[i].addEventListener(MouseEvent.CLICK, buyHandler);
      resultsHolder.addChild(buyArray[i]);
[/as]

Using a for loop we start to add our results to our resultsHolder container. First we create the bar so it will sit underneath everything else. Down near the bottom of this class you will find a function called createSprite, which does just that. We pass the parameters of barColor, the width, the height, the xPos and the yPos to the createSprite class. Our bar lives in the array called barArray.

Now we add 2 pixels to our yPos. This is because I want to center the text vertically inside the bar which we created. The bar is 24 pixels in height and the text is set up to be 20 pixels in height, so that by adding 2 pixels to our yPos our text will now have a 2 pixel margin on top and bottom. Provided the text is a single line, of course. If it isn't, well, we'll deal with that in a minute.

Now we set up our titleArray, which will be an array of TextFields that will display our track titles. Notice that we give the TextField an initial height of 20, but set it's multiline and wordWrap properties to true and set the autoSize property to LEFT. What this does is maintain the width of the TextField, but, should there be more text than is capable of fitting in the set width, the TextField will automatically adjust it's height to
accommodate the extra text. Remember our musicInfoArray where we stored all of our song properties in an object? Here's where we get to use them. The first track title will be taken from the musicInfoArray[i].Track. Here i is the first object in the musicInfoArray and .Track is one of the properties of that object. Then we set our TextFormat property and finally we add it to our resultsHolder container.

The next bit of information we are going to display is our song length, and since this value won't ever exceed the width we've set for this TextField, we don't have to use the autoSize property. Again, the information we want is inside of musicInfoArray[i].Tracklength property.

Using the same technique we used for our titleArray, we add in our composerArray and descArray information.

When we get down to the buyArray it is a wee bit different. First of all, it is going to be an array of buttons. If you recall, the buyHead TextField was set up to be 40 pixels in width. These buttons are going to be 35 pixels wide, but I don't want them jammed up to the left with a gap on the right. I want them centered horizontally within the 40 pixels. So, we add 2.5 to the xPos. The other thing to note is that we are giving the
buttons a name property and setting that property to the value of i. We do this so that when a button is clicked we can use the name property value as an index to get at whatever other information we want that is inside the musicInfoArray. Lastly we add it to our resultsHolder container.
[as]
      // Because one of the three text fields using the autoSize property could be larger than our bar height
      // we need to check and see which one is the biggest and then adjust the bar height to suit
      // Once we've figured out the new bar height, we adjust our y position so that the next item will line up properly
      if(descArray[i].height > 20 || titleArray[i].height > 20 || composerArray[i].height > 20){
        if(descArray[i].height >= titleArray[i].height && descArray[i].height >= composerArray[i].height){
          barArray[i].height = descArray[i].height + 2;
          yPos += bar1Array[i].height - 2;
      }
      if(titleArray[i].height >= descArray[i].height && titleArray[i].height >= composerArray[i].height){
        barArray[i].height = titleArray[i].height + 2;
        yPos += bar1Array[i].height - 2;
      }
      if(composerArray[i].height >= descArray[i].height && composerArray[i].height >= titleArray[i].height){
        barArray[i].height = composerArray[i].height + 2;
        yPos += bar1Array[i].height - 2;
      }
      // If none of the text fields is larger than the bar height, we adjust our y position so that the next item will line up properly
      }else{
        yPos += 23;
      }
      // We want the bars to be alternating colors so we switch the barColor by determining which color is it currently and then assigning
      // it the opposite color
      if(barColor == bar1Color){
        barColor = bar2Color;
      }else{
        barColor = bar1Color;
      }
      // Reset out x position to 0 and add in the next item
      xPos = 0;
    }
    // Once all the items are on stage, if we are using a bottom next/prev widget we add some space to our y position
    yPos += 17;
[/as]

We went to a lot of trouble to accommodate unknown lengths of text. In the demo you'll notice that some of the descriptions are pretty long. Now that we have all that set up, we have to deal with it. We have three TextFields set to autoSize, so we need to check each of those TextFields to see if they have grown beyond their default height.

The first if statement checks to see if the descArray[i] TextField's height is greater than 20, or if the titleArray[i] TextField's height is greater than 20, or if the composerArray[i] TextField's height is greater than 20. If any of those conditions is true, then we start our secondary checks to see which of our TextFields is the highest. We want to increase the height of barArray[i] to match the highest of the three TextFields. So, if descArray[i].height is greater than or equal to titleArray[i].height and descArray[i].height is greater than or equal to composerArray[i].height, then the descArray is the highest, so we set the barArray[i].height to equal the descArray[i].height and add 2 just to keep our little margin, at least on top. Now we recalculate the yPos to take this new circumstance into account by taking the current yPos and adding in the new barArray[i].height minus the 2 pixels we added for the margin. If the descArray isn't the highest, then we run the same tests on the other two arrays. And, if all the arrays remain within the 20 pixels height, then we simply add 23 pixels to our existing yPos and carry on.

The next thing we want to do is change the bar color. We want to alternate the colors so things stand out nicely, so what we do is check to see which color the current barColor is, and then change to the other one. So if barColor is equal to bar1Color then set barColor to equal bar2Color, and vice versa.

The last thing we want to do before returning to the top of the loop and adding the next item is reset our xPos to 0.

Once we've looped through all the results, we add 17 to our yPos. The 17 is just an arbitary number to give us a wee bit of space. It can be as much or a little as you want it to be.
[as]
    // We check once again to see if the boolean useNP is true and if the boolean firstPage is true
    // If both of those variables are true, we set up the next/prev widget the same as the top next/prev
    // except for the name property
    // Our y position is made up from the current yPos (which was set to 0 when we started to build the results) plus the height of the elements
    // at the very top plus a 10 pixel margin, so in this case we need 75 pixels for the top bits plus 10 for the margin
    if(useNP && firstPage){
      itemsToShow = maxNum;
      npB = new NextPrev(numSongs, maxNum, maxNum, 0x000000, 0x000000);
      npB.x = ((stage.stageWidth - (npB.width - 40))/2);
      npB.y = yPos + 85;
      npB.name = "NPB";
      npB.addEventListener(CustomEvent.CUSTOM, reBuild, false, 0, true);
      addChildAt(npB, 0);
      // If the boolean useNP is true, but firstPage is false, we need only to reposition our next/prev widget
    }else if(useNP && !firstPage){
      npB.y = yPos + 85;
    }
    // Once the first page is set up we set our boolean firstPage to false
    firstPage = false;
}
[/as]

Now, if you are using both a top and a bottom instance of the NextPrev class, here is where you'll want to add in the second instance. First check to make sure that useNP is true and that firstPage is true. Then set it up exactly the same as the first instance, except that you will give it a different name to differentiate it from the top instance.  In this case I've called it NPB. The y position is set to yPos + 85 in this case because our elements on top use 75 pixels, our yPos is currently set to reflect the resultsHolder container and I want a 10 pixel margin between the resultsHolder and the npB instance.

Alternately, if useNP is true, but firstPage is not, then the only thing we will have to do is adjust the existing instance of npB's y position using the same rational as explained just above.

Finally we set our firstPage variable to false.

Next up is our buyHandler function.
[as]
private function buyHandler(e:MouseEvent):void{
  var musicIndex:int = e.target.name;
  trace("Item Clicked = " + musicIndex);
}
[/as]

This function is much simplier than it would be under normal operating conditions. Here it just shows you which item has been clicked. If you click the first item it will trace 0. Why 0? Because arrays use a zero based index. So to get the track title of the first item we would use musicInfoArray[musicIndex].TrackTitle, where musicIndex is equal to 0. Presumably you would at this point be gathering information to enable someone to buy the item they have clicked, but that code is up to you to put into place depending on your circumstances.

The next function comes into play when either the next or Prev buttons are clicked. It is called inside of the NextPrev class. Remember that when we set up our instances npT and npB we added an event listener with the function set to reBuild. So, when you click either the Prev or Next buttons, the NextPrev class manipulates the data and sends the updated data back to our Document Class via our CustomEvent.
[as]
public function reBuild(e:CustomEvent):void{
  showingItem = e.arg[0];
  itemStart = e.arg[1];
  itemsToShow = e.arg[2];
  nBtn = e.arg[3];
  pBtn = e.arg[4];
  removeChild(resultsHolder);
  trace("Cur Target Name: " + e.currentTarget.name);
  if(e.currentTarget.name == "NPT"){
    npB.manualUpdate(showingItem, itemStart, itemsToShow, nBtn, pBtn);
  }else if(e.currentTarget.name == "NPB"){
    npT.manualUpdate(showingItem, itemStart, itemsToShow, nBtn, pBtn);
    /*if (ExternalInterface.available) { 
      ExternalInterface.call("backToTop"); 
    }else{
      trace("ExternalInterface not available");
    }*/
  }
  buildResults();
}
[/as]

The first thing to make note of is that this is declared as a public function. This is because it is being called from our NextPrev class so we need it to have inter-class operability. This uses our CustomEvent class to allow the event listener to pass a parameter. First I'll run through this function and then we'll take a side trip to have a look at our CustomEvent class.

The pieces of information we need in order to update the resultsHolder and the unclicked instance of NextPrev are taken from the array that is first prepared in the NextPrev class and then passed back. So, showingItem is set to equal e.arg[0], itemStart is set to equal e.arg[1] and so forth. Then we remove the existing resultsHolder in preparation for rebuilding it.

To update the unclicked NextPrev instance, we first determine which instance was clicked. So if e.currentTarget.name equals NPT, then we need to update the bottom instance, and vice versa if the name equals NPB. Now, if you are using a single instance of the Nextprev class, and therefore have not given it a name, you need not worry about an error creeping in here because Flash assigns every instance a name by default, and our if statements will simply be false and the program will move on. Once we know which instance has been clicked, we send the information gathered to the other instance and use the built in function in the NextPrev class called manualUpdate which will set the parameters in that instance to reflect the parameters in the instance that was clicked. We'll have a closer look at the manualUpdate function once we get to the NextPrev class.

If the instance of the NextPrev class that was clicked was the bottom one, then we use the ExternalInterface class to connect to a small bit of JavaScript we place on the html page holding our movie. It's commented out here since we won't be able to use it in this situation, but what's it for? Using the Actionscript.org tutorials as an example, when you are at the bottom of the page and you clicked next, did you end up back at the top of the page when the next page loaded? I get really grumpy if I don't get automatically taken back to the top of the next or previous page, so the way I make sure this doesn't happen to other people using my creations is to add the following bit of code to the html page and call the JavaScript function from Flash using the ExternalInterface. You will find this code in the backToTop.txt file included in the zip file.

Inbetween the <head> and </head> tags put the following:
[code]
<script type="text/javascript">
  function backToTop(){
    self.scrollTo(0,0);
  }
</script>
[/code]

That simple function causes the page to scroll to a left position of 0 and a top position of 0. It is not likely that you will ever need to adjust the left position but, if, for instance, you had a 100 pixel high header on the page you would want to adjust the top position. The values would then be self.scrollTo(0, 100);.

Now that everything is lined up, we go back and do the buildResults() again.

The final function createSprite does just that.
[as]
private function createSprite(color:int, w:int, h:int, x:int, y:int):Sprite{
  var s:Sprite = new Sprite();
  s.graphics.beginFill(color);
  s.graphics.drawRect(0, 0, w, h);
  s.graphics.endFill();
  s.x = x;
  s.y = y;
  s.alpha = 1;
  addChild(s);
  return s;       
}
[/as]

We pass in the color, width, height, x position, and y position and using this information the function draws a rectangle and returns us a shiny new Sprite.

Now let's take a look at our CustomEvent class before we move on and examine the NextPrev class.

Custom Events
This class is pleasantly shorter than the last class we looked, eh? First of all, let me say that this class comes from an article I found at this site, which contains a terrific explanation of all the pieces involved, so I will only go over it briefly here.
[as]
package ca.xty.myUtils {
import flash.events.Event;
   
  public class CustomEvent extends Event {
    public static const CUSTOM:String = "custom";
    public var arg:*;
       
      public function CustomEvent(type:String, customArg:* = null, bubbles:Boolean = false, cancelable:Boolean = false) {
        super(type, bubbles, cancelable);
        this.arg = customArg;
      }
       
      public override function clone():Event {
        return new CustomEvent(type, arg, bubbles, cancelable);
      }
       
      public override function toString():String {
    return formatToString("CustomEvent", "type", "arg", "bubbles", "cancelable", "eventPhase");
      }
       
  }
}
[/as]

All of the classes we have created in this tutorial and the others I have done are custom classes which extend Sprite or MovieClip. This is the first class which we have looked at which extends another class besides the default Sprite or MovieClip, in this case, the Event class. The reasoning behind this is that we want to use all the capabilities of the Event class as they are, but we want to add the ability, or extend it, to be able to pass back an argument within the event listener.

The variables we declare are both set to public as we will be needing them outside of this class. The first one is a static constant named CUSTOM which is typed to a string. The second one is called arg and is typed with an asterisk so that it can be any type of variable from a String to an Array and anything in between. This functionality enables you to reuse this class anytime you have a situation where you need to pass an argument back.

In the constructor we accept the parameters of type, which will be our CUSTOM variable, our customArg, which can be of any type and is optional because we have given it a default value of null, along with the bubbles and cancelable parameters which are also optional.

We have two public functions which override the clone function and toString functions found in the original Event class. To use this class you do something like this:

[as]someObject.addEventListener(CustomEvent.CUSTOM, someFunction, false, 0, true);[/as]

And to dispatch the event you do this:

[as]dispatchEvent(new CustomEvent(CustomEvent.CUSTOM, yourArgs));[/as]

We'll see this in action when we look at the NextPrev class next.

NextPrev Class

Well, it's be a bit of journey to get to this point, but here we are at last. Once again, first we import the necessary classes.
[as]
package ca.xty.myUtils {
  import flash.display.*;
  import flash.text.*;
  import flash.events.*;
  import flash.utils.*;
  import flash.net.*;
  import fl.controls.Button;
  import ca.xty.myUtils.*;
[/as]

No surprises there. Next we set up our variables.
[as]
public class NextPrev extends MovieClip {
       
  private var nextBtn:Button;
  private var prevBtn:Button;
   
  private var _numItems:int;// This is our total number of items
  private var _maxItems:int;// This is the number of items we want to show at one time
  private var itemStart:int;// This is the number we use in our for loop to tell us which point in the array to start counting at
  private var _itemsToShow:int;// This is the number of items we are currently showing
  private var _txtColor:Number;// Text color
  private var _btnColor:Number;// Text color for the button
  private var showingItem:int = 1;// This is the visible equivalent of the var itemStart, but it needs to be 1 greater because humans start counting at 1, not like an array who starts at 0
  private var _nBtn:Boolean;// This variable lets us know whether the Next button is currently enabled
  private var _pBtn:Boolean;// This variable lets us know whether the Prev button is currently enabled
       
  private var statsArray:Array;// This is the array we will be passing back to the main class via our Custom Event
       
  //Display text fields
  private var st1:TextField;
  private var st2:TextField;
  private var st3:TextField;
  private var st4:TextField;
  //Our text formats
  private var showingFormat:TextFormat;
  private var showingFormatC:TextFormat;
  private var butFormat:TextFormat;
  //Our x and y position variables
  private var xPos:int;
  private var yPos:int;
[/as]

The comments tell you pretty much what we are doing. Next up is our constructor function. From the main class we are receiving the Total Number of Items, the Number of Items to Show at Once, the visual Number of Items to be Shown, the Text Color, the Button text color.
[as]
public function NextPrev(numItems:int, maxItems:int, itemsToShow:int, txtColor:Number, btnColor:Number){
[/as]

Now we assign our passed in parameters to our class variables.
[as]
_numItems = numItems;
_maxItems = maxItems;
_itemsToShow = itemsToShow;
_txtColor = txtColor;
_btnColor = btnColor;
[/as]

Now we give our TextFormats some values and build the NextPrev buttons and TextFields.
[as]
showingFormat = new TextFormat();
showingFormat.color = _txtColor;
showingFormat.align = "left";
showingFormat.size = 10;
showingFormat.font = "verdana";
           
showingFormatC = new TextFormat();
showingFormatC.color = _txtColor;
showingFormatC.align = "center";
showingFormatC.size = 10;
showingFormatC.font = "verdana";
           
butFormat = new TextFormat();
butFormat.color = _btnColor;
butFormat.align = "left";
butFormat.size = 10;
butFormat.font = "verdana";
// Set our initial x and y positions
xPos = 0;
yPos = 0;
// Set up our previous button
prevBtn = new Button();
prevBtn.x = xPos;
prevBtn.y = yPos;
prevBtn.width = 60;
prevBtn.height = 20;
prevBtn.useHandCursor = true;
prevBtn.label = "<< Prev";
prevBtn.setStyle("textFormat", butFormat);
prevBtn.addEventListener(MouseEvent.CLICK, npHandler);
prevBtn.enabled = false;
addChild(prevBtn);
               
xPos += 70;
// Set up our text fields to show the results
st1 = new TextField();
st1.x = xPos;
st1.y = yPos;
st1.width = 60;
st1.text = "Showing";
st1.setTextFormat(showingFormatC);
addChild(st1);
           
xPos += 60;
           
st2 = new TextField();
st2.x = xPos;
st2.y = yPos;
st2.width = 50;
st2.text = String(showingItem) + " - " + String(_itemsToShow);
st2.setTextFormat(showingFormatC);
addChild(st2);
               
xPos += 45;
           
st3 = new TextField();
st3.x = xPos;
st3.y = yPos;
st3.width = 25;
st3.text = "of";
st3.setTextFormat(showingFormatC);
addChild(st3);
               
xPos += 20;
           
st4 = new TextField();
st4.x = xPos;
st4.y = yPos;
st4.width = 25;
st4.text = String(_numItems);
st4.setTextFormat(showingFormatC);
addChild(st4);
               
xPos += 35;
// Set up our Next button
nextBtn = new Button();
nextBtn.x = xPos;
nextBtn.y = yPos;
nextBtn.width = 60;
nextBtn.height = 20;
nextBtn.useHandCursor = true;
nextBtn.label = "Next >>";
nextBtn.setStyle("textFormat", butFormat);
nextBtn.addEventListener(MouseEvent.CLICK, npHandler);
//nextBtn.enabled = false;
addChild(nextBtn);
// Our variable itemStart will always be 0 at the beginning
itemStart = 0;
}
[/as]

Where the buttons are concerned it's worth noting that they will use whatever the default skins are in your fla file. To change the appearance of your buttons, go into the library in the fla and play with the skins until you get them looking right for your application. This last parameter you pass in the NextPrev constructor is the button text color, so, if your buttons are black for instance, you can set the button text color to white.

After we put the previous button on stage, we add in the TextFields that will display our changing data. The first TextField, st1 simply contains the word "Showing". Next, st2 gives us the showingItem variable plus a hyphen plus the _itemsToShow variable. The TextField st3 just holds the word "of", while st4 displays the total number of items available held in the variable _numItems. Then we set up our next button, and lastly we set the variable itemStart to 0 for the first run through the data.

Now we'll look at the button handler function npHandler.
[as]
private function npHandler(e:MouseEvent):void{
  switch(e.target.label){
    case "Next >>":
      // Set our Prev button to be enabled
      prevBtn.enabled = true;
      // Add the _maxItems to our itemStart, _itemsToShow and showingItem variables
      itemStart += _maxItems;
      _itemsToShow += _maxItems;
      showingItem += _maxItems;
      // If our _itemsToShow variable is greater than or equal to our _numItems
      // we set our _itemsToShow to equal our _numItems and set our Next buttons enabled property to false
      if(_itemsToShow >= _numItems){
        _itemsToShow = _numItems;
        nextBtn.enabled = false;
      }
      // Now we update our text field
      st2.text = String(showingItem) + " - " + String(_itemsToShow);
      st2.setTextFormat(showingFormatC);
      // Here we package up our variables into our statsArray and send it back to our main class using our Custom Event
      statsArray = new Array();
      statsArray.push(showingItem);
      statsArray.push(itemStart);
      statsArray.push(_itemsToShow);
      statsArray.push(nextBtn.enabled);
      statsArray.push(prevBtn.enabled);
      dispatchEvent(new CustomEvent(CustomEvent.CUSTOM, statsArray));
      break;
    case "<< Prev":
      // First we set our Next buttons enabled property to true
      nextBtn.enabled = true;
      // Now we subtract our _maxItems from each of the other variables
      itemStart -= _maxItems;
      _itemsToShow -= _maxItems;
      showingItem -= _maxItems;
      // Now, if our _itemsToShow variable is greater than our _maxItems, then we might have an odd number as our _itemsToSho               

  // We use the modulo operater % to find the remainder of our _itemsToShow divided by our _maxItems
      // Example: if we have a total of 13 items and our _maxItems is 5, and we are at our last possible Next setting then we are currently
      // showing 11 to 13 of 13
      // So, _itemsToShow was 13, but we subtracted our _maxItems and ended up with 8 and we don't want that because pressing the prev button
      // should take us back to showing 6 - 10 of 13, not 6 - 8 of 13
      // By subtracting the variable mod (3 in this case) from our _itemsToShow we end up with 5, to which we then add our _maxItems to to end up with 10
      if(_itemsToShow > _maxItems){
        var mod = _itemsToShow % _maxItems;
        if(mod > 0){
          _itemsToShow -= mod;
          _itemsToShow += _maxItems;
        }
      }
      // Now, if our _itemsToShow is less than or equal to _maxItems and our _numItems is greater than our equal to our _maxItems
      // then we want to make sure that our itemsToShow equals _maxItems and that our previous button has its enabled property set to false
      if(_itemsToShow <= _maxItems && _numItems >= _maxItems){
        _itemsToShow = _maxItems;
        prevBtn.enabled = false;
      }
      st2.text = String(showingItem) + " - " + String(_itemsToShow);
      st2.setTextFormat(showingFormatC);
      statsArray = new Array();
      statsArray.push(showingItem);
      statsArray.push(itemStart);
      statsArray.push(_itemsToShow);
      statsArray.push(nextBtn.enabled);
      statsArray.push(prevBtn.enabled);
      dispatchEvent(new CustomEvent(CustomEvent.CUSTOM, statsArray));
      break;
    }
}
[/as]

The npHandler function is pretty well commented, and, hopefully, makes sense to you. In a nutshell, what we are doing is adding (for next) or subtracting (for previous) our _maxItems variable from the other variables. We need to check if any of the variables go over or under the limits and adjust things accordingly if they do. By toggling the enabled property of our next and prev buttons we avoid a case where the numbers could go negative, by simply not allowing that situation to take place. In both the next and the prev cases we set up our statsArray to a new, empty array, then add in the updated variables using push() and then dispatch our CustomEvent back to the Document Class to allow it to show the next, or previous, set of results.

The next function takes care of our manual updates for the instance of the NextPrev class that was not clicked, in the case where you are using both a top and bottom instance.
[as]
public function manualUpdate(showItem:int, sItem:int, itemsTS:int, nBtn:Boolean, pBtn:Boolean):void{
  showingItem = showItem;
  itemStart = sItem;
  _itemsToShow = itemsTS;
  nextBtn.enabled = nBtn;
  prevBtn.enabled = pBtn;
  st2.text = String(showingItem) + " - " + String(_itemsToShow);
  st2.setTextFormat(showingFormatC);
}
[/as]

First, we receive in the parameters we sent from the Document Class and transfer them to our local class variables. The next and prev buttons enabled properties are updated accordingly, and we update the information displayed in our TextFields so that now the two instances of the NextPrev class we are using are identical. And that's all there is to it, believe it or not.

A Real World Example

With limited space and somewhat restricted capabilities, it's hard to give you a real world example of this here. I am currently working on the prototype for an upcoming project which will demonstrate the actual usage of pagination with a bit more depth. You can find the example here. The only category that has any test data in it is the Genre category, and you will need to scroll down it's results list until you come to Popular Styles. Click on that and you will get a sub menu, from which you choose Rock. All the buttons to play the music or add to cart are working. Keep in mind it is a work in progress, so no guarantee that everything works as it's supposed to, but it should give you an idea of what things can be accomplished with pagination in Flash versus PHP/HTML.

Where to Next?

Suppose you want to have individual page buttons instead of being stuck with consecutively moving through the list of results using the next and previous buttons? This might be a good idea if your results set gives you 50 pages. All the raw material you need is here. You will need a few more variables, such as numPages, but it should be relatively easy to work out. Give it a try, and if you beat me to it, put up a new tutorial here
on ActionScript.org yourself that extends our NextPrev class. Go on, you can do it!

A Word of Thanks

To all the people who have written to me with questions and suggestions, thank you! It's great to hear from you, and really nice to know that these tutorials have been helpful. Some of you have written with terrific suggestions to tighten up the code, or take care of situations I hadn't imagined, and that feedback is very helpful in my continuing quest to learn and write better code. A frequently asked question I receive is, Are you
available to be hired? The answer is yes, so if you have a project in mind, feel free to contact me and I'll see if I can help out. And, last, but not least, a big thumbs up to the folks at ActionScript.org who make all this community interaction possible through their hard work. The Flash community is a stronger and better place because of you!