BindingUtils.bindProperty et les comboBox

Portrait de titouille

Dans le projet sur lequel je travaille actuellement, nous cherchions à mettre en place des liaisons de données. Le concept est le suivant : Je récupère via LCDS un tableau d'objet (Value Object) de type UserVO. Une liste d'utilisateurs. Cette liste va remplir une dataGrid.

D'un click sur un nom dans la grille, cela va afficher un formulaire situé dans un état. La liaison de données entre le formulaire devant se faire dans les 2 sens, il était nécessaire de pouvoir affecter les valeurs du UserVO sélectionné dans les champs de mon formulaire, mais également de pouvoir faire en sorte que lorsqu'on modifie une valeur via un contrôle du formulaire, la valeur soit modifiée directement dans le VO et qu'elle soit reflétée automatiquement dans la grille. Ainsi, lorsque j'appelerai la méthode "commit" de mon dataService, les modifications seraient prises en compte directement sur le serveur. Le but était de générer un maximum d'automatisme, pour éviter de devoir réaffecter les valeurs manuellement dans le UserVO concerné à chaque modification dans le formulaire. Le tout, bien entendu, en ActionScript. J'aime séparer la logique "visuelle" de la logique "codage", et j'essaye au maximum de ne pas placer de code dans mes fichiers MXML.

La solution se trouve dans la classe BindingUtils, qui permet de faire de la liaison de données par code.

Après avoir sélectionné un utilisateur dans la grille, j'affiche mon formulaire, et j'affecte à ce dernier le UserVO sélectionné dans une variable interne via une méthode nommée fill. Mon formulaire contient plusieurs textInput et une comboBox, permettant d'afficher les types d'utilisateurs disponibles :

private var user:UserVO;
 
public var ctiFirstname:TextInput;
public var ctiSurname:TextInput;
public var ccbKind:ComboBox;
 
private function init():void
{
this.ccbKind.dataProvider = [
{id:1, value:"admin"},
{id:2, value:"user"},
{id:3, value:"viewer"}
];
this.ccbKind.labelField = "value";
}
public function selectCorrectFieldInCbo( data:uint ):void
{
// boucle permettant d'itérer sur 
// chaque item du dataProvider de la 
// combobox pour sélectionner l'item
// correct par rapport à la valeur
// contenue dans le UserVO
// ...
}
public function fill( uvo:UserVO ):void
{
this.user = uvo;
 
this.ctiFirstname = this.user.firstname;
this.ctiSurname = this.user.surname;
this.selectCorrectFieldInCbo( this.user.kindid );
 
BindingUtil.bindProperty( this.user, "firstname", this.ctiFirstname, "text" );
BindingUtil.bindProperty( this.user, "surname", this.ctiSurname, "text" );
 
//... ici se situe le problème que je vais expliquer...
}

En substance, la méthode BindingUtils.bindProperty prend en compte 4 paramètres obligatoires :

  1. site:Object correspond à l'objet qui sera destinataire des modifications
  2. prop:String correspond à la propriété de l'objet destinataire qui sera modifiée
  3. host:Object correspond à l'objet qui subit les modifications
  4. chain:Object correspond à la propriété modifiée dans l'objet subissant les modifications

Le plus simple est de lire les propriétés à l'envers, exemple :

BindingUtil.bindProperty( this.user, "firstname", this.ctiFirstname, "text" );

"A chaque modification de la propriété 'text' du contrôle 'ctiFirstname', la modification devra être reflétée sur la propriété 'firstname' de l'objet 'actor'.

Jusqu'ici, pas de problème. C'est lorsqu'on commence à travailler sur des objets un peu plus complexe que ça se complique.

La ComboBox peut, par défaut, automatiser son comportement si on passe un dataProvider dont les objets sont du genre {data:1, label:"hello"}. Dans ce cas précis, la ComboBox (et les autres composants héritant de ListBase) savent qu'ils doivent prendre la propriété "label" en tant que propriété à afficher, et la propriété "data" en tant que valeur identifiante. Seulement, il est rare qu'on puisse récupérer des données avec ces propriétés précises, ou alors il faut réfléchir toute l'application dans ce sens.

Le problème de bindingUtils, c'est que par défaut, nous écririons de la manière suivante la liaison sur la ComboBox :

BindingUtil.bindProperty( this.user, "kindid", this.ccbKind, "value" );

Il serait nécessaire d'utiliser la propriété "value" de la comboBox, qui est sensée être valorisée avec la propriété identifiante, c'est à dire "data". Seulement, dans le cas d'un dataProvider qui ne suit pas cette logique, la propriété "value" va nous retourner la propriété utilisée par "labelField", qui correspond à la variable utilisée "visuellement".

Donc la ComboBox ne me retournera jamais la valeur "id" dont j'aurai besoin pour faire une liaison de données correcte.

Après quelques recherches sur internet et dans la mailing list FlexCoders, j'ai fini par tomber un peu par hasard sur un bout de code dont l'auteur disait qu'il ne fonctionnait pas. J'ai tout de même voulu essayer, juste au cas où, et ça correspondait à la solution cherchée :

(binding correct pour la ComboBox, permettant de récupérer la valeur id de l'objet sélectionné dans mon dataProvider)

BindingUtil.bindProperty( this.user, "kindid", this.ccbKind, ["selectedItem", "id"] );

C'est également là que j'ai compris pourquoi le 4ème paramètre était de type "Object" et se nommait "chain".
En fait, on doit passer la "chaine" à suivre dans la hiérarchie de propriété pour arriver sur la propriété qu'on désire utiliser. Le processus doit utiliser une boucle du genre :

var o:Object = this.ccbKind;
for( var i:uint = 0, l:uint = chain.length; i < l; i++ )
{
o = o[chain[i]];
}

qui correspondrai à la logique suivante :

o = this.ccbKind;
o = o["selectedItem"];
o = o["id"];

Permettant d'atteindre la bonne valeur.

Après coup, ça semble logique, mais tant qu'on n'a pas compris le truc, ça reste assez peu clair.





Merci ! tout simplement !

Merci ! tout simplement !