Success! Looks like we've fixed this one. According to our records the fix was applied for
EXTJS-8947
in
4.2.1.744.
-
4.2 ComboBox Query Enhancements
First, please add config options to combobox for anyMatch and caseSensitive, which are passed to the stored queryFilter.
Second, please add code to the top of doQuery for:
Code:
if( queryString.length < this.minChars ) {
forceAll = true;
}
Because when using field validation (allowBlank: false, forceSelection: true, typeAhead: true), and deleting values typed into the combo box, the full list does not appear (basically forceAll isn't being forced to true).
Thanks,
Brian
-
Good ideas. The 4.2.0 release is frozen, but I'll add a ticket.
-
I think what you need to provide the expected user experience is below. Let me know if that works for you.
Code:
Ext.override(Ext.form.field.ComboBox, {
doQuery: function(queryString, forceAll, rawQuery) {
queryString = queryString || '';
// store in object and pass by reference in 'beforequery'
// so that client code can modify values.
var me = this,
qe = {
query: queryString,
forceAll: forceAll,
combo: me,
cancel: false
},
store = me.store,
isLocalMode = me.queryMode === 'local';
if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
return false;
}
// get back out possibly modified values
queryString = qe.query;
forceAll = qe.forceAll;
// Re-filter if forceAll being passed
// or query string is at least the required length
// or dropdown is visible and the user will expect feedback - eg erasing and widening the query.
if (forceAll || (queryString.length >= me.minChars) || (me.picker && me.picker.isVisible())) {
// expand before starting query so LoadMask can position itself correctly
me.expand();
// make sure they aren't querying the same thing
if (!me.queryCaching || me.lastQuery !== queryString) {
me.lastQuery = queryString;
if (isLocalMode) {
// Querying by a typed string...
if (queryString || !forceAll) {
// Ensure queryFilter is enabled and set the new value
me.queryFilter.disabled = false;
me.queryFilter.setValue(me.enableRegEx ? new RegExp(queryString) : queryString);
}
// Disable query value filter if no query string or forceAll passed
else {
me.queryFilter.disabled = true;
}
// Filter the Store according to the updated filter
store.filter();
} else {
// Set flag for onLoad handling to know how the Store was loaded
me.rawQuery = rawQuery;
// In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
// and these are automatically passed as params with every load call, so we do *not* call clearFilter.
if (me.pageSize) {
// if we're paging, we've changed the query so start at page 1.
me.loadPage(1);
} else {
store.load({
params: me.getParams(queryString)
});
}
}
}
// Clear current selection if it does not match the current value in the field
if (me.getRawValue() !== me.getDisplayValue()) {
me.ignoreSelection++;
me.picker.getSelectionModel().deselectAll();
me.ignoreSelection--;
}
if (isLocalMode) {
me.doAutoSelect();
}
if (me.typeAhead) {
me.doTypeAhead();
}
}
return true;
}
});
-
My understanding of minChars is that it's to be used with very large, remote datasets where a short query is too small to meaningfully filter the results set. It kind of defeats the purpose if the user can just delete their way around it.
Could a beforequery listener not be used to achieve the desired effect without any modifications to combobox itself?
-
That's true.
But for local datasets, it's intuitive that the query widens as you erase.
Perhaps the fix should be
Code:
if (forceAll || (queryString.length >= me.minChars) || (isLocalMode && me.picker && me.picker.isVisible())) {
-

Originally Posted by
Animal
That's true.
But for local datasets, it's intuitive that the query widens as you erase.
Perhaps the fix should be
Code:
if (forceAll || (queryString.length >= me.minChars) || (isLocalMode && me.picker && me.picker.isVisible())) {
Sounds right as a more general solution. For me, I've got a custom AnyMatchComboBox that I use only for local data sets. So for simplicity, I just have this (I'm using CoffeeScript):
Code:
config:
anyMatch: true
caseSensitive: false
initComponent: ->
@callParent( [ arguments ] )
@queryFilter.anyMatch = @anyMatch
@queryFilter.caseSensitive = @caseSensitive
doQuery: ( queryString="", forceAll, rawQuery ) ->
forceAll = true if queryString.length < @minChars
@callParent( queryString, forceAll, rawQuery )
return true
-
I'm not sure it's intuitive for minChars to behave differently for local and remote data. Personally I think it would make more sense for the picker to be automatically hidden if the number of characters drops below minChars, though that would be a change of long-established behaviour.
In my opinion the main change that is required is to split up the monolith of doQuery to give a few nice template methods for hooking in these kinds of customizations. I've lost count of the number of times that I've seen someone have to copy and paste that whole crazy method just to make a tiny change for their specific use-case.
A few overridable methods and a bit of documentation and everything would be super-flexible without adding a load of new config options. The documentation here is key, e.g. the existing beforequery event is massively powerful but very few people understand how to use it.
-
Yes, breaking that up so that it could be operated by template methods would be a great solution to this one.
Configuring the Filter with matchAny and caseSensitive is done. I will look at breaking that method up to allow finer control.
-
How's this for an idea?
The top of doQuery is simply:
Code:
// Decide if, and how we are going to query the store
queryPlan = me.beforeQuery(queryString, forceAll, rawQuery);
if (queryPlan === false || queryPlan.cancel) {
return false;
}
After that, it continues with the test for previously cached queryString, and the bifurcation for isLocalMode/non-local.
That queryPlan is that object containing the queryString, cancel flag, the forceAll flag.
So the beforeQuery method can be overridden. The base implementation does the default:
Code:
beforeQuery: function(queryString, forceAll, rawQuery) {
var me = this,
queryPlan = {
query: queryString,
forceAll: forceAll,
combo: me,
cancel: false
};
if (me.fireEvent('beforequery', queryPlan) !== false) {
if (!queryPlan.cancel && (queryPlan.forceAll || (queryPlan.queryString.length >= me.minChars))) {
return queryPlan;
}
}
return false;
},
And a bit more documentation, and perhaps some examplage.
-
Actually, better would be
Code:
beforeQuery: function(queryString, forceAll, rawQuery) {
var me = this,
queryPlan = {
query: queryString,
forceAll: forceAll,
combo: me,
cancel: false
};
// Allow beforequery event to veto by returning false
if (me.fireEvent('beforequery', queryPlan) == false) {
queryPlan.cancel = true;
}
// Allow beforequery event to veto by returning setting the cancel flag
else if (!queryPlan.cancel) {
// If the minChars threshold has not been met, and we're not forcing an "all" query, cancel the query
if (queryPlan.query.length < me.minChars && !queryPlan.forceAll) {
queryPlan.cancel = true;
}
}
return queryPlan;
},
So that override can always rely on a plan returning with the information in it rather than false.
They may still want to overrule any cancel flag and manipulate the plan.