Creating an AIR RSS Reader Application from Scratch with Flex and PureMVC

by Pedro Furtado 3

Adobe AIR counts already with over 100 million deployments which represents the importance of this development tool nowadays. Nevertheless still some to get introduced to it and start creating their own applications. In this tutorial you will learn how to create an AIR application a bit different on what we have shown before. I mean, we will be showing how to create a RSS Reader with AIR and PureMVC.

As this a very extensive tutorial, due to the several files used in this example, we have opted to use the comments on code to explain it.

Requirements:

Download AIR RSS Reader Source Files

Prerequisites

This tutorial is meant for intermediate to advanced developers that already have a reasonable grasp of AS3 and have a small idea about meta-patterns, specifically the MVC meta-pattern.

This isn’t for beginners and designers that are now starting with AS3 or don’t have a reasonable grasp of AS3 yet.

Table of Contents

1 – Goals

2 – Introduction

3 – First Steps

4 – CODE AND COMMENTS

5 – Other frameworks and considerations

6 – Conclusion

Tutorial Creating an AIR Application with PureMVC - RSS Reader

 

1 – Goals

The main goals for this tutorial are to guide you in the development of an AIR application using, and taking advantage of, PureMVC. Also it will try to cover some of the most common changes, and modules, in PureMVC.

2 – Introduction

In this tutorial we will build a simple RSS Reader with AIR using PureMVC.

PureMVC is a framework based on the the Model-View-Controller Meta-Pattern for AS3 (amongst other languages), and although there many others PureMVC seems to be setting itself as the most common choice for medium to large projects due to it’s incredible community, flexibility and utilities ( a concept we won’t discuss here but that you can check over at PureMVC wiki).

As PureMVC comes in many flavors and it’s worth checking them out before you start your project as they have different aims and can greatly ease your development process.

3 – First Steps

After downloading the PureMVC framework and setting up your Flex AIR Project we can copy the PureMVC to the source folder and start coding, but before we dig into the code we’ll go over the package (folder) structure and essential classes of a PureMVC project and what each package means and does.

Default application package (in our case com.techlabs.puremvc)

It should contain the ApplicationFacade.as

The concrete facade acts 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. 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.

Controller (com.techlabs.puremvc.controller)

The purpose of the controller package is to store the command concrete classes and we’ll delve into their types further down.

The concrete commands are meant to be used as actions in the application and often include commands like Application Startup that takes care of initializing the model and views in the PureMVC framework.

Model (com.techlabs.puremvc.model)

The model contains the proxies and further sub-packages that relate to data.

The function of the proxies is to handle all data interactions and to keep that data. Some people prefer to use a command to actually perform the operation and have it affect the proxies, but again this is mostly a question of preference and of the type/size of application.

Business (com.techlabs.puremvc.model.business)

Business usually holds specific service actions that are often referred to as delegates, these take care of making the actual interaction with the webservices and pass the results to the calling proxy, they shouldn’t however do any data handling or parsing.

Helpers (com.techlabs.puremvc.model.helpers)

Helpers are meant for simple classes that perform tasks related to the data that the proxies require such as parsing the xml or doing some data handling. These are useful if several proxies need to do that one same thing ( as parsing xml )

VO (com.techlabs.puremvc.model.vo)

Value Objects are the specific types of data that your application will use, think of them as a class that you make so that you can strong type and cast your data. And they can be anything from a CostumerVo to a BookVo or even a ResourceVo.

View (com.techlabs.puremvc.view)

The view package will hold all the classes that relate to the interface of your application and it’s workings, to that effect it’s common to separate the actual MXML files from their logic using a sub-package called components and using the view package for the mediators.

The mediators will be the gateway between the actual MXML interface and the rest of your application, they manage, listen and interact with all things visual and make sure that all those interactions are sent to the frame work. There are some nuances that we’ll cover further down.

Components (com.techlabs.puremvc.view.components)

The components sub-package holds all the MXML files and visual elements but it does not contain any non-required logic. Ideally it should be a simple description of the user interface.

Note that this is just one way to arrange your classes and most programmers choose to organize it a bit differently to personal taste, so consider this to be a guideline instead of a rule.

4 – Code!

TheTechLabsRssReader.mxml

Let’s initialize the application facade, this is where the framework get’s booted and the fun begins. There different ways of instantiating the facade, like having it on an onInitialize method, or some even more drastic that hack into the Flex initialization process.

<mx:WindowedApplication  xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 

            creationComplete="facade.startup(this);" 

            xmlns:components="com.techlabs.puremvc.view.components.*" 

            width="800"  height="550" 

            verticalScrollPolicy="off"  horizontalScrollPolicy="off"  viewSourceURL="srcview/index.html">

            <mx:Style  source="SunNight.css"/>

            <mx:Script>

                        <![CDATA[

                                    import  com.techlabs.puremvc.ApplicationFacade;

                                    private  var facade:ApplicationFacade = ApplicationFacade.getInstance();

                        ]]>

            </mx:Script>

            <components:MainScreen  id="mainScreen"/>

            <components:AddFeed  id="addFeed" x="204" visible="false"/>

            <mx:Image  y="10" source="assets/thetechlabslogo.png"  right="10"/>

</mx:WindowedApplication>

ApplicationFacade.as

package com.techlabs.puremvc

{

    import  com.techlabs.puremvc.controller.AddFeedCommand;

    import  com.techlabs.puremvc.controller.ApplicationStartupCommand;

    import  com.techlabs.puremvc.controller.RemoveFeedCommand;

    import  org.puremvc.as3.interfaces.*;

    import  org.puremvc.as3.patterns.facade.*;

    import  org.puremvc.as3.patterns.proxy.*;

    /**

     *

     */

    public class  ApplicationFacade extends Facade

    {

        //  Notification name constants

                        //  application

        public static  const STARTUP:String =  "startup";

        public static  const SHUTDOWN:String =  "shutdown";

                        //  proxy

        public static  const LOADING_STEP:String =  "loadingStep";

        public static  const LOADING_COMPLETE:String =  "loadingComplete";

        public static  const LOADING_FEED_COMPLETE:String =  "loadingFeedComplete";

                        public  static const LOAD_CONFIG_FAILED:String =  "loadConfigFailed";

        public static  const LOAD_RESOURCE_FAILED:String =  "loadResourceFailed";

                        //  command

        public static  const COMMAND_STARTUP_MONITOR:String =  "StartupMonitorCommand";

        public static  const FEED_ADDED:String =  "feedAdded";

                        public  static const REMOVE_FEED:String =  "removeFeed";

                        //  view

                        public  static const VIEW_ADD_FEED:String =  "viewAddFeed";

                        public  static const VIEW_MAIN_SCREEN:String =  "viewMainScreen";

                        //  common messages

                        public  static const ERROR_LOAD_FILE:String =  "Could Not Load the File!";

        /**

         * 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 );

             registerCommand( FEED_ADDED, AddFeedCommand );

             registerCommand( REMOVE_FEED, RemoveFeedCommand );

        }

                        /**

                         * Start the application

                         */

                        public  function startup( app:TheTechLabsRssReader ):void

                        {

                                    sendNotification( STARTUP, app );

                        }

    }

}

AddFeedCommad.as

package com.techlabs.puremvc.controller

{

            import  com.techlabs.puremvc.model.DataProxy;

            import  org.puremvc.as3.interfaces.INotification;

            import  org.puremvc.as3.patterns.command.SimpleCommand;

            public  class AddFeedCommand extends SimpleCommand

            {

                        /**

         * Adds a feed  to by affecting the feeds in dataProxy

         */

                        override  public function execute( note:INotification ) :void    

                        {

                                    var  dataProxy:DataProxy = facade.retrieveProxy(DataProxy.NAME) as DataProxy;

                                    dataProxy.feeds.addItem(note.getBody())

                                    dataProxy.saveData()

        }

            }

}

 

ApplicationStartupCommand.as

package com.techlabs.puremvc.controller

{

    import  org.puremvc.as3.interfaces.*;

    import  org.puremvc.as3.patterns.command.*;

    public class  ApplicationStartupCommand extends MacroCommand

    {

        /**

         * Here we  have the a MacroCommand that has the ability to combine several Commands

         * and be  called as one, thus maintaining the appropriate order of execution

         */

        override  protected function initializeMacroCommand() :void

        {

             addSubCommand( ModelPrepCommand );

             addSubCommand( ViewPrepCommand );

        }

    }

}

ModelPrepCommand.as

package com.techlabs.puremvc.controller

{

    import  com.techlabs.puremvc.model.ConfigProxy;

    import  com.techlabs.puremvc.model.DataProxy;

    import  org.puremvc.as3.interfaces.*;

    import  org.puremvc.as3.patterns.command.*;

    import  org.puremvc.as3.patterns.observer.*;

    public class  ModelPrepCommand extends SimpleCommand

    {

            /**

         * This  creates all the required proxies, or parts of the model. 

         * In our case  we've only two but in a webservice driven application

         * you'd  initialize all your proxies here.

         */

        override  public function execute( note:INotification ) :void    

                        {

             facade.registerProxy(new ConfigProxy());

             facade.registerProxy(new DataProxy());

        }

    }

}

RemoveFeedCommand.as

package com.techlabs.puremvc.controller

{

            import  com.techlabs.puremvc.model.DataProxy;

            import  org.puremvc.as3.interfaces.INotification;

            import  org.puremvc.as3.patterns.command.SimpleCommand;

            public  class RemoveFeedCommand extends SimpleCommand

            {

                        /**

         * As the name  says, this command will affect the dataProxy and remove the selected feed.

         */

                        override  public function execute(notification:INotification):void

                        {

                                    var  dataProxy:DataProxy = facade.retrieveProxy(DataProxy.NAME) as DataProxy;

                                    dataProxy.feeds.removeItemAt(notification.getBody()  as int)

                                    dataProxy.saveData();

                        }

            }

}

 

ViewPrepCommand.as

package com.techlabs.puremvc.controller

{

    import  com.techlabs.puremvc.ApplicationFacade;

    import  com.techlabs.puremvc.view.ApplicationMediator;

    import  org.puremvc.as3.interfaces.*;

    import  org.puremvc.as3.patterns.command.*;

    import  org.puremvc.as3.patterns.observer.*;

    /**

     * Here we'll  initialize the application mediator that will in hand initialize

     * the rest of the  mediators.

     */

    public class  ViewPrepCommand extends SimpleCommand

    {

        override  public function execute( note:INotification ) :void    

                        {

            //  Register the ApplicationMediator

             facade.registerMediator( new ApplicationMediator( note.getBody() as  TheTechLabsRssReader ) );

        }

    }

}

 

ConfigProxy.as

package com.techlabs.puremvc.model

{

            import  com.techlabs.puremvc.ApplicationFacade;

            import  com.techlabs.puremvc.model.business.LoadXMLDelegate;

            import  com.techlabs.puremvc.model.helpers.XmlResource;

            import  flash.net.SharedObject;

            import  mx.rpc.IResponder;

            import  org.puremvc.as3.interfaces.*;

            import  org.puremvc.as3.patterns.proxy.Proxy;

    /**

     * A proxy for  read the config file

     */

    public class  ConfigProxy extends Proxy implements IProxy, IResponder

    {

                        public  static const NAME:String = "ConfigProxy";

                        private  var sharedObject:SharedObject;

                        public  var mySoData:Object;

                        public  function ConfigProxy ( data:Object = null ) 

        {

            super (  NAME, data );

        }

                        public  function load():void

                        {

                                    //  reset the data 

                                    this.data  = new Object();

                                    //  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 : LoadXMLDelegate = new LoadXMLDelegate(this, 'assets/config.xml');

                                    //  make the delegate do some work

                                    delegate.load();

                                    try{

                                                sharedObject  = SharedObject.getLocal('rssReader');

                                                mySoData  = sharedObject.data

                                    }catch(err:Error){

                                                this.sendNotification(ApplicationFacade.ERROR_LOAD_FILE,  err.message)

                                    }

                        }

                        //  this is called when the delegate receives a result from the service

                        public  function result( rpcEvent : Object ) : void

                        {

                                    //  call the helper class for parse the XML data

                                    XmlResource.parse(data,  rpcEvent.result);

                        }

                        //  this is called when the delegate receives a fault from the service

                        public  function fault( rpcEvent : Object ) : void 

                        {

                                    //  send the failed notification

                                    this.sendNotification(  ApplicationFacade.LOAD_CONFIG_FAILED, ApplicationFacade.ERROR_LOAD_FILE );

                        }

                        /**

         * Get the  config value

                         * 

         * @param key  the key to read 

         * @return  String the key value stored in internal object

         */

                        public  function getValue(key:String):String

                        {

                                    return  data[key.toLowerCase()];

                        }

            }

}

DataProxy.as

package com.techlabs.puremvc.model

{

            import  com.techlabs.puremvc.ApplicationFacade;

            import  com.techlabs.puremvc.model.business.LoadXMLDelegate;

            import  com.techlabs.puremvc.model.helpers.SharedObjectActions;

            import  mx.collections.ArrayCollection;

            import  mx.rpc.IResponder;

            import  org.puremvc.as3.interfaces.IProxy;

            import  org.puremvc.as3.patterns.proxy.Proxy;

            public  class DataProxy extends Proxy implements IProxy, IResponder

            {

                        public  static const NAME:String = "DataProxy";

                        [Bindable]

                        public  var feeds:ArrayCollection;

                        public  function DataProxy( data:Object=null)

                        {

                                    super(NAME,  data);

                                    //check  if a shared object already exists

                                    if(SharedObjectActions.getLocalSOData('rssFeeds')  == null)

                                                feeds  = new ArrayCollection();

                                    else

                                                feeds  = SharedObjectActions.getLocalSOData('rssFeeds') as ArrayCollection;

                        }

                        public  function saveData():void

                        {

                                    SharedObjectActions.saveToLocalSO('rssFeeds',feeds);

                        }

                        public  function loadFeed(url:String):void

                        {

                                    var  delegate : LoadXMLDelegate = new LoadXMLDelegate(this, url);

                                    //  make the delegate do some work

                                    delegate.load();

                        }

                        public  function result(data:Object):void

                        {

                                    //parse  the data.

                                    var  xml:XML = new XML(data.result);

                                    var  postList:XMLList = xml..item;

                                    sendNotification(ApplicationFacade.LOADING_FEED_COMPLETE,  postList);

                        }

                        public  function fault(info:Object):void

                        {

                        }

            }

}

LoadXMLDelegate.as

package com.techlabs.puremvc.model.business

{

            import  mx.rpc.AsyncToken;

            import  mx.rpc.events.ResultEvent;

            import  mx.rpc.IResponder;

            import  mx.rpc.http.HTTPService;

            public  class LoadXMLDelegate

            {

                        private  var responder : IResponder;

                        private  var service : HTTPService;

                        public  function LoadXMLDelegate( responder : IResponder, url:String) 

                        {

                                    //  constructor will store a reference to the service we're going to call

                                    this.service  = new HTTPService();

                                    this.service.resultFormat  = 'xml';

                                    this.service.url  = url;

                                    //  and store a reference to the proxy that created this delegate

                                    this.responder  = responder;

                        }

                        public  function load() : void 

                        {

                                    //  call the service

                                    var  token:AsyncToken = service.send();

                                    //  notify this responder when the service call completes

                                    token.addResponder(  this.responder );

                        }

            }

}

SharedObjectActions.as

package com.techlabs.puremvc.model.helpers

{

            import  flash.net.SharedObject;

            public  class SharedObjectActions

            {

                        public  static function saveToLocalSO(sharedObjectName:String,data:Object):void

                        {

                                    var  so:SharedObject = SharedObject.getLocal(sharedObjectName);

                                    so.data.data = data;

                                    so.flush();

                                    so.close();

                        }

                        public  static function getLocalSOData(sharedObjectName:String):Object

                        {

                                    var  so:SharedObject = SharedObject.getLocal(sharedObjectName);

                                    return  so.data.data;

                        }

            }

}

XMLResource.as

package com.techlabs.puremvc.model.helpers

{

            public  class XmlResource

            {

                        static  public function parse(data:Object, node:Object, prefix:String=''):void

                        {

                                    for(var  i:Number=0;i<node.childNodes.length;i++)

                                    {

                                                var  currentNode:Object = node.childNodes[i];

                                                if  (currentNode.nodeName=='param' || currentNode.nodeName=='item')

                                                {

                                                            if  (currentNode.attributes.value!=null)

                                                                        data[(prefix+currentNode.attributes.name).toLowerCase()]  = currentNode.attributes.value;

                                                            else

                                                                        data[(prefix+currentNode.attributes.name).toLowerCase()]  = currentNode.firstChild.nodeValue;                

                                                }

                                                else  if (currentNode.nodeName=='group' && currentNode.hasChildNodes())

                                                {

                                                            XmlResource.parse(data,  currentNode, prefix+currentNode.attributes.name+'/');        

                                                            continue;

                                                }

                                                if  (currentNode.hasChildNodes()) XmlResource.parse(data, currentNode, prefix);

                                    }

                        }

            }

}

AddFeedMediator.as

package com.techlabs.puremvc.view

{

            import  com.techlabs.puremvc.ApplicationFacade;

            import  com.techlabs.puremvc.view.components.AddFeed;

            import  flash.events.Event;

            import  flash.events.MouseEvent;

            import  org.puremvc.as3.interfaces.IMediator;

            import  org.puremvc.as3.interfaces.INotification;

            import  org.puremvc.as3.patterns.mediator.Mediator;

            /**

             * The AddFeedMediator relates to the AddFeed  MXML component

             * and serves to perform all the visual logic  and handling 

             * of AddFeed component

             * 

             * */

            public  class AddFeedMediator extends Mediator implements IMediator

            {

                        //  Cannonical name of the Mediator

        public static  const NAME:String = "AddFeedMediator";

                        public  function AddFeedMediator(viewComponent:Object=null)

                        {

                                    super(NAME,  viewComponent);

                                    /*There  are several reasons why I chose to have an init method

                                    instead  of just initializing in the constructor, but that's a different topic.

                                    */

                                    init()

                        }

                        private  function init():void

                        {

                                    //here  we connect all the event listeners that we require

                                    //also  we would perform other initialization tasks, like 

                                    //instantiating  variables and such.

                                    addFeed.addButton.addEventListener(MouseEvent.CLICK,  addAFeed);

                        }

                          /**

         * List all  notifications this Mediator is interested in.

         * <P>

         *  Automatically called by the framework when the mediator

         * is  registered with the view.</P>

         * 

         * @return  Array the list of Nofitication names

         */

                        override  public function listNotificationInterests():Array

                        {

                                    return  [ApplicationFacade.VIEW_ADD_FEED, 

                                                            ApplicationFacade.VIEW_MAIN_SCREEN];

                        }

                        override  public function handleNotification(notification:INotification):void

                        {

                                    switch(notification.getName())

                                    {

                                                case  ApplicationFacade.VIEW_ADD_FEED:

                                                            addFeed.visible  = true;

                                                break;

                                                case  ApplicationFacade.VIEW_MAIN_SCREEN:

                                                            addFeed.visible  = false;

                                                break;

                                    }

                        }

                         /**

         * Cast the viewComponent  to its actual type.

         * 

         * <P>

         * This is a  useful idiom for mediators. The

         * PureMVC  Mediator class defines a viewComponent

         * property of  type Object. </P>

         * 

         * <P>

         * Here, we  cast the generic viewComponent to 

         * its actual  type in a protected mode. This 

         * retains  encapsulation, while allowing the instance

         * (and  subclassed instance) access to a 

         * strongly  typed reference with a meaningful

         *  name.</P>

         * 

         * @return  MainScreen the viewComponent cast to  org.puremvc.as3.demos.flex.appskeleton.view.components.MainScreen

         */

        protected  function get addFeed():AddFeed

                        {

            return  viewComponent as AddFeed;

        }

                        /*********************************/

                        /*  events handler                                              */

                        /*********************************/

                        private  function addAFeed(evt:Event):void

                        {

                                    //when  we click the add a feed, we should first perform some sort of check

                                    //to  ensure that the fields are properly filed and then dispatch the appropriate

                                    //notifications,  in this case we want to call the AddFeedCommand and close the

                                    //this  component while enabling the main one.

                                    sendNotification(ApplicationFacade.FEED_ADDED,  {feedName:addFeed.nameInput.text, feedUrl:addFeed.urlInput.text});

                                    sendNotification(ApplicationFacade.VIEW_MAIN_SCREEN);

                        }

            }

}

ApplicationMediator.as

package com.techlabs.puremvc.view

{

    import  com.techlabs.puremvc.ApplicationFacade;

    import  org.puremvc.as3.interfaces.*;

    import  org.puremvc.as3.patterns.mediator.Mediator;

    /**

     * A Mediator for  interacting with the top level Application.

     * 

     * <P>

     * In addition to  the ordinary responsibilities of a mediator

     * the MXML  application (in this case) built all the view components

     * and so has a  direct reference to them internally. Therefore

     * we create and  register the mediators for each view component

     * with an  associated mediator here.</P>

     * 

     * <P>

     * Then, ongoing  responsibilities are: 

     * <UL>

     *  <LI>listening for events from the viewComponent (the  application)</LI>

     * <LI>sending  notifications on behalf of or as a result of these 

     * events or other  notifications.</LI>

     *  <LI>direct manipulating of the viewComponent by method invocation

     * or property  setting</LI>

     * </UL>

     */

    public class  ApplicationMediator extends Mediator implements IMediator

    {

        // Cannonical  name of the Mediator

        public static  const NAME:String = "ApplicationMediator";

        /**

         *  Constructor. 

         * 

         * <P>

         * On  <code>applicationComplete</code> in the  <code>Application</code>,

         * the  <code>ApplicationFacade</code> is initialized and the 

         *  <code>ApplicationMediator</code> is created and  registered.</P>

         * 

         * <P>

         * The  <code>ApplicationMediator</code> constructor also registers the 

         * Mediators  for the view components created by the application.</P>

         * 

         * @param  object the viewComponent (the ApplicationSkeleton instance in this case)

         */

        public  function ApplicationMediator( viewComponent:TheTechLabsRssReader ) 

        {

            // pass  the viewComponent to the superclass where 

            // it will  be stored in the inherited viewComponent property

            super( NAME, viewComponent );

            // Create  and register Mediators

            //  components that were instantiated by the mxml application 

                                    facade.registerMediator(  new MainScreenMediator( app.mainScreen ) );

                                    facade.registerMediator(  new AddFeedMediator(app.addFeed) );

        }

        /**

         * List all  notifications this Mediator is interested in.

         * <P>

         *  Automatically called by the framework when the mediator

         * is  registered with the view.</P>

         * 

         * @return  Array the list of Nofitication names

         */

        override  public function listNotificationInterests():Array 

        {

            return [

                                    ApplicationFacade.VIEW_MAIN_SCREEN

                                                            ];

        }

        /**

         * 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() ) 

                                    {

                                                default:

                                                break;

            }

        }

        /**

         * Cast the  viewComponent to its actual type.

         * 

         * <P>

         * This is a  useful idiom for mediators. The

         * PureMVC  Mediator class defines a viewComponent

         * property of  type Object. </P>

         * 

         * <P>

         * Here, we  cast the generic viewComponent to 

         * its actual  type in a protected mode. This 

         * retains  encapsulation, while allowing the instance

         * (and  subclassed instance) access to a 

         * strongly  typed reference with a meaningful

         *  name.</P>

         * 

         * @return app  the viewComponent cast to AppSkeleton

         */

        protected  function get app():TheTechLabsRssReader

                        {

            return  viewComponent as TheTechLabsRssReader

        }

    }

}

MainScreenMediator.as

package com.techlabs.puremvc.view

{

            import  com.techlabs.puremvc.ApplicationFacade;

            import  com.techlabs.puremvc.model.ConfigProxy;

            import  com.techlabs.puremvc.model.DataProxy;

            import  com.techlabs.puremvc.view.components.MainScreen;

            import  flash.events.Event;

            import  flash.events.MouseEvent;

            import  flash.net.URLRequest;

            import  flash.net.navigateToURL;

            import  mx.events.ListEvent;

            import  org.puremvc.as3.interfaces.*;

            import  org.puremvc.as3.patterns.mediator.Mediator;

            /**

             * The main screen mediator, is in our case,  where most of the action happens

             * as it's the main screen of our application  and you can consider it

             * to mediate the user interactions with the  required data operations.

             * */

    public class  MainScreenMediator extends Mediator implements IMediator

    {

        // Cannonical  name of the Mediator

        public static  const NAME:String = "MainScreenMediator";

                        private  var configProxy:ConfigProxy;

                        private  var dataProxy:DataProxy;

        /**

         *  Constructor. 

         */

        public  function MainScreenMediator( viewComponent:MainScreen ) 

        {

            // pass  the viewComponent to the superclass where 

            // it will  be stored in the inherited viewComponent property

            super( NAME, viewComponent );

                                    init()

        }

        private  function init():void

                        {

                                    //retrieve  the required proxies

                                    dataProxy  = facade.retrieveProxy(DataProxy.NAME) as DataProxy;

                                    configProxy  = facade.retrieveProxy(ConfigProxy.NAME) as ConfigProxy;

                                    //add  the listeners

                                    mainScreen.addFeed.addEventListener(MouseEvent.CLICK,  addFeed);

                                    mainScreen.removeFeed.addEventListener(MouseEvent.CLICK,  removeFeed);

                                    mainScreen.feedsList.dataProvider  = dataProxy.feeds;

                                    mainScreen.feedsList.addEventListener(ListEvent.CHANGE,  handleFeedListChange);

                                    mainScreen.postsList.addEventListener(ListEvent.CHANGE,  handlePostListChange);

                                    mainScreen.goToPostButton.addEventListener(MouseEvent.CLICK,  handleGoToClick);

                                    //and  disable the gotopost button as an initial state, this could of course also 

                                    //be  set in the mxml, but I think it makes more sense here.

                                    mainScreen.goToPostButton.enabled  = false;

                        }

                          /**

         * List all  notifications this Mediator is interested in.

         * <P>

         *  Automatically called by the framework when the mediator

         * is  registered with the view.</P>

         * 

         * @return  Array the list of Nofitication names

         */

                        override  public function listNotificationInterests():Array

                        {

                                    return  [ApplicationFacade.VIEW_ADD_FEED, 

                                                            ApplicationFacade.VIEW_MAIN_SCREEN,

                                                            ApplicationFacade.LOADING_FEED_COMPLETE];

                        }

                        override  public function handleNotification(notification:INotification):void

                        {

                                    switch(notification.getName())

                                    {

                                                case  ApplicationFacade.VIEW_ADD_FEED:

                                                            mainScreen.enabled  = false;

                                                break;

                                                case  ApplicationFacade.VIEW_MAIN_SCREEN:

                                                            mainScreen.enabled  = true;

                                                break;

                                                case  ApplicationFacade.LOADING_FEED_COMPLETE:

                                                            mainScreen.postsList.dataProvider  = notification.getBody();

                                                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. 

         * 

         * 

         * Here, we  cast the generic viewComponent to 

         * its actual  type in a protected mode. This 

         * retains  encapsulation, while allowing the instance

         * (and  subclassed instance) access to a 

         * strongly  typed reference with a meaningful

         *  name.

         * 

         * @return  MainScreen the viewComponent cast to  org.puremvc.as3.demos.flex.appskeleton.view.components.MainScreen

         */

        protected  function get mainScreen():MainScreen

                        {

            return  viewComponent as MainScreen;

        }

                        /*********************************/

                        /*  events handler                                              */

                        /*********************************/

                        private  function handlePostListChange(e:ListEvent):void

                        {

                                    var  tmpXml:XML = e.itemRenderer.data as XML;

                                    //this  xml hacking is required due to the way that rss feeds are served,

                                    //and  although the most common approach is to do this server side this also works  well

                                    //we  could also use a RegEx but I chose to simplify the example.

                                    var  tmpStr:String = tmpXml.toString().replace("content:encoded",  "content").replace("content:encoded", "content")

                                    tmpXml  = new XML(tmpStr)

                                    mainScreen.postText.htmlText  = tmpXml.content

                                    mainScreen.goToPostButton.enabled  = true;

                        }

                        private  function handleGoToClick(e:MouseEvent):void

                        {

                                    var  tmpDt:* = mainScreen.postsList.selectedItem.data

                                    navigateToURL(new  URLRequest(mainScreen.postsList.selectedItem.link));

                        }

                        private  function handleFeedListChange(e:ListEvent):void

                        {

                                    //get  the feed

                                    mainScreen.goToPostButton.enabled  = false;

                                    dataProxy.loadFeed(e.itemRenderer.data.feedUrl);

                        }

                        private  function removeFeed(evt:MouseEvent):void

                        {

                                    sendNotification(ApplicationFacade.REMOVE_FEED,  mainScreen.feedsList.selectedIndex);

                        }

                        private  function addFeed(evt:Event):void

                        {

                                    sendNotification(ApplicationFacade.VIEW_ADD_FEED)

                        }

    }

}

AddFeed.mxml

<mx:Canvas  xmlns:mx="http://www.adobe.com/2006/mxml" width="444"  height="116" backgroundColor="#212121">

            <mx:Label  x="192.5" y="10" text="Add a feed"/>

            <mx:Label  x="10" y="37" text="Feed Name:"/>

            <mx:TextInput  x="75" y="37" width="296"  id="nameInput"/>

            <mx:Label  x="10" y="67" text="Feed URL:"/>

            <mx:TextInput  x="75" y="67" width="296"  id="urlInput"/>

            <mx:Button  x="379" y="37" label="ADD" width="55"  height="52" id="addButton"/>

</mx:Canvas>

MainScreen.mxml

<mx:Canvas  xmlns:mx="http://www.adobe.com/2006/mxml"

            xmlns="*"

            width="100%"  height="100%"

            creationComplete="onCreationComplete()">

            <mx:Script>

                        <![CDATA[

                                    import  com.techlabs.puremvc.model.DataProxy;

                        public  static const CREATION_COMPLETE: String = "myCreationComplete";

                        public  function onCreationComplete():void

                        {

                                    dispatchEvent(  new Event( CREATION_COMPLETE ) )

                        }

                        ]]>

            </mx:Script>

            <mx:Label  y="10" text="The TechLabs RSS Reader" horizontalCenter="0"  fontSize="15" fontWeight="bold"/>

            <mx:Label  x="70" y="55" text="Feeds"/>

            <mx:List  id="feedsList" x="10" width="160"  labelField="feedName" bottom="60" top="82"/>

            <mx:Button  id="addFeed" x="10" label="+"  width="96" bottom="10"/>

            <mx:Button  id="removeFeed" x="114" label="-"  width="56" bottom="10"/>

            <mx:List  id="postsList" y="82" height="106"  labelField="title" left="211" right="10"/>

            <mx:Label  x="452" y="55" text="Posts"/>

            <mx:HTML  id="postText" backgroundAlpha="0" top="196"  bottom="60" left="211" right="10"/>

            <mx:LinkButton  x="653" label="Go to Post" id="goToPostButton"  bottom="10"/>

</mx:Canvas>

5 – Other frameworks and considerations

As a final note I would like to point out that there are several alternatives to Pure MVC and all have their strong and weak points, I’ve written a small article about them  at Dreaming In Flash.

Also I would like to point out that Pure MVC is a very dynamic and flexible framework that allows a great deal of customization also that the code in this tutorial is a mere guideline, there are different ways of achieving the same results.

6 – Conclusion

That concludes our tutorial, and I would like to take the time to tell you about some other aspects of Pure MVC and Flex:

You can also get the Pure MVC as a SWC file and just drop it in the libs folder of your Flex project, this has advantages and disadvantages that I will not cover here but that you can find out in the Pure MVC forums, there is also a multitude of different flavors and utilities available at the plugin page.

I sincerely hope that this tutorial will help you get started with Pure MVC and will gladly answer any questions you might have.

I would also like to thank to Scalenine for the skin, and Daniele Ugoletti for his great work on the PureMVC AS3 Demo – Flex Application Skeleton.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>