I've recently started learning ExtJS, and have trouble understanding how to handle Events. I have no experience of any previous versions of ExtJS.

From reading various manuals, guides and documentation pages, I've figured out how to use it, but I'm not clear on how it works. I've found several tutorials for older versions of ExtJS, but I'm not sure how applicable they are in ExtJS 4.

I'm specifically looking on the "final word" on things like

  • what arguments does an event handling function get passed? Is there a standard set of args that always get passed?
  • how to define custom events for custom components we write? how can we fire these custom event?
  • does the return value (true/false) affect how the event bubbles? If not, how can we control event bubbling from within, or outside of the event handler?
  • is there a standard way to register event listeners? (I've come across two different ways til now, and I'm not sure why each method was used).

For example, this question leads me to believe that an event handler can receive quite a few arguments. I've seen other tutorials where there are just two arguments to the handler. What changes?


Solution 1:

Let's start by describing DOM elements' event handling.

DOM node event handling

First of all you wouldn't want to work with DOM node directly. Instead you probably would want to utilize Ext.Element interface. For the purpose of assigning event handlers, Element.addListener and Element.on (these are equivalent) were created. So, for example, if we have html:

<div id="test_node"></div>

and we want add click event handler.
Let's retrieve Element:

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

Now let's check docs for click event. It's handler may have three parameters:

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

Knowing all this stuff we can assign handler:

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

Widgets event handling

Widgets event handling is pretty much similar to DOM nodes event handling.

First of all, widgets event handling is realized by utilizing Ext.util.Observable mixin. In order to handle events properly your widget must containg Ext.util.Observable as a mixin. All built-in widgets (like Panel, Form, Tree, Grid, ...) has Ext.util.Observable as a mixin by default.

For widgets there are two ways of assigning handlers. The first one - is to use on method (or addListener). Let's for example create Button widget and assign click event to it. First of all you should check event's docs for handler's arguments:

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

Now let's use on:

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

The second way is to use widget's listeners config:

var myButton = Ext.create('Ext.button.Button', {
  text: 'Test button',
  listeners : {
    click: function(btn, e, eOpts) {
      // event handling here
      console.log(btn, e, eOpts);
    }
  }
});

Notice that Button widget is a special kind of widgets. Click event can be assigned to this widget by using handler config:

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

Custom events firing

First of all you need to register an event using addEvents method:

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

Using the addEvents method is optional. As comments to this method say there is no need to use this method but it provides place for events documentation.

To fire your event use fireEvent method:

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

arg1, arg2, arg3, /* ... */ will be passed into handler. Now we can handle your event:

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

It's worth mentioning that the best place for inserting addEvents method call is widget's initComponent method when you are defining new widget:

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

Preventing event bubbling

To prevent bubbling you can return false or use Ext.EventObject.preventDefault(). In order to prevent browser's default action use Ext.EventObject.stopPropagation().

For example let's assign click event handler to our button. And if not left button was clicked prevent default browser action:

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

Solution 2:

Firing application wide events

How to make controllers talk to each other ...

In addition to the very great answer above I want to mention application wide events which can be very useful in an MVC setup to enable communication between controllers. (extjs4.1)

Lets say we have a controller Station (Sencha MVC examples) with a select box:

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]);
    },    
   ...
});

When the select box triggers a change event, the function onStationSelect is fired.

Within that function we see:

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

This creates and fires an application wide event that we can listen to from any other controller.

Thus in another controller we can now know when the station select box has been changed. This is done through listening to this.application.on as follows:

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?');
    },
}

If the selectbox has been changed we now fire the function onStationStart in the controller Song also ...

From the Sencha docs:

Application events are extremely useful for events that have many controllers. Instead of listening for the same view event in each of these controllers, only one controller listens for the view event and fires an application-wide event that the others can listen for. This also allows controllers to communicate with one another without knowing about or depending on each other’s existence.

In my case: Clicking on a tree node to update data in a grid panel.

Update 2016 thanks to @gm2008 from the comments below:

In terms of firing application-wide custom events, there is a new method now after ExtJS V5.1 is published, which is using Ext.GlobalEvents.

When you fire events, you can call: Ext.GlobalEvents.fireEvent('custom_event');

When you register a handler of the event, you call: Ext.GlobalEvents.on('custom_event', function(arguments){/* handler codes*/}, scope);

This method is not limited to controllers. Any component can handle a custom event through putting the component object as the input parameter scope.

Found in Sencha Docs: MVC Part 2

Solution 3:

One more trick for controller event listeners.

You can use wildcards to watch for an event from any component:

this.control({
   '*':{ 
       myCustomEvent: this.doSomething
   }
});

Solution 4:

Just wanted to add a couple of pence to the excellent answers above: If you are working on pre Extjs 4.1, and don't have application wide events but need them, I've been using a very simple technique that might help: Create a simple object extending Observable, and define any app wide events you might need in it. You can then fire those events from anywhere in your app, including actual html dom element and listen to them from any component by relaying the required elements from that component.

Ext.define('Lib.MessageBus', {
    extend: 'Ext.util.Observable',

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

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

Then you can, from any other component:

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

And fire them from any component or dom element:

 MessageBus.fireEvent('event1', somearg);

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

Solution 5:

Just two more things I found helpful to know, even if they are not part of the question, really.

You can use the relayEvents method to tell a component to listen for certain events of another component and then fire them again as if they originate from the first component. The API docs give the example of a grid relaying the store load event. It is quite handy when writing custom components that encapsulate several sub-components.

The other way around, i.e. passing on events received by an encapsulating component mycmp to one of its sub-components subcmp, can be done like this

mycmp.on('show' function (mycmp, eOpts)
{
   mycmp.subcmp.fireEvent('show', mycmp.subcmp, eOpts);
});