PDA

View Full Version : [OPEN] [UNKNOWN][3.0.1] Calling setValue(0) on ComboBox sets value to option with "" value



paulcrowder
1 Sep 2009, 11:19 AM
Ext version tested:


Ext 3.0.0
Ext 3.0.1



Adapter used:


ext



css used:


only default ext-all.css





Browser versions tested against:


IE8
FF3.5.2



Operating System:


WinXP Pro



Description:


When a ComboBox has both a record in its store with a value of 0 (the number) and a a record with the value of "" (empty string), and the record in the store with the empty string value is before the record with the 0 value, calling setValue(0) sets the combo's value to the record with the empty string value.



Test Case:



<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Grid Test</title>

<!-- ** CSS ** -->
<!-- base library -->
<link type="text/css" rel="stylesheet" href="extjs/ext-3.0.1/resources/css/ext-all.css" />

<!-- ** Javascript ** -->
<!-- base library -->
<script type="text/javascript" src="extjs/ext-3.0.1/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="extjs/ext-3.0.1/ext-all-debug.js"></script>

<!-- page specific -->
<script type="text/javascript">
Ext.onReady(function () {

Ext.BLANK_IMAGE_URL = "extjs/ext-3.0.0/resources/images/default/s.gif";

var store = new Ext.data.JsonStore({
fields: ["lbl", "val"],
root: "items"
});

store.loadData({
items: [
{lbl: "Empty String Option", val: ""},
{lbl: "Zero Option", val: 0},
{lbl: "One Option", val: 1}
]
});

var combo = new Ext.form.ComboBox({
applyTo: "combo-example",
disableKeyFilter: true,
displayField: "lbl",
emptyText: "",
mode: "local",
store: store,
triggerAction: "all",
valueField: "val",
valueNotFoundText: ""
});

var button = new Ext.Button({
applyTo: "set-button",
text: "Set combo to Zero Option",
handler: function () {
combo.setValue(0);
}
});
});
</script>
</head>
<body>
<input type="text" id="combo-example" />
<br />
<div id="set-button"></div>
</body>
</html>
Steps to reproduce the problem:


Create a ComboBox using a JsonStore that contains a record with a 0 value and a record with an empty string value.
Call setValue(0) on the ComboBox.



The result that was expected:


The record with the value of 0 should be selected in the ComboBox.



The result that occurs instead:


The record with the empty string value is selected in the ComboBox.



Debugging already done:


The error exists in findRecord() method in Combo.js. The problem is that 0 and empty string are both "falsey" values, and the use of double-equals is evaluating them to be the same value.



Possible fix:


Change line 934 (in 3.0.1 source) to use triple-equals instead of double-equals to avoid type coercion between "falsey" values.

Condor
1 Sep 2009, 11:35 PM
Your suggestion would result in:

Ext.override(Ext.form.ComboBox, {
findRecord : function(prop, value){
var index = this.store.findExact(prop, value);
return index >= 0 ? this.store.getAt(index) : undefined;
}
});

But this isn't fully backward compatible (e.g. a combobox without a valueField where the store displayField contains numbers instead of strings wouldn't work anymore).

I would recommend:

Ext.override(Ext.form.ComboBox, {
findRecord : function(prop, value){
var record;
if(this.store.getCount() > 0){
this.store.each(function(r){
if(String(r.data[prop]) == String(value)){
record = r;
return false;
}
});
}
return record;
}
});

mystix
2 Sep 2009, 2:34 AM
how about breaking that comparison function out into the Record itself?


Ext.override(Ext.data.Record, {
// custom Record data comparator
// by @mystix -- see 372751 for more info
compare: function(v1, v2) {
return String(v1) == String(v2) ? 0 : -1; // or whatever comparison mechanism tickles your fancy
},

set: function(name, value) {
if (this.compare(this.data[name], value) === 0) {
return;
}

this.dirty = true;
if (!this.modified) {
this.modified = {};
}
if (typeof this.modified[name] == 'undefined') {
this.modified[name] = this.data[name];
}
this.data[name] = value;
if (!this.editing) {
this.afterEdit();
}
}
});

Ext.override(Ext.form.ComboBox, {
findRecord : function(prop, value){
var record;
if(this.store.getCount() > 0){
this.store.each(function(r){
if(r.compare(r.data[prop], value) === 0){
record = r;
return false;
}
});
}
return record;
}
});

Animal
2 Sep 2009, 2:42 AM
I like the idea of allowing implementation of a compare method in Record.

It allows sorting and filtering to be implemented in a very simple way by any widgets based around Stores, and this is a constant point of contention. Very good idea.

Condor
2 Sep 2009, 2:57 AM
Also a good solution!

I would prefer a compare function with different parameters (name and value):

Ext.override(Ext.data.Record, {
compare: function(name, value) {
var v1 = String(this.data[name]), v2 = String(value);
return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
},
set: function(name, value) {
if (!this.compare(name, value)) {
return;
}
this.dirty = true;
if (!this.modified) {
this.modified = {};
}
if (typeof this.modified[name] == 'undefined') {
this.modified[name] = this.data[name];
}
this.data[name] = value;
if (!this.editing) {
this.afterEdit();
}
}
});

Ext.override(Ext.form.ComboBox, {
findRecord : function(prop, value){
var record;
if(this.store.getCount() > 0){
this.store.each(function(r){
if(!r.compare(prop, value)){
record = r;
return false;
}
});
}
return record;
}
});

(that way you can write a compare function that only does a different comparison for a single field)

mystix
2 Sep 2009, 3:08 AM
Also a good solution!

I would prefer a compare function with different parameters (name and value):


that looks fine, though i would strongly recommend sticking with standard comparator return values of -1, 0, and 1, which will allow the comparator to be used in sorting functions, as opposed to a simple boolean.

Condor
2 Sep 2009, 3:31 AM
Yeah, I came to the same conclusion and already updated my previous post.