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*/);").
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*/);").