neo.mjs 8.4.2 → 8.6.0

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.
@@ -1,4 +1,5 @@
1
- import Base from '../core/Base.mjs';
1
+ import Base from '../core/Base.mjs';
2
+ import RecordFactory from './RecordFactory.mjs';
2
3
 
3
4
  /**
4
5
  * @class Neo.data.Model
@@ -17,9 +18,9 @@ class Model extends Base {
17
18
  */
18
19
  ntype: 'model',
19
20
  /**
20
- * @member {Array|null} fields=null
21
+ * @member {Object[]|null} fields_=null
21
22
  */
22
- fields: null,
23
+ fields_: null,
23
24
  /**
24
25
  * @member {String} keyProperty_='id'
25
26
  */
@@ -38,23 +39,80 @@ class Model extends Base {
38
39
  trackModifiedFields: false
39
40
  }
40
41
 
42
+ /**
43
+ * @member {Map} fieldsMap=new Map()
44
+ * @protected
45
+ */
46
+ fieldsMap = new Map()
47
+ /**
48
+ * @member {Boolean} hasNestedFields=false
49
+ * @protected
50
+ */
51
+ hasNestedFields = false
52
+
53
+ /**
54
+ * @param {Object} config
55
+ */
56
+ construct(config) {
57
+ super.construct(config);
58
+ RecordFactory.createRecordClass(this)
59
+ }
60
+
61
+ /**
62
+ Triggered after the fields config got changed
63
+ * @param {Object[]|null} value
64
+ * @param {Object[]|null} oldValue
65
+ * @protected
66
+ */
67
+ afterSetFields(value, oldValue) {
68
+ if (value) {
69
+ let me = this;
70
+
71
+ me.updateFieldsMap(value);
72
+
73
+ // Fields can get changed multiple times before the model instance is getting constructed.
74
+ // We only need the latest state before construction & honor run-time changes.
75
+ if (me.isConstructed) {
76
+ RecordFactory.createRecordClass(me, true)
77
+ }
78
+ }
79
+ }
80
+
41
81
  /**
42
82
  * Finds a field config by a given field name
43
83
  * @param {String} name
44
84
  * @returns {Object|null} The field config object or null if no match was found
45
85
  */
46
86
  getField(name) {
47
- let me = this,
48
- i = 0,
49
- len = me.fields?.length || 0;
87
+ return this.fieldsMap.get(name) || null
88
+ }
50
89
 
51
- for (; i < len; i++) {
52
- if (me.fields[i].name === name) {
53
- return me.fields[i]
54
- }
90
+ /**
91
+ * @param {Object[]} fields
92
+ * @param {Boolean} isRoot=true
93
+ * @param {String} path=''
94
+ */
95
+ updateFieldsMap(fields, isRoot=true, path='') {
96
+ let me = this,
97
+ {fieldsMap} = me,
98
+ fieldName;
99
+
100
+ if (isRoot) {
101
+ fieldsMap.clear();
102
+ me.hasNestedFields = false
55
103
  }
56
104
 
57
- return null
105
+ fields.forEach(field => {
106
+ fieldName = path + field.name
107
+
108
+ // Assuming that nested fields contain the full path as the name, we do not need a prefix.
109
+ if (field.fields) {
110
+ me.hasNestedFields = true;
111
+ me.updateFieldsMap(field.fields, false, field.name + '.')
112
+ } else {
113
+ fieldsMap.set(fieldName, field)
114
+ }
115
+ })
58
116
  }
59
117
  }
60
118
 
@@ -2,6 +2,8 @@ import Base from '../core/Base.mjs';
2
2
  import Logger from '../util/Logger.mjs';
3
3
  import Model from './Model.mjs';
4
4
 
5
+ const dataSymbol = Symbol.for('data');
6
+
5
7
  let instance;
6
8
 
7
9
  /**
@@ -35,57 +37,46 @@ class RecordFactory extends Base {
35
37
 
36
38
  /**
37
39
  * @param {Object} data
38
- * @param {Object} data.config
39
40
  * @param {Object} data.field
40
41
  * @param {Neo.data.RecordFactory} data.me
41
42
  * @param {Neo.data.Model} data.model
42
43
  * @param {String} data.path=''
43
44
  */
44
- createField({config, field, me, model, path=''}) {
45
- let value = Neo.ns(field.mapping || field.name, false, config),
46
- fieldName = field.name.split('.').pop(),
47
- symbol = Symbol.for(fieldName),
48
- fieldPath, parsedValue, properties;
45
+ createField({field, me, model, path=''}) {
46
+ let fieldName = field.name,
47
+ fieldPath = path === '' ? fieldName : `${path}.${fieldName}`,
48
+ properties;
49
49
 
50
50
  if (field.fields) {
51
51
  field.fields.forEach(childField => {
52
- fieldPath = path.split('.');
53
- fieldPath = fieldPath.filter(Boolean);
54
- fieldPath.push(field.name);
55
-
56
- this.createField({config, field: childField, me, model, path: fieldPath.join('.')})
52
+ this.createField({field: childField, me, model, path: fieldPath})
57
53
  })
58
54
  } else {
59
- if (value === undefined && Object.hasOwn(field, 'defaultValue')) {
60
- value = field.defaultValue
61
- }
62
-
63
- parsedValue = instance.parseRecordValue(me, field, value, config);
64
-
65
55
  properties = {
66
- [symbol]: {
67
- value : Neo.clone(parsedValue, true),
68
- writable: true
69
- },
70
- [fieldName]: {
56
+ [fieldPath]: {
71
57
  configurable: true,
72
58
  enumerable : true,
73
59
  get() {
74
- return this[symbol]
60
+ if (model.hasNestedFields) {
61
+ return Neo.ns(fieldPath, false, this[dataSymbol])
62
+ }
63
+
64
+ return this[dataSymbol][fieldName]
75
65
  },
76
66
  set(value) {
77
- let oldValue = this[symbol];
67
+ let me = this,
68
+ oldValue = me[dataSymbol][fieldName];
78
69
 
79
70
  value = instance.parseRecordValue(me, field, value);
80
71
 
81
72
  if (!Neo.isEqual(value, oldValue)) {
82
- this[symbol] = value;
73
+ instance.setRecordData(fieldPath, model, me, value);
83
74
 
84
75
  me._isModified = true;
85
76
  me._isModified = instance.isModified(me, model.trackModifiedFields);
86
77
 
87
78
  instance.onRecordChange({
88
- fields: [{name: field.name, oldValue, value}],
79
+ fields: [{name: fieldPath, oldValue, value}],
89
80
  model,
90
81
  record: me
91
82
  })
@@ -97,11 +88,11 @@ class RecordFactory extends Base {
97
88
  // adding the original value of each field
98
89
  if (model.trackModifiedFields) {
99
90
  properties[instance.ovPrefix + field.name] = {
100
- value: parsedValue
91
+ value
101
92
  }
102
93
  }
103
94
 
104
- Object.defineProperties(path ? Neo.ns(path, true, me) : me, properties)
95
+ Object.defineProperties(me, properties)
105
96
  }
106
97
  }
107
98
 
@@ -122,37 +113,28 @@ class RecordFactory extends Base {
122
113
 
123
114
  /**
124
115
  * @param {Neo.data.Model} model
116
+ * @param {Boolean} overwrite=false
125
117
  * @returns {Object}
126
118
  */
127
- createRecordClass(model) {
119
+ createRecordClass(model, overwrite=false) {
128
120
  if (model instanceof Model) {
129
121
  let className = `${this.recordNamespace}.${model.className}.${model.id}`,
130
122
  ns = Neo.ns(className),
131
123
  key, nsArray, cls;
132
124
 
133
- if (!ns) {
125
+ if (!ns || overwrite) {
134
126
  nsArray = className.split('.');
135
127
  key = nsArray.pop();
136
128
  ns = Neo.ns(nsArray, true);
137
129
  cls = ns[key] = class Record {
138
130
  // We do not want to minify the ctor class name in dist/production
139
- static name = 'Record'
131
+ static name = 'Record';
132
+
133
+ [dataSymbol] = {}
140
134
 
141
135
  constructor(config) {
142
- let me = this;
143
-
144
- Object.defineProperties(me, {
145
- _isModified: {
146
- value : false,
147
- writable: true
148
- }
149
- });
150
-
151
- if (Array.isArray(model.fields)) {
152
- model.fields.forEach(field => {
153
- instance.createField({config, field, me, model})
154
- })
155
- }
136
+ this.setSilent(config);
137
+ this._isModified = false
156
138
  }
157
139
 
158
140
  /**
@@ -170,8 +152,22 @@ class RecordFactory extends Base {
170
152
  setSilent(fields) {
171
153
  instance.setRecordFields(model, this, fields, true)
172
154
  }
155
+
156
+ /**
157
+ * When using JSON.stringify(this), we want to get the raw data
158
+ * @returns {Object}
159
+ */
160
+ toJSON() {
161
+ return this[dataSymbol]
162
+ }
173
163
  };
174
164
 
165
+ if (Array.isArray(model.fields)) {
166
+ model.fields.forEach(field => {
167
+ instance.createField({field, me: cls.prototype, model})
168
+ })
169
+ }
170
+
175
171
  Object.defineProperty(cls.prototype, 'isRecord', {value: true});
176
172
  Object.defineProperty(cls, 'isClass', {value: true});
177
173
 
@@ -256,7 +252,7 @@ class RecordFactory extends Base {
256
252
  * @param {Object} recordConfig=null
257
253
  * @returns {*}
258
254
  */
259
- parseRecordValue(record, field, value, recordConfig=null) {
255
+ parseRecordValue(record, field, value, recordConfig=null) {!field && console.log(record, value);
260
256
  if (field.calculate) {
261
257
  return field.calculate(record, field, recordConfig)
262
258
  }
@@ -325,24 +321,55 @@ class RecordFactory extends Base {
325
321
  return value
326
322
  }
327
323
 
324
+ /**
325
+ * @param {String} fieldName
326
+ * @param {Neo.data.Model} model
327
+ * @param {Record} record
328
+ * @param {*} value
329
+ * @protected
330
+ */
331
+ setRecordData(fieldName, model, record, value) {
332
+ if (model.hasNestedFields && fieldName.includes('.')) {
333
+ let ns, nsArray;
334
+
335
+ nsArray = fieldName.split('.');
336
+ fieldName = nsArray.pop();
337
+ ns = Neo.ns(nsArray, true, record[dataSymbol]);
338
+
339
+ ns[fieldName] = value
340
+ } else {
341
+ record[dataSymbol][fieldName] = value
342
+ }
343
+ }
344
+
328
345
  /**
329
346
  * @param {Neo.data.Model} model
330
347
  * @param {Object} record
331
348
  * @param {Object} fields
332
349
  * @param {Boolean} silent=false
350
+ * @param {Object[]} changedFields=[] Internal flag
333
351
  */
334
- setRecordFields(model, record, fields, silent=false) {
335
- let changedFields = [],
336
- oldValue;
352
+ setRecordFields(model, record, fields, silent=false, changedFields=[]) {
353
+ let {fieldsMap} = model,
354
+ fieldExists, oldValue;
337
355
 
338
356
  Object.entries(fields).forEach(([key, value]) => {
339
- oldValue = record[key];
340
- value = instance.parseRecordValue(record, model.getField(key), value);
357
+ fieldExists = fieldsMap.has(key);
341
358
 
342
- if (!Neo.isEqual(oldValue, value)) {
343
- record[Symbol.for(key)] = value; // silent update
344
- record._isModified = true;
345
- changedFields.push({name: key, oldValue, value})
359
+ if (Neo.isObject(value) && !fieldExists) {
360
+ Object.entries(value).forEach(([childKey, childValue]) => {
361
+ this.setRecordFields(model, record, {[`${key}.${childKey}`]: childValue}, true, changedFields)
362
+ })
363
+ } else if (fieldExists) {
364
+ oldValue = record[key];
365
+ value = instance.parseRecordValue(record, model.getField(key), value);
366
+
367
+ if (!Neo.isEqual(oldValue, value)) {
368
+ instance.setRecordData(key, model, record, value);
369
+
370
+ record._isModified = true;
371
+ changedFields.push({name: key, oldValue, value})
372
+ }
346
373
  }
347
374
  });
348
375
 
@@ -197,8 +197,7 @@ class Store extends Base {
197
197
  */
198
198
  afterSetModel(value, oldValue) {
199
199
  if (value) {
200
- value.storeId = this.id;
201
- RecordFactory.createRecordClass(value)
200
+ value.storeId = this.id
202
201
  }
203
202
  }
204
203
 
@@ -414,6 +414,8 @@ class Dialog extends Panel {
414
414
  // rendered outside the visible area
415
415
  await me.render(true);
416
416
 
417
+ await me.timeout(30);
418
+
417
419
  let [dialogRect, bodyRect] = await me.getDomRect([me.id, 'document.body']);
418
420
 
419
421
  // Move to cover the animation target
@@ -1,7 +1,6 @@
1
1
  import BaseContainer from '../container/Base.mjs';
2
2
  import ClassSystemUtil from '../util/ClassSystem.mjs';
3
3
  import GridView from './View.mjs';
4
- import RowModel from '../selection/grid/RowModel.mjs';
5
4
  import Store from '../data/Store.mjs';
6
5
  import * as header from './header/_export.mjs';
7
6
 
@@ -58,11 +57,6 @@ class GridContainer extends BaseContainer {
58
57
  * @member {String|null} headerToolbarId_=null
59
58
  */
60
59
  headerToolbarId_: null,
61
- /**
62
- * Additional used keys for the selection model
63
- * @member {Object} keys
64
- */
65
- keys: {},
66
60
  /**
67
61
  * @member {String} layout='base'
68
62
  */
@@ -81,10 +75,6 @@ class GridContainer extends BaseContainer {
81
75
  * @protected
82
76
  */
83
77
  scrollbarId_: null,
84
- /**
85
- * @member {Neo.selection.Model} selectionModel_=null
86
- */
87
- selectionModel_: null,
88
78
  /**
89
79
  * @member {Boolean} showHeaderFilters_=false
90
80
  */
@@ -294,16 +284,6 @@ class GridContainer extends BaseContainer {
294
284
  }
295
285
  }
296
286
 
297
- /**
298
- * Triggered after the selectionModel config got changed
299
- * @param {Neo.selection.Model} value
300
- * @param {Neo.selection.Model} oldValue
301
- * @protected
302
- */
303
- afterSetSelectionModel(value, oldValue) {
304
- this.rendered && value.register(this)
305
- }
306
-
307
287
  /**
308
288
  * Triggered after the showHeaderFilters config got changed
309
289
  * @param {Boolean} value
@@ -352,18 +332,6 @@ class GridContainer extends BaseContainer {
352
332
  return value || oldValue
353
333
  }
354
334
 
355
- /**
356
- * Triggered before the selectionModel config gets changed.
357
- * @param {Neo.selection.Model} value
358
- * @param {Neo.selection.Model} oldValue
359
- * @protected
360
- */
361
- beforeSetSelectionModel(value, oldValue) {
362
- oldValue?.destroy();
363
-
364
- return ClassSystemUtil.beforeSetInstance(value, RowModel)
365
- }
366
-
367
335
  /**
368
336
  * Triggered before the store config gets changed.
369
337
  * @param {Neo.data.Store} value
@@ -534,14 +502,6 @@ class GridContainer extends BaseContainer {
534
502
  return `${this.id}__wrapper`
535
503
  }
536
504
 
537
- /**
538
- *
539
- */
540
- onConstructed() {
541
- super.onConstructed();
542
- this.selectionModel?.register(this)
543
- }
544
-
545
505
  /**
546
506
  * @param {Object} data
547
507
  */
package/src/grid/View.mjs CHANGED
@@ -1,6 +1,8 @@
1
- import Component from '../component/Base.mjs';
2
- import NeoArray from '../util/Array.mjs';
3
- import VDomUtil from '../util/VDom.mjs';
1
+ import ClassSystemUtil from '../util/ClassSystem.mjs';
2
+ import Component from '../component/Base.mjs';
3
+ import NeoArray from '../util/Array.mjs';
4
+ import RowModel from '../selection/grid/RowModel.mjs';
5
+ import VDomUtil from '../util/VDom.mjs';
4
6
 
5
7
  /**
6
8
  * @class Neo.grid.View
@@ -75,9 +77,10 @@ class GridView extends Component {
75
77
  */
76
78
  isScrolling_: false,
77
79
  /**
78
- * @member {Object} recordVnodeMap={}
80
+ * Additional used keys for the selection model
81
+ * @member {Object} keys
79
82
  */
80
- recordVnodeMap: {},
83
+ keys: {},
81
84
  /**
82
85
  * @member {String} role='rowgroup'
83
86
  */
@@ -91,6 +94,10 @@ class GridView extends Component {
91
94
  * @member {Object} scrollPosition_={x:0,y:0}
92
95
  */
93
96
  scrollPosition_: {x: 0, y: 0},
97
+ /**
98
+ * @member {Neo.selection.Model} selectionModel_=null
99
+ */
100
+ selectionModel_: null,
94
101
  /**
95
102
  * @member {String} selectedRecordField='annotations.selected'
96
103
  */
@@ -103,10 +110,6 @@ class GridView extends Component {
103
110
  * @member {Neo.data.Store|null} store_=null
104
111
  */
105
112
  store_: null,
106
- /**
107
- * @member {Boolean} useRowRecordIds=true
108
- */
109
- useRowRecordIds: true,
110
113
  /**
111
114
  * Stores the indexes of the first & last painted columns
112
115
  * @member {Number[]} visibleColumns_=[0,0]
@@ -121,7 +124,7 @@ class GridView extends Component {
121
124
  * @member {Object} _vdom
122
125
  */
123
126
  _vdom:
124
- {cn: [
127
+ {tabIndex: '-1', cn: [
125
128
  {cn: []},
126
129
  {cls: 'neo-grid-scrollbar'}
127
130
  ]}
@@ -143,10 +146,10 @@ class GridView extends Component {
143
146
  * @member {String[]} selectedRows
144
147
  */
145
148
  get selectedRows() {
146
- let {gridContainer} = this;
149
+ let {selectionModel} = this;
147
150
 
148
- if (gridContainer.selectionModel.ntype === 'selection-grid-rowmodel') {
149
- return gridContainer.selectionModel.items
151
+ if (selectionModel.ntype === 'selection-grid-rowmodel') {
152
+ return selectionModel.items
150
153
  }
151
154
 
152
155
  return []
@@ -319,6 +322,16 @@ class GridView extends Component {
319
322
  }
320
323
  }
321
324
 
325
+ /**
326
+ * Triggered after the selectionModel config got changed
327
+ * @param {Neo.selection.Model} value
328
+ * @param {Neo.selection.Model} oldValue
329
+ * @protected
330
+ */
331
+ afterSetSelectionModel(value, oldValue) {
332
+ this.rendered && value.register(this)
333
+ }
334
+
322
335
  /**
323
336
  * Triggered after the startIndex config got changed
324
337
  * @param {Number} value
@@ -375,7 +388,7 @@ class GridView extends Component {
375
388
  cellCls = ['neo-grid-cell'],
376
389
  colspan = record[me.colspanField],
377
390
  {dataField} = column,
378
- fieldValue = Neo.ns(dataField, false, record),
391
+ fieldValue = record[dataField],
379
392
  cellConfig, rendererOutput;
380
393
 
381
394
  if (fieldValue === null || fieldValue === undefined) {
@@ -428,8 +441,7 @@ class GridView extends Component {
428
441
  id : cellId,
429
442
  cls : cellCls,
430
443
  role : 'gridcell',
431
- style : rendererOutput.style || {},
432
- tabIndex : '-1'
444
+ style : rendererOutput.style || {}
433
445
  };
434
446
 
435
447
  if (column.width) {
@@ -449,6 +461,18 @@ class GridView extends Component {
449
461
  return cellConfig
450
462
  }
451
463
 
464
+ /**
465
+ * Triggered before the selectionModel config gets changed.
466
+ * @param {Neo.selection.Model} value
467
+ * @param {Neo.selection.Model} oldValue
468
+ * @protected
469
+ */
470
+ beforeSetSelectionModel(value, oldValue) {
471
+ oldValue?.destroy();
472
+
473
+ return ClassSystemUtil.beforeSetInstance(value, RowModel)
474
+ }
475
+
452
476
  /**
453
477
  * @param {Object} opts
454
478
  * @param {Object} opts.record
@@ -467,13 +491,11 @@ class GridView extends Component {
467
491
  trCls = me.getTrClass(record, rowIndex),
468
492
  config, column, endIndex, gridRow, i, startIndex;
469
493
 
470
- me.recordVnodeMap[id] = rowIndex;
471
-
472
494
  if (rowIndex % 2 !== 0) {
473
495
  trCls.push('neo-even')
474
496
  }
475
497
 
476
- if (selectedRows && Neo.ns(me.selectedRecordField, false, record)) {
498
+ if (selectedRows && record[me.selectedRecordField]) {
477
499
  NeoArray.add(selectedRows, id)
478
500
  }
479
501
 
@@ -491,7 +513,6 @@ class GridView extends Component {
491
513
  cls : trCls,
492
514
  cn : [],
493
515
  role : 'row',
494
- tabIndex : '-1',
495
516
 
496
517
  style: {
497
518
  height : me.rowHeight + 'px',
@@ -657,27 +678,28 @@ class GridView extends Component {
657
678
 
658
679
  /**
659
680
  * @param {String} rowId
660
- * @returns {Object}
681
+ * @returns {Record}
661
682
  */
662
683
  getRecordByRowId(rowId) {
663
- return this.store.getAt(this.recordVnodeMap[rowId])
684
+ let recordId = rowId.split('__')[2],
685
+ {store} = this,
686
+ {model} = store,
687
+ keyField = model?.getField(store.getKeyProperty()),
688
+ keyType = keyField?.type?.toLowerCase();
689
+
690
+ if (keyType === 'int' || keyType === 'integer') {
691
+ recordId = parseInt(recordId)
692
+ }
693
+
694
+ return store.get(recordId)
664
695
  }
665
696
 
666
697
  /**
667
698
  * @param {Object} record
668
- * @param {Number} [index]
669
699
  * @returns {String}
670
700
  */
671
- getRowId(record, index) {
672
- let me = this,
673
- {store} = me;
674
-
675
- if (me.useRowRecordIds) {
676
- return `${me.id}__tr__${record[store.keyProperty]}`
677
- } else {
678
- index = Neo.isNumber(index) ? index : store.indexOf(record);
679
- return me.vdom.cn[index]?.id || Neo.getId('tr')
680
- }
701
+ getRowId(record) {
702
+ return `${this.id}__tr__${record[this.store.getKeyProperty()]}`
681
703
  }
682
704
 
683
705
  /**
@@ -727,6 +749,14 @@ class GridView extends Component {
727
749
  this.fireCellEvent(data, 'cellDoubleClick')
728
750
  }
729
751
 
752
+ /**
753
+ *
754
+ */
755
+ onConstructed() {
756
+ super.onConstructed();
757
+ this.selectionModel?.register(this)
758
+ }
759
+
730
760
  /**
731
761
  * @param {Object} data
732
762
  */
@@ -768,7 +798,7 @@ class GridView extends Component {
768
798
  * @param {Neo.data.Model} opts.model The model instance of the changed record
769
799
  * @param {Object} opts.record
770
800
  */
771
- onStoreRecordChange({fields, model, record}) {
801
+ onStoreRecordChange({fields, record}) {
772
802
  let me = this,
773
803
  fieldNames = fields.map(field => field.name),
774
804
  needsUpdate = false,
@@ -426,9 +426,9 @@ class Button extends BaseButton {
426
426
  if (store) {
427
427
  filter = store.getFilter(me.dataField);
428
428
  model = store.model;
429
- field = model && model.getField(me.dataField);
429
+ field = model.getField(me.dataField);
430
430
 
431
- if (value && field.type.toLowerCase() === 'date') {
431
+ if (value && field?.type.toLowerCase() === 'date') {
432
432
  value = new Date(value)
433
433
  }
434
434
 
@@ -106,7 +106,8 @@ class Toolbar extends BaseToolbar {
106
106
  *
107
107
  */
108
108
  createItems() {
109
- let me = this;
109
+ let me = this,
110
+ {mounted} = me;
110
111
 
111
112
  me.itemDefaults.showHeaderFilter = me.showHeaderFilters;
112
113
 
@@ -130,7 +131,8 @@ class Toolbar extends BaseToolbar {
130
131
  });
131
132
 
132
133
  me.promiseUpdate().then(() => {
133
- me.mounted && me.passSizeToView()
134
+ // To prevent duplicate calls, we need to check the mounted state before the update call
135
+ mounted && me.passSizeToView()
134
136
  })
135
137
  }
136
138