В данном примере я покажу как заменить Ext.view.BoundList, который используется по умолчанию для отображения выпадающего списка в Ext.form.field.ComboBox, на обычный Ext.grid.Panel. Так же во второй части мы попробуем доработать Ext.view.BoundList , так что бы он выглядел как таблица.
PS: Поддерживается и локальная и удаленная фильтрация.
Часть 1 — Используем обычный Grid
Так же я использовал вычисляемое поле fullName в модели, что бы использовать его в качестве значения в combobox. Вы также можете использовать свойство displayTpl для достижения аналогичного результата. Свойство filter (имя поля модели) — используется для фильтрации данных, оно определяется в настройке filterField для Ext.form.field.ComboBox.
Ext.define('app.ux.form.field.GridComboBox', {
extend: 'Ext.form.field.Picker',
requires: [
'app.ux.form.field.GridComboBoxList'
],
store: false,
queryMode: 'local',
anyMatch: false,
filterDelayBuffer: 300,
enableKeyEvents: true,
valueField: 'text',
selectedRecord: false,
gridConfig: {
// Grid Config
},
initComponent: function () {
this.on('change', this.onGridComboValueChange, this, {
buffer: this.filterDelayBuffer
});
this.on('keydown', this.onItemKeyDown, this, {
buffer: this.filterDelayBuffer
});
this.callParent();
},
onGridComboValueChange: function (field, value) {
this.selectedRecord = false;
switch (this.queryMode) {
case 'local':
this.getPicker().doLocalQuery(value)
break;
case 'remote':
this.getPicker().doRemoteQuery(value);
break;
}
},
onItemKeyDown: function() {
this.expand();
},
expand: function () {
this.callParent([arguments]);
},
createPicker: function () {
var gridConfig = Ext.apply({
xtype: 'gridcomboboxlist',
id: this.getId() + '-GridPicker',
store: this.getPickerStore(),
valueField: this.valueField,
displayField: this.displayField,
anyMatch: this.anyMatch,
allowFolderSelect: this.allowFolderSelect,
columns: this.columns,
filterField: this.filterField
}, this.gridConfig);
var gridPanelPicker = Ext.widget(gridConfig);
gridPanelPicker.on({
picked: this.onPicked,
filtered: this.onFiltered,
beforeselect: this.onBeforeSelect,
beforedeselect: this.onBeforeDeselect,
scope: this
});
return gridPanelPicker;
},
onFiltered: function (store, gridList) {
if (store.getCount() > 0) {
this.focus();
}
},
getPickerStore: function () {
return this.store;
},
onPicked: function (record) {
this.suspendEvent('change');
this.selectedRecord = record;
this.setValue(record.get(this.displayField));
this.collapse();
this.resumeEvent('change');
this.fireEvent('select', record);
},
getValue: function () {
var value;
if (this.valueField && this.selectedRecord) {
value = this.selectedRecord.get(this.valueField);
} else {
value = this.getRawValue();
}
return value;
},
getSubmitValue: function () {
var value = this.getValue();
if (Ext.isEmpty(value)) {
value = '';
}
return value;
},
onBeforeSelect: function (comboBox, record, recordIndex) {
return this.fireEvent('beforeselect', this, record, recordIndex);
},
onBeforeDeselect: function (comboBox, record, recordIndex) {
return this.fireEvent('beforedeselect', this, record, recordIndex);
},
getSelectedRecord: function () {
return this.selectedRecord;
}
});
Ext.define('app.ux.form.field.GridComboBoxList', {
extend: 'Ext.grid.Panel',
alias: 'widget.gridcomboboxlist',
floating: true,
hidden: true,
value: false,
anyMatch: false,
initComponent: function () {
this.listeners = {
'cellclick': this.onCellClick,
'itemkeydown': this.onItemKeyDown
};
this.callParent();
},
onCellClick: function (tree, td, cellIndex, record, tr, rowIndex, e, eOpts) {
this.fireEvent('picked', record);
},
onItemKeyDown: function (view, record, item, index, e, eOpts) {
if (e.keyCode == e.ENTER) {
this.fireEvent('picked', record);
}
},
selectFirstRow: function () {
var firstRecord = this.getStore().getAt(0);
this.getSelectionModel().select(firstRecord);
},
doLocalQuery: function (searchValue) {
var store = this.getStore();
this.searchValue = searchValue.toLowerCase();
store.setRemoteFilter(false);
store.filterBy(this.pickerStoreFilter, this);
this.fireEvent('filtered', store, this);
},
pickerStoreFilter: function (record) {
var itemValue = record.get(this.filterField).toLowerCase();
if (this.anyMatch) {
if (itemValue.indexOf(this.searchValue) != -1) {
return true;
}
} else {
if (itemValue.startsWith(this.searchValue)) {
return true;
}
}
return false;
},
doRemoteQuery: function (searchValue) {
var store = this.getStore();
store.setRemoteFilter(true);
store.on('load', this.onPickerStoreLoad, this, {
single: true
});
store.filter(new Ext.util.Filter({
anyMatch: this.anyMatch,
disableOnEmpty: true,
property: this.filterField,
value: searchValue
}));
},
onPickerStoreLoad: function (store, records) {
this.fireEvent('filtered', store, this);
}
});
Fiddle:
Часть 2 — Дорабатываем стандартный boundlist
Если вам не нужен такой сложный компонент как Ext.grid.Panel , вы всегда можете изменить XTemplate у boundlist — так что бы он выглядел как таблица. Конечно, у вас не будет функционала который могла бы дать Ext.grid.Panel — например selectionModel, фильтрация и пр. Так же понадобиться хорошо сверстать html и css да так, что бы не было кросс-браузерных проблем. В не которых случаях это будет требовать определенного времени, все это вы сможете сделать сами. В этой части я расширил Ext.form.field.ComboBox и Ext.view.BoundList
Ext.define('app.ux.form.field.GridBoundList', {
extend: 'Ext.view.BoundList',
alias: 'widget.gridboundlist',
generateTpl: function () {
var me = this;
me.tpl = new Ext.XTemplate(
'<table style="border-collapse: collapse;">',
'<thead>',
'<tr>' + me.getHeaderTpl() + '</tr>',
'</thead>',
'<tbody style="height: 120px; overflow-y: auto;">',
'<tpl for=".">',
'<tr role="option" unselectable="on" class="x-grid-row ' + me.itemCls + '">' + me.getInnerTpl() + '</tr>',
'</tpl>',
'</tbody>',
'</table>'
);
},
getHeaderTpl: function() {
var tpl = [];
Ext.Array.each(this.columns, function(column) {
tpl.push('<th class="x-grid-header-ct"><div class="x-column-header-inner">' + column.text + '</div></th>');
}, this);
return tpl.join('');
},
getInnerTpl: function() {
var tpl = [];
Ext.Array.each(this.columns, function(column) {
tpl.push('<td class="x-grid-cell x-grid-td"><div class="x-grid-cell-inner">{' + column.dataIndex + '}</div></td>');
}, this);
return tpl.join('');
},
initComponent: function () {
this.callParent();
}
});
Ext.define('app.ux.form.field.GridComboBox', {
extend: 'Ext.form.field.ComboBox',
alias: ['widget.gridcombobox', 'widget.gridcombo'],
requires: [
'Ext.util.DelayedTask',
'app.ux.form.field.GridBoundList',
'Ext.data.StoreManager'
],
createPicker: function () {
var me = this,
picker,
pickerCfg = Ext.apply({
xtype: 'gridboundlist',
id: me.id + '-gridpicker',
pickerField: me,
selectionModel: me.pickerSelectionModel,
floating: true,
hidden: true,
store: me.getPickerStore(),
displayField: me.displayField,
preserveScrollOnRefresh: true,
pageSize: me.pageSize,
tpl: me.tpl,
ariaSelectable: me.ariaSelectable,
columns: me.columns
}, me.listConfig, me.defaultListConfig);
picker = me.picker = Ext.widget(pickerCfg);
if (me.pageSize) {
picker.pagingToolbar.on('beforechange', me.onPageChange, me);
}
// We limit the height of the picker to fit in the space above
// or below this field unless the picker has its own ideas about that.
if (!picker.initialConfig.maxHeight) {
picker.on({
beforeshow: me.onBeforePickerShow,
scope: me
});
}
picker.getSelectionModel().on({
beforeselect: me.onBeforeSelect,
beforedeselect: me.onBeforeDeselect,
focuschange: me.onFocusChange,
scope: me
});
picker.getNavigationModel().navigateOnSpace = false;
return picker;
},
initComponent: function () {
this.callParent();
}
});
Fiddle:
А почему бы не вернуть createPicker, обычный Grid? Тогда можно было бы Grid конфигурировать и не мучиться с XTemplate.
В первой части заметки так и делается, это просто концепт.