Flex ActionScript – Stratégies pour la communication entre Composants Flex
Tous les développeurs Flex, débutants et expérimentés, doivent faire face à un défi commun: permettre la communication entre composants Flex de la manière la plus puissante et la plus rapide. Pour permettre la communication entre composants, chaque composant doit connaître l'existence de l'autre composant, pour pouvoir échanger des données. En tant que développeurs, nous voulons suivre les principes de la POO (Programmation Orientée Objet) qui nous disent que chaque composant doit être le plus autonome possible. C'est là que les développeurs doivent cogiter: Comment deux composants Flex peuvent échanger de la data, sans connaître l'existence de l'autre?
Il y a de nombreuses manières d'aborder ce problème, mais pour cet article, on va voir 3 approches différentes pour Adobe Flex, voir leur fonctionnement, les problèmes qu'ils résolvent, et quelles sont leurs limitations.
Tout est une question d'évènement (Events)
Une des réponses possibles à ce problème de POO est d'utiliser le modèle évènementiel de Flash pour permettre à chaque composant de réagir aux évènements des autres. Ce modèle évènementiel permet aux composants de propager (dispatch) des objets de type évènement qui contiennent des informations sur l'information qui les a crée, comme par exemple, le click sur un bouton, ou une modification dans une ComboBox. Le modèle évènementiel permet aussi aux composants d'écouter un type d'évènement spécifique, pour que lorsqu'un changement se produit dans le composant source, l'évènement est propagé et l'écouteur (listener) est informé.
Le problème avec ce modèle est que l'écouteur doit savoir quel composant écouter, pour qu'il puisse recevoir l'évènement lorsqu'il est propagé. Par exemple, dans une application, on a un composant List et un composant DataGrid. Quand l'utilisateur modifie sa sélection dans la List, on veut mettre à jour la DataGrid. Pour cela, on doit écrire un peu de code dans le fichier MXML qui va contenir nos 2 composants. Quand la List propage un évènement de type CHANGE, le code met à jour le dataProvider de la DataGrid. Pour cela, notre code doit accéder à la référence de la List, et à celle de la DataGrid pour pouvoir utiliser la méthode addEventListener() pour écouter les changements:
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
[Bindable] public var dataOne:Array;
[Bindable] public var dataTwo:Array;
public function handleChangeEvent(event:ListEvent):void {
myDataGrid.dataProvider = dataTwo;
}
]]>
</mx:Script>
<mx:List id="myList" change=handleChangeEvent(event)" />
<mx:DataGrid id="myDataGrid" dataProvider="{dataOne}" />
Quand la List et la DataGrid sont situées dans le même fichier MXML, comme dans cet exemple, cette fonctionnalité est simple à créer. On écrit seulement un peu de code pour écouter le changement de sélection dans la List et on met ensuite à jour la DataGrid. Cela devient plus difficile quand un des deux composants n'est pas dans le même MXML ou quand l'emplacement du composant est modifié.
Par exemple, disons que notre List est dans une barre d'outil de notre application qui est toujours affichée, mais la DataGrid n'est disponible que dans un onglet spécifique. Comment votre code va-t-il connaître l'emplacement de la DataGrid et de la List? Une pratique possible (mais mauvaise) est de mettre du code dans le fichier MXML de la DataGrid, et de référencer le composant List en remontant dans la hiérarchie par la propriété "parent", comme ceci:
parent.parent.parent.myList.addEventListener()
Une autre solution (toute aussi mauvaise mais "moins pire") est de référencer l'application et ensuite, de descendre dans la hiérarchie:
Application.application.sidebar.myList.addEventListener()
Ce genre de solution marche sur le court terme, mais crée un code fragile, facile à casser avec le plus petit des changements. Que se passera-t-il si vous devez bouger la List ou la DataGrid et que le chemin n'est plus le même? Dans ce cas, le compilateur va lancer une erreur car le chemin n'est plus valide. Au moins, vous sauriez quelle partie du code dysfonctionne, mais ce n'est pas une solution si vous voulez continuer à monter votre application. C'est à ce moment-la que la plupart des développeurs commencent à chercher différentes manières de résoudre ce problème.
Monter jusqu'au sommet
La principale faille de cette première approche est le manque de point d'entrée commun pour faire transiter toute les communications. On veut continuer à utiliser le modèle évènementiel, mais on a besoin d'identifier un point de contact qui va permettre à donner à notre application de modifier la disposition de nos composants sans affecter les voies de communication. Dans ce cas-là, le meilleur endroit où chercher est le haut de la Display List (liste d'affichage) d'Adobe Flash Player.
La Display List est une représentation hiérarchique de ce qui est affiché dans Flash Player. Tout en haut de la liste d'affichage se trouve la scène (stage), et tous les composants visuels sont les descendants. Peut importe où les composants sont situés dans votre application, la scène peut être accédée par la propriété "stage". Le stage est un très bon point de départ si vous travaillez avec Adobe Flash Professional, ou dans un projet ActionScript, mais dans Flex, vous n'avez pas besoin de voyager si loin dans la Display List pour cibler l'objet application. L'objet Application est un enfant du stage et le plus haut pour le développement Flex. Tous les composants seront descendants de l'application. L'application peut être accédée par n'importe quel composant en utilisant la syntaxe suivante:
Application.application
Dans l'exemple suivant, notre composant écoute sur l'application, l'évènement ListEvent et met à jour la donnée de la DataGrid quand l'évènement est propagé à travers l'application.
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
[Bindable] public var dataOne:Array;
[Bindable] public var dataTwo:Array;
public function subscribeToListChange():void {
Application.application.addEventListener(ListEvent.CHANGE, handleListChange);
}
public function handleListChange(event:ListEvent):void {
myDataGrid.dataProvider = dataTwo;
}
]]>
</mx:Script>
<mx:DataGrid id="myDataGrid" dataProvider="{dataOne}" />
La différence entre cette approche est celle utilisant l'évènement ListEvent.CHANGE par défaut est que l'on doit être sur que la propriété "bubbles" de l'évènement propagé est bien à "true" ou notre objet application ne va jamais recevoir l'évènement. Le "Bubbling" est le processus qui permet aux évènements de se diffuser dans la Display List. Par défaut, les évènements Flex sont seulement envoyés directement aux composants inscrits à cet évènement. Mais si on fixe la propriété "bubbles" à true, l'évènement sera diffusé à toute le chaîne jusqu'à ce qu'il atteigne le haut du Stage. En utilisant le "Bubbling", on peut aller dans notre Application.application pour écouter les évènements, en sachant que quand notre List va propager notre évènement "bubbled", il sera passé à l'application.
Puisque les évènements Flex ListEvent.CHANGE ne sont pas "bubbled" par défaut, on doit propager notre propre évènement quand le changement qui nous intéresse intervient. Dans notre exemple, on doit écouter l'évènement ListEvent de notre List et créer un nouvel évènement ListEvent avec le bubbling activé pour qu'il puisse être diffusé jusqu'en haut:
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
public function handleChangeEvent(event:ListEvent):void {
var event:ListEvent = new ListEvent(event.type, true);
dispatchEvent(event);
}
]]>
</mx:Script>
<mx:List change="handleChangeEvent(event)" />
Notez la flexibilité de cette solution. Notre application a maintenant la capacité de bouger les composants vers différents emplacements sans devoir ré-écrire tout le code qui concerne les évènements. Peu importe où se trouvent les composants, leur évènements seront "bubbled" jusqu'à l'application. En plus de cela, de nouveaux composants peuvent être introduits qui peuvent écouter le même évènement, et peuvent être mis à jour par la même action-utilisateur. Nos composants sont maintenant complètement découplés.
Regardons maintenant cette approche au niveau performances. Puisque l'on se base sur le "bubbling" pour communiquer votre évènement vers le haut de la Display List, Flash Player va diffuser l'évènement à travers tous la chaîne de composants. Si notre composant est inséré profondément dans de nombreuses HBox, VBox, Canvas et autres conteneurs, notre évènement pourrait "bubble" à travers des centaines d'objets avant d'atteindre le sommet. Si notre application est lourde en communication, les performances globales de l'application pourraient se dégrader quand un grand nombre de message doit être passé. C'est un facteur que les développeurs doivent considérer quand ils choisissent cette solution.
Utiliser un médiateur tierce
Une autre solution est d'implémenter le Design Pattern Mediator. Le pattern Mediator permet la communication en donnant un objet spécialisé, qui va être seulement responsable de l'acheminement de la data vers la bonne cible. Cette objet est appelé un Mediator (Médiateur pour les francophones), et il est le point de contact unique pour tous les évènements de communication entre les composants, un peu comme l'objet en haut de la Display List dont on parlait plus tôt. Le Mediator fonctionne comme un site agrégateur de news. Tout le monde envoie de news, des blogs, ou met à jour le site, et les abonnés peuvent ensuite choisir quels thèmes ils veulent rechercher. Les utilisateurs savent aller sur un seul site pour récupérer le contenu dont ils ont besoin, sans passer la journée à surfer sur le net en cherchant les bouts d'informations dont ils ont besoin.
Dans notre case, au lieu des news, on va envoyer de la donnée à l'objet Mediator. Les utilisateurs qui sont ensuite intéressés par cette donnée peuvent s'inscrire au Mediator, et le Mediator va immédiatement les informer quand la donnée arrive. L'idée est de déclarer un objet commun que tout le monde dans le système peut accéder, et faire transiter l'information par cet objet.
C'est un problème très commun, surtout dans les applications de grande envergure. Pour pallier à ce problème, Development Arc a crée une classe utilitaire appelée EventBroker, qui a été conçue pour être un Mediator global. EventBroker a deux méthodes static importantes, que tout le monde peut accéder: subscribe() et broadcast(). A travers cette API, on peut diffuser des Event à travers l'EventBroker, et tout ceux qui sont inscrit à cet évènement vont recevoir les mises à jour.
Pour revenir à notre exemple de DataGrid et de List, on peut écrire ce code dans le fichier MXML de la DataGrid:
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
import com.developmentarc.framework.utils.EventBroker;
[Bindable] public var dataOne:Array;
[Bindable] public var dataTwo:Array;
public function subscribeToListChange():void {
EventBroker.subscribe(ListEvent.CHANGE, handleListChange);
}
public function handleListChange(event:ListEvent):void {
myDataGrid.dataProvider = dataTwo;
}
]]>
</mx:Script>
<mx:DataGrid id="myDataGrid" dataProvider="{dataOne}" />
Et dans notre MXML contenant la List, on aurait ce code:
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
import com.developmentarc.framework.utils.EventBroker;
public function handleChangeEvent(event:ListEvent):void {
EventBroker.broadcast(event);
}
]]>
</mx:Script>
<mx:List change="handleChangeEvent(event)" />
On s'inscrit ici à l'évènement CHANGE de la List pour le passer ensuite à l'EventBroker. L'EventBroker va ensuite chercher tous les abonnés à ce type d'évènements, et appeler les méthodes passées lors des appels à la méthode subscribe(). Dans notre exemple, dans le MXML contenant la DataGrid , on écoute l'évènement CHANGE. Quand il se produit, la méthode handleListChange() est appelée.
Notez que l'on peut bouger notre composant qui contient la DataGrid n'importe où dans l'application, et le pont de communication ne sera pas coupé.
C'est un exemple très simple du fonctionnement du Mediator. Dans une vraie application, on va propager un évènement personnalisé qui aurait bien plus de signification qu'un simple CHANGE Event. De nombreux composants dispatchent un évènement CHANGE, et si votre application doit se tenir au courant des changements dans des List ou des ComboBox, vous devriez créer des évènements personnalisés (qui héritent simplement de la classe Event) pour chaque action utilisateur, comme un ChangedPriceSelectorEvent par exemple. Cela aide à différencier les changements pour que votre code puisse répondre correctement.
La classe EventBroker est disponible dans la librairie Open Source sous licence MIT de Development Arc. Ils ont aussi de nombreux exemples d'utilisation et comme le projet est Open Source, vous pouvez même voir comment est construit l'EventBroker.
Consulter le projet Open Source de Development Arc
Télécharger la classe EventBroker.as
Cet article est une traduction de l'article "Enabling Flex components to talk to each other" écrit par James Polanco et Aaron Pedersen de la société Development Arc.
Articles similaires
- Flex UIComponent – Déclencher l'évènement change lors de la modification du selectedItem en AS3 (ComboBox, List et DataGrid)
- Flex Modules – Communication découplée entre modules avec EventBroker
- Flex UIComponent – Les Composants de type Liste (List, ComboBox, DataGrid, HorizontalList, TileList, Tree)
- Flex UIComponent – Les évènements standard des Composants Flex
- Flex Tips – Adapter automatiquement la taille d'une List / DataGrid / ComboBox suivant la donnée
Aucun trackbacks pour l'instant






20 octobre 2009
Ou alors, on peut utiliser un framework MVC comme PureMVC : http://www.puremvc.org
20 octobre 2009
C'est sur, mais si a une application qui n'a pas été créée de base avec un framework MVC ou que c'est juste un quick fix, cette classe est plus souple et plus rapide à implémenter.
Fabien
23 octobre 2009
Le EventBroker n'est pas du tout le pattern Mediator et s'apparente plus au pattern Observer dans ce cas. Mais l'implémentation est complétement différente de celle d'origine et oblige tous les composants à être dépendent de cette classe. Enfait, il s'agit ni plus ni moins de ce que l'on nomme un Global Dispatcher, une variable globale déguisée en Singleton.
Une autre chose, un event n'est pas obligé d'être en bubble pour remonté jusqu'au somment de l'application. C'est une chose qu'on lit souvent sur les blogs Flex et qui est totalement fausse.
Il suffit de se replonger un peu dans les docs pour lire qu'un évenement diffusé par un objet appartenant à la DisplayList commence en haut de la hiérarchie, est diffusé jusqu'à cet objet et remonte éventuellemet si il est bubble. Pourquoi attendre qu'il remonte alors qu'il commence déjà tout en haut
Enfin, je ne suis pas sûr que Application.application soit situé en haut de la hiérarchie. Essaye voir de diffuser un évenement depuis une Popup et je ne pense pas que tu le recevra. Le SystemManager doit être au-dessus lui.
1 juillet 2010
Les évènements Flash sont ils plus optimisés, d'un point de vue performance, qu'un Mediator ou un Observer ?
En fait j'ai une application que j'ai faite qui envoie une quantité incroyable d'évènements pour tout (EnterFrame, ProgressEvent, Actions, LoadEvents, Affichage, Initialisation, Erreurs)
J'essaye de l'optimiser pour qu'elle soit le moins lente possible, et je me demande si remplacer les évènements par une autre technique, comme l'EventBroker, ne serait pas plus rapide (point de vue Perf de l'application, RAM et CPU ).
Quelqu'un aurait une réponse, ou une idée ?
1 juillet 2010
Salut,
Qu'appelles-tu un Mediator / Observer? Pour moi ce sont des design patterns qui utilisent les Event,pas des "concurrents".
Il y a un remplacement de Event qui est As3Signals, supposés plus rapide, jamais testé. Tu peux y jeter un coup d'oeil.
EventBroker te permet uniquement de faire transiter tes évènements par un singleton. Derrière, c'set la même gymnastique
Fabien