Данная статья расскажет как можно расширить существующий 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: