1. #1
    Sencha User
    Join Date
    Jun 2010
    Location
    Buenos Aires, Argentina
    Posts
    213
    Vote Rating
    9
    ldonofrio will become famous soon enough

      3  

    Default Ext.ux.grid.FilterBar plugin

    Ext.ux.grid.FilterBar plugin


    v1.1 on Sencha Market. Supports operators for number and date filters
    https://market.sencha.com/users/68/addons/95


    UPDATED CODE FOR 4.1 (2012-06-01):
    Code:
    /**
     * Plugin that enable filters on the grid header.<br>
     * The header filters are integrated with new Ext4 <code>Ext.data.Store</code> filters.<br>
     *
     * @author Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * @version 1.0 beta 1 (supports 4.1 beta)
     * @updated 2011-10-18 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Support renderHidden config option, isVisible(), and setVisible() methods (added getFilterBar() method to the grid)
     * Fix filter bug that append filters to Store filters MixedCollection
     * Fix layout broken on initial render when columns have width property
     * @updated 2011-10-24 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Rendering code rewrited, filters are now rendered inside de column headers, this solves scrollable grids issues, now scroll, columnMove, and columnHide/Show is handled by the headerCt
     * Support showClearButton config option, render a clear Button for each filter to clear the applied filter (uses Ext.ux.form.field.ClearButton plugin)
     * Added clearFilters() method.
     * @updated 2011-10-25 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Allow preconfigured filter's types and auto based on store field data types
     * Auto generated stores for combo and list filters (local collect or server in autoStoresRemoteProperty response property)
     * @updated 2011-10-26 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Completelly rewriten to support reconfigure filters on grid's reconfigure
     * Supports clearAll and showHide buttons rendered in an actioncolumn or in new generetad small column
     * @updated 2011-10-27 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Added support to 4.0.7 (columnresize not fired correctly on this build)
     * @updated 2011-11-02 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Filter on ENTER
     * Defaults submitFormat on date filter to 'Y-m-d' and use that in applyFilters for local filtering
     * Added null value support on combo and list filters (autoStoresNullValue and autoStoresNullText)
     * Fixed some combo styles
     * @updated 2011-11-10 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Parse and show initial filters applied to the store (only property -> value filters, filterFn is unsuported)
     * @updated 2011-12-12 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Extends AbstractPlugin and use Observable as a Mixin
     * Yes/No localization on constructor
     * @updated 2012-01-03 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Added some support for 4.1 beta
     * @updated 2012-01-05 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * 99% support for 4.1 beta. Seems to be working
     * @updated 2012-03-22 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Fix focusFirstField method
     * Allow to specify listConfig in combo filter
     * Intercept column's setPadding for all columns except actionColumn or extraColumn (fix checkBoxSelectionModel header)
     * @updated 2012-05-07 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Fully tested on 4.1 final
     * @updated 2012-05-31 by Ing. Leonardo D'Onofrio (leonardo_donofrio at hotmail.com)
     * Fix padding issue on checkbox column
    */
    
    Ext.define('Ext.ux.grid.FilterBar', {
        extend: 'Ext.AbstractPlugin',
        alias: 'plugin.filterbar',
        uses: [
            'Ext.window.MessageBox',
            'Ext.ux.form.field.ClearButton',
            'Ext.container.Container',
            'Ext.util.DelayedTask',
            'Ext.layout.container.HBox',
            'Ext.data.ArrayStore',
            'Ext.button.Button',
            'Ext.form.field.Text',
            'Ext.form.field.Number',
            'Ext.form.field.Date',
            'Ext.form.field.ComboBox'
        ],
        mixins: {
            observable: 'Ext.util.Observable'
        },
    
        updateBuffer                : 800,                    // buffer time to apply filtering when typing/selecting
    
        columnFilteredCls            : Ext.baseCSSPrefix + 'column-filtered', // CSS class to apply to the filtered column header
    
        renderHidden                : true,                    // renders the filters hidden by default, use in combination with showShowHideButton
        showShowHideButton            : true,                    // add show/hide button in actioncolumn header if found, if not a new small column is created
        showHideButtonTooltipDo        : 'Show filter bar',    // button tooltip show
        showHideButtonTooltipUndo    : 'Hide filter bar',    // button tooltip hide
        showHideButtonIconCls        : 'filter',                // button iconCls
    
        showClearButton                : true,                    // use Ext.ux.form.field.ClearButton to allow user to clear each filter, the same as showShowHideButton
        showClearAllButton            : true,                    // add clearAll button in actioncolumn header if found, if not a new small column is created
        clearAllButtonIconCls        : 'clear-filters',         // css class with the icon of the clear all button
        clearAllButtonTooltip        : 'Clear all filters',    // button tooltip
    
        autoStoresRemoteProperty    : 'autoStores',            // if no store is configured for a combo filter then stores are created automatically, if remoteFilter is true then use this property to return arrayStores from the server
        autoStoresNullValue            : '###NULL###',            // value send to the server to expecify null filter
        autoStoresNullText            : '[empty]',            // NULL Display Text
        autoUpdateAutoStores        : false,                // if set to true combo autoStores are updated each time that a filter is applied
    
        boolTpl: {
            xtype: 'combo',
            queryMode: 'local',
            forceSelection: true,
            triggerAction: 'all',
            editable: false,
            store: [
                [1, 'Yes'],
                [0, 'No']
            ],
            operator: 'eq'
        },
        dateTpl: {
            xtype: 'datefield',
            editable: true,
            submitFormat: 'Y-m-d',
            operator: 'eq'
        },
        floatTpl: {
            xtype: 'numberfield',
            allowDecimals: true,
            minValue: 0,
            hideTrigger: true,
            keyNavEnabled: false,
            mouseWheelEnabled: false,
            operator: 'eq'
        },
        intTpl: {
            xtype: 'numberfield',
            allowDecimals: false,
            minValue: 0,
            operator: 'eq'
        },
        stringTpl: {
            xtype: 'textfield',
            operator: 'like'
        },
        comboTpl: {
            xtype: 'combo',
            queryMode: 'local',
            forceSelection: true,
            editable: false,
            triggerAction: 'all',
            operator: 'eq'
        },
        listTpl: {
            xtype: 'combo',
            queryMode: 'local',
            forceSelection: true,
            editable: false,
            triggerAction: 'all',
            multiSelect: true,
            operator: 'in'
        },
    
        constructor: function() {
            var me = this;
    
            me.boolTpl.store[0][1] = Ext.htmlDecode(Ext.MessageBox.buttonText.yes);
            me.boolTpl.store[1][1] = Ext.htmlDecode(Ext.MessageBox.buttonText.no);
    
            me.mixins.observable.constructor.call(me);
            me.callParent(arguments);
        },
    
        // private
        init: function(grid) {
            var me = this;
    
            grid.on({
                columnresize: me.resizeContainer,
                columnhide: me.resizeContainer,
                columnshow: me.resizeContainer,
                beforedestroy: me.unsetup,
                reconfigure: me.resetup,
                scope: me
            });
    
            grid.addEvents('filterupdated');
    
            Ext.apply(grid, {
                filterBar: me,
                getFilterBar: function() {
                    return this.filterBar;
                }
            });
    
            me.setup(grid);
        },
    
        // private
        setup: function(grid) {
            var me = this;
    
            me.grid = grid;
            me.visible = !me.renderHidden;
            me.autoStores = Ext.create('Ext.util.MixedCollection');
            me.autoStoresLoaded = false;
            me.columns = Ext.create('Ext.util.MixedCollection');
            me.containers = Ext.create('Ext.util.MixedCollection');
            me.fields = Ext.create('Ext.util.MixedCollection');
            me.actionColumn = me.grid.down('actioncolumn') || me.grid.down('actioncolumnpro');
            me.extraColumn = null;
            me.clearAllEl = null;
            me.showHideEl = null;
            me.task = Ext.create('Ext.util.DelayedTask');
            me.filterArray = [];
    
            me.overrideProxy();
            me.parseFiltersConfig();     // sets me.columns and me.autoStores
            me.parseInitialFilters();   // sets me.filterArray with the store previous filters if any (adds operator and type if missing)
            me.renderExtraColumn();     // sets me.extraColumn if applicable
    
            // renders the filter's bar
            if (grid.rendered) {
                me.renderFilterBar(grid);
            } else {
                grid.on('afterrender', me.renderFilterBar, me, { single: true });
            }
        },
    
        // private
        unsetup: function(grid) {
            var me = this;
    
            if (me.autoStores.getCount()) {
                me.grid.store.un('load', me.fillAutoStores, me);
            }
    
            me.autoStores.each(function(item) {
                Ext.destroy(item);
            });
            me.autoStores.clear();
            me.autoStores = null;
            me.columns.each(function(column) {
                if (column.rendered) {
                    if(column.getEl().hasCls(me.columnFilteredCls)) {
                        column.getEl().removeCls(me.columnFilteredCls);
                    }
                }
            }, me);
            me.columns.clear();
            me.columns = null;
            me.fields.each(function(item) {
                Ext.destroy(item);
            });
            me.fields.clear();
            me.fields = null;
            me.containers.each(function(item) {
                Ext.destroy(item);
            });
            me.containers.clear();
            me.containers = null;
            if (me.clearAllEl) {
                Ext.destroy(me.clearAllEl);
                me.clearAllEl = null;
            }
            if (me.showHideEl) {
                Ext.destroy(me.showHideEl);
                me.showHideEl = null;
            }
            if (me.extraColumn) {
                me.grid.headerCt.items.remove(me.extraColumn);
                Ext.destroy(me.extraColumn);
                me.extraColumn = null;
            }
            me.task = null;
            me.filterArray = null;
        },
    
        // private
        resetup: function(grid) {
            var me = this;
    
            me.unsetup(grid);
            me.setup(grid);
        },
    
        // private
        overrideProxy: function() {
            var me = this;
    
            // override encodeFilters to append type and operator in remote filtering
            Ext.apply(me.grid.store.proxy, {
                encodeFilters: function(filters) {
                    var min = [],
                        length = filters.length,
                        i = 0;
    
                    for (; i < length; i++) {
                        min[i] = {
                            property: filters[i].property,
                            value   : filters[i].value
                        };
                        if (filters[i].type) {
                            min[i].type = filters[i].type;
                        }
                        if (filters[i].operator) {
                            min[i].operator = filters[i].operator;
                        }
                    }
                    return this.applyEncoding(min);
                }
            });
        },
    
        // private
        parseFiltersConfig: function() {
            var me = this;
            var columns = this.grid.headerCt.getGridColumns(true);
            me.columns.clear();
            me.autoStores.clear();
            Ext.each(columns, function(column) {
                if (column.filter) {
                    if (column.filter === true || column.filter === 'auto') { // automatic types configuration (store based)
                        var type = me.grid.store.model.prototype.fields.get(column.dataIndex).type.type;
                        if (type == 'auto') type = 'string';
                        column.filter = type;
                    }
                    if (Ext.isString(column.filter)) {
                        column.filter = {
                            type: column.filter // only set type to then use templates
                        };
                    }
                    if (column.filter.type) {
                        column.filter = Ext.applyIf(column.filter, me[column.filter.type + 'Tpl']); // also use templates but with user configuration
                    }
    
                    if (column.filter.xtype == 'combo' && !column.filter.store) {
                        column.autoStore = true;
                        column.filter.store = Ext.create('Ext.data.ArrayStore', {
                            fields: [{
                                name: 'text'
                            },{
                                name: 'id'
                            }]
                        });
                        me.autoStores.add(column.dataIndex, column.filter.store);
                        column.filter = Ext.apply(column.filter, {
                            displayField: 'text',
                            valueField: 'id'
                        });
                    }
    
                    if (!column.filter.type) {
                        switch(column.filter.xtype) {
                            case 'combo':
                                column.filter.type = (column.filter.multiSelect ? 'list' : 'combo');
                                break;
                            case 'datefield':
                                column.filter.type = 'date';
                                break;
                            case 'numberfield':
                                column.filter.type = (column.filter.allowDecimals ? 'float' : 'int');
                                break;
                            default:
                                column.filter.type = 'string'
                        }
                    }
    
                    if (!column.filter.operator) {
                        column.filter.operator = me[column.filter.type + 'Tpl'].operator;
                    }
                    me.columns.add(column.dataIndex, column);
                }
            }, me);
            if (me.autoStores.getCount()) {
                if (me.grid.store.getCount() > 0) {
                    me.fillAutoStores(me.grid.store);
                }
                if (me.grid.store.remoteFilter) {
                    var autoStores = [];
                    me.autoStores.eachKey(function(key, item) {
                        autoStores.push(key);
                    });
                    me.grid.store.proxy.extraParams = me.grid.store.proxy.extraParams || {};
                    me.grid.store.proxy.extraParams[me.autoStoresRemoteProperty] = autoStores;
                }
                me.grid.store.on('load', me.fillAutoStores, me);
            }
        },
    
        // private
        fillAutoStores: function(store) {
            var me = this;
    
            if (!me.autoUpdateAutoStores && me.autoStoresLoaded) return;
    
            me.autoStores.eachKey(function(key, item) {
                var field = me.fields.get(key);
                if (field) {
                    field.suspendEvents();
                    var fieldValue = field.getValue();
                }
                if (!store.remoteFilter) { // values from local store
                    var data = store.collect(key, true, false).sort();
                    var records = [];
                    Ext.each(data, function(txt) {
                        if (Ext.isEmpty(txt)) {
                            Ext.Array.insert(records, 0, [{
                                text: me.autoStoresNullText,
                                id: me.autoStoresNullValue
                            }]);
                        } else {
                            records.push({
                                text: txt,
                                id: txt
                            });
                        }
                    });
                    item.loadData(records);
                } else { // values from server
                    if (store.proxy.reader.rawData[me.autoStoresRemoteProperty]) {
                        var data = store.proxy.reader.rawData[me.autoStoresRemoteProperty];
                        if (data[key]) {
                            var records = [];
                            Ext.each(data[key].sort(), function(txt) {
                                if (Ext.isEmpty(txt)) {
                                    Ext.Array.insert(records, 0, [{
                                        text: me.autoStoresNullText,
                                        id: me.autoStoresNullValue
                                    }]);
                                } else {
                                    records.push({
                                        text: txt,
                                        id: txt
                                    });
                                }
                            });
                            item.loadData(records);
                        }
                    }
                }
                if (field) {
                    field.setValue(fieldValue);
                    field.resumeEvents();
                }
            }, me);
            me.autoStoresLoaded = true;
            if (me.grid.store.remoteFilter && !me.autoUpdateAutoStores) {
                delete me.grid.store.proxy.extraParams[me.autoStoresRemoteProperty];
            }
        },
    
        // private
        parseInitialFilters: function() {
            var me = this;
    
            me.filterArray = [];
            me.grid.store.filters.each(function(filter) {
                // try to parse initial filters, for now filterFn is unsuported
                if (filter.property && !Ext.isEmpty(filter.value) && me.columns.get(filter.property)) {
                    if (!filter.type) filter.type = me.columns.get(filter.property).filter.type;
                    if (!filter.operator) filter.operator = me.columns.get(filter.property).filter.operator;
                    me.filterArray.push(filter);
                }
            }, me);
        },
    
        // private
        renderExtraColumn: function() {
            var me = this;
    
            if (me.columns.getCount() && !me.actionColumn && (me.showClearAllButton || me.showShowHideButton)) {
                var extraColumnCssClass = Ext.baseCSSPrefix + 'filter-bar-extra-column-hack';
                if (!document.getElementById(extraColumnCssClass)) {
                    var style = document.createElement('style');
                    var css = 'tr.' + Ext.baseCSSPrefix + 'grid-row td.' + extraColumnCssClass + ' { background-color: #ffffff !important; border-color: #ffffff !important; }';
                    style.setAttribute('type', 'text/css');
                    style.setAttribute('id', extraColumnCssClass);
                    document.body.appendChild(style);
                    if (style.styleSheet) {       // IE
                        style.styleSheet.cssText = css;
                    } else {                    // others
                        var cssNode = document.createTextNode(css);
                        style.appendChild(cssNode);
                    }
                }
                me.extraColumn = Ext.create('Ext.grid.column.Column', {
                    draggable: false,
                    hideable: false,
                    menuDisabled: true,
                    sortable: false,
                    resizable: false,
                    fixed: true,
                    width: 28,
                    minWidth: 28,
                    maxWidth: 28,
                    header: '&nbsp;',
                    tdCls: extraColumnCssClass
                });
                me.grid.headerCt.add(me.extraColumn);
            }
        },
    
        // private
        renderFilterBar: function(grid) {
            var me = this;
    
            me.containers.clear();
            me.fields.clear();
            me.columns.eachKey(function(key, column) {
                var listConfig = column.filter.listConfig || {};
                listConfig = Ext.apply(listConfig, {
                    style: 'border-top-width: 1px'
                });
                var field = Ext.widget(column.filter.xtype, Ext.apply(column.filter, {
                    dataIndex: key,
                    flex: 1,
                    margin: 0,
                    fieldStyle: 'border-left-width: 0px; border-bottom-width: 0px;',
                    listConfig: listConfig,
                    preventMark: true,
                    enableKeyEvents: true,
                    listeners: {
                        change: me.applyFilters,
                        keypress: function(txt, e){
                            if(e.getCharCode() == 13) {
                                me.applyFilters(txt, txt.getValue());
                                e.stopEvent();
                            }
                        },
                        scope: me
                    },
                    plugins: (!me.showClearButton ? [] : [{
                        ptype: 'clearbutton'
                    }])
                }));
                me.fields.add(column.dataIndex, field);
                var container = Ext.create('Ext.container.Container', {
                    dataIndex: key,
                    layout: 'hbox',
                    bodyStyle: 'background-color: "transparent";',
                    width: column.getWidth(),
                    items: [field],
                    listeners: {
                        scope: me,
                        element: 'el',
                        mousedown: function(e) { e.stopPropagation(); },
                        click: function(e) { e.stopPropagation(); },
                        dblclick: function(e) { e.stopPropagation(); }/*,
                        keydown: function(e) { e.stopPropagation(); },
                        keypress: function(e) { e.stopPropagation(); },
                        keyup: function(e) { e.stopPropagation(); }*/
                    }
                });
                me.containers.add(column.dataIndex, container);
                container.render(Ext.get(column.id));
            }, me);
            var excludedCols = [];
            if (me.actionColumn) excludedCols.push(me.actionColumn.id);
            if (me.extraColumn) excludedCols.push(me.extraColumn.id);
            Ext.each(me.grid.headerCt.getGridColumns(true), function(column) {
                if (!Ext.Array.contains(excludedCols, column.id)) {
                    column.setPadding = Ext.Function.createInterceptor(column.setPadding, function(h) {
                        if (column.text == ' ') { //checkbox column
                            this.titleEl.setStyle({
                                paddingTop: '4px'
                            });
                        }
                        return false;
                    });
                }
            });
    
    
            me.setVisible(me.visible);
    
            me.renderButtons();
    
            me.showInitialFilters();
        },
    
        //private
        renderButtons: function() {
            var me = this;
    
            if (me.showShowHideButton && me.columns.getCount()) {
                var column = me.actionColumn || me.extraColumn;
                var buttonEl = column.el.first().first();
                me.showHideEl = Ext.get(Ext.core.DomHelper.append(buttonEl, {
                    tag: 'div',
                    style: 'position: absolute; width: 16px; height: 16px; top: 3px; cursor: pointer; left: ' + parseInt((column.el.getWidth() - 16) / 2) + 'px',
                    cls: me.showHideButtonIconCls,
                    'data-qtip': (me.renderHidden ? me.showHideButtonTooltipDo : me.showHideButtonTooltipUndo)
                }));
                me.showHideEl.on('click', function() {
                    me.setVisible(!me.isVisible());
                    me.showHideEl.set({
                        'data-qtip': (!me.isVisible() ? me.showHideButtonTooltipDo : me.showHideButtonTooltipUndo)
                    });
                });
            }
    
            if (me.showClearAllButton && me.columns.getCount()) {
                var column = me.actionColumn || me.extraColumn;
                var buttonEl = column.el.first().first();
                me.clearAllEl = Ext.get(Ext.core.DomHelper.append(buttonEl, {
                    tag: 'div',
                    style: 'position: absolute; width: 16px; height: 16px; top: 25px; cursor: pointer; left: ' + parseInt((column.el.getWidth() - 16) / 2) + 'px',
                    cls: me.clearAllButtonIconCls,
                    'data-qtip': me.clearAllButtonTooltip
                }));
    
                me.clearAllEl.hide();
                me.clearAllEl.on('click', function() {
                    me.clearFilters();
                });
            }
        },
    
        // private
        showInitialFilters: function() {
            var me = this;
    
            Ext.each(me.filterArray, function(filter) {
                var column = me.columns.get(filter.property);
                var field = me.fields.get(filter.property);
                if(!column.getEl().hasCls(me.columnFilteredCls)) {
                    column.getEl().addCls(me.columnFilteredCls);
                }
                field.suspendEvents();
                field.setValue(filter.value);
                field.resumeEvents();
            });
    
            if (me.filterArray.length && me.showClearAllButton) {
                me.clearAllEl.show({duration: 1000});
            }
        },
    
        // private
        resizeContainer: function(headerCt, col) {
            var me = this;
            var dataIndex = col.dataIndex;
    
            if (!dataIndex) return;
            var item = me.containers.get(dataIndex);
            if (item && item.rendered) {
                var itemWidth = item.getWidth();
                var colWidth = me.columns.get(dataIndex).getWidth();
                if (itemWidth != colWidth) {
                    item.setWidth(me.columns.get(dataIndex).getWidth());
                    item.doLayout();
                }
            }
        },
    
        // private
        applyFilters: function(field, newVal) {
            var me = this;
    
            if (!field.isValid()) {
                return;
            }
            var grid = me.grid;
            var column = me.columns.get(field.dataIndex);
            newVal = (grid.store.remoteFilter ? field.getSubmitValue() : newVal);
            me.task.delay(me.updateBuffer, function() {
                if (Ext.isArray(newVal) && newVal.length == 0) {
                    newVal = '';
                }
                var myIndex = -1;
                Ext.each(me.filterArray, function(item2, index, allItems) {
                    if(item2.property === column.dataIndex) {
                        myIndex = index;
                    }
                });
                if(myIndex != -1) {
                    me.filterArray.splice(myIndex, 1);
                }
                if(!Ext.isEmpty(newVal)) {
                    if (!grid.store.remoteFilter) {
                        var filterFn;
                        switch(column.filter.operator) {
                            case 'eq':
                                filterFn = function(item) {
                                    if (column.filter.type == 'date') {
                                        return Ext.Date.clearTime(item.get(column.dataIndex), true).getTime() == Ext.Date.clearTime(newVal, true).getTime();
                                    } else {
                                        return (Ext.isEmpty(item.get(column.dataIndex)) ? me.autoStoresNullValue : item.get(column.dataIndex)) == (Ext.isEmpty(newVal) ? me.autoStoresNullValue : newVal);
                                    }
                                };
                                break;
                            case 'like':
                                filterFn = function(item) {
                                    var re = new RegExp(newVal, 'i');
                                    return re.test(item.get(column.dataIndex));
                                };
                                break;
                            case 'in':
                                filterFn = function(item) {
                                    var re = new RegExp('^' + newVal.join('|') + '$', 'i');
                                    return re.test((Ext.isEmpty(item.get(column.dataIndex)) ? me.autoStoresNullValue : item.get(column.dataIndex)));
                                };
                                break;
                        }
                        me.filterArray.push(Ext.create('Ext.util.Filter', {
                            property: column.dataIndex,
                            filterFn: filterFn,
                            me: me
                        }));
                    } else {
                        me.filterArray.push(Ext.create('Ext.util.Filter', {
                            property: column.dataIndex,
                            value: newVal,
                            type: column.filter.type,
                            operator: column.filter.operator
                        }));
                    }
                    if(!column.getEl().hasCls(me.columnFilteredCls)) {
                        column.getEl().addCls(me.columnFilteredCls);
                    }
                } else {
                    if(column.getEl().hasCls(me.columnFilteredCls)) {
                        column.getEl().removeCls(me.columnFilteredCls);
                    }
                }
                grid.store.currentPage = 1;
                if(me.filterArray.length > 0) {
                    if (!grid.store.remoteFilter) grid.store.clearFilter();
                    grid.store.filters.clear();
                    grid.store.filter(me.filterArray);
                    if (me.clearAllEl) {
                        me.clearAllEl.show({duration: 1000});
                    }
                } else {
                    grid.store.clearFilter();
                    if (me.clearAllEl) {
                        me.clearAllEl.hide({duration: 1000});
                    }
                }
                if (!grid.store.remoteFilter && me.autoUpdateAutoStores) {
                    me.fillAutoStores();
                }
                me.fireEvent('filterupdated', me.filterArray);
            }, me);
        },
    
        //private
        getFirstField: function() {
            var me = this,
                field = undefined;
    
            Ext.each(me.grid.headerCt.getGridColumns(true), function(col) {
                if (col.filter) {
                    field = me.fields.get(col.dataIndex);
                    return false;
                }
            });
    
            return field;
        },
    
        //private
        focusFirstField: function() {
            var me = this;
    
            var field = me.getFirstField();
    
            if (field) {
                field.focus(false, 200);
            }
        },
    
        clearFilters: function() {
            var me = this;
    
            if (me.filterArray.length == 0) return;
            me.filterArray = [];
            me.fields.eachKey(function(key, field) {
                field.suspendEvents();
                field.reset();
                field.resumeEvents();
                var column = me.columns.get(key);
                if(column.getEl().hasCls(Ext.baseCSSPrefix + 'column-filtered')) {
                    column.getEl().removeCls(Ext.baseCSSPrefix + 'column-filtered');
                }
            }, me);
            me.grid.store.clearFilter();
            if (me.clearAllEl) {
                me.clearAllEl.hide({duration: 1000});
            }
    
            me.fireEvent('filterupdated', me.filterArray);
        },
    
        isVisible: function() {
            var me = this;
    
            return me.visible;
        },
    
        setVisible: function(visible) {
            var me = this;
    
            me.containers.each(function(item) {
                item.setVisible(visible);
            });
    
            if (visible) {
                me.focusFirstField();
            }
            me.grid.headerCt.doLayout();
            me.visible = visible;
        }
    });

    None of the 4.0 filter extensións seems to work correctly in all scenarios, so i've decided to create my own.
    Inially i use as a base this:
    http://www.sencha.com/forum/showthre...d.HeaderFIlter
    and this
    http://www.sencha.com/forum/showthre...Header-Filters

    Finally last week i've rewriten the code to support all escenarios (remoteFilter true and false, grid reconfigure, etc).

    I've added a lot of features like, autoConfigurated fields, autoLoaded combo stores, clearButton, clearAllButton, show/hide button, etc (See full features in the code comments).

    Plugin Code and test example attached as zip.

    Regards

    Leonardo
    Last edited by ldonofrio; 27 Sep 2012 at 6:37 AM. Reason: v1.1 supports operators

  2. #2
    Sencha - Support Team scottmartin's Avatar
    Join Date
    Jul 2010
    Location
    Houston, Tx
    Posts
    9,069
    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


    I like the update ... very nice. Here are a few comments:

    -If I set Filter to false, the column does not display the same as the rest of the header (it displays the same as when you hover the clear all column). It should display the same as the action column that you created for clear all filter before hover.

    -If I hover the 'clear all' column, the CSS is wrong ... it displays incorrectly from all the other columns

    -There is a drawing delay on flex columns. You can see it resize live on the grid. I understand the reason, but is there a way to get the fix this? It makes the column look like it is rendering slow.

    -It would be nice to have a setting for filter on pressing enter key, instead of a live buffer delay only.

    -Thought: It 'may' be helpful to display checkboxes on a 'list' column. I figured it out quickly, but a end-user may be confused as to why the combo is not closing on selection.

    -A live demo would be helpful for displaying all the features of your plugin.

    I can send some screens of the drawing issues if needed.
    For a first release, this is a very nice plugin!

    Regards,
    Scott.

  3. #3
    Sencha - Support Team scottmartin's Avatar
    Join Date
    Jul 2010
    Location
    Houston, Tx
    Posts
    9,069
    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 Filters column editors gone on reconfigure

    Filters column editors gone on reconfigure


    On reconfigure, I lose all of the columns editors for columns (filter bar is still displayed, editors missing)

    Example:
    I have a grid setup that displays 10 columns with filters: true for each.

    When I click on a row, I have a toggle function that expands a panel to display a form and it also re-configures the grid to display 3 fields, since there is now less room on the screen for the grid due to the form.

    When I perform this, I lose all of the column editors in FilterBar. Is there something that is needed to maintain the filter columns?

    Code: (user click on grid row)

    Code:
      this.grid.getView().on('itemdblclick', function(view, record, item, index, node, e) {
         this.on_toggle_form(false);
     }, this);
    
     ...
    
    
        on_toggle_form: function(toggle) {
            if (this.form) {
                if (this.form.hidden) {
                    this.form.show();
                    this.form.setVisible(true);
                    this.grid.reconfigure(storeUsers, colUsersMini);
                } else {
                    if (toggle === true){
                        this.form.hide();
                        this.form.setVisible(false);
                        this.grid.reconfigure(storeUsers, colUsersFull);
                    }
                }
            }
        },
    Code:
    var colUsersFull = [
        { dataIndex: 'user_name', header: 'User Name', width: 100, filter: true },
        { dataIndex: 'first_name', header: 'First Name', filter: true },
        { dataIndex: 'last_name', header: 'Last Name', filter: true },
        { dataIndex: 'level_name', header: 'Level', filter: true },
        { dataIndex: 'location_name', header: 'Location', filter: true },
        { dataIndex: 'phone', header: 'Phone', filter: true },
        { dataIndex: 'email_address', header: 'Email', flex: 1, filter: true  }
    ];
    
    
    var colUsersMini = [
        { dataIndex: 'user_name', header: 'User Name', width: 125, filter: true },
        { dataIndex: 'first_name', header: 'First Name', filter: true },
        { dataIndex: 'last_name', header: 'Last Name', filter: true },
        { dataIndex: 'level_name', header: 'Level', flex: 1, filter: true }
    ];
    Regards,
    Scott.

  4. #4
    Sencha User
    Join Date
    Jun 2010
    Location
    Buenos Aires, Argentina
    Posts
    213
    Vote Rating
    9
    ldonofrio will become famous soon enough

      0  

    Default


    Thanks Scott,

    Will try to answer all.

    1) filter = false or not filter config columns: That is because if filter is false no container is added to the column el, so what you are viewing is "extended" column header, will try to add a dummy container to that columns maybe configurable via a css class.
    2) extraColumn: Don't understand the point? Do you mean the background-color = white on hover? That's intentional, i get the idea from the saki's one that you linked. If is not, post screenshot please.
    3) flex columns: I know that, seems like a bug in 4.0.7, in 4.0.2a columnresize is fired when the column's width change, doesn't matter if the user or the layout do that. In 4.0.7 only resize by the user is fired, so i've to listen to the headerCt afterlayout event. For now is the best that i can get. I suggest you wait for 4.1, i'm sure it's a bug, maybe an override looking at 4.0.2 code can work.
    4) filter on enter key: I'll add it, is trivial.
    5) checkboxes in multiSelect combo: This is an Ext issue, the multiSelect combos look like that by default on Ext, i've my JS/SASS override to add the check unchek icon stuff.
    6) Live demo: Currently i don't have an open server to host it, will try to find one
    7) Reconfigure: I remember testing something like that, i've rewrited all of this to support reconfigure, put a breakpoint, console.log on whatever in resetup method and check if is running. I'll check that tomorrow too.

    Regards

    Leonardo

  5. #5
    Ext JS Premium Member
    Join Date
    Nov 2008
    Posts
    292
    Vote Rating
    3
    wki01 is on a distinguished road

      0  

    Default


    Great plugin.

    I need two new features:

    - The use of paramPrefix as in the standard filter.
    - Run the submit date filter as YYYYMMDD.

    thanks

  6. #6
    Sencha User
    Join Date
    Jun 2010
    Location
    Buenos Aires, Argentina
    Posts
    213
    Vote Rating
    9
    ldonofrio will become famous soon enough

      0  

    Default


    1) This plugin uses the ext4 filters, so you can change that in the store server proxy (filterParam defaults to filter). Nothing to do with this
    2) I'll add submitFormat = 'Y-m-d' by default to the next release, for now you can override dateTpl config option or simple pass filter: {type: 'date', submitFormat: 'Y-m-d'} in each date column

    Regards
    Leonardo

    Quote Originally Posted by wki01 View Post
    Great plugin.

    I need two new features:

    - The use of paramPrefix as in the standard filter.
    - Run the submit date filter as YYYYMMDD.

    thanks

  7. #7
    Ext JS Premium Member Spenna's Avatar
    Join Date
    Apr 2008
    Posts
    65
    Vote Rating
    0
    Spenna is on a distinguished road

      0  

    Default


    I'm using a custom renderer for one of my columns (basically the column field is an id which i render by looking up appropriate record in a store).
    How can i use this store for the combo in the filter? (the autogenerated combo only shows the original id, so perhaps you should build the combo after the grid records has been rendered..)

  8. #8
    Sencha User
    Join Date
    Jun 2010
    Location
    Buenos Aires, Argentina
    Posts
    213
    Vote Rating
    9
    ldonofrio will become famous soon enough

      0  

    Default


    This plugin definitely is not going to use your column's renderer, it apply filters to the store fisical data.

    I don't know why your doing that (renderer using child store thing), i suggest that you add the description field to the grid store.

    If not you can use something like this
    PHP Code:
    filter: {
       
    type'combo',
       
    store'YOUR_CHILD_STORE_ID_HERE',
       
    valueFiled'YOUR_ID_HERE',
       
    displayField'YOUR_DESCRIPTION_FIELD_HERE'


  9. #9
    Sencha Premium Member
    Join Date
    May 2007
    Location
    USA
    Posts
    73
    Vote Rating
    0
    notjoshing is on a distinguished road

      0  

    Default Textfield vs. Textarea

    Textfield vs. Textarea


    Using 4.0.7, I found that the text was cut off for text fields. I switched to a Textarea instead, and that resolved the issue.

    Josh
    Using:
    ExtJS 2.2, 3.2, 4.0.7a
    WinXP SP3
    FF10,6; IE6; IE8; Safari

  10. #10
    Sencha Premium Member
    Join Date
    May 2007
    Location
    USA
    Posts
    73
    Vote Rating
    0
    notjoshing is on a distinguished road

      0  

    Default RowNumber columns

    RowNumber columns


    When using the RowNumberer plugin, you'll get errors unless you escape the column when creating the filter header. I changed line 268 of the most recent version to do this:

    if (column.filter && column.dataIndex.length ) {

    Josh
    Using:
    ExtJS 2.2, 3.2, 4.0.7a
    WinXP SP3
    FF10,6; IE6; IE8; Safari