今天同事说combobox有两个选项选出来的id是一样的,看了一下还确实是两个选项选出来的id一样(store里面这两个record的值确实不一样),头一次遇见这种诡异的bug,但是有个特征就是这两个选项的字是一样的,就是displayField的值相同才会出现这种bug。
查了一下源码(Combo.js)里面没有太特殊的实现,有几处值得怀疑的
// private
onSelect : function(record, index){
if(this.fireEvent('beforeselect', this, record, index) !== false){
this.setValue(record.data[this.valueField || this.displayField]);
this.collapse();
this.fireEvent('select', this, record, index);
}
},
assertValue : function(){
var val = this.getRawValue(),
rec = this.findRecord(this.displayField, val);
if(!rec && this.forceSelection){
if(val.length > 0 && val != this.emptyText){
this.el.dom.value = Ext.value(this.lastSelectionText, '');
this.applyEmptyText();
}else{
this.clearValue();
}
}else{
if(rec){
// onSelect may have already set the value and by doing so
// set the display field properly. Let's not wipe out the
// valueField here by just sending the displayField.
if (val == rec.get(this.displayField) && this.value == rec.get(this.valueField)){
return;
}
val = rec.get(this.valueField || this.displayField);
}
this.setValue(val);
}
},
来分析一下,onSelect的时候是先setValue然后fireEvent select事件,我让同事实验了一下,发现在select事件响应函数中取得的选择是对的,那是不是setValue的时候出错了,很有可能,因为getValue就是返回之前setValue设置的值
看一下setValue的实现:
setValue : function(v){
var text = v;
if(this.valueField){
var r = this.findRecord(this.valueField, v);
if(r){
text = r.data[this.displayField];
}else if(Ext.isDefined(this.valueNotFoundText)){
text = this.valueNotFoundText;
}
}
this.lastSelectionText = text;
if(this.hiddenField){
this.hiddenField.value = Ext.value(v, '');
}
Ext.form.ComboBox.superclass.setValue.call(this, text);
this.value = v;
return this;
},
setValue里面最可疑的就是那个this.findRecord,
findRecord : function(prop, value){
var record;
if(this.store.getCount() > 0){
this.store.each(function(r){
if(r.data[prop] == value){
record = r;
return false;
}
});
}
return record;
},
findRecord使用record中的一个字段做循环比较,是不是哪里弄错了误用了displayField做为比较的字段,这里面displayField是有相同值的,那肯定findRecord的结果是第一个匹配的record了,但是查了一下这个过程,确实没有误用的地方。
这个时候我同事说在 combobox的blur事件之后 再调用这个combobox的getValue就不对了,之前提到的可疑点assertValue 在beforeBlur的时候有调用,而且就只有这一个地方调用,firebug调试了一下果然是这个函数的问题,这个函数的实际运行结果是走到了函数中setValue那一行,而setValue设置的值却是用getRawValue的结果去findRecord,之前说了,如果findRecord用displayField作为比较字段,取出来的肯定是displayField所有相同值中排在最前面的那个值。 让同事又增加了一个相同的displayField值试验了一下,果然如此。
但是有个疑问:assertValue这个函数从名字上看不出什么作用,那beforeBlur中为什么要调用这个校验函数呢,结合这个函数中一个if语句判断了this.forceSelection 突然明白了:
如果forceSelection为false,Extjs的combobox是既可以选择,又可以手动输入值的,这个时候它就需要校验你手动输入的值,是否在displayField中已经出现,如果已经出现,那提交的时候需要提交这个displayField对应的valueField,如果没有出现,那么直接提交手动输入的内容。这个时候,如果displayField中有两个相同的值,这个combobox的value 就被设为第一个相同值的id了,由此导致了这个bug
但是想想,这真的算个bug吗,当用户点开下拉框,看到两个相同的选项,他应该选哪个?在Extjs出bug之前,我们已经犯了错误。