XML Accordion Menu / Content - Question about best approach

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

AS3 version
AS2 version

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