8

假设我正在扩展一个标准的 Sencha ExtJS 4 小部件/组件,我发现一堆东西不能按我想要的方式工作,或者它们只是坏了,Sencha 还没有解决问题组件呢。我将使用 Sencha ExtJS Ext.tree.Panel 和 Ext.tree.Store 作为两个示例组件。覆盖构造函数、配置、属性、方法和事件的最基本步骤是什么,以便我可以在不修改我当前使用的核心 ExtJS 4 框架 JS 文件的情况下找到并修复该组件的问题?

我意识到有时框架中的功能太多了,以至于人们可能会忽略某处的配置,而没有意识到他们可以通过标准实现来解决问题。这可以通过更多的框架经验来纠正。撇开这些不谈,这些最基本的步骤是什么?

假设我们从这两个实现开始,从最基本的开始。

仅供参考:我在使用 Ext.Direct 服务器端堆栈的情况下无需太多努力就可以运行这两个组件的核心功能,并且我可以用 IE 解释 Sencha ExtJS Ext.tree.Panel 组件的所有跨浏览器兼容问题, Mozilla Firefox 和 Google Chrome,但我可能会花太多时间问其他问题。我并不是说 IE 首先是刻板印象,因为所有这些浏览器都存在与 Ext.tree.Panel 组件有关的问题。我宁愿在这里学习如何钓鱼,这样我就可以自己钓到鱼了。一旦我更好地理解了这些与树相关的类,我会提出更具体的问题。

http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.data.TreeStore

自定义 Ext.data.TreeStore 实现:

Ext.define('MyApp.store.TreeNodes', {
    extend: 'Ext.data.TreeStore',
    xtype: 'store-tree-nodes',
    model : 'MyApp.model.TreeNode',
    proxy: {
        type: 'direct',
        directFn: Tree_Node_CRUD.read,
        reader: {
            root: 'data'
        }
    },
    nodeParam: 'node',
    parentField: 'parentId',
    root: {
        text: 'root',
        id: '0',
        expanded: true
    },
    autoLoad: false,
    single: true,
    listeners: {
        beforeload: function(store, operation, options) {
        },
        append: function( thisNode, newChildNode, index, eOpts ) {
        }    
    }
});

http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.tree.Panel

自定义 Ext.tree.Panel 实现:

Ext.define('MyApp.view.MainTree', {
    extend: 'Ext.tree.TreePanel',
    xtype: 'view-main-tree',
    requires: [
        'MyApp.store.TreeNodes'
    ],
    initComponent: function() 
    {        
        this.store = 'TreeNodes';
        this.superclass.initComponent.call(this);
    },
    animate: false,
    title: 'Tree',
    rootVisible: true,
    collapsible: true,   
    dockedItems: [{
        xtype: 'toolbar',
        items: [{
            text: 'Open Node'
        }, {
            text: 'Create Node'
        }, {
            text: 'Delete Node'
        }, {
            text: 'Expand All'
        }, {
            text: 'Collapse All'
        }]
    }], 
    listeners: {
        afterrender: function() {
        },
        itemclick: function(view, node, item, index, e) {
        },
        afteritemexpand: function() {  //node, index, item, eOpts) {
        },
        afteritemcollapse: function() { //node, index, item, eOpts) {
        }
    }
});
4

2 回答 2

34

概述

在 Ext JS 4.x 中,在不更改框架源代码的情况下,可以通过三种方式来增强常用类的行为:子类化、类覆盖和实例配置。

子类化

当您需要为您的应用程序创建自定义组件时,您需要进行子类化。这实际上就是您在上面的代码中所做的事情:您正在获取一个库存组件,更改其行为以满足您的需求,并将其用作新组件。重要的一点是,通过子类化您不会更改库存组件的行为,因此您可以同时使用自定义组件和库存组件。

覆盖

覆盖是另一种改变股票类行为的方法:

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() {
        ...
    }
});

这样,您可以应用将影响TreePanel 的所有实例的更改,包括库存和自定义。这种方法主要用于补丁和修复;它可用于向库存组件添加新功能,但您会发现以后很难维护。

实例配置

也就是说,到目前为止,最流行的方法是实例化 stock 类并通过设置配置选项和覆盖方法来更改实例的行为:

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
    }
});

这种做事方式在早期的 Ext JS 版本中很流行,并且仍然被大量使用。我不建议将这种方法用于新的 4.x 应用程序,因为它不允许您正确模块化代码并且从长远来看更难以维护。

声明类

采用子类化方式的另一个好处是它允许您保持代码声明性而不是命令性:

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();
    }
});

上面的类声明由 的所有实例共享MyApp.view.Panel,包括store配置选项和initComponent方法覆盖,但是当您实例化此类或其子类时,方法将对特定类initComponent的当前配置进行操作。

因此,当使用此类时,您可以选择覆盖实例store的配置:

var panel = new MyApp.view.Panel({
    store: 'BarStore'
});

var items = panel.getItems(); // Return all items from BarStore

或者只是回退到类提供的默认配置:

var panel = new MyApp.view.Panel();

var items = panel.getItems(); // Return all items from FooStore

您也可以将其子类化,覆盖部分配置或行为,但不是所有内容:

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

声明式与命令式

将其与每次都必须为 stock 类实例指定完整配置的命令式方法进行比较:

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);
    }
});

上面代码的最大缺点是,当类实例已经初始化到一半时(构造函数调用了 initComponent) ,所有的事情都发生在类实例上。你不能概括这种方法,你不能轻易地让实例共享大部分行为但在细节上有所不同——你将不得不为每个实例重复代码。

子类化陷阱

这给我们带来了人们在子类化中最常犯的错误:只做了一半。如果您查看上面的代码,您可能会注意到这个确切的错误:

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';
        ...
    }
});

将您的代码与上面的声明性示例进行比较。不同之处在于,在您的类中,配置发生在实例化时,而在示例中,配置发生在类声明时

假设您需要在应用程序的其他地方重用 MainTree 类,但现在使用不同的存储或行为。使用上面的代码,您不能轻易做到这一点,您将不得不创建另一个类并覆盖该initComponent方法:

Ext.define('MyApp.view.AnotherMainTree', {
    extend: 'MyApp.view.MainTree',

    initComponent: function() {
        this.store = 'AnotherTreeNodes';
        ...
    }
});

将其与上面的实例配置覆盖进行比较。声明式方法不仅更易于编写和维护,而且还具有无限的可测试性。

于 2013-08-16T20:53:15.360 回答
0

覆盖您认为无法正常工作的功能或您希望它工作的方式

例子

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;
},  
....

在上面的示例中,我希望 getTotalCount 函数以不同的方式计算计数,因此我扩展了树存储并覆盖了该方法。

于 2013-08-15T17:35:14.460 回答