Home >
Last week we took a look at how to clean up our Twitter client by using Cairngorm to separate the concerns according to the time honored MVC architectural pattern. Now we’re going to see how it feels to work with PureMVC, an ActionScript framework that wants to make it easier to do just that.
PureMVC was created by Cliff Hall, and is maintained in a few different forms by a community of developers.
Notice that PureMVC is an ActionScript framework. More specifically, there is an ActionScript 2 and two ActionScript 3 versions of PureMVC, one regular and one multi-core, which can work with Flex with Modules for instance. That’s one thing that PureMVC makes clear is that it has no dependencies on the Flex Framework, which means that it’s flexible enough to work with Flash projects as well as Flex. In fact PureMVC also has ports in the works or already developed for Java, C#, ColdFusion, Ruby, and others, so it tries to be a language independent take on an MVC framework.
Being framework independent means that unlike Cairngorm, PureMVC doesn’t rely on Flex binding. Not only that, but PureMVC wants to make sure that framework code doesn’t make it outside any of the classes that aren’t clearly framework oriented. This certainly applies to view classes, so PureMVC uses Flash events instead of a framework specific event type. This encourages the development of views as black box components, which encourages a loosely coupled architecture (which is also possible with Cairngorm) as well as a framework independent view (which is not, strictly speaking, possible with Cairngorm).
A little bit about the framework. PureMVC has a Controller, a Model, and a Facade, all singletons. The Controller has an API for sending and receiving Notifications, which are event-like messages that any PureMVC object, except, by convention, Proxies, can subscribe to. Proxies never receive, they only send, Notifications. The Model keeps a set of named Proxy objects, which are responsible for a particular type of data in the model. For instance an application that has User objects would probably have a UserProxy that handled requests for users and managed a collection of users. The Model and Controller are unseen, and only accessed through the Facade. The Facade has an API for working with both the Model and Controller, sending Notifications, looking up Proxies, and so forth. Your application implements the Facade class either by implementing the interface or extending a Facade class that PureMVC provides. That brings up another point about PureMVC. Every part of PureMVC implements an interface, which is also provided so that you could replace any part of PureMVC with your own implementation if you wanted.
Let’s look at a flow of events in a PureMVC application.
When a user interacts with the view, a Flash event is dispatched from the view. A Mediator responsible for that view has already registered for interesting events and when it receives this one, it may do view specific logic. Most likely, it will also send a Notification which any other PureMVC objects may elect to receive. Keep that point in mind - that’s a very loosely coupled architecture. Any number of other Mediators or Commands can listen for a certain notification and do whatever it is they do.
The Controller is what gets called when a Notification is sent, and it tells anyone interested in those types of Notifications that they have some new mail. It’s also possible to register a Command to a certain type of Notification, and if one is found, it’s instantiated and executed. This is typically the case in service calls. The Command’s job is then to get reference to a Proxy through the Facade and ask it to take some appropriate action. The Proxy can either interact directly with some service if necessary, or as is sometimes done, a Cairngorm-style BusinessDelegate can be invoked instead. PureMVC is flexible on this point, and some developers find it desirable to have this service encapsulation for the same reasons as we already described last time - it helps cushion the application from change.
Once the service returns to the Proxy directly or through a Delegate, it sends a Notification, which a Mediator or another Command can receive. If the Mediator listens for that Notification, it’s because something needs to change on the view in response to the new state of the data in the Proxy, and the Mediator is responsible for changing the view. It may do this by wiring data in a data provider directly to a property on the Proxy.
So, a few differences from what we’ve seen with Cairngorm are:
1. No
framework code in the views.
2. No
singleton model, instead a set of Proxies
3. No
Flex Binding, instead all views are encapsulated from the framework by Mediators.
Now that we've heard the dry details, let's take a tour of our app.
PureMVC TwitteRIA
As mentioned, most PureMVC actions go through the Facade we created, ApplicationFacade in this case, and most access to the Facade is through helper methods on Mediator, Proxy, or Command. One time you’ll probably act directly on the Facade instance is at application startup, like we do here:
twitteria_puremvc/src/twitteria_puremvc.mxml #8, 16
creationComplete="facade.startup(this)"
...
private var facade:ApplicationFacade =
ApplicationFacade.getInstance();
We get a reference to the instance of the Facade and call startup, passing the instance of the view, when the application is complete. Let’s go through the startup phase and see what happens there.
twitteria_puremvc/src/com/insideria/twitteria/ApplicationFacade.as
#23-30
override protected function initializeController():void {
super.initializeController();
registerCommand(STARTUP, StartupCommand);
registerCommand(LOG_IN, LogInCommand);
registerCommand(SET_STATUS, SetStatusCommand);
registerCommand(LOAD_TIMELINE, LoadTimelineCommand);
}
First of all, when ApplicationFacade is first instantiated, initializeController is called by the framework. Here we register commands to string constants. Later on, when we send Notifications created with these strings, the commands will be created and executed. For instance, here’s the one for STARTUP.
twitteria_puremvc/src/com/insideria/twitteria/ApplicationFacade.as
#10
public static const STARTUP:String = "startup";
And now when we call startup we send a notification with the STARTUP string, which will cause a StartupCommand to be
executed. When we send the notification we also send along a reference to the
application.
twitteria_puremvc/src/com/insideria/twitteria/ApplicationFacade.as
#31-33
public function startup(app:twitteria_puremvc):void {
sendNotification(STARTUP, app);
}
StartupCommand is a MacroCommand, which means it executes a set of sub-commands which we add in the framework called initializeMacroCommand.
twitteria_puremvc/src/com/insideria/twitteria/controller/StartupCommand.as
#5-10
public class StartupCommand extends MacroCommand {
override protected function initializeMacroCommand():void {
addSubCommand(ModelPrepCommand);
addSubCommand(ViewPrepCommand);
}
}
The ModelPrepCommand sets up the model:
twitteria_puremvc/src/com/insideria/twitteria/controller/ModelPrepCommand.as
#9-16
public class ModelPrepCommand extends SimpleCommand {
override public function execute(note:INotification):void {
facade.registerProxy(new TimelineProxy());
facade.registerProxy(new StatusProxy());
facade.registerProxy(new UserProxy());
}
}
And the ViewPrepCommand sets up the Mediators that control the views.
twitteria_puremvc/src/com/insideria/twitteria/controller/ViewPrepCommand.as
#12-18
public class ViewPrepCommand extends SimpleCommand {
override public function execute(note:INotification):void {
var app:twitteria_puremvc = note.getBody()
as twitteria_puremvc;
facade.registerMediator(
new ApplicationMediator(app)
);
facade.registerMediator(
new LoginViewMediator(app.loginView)
);
facade.registerMediator(
new MainViewMediator(app.mainView)
);
}
}
Let’s analyze those two commands. First of all, note that they both extend SimpleCommand. Doing that gives them access through the ApplicationFacade singleton, which is registered by the framework and provided to all framework classes. That lets the commands register proxies and mediators easily. Registering those guys lets anyone with access to the facade get a reference by name. We’ll look at that in a bit.
One more thing to note is how we get the STARTUP notification passed to each sub-command. Since we attached the reference to the application to the note, we can grab that off the body and get a reference to the view items that each Mediator needs to mediate.
Whew! That was a lot, and it all took place before the user even sees the view. Now that all that is in place we can finally look at logging in.
Logging In
One other big difference in the root application from the Cairngorm version is an omission on the ViewStack.
twitteria_puremvc/src/twitteria_puremvc.mxml #26
<mx:ViewStack id="mainViewStack" bottom="0" left="0" right="0" top="30">
There’s no binding to tell the stack which child to show. To see where the ViewStack is actually controlled, we look at the ApplicationMediator. When the Mediator is created in the ViewPrepCommand and passed the application instance, the constructor keeps that reference.
twitteria_puremvc/src/com/insideria/twitteria/view/ApplicationMediator.as
#15-17
public function ApplicationMediator(viewComponent:twitteria_puremvc) {
super(NAME, viewComponent);
}
Passing the app reference along with the NAME constant sets the viewComponent property for this mediator and also registers with the Facade using NAME, which is an arbitrary string:
twitteria_puremvc/src/com/insideria/twitteria/view/ApplicationMediator.as
#10
public static const NAME:String = "ApplicationMediator";
Using that string lets anyone look the mediator up by that name. It’s also usual to set up a little helper lookup to bring back the viewComponent as a typed object of the type we know it to be:
twitteria_puremvc/src/com/insideria/twitteria/view/ApplicationMediator.as
#33-35
protected function get app():twitteria_puremvc {
return viewComponent as twitteria_puremvc
}
When the ApplicationMediator is registered, a framework method is called to set up the mediator.
twitteria_puremvc/src/com/insideria/twitteria/view/ApplicationMediator.as
#33-35
override public function listNotificationInterests():Array {
return [
ApplicationFacade.VIEW_TIMELINE
];
}
One method the framework calls is listNotificationInterests. This should return a list
of strings that the mediator is interested in receiving. Then, whenever any
notification with those types are sent through the facade, this mediator will
get notified. Notification happens when the framework calls handleNotification.
twitteria_puremvc/src/com/insideria/twitteria/view/ApplicationMediator.as
#25-31
override public function handleNotification(note:INotification):void {
switch (note.getName()) {
case ApplicationFacade.VIEW_TIMELINE:
app.mainViewStack.selectedIndex = MAIN_VIEW;
break;
}
}
This method gets called when any notification that the mediator is interested in gets sent. The usual process is to switch on the notification name and then do the right thing. In this case, when the VIEW_TIMELINE note comes through, we want to switch the ViewStack index to the MAIN_VIEW constant, which just like the Cairngorm implementation and just like the no-framework implementation, points to the right index to show MainView. So that’s how the ApplicationMediator does what it do, as they say. When does the VIEW_TIMELINE notification get sent? Let’s look at the LoginView to see.
twitteria_puremvc/src/com/insideria/twitteria/view/components/LoginView.mxml
#4-16
<mx:Metadata>
[Event(name="login", type="flash.events.Event")]
</mx:Metadata>
<mx:Script>
<![CDATA[
public static const LOGIN:String = 'login';
public function login():void {
dispatchEvent(new Event(LOGIN));
}
]]>
</mx:Script>
The first thing you may notice is that we’re back to dispatching a regular Flash event when enter is pressed on the password field:
twitteria_puremvc/src/com/insideria/twitteria/view/components/LoginView.mxml
#27
enter="login()"
Who’s listening? The LoginViewMediator, which as also set up in the ViewPrepCommand. When the mediator is created, it adds itself as an event listener for this type.
twitteria_puremvc/src/com/insideria/twitteria/view/LoginViewMediator.mxml
#15-19
public function LoginViewMediator(viewComponent:LoginView) {
super(NAME, viewComponent);
view.addEventListener(LoginView.LOGIN, login);
}
So when the view dispatches that event, the mediator’s login method will be called.
twitteria_puremvc/src/com/insideria/twitteria/view/LoginViewMediator.mxml
#15-19
private function login(event:Event):void {
var credentials:Object = {
username:view.usernameText.text,
password:view.passwordText.text
};
sendNotification(ApplicationFacade.LOG_IN, credentials);
}
The login method gets a little ad-hoc object together and sends it along with a notification. We could change that to be a typed object, but for now a hash is good enough. The note name is in the LOG_IN variable on the facade, and if you’ll remember, that was registered to the LoginCommand.
twitteria_puremvc/src/com/insideria/twitteria/controller/LoginCommand.as
#11-19
override public function execute(note:INotification):void {
var userProxy:UserProxy =
facade.retrieveProxy(UserProxy.NAME) as UserProxy;
var timelineProxy:TimelineProxy =
facade.retrieveProxy(TimelineProxy.NAME) as TimelineProxy;
var credentials:Object = note.getBody();
userProxy.username = credentials['username'];
userProxy.password = credentials['password'];
sendNotification(ApplicationFacade.LOAD_TIMELINE);
}
The UserProxy simply stores the username and password; have a look at it if you want. With a more complex login process it would get a little more complex too, but that’s enough for here. Again, notice that the username/password hash is on the body of the Notification. At the end of the command, we send out another notification, signaling that log-in is complete and it’s time to load the timeline.
Loading The Timeline
To load the timeline, we call on the TimelineProxy in the LoadTimelineCommand, which was registered to the LOAD_TIMELINE notification type.
twitteria_puremvc/src/com/insideria/twitteria/controller/LoadTimelineCommand.as
#10-13
override public function execute(note:INotification):void {
var timelineProxy:TimelineProxy =
facade.retrieveProxy(TimelineProxy.NAME) as TimelineProxy;
timelineProxy.reload();
}
The reload method goes like this:
twitteria_puremvc/src/com/insideria/twitteria/model/TimelineProxy.as
#22-25
public function reload():void {
var delegate:TwitterDelegate = new TwitterDelegate(this);
delegate.loadTimeline();
}
Notice that the TimelineProxy implements IResponder, and that I’ve chosen to use the Business Delegate pattern. Nothing from here to the service changed from the Cairngorm implementation except that it’s a Proxy instead of a Command calling the delegate. These choices aren’t mandated by PureMVC, but it’s flexible enough to allow this way of doing things. I’m not sure I like the Proxy being an IResponder because that limits it to only one set of result/fault methods and only one call on the delegate. That could be changed to a more flexible event listener approach, but for now I chose to keep things pretty similar to how they were.
twitteria_puremvc/src/com/insideria/twitteria/model/TimelineProxy.as
#27-32
public function result(result:Object):void {
var stati:Array = result as Array;
currentTweets = new ArrayCollection(stati);
sendNotification(ApplicationFacade.VIEW_TIMELINE);
sendNotification(ApplicationFacade.TIMELINE_LOADED);
}
When the delegate returns to the result method, we fire off two notifications: VIEW_TIMELINE and TIMELINE_LOADED. Why two? One lets the application know to change view states, and the other lets the application know that there’s new data in the TimelineProxy. Those may happen concurrently here, but they don’t always have to. If you remember back a bit, VIEW_TIMELINE was a notification that the ApplicationMediator was interested in. When this notification makes it there, that mediator switches the index in the ViewStack to show the MainView, as you saw above. Who’s interested in the TIMELINE_LOADED notification? That would be the MainViewMediator.
twitteria_puremvc/src/com/insideria/twitteria/view/MainViewMediator.mxml
#33-39
override public function handleNotification(note:INotification):void {
switch (note.getName()) {
case ApplicationFacade.TIMELINE_LOADED:
view.currentTweets = getTimelineProxy().currentTweets;
break;
}
}
When the MainViewMediator gets the TIMELINE_LOADED note, it sets the currentTweets property to the TimelineProxy’s currentTweets. This is how the framework gets around binding the view to the model - the mediator hooks the properties on a proxy directly to the view.
Now the view is on the MainView and the tweets are loaded into the list. It’s time to set the status.
Setting Status
Just as the LoginView dispatches a plain-old-flash-event when you hit enter, the MainView dispatches an event when the status is set by pressing enter in the status field
twitteria_puremvc/src/com/insideria/twitteria/view/components/MainView.mxml
#4-23
<mx:Metadata>
[Event(name="setStatus", type="flash.events.Event")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
public static const SET_STATUS:String = 'setStatus';
[Bindable]
public var currentTweets:ArrayCollection; // <TwitterStatus>
private function setStatus():void {
dispatchEvent(new Event(SET_STATUS));
statusText.clear();
}
]]>
</mx:Script>
The MainViewMediator has an event listener, setStatus, that handles that event.
twitteria_puremvc/src/com/insideria/twitteria/view/MainViewMediator.mxml
#41-43 (formatted)
private function setStatus(event:Event):void {
sendNotification(
ApplicationFacade.SET_STATUS,
view.statusText.text
);
}
The SET_STATUS notification corresponds to the SetStatusCommand.
twitteria_puremvc/src/com/insideria/twitteria/controller/SetStatusCommand.as
#10-13 (formatted)
override public function execute(note:INotification):void {
var statusProxy:StatusProxy =
facade.retrieveProxy(StatusProxy.NAME) as StatusProxy;
statusProxy.setStatus(note.getBody() as String);
}
The StatusProxy calls out to the TwitterDelegate just like the Cairngorm SetStatusCommand used to.
twitteria_puremvc/src/com/insideria/twitteria/model/StatusProxy.as
#19-22
public function setStatus(statusText:String):void {
var delegate:TwitterDelegate = new TwitterDelegate(this);
delegate.setStatus(statusText);
}
And when the result comes back, it sends out just the LOAD_TIMELINE notification, which as we saw above, kicks off the timeline load sequence.
twitteria_puremvc/src/com/insideria/twitteria/model/StatusProxy.as
#24-26
public function result(result:Object):void {
sendNotification(ApplicationFacade.LOAD_TIMELINE);
}
Play around with the application, in dummy data mode and out. Kick it around and see if you can think of anything to add. There are a lot of moving parts to PureMVC, so think through the flows a bit again, just to make sure you have an idea of what each piece does.
Next Up
Well folks, that's PureMVC. Some key parts again are that nothing from the framework enters the view components (except once, in the root application), the model is broken up into many Proxies, views that need framework action are acted upon by Mediators, Commands generally tell a Proxy to do something, and communication between all the parts is done through Notifications.
It may strike you that you have to do a lot by hand to get
everything working together with PureMVC. If you'd like your framework to take
a little bit more initiative for you, then you might want to see what the Swiz
way of doing things looks like. That's just what we're going to do next time,
so watch this space!
Read the rest of FrameworkQuest 2008:
- Part 1 - Introduction
- Part 2 - Get Control with Cairngorm
- Part 3 - Framework Agnostic Views with PureMVC
- Part 4 - loC with Swiz
- Part 5 - Mate, the Pure MXML Framework
- Part 6 - The Exciting Conclusion




Facebook Application Development
Hi Tony,
I'm very much enjoying your series. I have one small comment:
..as well as a framework independent view (which is not, strictly speaking, possible with Cairngorm).
It is entirely possible to keep framework code out of views in Cairngorm applications, and that's what we definitely advocate. We typically dispatch our CairngormEvent from our presentation model objects, which are quite similar to the PureMVC Proxies. Views should not contain any framework dependent code.
I'd also clarify that Cairngorm does not have singleton models, but does provide a singleton ModelLocator as the root of your domain model (though there's nothing forcing you to use it). We'd typically not access the ModelLocator directly from the views, and definitely not deep within a component hierarchy.
Looking forward to hearing your thoughts on Swiz.
@Alistair - Again it seems like my style of working with Cairngorm is outdated. All the examples I learned from 3 or so years ago looked more like my code here, so it seems like I haven't kept up to speed with new thought in the framework. Can you give any other resources (besides the ones you already have, e.g. http://weblogs.macromedia.com/amcleod/archives/2008/12/max_milan_-_fle.html) out there on the web to the readers here showing a better way to learn about Cairngorm?
Great series Tony. As a relatively new user to Flex and Cairngorm it's very useful to have a basic shootout of the major frameworks available and I think you're getting the job done.
Alistair, it's also interesting to hear your thoughts on Cairngorm and it's uses. Though, one major struggle I (and it seems many others) have had to date is finding the best ways to use Cairngorm with practical examples.
I read through the presentation you posted last week which looks great but is less effective without you speaking along with it and seeing your demos :)
Any chance you could consider giving this presentation on AdobeTV? I feel there's a lack of content there for people who are interested and trying to build larger applications 'the right way'.
Some aspects I'm particularly interested in are examples of best use of the Presentation Model, and the best way to notify Views or other Components when a service returns a Result/Fault (so as to trigger more events as an example).
Cheers
Great series Tony, there have been so many Which Framework is Best conversations/flamewars on various forums/blogs, so this is will be a valuable resource.
A few comments:
And finally, a shameless, but related plug. I released a generator for PureMVC a little over a week ago. You can find a blog post about it at http://bit.ly/puremvc-gen NOTE: There is a bug right now that is causing some problems in Windows, but I hope to have it fixed this week.
Apparently making the Command an IResponder isn't best practice, but I'm not entirely satisfied with throwing it on the Proxy quite yet...
@Greg Jastrab: Indeed. Commands in PureMVC are stateless, they should execute and die immediately. See: http://puremvc.org/content/view/80/188/
Also, a Command that does nothing more than retrieve a Proxy and call a method on it, is not really recommended either. See: http://puremvc.org/content/view/76/188/
@Greg - cool.. thanks for the link, and thanks for the correction. Re: binding, If you're using Actionscript binding you may as well use curly brace syntax, though, because the point of not using binding is to not have the Flex dependency. Again, I think that's a small win at best.
Thanks to everyone providing corrections to my code or my method of using these frameworks if I'm off a bit - again, I'm mostly used to using Cairngorm, so I'm not trying to show my expertise in these frameworks, just show how each one of these compares to eachother on a small project when you're coming up to speed with them. Then again, Alistair schooled me on how I was using Cairngorm, so maybe I know nothing! Nothing at all!
Anyhow, I'm glad the series seems to be helpful all the same.
@Tony, good question, and one where i don't have an immediate answer I'm afraid.
Since donating Cairngorm to the community, real world projects have taken over the time of our consultants, so getting the time to provide the documentation and sample we'd want to just hasn't materialized.
However, some of our team here are starting to get some stuff together, so hopefully we'll be able to start providing more in the new year.
In particular, one of my colleagues, Tom Sugden, is the co-author of a new book coming out next year, where a lot of the latest best practices (including the refactored Cairngorm Store) are described. However, we hope to get more out there before that is published.
Alistair,
With all due respect, if there's no examples or documentation available showing a better (best practice) way of using Cairngorm, then whatever the common practice out there right now is still "the standard way" to use it. I understand your comments and points about better practices possible (or even established within certain circles), but these points aren't relevant when Cairngorm is compared to alternatives that do provide "best practice" samples and documentation.
i.e. Given two table saws, one with diagrams and an instruction manual, the other without (or with outdated/incorrect instructions)... I'll take the one with documentation and keep my fingers. ^_^
Just to inform community that a new tool has been released by Kap IT Lab: consoles for pureMVC and cairngorm. It becomes really essential as soon as you start to use it.
You can see them here:
http://lab.kapit.fr/display/puremvcconsole/PureMVC+Console
and here:
http://lab.kapit.fr/display/cairngormconsole/Cairngorm+Console
Just to inform community that two new tools have been released by Kap IT Lab: consoles for pureMVC and cairngorm. It becomes really essential as soon as you start to use it.
You can see them here:
http://lab.kapit.fr/display/puremvcconsole/PureMVC+Console
and here:
http://lab.kapit.fr/display/cairngormconsole/Cairngorm+Console
@Alistair: I agree with Ansury.
I do definitely appreciate your chipping in on the discussion, that's cool. But essentially Ansury is right. What you're saying is that if we want to learn Cairgnorm we have two choices:
1. Use it badly / in a way that Adobe discourages, but that is easy to do and all over the internet (possibly in some [outdated] official doco too?)
2. Spend a good deal of time trying to find the gems of info that explain how to use it well and then hoping that we have acheived this correctly.
If more doco comes out of Adobe which clearly establishes best practices that would be great.
@Fletch and @Ansury,
Well, I agree with Alistair and disagree with both of you. What you are doing is criticising the documentation and not the framework as Alistair is quite right to point out. Anyone who is familiar with the benefits of DI will naturally use this pattern to keep their dependencies out of their view components regardless of which framework is utilised. They won't need to be told how to do this. So the critcism should be 'good framework, poor documentation' rather than 'flawed framework'. I would say that most people who own a Callaway Big Bertha golf driver can't use it properly - is this an inherent problem with the golf driver?