PDA

View Full Version : Extending Ext for Newbies - Tutorial (for your comments and suggestions)



santosh.rajan
17 Sep 2008, 4:30 AM
I am posting here the 'constructor model' for extending Ext which I think will be easier for newbies to understand. I would like to post this as a tutorial, but before that I would like all of your comments and suggestions.

You create a class in Ext by extending an Ext class. You do that for one of two reasons.
1) You have a component whose config option you want to make reusable. Eg. You have a set of panels with same width and height only the title is different. This is called a preconfigured class. You can do it like this.


// Constructor
var MyPanel = function(config) {
Ext.apply(this, { // Put your pre-configured config options here
width: 300,
height: 300
});
MyPanel.superclass.constructor.apply(this, arguments);
};
// MyPanel Extends Ext.Panel
Ext.extend(MyPanel, Ext.Panel, {});

var myfirstpanel = new MyPanel({
title: 'My First Panel'
});

var mysecondpanel = new MyPanel({
title: 'My Second Panel'
});

Another way to create preconfigured objects is by creating a factory function (a pre-configuring function) that returns a new instance of the object, as shown by @Animal in another thread. This does not require extending a class, and strictly speaking does not come under the scope of this discussion. I have added this here to show an alternative to the above method. This is simpler than the above method .

function createMyPanel(config) {
return new Ext.Panel(Ext.apply({ // Put your pre-configured config options here
width: 300,
height: 300
}, config));
};

var myfirstpanel = createMyPanel({
title: 'My First Panel'
});

var mysecondpanel = createMyPanel({
title: 'My Second Panel'
});


2) The second reason you want to extend a class is the traditional reason of OOP, ie you want to extend the functionality of the of the class. Let us say you want to add a function in the above panel and overwride an existing function. This is how you will do it.

// Constructor
var MyPanel = function(config) {
//Reusable config options here
Ext.apply(this,
width: 300,
height: 300
});
// And Call the superclass to preserve baseclass functionality
MyPanel.superclass.constructor.apply(this, arguments);
// Here you can add functionality that requires the object to exist,
// like event handling.
this.on('click', function() {alert("You Clicked " + this.title);}, this);
};
// MyPanel Extends Ext.Panel
Ext.extend(MyPanel, Ext.Panel, {
// Here you can add static variables for the class. variables that will have
// the same value for all object instances of this class.
// If you are not sure put it in the constructor above. Dont put any abject
// created with 'new' or 'xtype' here. You are safer putting it in the config
// option in the constructor.

// New function added
myNewFunction: function() {
},
// Override an existing function
onRender: function() {
MyPanel.superclass.onRender.apply(this, arguments);
this.myNewFunction();
}
});

var myfirstpanel = new MyPanel({
title: 'My First Panel'
});

var mysecondpanel = new MyPanel({
title: 'My Second Panel'
});

Another way to write the constructor above as shown by @Condor is

var MyPanel = function(config) {
// Call the superclass to preserve baseclass functionality
MyPanel.superclass.constructor.call(this, Ext.apply({
//Reusable config options here
width: 300,
height: 300
}, config));
// Here you can add functionality that requires the object to exist,
// like event handling.
this.on('click', function() {alert("You Clicked " + this.title);}, this);
};

The above manner for extending an Ext class is the 'constructor model'. Another way of extending an Ext class is the 'initComponent model'. As the name suggests this method is only applicable to extending Ext Components. Here is an example.


var MyPanel = Ext.extend(Ext.Panel, {
// Here you can add static variables for the class. variables that will have
// the same value for all object instances of this class.
// If you are not sure put it in the constructor above. Dont put any abject
// created with 'new' or 'xtype' here. You are safer putting it in the config
// option in the constructor.

// New function added
initComponent: function() {
//Reusable config options here
Ext.apply(this,
width: 300,
height: 300
});
// And Call the superclass to preserve baseclass functionality
MyPanel.superclass.initComponent.apply(this, arguments);
// Here you can add functionality that requires the object to exist,
// like event handling.
this.on('click', function() {alert("You Clicked " + this.title);}, this);
},
myNewFunction: function() {
},
// Override an existing function
onRender: function() {
MyPanel.superclass.onRender.apply(this, arguments);
this.myNewFunction();
}
});

The first thing you will notice is that there is no constructor here. Ext creates the constructor for you. The constructor created by Ext will call initComponent. This is a widely used method you will find in the advanced tutorials and Examples. But Just remember for now it does the same thing as the constructor model.

The preferred way to handle event handling (listeners) is to add them after the call to the superclass in the constructor or initComponent.

MyPanel.superclass.constructor.apply(this, arguments);
// Here you can add functionality that requires the object to exist,
// like event handling.
this.on('click', function() {alert("You Clicked " + this.title);}, this);

In the case of the factory method you would add a event handler outside of the factory method like this.

myFirstPanel.on('click', function() {alert("You Clicked " + this.title);}, myFirstPanel);

There are other ways of handling listeners notably by adding a 'listeners' config option. But I would recommend that to advanced users.

Further Reading:
1) Saki's Tutorial: Extending Ext Class (http://extjs.com/learn/Tutorial:Extending_Ext_Class)
2) mjlecomte's Sticky on Extending Ext Class (http://www.extjs.com/forum/showthread.php?t=28085)
3) Discussion on this Tutorial (http://www.extjs.com/forum/showthread.php?t=47413)

________________

jay@moduscreate.com
17 Sep 2008, 5:49 AM
Thanks for your contribution. Have you thought about placing it in the wiki?

santosh.rajan
17 Sep 2008, 5:52 AM
Yes I am going to do that. But I posted here for comments and suggestions to improve on it before posting it to the wiki.

santosh.rajan
17 Sep 2008, 6:21 AM
I have added to my tutorial on how the create a preconfiguring function that preconfigures a class and returns the new instance of the object.

Condor
17 Sep 2008, 6:26 AM
I prefer:


var MyPanel = function(config) {
MyPanel.superclass.constructor.call(this, Ext.apply({
//Reusable config options here
width: 300,
height: 300
}, config));
// Here you can add functionality that requires the object to exist,
// like event handling. eg. MyPanel.on('click', function() {}, this);
};

mjlecomte
17 Sep 2008, 6:29 AM
Well, you've avoided the initComponent debate entirely. At the least, in the tutorial, it should be mentioned or refer to Saki's blog and perhaps tutorial. You have "I am posting here the 'constructor model'"...maybe say "The model presented here is a 'constructor model' as opposed to using initComponent".

I think you should add where to do addEvents and how to specify listeners (my thread illustrated that for example listeners: vs. 'on').

I don't know if you added Animal's factory pattern to this yet from the other thread today, his presentation looks simpler than what is currently shown above. Oh, I think you've edited this while I was posting.

Ok, other comments:

You have "extending a Ext component". But this is not just for extending and not just for component, or you should be specific on that. Part of Animal's point is not to extend if you're just configuring, use a factory.

Also, especially in your illustration with no initComponent, this 'template' you've shown is not just for 'component'.

Add comment before MyPanel.superclass.onRender.apply(this, arguments);
that you need to do that to preserve base class functionality, etc.

mjlecomte
17 Sep 2008, 6:34 AM
Do you want MyPanel.on in the constructor, or this.on?

For each option show specifically how you would:

add items,
add listeners,
add events
as these are points/problems that come up in the forums typically.

jay@moduscreate.com
17 Sep 2008, 6:42 AM
I prefer:


var MyPanel = function(config) {
MyPanel.superclass.constructor.call(this, Ext.apply({
//Reusable config options here
width: 300,
height: 300
}, config));
// Here you can add functionality that requires the object to exist,
// like event handling. eg. MyPanel.on('click', function() {}, this);
};

You're a very senior member here. For folks learning how to use Ext, OOJS, and extend ext components, that could be confusing. Breaking up the code into smaller, digestible chunks is best for learning.

santosh.rajan
17 Sep 2008, 6:49 AM
I take your point @jgarcia, however we can show all the possible ways and let them chose whichever they understand.
I have added @Condors suggestion.
@mjlecomte I will add sakis blog and your sticky as links for further reading at the end of the tutorial.
Personally my preference would be @Animals factory method for pre configured classes. However let the reader choose whichever he want.
@I will incorporate your other suggestions now.

Condor
17 Sep 2008, 6:51 AM
OK, how about:


var MyPanel = function(config) {
var finalConfig = Ext.apply({
//Reusable config options here
width: 300,
height: 300
}, config);
MyPanel.superclass.constructor.call(this, finalConfig);
// Here you can add functionality that requires the object to exist,
// like event handling. eg. this.on('click', function() {}, this);
};

Another note:
You can't use nested objects in the reusable config options (listeners etc.).
Specifying a single listener in config would replace all listeners in the reusable config options.
You'll have to add extra code for adding listeners to the finalConfig or you can add listeners after calling the superclass constructor using this.on().

mjlecomte
17 Sep 2008, 6:51 AM
I prefer:


var MyPanel = function(config) {
MyPanel.superclass.constructor.call(this, Ext.apply({
//Reusable config options here
width: 300,
height: 300
}, config));
// Here you can add functionality that requires the object to exist,
// like event handling. eg. MyPanel.on('click', function() {}, this);
};

Personally, I like this one also, I think it looks cleaner and is easier to follow. I'd show both. When you see both at the same time, doing the same thing it's pretty easy to compare and see what is going on. I understand what Jay is saying about digestable chunks, but at the same time, too many chunks gets confusing as well. If someone doesn't understand one way they can look at the other. If I read a book and don't understand a chapter, I just come back to it, if that chapter was omitted entirely then I'd never learn it.

mjlecomte
17 Sep 2008, 6:58 AM
Another note:
You can't use nested objects in the reusable config options (listeners etc.).
Specifying a single listener in config would replace all listeners in the reusable config options.
You'll have to add extra code for adding listeners to the finalConfig or you can add listeners after calling the superclass constructor using this.on().

Exactly. I recall a few posts recently where these points were screwing some people up. Animal even suggested a feature request to help bail people out, but Jack ended up pointing out that just doing this.on was the cleanest/quickest way to go (which is what I added to my other thread).

@santosh you might also have the wiki link back to this thread where they can see or add discussion.

jay@moduscreate.com
17 Sep 2008, 6:59 AM
OK, how about:
....

that looks much better.

I usually do something like this for any possibly nested obj.


var MyPanel = function(config) {
config = config || {};
config.listeners = config.listeners || {};

Ext.applyIf(config.listeners, {
click : {
scope : this,
fn : this.onClick
}
});


var finalConfig = Ext.apply({
//Reusable config options here
width: 300,
height: 300
}, config);
MyPanel.superclass.constructor.call(this, finalConfig);
// Here you can add functionality that requires the object to exist,
// like event handling. eg. this.on('click', function() {}, this);
};

Condor
17 Sep 2008, 7:02 AM
My only problem with that (and it is only minor) is that config.listeners will be modified (it should modify finalConfig.listeners).

santosh.rajan
17 Sep 2008, 7:03 AM
Do you want MyPanel.on in the constructor, or this.on?

For each option show specifically how you would:

add items,
add listeners,
add events
as these are points/problems that come up in the forums typically.

I have made the change to 'this'. Regarding your second comment, I am not sure I want to add this in this introductory tutorial. Because there are many ways to add components, events, listeners etc. That would be another tutorial. Maybe I will give more pointers here.

jay@moduscreate.com
17 Sep 2008, 7:05 AM
Condor, can you please elaborate as to why you feel that way? :)

Condor
17 Sep 2008, 7:07 AM
Let's say I have a config object that I want to use to create several objects.
The contructor of your object would add listeners to my config object, which would cause the listeners to be used by all objects I create with the config object.

santosh.rajan
17 Sep 2008, 7:45 AM
Added a brief description of the 'initComponent model' and 'Further reading'.

mjlecomte
17 Sep 2008, 7:54 AM
I'll refrain from further nitpicking, but you still have for item 1: "You create a class in Ext by extending an Ext class."

And within that item you say another option is factory pattern. Factory pattern is not extending.

I think you have 3 sections:
1. Preconfiguring by using the factory pattern
2A. Preconfiguring by extending using constructor
2B. Preconfiguring by extending using initComponent (see Saki's blog/tutorial)
3. Override/supplement by extending

santosh.rajan
17 Sep 2008, 8:08 AM
@mjlecomte I appreciate your eye for detail, so go ahead and point out as much as you like. Actually come to think of it @Animals factory method doesnt come under the scope of the title 'extending'. I will take your points and rewrite within the spirit of "this is for newbies".

santosh.rajan
17 Sep 2008, 9:01 PM
First Cut of Tutorial posted here
http://extjs.com/learn/Tutorial:Extending_Ext_for_Newbies

Thank you to all, for your comments and suggestions. I look forward to more comments and suggestions here. I will update the tutorial based on your comments and suggestions.

mystix
17 Sep 2008, 9:42 PM
a little late to the chase, but...

should i move this thread to the Example forum instead?

santosh.rajan
17 Sep 2008, 9:46 PM
Sure you can thanks.

mystix
17 Sep 2008, 10:07 PM
Sure you can thanks.

done ;)

tobiu
18 Sep 2008, 6:13 AM
i personally like the factory-method quite much, because it helps to shorten code with config-params.

idea:



function createMyPanel(config, type) {

switch(type){ //set type-specific configs, listeners and functions here and include in the panel below.
case 1:...
case 2:...
case 3:...
}

return new Ext.Panel(Ext.apply({ // Put your pre-configured config options here
width: 300,
height: 300
}, config));

};

var myfirstpanel = createMyPanel({
title: 'My First Panel'
}, 1);

var mysecondpanel = createMyPanel({
title: 'My Second Panel'
}, 2);


this makes sense if you have quite big objects and do not like to make to many "subclasses". for example creating a customerFormPanel, using the same fieldsets and elements for 3 different types: new customer, edit customer, customerDetails. here with new customer, the fields are empty, edit loads values via ajax and details is read-only.


kind regards, tobiu

pgraju
27 Sep 2008, 8:11 PM
hi santosh and tobui,

edit: I was able to get namespaces working now still having issues with functions inside the switch statement

im trying to implement the factory method using switch which tobui mentioned however I'm hitting some snags with using functions

so here is my factory preconfig class for a form panel



Ext.ns('App');

App.newForm = function(config, type){

//set type-specific configs, listeners and functions
switch (type) {
case 1:
var addNew = function(){
alert('You clicked the Add button!');
}, App.createNewForm // scope
break;
case 2:
break;
case 3:
break;
}

// put pre-configured config options
return new Ext.form.FormPanel(Ext.apply({
bodyStyle: 'padding: 10px;',
labelWidth: 125,
autoScroll: true
}, config));

}

App.createNewForm = App.newForm({
title: 'New Form',
items: [{
border: false,
items: {
xtype: 'fieldset',
title: 'New Form',
autoHeight: true,
autoWidth: true,
defaultType: 'textfield',
items: [{ // form elements }]
}
}],
buttons: [{
text: 'Add',
handler: addNew
}]
}, 1);
My problem is that it doesn't recognise the function addNew ? Have I declared the function incorrectly?

Also, when I try and use my form panel in my layout.js file:




Ext.onReady(function(){

var viewport = new Ext.Viewport({
layout:'border',
items:[{
region:'north',
// north config
},{
region:'south',
// south config
}, {
region:'east',
// east config
},{
new Ext.TabPanel({
region:'center',
deferredRender:false,
activeTab:0,
items:[
App.createNewForm
}]
})
]
})
});
I know I have incorrectly done this to include a function but seem to be having issues in getting it working as I'm new to JS and OOP.

Thanks!

tobiu
28 Sep 2008, 6:49 AM
hi pgraju,

i'm a bit short in time at the moment.
i will write down some suggestions how to implement it, but i do not have the time to test them myself.



Ext.ns('App');

App.newForm = function(config, type){

switch (type) {
case 1:
var addNew = function(){ private function
alert('You clicked the Add button!');
}
break;
case 2:
break;
case 3:
break;
}


var myForm = new Ext.form.FormPanel(Ext.apply({
bodyStyle: 'padding: 10px;',
labelWidth: 125,
autoScroll: true
}, config));

myForm.addNew = addNew; make the private function public. you can use Ext.apply if you prefer to add it this way

return myForm;

}



to describe it short (if it does not get clear, just ask): App.newForm is the factory-method. giving that class public functions does not make sense since it returns an instance of another class. App.createNewForm is an instance of Ext.form.FormPanel. setting the scope to this instance does not make sense either. the instance can get functions in the creating-process or afterwards. what you did is trying to give the instance a function before it exists.

a suggestion: if you want to create many instances of a "class" that all use exactly the same function, do not put in the the constructor-class, since the function will be re-created for each instance. put it outside, where it is created just once. example:



Ext.ns('App');

App.addNew = function(){
alert('You clicked the Add button!');
}



kind regards, tobiu

EDIT:



var addNew = function(){
alert('You clicked the Add button!');
}, App.createNewForm // scope


is a wrong syntax -> you can not just write ,scope behind a function. i guess you mixed it up with addListener(), where you can specify scopes like that.

rtconner
29 Sep 2008, 9:32 PM
I the first example, wouldn't 'arguments' be undefined? I assume that should be 'config', right?

santosh.rajan
30 Sep 2008, 12:27 AM
'arguments' is a javascript predifined variable which simply contains all the arguments passed, in an array. You should always use arguments, so that you make sure all the arguments passed are taken care of.

mjlecomte
30 Sep 2008, 4:14 AM
http://developer.mozilla.org/En/Core_JavaScript_1.5_Reference:Functions:arguments

SunnySven
23 Mar 2009, 2:55 AM
Hi,

great tutorial, but there is a bug:

Ext.apply(this, {
width: 300,
height: 300
});

It is the fith code block...

greets
sunnysven.

sandiptikole
15 Nov 2011, 9:07 AM
Hi Santosh/others,

How to extend the Ext JS class at tow levels
In my case I have following code


XXX.ContactFieldSet = Ext.extend(Ext.form.FieldSet, {
title: 'Contact',
initComponent: function() {
var config = {
layout: 'form',
labelAlign : 'top',
defaults: {
border : false,
autoHeight: true
},
items: [{
id: this.prefix + '-ContactName',
ref: 'ContactName',
xtype: 'combo',
mode : 'local',
fieldLabel: 'Contact Name'
}]
};
Ext.apply(this, config);
Ext.apply(this, Ext.apply(this.initialConfig, config));
XXX.ContactFieldSet.superclass.initComponent.apply(this, arguments);
}
});

XXX.frontOfficeContactFieldSet = new XXX.ContactFieldSet({
prefix: 'frontoffice',
title: 'Front Office Contact',
height: 170
});

How to extend XXX.ContactFieldSet and add one more textfield and initiate the class like frontOfficeContactFieldSet

Regards,
Sandip