Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Flex & PureMVC

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Compatible Flex Builder. Cliquer pour en savoir plus sur les compatibilités.Par slyc3 (Steve Domin), le 05 avril 2009

Introduction

Bonjour,
Dans cet article, nous allons voir comment créer un projet en utilisant le framework PureMVC.
Je vais commencer par vous présenter le framework, ses composantes, puis nous créerons un premier projet en utilisant le framework.

Présentation de PureMVC

PureMVC est une implémentation du méta pattern Modèle Vue Contrôleur.
Le MVC permets de séparer votre code en trois parties bien distincts : le Modèle, la Vue et le Contrôleur.
Le Modèle est chargé de contenir les données ou de les décrire. Il ne doit en aucun cas les présenter.
La Vue est quand à elle chargé d'afficher une représentation concrète du Modèle. C'est aussi avec la vue que l'utilisateur interagira. La vue se charge d'afficher les données, sans les traiter.
Pour finir, le Contrôleur, qui est chargé de synchroniser le Modèle et la Vue. Il reçoit les évènements de l'utilisateur et réagit en conséquence.
Je ne vais pas m'étendre plus ici sur le pattern MVC, de nombreux tutoriels et articles sont disponibles sur internet.
Revenons donc sur le framework, et découvrons ses composantes :

- Les proxys (Modèle) : les proxys sont chargés de gérer les données, ils peuvent communiquer avec vos services distants, par exemple pour récupérer des données d'une base de donnée, ou encore récupérer le contenu d'un fichier xml.

- Les médiateurs (Vue) : les médiateurs représente les composants visuels de votre application, c'est au travers d'eux que vous allez manipulé votre vue. Ils ajouteront les écouteurs d'évènements, seront à l'écoute de notifications et en enverront eux-mêmes.

- Les commandes (Contrôleur) : Les commandes font le lien entre les proxys et les médiateurs, exécutent d'autres commandes, envoient des notifications.

- La façade : la façade est un singleton, qui initialise les acteurs centraux (modèle, vue, contrôleur, en effet avec PureMVC vous n'instancierez jamais directement ces acteurs), leurs permettant ainsi de communiquer entre eux, par le biais de cette façade.

- Les notifications : Un énorme avantage de PureMVC est qu'il a été porté pour de nombreux autres langages en dehors de l'AS3, à savoir pour l'instant : AS2, C#, Objective-C, Ruby, Python, haXe, ColdFusion, Java, Javascript et PHP.
Cela vous permet donc d'utiliser le même framework MVC pour vos différents projets dans d'autres langages. Un des conséquences directes de ces portages est qu'il ne fallait en aucun utiliser le système d'Event propre à Flash, celui-ci n'étant pas forcément existant dans d'autres environnements.
PureMVC a donc son propre mécanisme, les notifications. Concrètement ils fonctionnent un peu de la même manière que le système d'Event de Flash : les médiateurs “s'abonnent” à des notifications et exécutent les traitements voulus à la réception de celle-ci, ou en envoient. Les proxys peuvent envoyer des notifications, par exemple en cas échec ou de réussite de l'appel d'une procédure distante, mais pas en recevoir. Les commandes sont quand à elles directement “mappés” aux noms des notifications et se déclenchent automatiquement lorsque ces dernières sont envoyées.

Un des points qu'il faut retenir est que l'on ne doit pas voir les notifications et les events comme concurrent. Il est tout a fait indiqué d'utiliser les Events pour “créer des composants visuels qui soient facilement réutilisables et qui, lorsqu’ils sont correctement construits, ignorent même le système PureMVC auquel ils sont couplés” (Doc. PureMVC).

Voilà pour la présentation du framework, si vous voulez en savoir plus je vous invite à vous rendre sur le site de PureMVC (http://puremvc.org/), vous y trouverez de nombreuses informations, des pdfs, des exemples de codes, et il y a un forum où vous pourrez éventuellement poser vos questions.

Nous allons maintenant nous pencher sur la création de notre première application.

Première application avec PureMVC

Tout d'abord commençons par aller récupérer le SWC du framework PureMVC.
Pour cela rendez-vous à cette adresse : http://trac.puremvc.org/PureMVC_AS3/wiki/Downloads
et cliquez sur “PureMVC Standard Framework for AS3”.
Vous allez avoir une archive zip, dézippez-la à l'endroit de votre choix.
Maintenant lancez Flex Builder et créer un nouveau projet.
File>New>Flex Project.
Donner un nom à votre projet, choisissez Web application, et “none” pour Application server type, puis cliquez sur “Next”.
Dans la page suivante ne changez rien et faites “Next”.
Ici, nous allons ajouter le SWC de PureMVC à notre projet.
Pour cela cliquez sur “Library path”, puis cliquez à droite sur “Add SWC”. Indiquez le chemin qui mène au SWC de PureMVC et cliquez sur “Ok”.
Ensuite vous pouvez cliquer sur Finish. Nous pouvons dès maintenant utiliser toutes les classes de PureMVC dans notre projet.

Avant tout chose, nous allons définir l'organisation des différentes classes et composants pour notre projet.
Créer l'arborescence suivante : org/puremvc/app.
Dans le dossier app, créer trois dossiers : model - view - controller.
Votre projet devrait ressembler à quelquechose comme ceci après :

Bon, commençons à coder !

La première chose à faire dans tout projet utilisant PureMVC est de créer la façade concrète de votre application.
Pour cela vous devez créer une nouvelle classe qui étend la classe Facade de PureMVC, dans le dossier app.
Voici le code de cette classe:

package org.puremvc.app
{
    import org.puremvc.as3.patterns.facade.Facade;
 
    public class ApplicationFacade extends Facade
    {
        public function ApplicationFacade()
        {
            super();
        }
 
    }
}

Dans cette classe nous allons créer 3 méthodes:
- getInstance : qui sera chargé de nous retourner l'instance de notre façade concrète.
- initializeController : qui sera chargé d'initialiser le contrôleur et d'enregistrer toutes nos commandes pour que celles-ci s'exécutent à l'envoi des notifications correspondantes.
- startup : celle-ci envoie une notification qui fera s'exécuter la commande chargée de démarrer l'application, nous verrons ce que fait cette commande plus en détail dans quelques instants.

Voici le code de ces méthodes:

public function startup(app:PureMVC):void
{
	sendNotification(STARTUP, app);
}
 
public static function getInstance():ApplicationFacade
{
	if (instance == null)
     		instance = new ApplicationFacade();
     return instance as ApplicationFacade;
}
 
protected function initializeController():void
{
	super.initializeController();
     registerCommand(STARTUP, ApplicationStartupCommand);
	registerCommand(COMMAND_LOAD_MENU, LoadMenuCommand);
}

Nous devons créer dans cette façade cinq constantes, en fait le nom de nos notifications.
Le code :

public static const STARTUP:String = "startup";
public static const VIEW_MENU:String = "viewContact";
public static const LOADING_MENU_COMPLETE:String = "loadingMenuComplete";
public static const ERROR_LOAD_FILE:String = "Le fichier ne peut pas être chargé";
public static const COMMAND_LOAD_MENU:String = "LoadMenuCommand";

Et voilà, pour notre façade, c'est tout pour l'instant mais nous y reviendrons plus tard.
Retournons dans le mxml de notre application.
Nous devons maintenant créer une nouvelle instance de notre façade et envoyer une notification pour signaler à notre application de démarrer.
Nous allons aussi y créer un viewstack, qui contiendra les différentes vues de notre application.
Ajoutez à ce viewstack un canvas contenant un bouton.

Tant que nous sommes dans les composants visuels nous allons en profiter pour créer une vue de notre application.
Un simple canvas contenant une Tile qui contiendra les boutons de notre menu.
Créer un dossier mycomponents dans le dossier view et créez y un nouveau composant MXML. Nommez le MenuView, basé sur Canvas, width et height à 100%.
Voici le code de composant:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
 
    <mx:Tile id="menuT" 
        horizontalCenter="0" 
        verticalCenter="0"
        width="100%"
        height="100%">
 
    </mx:Tile> 
 
</mx:Canvas>

Ajouter un composant du type de celui que vous venez de créer dans le viewstack de votre application.
Voici le code votre mxml principal.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
    xmlns:mycomponents="org.puremvc.app.view.mycomponents.*"
    layout="absolute"
    creationComplete="facade.startup(this);" >
 
    <mx:Script>
        <![CDATA[
            import org.puremvc.app.ApplicationFacade;
 
 
            private var facade:ApplicationFacade = ApplicationFacade.getInstance();
 
        ]]>
    </mx:Script>
 
    <mx:ViewStack id="appVS" width="100%" height="100%">
 
        <mx:Canvas>
            <mx:Button id="btnMenu" label="Menu" horizontalCenter="0" verticalCenter="0"/>   
        </mx:Canvas>
 
        <mycomponents:MenuView id="menuView" width="100%" height="100%"/>
 
    </mx:ViewStack>
 
</mx:Application>

A ce stade vous devriez normalement avoir deux erreurs, provenant de votre ApplicationFacade, vous indiquant que vous tentez d'accéder à une propriété non définie ApplicationStartupCommand et LoadMenuCommand.
En effet, nous avons mappé nos commandes à une notification mais nous n'avons pas encore créé les commandes correspondante.
Nous allons y remédier dès maintenant.
Créer une nouvelle classe dans le dossier controller, nommé ApplicationStartupCommand. Cette classe doit étendre MacroCommand.
Cette commande est un peu particulière, c'est une MacroCommand, c'est à dire que c'est une commande qui exécute d'autres sous-commandes.
En l'occurrence, deux autres commandes : ModelPrepCommand et ViewPrepCommand, qui comme vous vous en doutiez certainement sont deux commandes dédiées à la préparation du modèle et de la vue.
Dans ModelPrepCommand nous allons enregistrer les proxys qu'utilisera notre application et dans ViewPrepCommand, le médiateur de notre vue principale.
Pour créer ces commandes vous devez créer deux nouvelles classes dans le dossier controller, ModelPrepCommand et ViewPrepCommand, qui étendent SimpleCommand.
Trêve de palabre, voici le code de toutes ces commandes, excepté LoadMenuCommand que nous verrons plus tard.

ApplicationStartupCommand.as

package org.puremvc.app.controller
{
    import org.puremvc.as3.interfaces.*;
    import org.puremvc.as3.patterns.command.*;
 
    public class ApplicationStartupCommand extends MacroCommand
    {
        override protected function initializeMacroCommand():void
        {
            addSubCommand(ModelPrepCommand);
            addSubCommand(ViewPrepCommand);
        }
    }
}

ModelPrepCommand.as

package org.puremvc.app.controller
{
 
    import org.puremvc.as3.interfaces.*;
    import org.puremvc.as3.patterns.command.*;
    import org.puremvc.as3.patterns.observer.*;
 
    public class ModelPrepCommand extends SimpleCommand
    {
        override public function execute(note:INotification):void    
        {
            facade.registerProxy(new MenuProxy());
        }
    }
}

ViewPrepCommand.as

package org.puremvc.app.controller
{
    import org.puremvc.as3.interfaces.*;
    import org.puremvc.as3.patterns.command.*;
    import org.puremvc.as3.patterns.observer.*;
 
    public class ViewPrepCommand extends SimpleCommand
    {
        override public function execute(note:INotification):void    
        {
            facade.registerMediator(new ApplicationMediator(note.getBody() as PureMVC));
        }
    }
}

Vous voyez deux nouveaux éléments apparaître : ApplicationMediator et MenuProxy. Un proxy et un médiateur.
ApplicationMediator est le médiateur de notre vue principale.
Il est chargé d'enregistrer tous les autres médiateurs de notre application.
Il sera à l'écoute d'une notification, VIEW_MENU et lorsqu'il recevra cette notification il changera l'index du viewstack pour afficher la vue contenant notre composant MenuView.
Il dispose aussi d'un getter permettant de récupérer l'instance de notre mxml principal.
Voci le code de cette classe:

package org.puremvc.app.view
{   
    import org.puremvc.app.ApplicationFacade;
    import org.puremvc.as3.interfaces.*;
    import org.puremvc.as3.patterns.mediator.Mediator;
 
    public class ApplicationMediator extends Mediator implements IMediator
    {
        public static const NAME:String = "ApplicationMediator";
 
        public static const HOME_VIEW:Number = 0;
        public static const MENU_VIEW:Number = 1;
 
        public function ApplicationMediator(viewComponent:PureMVC)
        {
            super(NAME, viewComponent);
            facade.registerMediator(new MenuViewMediator(app.menuView));
        }
 
        override public function listNotificationInterests():Array
        {
            return [ApplicationFacade.VIEW_MENU];
        }
 
 
        override public function handleNotification(note:INotification):void
        {
            switch (note.getName())
            {
                case ApplicationFacade.VIEW_MENU:
                    app.appVS.selectedIndex = MENU_VIEW;
                    break;
            }
        }
 
 
        protected function get app():PureMVC
        {
            return viewComponent as PureMVC;
        }
 
    }
}

MenuProxy est quand à lui un proxy chargé de lire un fichier xml contenant les éléments de notre menu. Il s'appuie sur une classe permettant de charger le xml.
Créez un nouveau dossier nommé business dans votre dossier model et créez y une nouvelle classe : LoadXMLDelegate.
Voici le code de cette classe:

package org.puremvc.app.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) 
        {
            this.service = new HTTPService();
            this.service.resultFormat = 'xml';
            this.service.url = url;
 
            this.responder = responder;
        }
 
        public function load() : void 
        {
            var token:AsyncToken = service.send();
 
            token.addResponder(this.responder);
        }
    }
}

C'est une classe que j'ai repris d'un exemple sur PureMVC.
Suivant le succès ou l'échec le proxy effectue diverses opérations.
Dans le cas d'un succès il envoie une notification disant que le chargement à réussi et contenant le résultat du chargement du xml.
Dans le cas contraire il envoie une notification indiquant que le chargement à échoué et un message s'affiche alors.
Voici le code de ce proxy:

package org.puremvc.app.model
{
    import mx.controls.Alert;
    import mx.rpc.IResponder;
    import mx.rpc.events.ResultEvent;
 
    import org.puremvc.app.ApplicationFacade;
    import org.puremvc.app.model.business.LoadXMLDelegate;
    import org.puremvc.as3.interfaces.IProxy;
    import org.puremvc.as3.patterns.proxy.Proxy;
 
    public class MenuProxy extends Proxy implements IProxy, IResponder
    {
        public static const NAME:String = "MenuProxy";
 
 
        public function MenuProxy()
        {
            super(NAME);            
        }
 
        public function load():void
        {            
            var delegate:LoadXMLDelegate = new LoadXMLDelegate(this, 'assets/menu.xml');
            delegate.load();           
        }
 
        public function result(data:Object) : void
        {            
            var menuXml:XML = new XML((data as ResultEvent).result);
 
            sendNotification(ApplicationFacade.LOADING_MENU_COMPLETE, menuXml, "XML");
 
        }
 
        public function fault(info:Object):void 
        {
            Alert.show(ApplicationFacade.ERROR_LOAD_FILE);
        }
 
    }
}

Nous allons en profiter pour créer notre xml.
Créer un nouveau dossier à la racine de votre projet, assets.
Et créer dedans un nouveau fichier xml, menu.xml dont voici le contenu:

<root>
    <menuitem id="menu1">
        <name value="Menu 1"/>
    </menuitem>
    <menuitem id="menu2">
        <name value="Menu 2"/>
    </menuitem>
    <menuitem id="menu3">
        <name value="Menu 3"/>
    </menuitem>
</root>

Il est temps de nous occuper du médiateur de notre composant MenuView.
Créer une nouvelle classe dans le dossier view, nommé MenuViewMediator. Celle-ci doit étendre Mediator et implémenter IMediator.
Ce médiateur est à l'écoute de la notification LOAD_MENU_COMPLETE. Dès que celle-ci est envoyée il en récupère le contenu et créez le menu.
Voici le code du médiateur.

package org.puremvc.app.view
{
    import flash.events.MouseEvent;
 
    import mx.controls.Alert;
    import mx.controls.Button;
 
    import org.puremvc.app.ApplicationFacade;
    import org.puremvc.app.view.mycomponents.MenuView;
    import org.puremvc.as3.interfaces.*;
    import org.puremvc.as3.patterns.mediator.Mediator;
 
 
    public class MenuViewMediator extends Mediator implements IMediator
    {
        public static const NAME:String = "MenuViewMediator";
 
        public function MenuViewMediator(viewComponent:MenuView)
        {
            super(NAME, viewComponent);
        }
 
        protected function get menuView():MenuView
        {
            return viewComponent as MenuView;
        }
 
        override public function listNotificationInterests():Array
        {
            return [ApplicationFacade.LOADING_MENU_COMPLETE];
        }
 
        override public function handleNotification(note:INotification):void
        {
            switch (note.getName())
            {
                case ApplicationFacade.LOADING_MENU_COMPLETE:
                    Alert.show("ok");
                    for each (var menuitem:XML in note.getBody().menuitem)
                    {
                        var button:Button = new Button();
                        button.width = 220;
                        button.height = 120;
                        button.label = menuitem.name.@value;
                        button.addEventListener(MouseEvent.CLICK, menuClickHandler);
                        this.menuView.menuT.addChild(button);
                        trace(menuitem.name.@value + " button created");
                    }
                    break;
            }
        }
 
        private function menuClickHandler(event:MouseEvent):void
        {
            switch (event.currentTarget.label)
            {
                case "Menu 1":
                    Alert.show(event.currentTarget.label);
                    break;
                case "Menu 2":
                    Alert.show(event.currentTarget.label);
                    break;
                case "Menu 3":
                    Alert.show(event.currentTarget.label);
                    break;
            }
        }
    }
}

Dernière ligne droite, nous devons faire en sorte que la notification LOAD_MENU_COMMAND soit envoyée quand un utilisateur clique sur le bouton “Menu”.
Pour cela nous devons ajouter un écouteur d'évènement click à notre bouton et y associer une méthode qui enverra la notification.
Rajouter le code suivant dans le constructeur de ApplicationMediator:

this.app.btnMenu.addEventListener(MouseEvent.CLICK, clickHandler);

et créer la fonction correspondante :

public function clickHandler(event:MouseEvent):void
{
	sendNotification(ApplicationFacade.COMMAND_LOAD_MENU);
     sendNotification(ApplicationFacade.VIEW_MENU);
}

Occupons-nous maintenant la commande qui fera appel au proxy pour charger le xml.
Créez une nouvelle classe, basé sur SimpleCommand et implémentant ICommand
Voici le code de cette commande:

package org.rnja.platform.controller
{
    import org.puremvc.app.model.MenuProxy;
    import org.puremvc.as3.interfaces.ICommand;
    import org.puremvc.as3.interfaces.INotification;
    import org.puremvc.as3.patterns.command.SimpleCommand;
 
    public class LoadMenuCommand extends SimpleCommand implements ICommand
    {
        private var menuProxy:MenuProxy;
 
        override public function execute(notification:INotification):void
        {
            menuProxy = facade.retrieveProxy(MenuProxy.NAME) as MenuProxy;
 
            menuProxy.load();
        }
 
    }
}

On récupère l'instance de notre proxy et on fait appel à sa méthode pour charger le xml. Rien de très compliqué. Suivant le résultat de l'opération le proxy agira en conséquence via les méthodes que nous lui avons défini.

Et voilà, c'est fini, peut-être encore quelques imports à faire à droite à gauche.
Pour cela placez vous derrière la classe qui pose problème et et appuyez sur Cmd+Espace ou Ctrl+Espace selon votre plate-forme.

Voilà à quoi devrez ressembler votre arborescence à la fin de ce projet:

En savoir plus

Vous pouvez trouvez l'intégralité du projet en archive Flex ici:

Si vous souhaitez en savoir plus sur PureMVC je ne saurais que vous conseiller de vous rendre sur le site officiel de PureMVC:
- http://puremvc.org
Vous y trouverez un document intitulé, PureMVC, implémentations, idiomes et meilleures pratiques qui est une vraie mine d'information sur ce framework.
Si vous le souhaitez vous pouvez aussi me contacter directement :
slyc3isback[at]me.com