mercredi 19 septembre 2012

Utiliser les attributs imbriqués dans les DataGrid


Je sais pas si ça vous parle le terme 'attribut imbriqué', mais c'est le terme que j'emploie pour désigner un attribut d'un attribut d'un attribut (etc) d'un objet. En d'autre terme si une classe 'Maison' contient l'attribut cuisine de type 'Cuisine', et que la classe 'Cuisine' contient un attribut four de type 'Four', alors on pourrait dire que four est un attribut imbriqué de cuisine.

Si vous faites partie des personnes intelligentes qui ont tout compris jusque là, sachez que les DataGrid peuvent prendre des noms d'attributs imbriqués dans les 'dataField' des colonnes. En gros on peut bien faire ça :
<mx:DataGridColumn labelFunction="dateFormatFunction"
                     dataField="cuisine.four"
             headerText="Le four de la cuisine"/>                      


Grâce à cette possibilité on a pas besoin du labelFunction juste pour accéder à l'attribut imbriqué dans la colonne.

Par contre si on veut utiliser 'labelFunction', on ne peut plus faire l'habituel item[column.dataField]. Sans oublier que les tris ne marchent plus puisqu'ils arrivent pas accéder à l'attribut  du nom bizarre ('cuisine.four'). J'entends vos cris et vos regrets. Ne vous inquiétez pas, j'ai la solution qu'il vous faut!

J'ai créé d'abord une méthode dans la classe 'Utils.as' par exemple, qui extrait l'attribut imbriqué à partir de l'objet :

Utils.as
/**
 * Récupérer la valeur d'un impriqué à partir de l'objet et du nom de  
 * l'attribut.
 */
public static function getValeurAttributImbrique(item:Object,nomAttributImbrique:String):Object {
     var nomAttributs:Array = nomAttributImbrique.split(".");
     var currentItem:Object = item;
     for each (var attribut:String in nomAttributs) {
              currentItem = currentItem[attribut]
     }
     return currentItem;
}                  



Ensuite je l'utilises dans les méthodes de création de label ('labelFunction') et de tri ('sortCompareFunction'). Je vous donne l'exemple d'un champ de type 'Date' :

Composant.mxml
<mx:DataGridColumn 
dataField="dateDebutValidite"                   
labelFunction="Utils.formaterDateDefault"

sortCompareFunction="{Utils.dateSortCompareFunction('dateDebutValidite')}"
headerText="Date de début de validité"/>           



Et d'ajouter à Utils.as les méthodes que je viens d'utiliser:

Utils.as
/**
 * Formater la date en entrée sous la forme la plus connue JJ/MM/AAAA
 */
public static function formaterDateDefault(item:Object,column:DataGridColumn):String {
       var date:Object = Utils.getValeurAttributImbrique(item, column.dataField);
       if(date){
              if(date is Date){
                     return formater(date as Date);
              }
              if(date is String){
                    return date as String;
              }
       }
       return "";
}
/**
 * @param date
 * @format format de la date, par défault "DD/MM/YYYY"
 * @return La date au format passé en paramètre
 */
public static function formater(date:Date, format:String = "DD/MM/YYYY"):String {
       var result:String = "";
       var dateFormater:DateFormatter = new DateFormatter();
       dateFormater.formatString = format;
       result = dateFormater.format(date);
       return result;
}

/**
 * Utile pour les propriété 'sortCompareFunction' des DataGridColumn
 */
public static function dateSortCompareFunction(field:String):Function {
       return function (obj1:Object, obj2:Object):int {
             var date1:Date = Utils.getValeurAttributImbrique(obj1,field) as Date;
             var date2:Date = Utils.getValeurAttributImbrique(obj2,field) as Date;
             return comparerDates(date1,date2);
       }
}
public static function comparerDates(date1:Date, date2:Date):int {
       var comparaison:int = ObjectUtil.dateCompare(date1, date2);
       return comparaison;
}                         



Apluche.

mardi 11 septembre 2012

Lire les commentaires c'est pas bien des fois


Un jour je voulais trier les éléments d'un tableau suivant une colonne contenant des dates. J'ai cherché un peu et je suis tombé sur la méthode 'dateCompare' de la classe native flex 'ObjectUtil' . La première chose que j'ai fait était de lire le commentaire de l'entête de cette méthode (Je suis bon élève quand je veux :D). Une fois que j'ai compris comment il fallait l'utiliser, j'ai écris mes lignes de code à la vitesse de l'éclair. Ensuite j'ai compilé le code flex de mon application (pas à la vitesse de l'eclair), il n'y avait pas d'erreurs de syntaxe bien sure :D. J'ai lancé l'application. Et !! Qu'est ce que je constates? Je constates que tous mes tris de dates étaient inversés. Je reviens dans mon code, je cherches, je recherches, je modifie des trucs qui n'ont rien avoir en espérant que c'est ça, je recompiles, je testes, RIEN!

Après à peu prés une heure de recherches et de théories farfelues portant sur les limites du compilateurs flex, j'ai eu l'idée de relire l'entête de la méthode et de lire le code de cette méthode. C'est à cet instant que j'ai remarqué que les commentaires expliquaient l'inverse de ce qui était implémenté dans le code de la méthode. Concrètement dans le code si date1<date2 par exemple alors la méthode retourne -1.
if (na < nb)
    return -1 ;

Mais dans le commentaire si date1<date2 alors la méthode retournerait 1.
* 1 if <code>a</code> is <code>null</code>
* or before <code>b</code>;

Si vous voulais voir tout l'exploit, je vous laisse juger par vous même :
    /**
     *  Compares the two Date objects and returns an integer value
     *  indicating if the first Date object is before, equal to,
     *  or after the second item.
     *
     *  @param a Date object.
     *
     *  @param b Date object.
     *
     *  @return 0 if <code>a</code> and <code>b</code>
     *  are <code>null</code> or equal;
     *  1 if <code>a</code> is <code>null</code>
     *  or before <code>b</code>;
     *  -1 if <code>b</code> is <code>null</code>
     *  or before <code>a</code>.
     */
    public static function dateCompare(a:Date, b:Date):int
    {
        if (a == null && b == null)
            return 0;

        if (a == null)
          return 1;

        if (b == null)
           return -1;

        var na:Number = a.getTime();
        var nb:Number = b.getTime();
       
        if (na < nb)
            return -1;

        if (na > nb)
            return 1;

        return 0;
    }

J'ai envie de dire que le stagiaire qui s'est chargé de passer sur toutes les méthodes pour les commenter n'a pas bien lu celle là avant de la commenter :p