AS3 PureMVC Review
The entry is intended to be a review of the basic usage of the AS3 PureMVC framework not an explanation of how to implement it. The PureMVC website has awesome documentation for those unfamiliar with how to use the framework. I am therefore not explaining too much of the why but rather focusing on the how.
This example will create a simple application with a view consisting of two panels: one that updates the model and one that reflects the model.
DOWNLOAD SOURCE
Folder Structure
I begin by creating the folder structure of the framework in Flex. At this time you also what to ensure that the PureMVC bin is in your projects build path.

Creating the ApplicationFacade and Starting it Up
The ApplicationFacade is responsible for:
- 1. Defining the notification constants, these are names of the notification which will be mapped to the commands in order to call them as well as used to update the view.
- 2. Actually mapping the command constants to the commands, this is done by overriding the initializeController method and initializing and registering each command.
- 3. Providing a way to startup your application, this is done with a singleton method which returns an instance of the ApplicationFacade, and a startup method which does just that startup your app.
ApplicationFacade.as
package com.randall.puremvcbones
{
import com.randall.puremvcbones.controller.*;
import org.puremvc.as3.interfaces.IFacade;
import org.puremvc.as3.patterns.facade.Facade;
public class ApplicationFacade extends Facade implements IFacade
{
public static var instance:ApplicationFacade;
//notifications
public static const START_UP:String = "startup";
public static const ADD_NAME:String = "addname";
public static const UPDATE_NAMES:String = "updatenames";
//singleton
public static function getInstance():ApplicationFacade
{
if (instance == null)
instance = new ApplicationFacade();
return instance as ApplicationFacade;
}
//initializes commands
override protected function initializeController():void
{
super.initializeController();
registerCommand(START_UP,StartUpCommand);
registerCommand(ADD_NAME,AddNameCommand);
}
//calls startup command
public function startUp(app:PureMVCBones):void
{
sendNotification(START_UP,app);
}
}
}
PureMVCBones.mxml (main MXML class)
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:components="com.randall.puremvcbones.view.components.*"
layout="absolute" creationComplete="init()"
width="527" height="319">
<mx:Script>
<![CDATA[
import com.randall.puremvcbones.ApplicationFacade;
private var facade:ApplicationFacade = ApplicationFacade.getInstance();
private function init():void
{
facade.startUp(this);
}
]]>
</mx:Script>
<components:AddNamePanel id="addNamesPanel" x="10" y="10">
</components:AddNamePanel>
<components:NamesPanel id="namesPanel" x="278" y="10">
</components:NamesPanel>
</mx:Application>
Creating the StartUpCommand
The next step is created a StartUpCommand class. And then send a notification to call this command from the ApplicationFacade on when its startup method is called. Like all commands the StartUpCommand is located in the controller package. For this example I am created this class by extending the SimpleCommand class, and both register the applicationmediator and all of the applications proxies here. You may want to extend the MacroCommand class and separate these two tasks for your application. A reference to the application itself is sent to the startUpCommand, and then from there to the ApplicationMediator instance.
StartUpCommand.as
package com.randall.puremvcbones.controller
{
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.command.SimpleCommand;
public class StartUpCommand extends SimpleCommand
{
override public function execute(note:INotification):void
{
//register proxies
facade.registerProxy(new NamesProxy());
//register mediators
var app:PureMVCBones = note.getBody() as PureMVCBones;
facade.registerMediator(new ApplicationMediator(app));
}
}
}
The Model: Creating a Proxy and its Value Object
The model portion of the application is stored in proxy objects. My application will have only one named NamesProxy. Creating a Proxy involves setting its const NAME, creating and assigning its value object property, and adding any methods needed to alter that proxy’s value object data. For the sake of simplicity I only am storing an array named names.
NamesProxy.as
package com.randall.puremvcbones.model
{
import com.randall.puremvcbones.model.vo.NamesVO;
import org.puremvc.as3.interfaces.IProxy;
import org.puremvc.as3.patterns.proxy.Proxy;
public class NamesProxy extends Proxy implements IProxy
{
public static const NAME:String = "NamesProxy";
public function NamesProxy()
{
super(NAME, new NamesVO());
}
//adds a name to the names array
public function addName(name:String):void
{
getVO().names.push(name);
}
//returns names array
public function get names():Array
{
return getVO().names;
}
//returns NamesVO reference
private function getVO():NamesVO
{
return data as NamesVO;
}
}
}
NamesVO.as
package com.randall.puremvcbones.model.vo
{
[Bindable]
public class NamesVO
{
public var names:Array = [];
}
}
Connect the View to the Application: The Application Meditator Registers the Other Mediators
The mediators responsibility is to connect the view components to the application will keeping a loose coupling between the two. The view component knows nothing about the application and its mediator only knows what the component exposes to it.
All this mediator does is listener for its viewComponent the AddNamePanel to dispatch a ADD_NAME event. It then gets the value of the new name from the view and sends it as an argument of the ADD_NAME notification which calls the AddNameCommand.
AddNamePanelMediator.as
package com.randall.puremvcbones.view
{
import com.randall.puremvcbones.ApplicationFacade;
import com.randall.puremvcbones.view.components.AddNamePanel;
import flash.events.Event;
import org.puremvc.as3.interfaces.IMediator;
import org.puremvc.as3.patterns.mediator.Mediator;
public class AddNamePanelMediator extends Mediator implements IMediator
{
public static const NAME:String = "AddNamePanelMediator";
public function AddNamePanelMediator(viewComp:AddNamePanel):void
{
super(NAME,viewComp);
viewComponent.addEventListener(addNamePanel.ADD_NAME, onAddName);
}
//called when addNamesPanel Dispatches the ADD_NAME event
private function onAddName(e:Event):void
{
var note:Object = new Object();
note.name = addNamePanel.newName_txt.text;
facade.sendNotification(ApplicationFacade.ADD_NAME,note);
addNamePanel.clear();
}
//returns the AddNamePanel
private function get addNamePanel():AddNamePanel
{
return viewComponent as AddNamePanel;
}
}
}
AddNamePanel.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="260" height="122" title="ADD NAME">
<mx:Script>
<![CDATA[
public const ADD_NAME:String = "addname";
//clears form
public function clear():void
{
newName_txt.text = "";
}
]]>
</mx:Script>
<mx:Form x="0" y="0" width="240" height="82" id="addNameForm" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:FormItem label="name:" >
<mx:TextInput id="newName_txt"/>
</mx:FormItem>
<mx:FormItem>
<mx:Button label="Add" click="dispatchEvent(new Event(ADD_NAME,true))"/>
</mx:FormItem>
</mx:Form>
</mx:Panel>
The NamesPanel Mediator has overridden the listNotificationsInterests so that it listenes for the UPDATE_NAMES notification which will be sent from the AddNameCommand after it has updated the model. When the framework sees that this mediator is listening for the UPDATE_NAMES notification it calls its handleNotification methods sending the name of the notification as a parameter.
NamesPanelMediator.as
package com.randall.puremvcbones.view
{
import com.randall.puremvcbones.ApplicationFacade;
import com.randall.puremvcbones.model.NamesProxy;
import com.randall.puremvcbones.view.components.NamesPanel;
import org.puremvc.as3.interfaces.IMediator;
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.mediator.Mediator;
public class NamesPanelMediator extends Mediator implements IMediator
{
public static const NAME:String = "NamesPanelMediator";
public function NamesPanelMediator(viewComp:NamesPanel):void
{
super(NAME,viewComp);
}
override public function listNotificationInterests():Array
{
return [ApplicationFacade.UPDATE_NAMES];
}
override public function handleNotification(note:INotification):void
{
switch(note.getName())
{
case ApplicationFacade.UPDATE_NAMES:
updateNames();
break;
}
}
//updates names array from model
private function updateNames():void
{
var namesProxy:NamesProxy = facade.retrieveProxy("NamesProxy") as NamesProxy;
namesPanel.names = namesProxy.names;
}
private function get namesPanel():NamesPanel
{
return viewComponent as NamesPanel;
}
}
}
NamesPanel.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="240" height="300" title="NAMES">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable] private var _names:Array = [];
//updates the _name array
public function set names(names:Array):void
{
_names = [];
_names = names;
}
]]>
</mx:Script>
<mx:List dataProvider="{_names}" width="100%" height="100%">
</mx:List>
</mx:Panel>
Add Commands
The AddNameCommand class mentioned a moment ago is now added to controller directory.
package com.randall.puremvcbones.controller
{
import com.randall.puremvcbones.ApplicationFacade;
import com.randall.puremvcbones.model.NamesProxy;
import org.puremvc.as3.interfaces.INotification;
import org.puremvc.as3.patterns.command.SimpleCommand;
public class AddNameCommand extends SimpleCommand
{
//adds name to model
//then send the UPDATE_NAMES notification to update the view
override public function execute(note:INotification):void
{
var namesProxy:NamesProxy = facade.retrieveProxy("NamesProxy") as NamesProxy;
namesProxy.addName(note.getBody().name);
facade.sendNotification(ApplicationFacade.UPDATE_NAMES);
}
}
}
Summary
What happens when the user enters a name and clicks the add button:
- The AddNamePanel Dispatches the ADD_NAME event.
- The AddNamePanelMeditator which was listening for this event gets the value of this new name and sends it as a parameter of the ADD_NAME notification.
- The AddNameCommand which was listening for the ADD_NAME notification fires its execute method which gets a reference to the NamesProxy and tells it to add the name value to its Value Object. It then sends the UPDATE_NAMES notification.
- The NamesPanelMediator which was listening for the UPDATE_NAMES notification then updates the NamesPanel reflecting the changes to the model.
This is a very simple application, where using a framework is really not needed. However for more complex applications the PureMVC or at least using a MVC design pattern is a must. Once you have its foundation setup as demonstrating above adding new commands, components, and services to the application becomes a very quick process. Also modularization provided by the loose coupling application elements makes maintenance and upgrading much easier a process.