Ext JS: This Tricky Button Click Event

The chances are that during your JavaScript development with the Ext JS framework you’ll use the object notation for declarative UI creation. It’s easy, simple, quick but has a little gotcha, especially if you came to Ext JS framework from a strict object-oriented environment. For example, consider a custom component that contains a button:

Ext.define('app.MyComponent', {

    extend: 'Ext.container.Container',
    requires: ['Ext.button.Button'],
    alias: 'widget.mycomponent',

    items: [{
        xtype: 'button',
        text: 'Button 1',
      handler: function() {
            console.log(‘button clicked!’);
        }
    }]
});

So far so good. The handler function is invoked when the user clicks the button. But what if we want to add our component’s context to that handler. Here’s the first solution that came into my mind:

Ext.define('app.MyComponent', {

    extend: 'Ext.container.Container',
    requires: ['Ext.button.Button'],
    alias: 'widget.mycomponent',

    items: [{
        xtype: 'button',
        text: 'Button 1',
        handler: this.onButton,
        scope: this
    }],

    onButton: function() {
        console.log(‘button clicked!’);
    }
});

But it doesn’t work as this pointer can be tricky in JavaScript: there is no instance of MyComponent created yet and “this.onButton” points not where you might think it does.

From my experience with jQuery framework I recalled the “unobtrusive Javascript” technique in part of separation of functionality (the “behavior layer”) from a Web page’s structure/content and presentation, so I decided to bind the event listener after the rendering is complete. To get a hold of the button inside the container (after it’s been created) I’ve added the itemId:

Ext.define('app.MyComponent', {

    extend: 'Ext.container.Container',
    requires: ['Ext.button.Button'],
    alias: 'widget.mycomponent',

    items: [{
        xtype: 'button',
        itemId: ‘button’,
        text: 'Button 1'
    }],

    constructor: function() {
        this.callParent(parameters);
        this.on(‘afterrender’, function() {
            this.down(‘#button’).on(‘click’, this.onButton, this);
        }, this);
    },

    onButton: function() {
        console.log(‘button clicked!’);
    }
});

This time it worked! The variable this pointed at the container, and with the help of the Ext JS function down() I found the reference to my button.

Here’s a slightly different version, which uses a template method:

Ext.define('app.MyComponent', {

    extend: 'Ext.container.Container',
    requires: ['Ext.button.Button'],
    alias: 'widget.mycomponent',

    items: [{
        xtype: 'button',
        itemId: ‘button’,
        text: 'Button 1'
    }],

    afterRender: function() {
        this.callParent(arguments);
        this.down('#button').on('click', this.onButton, this);
    },

    onButton: function() {
        console.log(‘button clicked!’);
    }
});

While for jQuery developers the above code looks good, for Flex developers it looks ugly. The UI definition and event binding are in different places. To make them happy I looked at the component lifecycle and tried to find the place there I can set items and have this pointing to the freshly created component.

Here’s what’s happening in the initialization phase of the Ext JS component:

1. The component configuration code is applied.
2. The base Component events are registered
3. The ComponentMgr gets registered
4. The initComponent is executed
5. The plug-ins (if any) are initialized
6. The State is initialized
7. The Ext JS Component is rendered.

The most interesting for me was the initComponent as it’s responsible for the creation of child components. So you can set the items right before the component is created:

Ext.define('app.MyComponent', {

    extend: 'Ext.container.Container',
    requires: ['Ext.button.Button'],
    alias: 'widget.mycomponent',

    initComponent: function() {
        this.items = [{
            xtype: 'button',
            text: 'Button 1',
            handler: this.onButton,
            scope: this
        }];

        this.callParent(arguments);
    },

    onButton: function() {
        console.log('button clicked!');
    }
});

Voila! This is almost perfect. To provide the component scope in the event handler we override one method and add one additional invocation to the callParent function.

I am wondering why ExtJS doesn’t have documented elegant solution for this use case yet? In the config object “items” can hold single object or array of objects but it also could contain a function returning object or objects array having “this” scoped to container itself.

To learn more about the proper way of architecting enterprise applications attend our 2 day Ext JS workshop in June in New York City. Enter AlexO as a promo code to get $100 off the price.

Alex Oleynik