Email Updates RSS Subscribe
Line

This blog is created and maintained by the technical team at Hook in an effort to preserve and share the insights and experience gained during the research and testing phases of our development process. Often, much of this information is lost or hidden once a project is completed. These articles aim to revisit, expand and/or review the concepts that seem worth exploring further. The site also serves as a platform for releasing tools developed internally to help streamline ad development.

Launch
Line

Hook is a digital production company that develops interactive content for industry leading agencies and their brands. For more information visit www.byhook.com.

Line

MicroMVC – An AS2 framework mashup

Line
Posted on November 18th, 2010 by Chris
Line

MicroMVC is a light weight, AS2 MVC framework intended for applications that have tight file weight restrictions. Its goal is to provide an easy MVC set up and improve on some of the limitations of the AS2 platform. The project started as an AS2 port of the fantastic AS3 framework RobotLegs combined with some ideas from casalib.org and pureMVC.

Project Goals:

  1. Small Fileweight
  2. Ease of use in AS2
  3. Improve upon AS2 event system
    1. Improve upon AS2 event system
    2. Better event dispatch
    3. Event bubbling
  4. Ability to be added on top of existing projects
  5. Separation of MVC layer from main AS2 layer

Introduction – Wallowing In the AS2 Sewer

Working Around the Limitations of Available Type Information in AS2:

The first issue with AS2 is that it lacks the concept of “Class” as a top level type. This mean you cannot retrieve classes using getQualifiedClassName () and cannot pass a Class as a reference. The best you can do is a string representation of the fully qualified class name, from that you can retrieve the constructor function of the Class. This limitation required rethinking how some parts of RobotLegs work when porting the existing frameworks from AS3.

In order to solve this type issue, all classes in MicroMVC implement ICoreObject which specifies a function to retrieve a string description of the class type. Since we need to be able to retrieve this string as a static property of each class and from an instance; each class has static and instance functions to provide this name as a String. This is a bit of a pain to remember in each class so MicroMVC comes with FlashDevelop Templates that do it for you.

In order to solve this type issue, all classes in MicroMVC implement ICoreObject which specifies a function to retrieve a string description of the class type. Since we need to be able to retrieve this string as a static property of each class and from an instance; each class has static and instance functions to provide this name as a String. This is a bit of a pain to remember in each class so MicroMVC comes with FlashDevelop Templates that do it for you.

	/**
	 * <strong>String</strong> representation of the <em>Fully Qualified Class Name</em>
	 */
	private static var _stringDescription:String = "com.MicroMVC.mvcs.Command"
	/**
	 * get the <strong>String</strong> representation of the <em>Fully Qualified Class Name</em>
	 */
	public static function get stringDescription ():String {
 
		return _stringDescription
 
	}
/**
	 * Provides access to a description of the <em>Fully Qualified Class Name</em>
	 * @return  <strong>String</strong> version of the <em>Fully Qualified Class Name</em>.
	 *
 
For example <em>"com.MicroMVC.core.ICoreObject"</em>.
 
	 */
	public function getStringDescription():String
	{
		return Command.stringDescription
	}

AS2 Interfaces and Coding Style Decisions When Designing to Interfaces:

AS2 Interfaces lack a key feature on AS3 Interfaces, the ability to specify Getters and Setters in the interface. Public function get propery() will throw a compiler error so if you want that getter to be part of the Interface you have to use getProperty(). This is a pain since it makes coding in the actual implementation more verbose but it allows for more a more specific definition within Interfaces making it less likely you will have to cast things to a specific type during implementation. Most of us use an IDE with some sort of Intellisense code completion and since I wanted to specify as much as possible by Interface for more flexible implementation I choose to add function based getters and setters to Interfaces rather than use get and set.

Since AS2 is so poor in type implementation I wanted to try and make sure as much as possible could be typed to Interfaces that provided actual information, rather than typing to more generic Objects or Interfaces that didn’t specify enough of the API.

Garbage Collection:

To help with garbage collection the base interface in MicroMVC, ICoreObject, specifies a destroy () method. This method is called within each super class to remove any MicroMVC parts when a instance is ready for GC but you will need to override it to remove your own variables. Each component will remove all its listeners when destroy is called.

public function destroy():Void
	{
		// Delete your references here
		// don't forget to call the superclass version also
		super.destroy ()
 
	}

Templates:

In the MicroMVC source code are templates that make creating new classes easier. I have included templates for all the parts of MicroMVC you will actually use. These templates are for FlashDevelop but they can also serve as a guide for building templates in other IDEs. Since AS2 does not an override statement working with inheritance is a bit of a pain, the templates hopefully make it easier.

A New Event System

Typed Events and the MVCEventDispatcher:

Event dispatch in AS2 is a disaster and the first step towards MicroMVC was creating a better AS2 event system. This set up is the basis of MicroMVC and can also be used on its own if you wish.

The main problem with the built is AS2 event system is that mx.events.EventDispatcher is very basic and is implemented in an odd way in classes since it is added as sort of an afterthought. Another large problem is the scope issues in AS2. This problem forces you to use mx.utils.Delegate, which produces ugly code and can play havoc with garbage collection since it is easy to lose track of what events have listeners and which don’t and no way to just remove them all. There is also no consistent base event type, event payloads come through as Object so you don’t know what they contain. All this makes for an unreliable system that is just a headache to use.

MicroMVC’s event dispatcher is a port of the AS2 CasaLib.org event dispatcher, customized for the events that implement our IEvent interface. This system has several advantages:

  1. No need to use mx.utils.Delegate
  2. All observers for an event type can be removed via removeEventObserversForEvent ()
  3. All observers for a given scope can be removed via removeEventObserversForScope()
  4. All observers within the dispatcher can be removed via removeAllEventObservers () allowing for successful garbage collection
  5. The dispatcher can be queried to determine if specific events have a listener via hasEventListener()

This is a much more reliable system that is closer in spirit to AS3 then AS2.

MVCEventDispatcher uses typed events that implement IEvent. This Event class is an attempt to bring some of the utility of AS3 events to AS2. Events have a target property and can carry any payload you specify. This system will feel a lot closer to AS3 events then as2. There is even event bubbling, which we will get to later.

MVC Framework Basics – “Hey This Looks A Lot Like RobotLegs”

I am very impressed with RobotLegs for its simplicity and ease of use so I set out to bring a lot of that magic to AS2 for all of us still wallowing down in this muck. Since there is not Dependency Injection in AS2 I had to find other ways to work the same level of awesome into MicroMVC.

Application Structure:

These components are built in top of our new event system and are the parts of MicroMVC you will extend as part of your implementation. If you are familiar with RobotLegs these will seem very familiar to you. They also follow the basic theory of MVC development http://en.wikipedia.org/wiki/Model-view-controller so previous experience with other MVC systems will translate well.

  1. Context: This is the bootstrap for MicroMVC, it provides the centralized event dispatcher and the utilities to create and store relationships of the various components
  2. Mediators: Manage communication between Views and other parts of the framework
  3. Commands: execute actions based on user input or other events. Most often used to update Models or Services
  4. Models: Store data and dispatch events based on changes to that data
  5. Services: Act as gateways between outside data and data within the application

Context.as

The context is the grand central station of MicroMVC, it provides the central event bus used for communication by all the other components and stores the 3 main mapping components. The context is a non-visual class that is the entry point between your MicroMVC application and the main AS2 layer, in a sense it straddles the line between your application and the rest of the swf. You must provide the context with a reference to some external view that will serve as the attach point for all views within the MicroMVC application. Often this is “_root” but it can be any view on the display list.

The context also creates the 3 main Maps used by MicroMVC:

  1. CommandMap
  2. MediatorMap
  3. ActorMap

You won’t work with Context directly, you will extend it to do the bootstrapping in your application, below is an example of a class that extends context in a real application.

class ExtendContext extends Context
{
	private static var _stringDescription:String = "ExtendContext"
	public static function get stringDescription ():String {
 
		return _stringDescription
 
	}
	public function ExtendContext(externalView:MovieClip, autoStartup:Boolean)
	{
		super(externalView, autoStartup);
 
	}
 
	public function startup () {
 
		_commandMap.mapEvent (ContextEvent.STARTUP_COMPLETE,SetUpViewCommand.stringDescription,true)
 
		_commandMap.mapEvent (ContextEvent.STARTUP_COMPLETE,SetUpControlCommand.stringDescription,true)
 
		_commandMap.mapEvent (ContextEvent.STARTUP_COMPLETE, SetUpModelCommand.stringDescription, true);
 
		super.startup()
 
	}
 
	public function destroy():Void
	{
		super.destroy()
	}
 
	public function getStringDescription():String
	{
		return ExtendContext.stringDescription
	}
 
}

ContextView.as

The ContextView serves as the “_root” equivalent of the display hierarchy in MicroMVC. It is automatically created when the Context is created and all additional views should be attached to it. It is necessary to have a ContextView so that bubbling events have a top level to hit so that automatic mediation of views can occur. ContextView can be mediated like any other view, though you have to register the mediator yourself, it can’t be done automatically.

Actors, Services, Models and ModelMap:

Actor.as

The foundation of the data (model) tier in MicroMVC is Actor.as. Actor.as is the concrete class used in 2 conceptual implementations:

  1. Models: wrap data needed by the application and provide an API to access that data.
  2. Services: retrieve data from outside the application and format it for use within the application.

As Actors, Models and Services can dispatch events that can be listened for by observers, they do not listen to event directly. Like all parts of MicroMVC you won’t implement an Actor directly, you will extend Actor to your implementation. Below is a simple example that maintains a count of how many times a button was clicked and dispatches events via the event dispatcher in the context.

class ExtendActor extends Actor
{
	private static var _stringDescription:String = "ExtendActor"
	public static function get stringDescription ():String {
 
		return _stringDescription
 
	}	
 
	private var _buttonCount:Number = 0
 
	public function ExtendActor(eventDispatcher:IEventDispatcher)
	{
		super(eventDispatcher);
	}
 
	public function get buttonCount():Number { return _buttonCount; }
 
	public function set buttonCount(value:Number):Void
	{
		_buttonCount = value;
 
		dispatchEvent (new ExtendActorEvent (this, ExtendActorEvent.DATA_CHANGE, _buttonCount, null));
 
	}
 
	/* INTERFACE com.MicroMVC.core.ICoreObject */
 
	public function destroy():Void
	{
		super.destroy()
	}
 
	public function getStringDescription():String
	{
		return ExtendActor.stringDescription
	}
 
}

ActorMap.as

Instances of Models and Services are stored in ActorMap.as since we don’t have the benefit of Dependency Injection in AS2. Instances are stored by a named String via the registerActor(actor:IActor,actorName:String) function. There are also functions to retrieve and remove Actors. You can retrieve the ActorMap from any class that has a reference to the context; this includes Commands and Mediators and Actors.

Below is an example of creating and mapping an Actor in the ActorMap, in this case it is done within the execute function of a command class.

	public function execute(context:IContext,payLoad:IEvent)
	{
		var actor:ExtendActor = new ExtendActor (context.getEventDispatcher());
		context.getActorMap().registerActor (actor, "ExtendActor")
	}

Command and CommandMap:

Command.as

Commands are stateless responses to events, their purpose is to update the Model tier of your application or perform other similar tasks. They allow you to encapsulate logic for reuse outside of the models and mediators. When an event is dispatched if it has an associated command in CommandMap.as an instance of the command class will be created and executed with the Event instance as the payload.

References to Commands should never be stored, they are intended to be executed then disposed. Each Command.as has an execute () function that you will override in your extension class. Place all the work the command should do in execute(). Commands can also dispatch events through the central event dispatcher to trigger other commands or any other activity. If you dispatch an event from a command you should probably set the target property of the event to null instead of a reference to the command.

Here is a simple example of a command

class SetUpControlCommand extends Command
{
	private static var _stringDescription:String = "SetUpControlCommand"
	public static function get stringDescription ():String {
 
		return _stringDescription
 
	}
	public function SetUpControlCommand()
	{
		super();
 
	}
 
	public function execute(context:IContext,payLoad:IEvent)
	{
			context.getCommandMap().mapEvent (ExtendButtonMediatorEvent.DATA_CHANGE,DataChangeCommand.stringDescription)
 
		context.getCommandMap().mapEvent (ExtendButtonMediatorEvent.REMOVED,RemovedCommand.stringDescription,true)
 
	}
 
	public function destroy():Void
	{
		super.destroy()
	}
 
	public function getStringDescription():String
	{
		return SetUpControlCommand.stringDescription
	}
 
}

CommandMap.as

CommandMap.as stores the relationships between event types and classes that extend Command.as. Once the relationship is mapped, each time an instance of a specific event type is dispatched, an instance of command class you specified is created, passed in the event as a payload and the method execute () is called allowing the command class to do its work. Relationships are created with the mapEvent () function.

mapEvent takes 3 parameters:

  • eventName:String
  • commandClassString:String
  • oneShot:Boolean
    • specifies if the map relationship should be removed after first time the event is dispatched, defaults to false

Here is an example:

// this command executes each time an event of this type is dispatched
context.getCommandMap().mapEvent (ExtendButtonMediatorEvent.DATA_CHANGE,DataChangeCommand.stringDescription)
// this command is executed only the first time an event of this type is dispatched, then the relationship is removed.
context.getCommandMap().mapEvent (ExtendButtonMediatorEvent.REMOVED,RemovedCommand.stringDescription,true)

Notice that the event type is specified by the static property of the Event Class, similar to AS3. The command class is specified by the static property “stringDescription”. This is because Class is not a type in AS2, so you have to use the stored String description.

Map relationships are removed with the unmapEvent () function.

Views, Mediators and MediatorMap:

The View tier of MicroMVC is the most complex of the 3 main areas, mostly due to the fact that is has to deal with and shield you from many of the failures of AS2 in regards to display classes. The design of the View tier was based on the following goals.

  1. Encapsulate the terrible AS2 MovieClip event system in the MicroMVC system so people don’t have to deal nonsense like onMouseOver = function (){}
  2. No use of Delegate of the built in AS2 EventDispatcher
  3. Event Bubbling

Here is a graphic that illustrates the basic skeleton of the view structure of a MicroMVC application.

View.as

View.as extends the built in MovieClip class and is intended to be used as the class associated with items in the Library of your FLA. View replaces MovieClip for all intents and purposes with a MicroMVC application.

Dealing with events from MovieClip

It is not possible to completely do away with the event system used in MovieClip so View.as acts as sort of a proxy between those events and your MicroMVC application. View.as is one of the few classes to straddle the line between regular AS2 and MicroMVC.

MovieClip handles events by assigning handler functions to variables, like onLoad = function () {}. View.as defines handlers for each of these events; these handlers then dispatch the equivalent type of the class MovieClipEvent. This is how the bridge is made.

Below is the onLoadHandler used by View.as to catch the onLoad events dispatched by MovieClip, its job is pretty basic, set the event to bubble then dispatch it.

private function onLoadHandler():Void {
 
		var theEvent:MovieClipEvent = new MovieClipEvent (this, MovieClipEvent.LOAD, null, null)
		theEvent.setBubbles (true)
		this.dispatchEvent(theEvent);
	}

So that sounds easy enough, but there is one wrinkle. In the AS2 MovieClip class if you assign a function to any handler associated with events based on the mouse:

  • onMouseDown
  • onMouseUp
  • onMouseMove
  • onPress
  • onRelease
  • onReleaseOutside
  • onRollout
  • onRollover

The MovieClip blocks event propagation to any child movie clips, forever. Even if you remove all the handlers you are still screwed since propagation will be stopped. This means you have to be careful about when you turn on mouse events.

Senocular has a good article on this problem that you should read: http://www.senocular.com/flash/tutorials/buttoncapturing/

Also you don’t want onEnterFrame running all the time if you are not actually using the events since it will destroy performance. So in order deal with this, View.as has 2 functions: enableEvents (value:Array) and disableEvents(value:Array). These accept an array of Strings of the event types you want to turn on or off, the Strings should come from the static variables in MovieClipEvent.

These are used to turn on and off handlers for specific event types. So for example if you want to use a View as a button that dispatches events when the mouse is released over it you would call the following in the constructor for the view.

public function ExtendButton()
	{
		super();
		enableEvents ([MovieClipEvent.RELEASE])
	}

Then if you later needed to disable the event dispatch you would call disableEvents ([MovieClipEvent.RELEASE]). These functions are public so they can be called from outside any View.

Special Handling of onLoad and onUnload

MicroMVC has the ability to automatically register mediators when views are added to the display and automatically remove them when the views are removed. In order to do this the event handlers onLoadHandler and onUnloadHandler must be enabled by default and should not be disabled unless you have a very good reason. This is already set up in View.as so it should not need to be altered. If you remove onUnloadHandler you will also break your garbage collection since destroy() is automatically called during onUnloadHandler. These events bubble up the MicroMVC display hierarchy to the ContextView where they are caught and dealt with by MediatorMap. All your display classes within the hierarchy must extend View.as for this to work.

Listening for MicroMVC events from Views

View.as extends IEventDispatcher so it will dispatch events within the MicroMVC framework. Note that you are now no longer listening for any MovieClip events at this level.

Event Bubbling

Event classes that extend Event in MicroMVC have the ability to bubble up the view hierarchy inside your application. There is a property in Event called bubbles (getBubbles () and setBubbles (value:Boolean )) that if set to true will enable event bubbling. Events that are bubbling also have the property currentTarget  (getCurrentTarget () and setCurrentTarget (value:Object)) set as it ascends the view hierarchy. The actual bubbling is handled by View.as

In order for events to bubble up the view hierarchy all the instances in that hierarchy must be classes that extend View.as all the way to up the ContextView instance in Context. The events will not bubble beyond the ContextView.

Mediator.as

Mediators separate the MVC logic from the view’s functional logic. Keeping this logic in Mediators make your views simpler and more modular since they can focus solely on their responsibility to display content or respond to user input and they no longer tied to specific situations. All the logic to react to a specific situation is isolated in the Mediator.

Mediators listen for events in 2 directions, from their associated View component and the context event dispatcher. Events come up from the View and down from the event dispatcher. Mediators can also dispatch events that can be heard by other components, this is done in response to things like user input.

Mediators are not part of the display hierarchy, they are tied to a specific instance of a View but don’t sit in the display. They do not extend MovieClip.

Registration and Removal

Mediators are associated with instances of classes that extend View.as when those instances are added to the display and their onLoadHandler dispatches MovieClipEvent.LOAD. Mediators are removed when their view component instances leave the display. This is done automatically by default but can also be done manually.

There are 2 functions you can use to bootstrap and take down the mediator during this process.

onRegister() is called automatically when the mediator is created and associated with a specific view instance. You should override this in your specific Mediator classes to do things such as configure the view component or add event observers.

onRemove() is called when the mediator association is removed and the mediators is about to be destroyed. You should remove any event handlers here and do any other clean up. Destroy () is called automatically for you after this function is called.

Adding and Removing Event Observers

Use these functions to manage events coming from the view

  • addViewObserver (eventName:String, eventHandler:Function)
  • removeViewObserver( eventName:String, eventHandler:Function)

Use these function to manage events from the Context event dispatcher

  • addContextObserver(eventName:String, eventHandler:Function)
  • removeContextObserver( eventName:String, eventHandler:Function)

Accessing Models and other parts of the application

Mediators have a reference to their Context via getContext(), you can use that to get access to the Context’s ActorMap instance or any other property of context. It is preferred that mediators don’t update values on Models directly; rather they should dispatch events which are handled by Commands which update the model.

MediatorMap.as

The MediatorMap manages associations between Views and Mediators by type. Similar to the CommandMap you can associate a specific type of View with a specific type of Mediator; then each time an instance of that View is added to the display hierarchy the appropriate Mediator will be created and associated with the view. This is useful because you can set up your mediator relationships when you start the application and have them take effect in later frames with the views are actually created, making it easier to add MicroMVC on top of presentations that contain a lot of timeline based animation. Mediators can also be added to existing views via the registerMediator() function.

There are a few conditions that must be met for automatic mediation to work.

  • All instances on the display hierarchy up to the ContextView must extend View.as for the bubbling to work
  • The Mediator relationship in MediatorMap must be created before the view’s onLoad event is fired
  • Don’t disable the automatic dispatch of MovieClipEvents for onLoad and onUnload in classes that extend View.as

Below is an example showing how to map views. Remember you have to provide the string references of the fully qualified class names.

context.getMediatorMap().mapView (ExtendView.stringDescription, ExtendMediator.stringDescription, true, true)
context.getMediatorMap().mapView (ExtendButton.stringDescription,ExtendButtonMediator.stringDescription,true,true)

Mediating existing Views and the ContextView

If a view is already created it can be mapped using the registerMediator(viewComponent:IView, mediator:IMediator) command. Just create an instance of your mediator class to be added. This does not create a mapped relationship so other instances are not affected. This is also used if you want to mediate the ContextView, since it is created when the Context is instantiated so you will miss it’s onLoad event dispatch.

Example of registering a mediator for the ContextView:

var cvm:ContextViewMediator = new ContextViewMediator ()
context.getMediatorMap().registerMediator (context.getContextView(), cvm);
// attach with Casalib.org's MovieClipUtility
MovieClipUtil.attachMovie (MovieClip (context.getContextView()), "ViewMC", "_extendView");

Dependencies

MicroMVC makes use of the AS2 version of CasaLib http://casalib.org/#/2/ . The version of CasaLib included is a fork of the current AS2 build. I have modified the TypeUtil.as class to remove some of the imports to make the file weight smaller. I have only included the classes actually used to MicroMVC, not the entire package.

Download

Complete Source, Templates and Docs are available here.

Line
1 Response to “MicroMVC – An AS2 framework mashup”
  1. [...] This post was mentioned on Twitter by Kevin Cao, karannnnnnnnnnn3. karannnnnnnnnnn3 said: MicroMVC – An AS2 framework mashup: MicroMVC is a light weight, AS2 MVC framework intended for applications that… http://bit.ly/bRsYyK [...]


Leave a Reply

*

Line
Line
Pony