Advertisement

Develop an OOP Drop Down Menu Using ActionScript 3.0 Documents

by

In this, my first tutorial for Flashtuts+, I'll quickly explain how to create an object oriented drop down menu. We'll use ActionScript 3.0 and pull all the necessary data from an xml file.

The resulting menu can be used in any flash file without needing recoding.

Step 1 - Setting the Classpath

First, we'll setup a classpath. If you dont yet have one, it's a useful place for putting ActionScript files that you use in every project. This way you dont need to copy the ActionScript files to a folder of the fla document everytime that you create a new website or application.

Create a folder for ActionScripts files in c:. I'll create the folder c:/myActionScripts, but you can create a directory anywhere you want with any name you wish.

For flash CS3, go to Edit > Preferences. In the category menu of the preferences panel, select "ActionScript". On right side of the panel, click the button "ActionsScript 3.0 settings...".

This will open another panel (Actionscript 3.0 settings panel). Click the plus button ( + ) and type "c:/myActionScripts", or whatever your directory location is. Then just click OK in all the open panels, now we have a classpath.

For flash CS4, go to Edit > Preferences. In the category menu of the preferences panel, select "ActionScript". On right side of the panel, click the button "ActionsScript 3.0 settings...".

This will open a panel called "Actionscript 3.0 Advanced Settings". In the "source path" area, click the plus button ( + ) and type c:/myActionScripts, or whatever your directory location is. Then just click OK in all the open panels, now we have a classpath.

Step 2 - Downloading Classes and Creating Folders

Go to http://blog.greensock.com/tweenliteas3/ and download the TweenLite ActionScript 3.0 class, courtesy of Jack Doyle. This class is much better than the Tween class built in flash CS3 and flash CS4.

When the download is finished, just uncompress the contents of "gs" inside the .zip file to c:/myActionScripts/gs to make the TweenLite class available for use any time you want, without needing to copy the gs folder to an equivalent folder of your project.

Also, create the folder c:/myActionScripts/dropdownmenu. We'll put the ActionScript files from this tutorial here. We'll then be able to use our DropDownMenu Object whenever we want, without needing to copy the dropdownmenu folder to each project. This is the advantage of using a classpath.

Step 3 - Creating the XML Documents

I'll be using 5 xml files, one xml for the main drop down menu and others to populate data in the second drop down menu. Save all the xml files in c:/dropdownexample/xml.

Save the first xml document as "menu.xml" in the folder "xml". Fill it with data such as:

<?xml version="1.0" encoding="utf-8"?>
<menu>
	<item caption="Company contacts" value="xml/contactlist1.xml" />
	<item caption="Home contacts" value="xml/contactlist2.xml" />
	<item caption="Friends" value="xml/contactlist3.xml" />
	<item caption="School contacts" value="xml/contactlist4.xml" />
</menu>

Save a second xml document as "contactlist1.xml" in the xml folder, data as follows:

<?xml version="1.0" encoding="utf-8"?>
<menu>
	<item caption="Director area" value="director@company.com" />
	<item caption="Finance area" value="finance@company.com" />
	<item caption="Designing area" value="designing@company.com" />
	<item caption="Care area" value="care@company.com" />	
	<item caption="Print area" value="print@company.com" />
	<item caption="Internet area" value="internet@company.com" />
	<item caption="Knowlege area" value="knowlege@company.com" />
</menu>

Save a third xml document as "contactlist2.xml" in the xml folder, data as follows:

<?xml version="1.0" encoding="utf-8"?>
<menu>
	<item caption="Mom" value="mom@hotmail.com" />
	<item caption="Daddy" value="daddy@gmail.com" />
	<item caption="Brother" value="brother@live.com" />
	<item caption="Syster" value="syster@email.com" />	
</menu>

Save another xml document as "contactlist3.xml" in the xml folder, data as follows:

<?xml version="1.0" encoding="utf-8"?>
<menu>
	<item caption="Tim" value="tim@email.com" />
	<item caption="Sam" value="sam@hotmail.com" />
	<item caption="David" value="david@live.com" />
	<item caption="Julio" value="julio@gmail.com" />	
	<item caption="Lucas" value="lucas@uol.com.br" />
</menu>

Finally, create the last xml document as "contactlist4.xml", again in the xml folder with the following data:

<?xml version="1.0" encoding="utf-8"?>
<menu>
	<item caption="Math teacher" value="math@myschool.com" />
	<item caption="English teacher" value="english@myschool.com" />
	<item caption="Geography teacher" value="geography@myschool.com" />
	<item caption="History teacher" value="hisroty@myschool.com" />
	<item caption="Fisic teacher" value="fisic@myschool.com" />
</menu>

Step 4 - Coding the MenuItem Object

Now we'll start coding the MenuItem Object. This is required for our drop down menu and will build the items in the list.

Start creating the package, we'll save the file in c:/myActionScripts/dropdownmenu so the package is called "dropdownmenu", then we'll import the classes that we want to use in this object.

package dropdownmenu{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.events.*;
	import gs.TweenLite;

Now create the class MenuItem. As we're extending the Sprite class, we'll have all the methods and properties of that class, but we'll still add more.

Note that we'll save this ActionScript file as "MenuItem.as" in the dropdownmenu folder. The package therefore has the same name as the folder and the class has the same name as the .as file.

public class MenuItem extends Sprite{

Declare and set the variables here, so you can use them with any function in the class. We'll set as them private so only this class has access to them. If you declare the variables inside a function, only that function will have access to the variable. In our case, we want the entire class to have access to the variables.

private var _value:String;
private var txt:TextField=new TextField();
private var txtFormat:TextFormat=new TextFormat();

We'll declare the following function as public, so you can access it from anywhere. This function has the same name as the class It's the constructor function and will be called when we instantiate this object. It will be the first function to be executed and will be executed every time you create a new object of MenuItem. The constructor function is always public.

We'll pass the caption, value, width and height values through the constructor:

public function MenuItem(caption:String,value:String,_width:int=200,_height:int=20):void{

Now we'll set this class to "buttonMode", so it will behave like a button and the hand cursor will show every time you pass the mouse over it. We set the alpha to .8 (this will be the mouse out state) we've declared the _value variable before, outside the function, so here we make the value visible to any other function of the class and the _width we reduce by 10, so the menu item will be a little bit smaller.

this.buttonMode=true;
this.alpha=.8;
_value=value;
_width-=10

Draw a black rectangle for the background (this can be any color, just change the 0x000000 to whatever you want).

this.graphics.beginFill(0x000000);
this.graphics.drawRect(0,0,_width,_height);

Now we'll set the text format object. It's created outside the function and will be accessible to any function in this class. This will be our item caption (a dynamic textfield).

txtFormat.color=0xFFFFFF;
txtFormat.size=10;
txtFormat.font="Verdana";

Now we'll set the default textfield format to the text format object. We'll disable the mouse interactivity of the textfield, so the text wont be clickable (this works with any displayed object). We'll set the text of txt dynamic text field as the variable "caption" passed through the constructor parameter. Then it's a question of setting the position x and y of the textfield and its width and height. Then we add the text to the MenuItem Object´s display list.

txt.defaultTextFormat=txtFormat;			
txt.mouseEnabled=false;
txt.text=caption;
txt.x=5;
txt.y=2;
txt.width=_width-10;
txt.height=_height-4;
addChild(txt);

Now let's add the events. When we add this object to the stage, the addedToStage function will be called. When we pass the mouse over the object, the mouseOver function will be called. When we take the mouse out of this object the mouseOut function will be called. We also close the constructor function with a "}" at the end.

addEventListener(Event.ADDED_TO_STAGE,addedToStage);
addEventListener(MouseEvent.MOUSE_OVER,mouseOver);
addEventListener(MouseEvent.MOUSE_OUT,mouseOut);
}

Insert a new function here. When the mouse is over the object it will smoothly set the alpha to 1, so this object appears different to the others (mouse over state).

private function mouseOver(e:MouseEvent):void{ 
    new TweenLite(this,1,{alpha:1}); 
}

When we take the mouse out of the object, the alpha will smoothly be set back to .8 (the mouse out state).

private function mouseOut(e:MouseEvent):void{ 
    new TweenLite(this,1,{alpha:.8}); 
}

When this object is added to the stage, we set the object's position by getting its index in the displaylist and multiplying this by its height. We have a sequence of indexes (0, 1, 2, 3) and will position one object upon the other, not over one another.

private function addedToStage(e:Event):void{ 
    this.y=this.parent.getChildIndex(this)*this.height; 
}

The function "get" is a property, it works like a variable but it is read only. You cant set those values outside this class, but you can read them. They return the value of variables or textfields which are inaccessible outside the class.

public function get caption():String{ return txt.text; }
public function get value():String{ return _value; }

Now close the class and the package we opened at the start of the file. Save it as "MenuItem.as" at c:/myActionScripts/dropdownmenu, the file should be located at c:/myActionScripts/dropdownmenu/MenuItem.as

  }
}

Step 5 - Create the Custom Event

Now we'll create a custom event for this class. When we change the menu item we'll get the value through this event.

Start the package, import the Event and create the class extending the Event class. Now we have a new Event class which we'll call "DropDownMenuEvent".

package dropdownmenu{
import flash.events.Event;
public class DropDownMenuEvent extends Event{

Static variables, functions or constants can be accessed without needing to instantiate the object. You don't need to use the new statement, you can simply access it by using: DropDownMenuEvent.CHANGE

The const are constants. Once you have set a value to the const, its value can't be changed. You can use lowercase to name constants, but by default almost everbody uses uppercase for constants and we won't do any different.

public static const CHANGE:String="change";

Instantiate these String objects right here to ensure access from anywhere in this class.

private var _caption:String;
private var _value:String;

Now create the constructor function. The super(type) is to set this class as a superclass.

public function DropDownMenuEvent(type:String,caption:String,value:String):void{
	super(type);
	_caption=caption;
	_value=value;
}

Now make the getter functions of the class, to get the values as read only. I´ve explained before how the getter function works.

public function get caption():String{ return _caption; }
public function get value():String{ return _value; }

Close the package and the class using "}" and save as c:/myActionScripts/dropdownmenu/DropDownMenuEvent.as

}
}

Step 6 - Create the DropDownMenu Object

I saved this object for last because it´s more complex. Also, it won't work without the 2 classes created above.

This part is already well explained; we'll define the package, import all the classes that we'll use, create the class extending the Sprite, set the variables outside the functions to use with any other function of this class, then start the constructor function. Nothing new here.

I also shouldn't need to say again that the class, the constructor and the file name are the same.

The constructor function has all the parameters pre-defined, so we can choose whether or not to pass parameters. We could pass just the first parameter, or the first and the second, or the first, the second and the third. We could also pass the first parameter as null, so it will be the same value as the pre-defined value in the constructor.

package dropdownmenu{	
	import flash.display.Sprite;
	import flash.display.Shape;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.xml.*;
	import flash.events.*;
	import flash.display.Graphics;
	import flash.display.GradientType;
	import flash.geom.Matrix;
	import flash.display.SimpleButton;
	import gs.TweenLite;
	public class DropDownMenu extends Sprite{
		private var container:Sprite=new Sprite();
		private var req:URLRequest=new URLRequest();
		private var loader:URLLoader=new URLLoader();
		private var txt:TextField=new TextField();
		private var txtFormat:TextFormat=new TextFormat();
		private var button:SimpleButton=new SimpleButton();
		private var _value:String;
       public function DropDownMenu(url:String=null,_width:int=200,_height:int=30):void{

The container is a sprite, created above outside any function for the entire class have access. We'll put all the menu items inside the container; set the y property to the _height param. The container will be invisible to begin with(alpha=0), so it can't be clicked. Even if it were visible, the alpha is still 0, so it won't show yet.

container.y=_height;
container.alpha=0;
container.visible=false;
addChild(container);

Create a background and add to the object. The drawObject is a function that returns a Shape, we'll see this a little later.

var bg:Shape=drawObject(_width,_height);
addChild(bg);

Again, we set the textFormat object properties and set the defaultTextFormat property of the txt object as the textFormat object. We disable interactivity with the mouse, set a default text while the xml is not loaded yet and add the txt textField to this object display list. All of these actions have been seen before, so they don't need too much explanation.

txtFormat.bold=true;
txtFormat.size=15;
txtFormat.font="Verdana";
txt.defaultTextFormat=txtFormat;
txt.x=5;
txt.y=3;
txt.width=_width-10;
txt.height=_height-6;
txt.mouseEnabled=false;
txt.text="XML not loaded";
addChild(txt);

The button, created outside the function, can be acessed from anywhere in this class. We set a new Shape for the hitTestState, then add an event. When clicked it will call for function "open". We then add the button to this object´s display list.

button.hitTestState=drawObject(_width,_height);
button.addEventListener(MouseEvent.CLICK,open);
addChild(button);

Now add the event listeners. One for the loader when the loader has fully completed loading the xml document. This will call the function loaderComplete, or if the loader doesn't find the xml document it will call the function loaderIOError

This class when added to the stage will call the function addedToStage. When removed from stage it will call the function removedFromStage.

loader.addEventListener(Event.COMPLETE,loaderComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR,loaderIOError);
addEventListener(Event.ADDED_TO_STAGE,addedToStage);
addEventListener(Event.REMOVED_FROM_STAGE,removedFromStage);

Here we check if any value other than null was passed to url param of the constructor. If so, we'll set the url ( private var url:URLRequest=new URLRequest() )and call the function load. Close the constructor function with a " } "

if(url) load(url);
}

As the load function is public, it allows you to load xml anywhere outside or inside this class. First this function will remove all the menu items inside the container. If any exist, then it will start loading the xml document.

public function load(url:String):void{
  txt.text="Loading";
  _value=null;
  var total:int=container.numChildren;
  for(var i:int=0;i<total;i++){ container.removeChildAt(0); }
  req.url=url;
  loader.load(req);
}

This function will be called when the button is pressed. It will check if the alpha of the container is equal to zero. If so, it will set the visibility of the container to true, alpha to 0 and will smoothly set the alpha of the container to 1. If the alpha of the container is already more than 0, it will call for the close function.

private function open(e:MouseEvent):void{
	if(container.alpha==0){
		container.visible=true;
		container.alpha=0;
		new TweenLite(container,.5,{alpha:1});
	}
	else{ close(); }
}

The close function will be called when you click anywhere, because we'll add an event to the stage for when the mouse is down. This function will check if a container´s alpha is more than 0. If that's the case it will smoothly set the container´s alpha to 0. When finished it will call the function hideContainer() that sets the container´s visibility to false (invisible and unclickable).

We'll add an event to the stage, the MouseEvent.MOUSE_DOWN, so the function stageMouseDown.

private function close():void{ 
	if(container.alpha>0) new TweenLite(container,.5,{alpha:0,onComplete:hideContainer}); 
}
private function hideContainer():void{ container.visible=false; }
private function stageMouseDown(e:MouseEvent):void{ this.close(); }
private function stageMouseLeave(e:Event):void{ this.close(); }

loaderIOError will be called if the load function of the loader doesn't find the xml document and will set the caption to "XML not found".

The addedToStage will be called when this object is added to the stage display list. It will add an event for when you click anywhere on the stage to call the stageMouseDow function.

The removedFromStage will be called when this object is removed from the stage. It removes the event from stage so that clicks on stage will no longer call for the stageMouseDown function.

When the mouse leaves the stage, we'll also close the drop down menu.

private function loaderIOError(e:IOErrorEvent):void{ txt.text="XML not found"; }
private function addedToStage(e:Event):void{ 
	stage.addEventListener(MouseEvent.MOUSE_DOWN,stageMouseDown); 
   stage.addEventListener(Event.MOUSE_LEAVE,stageMouseLeave);
}
private function removedFromStage(e:Event):void{ 
	stage.removeEventListener(MouseEvent.MOUSE_DOWN,stageMouseDown); 
	stage.removeEventListener(Event.MOUSE_LEAVE,stageMouseLeave);
}

The function loaderComplete is called when the xml is fully loaded, it will work like this:

  • Set the default text of txt object to "Select" and the value to null, so we don't have a value and the default caption is just "Select"
  • Create a new XML instance
  • Check the total number of nodes that have an <item> tag in the xml document and insert into the "total" variable.
  • Loop the values of the xml document, getting their caption and value properties. The properties in xml are gotten by @property and the node value just .node without @.
  • Create a new MenuItem object with a menuItem instance, passing the caption and the value to the MenuItem constructor.
  • Add an event to the menuItem, when clicked it will call the function menuItemClick.
  • Add the menuItem just created to the container display list.
  • Set the position x of the container to the center of the DropDownMenu Object using some calculation.
private function loaderComplete(e:Event):void{
	txt.text="Select";
	_value=null;
	var xml:XML=XML(e.target.data);
	var total:int=xml.item.length();
	var _w:int=this.width;
	var _h:int=this.height;
	for(var i=0;i<total;i++){
		var caption:String=xml.item[i].@caption;
		var swf:String=xml.item[i].@value;
		var menuItem:MenuItem=new MenuItem(caption,swf,_w,_h);
		menuItem.addEventListener(MouseEvent.CLICK,menuItemClick);
		container.addChild(menuItem);
	}
	container.x=(this.width-container.width)*.5;
}

The function menuItemClick will be called when you click any menu item object, the currentTarget parameter will get the object that was clicked.
We compare the actual value of DropDownMenu Object to the value of the clicked menu item and compare the current text of the txt textfield to the caption of the menu item. If there is any difference in the value, the function will dispatch a new DropDownMenuEvent passing the new caption and the new value.

private function menuItemClick(e:MouseEvent):void{
	if(e.currentTarget.value!=_value && e.currentTarget.caption!=txt.text){
		dispatchEvent(new DropDownMenuEvent(DropDownMenuEvent.CHANGE,e.currentTarget.caption,e.currentTarget.value));
		_value=e.currentTarget.value;
		txt.text=e.currentTarget.caption;
	}
}

This is the function we have used to create the background and the hit state of the button. In the constructor function, this function returns a new Shape Object.

private function drawObject(_width:int,_height:int):Shape{
	var newShape=new Shape();
	var colours=[0xA6A6A6,0x515151];
	var alphas=[1,1];
	var ratios=[0,255];
	var radians:Number=90*(Math.PI/180);
	var matrix:Matrix=new Matrix();
	matrix.createGradientBox(_width,_height,radians);
	newShape.graphics.beginGradientFill(GradientType.LINEAR,colours,alphas,ratios,matrix);
	newShape.graphics.drawRoundRect(0,0,_width,_height,5,5);
	return newShape;
}

We create 2 read-only properties, one to get the caption and other to get the value of this object. These 2 properties will be visible outside the class, but can't be changed outside the class, only read.

Use 2x " }" to close the package and the class.

public function get caption():String{ return txt.text; }
public function get value():String{ return _value; }
}
}

Now save this file as c:/myActionScripts/dropdownmenu/DropDownMenu.as. We have 3 files in the c:/myActionScripts/dropdownmenu:

  1. DropDownMenu.as
  2. MenuItem.as
  3. DropDownMenuEvent.as

Now we have a dropdownmenu package with 3 classes.

Step 7 - The .fla and the Document Class

In Flash, go to File > New.

Select Flash File (ActionScript 3.0) and click OK.

Then go to Window > Properties. The properties panel will open

In the properties panel enter "Main" in the Class textbox (without the " surround) and save your .fla file in c:/dropdownexample/dropdown.fla (remember that we already have the c:/dropdownexample folder with the xml folder inside.

Step 8 - Coding the Main Document Class

Create a new ActionScript file document (File > New > ActionScript file).

We'll save the document in the same folder as the .fla document, with the name Main.as. This class belongs to the TopLevel package, so its package is just package...

We import the necessary classes. Notice that we import all the dropdownmenu package, so the three files created above (DropDownMenu, DropDownMenuEvent and MenuItem) will be imported to the Main class.

package{ 	import flash.display.Sprite; 	import dropdownmenu.*; 	import flash.events.*;	 	public class Main extends Sprite{

Attention here, we're creating 2 drop down menus; the first drop down menu will select a new xml document and make the second drop down menu load the value selected.

The first parameter, if not null, will automatically load the xml document when creating this object. The second parameter is the width of the drop down menu. We didn't pass the third parameter; the height of the drop down menu, but we set the default value to 30, which I think it is a good height.

The first drop down menu will automatically load the xml called menu.xml in the folder xml, the second will load an xml file only when we use the load function.

private var d1:DropDownMenu=new DropDownMenu("xml/menu.xml",150); private var d2:DropDownMenu=new DropDownMenu(null,150);

We create the constructor. As this class is the document class, the function will be called when we compile the .swf and every time we open the .swf file too. We add the two drop down menus we´ve created, set the x position of both, then set the same y value to them. Add event listeners to both drop down menus, when we change the value we'll call for the listeners.

public function Main():void{ 	addChild(d1); 	addChild(d2); 	d1.x=50; 	d2.x=210; 	d1.y=d2.y=40;			 	d1.addEventListener(DropDownMenuEvent.CHANGE,d1Change); 	d2.addEventListener(DropDownMenuEvent.CHANGE,d2Change); }

This is the listener of drop down menu 1. When the d1 value changes, it will load its value in d2 drop down menu.

private function d1Change(e:DropDownMenuEvent):void{ 	d2.load(e.value); }

This is just to test the second drop down menu, but you can get the drop down menu´s values by getting instance.value and get the drop down menu´s captions by getting instance.caption.

private function d2Change(e:DropDownMenuEvent):void{ 	trace(e.caption+" = "+e.value);
   //or trace(d2.caption+" = "+e.value); }

Close the package and the class with 2x " } "

}
}

Now we have the Main class to test our work, just save in c:/dropdowexample/Main.as and compile the dropdown.fla document. Test it in flash so the trace will work.

The captions and the values can, of course, be anything you like.

Conclusion

The classes are a little bit big, but they're correctly coded, don't have bugs and you can use them whenever you want by just importing the dropdownmenu package and creating DropDownMenu objects. Classes and the Document class are the biggest difference between ActionScript 2.0 and 3.0. Anybody that learns how to work with ActionScript files and learns how to use the classpath will find it far better than using the timeline to code.

Thats all for now!

Advertisement