neo.mjs 5.14.0 → 5.14.2

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='5.14.0'
23
+ * @member {String} version='5.14.2'
24
24
  */
25
- version: '5.14.0'
25
+ version: '5.14.2'
26
26
  }
27
27
 
28
28
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='5.14.0'
23
+ * @member {String} version='5.14.2'
24
24
  */
25
- version: '5.14.0'
25
+ version: '5.14.2'
26
26
  }
27
27
 
28
28
  /**
@@ -191,6 +191,7 @@ class MainContainer extends ConfigurationViewport {
191
191
  iconCls: 'fa fa-home',
192
192
  text : 'Item 1'
193
193
  }, {
194
+ cls : ['neo-red'],
194
195
  handler: data => console.log('menu item 2 click =>', data),
195
196
  iconCls: 'fa fa-user',
196
197
  text : 'Item 2'
@@ -50,8 +50,8 @@ class MainContainerController extends ComponentController {
50
50
 
51
51
  output.update();
52
52
 
53
- await Neo.timeout(20)
54
- me.syntaxHighlight();
53
+ await me.timeout(20);
54
+ me.syntaxHighlight()
55
55
  }
56
56
  }
57
57
 
@@ -50,8 +50,8 @@ class MainContainerController extends ComponentController {
50
50
 
51
51
  output.update();
52
52
 
53
- await Neo.timeout(20)
54
- me.syntaxHighlight();
53
+ await me.timeout(20);
54
+ me.syntaxHighlight()
55
55
  }
56
56
  }
57
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "5.14.0",
3
+ "version": "5.14.2",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,7 @@
1
+ .neo-menu-list {
2
+ .neo-list-item {
3
+ &.neo-red {
4
+ color: red;
5
+ }
6
+ }
7
+ }
@@ -245,12 +245,12 @@ const DefaultConfig = {
245
245
  useVdomWorker: true,
246
246
  /**
247
247
  * buildScripts/injectPackageVersion.mjs will update this value
248
- * @default '5.14.0'
248
+ * @default '5.14.2'
249
249
  * @memberOf! module:Neo
250
250
  * @name config.version
251
251
  * @type String
252
252
  */
253
- version: '5.14.0'
253
+ version: '5.14.2'
254
254
  };
255
255
 
256
256
  Object.assign(DefaultConfig, {
@@ -82,6 +82,10 @@ class Base extends Component {
82
82
  * @member {Object[]|null} menu_=null
83
83
  */
84
84
  menu_: null,
85
+ /**
86
+ * @member {Object} menuListConfig=null
87
+ */
88
+ menuListConfig: null,
85
89
  /**
86
90
  * The pressed state of the Button
87
91
  * @member {Boolean} pressed_=false
@@ -258,7 +262,8 @@ class Base extends Component {
258
262
  hidden : true,
259
263
  items : value,
260
264
  parentComponent: me,
261
- style : {left: '-5000px', top: '-5000px'}
265
+ style : {left: '-5000px', top: '-5000px'},
266
+ ...me.menuListConfig
262
267
  })
263
268
  })
264
269
  }
@@ -476,7 +481,7 @@ class Base extends Component {
476
481
  delete rippleWrapper.removeDom;
477
482
  me.update();
478
483
 
479
- await Neo.timeout(1);
484
+ await me.timeout(1);
480
485
 
481
486
  rippleEl.style.animation = `ripple ${rippleEffectDuration}ms linear`;
482
487
  me.update();
@@ -502,7 +507,7 @@ class Base extends Component {
502
507
  menuList.hidden = hidden;
503
508
 
504
509
  if (!hidden) {
505
- await Neo.timeout(50);
510
+ await this.timeout(50);
506
511
  menuList.focus()
507
512
  }
508
513
  }
@@ -452,7 +452,7 @@ class Component extends BaseComponent {
452
452
  me.needsEventUpdate = false;
453
453
  }
454
454
 
455
- await Neo.timeout(70);
455
+ await me.timeout(70);
456
456
 
457
457
  rect = await me.getDomRect(me.getColumnContainer().id);
458
458
 
@@ -1479,7 +1479,7 @@ class Base extends CoreBase {
1479
1479
 
1480
1480
  delete me.vdom.removeDom;
1481
1481
 
1482
- await Neo.timeout(30);
1482
+ await me.timeout(30);
1483
1483
 
1484
1484
  me.mounted = true;
1485
1485
  }
@@ -53,18 +53,19 @@ class AccordionContainer extends Base {
53
53
  * @protected
54
54
  */
55
55
  createItems() {
56
- const me = this,
57
- items = me.items,
58
- title = me.title;
59
- let iconCls = me.iconCls || ['no-icon'],
56
+ let me = this,
60
57
  arrowCls = me.arrowCls || 'fa-caret-down',
58
+ iconCls = me.iconCls || ['no-icon'],
59
+ items = me.items,
60
+ title = me.title,
61
61
  header, content;
62
62
 
63
63
  if (!Neo.isArray(iconCls)) {
64
- iconCls = iconCls.split(' ');
64
+ iconCls = iconCls.split(' ')
65
65
  }
66
+
66
67
  if (!Neo.isArray(arrowCls)) {
67
- arrowCls = arrowCls.split(' ');
68
+ arrowCls = arrowCls.split(' ')
68
69
  }
69
70
 
70
71
  header = Neo.create({
@@ -85,6 +86,7 @@ class AccordionContainer extends Base {
85
86
  cls : ['fa', ...arrowCls]
86
87
  }]
87
88
  });
89
+
88
90
  content = {
89
91
  ntype : 'container',
90
92
  flag : 'content',
@@ -98,7 +100,7 @@ class AccordionContainer extends Base {
98
100
 
99
101
  me.addDomListeners([
100
102
  {click: me.onExpandClick, delegate: 'neo-accordion-header-arrow'}
101
- ]);
103
+ ])
102
104
  }
103
105
 
104
106
  /**
@@ -106,15 +108,15 @@ class AccordionContainer extends Base {
106
108
  * @param {Boolean} isExpanded
107
109
  */
108
110
  afterSetExpanded(isExpanded) {
109
- const me = this,
110
- cls = me.cls,
111
- fn = isExpanded ? 'add' : 'remove';
111
+ let me = this,
112
+ cls = me.cls,
113
+ fn = isExpanded ? 'add' : 'remove';
112
114
 
113
115
  NeoArray[fn](cls, 'neo-expanded');
114
116
  me.cls = cls;
115
117
 
116
118
  // Ensure scrollbars are not flipping in and out
117
- Neo.timeout(450).then(() => {
119
+ me.timeout(450).then(() => {
118
120
  NeoArray[fn](cls, 'neo-scrollable');
119
121
  me.cls = cls;
120
122
  })
@@ -133,7 +135,9 @@ class AccordionContainer extends Base {
133
135
  if (iconEl) {
134
136
  let cls = iconEl.cls;
135
137
 
136
- if (!Neo.isArray(newValue)) newValue = newValue.split(' ');
138
+ if (!Neo.isArray(newValue)) {
139
+ newValue = newValue.split(' ')
140
+ }
137
141
 
138
142
  NeoArray.remove(cls, oldValue);
139
143
  NeoArray.add(cls, newValue);
@@ -158,9 +162,9 @@ class AccordionContainer extends Base {
158
162
  * Otherwise we set this.expanded to the new value.
159
163
  */
160
164
  onExpandClick() {
161
- const me = this,
162
- currentState = me.expanded;
163
- let parent = me.up('accordion');
165
+ let me = this,
166
+ currentState = me.expanded,
167
+ parent = me.up('accordion');
164
168
 
165
169
  if (parent.ntype === 'accordion') {
166
170
  parent.childExpandChange({
@@ -93,7 +93,7 @@ class Dialog extends Base {
93
93
 
94
94
  iconNode.removeDom = !value || value === '';
95
95
  this.update();
96
- }
96
+ }
97
97
 
98
98
  /**
99
99
  * Triggered after the title config got changed
@@ -104,7 +104,7 @@ class Dialog extends Base {
104
104
  afterSetTitle(value, oldValue) {
105
105
  this.headerToolbar?.down({flag: 'panel-header-title'}).set({
106
106
  text: value
107
- });
107
+ })
108
108
  }
109
109
 
110
110
  /**
@@ -149,7 +149,7 @@ class Dialog extends Base {
149
149
  hidden: !me.title,
150
150
  text : me.title
151
151
  }, ...me.headerConfig.items || []],
152
-
152
+
153
153
  ...headerConfigCopy
154
154
  });
155
155
 
@@ -190,11 +190,12 @@ class Dialog extends Base {
190
190
  */
191
191
  async show(modal = true) {
192
192
  let me = this;
193
- await Neo.timeout(20);
193
+
194
+ await me.timeout(20);
194
195
 
195
196
  Neo.main.addon.Dialog[modal ? 'showModal': 'show']({
196
- id: me.id,
197
- appName: me.appName
197
+ appName: me.appName,
198
+ id : me.id
198
199
  });
199
200
  }
200
201
  }
@@ -88,9 +88,9 @@ class Application extends Base {
88
88
  let me = this;
89
89
 
90
90
  // short delay to ensure changes from onHashChange() got applied
91
- await Neo.timeout(Neo.config.hash ? 200 : 10);
91
+ await me.timeout(Neo.config.hash ? 200 : 10);
92
92
 
93
- value.on('mounted', me.registerLoggerClickEvent, me);
93
+ Logger.addContextMenuListener(me.mainView);
94
94
 
95
95
  value.render(true)
96
96
  }
@@ -122,13 +122,6 @@ class Application extends Base {
122
122
  Neo.currentWorker.removeAppFromThemeMap(this.name);
123
123
  super.destroy(...args)
124
124
  }
125
-
126
- /**
127
- * @protected
128
- */
129
- registerLoggerClickEvent() {
130
- Logger.addContextMenuListener(this.mainView)
131
- }
132
125
  }
133
126
 
134
127
  Neo.applyClassConfig(Application);
package/src/core/Base.mjs CHANGED
@@ -79,6 +79,13 @@ class Base {
79
79
  module: null
80
80
  }
81
81
 
82
+ /**
83
+ * Internal cache for all timeout ids when using this.timeout()
84
+ * @member {Number[]} timeoutIds=[]
85
+ * @private
86
+ */
87
+ #timeoutIds = []
88
+
82
89
  /**
83
90
  * Applies the observable mixin if needed, grants remote access if needed.
84
91
  * @param {Object} config={}
@@ -240,6 +247,10 @@ class Base {
240
247
  destroy() {
241
248
  let me = this;
242
249
 
250
+ me.#timeoutIds.forEach(id => {
251
+ clearTimeout(id)
252
+ });
253
+
243
254
  if (Base.instanceManagerAvailable === true) {
244
255
  Neo.manager.Instance.unregister(me)
245
256
  } else if (Neo.idMap) {
@@ -503,6 +514,20 @@ class Base {
503
514
  return false
504
515
  }
505
516
 
517
+ /**
518
+ * Stores timeoutIds internally, so that destroy() can clear them if needed
519
+ * @param {Number} time in milliseconds
520
+ * @returns {Promise<any>}
521
+ */
522
+ timeout(time) {
523
+ return new Promise(resolve => {
524
+ let timeoutIds = this.#timeoutIds,
525
+ timeoutId = setTimeout(() => {timeoutIds.splice(timeoutIds.indexOf(timeoutId, 1)); resolve()}, time);
526
+
527
+ timeoutIds.push(timeoutId)
528
+ })
529
+ }
530
+
506
531
  /**
507
532
  * <p>Enhancing the toString() method, e.g.</p>
508
533
  * `Neo.create('Neo.button.Base').toString() => "[object Neo.button.Base (neo-button-1)]"`
package/src/core/Util.mjs CHANGED
@@ -199,14 +199,6 @@ class Util extends Base {
199
199
  return typeof value === 'string';
200
200
  }
201
201
 
202
- /**
203
- * @param {Number} time in milliseconds
204
- * @returns {Promise<unknown>}
205
- */
206
- static timeout(time) {
207
- return new Promise(resolve => setTimeout(resolve, time));
208
- }
209
-
210
202
  /**
211
203
  * Converts any iterable (strings, numeric indices and a length property) into a true array
212
204
  * @param {Object|String} iterable
@@ -246,7 +238,6 @@ Neo.applyFromNs(Neo, Util, {
246
238
  isNumber : 'isNumber',
247
239
  isObject : 'isObject',
248
240
  isString : 'isString',
249
- timeout : 'timeout',
250
241
  toArray : 'toArray'
251
242
  }, true);
252
243
 
@@ -298,7 +298,7 @@ class Base extends Panel {
298
298
 
299
299
  me.closeOrHide(false);
300
300
 
301
- await Neo.timeout(30);
301
+ await me.timeout(30);
302
302
 
303
303
  await Neo.currentWorker.promiseMessage('main', {
304
304
  action: 'updateDom',
@@ -314,7 +314,7 @@ class Base extends Panel {
314
314
  }]
315
315
  });
316
316
 
317
- await Neo.timeout(250);
317
+ await me.timeout(250);
318
318
 
319
319
  await Neo.currentWorker.promiseMessage('main', {
320
320
  action: 'updateDom',
@@ -340,7 +340,7 @@ class Base extends Panel {
340
340
  parentId: 'document.body'
341
341
  });
342
342
 
343
- await Neo.timeout(30);
343
+ await me.timeout(30);
344
344
 
345
345
  await Neo.currentWorker.promiseMessage('main', {
346
346
  action: 'updateDom',
@@ -357,9 +357,9 @@ class Base extends Panel {
357
357
  }]
358
358
  });
359
359
 
360
- await Neo.timeout(200);
360
+ await me.timeout(200);
361
361
 
362
- me.show(false);
362
+ me.show(false)
363
363
  }
364
364
 
365
365
  /**
@@ -82,14 +82,14 @@ class SortZone extends DragZone {
82
82
  * @param {Object} data
83
83
  */
84
84
  async onDragEnd(data) {
85
- await Neo.timeout(10);
86
-
87
85
  let me = this,
88
86
  owner = me.owner,
89
87
  itemStyles = me.itemStyles,
90
88
  ownerStyle = owner.style || {},
91
89
  itemStyle;
92
90
 
91
+ await me.timeout(10);
92
+
93
93
  if (owner.sortable) {
94
94
  ownerStyle.height = me.ownerStyle.height || null;
95
95
  ownerStyle.width = me.ownerStyle.width || null;
@@ -127,7 +127,7 @@ class SortZone extends DragZone {
127
127
  startIndex : -1
128
128
  });
129
129
 
130
- await Neo.timeout(30);
130
+ await me.timeout(30);
131
131
 
132
132
  me.dragEnd(data); // we do not want to trigger the super class call here
133
133
  }
@@ -268,7 +268,7 @@ class Picker extends Text {
268
268
  picker = me.getPicker();
269
269
 
270
270
  // avoid breaking selection model cls updates
271
- await Neo.timeout(30);
271
+ await me.timeout(30);
272
272
 
273
273
  if (me.pickerIsMounted) {
274
274
  picker.unmount();
package/src/list/Base.mjs CHANGED
@@ -383,6 +383,10 @@ class Base extends Component {
383
383
  }
384
384
  }
385
385
 
386
+ if (record.cls) {
387
+ NeoArray.add(cls, record.cls)
388
+ }
389
+
386
390
  if (record[me.disabledField]) {
387
391
  cls.push('neo-disabled')
388
392
  }
@@ -74,7 +74,7 @@ class ScrollSync extends Base {
74
74
  */
75
75
  async register(data) {
76
76
  // short delay to ensure the target node got mounted
77
- await Neo.timeout(50)
77
+ await this.timeout(50)
78
78
 
79
79
  let sourceId = data.sourceId,
80
80
  sourceMap = this.sourceMap,
@@ -19,6 +19,9 @@ class Model extends BaseModel {
19
19
  * @member {Object[]} fields
20
20
  */
21
21
  fields: [{
22
+ name: 'cls',
23
+ type: 'Array'
24
+ }, {
22
25
  name: 'handler',
23
26
  type: 'Function'
24
27
  }, {
@@ -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
 
@@ -18,7 +18,7 @@ class Logger extends Base {
18
18
  *
19
19
  * Neo.util.Logger.enableLogsInProduction = true;
20
20
  *
21
- * @member {boolean} enableLogsInProduction=true
21
+ * @member {Boolean} enableLogsInProduction=true
22
22
  */
23
23
  enableLogsInProduction: false,
24
24
  /**
@@ -27,9 +27,9 @@ class Logger extends Base {
27
27
  *
28
28
  * Neo.util.Logger.enableComponentLogger = true;
29
29
  *
30
- * @member {boolean} enableComponentLogger_=true
30
+ * @member {Boolean} enableComponentLogger=true
31
31
  */
32
- enableComponentLogger_: true,
32
+ enableComponentLogger: true,
33
33
  /**
34
34
  * Set the minimum level, which you want to output.
35
35
  * Change this at any time using a value of logLevels: ['info', 'log', 'warn', 'error']
@@ -101,7 +101,8 @@ class Logger extends Base {
101
101
  */
102
102
  addContextMenuListener(view) {
103
103
  view.addDomListeners({
104
- contextmenu: this.onContextMenu
104
+ contextmenu: this.onContextMenu,
105
+ scope : this
105
106
  })
106
107
  }
107
108
 
@@ -184,7 +185,11 @@ class Logger extends Base {
184
185
  * @param {Object} data
185
186
  */
186
187
  onContextMenu(data) {
187
- if (data.ctrlKey) {
188
+ if (
189
+ data.ctrlKey
190
+ && this.enableComponentLogger
191
+ && !(Neo.config.env === 'dist/production' && this.enableLogsInProduction)
192
+ ) {
188
193
  let isGroupSet = false,
189
194
  component;
190
195
 
@@ -20,6 +20,17 @@ class App extends Base {
20
20
  * @protected
21
21
  */
22
22
  className: 'Neo.worker.App',
23
+ /**
24
+ * Remote method access for other workers
25
+ * @member {Object} remote
26
+ * @protected
27
+ */
28
+ remote: {
29
+ main: [
30
+ 'createNeoInstance',
31
+ 'destroyNeoInstance'
32
+ ]
33
+ },
23
34
  /**
24
35
  * @member {Boolean} singleton=true
25
36
  * @protected
@@ -72,6 +83,62 @@ class App extends Base {
72
83
  return this.promiseMessage('main', {action: 'updateDom', appName, deltas})
73
84
  }
74
85
 
86
+ /**
87
+ * Remote method to use inside main threads for creating neo based class instances.
88
+ * Be aware that you can only pass configs which can get converted into pure JSON.
89
+ *
90
+ * Rendering a component into the document.body
91
+ * @example:
92
+ * Neo.worker.App.createNeoInstance({
93
+ * ntype : 'button',
94
+ * autoMount : true,
95
+ * autoRender: true
96
+ * text : 'Hi Nige!'
97
+ * }).then(id => console.log(id))
98
+ *
99
+ * Inserting a component into a container
100
+ * @example:
101
+ * Neo.worker.App.createNeoInstance({
102
+ * ntype : 'button',
103
+ * parentId : 'neo-container-3',
104
+ * parentIndex: 0
105
+ * text : 'Hi Nige!'
106
+ * }).then(id => console.log(id))
107
+ *
108
+ * @param {Object} config
109
+ * @param {String} [config.parentId] passing a parentId will put your instance into a container
110
+ * @param {Number} [config.parentIndex] if a parentId is passed, but no index, neo will use add()
111
+ * @returns {String} the instance id
112
+ */
113
+ createNeoInstance(config) {
114
+ let appName = Object.keys(Neo.apps)[0], // fallback in case no appName was provided
115
+ Container = Neo.container?.Base,
116
+ index, instance, parent;
117
+
118
+ config = {appName: appName, ...config};
119
+
120
+ if (config.parentId) {
121
+ parent = Neo.getComponent(config.parentId);
122
+
123
+ if (Container && parent && parent instanceof Container) {
124
+ index = config.parentIndex;
125
+
126
+ delete config.parentId;
127
+ delete config.parentIndex;
128
+
129
+ if (Neo.isNumber(index)) {
130
+ instance = parent.insert(index, config)
131
+ } else {
132
+ instance = parent.add(config)
133
+ }
134
+ }
135
+ } else {
136
+ instance = Neo[config.ntype ? 'ntype' : 'create'](config)
137
+ }
138
+
139
+ return instance.id
140
+ }
141
+
75
142
  /**
76
143
  * @param {Object} data
77
144
  */
@@ -81,6 +148,36 @@ class App extends Base {
81
148
  this.resolveThemeFilesCache()
82
149
  }
83
150
 
151
+ /**
152
+ * Remote method to use inside main threads for destroying neo based class instances.
153
+ *
154
+ * @example:
155
+ * Neo.worker.App.destroyNeoInstance('neo-button-3').then(success => console.log(success))
156
+ *
157
+ * @param {String} id
158
+ * @returns {Boolean} returns true, in case the instance was found
159
+ */
160
+ destroyNeoInstance(id) {
161
+ let instance = Neo.get(id),
162
+ parent;
163
+
164
+ if (instance) {
165
+ if (instance.parentId) {
166
+ parent = Neo.getComponent(instance.parentId);
167
+
168
+ if (parent) {
169
+ parent.remove(instance);
170
+ return true
171
+ }
172
+ }
173
+
174
+ instance.destroy(true, true);
175
+ return true
176
+ }
177
+
178
+ return false
179
+ }
180
+
84
181
  /**
85
182
  * Only needed for the SharedWorkers context
86
183
  * @param {String} eventName