X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/examples/ux/form/MultiSelect.js?ds=sidebyside
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:
+ *
+ * - any {@link Ext.data.Store Store} subclass
+ * - an Array : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
+ *
+ * - 1-dimensional array : (e.g., ['Foo','Bar'])
+ * A 1-dimensional array will automatically be expanded (each array item will be the combo
+ * {@link #valueField value} and {@link #displayField text})
+ * - 2-dimensional array : (e.g., [['f','Foo'],['b','Bar']])
+ * 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}.
+ *
+ */
+
+ 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();
+ }
+});
+
+