Steps to overriding Sencha ExtJS standard component functionality (Ext.tree.Panel & Ext.data.TreeStore as two examples)

后端 未结 2 1612
孤城傲影
孤城傲影 2020-12-24 03:44

Suppose I am extending a standard Sencha ExtJS 4 widget/component, and I found a bunch of things that don\'t work the way I want them to, or perhaps they are just broken and

相关标签:
2条回答
  • 2020-12-24 04:21

    Overview

    There are three ways of augmenting the stock classes behavior in Ext JS 4.x without changing the framework source: subclassing, class overriding, and instance configuration.

    Subclassing

    Subclassing is what you do when you need to create a custom component tailored for your application. This in fact is what you are doing in the code above: you're taking a stock component, changing its behavior to suit your needs, and using it as a new component. The important point is that by subclassing you are not changing the stock component's behavior so you can use both custom and stock components.

    Overriding

    Overriding is another approach that will change the behavior of the stock class:

    Ext.define('MyApp.tree.TreePanel', {
        override: 'Ext.tree.Panel',
    
        // Stock fooMethod has a bug, so we are
        // replacing it with a fixed method
        fooMethod: function() {
            ...
        }
    });
    

    This way you can apply the changes that are going to affect all instances of the TreePanel, both stock and custom. This approach is mostly used for patches and fixes; it can be used for adding new features to the stock components but you will find it harder to maintain down the road.

    Instance configuration

    That said, the most popular approach so far is to instantiate the stock classes and change the behavior of the instances by setting config options and overriding methods:

    var tree = new Ext.tree.Panel({
        fooConfig: 'bar', // override the default config option
    
        fooMethod: function() {
            // Nothing wrong with this method in the stock class,
            // we just want it to behave differently
        }
    });
    

    This way of doing things was popularized in earlier Ext JS versions and is still heavily used. I do not recommend this approach for new 4.x applications, because it does not allow you to modularize your code properly and is harder to maintain in the long run.

    Declarative classes

    Another benefit of going the subclassing way is that it allows you to keep your code declarative instead of imperative:

    Ext.define('MyApp.view.Panel', {
        extend: 'Ext.panel.Panel',
    
        store: 'FooStore',
    
        // Note the difference with your code: 
        // the actual function reference
        // will be resolved from the *object instance*
        // at the object instantiation time
        // and may as well be overridden in subclasses
        // without changing it here
        listeners: {
            itemclick: 'onItemClick'
        },
    
        initComponent: function() {
            var store = this.store;
    
            if (!Ext.isObject(store) || !store.isStore) {
                // The store is not initialized yet
                this.store = Ext.StoreManager.lookup(store);
            }
    
            // You don't need to address the superclass directly here.
            // In the class method scope, callParent will resolve
            // the superclass method and call it.
            this.callParent();
        },
    
        // Return all items in the store
        getItems: function() {
            return this.store.getRange();
        },
    
        onItemClick: function() {
            this.doSomething();
        }
    });
    

    The above class declaration is shared by all instances of the MyApp.view.Panel, including both the store config option and the initComponent method override, but when you instantiate this class or its subclasses, initComponent method will operate on whatever configuration is current for the particular class.

    So when using such class, you will have a choice of either overriding the store config for the instance:

    var panel = new MyApp.view.Panel({
        store: 'BarStore'
    });
    
    var items = panel.getItems(); // Return all items from BarStore
    

    Or just falling back to the default configuration provided by the class:

    var panel = new MyApp.view.Panel();
    
    var items = panel.getItems(); // Return all items from FooStore
    

    You can also subclass it, overriding part of the config or behavior, but not everything:

    Ext.define('MyApp.view.NewPanel', {
        extend: 'MyApp.view.Panel',
    
        // For this Panel, we only want to return first 10 items
        getItems: function() {
            return this.store.getRange(0, 9);
        },
    
        onItemClick: function() {
            this.doSomethingElse();
        }
    });
    
    var panel = new MyApp.view.NewPanel();
    
    var items = panel.getItems(); // Return first 10 items from FooStore
    

    Declarative vs Imperative

    Compare that to the imperative approach in which you will have to specify the full configuration for the stock class instance every time:

    var panelFoo = new Ext.panel.Panel({
        initComponent: function() {
            this.store = Ext.StoreManager.lookup('FooStore');
    
            // Note that we can't use this.callParent() here
            this.superclass.initComponent.call(this);
        }
    });
    
    var panelBar = new Ext.panel.Panel({
        initComponent: function() {
            this.store = Ext.StoreManager.lookup('BarStore');
            this.superclass.initComponent.call(this);
        }
    });
    

    The biggest disadvantage of the code above is that everything happens to the class instance when it is already halfway initialized (initComponent is called by the constructor). You can't generalize this approach, and you can't easily make instances share most of the behavior but differ in details -- you will have to repeat the code for every instance.

    Subclassing pitfalls

    This brings us to the most common mistake people make with subclassing as well: going just half way. If you take a look at your code above, you may notice this exact mistake:

    Ext.define('MyApp.view.MainTree', {
        extend: 'Ext.tree.TreePanel', // You're using subclassing
    
        initComponent: function() {
    
            // But here you are assigning the config options
            // to the the *class instance* that has been
            // instantiated and half way initialized already
            this.store = 'TreeNodes';
            ...
        }
    });
    

    Compare your code with the declarative example above. The difference is that in your class, configuration happens at instantiation time while in the example it happens at the class declaration time.

    Suppose that down the road you will need to reuse your MainTree class elsewhere in the application, but now with a different store, or behavior. With the code above you can't do that easily and you will have to create another class and override the initComponent method:

    Ext.define('MyApp.view.AnotherMainTree', {
        extend: 'MyApp.view.MainTree',
    
        initComponent: function() {
            this.store = 'AnotherTreeNodes';
            ...
        }
    });
    

    Compare that to the instance config override above. Not only the declarative approach is easier to write and maintain, but it is infinitely more testable as well.

    0 讨论(0)
  • 2020-12-24 04:33

    Override the functions which you think are not working correctly or the way you want it to work

    Example

    Ext.define('MyApp.store.TreeGridStore', {
    extend: 'Ext.data.TreeStore',
    getTotalCount : function() {
        if(!this.proxy.reader.rawData) return 0;
        this.totalCount = this.proxy.reader.rawData.recordCount;
        return this.totalCount;
    },  
    ....
    

    In the above example I want the getTotalCount function to calculate the count differently, so I extended the treestore and overrided the method.

    0 讨论(0)
提交回复
热议问题