PDA

View Full Version : Linked combo boxes: store filter



ctp
6 Aug 2010, 5:23 AM
Hi folks,

I'm working on some simple examples. One of them are two linked / cascaded combo boxes. I'm trying to get the filtering for hours now but no success. The second combo should be filtered by 'code' of the first combo. Anyone here has a hint what's going wrong there?



Ext.BLANK_IMAGE_URL = '/ext/resources/images/default/s.gif';
Ext.ns('App');

App.ManufacturerStore = Ext.extend(Ext.data.JsonStore, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
root: '',
autoLoad: true,
autoSave: false,
restful: true,
mode: 'remote',
url: '/manufacturers',
writer: new Ext.data.JsonWriter({
encode: false
}),
fields: [
'id',
'name',
'code'
]
});
App.ManufacturerStore.superclass.constructor.call(this, config);
}
});

App.ModelStore = Ext.extend(Ext.data.JsonStore, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
root: '',
autoLoad: true,
autoSave: false,
restful: true,
mode: 'local',
url: '/models',
writer: new Ext.data.JsonWriter({
encode: false
}),
fields: [
'id',
'name',
'manufacturer_id'
]
});
App.ModelStore.superclass.constructor.call(this, config);
}
});

App.ManufacturerCombo = Ext.extend(Ext.form.ComboBox, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
id: 'manufacturer-combo',
fieldLabel: 'Hersteller',
name: 'manufacturers',
store: new App.ManufacturerStore,
mode: 'remote',
forceSelection: true,
triggerAction: 'all',
lazyInit: true,
typeAhead: true,
displayField: 'name',
valueField: 'code',
emptyText: 'Bitte Hersteller wählen ...',
selectOnFocus: true,
listeners: {
select: {
fn:function(combo, value) {
var modelCombo = Ext.getCmp('model-combo');
modelCombo.clearValue();
modelCombo.store.load({
params: {filter: this.value, filterfield: 'code'}
});
}
}
}
});
App.ManufacturerCombo.superclass.constructor.call(this, config);
}
});
Ext.reg('manufacturer_combo', App.ManufacturerCombo);

App.ModelCombo = Ext.extend(Ext.form.ComboBox, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
id: 'model-combo',
fieldLabel: 'Modell',
name: 'models',
store: new App.ModelStore,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
lazyInit: true,
typeAhead: true,
displayField: 'name',
valueField: 'id',
emptyText: 'Bitte Hersteller wählen ...',
selectOnFocus: true
});
App.ModelCombo.superclass.constructor.call(this, config);
}
});
Ext.reg('model_combo', App.ModelCombo);

CarChoiceFormPanel = Ext.extend(Ext.FormPanel, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
frame: true,
title: 'Car choice',
items: [{
xtype: 'manufacturer_combo'
},{
xtype: 'model_combo'
}
]
});
CarChoiceFormPanel.superclass.constructor.call(this, config);
}
});
Ext.reg('car_choice_form_panel', CarChoiceFormPanel);

Ext.onReady(function () {
var window;
if(!window){
window = new Ext.Window({
layout: 'fit',
width: 400,
height: 200,
closeAction: 'hide',
plain: true,
items: [
{xtype: 'car_choice_form_panel'}
]
});
}
window.show(this);
});

Condor
6 Aug 2010, 5:35 AM
1. You should configure the manufacturer combobox with editable:false if you are using the select event (use the change event if you allow manual input).
2. You are using this.value instead of value for the 'filter' parameter.
3. I would have used:

select: function(combo, value){
if (combo.store.baseParams.filter != value) {
modelCombo.store.baseParams.filter = value;
modelCombo.clearValue();
delete modelCombo.lastQuery; // <- force reload on next use
}
}
and configure the ModelStore with:

mode: 'remote',
baseParams: {filterfield: 'code'}

ctp
6 Aug 2010, 6:10 AM
Hi Condor,

many thanks for the prompt reply. I've changed the code but the second combo still remains unfiltered :-(



Ext.BLANK_IMAGE_URL = '/ext/resources/images/default/s.gif';
Ext.ns('App');

App.ManufacturerStore = Ext.extend(Ext.data.JsonStore, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
root: '',
autoLoad: true,
autoSave: false,
restful: true,
mode: 'local',
url: '/manufacturers',
writer: new Ext.data.JsonWriter({
encode: false
}),
fields: [
'id',
'name',
'code'
]
});
App.ManufacturerStore.superclass.constructor.call(this, config);
}
});

App.ModelStore = Ext.extend(Ext.data.JsonStore, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
root: '',
autoLoad: true,
autoSave: false,
restful: true,
mode: 'remote',
baseParams: {filterfield: 'code'},
url: '/models',
writer: new Ext.data.JsonWriter({
encode: false
}),
fields: [
'id',
'name',
'manufacturer_id'
]
});
App.ModelStore.superclass.constructor.call(this, config);
}
});

App.ManufacturerCombo = Ext.extend(Ext.form.ComboBox, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
id: 'manufacturer-combo',
fieldLabel: 'Hersteller',
name: 'manufacturers',
store: new App.ManufacturerStore,
mode: 'remote',
forceSelection: true,
triggerAction: 'all',
lazyInit: true,
typeAhead: true,
displayField: 'name',
valueField: 'code',
editable: false,
emptyText: 'Bitte Hersteller wählen ...',
selectOnFocus: true,
listeners: {
select: {
fn:function(combo, value) {
var modelCombo = Ext.getCmp('model-combo');
if (combo.store.baseParams.filter != value) {
modelCombo.store.baseParams.filter = value;
modelCombo.clearValue();
delete modelCombo.lastQuery; // <- force reload on next use
}
}
}
}
});
App.ManufacturerCombo.superclass.constructor.call(this, config);
}
});
Ext.reg('manufacturer_combo', App.ManufacturerCombo);

App.ModelCombo = Ext.extend(Ext.form.ComboBox, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
id: 'model-combo',
fieldLabel: 'Modell',
name: 'models',
store: new App.ModelStore,
mode: 'local',
forceSelection: true,
triggerAction: 'all',
lazyInit: true,
typeAhead: true,
displayField: 'name',
valueField: 'id',
emptyText: 'Bitte Hersteller wählen ...',
selectOnFocus: true
});
App.ModelCombo.superclass.constructor.call(this, config);
}
});
Ext.reg('model_combo', App.ModelCombo);

CarChoiceFormPanel = Ext.extend(Ext.FormPanel, {
constructor: function (config) {
var config = Ext.apply(config || {}, {
frame: true,
title: 'Car choice',
items: [{
xtype: 'manufacturer_combo'
},{
xtype: 'model_combo'
}
]
});
CarChoiceFormPanel.superclass.constructor.call(this, config);
}
});
Ext.reg('car_choice_form_panel', CarChoiceFormPanel);

Ext.onReady(function () {
var window;
if(!window){
window = new Ext.Window({
layout: 'fit',
width: 400,
height: 200,
closeAction: 'hide',
plain: true,
items: [
{xtype: 'car_choice_form_panel'}
]
});
}
window.show(this);
});

Condor
6 Aug 2010, 6:14 AM
This all still assumes that your server actually handles the 'filter' and 'filterfield' parameters. Does it?

ctp
6 Aug 2010, 6:18 AM
Hmhm, I'm running Rails 3 + WEBrick as Backend. You mean, the _web_ server should handle both?

Condor
6 Aug 2010, 6:23 AM
You could also do local filtering, but you configured it for remote filtering.

ctp
8 Aug 2010, 12:31 PM
Hi Condor,

many thanks for your help. Filtering works fine now at the Rails 3 backend ;-)

epieters
9 Aug 2010, 10:51 AM
Hi Condor,

Could you explain how this would work with local filtering?
I have a similar use case, where the value of 1 combobox should be used as a filter on the 2nd combobox.
But since the values don't change ofter I'm using ArrayStore instead of JsonStore and therefore need local filtering.
I've looked al over the place but can't find a good example.

Thanks.

epieters
9 Aug 2010, 12:16 PM
Hi Condor,

Could you explain how this would work with local filtering?
I have a similar use case, where the value of 1 combobox should be used as a filter on the 2nd combobox.
But since the values don't change ofter I'm using ArrayStore instead of JsonStore and therefore need local filtering.
I've looked al over the place but can't find a good example.

Thanks.

OK, 1 step closer because I found the FAQ for combo.
I have the following listener:


listeners : {
'select' : function(combo,value) {
console.info('Hello, my value is: '+ combo.getValue());
var comboWeight = Ext.getCmp('weight-combo');
console.info(comboWeight.store);
comboWeight.clearValue();
comboWeight.store.filter('sexe', combo.getValue());
}
}


Which is triggered correctly and prints its value to the console when selected.
However, the 2nd combobox is not filtered as I would expect.
Is there something I need to do in the 2nd combobox before I get the filtered data from its store?

Reimius
9 Aug 2010, 1:17 PM
For the combobox you made epieters, did you set clearFilterOnReset to false?

It is true by default which means it will unfilter when the the store is set to local and a 'reset' event is fired. I'm not completely sure, but I think the reset event may be firing quite often when using a combobox, so all filters you are applying are immediately removed when something changes on your combobox.

epieters
9 Aug 2010, 1:25 PM
Hi Reimius,

Ah, that helped. However, the filter is not applied the first time. only after I toggle the values in the 1ste combo, the filter gets applied.
Is there a way to force the filter to apply so the 2nd combo is filtered immediately?

Thanks

Condor
10 Aug 2010, 12:33 AM
The combobox that you filter needs to be configured with:

triggerAction: 'all',
lastQuery: '',
clearFilterOnReset: false

epieters
10 Aug 2010, 8:45 AM
Excellent. Thanks. Works fine now.

tangix
16 Aug 2010, 10:45 PM
Working on the same issue with two combo boxes but where ComboBox 2 has a store with duplicate display fields (the value fields are different) and the store (ArrayStore) contains another field that I'd like to filter on;


[[0,1,"1|BE","Belgium"],[1,-1,"-1|BE","Belgium"],[2,1,"1|LU","Luxembourg"],
[3,-1,"-1|LU","Luxembourg"],[4,1,"1|NL","Netherlands"],[5,-1,"-1|NL","Netherlands"] ... ]
First field is an index, second a reference to the first ComboBox (that I'd like to filter the store for ComboBox 2), then valueField and displayField.

All is well when I select a value from ComboBox 1, ComboBox 2 filters properly.
However, as the list of countries is long I'd like to configure ComboBox 2 to be editable and then the filtering fails and the store of ComboBox 2 is unfiltered showing duplicate entries (for example Luxembourg) when typing. After typing, the store is again unfiltered and showing all values when dropping down the list.

The stores are local and ComboBox 2 is configured with;


mode: 'local',
triggerAction: 'all',
clearFilterOnReset: false,
lastQuery: '',
lazyInit: false,
forceSelection: true,
typeAhead: true,
editable: true
I have tried to understand the API documentation and the beforeQuery/doQuery but failed.... Using ExtJS 3.2.2.

Any insight greatly appreciated!
/msa

Condor
16 Aug 2010, 11:34 PM
You can't combine local filtering with editable:true (which also uses local filtering).

The only option you have left is to use another store to hold all records and copy only the valid records to the combobox2 store when an entry from combobox1 is selected.

tangix
17 Aug 2010, 12:56 AM
Thanks for the answer Condor! I'll give the dual store approach a try (or remove the editable ComboBoxes and hope the users won't complain)

Cheers,
/msa

tangix
17 Aug 2010, 1:41 AM
Hi Condor,
sorry for another stupid question... OK, in ComboBox 1 I now create a new Store, filter it and the debug is OK, soving many records first and then less records. However, ComboBox 2 does not pick up the change to the store (combo.terrCountryStore). I think I am not setting it properly on ComboxBox 2 or ComboBox 2 needs some kind of refresh (like GridPanel.reconfigure())?


// Listener in ComboBox 1
// country_terr_data contains all information for the full store

select: function(combo, rec, index) {
var t = combo.getValue();
combo.terrCountryStore = new Ext.data.ArrayStore({
idIndex: 0,
fields: [ { name: 'idx', type: 'int' }, { name: 'terr', type: 'int' }, 'iso', 'printable_name' ],
data: country_terr_data
});
console.log('before: ' + combo.terrCountryStore.getCount());
combo.terrCountryStore.each(function(rec) {
console.log('find: ' + t);
if (rec.data.terr != combo.getValue()) {
combo.terrCountryStore.remove(rec);
}
});
console.log('after: ' + combo.terrCountryStore.getCount());
combo.terrCountryStore.sort('printable_name', 'ASC');
Ext.getCmp(combo.countryCombo).setEmpty();
combo.reloadFunction();
}

How can I kick ComboBox 2 to understand that there is a new Store?

/msa

tangix
17 Aug 2010, 1:56 AM
Hmmm... doing Ext.getCmp('ComboBox2').getStore(); I see the newly created filtered store just fine - with the correct data in it.
However, the idx field contains non-consecutive values - could this matter for ComboBox2?
Maybe I should move to a remote store to handle this.... :-/

Condor
17 Aug 2010, 2:42 AM
No!!! You can't change the store of a combobox by simply assigning a new one!

But why would you want to? You only need to fill the existing combobox store with new data.

tangix
17 Aug 2010, 2:45 AM
Doh, doh, doh!! Me stupid, me will recode... :)

Thanks,
/msa

tangix
17 Aug 2010, 4:44 AM
So - cranked up the coffee beans dial on the espresso machine and managed to get everything working.
Took some time to get the "statefulness" in place for the combination but all is now working beautifully!
Thanks Condor,
/msa

ranjansingh
29 Aug 2011, 4:59 AM
Hi Condor,

I have some issue on local filtering.

1. Have two combobox - Country and State.
2. Getting data from DB and can populate the data and am doing local filtering
3 On select of country, corresponding state list will display.

Need help: Can able to filter for first country select but can't able to filter for Second time.

Code:
Ext.define('DEMO.view.ComboDemo.LinkedCombo' ,{
extend : 'Ext.form.Panel',
alias : 'widget.LinkedCombo',
layout : {
type : 'vbox', // Arrange child items vertically
align : 'stretch' // Each takes up full width
},
height : 240,
width : 300,
frame : true,
title : 'Linked Combo',
closable: true,

items: [{
xtype : 'form',
padding : '5 5 5 5',
border : false,
frame : true,
items: [
{
xtype : 'combo',
fieldLabel : 'Country',
valueField : 'countryCode',
displayField : 'countryName',
triggerAction : 'all',
mode : 'local',
emptyText : 'Select a Country',
store : 'CountryStore',
listeners: {
scope: this,
'select' : function(combo, value) {
console.info(combo.getValue());
var comboState = Ext.getCmp('combo-state');
comboState.clearValue();
comboState.store.filter('countryCode', combo.getValue());
}
}
},{
xtype : 'combo',
id : 'combo-state',
fieldLabel : 'State',
valueField : 'stateCode',
displayField : 'stateName',
triggerAction : 'all',
mode : 'local',
lastQuery : '',
emptyText : 'Select a Country first',
store : 'StateStore',
lazyInit : true,
typeAhead : true,
clearFilterOnReset: false,
selectOnFocus: true
},
]
}],

initComponent: function() {
this.callParent(arguments);
}
});

Could you please let me know where am i gone wrong.