ItemSelector.js 11 KB
/*
 * 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.
 */

/**
 * @class Ext.ux.form.ItemSelector
 * @extends Ext.form.field.Base
 * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
 *
 *  @history
 *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
 *
 * @constructor
 * Create a new ItemSelector
 * @param {Object} config Configuration options
 * @xtype itemselector 
 */
Ext.define('Ext.ux.form.ItemSelector', {
    extend: 'Ext.ux.form.MultiSelect',
    alias: ['widget.itemselectorfield', 'widget.itemselector'],
    alternateClassName: ['Ext.ux.ItemSelector'],
    requires: ['Ext.ux.layout.component.form.ItemSelector', 'Ext.button.Button'],
    
    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'],

    buttonsText: {
        top: "Move to Top",
        up: "Move Up",
        add: "Add to Selected",
        remove: "Remove from Selected",
        down: "Move Down",
        bottom: "Move to Bottom"
    },

    /**
     * @cfg {Array} multiselects An optional array of {@link Ext.ux.form.MultiSelect} config objects, containing
     * additional configuration to be applied to the internal MultiSelect fields.
     */
    multiselects: [],

    componentLayout: 'itemselectorfield',

    fieldBodyCls: Ext.baseCSSPrefix + 'form-itemselector-body',


    bindStore: function(store, initial) {
        var me = this,
            toField = me.toField,
            fromField = me.fromField,
            models;

        me.callParent(arguments);

        if (toField) {
            // Clear both field stores
            toField.store.removeAll();
            fromField.store.removeAll();

            // Clone the contents of the main store into the fromField
            models = [];
            me.store.each(function(model) {
                models.push(model.copy(model.getId()));
            });
            fromField.store.add(models);
        }
    },

    onRender: function(ct, position) {
        var me = this,
            baseCSSPrefix = Ext.baseCSSPrefix,
            ddGroup = 'ItemSelectorDD-' + Ext.id(),
            commonConfig = {
                displayField: me.displayField,
                valueField: me.valueField,
                dragGroup: ddGroup,
                dropGroup: ddGroup,
                flex: 1,
                hideLabel: true
            },
            fromConfig = Ext.apply({
                listTitle: 'Available',
                store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
                listeners: {
                    boundList: {
                        itemdblclick: me.onItemDblClick,
                        scope: me
                    }
                }
            }, me.multiselects[0], commonConfig),
            toConfig = Ext.apply({
                listTitle: 'Selected',
                store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
                listeners: {
                    boundList: {
                        itemdblclick: me.onItemDblClick,
                        scope: me
                    },
                    change: me.onToFieldChange,
                    scope: me
                }
            }, me.multiselects[1], commonConfig),
            fromField = Ext.widget('multiselect', fromConfig),
            toField = Ext.widget('multiselect', toConfig),
            innerCt,
            buttons = [];

        // Skip MultiSelect's onRender as we don't want its content
        Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);

        me.fromField = fromField;
        me.toField = toField;

        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: baseCSSPrefix + 'form-itemselector-btn',
                    iconCls: baseCSSPrefix + 'form-itemselector-' + name,
                    scope: me
                });
                //div separator to force vertical stacking
                buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
            });
        }

        innerCt = me.innerCt = Ext.widget('container', {
            renderTo: me.bodyEl,
            layout: {
                type: 'hbox',
                align: 'middle'
            },
            items: [
                me.fromField,
                {
                    xtype: 'container',
                    margins: '0 4',
                    items: buttons
                },
                me.toField
            ]
        });

        // Must set upward link after first render
        innerCt.ownerCt = me;

        // Rebind the store so it gets cloned to the fromField
        me.bindStore(me.store);

        // Set the initial value
        me.setRawValue(me.rawValue);
    },
    
    onToFieldChange: function() {
        this.checkChange();
    },
    
    getSelections: function(list){
        var store = list.getStore(),
            selections = list.getSelectionModel().getSelection(),
            i = 0,
            len = selections.length;
            
        return Ext.Array.sort(selections, 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),
            i = selected.length - 1,
            selection;
        
        
        store.suspendEvents();
        for (; i > -1; --i) {
            selection = selected[i];
            store.remove(selected);
            store.insert(0, selected);
        }
        store.resumeEvents();
        list.refresh();    
    },

    onBottomBtnClick : function() {
        var list = this.toField.boundList,
            store = list.getStore(),
            selected = this.getSelections(list),
            i = 0,
            len = selected.length,
            selection;
            
        store.suspendEvents();
        for (; i < len; ++i) {
            selection = selected[i];
            store.remove(selection);
            store.add(selection);
        }
        store.resumeEvents();
        list.refresh();
    },

    onUpBtnClick : function() {
        var list = this.toField.boundList,
            store = list.getStore(),
            selected = this.getSelections(list),
            i = 0,
            len = selected.length,
            selection,
            index;
            
        store.suspendEvents();
        for (; i < len; ++i) {
            selection = selected[i];
            index = Math.max(0, store.indexOf(selection) - 1);
            store.remove(selection);
            store.insert(index, selection);
        }
        store.resumeEvents();
        list.refresh();
    },

    onDownBtnClick : function() {
        var list = this.toField.boundList,
            store = list.getStore(),
            selected = this.getSelections(list),
            i = 0,
            len = selected.length,
            max = store.getCount(),
            selection,
            index;
            
        store.suspendEvents();
        for (; i < len; ++i) {
            selection = selected[i];
            index = Math.min(max, store.indexOf(selection) + 1);
            store.remove(selection);
            store.insert(index, selection);
        }
        store.resumeEvents();
        list.refresh();
    },

    onAddBtnClick : function() {
        var me = this,
            fromList = me.fromField.boundList,
            selected = this.getSelections(fromList);
            
        fromList.getStore().remove(selected);
        this.toField.boundList.getStore().add(selected);
    },

    onRemoveBtnClick : function() {
        var me = this,
            toList = me.toField.boundList,
            selected = this.getSelections(toList);
            
        toList.getStore().remove(selected);
        this.fromField.boundList.getStore().add(selected);
    },

    onItemDblClick : function(view) {
        var me = this;
        if (view == me.toField.boundList){
            me.onRemoveBtnClick();
        }
        else if (view == me.fromField.boundList) {
            me.onAddBtnClick();
        }
    },

    setRawValue: function(value) {
        var me = this,
            Array = Ext.Array,
            toStore, fromStore, models;

        value = Array.from(value);
        me.rawValue = value;

        if (me.toField) {
            toStore = me.toField.boundList.getStore();
            fromStore = me.fromField.boundList.getStore();

            // Move any selected values back to the fromField
            fromStore.add(toStore.getRange());
            toStore.removeAll();

            // Move the new values over to the toField
            models = [];
            Ext.Array.forEach(value, function(val) {
                var undef,
                    model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
                if (model) {
                    models.push(model);
                }
            });
            fromStore.remove(models);
            toStore.add(models);
        }

        return value;
    },

    getRawValue: function() {
        var me = this,
            toField = me.toField,
            rawValue = me.rawValue;

        if (toField) {
            rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
                return model.get(me.valueField);
            });
        }

        me.rawValue = rawValue;
        return rawValue;
    },

    /**
     * @private Cascade readOnly/disabled state to the sub-fields and buttons
     */
    updateReadOnly: function() {
        var me = this,
            readOnly = me.readOnly || me.disabled;

        if (me.rendered) {
            me.toField.setReadOnly(readOnly);
            me.fromField.setReadOnly(readOnly);
            Ext.Array.forEach(me.innerCt.query('button'), function(button) {
                button.setDisabled(readOnly);
            });
        }
    },

    onDestroy: function() {
        Ext.destroyMembers(this, 'innerCt');
        this.callParent();
    }

});