    /*
     * 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'
        ],

        /**
         * @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"
        },

        layout: {
            type: 'hbox',
            align: 'stretch'
        },

        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(title){
            var me = this;

            return Ext.create('Ext.ux.form.MultiSelect', {
                // We don't want the multiselects themselves to act like fields,
                // so override these methods to prevent them from including
                // any of their values
                submitValue: false,
                getSubmitData: function(){
                    return null;
                },
                getModelData: function(){
                    return null;
                },
                flex: 1,
                dragGroup: me.ddGroup,
                dropGroup: me.ddGroup,
                title: title,
                store: {
                    model: me.store.model,
                    data: []
                },
                displayField: me.displayField,
                valueField: me.valueField,
                disabled: me.disabled,
                listeners: {
                    boundList: {
                        scope: me,
                        itemdblclick: me.onItemDblClick,
                        drop: me.syncValue
                    }
                }
            });
        },

        setupItems: function() {
            var me = this;

            me.fromField = me.createList(me.fromTitle);
            me.toField = me.createList(me.toTitle);

            return [
                me.fromField,
                {
                    xtype: 'container',
                    margins: '0 4',
                    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();
        }
    });