ListMenu.js 7.28 KB
/**
 * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.
 * Although not listed as configuration options for this class, this class
 * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.
 */
Ext.define('Ext.ux.grid.menu.ListMenu', {
    extend: 'Ext.menu.Menu',
    
    /**
     * @cfg {String} idField
     * Defaults to 'id'.
     */
    idField :  'id',

    /**
     * @cfg {String} labelField
     * Defaults to 'text'.
     */
    labelField :  'text',
    /**
     * @cfg {String} paramPrefix
     * Defaults to 'Loading...'.
     */
    loadingText : 'Loading...',
    /**
     * @cfg {Boolean} loadOnShow
     * Defaults to true.
     */
    loadOnShow : true,
    /**
     * @cfg {Boolean} single
     * Specify true to group all items in this list into a single-select
     * radio button group. Defaults to false.
     */
    single : false,

    plain: true,

    constructor: function (cfg) {
        var me = this,
            gridStore;
            
        me.selected = [];
        me.addEvents(
            /**
             * @event checkchange
             * Fires when there is a change in checked items from this list
             * @param {Object} item Ext.menu.CheckItem
             * @param {Object} checked The checked value that was set
             */
            'checkchange'
        );

        me.callParent(arguments);

        gridStore = me.grid.store;

        if (me.store) {
            me.add({
                text: me.loadingText,
                iconCls: 'loading-indicator'
            });
            me.store.on('load', me.onLoad, me);

        // A ListMenu which is completely unconfigured acquires its store from the unique values of its field in the store.
        // If there are no records in the grid store, then we know it's async and we need to listen for its 'load' event.
        } else if (gridStore.data.length) {
            me.createMenuStore();
        } else {
            gridStore.on('load', me.createMenuStore, me, {single: true});
        }
    },

    destroy : function () {
        var me = this,
            store = me.store;

        if (store) {
            if (me.autoStore) {
                store.destroyStore();
            } else {
                store.un('unload', me.onLoad, me);
            }
        }

        if (me.autoGeneratedOptions) {
            me.autoGeneratedOptions = null;

            me.grid.store.un({
                scope: me,
                datachanged: me.onDataChanged,
                update: me.onDataChanged
            });
        }

        me.callParent();
    },

    /**
     * Lists will initially show a 'loading' item while the data is retrieved from the store.
     * In some cases the loaded data will result in a list that goes off the screen to the
     * right (as placement calculations were done with the loading item). This adapter will
     * allow show to be called with no arguments to show with the previous arguments and
     * thus recalculate the width and potentially hang the menu from the left.
     */
    show : function () {
        var me = this;
        if (me.loadOnShow && !me.loaded && !me.store.loading) {
            me.store.load();
        }
        me.callParent();
    },

    onDataChanged: function (store) {
        // If the menu item options (and the options store) are being auto-generated from the grid store, then it
        // needs to know when the grid store has changed its data so it can remain in sync.
        //
        // We need to gather the `autoGeneratedOptions` every time the menu items are created so we can compare values.
        var autoGeneratedOptions = this.autoGeneratedOptions;

        // Note that autoGeneratedOptions won't be populated with values until the menu is shown and the Filter item's
        // items are created.
        if (autoGeneratedOptions) {
            // Get all unique values, including nulls, either from .data or ._source (if filtered) and compare to the
            // unique options gathered when the menu items are instanced.
            if (!Ext.Array.equals(store.collect(this.idField, true, store.isFiltered()).sort(), autoGeneratedOptions.sort())) {
                this.menu.createMenuStore(store);
            }
        }
    },

    /** @private */
    onLoad: function (store, records) {
        var me = this,
            gid, itemValue, i, len,
            listeners = {
                checkchange: me.checkChange,
                scope: me
            };

        Ext.suspendLayouts();
        me.removeAll(true);
        gid = me.single ? Ext.id() : null;
        for (i = 0, len = records.length; i < len; i++) {
            itemValue = records[i].get(me.idField);
            me.add(Ext.create('Ext.menu.CheckItem', {
                text: records[i].get(me.labelField),
                group: gid,
                checked: Ext.Array.contains(me.selected, itemValue),
                hideOnClick: false,
                value: itemValue,
                listeners: listeners
            }));
        }

        me.loaded = true;
        Ext.resumeLayouts(true);
        me.fireEvent('load', me, records);
    },

    createMenuStore: function () {
        var me = this,
            options = me.options || me.grid.store.collect(me.dataIndex, false, true),
            i = 0,
            len = options.length,
            storeOptions = [],
            idField = me.idField,
            labelField = me.labelField,
            value;

        for (; i < len; i++) {
            value = options[i];

            switch (Ext.type(value)) {
                case 'array':
                    storeOptions.push(value);
                    break;
                case 'object':
                    storeOptions.push([value[idField], value[labelField]]);
                    break;
                default:
                    if (value != null) {
                        storeOptions.push([value, value]);
                    }
            }
        }

        me.store = Ext.create('Ext.data.ArrayStore', {
            fields: [idField, labelField],
            data: storeOptions,
            listeners: {
                load: me.onLoad,
                scope: me
            }
        });

        me.loaded = true;
        me.autoStore = true;
    },

    /**
     * Get the selected items.
     * @return {Array} selected
     */
    getSelected : function () {
        return this.selected;
    },

    /** @private */
    setSelected : function (value) {
        value = this.selected = [].concat(value);

        if (this.loaded) {
            this.items.each(function(item){
                item.setChecked(false, true);
                for (var i = 0, len = value.length; i < len; i++) {
                    if (item.value == value[i]) {
                        item.setChecked(true, true);
                    }
                }
            });
        }
    },

    /**
     * Handler for the 'checkchange' event from an check item in this menu
     * @param {Object} item Ext.menu.CheckItem
     * @param {Object} checked The checked value that was set
     */
    checkChange : function (item, checked) {
        var value = [];
        this.items.each(function(item){
            if (item.checked) {
                value.push(item.value);
            }
        });
        this.selected = value;

        this.fireEvent('checkchange', item, checked);
    }
});