To create a TreeComboBox you will have to extend the ‘Ext.form.field.Picker’ abstract class. In the following example I have tried to implement the remote and local filtering of the tree.
You can also define the ‘valueField’ and ‘displayField’ as it is done in normal ComboBox. To enable the selection of the folders set the ‘alloFolderSelect’ property to true. The element consists of two classes the TreeComboBox and the TreeComboBoxList panel (something like BoundList of the standard ComboBox). Please do not forget, it is only sample implementation which was never used in any application and can contain bugs. Feel free to understand the code and change it according to your requirements.
TreeComboBoxList class:
Ext.define('app.ux.form.field.TreeComboBoxList', { extend: 'Ext.tree.Panel', alias: 'widget.treecomboboxlist', floating: true, hidden: true, rootVisible: false, value: false, anyMatch: false, allowFolderSelect: false, initComponent: function () { this.listeners = { 'cellclick': this.onCellClick, 'itemkeydown': this.onItemKeyDown }; this.callParent(); }, onCellClick: function (tree, td, cellIndex, record, tr, rowIndex, e, eOpts) { if (this.allowFolderSelect || record.isLeaf()) { this.fireEvent('picked', record); } }, onItemKeyDown: function (view, record, item, index, e, eOpts) { if (this.allowFolderSelect || record.isLeaf() && e.keyCode == e.ENTER) { this.fireEvent('picked', record); } }, selectFirstLeaf: function () { var firstLeaf = this.getStore().findRecord('leaf', true); this.getSelectionModel().select(firstLeaf); }, 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.displayField).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.displayField, value: searchValue })); }, onPickerStoreLoad: function (store, records) { this.fireEvent('filtered', store, this); } });
TreeComboBox class:
Ext.define('app.ux.form.field.TreeComboBox', { extend: 'Ext.form.field.Picker', requires: [ 'app.ux.form.field.TreeComboBoxList' ], store: false, queryMode: 'local', anyMatch: false, allowFolderSelect: false, filterDelayBuffer: 300, enableKeyEvents: true, valueField: 'text', selectedRecord: false, treeConfig: { // Tree Config }, initComponent: function () { this.on('change', this.onTreeComboValueChange, this, { buffer: this.filterDelayBuffer }); this.callParent(); }, onTreeComboValueChange: function (field, value) { this.selectedRecord = false; switch (this.queryMode) { case 'local': this.getPicker().doLocalQuery(value) break; case 'remote': this.getPicker().doRemoteQuery(value); break; } }, expand: function () { this.getPicker().expandAll(); this.callParent([arguments]); }, createPicker: function () { var treeConfig = Ext.apply({ xtype: 'treecomboboxlist', id: this.getId() + '-TreePicker', store: this.getPickerStore(), valueField: this.valueField, displayField: this.displayField, anyMatch: this.anyMatch, allowFolderSelect: this.allowFolderSelect }, this.treeConfig); var treePanelPicker = Ext.widget(treeConfig); treePanelPicker.on({ picked: this.onPicked, filtered: this.onFiltered, beforeselect: this.onBeforeSelect, beforedeselect: this.onBeforeDeselect, scope: this }); return treePanelPicker; }, onFiltered: function (store, treeList) { if (store.getCount() > 0) { this.expand(); 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; } });
To change TreeComboBoxList configuration by initialization you will need to change the ‘treeConfig’ property.
In the fiddle example for ExtJS 6.5.3 you can see two product fields for remote and local query. If you need simple group ComboBox you can use the following implementation or Ext.ux.TreePicker.