Extjs - как правильно вызвать метод контроллера с другого контроллера или закрыть

Я новичок в extjs, и я использую архитектуру MVC.

Когда мое приложение ссылается на метод контроллера, я делаю это так (в MyApp.Application):

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

Это уже давно, но я думаю, что это способ сделать.

Когда контроллер вызывает его собственный метод в закрытии, я был вынужден использовать этот код (в 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()}
                    })
                )
            })
        })
    })
},

Я ссылался на метод с MyApp.app.getController('Main').controllerMethodTwo(), потому что this не ссылается на объект контроллера в закрытии и, следовательно, this..controllerMethodTwo() не работает.

Я нахожу это чрезвычайно запутанным, и я надеюсь, что у кого-то есть идея обойти это MyApp.app.getController -workaround.

Update

Благодаря всем предложениям я мог бы оптимизировать свой код и придумал:

// 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){
        ...
    }
});

Ответы

Ответ 1

На самом деле в коде есть по крайней мере четыре совершенно разные проблемы:

  • Обработка области для вызовов методов внутри класса
  • Неэффективность создания компонентов
  • Обработка событий компонентов в контроллере
  • Связь между контроллерами

Обработка области

Первый разрешается либо с помощью замыкания, либо с передачей в параметре области запроса Ajax, как @kevhender, описанным выше. Учитывая это, я защищал бы более четкий код:

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) {
    ...
}

Создание компонентов

Способ создания элементов меню менее эффективен, потому что каждый элемент меню будет создан и отображен в DOM один за другим. Это вряд ли необходимо: либо у вас есть список элементов вверх, и вы находитесь под контролем, так что держите код приятным и декларативным, а также создавайте все пункты меню за один раз:

// 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);

Что здесь происходит, так это то, что мы выполняем итерацию по list и создаем новый массив скоро появляющихся объявлений элементов меню. Затем мы добавляем их все за один раз, сохраняя много ресурсов при повторном рендеринге и перекладывая ваш storeMenu.

Компонент даже для обработки

Совершенно необязательно, а также неэффективно устанавливать функцию обработчика для каждого элемента меню, когда вся эта функция выполняет вызов контроллера. Когда щелкнут элемент меню, он запускает событие click - все, что вам нужно сделать, - это подключить ваш контроллер для прослушивания этих событий:

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

// Controller 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) {
        ...
    }
});

Одна из лучших практик заключается в том, чтобы написать селектор ComponentQuery как можно более мелкозернистый, поскольку они глобальны, и если вы недостаточно точны, ваш метод контроллера может захватывать события из нежелательных компонентов.

Связь между контроллерами

На данный момент это, вероятно, немного странно, но поскольку вы используете Ext JS 4.2, вы также можете воспользоваться улучшенными улучшениями, которые мы добавили в этом отношении. До 4.2 был предпочтительный (и единственный) подход для вызова одного метода контроллера с другого контроллера:

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
    }
});

В Ext JS 4.2 мы добавили концепцию доменов событий. Это означает, что теперь контроллеры могут прослушивать не только события компонентов, но и события других объектов. Включая собственный домен контроллера:

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*
    }
});

Это может выглядеть как более сложный способ сделать что-то, но на самом деле его гораздо проще использовать в больших (иш) приложениях, и он решает основную проблему, которую мы имели: нет необходимости в жестком привязке между контроллеров, и вы можете тестировать каждый контроллер отдельно от других.

Подробнее в своем сообщении в блоге: События контроллера в Ext JS 4.2

Ответ 2

this не работает в обратном вызове success, поскольку он не имеет правильной области. Ваши 2 варианта:

1: создайте переменную в начале функции для ссылки в обратном вызове:

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: используйте scope конфигурацию вызова Ext.Ajax.request:

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()}
                    })
                )
            })
        })
    })
},