Implémenter le binding two-way en WinJS

Afin de simplifier le développement d’applications Windows 8 en JavaScript, WinJS comporte une fonctionnalité chère aux yeux des amateurs de XAML : le binding. Le principe est simple : mettre la vue d’un côté, les données de l’autre, et un mécanisme au centre qui notifie le premier que le second a changé.

En XAML, il existe trois modes de binding : OneTime, OneWay et TwoWay.

  • Le binding OneTime fait la liaison entre données et interface une première fois, puis ne s’occupe plus de rien.
  • Le binding OneWay permet de changer l’interface à chaque fois que les objets métiers changent.
  • Le binding TwoWay fait en plus la liaison dans l’autre sens, c’est-à-dire que lorsqu’il est lié à un contrôle graphique qui permet de modifier la donnée (par exemple un champ texte, une check box, ou tout autre contrôle pouvant être utilisé de manière interactive), la modification de la donnée dans l’interface va entrainer automatiquement la modification de la donnée elle-même.

Seulement, le binding TwoWay n’existe pas dans la version actuelle de WinJS… ce qui ne veut pas dire qu’on ne peut pas le mettre en place. C’est d’ailleurs ce que nous allons faire dans cet article !

Les initializers

En effet, le binding WinJS est facilement personnalisable via un mécanisme d’initializer. C’est une fonction qui s’exécutera lors de l’application du binding (au moment où on appelle la fonction WinJS.Binding.processAll).

Tout binding utilise un initializer et dans la syntaxe du binding WinJS c’est le troisième membre ; en revanche il peut être omis, et c’est dans ce cas defaultBind, l’initializer par défaut (c’est l’équivalent du binding OneWay de XAML) qui sera utilisé.

 <input data-win-bind="dest.prop: source.prop [initializer]" />

Un initializer peut être n’importe quelle fonction, à condition qu’elle soit sûre : appeler WinJS.Utilities.markSupportedForProcessing ou WinJS.Binding.initializer (qui font strictement la même chose) suffit à WinJS pour faire confiance à la fonction et la rend utilisable en tant qu’initializer :

 WinJS.Namespace.define("Ext.Binding", {

    twoWay: WinJS.Binding.initializer(
        function (source, sourceProps, dest, destProps) {
            //TODO: implémenter l'initializer
        }
    )
});

Utilisation d’un initializer pour implémenter le binding two-way

WinJS fournie à la fonction initializer quatre paramètres qui nous donnent les informations suivantes :

  • Source : Objet métier utilisé comme source du binding.
  • SourceProps : Tableau des propriétés de l’objet métier.
  • Dest : Elément HTML mis à jour quand la donnée change.
  • DestProps : Tableau des propriétés de l’élément graphique.

Par exemple :

 <input data-win-bind="prop: source.propA.propB" />

Dans cet exemple, on a :

  • Dans le tableau sourceProps : [propA, propB]
  • Dans le tableau destProps : [prop]

On peut s’abonner aux évènements de l’élément HTML (par exemple l’évènement onchange utilisable sur les éléments HTML et WinJS) et ainsi être notifié des changements initiées par l’utilisateur. Lorsque l’élément change, on peut alors mettre à jour l’objet métier, et on obtient un binding two way !

C’est ce que l’on fait dans le bout de code suivant :

  • On commence par appeler le defaultBind pour gérer le binding OneWay
  • On utilise addEventListener pour s’enregistrer à l’évènement change
  • On récupère l’objet source et on change sa propriété en lui donnant la nouvelle valeur
 var oldValue, newValue, destObject, sourceObject;

var destinationChanged = function () {
    newValue = getValue(dest, destProps);
    sourceObject = getObject(source, sourceProps);

    if (sourceObject) {
        oldValue = sourceObject[sourceProps[sourceProps.length - 1]];
        if (oldValue !== newValue) {
            sourceObject[sourceProps[sourceProps.length - 1] ] = newValue;
        }
    }
};

WinJS.Binding.defaultBind(source, sourceProps, dest, destProps);
destObject = getObject(dest, destProps);

if (destObject !== undefined) {
    destObject.addEventListener( 'change' , destinationChanged);
}

Pour récupérer l’objet source et la valeur de destination, on utilise les deux petites fonctions suivantes :

  • getValue permet de récupérer la valeur de propriété spécifiée par le binding
  • getObject permet de récupérer l’objet concret dont on veut modifier la propriété
 function getValue(obj, objProps) {
    var i,
        o = obj;

    for (i = 0; i < objProps.length; ++i) {
        o = o[objProps[i]];
    }

    return o;
}

function getObject(obj, objProps) {
    var i,
        o = obj;

    for (i = 0; i < objProps.length - 1; ++i) {
        o = o[objProps[i]];
    }

    return o;
}

Conclusion

Bien que le framework WinJS soit jeune, et par conséquent qu’il ait quelques lacunes, certaines peuvent être comblés par son côté très personnalisable. C’est le cas du binding two way, qui peut être implémenté très facilement grâces à des mécanismes bien présents que sont les initializers.

- Sébastien Mornas -