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.
- package/apps/ServiceWorker.mjs +2 -2
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/table/nestedRecordFields/MainContainer.mjs +5 -1
- package/examples/table/nestedRecordFields/MainModel.mjs +16 -3
- package/examples/table/nestedRecordFields/MainStore.mjs +1 -1
- package/package.json +1 -1
- package/resources/scss/src/table/View.scss +18 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/data/RecordFactory.mjs +108 -70
- package/src/form/field/ComboBox.mjs +4 -7
- package/src/list/Base.mjs +27 -7
- package/src/table/View.mjs +10 -0
package/apps/ServiceWorker.mjs
CHANGED
package/apps/portal/index.html
CHANGED
@@ -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
|
-
|
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.
|
10
|
+
className : 'Neo.examples.table.nestedRecordFields.MainStore',
|
11
11
|
keyProperty: 'githubId',
|
12
12
|
model : Model,
|
13
13
|
|
package/package.json
CHANGED
@@ -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);
|
package/src/DefaultConfig.mjs
CHANGED
@@ -262,12 +262,12 @@ const DefaultConfig = {
|
|
262
262
|
useVdomWorker: true,
|
263
263
|
/**
|
264
264
|
* buildScripts/injectPackageVersion.mjs will update this value
|
265
|
-
* @default '8.
|
265
|
+
* @default '8.7.0'
|
266
266
|
* @memberOf! module:Neo
|
267
267
|
* @name config.version
|
268
268
|
* @type String
|
269
269
|
*/
|
270
|
-
version: '8.
|
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
|
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 =
|
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
|
137
|
-
|
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
|
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,
|
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
|
187
|
-
|
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
|
-
|
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) {
|
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 {
|
326
|
-
* @param {
|
327
|
-
* @param {
|
328
|
-
* @param {
|
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[
|
363
|
+
ns = Neo.ns(nsArray, true, record[scope]);
|
338
364
|
|
339
365
|
ns[fieldName] = value
|
340
366
|
} else {
|
341
|
-
record[
|
367
|
+
record[scope][fieldName] = value
|
342
368
|
}
|
343
369
|
}
|
344
370
|
|
345
371
|
/**
|
346
|
-
* @param {
|
347
|
-
* @param {Object}
|
348
|
-
* @param {Object}
|
349
|
-
* @param {
|
350
|
-
* @param {Object
|
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,
|
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(
|
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
|
-
|
424
|
-
|
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
|
-
|
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);
|
package/src/table/View.mjs
CHANGED
@@ -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) {
|