PDA

View Full Version : ComboBox Bug?



Brendan Carroll
12 Jul 2007, 9:50 PM
I'm experiencing an odd bug where a combobox doesn't display the correct record but does catch it and pass it to the onselect handler. If I use firebug I see the record passed to the event handelr is correct. However, the combobox doesn't display the record. The following describes how I'm employing the comobox in question:

First I load an empty datastore on init in an object called BmpModelStore:


var recorddef = Ext.data.Record.create([
{name: 'TableName', name: 'FieldName'}, {name: 'DataType'}
]);

_ds_fields = new Ext.data.JsonStore({
root: 'rows'
},recorddef);


getFields : function() {
return _ds_fields;
}

Next I build a form in my Viewer object init and add a number of items:


_frm_fields = new Ext.form.ComboBox({
fieldLabel: 'Field',
store: BmpModelStore.getFields(),
displayField:'FieldName',
valueField:'TableName',
triggerAction: 'all',
typeAhead: true,
mode: 'local',
emptyText:'-Choose One-',
selectOnFocus:true,
readonly:true,
disabled:true,
lazyRender: true
});


_frm_fields.on('select', this.cmbSelectField);

At this point the combo references an empty datastore and has a select handler. After all the loading is completed and the form rendered i then add recoreds to the store passing in a json string:


loadLUT : function(jsnObj) {
var j = Ext.util.JSON.decode(jsnObj);
_ds_fields.removeAll();
for (var i=0;i<j.rows.length;i++)
{
var theRec = j.rows[i];
var newRec = new Ext.data.Record({ID: i, TableName: theRec.TableName, FieldName: theRec.FieldName, DataType: theRec.DataType});
_ds_fields.add(newRec);
}

In firebug all the records are present and accounted for. As the user works through the page they click radio inputs and this triggers filtering of the combox's data store:


//clear and filter fields combobox
_frm_fields.collapse();
_frm_fields.clearValue();
_frm_fields.reset();

_frm_fields.store.filter('TableName',targetId,true);

//search filter is set now enable fields combo
_frm_fields.enable();

Again, in Firebug, the filtering appears to work as expected and the combobox is updated with a subset of the datastore. Everything seems reasonablly well up to this point. When the user selects an item in the combo, however, a random item from the list is displayed, not the item the user selected.



///handle select event for field combo
cmbSelectField : function(cmb, rec, idx) {

//select table and field...set global
_fieldname = rec.data.FieldName;
_tablename = rec.data.TableName;
_datatype = rec.data.DataType;

cmb.setValue(_tablename);

_tmpTotals.overwrite('recDataType', {
id:'tp1',
textField:_fieldname,
textType:_datatype
});

//check if anything is in the array...if not emtpy we'll need an andor
if(_sqlArray)
{
_frm_andor.enable();
_tmpTotals.overwrite('recTotals', {
id:'tp2',
text:'Total Records'
});
}

//search field and type is set now enable operators combo
_frm_ops.enable();

},

Again, in Firebug, the rec passed in is correct and i have a template that shows it. Any thoughts?

jack.slocum
12 Jul 2007, 10:19 PM
This could be an issue:

var recorddef = Ext.data.Record.create([
{name: 'TableName', name: 'FieldName'}, {name: 'DataType'}
]);

Both fields are defined as 1.

Brendan Carroll
13 Jul 2007, 5:21 AM
Good catch. I tried it with the corrected record def but am still seeing the same issue. I also tried adding a UniqueID to each record to see if that would afffect a change. Not sure if this is an idicator of the issue but it appears the combo in question always displays the first record in the filtered list. Aslo, if I type in the combo, type ahead filters correctly and, of course, I end up with the field I selected dispalyed correctly in the combo. It just the expand, dropdown selection function that's buggy.

Brendan Carroll
13 Jul 2007, 8:54 AM
This input control seems to have some real issues. Not sure if its how I'm using it but the filters set on comboboxs don't seem to persist very well and are seemingly reset at random. The Chage event never seems to fire and overall buggieness is about a 7+. Hopefully these issues will be addressed....prior to 2.0.

tryanDLS
13 Jul 2007, 9:01 AM
The change event only fires when the user types in the input field - in your case readonly:true, it will never happen. The select event is fired when a choice is made from the dropdown.

mystix
13 Jul 2007, 10:17 AM
some things i noticed in your code
(i'll assume you're referring to the Ext 1.1 RC1 release since you've made no mention of the build no. anywhere):

[1st code chunk]
the JsonStore constructor accepts only 1 config argument, so your recorddef never gets passed to the JsonStore's underlying Store.


// incorrect
var recorddef = Ext.data.Record.create([
// ??? // <== you seem to be missing an ID field which you reference in your Json object in the 3rd code chunk
{name: 'TableName'},
{name: 'FieldName'},
{name: 'DataType'}
]);
_ds_fields = new Ext.data.JsonStore({
root: 'rows'
}, recorddef); // <== recorddef never gets passed to underlying Store


// should be
_ds_fields = new Ext.data.JsonStore({
root: 'rows',
fields: [ // <== equivalent to Record definition, and gets passed to the underlying JsonReader
{name: 'ID'},
{name: 'TableName'},
{name: 'FieldName'},
{name: 'DataType'}
]
});


[3rd code chunk]


// incorrect
loadLUT : function(jsnObj) {
var j = Ext.util.JSON.decode(jsnObj);
_ds_fields.removeAll(); // <== unnecessary
for (var i = 0; i < j.rows.length; i++) { // <== unnecessary loop
var theRec = j.rows[i];
var newRec = new Ext.data.Record({ // <== that's not how you create new Records
ID: i,
TableName: theRec.TableName,
FieldName: theRec.FieldName,
DataType: theRec.DataType
});

_ds_fields.add(newRec);
}
}


// should be
loadLUT : function(jsnObj) {
var j = Ext.util.JSON.decode(jsnObj);

// simply load the json data; the underlying JsonReader already knows how to read the data
// because we already defined it in the 1st code chunk when we initially created the JsonStore.
// also, the Store's loadData() method wipes out existing data by default, so there's no need to call removeAll()
_ds_fields.loadData(j);
}


[final code chunk]

cmbSelectField : function(cmb, rec, idx) {
_fieldname = rec.data.FieldName;
_tablename = rec.data.TableName;
_datatype = rec.data.DataType;

cmb.setValue(_tablename); // <== why do this again when it's being done for you automatically onSelect?

_tmpTotals.overwrite('recDataType', {
id:'tp1',
textField:_fieldname,
textType:_datatype
});

if (_sqlArray) {
_frm_andor.enable();
_tmpTotals.overwrite('recTotals', {
id:'tp2',
text:'Total Records'
});
}

_frm_ops.enable();
}

hope this helps.

p.s. pls correct me if i'm wrong.

Brendan Carroll
13 Jul 2007, 11:10 AM
Ok I've made the updates but still have the same issue with the combo. Some of the code was going through testing. Here's how it looks now.

The original data store definition:


_ds_fields = new Ext.data.JsonStore({
root: 'rows',
id: 'UniqueId',
fields: [
{name: 'UniqueId', type: 'int'},
{name: 'TableName', type: 'string'},
{name: 'FieldName', type: 'string'},
{name: 'DataType', type: 'string'}
]
});

The function to get the datastore:


getFields : function() {
return _ds_fields;
}

The combobox definiton:


_frm_fields = new Ext.form.ComboBox({
fieldLabel: 'Field',
store: BmpModelStore.getFields(),
displayField:'FieldName',
valueField:'TableName',
triggerAction: 'all',
typeAhead: true,
mode: 'local',
emptyText:'-Choose One-',
selectOnFocus:false,
readonly:true,
editable:true,
disabled:true,
lazyRender: true
});

The load routine for populating the datastore:


loadLUT : function(jsnObj) {
_ds_fields.loadData(Ext.util.JSON.decode(jsnObj));
}

The click event that filters the combobox's datastore:


//radio click event
function radioClicked(e, target, options) {

var targetId = e.getTarget().id;
var targetName = e.getTarget().name;

//disable all combos
_frm_fields.disable();
_frm_vals.disable();
_frm_ops.disable();
_frm_andor.disable();

//clear values combobox
_frm_vals.collapse();
_frm_vals.clearValue();
_frm_vals.reset();

//clear operators combobox
_frm_ops.collapse();
_frm_ops.clearValue();
_frm_ops.reset();

//clear and filter fields combobox
_frm_fields.collapse();
_frm_fields.clearValue();
_frm_fields.reset();

_frm_fields.store.filter('TableName',targetId,true);

//search filter is set now enable fields combo
_frm_fields.enable();
}

Finally, the event to handle onselect



//handle select event for field combo
function cmbSelectField(cmb, rec, idx) {

//select table and field...set global
_fieldname = rec.data.FieldName;
_tablename = rec.data.TableName;
_datatype = rec.data.DataType;

_tmpTotals.overwrite('recDataType', {
id:'tp1',
textField:_fieldname,
textType:_datatype
});

//check if anything is in the array...if not emtpy we'll need an andor
if(_sqlArray)
{
_frm_andor.enable();
_tmpTotals.overwrite('recTotals', {
id:'tp2',
text:'Total Records'
});
}

//search field and type is set now enable operators combo
_frm_ops.enable();

}

I've noticed that the first time the user click the radio, the filter doesn't seem to take. After the inital radio click, the filter works as expected. Nevertheless, no matter which item the user click on the combo list, it reverts back to displaying the first item but passes the correct record to the select handler. So, despite the code changes, the problem persists.

Thanks for the help. this is killin me.

Brendan Carroll
13 Jul 2007, 11:33 AM
I forgot to mention, I'm using the latest ext-1.1-rc1 release in both IE and FF.

mystix
13 Jul 2007, 9:43 PM
i'm guessing: in your ComboBox data, the values for TableName (i.e. the valueField) aren't unique, are they?

i'm reproducing the code for the setValue() method (called onSelect to set the display and valueFields) and the code for the private method findRecord() (called internally by setValue()) for your perusal
setValue : function(v){
var text = v;
if(this.valueField){
var r = this.findRecord(this.valueField, v); // <== here's your problem -- returns the first record whose valueField matches
if(r){
text = r.data[this.displayField];
}else if(this.valueNotFoundText !== undefined){
text = this.valueNotFoundText;
}
}
this.lastSelectionText = text;
if(this.hiddenField){
this.hiddenField.value = v;
}
Ext.form.ComboBox.superclass.setValue.call(this, text);
this.value = v;
},

// private
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;
},
if i guessed right, you should be able to work something out from here on.
hope this helps ;)

[edit]
i'll take a look at this when i get more time. seems kind of fishy to me... :-?

mystix
13 Jul 2007, 10:44 PM
in the meantime, try this ComboBox override and let me know how it works for you
Ext.override(Ext.form.ComboBox, {
onSelect : function(record, index) {
if (this.fireEvent('beforeselect', this, record, index) !== false) {
this.setValue(record); // pass in selected record directly, since we already have it
this.collapse();
this.fireEvent('select', this, record, index);
}
},

setValue : function(v) { // v can either be a simple value or an Ext.data.Record in the ComboBox's store
var record;
if (typeof(v) == 'object') {
record = v; // use record to set values
v = record.data[this.valueField || this.displayField] || ''; // handle case where passed in Record does not exist in Store
}
var text = v;
if (this.valueField) {
if (record) {
if (this.store.indexOf(record) != -1) { // ensure passed record exists in this ComboBox's Store
text = record.data[this.displayField];
} else if (this.valueNotFoundText !== undefined) {
text = this.valueNotFoundText;
}
} else {
var r = this.findRecord(this.valueField, v);
if (r) {
text = r.data[this.displayField];
}else if(this.valueNotFoundText !== undefined){
text = this.valueNotFoundText;
}
}
}
this.lastSelectionText = text;
if (this.hiddenField) {
this.hiddenField.value = v;
}
Ext.form.ComboBox.superclass.setValue.call(this, text);
this.value = v;
}
});

Brendan Carroll
14 Jul 2007, 9:21 AM
Your assuption is correct. The filter is by TableName and returns the fields for the selected table. TableName is not unique. I'll try the code. Thx.

Brendan Carroll
14 Jul 2007, 10:23 AM
Thanks for the code. However, it seems to populte the combo with the value field and not t he display field. Thx.

Brendan Carroll
14 Jul 2007, 10:43 AM
Thanks for all the help. By modifying your setValue override, I was able to get it. You guys are great! Keep turnin' out the good stuff....:D




setValue : function(v) {
var record;
var sVal;
if (typeof(v) == 'object')
{
record = v; // use record to set values
v = record.data[this.valueField || this.displayField] || ''; // handle case where passed in Record does not exist in Store
sVal = record.data[this.displayField];
}
var text = v;
if (this.valueField)
{
if (record)
{
if (this.store.indexOf(v) != -1)
{ // ensure passed record exists in this ComboBox's Store
text = record.data[this.displayField];
}
else if (this.valueNotFoundText !== undefined)
{
text = this.valueNotFoundText;
}
}
else
{

//var q = this.findRecord(this.displayField, v);
var r = this.findRecord(this.valueField, v);
if(r)
{
text = r.data[this.displayField];
}
else if(this.valueNotFoundText !== undefined)
{
text = this.valueNotFoundText;
}
}
}
this.lastSelectionText = text;
if (this.hiddenField)
{
this.hiddenField.value = v;
}
Ext.form.ComboBox.superclass.setValue.call(this, sVal);
this.value = v;
}

mystix
14 Jul 2007, 9:53 PM
i found the bug in my setValue() override, and i've corrected the code above.

this
if (this.store.indexOf(v) != -1) { // ensure passed record exists in this ComboBox's Storeshould've been this
if (this.store.indexOf(record) != -1) { // ensure passed record exists in this ComboBox's Store

your modified override breaks setValue() functionality because it's always assuming a Record is passed in as the argument to setValue and hardwires sVal to be a field from this Record.

mystix
15 Jul 2007, 8:09 PM
please note that the above override is a hack to allow duplicate values in Brendan's case.

valueField values should be unique. Duplicate values are not supported by the existing ComboBox setValue() method.

Brendan Carroll
16 Jul 2007, 7:49 PM
Thanks for helping me out with this. The fix you supplied is working great=D>. One other issue I'm having with this particular combobox is the filter I supply doesn't seem to work until after I manually click and cause a fire of the expand and collapse then go back and reclick on the radio input. Would this be related also to the fact that my filter values are not unique. In firebug it shows the filter working correctly on the inital radio click. Too, when I type in a value and then backspace to empty the combobox value, the filter seems to drop and I see all my values, unfiltered.

Thanks again.

akhalil
16 Oct 2007, 12:58 AM
It's very simple:

I've used your code like this ....
Ext.override(Ext.form.ComboBox, {
onSelect : function(record, index)
{
if (this.fireEvent('beforeselect', this, record, index) !== false) {
this.setValue(record); // pass in selected record directly, since we already have it
this.collapse();
this.fireEvent('select', this, record, index);
}


alert("override index: " + index);
},


Can you see this alert at the end of your onSelect ..... well if you use the mouse and select an option from the ComboBox then the index is correct but if you instead, start typing (autocomplete/filtering) and press enter to select an item ... well, the index is always 0 no matter what item you select ....

I'm having a night mate with this but guys - I'd really love your support and help on this one - thankyou all so very much - cheers