Simple MVC with ActionScript 3
-------------------
UPDATED - I have posted a new approach to MVC which can be read here: Simple MVC with ActionScript 3 - An Updated Approach. I would still read this article first to get a good idea of this approach, but the new approach is better IMO. Read On......
-------------------
In my search for a framework to implement the MVC pattern in my AS3 projects I found many complex solutions. These solutions seem to be very robust, and are probably good to explore for very large RIA projects. In my case, working in the fast paced world of online advertising, I needed a solution that was a little more basic and flexible.
Most of the projects I work on are small microsites and widgets for ad campaigns, I'm also working inside of the Flash IDE and interacting with designers who don't know much about AS, so be forwarned that the solution I'm using is implemented with that in mind.
Controllers
Controllers were a tough one for me to conceptualize in Actionscript 3, coming from the world of CakePHP and Ruby on Rails. So what helped was to refresh my mind with the core idea of a controller.
According to Wikipedia a controller does the following:
- A controller handles the input event from the user interface, often via a registered handler or callback.
- The controller notifies the model of the user action, possibly resulting in a change in the model's state. (e.g. controller updates user's Shopping cart).
The easiest way for me to implement this was using the Document Class. Here is an example:
package com.gn.controllers { /* Import Classes */ import flash.display.*; import flash.events.*; import com.gn.views.VideoView; import com.gn.views.QuizView; import com.gn.views.ThumbnailView; import com.gn.models.XMLDataModel; import com.gn.events.XMLDataEvent; import com.gn.models.QuizModel; import com.gn.events.QuizEvent; /** * Document Class: Quiz Based Video Player * * @langversion ActionScript 3 * @playerversion Flash 9.0.0 * * @author Adam.Duro * @since 10.10.2008 */ public class AppController extends MovieClip { /* Public Properties */ public var videoView:VideoView; public var thumbnailView:ThumbnailView; public var quizView:QuizView; public var xmlDataModel:XMLDataModel; public var quizModel:QuizModel; /** * Constructor * * @private */ public function AppController(){ super(); init(); } /** * Initialize Application * * @private */ private function init():void { /* Initialize Models */ xmlDataModel = new XMLDataModel(); quizModel = new QuizModel(); /* Initialize Views */ videoView = new VideoView(vidContainer_mc, this); thumbnailView = new ThumbnailView(thumbContainer_mc, this); quizView = new QuizView(quizContainer_mc, this); } /** * Start Quiz Engine * * @private */ public function startQuizEngine():void { var xmlData:XML = xmlDataModel.data.titles.title[0]; quizModel.loadQuiz(0, xmlData); } public function loadMovie(index:Number, xmlData:XML):void { quizModel.loadQuiz(index, xmlData); } /** * Unlock Movie * * @private */ public function unlockMovie():void { quizModel.unlockMovie(); } } }
So this class is responsible for instantiating the models, and view classes. It then passes reference to itself and the models along to the view classes. It is also is used to process user interaction from the views. If a user clicks a button that needs to interact with a model, it goes through the Controller to do that.
Models
Models hold data. They also interact with external web services and databases. I think of them as my data/state layer in an ActionScript project. In my simple MVC framework, Models are subclasses of the EventDispatcher class. For each Model I also create a custom Event class. Here is an example:
Model
package com.gn.models { /* Import Classes */ import flash.net.*; import flash.events.*; import com.gn.events.XMLDataEvent; /** * XML Data Model * * @langversion ActionScript 3 * @playerversion Flash 9.0.0 * * @author Adam.Duro * @since 10.10.2008 */ public class XMLDataModel extends EventDispatcher { /* Static Constants */ static const DATA_URL:String = 'flash/xml/datasheet.xml'; /* Private Properties */ private var _XMLLoader:URLLoader; /* Public Properties */ public var data:XML; /** * Constructor * * @private */ public function XMLDataModel(){ init(); } /** * Initialize Model * * @private */ private function init():void { _XMLLoader = new URLLoader(); _XMLLoader.addEventListener(Event.COMPLETE, xmlLoaded); _XMLLoader.load(new URLRequest(DATA_URL)); } /** * Listener: XML Finished Loading * * @private */ private function xmlLoaded(e:Event):void { data = new XML(e.target.data); dispatchEvent(new XMLDataEvent(XMLDataEvent.XML_LOADED)); } } }
Model Event Class
package com.gn.events { /* Import Classes */ import flash.events.Event; /** * Event: XML Data Event * * @langversion ActionScript 3 * @playerversion Flash 9.0.0 * * @author Adam.Duro * @since 10.10.2008 */ public class XMLDataEvent extends Event { /* Static Constants */ public static const XML_LOADED:String = "onXMLLoaded"; /** * Constructor * * @private */ public function XMLDataEvent( type:String, bubbles:Boolean=true, cancelable:Boolean=false ){ super(type, bubbles, cancelable); } /** * Override: Event Clone Function * * @inheritDoc */ override public function clone() : Event { return new XMLDataEvent(type, bubbles, cancelable); } } }
When the model is loaded or modified, it dispatches an event from its associated ModelEvent. Any views that are listening for updates on that model receive those events.
Views
Views handle the presentation layer of the application. They are the GUI. In my case, I am often using Flash files that have been prepared by a designer. Thus, most of them consist of MovieClips within MovieClips, so on and so forth. Most of my views are wrapper classes for these MovieClips.
Rather than use the Linkage feature in the Flash IDE to bind these classes to their associated MovieClips, I use a private var to store reference to the MovieClip. That reference is passed in via the controller, when the view is instantiated. Here is that again for reference:
/* Initialize Views */ videoView = new VideoView(vidContainer_mc, this); thumbnailView = new ThumbnailView(thumbContainer_mc, this); quizView = new QuizView(quizContainer_mc, this);
The first argument is the container MovieClip, and the second is a reference to the controller so that the view can make calls to it when a user interaction occurs. Here is an example of a full view. (Be aware, this is going to be long):
package com.gn.views { /* Import Classes */ import flash.display.*; import flash.events.*; import flash.text.*; import com.gn.controllers.AppController; import com.gn.events.QuizEvent; import com.gn.objects.Answer; import gs.TweenLite; /** * View: Quiz View * * @langversion ActionScript 3 * @playerversion Flash 9.0.0 * * @author Adam.Duro * @since 10.10.2008 */ public class QuizView extends MovieClip { /* Private Properties */ private var _quizContainer:MovieClip; private var _AppController:AppController; /** * Constructor * * @private */ public function QuizView(clip:MovieClip, controller:AppController){ _quizContainer = clip; _AppController = controller init(); } /** * Initialize View * * @private */ private function init():void { /* Add Event Listeners */ _AppController.quizModel.addEventListener(QuizEvent.QUIZ_LOADED, onQuizLoad); _AppController.quizModel.addEventListener(QuizEvent.MOVIE_UNLOCKED, onMovieUnlock); } /** * Listener: On Quiz Load * * @private */ private function onQuizLoad(e:QuizEvent):void { /* Show Quiz Container if Hidden */ if (!_quizContainer.visible) { TweenLite.to(_quizContainer, 0.5, {autoAlpha: 1}); } /* Populate Question */ _quizContainer.question_txt.text = e.target.currentData.question; _quizContainer.question_txt.autoSize = TextFieldAutoSize.CENTER; /* Position Answers Container */ _quizContainer.answersContainer_mc.y = _quizContainer.question_txt.textHeight + 1; /* Remove Old Answers from a previous Quiz */ if (_quizContainer.answersContainer_mc.numChildren > 0) { for (var h:int = 0; h < _quizContainer.answersContainer_mc.numChildren; i++) { _quizContainer.answersContainer_mc.removeChildAt(h); } } /* Populate Answers for Current Quiz */ for (var i:int = 0; i < e.target.currentData.answers.answer.length(); i++) { var answer:Answer = new Answer(i, e.target.currentData.answers.answer[i]); answer.isCorrect = (e.target.currentData.answers.answer[i].@correct == 1); if (i !== 0) { answer.y = _quizContainer.answersContainer_mc.height + 10; } answer.addEventListener(MouseEvent.CLICK, onAnswerClick); _quizContainer.answersContainer_mc.addChild(answer); } } /** * Listener: On Answer Click * * @private */ private function onAnswerClick(e:MouseEvent):void { if (e.target.isCorrect) { e.target.markRight(_AppController.unlockMovie); } else { e.target.markWrong(); } } /** * Listener: On Movie Unlocked * * @private */ private function onMovieUnlock(e:QuizEvent):void { TweenLite.to(_quizContainer, 0.5, {autoAlpha: 0}); } } }
Hooking Everything Together
So here is a basic over view of the flow an application takes when using this framework:
- The Controller (Document Class) is loaded
- The Controller instantiates the models
- The Controller instantiates the views, passing reference to the MovieClip on stage that contains the views various DisplayObjects
- When a Model is updated it dispatches an Event
- The views are setup to listen for those events, and react accordingly.
- When a user interacts with the app, the view calls a method on the Controller, which either updates the model, or handles the user interaction and reports back to the view.
Wrapping Up
This concludes a very fast paced overview of how one ActionScript developer implements MVC. This by no means is the only way. If you have questions or suggestions, please feel free to leave a comment.


Comments
I am going to add an RSS feed to the blog in the next few days.
i was also searching for a simple mvc exmaple to understand the mvc basics! I´m new to MVC and I felt like you and coded an easy mvc example on my own... :) you can have a look at it on my blog (http://www.spierala.de). Our examples are similar but slightly different. You seem to use the controller to initialize the application. Your controller knows about the view and model. View accesses models data via the controller ( _AppController.quizModel ). I use the View for application initialization - it holds references of model and controller and knows about everything... My Controller is just waiting for to be called by the View and then calls functions of the model... Our Models are nearly the same - both extending Event Dispatcher, which makes sence. Why do you create a custom Event Class? As far as I can see the Event is not containing any special data? You can get the e.target Data also with normal Events. Could you make your project ready for download to have a deeper look at it? Thanks, Florian
According to your architecture you put all your view code into the main document class? Doesn't that force you to put a whole lot of code for different components of the application all in one class? Do you have just one model, one view, and one controller for the entire application? I'm interested to know how you have approached this.
If you'd like, I can email you a zip file of an example application, but at this point I don't have any code that I can publish publicly. I will see if I can put something together to post publicly.
Since writing this article I have moved away from using the DocumentClass as the controller, and started breaking my controllers out as separate classes. When I get a chance I'll publish a new post that outlines the new method I'm using.
http://duromedia.com/blog/rss
Can't wait to see an update to this article with your new approach!
I am just beginning to use the MVC frame work - and can't seem to figur out how make the communication between the different views and the controller.
/Dennis
\nn/
.ore
Nice article. Thank you very much for valuable information.
It would be nice to here from you about your recent attitude to MVC. You've told that you are not using DocumentClass anymore, for example.
Cheers,
h3d0
http://duromedia.com/blog/read/72:simple-mvc-with-actionscript-3---an-updated-approach
Leave a Comment