Simple MVC with ActionScript 3 - An Updated Approach

March 9, 2010 :: 17 Comments

MVC Back in October of 2008, I wrote an article on implementing a simple MVC micro-architecture. Apparently it was well received, and is by far the page on my site that gets the most traffic (about 5 times the amount).

Well that was roughly 18 months ago, and since then I have started using a much more efficient, flexible, and low footprint implementation. The goals of the new framework were to build one that allowed components to be reused, created better separation of concerns, made the application structure more modular, and gave the developer the freedom to structure the app the way it worked best for that project.

What was wrong with the old implementation?

First of all, the old implementation allowed for really only one Controller. This controller was used as the document class, and then passed as reference to the other elements of the framework. For larger projects, this approach would force the single controller to become bloated with actions, many of which would be totally unrelated to one another.

Second, the passing of reference to the Views and the subsequent direct calls from the Views to the Controller broke the loose coupling best practices. If you wanted to reuse one of the view components, you would likely have to strip out many of these direct calls in order to use a different controller in a different project.

Third, the views were setup using the Decorator Pattern. Now, there is nothing wrong with the Decorator Pattern. It makes a lot of sense in a lot of scenarios. The problem in this case is that the class controlling the functionality of the View was no longer a part of the Display List. So, events dispatched always had to be dispatched from the decorated object, which I just didn't like too much. As well, if you needed to effect the decorated DisplayObject before it had been added to stage, this often was not possible since the objects on the stage we instantiated when the DocClass (aka Controller) was instantiated, and so by the time the View decorator classes were instantiated the next frame had been rendered and you would end up with som unpredictable results with objects flashing on the screen, or appearing incorrectly for a frame.

So whats the new approach?

The new approach, like I mentioned, tries to make the app more readable, more modular, more reusable, and more decoupled. This was accomplished by 1) Creating a lower footprint for the framework, 2) Embracing the Display List, and 3) Borrowing a bit from Flex.

A few things to point out about this approach is, just like the last implementation, this is built to work using the Flash IDE and pure AS3. There is no dependencies on the Flex framework. In fact, I would recommend if you need MVC for a Flex project, use something else like: Mate, Swiz, Parsley, or Robotlegs.

The Document Class

Where the document class used to act as the controller for the entire application, it now acts as more of an initializer. It is used to load any config files, handle the preloading of the app, and then advance to the next frame when preloading is complete. As we will se when we discuss controllers, it can be an area where controllers are initialized (being that it is the top most DisplayObject in the display list).

Here is an example of a typical Document Class:

 
package  {
 
import flash.events.Event
 
import com.gn.mvc2.App;
import com.gn.mvc2.Config;
import com.greensock.plugins.*;
import com.greensock.TweenLite;
 
import control.HelloController;
 
public class HelloWorld extends App {
 
	/* --------------------------------------- */
	/* --( PUBLIC PROPERTIES )-- */
	/* --------------------------------------- */
 
	public var helloController:HelloController;
 
	/* --------------------------------------- */
	/* --( GLOBAL STATIC PROPERTIES )-- */
	/* --------------------------------------- */
 
	public static var config:Config;
 
	/* --------------------------------------- */
	/* --( CONSTRUCTOR )-- */
	/* --------------------------------------- */
 
	public function HelloWorld()
	{
		__init();
	}
 
	/* --------------------------------------- */
	/* --( PRIVATE METHODS )-- */
	/* --------------------------------------- */
 
	private function __init():void
	{
	    HelloWorld.config = new Config(this);
 
	    preloader.addEventListener(Event.COMPLETE, __onPreloaderComplete);
	    preloader.init(HelloWorld.config);
 
	    TweenPlugin.activate([AutoAlphaPlugin]);
 
	}
 
	private function __onPreloaderComplete(event:Event):void
	{
	    TweenLite.to(preloader, 0.5, {autoAlpha: 0, onComplete: function(){
	        gotoAndStop(2);
	    }});
	}
 
	/* --------------------------------------- */
	/* --( PROTECTED OVERRIDES )-- */
	/* --------------------------------------- */
 
	override protected function _initControllers():void
	{
	    helloController = new HelloController(this);
	}
 
}
 
}
 

Views

Views are handled a bit differently in the new approach. The main difference is we now use the Export/Linkage properties of the Flash Library to directly attach our Library assets to our view classes. See the below example:

Flash Library Linkage Example

Now that direct linkage is used to attach the view classes, we are able to treat these classes as members of the display list. The advantage is now any events dispatched from these classes can bubble up the display chain. If you notice in our example above of our Document Class, we instantiated our controller in that class. Being that it is the top most object in the display list, it will receive all events bubbling up from the views. The controllers basically listen to the class passed into their constructor (ie. new Controller(this) ), and the handlers within them interact with the models, or web services.

Here is an example of our view class:

 
package views {
 
import flash.display.Sprite;
import flash.events.MouseEvent;
 
import events.CommandEvent;
import events.HelloModelEvent;
 
import model.ModelLocator;
 
public class Main extends Sprite {
 
	/* --------------------------------------- */
	/* --( CONSTRUCTOR )-- */
	/* --------------------------------------- */
 
	public function Main()
	{
		super();
		_addListeners();
		_setupBindings();
		_setupChildren();
	}
 
	/* --------------------------------------- */
	/* --( PROTECTED METHODS )-- */
	/* --------------------------------------- */
 
    /**
     *  Setup initial state of children
     */
    protected function _setupChildren():void
    {
        // This is an example of using the global config object
        helloMessage_txt.text = HelloWorld.config.data.defaultMessage;
    }
 
    /**
     *  Add event listeners to objects
     */
	protected function _addListeners():void
	{
	    submit.addEventListener(MouseEvent.CLICK, _onSubmitClick);
	}
 
	/**
	 *  Setup model bindings
	 */
	protected function _setupBindings():void
	{
	    ModelLocator.instance.helloModel.addEventListener(
	        HelloModelEvent.MESSAGE_UPDATED,
	        _onMessageUpdate
	    )
	}
 
	/**
	 *  Submit button event handler
	 */
	protected function _onSubmitClick(event:MouseEvent):void
	{
	    if (textInput.text.length > 0)
	    {
	        dispatchEvent(new CommandEvent(CommandEvent.SUBMIT_INPUT, {
    	        message: textInput.text
    	    }))
	    }
	}
 
	/**
	 *  Model Binding Handler: When helloMessage is update in model
	 */
	protected function _onMessageUpdate(event:HelloModelEvent):void
	{
	    helloMessage_txt.text = event.message;
	}
 
}
 
}
 

You'll notice that when our submit button is pressed an event is dispatched which will then bubble up to the controller

Controllers

You can think of Controllers in this framework as little functionality branches of the display tree. They listen to events coming from the DisplayObject they are instantiated from, and any of that DisplayObject's children. This allows you to place the controller wherever you need to in the application architecture to create an easy to read, modularized application.

Here is a controller:

 
package control {
 
import flash.display.DisplayObject;
 
import com.gn.mvc2.Controller;
 
import events.CommandEvent;
 
import model.ModelLocator;
import model.HelloModel;
 
public class HelloController extends Controller {
 
	/* --------------------------------------- */
	/* --( PROTECTED PROPERTIES )-- */
	/* --------------------------------------- */
 
    // Models used by controller
	protected var _helloModel:HelloModel;
 
	/* --------------------------------------- */
	/* --( CONSTRUCTOR )-- */
	/* --------------------------------------- */
 
	public function HelloController(parent:DisplayObject)
	{
		super(parent);
 
		_helloModel = ModelLocator.instance.helloModel;
 
		_setupCommands();
	}
 
	/* --------------------------------------- */
	/* --( PROTECTED METHODS )-- */
	/* --------------------------------------- */
 
    /**
     *  Setup controller commands
     */
	protected function _setupCommands():void
	{
	    addListener(CommandEvent.SUBMIT_INPUT, _onSubmitInput);
	}
 
	/**
	 *  On SUBMIT_INPUT Command Event
	 */
	protected function _onSubmitInput(event:CommandEvent):void
	{
	    if (event.data.message)
	    {
	        _helloModel.helloMessage = event.data.message;
	    }
	}
 
}
 
}
 

To elaborate on this, say you have an application that is made up of 5 different "sections". Each section is its own View class. You could make it so that each one of those view classes instantiates it's own controller, and that controller only handles events/commands coming from that View and its children

This is handled by passing the parent display object in as a property of the constructor and storing it as a property of the Controller. I then run an init function (called _setupCommands in the above example) which sets up the listeners on the parent DisplayObject

In the above example, I have abstracted the storage of the parent DisplayObject into a property of a Class called Controller, which I extend all my controllers from. It has a helper function called 'addListener' which simply adds a listener to the parent DisplayObject

Models

Models are still dirt simple. They are basic classes that extend EventDispatcher and are used to hold state and data for the application. When Models are updated they should fire off an event that tells anyone listening that they have been updated.

The main difference is that we now implement a ModelLocator pattern (borrowed from Cairngorm and other such frameworks) to instantiate and gain access to the models from the views and controllers.

Here is an example of a ModelLocator:

 
package model {
 
import flash.events.EventDispatcher;
 
public class ModelLocator extends EventDispatcher {
 
	/* --------------------------------------- */
	/* --( PUBLIC PROPERTIES )-- */
	/* --------------------------------------- */
 
	// Models
	public var helloModel:HelloModel = new HelloModel();
 
	/* --------------------------------------- */
	/* --( STATIC PROPERTIES )-- */
	/* --------------------------------------- */
 
	private static var _instance:ModelLocator;
 
	/* --------------------------------------- */
	/* --( STATIC READ-ONLY PROPERTIES )-- */
	/* --------------------------------------- */
 
	public static function get instance():ModelLocator
	{
		return initialize();
	}
 
	/* --------------------------------------- */
	/* --( STATIC PUBLIC METHODS )-- */
	/* --------------------------------------- */
 
	public static function initialize():ModelLocator
	{
		if (_instance == null){
			_instance = new ModelLocator();
		}
		return _instance;
	}
 
	/* --------------------------------------- */
	/* --( CONSTRUCTOR )-- */
	/* --------------------------------------- */
 
	public function ModelLocator()
	{
		super();
		if( _instance != null ) throw new Error( "Error:ModelLocator already initialised." );
		if( _instance == null ) _instance = this;
	}
 
}
 
}
 

The ModelLocator uses the singleton pattern so that these models can be accessed from anywhere in the application. The Models themselves are created as properties of the ModelLocator, and are instantiated at the same time the ModelLocator instance is instantiated.

Here is a typical Model:

 
package model {
 
import flash.events.EventDispatcher;
 
import events.HelloModelEvent;
 
public class HelloModel extends EventDispatcher {
 
	/* --------------------------------------- */
	/* --( READ/WRITE PROPERTIES )-- */
	/* --------------------------------------- */
 
	public function get helloMessage():String{return __helloMessage;}
	public function set helloMessage(value:String):void 
	{
	    __helloMessage = value;
	    var modelEvent:HelloModelEvent = new HelloModelEvent(
	        HelloModelEvent.MESSAGE_UPDATED
	    );
	    modelEvent.message = __helloMessage;
	    dispatchEvent(modelEvent);
	}
	private var __helloMessage:String
 
	/* --------------------------------------- */
	/* --( CONSTRUCTOR )-- */
	/* --------------------------------------- */
 
	public function HelloModel()
	{
		super();
	}
 
}
 
}
 

Wrap it all up

That is the basics of how I am implementing the MVC micro-architecture in my projects these days. So far it has covered all of the bases for the projects I work on. I'm interested to know how you guys implement what I've detailed, or any other implementations you may have yourself.

The source code for the HelloWorld application that I used as examples for this post can be downloaded here:

Download HelloWorld Source

**SPECIAL NOTE** - This HelloWorld App will not compile. It was built with some dependencies on a framework that we use here at work, which I am not able to publish. None of these classes change the the architecture of this MVC implementation, and it would be easy enough to remove these and everything would function the same. I left these in to provide some clues as to ways you would be able to implement global variables using static properties in the DocClass, and how we handle listening for preloaders and setting up config objects. I may not be able to give you any code for these, but I can answer questions if any of you have them.

Share |

Comments

Kevin :: April 16th, 2010
Hello,
Just wrote a comment, but I guess it needs to be approved before it is shown here.

In the meantime, I have run across an issue. You say that we can remove references to the dependency classes without a problem. But it looks like the Controller class that HelloController is extending has an important function:
addListener(CommandEvent.SUBMIT_INPUT, _onSubmitInput);

Any chance you can shed some light on what this function does?

Thanks!
kevin :: April 16th, 2010
I see now that there is no comment approval system, so my first comment must have just gotten lost.

Basically, it said, "thanks for the great blog posts about Flash and MVC"

:-)
Adam Duro :: April 19th, 2010
@kevin: The addListener function is really just a convenience function that adds a listener to the display object that is passed in when the controller is constructed. You could easily replace this by storing the display object in a property of the controller, and then just use the standard object.addEventListener method. I see now that this is a little under explained in the post. I'll clear that up.
Adam Duro :: April 19th, 2010
@kevin: And thanks for the kind words. I'm glad you appreciate the post. I encourage you to Digg the post or share it with your fellow developers if you think it would help others.
Danyal :: April 21st, 2010
I think that overall I prefer your last implementation of MVC, which was simple and pleasant. I used it successfully on some projects.

I'm happy with controllers and views having links to models, and think that doing that is a more fine-grained approach to maintaining them than using a model locator, which is a humongous global variable with a hollywood name.

I like the ability to have new controllers in the new approach, but the only functionality they seem to add here is the ability to neaten your code by separating it into multiple classes. This could have been done in other ways anyway, without calling all of the other classes controllers. The controllers here do not add the same benefits as multiple controllers do in other scenarios, such as server-side web development, since your inputs are events rather than URLs and arguments.

Also I don't know why you needed to export the views from the FLA. Why is not enough just to extend from Sprite, MovieClip or DisplayObjectContainer in code?

All this said, thanks very much for posting this. Keep posting. :) It made me think and it may be that if you reply to my comment it will prove me wrong on several counts and help broaden my knowledge of MVC.

Adam Duro :: June 3rd, 2010
@Danyal: You are right that the previous approach was simpler. Being simple, it quickly becomes cumbersome when when working on large projects, with more than one developer. As well, since the controllers, models and views were so tightly coupled, it made the code you created less reusable.

For example, if you have a view, that makes lots of direct calls to a controller, and then want to re-use that view, but change the way the controller is structured, you end up having to strip out all of those direct calls. Where as, if the view is simply firing off events, and the controller is listening for those events to bubble up the display list, you can completely change the way the Controller is structured, it's business logic, whatever. The view is completely decoupled from the controller. The view doesn't even need to know that a controller exists, or what it's called. It is simply firing off events, that some other class listens for.

In regards to using the Export feature of the IDE, this just makes more sense for so many reasons. Think of it like this, when an object is created and added to the stage by the IDE, that object, and all it's functionality should be immediately available. Well, using the decorator pattern that the last approach used, that's not the case. Some other line of code somewhere in the app has to add the graphical parts of the object to the decorator. What I ran into with this is:

Say you have parts of the object/view that are not supposed to be seen in the initial display state of the object. Or, you need to programmatically decide based on some other data in the app what is shown. The problem with a decorator is that the constructor for the asset package has already ran (and may already have been added to stage), when the constructor for the decorator runs. So even if you change the display state in the decorators constructor, there may be enough of a delay between the two objects constructing that you see a frame of assets that shouldn't be seen for the current state.

This approch encourages componentization. Since starting to use this approch, I have found that more and more, them components I use for one project, can easily be moved to another project without doing anything more than reskinning in the IDE.

Another benefit to loose coupling is it allows multiple developers to work together without having this know the exact API's if the Controllers and Models. So you can have one developer working on the view code, firing off events when interactions occur, while another developer is building the controller logic, which gets triggered by those events.

I hope that gives you some more clairty on why I've moved towards this approch to MVC. The transition came after writing dozens and dozens of apps, and hundreds of thousands lines of code, so it's sometimes difficult to explain all the reasons why I find this MVC structure more reliable and favorable.
Lesrae :: June 4th, 2010
Hi i'm new to MVC and i find your method really insightful, just a few loose ends i cannot seem to get my head around the controller, your HelloController extends Controller, but what should the extended controller do or look like.
Danyal :: June 9th, 2010
Thanks for your reply Adam - I appreciate it. I'll give it a go next time I start a project.

I think that your approach probably incorporates solutions to things you have encountered while developing over time, which I can't imagine unless I actually have a go at incorporating it myself. So I'll do that and I'm sure the reasons will become clearer to me. At the moment I'm probably having a failure of imagination.
Adam Duro :: June 9th, 2010
@Lesrae: The Controller base class is really simple. All it does is make two helper functions available, and hold onto reference to the parent DisplayObject that gets passed into the contstructor.

Those two helper functions are addListener and removeListener which basically add and remove a listener to the parent DisplayObject that is passed into the constructor. The method signatures are identical to the the addEventListener and removeEventListener methods in the Flash API.

So rather than having to type 'parent.addEventListener' or 'parent.removeEventListener' for every command, I can just do 'addListener' and 'removeListener'. Saves the fingers some typing :)
Broady :: November 25th, 2010
What about PureMVC?
Jan :: January 24th, 2011
Actually the class Controller (com.gn.mvc2.Controller) is not part of the download archive you share here so you cannot complite the example - would you mind adding it to the download archive?
Other than that great work - I'm currently working this example through, its my first MVC attempt and having found several MVC examples in the web this one makes the best impression!
Thank you!
Jan :: January 24th, 2011
Also the class "App" and "com.gn.mvc2.Preloader" is not part of the archive...
Adam Duro :: February 1st, 2011
@Jan: As mentioned in the note at the end of the post, those classes are proprietary classes that are part of the framework we use here at work. I have yet to receive permission to post those classes up here for the world to use, but as soon as I can I will. However, keep in mind, neither are required to get the example above working. The App class is pretty much empty, and could be omitted and just turned into a MovieClip or Sprite, and the Controller class is also pretty bare bones and could be replaced with a generic EventDispatcher. I've updated the example code to reflect this as much as possible. The Config and preloader classes will need to be written by you for the same reason. The updated example code has some comments regarding this that may or may not be helpful. Sorry I cannot be more specifically helpful about those items, but my company has not decided to release any classes, and I am only able to give a rough overview of the concepts used.
bruno :: February 10th, 2011
Your post is great, but I am thinking about the idea of using MVC for most ActionScript projects. It might be like using a bazooka to kill a fly. Or maybe I just didn't understand it correctly.
Let me pick an example: an image view. This ImageView would have a progress bar and a container for a bitmap. When ImageView is ready to load the bitmap it would fire an event to ImageController asking for it to load the image. ImageController would then try to pass the progress information somehow (?) to the ImageView. When image is completely loaded, ImageController would tell ImageModel to store the data and tell ImageView that it is ready to show.
Is that correct? If so, wouldn't it be much easier to just add progress and complete listeners right inside ImageView?
I appreciate if you can help me understand that.
rascalpants :: September 12th, 2011
I completely agree with loose coupling, but there really is no need to de-couple the view from the controller, and all MVC triad discussions I have read talk about specifically coupling the view to the controller, because of their direct connection. The custom event approach seems interesting, but might be overkill on the coupling issue.

I think breaking up your components into smaller chunks and having an MVC for each component might be a better option if you are worried about reuse. But I have seen this go way too far to the point where there is more "structure code" in a class than "action code".

rp
FlexFlashApps :: September 25th, 2011
import com.gn.mvc2.App;
import com.gn.mvc2.Config;

I am interested to know about these two classes which you not discussed here. Will you please attached in your example file?
Or even show the classes so that I can able you run this approach.

Thank You

Regards

FFA
Howard :: November 10th, 2011
It would be great if you could provide a clean example without all the external dependencies.

Leave a Comment