1. #1
    Sencha User
    Join Date
    Sep 2009
    Location
    Denver
    Posts
    51
    Vote Rating
    2
    McQuack_82 will become famous soon enough

      0  

    Default I made some small improvement to itemselector and multiselect

    I made some small improvement to itemselector and multiselect


    I made some changes to bring back the to and from labels that used to exist in 4.0 versions

    I enclosed a screenshot with old state of itemselector new state of itemselector and purposed state and the needed code changes bellow. [ATTAC
    H=CONFIG]36881[/ATTACH]

    Code:
    MultiSelect.js
    
    /**
    * 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'],
    
    
        uses: ['Ext.view.DragZone', 'Ext.view.DropZone'],
    
    
        /**
        * @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: 'Minimum {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 <tt>null</tt>.
        */
        delimiter: ',',
    
    
        /**
        * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect 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>
        */
    
    
        ignoreSelectChange: 0,
    
    
        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;
            }
            Ext.apply(me, me.setupItems());
    
    
    
    
            me.callParent();
            me.initField();
            me.addEvents('drop');
        },
    
    
        setupItems: function () {
            var me = this;
    
    
            me.boundList = Ext.create('Ext.view.BoundList', {
                deferInitialRefresh: false,
                multiSelect: true,
                store: me.store,
                displayField: me.displayField,
                disabled: me.disabled
            });
    
    
            me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me);
    
    
            return {
                layout: 'fit',
                labelAlign: 'top',
                fieldLabel: me.fieldLabel,
                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;
    
    
            me.callParent();
            if (me.selectOnRender) {
                ++me.ignoreSelectChange;
                me.boundList.getSelectionModel().select(me.getRecordsForValue(me.value));
                --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: '{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);
                        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 not loaded yet - we cannot set the value
            if (!me.store.getCount()) {
                me.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();
                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);
            }
        }
    
    
    });
    
    ItemSelector.js
    
    /*
    * Note that this control will most likely remain as an example, and not as a core Ext form
    * control.  However, the API will be changing in a future release and so should not yet be
    * treated as a final, stable API at this time.
    */
    
    
    /**
    * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
    */
    Ext.define('Ext.ux.form.ItemSelector', {
        extend: 'Ext.ux.form.MultiSelect',
        alias: ['widget.itemselectorfield', 'widget.itemselector'],
        alternateClassName: ['Ext.ux.ItemSelector'],
        requires: [
            'Ext.button.Button',
            'Ext.ux.form.MultiSelect'
        ],
    
    
        fromLabel: 'From',
    
    
        toLabel: 'To',
    
    
        /**
        * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons
        */
        hideNavIcons: false,
    
    
        /**
        * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
        * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
        * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
        * This can be overridden with a custom Array to change which buttons are displayed or their order.
        */
        buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
    
    
        /**
        * @cfg {Object} buttonsText The tooltips for the {@link #buttons}.
        * Labels for buttons.
        */
        buttonsText: {
            top: "Move to Top",
            up: "Move Up",
            add: "Add to Selected",
            remove: "Remove from Selected",
            down: "Move Down",
            bottom: "Move to Bottom"
        },
    
    
        initComponent: function () {
            var me = this;
    
    
            me.ddGroup = me.id + '-dd';
            me.callParent();
    
    
            // bindStore must be called after the fromField has been created because
            // it copies records from our configured Store into the fromField's Store
            me.bindStore(me.store);
        },
    
    
        createList: function (fieldLabel) {
            var me = this;
    
    
            return Ext.create('Ext.ux.form.MultiSelect', {
                submitValue: false,
                flex: 1,
                fieldLabel: fieldLabel,
                dragGroup: me.ddGroup,
                dropGroup: me.ddGroup,
                store: {
                    model: me.store.model,
                    data: []
                },
                displayField: me.displayField,
                disabled: me.disabled,
                listeners: {
                    boundList: {
                        scope: me,
                        itemdblclick: me.onItemDblClick,
                        drop: me.syncValue
                    }
                }
            });
        },
    
    
        setupItems: function () {
            var me = this;
    
    
            me.fromField = me.createList(me.fromLabel);
            me.toField = me.createList(me.toLabel);
    
    
            return {
                layout: {
                    type: 'hbox',
                    align: 'stretch'
                },
                items: [
                    me.fromField,
                    {
                        xtype: 'container',
                        margins: '0 4',
                        width: 22,
                        layout: {
                            type: 'vbox',
                            pack: 'center'
                        },
                        items: me.createButtons()
                    },
                    me.toField
                ]
            };
        },
    
    
        createButtons: function () {
            var me = this,
                buttons = [];
    
    
            if (!me.hideNavIcons) {
                Ext.Array.forEach(me.buttons, function (name) {
                    buttons.push({
                        xtype: 'button',
                        tooltip: me.buttonsText[name],
                        handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
                        cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
                        iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name,
                        navBtn: true,
                        scope: me,
                        margin: '4 0 0 0'
                    });
                });
            }
            return buttons;
        },
    
    
        /**
        * Get the selected records from the specified list.
        * 
        * Records will be returned *in store order*, not in order of selection.
        * @param {Ext.view.BoundList} list The list to read selections from.
        * @return {Ext.data.Model[]} The selected records in store order.
        * 
        */
        getSelections: function (list) {
            var store = list.getStore();
    
    
            return Ext.Array.sort(list.getSelectionModel().getSelection(), function (a, b) {
                a = store.indexOf(a);
                b = store.indexOf(b);
    
    
                if (a < b) {
                    return -1;
                } else if (a > b) {
                    return 1;
                }
                return 0;
            });
        },
    
    
        onTopBtnClick: function () {
            var list = this.toField.boundList,
                store = list.getStore(),
                selected = this.getSelections(list);
    
    
            store.suspendEvents();
            store.remove(selected, true);
            store.insert(0, selected);
            store.resumeEvents();
            list.refresh();
            this.syncValue();
            list.getSelectionModel().select(selected);
        },
    
    
        onBottomBtnClick: function () {
            var list = this.toField.boundList,
                store = list.getStore(),
                selected = this.getSelections(list);
    
    
            store.suspendEvents();
            store.remove(selected, true);
            store.add(selected);
            store.resumeEvents();
            list.refresh();
            this.syncValue();
            list.getSelectionModel().select(selected);
        },
    
    
        onUpBtnClick: function () {
            var list = this.toField.boundList,
                store = list.getStore(),
                selected = this.getSelections(list),
                rec,
                i = 0,
                len = selected.length,
                index = 0;
    
    
            // Move each selection up by one place if possible
            store.suspendEvents();
            for (; i < len; ++i, index++) {
                rec = selected[i];
                index = Math.max(index, store.indexOf(rec) - 1);
                store.remove(rec, true);
                store.insert(index, rec);
            }
            store.resumeEvents();
            list.refresh();
            this.syncValue();
            list.getSelectionModel().select(selected);
        },
    
    
        onDownBtnClick: function () {
            var list = this.toField.boundList,
                store = list.getStore(),
                selected = this.getSelections(list),
                rec,
                i = selected.length - 1,
                index = store.getCount() - 1;
    
    
            // Move each selection down by one place if possible
            store.suspendEvents();
            for (; i > -1; --i, index--) {
                rec = selected[i];
                index = Math.min(index, store.indexOf(rec) + 1);
                store.remove(rec, true);
                store.insert(index, rec);
            }
            store.resumeEvents();
            list.refresh();
            this.syncValue();
            list.getSelectionModel().select(selected);
        },
    
    
        onAddBtnClick: function () {
            var me = this,
                selected = me.getSelections(me.fromField.boundList);
    
    
            me.moveRec(true, selected);
            me.toField.boundList.getSelectionModel().select(selected);
        },
    
    
        onRemoveBtnClick: function () {
            var me = this,
                selected = me.getSelections(me.toField.boundList);
    
    
            me.moveRec(false, selected);
            me.fromField.boundList.getSelectionModel().select(selected);
        },
    
    
        moveRec: function (add, recs) {
            var me = this,
                fromField = me.fromField,
                toField = me.toField,
                fromStore = add ? fromField.store : toField.store,
                toStore = add ? toField.store : fromField.store;
    
    
            fromStore.suspendEvents();
            toStore.suspendEvents();
            fromStore.remove(recs);
            toStore.add(recs);
            fromStore.resumeEvents();
            toStore.resumeEvents();
    
    
            fromField.boundList.refresh();
            toField.boundList.refresh();
    
    
            me.syncValue();
        },
    
    
        // Synchronizes the submit value with the current state of the toStore
        syncValue: function () {
            var me = this;
            me.mixins.field.setValue.call(me, me.setupValue(me.toField.store.getRange()));
        },
    
    
        onItemDblClick: function (view, rec) {
            this.moveRec(view === this.fromField.boundList, rec);
        },
    
    
        setValue: function (value) {
            var me = this,
                fromField = me.fromField,
                toField = me.toField,
                fromStore = fromField.store,
                toStore = toField.store,
                selected;
    
    
            // Wait for from store to be loaded
            if (!me.fromStorePopulated) {
                me.fromField.store.on({
                    load: Ext.Function.bind(me.setValue, me, [value]),
                    single: true
                });
                return;
            }
    
    
            value = me.setupValue(value);
            me.mixins.field.setValue.call(me, value);
    
    
            selected = me.getRecordsForValue(value);
    
    
            // Clear both left and right Stores.
            // Both stores must not fire events during this process.
            fromStore.suspendEvents();
            toStore.suspendEvents();
            fromStore.removeAll();
            toStore.removeAll();
    
    
            // Reset fromStore
            me.populateFromStore(me.store);
    
    
            // Copy selection across to toStore
            Ext.Array.forEach(selected, function (rec) {
                // In the from store, move it over
                if (fromStore.indexOf(rec) > -1) {
                    fromStore.remove(rec);
                }
                toStore.add(rec);
            });
    
    
            // Stores may now fire events
            fromStore.resumeEvents();
            toStore.resumeEvents();
    
    
            // Refresh both sides and then update the app layout
            Ext.suspendLayouts();
            fromField.boundList.refresh();
            toField.boundList.refresh();
            Ext.resumeLayouts(true);
        },
    
    
        onBindStore: function (store, initial) {
            var me = this;
    
    
            if (me.fromField) {
                me.fromField.store.removeAll()
                me.toField.store.removeAll();
    
    
                // Add everything to the from field as soon as the Store is loaded
                if (store.getCount()) {
                    me.populateFromStore(store);
                } else {
                    me.store.on('load', me.populateFromStore, me);
                }
            }
        },
    
    
        populateFromStore: function (store) {
            var fromStore = this.fromField.store;
    
    
            // Flag set when the fromStore has been loaded
            this.fromStorePopulated = true;
    
    
            fromStore.add(store.getRange());
    
    
            // setValue waits for the from Store to be loaded
            fromStore.fireEvent('load', fromStore);
        },
    
    
        onEnable: function () {
            var me = this;
    
    
            me.callParent();
            me.fromField.enable();
            me.toField.enable();
    
    
            Ext.Array.forEach(me.query('[navBtn]'), function (btn) {
                btn.enable();
            });
        },
    
    
        onDisable: function () {
            var me = this;
    
    
            me.callParent();
            me.fromField.disable();
            me.toField.disable();
    
    
            Ext.Array.forEach(me.query('[navBtn]'), function (btn) {
                btn.disable();
            });
        },
    
    
        onDestroy: function () {
            this.bindStore(null);
            this.callParent();
        }
    });
    Attached Images
    Last edited by scottmartin; 6 Jul 2012 at 8:39 AM. Reason: Please format your code

  2. #2
    Sencha - Support Team scottmartin's Avatar
    Join Date
    Jul 2010
    Location
    Houston, Tx
    Posts
    9,081
    Vote Rating
    467
    scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future scottmartin has a brilliant future

      0  

    Default


    Thank you for the contribution. We have had several requests for this.

    Scott.

Thread Participants: 1

Tags for this Thread