Writing a Big Application in Ext (Part 2)

2. February 2009 – 14:12

Important

If you have not already done so, study Writing a Big Application in Ext (Part 1) before you read this article. It would be very hard, if not impossible, to understand concepts explained here before you fully understand the first part.

Introduction

It has been almost one year since I have published the first part of this article. I have been successfully using the concept of pre-configured classes personally to write a really big application (~150 javascript files, ~60,000 lines of code, plus server-side and css). The application is fully modularized, each class in separate file and it has proven that it can be easily developed, managed and debugged.

Unfortunately, the same has not been always true for other users, they were hitting various problems and Ext Support Team have had to handle may questions and help requests. These accumulated to the degree where the concept has been called “absolute abomination” (read absolutely hated) and it was stated that “it causes problems”.

Note: As “fast cars” do not cause accidents but “slow drivers driving fast cars” do, the concept itself cannot be a cause of the problems but its application can.

In any case, there must be some illogic if I can use the concept but others cannot.

Thus, I have looked deeper in it and I have isolated some pitfalls the users can run into on the course of development. I will also write on “dos” and “don’ts” and on when to use a pre-configured class and when not. I will not compare this concept to another application design concepts (factory functions, in-line configuration, on-demand load, or other) because 1) I do not use them and 2) I do not want to turn this article into a Linux versus Windows discussion.

It is fully up to you which application design concept you choose. However, if you do choose this one then follow the rules I’m going to lay down.

Most Common Sources of Troubles

  • Dull Copy&Paste
  • Extending a non-Ext.Component class
  • Not calling parent methods
  • initialConfig
  • listeners

Dull Copy&Paste

Do you know such people? They post on a forum:

I need to drag from tree to qrid, gimme plz complete codez

And if somebody altruistic writes a fragment of “codez” for him in a sheer attempt to help the response is going to be:

Your codez don’t work. Help me plz my manager wants it working

Do you see what happened? A dull “developer” ordered a code on the forum, he’s got some, copied&pasted it to his application without a clue what the code does, maybe hasn’t even changed url that still points to your server and the result is: it doesn’t work.

Well, this is an extreme (but not so rare as you would think), nevertheless, copying&pasting without understanding of what the copied&pasted code does can lead only to frustrations.

I am not against Copy&Paste in general, it can save a lot of time and I also occasionally do it, but I am against not-understanding or mis-understanding not only of coding but also of life.

The Rule: Do Copy&Paste but always with full understanding of what the code does.

Extending a non-Ext.Component class

If an Ext class does not inherit from Ext.Component the initComponent is never called so the code you have written there is never executed. This is fragment from Ext.Component constructor:

1
2
3
4
5
6
7
8
9
    this.getId();
    Ext.ComponentMgr.register(this);
    Ext.Component.superclass.constructor.call(this);
 
    if(this.baseAction){
        this.baseAction.addComponent(this);
    }
 
    this.initComponent();

Ext classes that do not inherit from Ext.Component do not have this.initComponent(); line in their constructors.

The Rule: Always check if the Ext class you are going to pre-configure inherits from Ext.Component. You have to use an another approach if it does not.

Not calling parent methods

It happens very often that you do not only add some methods in your extended class but that you modify existing ones. initComponent being the first example. onRender, afterLayout are other (but not only) frequently overriden methods.

These methods are already implemented in the class you are extending and its parents so if you forget the line:

// in initComponent override
YourExtension.superclass.initComponent.apply(this, arguments);
 
// or in onRender override
YourExtension.superclass.onRender.apply(this, arguments);
 
// or in afterLayout override
YourExtension.superclass.afterLayout.apply(this, arguments);

your class will not work.

The Rule: Never forget to call the parent method, unless you exactly know what you are doing.

initialConfig

The constructor of Ext.Component saves the config passed to it as initialConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    /**
     * This Component's initial configuration specification. Read-only.
     * @type Object
     * @property initialConfig
     */
    this.initialConfig = config;
 
    Ext.apply(this, config);
    this.addEvents(/* abbreviated for clarity */);
    this.getId();
    Ext.ComponentMgr.register(this);
    Ext.Component.superclass.constructor.call(this);
 
    if(this.baseAction){
        this.baseAction.addComponent(this);
    }
 
    this.initComponent();

You see what happens? The constructor saves initialConfig before initComponent is executed. Thus, all configuration you write in initComponent is not saved. I have overlooked this in first versions of my templates and examples mainly because there is only a couple of classes that refer to initialConfig and even in these classes the absence of properly saved initialConfig causes problems very rarely. These Ext classes refer to initialConfig:

  • AnchorLayout
  • BorderLayout
  • Action
  • GridPanel
  • Tip
  • Combo
  • Form

Now, I have updated all my examples, extensions, templates and main site to include this “magic” pattern:

1
2
3
4
5
6
7
8
9
10
// create config
var config = {
    // put your config here
}; // eo config object
 
// apply config
Ext.apply(this, Ext.apply(this.initialConfig, config));
 
// call parent
YourExtension.superclass.initComponent.apply(this, arguments);

The Rule: Ensure that your extension saves initialConfig.

listeners

If you try to install event handlers by setting property listeners in your config they will not work. Why? The answer lies again in the order of actions in Ext.Component constructor:

1
2
3
4
5
6
7
    Ext.Component.superclass.constructor.call(this);
 
    if(this.baseAction){
        this.baseAction.addComponent(this);
    }
 
    this.initComponent();

As you can see, the constructor calls its parent, that is Ext.util.Observable before initComponent. Ext.util.Observable constructor executes:

1
2
3
4
5
6
Ext.util.Observable = function(){
    if(this.listeners){
        this.on(this.listeners);
        delete this.listeners;
    }
};

Any listeners set in initComponent are thus ignored.

There is an easy workaround. Put constructor method in your extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
 constructor:function(config) {
    // parent constructor call pre-processing - configure listeners here
    config = config || {};
    config.listeners = config.listeners || {};
    Ext.applyIf(config.listeners, {
        // add listeners config here
    });
 
    // call parent constructor
    AnExtension.superclass.constructor.call(this, config);
 
    // parent constructor call post-processing
 
} // eo function constructor

and define your listeners therein.

The Rule: Define listeners in constructor method.

Conclusion

If you decide to use my way of writing a big application then follow these rules:

  1. Do Copy&Paste but always with full understanding of what the code does.
  2. Always check if the Ext class you are going to pre-configure inherits from Ext.Component. You have to use an another approach if it does not.
  3. Never forget to call the parent method, unless you exactly know what you are doing.
  4. Ensure that your extension saves initialConfig.
  5. Define listeners in constructor method.

Happy extending!

Do not forget to read Part 1 and Part 3 of this article.

New example

15. January 2009 – 17:47

I have just uploaded new example Keeping State of Active Tab and Window that shows how to keep active tab of TabPanel and Window position and size between sessions.

Enjoy!

New example

30. May 2008 – 01:54

I have just uploaded new example Tabs in Form that shows how to correctly configure a form spreading multiple tabs.

Enjoy!

Examples Amended

26. May 2008 – 20:03

I am proud to announce that I have amended examples at http://examples.extjs.eu to show brief explanation of each example.

Enjoy!

Saki

New Example

21. May 2008 – 13:10

I am glad to announce new example:

Displaying Form Submit Errors

Enjoy!

Which events are fired?

18. May 2008 – 11:13

We need to know all events that are fired by a component and in which order sometimes. It is easy to find out.

Let’s say that the component in question has id my-comp. Load the page with the component, open Firebug, Console tab, and type:

Ext.util.Observable.capture(Ext.getCmp('my-comp'), console.info)

Now do some actions with the component and watch the Firebug output.

Events Explained

18. May 2008 – 10:44

Historical Background

Most likely many of you who will read this article do not remember times of Fortran language and computers that were fed with tons of punch cards to have some some job done.

The main purpose of computers at that time was to compute something really; that is not true anymore because computers are used for getting computational result very rarely at schools or scientific institutions nowadays.

How did it work at that time? If you wanted a computer to run a program you went to a shelf with your punched cards, you found the drawer with the stack of them, fed them through card reader and the computer started your program.

First task was to ask for user inputs and once you filled them in the program computed the result, printed it and ended. Easy, straightforward, “single thread” job.

Events Introduced

With the invent of GUI and Mouse this concept of load-run-end just couldn’t work anymore as we needed some infinite loop that would wait for user actions (mouse movement and clicks) and that would process them to execute the required actions.

I has became clear very soon that putting the code that processes these action directly in that loop is the route to nowhere so the Event driven programming was born.

Event Definition

Event is a message, a function call, generated by one (part of) program, the event source, that notifies another (part of) program, the event listener, that something happened. Events are generated as responses to user actions or to state changes of the event source.

The event source is independent of event listeners and it generates events also if nobody listens or even if there is no listener defined. The viewpoint of our infinite loop would be: “I’m informing everybody that user moved the mouse to position [x,y] and I do not care who listens, if anybody.”

The viewpoint of the listener would be: “Let me know when user moves the mouse, I need to do something with it.”

Events in Ext

There are two main “sorts” of events in Ext: DOM events and JavaScript, or software, events.

DOM Events

Browsers that display (X)HTML pages already have our “infinite loop” that watches user actions and fires events if these actions are occurring on DOM elements. Before Ext we were used to install event listeners on DOM elements this way:

<div id="mydiv" onclick="alert('You clicked me')">Click me!</div>

Ext.Element wraps DOM elements together with their events so now we install the same event handlers this way:

Ext.get('mydiv').on('click', function() {alert('You clicked me');});

It can be said that DOM events are “passed-through” from DOM through Ext.Element to listeners.

JavaScript Events

Now, DOM elements are not only possible event sources; it is quite easy to implement event source logic and event listener installation to any JavaScript object. But, what could it be good for?

Imagine that you have a complex component such as grid. If you had only DOM events, the handling of user actions such as column move would be extremely difficult. You would need to listen to DOM elements, process mouse clicks, moves, calculate from where to where the column has been moved, etc. If would be much easier if grid component would do all this dirty work for you and, after everything has be done, just informed you: “User moved column 3 to position 1.”

That is exactly what grid does: it fires JavaScript events that inform potential listeners what has happened to it. The same is true for another Ext components. Form validation events, Panel resize events, Tree expand/collapse events can serve as examples, to name a few.

How do I listen to events?

If you have an object of Ext class, for example Panel, and you need to do some action when panel resizes you would install a listener to implement your action:

// create panel
var myPanel = new Ext.Panel({...});
 
// install resize event listener
myPanel.on('resize', function(panel, w, h) {
    alert('Panel resized to ' + w + 'x' + h);
});

From this point on, whenever the panel myPanel is resized your function is called so you can do your actions.

How do I create event source?

Events related functionality is implemented in Ext.util.Observable class so if you want your extension to be an event source just extend Observable. Also, if you extend a class that is already descendant of Observable (Panel, Grid, Form, Tree, etc), your extension is automatically the event source.

Events fired by your extension are events fired by parent class(es).

Custom Events

It happens very often that you need add new events, for example you create Employee class and Organization Chart class and you implement drag&drop assignment/dismissal of employee to/from a position. I would come handy to fire event assigned and dismissed, wouldn’t it?

We could listen to these events and the listeners could send e-mails to the employee informing him that he has been assigned to a position or dismissed from it.

We do it this way:

OrgChart = Ext.extend(Ext.Panel, {
    initComponent:function() {
        // call parent init component
        OrgChart.superclass.initComponent.apply(this, arguments);
 
        // add custom events
        this.addEvents('assigned', 'dismissed');
    }
 
    ,assign:function(employee, position) {
        // do whatever is necessary to assign the employee to position
 
        // fire assigned event
        this.fireEvent('assigned', this, employee, position);
    }
 
    ,dismiss:function(empoyee, position) {
        // do whatever is necessary to dismiss employee from position
 
        // fire dismissed event
        this.fireEvent('dismissed', this, employee, position);
    }
});

In the initComponent function we inform Observable class that we are going to fire our new events so it can do all necessary setup.

Note: We do not extend Observable directly here but Panel, what we extend, does. Panel’s inheritance chain is: Observable -> Component -> BoxComponent -> Container -> Panel.

And in assign and dismiss functions we fire our events after all assign/dismiss job has been done with signature (arguments) of our choice.

When we fireEvent, Observable looks if there are some listeners to this event and calls all listeners with arguments we supplied in fireEvent call. If there is no listener it just does nothing.

Summary

  • event is a message sent (fired) by an event source to inform listeners that something happened
  • event source is an object that can fire events
  • event listener is a function that is called when event source fires an event
  • to listen to events we use on function to install an event listener
  • to create an event source we extend Observable class, addEvents and fireEvent

Enjoy firing your events and listening to them!

New Examples

14. May 2008 – 01:29

There are two new examples since last announcement:

Enjoy!

Saki

Displaying 1:n Data in Grid

11. May 2008 – 19:13

The Problem

Imagine that you have a one-to-many relationship in your database, for example, you have table person in which you keep personal data (first, middle, last names, etc.) and you have table phone where you keep phone numbers (phone type, phone number).

It is quite common to have person:phones, company:phones, order:items, invoice:items, etc relationships, isn’t it?

Now, it is quite easy to create a grid that displays list of persons but what about their phones? They are in the different table. Yes, we could create two stores: one for persons grid and another, hidden, for phones, load them from server and somehow filter phones depending on persons.

Nevertheless, I was looking for a simpler solution as I wanted one client server round trip and I wanted to display “many” data in QuickTip. And I found one…

Solution – Server Side

MySql, that I use as my main database backend, has function group_concat since version 4.1 and SQLite has it since version 3.5.

The idea is to join tables person and phone server side and return “many” data in one extra field as string separated by arbitrary separators. The SQL statement would be as follows:

SELECT 
    persFirstName, persMidName, persLastName,
    group_concat(concat_ws('~', phoneType, phoneNumber), '|') AS phones
FROM person
    LEFT JOIN phone ON person.persID=phone.persIDs
GROUP BY person.persID

phones part of the output of the above sql would look like

Home~123456|Work~87654|Mobile~654321

Solution – Client Side

We cannot display received phones directly in person grid (well we could but users would hate us) but we need some processing. I decided to display phones in QuickTips so I needed custom renderer for persLastName:

/**
 * Last Name rederer including tooltip with phones
 * @param {Mixed} val Value to render
 * @param {Object} cell
 * @param {Ext.data.Record} record
 */
,renderLastName:function(val, cell, record) {
 
    // get data
    var data = record.data;
 
    // convert phones to array (only once)
    data.phones = 
        Ext.isArray(data.phones) ? data.phones : this.getPhones(data.phones);
 
    // create tooltip
    var qtip = this.qtipTpl.apply(data.phones);
 
    // return markup
    return '<div qtip="' + qtip +'">' + val + '</div>';
} // eo function renderLastName

and getPhones function:

/**
 * Converts string phones to array of objects
 * @param {String} phones
 * @return {Array} Array of phone objects
 */
,getPhones:function(phones) {
 
    // empty array if nothing to do
    if(!phones) {
        return [];
    }
 
    // init return value
    var retval = [];
 
    // split string to phones
    var aps = phones.split('|');
 
    // iterate through phones to extract phoneType and phoneNumber
    Ext.each(aps, function(phone) {
        var a = phone.split('~');
        retval.push({phoneType:a[0], phoneNumber:a[1]});
    });
 
    return retval;
} // eo function getPhones

A bit of XTemplate work for QuickTips and we’re done.

Conclusion

This is not full fledged one-to-many data handling with editing, adding and deleting items at “many” side, it is just simple display of data from “many” table, anyway, it can come handy sometimes.

You can see the working example here: http://examples.extjs.eu

The “many” display target does not need to be QuickTip, it can be row expander as well.

xtype defined

9. May 2008 – 02:51

Preface

There has been a lot of confusion I have observed on the Ext Forums as to xtype. Some people ignore it fully, some think that it is what it is not. So I’ve decided to clarify it.

Definition

xtype is a symbolic name given to a class. Nothing less, nothing more.

For example, your class has the name Ext.ux.MyGrid. This is the normal class name that you use when you need to instantiate this class (create an object of the class).

In addition to class name you can give your class xtype this way:

Ext.reg('mygrid', Ext.ux.MyGrid);

xtype here is mygrid and normal class name is Ext.ux.MyGrid. The above statement registers a new xtype or, in other words, connects xtype mygrid with class Ext.ux.MyGrid.

What is it good for?

Imagine you have a big application where objects (windows, forms, grids) are created when they are needed as responses to user actions. For example, the user clicks an icon or button and a new window with a grid inside is created, rendered and displayed on the screen.

Now, if you code such application in an before-Ext-2.x way you need to instantiate all objects in the application at the time of first page loading (application code first run). You will have an object of class Ext.ux.MyGrid somewhere in the browser’s memory waiting for rendering on a user click.

This is just for one grid – you may have hundreds of them… What a waste of resources! The grid is sitting somewhere there but the user may never click that button, may never need that grid.

Lazy Instantiation

If you have xtype, the only thing that sits in the memory is a simple configuration object such as:

{xtype:'mygrid", border:false, width:600, height:400, ...}

That is not as expensive as a complex instantiated class.

Now, what happens if the user clicks our button? Ext will see that the to-be-rendered-grid is not even instantiated but it knows how to deal with it. ComponentMgr knows: “If I need to instantiate an object of xtype mygrid I need to create an object of class Ext.ux.MyGrid” so it runs this code:

create : function(config, defaultType){
    return new types[config.xtype || defaultType](config);
}

In other “words”:

return new Ext.ux.MyGrid(config);

That instantiates our grid; rendering and displaying will follow. Remember: Instantiated only if needed.

Further Reading

Writing a Big Application in Ext