neo.mjs 8.0.0-beta.2 → 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 (63) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/covid/view/MainContainerController.mjs +7 -4
  3. package/apps/portal/index.html +1 -1
  4. package/apps/portal/view/about/Container.mjs +0 -2
  5. package/apps/portal/view/about/MemberContainer.mjs +1 -20
  6. package/apps/portal/view/home/FooterContainer.mjs +1 -5
  7. package/apps/sharedcovid/view/MainContainerController.mjs +7 -4
  8. package/examples/ConfigurationViewport.mjs +37 -32
  9. package/examples/ServiceWorker.mjs +2 -2
  10. package/examples/calendar/weekview/MainContainer.mjs +6 -6
  11. package/examples/grid/cellEditing/MainContainer.mjs +175 -0
  12. package/examples/grid/cellEditing/MainContainerStateProvider.mjs +62 -0
  13. package/examples/grid/cellEditing/MainModel.mjs +30 -0
  14. package/examples/grid/cellEditing/MainStore.mjs +54 -0
  15. package/examples/grid/cellEditing/app.mjs +6 -0
  16. package/examples/grid/cellEditing/index.html +11 -0
  17. package/examples/grid/cellEditing/neo-config.json +6 -0
  18. package/examples/grid/container/MainContainer.mjs +7 -6
  19. package/examples/grid/covid/GridContainer.mjs +36 -36
  20. package/examples/grid/covid/Util.mjs +1 -1
  21. package/examples/table/cellEditing/MainContainer.mjs +174 -0
  22. package/examples/table/cellEditing/MainContainerStateProvider.mjs +62 -0
  23. package/examples/table/cellEditing/MainModel.mjs +30 -0
  24. package/examples/table/cellEditing/MainStore.mjs +54 -0
  25. package/examples/table/cellEditing/app.mjs +6 -0
  26. package/examples/table/cellEditing/index.html +11 -0
  27. package/examples/table/cellEditing/neo-config.json +6 -0
  28. package/examples/table/nestedRecordFields/MainContainerStateProvider.mjs +2 -1
  29. package/package.json +8 -8
  30. package/resources/scss/src/apps/portal/home/FooterContainer.scss +11 -2
  31. package/resources/scss/src/grid/Container.scss +0 -13
  32. package/resources/scss/src/grid/plugin/CellEditing.scss +11 -0
  33. package/resources/scss/src/table/plugin/CellEditing.scss +11 -0
  34. package/src/DefaultConfig.mjs +2 -2
  35. package/src/Neo.mjs +2 -2
  36. package/src/code/LivePreview.mjs +2 -2
  37. package/src/component/DateSelector.mjs +15 -0
  38. package/src/core/Base.mjs +1 -1
  39. package/src/form/field/Base.mjs +1 -4
  40. package/src/form/field/ComboBox.mjs +18 -2
  41. package/src/form/field/Date.mjs +10 -4
  42. package/src/grid/Container.mjs +242 -39
  43. package/src/grid/README.md +1 -1
  44. package/src/grid/View.mjs +282 -129
  45. package/src/grid/header/Button.mjs +327 -36
  46. package/src/grid/header/Toolbar.mjs +68 -4
  47. package/src/grid/plugin/CellEditing.mjs +30 -0
  48. package/src/main/DomEvents.mjs +12 -3
  49. package/src/manager/Focus.mjs +2 -2
  50. package/src/plugin/Base.mjs +15 -1
  51. package/src/plugin/Resizable.mjs +1 -5
  52. package/src/selection/Model.mjs +5 -1
  53. package/src/selection/grid/CellColumnModel.mjs +1 -1
  54. package/src/selection/grid/CellColumnRowModel.mjs +1 -1
  55. package/src/selection/grid/CellModel.mjs +1 -1
  56. package/src/selection/grid/ColumnModel.mjs +2 -2
  57. package/src/table/Container.mjs +32 -3
  58. package/src/table/View.mjs +9 -4
  59. package/src/table/header/Toolbar.mjs +15 -1
  60. package/src/table/plugin/CellEditing.mjs +330 -0
  61. package/src/tooltip/Base.mjs +17 -17
  62. package/src/util/KeyNavigation.mjs +14 -8
  63. package/src/vdom/Helper.mjs +1 -3
package/src/grid/View.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import Component from '../component/Base.mjs';
2
+ import NeoArray from '../util/Array.mjs';
2
3
  import VDomUtil from '../util/VDom.mjs';
3
4
 
4
5
  /**
@@ -20,7 +21,46 @@ class View extends Component {
20
21
  /**
21
22
  * @member {String[]} baseCls=['neo-grid-view']
22
23
  */
23
- baseCls: ['neo-grid-view']
24
+ baseCls: ['neo-grid-view'],
25
+ /**
26
+ * Define which model field contains the value of colspan definitions
27
+ * @member {String} colspanField='colspan'
28
+ */
29
+ colspanField: 'colspan',
30
+ /**
31
+ * @member {String|null} containerId=null
32
+ * @protected
33
+ */
34
+ containerId: null,
35
+ /**
36
+ * @member {Object} recordVnodeMap={}
37
+ */
38
+ recordVnodeMap: {},
39
+ /**
40
+ * @member {String} selectedRecordField='annotations.selected'
41
+ */
42
+ selectedRecordField: 'annotations.selected',
43
+ /**
44
+ * @member {Neo.data.Store|null} store=null
45
+ */
46
+ store: null,
47
+ /**
48
+ * @member {Boolean} useRowRecordIds=true
49
+ */
50
+ useRowRecordIds: true
51
+ }
52
+
53
+ /**
54
+ * @member {String[]} selectedRows
55
+ */
56
+ get selectedRows() {
57
+ let gridContainer = this.parent;
58
+
59
+ if (gridContainer.selectionModel.ntype === 'selection-grid-rowmodel') {
60
+ return gridContainer.selectionModel.items
61
+ }
62
+
63
+ return []
24
64
  }
25
65
 
26
66
  /**
@@ -45,149 +85,211 @@ class View extends Component {
45
85
  }
46
86
 
47
87
  /**
48
- * @param {Array} inputData
88
+ * @param {Object} data
89
+ * @param {String} [data.cellId]
90
+ * @param {Object} data.column
91
+ * @param {Neo.grid.Container} data.gridContainer
92
+ * @param {Number} data.index
93
+ * @param {Object} data.record
94
+ * @returns {Object}
49
95
  */
50
- createViewData(inputData) {
51
- let me = this,
52
- amountRows = inputData.length,
53
- container = me.parent,
54
- columns = container.items[0].items,
55
- colCount = columns.length,
56
- data = [],
57
- i = 0,
58
- {vdom} = me,
59
- cellCls, cellStyle, config, column, dockLeftMargin, dockRightMargin, id, index, j, rendererOutput,
60
- record, rendererValue, selectedRows, trCls;
61
-
62
- me.recordVnodeMap = {}; // remove old data
63
-
64
- if (container.selectionModel?.ntype === 'selection-grid-rowmodel') {
65
- selectedRows = container.selectionModel.items || []
96
+ applyRendererOutput(data) {
97
+ let {cellId, column, gridContainer, index, record} = data,
98
+ me = this,
99
+ cellCls = ['neo-grid-cell'],
100
+ colspan = record[me.colspanField],
101
+ {dataField} = column,
102
+ fieldValue = Neo.ns(dataField, false, record),
103
+ cellConfig, rendererOutput;
104
+
105
+ if (fieldValue === null || fieldValue === undefined) {
106
+ fieldValue = ''
66
107
  }
67
108
 
68
- for (; i < amountRows; i++) {
69
- record = inputData[i];
70
- id = me.getRowId(record, i);
71
-
72
- me.recordVnodeMap[id] = i;
109
+ rendererOutput = column.renderer.call(column.rendererScope || gridContainer, {
110
+ column,
111
+ dataField,
112
+ gridContainer,
113
+ index,
114
+ record,
115
+ value: fieldValue
116
+ });
73
117
 
74
- trCls = me.getTrClass(record, i);
118
+ switch (Neo.typeOf(rendererOutput)) {
119
+ case 'Object': {
120
+ if (rendererOutput.html) {
121
+ rendererOutput.cls && cellCls.push(...rendererOutput.cls);
122
+ } else {
123
+ rendererOutput = [rendererOutput];
124
+ }
125
+ break
126
+ }
127
+ case 'Date':
128
+ case 'Number':
129
+ case 'String': {
130
+ rendererOutput = {
131
+ cls : cellCls,
132
+ html: rendererOutput?.toString()
133
+ };
134
+ break
135
+ }
136
+ }
75
137
 
76
- if (selectedRows?.includes(id)) {
77
- trCls.push('neo-selected');
138
+ if (rendererOutput === null || rendererOutput === undefined) {
139
+ rendererOutput = ''
140
+ }
78
141
 
79
- Neo.getComponent(me.containerId).fire('select', {
80
- record
81
- })
82
- }
142
+ if (column.cellAlign !== 'left') {
143
+ cellCls.push('neo-' + column.cellAlign)
144
+ }
83
145
 
84
- data.push({
85
- id,
86
- cls : trCls,
87
- cn : [],
88
- tabIndex: '-1'
89
- });
146
+ if (!cellId) {
147
+ cellId = me.getCellId(record, column.dataField)
148
+ }
90
149
 
91
- dockLeftMargin = 0;
92
- dockRightMargin = 0;
150
+ cellConfig = {
151
+ id : cellId,
152
+ cls : cellCls,
153
+ style : rendererOutput.style || {},
154
+ tabIndex: '-1'
155
+ };
93
156
 
94
- j = 0;
157
+ if (column.width) {
158
+ cellConfig.style.minWidth = `${column.width}px`
159
+ }
95
160
 
96
- for (; j < colCount; j++) {
97
- column = columns[j];
98
- rendererValue = Neo.ns(column.field, false, record);
161
+ if (colspan && Object.keys(colspan).includes(dataField)) {
162
+ cellConfig.colspan = colspan[dataField]
163
+ }
99
164
 
100
- if (rendererValue === null || rendererValue === undefined) {
101
- rendererValue = ''
102
- }
165
+ if (Neo.typeOf(rendererOutput) === 'Object') {
166
+ cellConfig.innerHTML = rendererOutput.html || ''
167
+ } else {
168
+ cellConfig.cn = rendererOutput
169
+ }
103
170
 
104
- rendererOutput = column.renderer.call(column.rendererScope || container, {
105
- column,
106
- field: column.field,
107
- index: i,
108
- record,
109
- value: rendererValue
110
- });
171
+ return cellConfig
172
+ }
111
173
 
112
- cellCls = rendererOutput?.cls || ['neo-grid-cell'];
174
+ /**
175
+ * @param {Object} opts
176
+ * @param {Object} opts.record
177
+ * @param {Number} [opts.rowIndex]
178
+ * @returns {Object}
179
+ */
180
+ createRow({record, rowIndex}) {
181
+ if (!Neo.isNumber(rowIndex)) {
182
+ rowIndex = this.store.indexOf(record)
183
+ }
113
184
 
114
- if (column.align !== 'left') {
115
- cellCls.push('neo-' + column.align)
116
- }
185
+ let me = this,
186
+ gridContainer = me.parent,
187
+ colspan = record[me.colspanField],
188
+ colspanKeys = colspan && Object.keys(colspan),
189
+ columns = gridContainer.items[0].items,
190
+ colCount = columns.length,
191
+ dockLeftMargin = 0,
192
+ dockRightMargin = 0,
193
+ id = me.getRowId(record, rowIndex),
194
+ {selectedRows} = me,
195
+ trCls = me.getTrClass(record, rowIndex),
196
+ config, column, columnIndex, gridRow, i;
197
+
198
+ me.recordVnodeMap[id] = rowIndex;
199
+
200
+ if (selectedRows && Neo.ns(me.selectedRecordField, false, record)) {
201
+ NeoArray.add(selectedRows, id)
202
+ }
117
203
 
118
- if (!Neo.isObject(rendererOutput)) {
119
- rendererOutput = {
120
- cls : cellCls,
121
- html: rendererOutput?.toString()
122
- }
123
- }
204
+ if (selectedRows?.includes(id)) {
205
+ trCls.push('neo-selected');
124
206
 
125
- cellStyle = rendererOutput.style || {};
207
+ me.parent.fire('select', {
208
+ record
209
+ })
210
+ }
126
211
 
127
- if (column.width) {
128
- cellStyle.minWidth = `${column.width}px`
129
- }
212
+ gridRow = {
213
+ id,
214
+ cls : trCls,
215
+ cn : [],
216
+ tabIndex: '-1'
217
+ };
130
218
 
131
- config = {
132
- id : me.getCellId(record, column.field),
133
- cls : cellCls,
134
- innerHTML: rendererOutput.html || '',
135
- style : cellStyle,
136
- tabIndex : '-1'
137
- };
219
+ for (i=0; i < colCount; i++) {
220
+ column = columns[i];
221
+ config = me.applyRendererOutput({column, gridContainer, index: rowIndex, record});
138
222
 
139
- if (column.dock) {
140
- config.cls = ['neo-locked', ...config.cls || []];
223
+ if (column.dock) {
224
+ config.cls = ['neo-locked', ...config.cls || []];
141
225
 
142
- if (column.dock === 'left') {
143
- config.style.left = dockLeftMargin + 'px';
144
- dockLeftMargin += column.width
145
- }
226
+ if (column.dock === 'left') {
227
+ config.style.left = dockLeftMargin + 'px';
228
+ dockLeftMargin += (column.width + 1) // todo: borders fix
146
229
  }
230
+ }
147
231
 
148
- if (column.flex) {
149
- config.style.width = '100%'
150
- }
232
+ if (column.flex) {
233
+ config.style.width = '100%'
234
+ }
151
235
 
152
- data[i].cn.push(config)
236
+ gridRow.cn.push(config);
237
+
238
+ if (colspanKeys?.includes(column.dataField)) {
239
+ i += (colspan[column.dataField] - 1)
153
240
  }
241
+ }
154
242
 
155
- j = 0;
243
+ for (i=0; i < colCount; i++) {
244
+ columnIndex = colCount - i -1;
245
+ column = columns[columnIndex];
156
246
 
157
- for (; j < colCount; j++) {
158
- index = colCount - j -1;
159
- column = columns[index];
247
+ if (column.dock === 'right') {
248
+ gridRow.cn[columnIndex].style.right = dockRightMargin + 'px';
249
+ dockRightMargin += (column.width + 1) // todo: borders fix
250
+ }
160
251
 
161
- if (column.dock === 'right') {
162
- data[i].cn[index].style.right = dockRightMargin + 'px';
163
- dockRightMargin += (column.width + 1) // todo: borders fix
164
- }
252
+ if (colspanKeys?.includes(column.dataField)) {
253
+ i += (colspan[column.dataField] - 1)
165
254
  }
166
255
  }
167
256
 
168
- vdom.cn = data;
257
+ // the dock margins are the same for each row
258
+ rowIndex === 0 && Object.assign(gridContainer, {dockLeftMargin, dockRightMargin});
169
259
 
170
- container.dockLeftMargin = dockLeftMargin;
171
- container.dockRightMargin = dockRightMargin;
260
+ return gridRow
261
+ }
262
+
263
+ /**
264
+ * @param {Object[]} inputData
265
+ */
266
+ createViewData(inputData) {
267
+ let me = this,
268
+ amountRows = inputData.length,
269
+ i = 0,
270
+ rows = [],
271
+ {selectedRows} = me;
272
+
273
+ for (; i < amountRows; i++) {
274
+ rows.push(me.createRow({record: inputData[i], rowIndex: i}))
275
+ }
276
+
277
+ me.vdom.cn = rows;
172
278
 
173
279
  me.promiseUpdate().then(() => {
174
280
  if (selectedRows?.length > 0) {
175
- // this logic only works for selection.table.RowModel
176
- Neo.main.DomAccess.scrollToTableRow({
177
- appName: me.appName,
178
- id : selectedRows[0]
179
- })
281
+ // this logic only works for selection.grid.RowModel
282
+ Neo.main.DomAccess.scrollToTableRow({appName: me.appName, id: selectedRows[0]})
180
283
  }
181
284
  })
182
285
  }
183
286
 
184
287
  /**
185
- * @param {Boolean} updateParentVdom
186
- * @param {Boolean} silent
288
+ * @param args
187
289
  */
188
- destroy(updateParentVdom, silent) {
290
+ destroy(...args) {
189
291
  this.store = null;
190
- super.destroy(updateParentVdom, silent)
292
+ super.destroy(...args)
191
293
  }
192
294
 
193
295
  /**
@@ -196,11 +298,11 @@ class View extends Component {
196
298
  */
197
299
  fireCellEvent(data, eventName) {
198
300
  let me = this,
199
- {id} = data.target,
301
+ id = data.currentTarget,
200
302
  dataField = me.getCellDataField(id),
201
303
  record = me.getRecord(id);
202
304
 
203
- me.parent.fire(eventName, {id: me, data, dataField, record})
305
+ me.parent.fire(eventName, {data, dataField, record, view: me})
204
306
  }
205
307
 
206
308
  /**
@@ -209,10 +311,10 @@ class View extends Component {
209
311
  */
210
312
  fireRowEvent(data, eventName) {
211
313
  let me = this,
212
- {id} = data.target,
314
+ id = data.currentTarget,
213
315
  record = me.getRecord(id);
214
316
 
215
- me.parent.fire(eventName, {id: me, data, record})
317
+ me.parent.fire(eventName, {data, record, view: me})
216
318
  }
217
319
 
218
320
  /**
@@ -225,15 +327,39 @@ class View extends Component {
225
327
 
226
328
  /**
227
329
  * @param {Object} record
228
- * @param {String} field
330
+ * @param {String} dataField
229
331
  * @returns {String}
230
332
  */
231
- getCellId(record, field) {
232
- return this.id + '__' + record[this.store.keyProperty] + '__' + field
333
+ getCellId(record, dataField) {
334
+ return this.id + '__' + record[this.store.keyProperty] + '__' + dataField
335
+ }
336
+
337
+ /**
338
+ * Get a grid column or column index by a given field name
339
+ * @param {String} field
340
+ * @param {Boolean} returnIndex=false
341
+ * @returns {Object|Number|null}
342
+ */
343
+ getColumn(field, returnIndex=false) {
344
+ let container = this.parent,
345
+ columns = container.headerToolbar.items,
346
+ i = 0,
347
+ len = columns.length,
348
+ column;
349
+
350
+ for (; i < len; i++) {
351
+ column = columns[i];
352
+
353
+ if (column.dataField === field) {
354
+ return returnIndex ? i : column
355
+ }
356
+ }
357
+
358
+ return null
233
359
  }
234
360
 
235
361
  /**
236
- * Get the matching record by passing a row id, a cell id or an id inside a table cell.
362
+ * Get the matching record by passing a row id, a cell id or an id inside a grid cell.
237
363
  * @param {String} nodeId
238
364
  * @returns {Object|null}
239
365
  */
@@ -243,7 +369,7 @@ class View extends Component {
243
369
  node, parentNodes;
244
370
 
245
371
  if (record) {
246
- return record
372
+ return record;
247
373
  }
248
374
 
249
375
  parentNodes = VDomUtil.getParentNodes(me.vdom, nodeId);
@@ -273,7 +399,15 @@ class View extends Component {
273
399
  * @returns {String}
274
400
  */
275
401
  getRowId(record, index) {
276
- return `${this.id}__tr__${record[this.store.keyProperty]}`
402
+ let me = this,
403
+ {store} = me;
404
+
405
+ if (me.useRowRecordIds) {
406
+ return `${me.id}__tr__${record[store.keyProperty]}`
407
+ } else {
408
+ index = Neo.isNumber(index) ? index : store.indexOf(record);
409
+ return me.vdom.cn[index]?.id || Neo.getId('tr')
410
+ }
277
411
  }
278
412
 
279
413
  /**
@@ -322,24 +456,43 @@ class View extends Component {
322
456
  * @param {Neo.data.Model} opts.model The model instance of the changed record
323
457
  * @param {Object} opts.record
324
458
  */
325
- onStoreRecordChange(opts) {
326
- let me = this,
327
- deltas = [],
328
- cellId, cellNode;
329
-
330
- opts.fields.forEach(field => {
331
- cellId = me.getCellId(opts.record, field.name);
332
- cellNode = me.getVdomChild(cellId);
333
-
334
- cellNode.innerHTML = field.value; // keep the vdom in sync
335
-
336
- deltas.push({
337
- id : cellId,
338
- innerHTML: field.value
459
+ onStoreRecordChange({fields, model, record}) {
460
+ let me = this,
461
+ fieldNames = fields.map(field => field.name),
462
+ needsUpdate = false,
463
+ gridContainer = me.parent,
464
+ {selectionModel} = gridContainer,
465
+ {vdom} = me,
466
+ cellId, cellNode, column, index, scope;
467
+
468
+ if (fieldNames.includes(me.colspanField)) {
469
+ index = me.store.indexOf(record);
470
+ me.vdom.cn[index] = me.createRow({record, rowIndex: index});
471
+ me.update()
472
+ } else {
473
+ fields.forEach(field => {
474
+ if (field.name === me.selectedRecordField) {
475
+ if (selectionModel.ntype === 'selection-grid-rowmodel') {
476
+ selectionModel[field.value ? 'select' : 'deselect'](me.getRowId(record))
477
+ }
478
+ } else {
479
+ cellId = me.getCellId(record, field.name);
480
+ cellNode = VDomUtil.find(vdom, cellId);
481
+
482
+ // the vdom might not exist yet => nothing to do in this case
483
+ if (cellNode?.vdom) {
484
+ column = me.getColumn(field.name);
485
+ index = cellNode.index;
486
+ needsUpdate = true;
487
+ scope = column.rendererScope || gridContainer;
488
+
489
+ cellNode.parentNode.cn[index] = me.applyRendererOutput({cellId, column, gridContainer, index, record})
490
+ }
491
+ }
339
492
  })
340
- });
493
+ }
341
494
 
342
- deltas.length > 0 && Neo.applyDeltas(me.appName, deltas)
495
+ needsUpdate && me.update()
343
496
  }
344
497
  }
345
498