X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/examples/ux/form/MultiSelect.js diff --git a/examples/ux/form/MultiSelect.js b/examples/ux/form/MultiSelect.js new file mode 100644 index 00000000..85c46a5e --- /dev/null +++ b/examples/ux/form/MultiSelect.js @@ -0,0 +1,385 @@ +/** + * @class Ext.ux.form.MultiSelect + * @extends Ext.form.field.Base + * A control that allows selection and form submission of multiple list items. + * + * @history + * 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 MultiSelect + * @param {Object} config Configuration options + * @xtype multiselect + */ +Ext.define('Ext.ux.form.MultiSelect', { + extend: 'Ext.form.field.Base', + alternateClassName: 'Ext.ux.Multiselect', + alias: ['widget.multiselect', 'widget.multiselectfield'], + uses: [ + 'Ext.view.BoundList', + 'Ext.form.FieldSet', + 'Ext.ux.layout.component.form.MultiSelect', + 'Ext.view.DragZone', + 'Ext.view.DropZone' + ], + + /** + * @cfg {String} listTitle An optional title to be displayed at the top of the selection list. + */ + + /** + * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined). + */ + + /** + * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined). + */ + + /** + * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false). + */ + ddReorder: false, + + /** + * @cfg {Object/Array} tbar An optional toolbar to be inserted at the top of the control's selection list. + * This can be a {@link Ext.toolbar.Toolbar} object, a toolbar config, or an array of buttons/button configs + * to be added to the toolbar. See {@link Ext.panel.Panel#tbar}. + */ + + /** + * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled + * (use for lists which are sorted, defaults to false). + */ + appendOnly: false, + + /** + * @cfg {String} displayField Name of the desired display field in the dataset (defaults to 'text'). + */ + displayField: 'text', + + /** + * @cfg {String} valueField Name of the desired value field in the dataset (defaults to the + * value of {@link #displayField}). + */ + + /** + * @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 'This field is required') + */ + blankText: 'This field is required', + + /** + * @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 {String} delimiter The string used to delimit the selected values when {@link #getSubmitValue submitting} + * the field as part of a form. Defaults to ','. If you wish to have the selected values submitted as separate + * parameters rather than a single delimited parameter, set this to null. + */ + delimiter: ',', + + /** + * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to undefined). + * Acceptable values for this property are: + *
+ */ + + componentLayout: 'multiselectfield', + + fieldBodyCls: Ext.baseCSSPrefix + 'form-multiselect-body', + + + // private + initComponent: function(){ + var me = this; + + me.bindStore(me.store, true); + if (me.store.autoCreated) { + me.valueField = me.displayField = 'field1'; + if (!me.store.expanded) { + me.displayField = 'field2'; + } + } + + if (!Ext.isDefined(me.valueField)) { + me.valueField = me.displayField; + } + + me.callParent(); + }, + + bindStore: function(store, initial) { + var me = this, + oldStore = me.store, + boundList = me.boundList; + + if (oldStore && !initial && oldStore !== store && oldStore.autoDestroy) { + oldStore.destroy(); + } + + me.store = store ? Ext.data.StoreManager.lookup(store) : null; + if (boundList) { + boundList.bindStore(store || null); + } + }, + + + // private + onRender: function(ct, position) { + var me = this, + panel, boundList, selModel; + + me.callParent(arguments); + + boundList = me.boundList = Ext.create('Ext.view.BoundList', { + multiSelect: true, + store: me.store, + displayField: me.displayField, + border: false + }); + + selModel = boundList.getSelectionModel(); + me.mon(selModel, { + selectionChange: me.onSelectionChange, + scope: me + }); + + panel = me.panel = Ext.create('Ext.panel.Panel', { + title: me.listTitle, + tbar: me.tbar, + items: [boundList], + renderTo: me.bodyEl, + layout: 'fit' + }); + + // Must set upward link after first render + panel.ownerCt = me; + + // Set selection to current value + me.setRawValue(me.rawValue); + }, + + // No content generated via template, it's all added components + getSubTplMarkup: function() { + return ''; + }, + + // private + afterRender: function() { + var me = this; + me.callParent(); + + if (me.ddReorder && !me.dragGroup && !me.dropGroup){ + me.dragGroup = me.dropGroup = 'MultiselectDD-' + Ext.id(); + } + + if (me.draggable || me.dragGroup){ + me.dragZone = Ext.create('Ext.view.DragZone', { + view: me.boundList, + ddGroup: me.dragGroup, + dragText: '{0} Item{1}' + }); + } + if (me.droppable || me.dropGroup){ + me.dropZone = Ext.create('Ext.view.DropZone', { + view: me.boundList, + ddGroup: me.dropGroup, + handleNodeDrop: function(data, dropRecord, position) { + var view = this.view, + store = view.getStore(), + records = data.records, + index; + + // remove the Models from the source Store + data.view.store.remove(records); + + index = store.indexOf(dropRecord); + if (position === 'after') { + index++; + } + store.insert(index, records); + view.getSelectionModel().select(records); + } + }); + } + }, + + onSelectionChange: function() { + this.checkChange(); + }, + + /** + * Clears any values currently selected. + */ + clearValue: function() { + this.setValue([]); + }, + + /** + * Return the value(s) to be submitted for this field. The returned value depends on the {@link #delimiter} + * config: If it is set to a String value (like the default ',') then this will return the selected values + * joined by the delimiter. If it is set to null then the values will be returned as an Array. + */ + getSubmitValue: function() { + var me = this, + delimiter = me.delimiter, + val = me.getValue(); + return Ext.isString(delimiter) ? val.join(delimiter) : val; + }, + + // inherit docs + getRawValue: function() { + var me = this, + boundList = me.boundList; + if (boundList) { + me.rawValue = Ext.Array.map(boundList.getSelectionModel().getSelection(), function(model) { + return model.get(me.valueField); + }); + } + return me.rawValue; + }, + + // inherit docs + setRawValue: function(value) { + var me = this, + boundList = me.boundList, + models; + + value = Ext.Array.from(value); + me.rawValue = value; + + if (boundList) { + models = []; + Ext.Array.forEach(value, function(val) { + var undef, + model = me.store.findRecord(me.valueField, val, undef, undef, true, true); + if (model) { + models.push(model); + } + }); + boundList.getSelectionModel().select(models, false, true); + } + + return value; + }, + + // no conversion + valueToRaw: function(value) { + return value; + }, + + // compare array values + isEqual: function(v1, v2) { + var fromArray = Ext.Array.from, + i, len; + + v1 = fromArray(v1); + v2 = fromArray(v2); + len = v1.length; + + if (len !== v2.length) { + return false; + } + + for(i = 0; i < len; i++) { + if (v2[i] !== v1[i]) { + return false; + } + } + + return true; + }, + + getErrors : function(value) { + var me = this, + format = Ext.String.format, + errors = me.callParent(arguments), + numSelected; + + value = Ext.Array.from(value || me.getValue()); + numSelected = value.length; + + if (!me.allowBlank && numSelected < 1) { + errors.push(me.blankText); + } + if (numSelected < this.minSelections) { + errors.push(format(me.minSelectionsText, me.minSelections)); + } + if (numSelected > this.maxSelections) { + errors.push(format(me.maxSelectionsText, me.maxSelections)); + } + + return errors; + }, + + onDisable: function() { + this.callParent(); + this.disabled = true; + this.updateReadOnly(); + }, + + onEnable: function() { + this.callParent(); + this.disabled = false; + this.updateReadOnly(); + }, + + setReadOnly: function(readOnly) { + this.readOnly = readOnly; + this.updateReadOnly(); + }, + + /** + * @private Lock or unlock the BoundList's selection model to match the current disabled/readonly state + */ + updateReadOnly: function() { + var me = this, + boundList = me.boundList, + readOnly = me.readOnly || me.disabled; + if (boundList) { + boundList.getSelectionModel().setLocked(readOnly); + } + }, + + onDestroy: function(){ + Ext.destroyMembers(this, 'panel', 'boundList', 'dragZone', 'dropZone'); + this.callParent(); + } +}); + +