web-dev-qa-db-fra.com

extjs - comment appeler correctement une méthode de contrôleur depuis un autre contrôleur ou une fermeture

Je suis nouveau sur extjs et j'utilise l'architecture MVC.

Lorsque mon application fait référence à une méthode d'un contrôleur, je le fais de cette façon (dans MyApp.Application):

Mb.app.getController('Main').myMethod();

C'est déjà long, mais je pense que c'est la façon de procéder.

Lorsqu'un contrôleur appelle sa propre méthode dans une fermeture, j'ai été amené à utiliser ce code (dans MyApp.controller.Main:

controllerMethodOne: function(){
    Ext.Ajax.request({
        url: ...,
        params: ...,
        success: (function(response){
            list = Ext.JSON.decode(response.responseText);
            list.forEach(function(item){
                storeMenu.add(
                    Ext.create('Ext.menu.Item', {
                        text: item.text,
                        handler: function(el){MyApp.app.getController('Main').controllerMethodTwo()}
                    })
                )
            })
        })
    })
},

J'ai référencé la méthode avec MyApp.app.getController('Main').controllerMethodTwo() parce que this ne fait pas référence à l'objet contrôleur dans la fermeture, et donc this..controllerMethodTwo() ne fonctionne pas.

Je trouve cela complètement compliqué, et j'espère que quelqu'un a une idée pour contourner cette solution de contournement MyApp.app.getController.

Mettre à jour

Grâce à toutes les suggestions que j'ai pu optimiser mon code, j'ai trouvé:

// in my controller
    mixins: ['Mb.controller.mixin.StoreMenu'],
    // I use that style of menus in two controllers thats why I use a mixin
    init: function() {
        this.control({
            '#vg_storeMenu menuitem': {
                click: this.onStoreMenuClicked
            }
        })
    },

// the controller mixin
Ext.define('Mb.controller.mixin.StoreMenu', {
    extend: 'Ext.app.Controller',
    buildStoreMenu: function(store_name){
        var storeMenu = Ext.ComponentQuery.query('#' + store_name + 'Menu')[0];
        Ext.Ajax.request({
            url: Paths.ajax + 'json.php',
            params: {list: store_name + 's'},
            success: (function(response){
            list = Ext.JSON.decode(response.responseText);
            items = Ext.Array.map(list, function(item) {
                return {
                    xtype: 'menuitem',
                    text: item.text
                }
            });
                storeMenu.add(items);
            })
        })
    },
    onStoreMenuClicked: function(el){
        ...
    }
});
15
Lorenz Meyer

En fait, il y a au moins quatre problèmes très différents dans votre code:

  • Gestion de l'étendue des appels de méthode intra-classe
  • Inefficacité de création de composants
  • Gestion des événements de composants dans un contrôleur
  • Communication entre contrôleurs

Gestion de la portée

Le premier est résolu soit en utilisant une fermeture, soit en passant le paramètre scope à la requête Ajax, comme @kevhender décrit ci-dessus. Cela étant, je recommanderais d'écrire un code plus clair:

controllerMethodOne: function() {
    Ext.Ajax.request({
        url: ...,
        params: ...,
        scope: this,
        success: this.onMethodOneSuccess,
        failure: this.onMethodOneFailure
    });
},

// `this` scope is the controller here
onMethodOneSuccess: function(response) {
    ...
},

// Same scope here, the controller itself
onMethodOneFailure: function(response) {
    ...
}

Création de composants

La façon dont vous créez des éléments de menu est moins efficace, car chaque élément de menu sera créé et rendu au DOM un par un. Ce n'est pas vraiment nécessaire non plus: vous avez la liste des éléments à l'avance et vous avez le contrôle, alors gardons le code Nice et déclaratif, ainsi que créons tous les éléments de menu en une seule fois:

// I'd advocate being a bit defensive here and not trust the input
// Also, I don't see the `list` var declaration in your code,
// do you really want to make it a global?
var list, items;

list  = Ext.JSON.decode(response.responseText);
items = Ext.Array.map(list, function(item) {
    return {
        xtype: 'menuitem',
        text: item.text
    }
});

// Another global? Take a look at the refs section in Controllers doc
storeMenu.add(items);

Ce qui change ici, c'est que nous itérons sur le list et créons un nouveau tableau des déclarations des éléments de menu à venir. Ensuite, nous les ajoutons tous en une seule fois, économisant beaucoup de ressources sur le re-rendu et la re-disposition de votre storeMenu.

Manipulation uniforme des composants

Il est tout à fait inutile et inefficace de définir une fonction de gestionnaire sur chaque élément de menu, alors que cette fonction ne fait qu'appeler le contrôleur. Lorsqu'un élément de menu est cliqué, il déclenche un événement click - tout ce que vous devez faire est de câbler votre contrôleur pour écouter ces événements:

// Suppose that your storeMenu was created like this
storeMenu = new Ext.menu.Menu({
    itemId: 'storeMenu',
    ...
});

// Controller's init() method will provide the wiring
Ext.define('MyController', {
    extend: 'Ext.app.Controller',

    init: function() {
        this.control({
            // This ComponentQuery selector will match menu items
            // that descend (belong) to a component with itemId 'storeMenu'
            '#storeMenu menuitem': {
                click: this.controllerMethodTwo
            }
        });
    },

    // The scope is automatically set to the controller itself
    controllerMethodTwo: function(item) {
        ...
    }
});

Une meilleure pratique consiste à écrire les sélecteurs ComponentQuery aussi finement granulés que possible, car ils sont globaux et si vous n'êtes pas assez précis, votre méthode de contrôleur peut intercepter les événements des composants indésirables.

Communication entre contrôleurs

C'est probablement un peu exagéré pour le moment, mais puisque vous utilisez Ext JS 4.2, vous pouvez également profiter des améliorations que nous avons ajoutées à cet égard. Avant 4.2, il existait une approche préférée (et unique) pour appeler les méthodes d'un contrôleur à partir d'un autre contrôleur:

Ext.define('My.controller.Foo', {
    extend: 'Ext.app.Controller',

    methodFoo: function() {
        // Need to call controller Bar here, what do we do?
        this.getController('Bar').methodBar();
    }
});

Ext.define('My.controller.Bar', {
    extend: 'Ext.app.Controller',

    methodBar: function() {
        // This method is called directly by Foo
    }
});

Dans Ext JS 4.2, nous avons ajouté le concept de domaines d'événements. Cela signifie que les contrôleurs peuvent désormais écouter non seulement les événements des composants, mais aussi ceux des autres entités. Y compris leur propre domaine de contrôleur:

Ext.define('My.controller.Foo', {
    extend: 'Ext.app.Controller',

    methodFoo: function() {
        // Effectively the same thing as above,
        // but no direct method calling now
        this.fireEvent('controllerBarMethodBar');
    }
});

Ext.define('My.controller.Bar', {
    extend: 'Ext.app.Controller',

    // Need some wiring
    init: function() {
        this.listen({
            controller: {
                '*': {
                    controllerBarMethodBar: this.methodBar
                }
            }
        });
    },

    methodBar: function() {
        // This method is called *indirectly*
    }
});

Cela peut sembler une façon plus compliquée de faire les choses, mais en fait, il est beaucoup plus simple à utiliser dans les grandes applications (ish), et cela résout le principal problème que nous avons eu: il n'y a plus besoin de liaison stricte entre les contrôleurs, et vous pouvez tester chaque contrôleur indépendamment des autres.

Voir plus dans mon article de blog: Événements de contrôleur dans Ext JS 4.2

33
Alex Tokarev

this ne fonctionne pas dans le rappel success car il n'a pas la bonne portée. Vos 2 options sont les suivantes:

1: Créez une variable au début de la fonction à référencer dans le rappel:

controllerMethodOne: function(){
    var me = this;
    Ext.Ajax.request({
        url: ...,
        params: ...,
        success: (function(response){
            list = Ext.JSON.decode(response.responseText);
            list.forEach(function(item){
                storeMenu.add(
                    Ext.create('Ext.menu.Item', {
                        text: item.text,
                        handler: function(el){me.controllerMethodTwo()}
                    })
                )
            })
        })
    })
},

2: utilisez la configuration scope du Ext.Ajax.request appel:

controllerMethodOne: function(){
    Ext.Ajax.request({
        url: ...,
        params: ...,
        scope: this,
        success: (function(response){
            list = Ext.JSON.decode(response.responseText);
            list.forEach(function(item){
                storeMenu.add(
                    Ext.create('Ext.menu.Item', {
                        text: item.text,
                        handler: function(el){me.controllerMethodTwo()}
                    })
                )
            })
        })
    })
},
2
kevhender