ActionScript.org Flash, Flex and ActionScript Resources - http://www.actionscript.org/resources
The InfoBox Class
http://www.actionscript.org/resources/articles/1010/1/The-InfoBox-Class/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 May 24, 2010
 
The InfoBox class creates a pop-up box which can contain an image, a title, a description and a link. It comes in handy in spots where you need to display additional information, but don't have the room, or don't want to dedicate the necessary space. With just a few lines of code, you can add it to any project.

Onward and Upward
The InfoBox class is built upon the back of the WordBubble class. As you start creating your own classes, and then retool them to do other things, you will discover better ways of doing some things, and ways of optimizing your code. We'll see a couple of examples of that in this tutorial.

In the WordBubble class we had a somewhat limited number of options to change the look of the WordBubble. This time we will go a little over board and give ourselves many customizable options. How many are too many? Well, you'll have to decide that for yourself, but, remember, the number of parameters being passed to the constructor is in direct proportion to the flexibility of the class. CS5 promises to bring us greater introspection of custom classes, so this should help greatly with classes that use a lot of parameters. Once you get comfortable with how this class works, you can always hard code some of the options and cut down the number of parameters it is necessary to pass.

Let's take a look at what we are going to be building. This demo is an extremely simplified use of the InfoBox class. In a more realistic example, there would be a block of text outlining something that, as a visitor, you might want More Info on. Use your imagination, and click the More Info buttons. 



All the classes, example fla files and images we'll be using in this tutorial are included in the infoBox.zip file. Go ahead and download it now: infoBox.zip

Getting Set Up

You can unpack the zip file directly to the folder you are keeping this project in and it will be all ready to go. Inside the ca folder you'll find the xty folder, then inside the xty folder you'll find the myUtils folder. Inside myUtils you'll find the InfoBox.as class file. If you have a class library of your own, you can move the InfoBox.as file to that - just make sure to change the package information to reflect the new location of the file. If a class library is a new concept, have a look at the WordBubble class for some further information.

Inside the zip file you will find infoBoxDemo.fla (CS4) and infoBoxDemoCS3.fla (CS3). The class files, InfoBoxDemo.as and InfoBox.as don't care which version you are using. You'll also notice a folder called "images". This is where the images used in the example are stored. And lastly, there is the caurina folder which hold all the Tweener classes.

Open up whichever fla you are using. Notice that there is nothing on the stage. In the library, the only thing there is an instance of the Button component. In the example fla, if you look closely at the button skins you will see that I have modified them from the default skin. The InfoBox uses a button to close itself, and it's important to note that it will use whatever button skin is in the main fla's library. The other thing to note is the Document Class in the Properties panel for the fla, which is set to InfoBoxDemo. No .as extension - just the name of the class. Speaking of the Document Class, let's take a look at it.

The Document Class
Open up the InfoBoxDemo.as file. A Document Class can be thought of as a replacement for your timeline code, or external AS files. If you want to use timeline code, you need to modify this code slightly by removing all references to the declaration "private", and it will work just fine. But, since I am trying to show you the benefits of using classes, we'll stick with our Document Class approach.
[as]
package{
    import flash.display.*;
    import flash.events.*;
    import flash.text.*;
    import fl.controls.Button;
    // Import the Info class - change this if you have set it up in your own class library
    import ca.xty.myUtils.InfoBox;
[/as]

First of all we'll look at the package. Notice that in this case there is nothing but the word package. That is because we are including this class in the same folder as the fla, so there is no library address necessary. Our import statements bring in the classes we will be using, including the Button component and our InfoBox class. Note that the InfoBox class references my library path, and, if you are using your own library, you will need to change the package declaration to match where you are storing the file.

Next, we declare our variables, starting with an instance of the InfoBox. Right under that we declare a Boolean variable called isOpen, which will keep track of whether or not an instance of our InfoBox is already open. Since it starts out false, we'll set it to that here. Then come the variables that we will be using to pass information to the InfoBox constructor function. The color arrays have been initialized here to get them ready to use. Then we set up a few instances of Button, then a TextFormat for our buttons and finally the xPos and yPos to hold the x and y positions.

Inside the constructor function we fill out some properties for our button format.
[as]
butFormat = new TextFormat();
butFormat.font = "verdana";
butFormat.size = 11;
butFormat.color = 0xffffff;
[/as]
Then we give our xPos and yPos variables a starting point, after which we set up the buttons.
[as]
xPos = 215;
yPos = 100;

moreInfo1 = new Button();
moreInfo1.x = xPos;
moreInfo1.y = yPos;
moreInfo1.width = 70;
moreInfo1.height = 20;
moreInfo1.label = "More Info";
moreInfo1.name = "info1";
moreInfo1.setStyle("textFormat", butFormat);
moreInfo1.addEventListener(MouseEvent.CLICK, moreInfoHandler);
addChild(moreInfo1);
[/as]

Nothing fancy is happening with the buttons, except that we are using the "name" property to assign a unique name to each button. This is because we are using the same label property for each button, and will need something unique to compare when we get to the moreInfoHandler function which uses a switch statement to tell us which button has been clicked.
[as]
private function moreInfoHandler(e:MouseEvent):void{
if(isOpen){
                removeIB();
            }
    switch(e.target.name){
        case "info1":
            trace("Info1");
            bgColorArray1 = [0xffcc00, 0xff9900];
            bgColorArray2 = [0xffffff, 0xdddddd];
            infoTitle = "My Cat Sam";
            infoDescription = "You first met Sam in my last tutorial about a WordBubble class. Now that I have some room, I can tell you a little more about him. As you can tell by the sleepy look on his face, he likes his naps.";
            var infoImage1:String = "images/samSmall.jpg";
            ib = new InfoBox(400, 160, 0x000000, bgColorArray1, bgColorArray2, infoTitle, infoDescription, infoImage1, "www.xtydigitaldesign.ca/images/samSara1280.jpg", "_blank", "See a big picture", 0x0000ff);
            ib.x = 50;
            ib.y = 50;
            ib.addEventListener(InfoBox.CLEAN_UP, removeIB);
            addChild(ib);
      isOpen = true;
            break;
        case "info2":
            trace("Info2");
            bgColorArray1 = [0xffcc00, 0xff9900];
            bgColorArray2 = [0x6D4201, 0x543201];
            infoTitle = "Sam's Sister Sara";
            infoDescription = "Sara is Sam's little sister now, but this was not always the case. When they were just 6 weeks old Sam was the runt and his sister stood taller than he did. Sara is still the boss, however, and Sam always defers to her judgement. If Sara runs upstairs, Sam knows there is a good reason and follows immediately.";
            var infoImage2:String = "images/mugShot.jpg";
            ib = new InfoBox(400, 180, 0xffffff, bgColorArray1, bgColorArray2, infoTitle, infoDescription, infoImage2);
            ib.x = 50;
            ib.y = 50;
            ib.addEventListener(InfoBox.CLEAN_UP, removeIB);
            addChild(ib);
           isOpen = true;
            break;
        case "info3":
            trace("Info3");
            bgColorArray1 = [0xffcc00, 0xff9900];
            bgColorArray2 = [0x6D4201, 0x543201];
            infoTitle = "This Choice Has No Photo";
            infoDescription = "This option does not use a photo, as you can plainly see. What else is different about this implementation of the InfoBox is that I did not make the height of the box quite large enough, so the box is auto-stretching to fit.";
            var infoImage3:String = "none";
            ib = new InfoBox(300, 120, 0xffffff, bgColorArray1, bgColorArray2, infoTitle, infoDescription, infoImage3, "www.xtydigitaldesign.ca/images/samSara1280.jpg", "_blank", "See a big picture", 0x00ff00);
            ib.x = 100;
            ib.y = 50;
            ib.addEventListener(InfoBox.CLEAN_UP, removeIB);
            addChild(ib);
    isOpen = true;
            break;
            break;
    }
}
[/as]

The first thing we do is find out if there is already an instance of the InfoBox open. If there is, we use the removeIB() function to get rid of it and return the isOpen variable to false. The switch statement checks to see which button has been clicked by comparing the name property. In the case of "info1" the first button has been clicked and so we want to send the information relevant to that button to our InfoBox class. Since we will always be sending the bgColorArrays, and the info title and description, I have set these variables up so that we do not need to declare them each time. But, since we may not always be using an image, I have left the infoImage variable to be created each time. When we get to the InfoBox class, you will see that the infoImage parameter has a default value of "none". By giving the parameter a default value we do not have to include it when we create a new InfoBox; it is considered optional. Now, if we were to set up an infoImage variable in the same way as we did the infoTitle variable, you would have to pass a value of "none" each time for the simple reason that once you set the infoImage to have a value it will retain that value, and so you could inadvertently pass along an image to be displayed when you had no intention of doing so. Having said that, you will also notice in the case "info3" that we have to set a variable called infoImage3 to "none". Why is that? Before I explain that bit of contradiction, let's take a look at how to set up a new InfoBox using the "info3" example.
[as]
ib = new InfoBox(300, 120, 0xffffff, bgColorArray1, bgColorArray2,
                 infoTitle, infoDescription, infoImage3,
                 "www.xtydigitaldesign.ca/images/samSara1280.jpg",
                 "_blank", "See a big picture", 0x00ff00);
[/as]
The first parameter is the width you want the InfoBox to be, and the second parameter is the height. The third parameter is the text color, then come our background colors, first the array for bg1 and then the array for bg2. Following these are our variables for the Title and the Description. Now, everything after this has a default value in the InfoBox constructor and are therefore all optional. Looking at example 3, you see that we are using the link option, which includes the url, the target, the link text and finally the link color. Just before the link options we have declared infoImage3, even though we are not using an image in this case, because, in order to use the link options we must include the image option to hold its place in the InfoBox constructor, otherwise the parameters will all be off by one. If we leave out the parameter for infoImage, the constructor will use the url for the link as the image, the target as the link, the link text as the target and the link color as the link text. A nasty mess and a pile of errors. If you were not going to use a image or a link, then you could leave everything after the infoDescription blank.

The three examples are here to illustrate the different ways in which you can set up your InfoBox. For an individual project, you would most likely be serving up the same kinds of information and consistently using, or not using, either an image or a link. In the InfoBoxDemo class you will notice a function called buildInfoBox which has been commented out. Using this function will save you making the same InfoBox over and over. In your moreInfoHandler function you would change the information for things like title and description and then call buildInfoBox().
The only other function in this class is the removeIB function.
[as]
private function removeIB(e:Event = null):void{
    ib.removeEventListener(InfoBox.CLEAN_UP, removeIB);
    removeChild(ib);
    isOpen = false;
}
[/as]
This does what it says and removes the instance of the InfoBox you created, and sets our variable isOpen to false. Note that the event passed is set to null. This is so we can use it without having an event passed to it, as we do in the moreInfoHandler function. The other thing to notice is the event type on the listener for the ib instance - InfoBox.CLEAN_UP. This will not look like anything you've run across before because it is our own creation. We'll talk about it more when we get to the InfoBox.as class. And, we might as well do that right now.


The InfoBox Class
Once again, let's start at the top with our package declaration.
[as]
package ca.xty.myUtils {
    import flash.display.*;
    import flash.text.*;
    import flash.geom.Matrix;
    import flash.events.*;
    import flash.net.*;
    import fl.controls.Button;
    import caurina.transitions.Tweener;
[/as]
Here the package is set to use my class library address. You will need to change it to yours if you are using your own class library. We import most of the same classes, with the addition of flash.geom.Matrix for the gradient fill we will be using, the flash.net.* classes for the url handling of the link option, and caurina.transitions.Tweener classes for the tweening. If you prefer another Tweening engine simply import yours here and change the Tween code further down in the class.
Next we set up the variables we will be using in this class.
[as]
public static const CLEAN_UP:String = "CLEAN_UP";
// These variables will be used to store the data we are transferring from the constructor
private var _ibWidth:int;
private var _ibHeight:int;
private var _txtColor:Number;
private var _bgColorArray1:Array = new Array();
private var _bgColorArray2:Array = new Array();
private var _infoTitle:String;
private var _infoDescription:String;
private var _infoImage:String;
private var _infoLink:String;
private var _linkTarget:String;
private var _linkText:String;
private var _linkColor:Number;
// These variables will be used to load and hold the image
private var holder:MovieClip;
private var fakeHolder:Sprite;
private var loader:Loader = new Loader();
private var myBitmapData:BitmapData;
private var myBitMap:Bitmap;
// These two Shapes make up the background for the InfoBox
private var bg1:Shape;
private var bg2:Shape;
// This button will be used to close the InfoBox
private var xBut:Button;
// These are the text fields we will be using
private var t1:TextField;
private var t2:TextField;
private var t3:TextField;
// These are the TextFormats we will be using
private var ibFormat:TextFormat;
private var ibFormatC:TextFormat;
private var ibFormatL:TextFormat;
private var butFormat:TextFormat;
private var butOverFormat:TextFormat;
// These variables are used to simplfy the positioning of the elements in the InfoBox
private var xPos:int;
private var yPos:int;
private var xOffSet:int;
// The popUpPanel will be the container that holds the rest of the elements in the InfoBox
// The moveBar is a Sprite that we will be using to drag the InfoBox around
private var popUpPanel:Sprite;
private var moveBar:Sprite;
[/as]
The first variable is a static constant that we will be using to dispatch an event to the InfoBoxDemo class. It is public so that it can travel between classes. Then, we set up the variables that will hold the parameters passed in constructor function. The next set of variables are used to load, create and display an image. The remaining variables are all the parts of the InfoBox itself, such as Shapes and text fields.
Next comes the constructor function.
[as]
public function InfoBox(wdth:int, hght:int, txtColor:Number, bgColorArray1:Array, bgColorArray2:Array, infoTitle:String, infoDescription:String, infoImage:String = "none", infoLink:String = "none", linkTarget:String = "_blank", linkText:String = "Visit this site", linkColor:Number = 0x0000ff):void{
    // Transfer the constructors parameters into the variables we declared above
    _ibWidth = wdth;
    _ibHeight = hght;
    _txtColor = txtColor;
    _bgColorArray1 = bgColorArray1;
    _bgColorArray2 = bgColorArray2;
    _infoTitle = infoTitle;
    _infoDescription = infoDescription;
    _infoImage = infoImage;
    _infoLink = infoLink;
    _linkTarget = linkTarget;
    _linkText = linkText;
    _linkColor = linkColor;
    // Add an event listener to our image loader
    loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded);
[/as]
We transfer the parameter values into the class variables we created. You see in the constructor function how all the parameters after infoDescription have default values. This lets us know that they are optional and that their default values will be passed to our class variables whether we set the parameter when create a new instance of InfoBox or not. Lastly, we add an event listener to our loader which will fire when the loading of an image is complete.
The next thing to do is add some properties to our text formats.
[as]
ibFormat = new TextFormat();
ibFormat.font = "Verdana";
ibFormat.size = 11;
ibFormat.color = _txtColor;
ibFormat.align = "justify";
           
ibFormatC = new TextFormat();
ibFormatC.font = "Verdana";
ibFormatC.size = 12;
ibFormatC.color = _txtColor;
ibFormatC.align = "center";
ibFormatC.bold = true;
           
ibFormatL = new TextFormat();
ibFormatL.font = "Verdana";
ibFormatL.size = 12;
ibFormatL.color = _linkColor;
ibFormatL.align = "center";
           
butFormat = new TextFormat();
butFormat.font = "Verdana";
butFormat.size = 9;
butFormat.bold = true;
butFormat.color = 0xffffff;
           
butOverFormat = new TextFormat();
butOverFormat.font = "Verdana";
butOverFormat.size = 9;
butOverFormat.color = 0x00ff00;
butOverFormat.bold = true;
[/as]
With everything ready to go, we create the first element that makes up our InfoBox.
[as]
popUpPanel = new Sprite();
addChild(popUpPanel);
popUpPanel.alpha = 0;
           
xPos = 0;
yPos = 0;
[/as]
The popUpPanel is the Sprite that will be the container for all the rest of the elements which make up the InfoBox. Since we are not ready to show it to the world quite yet, we set it's alpha value to 0. Now, set the x and y position variables. Remember, these are going to be relative to the popUpPanel, not the main stage, so we want to start in the upper left hand corner, or 0,0.
Create the first background Shape.
[as]
bg1 = createShape(bgColorArray1, _ibWidth, _ibHeight);
bg1.x = xPos;
bg1.y = yPos;
popUpPanel.addChild(bg1);
[/as]
Those of you who studied the WordBubble tutorial will immediately see the improvement here. In the WordBubble class the drawing of the rounded rectangle and the application of a gradient fill was laid out here - and it was laid out again to draw the second background Shape! Now why do that instead of creating a single function to draw them both? That's the question I asked myself when I was putting together the InfoBox class and decided I wasn't practicing what I preach, so I put together the createShape function with the following parameters: a color array, the width and the height. Now I can move this function to any class where I need to draw a rounded rectangle with a gradient background, and use it to draw any number of them.
[as]
xPos = 3;
yPos = 3;

bg2 = createShape(_bgColorArray2, _ibWidth - 6, _ibHeight - 6);
bg2.x = xPos;
bg2.y = yPos;
popUpPanel.addChild(bg2);
[/as]
Before we draw the second background Shape, we change the x and y positions to 3. This is because we want to have a 3 pixel "border" effect. So, the second background Shape will be 6 pixels less in both width and height and positioned 3 pixels down and to the right of the first background Shape.
[as]
xOffSet = 13;
[/as]
The xOffSet variable is set to 13 in order to provide us with some default padding for the text fields that we will draw shortly. The reason for the number 13 is that we are calculating the margins we want based on the second background Shape, but we have to take into account the existing 3 pixels we are already using as a "border" from the first background Shape. So, if I want the margins on the left and right of the image to be 5 pixels, the formula is: 2 x 5 + 3 = 13. You will see the value of having this variable in place in a few moments.

Next we create our moveBar Sprite which will act as our drag bar. To make this, we have another little function called createSprite, which does exactly that.
[as]
moveBar = createSprite(0x000000,_ibWidth - 6, 20, xPos, yPos);
moveBar.alpha = 1;
moveBar.addEventListener(MouseEvent.MOUSE_DOWN, onStartMove);
moveBar.addEventListener(MouseEvent.MOUSE_UP, onStopMove);
popUpPanel.addChild(moveBar);
[/as]
We create our moveBar here so that it sits on top of the background shapes. The alpha value has been set at 1 in the function that creates the Sprite, but it is in here so that you can adjust it if you want to. The level of alpha does not affect the drag-ability, so you can have set it 0 if you want it completely transparent. We give our moveBar a couple of event listeners - MOUSE_DOWN and MOUSE_UP - which will control the drag capabilities.
[as]
xPos += 5;
yPos += 25;
[/as]
Getting ready to add the next element, we update the xPos and yPos values. To give us a bit of padding below the moveBar, which is set at 20 pixels in height, we add 25 pixels to the yPos and, to bring it out from the left a wee bit, we add 5 pixels to the xPos variable.
[as]
if(_infoImage != "none"){
    holder = new MovieClip();
    holder.x = xPos;
    holder.y = yPos;
    popUpPanel.addChild(holder);
               
    fakeHolder = new Sprite();
    fakeHolder.addChild(loader);
           
    loader.load(new URLRequest(_infoImage));
}else{
    buildRest();
    }
}
[/as]
Now, if there is an image declared in your constructor, we add in the holder MovieClip which will hold the image. Then we create the fakeHolder Sprite to hold our instance of loader, and then we load the image. Since we don't know the size of the image until it arrives, we want to load it first before we continue building the rest of the InfoBox. If there is no image, then we continue building the InfoBox by going to the buildRest function. Assuming there is an image to load, let's skip down to the onImageLoaded function now.
[as]
private function onImageLoaded(event:Event) {
    xOffSet += loader.width;
    myBitmapData = new BitmapData(loader.width, loader.height);
    myBitmapData.draw(fakeHolder);
    myBitMap = new Bitmap();
    myBitMap.bitmapData = myBitmapData;
          
    holder.addChild(myBitMap);
    holder.alpha = 0;
    buildRest();
    Tweener.addTween(holder, {alpha:1, time:1, delay:.1, transition:"linear"});
}
[/as]
This function handles the COMPLETE event for our image. And here is where the value of the xOffSet variable comes into play. We have set its default at 13 earlier on. Now that our image is loaded, we can calculate the positioning for the text fields by adding the loader's width property to our xOffSet. This gives us the 5 + 3 pixels on the left of our image, plus the width of the image, and 5 pixels on the right of the image. Back to our image.

We use the BitmapData variable myBitmapData to put the loader info into, then draw our fakeHolder Sprite. Using our Bitmap variable myBitMap, we create an new instance of Bitmap, and set it's bitmapData property to our myBitmapData variable. Next we add our bitmap as a child of our MovieClip holder and set it's alpha property to 0. Once we are ready to show the image, we build the rest of the InfoBox using the buildRest function and then use Tweener to bring the holder's alpha from 0 to 1.

Scroll back up and we'll take a look at the buildRest function.
[as]
private function buildRest():void{
    xPos = xOffSet;
          
    t1 = new TextField();
    t1.x = xPos;
    t1.y = yPos;
    t1.width = bg2.width - (xOffSet + 5);
    t1.height = 20;
    t1.text = _infoTitle;
    t1.setTextFormat(ibFormatC);
    popUpPanel.addChild(t1);
          
    yPos += 25;
          
    t2 = new TextField();
    t2.x = xPos;
    t2.y = yPos;
    t2.width = bg2.width - (xOffSet + 5);
    t2.height = 80;
    t2.autoSize = TextFieldAutoSize.LEFT;
    t2.multiline = true;
    t2.wordWrap = true;
    t2.htmlText = _infoDescription;
    t2.setTextFormat(ibFormat);
    popUpPanel.addChild(t2);
          
    if(_infoLink != "none"){
        var lk:String = '<a href="http://' + _infoLink + '" target="' + _linkTarget + '"><u>' + _linkText + '</u>';
        t3 = new TextField();
        t3.x = xPos;
        t3.y = t2.y + t2.height + 10;
        t3.width = bg2.width - (xOffSet + 5);
        t3.height = 20;
        t3.htmlText = lk;
        t3.setTextFormat(ibFormatL);
        popUpPanel.addChild(t3);
    }
          
    if(_infoLink == "none"){
        if(bg1.height - (yPos + t2.height) < 12){
            bg1.height = (yPos + t2.height) + 12;
            bg2.height = bg1.height - 6;
        }
    }else{
        if(bg1.height - (t3.y + t3.height) < 12){
            bg1.height = (t3.y + t3.height) + 12;
            bg2.height = bg1.height - 6;
        }
    }
          
    xBut = new Button();
    xBut.x = _ibWidth - 25;
    xBut.y = 3;
    xBut.width = 20;
    xBut.height = 20;
    xBut.label = "X";
    xBut.useHandCursor = true;
    xBut.setStyle("textFormat", butFormat);
    xBut.addEventListener(MouseEvent.CLICK, xButHandler);
    xBut.addEventListener(MouseEvent.ROLL_OVER, xOverHandler);
    xBut.addEventListener(MouseEvent.ROLL_OUT, xOutHandler);
    popUpPanel.addChild(xBut);
    // Now that everything is in place, we use Tweener to bring the alpha from 0 to 1
    Tweener.addTween(popUpPanel, {alpha:1, time:1, delay:.1, transition:"easeOutExpo"});
}
[/as]
The xPos variable is set to our xOffSet, which is the original value of 13 if we don't have an image. If we do have an image being loaded, xOffSet has been adjusted to accommodate the image size in our onImageLoaded function.

Our variable t1 is created to use as our Title field. The width of it runs the entire width of the InfoBox minus the XOffSet for the stuff on the left, and minus an additional 5 to give us a little extra padding on the right. The text format for this field places the text in the center, perfect for a title. Add in the text from the _infoTitle variable and we're all set.

We don't want to change the x position of the next element, but we do need to move it down, so we add 25 to our yPos before we add our next text field to give us a bottom margin of 5 underneath the title field.

The next text field, t2, is our description field. The width of it is calculated in the same manner as the title field and we give it a starting height of 80. Because we don't know in advance how much text there will be, we set this text fields autoSize property to LEFT, and, by declaring the multiline and wordWrap properties to be true, this will force the text field to expand downwards to accommodate any quantity of description text.

Our final text field, t3, is only added on the condition that the variable _infoLink does not equal "none". The first order of business is to build the link using the variable lk. We are using some simple HTML mark up to create a link out of the variables _infoLink, _linkTarget, and _linkText. We used the _linkColor variable in the text format ibFormatL. Make note that the t3 text field is set to receive htmlText. Once we have the link built, we can set up the t3 text field. The y position of this text field will be dependent on the final size of the t2 text field, so we grab the y position of t2, add the height of t2 to it, and then give ourselves a 10 pixel bottom margin.

So, everything is in place but, what if the t2 description text field is larger than the background shapes bg1 and bg2? Well, we need to adjust each of those accordingly. First of all we need to know if there is a link text field present. In the first if statement there is no link, so we base our calculations on the t2 text field. In the second part of the if statement, we do have a link text field so we base our calculations on the t3 text field. In either case we are using the number 12 for our comparison because we have set up a 3 pixel margin for each of our background shapes, so that's 6 on top and 6 on the bottom to make up the number 12. If you change the width of the "border" remember to come here and change these numbers to match.

Now that everything is the right size, we add in our xBut, which will close the InfoBox. We add this element last because we want the xBut on top of everything else so that nothing interferes with the users ability to click it. Nothing worse than having a button you can see, but can't use because it's underneath a text field!

With a way for the user to get rid of the InfoBox, we are all ready to go, and so we use Tweener to bring the whole thing to life by changing the alpha of the popUpPanel from 0 to 1.
[as]
private function onStartMove(event:MouseEvent):void{
   popUpPanel.startDrag();
}
       
private function onStopMove(event:MouseEvent):void{
  stopDrag();
}
[/as]

The onStartMove and onStopMove functions are associated with the moveBar and either start the dragging on MOUSE_DOWN or stop it on MOUSE_UP.
[as]
private function xOverHandler(e:MouseEvent):void{
   e.target.setStyle("textFormat", butOverFormat);
}

private function xOutHandler(e:MouseEvent):void{
  e.target.setStyle("textFormat", butFormat);
}

private function xButHandler(e:MouseEvent):void{
  dispatchEvent(new Event(InfoBox.CLEAN_UP));
}
[/as]

The next two functions, xOverHandler and xOutHandler, take care of the ROLL_OVER and ROLL_OUT events for the xBut, by manipulating the text format.

The function xButHandler looks after the CLICK event for the xBut, and here is where one more improvement comes in over the WordBubble class. In that class, the WordBubble can be "disappeared" using a Timer event, but what it does is remove the popUpPanel inside the class itself. This works fine, and it disappears all right, but the problem is that we left behind the instance of the WordBubble, wb, in the Document Class. What you could end up with are a bunch of invisible instances of WordBubble, and each one with it's own event listener, that will never be garbage collected by the Flash Player because they are still an active part of the display list according to the player's terms. We will improve on that situation by dispatching our own event, InfoBox.CLEAN_UP, to our Document Class and clean up our instances of InfoBox properly in the function removeIB. In the dispatchEvent function we create a new Event using the public static constant we created back at the begininng of this class.

The last two functions in the class are our work horses, createSprite and createShape. It is worth noting that in the createShape function I have used a color array containing two colors. You can use more than two colors, but if you do, you must also be sure and change the var alphas:Array = [1, 1] and the var ratios:Array = [0, 255] so that they have the same number of elements as your color array.
[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;
}
       
private function createShape(ca:Array, w:int, h:int):Shape{
    var bg:Shape = new Shape();
          
    var fillType:String = GradientType.LINEAR;
    var colors:Array = ca;
    var alphas:Array = [1, 1];
    var ratios:Array = [0, 255];
    var matrix:Matrix = new Matrix();
    var gradWidth:Number = w;
    var gradHeight:Number = h;
    var gradRotation:Number = 90 / 180 * Math.PI;
    var gradOffsetX:Number = 0;
    var gradOffsetY:Number = 0;

    matrix.createGradientBox(gradWidth, gradHeight, gradRotation, gradOffsetX, gradOffsetY);
    var spreadMethod:String = SpreadMethod.PAD;
    bg.graphics.beginGradientFill(fillType, colors, alphas, ratios, matrix, spreadMethod);
    bg.graphics.drawRoundRect(0, 0, w, h, 12);
    bg.graphics.endFill();
    return bg;
}
[/as]

And that's it. But what kind of a real world application would you use this in?

Out in the Real World
There are many places where this might come in handy. The first place I used it was in a shopping cart for a photo site. There are 4 options to choose from when you are purchasing a photograph, and the visitor must choose one before going further. To act as a quick reminder of what each option is I created the InfoBox and it works perfectly. It takes no more space than the More Info button and can provide an image and a link as well as the necessary information to help the visitor make the right decision. So, any place where you need to provide additional information, but lack the room to do so, is the spot to use a class like this.

Alert! Where to Next?

The InfoBox class grew from the WordBubble class, so there is no reason to think that other classes can't be sprouted from this one. Do you miss the Alert component which used to be in Flash, but was kidnapped by Flex? If so, you have all the necessary pieces in the InfoBox class to put together your own AlertBox. Give it a try and see what happens!

One Last Story

The project I am currently working on involves displaying lists of music, and, as there will be something like 4000 mp3s to choose from, I need to use some pagination.  While digging up some pagination code I'd written for past projects, I heard a voice in the background saying, "If you find yourself copying and pasting the same code into different projects over and over, it's time to make it a class". Sounds like the topic for my next tutorial.

As always, if you have any questions, don't be shy. Contact information can be found in my profile.