Get Adobe Flash player

The InfoBox Class

Once again, let's start at the top with our package declaration.

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;

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.

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;

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.

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

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.

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;

With everything ready to go, we create the first element that makes up our InfoBox.

popUpPanel = new Sprite();
addChild(popUpPanel);
popUpPanel.alpha = 0;

xPos = 0;
yPos = 0;

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.

bg1 = createShape(bgColorArray1, _ibWidth, _ibHeight);
bg1.x = xPos;
bg1.y = yPos;
popUpPanel.addChild(bg1);

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.

xPos = 3;
yPos = 3;

bg2 = createShape(_bgColorArray2, _ibWidth - 6, _ibHeight - 6);
bg2.x = xPos;
bg2.y = yPos;
popUpPanel.addChild(bg2);

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.

xOffSet = 13;

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.

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

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.

xPos += 5;
yPos += 25;

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.

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

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.

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

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.

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 = '' + _linkText + '';
        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"});
}

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 dependant 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.

private function onStartMove(event:MouseEvent):void{
    popUpPanel.startDrag();
}

private function onStopMove(event:MouseEvent):void{
    stopDrag();
}

The onStartMove and onStopMove functions are associated with the moveBar and either start the dragging on MOUSE_DOWN or stop it on MOUSE_UP.

private function xOverHandler(e:MouseEvent):void{
    e.target.setStyle("textFormat", butOverFormat);
}
// This function handles the ROLL_OUT event on our xBut button and changes the textFormat to our normal format
private function xOutHandler(e:MouseEvent):void{
    e.target.setStyle("textFormat", butFormat);
}
private function xButHandler(e:MouseEvent):void{
    dispatchEvent(new Event(InfoBox.CLEAN_UP));
}

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.

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

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 an InfoBox.

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. Use the comment form below, or drop me a line./p>

Page 1

 

Get Adobe Flash player

Comments

No Comments


Warning: date() [function.date]: It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'America/New_York' for 'EST/-5.0/no DST' instead in /home/xtydigi/public_html/footer.php on line 2