web-dev-qa-db-ja.com

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

thisがクロージャー内のコントローラーオブジェクトを参照していないため、MyApp.app.getController('Main').controllerMethodTwo()が機能しないため、this..controllerMethodTwo()でメソッドを参照しました。

私はこれが完全に複雑であることを発見し、誰かがその_MyApp.app.getController_-回避策を回避するアイデアを持っていることを願っています。

更新

コードを最適化して、思いついたすべての提案のおかげで:

_// 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

実際、コードには少なくとも4つの明らかに異なる問題があります。

  • クラス内メソッド呼び出しのスコープ処理
  • コンポーネント作成の非効率性
  • コントローラーでのコンポーネントイベント処理
  • コントローラー間の通信

スコープの取り扱い

最初の問題は、上記の@kevhenderのように、クロージャーを使用するか、スコープパラメーターをAjaxリクエストに渡すことによって解決されます。それを踏まえて、より明確なコードを書くことを提唱します。

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

コンポーネントの作成

everyメニュー項目が1つずつ作成され、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'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) {
        ...
    }
});

1つのベストプラクティスは、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のコントローラーイベント

33
Alex Tokarev

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:Ext.Ajax.request呼び出しのscope構成を使用します。

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