PDA

View Full Version : BinaryMultiSelect - int value to select multiple binary items



Artistan
10 Sep 2009, 1:00 PM
I recently started using Ext in a project and wanted to share this.
Due to a friend's lack of fore-thought, I have to use a binary field for options in many places. Ext does not have a great interface for this that I know of (multi-select with binary values). I found the MultiSelect.js extension and decided to modify it for binary values.
This allows for my Direct api to auto load the selected values and submit the sum back to the server. PERFECT!

Here is an example of the form item I am using...

[update Sun Sept 12,2009] - modified the get and set functions so they will save the setValue before being rendered (tabs within form).
It will then render with the correct selections without reloading the data source.
[update Mon Sept 14,2009] - updated for better selection of values and updated a few parts that needed to use the valueField to do its work.
Now it will work with my DirectStore functions to load dynamic lists and preselect them. WooHoo!

Basic example...


{
xtype: 'binarymultiselect',
fieldLabel: 'Trading Regions',
name: 'trading_regions',
width: 250,
height: 200,
allowBlank:true,
store: [
[1 ,'North America' ],
[2 ,'South America' ],
[4 ,'Africa' ],
[8 ,'Middle East' ],
[16,'Europe' ],
[32,'Oceania' ],
[64,'Asia' ],
],
tbar:[{
text: 'CLEAR',
handler: function(x_object,x_action){
x_object.getForm().findField('multiselect').reset();
}
}]
}


DirectStore example...


{
xtype: 'binarymultiselect',
fieldLabel: 'Categories',
name: 'categories',
width: 250,
height: 185,
allowBlank:true,
displayField:'name',
valueField:'flag',
store: {
xtype: 'directstore',
autoDestroy: true,
autoLoad: true,
directFn: Ext.bb.Common.categories,
totalProperty: 'totalCount',
root: 'items',
paramOrder: ['grouping_id'],
baseParams: {grouping_id:0},
paramsAsHash:false,
fields: [
{name: 'flag', type: 'int'},
{name: 'name'},
{name: 'default', type: 'int'},
{name: 'group', type: 'int'},
{name: 'order'}
]
}
}


And the extension code....

/*!
* Ext JS Library 3.0.0
* Copyright(c) 2006-2009 Ext JS, LLC
* licensing@extjs.com
* http://www.extjs.com/license
*/
Ext.ns('Ext.ux.form');

/**
* @class Ext.ux.form.BinaryMultiSelect
* @extends Ext.form.Field
* A control that allows selection and form submission of multiple binary items added together
*
* @history
* 2009-09-10 Charles Peterson - Reimplemented MultiSelect for binary values, Modified code for Binary Selection
* 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
* 2008-06-19 bpm Docs and demo code clean up
*
* @constructor
* Create a new BinaryMultiSelect
* @param {Object} config Configuration options
* @xtype BinaryMultiSelect
*/
Ext.ux.form.BinaryMultiSelect = Ext.extend(Ext.form.Field, {
/**
* @cfg {String} legend Wraps the object with a fieldset and specified legend.
*/
/**
* @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the BinaryMultiSelect list.
*/
view:false,
/**
* @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a
* toolbar config, or an array of buttons/button configs to be added to the toolbar.
*/
/**
* @cfg {Number} width Width in pixels of the control (defaults to 100).
*/
width:100,
/**
* @cfg {Number} height Height in pixels of the control (defaults to 100).
*/
height:100,
/**
* @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).
*/
displayField:0,
/**
* @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).
*/
valueField:1,
/**
* @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no
* selection (defaults to true).
*/
allowBlank:true,
/**
* @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).
*/
minSelections:0,
/**
* @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).
*/
maxSelections:Number.MAX_VALUE,
/**
* @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as
* {@link Ext.form.TextField#blankText}.
*/
blankText:Ext.form.TextField.prototype.blankText,
/**
* @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}
* item(s) required'). The {0} token will be replaced by the value of {@link #minSelections}.
*/
minSelectionsText:'Minimum {0} item(s) required',
/**
* @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}
* item(s) allowed'). The {0} token will be replaced by the value of {@link #maxSelections}.
*/
maxSelectionsText:'Maximum {0} item(s) allowed',
/**
* @cfg {Ext.data.Store/Array} store The data source to which this BinaryMultiSelect is bound (defaults to <tt>undefined</tt>).
* Acceptable values for this property are:
* <div class="mdetail-params"><ul>
* <li><b>any {@link Ext.data.Store Store} subclass</b></li>
* <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
* <div class="mdetail-params"><ul>
* <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
* A 1-dimensional array will automatically be expanded (each array item will be the combo
* {@link #valueField value} and {@link #displayField text})</div></li>
* <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
* For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
* {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
* </div></li></ul></div></li></ul></div>
*/

// private
defaultAutoCreate : {tag: "div"},

// private
initComponent: function(){
console.log('BINARY SELECT: initComponent');
Ext.ux.form.BinaryMultiSelect.superclass.initComponent.call(this);

if(Ext.isArray(this.store)){
if (Ext.isArray(this.store[0])){
this.store = new Ext.data.ArrayStore({
fields: ['value','text'],
data: this.store
});
this.valueField = 'value';
}else{
this.store = new Ext.data.ArrayStore({
fields: ['text'],
data: this.store,
expandData: true
});
this.valueField = 'text';
}
this.displayField = 'text';
} else {
this.store = Ext.StoreMgr.lookup(this.store);
}
this.addEvents({
'dblclick' : true,
'click' : true,
'change' : true
});
console.log('BINARY SELECT: /initComponent');
console.log('BINARY SELECT: this',this);
},

// private
onRender: function(ct, position){
console.log('BINARY SELECT: onRender');
Ext.ux.form.BinaryMultiSelect.superclass.onRender.call(this, ct, position);

var fs = this.fs = new Ext.form.FieldSet({
renderTo: this.el,
title: this.legend,
height: this.height,
width: this.width,
style: "padding:0;",
tbar: this.tbar,
bodyStyle: 'overflow: auto;'
});

this.view = new Ext.ListView({
multiSelect: true,
simpleSelect: true,
store: this.store,
columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],
hideHeaders: true
});

fs.add(this.view);

this.view.on('click', this.onViewClick, this);
this.view.on('beforeclick', this.onViewBeforeClick, this);
this.view.on('dblclick', this.onViewDblClick, this);

this.hiddenName = this.name || Ext.id();
var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };
this.hiddenField = this.el.createChild(hiddenTag);
this.hiddenField.dom.disabled = this.hiddenName != this.name;
fs.doLayout();
console.log('BINARY SELECT: /onRender');
},

// private
afterRender: function(){
console.log('BINARY SELECT: afterRender');
Ext.ux.form.BinaryMultiSelect.superclass.afterRender.call(this);
// add after render stuff...
console.log('BINARY SELECT: afterRender2');
},

// private
onViewClick: function(vw, index, node, e) {
console.log('BINARY SELECT: onViewClick',this.getValue());
var result = this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
console.log('BINARY SELECT: onViewClick, result1',result);
result = this.hiddenField.dom.value = this.getValue();
console.log('BINARY SELECT: onViewClick, result2',result);
result = this.fireEvent('click', this, e);
console.log('BINARY SELECT: onViewClick, result3',result);
result = this.validate();
},

// private
onViewBeforeClick: function(vw, index, node, e) {
if (this.disabled) {return false;}
},

// private
onViewDblClick : function(vw, index, node, e) {
return this.fireEvent('dblclick', vw, index, node, e);
},

/**
* Returns an sum value of data values for the selected items in the list.
* @return {Int} value Sum of all data values
*/
getValue: function(valueField){
try{
valueField = (valueField != null) ? valueField : this.valueField;
console.log('BINARY SELECT: getValue,valueField',valueField);

var returnInt = 0;
if(this.rendered){
console.log('BINARY SELECT: this.rendered');
var selectionsArray = this.view.getSelectedIndexes();
if (selectionsArray.length == 0) {
if(this.value){
returnInt = this.value;
} else {
returnInt = '';
}
} else {
for (var i=0; i<selectionsArray.length; i++) {
returnInt += this.store.getAt(selectionsArray[i]).get(valueField);
}
}
} else {
console.log('BINARY SELECT: NOT this.rendered');
returnInt = this.value;
}
console.log('BINARY SELECT: getValue',returnInt);
return returnInt;
} catch(e) {
console.log('BINARY SELECT: getValue:ERROR',e)
}
},

/**
* Sets an binary value or array of data values into the list.
* @param {Int/Array} values The values to set
*/
setValue: function(values) {
console.log('BINARY SELECT: setValue',values);

if (!values || (values == '')) { return; }

if (!Ext.isArray(values)) {
// create a array of the binary values
// check each field for match against value
var tmpVal = values;
var total=1;
var totalBinary = this.store.sum(this.valueField);
var currentVal='';
values = [];
while( total <= totalBinary ){
item = this.store.query(this.valueField,new RegExp('^' + total + '$', "i")).itemAt(0);
if(typeof item != "undefined"){
currentVal = item.data[this.valueField];
if(tmpVal & currentVal){
// if value of current item matches binary Values selected...
values.push( currentVal );
}
}
total = total * 2;
}

}
console.log('BINARY SELECT: setValue2',values);

var sum = 0;
for (i=0; i<values.length; i++){
sum = sum + values[i];
}
this.value=sum;

if(this.view == false){
return;
}
var index;
var selections = [];
this.view.clearSelections();
this.hiddenField.dom.value = '';

for (var i=0; i<values.length; i++) {
index = this.store.indexOf(this.store.query(this.valueField,new RegExp('^' + values[i] + '$', "i")).itemAt(0));
selections.push(index);
}
this.view.select(selections);
this.hiddenField.dom.value = this.getValue();
this.validate();
},

// inherit docs
reset : function() {
console.log('BINARY SELECT: reset');
this.setValue('');
},

// inherit docs
getRawValue: function(valueField) {
valueField = (valueField != null) ? valueField : this.valueField;

var tmpVal = this.getValue(valueField);
if(!this.rendered){
tmpVal=this.value;
}
// create a array of the binary values
// check each field for match against value
var total=1;
var totalBinary = this.store.sum(valueField);
var currentVal='';
var values = [];
while( total <= totalBinary ){
item = this.store.query(this.valueField,new RegExp('^' + total + '$', "i")).itemAt(0);
if(typeof item != "undefined"){
currentVal = item.data[this.valueField];
if(tmpVal & currentVal){
// if value of current item matches binary Values selected...
values.push( currentVal );
}
}
total = total * 2;
}
console.log('BINARY SELECT: getRawValue',values);
// return the array of individual binary values...
return values;
},

// inherit docs
setRawValue: function(values){
console.log('BINARY SELECT: setRawValue',values);
this.setValue(values);
},

// inherit docs
validateValue : function(value){
console.log('BINARY SELECT: validateValue',value);
if (value.length < 1) { // if it has no value
if (this.allowBlank) {
this.clearInvalid();
return true;
} else {
this.markInvalid(this.blankText);
return false;
}
}
if (value.length < this.minSelections) {
this.markInvalid(String.format(this.minSelectionsText, this.minSelections));
return false;
}
if (value.length > this.maxSelections) {
this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));
return false;
}
return true;
},

// inherit docs
disable: function(){
console.log('BINARY SELECT: disable');
this.disabled = true;
this.hiddenField.dom.disabled = true;
this.fs.disable();
},

// inherit docs
enable: function(){
console.log('BINARY SELECT: enable');
this.disabled = false;
this.hiddenField.dom.disabled = false;
this.fs.enable();
},

// inherit docs
destroy: function(){
console.log('BINARY SELECT: destroy');
// destroy stuff...
Ext.ux.form.BinaryMultiSelect.superclass.destroy.call(this);
}
});


Ext.reg('binarymultiselect', Ext.ux.form.BinaryMultiSelect);

ajaxvador
10 Sep 2009, 5:32 PM
demo ?

Artistan
12 Sep 2009, 7:17 AM
Temp Ext.ux.form.BinaryMultiSelect Demo (http://artistan.org/ext/temp_ext/www/)
This uses an Ext.Direct api to load and save a binary value a BinaryMultiSelect object.
Should be able to use any type of Ext.Store to populate the select object.

Bulle Bas
12 Sep 2009, 10:00 AM
Your example is broken :)

Artistan
12 Sep 2009, 10:54 AM
Works fine in firefox, have not done testing in other browsers yet.
[update]
I added the appropriate
if(typeof(console) == 'undefined')
so now everything works in IE and FF without console.

galdaka
13 Sep 2009, 12:47 AM
Good work!

I prefer superboxselect component for this purpose ;)

Greetings,

Artistan
13 Sep 2009, 7:48 PM
superboxselect is excellent for multiselect, but it does not do what I need, which is to accept a single value (binary sum) to select multiple choices and have an updated single value submited back to the server for storage. Thanks for pointing out that excellent ux though!