Пишем альтернативный фильтр для Grid’a — Часть первая

Данная статья расскажет как можно расширить существующий Ext.toolbar.Paging добавив в него функционал для фильтрации Ext.grid.Panel по выбранным, или выбранным колонкам. Поддерживает и удаленная и локальная сортировка. Имплементацию данного функционала мы сделаем дважды, первый раз в виде наследника Ext.toolbar.Paging, а второй в виде плагина для таблицы.

Имплементация 1

Доступны следующие настройки

{
    filterItemWidth: 300,
    filterOnType: true,
    filterSplitButtonGlyph: "xf0b0@FontAwesome",
    filterSplitButtonText: "Filter"
}

Не забудьте установить свойство remoteFilter в store, если вам необходимо фильтровать все данные, а не только содержимое текущей страницы.

Ext.define('PagingToolbarWithSearch', {
    extend: 'Ext.toolbar.Paging',
    xtype: 'pagingtoolbarwithsearch',

    filterItemWidth: 300,
    filterOnType: true,
    filterSplitButtonGlyph: 'xf0b0@FontAwesome',
    filterSplitButtonText: "Filter",

    getPagingItems: function () {
        var me = this,
            inputListeners = {
                scope: me,
                blur: me.onPagingBlur
            };

        inputListeners[Ext.supports.SpecialKeyDownRepeat ? 'keydown' : 'keypress'] = me.onPagingKeyDown;

        return [{
                itemId: 'first',
                tooltip: me.firstText,
                overflowText: me.firstText,
                iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
                disabled: true,
                handler: me.moveFirst,
                scope: me
            }, {
                itemId: 'prev',
                tooltip: me.prevText,
                overflowText: me.prevText,
                iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
                disabled: true,
                handler: me.movePrevious,
                scope: me
            },
            '-',
            me.beforePageText, {
                xtype: 'numberfield',
                itemId: 'inputItem',
                name: 'inputItem',
                cls: Ext.baseCSSPrefix + 'tbar-page-number',
                allowDecimals: false,
                minValue: 1,
                hideTrigger: true,
                enableKeyEvents: true,
                keyNavEnabled: false,
                selectOnFocus: true,
                submitValue: false,
                // mark it as not a field so the form will not catch it when getting fields
                isFormField: false,
                width: me.inputItemWidth,
                margin: '-1 2 3 2',
                listeners: inputListeners
            }, {
                xtype: 'tbtext',
                itemId: 'afterTextItem',
                html: Ext.String.format(me.afterPageText, 1)
            },
            '-', {
                itemId: 'next',
                tooltip: me.nextText,
                overflowText: me.nextText,
                iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
                disabled: true,
                handler: me.moveNext,
                scope: me
            }, {
                itemId: 'last',
                tooltip: me.lastText,
                overflowText: me.lastText,
                iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
                disabled: true,
                handler: me.moveLast,
                scope: me
            },
            '-', {
                itemId: 'refresh',
                tooltip: me.refreshText,
                overflowText: me.refreshText,
                iconCls: Ext.baseCSSPrefix + 'tbar-loading',
                disabled: me.store.isLoading(),
                handler: me.doRefresh,
                scope: me
            },
            '-',
            this.getFilterSplitButton(),
            this.getFilterTextField()
        ];
    },

    getFilterSplitButton: function () {
        if (!this.filterSplitButton) {
            this.filterSplitButton = Ext.create('Ext.button.Split', {
                text: this.filterSplitButtonText,
                handler: this.doFilter,
                glyph: this.filterSplitButtonGlyph,
                scope: this
            });
        }
        return this.filterSplitButton;
    },

    getFilterTextField: function () {
        if (!this.filterTextField) {
            this.filterTextField = Ext.create('Ext.form.field.Text', {
                submitValue: false,
                isFormField: false,
                width: this.filterItemWidth,
                margin: '-1 2 3 2',
                enableKeyEvents: true
            });
            if (this.filterOnType) {
                this.filterTextField.on('change', this.doFilter, this);
            } else {
                this.filterTextField.on('specialkey', function () {
                    if (e.getKey() == e.ENTER) {
                        this.doFilter();
                    }
                }, this);
            }
        }
        return this.filterTextField;
    },

    beforeRender: function () {
        this.callParent(arguments);

        this.updateBarInfo();
        this.updateSearchColumnsMenu();
    },

    getSelectAllColumnsMenuItem: function () {
        if (!this.selectAllColumnsMenuItem) {
            this.selectAllColumnsMenuItem = Ext.create('Ext.menu.CheckItem', {
                text: "All Columns",
                checked: true,
                listeners: {
                    checkchange: {
                        fn: function (menuCheckItem, checked) {
                            this.getFilterColumnsMenu().items.each(function (item) {
                                if (item.dataIndex) {
                                    item.setChecked(checked);
                                }
                            });
                            this.doFilter();
                        },
                        scope: this
                    }
                }
            });
        }
        return this.selectAllColumnsMenuItem;
    },

    getFilterColumnsMenu: function () {
        if (!this.filterColumnsMenu) {
            this.filterColumnsMenu = Ext.create('Ext.menu.Menu', {
                items: [
                    this.getSelectAllColumnsMenuItem(), {
                        xtype: 'menuseparator'
                    }
                ]
            });
        }
        return this.filterColumnsMenu;
    },

    updateSearchColumnsMenu: function () {
        var columns = this.up('grid').getColumns();
        Ext.Array.each(columns, function (column) {
            this.getFilterColumnsMenu().add({
                xtype: 'menucheckitem',
                text: column.text,
                dataIndex: column.dataIndex,
                checked: true,
                listeners: {
                    checkchange: {
                        fn: function (menuCheckItem, checked) {
                            if (checked) {
                                var selectAllColumnsMenuItemCheck = true;
                                this.getFilterColumnsMenu().items.each(function (item) {
                                    if (item.dataIndex && item.checked == false) {
                                        selectAllColumnsMenuItemCheck = false;
                                        return false;
                                    }
                                });
                                this.getSelectAllColumnsMenuItem().setChecked(selectAllColumnsMenuItemCheck, true);
                            } else {
                                this.getSelectAllColumnsMenuItem().setChecked(false, true);
                            }
                            this.doFilter();
                        },
                        scope: this
                    }
                }
            });
        }, this);
        this.getFilterSplitButton().setMenu(this.getFilterColumnsMenu());
    },

    doFilter: function () {
        var searchColumnIndexes = [],
            searchValue = this.getFilterTextField().getValue();;

        this.getFilterSplitButton().getMenu().items.each(function (item) {
            if (item.dataIndex && item.checked) {
                searchColumnIndexes.push(item.dataIndex);
            }
        }, this);
        if (this.store.remoteFilter) {
            this.remoteFilter(searchColumnIndexes, searchValue)
        } else {
            this.localFilter(searchColumnIndexes, searchValue);
        }

    },

    localFilter: function (searchColumnIndexes, searchValue) {
        this.store.removeFilter(this.filter);
        this.filter = new Ext.util.Filter({
            filterFn: function (record) {
                if (searchColumnIndexes.length === 0 || Ext.isEmpty(searchValue)) {
                    return true;
                }

                var found = false;
                Ext.Array.each(searchColumnIndexes, function (dataIndex) {
                    if (record.get(dataIndex) && record.get(dataIndex).indexOf(searchValue) != -1) {
                        found = true;
                        return false;
                    }
                }, this);
                return found;
            }
        });
        this.store.addFilter(this.filter);
    },

    remoteFilter: function (searchColumnIndexes, searchValue) {
        var remoteFilters = [];
        Ext.Array.each(searchColumnIndexes, function (columnIndex) {
            remoteFilters.push({
                property: columnIndex,
                value: searchValue
            });
        });
        this.store.clearFilter();
        this.store.filter(remoteFilters);
    }

});

Fiddle:

Имплементация 2

Конечно у таблицы, есть другие элементы GUI, поэтому я реализовал её с поддержкой простейшей разметки. В следующем примере у меня есть кнопка перезагрузки данных и два элемента фильтра: кнопка меню и текстовое поле фильтра будут размещаться между разделителями.

initComponent: function () {
        this.items = [
            this.getReloadButton(),
            '-',
            '**FILTER_SPLIT_BUTTON**',
            '**FILTER_TEXT_FIELD**',
            '-'
        ];
        this.callParent();
    },
Ext.define('App.ux.grid.toolbar.plugin.Filter', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.filterplugin',

    filterItemWidth: 100,
    filterOnType: true,
    filterSplitButtonGlyph: 'xf0b0@FontAwesome',
    filterSplitButtonText: "Filter",

    filterSplitButtonPlaceHolder: '**FILTER_SPLIT_BUTTON**',
    filterTextFieldPlaceHolder: '**FILTER_TEXT_FIELD**',

    init: function (toolbar) {
        this.placeFields();
        toolbar.on('afterrender', this.updateSearchColumnsMenu, this);
    },

    placeFields: function () {
        this.replacePlaceHolderByCmp(
            this.filterSplitButtonPlaceHolder,
            this.getFilterSplitButton()
        );
        this.replacePlaceHolderByCmp(
            this.filterTextFieldPlaceHolder,
            this.getFilterTextField()
        );
    },

    replacePlaceHolderByCmp: function (placeHolder, cmp) {
        var placeHolderIndex = this.getIndexByPlaceHolder(placeHolder),
            placeHolderCmp = this.getPlaceHolderCmp(placeHolder);
        this.getCmp().remove(placeHolderCmp);
        this.getCmp().insert(placeHolderIndex, cmp);
    },

    getIndexByPlaceHolder: function (placeHolder) {
        return this.getCmp().items.findIndex('text', placeHolder);
    },

    getPlaceHolderCmp: function (placeHolder) {
        var cmp = this.getCmp().down('[text="' + placeHolder + '"]');
        return cmp;
    },

    getFilterSplitButton: function () {
        if (!this.filterSplitButton) {
            this.filterSplitButton = Ext.create('Ext.button.Split', {
                text: this.filterSplitButtonText,
                handler: this.doFilter,
                glyph: this.filterSplitButtonGlyph,
                scope: this
            });
        }
        return this.filterSplitButton;
    },

    getFilterTextField: function () {
        if (!this.filterTextField) {
            this.filterTextField = Ext.create('Ext.form.field.Text', {
                submitValue: false,
                isFormField: false,
                width: this.filterItemWidth,
                margin: '-1 2 3 2',
                enableKeyEvents: true
            });
            if (this.filterOnType) {
                this.filterTextField.on('change', this.doFilter, this);
            } else {
                this.filterTextField.on('specialkey', function () {
                    if (e.getKey() == e.ENTER) {
                        this.doFilter();
                    }
                }, this);
            }
        }
        return this.filterTextField;
    },

    getSelectAllColumnsMenuItem: function () {
        if (!this.selectAllColumnsMenuItem) {
            this.selectAllColumnsMenuItem = Ext.create('Ext.menu.CheckItem', {
                text: "All Columns",
                checked: true,
                listeners: {
                    checkchange: {
                        fn: function (menuCheckItem, checked) {
                            this.getFilterColumnsMenu().items.each(function (item) {
                                if (item.dataIndex) {
                                    item.setChecked(checked);
                                }
                            });
                            this.doFilter();
                        },
                        scope: this
                    }
                }
            });
        }
        return this.selectAllColumnsMenuItem;
    },

    getFilterColumnsMenu: function () {
        if (!this.filterColumnsMenu) {
            this.filterColumnsMenu = Ext.create('Ext.menu.Menu', {
                items: [
                    this.getSelectAllColumnsMenuItem(), {
                        xtype: 'menuseparator'
                    }
                ]
            });
        }
        return this.filterColumnsMenu;
    },

    updateSearchColumnsMenu: function () {
        var columns = this.getCmp().up('grid').getColumns();
        Ext.Array.each(columns, function (column) {
            this.getFilterColumnsMenu().add({
                xtype: 'menucheckitem',
                text: column.text,
                dataIndex: column.dataIndex,
                checked: true,
                listeners: {
                    checkchange: {
                        fn: function (menuCheckItem, checked) {
                            if (checked) {
                                var selectAllColumnsMenuItemCheck = true;
                                this.getFilterColumnsMenu().items.each(function (item) {
                                    if (item.dataIndex && item.checked == false) {
                                        selectAllColumnsMenuItemCheck = false;
                                        return false;
                                    }
                                });
                                this.getSelectAllColumnsMenuItem().setChecked(selectAllColumnsMenuItemCheck, true);
                            } else {
                                this.getSelectAllColumnsMenuItem().setChecked(false, true);
                            }
                            this.doFilter();
                        },
                        scope: this
                    }
                }
            });
        }, this);
        this.getFilterSplitButton().setMenu(this.getFilterColumnsMenu());
    },

    doFilter: function () {
        var searchColumnIndexes = [],
            searchValue = this.getFilterTextField().getValue();;

        this.getFilterSplitButton().getMenu().items.each(function (item) {
            if (item.dataIndex && item.checked) {
                searchColumnIndexes.push(item.dataIndex);
            }
        }, this);

        if (this.getStore().remoteFilter) {
            this.remoteFilter(searchColumnIndexes, searchValue)
        } else {
            this.localFilter(searchColumnIndexes, searchValue);
        }

    },

    localFilter: function (searchColumnIndexes, searchValue) {
        this.getStore().removeFilter(this.filter);
        this.filter = new Ext.util.Filter({
            filterFn: function (record) {
                if (searchColumnIndexes.length === 0 || Ext.isEmpty(searchValue)) {
                    return true;
                }

                var found = false;
                Ext.Array.each(searchColumnIndexes, function (dataIndex) {
                    if (record.get(dataIndex) && record.get(dataIndex).indexOf(searchValue) != -1) {
                        found = true;
                        return false;
                    }
                }, this);
                return found;
            }
        });
        this.getStore().addFilter(this.filter);
    },

    remoteFilter: function (searchColumnIndexes, searchValue) {
        var remoteFilters = [];
        Ext.Array.each(searchColumnIndexes, function (columnIndex) {
            remoteFilters.push({
                property: columnIndex,
                value: searchValue
            });
        });
        this.getStore().clearFilter();
        this.getStore().filter(remoteFilters);
    },

    getStore: function () {
        if (!this.store) {
            this.store = this.getCmp().up('grid').getStore();
        }
        return this.store;
    }

});

Fiddle:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *