neo.mjs 5.13.10 → 5.14.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.
@@ -44,6 +44,27 @@ class Component extends Base {
44
44
  * @member {Object|null} data_=null
45
45
  */
46
46
  data_: null,
47
+ /**
48
+ * @member {Object|null} formulas_=null
49
+ *
50
+ * @example
51
+ * data: {
52
+ * a: 1,
53
+ * b: 2
54
+ * }
55
+ * formulas: {
56
+ * aPlusB: {
57
+ * bind: {
58
+ * foo: 'a',
59
+ * bar: 'b'
60
+ * },
61
+ * get(data) {
62
+ * return data.foo + data.bar
63
+ * }
64
+ * }
65
+ * }
66
+ */
67
+ formulas_: null,
47
68
  /**
48
69
  * @member {Neo.model.Component|null} parent_=null
49
70
  */
@@ -60,7 +81,7 @@ class Component extends Base {
60
81
  construct(config) {
61
82
  Neo.currentWorker.isUsingViewModels = true;
62
83
  super.construct(config);
63
- this.bindings = {};
84
+ this.bindings = {}
64
85
  }
65
86
 
66
87
  /**
@@ -77,12 +98,12 @@ class Component extends Base {
77
98
 
78
99
  Neo.ns(key, true, me.data);
79
100
 
80
- data = me.getDataScope(key);
101
+ data = me.getDataScope(key);
81
102
  scope = data.scope;
82
103
 
83
104
  scope[data.key] = value;
84
105
 
85
- me.createDataProperties(me.data, 'data');
106
+ me.createDataProperties(me.data, 'data')
86
107
  }
87
108
 
88
109
  /**
@@ -92,7 +113,17 @@ class Component extends Base {
92
113
  * @protected
93
114
  */
94
115
  afterSetData(value, oldValue) {
95
- value && this.createDataProperties(value, 'data');
116
+ value && this.createDataProperties(value, 'data')
117
+ }
118
+
119
+ /**
120
+ * Triggered after the formulas config got changed
121
+ * @param {Object|null} value
122
+ * @param {Object|null} oldValue
123
+ * @protected
124
+ */
125
+ afterSetFormulas(value, oldValue) {
126
+ value && this.resolveFormulas(null)
96
127
  }
97
128
 
98
129
  /**
@@ -101,7 +132,7 @@ class Component extends Base {
101
132
  * @protected
102
133
  */
103
134
  beforeGetData(value) {
104
- return value || {};
135
+ return value || {}
105
136
  }
106
137
 
107
138
  /**
@@ -111,7 +142,7 @@ class Component extends Base {
111
142
  * @protected
112
143
  */
113
144
  beforeSetParent(value, oldValue) {
114
- return value ? value : this.getParent();
145
+ return value ? value : this.getParent()
115
146
  }
116
147
 
117
148
  /**
@@ -126,23 +157,23 @@ class Component extends Base {
126
157
 
127
158
  value && Object.entries(value).forEach(([key, storeValue]) => {
128
159
  controller?.parseConfig(storeValue);
129
- value[key] = ClassSystemUtil.beforeSetInstance(storeValue);
160
+ value[key] = ClassSystemUtil.beforeSetInstance(storeValue)
130
161
  });
131
162
 
132
- return value;
163
+ return value
133
164
  }
134
165
 
135
166
  /**
136
167
  * @param {Function} formatter
137
- * @param {Object} [data=null] optionally pass this.getHierarchyData() for performance reasons
168
+ * @param {Object} data=null optionally pass this.getHierarchyData() for performance reasons
138
169
  * @returns {String}
139
170
  */
140
171
  callFormatter(formatter, data=null) {
141
172
  if (!data) {
142
- data = this.getHierarchyData();
173
+ data = this.getHierarchyData()
143
174
  }
144
175
 
145
- return formatter.call(this, data);
176
+ return formatter.call(this, data)
146
177
  }
147
178
 
148
179
  /**
@@ -162,21 +193,21 @@ class Component extends Base {
162
193
 
163
194
  if (scope?.hasOwnProperty(keyLeaf)) {
164
195
  bindingScope = Neo.ns(`${key}.${componentId}`, true, me.bindings);
165
- bindingScope[value] = formatter;
196
+ bindingScope[value] = formatter
166
197
  } else {
167
198
  parentModel = me.getParent();
168
199
 
169
200
  if (parentModel) {
170
- parentModel.createBinding(componentId, key, value, formatter);
201
+ parentModel.createBinding(componentId, key, value, formatter)
171
202
  } else {
172
- console.error('No model.Component found with the specified data property', componentId, keyLeaf, value);
203
+ console.error('No model.Component found with the specified data property', componentId, keyLeaf, value)
173
204
  }
174
205
  }
175
206
  }
176
207
 
177
208
  /**
178
209
  * Registers a new binding in case a matching data property does exist.
179
- * Otherwise it will use the closest model with a match.
210
+ * Otherwise, it will use the closest model with a match.
180
211
  * @param {String} componentId
181
212
  * @param {String} formatter
182
213
  * @param {String} value
@@ -186,8 +217,8 @@ class Component extends Base {
186
217
  formatterVars = me.getFormatterVariables(formatter);
187
218
 
188
219
  formatterVars.forEach(key => {
189
- me.createBinding(componentId, key, value, formatter);
190
- });
220
+ me.createBinding(componentId, key, value, formatter)
221
+ })
191
222
  }
192
223
 
193
224
  /**
@@ -196,13 +227,13 @@ class Component extends Base {
196
227
  createBindings(component) {
197
228
  Object.entries(component.bind).forEach(([key, value]) => {
198
229
  if (Neo.isObject(value)) {
199
- value = value.value;
230
+ value = value.value
200
231
  }
201
232
 
202
233
  if (!this.isStoreValue(value)) {
203
- this.createBindingByFormatter(component.id, value, key);
234
+ this.createBindingByFormatter(component.id, value, key)
204
235
  }
205
- });
236
+ })
206
237
  }
207
238
 
208
239
  /**
@@ -222,31 +253,31 @@ class Component extends Base {
222
253
  if (!(typeof descriptor === 'object' && typeof descriptor.set === 'function')) {
223
254
  keyValue = config[key];
224
255
  me.createDataProperty(key, newPath, root);
225
- root[key] = keyValue;
256
+ root[key] = keyValue
226
257
  }
227
258
 
228
259
  if (Neo.isObject(value)) {
229
- me.createDataProperties(config[key], newPath);
260
+ me.createDataProperties(config[key], newPath)
230
261
  }
231
262
  }
232
- });
263
+ })
233
264
  }
234
265
 
235
266
  /**
236
267
  * @param {String} key
237
268
  * @param {String} path
238
- * @param {Object} [root=this.data]
269
+ * @param {Object} root=this.data
239
270
  */
240
271
  createDataProperty(key, path, root=this.data) {
241
272
  let me = this;
242
273
 
243
274
  if (path?.startsWith('data.')) {
244
- path = path.substring(5);
275
+ path = path.substring(5)
245
276
  }
246
277
 
247
278
  Object.defineProperty(root, key, {
248
279
  get() {
249
- return root['_' + key];
280
+ return root['_' + key]
250
281
  },
251
282
 
252
283
  set(value) {
@@ -258,16 +289,16 @@ class Component extends Base {
258
289
  enumerable: false,
259
290
  value,
260
291
  writable : true
261
- });
292
+ })
262
293
  } else {
263
- root[_key] = value;
294
+ root[_key] = value
264
295
  }
265
296
 
266
297
  if (!Neo.isEqual(value, oldValue)) {
267
- me.onDataPropertyChange(path ? path : key, value, oldValue);
298
+ me.onDataPropertyChange(path ? path : key, value, oldValue)
268
299
  }
269
300
  }
270
- });
301
+ })
271
302
  }
272
303
 
273
304
  /**
@@ -276,13 +307,13 @@ class Component extends Base {
276
307
  * @returns {Neo.controller.Component|null}
277
308
  */
278
309
  getController(ntype) {
279
- return this.component.getController(ntype);
310
+ return this.component.getController(ntype)
280
311
  }
281
312
 
282
313
  /**
283
314
  * Access the closest data property inside the VM parent chain.
284
315
  * @param {String} key
285
- * @param {Neo.model.Component} [originModel=this] for internal usage only
316
+ * @param {Neo.model.Component} originModel=this for internal usage only
286
317
  * @returns {*} value
287
318
  */
288
319
  getData(key, originModel=this) {
@@ -293,16 +324,16 @@ class Component extends Base {
293
324
  parentModel;
294
325
 
295
326
  if (scope?.hasOwnProperty(keyLeaf)) {
296
- return scope[keyLeaf];
327
+ return scope[keyLeaf]
297
328
  }
298
329
 
299
330
  parentModel = me.getParent();
300
331
 
301
332
  if (!parentModel) {
302
- console.error(`data property '${key}' does not exist.`, originModel);
333
+ console.error(`data property '${key}' does not exist.`, originModel)
303
334
  }
304
335
 
305
- return parentModel.getData(key, originModel);
336
+ return parentModel.getData(key, originModel)
306
337
  }
307
338
 
308
339
  /**
@@ -321,13 +352,13 @@ class Component extends Base {
321
352
  if (key.includes('.')) {
322
353
  key = key.split('.');
323
354
  keyLeaf = key.pop();
324
- data = Neo.ns(key.join('.'), false, data);
355
+ data = Neo.ns(key.join('.'), false, data)
325
356
  }
326
357
 
327
358
  return {
328
359
  key : keyLeaf,
329
360
  scope: data
330
- };
361
+ }
331
362
  }
332
363
 
333
364
  /**
@@ -336,7 +367,7 @@ class Component extends Base {
336
367
  */
337
368
  getFormatterVariables(value) {
338
369
  if (Neo.isFunction(value)) {
339
- value = value.toString();
370
+ value = value.toString()
340
371
  }
341
372
 
342
373
  if (Neo.config.environment === 'dist/production') {
@@ -353,7 +384,7 @@ class Component extends Base {
353
384
  let dataName = value.match(variableNameRegex)[0],
354
385
  variableRegExp = new RegExp(`(^|[^\\w.])(${dataName})(?!\\w)`, 'g');
355
386
 
356
- value = value.replace(variableRegExp, '$1data');
387
+ value = value.replace(variableRegExp, '$1data')
357
388
  }
358
389
 
359
390
  let dataVars = value.match(dataVariableRegex) || [],
@@ -362,12 +393,12 @@ class Component extends Base {
362
393
  dataVars.forEach(variable => {
363
394
  // remove the "data." at the start
364
395
  variable = variable.substr(5);
365
- NeoArray.add(result, variable);
396
+ NeoArray.add(result, variable)
366
397
  });
367
398
 
368
399
  result.sort();
369
400
 
370
- return result;
401
+ return result
371
402
  }
372
403
 
373
404
  /**
@@ -383,16 +414,16 @@ class Component extends Base {
383
414
  return {
384
415
  ...parent.getHierarchyData(data),
385
416
  ...me.getPlainData()
386
- };
417
+ }
387
418
  }
388
419
 
389
- return me.getPlainData();
420
+ return me.getPlainData()
390
421
  }
391
422
 
392
423
  /**
393
424
  * Returns a plain version of this.data.
394
425
  * This excludes the property getters & setters.
395
- * @param {Object} [data=this.data]
426
+ * @param {Object} data=this.data
396
427
  * @returns {Object}
397
428
  */
398
429
  getPlainData(data=this.data) {
@@ -400,13 +431,13 @@ class Component extends Base {
400
431
 
401
432
  Object.entries(data).forEach(([key, value]) => {
402
433
  if (Neo.typeOf(value) === 'Object') {
403
- plainData[key] = this.getPlainData(value);
434
+ plainData[key] = this.getPlainData(value)
404
435
  } else {
405
- plainData[key] = value;
436
+ plainData[key] = value
406
437
  }
407
438
  });
408
439
 
409
- return plainData;
440
+ return plainData
410
441
  }
411
442
 
412
443
  /**
@@ -418,19 +449,19 @@ class Component extends Base {
418
449
  parentComponent, parentId;
419
450
 
420
451
  if (me.parent) {
421
- return me.parent;
452
+ return me.parent
422
453
  }
423
454
 
424
- parentId = me.component.parentId;
455
+ parentId = me.component.parentId;
425
456
  parentComponent = parentId && Neo.getComponent(parentId);
426
457
 
427
- return parentComponent?.getModel() || null;
458
+ return parentComponent?.getModel() || null
428
459
  }
429
460
 
430
461
  /**
431
462
  * Access the closest store inside the VM parent chain.
432
463
  * @param {String} key
433
- * @param {Neo.model.Component} [originModel=this] for internal usage only
464
+ * @param {Neo.model.Component} originModel=this for internal usage only
434
465
  * @returns {*} value
435
466
  */
436
467
  getStore(key, originModel=this) {
@@ -439,16 +470,16 @@ class Component extends Base {
439
470
  parentModel;
440
471
 
441
472
  if (stores?.hasOwnProperty(key)) {
442
- return stores[key];
473
+ return stores[key]
443
474
  }
444
475
 
445
476
  parentModel = me.getParent();
446
477
 
447
478
  if (!parentModel) {
448
- console.error(`store '${key}' not found inside this model or parents.`, originModel);
479
+ console.error(`store '${key}' not found inside this model or parents.`, originModel)
449
480
  }
450
481
 
451
- return parentModel.getStore(key, originModel);
482
+ return parentModel.getStore(key, originModel)
452
483
  }
453
484
 
454
485
  /**
@@ -469,26 +500,26 @@ class Component extends Base {
469
500
 
470
501
  if (Neo.isObject(key)) {
471
502
  Object.entries(key).forEach(([dataKey, dataValue]) => {
472
- me.internalSetData(dataKey, dataValue, originModel);
473
- });
503
+ me.internalSetData(dataKey, dataValue, originModel)
504
+ })
474
505
  } else {
475
506
  data = me.getDataScope(key);
476
- scope = data.scope;
477
507
  keyLeaf = data.key;
508
+ scope = data.scope;
478
509
 
479
510
  if (scope?.hasOwnProperty(keyLeaf)) {
480
- scope[keyLeaf] = value;
511
+ scope[keyLeaf] = value
481
512
  } else {
482
513
  if (originModel) {
483
514
  parentModel = me.getParent();
484
515
 
485
516
  if (parentModel) {
486
- parentModel.internalSetData(key, value, originModel);
517
+ parentModel.internalSetData(key, value, originModel)
487
518
  } else {
488
- originModel.addDataProperty(key, value);
519
+ originModel.addDataProperty(key, value)
489
520
  }
490
521
  } else {
491
- me.addDataProperty(key, value);
522
+ me.addDataProperty(key, value)
492
523
  }
493
524
  }
494
525
  }
@@ -500,7 +531,7 @@ class Component extends Base {
500
531
  * @returns {Boolean}
501
532
  */
502
533
  isStoreValue(value) {
503
- return Neo.isString(value) && value.startsWith('stores.');
534
+ return Neo.isString(value) && value.startsWith('stores.')
504
535
  }
505
536
 
506
537
  /**
@@ -511,10 +542,10 @@ class Component extends Base {
511
542
  */
512
543
  mergeConfig(config, preventOriginalConfig) {
513
544
  if (config.data) {
514
- config.data = Neo.merge(Neo.clone(this.constructor.config.data, true) || {}, config.data);
545
+ config.data = Neo.merge(Neo.clone(this.constructor.config.data, true) || {}, config.data)
515
546
  }
516
547
 
517
- return super.mergeConfig(config, preventOriginalConfig);
548
+ return super.mergeConfig(config, preventOriginalConfig)
518
549
  }
519
550
 
520
551
  /**
@@ -532,35 +563,32 @@ class Component extends Base {
532
563
 
533
564
  Object.entries(binding).forEach(([componentId, configObject]) => {
534
565
  component = Neo.getComponent(componentId) || Neo.get(componentId); // timing issue: the cmp might not be registered inside manager.Component yet
535
- config = {};
536
- model = component.getModel();
566
+ config = {};
567
+ model = component.getModel();
537
568
 
538
569
  if (!hierarchyData[model.id]) {
539
- hierarchyData[model.id] = model.getHierarchyData();
570
+ hierarchyData[model.id] = model.getHierarchyData()
540
571
  }
541
572
 
542
573
  Object.entries(configObject).forEach(([configField, formatter]) => {
543
574
  // we can not call me.callFormatter(), since a data property inside a parent model
544
575
  // could have changed which is relying on data properties inside a closer model
545
- config[configField] = model.callFormatter(formatter, hierarchyData[model.id]);
576
+ config[configField] = model.callFormatter(formatter, hierarchyData[model.id])
546
577
  });
547
578
 
548
- component?.set(config);
549
- });
579
+ component?.set(config)
580
+ })
550
581
  }
551
582
 
552
- me.fire('dataPropertyChange', {
553
- key,
554
- id: me.id,
555
- oldValue,
556
- value
557
- });
583
+ me.resolveFormulas({key, id: me.id, oldValue, value});
584
+
585
+ me.fire('dataPropertyChange', {key, id: me.id, oldValue, value})
558
586
  }
559
587
 
560
588
  /**
561
589
  * This method will assign binding values at the earliest possible point inside the component lifecycle.
562
590
  * It can not store bindings though, since child component ids most likely do not exist yet.
563
- * @param {Neo.component.Base} [component=this.component]
591
+ * @param {Neo.component.Base} component=this.component
564
592
  */
565
593
  parseConfig(component=this.component) {
566
594
  let me = this,
@@ -572,17 +600,17 @@ class Component extends Base {
572
600
  Object.entries(component.bind).forEach(([key, value]) => {
573
601
  if (Neo.isObject(value)) {
574
602
  value.key = me.getFormatterVariables(value.value)[0];
575
- value = value.value;
603
+ value = value.value
576
604
  }
577
605
 
578
606
  if (me.isStoreValue(value)) {
579
- me.resolveStore(component, key, value.substring(7)); // remove the "stores." at the start
607
+ me.resolveStore(component, key, value.substring(7)) // remove the "stores." at the start
580
608
  } else {
581
- config[key] = me.callFormatter(value);
609
+ config[key] = me.callFormatter(value)
582
610
  }
583
611
  });
584
612
 
585
- component.set(config);
613
+ component.set(config)
586
614
  }
587
615
  }
588
616
 
@@ -596,10 +624,56 @@ class Component extends Base {
596
624
  parentModel = me.getParent();
597
625
 
598
626
  Object.entries(me.bindings).forEach(([dataProperty, binding]) => {
599
- delete binding[componentId];
627
+ delete binding[componentId]
600
628
  });
601
629
 
602
- parentModel?.removeBindings(componentId);
630
+ parentModel?.removeBindings(componentId)
631
+ }
632
+
633
+ /**
634
+ * Resolve the formulas initially and update, when data change
635
+ * @param {Object} data data from event or null on initial call
636
+ */
637
+ resolveFormulas(data) {
638
+ let me = this,
639
+ formulas = me.formulas,
640
+ initialRun = !data,
641
+ affectFormula, bindObject, fn, key, result, value;
642
+
643
+ if (formulas) {
644
+ if (!initialRun && (!data.key || !data.value)) {
645
+ console.warn('[ViewModel:formulas] missing key or value', data.key, data.value)
646
+ }
647
+
648
+ for ([key, value] of Object.entries(formulas)) {
649
+ affectFormula = true;
650
+
651
+ // Check if the change affects a formula
652
+ if (!initialRun) {
653
+ affectFormula = Object.values(value.bind).includes(data.key)
654
+ }
655
+
656
+ if (affectFormula) {
657
+ // Create Bind-Object and fill with new values
658
+ bindObject = Neo.clone(value.bind);
659
+ fn = value.get;
660
+
661
+ Object.keys(bindObject).forEach((key, index) => {
662
+ bindObject[key] = me.getData(bindObject[key])
663
+ });
664
+
665
+ // Calc the formula
666
+ result = fn(bindObject);
667
+
668
+ // Assign if no error or null
669
+ if (isNaN(result)) {
670
+ me.setData(key, null)
671
+ } else {
672
+ me.setData(key, result)
673
+ }
674
+ }
675
+ }
676
+ }
603
677
  }
604
678
 
605
679
  /**
@@ -608,7 +682,7 @@ class Component extends Base {
608
682
  * @param {String} storeName
609
683
  */
610
684
  resolveStore(component, configName, storeName) {
611
- component[configName] = this.getStore(storeName);
685
+ component[configName] = this.getStore(storeName)
612
686
  }
613
687
 
614
688
  /**
@@ -618,7 +692,7 @@ class Component extends Base {
618
692
  * @param {*} value
619
693
  */
620
694
  setData(key, value) {
621
- this.internalSetData(key, value, this);
695
+ this.internalSetData(key, value, this)
622
696
  }
623
697
 
624
698
  /**
@@ -628,7 +702,7 @@ class Component extends Base {
628
702
  * @param {*} value
629
703
  */
630
704
  setDataAtSameLevel(key, value) {
631
- this.internalSetData(key, value);
705
+ this.internalSetData(key, value)
632
706
  }
633
707
  }
634
708