ExtJS

Ceci est la traduction de l'article de Phil Guerrant publié le 10 septembre 2014.

Introduction

Dans un article précédent, Using ViewControllers in Ext JS 5 (traduit ici), nous avons rapidement évoqué une fonctionnalité qui a été grandement améliorée dans Ext JS 5 — les declarative event listeners. Dans cet article, nous allons creuser le sujet et explorer la façon d'utiliser ces declarative listeners pour simplifier les vues de votre application et réduire le code spaghetti dans vos Components personnalisés.

NOTE: cet article suppose que vous utilisez au minimum Ext JS 5.0.1.

C'est quoi, des declarative listeners ?

Quand nous disons “declarative listeners,” nous faisons référence à des listeners qui sont déclarés dans le corps d'une classe ou dans l'objet de configuration d'une instance utilisant la config listeners. La capacité de déclarer des listeners de cette manière n'est pas une nouveauté de Ext JS 5. Avec Ext JS 4, vous pouviez déclarer des listeners dans une classe mais seulement si la fonction de gestion (handler) ou la portée (scope) avait déjà été définie. Par exemple :

    Ext.define('MyApp.view.User', {
        extend: 'Ext.panel.Panel',

        listeners: {
            // la fonction doit être « inline » ou avoir été définie au préalable :
            collapse: function() {
                // réagir au masquage du panel ici
            }
        },

        // Cette méthode ne peut pas être déclarée comme handler pour collapse
	  // (définie après) :
        onCollapse: function() {
        }
    });

Du fait que la fonction handler désirée n'est habituellement pas disponible au moment de la définition de la classe, les declarative listeners avaient une utilité limitée avec Ext JS 4. Typiquement, les développeurs ajoutaient des listeners en surchargeant initComponent et en utilisant la méthode on :

    Ext.define('MyApp.view.User', {
        extend: 'Ext.panel.Panel',

        initComponent: function() {
            this.callParent();

            this.on({
                collapse: this.onCollapse,
                scope: this
            });
        },

        onCollapse: function() {
            console.log(this); // l'instance du panel
        }
    });

Résolution de portée

Nous (NdT : eux, moi j'ai rien fait) avons amélioré la config listeners dans Ext JS 5 en permettant d'indiquer les handlers d'événement par leurs noms (sous forme d'une chaîne de caractères) correspondant aux noms des méthodes. Le framework transforme ces noms de méthodes en références réelles de fonctions à l'exécution (à chaque fois qu'un événement est lancé). Nous faisons référence à ce processus en tant que « listener scope resolution » (résolution de portée des listeners).

Avec Ext JS 4, vous ne pouviez résoudre les handlers via leur nom que si un scope était explicitement donné. Avec Ext JS 5, nous avons ajouté certaines règles spéciales permettant la résolution du scope par défaut quand un listener est déclaré via son nom sans que le scope soit explicitement indiqué.

La résolution de portée a deux résultats possibles : un composant (component) ou un ViewController. Quel que soit le résultat, la recherche commence avec le composant. Il se peut que le composant ou son ViewController soit la portée cherchée, mais si ce n'est pas le cas, le framework va « remonter » la hiérarchie du composant jusqu'à ce qu'il trouve un composant ou un ViewController qui corresponde.

Résolution de la portée en composants

La première façon dont le frameworlk résout la portée est de chercher un composant dont la config defaultListenerScope est positionnée à true. Pour les listeners déclarés sur la classe, la recherche commence par le composant lui-même.

    Ext.define('MyApp.view.user.User', {
        extend: 'Ext.panel.Panel',
        xtype: 'user',
        defaultListenerScope: true,

        listeners: {
            save: 'onUserSave'
        },

        onUserSave: function() {
            console.log('utilisateur enregistré');
        }
    });

Le listener est déclaré dans le corps de la classe de la vue User. Ça signifie que le framework va vérifier le defaultListenerScope de la vue User elle-même avant de remonter dans la hiérarchie. Dans le cas présent, comme la vue User possède la config defaultListenerScope mise à true, la portée pour le ce listener se résoudra avec la vue User.

Pour les listeners déclarés sur une config d'instance, le composant lui-même est ignoré et le framework cherche plus haut en commençant par le container parent. Examinez l'exemple suivant :

    Ext.define('MyApp.view.main.Main', {
        extend: 'Ext.container.Container',
        defaultListenerScope: true,

        items: [{
            xtype: 'user',
            listeners: {
                remove: 'onUserRemove'
            }
        }],

        onUserRemove: function() {
            console.log('utilisateur supprimé');
        }
    });

Le listener est déclaré sur la config de l'instance pour la vue User. Ça signifie que le framework va ignorer la vue User (même si elle a été déclarée avec defaultListenerScope:true) et résoudre la portée en remontant vers la vue Main.

Résolution de la portée en ViewControllers

Avec Ext JS 5, nous avons ajouté un nouveau type de Controller, Ext.app.ViewController. Nous avons décrit ces ViewControllers en détails dans Utiliser les ViewControllers avec Ext JS 5, si bien que nous allons seulement nous concentrer ici sur les event listeners et la façon dont ils sont liés aux ViewControllers.

Contrairement aux Ext.app.Controller qui peuvent gérer plusieurs vues, chaque instance de ViewController n'est liée qu'à une unique instance de vue. Cette relation un-un entre la vue et le ViewController permet au ViewController de servir de portée par défaut pour les listeners déclarés dans sa vue ou ses éléments de vue.

La même règle de defaultListenerScope s'applique aux ViewControllers. Les listeners du niveau de la classe cherchent toujours un ViewController sur le composant lui-même avant de remonter dans la hiérarchie des composants.

    Ext.define('MyApp.view.user.User', {
        extend: 'Ext.panel.Panel',
        controller: 'user',
        xtype: 'user',

        listeners: {
            save: 'onUserSave'
        }
    });

    Ext.define('MyApp.view.user.UserController', {
        extend: 'Ext.app.ViewController',
        alias: 'controller.user',

        onUserSave: function() {
            console.log('utilisateur enregistré');
        }
    });

Le listener précédent est déclaré dans le corps de la classe de la vue User. Puisque la vue User possède son propre contrôleur, le framework va résoudre la portée à UserController. Si la vue User n'avait pas eu son propre contrôleur, la portée aurait été résolue en remontant dans la hiérarchie.

D'un autre côté, les listeners du niveau d'instance ignorent le composant et se résolvent en tant que ViewController en remontant dans la hiérarchie en commençant par le container parent. Par exemple :

    Ext.define('MyApp.view.main.Main', {
        extend: 'Ext.container.Container',
        controller: 'main',

        items: [{
            xtype: 'user',
            listeners: {
                remove: 'onUserRemove'
            }
        }]
    });

    Ext.define('MyApp.view.main.MainController', {
        extend: 'Ext.app.ViewController',
        alias: 'controller.main',

        onUserRemove: function() {
            console.log('utilisateur supprimé');
        }
    });

Fusion des config listener

Avec Ext JS 4, les listeners qui étaient déclarés dans une classe auraient été complètement écrasés par une config listeners déclarée dans une sous-classe ou une instance. Avec Ext JS 5, nous avons amélioré l'API listeners en autorisant une fusion adaptée des listeners déclarés dans les classes, les sous-classes et les instances. Pour voir ceci en action, jetez un œil à cet exemple simple :

    Ext.define('BaseClass', {
        extend: 'Ext.Component',
        listeners: {
            foo: function() {
                console.log('foo fired');
            }
        }
    });

    Ext.define('SubClass', {
        extend: 'BaseClass',
        listeners: {
            bar: function() {
                console.log('bar fired');
            }
        }
    });

    var instance = new SubClass({
        listeners: {
            baz: function() {
                console.log('baz fired');
            }
        }
    });

    instance.fireEvent('foo');
    instance.fireEvent('bar');
    instance.fireEvent('baz');

Avec Ext JS 4, l'exemple précédent aurait simplement retourné “baz,” dans la console, mais avec Ext JS 5, les configs listeners sont correctement fusionnés et la sortie console est “foo bar baz.” Ceci permet aux classes de ne déclarer que les listeners qui les concernent sans s'occuper des listeners que leurs super-classes pourraient déjà posséder.

Conclusion

Nous pensons que les declarative listeners sont un formidable pas en avant pour simplifier la façon dont vous configurez les listeners d'événements dans vos applications. Associez-les avec des ViewControllers pour gérer la logique de votre application et des ViewModels pour le data binding bidirectionnel et vous devriez fortement améliorer votre façon de développer. Essayez et dites-nous ce que vous en pensez.

Écrit par Phil Guerrant

Phil est un ingénieur développement de Sencha qui travaille sur Ext JS. Il a plus de 10 ans d'expérience en tant que développeur et est spécialisé dans le développement HTML5 et web, les IHM et les méthodes agiles.

Articles connexes