The ComboGrid combonent requires row-like selection something that the default ExtJS Ext.selection.DataViewModel used for ComboBox cannot do.
This requires overriding the protected method onBindStore() where the selection model is set. --- www/manager6/form/ComboGrid.js | 271 +++++++++++++++++++++++++++++------------ 1 file changed, 194 insertions(+), 77 deletions(-) diff --git a/www/manager6/form/ComboGrid.js b/www/manager6/form/ComboGrid.js index 3088e95..18e5309 100644 --- a/www/manager6/form/ComboGrid.js +++ b/www/manager6/form/ComboGrid.js @@ -5,111 +5,228 @@ * https://www.sencha.com/forum/showthread.php?299909 * */ + Ext.define('PVE.form.ComboGrid', { - extend: 'Ext.form.field.Picker', + extend: 'Ext.form.field.ComboBox', alias: ['widget.PVE.form.ComboGrid'], // this value is used as default value after load() preferredValue: undefined, - - // If set to `true`, allows the combo field to hold more than one - // value at a time, and allows selecting multiple items from the - // dropdown list. - multiSelect: false, - - defaultPickerConfig: { - maxHeight: 300, - width: 400, - scrollable: true, - floating: true, - }, - displayField: false, - valueField: false, - matchFieldWidth: false, - // if we have value(s) in the textField, mark them as selected in the picker - // private - syncSelection: function() { - var me = this, previousItems = []; - - if (me.getRawValue()) { - Ext.Array.each(me.getRawValue().split(','), function(record) { - var previousItem = me.store.findRecord(me.valueField, record); - // select only what can be found in the ComboGrid store - previousItem != null && previousItems.push(previousItem); - }); - me.picker.getSelectionModel().select(previousItems); - } + // hack: allow to select empty value + // seems extjs does not allow that when 'editable == false' + onKeyUp: function(e, t) { + var me = this; + var key = e.getKey(); + + if (!me.editable && me.allowBlank && !me.multiSelect && + (key == e.BACKSPACE || key == e.DELETE)) { + me.setValue(''); + } + + me.callParent(arguments); }, - createPicker: function() { - var me = this; - var config = Ext.applyIf({ - store: me.getStore(), - selModel: { - selType: 'checkboxmodel', - mode: me.multiSelect ? 'SIMPLE' : 'SINGLE', - showHeaderCheckbox: false // shows a selectAll checkbox, not reliable - }, - listeners: { - selectionchange: { - fn: function(grid, selectedRecords) { - me.setRecords(selectedRecords); - me.fireEvent('select', me, selectedRecords); +// override ExtJS protected method + onBindStore: function(store, initial) { + var me = this, + picker = me.picker, + extraKeySpec, + valueCollectionConfig; + + // We're being bound, not unbound... + if (store) { + // If store was created from a 2 dimensional array with generated field names 'field1' and 'field2' + if (store.autoCreated) { + me.queryMode = 'local'; + me.valueField = me.displayField = 'field1'; + if (!store.expanded) { + me.displayField = 'field2'; + } + + // displayTpl config will need regenerating with the autogenerated displayField name 'field1' + me.setDisplayTpl(null); + } + if (!Ext.isDefined(me.valueField)) { + me.valueField = me.displayField; + } + + // Add a byValue index to the store so that we can efficiently look up records by the value field + // when setValue passes string value(s). + // The two indices (Ext.util.CollectionKeys) are configured unique: false, so that if duplicate keys + // are found, they are all returned by the get call. + // This is so that findByText and findByValue are able to return the *FIRST* matching value. By default, + // if unique is true, CollectionKey keeps the *last* matching value. + extraKeySpec = { + byValue: { + rootProperty: 'data', + unique: false + } + }; + extraKeySpec.byValue.property = me.valueField; + store.setExtraKeys(extraKeySpec); + + if (me.displayField === me.valueField) { + store.byText = store.byValue; + } else { + extraKeySpec.byText = { + rootProperty: 'data', + unique: false + }; + extraKeySpec.byText.property = me.displayField; + store.setExtraKeys(extraKeySpec); + } + + // We hold a collection of the values which have been selected, keyed by this field's valueField. + // This collection also functions as the selected items collection for the BoundList's selection model + valueCollectionConfig = { + rootProperty: 'data', + extraKeys: { + byInternalId: { + property: 'internalId' }, - scope: me + byValue: { + property: me.valueField, + rootProperty: 'data' + } }, - show: { - fn: function() { - me.syncSelection(); - }, + // Whenever this collection is changed by anyone, whether by this field adding to it, + // or the BoundList operating, we must refresh our value. + listeners: { + beginupdate: me.onValueCollectionBeginUpdate, + endupdate: me.onValueCollectionEndUpdate, scope: me } - } - }, me.defaultPickerConfig); + }; - Ext.apply(config, me.listConfig); + // This becomes our collection of selected records for the Field. + me.valueCollection = new Ext.util.Collection(valueCollectionConfig); - var picker = me.picker = Ext.create('Ext.grid.Panel', config); + // We use the selected Collection as our value collection and the basis + // for rendering the tag list. - return picker; - }, + //pve override: since the picker is represented by a grid panel, + // we changed here the selection to RowModel + me.pickerSelectionModel = new Ext.selection.RowModel({ + mode: me.multiSelect ? 'SIMPLE' : 'SINGLE', + // There are situations when a row is selected on mousedown but then the mouse is dragged to another row + // and released. In these situations, the event target for the click event won't be the row where the mouse + // was released but the boundview. The view will then determine that it should fire a container click, and + // the DataViewModel will then deselect all prior selections. Setting `deselectOnContainerClick` here will + // prevent the model from deselecting. + deselectOnContainerClick: false, + enableInitialSelection: false, + pruneRemoved: false, + selected: me.valueCollection, + store: store, + listeners: { + scope: me, + lastselectedchanged: me.updateBindSelection + } + }); + + if (!initial) { + me.resetToDefault(); + } - setRecords: function(records) { - if (records && !Ext.isArray(records)) { - records = [records]; + if (picker) { + picker.setSelectionModel(me.pickerSelectionModel); + if (picker.getStore() !== store) { + picker.bindStore(store); + } + } } - this.selectedRecords = records; - var rawValue = []; + }, + + // copied from ComboBox + createPicker: function() { + var me = this; + var picker; - Ext.Array.each(records, function(record) { - rawValue.push(record.get(this.displayField)); - }, this); + var pickerCfg = Ext.apply({ + // pve overrides: display a grid for selection + xtype: 'gridpanel', + id: me.pickerId, + pickerField: me, + floating: true, + hidden: true, + store: me.store, + displayField: me.displayField, + preserveScrollOnRefresh: true, + pageSize: me.pageSize, + tpl: me.tpl, + selModel: me.pickerSelectionModel, + focusOnToFront: false + }, me.listConfig, me.defaultListConfig); - this.setValue(rawValue); - }, + picker = me.picker || Ext.widget(pickerCfg); - getRecords: function() { - return this.selectedRecords; - }, + if (picker.getStore() !== me.store) { + picker.bindStore(store); + } - beforeReset: function() { - if(this.picker) { - this.picker.getSelectionModel().deselectAll() + if (me.pageSize) { + picker.pagingToolbar.on('beforechange', me.onPageChange, me); } - this.callParent(arguments); - }, - getStore: function() { - if (!this.store) { - this.store = Ext.create('Ext.data.Store', {}); + // pve overrides: pass missing method in gridPanel to its view + picker.refresh = function() { + picker.getSelectionModel().select(me.valueCollection.getRange()); + picker.getView().refresh(); + }; + picker.getNodeByRecord = function() { + picker.getView().getNodeByRecord(arguments); + }; + + // 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 + }); } - return this.store; + picker.getSelectionModel().on({ + beforeselect: me.onBeforeSelect, + beforedeselect: me.onBeforeDeselect, + focuschange: me.onFocusChange, + selectionChange: function (sm, selectedRecords) { + var me = this; + if (selectedRecords.length) { + me.setValue(selectedRecords); + me.fireEvent('select', me, selectedRecords); + } + }, + scope: me + }); + + picker.getNavigationModel().navigateOnSpace = false; + + return picker; }, initComponent: function() { var me = this; - me.callParent(arguments); + + if (me.initialConfig.editable === undefined) { + me.editable = false; + } + + Ext.apply(me, { + queryMode: 'local', + matchFieldWidth: false + }); + + Ext.applyIf(me, { value: ''}); // hack: avoid ExtJS validate() bug + + Ext.applyIf(me.listConfig, { width: 400 }); + + me.callParent(); + + // Create the picker at an early stage, so it is available to store the previous selection + if (!me.picker) { + me.createPicker(); + } me.store.on('beforeload', function() { if (!me.isDisabled()) { -- 2.1.4 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel