PDA

View Full Version : ComboBox filter on multiple store fields



lorezyra
16 Jun 2014, 9:44 PM
I'm trying to filter a combobox based on the fields available in the store.

For example, I have a language combo that is attached to a store "languages." Languages store gets the language in the native text and the "romanized" version of that language.

If a user types in "Japanese" in their native text ????then it will find it since I have configured the property displayField: 'geoLang_name'. However, I need the combo to also accept the geoLang_roman and geoLang_ISO639 from the store as the accepted value.
This way: "JA" and "??" and "Japanese" will all select the same value for the combobox. And the typeAhead feature will display the closest match based on these three store fields.

What is the best way of doing this?

Should I setup a keydown listener on the combobox and filter the store or is there a better way?
Or, should I override the typeAhead method for the combo class?


Store data:


{
"success": true,
"total": 66,
"dt": "2014-06-17T13:47:08+09:00",
"langs": [
{
"geoLang_id": "2",
"geoLang_name": "English",
"geoLang_roman": "English",
"geoLang_isRTL": "0",
"geoLang_ISO639": "EN",
"geoLang_isVisible": "1",
"geoLang_isSupported": "1"
},
{
"geoLang_id": "20",
"geoLang_name": "fran\u00e7ais",
"geoLang_roman": "French",
"geoLang_isRTL": "0",
"geoLang_ISO639": "FR",
"geoLang_isVisible": "1",
"geoLang_isSupported": "1"
},
{
"geoLang_id": "16",
"geoLang_name": "\u65e5\u672c\u8a9e",
"geoLang_roman": "Japanese",
"geoLang_isRTL": "0",
"geoLang_ISO639": "JA",
"geoLang_isVisible": "1",
"geoLang_isSupported": "1"
},
{
"geoLang_id": "14",
"geoLang_name": "\u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u044f\u0437\u044b\u043a",
"geoLang_roman": "Russian",
"geoLang_isRTL": "0",
"geoLang_ISO639": "RU",
"geoLang_isVisible": "1",
"geoLang_isSupported": "0"
}
]
}


Store code:


Ext.define('envitz.store.languages', {
extend: 'Ext.data.Store',
alias: 'store.languages',

requires: [
'envitz.model.language',
'Ext.data.proxy.Ajax',
'Ext.data.reader.Json'
],

constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([Ext.apply({
sortRoot: 'geoLang_id',
autoLoad: true,
model: 'envitz.model.language',
storeId: 'langSID',
pageSize: 100,
proxy: {
type: 'ajax',
timeout: 15000,
url: '/API/listLang',
reader: {
type: 'json',
idProperty: 'geoLang_id',
messageProperty: 'msg',
root: 'langs'
}
}
}, cfg)]);
}
});


View code:


//Ext.view... form items...
me.processLang({
xtype: 'combobox',
maxHeight: 200,
width: 350,
fieldLabel: 'Language',
name: 'usr_lang',
allowBlank: false,
emptyText: 'Select Language',
enforceMaxLength: false,
selectOnFocus: true,
displayField: 'geoLang_name',
queryMode: 'local',
store: 'languages',
typeAhead: true,
valueField: 'geoLang_id',
valueNotFoundText: 'Language not supported, please select from this list'
}),

// rest of form panel

processLang: function(config) {
var store = Ext.StoreMgr.lookup('languages');

store.clearFilter(true);
store.filter('geoLang_isSupported',1);

config.store=store;

return config;
},

// rest of processConfig functions...

lorezyra
18 Jun 2014, 7:48 PM
Looks like overriding OnTypeAhead method is the key to what I'm looking for here. Since this is the function that does the filtering based on the typed string. So, I'll add a custom array property "searchFields" with the list of fields to filter from the given store.

lorezyra
19 Jun 2014, 7:46 PM
Okay, to get comboBox to search through multiple store fields (instead of just displayField), I had to override Ext.util.Filter and the comboBox doLocalQuery() method. No need to bother with doRemoteQuery() method as the server can do the advanced filtering on any number of data sources...

First, override both Filter and data.Store:


//Inspired by: http://existdissolve.com/2011/11/extjs-4-filtering-on-multiple-fields/
// used for MyApp.view.ComboSearchMultiFields extension
Ext.override(Ext.util.Filter,{
/**
* @private
* Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
* Modified from ExtJS 4.2.1.883 to support property as an array (instead of single text value).
*/
createFilterFn: function() { // MUST return a function
/*
// ORIGINAL 4.2.1 FUNCTION:
var me = this,
matcher = me.createValueMatcher(),
property = me.property;

if (me.operator) {
return me.operatorFns[me.operator];
} else {
return function(item) {
var value = me.getRoot(item)[property];
return matcher === null ? value === null : matcher.test(value);
};
}
*/
var me = this,
matcher = me.createValueMatcher(),
property = !Ext.isArray(me.property) ? me.property.split(',') : me.property;

if (me.operator) {
return me.operatorFns[me.operator];
} else {
return function(item) { // item = Ext.data.Record
var i, p, value, chk,
hasmatch = false;

for(i=0; i < property.length; i++) {
p = property[i];
value = me.getRoot(item);
chk = value[p];

if(matcher.test(chk)) {
hasmatch = true;
break;
}
}
return matcher === null ? value === null : hasmatch;

};
}
}
});


// used for envitz.view.ComboSearchMultiFields extension
Ext.override(Ext.data.Store,{
/**
* @private
* Returns a filter function used to test a the given property's value. Defers most of the work to
* Ext.util.MixedCollection's createValueMatcher function.
*
* @param {String} property The property to create the filter function for
* @param {String/RegExp} value The string/regex to compare the property value to
* @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
* @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
* @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
* Ignored if anyMatch is true.

* Modified from ExtJS 4.2.1.883 to support property as an array (instead of single text value).
*/
createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
if (Ext.isEmpty(value)) {
return false;
}
property = !Ext.isArray(property) ? property.split(',') : property;

value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);


return function(r) {
//return value.test(r.data[property]);
var i, p,
hasmatch = false;

for(i=0; i < property.length; i++) {
p = property[i];


if(value.test(r.data[p])) {
hasmatch = true;
break;
}
}
return hasmatch;

};
},
});

Then, override the doLocalQuery() method:


Ext.define('MyApp.view.ComboSearchMultiFields', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.combosearchmultifields',

selectOnFocus: true,
anyMatch: true,
queryMode: 'local',
typeAhead: true,
typeAheadDelay: 200,

initComponent: function() {
var me = this;

Ext.applyIf(me, {
searchFields: [

]
});

me.callParent(arguments);
},

doLocalQuery: function(queryPlan) {
/**
* Overridden to prefer searchFields array over displayFields for search vector.
* Searches all store fields in searchFields for matching data. If searchFields is NULL then use displayField
*
* This function requires the Ext.util.Filter to be overridden to support property as an array
* @protected
*/

var me = this,
displayField = me.displayField,
searchFields = (me.searchFields === undefined ? me.displayField : me.searchFields),
queryString = queryPlan.query;

// Create our filter when first needed
if (!me.queryFilter) {
// Create the filter that we will use during typing to filter the Store
me.queryFilter = new Ext.util.Filter({
id: me.id + '-query-filter',
anyMatch: me.anyMatch,
caseSensitive: me.caseSensitive,
root: 'data',
property: searchFields
});
me.store.addFilter(me.queryFilter, false);
}

// Querying by a string...
if (queryString || !queryPlan.forceAll) {
me.queryFilter.disabled = false;
me.queryFilter.setValue(me.enableRegEx ? new RegExp(queryString) : queryString);
}

// If forceAll being used, or no query string, disable the filter
else {
me.queryFilter.disabled = true;
}

// Filter the Store according to the updated filter
me.store.filter();

// Expand after adjusting the filter unless there are no matches
if (me.store.getCount()) {
me.expand();
} else {
me.collapse();
}

me.afterQuery(queryPlan);
}

});

And the sample code that calls it:


{
xtype: 'combosearchmultifields',
searchFields: [
'geoLang_name',
'geoLang_roman',
'geoLang_ISO639'
],
maxHeight: 200,
minWidth: 300,
fieldLabel: 'Language',
name: 'usr_lang',
allowBlank: false,
emptyText: 'Select Language',
enforceMaxLength: false,
displayField: 'geoLang_name',
store: 'languages',
valueField: 'geoLang_id',
valueNotFoundText: 'Language not supported, please select from this list'
},



So, now when I type in my text, the string is used to search against all searchFields. Even if the user does not type in the kanji for Japanese, the combo will filter for all matches across all available searchFields!

P.S. {I created this extension in Sencha Architect! ;) }

This works great for me. Hope it helps someone else out there.

abhimanyus22
18 Sep 2014, 8:29 AM
Ext
.define(
'GMR.app.view.Components.SearchCombo',
{
extend : 'Ext.form.field.ComboBox',
alias : 'widget.searchCombo',
selectOnFocus : true,
anyMatch : true,
queryMode : 'local',
typeAhead : true,
typeAheadDelay : 200,


listConfig : {
loadingText : 'Searching...',
emptyText : 'No matching posts found.',


getInnerTpl : function() {
return '<div class="search-item">'
+ '<h3><span>{id}</span>&nbsp&nbsp|&nbsp&nbsp<span>{idSpace}&nbsp&nbsp|&nbsp&nbsp</span><span>{modelName}</span></h3>'
+ '</div>';


},
},
initComponent : function() {
var me = this;


Ext.applyIf(me, {
searchFields : [


]
});


me.callParent(arguments);
},



doQuery : function(queryString, forceAll, rawQuery) {
/**
* Overridden to prefer searchFields array over
* displayFields for search vector. Searches all
* store fields in searchFields for matching data.
* If searchFields is NULL then use displayField
*
* This function requires the Ext.util.Filter to be
* overridden to support property as an array
*
* @protected
*/


var me = this, displayField = me.displayField, searchFields = (me.searchFields === undefined ? me.displayField
: me.searchFields), queryString = queryString;


// Create our filter when first needed
if (!me.queryFilter) {
// Create the filter that we will use during
// typing to filter the Store
me.queryFilter = new Ext.util.Filter({
id : me.id + '-query-filter',
anyMatch : me.anyMatch,
caseSensitive : me.caseSensitive,
root : 'data',
property : searchFields
});
me.store.filter(me.queryFilter,queryString);
}


// Querying by a string...
if (queryString || !forceAll) {
me.queryFilter.disabled = false;
me.queryFilter
.setValue(queryString);
}


// If forceAll being used, or no query string,
// disable the filter
else {
me.queryFilter.disabled = true;
}


// Filter the Store according to the updated filter
me.store.filter();


// Expand after adjusting the filter unless there
// are no matches
if (me.store.getCount()) {
me.expand();
} else {
me.collapse();
}


//me.afterQuery(queryPlan);
return true;
}

});

I am trying to under stand your code but I am not getting methods used in you code "doLocalQuery"
I am new to ext js ...Am I missing something

abhimanyus22
18 Sep 2014, 8:31 AM
I am new to ext js I am trying to understand you code but ,I am not getting the methods you are using my version 4.1.3I did not get the method doLocalQuery in my combo Ext .define( 'GMR.app.view.Components.SearchCombo', { extend : 'Ext.form.field.ComboBox', alias : 'widget.searchCombo', selectOnFocus : true, anyMatch : true, queryMode : 'local', typeAhead : true, typeAheadDelay : 200, listConfig : { loadingText : 'Searching...', emptyText : 'No matching posts found.', getInnerTpl : function() { return '' + '{id}&nbsp&nbsp|&nbsp&nbsp{idSpace}&nbsp&nbsp|&nbsp&nbsp{modelName}' + '
'; }, }, initComponent : function() { var me = this; Ext.applyIf(me, { searchFields : [ ] }); me.callParent(arguments); }, doQuery : function(queryString, forceAll, rawQuery) { /** * Overridden to prefer searchFields array over * displayFields for search vector. Searches all * store fields in searchFields for matching data. * If searchFields is NULL then use displayField * * This function requires the Ext.util.Filter to be * overridden to support property as an array * * @protected */ var me = this, displayField = me.displayField, searchFields = (me.searchFields === undefined ? me.displayField : me.searchFields), queryString = queryString; // Create our filter when first needed if (!me.queryFilter) { // Create the filter that we will use during // typing to filter the Store me.queryFilter = new Ext.util.Filter({ id : me.id + '-query-filter', anyMatch : me.anyMatch, caseSensitive : me.caseSensitive, root : 'data', property : searchFields }); me.store.filter(me.queryFilter,queryString); } // Querying by a string... if (queryString || !forceAll) { me.queryFilter.disabled = false; me.queryFilter .setValue(queryString); } // If forceAll being used, or no query string, // disable the filter else { me.queryFilter.disabled = true; } // Filter the Store according to the updated filter me.store.filter(); // Expand after adjusting the filter unless there // are no matches if (me.store.getCount()) { me.expand(); } else { me.collapse(); } //me.afterQuery(queryPlan); return true; } });

abhimanyus22
18 Sep 2014, 8:32 AM
hi

abhimanyus22
18 Sep 2014, 8:37 AM
Hi lorezyra I am new to ext js and I am working on implementing similar combo ,I am missing 3 things1. where are you over riding the filter (in your store or combo)2. What do you mean when you say you are overriding the data.Store3. I am not able to find method doLocalQuery in combo box (my EXT js version is 4.1.3)

lorezyra
18 Sep 2014, 5:37 PM
First of all, use the code or PHP tags so that your example code is readable. Until then, you should re-read my solution as I provide everything needed to get it running.