Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<h2>Overview</h2> <p>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.</p> <h2>Subclassing</h2> <p>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.</p> <h2>Overriding</h2> <p>Overriding is another approach that will change the behavior of the stock class:</p> <pre><code>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() { ... } }); </code></pre> <p>This way you can apply the changes that are going to affect <em>all</em> 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.</p> <h2>Instance configuration</h2> <p>That said, the most popular approach so far is to instantiate the stock classes and change the behavior of the <em>instances</em> by setting config options and overriding methods:</p> <pre><code>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 } }); </code></pre> <p>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.</p> <h2>Declarative classes</h2> <p>Another benefit of going the subclassing way is that it allows you to keep your code declarative instead of imperative:</p> <pre><code>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(); } }); </code></pre> <p>The above <em>class declaration</em> is shared by all instances of the <code>MyApp.view.Panel</code>, including both the <code>store</code> config option and the <code>initComponent</code> method override, but when you instantiate this class <em>or its subclasses</em>, <code>initComponent</code> method will operate on whatever configuration is current <em>for the particular class</em>.</p> <p>So when using such class, you will have a choice of either overriding the <code>store</code> config for the <em>instance</em>:</p> <pre><code>var panel = new MyApp.view.Panel({ store: 'BarStore' }); var items = panel.getItems(); // Return all items from BarStore </code></pre> <p>Or just falling back to the default configuration provided by the class:</p> <pre><code>var panel = new MyApp.view.Panel(); var items = panel.getItems(); // Return all items from FooStore </code></pre> <p>You can also subclass it, overriding part of the config or behavior, but not everything:</p> <pre><code>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 </code></pre> <h2>Declarative vs Imperative</h2> <p>Compare that to the imperative approach in which you will have to specify the full configuration for the stock class instance every time:</p> <pre><code>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); } }); </code></pre> <p>The biggest disadvantage of the code above is that everything happens to the <em>class instance</em> when it is already <em>halfway initialized</em> (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.</p> <h2>Subclassing pitfalls</h2> <p>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:</p> <pre><code>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'; ... } }); </code></pre> <p>Compare your code with the declarative example above. The difference is that in your class, configuration happens <em>at instantiation time</em> while in the example it happens <em>at the class declaration time</em>.</p> <p>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 <code>initComponent</code> method:</p> <pre><code>Ext.define('MyApp.view.AnotherMainTree', { extend: 'MyApp.view.MainTree', initComponent: function() { this.store = 'AnotherTreeNodes'; ... } }); </code></pre> <p>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.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload