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.

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;


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 relative to the resultsTop container, not the stage.

if(useNP){
    itemsToShow = maxNum;
    npT = new NextPrevPN(numSongs, maxNum, maxNum, 0x000000, 0x000000, numPages);
    npT.x = (stage.stageWidth - (npT.width - 60))/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 += 50;
}


We check our variable useNP, and, if it is true, we set up the variables we need to interact with our NextPrevPN 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 NextPrevPN'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
numPages - this is the maximum number of page buttons we want to show at once in our NextPrevPN instance

Then we position our NextPrevPN 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 - 60 and then divide that result by 2. The reason for the -60 from the npT's width is because the last item to be set up in the NextPrevPN class is a button, and even though the width of that button is declared to be 40, Flash seems to stick with it's default button width of 100. So, 100 - 40 = 60. 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 NextPrevPN 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 50 to our yPos variable.

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;


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.

}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 = 111;
    //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();

// 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();


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 on with 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.

// 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]);


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 barArray array.

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 techniques, we add in our composerArray and descArray information.

When we get down to the buyArray it is a wee bit different. First off 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.

// 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 += barArray[i].height - 2;
 }else if(titleArray[i].height >= descArray[i].height && titleArray[i].height >= composerArray[i].height){
 barArray[i].height = titleArray[i].height + 2;
 yPos += barArray[i].height - 2;
 }else if(composerArray[i].height >= descArray[i].height && composerArray[i].height >= titleArray[i].height){
 barArray[i].height = composerArray[i].height + 2;
 yPos += barArray[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 += 37;


We went to a lot of trouble to accommodate unknown lengths of text. In the example 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 37 to our yPos. The 37 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.

// 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 NextPrevPN(numSongs, maxNum, maxNum, 0x000000, 0x000000, numPages);
    npB.x = ((stage.stageWidth - (npB.width - 60))/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;
}


Now, if you are using both a top and a bottom instance of the NextPrevPN 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.

private function buyHandler(e:MouseEvent):void{
 var musicIndex:int = e.target.name;
 trace("Item Clicked = " + musicIndex);
}


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. I'm just demonstrating how to get at that information.

The next function comes into play when any of the buttons, First, Prev, Page Buttons, Next or Last are clicked. It is called inside of the NextPrevPN 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 any of those buttons, the NextPrevPN class manipulates the data and sends the updated data back to our Document Class via our CustomEvent.

public function reBuild(e:CustomEvent):void{
    pageStart = e.arg[0];
    newPage = e.arg[1];
    newPageBut = e.arg[2]
    itemStart = e.arg[3];
    itemsToShow = e.arg[4];
    nBtn = e.arg[5];
    pBtn = e.arg[6];
    fBtn = e.arg[7];
    lBtn = e.arg[8];
    removeChild(resultsHolder);
    if(e.currentTarget.name == "NPT"){
         npB.manualUpdate(pageStart, newPage, newPageBut, itemStart, itemsToShow, nBtn, pBtn, fBtn, lBtn);
    }else if(e.currentTarget.name == "NPB"){
         npT.manualUpdate(pageStart, newPage, newPageBut, itemStart, itemsToShow, nBtn, pBtn, fBtn, lBtn);
         if (ExternalInterface.available) {
            ExternalInterface.call("backToPageTop", "Got This Far");
         }else{
             trace e("ExternalInterface not available");
         }
    }
    buildResults();
}


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 NextPrevPN class so we need it to have inter-class operability, and the access-modifier "public" does that. 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 NextPrevPN are taken from the array that is first prepared in the NextPrevPN class and then passed back. So, pageStart is set to equal e.arg[0], newPage 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 NextPrevPN 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 NextPrevPN 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 NextPrevPN 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 NextPrevPN class.

If the instance of the NextPrevPN 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. What's it for? If you have ever been on a web page that used pagination to break up a large number of search results, and were at the bottom of the page when you clicked next, did you end up back at the top of the page when the new set of results 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:
<script type="text/javascript">
 function backToTop(){
 self.scrollTo(0,0);
 }
</script>

That simple function causes the page to scroll to a left position of 0 and a top position of 0. You can adjust these values if, for instance, you had a 100 pixel header on the page. 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.

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;
}


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 NextPrevPN 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 <a href="http://www.learningactionscript3.com/2008/11/11/passing-arguments-with-events/" target="_blank">this site</a>, which contains a terrific explanation of all the pieces involved, so I will only go over it briefly here.

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");
 }
 
 }
}




This class differs in some other ways as well. 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 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 and Array and anything inbetween. 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:
someObject.addEventListener(CustomEvent.CUSTOM, someFunction, false, 0, true);
And to dispatch the event you do this:
dispatchEvent(new CustomEvent(CustomEvent.CUSTOM, yourArgs));
We'll see this in action when we look at the NextPrevPN class next.