AS3 Dropdown Menu with Reusable Classes

Further refining the MenuButton class
Modifying the draw() method:
For starters, let's modify the code that draws the background so that it uses the private variables instead of the hard-coded numbers we used before. For clarity, here's the current draw function just by itself:
private function draw():void {
//background shape:
var bg:Shape = new Shape();
bg.graphics.beginFill(0xFFCC00);
bg.graphics.drawRect(0, 0, 150, 30);
bg.graphics.endFill();
addChild(bg);
}
Change the fourth and fifth lines there to these:
bg.graphics.beginFill(_bgColor);
bg.graphics.drawRect(0, 0, _width, _height);
That takes care of the background, now all we have to do is create a TextField. But first, let's create an instance of the TextFormat class so that we can style the font's size and color:
var tFormat:TextFormat = new TextFormat();
tFormat.font = _fontName;
tFormat.size = _fontSize;
tFormat.color = _fontColor;
Again, the values supplied here are the private properties that were recorded from the constructor's parameter list. We're just passing them along to the TextFormat object. However, now that we are using a class that wasn't imported, it's going to be necessary to add this line to the list of imports:
import flash.text.*;
Next, we'll create a TextField instance, and keep using those private variables to customize it:
var tField:TextField = new TextField();
tField.multiline = false;
tField.selectable = false;
tField.embedFonts = _embedFonts;
tField.defaultTextFormat = tFormat;
tField.autoSize = TextFieldAutoSize.CENTER;
tField.antiAliasType = AntiAliasType.ADVANCED;
tField.text = _label;
tField.x = _width / 2 - tField.width / 2;
tField.y = _height / 2 - tField.height / 2;
addChild(tField);
We start off creating a new TextField instance called tField. Set multiline to false because all we need is one line. Selectable should be set to false because we are making a button, nobody is going to need to copy and paste our button's label. Standard stuff so far. The value for tField's embedFonts variable can be true or false. We just pass along the value that's in our private _embedFonts variable, whatever it is, because it will likewise contain either true or false itself. We set tField's defaultTextFormat property to the TextFormat object we previously made. Next, set the autoSize property to "center," and antiAliasType to "advanced."
(Using the constants TextFieldAutoSize.CENTER and AntiAliasType.ADVANCED is pretty typical here. If this is new to you, here's a a quick explanation: There are a zillion constants written into the built-in classes. Some classes are nothing more than just a collection of these. They are nothing mysterious at all, they are just public static variables that contain strings. When something is public static, you access it by using the name of the class with a dot. A familiar example would be MouseEvent.CLICK, which is a public static variable that just simply contains the string "click." Try tracing it out sometime and see for yourself).
Another brief aside you can skip if you already know it:
Helpful hint: think of the word static as meaning "only one copy." No matter how many instances are made from a class, if a property (variable) or method (function) of a class is static, there will only ever be one copy of it, which belongs to the class, and not to each instance of the class, and it is accessed by using the name of the class (an initial capital letter is usually your clue / giveaway) and a dot, instead of the name of any instance. You can make your own static items, too, but we won't be doing any of that for this project.
Next, the tField's text property is set to the private _label variable. Once again, the value here came down from the constructor function, so it will either contain "Click Here" if no argument was supplied, or it will contain whatever string of text was supplied for a first argument. The next two lines take the tField, which now has text in it, and center it both horizontally and vertically. Since the text has been added to it now, in the default font size and color, and it's been set to autoSize itself, it should now have a height and width of its own that can be used to center it. The _width and _height private variables are used for centering, instead of the built-in width and height properties inherited from the Sprite class. Now that the background shape has been drawn, really either one could be used, but it seemed more natural to me to keep using the supplied values. The centering code is typical and pretty easy to figure out. The tField's x property is set to the _width / 2 (which gives you the center of the whole button) minus the tField's width / 2. Even though the tField was set to autoSize from the center, the "registration point" of the tField is still considered to be in its the upper left hand corner.
Centering in the y direction is done exactly the same way, and finally the tField object is added to the display list with addChild(). One thing remains to be done and our draw() method will be complete. We'll add some code to the beginning of it that will remove everything from the display list if anything is there:
while(this.numChildren > 0) {
this.removeChildAt(0);
}
If there is nothing on the display list of this button (which will be the case the first time it is called), this while loop won't run at all. However, if the background shape and the text field have been previously created and added to the display list, this will see that they are removed just before they are created and added again. In this way, the whole button definitely refreshes itself every time, and everything is added from the back to the front again.
Creating "setter" functions:
Our final chore, before this class is complete, is to add eight "setter" functions so that the private variables in the class can be assigned new values at any time from the outside. If you haven't seen setter functions in action before, an explanation is in order. If you have, you can safely skip the following explanation. I have covered this before in my previous tutorials, but just in case you missed it, here goes. A "setter" function uses the set keyword along with a name of your choice, to create a function that from outside the class can be treated as though it were a property. Remember that in authoring classes we are always concerned with how instances of the class will be manipulated from the outside. Here is our first setter function:
public function set label(value:String):void {
_label = value;
draw();
}
Setter functions must be public, otherwise they wouldn't make any sense. With the above setter function in place, from the outside code, a call to this function might look like this:
btn.label = "This is my first button";
(Notice that from the outside, the word "label" is used without an underscore. This is the reason underscores are so handy for private class variables, because when using setter functions, the two names must be different inside the class).
When this command is encountered, Actionscript does us a huge favor: It finds our setter function in the class, and calls it, passing in the string of text after the equals sign ("This is my first button") as an argument to the parameter list. The parameter list for a setter function can only be one item long, so it all works out well. The "value" parameter is (magically) assigned the string of text. Next, in the body of the function, our _label (private variable) receives it, and the whole button is redrawn by calling the draw() method again. The draw() method removes everything from the display list, remember, and draws it all over again. Only this time it's redrawing it with a new string of text contained in the _label variable!
Anyway, that's the scoop on setter functions. They are methods (functions), but from the outside, they can be treated like properties, complete with dot syntax. Similarly, there are "getter" functions, but they do the opposite. They don't take any parameters, they return a value (but only one!), and from the outside code, they can be treated like properties using dot syntax. However, this class won't need any getter functions, so I just mention them in passing.
Overriding existing methods of the Sprite class:
So, we'll write similar setter functions for all of the private properties. But we still have one more thing to cover: the public override of the width and height properties. We can safely override these properties, and supply our own, because we won't be needing the original ones, ever. In case you didn't guess, the built-in classes pretty much always use getters and setters. So what we are really overriding is the public method set width(), like so:
public override function set width(value:Number):void {
_width = value;
draw();
}
Other than the override, this is exactly the same as all the other setters. And the override for height works exactly the same way. Also, each and every setter is going to call the draw() method, so that the button is updated each and every time a property is set. Here is the complete, finished class:
package {
import flash.display.*;
import flash.events.*;
import flash.text.*;
public class MenuButton extends Sprite {
private var _label:String;
private var _fontName:String;
private var _fontSize:int;
private var _fontColor:uint;
private var _embedFonts:Boolean;
private var _bgColor:uint;
private var _width:Number;
private var _height:Number;
public function MenuButton(label:String = "Click Here",
fontName:String = "Arial",
fontSize:int = 14,
fontColor:uint = 0x000000,
embedFonts:Boolean = false,
bgColor:uint = 0xFFCC00,
width:Number = 150,
height:Number = 30) {
_label = label;
_fontName = fontName;
_fontSize = fontSize;
_fontColor = fontColor;
_embedFonts = embedFonts;
_bgColor = bgColor;
_width = width;
_height = height;
buttonMode = true;
mouseChildren = false;
draw();
}
public function draw() {
//first remove any existing elements, if any:
while(this.numChildren > 0) {
this.removeChildAt(0);
}
//background shape:
var bg:Shape = new Shape();
bg.graphics.beginFill(_bgColor);
bg.graphics.drawRect(0, 0, _width, _height);
bg.graphics.endFill();
addChild(bg);
//text format:
var tFormat:TextFormat = new TextFormat();
tFormat.font = _fontName;
tFormat.size = _fontSize;
tFormat.color = _fontColor;
//text box:
var tField:TextField = new TextField();
tField.multiline = false;
tField.selectable = false;
tField.embedFonts = _embedFonts;
tField.defaultTextFormat = tFormat;
tField.autoSize = TextFieldAutoSize.CENTER;
tField.antiAliasType = AntiAliasType.ADVANCED;
tField.text = _label;
tField.x = _width / 2 - tField.width / 2;
tField.y = _height / 2 - tField.height / 2;
addChild(tField);
}
public function set label(value:String):void {
_label = value;
draw();
}
public function set fontName(value:String):void {
_fontName = value;
draw();
}
public function set fontSize(value:int):void {
_fontSize = value;
draw();
}
public function set fontColor(value:uint):void {
_fontColor = value;
draw();
}
public function set embedFonts(value:Boolean):void {
_embedFonts = value;
draw();
}
public function set backgroundColor(value:uint):void {
_bgColor = value;
draw();
}
public override function set width(value:Number):void {
_width = value;
draw();
}
public override function set height(value:Number):void {
_height = value;
draw();
}
}
}
It may seem like a lot of work that we had to do just to get a basic button going. But now when we test drive this, we can do awesomely cool stuff like this (try this in your fla file):
var btn:MenuButton = new MenuButton("Hey, this is cool!");
btn.width = 200;
btn.height = 50;
btn.fontName = "Comic Sans MS";
btn.fontSize = 18;
btn.backgroundColor = 0x0000FF; //blue
btn.fontColor = 0xFFFFFF; //white
addChild(btn);
But, what about rollovers?
You might say, yeah, but this is just a plain button, and I like rollover effects and stuff. Well, don't forget that we can affect all those properties from outside the class, even in response to events. As a demonstration, try adding this code to the above and check it out!
btn.addEventListener(MouseEvent.ROLL_OVER, over);
btn.addEventListener(MouseEvent.ROLL_OUT, out);
function over(event:MouseEvent):void {
MenuButton(event.currentTarget).backgroundColor = 0xFF0000; //change to red
}
function out(event:MouseEvent):void {
MenuButton(event.currentTarget).backgroundColor = 0x0000FF; //change back to blue again
}
You can change any of the properties that are built into the Sprite class (alpha, etc), but you can also change any of the properties we wrote our own setter functions for! So in the above, we are able to change the background color on roll over, and do so VERY easily, I might add! We could have just as easily changed the font or the label text, or any of the other properties. Nice, eh?
Later, I will cover the use of embedded fonts. For now, if you were to set embedFonts to true for a button instance, your text would disappear. This is because the font must be embedded in the swf file somehow. One way to accomplish this is to have a font symbol in the library of the fla file that's set for "export for actionscript," and I'll demonstrate how to do that.
The MenuButton class is now finished! On the next page, we will take on the creation of the DropMenu class.

