What is callParent and callSuper?

One of the best things about Ext JS is it's class system. Even though JavaScript doesn't have a class system with inheritance, Ext JS utilizes different JavaScript methods to create inheritance. This enables classes to extend one another to give power, flexibility and code reuse in a simple api otherwise not present in native JavaScript.

Introduction

First, let's setup a couple classes Bar which extends Foo:

Ext.define('Foo', {
    someProp : 3,

    someMethod : function() {
        console.log('Foo', 'someMethod');
    }
});

Ext.define('Bar', {
    extend : 'Foo',

    anotherProp : 'test',
    someProp    : 4
});

If we look at this code, Foo is defined with someProp and someMethod members on that class. Bar is defined and extends Foo, adds an anotherProp member and overwrites someProp to have a different value than was defined on Foo. In this case, Bar will inherit the someMethod and therefore instances of Bar can execute the someMethod method. Let's look at instantiation and execution:

var foo = new Foo();

foo.someProp === 3;
foo.someMethod(); //logs `Foo someMethod` to the console

var bar = new Bar();

bar.anotherProp === 'test';
bar.someProp === 4;
bar.someMethod(); //logs 'Foo someMethod' to the console

Using callParent

We have a need to add logic to Bar's someMethod method but we still need to execute the someMethod that was inherited from Foo. If we simply add someMethod to Bar like this:

Ext.define('Bar', {
    extend : 'Foo',

    anotherProp : 'test',
    someProp    : 4,

    someMethod : function() {
        console.log('Bar', 'someMethod');
    }
});

When we execute bar.someMethod(); it will now only log out Bar someMethod and will not execute the someMethod method on the Foo class because we overrode the method. We can still call Foo's someMethod by using the callParent method:

Ext.define('Bar', {
    extend : 'Foo',

    anotherProp : 'test',
    someProp    : 4,

    someMethod : function() {
        console.log('Bar', 'someMethod');

        this.callParent();
    }
});

Now when bar.someMethod(); is executed, we will get Bar someMethod and Foo someMethod both logged to the console because this.callParent(); calls the superclass' (Foo) someMethod method.

Function arguments

Thus far, the someMethod method did not accept any arguments and therefore we didn't need to deal with them and how to pass the arguments to the superclass' method. Let's modify Foo to handle a couple arguments:

Ext.define('Foo', {
    someProp : 3,

    someMethod : function(callback, scope) {
        console.log('Foo', 'someMethod');

        if (callback) {
            callback.call(scope || this);
        }
    }
});

We changed the signature of the someMethod method to accept callback and scope arguments which are optional but we need to now handle passing the arguments in our someMethod method in the Bar class in order to keep the behavior of the Foo someMethod method.

We can do this two ways. The preferred way is to know what the arguments are and pass an array of the arguments to the callParent method call. This may take some educating yourself on what arguments are valid for that method by reading the source for the superclass and it's ancestors. The other way is to use the special arguments keyword which holds the arguments for the current function block. Here are the two methods in action:

Ext.define('Bar', {
    extend : 'Foo',

    anotherProp : 'test',
    someProp    : 4,

    someMethod : function(callback, scope) {
        console.log('Bar', 'someMethod');

        this.callParent([callback, scope]);
    }
});

or with the arguments keyword:

Ext.define('Bar', {
    extend : 'Foo',

    anotherProp : 'test',
    someProp    : 4,

    someMethod : function(callback, scope) {
        console.log('Bar', 'someMethod');

        this.callParent(arguments);
    }
});

I personally always try to know what arguments are possible and use the first method. Documentation can really help here or simply reading the source from that class' ancestors. If you know a method will never have any arguments, initComponent in Ext.Component for example, then you can simply execute callParent without passing any parameters: this.callParent();

Using callSuper

Like callParent, callSuper allows you to call an ancestor method when overwriting one. There are two differences:

  • callSuper is only usable within an override
  • While callParent calls the inherited method, callSuper will skip the directly inherited method and call the next level's method.

Let's create an override to Bar to explore how to use the callSuper method:

Ext.define('Override.Bar', {
    override : 'Bar',

    someMethod : function(callback, scope) {
        console.log('Override.Bar', 'someMethod');
    }
});

In this example, we are globally overwriting the someMethod method on Bar. When we execute bar.someMethod(); we will only see the Override.Bar someMethod. Our need to is not call the someMethod method on the Bar class but skip it and call the method on the Foo class. We can do this with callSuper:

Ext.define('Override.Bar', {
    override : 'Bar',

    someMethod : function(callback, scope) {
        console.log('Override.Bar', 'someMethod');

        this.callSuper([callback, scope]);
    }
});

Now when bar.someMethod(); is executed, we will get both Override.Bar someMethod and Foo someMethod logged to the console. Also note that passing arguments works exactly like it did with callParent.

Handling returning something

What if the someMethod in the Foo class returned something? How do we handle this with callParent and callSuper? Well, it's actually quite simple. First, let's look at the changes to Foo:

Ext.define('Foo', {
    someProp : 3,

    someMethod : function(callback, scope) {
        console.log('Foo', 'someMethod');

        if (callback) {
            callback.call(scope || this);
        }

        return this;
    }
});

Now someMethod is chainable as it returns this which is likely the Foo class. Now we need to handle this in the Bar class where we use callParent:

Ext.define('Bar', {
    extend : 'Foo',

    anotherProp : 'test',
    someProp    : 4,

    someMethod : function(callback, scope) {
        console.log('Bar', 'someMethod');

        return this.callParent([callback, scope]);
    }
});

All I did here was add return, callParent is just a placeholder for calling the superclass' method and therefore will return whatever that method returns. The same goes for when callSuper is used:

Ext.define('Override.Bar', {
    override : 'Bar',

    someMethod : function(callback, scope) {
        console.log('Override.Bar', 'someMethod');

        return this.callSuper([callback, scope]);
    }
});

What if you need to do something with what is returned from callParent or callSuper? Just cache the execution to a local variable and return that variable. Once again this is exactly the same between callParent and callSuper so I will only show one:

Ext.define('Override.Bar', {
    override : 'Bar',

    someMethod : function(callback, scope) {
        console.log('Override.Bar', 'someMethod');

        var ret = this.callSuper([callback, scope]);

        console.log('Override.Bar', ret.someProp);

        return ret;
    }
});

With all the classes and the override in place, when bar.someMethod(); is executed it will return this (which will be the instance of Bar) but will now log these in this order: Override.Bar someMethod, Foo someMethod and Override.Bar 4. Couple things to note here, since we used callSuper in the override, the someMethod defined on the Bar class was not executed as it was skipped due to using callSuper and the scope is the instance that was created which is that of the Bar class (since we used var bar = new Bar(); in the beginning) which is why ret.someProp returns 4 not 3.

Mitchell Simoens

Mitchell has held many positions within Sencha currently maintaining the support portal and Sencha Fiddle. Anything expressed on this website are Mitchell's alone and do not represent his employer.

comments powered by Disqus