| Summary: This tutorial will walk you through steps necessary to extend an Ext 2 class. |
| Author: Jozef Sakalos |
| Published: January 2, 2008 |
| Ext Version: 2.0+ |
Languages: English Korean Chinese
|
Contents |
ObjectiveLet's create an extension of Ext.form.Combobox that will display icons in front of texts. Such combo could be useful, for example, for selection of countries when we'd have the country flag followed by the country name.
Let's give our extension name Ext.ux.IconCombo and we will also register xtype iconcombo.
A note for those who were used to Ext 1.xExtending Ext classes has not been difficult in Ext 1.x but it is even easier in Ext 2.x and the whole matter has not dramatically changed. You can even use the same procedure in Ext 2.x as you have used in Ext 1.x. However, every line of code you don't need to type contributes to code maintainability, readability and reduces number of possible bugs. Therefore, I'll show the easiest, simplest and shortest method here.
Create FilesOur first step is to prepare the files we will need in the process of development:
iconcombo.html<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <link rel="stylesheet" type="text/css" href="../extjs-2.0/resources/css/ext-all.css"> <script type="text/javascript" src="../extjs-2.0/adapter/ext/ext-base.js"></script> <script type="text/javascript" src="../extjs-2.0/ext-all-debug.js"></script> <script type="text/javascript" src="Ext.ux.IconCombo.js"></script> <style type="text/css"> .ux-flag-us { background-image:url(../img/flags/us.png) ! important; } .ux-flag-de { background-image:url(../img/flags/de.png) ! important; } .ux-flag-fr { background-image:url(../img/flags/fr.png) ! important; } .ux-icon-combo-icon { background-repeat: no-repeat; background-position: 0 50%; width: 18px; height: 14px; } /* X-BROWSER-WARNING: this is not being honored by Safari */ .ux-icon-combo-input { padding-left: 25px; } .x-form-field-wrap .ux-icon-combo-icon { top: 3px; left: 5px; } .ux-icon-combo-item { background-repeat: no-repeat ! important; background-position: 3px 50% ! important; padding-left: 24px ! important; } </style> <script type="text/javascript"> Ext.BLANK_IMAGE_URL = '../extjs-2.0/resources/images/default/s.gif'; Ext.onReady(function() { var win = new Ext.Window({ title:'Icon Combo Ext 2.0 Extension Class Example', width:400, height:300, layout:'form', bodyStyle:'padding:10px', labelWidth:70, defaults:{anchor:'100%'}, items:[{ xtype:'iconcombo', fieldLabel:'IconCombo', store: new Ext.data.SimpleStore({ fields: ['countryCode', 'countryName', 'countryFlag'], data: [ ['US', 'United States', 'ux-flag-us'], ['DE', 'Germany', 'ux-flag-de'], ['FR', 'France', 'ux-flag-fr'] ] }), valueField: 'countryCode', displayField: 'countryName', iconClsField: 'countryFlag', triggerAction: 'all', mode: 'local' }] }); win.show(); }); </script> <title>Icon Combo Ext 2.0 Extension Class Example</title> </head> <body> </body> </html>
This file contains, besides the necessary HTML markup, an onReady function that creates a window with form layout with our iconcombo as the only item. Beware, a real form is not created so do not use this example to create real forms. The iconcombo's store also contains inline data for testing purposes.
You will need to change references to Ext JS Library files to point to your location of the Ext installation.
You may also need to adjust paths to flag images depending on where you have installed them. You can download flags from famfamfam.com.
Ext.ux.IconCombo.js// vim: ts=4:sw=4:nu:fdc=2:nospell /** * Ext.ux.IconCombo Extension Class for Ext 2.x Library * * @author Ing. Jozef Sakalos * @version $Id: Ext.ux.IconCombo.js 617 2007-12-20 11:29:56Z jozo $ * * @class Ext.ux.IconCombo * @extends Ext.form.ComboBox */ Ext.ux.IconCombo = Ext.extend(Ext.form.ComboBox, { initComponent:function() { // call parent initComponent Ext.ux.IconCombo.superclass.initComponent.call(this); } // end of function initComponent }); // register xtype Ext.reg('iconcombo', Ext.ux.IconCombo); // end of file
The first step in extending a class is to create an extension that does not add any functionality to the original class. This way we can know that our extension pattern is workable and we can proceed to adding functionalities.
The JavaScript file above does exactly that.
TheoryTo extend an Ext class we do not need to create a constructor function. We just need to assign the return value of Ext.extend call to a variable in our name space. Ext.extend takes the original class and a config object as arguments and returns our extension.
All tasks that were done in a custom constructor function in Ext 1.x are now done in initComponent function that we almost always override. initComponent is called early from the parent constructor function.
However, initComponent of the original class contains useful code that needs to be executed. You can see how we can call initComponent of the parent class in the above code. The pattern of calling parent functions is same for any other functions we may override.
Registering an xtype for your extension is not mandatory but it is very good idea as you can then use your extension just by typing one word of its xtype. It's also the way it is used in this tutorial.
Let's goSo far so good. If you now navigate to iconcombo.html you should see one standard combo with three items and Germany should be selected, right? No icons yet, of course...
Now it's time to work. Add the following lines to Ext.ux.IconCombo.js just before the call to the parent initComponent call:
Ext.apply(this, { tpl: '<tpl for=".">' + '<div class="x-combo-list-item ux-icon-combo-item ' + '{' + this.iconClsField + '}">' + '{' + this.displayField + '}' + '</div></tpl>' });
What we do here is that we override the default combo box item template with our own that makes use of the iconClsField.
Good! Ready for next test, so reload the page. Nice yeah?
Well, we have nice icons when the list is open but we'd like to have a flag also when it's closed, wouldn't we?
Add the following code just after the end of initComponent function:
onRender:function(ct, position) { // call parent onRender Ext.ux.IconCombo.superclass.onRender.call(this, ct, position); // adjust styles this.wrap.applyStyles({position:'relative'}); this.el.addClass('ux-icon-combo-input'); // add div for icon this.icon = Ext.DomHelper.append(this.el.up('div.x-form-field-wrap'), { tag: 'div', style:'position:absolute' }); }, // end of function onRender setIconCls:function() { var rec = this.store.query(this.valueField, this.getValue()).itemAt(0); if(rec) { this.icon.className = 'ux-icon-combo-icon ' + rec.get(this.iconClsField); } }, // end of function setIconCls setValue: function(value) { Ext.ux.IconCombo.superclass.setValue.call(this, value); this.setIconCls(); } // end of function setValue
Our onRender calls the parent method first, and then adjusts styles and adds a div that will hold the icon.
We're also adding a function setIconCls and overriding the setValue function. Of course, we want the original setValue to do its job so we call it first in our scope and then we call our setIconCls function.
The grand finaleNow the final test: reload the page. If you (or me copying/pasting) haven't made a mistake, you have your new Ext.ux.IconCombo extension. Sure, you can further improve it but these are the basics of extending an Ext class.
Complete codeHere is the complete code for the IconCombo extension for your reference:
// vim: ts=4:sw=4:nu:fdc=2:nospell /** * Ext.ux.IconCombo Extension Class for Ext 2.x Library * * @author Ing. Jozef Sakalos * @version $Id: Ext.ux.IconCombo.js 617 2007-12-20 11:29:56Z jozo $ * * @class Ext.ux.IconCombo * @extends Ext.form.ComboBox */ Ext.ux.IconCombo = Ext.extend(Ext.form.ComboBox, { initComponent:function() { Ext.apply(this, { tpl: '<tpl for=".">' + '<div class="x-combo-list-item ux-icon-combo-item ' + '{' + this.iconClsField + '}">' + '{' + this.displayField + '}' + '</div></tpl>' }); // call parent initComponent Ext.ux.IconCombo.superclass.initComponent.call(this); }, // end of function initComponent onRender:function(ct, position) { // call parent onRender Ext.ux.IconCombo.superclass.onRender.call(this, ct, position); // adjust styles this.wrap.applyStyles({position:'relative'}); this.el.addClass('ux-icon-combo-input'); // add div for icon this.icon = Ext.DomHelper.append(this.el.up('div.x-form-field-wrap'), { tag: 'div', style:'position:absolute' }); }, // end of function onRender setIconCls:function() { var rec = this.store.query(this.valueField, this.getValue()).itemAt(0); if(rec) { this.icon.className = 'ux-icon-combo-icon ' + rec.get(this.iconClsField); } }, // end of function setIconCls setValue: function(value) { Ext.ux.IconCombo.superclass.setValue.call(this, value); this.setIconCls(); } // end of function setValue }); // register xtype Ext.reg('iconcombo', Ext.ux.IconCombo); // end of file
A Workable Extension Pattern// vim: ts=4:sw=4:nu:fdc=4:nospell /** * AnExtension * * @author Ing. Jozef Sakáloš * @copyright (c) 2008, by Ing. Jozef Sakáloš * @date 9. October 2008 * @version $Id$ * * @license AnExtension.js is licensed under the terms of the Open Source * LGPL 3.0 license. Commercial use is permitted to the extent that the * code/component(s) do NOT become part of another Open Source or Commercially * licensed development library or toolkit without explicit permission. * * License details: http://www.gnu.org/licenses/lgpl.html */ /*global Ext, AnExtension */ /** * * @class AnExtension * @extends Ext.Panel */ AnExtension = Ext.extend(Ext.Panel, { // soft config (can be changed from outside) border:false // {{{ // uncomment constructor if you need it, e.g. if you need listeners // ,constructor:function(config) { // // constructor pre-processing - configure listeners here // config = config || {}; // config.listeners = config.listeners || {}; // Ext.applyIf(config.listeners, { // expand:{scope:this, fn:function() { // }} // ,collapse:{scope:this, fn:function() { // }} // }); // // // call parent contructor // AnExtension.superclass.constructor.apply(this, arguments); // // // constructor post-processing // // } // eo function constructor // }}} // {{{ ,initComponent:function() { // {{{ // hard coded (cannot be changed from outside) var config = { }; // apply config Ext.apply(this, config); Ext.apply(this.initialConfig, config); // }}} // call parent AnExtension.superclass.initComponent.apply(this, arguments); // after parent code here, e.g. install event handlers } // eo function initComponent // }}} // {{{ ,onRender:function() { // before parent code // call parent AnExtension.superclass.onRender.apply(this, arguments); // after parent code, e.g. install event handlers on rendered components } // eo function onRender // }}} // any other added/overrided methods }); // eo extend // register xtype Ext.reg('anextension', AnExtension); // eof