【问题标题】:extjs - how correctly call a controller method from another controller or closureextjs - 如何正确地从另一个控制器或闭包调用控制器方法
【发布时间】:2013-10-08 17:00:03
【问题描述】:

我是 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。

更新

感谢所有可以优化我的代码并提出的建议:

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

【问题讨论】:

    标签: extjs controller closures extjs4.2


    【解决方案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'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) {
            ...
        }
    });
    

    一个最佳实践是尽可能细粒度地编写 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*
        }
    });
    

    这可能看起来像是一种更复杂的做事方式,但实际上它在大型(ish)应用程序中使用起来要简单得多,并且它解决了我们遇到的主要问题:不需要硬绑定控制器不再存在,您可以单独测试每个控制器。

    在我的博文中查看更多信息:Controller events in Ext JS 4.2

    【讨论】:

    • 非常感谢!你的回答远远超出了我的预期。
    • 也许另一个要添加的选项是应用程序级事件。按照 Tommy 的 MVC 指南第 2 部分 docs.sencha.com/extjs/4.1.3/#!/guide/… 中的建议,让应用程序对象中的控制器触发事件​​并从另一个控制器中监听它们
    • @dbrin 应用程序级别(或全局)事件从 Ext JS 4.0 开始以一种骇人听闻的方式可用,而从 4.1 开始以更官方支持的方式提供;然而,这种方法与直接调用控制器方法有相同的缺陷:你有一个硬绑定,这一次是在应用程序本身上。实际上更糟糕的是,为了测试您的代码,您必须要么加载整个应用程序,要么将其集中存根。使用控制器事件域可以解决所有这些问题而不会引入负面影响,并且是 4.2+ 中推荐的执行方式。
    • 很高兴知道,谢谢。就测试而言,我希望看到使用 Jasmine(无 DOM)进行纯单元测试的明智方法。我已经看到了很多尝试,但没有任何我认为是黄金标准的东西。 Fiesta 很棒,但更多的是全功能测试方法。
    • 大家好,不幸的是,在 extjs 6.3.5 中,Ext.app.ViewController 中的 getController 不是有效方法,我如何从 ViewController 中调用另一个控制器?
    【解决方案2】:

    thissuccess 回调中不起作用,因为它没有正确的范围。您的 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()}
                        })
                    )
                })
            })
        })
    },
    

    【讨论】:

    • 第二个解决方案看起来特别好。我明天试试看。
    • 我通常更喜欢第一个,以帮助减少使用this时的混淆,并且它可以在缩小时节省字符......但这确实是个人喜好问题。
    猜你喜欢
    • 1970-01-01
    • 2019-10-17
    • 1970-01-01
    • 2011-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多