PDA

View Full Version : Multiselect



thejoker101
28 Oct 2007, 10:56 AM
Decided to try my hand at a multiselect component. Mine uses the regular HTML select with the multiple attribute so it will work better in a form than the component you can find in the 1.1 user extensions since it will pass the actual values and not the indexes of the selected elements (using a hidden input). Should be a lot easier to change between major versions too since it's only a few functions added onto a regular field. It's got a store backend for easy reloading of values.


Ext.ux.MultiSelect = Ext.extend(Ext.form.Field,{
allowBlank:true,
stripe: false,
oddStripeColor:false,
evenStripeColor:'#f1f2f4',
defaultAutoCreate: {tag:'select',multiple:'multiple'},
blankText:'You must select at least one option',
initComponent: function() {
if (this.name && this.name.search(/\[\]/) == -1) {
this.name = this.name+"[]";
}
Ext.ux.MultiSelect.superclass.initComponent.call(this);
this.addEvents({
'selectionchange': true
});
},
initEvents: function() {
Ext.ux.MultiSelect.superclass.initEvents.call(this);
this.selected = new Array();
if (this.transform) {
this.allowDomMove = false;
var s = Ext.getDom(this.transform);
if (!this.store) {
this.mode = 'local';
var d = [], opts = s.options;
for (var i = 0, len = opts.length; i < len; i++) {
var o = opts[i];
var value = (Ext.isIE ? o.getAttributeNode('value').specified : o.hasAttribute('value')) ? o.value : o.text;
if (o.selected) {
this.selected.push({
value: value,
index: i
});
}
d.push([value,o.text]);
}
this.store = new Ext.data.SimpleStore({
'id': 0,
fields: ['value','text'],
data: d
});
for (var i = 0, len = opts.length; i < len; i++) {
var o = opts[i];
if (o.selected) {
this.selected.push(this.store.getAt(i));
}
}
this.valueField = 'value';
this.displayField = 'text';
}
s.name = Ext.id();
if (!this.lazyRender) {
this.target = true;
this.el = Ext.DomHelper.insertBefore(s,this.autoCreate || this.defaultAutoCreate);
Ext.removeNode(s);
this.render(this.el.parentNode);
} else {
Ext.removeNode(s);
}
}
if (!this.valueField) {
this.valueField = this.displayField;
}
if (this.store) {
this.bindStore(this.store, true);
} else {
}
this.el.on('change',this.onSelectionChange,this);
},
onRender: function(ct,position) {
Ext.ux.MultiSelect.superclass.onRender.call(this, ct, position);
this.el.dom.setAttribute('multiple', 'multiple');
},
bindStore: function(store,initial) {
if (this.store && !initial) {
this.store.un('beforeload', this.onBeforeLoad, this);
this.store.un('load', this.onLoad, this);
this.store.un('loadexception', this.collapse, this);
if(!store){
this.store = null;
}
}
if (store) {
this.store = Ext.StoreMgr.lookup(store);
this.store.on('beforeload', this.onBeforeLoad, this);
this.store.on('load', this.onLoad, this);
this.store.on('loadexception', this.collapse, this);
}
},
onDestroy: function() {
this.bindStore(null);
Ext.ux.MultiSelect.superclass.onDestroy.call(this);
},
onResize: function() {
Ext.ux.MultiSelect.superclass.onResize.call(this,arguments);
},
onDisable: function() {
Ext.ux.MultiSelect.superclass.onDisable.call(this,arguments);
},
onBeforeLoad: function() {
this.selected = new Array();
/*Remove old values*/
this.el.innerHTML = '';
},
onLoad: function() {
if (this.store.getCount() > 0) {
/*Fill with new values*/
for (var i = 0, len = this.store.getCount(); i < len; i++) {
style = '';
if (this.stripe && this.evenStripeColor && (i+1)%2 == 0) {
style = 'background-color:'+this.evenStripeColor+';';
} else if (this.stripe && this.oddStripeColor && (i+1)%2 != 0) {
style = 'background-color:'+this.oddStripeColor+';';
}
var record = this.store.getAt(i);
var option = document.createElement("OPTION");
option.text= record.data[this.displayField];
option.value=record.data[this.valueField]
if (Ext.isIE) {
document.getElementById(Ext.get(this.el).id).add(option);
}
else {
document.getElementById(Ext.get(this.el).id).add(option, null);
}
}
}
},
onSelectionChange: function() {
ob = Ext.get(this.el).dom;
this.clearInvalid();
this.selected = new Array();
for (var i = 0; i < ob.options.length; i++) {
if (ob.options[i].selected) {
this.selected.push(this.store.getAt(i));
}
} // You can use the arSelected array for further processing.
this.fireEvent('selectionchange',this,this.selected);
},
getSelected: function() {
return this.selected;
},
clearSelection: function() {
ob = Ext.get(this.el).dom;
this.selected = new Array();
for (var i = 0; i < ob.options.length; i++) {
if (ob.options[i].selected) {
ob.options[i].selected = false;
}
}
}
});
Ext.reg('multiselect',Ext.ux.MultiSelect);
CSS:

.x-form-select-multiple, select.x-form-field {
background: #FFFFFF url(../images/default/form/text-bg.gif) repeat-x scroll top;
border: 1px solid #B5B8C8;
padding: 1px 3px;
}
.x-form-focus, select.x-form-focus {
border: 1px solid #7EADD9;
}
.x-form-invalid, select.x-form-invalid {
background: #FFFFFF url(../images/default/grid/invalid_line.gif) repeat-x scroll center bottom;
border: 1px solid #DD7870;
}

Let me know of any bugs.

I have an allowBlank variable, but I'm not sure whether that should be for selection count or option count being 0. I suppose probably the former since option count would only change by external forces affecting the store.

Next thing that might be nice is drag and drop which might be easy to do by just setting up the options for dragging.

Example (using xtype):

xtype: 'multiselect',
name: 'illnessHistory',
displayField:'text',
valueField:'value',
height: 50,
width: 275,
store: new Ext.data.JsonStore({
fields:['value','text'],
root:'illnesses',
url:'js/illnessHistory.php',
totalProperty:'totalCount',
autoLoad: true
}),
stripe:true,
listeners: {
selectionchange: function(select,selections) {
}
}


Or of course you can make it using the regular constructor (i.e. stuff all that into "new Ext.ux.Multiselect(/*config here*/);").

wvuong
2 Nov 2007, 1:39 PM
do you have a good example or a test case that you can post? thanks.

wvuong
2 Nov 2007, 1:50 PM
actually i just figured it out. i was passing in a simplestore object that was already loaded. your multiselect component will render the select options when the store object's load event fires. in this case, the simplestore's load event never fires so the options never get rendered.

FuryVII
5 Nov 2007, 9:47 AM
Can someone post an example? I am having problems with validation and the onload event (which was previously mentioned).

galdaka
5 Nov 2007, 9:50 AM
For user extensions should be required an example if is posible.

Thanks in advance,

wvuong
6 Nov 2007, 11:48 AM
hmmm. it works fine in firefox but ie6 complains about the regular expression check in initComponent. and then after that, it appears that the select options get mangled by DomHelper, at least according to the ie dev bar.

RSidetrack
10 Nov 2007, 4:14 PM
I have this wonderful ux added into my application but IE is having problems, as mentioned in a previous post. I took care of the regex by removing that section since I am passing in the []'s, but in IE the data isn't populating the multiselect box. Have you been able to fix this? Or does anyone have any ideas on how to fix it - as I really need this to be working soon ;)

thejoker101
10 Nov 2007, 4:38 PM
Try replacing:
if (this.name && this.name.search(/\[\]/) == -1) {

with:
if (this.name && this.name.match("[]") == -1) {

thejoker101
10 Nov 2007, 5:22 PM
I have this wonderful ux added into my application but IE is having problems, as mentioned in a previous post. I took care of the regex by removing that section since I am passing in the []'s, but in IE the data isn't populating the multiselect box. Have you been able to fix this? Or does anyone have any ideas on how to fix it - as I really need this to be working soon ;)


Code updated for regular expression IE bug and also the problem with the select not getting populated in IE was that it was the years old select innerHTML IE bug (http://support.microsoft.com/kb/276228) that I'd forgotten about. Replaced DomHelper append using the options collection.

zygous
24 Sep 2008, 3:28 AM
actually i just figured it out. i was passing in a simplestore object that was already loaded. your multiselect component will render the select options when the store object's load event fires. in this case, the simplestore's load event never fires so the options never get rendered.

I'm trying to do the same thing - I need to pass-in pre-loaded SimpleStore objects and I can't easily keep references to them. How can I work around the options not being rendered?