neo.mjs 8.6.2 → 8.7.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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.6.2'
23
+ * @member {String} version='8.7.1'
24
24
  */
25
- version: '8.6.2'
25
+ version: '8.7.1'
26
26
  }
27
27
 
28
28
  /**
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-01-21",
19
+ "datePublished": "2025-01-23",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- html : 'v8.6.2'
110
+ html : 'v8.7.1'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.6.2'
23
+ * @member {String} version='8.7.1'
24
24
  */
25
- version: '8.6.2'
25
+ version: '8.7.1'
26
26
  }
27
27
 
28
28
  /**
@@ -39,7 +39,11 @@ class MainContainer extends Viewport {
39
39
  {dataField: 'githubId', text: 'Github Id'},
40
40
  {dataField: 'country', text: 'Country', renderer: 'up.countryRenderer'},
41
41
  {dataField: 'edit', text: 'Edit Action', renderer: 'up.editRenderer'}
42
- ]
42
+ ],
43
+
44
+ viewConfig: {
45
+ highlightModifiedCells: true
46
+ }
43
47
  }]
44
48
  }
45
49
 
@@ -6,8 +6,14 @@ import Model from '../../../src/data/Model.mjs';
6
6
  */
7
7
  class MainModel extends Model {
8
8
  static config = {
9
- className: 'Neo.examples.table.container.MainModel',
10
-
9
+ /**
10
+ * @member {String} className='Neo.examples.table.nestedRecordFields.MainModel'
11
+ * @protected
12
+ */
13
+ className: 'Neo.examples.table.nestedRecordFields.MainModel',
14
+ /**
15
+ * @member {Object[]} fields
16
+ */
11
17
  fields: [{
12
18
  name: 'annotations',
13
19
  type: 'Object',
@@ -20,6 +26,9 @@ class MainModel extends Model {
20
26
  }, {
21
27
  name: 'country',
22
28
  type: 'String'
29
+ }, {
30
+ name: 'edit',
31
+ type: 'String'
23
32
  }, {
24
33
  name: 'githubId',
25
34
  type: 'String'
@@ -34,7 +43,11 @@ class MainModel extends Model {
34
43
  name: 'lastname',
35
44
  type: 'String'
36
45
  }]
37
- }]
46
+ }],
47
+ /**
48
+ * @member {Boolean} trackModifiedFields=true
49
+ */
50
+ trackModifiedFields: true
38
51
  }
39
52
  }
40
53
 
@@ -7,7 +7,7 @@ import Store from '../../../src/data/Store.mjs';
7
7
  */
8
8
  class MainStore extends Store {
9
9
  static config = {
10
- className : 'Neo.examples.table.container.MainStore',
10
+ className : 'Neo.examples.table.nestedRecordFields.MainStore',
11
11
  keyProperty: 'githubId',
12
12
  model : Model,
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.6.2",
3
+ "version": "8.7.1",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -77,6 +77,10 @@
77
77
  margin-right: 0.3em;
78
78
  padding : 0;
79
79
 
80
+ &:active {
81
+ border: none !important;
82
+ }
83
+
80
84
  &:last-child {
81
85
  margin-right: 0;
82
86
  }
@@ -8,6 +8,24 @@
8
8
  }
9
9
 
10
10
  .neo-table-row {
11
+ .neo-table-cell {
12
+ position: relative;
13
+
14
+ &.neo-is-modified {
15
+ &:after {
16
+ border-color: transparent orange transparent transparent;
17
+ border-style: solid;
18
+ border-width: 0 10px 10px 0;
19
+ content : '';
20
+ height : 0;
21
+ position : absolute;
22
+ right : 0;
23
+ top : -1px;
24
+ width : 0;
25
+ }
26
+ }
27
+ }
28
+
11
29
  &:hover {
12
30
  .neo-table-cell {
13
31
  background-color: var(--table-cell-background-color-hover);
@@ -262,12 +262,12 @@ const DefaultConfig = {
262
262
  useVdomWorker: true,
263
263
  /**
264
264
  * buildScripts/injectPackageVersion.mjs will update this value
265
- * @default '8.6.2'
265
+ * @default '8.7.1'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '8.6.2'
270
+ version: '8.7.1'
271
271
  };
272
272
 
273
273
  Object.assign(DefaultConfig, {
@@ -2,7 +2,9 @@ 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');
5
+ const
6
+ dataSymbol = Symbol.for('data'),
7
+ originalDataSymbol = Symbol.for('originalData');
6
8
 
7
9
  let instance;
8
10
 
@@ -18,12 +20,6 @@ class RecordFactory extends Base {
18
20
  * @protected
19
21
  */
20
22
  className: 'Neo.data.RecordFactory',
21
- /**
22
- * The internal record prefix for original field values.
23
- * Only used in case the model has trackModifiedFields set to true.
24
- * @member {String} ovPrefix='ov_'
25
- */
26
- ovPrefix: 'ov_',
27
23
  /**
28
24
  * @member {String} recordNamespace='Neo.data.record'
29
25
  */
@@ -35,6 +31,37 @@ class RecordFactory extends Base {
35
31
  singleton: true
36
32
  }
37
33
 
34
+ /**
35
+ * Assigns model based default values to a data object
36
+ * @param {Object} data
37
+ * @param {Record} record
38
+ * @param {Neo.data.Model} model
39
+ * @returns {Object}
40
+ */
41
+ assignDefaultValues(data, record, model) {
42
+ let {hasNestedFields} = model,
43
+ scope;
44
+
45
+ model.fieldsMap.forEach((field, fieldName) => {
46
+ if (Object.hasOwn(field, 'defaultValue')) {
47
+ if (hasNestedFields && fieldName.includes('.')) {
48
+ let nsArray = fieldName.split('.');
49
+
50
+ fieldName = nsArray.pop();
51
+ scope = Neo.ns(nsArray, true, data)
52
+ } else {
53
+ scope = data
54
+ }
55
+
56
+ if (scope[fieldName] === undefined) {
57
+ scope[fieldName] = field.defaultValue
58
+ }
59
+ }
60
+ });
61
+
62
+ return data
63
+ }
64
+
38
65
  /**
39
66
  * @param {Object} data
40
67
  * @param {Object} data.field
@@ -70,10 +97,9 @@ class RecordFactory extends Base {
70
97
  value = instance.parseRecordValue(me, field, value);
71
98
 
72
99
  if (!Neo.isEqual(value, oldValue)) {
73
- instance.setRecordData(fieldPath, model, me, value);
100
+ instance.setRecordData({fieldName: fieldPath, model, record: me, value});
74
101
 
75
- me._isModified = true;
76
- me._isModified = instance.isModified(me, model.trackModifiedFields);
102
+ me._isModified = me.isModified;
77
103
 
78
104
  instance.onRecordChange({
79
105
  fields: [{name: fieldPath, oldValue, value}],
@@ -85,13 +111,6 @@ class RecordFactory extends Base {
85
111
  }
86
112
  };
87
113
 
88
- // adding the original value of each field
89
- if (model.trackModifiedFields) {
90
- properties[instance.ovPrefix + field.name] = {
91
- value
92
- }
93
- }
94
-
95
114
  Object.defineProperties(me, properties)
96
115
  }
97
116
  }
@@ -132,9 +151,63 @@ class RecordFactory extends Base {
132
151
 
133
152
  [dataSymbol] = {}
134
153
 
154
+ get isModified() {
155
+ let me = this;
156
+
157
+ if (model.trackModifiedFields) {
158
+ return Neo.isEqual(me[dataSymbol], me[originalDataSymbol])
159
+ }
160
+
161
+ return me._isModified
162
+ }
163
+
164
+ /**
165
+ * @param {Object} config
166
+ */
135
167
  constructor(config) {
136
- this.setSilent(config);
137
- this._isModified = false
168
+ let me = this;
169
+
170
+ config = instance.assignDefaultValues(config, me, model);
171
+
172
+ if (model.trackModifiedFields) {
173
+ me[originalDataSymbol] = {};
174
+ me.setOriginal(config)
175
+ }
176
+
177
+ me.setSilent(config); // We do not want to fire change events when constructing
178
+ me._isModified = false
179
+ }
180
+
181
+ /**
182
+ * @param {String} fieldName
183
+ * @returns {Boolean|null} null in case the model does not use trackModifiedFields, true in case a change was found
184
+ */
185
+ isModifiedField(fieldName) {
186
+ let me = this;
187
+
188
+ // Check if the field getter does exist
189
+ if (!me.__proto__.hasOwnProperty(fieldName)) {
190
+ Logger.logError('The record does not contain the field', fieldName, me)
191
+ }
192
+
193
+ if (model.trackModifiedFields) {
194
+ let dataScope, originalDataScope;
195
+
196
+ if (model.hasNestedFields && fieldName.includes('.')) {
197
+ let nsArray = fieldName.split('.');
198
+
199
+ fieldName = nsArray.pop();
200
+ dataScope = Neo.ns(nsArray, false, me[dataSymbol]);
201
+ originalDataScope = Neo.ns(nsArray, false, me[originalDataSymbol])
202
+ } else {
203
+ dataScope = me[dataSymbol];
204
+ originalDataScope = me[originalDataSymbol]
205
+ }
206
+
207
+ return !Neo.isEqual(dataScope[fieldName], originalDataScope[fieldName])
208
+ }
209
+
210
+ return null
138
211
  }
139
212
 
140
213
  /**
@@ -142,7 +215,17 @@ class RecordFactory extends Base {
142
215
  * @param {Object} fields
143
216
  */
144
217
  set(fields) {
145
- instance.setRecordFields(model, this, fields)
218
+ instance.setRecordFields({fields, model, record: this})
219
+ }
220
+
221
+ /**
222
+ * If the model uses trackModifiedFields, we will store the original data
223
+ * for tracking the dirty state (changed fields)
224
+ * @param {Object} fields
225
+ * @protected
226
+ */
227
+ setOriginal(fields) {
228
+ instance.setRecordFields({fields, model, record: this, silent: true, useOriginalData: true})
146
229
  }
147
230
 
148
231
  /**
@@ -150,7 +233,7 @@ class RecordFactory extends Base {
150
233
  * @param {Object} fields
151
234
  */
152
235
  setSilent(fields) {
153
- instance.setRecordFields(model, this, fields, true)
236
+ instance.setRecordFields({fields, model, record: this, silent: true})
154
237
  }
155
238
 
156
239
  /**
@@ -180,28 +263,10 @@ class RecordFactory extends Base {
180
263
 
181
264
  /**
182
265
  * @param {Object} record
183
- * @param {Boolean} trackModifiedFields
184
266
  * @returns {Boolean} true in case a change was found
185
267
  */
186
- isModified(record, trackModifiedFields) {
187
- if (trackModifiedFields) {
188
- let fields = Object.keys(record),
189
- i = 0,
190
- len = fields.length,
191
- field;
192
-
193
- for (; i < len; i++) {
194
- field = fields[i];
195
-
196
- if (!Neo.isEqual(record[field], record[this.ovPrefix + field])) {
197
- return true
198
- }
199
- }
200
-
201
- return false
202
- }
203
-
204
- return record._isModified
268
+ isModified(record) {
269
+ return record.isModified
205
270
  }
206
271
 
207
272
  /**
@@ -210,17 +275,7 @@ class RecordFactory extends Base {
210
275
  * @returns {Boolean|null} null in case the model does not use trackModifiedFields, true in case a change was found
211
276
  */
212
277
  isModifiedField(record, fieldName) {
213
- if (!record.hasOwnProperty(fieldName)) {
214
- Logger.logError('The record does not contain the field', fieldName, record)
215
- }
216
-
217
- let modifiedField = this.ovPrefix + fieldName;
218
-
219
- if (record.hasOwnProperty(modifiedField)) {
220
- return !Neo.isEqual(record[fieldName], record[modifiedField])
221
- }
222
-
223
- return null
278
+ return record.isModifiedField(fieldName)
224
279
  }
225
280
 
226
281
  /**
@@ -229,7 +284,7 @@ class RecordFactory extends Base {
229
284
  * @returns {Boolean}
230
285
  */
231
286
  isRecord(record) {
232
- return record?.isRecord
287
+ return record?.isRecord || false
233
288
  }
234
289
 
235
290
  /**
@@ -252,7 +307,7 @@ class RecordFactory extends Base {
252
307
  * @param {Object} recordConfig=null
253
308
  * @returns {*}
254
309
  */
255
- parseRecordValue(record, field, value, recordConfig=null) {!field && console.log(record, value);
310
+ parseRecordValue(record, field, value, recordConfig=null) {
256
311
  if (field.calculate) {
257
312
  return field.calculate(record, field, recordConfig)
258
313
  }
@@ -322,34 +377,40 @@ class RecordFactory extends Base {
322
377
  }
323
378
 
324
379
  /**
325
- * @param {String} fieldName
326
- * @param {Neo.data.Model} model
327
- * @param {Record} record
328
- * @param {*} value
380
+ * @param {Object} data
381
+ * @param {String} data.fieldName
382
+ * @param {Neo.data.Model} data.model
383
+ * @param {Record} data.record
384
+ * @param {Boolean} data.useOriginalData=false true will apply changes to the originalData symbol
385
+ * @param {*} data.value
329
386
  * @protected
330
387
  */
331
- setRecordData(fieldName, model, record, value) {
388
+ setRecordData({fieldName, model, record, useOriginalData=false, value}) {
389
+ let scope = useOriginalData ? originalDataSymbol : dataSymbol;
390
+
332
391
  if (model.hasNestedFields && fieldName.includes('.')) {
333
392
  let ns, nsArray;
334
393
 
335
394
  nsArray = fieldName.split('.');
336
395
  fieldName = nsArray.pop();
337
- ns = Neo.ns(nsArray, true, record[dataSymbol]);
396
+ ns = Neo.ns(nsArray, true, record[scope]);
338
397
 
339
398
  ns[fieldName] = value
340
399
  } else {
341
- record[dataSymbol][fieldName] = value
400
+ record[scope][fieldName] = value
342
401
  }
343
402
  }
344
403
 
345
404
  /**
346
- * @param {Neo.data.Model} model
347
- * @param {Object} record
348
- * @param {Object} fields
349
- * @param {Boolean} silent=false
350
- * @param {Object[]} changedFields=[] Internal flag
405
+ * @param {Object} data
406
+ * @param {Object[]} data.changedFields=[] Internal flag
407
+ * @param {Object} data.fields
408
+ * @param {Neo.data.Model} data.model
409
+ * @param {Object} data.record
410
+ * @param {Boolean} data.silent=false
411
+ * @param {Boolean} data.useOriginalData=false true will apply changes to the originalData symbol
351
412
  */
352
- setRecordFields(model, record, fields, silent=false, changedFields=[]) {
413
+ setRecordFields({changedFields=[], fields, model, record, silent=false, useOriginalData=false}) {
353
414
  let {fieldsMap} = model,
354
415
  fieldExists, oldValue;
355
416
 
@@ -358,16 +419,26 @@ class RecordFactory extends Base {
358
419
 
359
420
  if (Neo.isObject(value) && !fieldExists) {
360
421
  Object.entries(value).forEach(([childKey, childValue]) => {
361
- this.setRecordFields(model, record, {[`${key}.${childKey}`]: childValue}, true, changedFields)
422
+ this.setRecordFields({
423
+ changedFields,
424
+ fields: {[`${key}.${childKey}`]: childValue},
425
+ model,
426
+ record,
427
+ silent: true,
428
+ useOriginalData
429
+ })
362
430
  })
363
431
  } else if (fieldExists) {
364
432
  oldValue = record[key];
365
433
  value = instance.parseRecordValue(record, model.getField(key), value);
366
434
 
367
435
  if (!Neo.isEqual(oldValue, value)) {
368
- instance.setRecordData(key, model, record, value);
436
+ instance.setRecordData({fieldName: key, model, record, useOriginalData, value});
437
+
438
+ if (!useOriginalData) {
439
+ record._isModified = true
440
+ }
369
441
 
370
- record._isModified = true;
371
442
  changedFields.push({name: key, oldValue, value})
372
443
  }
373
444
  }
@@ -413,8 +413,7 @@ class Dialog extends Panel {
413
413
 
414
414
  // rendered outside the visible area
415
415
  await me.render(true);
416
-
417
- await me.timeout(30);
416
+ await me.timeout(150);
418
417
 
419
418
  let [dialogRect, bodyRect] = await me.getDomRect([me.id, 'document.body']);
420
419
 
@@ -32,6 +32,10 @@ class View extends Component {
32
32
  * @protected
33
33
  */
34
34
  containerId: null,
35
+ /**
36
+ * @member {Boolean} highlightModifiedCells_=false
37
+ */
38
+ highlightModifiedCells_: false,
35
39
  /**
36
40
  * @member {Object} recordVnodeMap={}
37
41
  */
@@ -150,6 +154,12 @@ class View extends Component {
150
154
  cellCls.push('neo-' + column.cellAlign)
151
155
  }
152
156
 
157
+ if (me.highlightModifiedCells && me.store.model.trackModifiedFields) {
158
+ if (record.isModifiedField(dataField)) {
159
+ cellCls.push('neo-is-modified')
160
+ }
161
+ }
162
+
153
163
  if (!cellId) {
154
164
  // todo: remove the else part as soon as all tables use stores (examples table)
155
165
  if (hasStore) {