aodz

FrameworkQuest 2008 Part 3: Framework Agnostic Views with PureMVC

Posted on: January 16, 2009


FrameworkQuest Part 3 by Tony Hillerson
Find the original post here

FrameworkQuest 2008: Introduction
FrameworkQuest 2008 Part 2: Get Control with Cairngorm
FrameworkQuest 2008 Part 3: Agnostic Views with PureMVC
FrameworkQuest 2008 Part 4: IoC With Swiz
FrameworkQuest 2008 Part 5: Mate, the Pure MXML Framework
FrameworkQuest 2008 Part 6: The Exciting Conclusion

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 more from
Tony Hillerson
.

Advertisements
Tags:

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

I have moved to a different location

check out my new home Flexout

calendar

January 2009
M T W T F S S
« Dec   Feb »
 1234
567891011
12131415161718
19202122232425
262728293031  

Blog Stat

  • 83,826 Hop's so far!!!

follow me

Archives

Linkedin Blogger Twitter Youtube Orkut

Top Clicks

  • None

latest flickr photos

top rated

%d bloggers like this: