В данном примере я покажу как заменить 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.
В первой части заметки так и делается, это просто концепт.