PDA

View Full Version : initComponent() vs constructor() when subclassing.



dereks
2 Sep 2010, 9:33 AM
In working with ExtJS, I have followed the sub-classing examples and created many subclasses of the template form:


Application.SalesContactForm = Ext.extend( Application.ContactForm, {
initComponent: function() {

var config = {
title: 'Sales Contact',
}

Ext.apply(this, Ext.apply(this.initialConfig, config));
Application.SalesContactForm.superclass.initComponent.apply(this, arguments);
}
});
Ext.reg( 'salescontactform', Application.SalesContactForm );
But this won't work if I need to install listeners in my sub-class. If I need listeners, I need to rewrite constructor() instead of initComponent().

So, why is the above initComponent() pattern used, instead of using contructor() for all sub-classing?

In other programming environments (like PHP, Python, C++, Java, etc.), the term "constructor" has a very clear meaning that is basically the same for any object-oriented language. When learning ExtJS, trying to understand when to use initComponent() vs. constructor() was confusing and tedious. (And it still is.)

Given that:

1. Overriding constructor() works 100% of the time (incl. with listeners), but overriding initComponent() only works part of the time (when not using listeners)

2. The term "constructor" is universally understood by OOP developers, whereas initComponent() is unique to ExtJS


...Why do the docs tell people to override initComponent() instead of constructor()? What am I missing? What is the intention behind initComponent()?

Any explanation is greatly appreciated.

Thank You,
Derek

griffiti93
2 Sep 2010, 10:50 AM
Given Javascript's prototypal nature, it behaves a bit differently than other languages. Mr. Crockford said it best:


JavaScript is a class-free, object-oriented language, and as such, it uses prototypal inheritance instead of classical inheritance. This can be puzzling to programmers trained in conventional object-oriented languages like C++ and Java. - Douglas Crockford (http://javascript.crockford.com/inheritance.html)

While there are many reasons the docs suggest using initComponent(), I can think of one right away. Simplicity. Less code. Plus, if you use the constructor method, you have to be sure to either call Ext.apply(this, config) or pass your config to the superclass, etc. Forgetting to do this yields undesired results. For a Javascript developer just starting out in Ext, this could be frustrating. Again, that's just a thought. I look forward to others' comments too.

When you refer to installing listeners in your sub-class, do you mean add custom events? If so, you can simply use the following snippet in your initComponent method:


this.addEvents({
event1: true,
event2: true
});

If you are wanting to attach event listeners, you can call this.addListener(...) in your initComponent method. You can also pass them in via your object config as such:


var salesForm = new Application.SalesContactForm({
...,
listeners: {...}
});

Are you instantiating your class directly or indirectly via xType: 'salescontactform' in a larger context? I'd like to see if I could duplicate your problem.

muncher
2 Sep 2010, 11:23 AM
not much to contribute.

I started using initcomponent to have at hand, preconfigured objects.
But in some objects/classes, i can't remember though, you have to use constructor specifically.

dereks
2 Sep 2010, 11:46 AM
Thank you for your prompt response.


I'd like to see if I could duplicate your problem.

I don't have a code problem. I am just trying to understand the "why" of this design decision... I wasted many hours trying to figure out the correct ExtJS way to do inheritance, and I still don't understand why I couldn't just override constructor() (like I would do to __construct() in PHP, or __init__() in Python, or ClassName() in C++, etc.)



When you refer to installing listeners in your sub-class, do you mean add custom events?

I am specifically referring to the problem described under the heading "listeners" in the following Tutorial:

http://blog.extjs.eu/know-how/writing-a-big-application-in-ext-part-2/

It says there (in part) "If you try to install event handlers by setting property listeners in your config they will not work. [...] There is an easy workaround. Put constructor method in your extension ... and define your listeners therein." (Read the article for a complete explanation.)

Furthermore, examine the subclass template provided here:

http://blog.extjs.eu/patterns/file-patters/javascript-extension-file-pattern/comment-page-1/

[Also found under "A Workable Extension Pattern" near the end]
http://hosted.extjs.com/learn/Tutorial:Extending_Ext2_Class

That handy template makes the same argument about using constructor() when listeners are involved. It has a comment that says:


// uncomment constructor if you need it, e.g. if you need listeners

Plus, if you use the constructor method, you have to be sure to either call Ext.apply(this, config) or pass your config to the superclass, etc. Forgetting to do this yields undesired results.

How is that any different than initComponent()? When overriding initComponent() one is still forced to remember the following lines of code at the end (within every subclass):



Ext.apply(this, Ext.apply(this.initialConfig, config));
Application.SalesContactForm.superclass.initComponent.apply(this, arguments);
That seems to be the same regardless of either initComponent() or construct(). (Plus, it's not really a huge deal; other languages, such as Python, also require that you manually call the parent's __init__() if you want to retain the parent's built-in construction functionality.)

I am still left with the following bit of your reply:


Simplicity. Less code.

How is it simpler? When I first found the construct() method, I knew exactly what it was. It took a good bit of reading and head-scratching to figure out why all the examples (and the template above) used initComponent() instead.

44gatti
2 Sep 2010, 12:37 PM
JS is class-free, object-oriented language, and as such, it uses prototypal inheritance instead of classical inheritance.
I think you don't understand
In a class that extends Component you have to override initComponent() for passing your config:{..} object to the initComponent() superclass method.
You can also override your superclass constructor with your own! That's no particular need to use initComponent. But you have to call your superclass.constructor in every subclasses, and your superclass constructor call initComponent.

When you extend classes like Ext.util.Observable you don't have to override initComponent because Observable is not a component.

Extending class Observable:


Ext.ux.Blah = Ext.extend(Ext.util.Observable, {
constructor: function(config){
defaultCfg:{...};
Ext.ux.Blah.superclass.constructor.call(this,Ext.applyIf(config,defaultCfg));
},..
});Extending Object:


Ext.ux.grid.Gnat = Ext.extend(Object, {
constructor: function(cfg){
Ext.apply(this, cfg || {});
this.c=cfg;
},..
});

darthwes
2 Sep 2010, 12:44 PM
constructor is what happens to the config object you pass in to create an Ext Component.

initComponent is what happens while initializing the Component.

constructor is necessary if you want to dynamically add attributes to the config object before it gets passed to the constructor.

initComponent is necessary if you want to programmatically alter the object as it's initialized.

For exmaple, consider the case where you want to create a class that adds the root object to a tree and attach some listeners to events on the tree. Well, you can't create a tree without a root. So you have to modify constructor to pick up the config object and add the root like
cfg.root = {.... Most Ext programmers are going to use
Ext.apply({root: {...}}, cfg}. However, to add a listener, I want to avoid overwriting any listeners defined in the config object, so I can use the initComponent call, let the super call occur, and then I can do
this.on('click', someFn, someScope, {single: true}); or similar to avoid the complexity of wrapping any listeners defined in the cfg.

They give me flexibility, I can jump in at the right time and take advantage of json objects or the Ext API, depending on my needs.

dereks
2 Sep 2010, 12:58 PM
I understand that initComponent() doesn't even work unless the subclass is a child of Component.

But this again raises my point: overriding "constructor()" always works. (Component or not; with listeners or not.)

So why even bother ever using initComponent()? Is constructor() not the same thing, but with more universality?

dereks
2 Sep 2010, 1:06 PM
However, to add a listener, I want to avoid overwriting any listeners defined in the config object, so I can use the initComponent call, let the super call occur, and then I can do [...snip...] or similar to avoid the complexity of wrapping any listeners defined in the cfg..

Can you elaborate on this? What do you mean by "avoid overwriting any listeners" and "avoid the complexity of wrapping any listeners defined in the cfg"?


Everything else you said still begs my question of "why use initComponent()". The constructor() gives you access to the object's construction, including creating new member objects which can be configured later on -- but it ALSO lets you set default config values. So why do people prefer initComponent() for child classes, when it has less power (and only works on Ext.Component children)?

44gatti
2 Sep 2010, 1:08 PM
So why even bother ever using initComponent()? Is constructor() not the same thing, but with more universality?

I need an aspirin /:)

VinylFox
2 Sep 2010, 1:18 PM
darthwes probably explained it the best...they are just different points in time. initComponent happens after events are added and the component registered. I personally use initComponent, because it just feels cleaner to me to separate my code from the constructor.

dereks
2 Sep 2010, 1:42 PM
I personally use initComponent, because it just feels cleaner to me to separate my code from the constructor.

That is what I am learning from these replies... as I understand it, there is no technical advantage to using initComponent(). One could just as easily configure a subclass in construct(). People just like separating out all configuration into initComponent() for some reason.

As a newcomer to ExtJS, I find that convention arbitrary and confusing, for the following reasons:

1. It only works for Components
2. It doesn't work for listener: config options (because those, for some reason, are initialized in construct())
3. The idea of initComponent() is very specific and unique to ExtJS, where as construct() is something any OOP programmer can immediately grok


I think the tutorials would be much clearer if they avoided the whole conversation and simply said "overwrite construct() to modify any child classes", because:

- no need to know if the parent is a Component or not
- no need to know that "listeners" are special
- no need to learn arbitrary details of the Ext Component lifecycle.

For now I will continue using initComponent(), to keep with ExtJS example code and best practices... even though construct() seems like better convention to me (for the reasons above).

Thanks for all the replies, this has been very helpful.

44gatti
2 Sep 2010, 2:56 PM
- initComponent method is executed in the Component constructor when you create a new Component instance;
- all Component classes share same constructor and then all of these have initComponent method inside;
- if you want to extend class constructor it is also essential that you write a line in any provided constructor (when you use Ext.extend) that calls the superclass constructor :
What is the pain of calling initComponent superclass method in any class that inherits from Component? I think is logical


the library code:



Ext.Component = function(config){
this.addEvents('','','')
Ext.ComponentMgr.register(this);
Ext.Component.superclass.constructor.call(this);
...
this.initComponent();
};

this is the code for initComponent


Ext.extend(Ext.Component, Ext.util.Observable, {
initComponent : function(){
/*
* this is double processing, however it allows people to be able to do
* Ext.apply(this, {
* listeners: {
* //here
* }
* });
* MyClass.superclass.initComponent.call(this);
*/
if(this.listeners){
this.on(this.listeners);
delete this.listeners;
}
this.enableBubble(this.bubbleEvents);
},..
});

44gatti
2 Sep 2010, 2:59 PM
:-/ he?


2. It doesn't work for listener: config options (because those, for some reason, are initialized in construct())

dereks
2 Sep 2010, 3:17 PM
Please see post #4, which gives references as to what I am talking about regarding "listener".

(I am simply referring to what I read in the documentation, see Post#4 for the links where I saw it.)

44gatti
2 Sep 2010, 3:40 PM
I assure that you can use config:{ listeners:..} in your initComponent..
See the code.. POST #12

darthwes
3 Sep 2010, 5:28 AM
I mean if I take an incoming config object in it might have a listeners property like


{
xtype: '...'...
listeners: {
'afterrender': function() {
...
}
}
}


Now if I have an extension on the constructor and I want to add an afterrender listener, I could do



cfg.listeners.afterrender = function() {...};


But that would replace the incoming after render listener. What I *want* to do is avoid overwriting my listeners so if I wait until the object is instantiated and use the "this.on('afterrender', ..." call then I don't overwrite myself.

To be direct, just use constructor. There isn't anything you can do within initComponent that you couldn't have done with the constructor. I think that's what you want/need to hear. For me, I like playing with my config in constructor, and using the instantiated object inside initComponent, but that's just the practices I mysteriously picked up while coding. I like the scope being wrapped (in initComponent I get "this" not "cfg").

griffiti93
3 Sep 2010, 6:38 AM
Well put darthwes. ;)

dereks
3 Sep 2010, 10:54 AM
Thanks for all the excellent replies!

Follow-up question: I'm using a lot of Grids (with RowEditor -- best SQL table editor I've found!). The Grids require that I instantiate an Ext.data.JsonReader, an Ext.data.HttpProxy, an Ext.data.JsonWriter, and an Ext.data.Store.

Is it okay to put that stuff in initComponent()? It feels weird to create new objects (i.e., to allocate memory) anywhere other than a construct() method.

3 Sep 2010, 11:09 AM
constructor is what happens to the config object you pass in to create an Ext Component.


I know what you mean, but this is really confusing.

constructor is a method that is called when any function is called with the "new" keyword. That "constructor" is called within the scope of the newly created object (this). Any function can be executed as a "constructor" with the "new" keyword in front of it.

The constructor for a component is executed when a new instance of any Ext.Component (or any subclass) is created. It sparks off the 'initialization phase' of the "Component Lifecycle".



initComponent is what happens while initializing the Component.


initComponent is merely a method that is called by the Ext.Component constructor to augment further 'constructor-like activities', such as the registration of events, etc.

Here are the conditions of the initialization phase:

http://tdg-i.com/img/screencasts/2010-09-03_1508.png

binoruv
26 Sep 2010, 12:56 AM
the same confusion to me

takes me days to understand this subject and constructor and initComponent~