AS3 Dropdown Menu with Reusable Classes

Creating the DropMenu Class
(Note: In the following discussion, I will sometimes refer to these parts using names without underscores in front of them. But the private properties in the class will always have an underscore. If the underscore is omitted in the following text, it's just because it makes for better prose).
So, in Flash, choose File / New... and choose Actionscript (AS) file. We'll begin with just the outline of a basic class, as before, and we will go ahead and create private variables for the three parts: _heading, _panel, and _mask. The heading is the button that serves as a masthead for the whole thing. The panel is going to be the stack of buttons that moves up and down behind the heading. The mask is going to be the window through which we see the panel moving up and down. The heading and the panel will be Sprite objects, and the mask can just be a Shape object:
package {
import flash.display.*;
import flash.events.*;
public class DropMenu extends Sprite {
private var _heading:Sprite;
private var _panel:Sprite;
private var _mask:Shape;
public function DropMenu(heading:Sprite = null) {
_heading = heading;
if (_heading != null) {
draw();
}
}
private function draw():void {
addChild(_heading);
}
}
}
Also (in the above), I know I'm going to employ a draw() method again, so I go ahead and create one, and call it from the constructor. Once again, I want to get something on the screen that I can see, so I can begin the process of testing the movie and checking the progress. So, I put enough things in place so that I can send the constructor an instance of the MenuButton class and it will display it. The constructor's sole parameter, "heading," is made optional by giving it a default value of "null." In the constructor, I set the value of the _heading private variable to the value that was passed in. Then I test it to see if it got a null value (using an if statement), which would mean that no argument was supplied to the constructor. But if there was an argument supplied, it calls the draw() method, which in turn adds the _heading to the display list.
Save the above to the same folder and name it DropMenu.as. Next, let's go back to the fla file and put in some code to test it. Highlight everything on frame 1 and delete it. Replace it with the following lines:
var btn:MenuButton = new MenuButton("My Dropdown Menu!");
var menu:DropMenu = new DropMenu(btn);
addChild(menu);
This code makes an instance of the MenuButton class with a custom label ("My Dropdown Menu!"). Then the next line creates a new instance of the DropMenu class, and passes to its constructor the instance of MenuButton that was just made on the previous line. Inside the class, the constructor runs, and since _heading is not null, the draw() method runs, and the passed-in heading Sprite is added to the display list of the menu. Finally, the menu is added to the (external) display list with addChild(menu). If not for this last line, nothing would display. As it is, so far what I see on the screen is visually no different than just adding the button to the display, but I do know that now I'm seeing my menu, so it's a start. The menu instance adds the btn object to its display list, and the fla file adds the menu object to its display list, and since the main timeline has already been added to the stage (which happens automatically), we get visual feedback. I have covered this before in my other articles that the main timeline of a fla file is already added to the stage for us automatically, behind the scenes. In the display hierarchy, any display object that's got an ultimate connection to the stage will be displayed (assuming that its x and y position doesn't place it out of view, of course).
To use the DropMenu class from the outside, the user of the class will make a new instance of it. They will either pass the heading to the constructor, or they will use the setter method we are going to make to set the heading. Next, in my conception of it anyway, they can start adding items to the menu using some kind of addItem() method, which will accept a Sprite as a parameter, and add it to the panel. That's why I knew I would need to employ a draw() method again, because I will have to tell the whole menu to redraw itself whenever the addItem() method runs and adds a new button to the panel. Not only will the item need to be added to the panel with addChild, but the mask will also have to be redrawn to a larger size whenever that happens. There is also the issue of y positioning, too, so I will need a variable that keeps track of the next available y position. I'll call it _currentY, so let's go ahead and add that to the variables list:
private var _currentY:Number;
Now we need to work on the constructor some more. Having declared the _panel and _mask private variables, I need to instantiate them, and also initialize the _currentY variable with a value of 0. So add these lines to the constructor, but insert them at the beginning of it:
_currentY = 0;
_panel = new Sprite();
_mask = new Shape();
Next, let's turn our attention the draw() method. First, let's borrow the while loop from the MenuButton class, which can be used verbatim. This will have the same effect it does in that other class; it will clear the display list. Again, insert these lines at the beginning of the function (ahead of the addChild(_heading) line):
while(this.numChildren > 0) {
this.removeChildAt(0);
}
Next, besides adding the heading to the display list, the draw() method also needs to position the panel and add it to the display list. Then it needs to draw the mask, and add the mask to the display list. But let's refer back to the drawing again:
Writing the draw() method
This drawing, like I stated before, will help us visualize how to position things. For example, where do we position the panel for its closed state? The x location we'll make equal to the heading's x, of course. The panel's y, though, can also be in relation the heading's y, but it's going to be the heading's y plus the heading's height, minus the panel's height. Translating that to code, add these lines to the draw() method (now place these lines just after the other lines that are there:
_panel.x = _heading.x;
_panel.y = _heading.y + _heading.height - _panel.height;
addChild(_panel);
The next thing to draw is the mask. We make it the same width and height as the panel. But for positioning, it needs to be positioned just beneath the heading. Again, the drawing comes in handy, it let's you see a visualization of where to place the mask (the heading's y + the heading's height). By the way, whenever I make a mask, I find myself just using red for a fill color. You have to supply some kind of color, and I figure red is as good as any, and once it is made into a mask, it is never seen anyway. So, translating the above to code, add these lines to the draw() method (again, after everything else that is there already):
_mask.graphics.clear();
_mask.graphics.beginFill(0xFF0000);
_mask.graphics.drawRect(0, 0, _panel.width, _panel.height);
_mask.graphics.endFill();
_mask.x = _heading.x;
_mask.y = _heading.y + _heading.height;
addChild(_mask);
_panel.mask = _mask;
Another brief aside, this time about using the drawing API
When you want to draw something using the drawing API (which is accessed via the graphics property of a display object), you have to keep in mind that each display object has its own coordinate space. Therefore, in the above, when we draw the mask, we want to use 0 and 0 for the x and y values that we send to the drawRect() method. We are telling the mask object (which is an instance of Shape) to draw a rectangle within itself, in other words, using its own coordinate space. After that, we can position the drawn shape by setting the x and y of the mask (Shape instance) itself. What we don't want to do is to send our desired x and y for positioning straight to the drawRect method. So draw first (usually starting at 0, 0), then position. The drawing happens within the shape, and the positioning happens from outside the shape, in other words.
Finally, since the draw() method could possibly be called from the constructor, and a heading may have been passed in as an argument, the draw method is going to have to add event listeners for ROLL_OVER and ROLL_OUT to the heading, so add these lines (again, after the lines that are there already):
_heading.addEventListener(MouseEvent.ROLL_OVER, over);
this.addEventListener(MouseEvent.ROLL_OUT, out);
Notice that the event listener for rollover is added to the heading. But the event listener for rollout is added to this. What is this? The keyword this is a way of referring to the current object. When objects are made from this class, the word this will be the way of referring to each instance itself. Let me translate it to English for you. That line of code is saying, "when the mouse pointer rolls off of me (meaning this instance, or in other words, the whole menu), run the out method." This will have the following effect: When the mouse rolls over the heading, the panel will open. When the mouse rolls off of the whole thing (heading, panel, the whole works! --signified by this!), the panel will close.
One more final touch, and then we're done with the draw() method. It would seem wise to enclose the whole thing in an if statement, and make it conditional on whether or not the _heading variable is null. We know that we probably aren't going to misuse our new menu. We will probably always remember to make a heading for it before we start adding items to the panel. In fact, as it is, the draw method won't work unless the _heading variable is set first, so just to prevent the creation of a "headless" menu by ourselves or someone else, we will write it like this:
if(_heading != null) {
//
//all of the draw() code we have written so far is enclosed here
//
} else {
throw new Error("Cannot draw DropDown menu. Please set heading first");
}
I know we already tested to see if the _heading was null in the constructor. But we need to do it here, too, because we are going to be calling this draw() method from other places besides the constructor. If the _heading is null, the else clause above will send a message to the output window that a heading needs to be set. This is just a reminder to another programmer (our ourselves at a later time) who might use our class. By the way, the Error class is a top level class, meaning that it doesn't need to be imported in order to use it.
Writing the "over" and "out" mouse handlers, and using TweenLite
Now we need to write the over and out functions that we just added event listeners for. Let's start with the over function, wherein the panel needs to open. But first, I am going to suggest that if you haven't already tried TweenLite (and related classes), that you head on over to this web page: http://www.greensock.com/tweenlite/ and click the "Download AS3" button you will see there. The greensock classes, which include TweenLite, are an excellent third party tweening library written by Mr. Jack Doyle. We will use the TweenLite class to make the menu open and close. When you download the classes, they will all be contained within a single com folder. All you need to do is make sure that this com folder is copied to our working folder (don't modify or move around the folders inside). If in doubt, just get the ZIP file at the end of this article, which will include it. Then, in our class, we just need to add the following import statement to use the TweenLite class (add this line to the list of import statements):
import com.greensock.*;
Having done all that, let's create the over function (add this to the list of class functions, right after the draw function):
private function over(event:MouseEvent):void {
TweenLite.to(_panel, 0.5, {y:_mask.y});
}
The TweenLite class has a public static method called to(). Remember, public static methods belong to the class itself. This means that we don't need to make an instance of TweenLite in order to use the to() method. We just need to put the name of the class, a dot, and then to(), in order to use the method. The to() method of the TweenLite class requires three arguments to its parameter list. The first argument is the variable name of the object we want to tween (_panel). The second argument is the time we would like the tween to take, in seconds. In the above, I have specified half a second (0.5). The third argument needs to be supplied an object, which is why it's wrapped in curly braces. The curly braces are part of the actionscript language itself, and are just a shorthand way of specifying an object, the same way brackets are a shorthand way of enclosing an array. In any case, it's inside this object that we can specify whatever property of the object we want to tween. In our case, we want to tween the y property. So we put y, then a colon, then the value of the y location we want to tween to. Now here's where the magic happens. We can tween to a number, like 250, or whatever. But we can also supply an expression, a value from somewhere else in our program. Consulting the drawing above, where we want to tween to is, once again, _heading.y + _heading.height. But since the _mask object has already been placed at that location, it is easier to just say _mask.y.
Next, let's write the out function:
private function out(event:MouseEvent):void {
TweenLite.to(_panel, 0.5, {y:_mask.y - _panel.height});
}
You can see that the place to tween to is again in relation to the mask location. It should be pretty clear by now what is going on here. We tween the panel back to where it started from when the mouse rolls off of our menu object.
Here is the class so far (yours should now look like this):
package {
import flash.display.*;
import flash.events.*;
import com.greensock.*;
public class DropMenu extends Sprite {
private var _heading:Sprite;
private var _panel:Sprite;
private var _mask:Shape;
private var _currentY:Number;
public function DropMenu(heading:Sprite = null) {
_currentY = 0;
_panel = new Sprite();
_mask = new Shape();
_heading = heading;
if (_heading != null) {
draw();
}
}
private function draw():void {
if (_heading != null) {
while (this.numChildren > 0) {
this.removeChildAt(0);
}
addChild(_heading);
_panel.x = _heading.x;
_panel.y = _heading.y + _heading.height - _panel.height;
addChild(_panel);
_mask.graphics.clear();
_mask.graphics.beginFill(0xFF0000);
_mask.graphics.drawRect(0, 0, _panel.width, _panel.height);
_mask.graphics.endFill();
_mask.x = _heading.x;
_mask.y = _heading.y + _heading.height;
addChild(_mask);
_panel.mask = _mask;
_heading.addEventListener(MouseEvent.ROLL_OVER, over);
this.addEventListener(MouseEvent.ROLL_OUT, out);
} else {
throw new Error("Cannot draw DropDown menu. Please set heading first");
}
}
private function over(event:MouseEvent):void {
TweenLite.to(_panel, 0.5, {y:_mask.y});
}
private function out(event:MouseEvent):void {
TweenLite.to(_panel, 0.5, {y:_mask.y - _panel.height});
}
}
}
There are only two more functions to write in this class: the setter method for the heading, and the addItem() method, and then we will be able to test the DropMenu class and see it working. See you on the next page, where we will do all that and more!

