PureMVC

PureMVC is not Flex but rather an ActionScript (AS) framework. PureMVC concentrates on the task of creating a generic framework for low-level AS objects; Flex comes with “prebuilt suggestions” for how a Model-View-Controller might work—and it offers lots of hooks throughout the data and UI classes that help implement MVC. But because Flex, AIR, and Flash understand this language, PureMVC can be used in any applications built in any of these environments.

Similarly to Cairngorm, PureMVC is built on singletons. The Model, View, Controller, and Facade classes are singletons. In Cairngorm, developers need to write code to instantiate each singleton; in PureMVC, only the Facade class has to be instantiated in the application code and creation of the Model, View, and Controller classes is done by the Facade class itself.

In Cairngorm, you create an application-specific FrontController and register event-command pairs; in PureMVC, you create a Facade class and register notification-command pairs there. With PureMVC, you can execute multiple commands as a reaction to a notification.

Object-oriented programming languages arrange event-driven communication between the objects by implementing the Observer design pattern. An observer object is registered with one or more observable objects that generate notifications to be consumed by the observer.

Cliff Hall, the author of PureMVC, went the same route to ensure that this framework can be used even in non-Flash environments that don’t offer flash.events.Event and EventDispatcher classes.

Views are controlled by their mediator objects, which maintain maps of notifications and their observers.

Notifications are a PureMVC implementation of event-driven communication between application components. The author of PureMVC wanted to make this framework portable to other languages; hence standard Flash events are not used in the framework, even though Flex developers still can use regular events to process, say, button clicks.

Although flash.events.Event is not leveraged by the PureMVC framework, the Notification class has the property called body typed as Object, which is a place for storing application-specific data that may need to be carried by a notification object. In pure ActionScript, you’d have to create a custom event object providing a placeholder for the custom data (on the other hand, in custom ActionScript events, the data can be strongly typed as opposed to being just Objects).

Café Townsend with PureMVC

To better understand this framework, take a walk through the code of Café Townsend that was ported to PureMVC by Michael Ramirez. Please download this application at http://trac.puremvc.org/Demo_AS3_Flex_CafeTownsend.

The data flow between PureMVC components while displaying a list of Café employees is depicted in Figure 1-8.

Your goal remains the same: walk the route that would display the list of Café employees. Figure 1-9 shows the structure of this application in Flash Builder.

The code of the CafeTownsend.mxml application is shown in Example 1-15. You’ll see a familiar ViewStack container that holds employee login, list, and detail views. It declares the variable facade, which holds the reference to the ApplicationFacade singleton that is created during initializing the value of this variable. Then the method startup() is called on this ApplicationFacade object inherited from PureMVC’s Facade class.

Example 1-15. CafeTownsend.mxml—the application

<?xml version="1.0"?>
<!-- PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License -->

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   xmlns:view="org.puremvc.as3.demos.flex.cafetownsend.view.components.*"
   xmlns:mvc="org.puremvc.as3.demos.flex.cafetownsend.*"
   layout="vertical" backgroundColor="#000000"
   creationComplete="facade.startup(this)">

   <mx:Script>
      <![CDATA[
      import org.puremvc.as3.demos.flex.cafetownsend.*;
      private var facade:ApplicationFacade =
                 ApplicationFacade.getInstance();
      ]]>
   </mx:Script>

   <mx:Style source="assets/main.css" />
   <mx:Image source="@Embed('assets/header.jpg')" width="700" />
   <mx:HBox paddingBottom="10" paddingLeft="10" paddingRight="10"
           paddingTop="10" backgroundColor="#ffffff" width="700">
      <mx:VBox width="100%" verticalScrollPolicy="off"
                             paddingRight="10">
         <mx:ViewStack id="vwStack" width="100%" paddingBottom="10"
        paddingTop="10" resizeToContent="true" creationPolicy="all">
            <view:EmployeeLogin id="employeeLogin" />
            <view:EmployeeList id="employeeList" />
            <view:EmployeeDetail id="employeeDetail" />
         </mx:ViewStack>
      </mx:VBox>
   </mx:HBox>
</mx:Application>
Bringing the employee list with PureMVC

Figure 1-8. Bringing the employee list with PureMVC

Café Townsend with PureMVC—the project structure

Figure 1-9. Café Townsend with PureMVC—the project structure

During creation of the Facade instance (see Example 1-16), PureMVC automatically initializes the instances of Model, View, and Controller classes, and if you need to execute application-specific code during this process, override the appropriate initialize method.

Example 1-16. ApplicationFacade.as

/* PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License */
package org.puremvc.as3.demos.flex.cafetownsend{
  import org.puremvc.as3.interfaces.*;
  import org.puremvc.as3.patterns.proxy.*;
  import org.puremvc.as3.patterns.facade.*;

  import org.puremvc.as3.demos.flex.cafetownsend.view.*;
  import org.puremvc.as3.demos.flex.cafetownsend.model.*;
  import org.puremvc.as3.demos.flex.cafetownsend.controller.*;

  /**
   * A concrete <code>Facade</code> for the <code>CafeTownsend</code>
     application.
   * The main job of the <code>ApplicationFacade</code> is to act as a single
   * place for mediators, proxies, and commands to access and communicate
   * with each other without having to interact with the Model, View, and
   * Controller classes directly. All this capability it inherits from
   * the PureMVC Facade class.</P>
   * This concrete Facade subclass is also a central place to define
   * notification constants which will be shared among commands, proxies, and
   * mediators, as well as initializing the controller with Command to
   * Notification mappings.</P>
   */
  public class ApplicationFacade extends Facade
  {
  // Notification name constants
  public static const STARTUP:String= "startup";
  public static const SHUTDOWN:String= "shutdown";
  public static const APP_LOGOUT:String= "appLogout";
  public static const APP_LOGIN:String= "appLogin";
  public static const LOAD_EMPLOYEES_SUCCESS:String="loadEmployeesSuccess";
  public static const LOAD_EMPLOYEES_FAILED:String="loadEmployeesFailed";
  public static const VIEW_EMPLOYEE_LOGIN:String= "viewEmployeeLogin";
  public static const VIEW_EMPLOYEE_LIST:String= "viewEmployeeList";
  public static const VIEW_EMPLOYEE_DETAIL:String= "viewEmployeeDetail";
  public static const ADD_EMPLOYEE:String= "addEmployee";
  public static const UPDATE_EMPLOYEE:String= "updateEmployee";
  public static const SAVE_EMPLOYEE:String= "saveEmployee";
  public static const DELETE_EMPLOYEE:String   = "deleteEmployee";
    /**
     * Singleton ApplicationFacade Factory Method
     */
  public static function getInstance() : ApplicationFacade{
      if ( instance == null ) instance = new ApplicationFacade( );
      return instance as ApplicationFacade;
  }
    /**
     * Register Commands with the Controller
     */
  override protected function initializeController( ) : void {
      super.initializeController();
      registerCommand( STARTUP, ApplicationStartupCommand );
  }

  public function startup( app:CafeTownsend ):void{
       sendNotification( STARTUP, app );
    }
  }
}

In Example 1-16, during controller initialization, the STARTUP notification is registered with the command class ApplicationStartupCommand. So far it looks pretty similar to Cairngorm’s FrontController from Example 1-4, doesn’t it?

But PureMVC allows you to invoke more than one command as a response to a notification. For example, the author of this version of Café Townsend decided to invoke two commands during the application startup—ModelPrepCommand and ViewPrepCommand. When your command class extends MacroCommand, you are allowed to register a sequence of subcommands, and the ApplicationStartupCommand looks like Example 1-17.

Example 1-17. ApplicationStartupCommand.as

/* PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License*/
package org.puremvc.as3.demos.flex.cafetownsend.controller
{
  import org.puremvc.as3.patterns.com7mand.*;
  import org.puremvc.as3.interfaces.*;
  /**
   * A MacroCommand executed when the application starts.
   */
  public class ApplicationStartupCommand extends MacroCommand {
    override protected function initializeMacroCommand() :void{
      addSubCommand( ModelPrepCommand );
      addSubCommand( ViewPrepCommand );
    }
  }
}

We’ll follow the model preparation route at this point, but we’ll get back to ViewPrepCommand in Example 1-22.

After the controller tier that routes commands come the proxy classes that deal with both—data models and the service calls if need be. Let’s follow the ModelPrepCommand (Example 1-18). It registers employee and user proxy classes with the Facade class, so they know where to send notifications.

Example 1-18. ModelPrepCommand.as

/*PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License */
package org.puremvc.as3.demos.flex.cafetownsend.controller {
  import org.puremvc.as3.interfaces.*;
  import org.puremvc.as3.patterns.command.*;
  import org.puremvc.as3.patterns.observer.*;
  import org.puremvc.as3.demos.flex.cafetownsend.*;
  import org.puremvc.as3.demos.flex.cafetownsend.model.*;
  /**
   * Create and register <code>Proxy</code>s with the <code>Model</code>.
   */
  public class ModelPrepCommand extends SimpleCommand{
    override public function execute( note:INotification ) :void{
     facade.registerProxy(new EmployeeProxy());
     facade.registerProxy(new UserProxy());
    }
  }
}

We are about halfway through the process of getting the employee list with PureMVC. This time, we’ll just get familiar with a fragment of the code for the EmployeeProxy class (Example 1-19).

Example 1-19. A fragment of EmployeeProxy.as

public class EmployeeProxy extends Proxy implements IResponder {
   public static const NAME:String = "EmployeeProxy";
   public var errorStatus:String;

   public function EmployeeProxy ( data:Object = null ){
      super ( NAME, data );
   }

   public function loadEmployees():void{
   // create a worker who will go get some data; pass it a reference to
  // this proxy so the delegate knows where to return the data
   var delegate : LoadEmployeesDelegate =new LoadEmployeesDelegate(this );

   // make the delegate do some work
   delegate.loadEmployeesService();
  }

   // this is called when the delegate receives a result from the service
   public function result( rpcEvent : Object ) : void{

   // populate the employee list in the proxy with the results
   // from the service call
    data = rpcEvent.result.employees.employee as ArrayCollection;
    sendNotification( ApplicationFacade.LOAD_EMPLOYEES_SUCCESS );
  }

   // this is called when the delegate receives a fault from the service
   public function fault( rpcEvent : Object ) : void {
      data = new ArrayCollection();
    // store an error message in the proxy
    // labels, alerts, etc can bind to this to notify the user of errors
    errorStatus = "Could Not Load Employee List!";
    sendNotification( ApplicationFacade.LOAD_EMPLOYEES_FAILED );
   }

Proxies link the data model with services. The model is represented by the variable data that’s predefined in the superclass. The service is available via the delegate class, which in this version of Café Townsend is called LoadEmployeesDelegate. Because EmployeeProxy implements the IResponder interface, it must include the methods result() and fault(). In the case of success, the variable data is populated with the retrieved list of employees and notification LOAD_EMPLOYEES_SUCCESS is sent to whoever is interested in hearing about it—you can take a peek at the method listNotificationInterests() in Example 1-21. In the case of failure, this version of Café Townsend just assigns a value to the variable errorStatus and sends the notification LOAD_EMPLOYEES_FAILED.

As you can see in Example 1-20, the delegate class to load employees has nothing specific to PureMVC—it just sets the responder and uses HTTPService to read the file Employees.xml.

Example 1-20. LoadEmployeesDelegate.as

 /*
 PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License
 */
package org.puremvc.as3.demos.flex.cafetownsend.model.business
{
   import mx.rpc.AsyncToken;
   import mx.rpc.IResponder;
   import mx.rpc.http.HTTPService;

   public class LoadEmployeesDelegate{
      private var responder : IResponder;
      private var service : HTTPService;

      public function LoadEmployeesDelegate( responder : IResponder ) {
         this.service = new HTTPService();
         this.service.url="assets/Employees.xml";

         // store a reference to the proxy that created this delegate
         this.responder = responder;
      }

      public function loadEmployeesService() : void {
         // call the service
         var token:AsyncToken = service.send();

         // notify this responder when the service call completes
         token.addResponder( responder );
      }
   }
}

Now trace how the employees will arrive to the View. The view tier in PureMVC has two players: the UI component and the mediator class. Chapter 2 discusses the Mediator pattern, but in general, its role is to arrange the communication of two or more components without them knowing about each other. For example, an application container has a shopping cart component and a product list component. When the user makes a selection, the product component sends an event carrying the selected product to the mediator (e.g., an application), which forwards it to the shopping cart component.

But PureMVC mediators play the role of middlemen between the UI components and proxy objects (not controllers), and the need for these middlemen is questionable. In our opinion, it would be cleaner to introduce a value object and pass it directly (in the body of Notification) between the view and its controller rather than having the mediator reaching out to internals of both the proxy and the view. But it is what it is, and the EmployeeList view interacts with the EmployeeListMediator, and the latter deals with the controller’s notifications.

In Example 1-21, note the method listNotificationInterests(), where you, the developer, have to list all events this mediator is interested in (similar to a subscription in messaging). The method handleNotification() will process notifications when they arrive.

Example 1-21. EmployeeListMediator.as

/*
 PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License
 */
package org.puremvc.as3.demos.flex.cafetownsend.view{
   import flash.events.Event;
  import org.puremvc.as3.interfaces.*;
   import org.puremvc.as3.patterns.mediator.Mediator;

  import org.puremvc.as3.demos.flex.cafetownsend.ApplicationFacade;
   import org.puremvc.as3.demos.flex.cafetownsend.view.components.*;
   import org.puremvc.as3.demos.flex.cafetownsend.model.EmployeeProxy;
  /**
   * A Mediator for interacting with the EmployeeList component
   */
 public class EmployeeListMediator extends Mediator{

   public static const NAME:String = "EmployeeListMediator";
   public function EmployeeListMediator( viewComponent:Object ){
    // pass the viewComponent to the superclass where
    // it will be stored in the inherited viewComponent property
    super( NAME, viewComponent );

     employeeProxy = EmployeeProxy( facade.retrieveProxy(
                          EmployeeProxy.NAME ) );

     employeeList.addEventListener( EmployeeList.APP_LOGOUT, logout );
     employeeList.addEventListener( EmployeeList.ADD_EMPLOYEE,
                             addEmployee );
     employeeList.addEventListener( EmployeeList.UPDATE_EMPLOYEE,
                            updateEmployee );
  }
    /**
     * List all notifications this Mediator is interested in.
     * Automatically called by the framework when the mediator
     * is registered with the view.
     * @return Array the list of Notification names
     */
   override public function listNotificationInterests():Array{
      return [ ApplicationFacade.LOAD_EMPLOYEES_SUCCESS,
               ApplicationFacade.LOAD_EMPLOYEES_FAILED ];
  }

    /**
     * Handle all notifications this Mediator is interested in.
     * <P>
     * Called by the framework when a notification is sent that
     * this mediator expressed an interest in when registered
     * (see <code>listNotificationInterests</code>.</P>
     *
     * @param INotification a notification
     */
   override public function handleNotification(note:INotification ):void{
      switch ( note.getName() ) {
       case ApplicationFacade.LOAD_EMPLOYEES_SUCCESS:
          employeeList.employees_li.dataProvider =
                   employeeProxy.employeeListDP;
         break;
        case ApplicationFacade.LOAD_EMPLOYEES_FAILED:
          employeeList.error.text = employeeProxy.errorStatus;
          break;
      }
   }
    /**
     * Cast the viewComponent to its actual type.
     *
     * This is a useful idiom for mediators. The
     * PureMVC Mediator class defines a viewComponent
     * property of type Object. </P>
     *
     * @return EmployeeList the viewComponent cast to EmployeeList
     */
   protected function get employeeList():EmployeeList{
      return viewComponent as EmployeeList;
   }

    private function logout( event:Event = null ):void{
         sendNotification( ApplicationFacade.APP_LOGOUT );
    }

    private function addEmployee( event:Event = null ):void{
         sendNotification( ApplicationFacade.ADD_EMPLOYEE );
    }

    private function updateEmployee( event:Event = null ):void{
      sendNotification( ApplicationFacade.UPDATE_EMPLOYEE,
              employeeList.employees_li.selectedItem);
    }

    private var employeeProxy:EmployeeProxy;
  }
}

The code of handleNotification() directly manipulates the internals of the view components (e.g., employeeList.employees_li), which leads to tight coupling between the mediator and the view. If the next version of the employeeList component will use a DataGrid instead of the List component, the mediator’s code has to be refactored, too.

The previous discussion of Example 1-17 did not cover the process of preparing the view for receiving the events. Handling that process is the branch of code originated by the following call:

addSubCommand( ViewPrepCommand );

Shown in Example 1-22, the ViewPrepCommand class registers the main application mediator (you’d have to write it), and asks the proxy to load the employee list.

Example 1-22. ViewPrepCommand.as

/* PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License
 */
package org.puremvc.as3.demos.flex.cafetownsend.controller{
  import org.puremvc.as3.interfaces.*;
  import org.puremvc.as3.patterns.command.*;
  import org.puremvc.as3.patterns.observer.*;
  import org.puremvc.as3.demos.flex.cafetownsend.*;
  import org.puremvc.as3.demos.flex.cafetownsend.model.*;
  import org.puremvc.as3.demos.flex.cafetownsend.view.ApplicationMediator;
  /**
   * Prepare the View for use.
   * The Notification was sent by the Application, and a reference to that
   * view component was passed on the note body.
   * The ApplicationMediator will be created and registered using this
   * reference. The ApplicationMediator will then register
   * all the Mediators for the components it created.
   */
  public class ViewPrepCommand extends SimpleCommand{
    override public function execute( note:INotification ) :void{
    // Register your ApplicationMediator
    facade.registerMediator( new ApplicationMediator( note.getBody()));

    // Get the EmployeeProxy
    var employeeProxy:EmployeeProxy = facade.retrieveProxy(
                  EmployeeProxy.NAME ) as EmployeeProxy;
    employeeProxy.loadEmployees();

    sendNotification( ApplicationFacade.VIEW_EMPLOYEE_LOGIN );
    }
  }
}

This command class issues a request to load employees without even waiting for the successful logon of the user. At the end of the execute() method, this code sends the VIEW_EMPLOYEE_LOGIN notification, which displays the logon view.

For brevity, Example 1-23 does have most of the comments from the code of ApplicationMediator. It builds all view components and registers the mediators for each of them.

Example 1-23. ApplicationMediator.as

/* PureMVC AS3 Demo - Flex CafeTownsend
 Copyright (c) 2007-08 Michael Ramirez <michael.ramirez@puremvc.org>
 Parts Copyright (c) 2005-07 Adobe Systems, Inc.
 Your reuse is governed by the Creative Commons Attribution 3.0 License*/
package org.puremvc.as3.demos.flex.cafetownsend.view {
  public class ApplicationMediator extends Mediator{
    public static const NAME:String = "ApplicationMediator";
    public static const EMPLOYEE_LOGIN : Number =   0;
    public static const EMPLOYEE_LIST : Number =   1;
    public static const EMPLOYEE_DETAIL : Number =   2;

    public function ApplicationMediator( viewComponent:Object )
    {
      // pass the viewComponent to the superclass where
      // it will be stored in the inherited viewComponent property
      super( NAME, viewComponent );

      // Create and register Mediators for the Employee
      // components that were instantiated by the mxml application
      facade.registerMediator( new EmployeeDetailMediator(
                         app.employeeDetail ) );
      facade.registerMediator( new EmployeeListMediator(
                          app.employeeList ) );
        facade.registerMediator( new EmployeeLoginMediator(
             app.employeeLogin ) );

      // retrieve and cache a reference to frequently accessed proxys
      employeeProxy = EmployeeProxy( facade.retrieveProxy(
                           EmployeeProxy.NAME ) );
        userProxy = UserProxy( facade.retrieveProxy( UserProxy.NAME ) );
    }

    override public function listNotificationInterests():Array
    {

      return [ ApplicationFacade.VIEW_EMPLOYEE_LOGIN,
                ApplicationFacade.VIEW_EMPLOYEE_LIST,
                ApplicationFacade.VIEW_EMPLOYEE_DETAIL,
                ApplicationFacade.APP_LOGOUT,
                ApplicationFacade.UPDATE_EMPLOYEE
               ];
    }
    /**
     * Handle all notifications this Mediator is interested in.
     */
    override public function handleNotification( note:INotification
                             ):void{
      switch ( note.getName() ){
              case ApplicationFacade.VIEW_EMPLOYEE_LOGIN:
               app.vwStack.selectedIndex = EMPLOYEE_LOGIN;
               break;
            case ApplicationFacade.VIEW_EMPLOYEE_LIST:
               employeeProxy.employee = null;
               app.vwStack.selectedIndex = EMPLOYEE_LIST;
               break;
            case ApplicationFacade.VIEW_EMPLOYEE_DETAIL:
               app.vwStack.selectedIndex = EMPLOYEE_DETAIL;
               break;
            case ApplicationFacade.APP_LOGOUT:
               app.vwStack.selectedIndex = EMPLOYEE_LOGIN;
               break;
            case ApplicationFacade.UPDATE_EMPLOYEE:
               app.vwStack.selectedIndex = EMPLOYEE_DETAIL;
               break;
      }
    }
    /**
     * Cast the viewComponent to its actual type.
     * The PureMVC Mediator class defines a viewComponent
     * property of type Object.
     */
    protected function get app():CafeTownsend{
      return viewComponent as CafeTownsend
    }
      // Cached references to needed proxies
      private var employeeProxy:EmployeeProxy;
      private var userProxy:UserProxy;
  }
}

The ApplicationMediator is also a central repository of all proxies that know how to get the data (EmployeeProxy and UserProxy in our case). So the ViewPrepCommand creates an instance of the ApplicationMediator (which creates other mediators and proxies to be cached), registers it with the facade, and asks the facade for a newly created instance of the EmployeeProxy, and calls its loadEmployees() method.

If the EmployeeProxy successfully retrieves the employee, it triggers the notification LOAD_EMPLOYEES_SUCCESS, which the EmployeeMediator processes, putting the data in the data provider of the EmployeeList (see Example 1-21 earlier):

case ApplicationFacade.LOAD_EMPLOYEES_SUCCESS:
 employeeList.employees_li.dataProvider = employeeProxy.employeeListDP;

The circle is closed. As you can see, the PureMVC way to bring Café Townsend’s employee list is a lot more complicated than the Cairngorm or Mate way.

Still, if you work with an application built on the PureMVC framework, consider using a freeware product by Kap IT called PureMVC Console, available at http://lab.kapit.fr/display/puremvcconsole/PureMVC+Console. This tool comes in handy if you’ve joined a PureMVC project and need to hit the ground running. This console allows you to monitor the internal flow of this framework in real time. The creators of PureMVC Console offer a nice demo of monitoring Café Townsend—check it out at the website.

The MultiCore version of PureMVC supports modular programming where singletons are replaced with so-called Multiton Core actors.

We are having difficulty finding reasons for recommending an architectural framework that requires developers to replace 20 lines of code from Example 1-1 with all the code shown in Examples 1-15 through 1-23 to achieve the same goal: display the list of employees from an XML file in a list control.

Report Card: PureMVC

The author of PureMVC wanted to create a framework that could have been ported to other programming languages, and this approach inadvertently delivers a product that underutilizes benefits offered by language-specific constructs. Because PureMVC was not created specifically for Flex, it doesn’t take advantage of the declarative nature of MXML, which would’ve substantially minimized the amount of handwritten code by application developers. For the same reason, PureMVC doesn’t use standard Flex events and data binding. As an old saying goes, “When in Rome, speak Latin.” It can be rephrased as, “When in Flex, speak MXML and ActionScript.”

The pros are:

  • It’s well documented.

  • It supports working with Flex modules.

  • It’s available for developers who want to use only ActionScript (e.g., Flash programmers). For Flex programmers, though, that can’t be considered a benefit.

The cons are:

  • It’s not a framework written for Flex, and thus does not use features offered by MXML.

  • It has too many layers, which are tightly coupled.

  • It requires staffing projects with more senior developers.

  • Developers have to write lots of additional classes, which adds to the project timeline.

  • Its standard version is built on singletons, and application code becomes cluttered by making multiple calls to them.

One of the main Flex selling points is its MXML-to-ActionScript code generator, which spares application developers from manually writing lots of code. PureMVC doesn’t use MXML and forces developers to write more code, which makes them less productive.

PureMVC notifications are more flexible than event maps of Mate, in that the latter relies on the enabled event bubbling, and if the EventMap object is not located in the ancestor of the object that triggers the event, it won’t get it. As a workaround, Mate offers a special Dispatcher class to trigger events, say from a pop-up window that is not a descendant of an Application object. But in PureMVC, any object can subscribe for any other object’s notifications regardless of their relations. Also, since the Notification class already has the property body to carry additional payload, application developers don’t need to create subclasses for each notification object.

PureMVC has too many layers, dependencies, and singletons, and as a result has a steeper learning curve than Cairngorm or Mate. Managers on the projects that use PureMVC would need to hire more experienced developers than managers on projects using Mate or Cairngorm.

Note

PureMVC Console is a convenient tool allowing you to monitor the Cairngorm and PureMVC applications; see http://lab.kapit.fr. To monitor the PureMVC version of Café Townsend, click on the image of the Café at http://lab.kapit.fr/display/puremvcconsole/PureMVC+Console.

PureMVC documentation states, “The PureMVC framework has a very narrow main goal: to help you separate your application’s coding concerns into three discrete tiers; Model, View, and Controller.” The framework attempts to achieve this goal by forcing application developers to write a lot of additional ActionScript code.

Unit testing of separate parts of the PureMVC application is nontrivial, because each test case would require additional work to register notifications, mediators, and other objects.

Get Agile Enterprise Application Development with Flex now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.