ExtJS - Extending - Mixins

extending

https://subscription.packtpub.com/book/web_development/9781849516860/1/ch01lvl1sec11/adding-mixins-to-your-class - done reading
https://docs.sencha.com/extjs/6.0.2/modern/Ext.Mixin.html - done reading
https://docs.sencha.com/extjs/6.0.2/classic/Ext.mixin.Observable.html
https://www.sencha.com/blog/using-plugins-and-mixins-in-your-sencha-apps/ - done reading
https://www.extjs-tutorial.com/extjs/mixins - done reading
https://www.tutorialspoint.com/extjs/extjs_class_system.htm
https://stackoverflow.com/questions/50137949/apply-custom-mixin-masking-to-extjs-grid-in-ext-js-6
https://stackoverflow.com/questions/28805491/extjs-5-initconfig-method-observable-mixin

Example mixin:

Ext.define('Foo.bar.Util', {
    extend: 'Ext.Mixin',

    mixinConfig: {
        on: {
            destroy: 'onDestroy'
        }
    }

    onDestroy: function () {
        console.log('M');
    }
 });
//Example by www.extjs-tutorial.com

Ext.define('Person', {
    name: 'Unknown',

    constructor: function(name) {
        if (name) {
            this.name = name;
        }
    },

    getName: function() {
        alert("My name is " + this.name);
    },

    eat: function(foodType) {
        alert("I'm eating " + foodType);
    }

});

Ext.define('Student', {
    schoolName: '',

    constructor: function(schoolName) {
        this.schoolName = schoolName || 'Unknown'
    },

    mixins: {
        eat: 'Person'
    },

    getSchoolName: function() {
        alert("I am a student of " + this.schoolName);
    }

});

var studentObj = new Ext.create('Student', 'XYZ');
studentObj.eat('Sandwich');

//Example by www.extjs-tutorial.com

How can we define a mixin and use it?

Ext.define('HasCamera', {
    takePhoto: function(){
        alert('Say Cheese! .... Click!');
    }
});

Ext.define('Cookbook.Smartphone', {
    mixins: {
        camera: 'HasCamera'
    },

    useCamera: function(){
        this.takePhoto();
    }
});

var smartphone = Ext.create('Cookbook.Smartphone');
smartphone.useCamera(); // alerts 'Say Cheese! .... Click!'

In the above code, we define a mixin named HasCamera.

How is a mixin different from a plugin?

A mixin is a class that also adds functionality to a class. However, it operates a bit differently than a plugin in the following ways.

  1. Mixins can be used to add functionality to any class, while plugins are used with an Ext.Component class.
  2. Mixins are declared only within a class definition using the “mixins” config, whereas a plugin may be declared on a class definition or on a class instance.
  3. Mixins may be designed to operate very generically on any class they’re mixed into (see Ext.mixin.Observable which adds event fire/listen functionality to any class it’s mixed into). That said, it can also be more expressly scoped to a particular category of classes (see Ext.panel.Pinnable which is designed to be mixed into panel classes only).
  4. Methods defined on the mixin are applied to the prototype of the target class.

When you create an instance of a class using a mixin, you call any mixin-defined method directly from the class. The scope of this within the method will be the owning class itself.

// In this example we set up a mixin that can be used on any class
// having a title or text.  The alertMyText method on the mixin is
// set directly on the prototype of the class it's mixed into so that
// it can be called directly from an instance of that class using
// 'this.alertMyText()'

Ext.define('Fiddle.mixin.SampleMixin', {
    extend: 'Ext.Mixin',

    mixinConfig: {
        id: 'samplemixin'
    },

    alertMyText: function () {
        var val = (this.getTitle && this.getTitle()) ? this.getTitle() : this.text;
        Ext.Msg.alert('Title / Text', val);
    }
});

Ext.define('Fiddle.button.MyButton', {
    extend: 'Ext.button.Button',
    xtype: 'mybutton',

    // The mixin's methods will be on all instances of 'mybutton'
    mixins: ['Fiddle.mixin.SampleMixin']
});

Ext.define('Fiddle.button.MyPanel', {
    extend: 'Ext.panel.Panel',
    xtype: 'mypanel',

    // The mixin's methods will be on all instances of 'mypanel'
    mixins: ['Fiddle.mixin.SampleMixin']
});

Ext.application({
    name: 'Fiddle',

    launch: function() {
        Ext.widget('mybutton', {
            renderTo: document.body,
            text: 'Click Me',
            listeners: {
                click: function () {
                    // when clicked call the button's new alertMyText method
                    this.alertMyText();
                }
            }
        });

        Ext.widget('mypanel', {
            renderTo: document.body,
            title: 'Sample Panel Title',
            height: 200,
            width: 300,
            frame: true,
            listeners: {
                afterrender: function () {
                    // when rendered call the panel's new alertMyText method
                    this.alertMyText();
                }
            }
        });
    }
});

It’s possible to define a method on your mixin that has the same name as a method on the owning class. In this case, the mixin method is not set on the target class prototype. Calling the method on the class will still call the original class-defined method.

To call a mixin’s method of the same name, you fetch a reference to the mixin from the owning class (more below) then call the mixin method directly. When calling the mixin’s method directly, the scope is the mixin. So, “this” will be the mixin itself.

// In this example we're giving the mixin a method name that is
// the same as that of the class it's being mixed into.  A mixin's
// methods will never superced the methods on the prototype of the
// class it's being mixed into.

// In this case, calling button.setText() will call the button's setText()
// method.  To call the mixin's setText() method we'll need to fetch a
// reference to it from the button and call it directly off of the mixin.

Ext.define('Fiddle.mixin.SampleMixin', {
    extend: 'Ext.Mixin',

    // Here is an example of how to set a mixin id on the mixin class
    mixinConfig: {
        id: 'samplemixin'
    },

    setText: function (cmp, text) {
        cmp.setText('Mixin set "' + text + '"');
    }
});

Ext.define('Fiddle.button.MyButton', {
    extend: 'Ext.button.Button',
    xtype: 'mybutton',

    mixins: ['Fiddle.mixin.SampleMixin']
});

Ext.application({
    name: 'Fiddle',

    launch: function() {
        Ext.widget('mybutton', {
            renderTo: document.body,
            text: 'Click Me',
            listeners: {
                click: function () {
                    // we use the mixins property along with the mixin id
                    // to call the mixin's setText() method

                    this.mixins.samplemixin.setText(this, 'TEST');

                    // uncomment the below line to use the button's
                    // setText() method instead of that of the mixin

                    //this.setText('Native setText()');
                }
            }
        });
    }
});

As an example, if the mixin had a mixin id of ‘util’, calling the mixin-defined destroy method would look like this:

this.mixins.util.destroy.call(this);

Why should we create our mixin by extending Ext.Mixin?

A mixin may be any class defined by Ext.define(), though we recommend extending Ext.Mixin. The primary benefit of extending Ext.Mixin when defining your mixin class is that it allows you to define “hooks”. Hooks are methods defined on the mixin that are called automatically before or after a corresponding method on the receiving class. For example, you can ensure that your mixin’s afterDestroy() method is called after the class has been destroyed by using an ‘after’ hook:

mixinConfig: {
        after: {
                destroy: 'afterDestroy'
        }
}

For more details on how to use the “before’, “after”, “on”, and “extended” hooks, see the description at the top of the Ext.Mixin API doc.

Starting with a basic class we might have:

 Ext.define('Foo.bar.Base', {
     destroy: function () {
         console.log('B');
         // cleanup
     }
 });

A derived class would look like this:

 Ext.define('Foo.bar.Derived', {
     extend: 'Foo.bar.Base',

     destroy: function () {
         console.log('D');
         // more cleanup

         this.callParent(); // let Foo.bar.Base cleanup as well
     }
 });

To see how using this class help, start with a "normal" mixin class that also needs to cleanup its resources. These mixins must be called explicitly by the classes that use them. For example:

 Ext.define('Foo.bar.Util', {
     destroy: function () {
         console.log('U');
     }
 });

 Ext.define('Foo.bar.Derived', {
     extend: 'Foo.bar.Base',

     mixins: {
         util: 'Foo.bar.Util'
     },

     destroy: function () {
         console.log('D');
         // more cleanup

         this.mixins.util.destroy.call(this);

         this.callParent(); // let Foo.bar.Base cleanup as well
     }
 });

 var obj = new Foo.bar.Derived();

 obj.destroy();
 // logs D then U then B

What is the preferred way to use our mixin?

The preferred way to use your mixins is to use the full class name in an array. The mixins will be processed in the order they are listed in the array.

mixins: [
        'My.utility.MixinClass'  // "util" is used to reference the mixin
]

The object syntax (see option 2 below) offers backwards compatibility, but it’s not recommended because the key name used will not respect the id defined on the mixin class.

How can we fetch a reference of our mixin?

You may want to fetch a reference to a class’s mixin from the class instance. This is done using a reference to the owning class instance’s “mixins” property followed by the id of the mixin (e.g. “this.mixins.util”). We recommend always setting a unique mixin id when defining your mixin class. There are three ways that mixin ids may be set/determined:

1. You can set a mixinId config on the mixin class body if it does not extend Ext.Mixin. For instance:

mixinId: 'util'

or if the mixin does extend Ext.Mixin, you can set an id config in the mixinConfig like this:

mixinConfig: {
        id: 'util'
}

2. Mixins can also be defined as an object and each mixin is keyed with a name/id. For instance:

mixins: {
        util: “My.utility.MixinClass”
}

3. When an id is not set using the above methods, the mixin’s full class name can be used:

Ext.define('My.utility.MixinClass');
var utilMixin = owningClass.mixins['My.utility.MixinClass'];

How can we use mixinConfig to provide "before" or "after" hooks that do not involve the derived class implementation?

Using mixinConfig the mixin class can provide "before" or "after" hooks that do not involve the derived class implementation. This also means the derived class cannot adjust parameters to the hook methods.

Ext.define('Foo.bar.Util', {
     extend: 'Ext.Mixin',

     mixinConfig: {
         after: {
             destroy: 'destroyUtil'
         }
     },

     destroyUtil: function () {
         console.log('U');
     }
 });

 Ext.define('Foo.bar.Class', {
     mixins: {
         util: 'Foo.bar.Util'
     },

     destroy: function () {
         console.log('D');
     }
 });

 var obj = new Foo.bar.Derived();

 obj.destroy();
 // logs D then U

If the destruction should occur in the other order, you can use before:

 Ext.define('Foo.bar.Util', {
     extend: 'Ext.Mixin',

     mixinConfig: {
         before: {
             destroy: 'destroyUtil'
         }
     },

     destroyUtil: function () {
         console.log('U');
     }
 });

 Ext.define('Foo.bar.Class', {
     mixins: {
         util: 'Foo.bar.Util'
     },

     destroy: function () {
         console.log('D');
     }
 });

 var obj = new Foo.bar.Derived();

 obj.destroy();
 // logs U then D

What is the purpose of chaining?

One way for a mixin to provide methods that act more like normal inherited methods is to use an on declaration. These methods will be injected into the callParent chain between the derived and superclass. For example:

 Ext.define('Foo.bar.Util', {
     extend: 'Ext.Mixin',

     mixinConfig: {
         on: {
             destroy: function () {
                 console.log('M');
             }
         }
     }
 });

 Ext.define('Foo.bar.Base', {
     destroy: function () {
         console.log('B');
     }
 });

 Ext.define('Foo.bar.Derived', {
     extend: 'Foo.bar.Base',

     mixins: {
         util: 'Foo.bar.Util'
     },

     destroy: function () {
         this.callParent();
         console.log('D');
     }
 });

 var obj = new Foo.bar.Derived();

 obj.destroy();
 // logs M then B then D

As with before and after, the value of on can be a method name:

 Ext.define('Foo.bar.Util', {
     extend: 'Ext.Mixin',

     mixinConfig: {
         on: {
             destroy: 'onDestroy'
         }
     }

     onDestroy: function () {
         console.log('M');
     }
 });

Because this technique leverages callParent, the derived class controls the time and parameters for the call to all of its bases (be they extend or mixin flavor).

How can we handle the scenarios where our mixin needs to process class extensions of their target class?

Some mixins need to process class extensions of their target class. To do this you can define an extended method like so:

 Ext.define('Foo.bar.Util', {
     extend: 'Ext.Mixin',

     mixinConfig: {
         extended: function (baseClass, derivedClass, classBody) {
             // This function is called whenever a new "derivedClass" is created
             // that extends a "baseClass" in to which this mixin was mixed.
         }
     }
 });

See the derivation section of https://docs.sencha.com/extjs/6.0.2/modern/Ext.Mixin.html

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License