关于javascript:解释ExtJS 4事件处理

Explain ExtJS 4 event handling

我最近开始学习extjs,并且很难理解如何处理事件。我没有任何以前版本的extjs的经验。

从阅读各种手册、指南和文档页面,我已经了解了如何使用它,但我不清楚它是如何工作的。我发现了一些针对旧版本的extjs的教程,但我不确定它们在extjs 4中的适用性。

我特别关注的是"最后一个词",比如

  • 传递事件处理函数的参数是什么?是否有一组标准的参数总是被传递?
  • 如何为我们编写的自定义组件定义自定义事件?我们如何启动这些自定义事件?
  • 返回值(真/假)是否影响事件冒泡的方式?如果不是,我们如何控制事件从事件处理程序内部或外部冒泡?
  • 有没有一种注册事件侦听器的标准方法?(到目前为止,我遇到了两种不同的方法,我不知道为什么使用每种方法)。

例如,这个问题让我相信一个事件处理程序可以接收到相当多的参数。我看过其他教程,其中只有两个处理程序参数。什么变化?


让我们从描述DOM元素的事件处理开始。

DOM节点事件处理

首先,您不想直接使用dom节点。相反,您可能希望使用Ext.Element接口。为了分配事件处理程序,创建了Element.addListenerElement.on(它们是等效的)。例如,如果我们有HTML:

1
 

我们需要添加click事件处理程序。让我们取回Element

1
var el = Ext.get('test_node');

现在让我们检查click事件的文档。它的处理程序可能有三个参数:

click( Ext.EventObject e, HTMLElement t, Object eOpts )

知道所有这些,我们可以分配处理程序:

1
2
3
4
//       event name      event handler
el.on(    'click'        , function(e, t, eOpts){
  // handling event here
});

小部件事件处理

小部件事件处理与DOM节点事件处理非常相似。

首先,小部件事件处理是通过使用Ext.util.Observable混合来实现的。为了正确地处理事件,您的小部件必须包含Ext.util.Observable作为混合。默认情况下,所有内置的小部件(如面板、窗体、树、网格等)都将Ext.util.Observable作为mixin。

对于小部件,有两种分配处理程序的方法。第一个是用于方法(或addListener)。例如,让我们创建Button小部件并将click事件分配给它。首先,您应该检查事件的文档中是否有处理程序的参数:

click( Ext.button.Button this, Event e, Object eOpts )

现在让我们使用on

1
2
3
4
5
6
7
var myButton = Ext.create('Ext.button.Button', {
  text: 'Test button'
});
myButton.on('click', function(btn, e, eOpts) {
  // event handling here
  console.log(btn, e, eOpts);
});

第二种方法是使用小部件的侦听器配置:

1
2
3
4
5
6
7
8
9
var myButton = Ext.create('Ext.button.Button', {
  text: 'Test button',
  listeners : {
    click: function(btn, e, eOpts) {
      // event handling here
      console.log(btn, e, eOpts);
    }
  }
});

注意,Button小部件是一种特殊的小部件。点击事件可以通过使用handler配置分配给这个小部件:

1
2
3
4
5
6
7
var myButton = Ext.create('Ext.button.Button', {
  text: 'Test button',
  handler : function(btn, e, eOpts) {
    // event handling here
    console.log(btn, e, eOpts);
  }
});

自定义事件触发

首先,您需要使用addEvents方法注册事件:

1
myButton.addEvents('myspecialevent1', 'myspecialevent2', 'myspecialevent3', /* ... */);

使用addEvents方法是可选的。正如对此方法的评论所说,不需要使用此方法,但它为事件文档提供了位置。

要触发事件,请使用FireEvent方法:

1
myButton.fireEvent('myspecialevent1', arg1, arg2, arg3, /* ... */);

arg1, arg2, arg3, /* ... */将传递给handler。现在我们可以处理您的活动:

1
2
3
4
myButton.on('myspecialevent1', function(arg1, arg2, arg3, /* ... */) {
  // event handling here
  console.log(arg1, arg2, arg3, /* ... */);
});

值得一提的是,在定义新的小部件时,插入addEvents方法调用的最佳位置是小部件的initComponent方法:

1
2
3
4
5
6
7
8
9
10
Ext.define('MyCustomButton', {
  extend: 'Ext.button.Button',
  // ... other configs,
  initComponent: function(){
    this.addEvents('myspecialevent1', 'myspecialevent2', 'myspecialevent3', /* ... */);
    // ...
    this.callParent(arguments);
  }
});
var myButton = Ext.create('MyCustomButton', { /* configs */ });

防止事件冒泡

为了防止起泡,可以使用return falseExt.EventObject.preventDefault()。为了防止浏览器的默认操作,请使用Ext.EventObject.stopPropagation()

例如,让我们将click事件处理程序分配给按钮。如果未单击左键,则阻止默认浏览器操作:

1
2
3
4
myButton.on('click', function(btn, e){
  if (e.button !== 0)
    e.preventDefault();
});


触发应用程序范围的事件如何使控制器相互对话…

除了上面非常好的答案之外,我还想提到在MVC设置中非常有用的应用程序范围的事件,以实现控制器之间的通信。(Extjs4.1)

假设我们有一个带有选择框的控制器站(sencha mvc示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Ext.define('Pandora.controller.Station', {
    extend: 'Ext.app.Controller',
    ...

    init: function() {
        this.control({
            'stationslist': {
                selectionchange: this.onStationSelect
            },
            ...
        });
    },

    ...

    onStationSelect: function(selModel, selection) {
        this.application.fireEvent('stationstart', selection[0]);
    },    
   ...
});

当选择框触发更改事件时,函数onStationSelect被激发。

在这个功能中,我们看到:

1
this.application.fireEvent('stationstart', selection[0]);

这将创建并激发一个应用程序范围的事件,我们可以从任何其他控制器监听该事件。

因此,在另一个控制器中,我们现在可以知道何时更改了工作站选择框。这是通过听this.application.on来完成的,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Ext.define('Pandora.controller.Song', {
    extend: 'Ext.app.Controller',
    ...
    init: function() {
        this.control({
            'recentlyplayedscroller': {
                selectionchange: this.onSongSelect
            }
        });

        // Listen for an application wide event
        this.application.on({
            stationstart: this.onStationStart,
                scope: this
        });
    },
    ....
    onStationStart: function(station) {
        console.info('I called to inform you that the Station controller select box just has been changed');
        console.info('Now what do you want to do next?');
    },
}

如果选择框已更改,我们现在启动控制器Song中的函数onStationStart

从Sencha文件:

应用程序事件对于具有多个控制器的事件非常有用。与在每个控制器中监听相同的视图事件不同,只有一个控制器监听视图事件并触发其他控制器可以监听的应用程序范围的事件。这也允许控制器在不知道或依赖彼此存在的情况下彼此通信。

在我的例子中:单击树节点以更新网格面板中的数据。

2016年更新,感谢@gm2008,评论如下:

在触发应用程序范围的自定义事件方面,在发布extjs v5.1之后,现在有了一种新的方法,即使用Ext.GlobalEvents

当你触发事件时,你可以打电话:Ext.GlobalEvents.fireEvent('custom_event');

注册事件处理程序时,调用:Ext.GlobalEvents.on('custom_event', function(arguments){/* handler codes*/}, scope);

此方法不限于控制器。任何组件都可以通过将组件对象作为输入参数范围来处理自定义事件。

在Sencha文档中找到:MVC第2部分


控制器事件侦听器的另一个技巧。

您可以使用通配符监视来自任何组件的事件:

1
2
3
4
5
this.control({
   '*':{
       myCustomEvent: this.doSomething
   }
});

只是想在上面的优秀答案中加上几便士:如果您正在处理pre-extjs 4.1,并且没有应用程序范围的事件,但需要它们,那么我一直在使用一种非常简单的技术,它可能有助于:创建一个简单的扩展可观察的对象,并定义您可能需要的应用程序范围内的事件。然后,您可以从应用程序的任何地方触发这些事件,包括实际的HTMLDOM元素,并通过从该组件中继所需的元素来从任何组件侦听这些事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Ext.define('Lib.MessageBus', {
    extend: 'Ext.util.Observable',

    constructor: function() {
        this.addEvents(
            /*
             * describe the event
             */

                 "eventname"

            );
        this.callParent(arguments);
    }
});

然后,您可以从任何其他组件:

1
 this.relayEvents(MesageBus, ['event1', 'event2'])

并从任何组件或DOM元素中激发它们:

1
2
3
 MessageBus.fireEvent('event1', somearg);

 <input type="button onclick="MessageBus.fireEvent('event2', 'somearg')">


还有两件事我觉得很有帮助,即使它们不是问题的一部分,真的。

您可以使用relayEvents方法告诉一个组件监听另一个组件的某些事件,然后像从第一个组件发出一样再次触发它们。api文档给出了一个网格中继存储load事件的示例。它在编写封装多个子组件的自定义组件时非常方便。

另一种方法是,将封装组件mycmp接收到的事件传递给它的子组件之一subcmp,可以这样做。

1
2
3
4
mycmp.on('show' function (mycmp, eOpts)
{
   mycmp.subcmp.fireEvent('show', mycmp.subcmp, eOpts);
});