Using Interfaces with Display List Objects in AS3.0

This is an example illustration of Option 2 from a great post by Mr. Wright.

Project structure:

Create an interface to be implemented by your group of display objects. In this case it’s called IEmployee, since I’ll be using it to call an identically named method on three employees (Developer, Designer, Project Manager):

package com.interfaces
{
	import com.interfaces.ISprite;
	
	public interface IEmployee extends ISprite
	{
		function talk():String;
	}
}

Create an ISprite (or IMovieClip, or IDisplayObjet) interface, since one doesn’t exist in AS3:

package com.interfaces
{
	import flash.display.Sprite;
	
	public interface ISprite
	{
		function get view():Sprite;	
	}
}

Each of the concrete classes below – Developer, Designer, ProjectManager – contains identically named methods “view” & “talk()” which can be called by a user class which doesn’t have to know exactly which class’s instance it’s calling.

Developer class implements IEmployee:

package com.view
{
	import com.interfaces.IEmployee;
	
	import flash.display.Sprite;
	
	public class Developer extends Sprite implements IEmployee
	{
		public function Developer()
		{
			super();
		}
	
		public function get view():Sprite
		{
			return this as Sprite;
		}

		public function talk():String
		{
			return "Code, code, code! Why are the PSD layers not organized? Code, code code!";
		}
	}
}

Designer class also implements IEmployee:

package com.view
{
	import com.interfaces.IEmployee;
	
	import flash.display.Sprite;
	
	public class Designer extends Sprite implements IEmployee
	{
		public function Designer()
		{
			super();
		}
		
		public function get view():Sprite
		{
			return this as Sprite;
		}
		
		public function talk():String
		{
			return "Pantone! CMYK! RGB! What the hell is HEX? Vector Smart Object!";
		}
	}
}

and ProjectManager class implements IEmployee as well:

package com.view
{
	import com.interfaces.IEmployee;
	
	import flash.display.Sprite;
	
	public class ProjectManager extends Sprite implements IEmployee
	{
		public function ProjectManager()
		{
			super();
		}
		
		public function get view():Sprite
		{
			return this as Sprite;
		}
		
		public function talk():String
		{
			return "Meeting! Do you need an LCD spec? Meeting! Can I get an ETA on that? Meeting, meeting.";
		}
	}
}

This is a basic user class, which randomly specifies which employee to use and calls the talk() method on that employee:

package
{
	import com.interfaces.IEmployee;
	import com.view.Designer;
	import com.view.Developer;
	import com.view.ProjectManager;
	
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.utils.getQualifiedClassName;
	
	[SWF (width="964", height="655", backgroundColor="#000000", frameRate="30")]	
	public class DisplayListPolyMorphismByInterface extends Sprite
	{
		private var worker:IEmployee;
		private var employees:Array = [new Developer(), 
                                       new Designer(), 
                                       new ProjectManager()];
		
		public function DisplayListPolyMorphismByInterface()
		{
			initApp();				
		}		
		
		public function initApp():void
		{
			stage.addEventListener(MouseEvent.MOUSE_UP, changeWorker, false, 0, true); 			 
			addWorker();					
		}	
		
		public function addWorker():void 
		{
			worker = employees[ randomInt(0,2) ] as IEmployee;		 			
			addChild( worker.view );
			
			talkTheTalk();			
		}
		
		public function changeWorker(e:MouseEvent):void
		{				
			removeChild( worker.view );
			worker = null; 
			
			addWorker();
		}

		public function talkTheTalk():void 
		{
			trace( "This employee is a " + getQualifiedClassName(worker.view) + " and says '" + worker.talk() + "'" );
		}
		
		public function randomInt(low:Number=0, high:Number=1):int
		{
			return Math.floor(Math.random() * (1+high-low)) + low;
		}				
	}
}

Note to self: “@protocol” in Objective-C is like an implementable “interface” in AS3.0 or Java

Here’s a quick example. Perhaps, before trying to decipher the grey code boxes, scroll down a bit to this section:

  • The protocol specification is quite simple. it is basically @protocol ProtocolName (methods you must implement) @end.
  • To conform to a protocol, you put the protocols you’re conforming to in <>’s, and comma separate them. Example: @interface SomeClass <Protocol1, Protocol2, Protocol3>

Note to self: Structs in C++ are like Value Objects in Actionscript or Java

Wiser folks than I have accurately pointed out that no analogy is perfect. In this case, it’s plain to see that Structs in C++ are an official data type that’s part of the language itself, whereas Value Objects are more of a convention (former design pattern[1]) created by Java programmers (among others) over the years.

Structs are similar to Value Objects since both are essentially containers for a bunch of data. Both don’t have methods. Perhaps, Value Objects could have Getters & Setters, but more often there aren’t methods.

An example of a Struct in a C++ tutorial:

struct database {
  int id_number;
  int age;
  float salary;
};

int main()
{
  database employee;  
  employee.age = 22;
  employee.id_number = 1;
  employee.salary = 12000.21;
}

An example of a Value Object from an AS3 tutorial using PureMVC:

package com.flashtuts.model.vo
{
	public class DataVO
	{
		public var dataURL:String          = 'assets/xml/data.xml';
		public var urlsArray:Array	  = [ ];
		public var urlsDataArray:Array  = [ ];
	}
}

1. In Java the Value Object Pattern is now called the Transfer Object Pattern.

Faking abstract classes in AS3: a slideshow app

Here’s a quick little exercise in writing an AS3 application. It’s a basic image slide show I wrote as a “code only” app in Flash CS4. There’s nothing in the Library, nothing on the stage. The .fla is only used to specify a Document Class (BasicSlideShow) and compile the SWF. The code can also be compiled via Flex Builder 3, FlashDevelop, FDT or Flash Builder with minimal change (mostly the metadata stuff like [SWF (width=”600″, height=”400″, backgroundColor=”#9999cc”, frameRate=”31″)]).

Faking an abstract class in AS3.0.

AS3.0 only supports Interfaces, so that’s what I started out with at first. However, soon enough I found myself repeating code (always inefficient and error prone). So I switched to a simulated abstract class to combine the benefits of an Interface with the code reuse utility offered by these puppies:

package com.timshaya.model
{
	import gs.TweenLite;
	import flash.display.Sprite;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	import flash.events.MouseEvent;

	//DO NOT implement, subclass instead
	public class AbstractSlide extends Sprite
	{
		protected var _path:String;
		protected var _link:String;

		public function get path():String { return _path; }
		public function set path(val:String):void { _path = val; }
		public function get link():String { return _link; }
		public function set link(val:String):void { _link = val; }

		public function AbstractSlide(path2:String="", link2:String=""){}

		//faking an abstract method in AS3.0
        public function setClickArea():void
		{
			throw new Error("AbstractSlide shouldn't be implemented. Subclass it insteaad and override this method: setClickArea");
		}

		//faking an abstract method in AS3.0
		public function doLink(e:MouseEvent):void
		{
			throw new Error("AbstractSlide shouldn't be implemented. Subclass it insteaad and override this method: doLink");
		}

		//faking an abstract method in AS3.0
		public function animateSlideIn(spd:Number):void
		{
			throw new Error("AbstractSlide shouldn't be implemented. Subclass it insteaad and override this method: animateSlideIn");
		}

		public function fadeInSlide(speed:Number):void
		{
			this.alpha = 0;
			TweenLite.to(this, speed, {alpha:1});
                //using this static method enables TweenLite's automatic memory management
		}
	}
}

Here’s a subclass that uses AbstractSlide:

package com.timshaya.model
{
	import flash.display.Sprite;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	import flash.events.MouseEvent;

	public class SimpleSlide extends AbstractSlide
	{
		public function SimpleSlide(path2:String = "", link2:String = "")
		{
			this.path = path2;
			this.link = link2;
			this.setClickArea();
		}

		public override function setClickArea():void
		{
			this.buttonMode = true;
			this.useHandCursor = true;
			this.addEventListener(MouseEvent.CLICK, doLink, false, 0, true);
		}

		public override function doLink(e:MouseEvent):void
		{
			var url:String = this.link; //grab url from getter method
			var req:URLRequest = new URLRequest(url);

			try {
				navigateToURL(req, '_self');
			} catch (e:Error) {
				trace("Something's wrong with the URL!");
			}
		}

		public override function animateSlideIn(spd:Number):void
		{
			fadeInSlide(spd);
		}
	}
}

The bulk of the business logic is in this class (the Document Class in this case):

package
{
	import com.timshaya.model.events.SlideRotator;
	import com.timshaya.model.SimpleSlide;

	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLLoaderDataFormat;
	import flash.display.Loader;

	public class BasicSlideShow extends Sprite
	{
		private var xmlPath:String;

		private var firstTime:Boolean = true;
		private var slideShow:SlideRotator;
		private var slideContainer:Sprite;
		private var currntSlide:int;
		private var currntImg:Sprite;
		private var myImages:Array = new Array();
		private var myImg:Sprite;
		private var loader:URLLoader;

		public function set theXmlPath(val:String):void { xmlPath = val; }
		public function get theXmlPath():String { return xmlPath; }

		public function BasicSlideShow(defaultPath:String = "slideshow.xml")
		{
			this.theXmlPath = defaultPath;
			buildImgList();
		}

		public function buildImgList():void
		{
			currntSlide = 0;
			slideContainer = new Sprite();
			slideContainer.x = slideContainer.y = 0;
			addChild(slideContainer);

			loadAllImages(); //load external images
		}

		public function loadAllImages():void
		{
			//load XML list of images (with paths & urls)
			var xmlURL:URLRequest = new URLRequest(this.theXmlPath);
			loader = new URLLoader();
            loader.dataFormat = URLLoaderDataFormat.TEXT;  
			loader.addEventListener(Event.COMPLETE, onXMLDoneLoading, false, 0, true);
			loader.load(xmlURL);
		}

		public function onXMLDoneLoading(e:Event):void
		{
			loader.removeEventListener(Event.COMPLETE, onXMLDoneLoading); //free up memory slot

			var myXml = new XML(e.target.data);
			for(var g:int=0; g < myXml.image.length(); g++)
			{
				trace("g = " + g);
				myImg = new SimpleSlide();
				myImages.push(myImg);
              // make myImages as long as the num of images in XML file
			}

			for each (var img:Sprite in myImages) //add img containers to main display object
			{
				img.visible = false;
				slideContainer.addChild(img);
			}

			for(var i:int=0; i < myXml.image.length(); i++)
			{
				SimpleSlide(myImages[i]).path = myXml.image[i].path.@src;
				SimpleSlide(myImages[i]).link = myXml.image[i].url.@src;

				var imgLoader = new Loader(); //load the actual JPEGs
				imgLoader.load(new URLRequest(myImages[i].path));
				imgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoaded, false, 0, true);
				imgLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, imageLoadProgress, false, 0, true);
				myImages[i].addChild(imgLoader);
			}

			doneLoading();
		}

		public function imageLoaded(e:Event):void { trace("imageLoaded"); }

		public function imageLoadProgress(e:ProgressEvent):void
		{
			trace((e.target.bytesLoaded / e.target.bytesTotal)*100 + "%"); 
            // cheezy; good for demo purposes
		}

		public function doneLoading():void
		{
			slideShow = new SlideRotator(); //begin main slideshow timer
			slideShow.addEventListener(SlideRotator.CHANGE_SLIDE, notifyAll, false, 0, true);
		}

		public function nextSlide():void
		{
		    for each (var img:Sprite in myImages) //hide all images
				img.visible = false;

			currntImg = myImages[currntSlide];//select next image to show
			currntImg.visible = true;
			SimpleSlide(currntImg).animateSlideIn(1.33);
		}

		public function notifyAll(e:Event):void
		{
			trace("BasicSlideShow.notifyAll says: SlideRotator dispatch tells us to change slide");

			if(currntSlide < myImages.length-1){  //increment currntSlide
				if(firstTime) {
					currntSlide = 0;
					firstTime = false;
				} else {
					currntSlide += 1;
				}
			} else {
				currntSlide = 0;
			}
			trace("currntSlide = " + currntSlide);

			nextSlide();

			//updateNavbar();
		}

	}
}

NOTE: the image loading code can and should be cleaned up.

 

Most of the custom event flow is in SlideRotator, which extends EventDispatcher:

package com.timshaya.model.events
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	public class SlideRotator extends EventDispatcher
	{
		public static const TIMER_DELAY:int = 5000;
		public static const CHANGE_SLIDE:String = "changeSlide";

		private var timeCount:Timer;

		public function SlideRotator()
		{
			timeCount = new Timer(TIMER_DELAY);
			timeCount.addEventListener(TimerEvent.TIMER, onTimer, false, 0, true);
			timeCount.start();
		}

		public function onTimer(e:TimerEvent):void
		{
			changeSlide();
		}

		public function changeSlide():void
		{
			dispatchEvent(new Event(SlideRotator.CHANGE_SLIDE));
		}

		public function pauseTimer():void
		{
			timeCount.stop();
		}

	    public function restartTimer():void
		{
			timeCount.start();
		}
	}
}

Here’s the XML source file:

<?xml version="1.0" encoding="UTF-8"?>
<slideshow>
	<image type="1">
		<path src="img/3.jpg"/>
		<url src="http://www.ft.com"/>
	</image>
	<image type="1">
		<path src="img/4.jpg"/>
		<url src="http://www.google.com"/>
	</image>
	<image type="1">
		<path src="img/b1.jpg"/>
		<url src="http://www.yahoo.com"/>
	</image>
	<image type="1">
		<path src="img/b2.jpg"/>
		<url src="http://www.guardian.co.uk"/>
	</image>
	<image type="1">
		<path src="img/b3.jpg"/>
		<url src="http://www.gmail.com"/>
	</image>
	<image type="1">
		<path src="img/Slot2_Flash_template1.jpg"/>
		<url src="http://www.nytimes.com"/>
	</image>
	<image type="1">
		<path src="img/c3.jpg"/>
		<url src="http://www.yahoo.com"/>
	</image>
	<image type="1">
		<path src="img/d3.jpg"/>
		<url src="http://www.amazon.com"/>
	</image>
	<image type="1">
		<path src="img/d3.jpg"/>
		<url src="http://www.amazon.com"/>
	</image>
</slideshow>

You can put any number of images in the “img” directory, BasicSlideShow.as counts the number of images via the XML file.

To build upon this exercise, you can extend AbstractSlide and make your own CustomSlide. For example, if you need a slide with 2 links instead of 1, you can use an XML image tag of type=”2″ and do some basic refactoring to make sure BasicSlideShow catches it. Yep, you can also write an AbstractSlideShow, so instead of refactoring, you’d be adding another implementation while keeping the old one, in case you need it later. Ofcourse, you can add your own NavBar, CustomBtn classes, Pause/Play buttons, etc.