Creating a (Simple) Website with Flash and Actionscript 3

Part III. Color and Content

devon o wolfgang


A BIT OF BACKTRACKING
In the third (and semi final) part of this project, before jumping into some new material, let's backtrack a little bit and review our Menu/MenuItem classes in Menu.as. If you've been following along, those two classes (both in a single .as file) currently look like this:

package classes.iface {
	
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.media.Sound;
	import flash.media.SoundChannel;
    import flash.net.URLRequest;
	
	public class Menu extends Sprite {
		
		private var _btns:Array;
		private var _btnLabels:Array;
		private var _btnColors:Array;
		private var _btnSpacing:Number;
		private var _buttonClick:Sound;
		private var _sc:SoundChannel;
		
		public function Menu() {
			_btns = new Array();
			_btnSpacing = 120;
			_btnLabels = new Array ("Home", "Product", "Contact");
			_btnColors = new Array(0x669900, 0xCC0066, 0x6699FF);
			_buttonClick = new Sound();
			
			init();
		}
		
		private function init():void {
			var numButtons:int = _btnLabels.length;
			_buttonClick.load(new URLRequest("sounds/click.mp3"));
			for (var i = 0; i < numButtons; i++) {
				var b:MenuButton = new MenuButton(_btnColors[i], _btnLabels[i], i);
				b.x = i * _btnSpacing;	
				addChild(b);
				_btns.push(b);
				b.addEventListener(MouseEvent.CLICK, onClick);
				b.addEventListener(MouseEvent.MOUSE_OVER, onRollOver);
			}
		}
		private function onClick(e:MouseEvent):void {
			dispatchEvent(e);
		}
		
		private function onRollOver(e:MouseEvent):void {
			_sc = _buttonClick.play();
		}
		
		public function reposition(xPos:Number, yPos:Number):void {
			this.x = Math.round(xPos);
			this.y = Math.round(yPos);
		}
	}
}



// imports for internal MenuButton class

import flash.display.Sprite;
import flash.display.SimpleButton;
import flash.display.GradientType;
import flash.display.SpreadMethod;
import flash.geom.Matrix;
import flash.filters.DropShadowFilter;
import flash.filters.BevelFilter;
import flash.filters.GlowFilter;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.text.AntiAliasType;

internal class MenuButton extends SimpleButton {
	
	private var _upShadow:DropShadowFilter;
	private var _downShadow:DropShadowFilter;
	private var _overGlow:GlowFilter;
	private var _bevel:BevelFilter;
	private var _color:uint;
	private var _idNum:int;
	private var _btnText:String;
	private var _newColor:uint;
	
	public function MenuButton(nc:uint, btnText:String, id:int) {
		_newColor = nc;
		_btnText = btnText;
		_idNum = id;
		_color = 0x669900;
		_bevel = new BevelFilter(4, 90, 0xFFFFFF, .8, 0x000000, .1, 0, 3, 1, 3);
		_upShadow = new DropShadowFilter(3, 90, 0x000000, .6, 4, 4, 1, 3);
		_downShadow = new DropShadowFilter(1, 90, 0x000000, .8, 2, 2, 1, 3);
		_overGlow = new GlowFilter(0xFFFF66, .5, 10, 10, 1, 3, true);
		_fmt = new TextFormat("Arial", 11, 0x000000, null, null, null, null, null, TextFormatAlign.CENTER);
			
		drawButton();
	}
	
	public function drawButton():void { 
		this.upState = drawUp();
		this.overState = drawOver();
		this.downState = drawDown();
		this.hitTestState = drawUp();
	}
		
	private function drawUp():Sprite{
		var cols:Array = [_color, 0x222222];
		var m:Matrix = new Matrix();
		m.createGradientBox(100, 25, 90*Math.PI/180);
			
		var s:Sprite = new Sprite();
		s.graphics.beginGradientFill(GradientType.LINEAR, cols, [1, 1], [100, 255], m, SpreadMethod.PAD);
		s.graphics.drawRoundRect(0, 0, 100, 20, 20, 20);
		s.graphics.endFill();
		var tf:TextField = buttonText();
		s.addChild(tf);
		s.filters = [_bevel, _upShadow];
			
		return s;
	}
	
	private function drawDown():Sprite {
		var cols:Array = [_color, 0x222222];
		var m:Matrix = new Matrix();
		m.createGradientBox(100, 25, 90*Math.PI/180);
	
		var s:Sprite = new Sprite();
		s.graphics.beginGradientFill(GradientType.LINEAR, cols, [1, 1], [100, 255], m, SpreadMethod.PAD);
		s.graphics.drawRoundRect(0, 2, 100, 20, 20, 20);
		s.graphics.endFill();
		var tf:TextField = buttonText();
		tf.y = 2;
		s.addChild(tf);
		s.filters = [_bevel, _downShadow];
			
		return s;
	}
		
	private function drawOver():Sprite {
		var cols:Array = [_color , 0x222222];
		var m:Matrix = new Matrix();
		m.createGradientBox(100, 25, 90*Math.PI/180);
			
		var s:Sprite = new Sprite();
		s.graphics.beginGradientFill(GradientType.LINEAR, cols, [1, 1], [100, 255], m, SpreadMethod.PAD);
		s.graphics.drawRoundRect(0, 0, 100, 20, 20, 20);
		s.graphics.endFill();
		var tf:TextField = buttonText();
		s.addChild(tf);
		s.filters = [_overGlow, _bevel, _upShadow];
			
		return s;
	}
	
	private function buttonText():TextField {
		var fmt = new TextFormat("Tahoma", 10, 0x000000, null, null, null, null, null, TextFormatAlign.CENTER);
		var tf:TextField = new TextField();
		tf.width = 100;
		tf.height = 20;
		tf.antiAliasType = AntiAliasType.ADVANCED;
		tf.selectable = false;
		tf.defaultTextFormat = fmt;
		tf.text = _btnText;
		return tf;
	}
		
	public function get id():int {
		return _idNum;
	}
}

Obviously, this worked just fine, but, personally speaking, I'm less than satisfied with that big ugly for loop inside our init() method. Let's take a look at a new Actionscript 3 array method that might help us write some slightly cleaner code. There are actually a few new additions to the array method list (see here), but the one we'll be looking at here is the forEach() method.

The Array.forEach() method will run a function on each element of the array that calls the method. The function in question is passed to the forEach() method and, in return, it is automatically passed three arguments: element (each element of the array), index (the index of that particular element), and array (a reference to the array calling the method). So, let's begin by creating a new private method named "addButton" that will accept those three arguments. For the body of the method, we can go back to that for loop in our init() method and simply cut it and paste it. Instead of referring to our _btnLabels array however, we'll use our passed "element" argument. Our addButton() method will look like this then:

private function addButton(element:String, i:int, arr:Array):void {
	var b:MenuButton = new MenuButton(_btnColors[i], element, i);
	b.x = i * _btnSpacing;	
	addChild(b);
	_btns.push(b);
	b.addEventListener(MouseEvent.CLICK, onClick);
	b.addEventListener(MouseEvent.MOUSE_OVER, onRollOver);
}

Now, we can replace that unsightly loop inside our init() method with this quick and easy line:

_btnLabels.forEach(addButton);

Now, how much more neat and concise is that?

COLORS
If you've been following this series of tutorials from the start, I'm sure you've noticed that in every display item we've created, we've included a private "_color" property. And if you've really been paying attention, you'll also notice that as each MenuButton instance was created it was passed a "_newColor" property which we pulled out of an array alongside the array of button labels. Let's finally see why all the extra work. Check out our final product again and try clicking on the buttons. Notice how each button changes the color of the entire display. That is exactly what we'll be doing now. What we're going to need first is a public method which gets that _newColor property from each button instance. After that, we'll need to add a setter method to each class that we wish to change colors. Let's begin with retrieving that _newColor property. Inside the MenuButton class (which is in the Menu.as file), let's add a new getter method. We already have a getter to retrieve the _id property, so to make our lives easier, we can simply copy and paste that method and make a couple minor changes, like so:

public function get color():uint {
	return _newColor;
}

While we have our MenuButton class open right there in front of us, let's go ahead and add a setter method to set the color of the button. The idea here is that we will set the _color property a specified uint value then we will redraw the button by calling the drawButton() method. Very straight forward. Our setter, then, will look like this:

public function set color(c:uint):void {
	_color = c;
	drawButton();
}

Well, that seems fine, but when we think about the structure of our site, we really don't want to call methods of our button instances from our WebSite class - we'd prefer to deal directly with our menu instance. Let's, then, add a similar setter to our Menu class. Basically, this will just call the setter method of each of our button instances for us. Since our addButton() method (which we just added earlier) pushes each button instance into an array named "_btns", this will be very easy. We will just use a quick for..in loop to loop through our _btns array and call the color setter method of each individual button. With that in mind, our Menu class' color setter will look like this:

public function set color(c:uint):void {
	for (var btn in _btns) _btns[btn].color = c;
}

Make sure you've saved the changes to your class files and test your movie. If all goes well, you'll now see your menu buttons change colors when you click on them. Very nice. Now that you see how it's done, doing the same for the background and main display instances should be a piece of cake. We'll really just do the same thing. Inside the Background.as add a public setter method that accepts a uint argument. This method will set the _color property then redraw the background by calling the drawBackground() method. Something like this:

public function set color(c:uint):void {
	_color = c;
	drawBackground();
}

Likewise, in our MainDisplay class we'll add a color setting method that looks like this:

public function set color(c:uint):void {
	_color = c;
	drawDisplay();
}

And again, back in our WebSite, class we'll make sure our onMenuClick() method calls all of our color setting method.

private function onMenuClick(e:MouseEvent):void {
	_menu.color  = e.target.color;
	_bg.color = e.target.color;
	_mainDisplay.color = e.target.color;
}

Test the movie once again, and voilá, everything now changes color. Bitchin'.

CONTENT
Finally, we get to the least important part of any website: the content (that's a joke, folks - hold off on the nasty emails). The "content" we'll be dealing with here (actually just some dummy text) will be loaded in from a .xml file. So, before we dig into some more actionscript, let's just take a look at the .xml file we'll be using:

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<content>

	<section id = "home">
	Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Fusce consequat. 
	In tempus. Praesent diam odio, facilisis vel, porttitor id, vulputate eu, ante. 
	Praesent dignissim metus sit amet dolor. Aliquam ullamcorper, felis vel tincidunt 
	vulputate, ante lorem malesuada leo, non dapibus turpis ipsum eu nunc. Ut euismod 
	tellus dignissim magna. Pellentesque nec magna sit amet lorem semper mattis. Proin 
	sapien turpis, commodo sed, congue at, euismod id, velit. Morbi vestibulum. Etiam 
	neque. Donec vel sem. Aliquam ultricies pharetra sapien. Proin molestie sem sed risus.
	</section>

	<section id = "product">
	Phasellus tortor. Fusce a orci. Fusce lacus. Nunc bibendum, lectus in consequat porta, 
	elit metus mollis justo, imperdiet vestibulum lorem lacus eu dolor. Phasellus odio. 
	Praesent vestibulum dictum pede. Cras tortor. Cras at magna. Aliquam malesuada rhoncus 
	leo. Fusce convallis neque nec diam. Morbi eros. Integer et velit ac orci vehicula lacinia. 
	Cras posuere turpis non orci. Sed leo ligula, rhoncus vitae, tincidunt aliquam, aliquam vitae, 
	justo. In rhoncus nisl ac nunc. Maecenas sapien mi, viverra nec, sagittis at, laoreet vel, 
	risus. Sed id nulla nec purus mollis tempor. Proin volutpat, diam sit amet fringilla rhoncus, 
	lacus dolor eleifend erat, id imperdiet quam augue ac elit. Etiam sed ante. Donec fermentum, 
	leo at vestibulum lobortis, nulla metus faucibus augue, at tempor dui magna a ipsum.
	</section>

	<section id = "contact">
	Curabitur sagittis. Vivamus erat. Donec semper urna in mi. Ut cursus cursus nulla. Vestibulum 
	aliquet posuere augue. Vivamus venenatis accumsan eros. In blandit, eros eget condimentum porta, 
	lacus ligula condimentum dolor, condimentum dignissim nisi tellus a felis. Maecenas pretium mollis 
	ipsum. Cras ut sem. Aliquam sit amet velit aliquam justo dapibus vehicula. Cum sociis natoque 
	penatibus et magnis dis parturient montes, nascetur ridiculus mus. In egestas adipiscing lectus. 
	Mauris sed elit et augue dictum porttitor. Aliquam varius eleifend lacus. Ut condimentum. Etiam et 
	nulla nec dolor fringilla placerat. Nulla facilisi.
	</section>

</content>

This is about as basic an example of xml as you're likely to find, but if you're new to the format, you might want to check out the w3schools basic xml tutorials just to get a feel for the subject. Save the above in a .xml file named "content.xml" inside the content directory we created in Part I of this tutorial.

THE XML OBJECT
The XML object in Actionscript 3 has been retooled and specifically designed to be used with E4X (Note: the legacy AS2 XML object is still available in AS3, but has been renamed "XMLDocument" to avoid name conflicts. We won't be troubling ourselves with it here though and it's recommended the new XML object be used). Before we get ahead of ourselves, though, let's see how we can get our xml into our Flash file. As in AS2, the xml must be loaded. Now, though, that requires making use of both the URLLoader class (an object which will actually load the xml file and hold its contents in a property named "data") and the URLRequest class which actually makes the HTML request to load external items (we got a quick look at the URLRequest in Part 2 when we used it to load a .mp3 file into a Sound object). Both these classes are contained in the flash.net package, so back in our WebSite.as file we can add the appropriate import statements to get access to these classes.

import flash.net.URLLoader;
import flash.net.URLRequest;

The XML class is a top level class in Actionscript 3, so requires no import statement to gain access. We will also be making use of another top level class, XMLList. The XMLList object is basically just as its name implies - it is a list of XML objects (or "pieces", such as nodes, of an XML object). The XML items inside an XMLList can be accessed in the same way you would access elements of an Array, and that is essentially how you can think of an XMLList - an Array of XML Objects. Let's add a few new private properties to our WebSite class, then:

private var _contentXML:XML;
private var _contentList:XMLList;

And now we can work on actually loading our xml formatted content. Still in the WebSite.as file, let's create a new method named loadContent(). In this method we will instantiate an URLLoader instance, make the HTML request to load our xml file and add an event listener to our URLLoader to listen for the Event.COMPLETE event (this event will fire when the entire contents of our .xml file is loaded into the data property of the URLLoader object). Our new method then will look like this:

private function loadContent():void {
	var loader = new URLLoader();
	loader.addEventListener(Event.COMPLETE, onContentLoaded);
	loader.load(new URLRequest("content/content.xml"));
}

We can see right away that we will also need a method named "onContentLoaded()", as specified in the addEventLisetner() call. Let's go ahead and make that now. For now, the only thing we will do in our onContentLoaded() method is trace our URLLoader's data property to be sure the .xml file is being loaded correctly. Thanks to Flash 9's event system, that URLLoader is passed to our event handler as the target property of the Event object (as we've seen when working with our Menu class). So, to trace the URLLoader's data property we can simply say this:

private function onContentLoaded(e:Event):void {
	trace (e.target.data);
}

Once you've saved your WebSite.as file, go ahead and test your movie. There in your output panel is the xml we just created. Perfect. Now let's see how we can manipulate that xml with our XML class and a little E4X. Rather than tracing our URLLoader data property, let's add it to our XML object, _contentXML, like so

_contentXML = new XML(e.target.data);

Now, using a little E4X, we can separate our xml nodes into our XMLList. E4X allows us to easily target xml nodes by name and dot syntax, so to do what we want in this situation all we need to say is:

private function onContentLoaded(e:Event):void {
	_contentXML = new XML(e.target.data);
	_contentList = new XMLList(_contentXML.section);
	// look at the first item in our _contentList
	trace (_contentList[0].toString());
}

A quick note here. AS 3 actually has two methods of casting a bit of xml as a String. The old standby XML.toString() and the new and improved XML.toXMLString(), which leaves the opening and closing tags intact regardless of the complexity of the XML object. To get a look at the difference, simply turn the above trace into "trace (_contentList[0].toXMLString());". We'll be using the toString() method for our purposes, though.

Incidentally, to check out some other magical features of E4X, be sure to peruse the ECMA standards in the .pdf file available here.

Before going any further with our content experiment, let's take another look at our MainDisplay.as file. As you'll recall, when we first created our MainDisplay class we included a private TextField property named "_tf". We never created any method for setting the text property of that TextField instance though, so let's do so now. We'll add a public setter function to our MainDisplay.as file named "text" that will accept a String as an argument and set our TextField's text property equal to that string.

public function set text(t:String):void {
	_tf.text = t;
}

Now that we have a way of displaying text within our MainDisplay, back in the WebSite.onContentLoaded() method we created above, replace the trace() function with

_mainDisplay.text = _contentList[0].toString();

Testing the movie again, you'll see that now, instead of displaying the first XML object of our XMLList in the output panel, it shows up in our MainDisplay instance. We're nearly in the home stretch now. We just need the rest of our content to show up when the user visits different sections of our website. Well, remember back when we created our MenuButton class they had both a color property and an id property that we played around with briefly just to test the functionality of our menu buttons. Well, now we will see that id property can actually pull its own weight around here. Recall that the id values of our three menu buttons range from 0 to 2. Interestingly enough, those are the same index values of our content XML elements inside our _contentList XMLList object. Back in our WebSite.onMenuClick() method just add this simple line of script:

_mainDisplay.text = _contentList[e.target.id].toString();

THAT'S A WRAP
And that is, as they say, it. The only other minor things I added was a title, which is just a simple un-selectable TextField instance with its blendMode property set to BlendMode.OVERLAY (the flash.display.BlendMode class needs to be imported for that feature), and a logo item which is actually created entirely with script and may be included in a bonus Part IV of this series at a later date. For now however, here are our final class files in their entirety and with comments:

The document class - "classes.WebSite"

package classes {
	
	// list of imported classes
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.filters.BevelFilter;
	import flash.filters.DropShadowFilter;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	
	// our own classes imported
	import classes.graphics.*;
	import classes.iface.Menu;
	
	public class WebSite extends Sprite {
		
		private var _bg:Background;
		private var _mainDisplay:MainDisplay;
		private var _menu:Menu;
		private var _contentXML:XML;
		private var _contentList:XMLList;
		
		public function WebSite() {
			init();
		}
		
		private function init():void {
			initStage();
			addBackground();
			addMenu();
			addMainDisplay();
			onStageResize();
			loadContent();
		}
		
		// Set the properties of our stage (frame rate scalemode and align)
		private function initStage():void {
			stage.frameRate = 31;
			stage.showDefaultContextMenu = false;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			stage.addEventListener(Event.RESIZE, onStageResize);
		}
		
		// this method is called every time the stage (i.e. the browser window) is resized
		// it resizes the background and repositions the main display and menu
		private function onStageResize(... eventArray:Array):void {
			_bg.resize(stage.stageWidth, stage.stageHeight);
			_mainDisplay.reposition(stage.stageWidth / 2 - _mainDisplay.width / 2, stage.stageHeight / 2 - _mainDisplay.height / 2);
			_menu.reposition(stage.stageWidth / 2 - _menu.width / 2, _mainDisplay.y + _mainDisplay.height + 15);
		}
		
		// creates the back ground
		private function addBackground():void {
			_bg = new Background();
			addChild(_bg);
		}
		
		// creates the main display
		private function addMainDisplay():void {
			_mainDisplay = new MainDisplay();
			_mainDisplay.filters = [new BevelFilter(3, 90, 0xFFFFFF, .8, 0x000000, .65, 3, 3, 1, 3), new DropShadowFilter(8, 90, 0, .75, 7, 7, 1, 3)];
			addChild(_mainDisplay);
		}
		
		// creates the menu
		private function addMenu():void {
			_menu = new Menu();
			_menu.addEventListener(MouseEvent.CLICK, onMenuClick, true);
			addChild(_menu);
		}
		
		// called when a menu button is clicked.
		// changes the display color and sets the text in the main display
		private function onMenuClick(e:MouseEvent):void {
			_menu.color  = e.target.color;
			_bg.color = e.target.color;
			_mainDisplay.color = e.target.color;
			_mainDisplay.text = _contentList[e.target.id].toString();
		}
		
		// loads the "content.xml" file
		private function loadContent():void {
			var loader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onContentLoaded);
			loader.load(new URLRequest("content/content.xml"));
		}
		
		// called when the "content.xml" file is loaded
		// creates a new xml item from the URLLoader data property,
		// populates a XMLList object with it's contents using some E4X,
		// and sets the text property of the main display
		private function onContentLoaded(e:Event):void {
			_contentXML = new XML(e.target.data);
			_contentList = new XMLList(_contentXML.section);
			_mainDisplay.text = _contentList[0].toString();
		}
	}
}

Our two graphics classes, "classes.graphics.Background"

package classes.graphics {
	
	// list of imported classes
	import flash.display.Sprite;
	import flash.display.GradientType;
	import flash.display.SpreadMethod;
	import flash.geom.Matrix;
	
	public class Background extends Sprite {
		
		private var _color:uint;
		
		// constructor method - initializes the _color property and calls the drawBackground() method
		public function Background() {
			_color = 0x669900;
			drawBackground();
		}
		
		// draws the background at 800x600 pixels (this may be resized by classes.WebSite)
		private function drawBackground():void {
			var m:Matrix = new Matrix();
			m.createGradientBox(800, 600);
			graphics.beginGradientFill(GradientType.RADIAL, [0xE9FEB4, _color], [1,1], [0, 255], m, SpreadMethod.PAD); 
			graphics.drawRect(0, 0, 800, 600);
			graphics.endFill();
		}
		
		// resizes the background
		public function resize(w:Number, h:Number):void {
			this.width = w;
			this.height = h;
		}
		
		// sets the value of the _color property then redraws the background with that color
		public function set color(c:uint):void {
			_color = c;
			drawBackground();
		}
	}
}

and "classes.graphics.MainDisplay"

package classes.graphics {
	
	// list of imported classes
	import flash.display.Sprite;
	import flash.display.Shape;
	import flash.filters.DropShadowFilter;
	import flash.text.TextField;
	import flash.text.TextFormat;
	
	public class MainDisplay extends Sprite {
		
		private var _color:uint;
		private var _tf:TextField;
		
		// constructor method - initializes the _color and _tf properties then inititializes the MainDisplay
		public function MainDisplay() {
			_color = 0x669900;
			_tf = new TextField();
			init();
		}
		
		private function init() {
			drawDisplay();
			addOverlay();
			addTextField();
		}
		
		// draws the graphics for the display
		private function drawDisplay():void {
			this.graphics.beginFill(_color);
			this.graphics.drawRoundRect(0, 0, 450, 400, 20, 20);
			this.graphics.endFill();
		}	
		
		// draws a simple white overlay on the display just for looks
		private function addOverlay():void {
			var overlay = new Shape();
			overlay.graphics.beginFill(0xFFFFFF, .4);
			overlay.graphics.drawRoundRect(10, 10, 430, 380, 20, 20);
			overlay.graphics.endFill();
			overlay.filters = [new DropShadowFilter(2, 90, 0x000000, .8, 3, 3, 1, 3, true)];
			addChild(overlay);
		}
		
		// formats the displays _tf property and adds it to the display list
		private function addTextField():void {
			var fmt:TextFormat = new TextFormat("Tahoma", 12, 0x000000, null, null, null, null, null, null, null, null, null, 10);
			_tf.multiline = true;
			_tf.wordWrap = true;
			_tf.width = 400;
			_tf.height = 360;
			_tf.x = 20;
			_tf.y = 20;
			_tf.defaultTextFormat = fmt;
			addChild(_tf);
		}
		
		// repositions the main display instance
		public function reposition(xPos:Number, yPos:Number):void {
			this.x = Math.round(xPos);
			this.y = Math.round(yPos);
		}
		
		// sets the _color property and redraws the main display's graphics
		public function set color(c:uint):void {
			_color = c;
			drawDisplay();
		}
		
		// sets the text property of the _tf TextField
		public function set text(t:String):void {
			_tf.text = t;
		}
	}
}

and finally our user interface Menu class (combined with the helper MenuButton class) "classes.iface.Menu"

package classes.iface {
	
	// list of imports for the Menu class
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.net.URLRequest;
	
	public class Menu extends Sprite {
		
		private var _btns:Array;
		private var _btnLabels:Array;
		private var _btnColors:Array;
		private var _btnSpacing:Number;
		private var _buttonClick:Sound;
		private var _sc:SoundChannel;
		
		// constructor method - initializes some variables and calls the init() method
		// TODO - try playing with the _btnLabels and _btnColors arrays to see what happens.
		//
		// maybe these arrays should be passed as arguments in our constructor.
		public function Menu() {
			_btns = new Array();
			_btnSpacing = 120;
			_btnLabels = new Array ("Home", "Product", "Contact");
			_btnColors = new Array(0x669900, 0xCC0066, 0x6699FF);
			_buttonClick = new Sound();
			
			init();
		}
		
		// load the click sound and loop through the _btnLabels array adding a new button for each member
		private function init():void {
			_buttonClick.load(new URLRequest("sounds/click.mp3"));
			_btnLabels.forEach(addButton); 
		}
		
		// adds a menu button and sets event listeners for that button
		private function addButton(element:String, i:int, arr:Array):void {
			var b:MenuButton = new MenuButton(_btnColors[i], element, i);
			b.x = i * _btnSpacing;	
			addChild(b);
			_btns.push(b);
			b.addEventListener(MouseEvent.CLICK, onClick);
			b.addEventListener(MouseEvent.MOUSE_OVER, onRollOver);
		}
		
		// called when a button is clicked 
		// redispatches the MouseEvent
		private function onClick(e:MouseEvent):void {
			dispatchEvent(e);
		}
		
		// called when a button is rolled over
		// plays our click sound
		private function onRollOver(e:MouseEvent):void {
			_sc = _buttonClick.play();
		}
		
		// repositions the menu
		public function reposition(xPos:Number, yPos:Number):void {
			this.x = Math.round(xPos);
			this.y = Math.round(yPos);
		}
		
		// sets the _color property and calls the color method of each button added to the menu
		public function set color(c:uint):void {
			for (var btn in _btns) _btns[btn].color = c;
		}
	}
}


// HELPER CLASS
// creates a menu button for the Menu class


// imports for internal MenuButton class
import flash.display.Sprite;
import flash.display.SimpleButton;
import flash.display.GradientType;
import flash.display.SpreadMethod;
import flash.geom.Matrix;
import flash.filters.DropShadowFilter;
import flash.filters.BevelFilter;
import flash.filters.GlowFilter;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.text.AntiAliasType;

internal class MenuButton extends SimpleButton {
	
	private var _upShadow:DropShadowFilter;
	private var _downShadow:DropShadowFilter;
	private var _overGlow:GlowFilter;
	private var _bevel:BevelFilter;
	private var _color:uint;
	private var _idNum:int;
	private var _btnText:String;
	private var _newColor:uint;
	
	// constructor method - initializes graphic properties as well text, id, and color properties
	public function MenuButton(nc:uint, btnText:String, id:int) {
		_newColor = nc;
		_btnText = btnText;
		_idNum = id;
		_color = 0x669900;
		_bevel = new BevelFilter(4, 90, 0xFFFFFF, .8, 0x000000, .1, 0, 3, 1, 3);
		_upShadow = new DropShadowFilter(3, 90, 0x000000, .6, 4, 4, 1, 3);
		_downShadow = new DropShadowFilter(1, 90, 0x000000, .8, 2, 2, 1, 3);
		_overGlow = new GlowFilter(0xFFFF66, .5, 10, 10, 1, 3, true);
		_fmt = new TextFormat("Arial", 11, 0x000000, null, null, null, null, null, TextFormatAlign.CENTER);
			
		drawButton();
	}
	
	// draws the button by setting SimpleButton states equal to Sprites created with various methods
	public function drawButton():void { 
		this.upState = drawUp();
		this.overState = drawOver();
		this.downState = drawDown();
		this.hitTestState = drawUp();
	}
	
	// Sprite drawing methods to populate the different SimpleButton states
	private function drawUp():Sprite{
		var cols:Array = [_color, 0x222222];
		var m:Matrix = new Matrix();
		m.createGradientBox(100, 25, 90*Math.PI/180);
			
		var s:Sprite = new Sprite();
		s.graphics.beginGradientFill(GradientType.LINEAR, cols, [1, 1], [100, 255], m, SpreadMethod.PAD);
		s.graphics.drawRoundRect(0, 0, 100, 20, 20, 20);
		s.graphics.endFill();
		var tf:TextField = buttonText();
		s.addChild(tf);
		
		s.filters = [_bevel, _upShadow];
			
		return s;
	}
	
	private function drawDown():Sprite {
		var cols:Array = [_color, 0x222222];
		var m:Matrix = new Matrix();
		m.createGradientBox(100, 25, 90*Math.PI/180);
	
		var s:Sprite = new Sprite();
		s.graphics.beginGradientFill(GradientType.LINEAR, cols, [1, 1], [100, 255], m, SpreadMethod.PAD);
		s.graphics.drawRoundRect(0, 2, 100, 20, 20, 20);
		s.graphics.endFill();
		var tf:TextField = buttonText();
		tf.y = 2;
		s.addChild(tf);
		s.filters = [_bevel, _downShadow];
			
		return s;
	}
		
	private function drawOver():Sprite {
		var cols:Array = [_color , 0x222222];
		var m:Matrix = new Matrix();
		m.createGradientBox(100, 25, 90*Math.PI/180);
			
		var s:Sprite = new Sprite();
		s.graphics.beginGradientFill(GradientType.LINEAR, cols, [1, 1], [100, 255], m, SpreadMethod.PAD);
		s.graphics.drawRoundRect(0, 0, 100, 20, 20, 20);
		s.graphics.endFill();
		var tf:TextField = buttonText();
		s.addChild(tf);
		s.filters = [_overGlow, _bevel, _upShadow];
			
		return s;
	}
	
	// adds text to the MenuButton instance 
	private function buttonText():TextField {
		var fmt = new TextFormat("Tahoma", 10, 0x000000, null, null, null, null, null, TextFormatAlign.CENTER);
		var tf:TextField = new TextField();
		tf.width = 100;
		tf.height = 20;
		tf.antiAliasType = AntiAliasType.ADVANCED;
		tf.selectable = false;
		tf.defaultTextFormat = fmt;
		tf.text = _btnText;
		return tf;
	}
	
	// sets the _color property and redraws the button accordingly
	public function set color(c:uint):void {
		_color = c;
		drawButton();
	}
	
	// returns the _newColor property
	// used to set the color of various display objects (Background, MainDisplay, and the MenuButtons themselves)
	public function get color():uint {
		return _newColor;
	}
	
	// returns the _idNum property
	// used to set the text of the MainDisplay
	public function get id():int {
		return _idNum;
	}
}

One other thing that should be mentioned - although we've created this simple website entirely in Actionscript, AS3 is not limited to script alone. As in previous versions of Flash, display items may be imported into the library and attached to class files. Unlike previous versions of Flash, these items can be instantiated using the "new" keyword then added to the display list just as we did with our Background, MainDisplay, and Menu classes. Actionscript has evolved into an incredibly powerful programming language. We've barely scratched the surface of its capabilities here. Still though, I hope this three part series has been helpful getting you acquainted with some of the new features and how a simple application may be structured.





Part II.