mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-08-13 19:34:21 +00:00
Ext6migrate: Update our ComboGrid component to work with ExtJS6
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.
This commit is contained in:
parent
5fc9bddb07
commit
a263665b16
@ -5,111 +5,228 @@
|
|||||||
* https://www.sencha.com/forum/showthread.php?299909
|
* https://www.sencha.com/forum/showthread.php?299909
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Ext.define('PVE.form.ComboGrid', {
|
Ext.define('PVE.form.ComboGrid', {
|
||||||
extend: 'Ext.form.field.Picker',
|
extend: 'Ext.form.field.ComboBox',
|
||||||
alias: ['widget.PVE.form.ComboGrid'],
|
alias: ['widget.PVE.form.ComboGrid'],
|
||||||
|
|
||||||
// this value is used as default value after load()
|
// this value is used as default value after load()
|
||||||
preferredValue: undefined,
|
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: {
|
// hack: allow to select empty value
|
||||||
maxHeight: 300,
|
// seems extjs does not allow that when 'editable == false'
|
||||||
width: 400,
|
onKeyUp: function(e, t) {
|
||||||
scrollable: true,
|
var me = this;
|
||||||
floating: true,
|
var key = e.getKey();
|
||||||
|
|
||||||
|
if (!me.editable && me.allowBlank && !me.multiSelect &&
|
||||||
|
(key == e.BACKSPACE || key == e.DELETE)) {
|
||||||
|
me.setValue('');
|
||||||
|
}
|
||||||
|
|
||||||
|
me.callParent(arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
displayField: false,
|
// override ExtJS protected method
|
||||||
valueField: false,
|
onBindStore: function(store, initial) {
|
||||||
matchFieldWidth: false,
|
var me = this,
|
||||||
// if we have value(s) in the textField, mark them as selected in the picker
|
picker = me.picker,
|
||||||
// private
|
extraKeySpec,
|
||||||
syncSelection: function() {
|
valueCollectionConfig;
|
||||||
var me = this, previousItems = [];
|
|
||||||
|
|
||||||
if (me.getRawValue()) {
|
// We're being bound, not unbound...
|
||||||
Ext.Array.each(me.getRawValue().split(','), function(record) {
|
if (store) {
|
||||||
var previousItem = me.store.findRecord(me.valueField, record);
|
// If store was created from a 2 dimensional array with generated field names 'field1' and 'field2'
|
||||||
// select only what can be found in the ComboGrid store
|
if (store.autoCreated) {
|
||||||
previousItem != null && previousItems.push(previousItem);
|
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'
|
||||||
|
},
|
||||||
|
byValue: {
|
||||||
|
property: me.valueField,
|
||||||
|
rootProperty: 'data'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This becomes our collection of selected records for the Field.
|
||||||
|
me.valueCollection = new Ext.util.Collection(valueCollectionConfig);
|
||||||
|
|
||||||
|
// We use the selected Collection as our value collection and the basis
|
||||||
|
// for rendering the tag list.
|
||||||
|
|
||||||
|
//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
|
||||||
|
}
|
||||||
});
|
});
|
||||||
me.picker.getSelectionModel().select(previousItems);
|
|
||||||
|
if (!initial) {
|
||||||
|
me.resetToDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (picker) {
|
||||||
|
picker.setSelectionModel(me.pickerSelectionModel);
|
||||||
|
if (picker.getStore() !== store) {
|
||||||
|
picker.bindStore(store);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// copied from ComboBox
|
||||||
createPicker: function() {
|
createPicker: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
var config = Ext.applyIf({
|
var picker;
|
||||||
store: me.getStore(),
|
|
||||||
selModel: {
|
var pickerCfg = Ext.apply({
|
||||||
selType: 'checkboxmodel',
|
// pve overrides: display a grid for selection
|
||||||
mode: me.multiSelect ? 'SIMPLE' : 'SINGLE',
|
xtype: 'gridpanel',
|
||||||
showHeaderCheckbox: false // shows a selectAll checkbox, not reliable
|
id: me.pickerId,
|
||||||
},
|
pickerField: me,
|
||||||
listeners: {
|
floating: true,
|
||||||
selectionchange: {
|
hidden: true,
|
||||||
fn: function(grid, selectedRecords) {
|
store: me.store,
|
||||||
me.setRecords(selectedRecords);
|
displayField: me.displayField,
|
||||||
me.fireEvent('select', me, selectedRecords);
|
preserveScrollOnRefresh: true,
|
||||||
},
|
pageSize: me.pageSize,
|
||||||
scope: me
|
tpl: me.tpl,
|
||||||
},
|
selModel: me.pickerSelectionModel,
|
||||||
show: {
|
focusOnToFront: false
|
||||||
fn: function() {
|
}, me.listConfig, me.defaultListConfig);
|
||||||
me.syncSelection();
|
|
||||||
},
|
picker = me.picker || Ext.widget(pickerCfg);
|
||||||
scope: me
|
|
||||||
|
if (picker.getStore() !== me.store) {
|
||||||
|
picker.bindStore(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (me.pageSize) {
|
||||||
|
picker.pagingToolbar.on('beforechange', me.onPageChange, me);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}, me.defaultPickerConfig);
|
scope: me
|
||||||
|
});
|
||||||
|
|
||||||
Ext.apply(config, me.listConfig);
|
picker.getNavigationModel().navigateOnSpace = false;
|
||||||
|
|
||||||
var picker = me.picker = Ext.create('Ext.grid.Panel', config);
|
|
||||||
|
|
||||||
return picker;
|
return picker;
|
||||||
},
|
},
|
||||||
|
|
||||||
setRecords: function(records) {
|
|
||||||
if (records && !Ext.isArray(records)) {
|
|
||||||
records = [records];
|
|
||||||
}
|
|
||||||
this.selectedRecords = records;
|
|
||||||
var rawValue = [];
|
|
||||||
|
|
||||||
Ext.Array.each(records, function(record) {
|
|
||||||
rawValue.push(record.get(this.displayField));
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.setValue(rawValue);
|
|
||||||
},
|
|
||||||
|
|
||||||
getRecords: function() {
|
|
||||||
return this.selectedRecords;
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeReset: function() {
|
|
||||||
if(this.picker) {
|
|
||||||
this.picker.getSelectionModel().deselectAll()
|
|
||||||
}
|
|
||||||
this.callParent(arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
getStore: function() {
|
|
||||||
if (!this.store) {
|
|
||||||
this.store = Ext.create('Ext.data.Store', {});
|
|
||||||
}
|
|
||||||
return this.store;
|
|
||||||
},
|
|
||||||
|
|
||||||
initComponent: function() {
|
initComponent: function() {
|
||||||
var me = this;
|
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() {
|
me.store.on('beforeload', function() {
|
||||||
if (!me.isDisabled()) {
|
if (!me.isDisabled()) {
|
||||||
|
Loading…
Reference in New Issue
Block a user