[pve-devel] [PATCH pve-manager 1/3] Ext6migrate: Update our ComboGrid component to work with ExtJS6
Emmanuel Kasper
e.kasper at proxmox.com
Mon Feb 15 09:22:58 CET 2016
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
More information about the pve-devel
mailing list