All Downloads are FREE. Search and download functionalities are using the official Maven repository.

static.extjs.ux.form.MultiSelect.js Maven / Gradle / Ivy

There is a newer version: 1.4.1
Show newest version
/**
 * A control that allows selection of multiple items in a list.
 */
Ext.define('Ext.ux.form.MultiSelect', {
    
    extend: 'Ext.form.FieldContainer',
    
    mixins: {
        bindable: 'Ext.util.Bindable',
        field: 'Ext.form.field.Field'    
    },
    
    alternateClassName: 'Ext.ux.Multiselect',
    alias: ['widget.multiselectfield', 'widget.multiselect'],
    
    requires: ['Ext.panel.Panel', 'Ext.view.BoundList', 'Ext.layout.container.Fit'],
    
    uses: ['Ext.view.DragZone', 'Ext.view.DropZone'],
    
    layout: 'anchor',
    
    /**
     * @cfg {String} [dragGroup=""] The ddgroup name for the MultiSelect DragZone.
     */

    /**
     * @cfg {String} [dropGroup=""] The ddgroup name for the MultiSelect DropZone.
     */
    
    /**
     * @cfg {String} [title=""] A title for the underlying panel.
     */
    
    /**
     * @cfg {Boolean} [ddReorder=false] Whether the items in the MultiSelect list are drag/drop reorderable.
     */
    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=false] `true` if the list should only allow append drops when drag/drop is enabled.
     * This is useful for lists which are sorted.
     */
    appendOnly: false,

    /**
     * @cfg {String} [displayField="text"] Name of the desired display field in the dataset.
     */
    displayField: 'text',

    /**
     * @cfg {String} [valueField="text"] Name of the desired value field in the dataset.
     */

    /**
     * @cfg {Boolean} [allowBlank=true] `false` to require at least one item in the list to be selected, `true` to allow no
     * selection.
     */
    allowBlank: true,

    /**
     * @cfg {Number} [minSelections=0] Minimum number of selections allowed.
     */
    minSelections: 0,

    /**
     * @cfg {Number} [maxSelections=Number.MAX_VALUE] Maximum number of selections allowed.
     */
    maxSelections: Number.MAX_VALUE,

    /**
     * @cfg {String} [blankText="This field is required"] Default text displayed when the control contains no items.
     */
    blankText: 'This field is required',

    /**
     * @cfg {String} [minSelectionsText="Minimum {0}item(s) required"] 
     * Validation message displayed when {@link #minSelections} is not met. 
     * The {0} token will be replaced by the value of {@link #minSelections}.
     */
    minSelectionsText: 'Minimum {0} item(s) required',
    
    /**
     * @cfg {String} [maxSelectionsText="Maximum {0}item(s) allowed"] 
     * Validation message displayed when {@link #maxSelections} is not met
     * The {0} token will be replaced by the value of {@link #maxSelections}.
     */
    maxSelectionsText: 'Maximum {0} item(s) required',

    /**
     * @cfg {String} [delimiter=","] The string used to delimit the selected values when {@link #getSubmitValue submitting}
     * the field as part of a form. If you wish to have the selected values submitted as separate
     * parameters rather than a single delimited parameter, set this to `null`.
     */
    delimiter: ',',
    
    /**
     * @cfg String [dragText="{0} Item{1}"] The text to show while dragging items.
     * {0} will be replaced by the number of items. {1} will be replaced by the plural
     * form if there is more than 1 item.
     */
    dragText: '{0} Item{1}',

    /**
     * @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}. *
*/ ignoreSelectChange: 0, /** * @cfg {Object} listConfig * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor. * Any configuration that is valid for BoundList can be included. */ 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.items = me.setupItems(); me.callParent(); me.initField(); me.addEvents('drop'); }, setupItems: function() { var me = this; me.boundList = Ext.create('Ext.view.BoundList', Ext.apply({ anchor: 'none 100%', deferInitialRefresh: false, border: 1, multiSelect: true, store: me.store, displayField: me.displayField, disabled: me.disabled }, me.listConfig)); me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me); // Only need to wrap the BoundList in a Panel if we have a title. if (!me.title) { return me.boundList; } // Wrap to add a title me.boundList.border = false; return { border: true, anchor: 'none 100%', layout: 'anchor', title: me.title, tbar: me.tbar, items: me.boundList }; }, onSelectChange: function(selModel, selections){ if (!this.ignoreSelectChange) { this.setValue(selections); } }, getSelected: function(){ return this.boundList.getSelectionModel().getSelection(); }, // compare array values isEqual: function(v1, v2) { var fromArray = Ext.Array.from, i = 0, len; v1 = fromArray(v1); v2 = fromArray(v2); len = v1.length; if (len !== v2.length) { return false; } for(; i < len; i++) { if (v2[i] !== v1[i]) { return false; } } return true; }, afterRender: function(){ var me = this, records; me.callParent(); if (me.selectOnRender) { records = me.getRecordsForValue(me.value); if (records.length) { ++me.ignoreSelectChange; me.boundList.getSelectionModel().select(records); --me.ignoreSelectChange; } delete me.toSelect; } 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: me.dragText }); } 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); me.fireEvent('drop', me, records); } }); } }, isValid : function() { var me = this, disabled = me.disabled, validate = me.forceValidation || !disabled; return validate ? me.validateValue(me.value) : disabled; }, validateValue: function(value) { var me = this, errors = me.getErrors(value), isValid = Ext.isEmpty(errors); if (!me.preventMark) { if (isValid) { me.clearInvalid(); } else { me.markInvalid(errors); } } return isValid; }, markInvalid : function(errors) { // Save the message and fire the 'invalid' event var me = this, oldMsg = me.getActiveError(); me.setActiveErrors(Ext.Array.from(errors)); if (oldMsg !== me.getActiveError()) { me.updateLayout(); } }, /** * Clear any invalid styles/messages for this field. * * __Note:__ this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true` * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set. */ clearInvalid : function() { // Clear the message and fire the 'valid' event var me = this, hadError = me.hasActiveError(); me.unsetActiveError(); if (hadError) { me.updateLayout(); } }, getSubmitData: function() { var me = this, data = null, val; if (!me.disabled && me.submitValue && !me.isFileUpload()) { val = me.getSubmitValue(); if (val !== null) { data = {}; data[me.getName()] = val; } } return data; }, /** * Returns the value that would be included in a standard form submit for this field. * * @return {String} The value to be submitted, or `null`. */ getSubmitValue: function() { var me = this, delimiter = me.delimiter, val = me.getValue(); return Ext.isString(delimiter) ? val.join(delimiter) : val; }, getValue: function(){ return this.value || []; }, getRecordsForValue: function(value){ var me = this, records = [], all = me.store.getRange(), valueField = me.valueField, i = 0, allLen = all.length, rec, j, valueLen; for (valueLen = value.length; i < valueLen; ++i) { for (j = 0; j < allLen; ++j) { rec = all[j]; if (rec.get(valueField) == value[i]) { records.push(rec); } } } return records; }, setupValue: function(value){ var delimiter = this.delimiter, valueField = this.valueField, i = 0, out, len, item; if (Ext.isDefined(value)) { if (delimiter && Ext.isString(value)) { value = value.split(delimiter); } else if (!Ext.isArray(value)) { value = [value]; } for (len = value.length; i < len; ++i) { item = value[i]; if (item && item.isModel) { value[i] = item.get(valueField); } } out = Ext.Array.unique(value); } else { out = []; } return out; }, setValue: function(value){ var me = this, selModel = me.boundList.getSelectionModel(), store = me.store; // Store not loaded yet - we cannot set the value if (!store.getCount()) { store.on({ load: Ext.Function.bind(me.setValue, me, [value]), single: true }); return; } value = me.setupValue(value); me.mixins.field.setValue.call(me, value); if (me.rendered) { ++me.ignoreSelectChange; selModel.deselectAll(); if (value.length) { selModel.select(me.getRecordsForValue(value)); } --me.ignoreSelectChange; } else { me.selectOnRender = true; } }, clearValue: function(){ this.setValue([]); }, onEnable: function(){ var list = this.boundList; this.callParent(); if (list) { list.enable(); } }, onDisable: function(){ var list = this.boundList; this.callParent(); if (list) { list.disable(); } }, getErrors : function(value) { var me = this, format = Ext.String.format, errors = [], numSelected; value = Ext.Array.from(value || me.getValue()); numSelected = value.length; if (!me.allowBlank && numSelected < 1) { errors.push(me.blankText); } if (numSelected < me.minSelections) { errors.push(format(me.minSelectionsText, me.minSelections)); } if (numSelected > me.maxSelections) { errors.push(format(me.maxSelectionsText, me.maxSelections)); } return errors; }, onDestroy: function(){ var me = this; me.bindStore(null); Ext.destroy(me.dragZone, me.dropZone); me.callParent(); }, onBindStore: function(store){ var boundList = this.boundList; if (boundList) { boundList.bindStore(store); } } });




© 2015 - 2024 Weber Informatics LLC | Privacy Policy