Config Objects on the Prototype

One of my favourite changes moving from ExtJS 3 to ExtJS 4 is that many more config settings can be specified on a class's prototype. In ExtJS 3 it was often necessary to resort to using an initComponent method to specify any settings that used reference types but in ExtJS 4 these can almost all be specified directly on the class. This change seems to have gone largely unnoticed yet many developers are taking advantage without realizing it.

ExtJS Classes and the Prototype

First, let's briefly recap how the ExtJS class system is built on JavaScript prototypes.

        Ext.define('MyClass', {
            height: 120
        });
    

In ExtJS 3 this would have been done using Ext.extend but the principle is exactly the same. It defines a class called MyClass and the height property is copied to the class's prototype. The following code is roughly equivalent:

        Ext.define('MyClass');

        MyClass.prototype.height = 120;
    

The prototype is shared between all instances of the class, so for example:

        var myObj = Ext.create('MyClass'); // could also use: var myObj = new MyClass();

        console.log(myObj.height); // 120, inherited through the prototype of the object's class
    

Instantiated Components on the Prototype

Putting reference types on the prototype is a common source of errors. The problems occur because the same instance of the reference type is shared between all instances of the class. One of the simplest examples is trying to specify child components on a container using items:

        Ext.define('MyPanel', {
            extend: 'Ext.panel.Panel',

            items: [
                Ext.create('Ext.button.Button', {text: 'Button'})
            ]
        });
    

This stores a button object on the prototype of MyPanel. As a result it will be shared between all instances of MyPanel. Let's see what happens when we create two of these panels.

        Ext.create('MyPanel', {height: 100, width: 100});
        Ext.create('MyPanel', {height: 100, width: 100});
    

Both panels tried to show the same button but it can only be in one place at once.

Item Configs and xtypes on the Prototype

Instead of trying to share a button, what we can do is share a button config. Each panel can then instantiate their own instance of the button and everything will work nicely.

        Ext.define('MyBetterPanel', {
            extend: 'Ext.panel.Panel',

            items: [
                {text: 'Button', xtype: 'button'}
            ]
        });
    

The setting xtype is used to tell the panel what type of child component to instantiate. This works perfectly:

In ExtJS 3 this wasn't possible. There was no fundamental reason why it couldn't be done, just a few small implementation details that prevented it from working.

Of course there's plenty more scope for it to go horribly wrong. Component configs are themselves objects, so if the button class mutated its config object then sharing it would cause all sorts of subtle failures. A common approach to avoid this kind of problem is to set the config options in initComponent instead. This is a technique taken straight from ExtJS 3 and while it does work it isn't as elegant as an inline config. Thankfully the classes in ExtJS 4 have almost all been written to avoid mutating their config objects.

Another potential problem is subclassing. Properties on the prototype of a subclass completely hide properties with the same name on the superclass. This may be what you want but with an object or an array it's common to want to somehow merge them. For a class that is intended to be subclassed it's usually better to avoid putting reference types on the prototype and to add a template method instead:

        Ext.define('ExtensiblePanel', {
            extend: 'Ext.panel.Panel',

            initComponent: function() {
                this.items = this.createItems();
                this.callParent();
            },

            createItems: function() {
                return [
                    {text: 'Button', xtype: 'button'}
                ]
            }
        });
    

Doing it this way allows a subclass to override the createItems method and change the items in whatever way is required. This is also quite a nice use of xtypes, as the subclass can call the overridden method and modify the configs prior to the components being instantiated.

Other Configs on the Prototype

The items config option is far from the only one to support this style of configuration. In ExtJS 3 it wasn't possible to specify a tbar or bbar on the prototype without an explosion but now it works fine:

        Ext.define('ToolbarPanel', {
            extend: 'Ext.panel.Panel',

            bbar: [{text: 'Bottom Button'}],
            tbar: [{text: 'Top Button'}]
        });

        Ext.create('ToolbarPanel', {height: 100, width: 100});
        Ext.create('ToolbarPanel', {height: 100, width: 100});
    

There are numerous other examples, some using xtypes and some using other types of alias. These include the settings dockedItems, plugins, columns, selModel, store, proxy, reader, writer, tools and buttons. In all cases it isn't safe to specify the instantiated object on the prototype but by using a config object instead there's no need to resort to using initComponent.

Let's take a look at an example that uses several of these config options with nested config objects:

        Ext.define('MyGrid', {
            extend: 'Ext.grid.Panel',

            buttons: [{ // config
                text: 'OK'
            }],

            columns: [{ // config
                dataIndex: 'text',
                editor: {xtype: 'textfield'}, // config
                flex: 1,
                text: 'Name'
            }],

            plugins: [{ // config
                ptype: 'cellediting'
            }],

            selModel: { // config
                mode: 'MULTI'
            },

            store: { // config
                autoLoad: true,
                fields: ['text'],

                proxy: { // config
                    data: [{text: 'Red'}, {text: 'Green'}, {text: 'Blue'}],
                    type: 'memory',

                    reader: { //config
                        type: 'json'
                    }
                }
            },

            tools: [{ // config
                type: 'refresh'
            }]
        });

        Ext.create('MyGrid', {height: 120, width: 120});
        Ext.create('MyGrid', {height: 120, width: 120});
    

If you've been using ExtJS 4 for a little while then you might think nothing of a class definition like this but there's a lot going on here. There are no fewer than 9 inline configs stored on the prototype of MyGrid. Had they been specified as their instantiated equivalents it would have caused all manner of problems. For example, sharing a store would appear to be working fine until the store is sorted, then suddenly all instances of the grid would show the same sorting.

In ExtJS 3 a class definition like this just wouldn't have been possible. Almost all of these options would need to have been moved into initComponent. The ExtJS 4 way is much cleaner.

-----