mirror of
https://git.proxmox.com/git/pve-manager
synced 2025-06-11 03:17:17 +00:00

when we click on a node/container/vm and quickly click on something else, there might be a race condition, where the store finished loading and we try to change dom elements, which are not there anymore so we change the store.on to me.mon, which deletes the handler when the component is gone in the logview panel we use API2Request, where we cannot do this, so we check if the component is destroyed manually also we change this whenever we change a dom element in a callback Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
291 lines
9.6 KiB
JavaScript
291 lines
9.6 KiB
JavaScript
/*
|
|
* ComboGrid component: a ComboBox where the dropdown menu (the
|
|
* "Picker") is a Grid with Rows and Columns expects a listConfig
|
|
* object with a columns property roughly based on the GridPicker from
|
|
* https://www.sencha.com/forum/showthread.php?299909
|
|
*
|
|
*/
|
|
|
|
Ext.define('PVE.form.ComboGrid', {
|
|
extend: 'Ext.form.field.ComboBox',
|
|
alias: ['widget.PVE.form.ComboGrid'],
|
|
|
|
// this value is used as default value after load()
|
|
preferredValue: undefined,
|
|
|
|
// 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);
|
|
},
|
|
|
|
// 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'
|
|
},
|
|
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
|
|
}
|
|
});
|
|
|
|
if (!initial) {
|
|
me.resetToDefault();
|
|
}
|
|
|
|
if (picker) {
|
|
picker.setSelectionModel(me.pickerSelectionModel);
|
|
if (picker.getStore() !== store) {
|
|
picker.bindStore(store);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// copied from ComboBox
|
|
createPicker: function() {
|
|
var me = this;
|
|
var picker;
|
|
|
|
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);
|
|
|
|
picker = me.picker || Ext.widget(pickerCfg);
|
|
|
|
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);
|
|
}
|
|
},
|
|
scope: me
|
|
});
|
|
|
|
// hack for extjs6
|
|
// when the clicked item is the same as the previously selected,
|
|
// it does not select the item
|
|
// instead we hide the picker
|
|
if (!me.multiSelect) {
|
|
picker.on('itemclick', function (sm,record) {
|
|
if (picker.getSelection()[0] === record) {
|
|
picker.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
picker.getNavigationModel().navigateOnSpace = false;
|
|
|
|
return picker;
|
|
},
|
|
|
|
initComponent: function() {
|
|
var me = this;
|
|
|
|
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.mon(me.store, 'beforeload', function() {
|
|
if (!me.isDisabled()) {
|
|
me.setDisabled(true);
|
|
me.enableAfterLoad = true;
|
|
}
|
|
});
|
|
|
|
// hack: autoSelect does not work
|
|
me.mon(me.store, 'load', function(store, r, success, o) {
|
|
if (success) {
|
|
me.clearInvalid();
|
|
|
|
if (me.enableAfterLoad) {
|
|
delete me.enableAfterLoad;
|
|
me.setDisabled(false);
|
|
}
|
|
|
|
var def = me.getValue() || me.preferredValue;
|
|
if (def) {
|
|
me.setValue(def, true); // sync with grid
|
|
}
|
|
var found = false;
|
|
if (def) {
|
|
if (Ext.isArray(def)) {
|
|
Ext.Array.each(def, function(v) {
|
|
if (store.findRecord(me.valueField, v)) {
|
|
found = true;
|
|
return false; // break
|
|
}
|
|
});
|
|
} else {
|
|
found = store.findRecord(me.valueField, def);
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
var rec = me.store.first();
|
|
if (me.autoSelect && rec && rec.data) {
|
|
def = rec.data[me.valueField];
|
|
me.setValue(def, true);
|
|
} else {
|
|
me.setValue(me.editable ? def : '', true);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|