Hi,
I’ve been messing around with this AS3 version of an AS2 menu I made. The AS2 menu uses AsBroadcaster to broadcast messages to each menu item, telling it to updates its position based off what ever item is active, and expanded. Changing this AS2 approach to use eventDispatcher etc in AS3 works fine. The AS3 version however, does not quite perform the same, tending to be “too reactive”. The problem is that with a vertical accordion menu when you move the mouse downwards, the menu items move too quickly to trigger each menu items rollOver event, and this causes the menu to look a little erratic, with odd menu items expanding, when they should simply expand one after the other. To get around it, I’ve found I can either slow down the tween, or use a combination of a mouseMove event with a basic “active area if clause” to trigger a variable. This does work, but I’m puzzled as to why AS2 would not need this approach. I tend to think that AS3 is just that much better performing and so responds to the movements of the menu items that much quicker. Or, its a overall architecture problem, and the way I’ve set up this menu is flawed.
Have a look at the code and see what you think. Is the way I have done it, a good way to approach this kind of menu system? This is the first thing I tried to make in AS3, so I’m interested in seeing whats “correct”.
Here are the source files in zip format. (Classes, XML, Images, CS3 Flash file). AS2 version included if you want to compare. (I’ve been meaning to put the AS2 version up in the source experiments).
Here is how it works - http://www.noponies.com/dev/accordion/ Theres no preloader yet, so give it a bit to load.
here is the class file for a menu item;
package {
import flash.display.*;
import flash.display.Stage;
import flash.events.*;
import caurina.transitions.Tweener;//using Tweener to tween the y property
import flash.text.*;
public class menu_as extends Sprite {
public var startY = int;
public static var SHUFFLEPOS:String = "shufflepos"; //set up for custom event dispatch
public static var RESETPOS:String = "resetpos"; //set up for custom event dispatch
public var menuBar:Sprite;
public var contentHolder:Sprite;
public var menuTxt:TextField;
public var menuFormat:TextFormat;
public var barHeight:int;
public var masker:Shape;
//public var pictureValue:int = undefined;//used if you want to link menu to an array index etc
public var activated:Boolean=false;//var to track if the menu is expanded.
// below vars passed in via arguments to constructor
public var loadedContent;//content of loader - varies
public var titleText:String;
public var parentXMLs:XML;//temp var for passing each menu its xml node. not final!
public function menu_as(loadedContent, titleText, parentXML) {
parentXMLs = parentXML;
//generic setup
barHeight = 14;//height of menu bar
buttonMode = true;
useHandCursor = true;
//add content
contentHolder = new Sprite();
contentHolder.y = -loadedContent.height;//set a negative height
contentHolder.addChild(loadedContent);
addChild(contentHolder);
//mask
masker = new Shape();
masker.graphics.beginFill(0xFFFFFF);
masker.graphics.drawRect(0, barHeight, loadedContent.width, loadedContent.height);
masker.graphics.endFill();
addChild(masker);
loadedContent.mask = masker; //mask the loaded content
//visible bar sprite
menuBar = new Sprite();
menuBar.graphics.beginFill(0x000000);
menuBar.graphics.drawRect(0, 0, loadedContent.width, barHeight);//height, width of menu bar
menuBar.graphics.endFill();
addChild(menuBar); //add in the black menu bar
//text field formatting
menuFormat = new TextFormat();
menuFormat.font = new standard07_65().fontName;//class name of font in parent library
menuFormat.size = 8;
menuFormat.color = 0xFFFFFF;
//text field constructing
menuTxt = new TextField();
menuTxt.type = TextFieldType.DYNAMIC;
menuTxt.autoSize = TextFieldAutoSize.LEFT;
menuTxt.embedFonts = true;
//menuTxt.antiAliasType = AntiAliasType.ADVANCED;
//menuTxt.gridFitType = "pixel";
//menuTxt.sharpness = 400;
menuTxt.x = 8;
menuTxt.y = 0;
menuTxt.mouseEnabled = false;
menuTxt.htmlText = titleText;
menuTxt.setTextFormat(menuFormat);
addChild(menuTxt);//add the menus title text
//event listeners
addEventListener(MouseEvent.ROLL_OUT, rollOutHandler); //use rollovers in order to treat each instance as a whole and so we only fire one event.
addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);//comment out this listener to make the menus stay open even if rolled out.
addEventListener(MouseEvent.ROLL_OVER, mouseOverHandler);
addEventListener(MouseEvent.CLICK, mouseClickHandler);
addEventListener(Event.ADDED, addedHandler);//use stage as listener for custom events
}
//added to stage handler
private function addedHandler(event:Event):void {
stage.addEventListener(menu_as.SHUFFLEPOS, shuffleHandler);
stage.addEventListener(menu_as.RESETPOS, resetHandler);
startY = event.target.y; //find out our ypos
}
/*mouse move handler, used to trigger a variable that enables smoother dowards movement through the menus
basically this creates a 'active zone', which triggers a switch inf the activated variable. If we are inside this zone, we keep it true. This
helps with downwards mouse movement across the menus.*/
private function mouseMoveHandler(event:MouseEvent):void {
if (stage.mouseY<=this.y+2 || stage.mouseY>=this.y+(this.menuBar.height+this.masker.height)-2 || stage.mouseX<=this.x+20 || stage.mouseX>=this.x+this.width-20) {
this.activated = false
}
}
//rollout
private function rollOutHandler(event:MouseEvent):void {
if (!this.activated) {
dispatchEvent(new Event(menu_as.RESETPOS,true));
Tweener.addTween(this.contentHolder,{y:-contentHolder.height, time:1.5});
this.activated = false;
}
}
//rollover
private function mouseOverHandler(event:MouseEvent):void {
dispatchEvent(new Event(menu_as.RESETPOS,true));
Tweener.addTween(this.contentHolder,{y:barHeight, time:1.5});
dispatchEvent(new Event(menu_as.SHUFFLEPOS,true));
this.activated = true;
}
//click
private function mouseClickHandler(event:MouseEvent):void {
trace("You clicked "+this.parentXMLs.about.text());//the about text for each element. Just a test.
dispatchEvent(new Event(menu_as.RESETPOS,true));
Tweener.addTween(this.contentHolder,{y:-this.contentHolder.height, time:1.5});
this.activated = false
}
//reset position of clips listener handler
private function resetHandler(event:Event):void {
Tweener.addTween(this,{y:startY, time:1.5 });
}
//shuffle the clips positions, dependent on their x pos and y pos in relation to the currently active menu
//using xpos gives us column support
private function shuffleHandler(event:Event):void {
if (this!==event.target) {
//slide the bars back up
Tweener.addTween(this.contentHolder,{y:-this.contentHolder.height, time:1.5});
}
if ((this.y>event.target.y) && (this.x == event.target.x)) {
//slide the bars back up
Tweener.addTween(this,{y:startY+event.target.contentHolder.height, time:1.5});
}
}
}
}
And, how it is used in Flash;
//load in xml via call to external xml parsing class
var menuXML:XML = new XML();
var myloadXml:loadXml = new loadXml("menu.xml", checkLoad);
//call back function for xml loading
function checkLoad(evt:Event):void {
menuXML = XML(evt.target.data);
loadThumbs();
}
//vars for setting up the loading of each thumbnail
var loader:Loader;
var p:int = 0;
var menuy:int = 0
function loadThumbs():void {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT, initListener);
loader.load(new URLRequest(String(menuXML.pic.image.text()[p])));
function initListener(e:Event):void {
//load the menu content - (loader content, title text, xml node data)
var newMenu:menu_as = new menu_as(loader.content, menuXML.pic.names.text()[p], menuXML.pic[p]);
//position the menus
newMenu.y = menuy+=20 //keep in mind the height of the menu bar, which is 14, so we have a 6px space
newMenu.x =100
//newMenu.pictureValue = p
addChild(newMenu)
p++;
if (p<menuXML.pic.length ()) {
loadThumbs();//create loop
}
if (p==menuXML.pic.length ()) {
//do when done
}
}
}