neo.mjs 8.0.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/portal/index.html +1 -1
  3. package/apps/portal/view/about/Container.mjs +0 -2
  4. package/apps/portal/view/about/MemberContainer.mjs +1 -20
  5. package/apps/portal/view/home/FooterContainer.mjs +1 -5
  6. package/examples/ConfigurationViewport.mjs +37 -32
  7. package/examples/ServiceWorker.mjs +2 -2
  8. package/examples/grid/cellEditing/MainContainer.mjs +175 -0
  9. package/examples/grid/cellEditing/MainContainerStateProvider.mjs +62 -0
  10. package/examples/grid/cellEditing/MainModel.mjs +30 -0
  11. package/examples/grid/cellEditing/MainStore.mjs +54 -0
  12. package/examples/grid/cellEditing/app.mjs +6 -0
  13. package/examples/grid/cellEditing/index.html +11 -0
  14. package/examples/grid/cellEditing/neo-config.json +6 -0
  15. package/examples/grid/container/MainContainer.mjs +7 -6
  16. package/examples/grid/covid/GridContainer.mjs +36 -36
  17. package/examples/grid/covid/Util.mjs +1 -1
  18. package/examples/table/cellEditing/MainContainer.mjs +174 -0
  19. package/examples/table/cellEditing/MainContainerStateProvider.mjs +62 -0
  20. package/examples/table/cellEditing/MainModel.mjs +30 -0
  21. package/examples/table/cellEditing/MainStore.mjs +54 -0
  22. package/examples/table/cellEditing/app.mjs +6 -0
  23. package/examples/table/cellEditing/index.html +11 -0
  24. package/examples/table/cellEditing/neo-config.json +6 -0
  25. package/examples/table/nestedRecordFields/MainContainerStateProvider.mjs +2 -1
  26. package/package.json +5 -5
  27. package/resources/scss/src/apps/portal/home/FooterContainer.scss +11 -2
  28. package/resources/scss/src/grid/Container.scss +0 -13
  29. package/resources/scss/src/grid/plugin/CellEditing.scss +11 -0
  30. package/resources/scss/src/table/plugin/CellEditing.scss +11 -0
  31. package/src/DefaultConfig.mjs +2 -2
  32. package/src/component/DateSelector.mjs +15 -0
  33. package/src/form/field/Base.mjs +1 -4
  34. package/src/form/field/ComboBox.mjs +18 -2
  35. package/src/form/field/Date.mjs +10 -4
  36. package/src/grid/Container.mjs +242 -39
  37. package/src/grid/README.md +1 -1
  38. package/src/grid/View.mjs +282 -129
  39. package/src/grid/header/Button.mjs +327 -36
  40. package/src/grid/header/Toolbar.mjs +68 -4
  41. package/src/grid/plugin/CellEditing.mjs +30 -0
  42. package/src/main/DomEvents.mjs +12 -3
  43. package/src/manager/Focus.mjs +2 -2
  44. package/src/plugin/Base.mjs +15 -1
  45. package/src/plugin/Resizable.mjs +1 -5
  46. package/src/selection/Model.mjs +5 -1
  47. package/src/selection/grid/CellColumnModel.mjs +1 -1
  48. package/src/selection/grid/CellColumnRowModel.mjs +1 -1
  49. package/src/selection/grid/CellModel.mjs +1 -1
  50. package/src/selection/grid/ColumnModel.mjs +2 -2
  51. package/src/table/Container.mjs +32 -3
  52. package/src/table/View.mjs +9 -4
  53. package/src/table/header/Toolbar.mjs +15 -1
  54. package/src/table/plugin/CellEditing.mjs +330 -0
  55. package/src/util/KeyNavigation.mjs +14 -8
@@ -88,7 +88,7 @@ class CellColumnRowModel extends CellRowModel {
88
88
  idArray = ColumnModel.getCellId(data.path).split('__'),
89
89
  currentColumn = idArray[2],
90
90
  {view} = me,
91
- fields = view.columns.map(c => c.field),
91
+ fields = view.columns.map(c => c.dataField),
92
92
  newIndex = (fields.indexOf(currentColumn) + step) % fields.length,
93
93
  columnNodeIds, tbodyNode;
94
94
 
@@ -87,7 +87,7 @@ class CellModel extends BaseModel {
87
87
  {view} = me,
88
88
  idArray = data.path[0].id.split('__'),
89
89
  currentColumn = idArray[2],
90
- dataFields = view.columns.map(c => c.field),
90
+ dataFields = view.columns.map(c => c.dataField),
91
91
  newIndex = (dataFields.indexOf(currentColumn) + step) % dataFields.length,
92
92
  id;
93
93
 
@@ -72,7 +72,7 @@ class ColumnModel extends BaseModel {
72
72
  static getColumnIndex(cellId, columns) {
73
73
  let idArray = cellId.split('__'),
74
74
  currentColumn = idArray[2],
75
- dataFields = columns.map(c => c.field);
75
+ dataFields = columns.map(c => c.dataField);
76
76
 
77
77
  return dataFields.indexOf(currentColumn)
78
78
  }
@@ -117,7 +117,7 @@ class ColumnModel extends BaseModel {
117
117
  idArray = ColumnModel.getCellId(data.path).split('__'),
118
118
  currentColumn = idArray[2],
119
119
  {view} = me,
120
- fields = view.columns.map(c => c.field),
120
+ fields = view.columns.map(c => c.dataField),
121
121
  newIndex = (fields.indexOf(currentColumn) + step) % fields.length,
122
122
  columnNodeIds, id, tbodyNode;
123
123
 
@@ -27,6 +27,11 @@ class Container extends BaseContainer {
27
27
  * @member {String[]} baseCls=['neo-table-container']
28
28
  */
29
29
  baseCls: ['neo-table-container'],
30
+ /**
31
+ * true uses table.plugin.CellEditing
32
+ * @member {Boolean} cellEditing_=false
33
+ */
34
+ cellEditing_: false,
30
35
  /**
31
36
  * Default configs for each column
32
37
  * @member {Object} columnDefaults=null
@@ -150,6 +155,30 @@ class Container extends BaseContainer {
150
155
  me.createColumns(me.columns)
151
156
  }
152
157
 
158
+ /**
159
+ * Triggered after the cellEditing config got changed
160
+ * @param {Boolean} value
161
+ * @param {Boolean} oldValue
162
+ * @protected
163
+ */
164
+ afterSetCellEditing(value, oldValue) {
165
+ if (value) {
166
+ import('./plugin/CellEditing.mjs').then(module => {
167
+ let me = this,
168
+ {appName, windowId} = me,
169
+ plugins = me.plugins || [];
170
+
171
+ plugins.push({
172
+ module : module.default,
173
+ appName,
174
+ windowId
175
+ });
176
+
177
+ me.plugins = plugins
178
+ })
179
+ }
180
+ }
181
+
153
182
  /**
154
183
  * Triggered after the columns config got changed
155
184
  * @param {Object[]|null} value
@@ -350,9 +379,9 @@ class Container extends BaseContainer {
350
379
  * @returns {*}
351
380
  */
352
381
  createColumns(columns) {
353
- let me = this,
354
- columnDefaults = me.columnDefaults,
355
- sorters = me.store?.sorters,
382
+ let me = this,
383
+ {columnDefaults} = me,
384
+ sorters = me.store?.sorters,
356
385
  renderer;
357
386
 
358
387
  if (!columns || !columns.length) {
@@ -131,6 +131,7 @@ class View extends Component {
131
131
  }
132
132
  break
133
133
  }
134
+ case 'Date':
134
135
  case 'Number':
135
136
  case 'String': {
136
137
  rendererOutput = {
@@ -182,10 +183,14 @@ class View extends Component {
182
183
  /**
183
184
  * @param {Object} opts
184
185
  * @param {Object} opts.record
185
- * @param {Number} opts.rowIndex
186
+ * @param {Number} [opts.rowIndex]
186
187
  * @returns {Object}
187
188
  */
188
- createTableRow({record, rowIndex}) {
189
+ createRow({record, rowIndex}) {
190
+ if (!Neo.isNumber(rowIndex)) {
191
+ rowIndex = this.store.indexOf(record)
192
+ }
193
+
189
194
  let me = this,
190
195
  tableContainer = me.parent,
191
196
  colspan = record[me.colspanField],
@@ -276,7 +281,7 @@ class View extends Component {
276
281
  {selectedRows} = me;
277
282
 
278
283
  for (; i < amountRows; i++) {
279
- rows.push(me.createTableRow({record: inputData[i], rowIndex: i}))
284
+ rows.push(me.createRow({record: inputData[i], rowIndex: i}))
280
285
  }
281
286
 
282
287
  me.vdom.cn = rows;
@@ -472,7 +477,7 @@ class View extends Component {
472
477
 
473
478
  if (fieldNames.includes(me.colspanField)) {
474
479
  index = me.store.indexOf(record);
475
- me.vdom.cn[index] = me.createTableRow({record, rowIndex: index});
480
+ me.vdom.cn[index] = me.createRow({record, rowIndex: index});
476
481
  me.update()
477
482
  } else {
478
483
  fields.forEach(field => {
@@ -28,7 +28,7 @@ class Toolbar extends BaseToolbar {
28
28
  * @member {Object} itemDefaults={ntype : 'table-header-button'}
29
29
  */
30
30
  itemDefaults: {
31
- ntype : 'table-header-button'
31
+ ntype: 'table-header-button'
32
32
  },
33
33
  /**
34
34
  * @member {Boolean} showHeaderFilters_=false
@@ -143,6 +143,20 @@ class Toolbar extends BaseToolbar {
143
143
  me.update()
144
144
  }
145
145
 
146
+ /**
147
+ * @param {String} dataField
148
+ * @returns {Neo.button.Base|null}
149
+ */
150
+ getColumn(dataField) {
151
+ for (const item of this.items) {
152
+ if (item.dataField === dataField) {
153
+ return item
154
+ }
155
+ }
156
+
157
+ return null
158
+ }
159
+
146
160
  /**
147
161
  * @param {String} dock
148
162
  * @returns {String} layoutConfig
@@ -0,0 +1,330 @@
1
+ import Plugin from '../../plugin/Base.mjs';
2
+ import TextField from '../../form/field/Text.mjs';
3
+ import VdomUtil from '../../util/VDom.mjs';
4
+
5
+ /**
6
+ * @class Neo.table.plugin.CellEditing
7
+ * @extends Neo.plugin.Base
8
+ */
9
+ class CellEditing extends Plugin {
10
+ static config = {
11
+ /**
12
+ * @member {String} className='Neo.table.plugin.CellEditing'
13
+ * @protected
14
+ */
15
+ className: 'Neo.table.plugin.CellEditing',
16
+ /**
17
+ * @member {String} ntype='plugin-table-cell-editing'
18
+ * @protected
19
+ */
20
+ ntype: 'plugin-table-cell-editing',
21
+ /**
22
+ * @member {String} cellCls='neo-table-cell'
23
+ */
24
+ cellCls: 'neo-table-cell',
25
+ /**
26
+ * @member {Boolean} disabled_=false
27
+ */
28
+ disabled_: false,
29
+ /**
30
+ * @member {String[]} editorCls=['neo-table-editor']
31
+ */
32
+ editorCls: ['neo-table-editor']
33
+ }
34
+
35
+ /**
36
+ * Storing editor instances per column
37
+ * @member {Object} editors={}
38
+ */
39
+ editors = {}
40
+ /**
41
+ * Storing the currently mounted editor
42
+ * @member {Neo.form.field.Base|null} mountedEditor=null
43
+ */
44
+ mountedEditor = null
45
+
46
+ /**
47
+ * @param {Object} config
48
+ */
49
+ construct(config) {
50
+ super.construct(config);
51
+
52
+ let me = this,
53
+ {owner} = me,
54
+ {selectionModel} = owner;
55
+
56
+ owner.on({
57
+ cellDoubleClick : me.onCellDoubleClick,
58
+ focusLeave : me.onFocusLeave,
59
+ selectionModelChange: me.onSelectionModelChange,
60
+ scope : me
61
+ });
62
+
63
+ // Connect an already registered selectionModel instance
64
+ if (Neo.typeOf(selectionModel) === 'NeoInstance') {
65
+ me.onSelectionModelChange({value: selectionModel})
66
+ }
67
+
68
+ owner.keys.add({
69
+ Enter: 'onTableKeyDown',
70
+ Space: 'onTableKeyDown',
71
+ scope: me
72
+ })
73
+ }
74
+
75
+ /**
76
+ * Triggered after the disabled config got changed
77
+ * @param {Boolean} value
78
+ * @param {Boolean} oldValue
79
+ * @protected
80
+ */
81
+ afterSetDisabled(value, oldValue) {
82
+ oldValue && this.unmountEditor()
83
+ }
84
+
85
+ /**
86
+ * @param {args} args
87
+ */
88
+ destroy(...args) {
89
+ Object.values(this.editors).forEach(editor => {
90
+ editor.destroy(false, true)
91
+ });
92
+
93
+ super.destroy(...args)
94
+ }
95
+
96
+ /**
97
+ * @param {Object} record
98
+ * @param {String} dataField
99
+ * @returns {Promise<void>}
100
+ */
101
+ async mountEditor(record, dataField) {
102
+ if (this.disabled) {
103
+ return
104
+ }
105
+
106
+ let me = this,
107
+ {appName, windowId} = me,
108
+ {view} = me.owner,
109
+ cellId = view.getCellId(record, dataField),
110
+ cellNode = VdomUtil.find(view.vdom, cellId).vdom,
111
+ column = me.owner.headerToolbar.getColumn(dataField),
112
+ editor = me.editors[dataField],
113
+ value = record[dataField];
114
+
115
+ if (me.mountedEditor) {
116
+ await me.unmountEditor();
117
+ await me.timeout(10)
118
+ }
119
+
120
+ if (!column.editable) {
121
+ return
122
+ }
123
+
124
+ if (!editor) {
125
+ me.editors[dataField] = editor = Neo.create({
126
+ module : TextField,
127
+ appName,
128
+ cls : me.editorCls,
129
+ dataField,
130
+ hideLabel: true,
131
+ parentId : view.id,
132
+ record,
133
+ value,
134
+ windowId,
135
+
136
+ keys: {
137
+ Enter : 'onEditorKeyEnter',
138
+ Escape: 'onEditorKeyEscape',
139
+ Tab : 'onEditorKeyTab',
140
+ scope : me
141
+ },
142
+
143
+ ...column.editor
144
+ })
145
+ } else {
146
+ editor.originalConfig.value = value;
147
+ editor.setSilent({record, value})
148
+ }
149
+
150
+ me.mountedEditor = editor;
151
+
152
+ cellNode.cn = [editor.createVdomReference()];
153
+ delete cellNode.innerHTML;
154
+
155
+ view.updateDepth = -1;
156
+
157
+ await view.promiseUpdate();
158
+
159
+ await me.timeout(10);
160
+
161
+ editor.focus()
162
+ }
163
+
164
+ /**
165
+ *
166
+ * @param {Object} data
167
+ * @param {Object} data.data
168
+ * @param {String} data.dataField
169
+ * @param {Object} data.record
170
+ * @param {Neo.table.View} data.view
171
+ * @returns {Promise<void>}
172
+ */
173
+ async onCellDoubleClick({data, dataField, record, view}) {
174
+ await this.mountEditor(record, dataField)
175
+ }
176
+
177
+ /**
178
+ * @param {Object} data
179
+ * @param {Neo.form.field.Base} field
180
+ * @returns {Promise<void>}
181
+ */
182
+ async onEditorKeyEnter(data, field) {
183
+ let me = this;
184
+
185
+ await me.submitEditor();
186
+ await me.timeout(20);
187
+ me.selectCell(data)
188
+ }
189
+
190
+ /**
191
+ * @param {Object} data
192
+ * @param {Neo.form.field.Base} field
193
+ * @returns {Promise<void>}
194
+ */
195
+ async onEditorKeyEscape(data, field) {
196
+ let me = this;
197
+
198
+ await me.unmountEditor();
199
+ await me.timeout(20);
200
+ me.selectCell(data)
201
+ }
202
+
203
+ /**
204
+ * @param {Object} event
205
+ * @param {Neo.form.field.Base} field
206
+ * @returns {Promise<void>}
207
+ */
208
+ async onEditorKeyTab(event, field) {
209
+ let me = this,
210
+ {store} = me.owner,
211
+ oldIndex = store.indexOf(field.record),
212
+ countRecords = store.getCount(),
213
+ index = (oldIndex + (event.altKey ? -1 : 1) + countRecords) % countRecords,
214
+ record = store.getAt(index);
215
+
216
+ await me.submitEditor();
217
+ await me.mountEditor(record, field.dataField)
218
+ }
219
+
220
+ /**
221
+ * @param {Object} data
222
+ * @returns {Promise<void>}
223
+ */
224
+ async onFocusLeave(data) {
225
+ await this.unmountEditor()
226
+ }
227
+
228
+ /**
229
+ * @param {Object} data
230
+ */
231
+ onSelectionChange(data) {
232
+ // todo: Once we separate cell selections & focus, we can use this event to mount editors
233
+ // console.log('onSelectionChange', data);
234
+ }
235
+
236
+ /**
237
+ * @param {Object} data
238
+ */
239
+ onSelectionModelChange(data) {
240
+ let selectionModel = data.value;
241
+
242
+ if (selectionModel.ntype.includes('cell')) {
243
+ selectionModel.on('selectionChange', this.onSelectionChange, this)
244
+ }
245
+ }
246
+
247
+ /**
248
+ * @param {Object} data
249
+ * @returns {Promise<void>}
250
+ */
251
+ async onTableKeyDown(data) {
252
+ let me = this,
253
+ {target} = data,
254
+ tableView = me.owner.view,
255
+ dataField, record;
256
+
257
+ if (!me.mountedEditor && target.cls?.includes('neo-selected')) {
258
+ dataField = tableView.getCellDataField(target.id);
259
+ record = tableView.getRecord(target.id);
260
+
261
+ await me.mountEditor(record, dataField)
262
+ }
263
+ }
264
+
265
+ /**
266
+ * @param {Object} data
267
+ * @param {Object[]} data.path
268
+ */
269
+ selectCell({path}) {
270
+ let me = this,
271
+ {selectionModel} = me.owner,
272
+ i = 0,
273
+ len = path.length,
274
+ cellId;
275
+
276
+ for (; i < len; i++) {
277
+ if (path[i].cls?.includes(me.cellCls)) {
278
+ cellId = path[i].id;
279
+ break
280
+ }
281
+ }
282
+
283
+ if (cellId) {
284
+ selectionModel?.deselect(cellId, true); // the cell might still count as selected => silent deselect first
285
+ selectionModel?.select(cellId);
286
+ me.owner.focus(cellId)
287
+ }
288
+ }
289
+
290
+ /**
291
+ * If the field is valid:
292
+ * Updates the record field, in case the value of the editor changed,
293
+ * otherwise unmounts the editor
294
+ * @returns {Promise<void>}
295
+ */
296
+ async submitEditor() {
297
+ let me = this,
298
+ field = me.mountedEditor;
299
+
300
+ if (field?.isValid()) {
301
+ if (field.isDirty) {
302
+ me.mountedEditor = null;
303
+ field.record[field.dataField] = field.getSubmitValue()
304
+ } else {
305
+ await me.unmountEditor()
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * @returns {Promise<void>}
312
+ */
313
+ async unmountEditor() {
314
+ if (!this.mountedEditor) {
315
+ return
316
+ }
317
+
318
+ let me = this,
319
+ record = me.mountedEditor.record,
320
+ tableView = me.owner.view,
321
+ rowIndex = tableView.store.indexOf(record);
322
+
323
+ me.mountedEditor = null;
324
+
325
+ tableView.vdom.cn[rowIndex] = tableView.createRow({record, rowIndex});
326
+ await tableView.promiseUpdate()
327
+ }
328
+ }
329
+
330
+ export default Neo.setupClass(CellEditing);
@@ -75,10 +75,14 @@ class KeyNavigation extends Base {
75
75
  upperCaseKey = me.parseUpperCaseKey(upperCaseKey);
76
76
 
77
77
  me.keys.forEach(key => {
78
- scope = Neo.get(key.scope);
78
+ scope = Neo.isString(key.scope) ? Neo.get(key.scope) : key.scope;
79
79
 
80
80
  if (key.key.toUpperCase() === upperCaseKey) {
81
- scope[key.fn]?.apply(scope, [data])
81
+ if (Neo.isFunction(key.fn)) {
82
+ key.fn.apply(scope, [data, me.component])
83
+ } else {
84
+ scope[key.fn]?.apply(scope, [data, me.component])
85
+ }
82
86
  }
83
87
  })
84
88
  }
@@ -94,12 +98,14 @@ class KeyNavigation extends Base {
94
98
  keyArray = [];
95
99
 
96
100
  if (componentId) {
97
- Object.entries(value).forEach(([key, value]) => {
98
- keyArray.push({
99
- fn : value,
100
- key,
101
- scope: componentId // todo: support VCs later on
102
- })
101
+ Object.entries(value).forEach(([key, val]) => {
102
+ if (key !== 'scope') {
103
+ keyArray.push({
104
+ fn : val,
105
+ key,
106
+ scope: value.scope || componentId // todo: support VCs later on
107
+ })
108
+ }
103
109
  });
104
110
 
105
111
  value = keyArray