ActionStep (plugin View) / Pixlib (MVC FrontController + Remoting) SUSHI Service

Translate original post with Google Translate

Le chef est de retour ^^. Au menu Sushis et MVC "on steroids".

Montez votre application en MVC. Une fois que le MVC a bien pris, truffez-le d'un FrontController. Parsemez avec quelques MovieClipHelpers. Arrosez finalement le tout avec, selon le choix et l'humeur, un ModelLocator et/ou un ServiceLocator...

Pixlib Remoting SUSHI service ? J'en entends déjà certains dire: il fait dans le recyclage intensif le gars...

Dans cet article je vais vous introduire à un joyau de Pixlib (il y en tellement...c'est une vraie caverne d'Ali Baba !).
SUSHI service repose sur une recette applicative incroyablement puissante et polyvalente basée sur le pattern MVC. "Recette" car ce sont bien des ingrédients qui sont mis à notre disposition. Selon ces ingrédients et l'inspiration on pourra, par exemple, faire gonfler notre MVC traditionnel en MVC à multiples Models (ModelLocator) ou bien comme c'est ici le cas, faire lever le tout en "Super MVC" (FrontController et ServiceLocator).

Get Adobe Flash player

sushiService.zip (250)

- L' User Interface

C'est toujours du ActionStep (j' aurai bien voulu tester ASWing but no time):
Cette fois-ci, les "forms" ou "states" sont regroupés dans un module swf autonome chargé au runtime dans l'application principale. Adopter ce design a supposé une nouvelle stratégie d'instantiation.

- "Un système totalement générique"

L'addition du FrontController, du MovieClipHelpers et d'un ModelLocator (classe core.Model) au pattern MVC confère à notre application un degré de modularité exemplaire. L'essentiel de cet article sera consacré à la description de ce "Super MVC". J' évoquerai les points qui, d'un point de vue personnel, m'ont semblé les plus pertinents.

- Le service Remoting

Il repose sur la version alpha du package remoting Pixlib. Il est déployé avec souplesse depuis un Singleton ServiceLocator.

Note:
le terme "state" à été employé pour désigner les différentes views contenues dans views.swf. Il n'est donc pas à prendre ici dans son sens premier "d'état" de l'application (généralement stocké dans un SharedObject, ValueObject, instance de Memento ou autre solution)

Commençons en images afin de prendre rapidemment des repères:

La structure du projet

Sushi service flp

Diagramme (maison) de la relation FrontController, MovieClipHelper, ServiceLocator & ModelLocator

Sushi Diagram

L'user Interface en standalone

Désolidariser de la structure de leur précédent hôte (ARP), les différents "states" de l'application (order, viewOrders et navigation) sont de nouveau de banales sous-classes de MovieClip dépourvues de toute fonctionnalité d' eventdispatching.

Ce nouveau profil de "plugin" a supposé changer la façon d'instancier le container des différents "states" de l'interface. Importée au runtime dans un MovieClip (via GraphicLib), l'UI doit déjà être instanciée sans dépendance de _root si nous souhaitons pouvoir le connecter à chaud tel un plugin.

L'astuce ? packager les classes et procéder à l' instantiation avec SWFMILL.

La première étape donc: compiler la view

/**
* @mtasc -version 8 -swf "classes.swf" -header 390:270:60:FCFCFC -trust
*/

// Imports...

class org.actionstep.view.sushi.Application extends MovieClip
{
    // MovieClip linkage
    static var id = (id="__Packages.org.actionstep.view.sushi.Application")+(Object.registerClass(id,Application)?"":"");

    private var navigation : MovieClip;
    private var order : MovieClip;
    private var viewOrders : MovieClip;
    private var dataPreloader : MovieClip;

    function onLoad()
    {
        // ActionStep theming
        org.actionstep.ASTheme.setCurrent(new
        org.actionstep.themes.plastic.ASPlasticTheme());

        // Set the initial display state of the application
        setInitialDisplayState();

        // Views Instantiation (no FLA here)
        navigation = attachMovie(Navigation.id,"navigation", 1);
        viewOrders = attachMovie(ViewOrders.id,"viewOrders", 2);
        order = attachMovie(Order.id,"order", 3);
    }
    // ...
}

La deuxième étape: instancier avec SWFMILL

<?xml version="1.0" encoding="iso-8859-1"?>
<movie version="8" width="390" height="270" framerate="60">
    <background color="#ffffff"/>
    <frame>
        <library>
            <clip id="Application" class="org.actionstep.view.sushi.Application" import="classes/classes.swf" />
        </library>
        <place id="Application" name="app" x="0" y="0" depth="1" />
    </frame>
</movie>

L'initialisation MVC

L'application principale (Application.as) est lancée de façon conventionnelle avec main().
Le Model est d'abord instancié. Notre unique vue, une sous-classe MovieClipHelper, s'abonne à l' écoute du Model. Le controller, un Singleton et sous-classe de FrontController est finalement initialisé. Le controller est le ciment de l'application. C'est lui qui va associer évènements typés et instances de commandes.

private function _init() : Void
{
    var model : Model = new SUSHIExpert();
    model.addListener( new SUSHIListUI() );
    Controller.getInstance().init();
}

I. LES VIEWS

Chargement de la View standalone

GraphicLib et Libstack, du chargement et multichargement intelligent.
Au travers de ces 2 implementations concrètes d'AbstractLib, nos assets sont chargés et stockés avec un identifiant de type String. En l'occurence, ces identifiants sont prédéfinis sous formes de constantes accessibles en static depuis sushi.uis.UIList. Ceci permet une meilleure traçabilité du couplage entre les assets et leurs 'accesseurs'.

var gl : GraphicLib = new GraphicLib( this, 20 );
gl.setName( UIList.SUSHI_List );
gl.addEventListener( GraphicLib.onLoadInitEVENT, this, _init );
gl.load( "deploy/views.swf" );

ou

var libs = new LibStack();
libs.enqueue(new GraphicLib(this, 20, false), UIList.SUSHI_List, "deploy/views.swf");
libs.addEventListener( LibStack.onLoadCompleteEVENT, this, _init );
libs.execute();

Les éléments sont ensuite accessibles globalement de plusieurs façons:

- Depuis le Singleton GraphicLibLocator:

var myView:MovieClip = GraphicLibLocator.getInstance().getGraphicLib( UIList.SUSHI_List ).getView();

- Depuis une sous-classe MovieClipHelper:

// SUSHIListUI.as

class sushi.uis.SUSHIListUI
    extends MovieClipHelper
{
    public function SUSHIListUI ()
    {
        super( UIList.SUSHI_List );
    }
}

- Depuis une sous-classe ViewHelper

import com.bourre.data.libs.GraphicLib;
import com.bourre.visual.ViewHelper;
import sushi.uis.UIList;

class sushi.uis.SUSHIListUI
    extends ViewHelper
{
    public function SUSHIListUI ( gl : GraphicLib )
    {
        super( gl.getView(), UIList.SUSHI_List );

        view.app.showDataPreloader();
        gl.show();
    }
}

C'est vraiment très pratique d'avoir un accès global sur les views:
Par exemple, si je souhaite déclencher une action sur une view depuis une Command:

Avec GraphicLibLocator:

import com.bourre.data.libs.GraphicLibLocator
import sushi.uis.UIList;

class sushi.commands.PlaceOrder
    implements Command
{
    public function execute( e : IEvent ) : Void
    {
        var myView:MovieClip = GraphicLibLocator.getInstance().getGraphicLib( UIList.SUSHI_List ).getView();
        myView.app.orderProcessed();
    }
}

avec MovieClipHelper

import com.bourre.visual.MovieClipHelper
import sushi.uis.UIList;

class sushi.commands.PlaceOrder
    implements Command
{
    public function execute( e : IEvent ) : Void
    {
        var myView = MovieClipHelper.getMovieClipHelper(UIList.SUSHI_List);
        myView.view.app.showDataPreloader();
        myView.traceTest(); // calling a method on SUSHIListUI
    }
}

Ici, en ce qui nous concerne, Sushi service n'a qu'une seule View de type MovieClipHelper

Sur une instance de MovieClipHelper on va, typiquement:

- définir le comportement des objets (TextField, MovieClips, v2 components...) présents dans la View au moyen de leurs noms d'instances.

Dans le cas de Sushi service, l'UI ActionStep n'est pas qu'une coque vide.

La view externe possède déjà des méthodes:

hideDataPreloader();
showDataPreloader();
orderFormSelect();
viewOrdersFormSelect();

Ensuite, dans chaque "state" ( Navigation, Order, ViewOrders ), les event handlers des boutons sont déjà définis, ex.:

// NSButton
placeOrderButton = (new NSButton()).initWithFrame(new NSRect(300, 205, 80, 22));
placeOrderButton.setStringValue("Place order");
placeOrderButton.setTarget(this);
placeOrderButton.setAction("placeOrder");
view.addSubview(placeOrderButton);

C'est pas grave du tout ^^. On va piloter et arranger le scope depuis notre sous-classe MovieClipHelper :)

class sushi.uis.SUSHIListUI
    extends MovieClipHelper
{
    private function _initBehaviors() : Void
    {
        _pbCancelOrder.setTarget(this);
        _pbCancelOrder.setAction("cancelOrder");

        _dSelectViewOrders = new Delegate(view.app, view.app.viewOrdersFormSelect);
        _dShowPreloader = new Delegate(view.app, view.app.showDataPreloader);
    }

    private function _gotoViewOrders() : Void
    {
        _dSelectViewOrders.execute();
        _dShowPreloader.execute();
    }

    private function cancelOrder() : Void
    {
        _dShowPreloader.execute();
        _fireEvent(new BasicEvent(EventList.cancelOrderEVENT, view.app.viewOrders.getOrder() ));
    }
}

Une "architecture de legos" où nous avons un contrôle total sur chaque pièce autonome pluggée.

Reprenons un peu le fil.
Sur une instance de MovieClipHelper, on va aussi:

- définir les callbacks du ou des Models.
- diffuser (avec notre EventBroadcaster global) des messages de type BasicEvent ou customs (sous-classe BasicEvent) vers les commandes correspondantes.

N'hésitez pas à étendre BasicEvent afin de créer vos propres event objects. C'est une autre pierre angulaire de Pixlib.

Jetez un oeil à la méthode placeOrder(). Vous verrez qu'elle broadcaste un événement de type OrderEvent.

private function placeOrder() : Void
{
    _gotoViewOrders();
    _fireEvent(new OrderEvent(EventList.placeOrderEVENT, view.app.order.orderName, view.app.order.orderTicket));
}

Attention, la comparaison est malheureuse, mais c'est juste pour faire capter rapidement l'idée à ceux qui sont d'un background ARP:
Voyez OrderEvent comme un Value Object (VO) passant les informations à la commande.
Voilà , vous pouvez vous lacher et crier hérésie haut et fort ^^.

II. Le FrontController

C'est dans sushi.commands.Controller (sous-classe de FrontController) que sont associés et stockés Events et Commands. A la différence du ControllerTemplate d'ARP, le FrontController n'impose aucune référence directe sur les views. Less coupling!

FrontController:

public function init() : Void
{
    push ( EventList.placeOrderEVENT, new PlaceOrder() );
    push ( EventList.getOrdersEVENT, new GetOrders() );
    push ( EventList.cancelOrderEVENT, new CancelOrder() );
}

ControllerTemplate:

private function addEventListeners ()
{
    app.orderForm.addEventListener ( "orderPizza", this );
    app.viewOrdersForm.addEventListener ( "getOrderList", this );
    app.viewOrdersForm.addEventListener ( "cancelOrder", this );
}

private function addCommands ()
{
    addCommand ( "orderPizzaCommand", OrderPizzaCommand );
    addCommand ( "getOrderListCommand", GetOrderListCommand );
    addCommand ( "cancelOrderCommand", CancelOrderCommand );
}

III. ServiceLocator et Commands

Dans le Singleton ServiceLocator nous définissons et stockons un ou plusieurs services remoting (selon nos besoins). Le nom d'alias de chaque service est défini en static.

- La définition du service remoting:

// sushi.service.ServiceLocator
public static var SUSHISERVICE:String = "dejavue_net.sushi.sushiService";

public function init( remotingURL : String ) : Void
{
    gatewayURL = "http://www.deja-vue.net/amfphp/gateway.php";
    push( ServiceLocator.SUSHISERVICE, ServiceLocator.SUSHISERVICE );
}

- Configuration type d'une commande:

1. Localisation du service remoting et appel et de la remote method

Attention, à l'heure où cet article est écrit, le package remoting est toujours en version alpha et non disponible depuis le svn. Je ne m' étendrai donc pas sur l'implémentation du responder ni sur l'API.

// class sushi.commands.PlaceOrder

public function PlaceOrder()
{
    _service = ServiceLocator.getInstance().getService( ServiceLocator.SUSHISERVICE );
}

public function execute( e : OrderEvent ) : Void
{
    _service.order( new ServiceResponder(this), e.getName(), e.getTicket() );
}

public function onResult( e : BasicResultEvent ) : Void
{

}

public function onFault(e : BasicFaultEvent) : Void
{

}

2. Tous les chemins mènent à Rome:

Là , c'est GRANDIOSE. Depuis une commande (pas seulement d'ailleurs) nous pouvons:

- appeler la view de notre choix (rappelez-vous de GraphicLibLocator, MovieClipHelper et ViewHelper).
- broadcaster un event typé et déclencher une nouvelle commande.

EventBroadcaster.getInstance().broadcastEvent( new BasicEvent(EventList.getOrdersEVENT) );

- appeler le model de notre choix.

SUSHIExpert(Model.getModel( ModelList.SUSHI_EXPERT )).onUpdate( e.getResult() );

Epoustouflant d'avoir autant de contrôle :)

Le mot de la fin (il est temps! ^^):

Il y a 2 jours, j' étais parti bille en tête avec l'idée de faire un papier sur le package MVC de Pixlib. En même temps, je me disais que c' était un peu léger comme sujet. C'est en approfondissant les packages events et visual que j'en suis arrivé au FrontController + MovieClipHelper et à approfondir. Pixlib n'avait pas fini de m' étonner...
Je reste bluffé. Je ne m'attendais vraiment pas ça. Devinez avec quoi je vais boulonner mon prochain projet ;) .

Chapeau bas à Francis.

Liens de Référence:

-MVC et FrontController
http://www.tweenpix.net/blog/index.php?2004/09/22/460-whiteboard-10
Discussion about models in mvc and front controller patterns on Pixlib list

GraphicLib, LibStack et MovieClipHelper
http://www.get-url.net/blog/?47--pixlib-libstack-ou-le-multi-chargement
http://www.get-url.net/blog/?48--pixlib-graphiclib-et-moviecliphelper-vs-movieclip-parti

AS2, ActionStep, Application Architecture, MVC, Pixlib

If you enjoyed this post, please consider to leave a comment or subscribe to the feed and get future articles delivered to your feed reader.

Comments

26 Responses to “ActionStep (plugin View) / Pixlib (MVC FrontController + Remoting) SUSHI Service”

Leave Comment

(required)

(required)