neo.mjs 8.6.1 → 8.7.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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.6.1'
23
+ * @member {String} version='8.7.0'
24
24
  */
25
- version: '8.6.1'
25
+ version: '8.7.0'
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.1'
110
+ html : 'v8.7.0'
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.1'
23
+ * @member {String} version='8.7.0'
24
24
  */
25
- version: '8.6.1'
25
+ version: '8.7.0'
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.1",
3
+ "version": "8.7.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -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.1'
265
+ * @default '8.7.0'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '8.6.1'
270
+ version: '8.7.0'
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
  */
@@ -70,10 +66,9 @@ class RecordFactory extends Base {
70
66
  value = instance.parseRecordValue(me, field, value);
71
67
 
72
68
  if (!Neo.isEqual(value, oldValue)) {
73
- instance.setRecordData(fieldPath, model, me, value);
69
+ instance.setRecordData({fieldName: fieldPath, model, record: me, value});
74
70
 
75
- me._isModified = true;
76
- me._isModified = instance.isModified(me, model.trackModifiedFields);
71
+ me._isModified = me.isModified;
77
72
 
78
73
  instance.onRecordChange({
79
74
  fields: [{name: fieldPath, oldValue, value}],
@@ -85,13 +80,6 @@ class RecordFactory extends Base {
85
80
  }
86
81
  };
87
82
 
88
- // adding the original value of each field
89
- if (model.trackModifiedFields) {
90
- properties[instance.ovPrefix + field.name] = {
91
- value
92
- }
93
- }
94
-
95
83
  Object.defineProperties(me, properties)
96
84
  }
97
85
  }
@@ -132,9 +120,61 @@ class RecordFactory extends Base {
132
120
 
133
121
  [dataSymbol] = {}
134
122
 
123
+ get isModified() {
124
+ let me = this;
125
+
126
+ if (model.trackModifiedFields) {
127
+ return Neo.isEqual(me[dataSymbol], me[originalDataSymbol])
128
+ }
129
+
130
+ return me._isModified
131
+ }
132
+
133
+ /**
134
+ * @param {Object} config
135
+ */
135
136
  constructor(config) {
136
- this.setSilent(config);
137
- this._isModified = false
137
+ let me = this;
138
+
139
+ if (model.trackModifiedFields) {
140
+ me[originalDataSymbol] = {};
141
+ me.setOriginal(config)
142
+ }
143
+
144
+ me.setSilent(config); // We do not want to fire change events when constructing
145
+ me._isModified = false
146
+ }
147
+
148
+ /**
149
+ * @param {String} fieldName
150
+ * @returns {Boolean|null} null in case the model does not use trackModifiedFields, true in case a change was found
151
+ */
152
+ isModifiedField(fieldName) {
153
+ let me = this;
154
+
155
+ // Check if the field getter does exist
156
+ if (!me.__proto__.hasOwnProperty(fieldName)) {
157
+ Logger.logError('The record does not contain the field', fieldName, me)
158
+ }
159
+
160
+ if (model.trackModifiedFields) {
161
+ let dataScope, originalDataScope;
162
+
163
+ if (model.hasNestedFields && fieldName.includes('.')) {
164
+ let nsArray = fieldName.split('.');
165
+
166
+ fieldName = nsArray.pop();
167
+ dataScope = Neo.ns(nsArray, false, me[dataSymbol]);
168
+ originalDataScope = Neo.ns(nsArray, false, me[originalDataSymbol])
169
+ } else {
170
+ dataScope = me[dataSymbol];
171
+ originalDataScope = me[originalDataSymbol]
172
+ }
173
+
174
+ return !Neo.isEqual(dataScope[fieldName], originalDataScope[fieldName])
175
+ }
176
+
177
+ return null
138
178
  }
139
179
 
140
180
  /**
@@ -142,7 +182,17 @@ class RecordFactory extends Base {
142
182
  * @param {Object} fields
143
183
  */
144
184
  set(fields) {
145
- instance.setRecordFields(model, this, fields)
185
+ instance.setRecordFields({fields, model, record: this})
186
+ }
187
+
188
+ /**
189
+ * If the model uses trackModifiedFields, we will store the original data
190
+ * for tracking the dirty state (changed fields)
191
+ * @param {Object} fields
192
+ * @protected
193
+ */
194
+ setOriginal(fields) {
195
+ instance.setRecordFields({fields, model, record: this, silent: true, useOriginalData: true})
146
196
  }
147
197
 
148
198
  /**
@@ -150,7 +200,7 @@ class RecordFactory extends Base {
150
200
  * @param {Object} fields
151
201
  */
152
202
  setSilent(fields) {
153
- instance.setRecordFields(model, this, fields, true)
203
+ instance.setRecordFields({fields, model, record: this, silent: true})
154
204
  }
155
205
 
156
206
  /**
@@ -180,28 +230,10 @@ class RecordFactory extends Base {
180
230
 
181
231
  /**
182
232
  * @param {Object} record
183
- * @param {Boolean} trackModifiedFields
184
233
  * @returns {Boolean} true in case a change was found
185
234
  */
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
235
+ isModified(record) {
236
+ return record.isModified
205
237
  }
206
238
 
207
239
  /**
@@ -210,17 +242,7 @@ class RecordFactory extends Base {
210
242
  * @returns {Boolean|null} null in case the model does not use trackModifiedFields, true in case a change was found
211
243
  */
212
244
  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
245
+ return record.isModifiedField(fieldName)
224
246
  }
225
247
 
226
248
  /**
@@ -229,7 +251,7 @@ class RecordFactory extends Base {
229
251
  * @returns {Boolean}
230
252
  */
231
253
  isRecord(record) {
232
- return record?.isRecord
254
+ return record?.isRecord || false
233
255
  }
234
256
 
235
257
  /**
@@ -252,7 +274,7 @@ class RecordFactory extends Base {
252
274
  * @param {Object} recordConfig=null
253
275
  * @returns {*}
254
276
  */
255
- parseRecordValue(record, field, value, recordConfig=null) {!field && console.log(record, value);
277
+ parseRecordValue(record, field, value, recordConfig=null) {
256
278
  if (field.calculate) {
257
279
  return field.calculate(record, field, recordConfig)
258
280
  }
@@ -322,34 +344,40 @@ class RecordFactory extends Base {
322
344
  }
323
345
 
324
346
  /**
325
- * @param {String} fieldName
326
- * @param {Neo.data.Model} model
327
- * @param {Record} record
328
- * @param {*} value
347
+ * @param {Object} data
348
+ * @param {String} data.fieldName
349
+ * @param {Neo.data.Model} data.model
350
+ * @param {Record} data.record
351
+ * @param {Boolean} data.useOriginalData=false true will apply changes to the originalData symbol
352
+ * @param {*} data.value
329
353
  * @protected
330
354
  */
331
- setRecordData(fieldName, model, record, value) {
355
+ setRecordData({fieldName, model, record, useOriginalData=false, value}) {
356
+ let scope = useOriginalData ? originalDataSymbol : dataSymbol;
357
+
332
358
  if (model.hasNestedFields && fieldName.includes('.')) {
333
359
  let ns, nsArray;
334
360
 
335
361
  nsArray = fieldName.split('.');
336
362
  fieldName = nsArray.pop();
337
- ns = Neo.ns(nsArray, true, record[dataSymbol]);
363
+ ns = Neo.ns(nsArray, true, record[scope]);
338
364
 
339
365
  ns[fieldName] = value
340
366
  } else {
341
- record[dataSymbol][fieldName] = value
367
+ record[scope][fieldName] = value
342
368
  }
343
369
  }
344
370
 
345
371
  /**
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
372
+ * @param {Object} data
373
+ * @param {Object[]} data.changedFields=[] Internal flag
374
+ * @param {Object} data.fields
375
+ * @param {Neo.data.Model} data.model
376
+ * @param {Object} data.record
377
+ * @param {Boolean} data.silent=false
378
+ * @param {Boolean} data.useOriginalData=false true will apply changes to the originalData symbol
351
379
  */
352
- setRecordFields(model, record, fields, silent=false, changedFields=[]) {
380
+ setRecordFields({changedFields=[], fields, model, record, silent=false, useOriginalData=false}) {
353
381
  let {fieldsMap} = model,
354
382
  fieldExists, oldValue;
355
383
 
@@ -358,16 +386,26 @@ class RecordFactory extends Base {
358
386
 
359
387
  if (Neo.isObject(value) && !fieldExists) {
360
388
  Object.entries(value).forEach(([childKey, childValue]) => {
361
- this.setRecordFields(model, record, {[`${key}.${childKey}`]: childValue}, true, changedFields)
389
+ this.setRecordFields({
390
+ changedFields,
391
+ fields: {[`${key}.${childKey}`]: childValue},
392
+ model,
393
+ record,
394
+ silent: true,
395
+ useOriginalData
396
+ })
362
397
  })
363
398
  } else if (fieldExists) {
364
399
  oldValue = record[key];
365
400
  value = instance.parseRecordValue(record, model.getField(key), value);
366
401
 
367
402
  if (!Neo.isEqual(oldValue, value)) {
368
- instance.setRecordData(key, model, record, value);
403
+ instance.setRecordData({fieldName: key, model, record, useOriginalData, value});
404
+
405
+ if (!useOriginalData) {
406
+ record._isModified = true
407
+ }
369
408
 
370
- record._isModified = true;
371
409
  changedFields.push({name: key, oldValue, value})
372
410
  }
373
411
  }
@@ -410,7 +410,8 @@ class ComboBox extends Picker {
410
410
 
411
411
  // List might not exist until the picker is created
412
412
  let {list} = me,
413
- {selectionModel} = list;
413
+ {selectionModel} = list,
414
+ index = store.indexOf(record);
414
415
 
415
416
  // On show, set the active item to be the current selected record or the first
416
417
  if (record) {
@@ -420,12 +421,8 @@ class ComboBox extends Picker {
420
421
  selectionModel.suspendEvents = false
421
422
  }
422
423
 
423
- me.timeout(100).then(() => {
424
- let index = store.indexOf(record);
425
-
426
- list._focusIndex = -1; // silent update to ensure afterSetFocusIndex() always gets called
427
- list.focusIndex = index > -1 ? index : 0
428
- })
424
+ list._focusIndex = -1; // silent update to ensure afterSetFocusIndex() always gets called
425
+ list.focusIndex = index > -1 ? index : 0
429
426
  }
430
427
  // Filtered down to nothing - hide picker if it has been created.
431
428
  else {
package/src/list/Base.mjs CHANGED
@@ -256,13 +256,7 @@ class List extends Component {
256
256
  * @protected
257
257
  */
258
258
  afterSetFocusIndex(value, oldValue) {
259
- let me = this;
260
-
261
- if (Neo.isNumber(value)) {
262
- Neo.main.addon.Navigator.navigateTo([me.getHeaderlessIndex(value), me.navigator])
263
- } else if (value) {
264
- Neo.main.addon.Navigator.navigateTo([me.getItemId(value[me.getKeyProperty()]), me.navigator])
265
- }
259
+ value !== null && this.updateItemFocus(value)
266
260
  }
267
261
 
268
262
  /**
@@ -846,6 +840,32 @@ class List extends Component {
846
840
  }
847
841
  }
848
842
  }
843
+
844
+ /**
845
+ *
846
+ * @param {Number|Object} value
847
+ * @returns {Promise<void>}
848
+ */
849
+ async updateItemFocus(value) {
850
+ let me = this,
851
+ {navigateTo} = Neo.main.addon.Navigator;
852
+
853
+ if (me.mounted) {
854
+ if (Neo.isNumber(value)) {
855
+ navigateTo([me.getHeaderlessIndex(value), me.navigator])
856
+ } else if (value) {
857
+ navigateTo([me.getItemId(value[me.getKeyProperty()]), me.navigator])
858
+ }
859
+ } else {
860
+ me.on('mounted', () => {
861
+ // We could subscribe multiple times before getting mounted,
862
+ // so only trigger the callback for the last focusIndex
863
+ if (value === me.focusIndex) {
864
+ me.updateItemFocus(me.focusIndex)
865
+ }
866
+ }, me, {once: true})
867
+ }
868
+ }
849
869
  }
850
870
 
851
871
  export default Neo.setupClass(List);
@@ -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) {