Ext JS - Learning Center

Tutorial:Extending Ext2 Class

From Learn About the Ext JavaScript Library

Revision as of 18:40, 9 October 2008 by Jsakalos (Talk | contribs)
(diff) ← Older revision | Current revision (diff) | Newer revision → (diff)
Jump to: navigation, search
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: en.png Englishkr.png Koreancn.png Chinese

Contents

Objective

Intended resulting IconCombo

Let'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.x

Extending 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 Files

Our first step is to prepare the files we will need in the process of development:

  • iconcombo.html: HTML markup for application that will use our new extension. To keep this tutorial as simple as possible, this file will contain also javascript code and the necessary stylesheets. You can put both javascript and styles into separate files in a real application.
  • Ext.ux.IconCombo.js: JavaScript file of our extension

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.

Theory

To 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 go

So 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 finale

Now 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 code

Here 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