View Full Version : I need a performance tip about combo boxes and OOP...
violinista
14 Sep 2007, 5:08 AM
Hello 2 all,
In recent 2 months a learned a lot about Extjs, thanks to unselfish help of Extjs team and others. Among other thinks, I learned to reuse comboBox with all params(it's simple, but it was nightmare for me):
App.cmbCustom = function(config) {
var store = new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.cmbUrl
}),
reader: new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
store.load();
config = config || {};
Ext.apply(config, {
store:store
//other config applyhere
});
App.cmbCustom.superclass.constructor.call(this, config);
//Other procedure here...
};
Ext.extend(App.cmbCustom, Ext.form.ComboBox);
This all works very well across my application, since this combos and other components are heavily used.
I saw on some other site a similar solution, and it looks like(differences are bolded):
App.cmbCustom = function(config) {
this.store = new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.cmbUrl
}),
reader: new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
this.store.load();
config = config || {};
Ext.apply(config, {
store:this.store
//other config applyhere
});
App.cmbCustom.superclass.constructor.call(this, config);
//Other procedure here...
};
Ext.extend(App.cmbCustom, Ext.form.ComboBox);
My question: which solution is more optimal? We are talking about ~150 combos across application with more than 1000 rows in each.
My opinion is that first solution is better since store object is not recorded into every instance of myCombo: when I declare store with var store=... it is loaded, added to combo and destroyed. Or I am wrong? Is the store object in second example instanted every time in combo and keeped in it, or not (i suppose yes)?
Thanks to all,
Violinist.
hendricd
14 Sep 2007, 6:12 AM
With that many combos (and likely -- many are using the same stores and reader ?) why not re-use those already defined stores again.
App.cmbCustom = function(config) {
config = config || {};
this.store = config.store || new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.cmbUrl
}),
reader: config.reader || new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
if(!this.store.getTotalCount()) this.store.load(); //probably already loaded?.
Ext.applyIf(config, {
store:this.store
//other config applyhere
});
App.cmbCustom.superclass.constructor.call(this, config);
//Other procedure here...
};
Ext.extend(App.cmbCustom, Ext.form.ComboBox);
then...
App.stores={};
App.stores.stateCodes = new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.stateCodesUrl
}),
reader: new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
var stateCmb = new App.cmbCustom( {store:App.stores.stateCodes})
violinista
14 Sep 2007, 6:49 AM
First, what disturbs me: is "store" variable declared in the first block really deleted or it stays forever in every instance of object cmbCustom (I suppose not)?
App.cmbCustom = function(config) {
var store = new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.cmbUrl
}),
reader: new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
store.load();
config = config || {};
Ext.apply(config, {
store:store
//other config applyhere
});
App.cmbCustom.superclass.constructor.call(this, config);
//Other procedure here...
};
Ext.extend(App.cmbCustom, Ext.form.ComboBox);
Next,what disturbs me, is the "store" variable, which is by my opinion now part of every instance of object, stays forever with every instance of myCombo (regarding the Second example)??
App.cmbCustom = function(config) {
this.store = new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.cmbUrl
}),
reader: new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
this.store.load();
config = config || {};
Ext.apply(config, {
store:this.store
//other config applyhere
});
App.cmbCustom.superclass.constructor.call(this, config);
//Other procedure here...
};
Ext.extend(App.cmbCustom, Ext.form.ComboBox);
Third, looking at your suggestion: I allways need same comboBoxes with same datastore (maybe I change width and nothing else), and that's the reason I embedded dataStore into my Combos. There are about of 5-6 and all have different dataStores, templates, embedded events etc. so that's the main reason I didn't reuse store object.
Thank you in advance!
violinista
14 Sep 2007, 7:30 AM
Simply question: which approach is better and more optimal? First or second? I think the first, because store is not keeped in memory - it is once created, applied to combo and then deleted. In the second example, store is keeped twice, without need.
hendricd
14 Sep 2007, 7:51 AM
Regarding #1,2:
As written, those stores only live as long as the ComboBox instance does. But, all those fetches by each combo from the server add up don't they ?
Re: #3:
and all have different dataStores, templates, embedded events etc. so that's the main reason I didn't reuse store object.
It's your call (and depends on the use-case of the stores/CB but, how many CB's share a common store? Are the stores static only ?
the stores have little (if anything) to do with the CB's rendering. Your CB (if your not using static stores) only cares about things that change in the store. You could simply add something like this to your CB class:
App.cmbCustom = function(config) {
config = config || {};
this._listeners = config.listeners?false:{ //you could pass an external config.listener package
'add':this.onDsAdd // but default to a standard one
,'clear':this.onDsClear
,'update':this.onDsUpdate
,'remove':this.onDsRemove
,scope:this
};
this.store = config.store || new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.cmbUrl
}),
reader: config.reader || new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
if(this._listeners){
this.store.on(this._listeners);
}
if(!this.store.getTotalCount()) this.store.load(); //probably already loaded?.
Ext.apply(config, {
store:this.store
//other config applyhere
});
App.cmbCustom.superclass.constructor.call(this, config);
//Other procedure here...
};
Ext.extend(App.cmbCustom, Ext.form.ComboBox,
{
onDsAdd:function(){}
,onDsClear:function(){}
,onDsUpdate:function(){}
,onDsRemove:function(){}
,destroy:function(){
this.store.un(this._listeners);
this.store = null;
}
});
Remember the event system lets many instances subscribe to these events (from a single store) and handle them differently.
violinista
16 Sep 2007, 1:07 AM
Yes Henricd, you're absolutely right. I thought about size of objects, but not about connections, and since all of them share almost same store, it will loaded only once-on the start, and this is main performance imporovement.
Thank you very much!
Troy Wolf
24 Sep 2007, 12:08 PM
App.cmbCustom = function(config) {
config = config || {};
this.store = config.store || new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: App.cmbUrl
}),
reader: config.reader || new Ext.data.JsonReader({
root: 'rows',
id: 'id'
}, ['id','name'])
});
if(!this.store.getTotalCount()) this.store.load(); //probably already loaded?.
Ext.applyIf(config, {
store:this.store
//other config applyhere
});
App.cmbCustom.superclass.constructor.call(this, config);
//Other procedure here...
};
Ext.extend(App.cmbCustom, Ext.form.ComboBox);
So what is the point of extending the standard ComboBox in this example? The constructor above does not seem to do anything more than a normal ComboBox other than automatically load the store if it was not already loaded. This part of the code seems extraneous to solving violinista's issue.
I'm seriously asking. Although I've been coding javascript for years, I'm relatively new to using javascript in this advanced way and with Ext. So I'm ready to be schooled!
violinista
24 Sep 2007, 11:13 PM
Sorry, but I think you haven't read whole thread thoroughly . I have tons of combos around my application and I worried about size of combo objects (in memory), but HenriCd proved to me that main performance issue was loading all that combos again and again around application and that was main improvement here.
On the other side, I was right - there is no need to embed store object into comboBox object - because it enlarges size of combo object in memory itself. One store object for all similar combos is sufficient.
Cheers!
Troy Wolf
25 Sep 2007, 5:53 AM
Sorry, but I think you haven't read whole thread thoroughly.
Thanks for the reply, violinista. I would say you didn't read my post thoroughly. I spent considerable time reading the thread and analyzing the code snippets. I understand your original issue and that hendricd did indeed help you find an answer. I am not debating that.
My specific question is regarding the extension of the default Ext ComboBox. I don't see why that is needed. It seems you could do almost exactly the same thing with the standard ComboBox. It appears to me that hendricd's extension only adds the feature to autoload the store during init--which is not specific to solving your issue, nor necessary to the solution.
If you can directly address this, please reply. I'm very interested in understanding the code more fully.
And hendricd, thank you for your help here and in the other related thread (http://extjs.com/forum/showthread.php?p=65818#post65818). ~o)
violinista
25 Sep 2007, 6:13 AM
Of course I will respond ;) . Regarding my application, first improvement in extending combos is code shortening. Instead of writing this here and there (this is original code fragment):
var cmbLE = new Ext.form.ComboBox({
store: new Ext.data.Store({
proxy: new Ext.data.ScriptTagProxy({
url: letsGo.populateComboBoxesUrl
}),
reader: new Ext.data.JsonReader({
//totalProperty:"rowCount",
root: 'rows',
id: 'id'
}, [
{name: 'id'},
{name: 'shortName'}
]),
baseParams: {
cmbBox:"cmbLE"
}
}),
tpl: new Ext.Template(
'<div class="x-combo-list-item" >',
'{id}',
'<span style="font-size:smaller; color:#888"> {shortName}</span>',
'</div>'
),
displayField: 'id',
typeAhead: true,
typeAheadDelay:0,
mode: 'local',
minChars:1,
triggerAction: 'all',
emptyText:'LE',
selectOnFocus:false,
forceSelection:true,
allowBlank:false,
hideTrigger:true,
width:25,
listWidth: 75,
name:"cmbLE",
hiddenName:"cmbLE",
valueField: 'id'
});
cmbLE.store.load();
cmbLE.on("select", function(combo,record,index){
cmbClient.reset();
cmbClient.store.load({params:{idClientFakturise:record.id}});
cmbProject.reset();
cmbProject.store.load({params:{idClient:this.getValue()}});
});
...I can simply write:
var cmbLE = new App.cmbLE({width:200});
See? :)
. It appears to me that hendricd's extension only adds the feature to autoload the store during init--which is not specific to solving your issue, nor necessary to the solution.
Yes, it directly solved my performance issue about application speed; you see, each time you have some HTTP request (AJAX, AHAH or whatever), there is need for browser to send some headers (including its overhead) to server, then server replays back and all that adds more slowdown to app (opening connections, closing connections, etc.). Since my dataStores for combos are not changed frequently (when I change them, I restart my whole app), store loading once is dramatic performance improvement; more combos I have on the form/page/toolbar, more performance is added.
Cheers :D
violinista
25 Sep 2007, 6:19 AM
I forgot to mention, but I found solution for adding combos automatically to grid as grid editors and renderers; now we call custom combos just a "black-boxes" which have embedded datastores, renderers, grid editor functions etc.
Example of this:
Ext.extend(Ext.letsGo.cmbJobPosition, Ext.form.ComboBox,{
renderJobPosition:(function(){
var instanceStore=null;
return function(v){
if(!instanceStore) {
instanceStore=Ext.letsGo.cmbJobPosition(null,true);
}
var a=instanceStore.getById(v);
//console.log(this);
return a?a.data.jobPosition:null;
};//returned function
})(),
editorJobPosition: function(){
return new Ext.grid.GridEditor(new Ext.letsGo.cmbJobPosition({
lazyRender:true
}));
}
});
...and then you can add to your editor grid column model:
...
renderer:Ext.letsGo.cmbJobPosition.prototype.renderJobPosition,
editor: Ext.letsGo.cmbJobPosition.prototype.editorJobPosition(),
...
This is so simple and elegant for me.
Troy Wolf
25 Sep 2007, 7:01 AM
Forgive me, violinista, but..... The extension example you just showed me is not the extension example that hendricd gave you. Yes, I can see that your extension simplifies your code--that is good. My question was specifically how could hendricd's extension example help. Perhaps it helped in simply giving you an example how to extend the CB. So that is good.
I think you are pointing out that it helps you because his extension loads the CB up front rather than later on-demand. One of my points is, you can do that with a CB anyway. You don't need that extension to do it.
I suppose I'm splitting hairs, but I am truly trying to understand all the code snippets I come across here on the excellent forums. Thanks!
violinista
25 Sep 2007, 7:22 AM
Yes, you're right about what I'm pointing out.
Yes, it can be all done with 'regular' combo boxes ;)
What I have done in post #10 is not extending comboBoxes (in a sense of adding custom features) but, say, 'pre-configuring' or just shortening of code. But, in post #11 I extended combobox object and added two more methods - for grid render and grid editing.
hendricd
25 Sep 2007, 6:51 PM
@Troy - Clearly [I think] the goal you want to achieve is different than Violinista's:
- load a well-rounded (re-usable view) once and apply that, as needed, to several widgets (progressively filtered).
He needed 5+ combos, all sharing, for example, a static 'state' datastore loaded once during startup. What i offered him was a reminder to consider [more] the event system built into the datastore itself as well as showed a way to share (via abstraction) that same datastore and it's reader by defining/loading them only once (or letting the first combobox initiate the load call) and passing those references to the constructor of other comboboxes yet still allow special use-cases (even event handlers).
You, on the other hand [I think], want to load a blob of 'related' (presumably JSON) data once, slice and dice via filters and apply those results for incremental renderings.
City and Starbucks would be empty until the user selects a State. Upon selection, City would be populated. Again, I'd use a custom FilterBy to find the distinct cities where state matches the selection.
Finally, Starbucks would be populated when the user selects a City.
..all sounds reasonable, as long as each combo's predecessor (eg State) is disabled prior to that-levels (eg City) filter application (since that main datastore can only have one filter applied at any one time).
Chewing on this some more, but why not loaded your blob once and create three JSON sub-stores (with appropriate readers) from that master hash and create filters for each based on the selected value of that-levels predecessor? [think I just bit my tongue on that one /:) ]
Are you more concerned over the # of AJAX calls (function) or the ability to use nifty filters (form)?
Have you started playing with your original idea? It might be easier to assist if we were all looking at something..
Troy Wolf
26 Sep 2007, 6:05 AM
hendricd, you are right on -- you understand my vision. :)
The part of the code that I'm struggling with was how to use different readers against one store. All the ways I've been using stores so far have not involved me directly creating readers--they must be getting auto-created behind the scenes. As I read about Readers, it seems they are properties of stores--not the other way around where a store could be a property of a reader. Because of this, I'm not sure how you could apply 3 readers to one store.
If I get over that hurdle, I think I know how to build my custom FilterBy() functions to get the correct list of options from the appropriate data column in the store.
HOWEVER, I have moved on because as usual, the need to produce a working application took priority over learning how to do cool things with Ext.
I am still interested in this topic, though, so if you want to continue thinking about this, I'd love to experiment with it.
My concern was not with the number of server calls, although that is one area I hoped my solutions would optimize. It's just that one simple, relatively small table encompasses all of the data my application will need for the comboboxes. After looking at all the Ext functionality for reloading and filtering stores, etc, it seemed (to me at least) to make sense to load that data once from the server, then let Ext do it's client-side magic against it.
The solution I launched creates 3 separate stores and loads them using Ajax calls triggered by the previous combobox selection. It works very well, so I am not in an urgent situation looking for a solution.
Thanks a ton for your assistance and careful attention to detail! ~o)
Powered by vBulletin® Version 4.1.5 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.