1. #1
    Sencha User
    Join Date
    Mar 2010
    Posts
    490
    Answers
    12
    Vote Rating
    1
    abcdef is on a distinguished road

      0  

    Default Answered: How to properly extend components in ExtJS 4.

    Answered: How to properly extend components in ExtJS 4.


    I am looking to find out how to properly extend components in ExtJS 4.

    A typical scenario: I have a Grid A that extends Ext's Grid Panel and I need Grids B, and C to extend Grid A.

    1. I need properties in child components to override those of their parents.
    2. In case of event handling, sometimes I need to completely override an event handler of the parent in the child, and sometimes I need the child to handle some stuff, then complete its parent's event handling routine.

    On that note, when do I use constructor, initComponent, initConfig, and callParent. I have some idea about all of this, but it'll be very helpful if an expert can break it down for me. Link to a page explaining these concepts would also be help!

    Thanks!

  2. One great source of confusion in this area is that 3 different things are referred to as 'the config object'.

    The first one looks like this:

    Code:
    Ext.define('MyClass', {...});
    I tend to refer to the object highlighted in red here as the overrides object but the term 'config object' is commonly used.

    Secondly, there's the object passed to the constructor:

    Code:
    Ext.create('MyClass', {...});
    or:

    Code:
    new MyClass({...})
    I don't know of another name for this object.

    Thirdly, new in ExtJS 4, there's this one:

    Code:
    Ext.define('MyClass', {
        config: {...}
    });
    Personally I think calling this object 'config' was a poor choice as it just leads to even more confusion in an already confusing area.

    To make matters worse, all 3 types of 'config object' actually do quite similar jobs. From here on in I'm going to refer to them as type 1, type 2 and type 3, in the order they appear above.

    So, type 1. Several property names in this object have special meaning (extend, config, alias, ...). Properties that don't have any special meaning will just be copied to the prototype of the class. If you don't know what that means then I suggest you go find out, it's fundamental to how JavaScript classes work.

    Type 3 has a similar objective to type 1 but approaches it slightly differently. It doesn't store the properties on the prototype, instead it creates accessor methods for them on the prototype. At instantiation time it will then call the setter for each property with the appropriate value, be it the default value or another value that has been provided. This is what initConfig() does. However, having to call initConfig() manually is a known weakness with this approach and I hope it gets sorted out in a future release. Unless you really want getters and setters for your properties I'd stay clear of this approach for now.

    Type 2 is a bit different. On the face of it, this type of config object is just an object passed to the constructor. However, in practice one of the first things that almost all Ext constructors do is copy this object onto themselves, i.e. Ext.apply(this, config);. This means they end up as instance properties, hiding the properties found on the prototype. In this way, properties on the prototype act as defaults.

    The call to initComponent() comes after the constructor has copied the type 2 config onto this. You'll notice that initComponent() isn't even passed the config, it is expected just to read the properties off this.

    One thing it is important to understand is that if you use reference types (i.e. object or arrays) as opposed to primitive types (i.e. boolean, strings, numbers) for default values then you need to be careful. Using either type 1 or type 3 above will cause the same default object to be shared between all instances of the class. Sometimes this is fine but often it causes subtle and difficult bugs. In such cases it is common to specify these options in initComponent() instead.

  3. #2
    Sencha Premium Member skirtle's Avatar
    Join Date
    Oct 2010
    Location
    UK
    Posts
    3,605
    Answers
    543
    Vote Rating
    326
    skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future

      3  

    Default


    On that note, when do I use constructor, initComponent, initConfig, and callParent.
    Whether you choose to use a constructor or initComponent is a ExtJS flame war. My own view is that you should avoid using a constructor wherever possible: use initComponent instead. It is a template method designed to be overridden for subclass initialization and, in my opinion, leads to cleaner code.

    callParent is just a way of calling the overridden method from within the overriding method. Usually this is a good idea but if it doesn't make sense for your class then don't call it. In my example below I haven't passed it any arguments but you can where required (see the docs).

    For overriding event listeners, I think the easiest way to do this is to move the listener to a method of the class, which can then be overridden. Here's an example. Rather than using grids I've used buttons because I think that makes it easier to understand.

    Code:
    Ext.define('ButtonA', {
        extend: 'Ext.button.Button',
    
        width: 60, // Default added to the prototype
    
        initComponent: function() {
            this.callParent(); // calls initComponent on Ext.button.Button
            this.on('click', this.onClickHandler); // note: on the subclasses this is the overriding method
        },
    
        onClickHandler: function() {
            console.log('ButtonA - click');
        }
    });
    
    Ext.define('ButtonB', {
        extend: 'ButtonA',
    
        width: 100, // override the default of 60
    
        onClickHandler: function() {
            console.log('ButtonB - click');
            this.callParent(); // calls the method 'onClickHandler' from ButtonA
        }
    });
    
    Ext.define('ButtonC', {
        extend: 'ButtonA',
    
        onClickHandler: function() {
            console.log('ButtonC - click');
            // Not calling this.callParent(), completely replacing it
        }
    });
    This page of the docs covers some aspects of your question:

    http://docs.sencha.com/ext-js/4-0/#/guide/class_system

  4. #3
    Sencha User
    Join Date
    Mar 2010
    Posts
    490
    Answers
    12
    Vote Rating
    1
    abcdef is on a distinguished road

      0  

    Default


    Quote Originally Posted by skirtle View Post
    Whether you choose to use a constructor or initComponent is a ExtJS flame war. My own view is that you should avoid using a constructor wherever possible: use initComponent instead. It is a template method designed to be overridden for subclass initialization and, in my opinion, leads to cleaner code.

    callParent is just a way of calling the overridden method from within the overriding method. Usually this is a good idea but if it doesn't make sense for your class then don't call it. In my example below I haven't passed it any arguments but you can where required (see the docs).

    For overriding event listeners, I think the easiest way to do this is to move the listener to a method of the class, which can then be overridden. Here's an example. Rather than using grids I've used buttons because I think that makes it easier to understand.

    Code:
    Ext.define('ButtonA', {
        extend: 'Ext.button.Button',
    
        width: 60, // Default added to the prototype
    
        initComponent: function() {
            this.callParent(); // calls initComponent on Ext.button.Button
            this.on('click', this.onClickHandler); // note: on the subclasses this is the overriding method
        },
    
        onClickHandler: function() {
            console.log('ButtonA - click');
        }
    });
    
    Ext.define('ButtonB', {
        extend: 'ButtonA',
    
        width: 100, // override the default of 60
    
        onClickHandler: function() {
            console.log('ButtonB - click');
            this.callParent(); // calls the method 'onClickHandler' from ButtonA
        }
    });
    
    Ext.define('ButtonC', {
        extend: 'ButtonA',
    
        onClickHandler: function() {
            console.log('ButtonC - click');
            // Not calling this.callParent(), completely replacing it
        }
    });
    This page of the docs covers some aspects of your question:

    http://docs.sencha.com/ext-js/4-0/#/guide/class_system
    Thanks a lot for your time. I now understand what 'callParent' does.

    Ok so WRT initComponent() and configuration options - I just place default configuration options of Button A in its config object, and those that I need to override in say Button B, I specify it in Button B's config object? As given in the guide to class system, I do not then need to use constructor(config) with initConfig(config)?

  5. #4
    Sencha Premium Member skirtle's Avatar
    Join Date
    Oct 2010
    Location
    UK
    Posts
    3,605
    Answers
    543
    Vote Rating
    326
    skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future skirtle has a brilliant future

      1  

    Default


    One great source of confusion in this area is that 3 different things are referred to as 'the config object'.

    The first one looks like this:

    Code:
    Ext.define('MyClass', {...});
    I tend to refer to the object highlighted in red here as the overrides object but the term 'config object' is commonly used.

    Secondly, there's the object passed to the constructor:

    Code:
    Ext.create('MyClass', {...});
    or:

    Code:
    new MyClass({...})
    I don't know of another name for this object.

    Thirdly, new in ExtJS 4, there's this one:

    Code:
    Ext.define('MyClass', {
        config: {...}
    });
    Personally I think calling this object 'config' was a poor choice as it just leads to even more confusion in an already confusing area.

    To make matters worse, all 3 types of 'config object' actually do quite similar jobs. From here on in I'm going to refer to them as type 1, type 2 and type 3, in the order they appear above.

    So, type 1. Several property names in this object have special meaning (extend, config, alias, ...). Properties that don't have any special meaning will just be copied to the prototype of the class. If you don't know what that means then I suggest you go find out, it's fundamental to how JavaScript classes work.

    Type 3 has a similar objective to type 1 but approaches it slightly differently. It doesn't store the properties on the prototype, instead it creates accessor methods for them on the prototype. At instantiation time it will then call the setter for each property with the appropriate value, be it the default value or another value that has been provided. This is what initConfig() does. However, having to call initConfig() manually is a known weakness with this approach and I hope it gets sorted out in a future release. Unless you really want getters and setters for your properties I'd stay clear of this approach for now.

    Type 2 is a bit different. On the face of it, this type of config object is just an object passed to the constructor. However, in practice one of the first things that almost all Ext constructors do is copy this object onto themselves, i.e. Ext.apply(this, config);. This means they end up as instance properties, hiding the properties found on the prototype. In this way, properties on the prototype act as defaults.

    The call to initComponent() comes after the constructor has copied the type 2 config onto this. You'll notice that initComponent() isn't even passed the config, it is expected just to read the properties off this.

    One thing it is important to understand is that if you use reference types (i.e. object or arrays) as opposed to primitive types (i.e. boolean, strings, numbers) for default values then you need to be careful. Using either type 1 or type 3 above will cause the same default object to be shared between all instances of the class. Sometimes this is fine but often it causes subtle and difficult bugs. In such cases it is common to specify these options in initComponent() instead.

  6. #5
    Sencha User
    Join Date
    Mar 2010
    Posts
    490
    Answers
    12
    Vote Rating
    1
    abcdef is on a distinguished road

      0  

    Default


    Quote Originally Posted by skirtle View Post
    One great source of confusion in this area is that 3 different things are referred to as 'the config object'.

    The first one looks like this:

    Code:
    Ext.define('MyClass', {...});
    I tend to refer to the object highlighted in red here as the overrides object but the term 'config object' is commonly used.

    Secondly, there's the object passed to the constructor:

    Code:
    Ext.create('MyClass', {...});
    or:

    Code:
    new MyClass({...})
    I don't know of another name for this object.

    Thirdly, new in ExtJS 4, there's this one:

    Code:
    Ext.define('MyClass', {
        config: {...}
    });
    Personally I think calling this object 'config' was a poor choice as it just leads to even more confusion in an already confusing area.

    To make matters worse, all 3 types of 'config object' actually do quite similar jobs. From here on in I'm going to refer to them as type 1, type 2 and type 3, in the order they appear above.

    So, type 1. Several property names in this object have special meaning (extend, config, alias, ...). Properties that don't have any special meaning will just be copied to the prototype of the class. If you don't know what that means then I suggest you go find out, it's fundamental to how JavaScript classes work.

    Type 3 has a similar objective to type 1 but approaches it slightly differently. It doesn't store the properties on the prototype, instead it creates accessor methods for them on the prototype. At instantiation time it will then call the setter for each property with the appropriate value, be it the default value or another value that has been provided. This is what initConfig() does. However, having to call initConfig() manually is a known weakness with this approach and I hope it gets sorted out in a future release. Unless you really want getters and setters for your properties I'd stay clear of this approach for now.

    Type 2 is a bit different. On the face of it, this type of config object is just an object passed to the constructor. However, in practice one of the first things that almost all Ext constructors do is copy this object onto themselves, i.e. Ext.apply(this, config);. This means they end up as instance properties, hiding the properties found on the prototype. In this way, properties on the prototype act as defaults.

    The call to initComponent() comes after the constructor has copied the type 2 config onto this. You'll notice that initComponent() isn't even passed the config, it is expected just to read the properties off this.

    One thing it is important to understand is that if you use reference types (i.e. object or arrays) as opposed to primitive types (i.e. boolean, strings, numbers) for default values then you need to be careful. Using either type 1 or type 3 above will cause the same default object to be shared between all instances of the class. Sometimes this is fine but often it causes subtle and difficult bugs. In such cases it is common to specify these options in initComponent() instead.
    Excellent explanation. Exactly what I was looking for. Thank you!

  7. #6
    Ext JS Premium Member
    Join Date
    Mar 2010
    Posts
    23
    Vote Rating
    0
    srknori is on a distinguished road

      0  

    Default


    Awesome Explanation. Thank you.

Thread Participants: 2