MAY THE SOURCE BE WITH YOU…

Pure MVC Awesome practices

April 30th, 2009

As I have worked extensively with PureMVC I have taken note of practices that make life easier. I wouldn’t call them “Best Practices” because who am I to say whats best. But these practices have saved me tons of time and I value my time, thus the titles.

#1: As we know each Mediator is responsible for exactly one view component. If you are loading the artwork from flash the visual elements of the view component should be stored in a single dedicated FLA (no monolith FLAs, one FLA/swf per viewcomponent). Its common work flow when you have a team of developers each gets assigned to a view component. This way you don’t get locking issues on the FLA or SWF.

 

#2: View components should aggregate Sprite (or MovieClip), not extend it. Extending Sprite has become an all to often practice for dealing with view components. Gang of Four tells us to prefer aggregation. If your Display Object hierarchy is defined in the Flash IDE, using the sprite instead of extending the sprite means you don’t have to link the sprite to your code inside of flash, and thus don’t have to compile code in the Flash IDE (which really helps out work flow and development time). View components should probably extend a “viewComponent” base class that comes with shared view component functionality (ie verifies the Display Object Hierarchy).

 

#3: Design a unit test for each view component. All to often when thinking about unit tests we think the class is the unit to test. Doing this usually creates constraints that take away from object oriented design. Remember, as front end developers our view components are our primary units, and they are event driven, so class testing becomes academic. Think of the view component as the unit to test. One view component should map to one swf so now all you need is a unit test that embeds or loads that swf, and runs the component through its paces. Every view component should pass its unit test before being integrated into the application.

 

#4: Use a “navigation” command and “navigation” notification: The navigation command should be tied to a navigation notification which is broadcast whenever the user wants to navigate to a new macro state of the application. The new macro state should have a name, which is carried in the payload of the notification. Allow for transition states with loading steps if you plan on tweening between states. The transition state and step should be stored in the state proxy as well.

 

Although its the mediators job to transition whenever a navigation notification is handled, it will be the navigation commands job to deep link using swfaddress, and update the stateproxy with the new application state. On the other hand the swfaddress.change event should send a navigation notification as well, via logic that parses the new address. Watchout though, responding to address change isn’t that simple as you need to make sure your application is sufficiently initialized before sending the navigation notification.

 

#5: Understand the difference between macro-states and view component states. Macro-states need to be accessible through-out the application. For example, any state information stored in the URL is a part of the macro-state. However only the given view component needs to know the view component state, so don’t store any view component state information in the model.

 

#6: Mediators should inherit from one of a few base Mediators. Your application represents certain visual metaphors to the user . For example the visual metaphors might consist of Pages, Popups, Modules and Menus. When this is the case, all of your mediators for pages should inherit from an Abstract Page Mediator, as with Popups , Modules and Menus. Before architecting your application, understand the view metaphors, and use those base metaphors as abstract mediators.

 

#7: Know what MVC will help you with, and what it wont help you with. MVC is all about a systematic way to startup, handle user gestures, and back-end events. This should be the starting point for designing out our architecture. The pure MVC tutorial’s fully cover startup best practices. As for user gestures, the view component should wisely keep some things to itself, while translating other gestures into dispatched events. The mediator will then respond to those events, sometimes by sending a notification, sometimes by accessing a proxy to change the model. It is important to separate what the gestures the view component needs to know about, versus what the mediator needs to know about, versus what the MVC layer needs to know about. The gestures should also change there name as they are passed from view component to mediator to MVC layer, because there significance changes in each separate context.

 

As for backed service calls, use the same proxy that handles the request to handle the response. Use a responder pattern and delegates to handle parsing.

 

If you need to handle some occurrence that is neither startup, a gesture, or back end event, consider handling it within a view component and not on the MVC layer . For example, MVC isn’t here to manage a game stream, so if you are writing a game use the MVC layer to navigate to the game, to manage widgets and the heads up display, but the game stream should be managed within a view component.

 

#8: Whenever the Model changes and view components need to be updated as a result , the proxy should send a DATA_REFRESH notification so Mediators may update data supplied to view components.

 

#9: All Vos should inherit from a base VO, all VOCollections should inherit from a base VOCollection. See the end of this article for example base VO and base VOCollection code.

 

#10: Don t worry about using the facade. One misunderstanding most developers have after reading the PureMVC tutorials is that they will be doing a bunch of work with the facade. Maybe this is true if you are creating a logic layer, and the facade exposes some complex api. Usually the facade only exposes startup and holds constants, and the applications entry point is the only entity that calls a function from the facade. Turns out the real work is under the hood of the facade for most RIAs.

 

#11: Here is a list of some proxies I always use and information about each:

ConfigProxy: Reads in the config.xml file. Uses a config VO custom to the data in the config.xml file. See ConfigProxy and LoadEXDelegate code below.

StartupMonitorProxy: Fully documented in PureMVC docs. Really makes loading easy.

TextProxy: Works a lot like the configProxy but reads a text.xml file and uses a textVO to contain all text that will be used in application.

 

 

#12: Use the PureMVC consule, this thing is wicked!!!

 

#13: A. Always, B. be, M. Multi coring, always be multi coring. Why use single core, its not easier, and it could paint you in a corner.

 

#14: Preload with a preloadorMediator that listens for the Loading Step notifications dispatched by the startupMonitor Proxy.

 

#15: Be happy you picked PureMVC. If it seems more complex than your needs are just wait until your client comes back with suggestions and you will be glad you picked a framework that can handle complexity.

 

Hey lets be friends,

add me to linked in: http://www.linkedin.com/pub/dir/?last=khabazian&first=iman

or come see me in person as I will be speaking at various Flash/Flex user groups throughout southern California in May or June (schedule will be posted soon but I have confirmed San Diego Users Group May 20th at 6pm and OC Flash at 7pm June second, and L.A. Flex in July).

 

///////////////////////////////////////////////////////////

// VO.as

// ActionScript 3.0 Implementation of the Class VO

// Created on: 03-May-2008 12:24:46 AM

// Original author: iMAN Khabazian

///////////////////////////////////////////////////////////

 

package com.iMANIT.ImplementationPatterns

{

 

 

/**

*

*

* public class VO

* {

* public var objectProxy:Proxy = new Proxy();

* public var voEvent:VOEvent;

*

* // id:String get/set

* public function get id():String { return objectProxy.getData(id)};

* public function set id( p_id:String ):void { objectProxy.setData(id , p_id)};

* }

* @author iMAN Khabazian

* @version 1.0

* @created 03-May-2008 12:24:46 AM

*/

public class VO

{

public var id: uint;

 

/**

* constructor.

*/

public function VO(tID: uint):void

{

if (tID)

{

id = tID;

}

 

}

public function toString():String

{

return (”VO: ” + id);

}

 

}//end VO

 

}

///////////////////////////////////////////////////////////

// VOCollection.as

// ActionScript 3.0 Implementation of the Class VOCollection

// Created on: 03-May-2008 12:24:46 AM

// Original author: iMAN Khabazian

///////////////////////////////////////////////////////////

 

package com.iMANIT.ImplementationPatterns

{

 

 

/**

* Contains all VOs of a particular type.

* @author iMAN Khabazian

* @version 1.0

* @created 03-May-2008 12:24:46 AM

*/

 

 

public class VOCollection

{

 

/**

* Array of VOs

* We need to support: add, removebyID

*/

// public var VOs: Array;

protected var localAr:Array;

protected var name:String = “Generic Collection”;

/**

* constructor, inits Array

*/

public function VOCollection(ar:Array=null, name:String = null)

{

if (name) this.name = name;

if (ar) localAr = ar;

else localAr = new Array();

}

 

/**

* @inheritDoc

*/

public function get length():int

{

return localAr.length;

}

 

public function getItemAt(index:int):Object

{

 

if (index < 0 || index >= length)

{

trace(” VOCollection.getItemAt OutOfBounds”);

return(new Object());

}

// trace (”VOCollection.getItemAt: ” + index + “)” + localAr[index]);

 

return localAr[index];

 

}

/*

*

*/

public function add(item:Object): int

{

// trace (”VOCollection.add: ” + item.toString());

localAr.push(item);

return localAr.length – 1;

}

/**

*

*/

public function setItemAt(item:Object, index:int):void

{

if (index < 0 || index >= length)

{

trace(” VOCollection.setItemAt OutOfBounds”);

}

else

{

localAr[index] = item;

}

}

 

public function getIndexFor(item:Object):int

{

 

for (var cntx:int =0; cntx < localAr.length; cntx++ )

{

if (localAr[cntx] == Object) return cntx;

}

return(-1);

}

public function getItemByName(name: String):Object

{

 

for (var cntx:int =0; cntx < localAr.length; cntx++ )

{

if (localAr[cntx].name == name)

{

return (localAr[cntx]);

}

}

return(undefined);

}

public function toString():String

{

var outputString: String = ” ” + this.name +”(”+localAr.length +”)” ;

 

if (localAr.length > 0)

for (var cntx:int =0; cntx < localAr.length; cntx++ )

{

outputString +=”\n “+ cntx+”)” + localAr[cntx].toString();

}

else

{

outputString = “:EMPTY”;

}

 

 

return outputString;

}

 

public function copy():VOCollection

{

var retCollection: VOCollection = new VOCollection();

for (var cntx:int =0; cntx < localAr.length; cntx++ )

{

retCollection.add(localAr[cntx].copy);

}

 

return retCollection;

}

 

public function sortOn(fieldName: Object): void

{

localAr.sortOn(fieldName,[Array.NUMERIC]);

}

 

}//end VOCollection

package AppMVC.Model.Business

{

import flash.errors.*;

import flash.events.Event;

import mx.rpc.IResponder;

import flash.net.URLLoader;

import flash.net.URLRequest;

import Utils.Debugger;

public class LoadEXDelegate

{

private var responder : IResponder;

private var url:String;

private var loader:URLLoader;

private var mainXML:XML;

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

{

loader = new URLLoader();

this.url = url;

this.responder = responder;

}

 

public function load() : void

{

loader.addEventListener(Event.COMPLETE, onComplete);

loader.load(new URLRequest(url));

}

private function onComplete(evt:Event): void

{

try

{

mainXML = new XML(loader.data)

 

} catch(e:Error)

{

this.responder.fault(e);

return;

}

this.responder.result(mainXML);

}

}

}

 

 

package AppMVC.Model

{

///////////////////////////////////////////////////////////

// ConfigProxy.as

// Macromedia ActionScript Implementation of the Class ConfigProxy

// Generated by Enterprise Architect

// Created on: 03-Jul-2008 5:32:12 PM

// Original author: iMAN

///////////////////////////////////////////////////////////

import VOs.ConfigVO;

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

import APPMVC.Model.Helpers.ConfigEXResource;

import D4MVC.Model.Business.LoadEXDelegate;

import D4MVC.ApplicationFacade;

import mx.rpc.IResponder;

import Utils.Debugger; 

/**

* anything loaded from external Config file

* @author iMAN

* @version 1.0

* @created 03-Jul-2008 5:32:12 PM

*/

public class ConfigProxy extends Proxy implements IResponder

{

public static const NAME:String = “ConfigProxy”;

 

/**

* Constructor

*

* @param proxyName

* @param data

*/

public function ConfigProxy(data:Object = null )

{

super(NAME, data);

startupMonitorProxy = facade.retrieveProxy( StartupMonitorProxy.NAME ) as StartupMonitorProxy;

startupMonitorProxy.addResource( ConfigProxy.NAME, true );

 

 

}

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 : LoadEXDelegate = new LoadEXDelegate(this, ‘data/config.xml’);

delegate.load();

}

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

public function result(rpcObject:Object ) : void

{

data= ConfigEXResource.parse(rpcObject);

this.startupMonitorProxy.resourceComplete( ConfigProxy.NAME );

}

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

public function fault(info:Object) : void

{

Debugger.log (”ConfigProxy.fault”);

// log.debug(”SuggestionProxy.fault”);

// send the failed notification

this.sendNotification( ApplicationFacade.LOAD_FAILED, “ConfigProxy Failed, ” + info.toString() );

}

Comments are closed.

Proudly powered by WordPress. Theme developed with WordPress Theme Generator.
Copyright © MAY THE SOURCE BE WITH YOU…. All rights reserved.