Précédent   Forum des professionnels en informatique > Webmasters - Développement Web > Flash/Flex > Flex
Flex Forum d'entraide sur la programmation Adobe Flex : applications Internet riches (RIA)
Partagez cette discussion sur d'autres réseaux sociaux : Viadeo Twitter Google Facebook Digg Delicious MySpace Yahoo
Réponse Proposer ce sujet en actualité
 
Outils de la discussion
Publicité
'
Vieux 12/09/2011, 15h59   #1
Membre Expert
 
Avatar de Madfrix
 
Inscription : juin 2007
Messages : 2 279
Détails du profil
Informations personnelles :
Localisation : France, Gironde (Aquitaine)

Informations forums :
Inscription : juin 2007
Messages : 2 279
Points : 2 327
Points : 2 327
Par défaut Utilité des états de composants dans une logique MVC

bonjour,

j'aimerais avoir votre opinion quant à l'utilité des states dans un composant personnalisé. Attention, j'ai bien dis "states" et non "skinStates".

Je m'explique :

il n'est plus à démontrer l'utilité du pattern MVC (Model, Vue, Controleur) et Flex via sa nouvelle architecture Spark permet de découper plus facilement un composant en ces 3 tiers (voir un article ici ). Ainsi, ces 3 tiers peuvent être représentés par le schéma ci dessous (source : lien ci dessus) :





La Vue, représentée dorénavant dans l'architecture Spark par la classe skin associée au composant, doit donc encapsuler toute la logique visuelle du composant en question. Logique visuelle comprenant entre autre :
  • les couleurs
  • les fonts
  • le placement relatif des composants entre eux (top, left, bottom, right...)
  • la présence ou non de des composants (includeIn, excludeFrom)
  • les transitions
  • ...

nota : j'appelle composants dans la liste non exhaustive ci dessus, les sous composants du composant personnalisé en question (ie : les fameux skinParts).

Or, Flex via la classe UIComponent (classe parente de tous les composants personnalisés), définit une propriété "states" de type tableau initialisée à null mais pouvant facilement être modifiée via les getters/setters. Cette propriété sert à créer des états sur le composant lui même contrairement au skinStates définissant des états sur les skins.

Cependant, nous sommes en droit de nous poser la question suivante : A quoi peut donc servir cette propriété states sur le composant personnalisé ?

Par "facilité" ou par méconnaissance du pattern MVC, nous pourrions être tentés de basculer la logique vue dans la logique contrôleur et de faire dans le cas d'un composant personnalisé étendant la classe SkinnableContainer et nommé MyPersoComp la chose suivante :

Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
<?xml version="1.0" encoding="utf-8"?>
<s:SkinnableContainer xmlns:fx="http://ns.adobe.com/mxml/2009" 
					  xmlns:s="library://ns.adobe.com/flex/spark" 
					  xmlns:mx="library://ns.adobe.com/flex/mx">
 
	<s:states>
		<s:State name="state1" />
		<s:State name="state2" />
	</s:states>
 
	<s:Button label.state1="label de l'état 1" label.state2="label de l'état 2" />
 
</s:SkinnableContainer>
Aucune erreur de compilation n'apparait et notre composant est opérationnel. Nous pouvons donc facilement instancier notre composant MyPersoComp et changer dynamiquement son label en jouant avec la propriété currentState du composant :

Code :
1
2
3
4
5
6
7
8
9
 
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark" 
			   xmlns:mx="library://ns.adobe.com/flex/mx"  xmlns:components="com.components.*">
 
	<components:MyPersoComp currentState="state2" />
 
</s:Application>
Un joli bouton labellisé "label de l'état 2" apparaît ce qui est le résultat attendu. Oui mais en faisant cela, nous avons déplacé la logique vue (ici représenté par le changement/affichage du label bouton) dans le tiers contrôleur représenté par le composant...
Cet exemple bien que simpliste montre la facilité avec laquelle un développeur peut se mélanger les pinceaux avec les notions de "component states" et de "skin states". Certes, l'exemple évoqué représente la façon la plus directe et la plus rapide pour modifier rapidement et automatiquement une propriété en fonction d'un état, mais comme souvent en informatique, la solution de facilité n'est pas la meilleure.

Voici maintenant 2 solutions qui selon moi représentent une bien meilleure alternative :

1ère solution : faire du "mirroring delegate"

description: nous créons toujours 2 états state1 et state2 sur notre contrôleur (notre composant dérivé de UIComponent) puis nous surchargeons la méthode getCurrentSkinState() de ce composant afin de faire correspondre (mirroring) l'état de notre composant avec l'état de notre skin. Nous avons donc un tableau de states correspondant au tableau de skinStates ou du moins une relation 1-1 entre les states et les skinStates.

MyPersoComp (version AS3):
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
 
package com.components
{
 
	import flash.events.MouseEvent;
 
	import mx.events.FlexEvent;
	import mx.states.State;
 
	import spark.components.Button;
	import spark.components.supportClasses.SkinnableComponent;
 
	/**
	 * notre skin devra contenir les skinStates "state1" et "state2" 
	 */
	[SkinState("state1")]
	[SkinState("state2")]
 
	public class MyPersoComp extends SkinnableComponent
	{
		/**
		 * notre skin devra obligatoirement implémenter un bouton dont l'id vaudra "buttonPerso"
		 */
		[SkinPart(name="buttonPerso", required="true")]
		public var buttonPerso:Button;		
 
		public function MyPersoComp()
		{
			super();
			initStates();
			addEventListener(FlexEvent.STATE_CHANGE_COMPLETE, onStateChange);
		}
 
		/**
		 * crée les états associés à notre composant
		 */
		private function initStates():void
		{
			var state1:State = new State();
			state1.name = "state1";
 
			var state2:State = new State();
			state2.name = "state2";
 
			states = [state1, state2];
		}
 
		/**
		 * un changement de state implique une invalidation de notre skin.
		 * Cette invalidation appelle (automatiquement) la fonction getCurrentSkinState() 
		 * qui retourne le nouvel état à la skin
		 */
		private function onStateChange(e:FlexEvent):void
		{
			invalidateSkinState();
		}
 
		/**
		* ajout d'un écouteur sur le bouton à l'ajout de celui dans notre composant personnalisé
		*/
		override protected function partAdded(partName:String, instance:Object):void
		{
			super.partAdded(partName, instance);
 
			if(instance == buttonPerso)
			{
				buttonPerso.addEventListener(MouseEvent.CLICK, onClick);
			}
		}
 
		/**
		 * suppression de l'écouteur ajouté par la fonction partAdded
		 */
		override protected function partRemoved(partName:String, instance:Object):void
		{
			super.partRemoved(partName, instance);
 
			if(instance == buttonPerso)
			{
				buttonPerso.removeEventListener(MouseEvent.CLICK, onClick);
			}
		}
 
		/**
		 * un click sur notre bouton change l'état de notre composant et finalement son label 
		 * via la diffusion de l'événement FlexEvent.STATE_CHANGE_COMPLETE 
		 */
		protected function onClick(e:MouseEvent):void
		{
			currentState = (currentState == "state1" || null == currentState) ? "state2" : "state1";
		}
 
		/**
		 * un changement d'état sur notre composant sera immédiatement affecté sur notre skin.
		 * Cette fonction sert donc de mirroir(composant -> skin)
		 */
		override protected function getCurrentSkinState():String
		{
			return currentState;
		}
 
	}
}
Appel dans notre application :
Code :
1
2
 
<components:MyPersoComp skinClass="com.components.skins.MyPersoCompSkin1" />
MyPersoCompSkin1 :
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" 
		xmlns:s="library://ns.adobe.com/flex/spark" 
		xmlns:mx="library://ns.adobe.com/flex/mx">
 
	<fx:Metadata>
		[HostComponent("com.components.MyPersoComp")]
	</fx:Metadata>
 
	<s:states>
		<s:State name="state1" />
		<s:State name="state2" />		
	</s:states>
 
	<s:Button id="buttonPerso" label.state1="label de l'état 1"  label.state2="label de l'état 2"/>
 
 
</s:Skin>
Ainsi, notre composant personnalisé délégue toute sa logique vue à son skin via la diffusion d'un événement FlexEvent.STATE_CHANGE_COMPLETE.


2ième solution : solution "stateless states"

description: cette solution repose grandement sur le fondement de la solution précédente excepté que l'on s'abstient d'utiliser la propriété states de notre composant. On préférera utiliser une simple propriété de notre classe afin de stocker le nom de notre skinState (un peu moins consommateur de ressources donc).

MyPersoComp :
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
 
package com.components
{
 
	import flash.events.MouseEvent;
 
	import mx.events.FlexEvent;
	import mx.states.State;
 
	import spark.components.Button;
	import spark.components.supportClasses.SkinnableComponent;
 
	/**
	 * notre skin devra contenir les skinStates "state1" et "state2" 
	 */
	[SkinState("state1")]
	[SkinState("state2")]
 
	public class MyPersoComp extends SkinnableComponent
	{
		/**
		 * notre skin devra obligatoirement implémenter un bouton dont l'id vaudra "buttonPerso"
		 */
		[SkinPart(name="buttonPerso", required="true")]
		public var buttonPerso:Button;		
 
		/**
		 * notre propriété nous donnant le nom de notre skinState
		 */
		private var _state:String;
 
		public function MyPersoComp()
		{
			super();		
		}				
 
		/**
		* ajout d'un écouteur sur le bouton à l'ajout de celui dans notre composant personnalisé
		*/
		override protected function partAdded(partName:String, instance:Object):void
		{
			super.partAdded(partName, instance);
 
			if(instance == buttonPerso)
			{
				buttonPerso.addEventListener(MouseEvent.CLICK, onClick);
			}
		}
 
		/**
		 * suppression de l'écouteur ajouté par la fonction partAdded
		 */
		override protected function partRemoved(partName:String, instance:Object):void
		{
			super.partRemoved(partName, instance);
 
			if(instance == buttonPerso)
			{
				buttonPerso.removeEventListener(MouseEvent.CLICK, onClick);
			}
		}
 
		/**
		 * un click sur notre bouton change l'état de notre composant via une invalidation de son skin
		 */
		protected function onClick(e:MouseEvent):void
		{
			_state = (_state == "state1" || null == _state) ? "state2" : "state1";
			invalidateSkinState();
		}
 
		/**
		 * la valkeur de notre skinState est contenu dans notre propriété privée _state
		 */
		override protected function getCurrentSkinState():String
		{
			return _state;
		}
 
	}
}

Appel dans notre application :
Code :
1
2
 
idem méthode précédente

MyPersoCompSkin1 :
Code :
1
2
 
idem méthode précédente


Les limites de ces 2 solutions : les solutions évoquées sont parfaitement fonctionnelles si le composant personnalisé ne comprend uniquement que des sous composants de type "skinPart" c'est à dire qu'il faut que tous ces sous composants soient listés via la metadata [SkinPart] dans notre fichier .as. A défaut, si nous ajoutons des composants non annotés par cette metadata dans notre composant (ce qui est loin d'être recommandé ni recommandable je pense), il nous faudra faire un "mix" entre la première solution proposée et une notation pointée dans notre composant (propriete.state1 = "..."). Nous retombons donc dans les travers évoqués en début de sujet.


Cette vision des choses n'engage que moi bien sûr. De mon côté, je n'utilise jamais les états de composants ou du moins je n'utilise jamais de notation pointée directement dans mes composants personnalisés. La seule façon dont j'utilise les états de composants est celle évoquées ici en méthode 1. Ceci dit, j'utilise la méthode 2 autant que faire se peut.


...et vous, comment voyez vous les choses concernant les "components states" ?



Merci de m'avoir lu et de votre participation au débat
__________________
Je ne réponds pas aux questions envoyées par mp
Madfrix est déconnecté   Envoyer un message privé Réponse avec citation 10
Vieux 17/09/2011, 19h36   #2
Membre Expert
 
Avatar de Madfrix
 
Inscription : juin 2007
Messages : 2 279
Détails du profil
Informations personnelles :
Localisation : France, Gironde (Aquitaine)

Informations forums :
Inscription : juin 2007
Messages : 2 279
Points : 2 327
Points : 2 327
Personne n'utilise de composants personnalisés ici ?
__________________
Je ne réponds pas aux questions envoyées par mp
Madfrix est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 19/09/2011, 13h49   #3
Rédacteur/Modérateur
 
Avatar de Jim_Nastiq
 
Homme Jean-Marie Macé
Ingénieur consultant, leader Flex
Inscription : avril 2006
Messages : 2 194
Détails du profil
Informations personnelles :
Nom : Homme Jean-Marie Macé
Localisation : France, Haute Garonne (Midi Pyrénées)

Informations professionnelles :
Activité : Ingénieur consultant, leader Flex
Secteur : Conseil

Informations forums :
Inscription : avril 2006
Messages : 2 194
Points : 3 377
Points : 3 377
Salut,

alors pour moi les states dont tu parles je les utilises comme des états des vues et les autres comme des états des skins. Oui je sépare le skin de la vue. Une vue est un container et un skin est l'habillage du container.

Typiquement j'ai un stack de vue dans un container parent avec pour chacune des vues un state. Et chacune de ces vues à un skin avec des skinState.

Par exemple, le skinState peut etre un habillage particulier si l'utilisateur est une femme ou bien un homme. (j'ai donc 2 skinStates 'menState' et 'womenState') et dans mon container des states pour chacune des vues ('loginState', 'viewOneState', ...)

les states sont donc plus 'métier'
__________________

Pensez vraiment à effectuer une recherche avant de poster, ici et sur un moteur de recherche! c'est la moindre des choses
Pensez au tag

Mon Blog sur la techno Flex
Ma page sur Developpez.com

Jim_Nastiq
Jim_Nastiq est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 21/09/2011, 10h35   #4
Membre Expert
 
Avatar de Madfrix
 
Inscription : juin 2007
Messages : 2 279
Détails du profil
Informations personnelles :
Localisation : France, Gironde (Aquitaine)

Informations forums :
Inscription : juin 2007
Messages : 2 279
Points : 2 327
Points : 2 327
Salut,

ça se défend ton point de vue c'est vrai. Finalement, pour un composant basique à ce que je comprends, tu n'utilises pas de skin en fait c'est ça ? Tout est externalisé en quelque sorte dans ce que tu appelles la vue ?

Pour toi, un skin te permet de "simplifier" le code des states il me semble non ?
Par exemple, pour un composant nommé Horloge affichant l'heure et un Label "vous êtes connecté" si on est connecté et rien sinon tu procéderas ainsi ?

2 états de vue :
Code :
1
2
3
4
5
 
<s:states>
	<s:State name="connecte" />
	<s:State name="non_connecte" />
</s:states>
Maintenant, si on veut un fond de couleur différente si connecté en tant que femme ou homme, dans ton cas de figure, tu procéderas ainsi si j'ai bien compris ?

2 états de vue :
Code :
1
2
3
4
5
 
<s:states>
	<s:State name="connecte" />
	<s:State name="non_connecte" />
</s:states>
2 états de skin
Code :
1
2
3
4
5
 
<s:states>
	<s:State name="homme" />
	<s:State name="femme" />
</s:states>
au lieu éventuellement de procéder de la sorte :

4 états de vue :
Code :
1
2
3
4
5
6
7
 
<s:states>
        <s:State name="homme_connecte" />
	<s:State name="homme_non_connecte" />
	<s:State name="femme_connecte" />
	<s:State name="femme_non_connecte" />
</s:states>
Dans ce cas de figure, tu as donc 2*2 états si on peut dire au lieu de 4 états ce qui change pas grand chose donc. Par contre, dans le cas de figure où on a les états "connecte", "non_connecte", "presque_connecte" et "en_train_de_se_connecter" (désolé pour l'exemple...) au lieu d'avoir 4*2=8 states de vue tu feras 4+2=6 states si tu gardes les états homme et femme dans le skin.

J'ai bien résumé/compris ton point de vue ou non ?

Merci de ta participation en tout cas
__________________
Je ne réponds pas aux questions envoyées par mp
Madfrix est déconnecté   Envoyer un message privé Réponse avec citation 00
Réponse Proposer ce sujet en actualité
Outils de la discussion



Fuseau horaire GMT +2. Il est actuellement 23h59.


 
 
 
 
Partenaires

Hébergement Web