neo.mjs 4.0.64 → 4.0.67

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/README.md CHANGED
@@ -236,12 +236,12 @@ More infos: <a href="./BACKERS.md">Sponsors & Backers</a>
236
236
  </br></br>
237
237
  <h2 id="jobs">14. Jobs</h2>
238
238
  Accenture is hiring multiple neo.mjs developers for the new Cloud Technology Studio in Kaiserslauern (Germany):</br>
239
- <a href="https://www.accenture.com/de-de/careers/jobdetails?id=R00057924_de">Senior neo.mjs Frontend Developer /Architect (all genders)</a></br></br>
240
-
239
+ </br>
241
240
  These full-time roles are based on German contracts, so they require living in (or relocating to) Germany.
241
+ Ping us on LinkedIn or Slack for details.
242
242
 
243
243
  </br></br>
244
- Logo contributed by <a href="https://www.linkedin.com/in/dinkheller/">Torsten Dinkheller</a>.
244
+ Logo contributed by <a href="https://www.linkedin.com/in/torsten-dinkheller-614516231/">Torsten Dinkheller</a>.
245
245
 
246
246
  </br></br>
247
247
  Build with :heart: in Germany.
@@ -360,6 +360,8 @@ class MainContainerController extends ComponentController {
360
360
  me.mainTabs.splice(1, 0, 'mapboxglmap');
361
361
  break;
362
362
  }
363
+
364
+ Neo.apps[name].destroy();
363
365
  }
364
366
  }
365
367
 
@@ -14,7 +14,7 @@ const
14
14
  cwd = process.cwd(),
15
15
  requireJson = path => JSON.parse(fs.readFileSync((path))),
16
16
  packageJson = requireJson(path.join(__dirname, 'package.json')),
17
- insideNeo = packageJson.name === 'neo.mjs',
17
+ insideNeo = process.env.npm_package_name === 'neo.mjs',
18
18
  program = new Command(),
19
19
  programName = `${packageJson.name} create-class`,
20
20
  questions = [],
@@ -36,6 +36,7 @@ program
36
36
  .version(packageJson.version)
37
37
  .option('-i, --info', 'print environment debug info')
38
38
  .option('-d, --drop', 'drops class in the currently selected folder')
39
+ .option('-n, --singleton <value>', 'Create a singleton? Pick "yes" or "no"')
39
40
  .option('-s, --source <value>', `name of the folder containing the project. Defaults to any of ${sourceRootDirs.join(',')}`)
40
41
  .option('-b, --baseClass <value>')
41
42
  .option('-c, --className <value>')
@@ -103,17 +104,38 @@ if (programOpts.info) {
103
104
  type : 'list',
104
105
  name : 'baseClass',
105
106
  message: 'Please pick the base class, which you want to extend:',
106
- choices: ['component.Base', 'container.Base', 'controller.Component', 'core.Base', 'model.Component'],
107
- default: 'container.Base'
107
+ default: 'container.Base',
108
+
109
+ choices: [
110
+ 'component.Base',
111
+ 'container.Base',
112
+ 'controller.Component',
113
+ 'core.Base',
114
+ 'data.Model',
115
+ 'data.Store',
116
+ 'model.Component'
117
+ ]
118
+ });
119
+ }
120
+
121
+ if (!programOpts.singleton) {
122
+ questions.push({
123
+ type : 'list',
124
+ name : 'singleton',
125
+ message: 'Singleton?',
126
+ default: 'no',
127
+ choices: ['yes', 'no']
108
128
  });
109
129
  }
110
130
 
111
131
  inquirer.prompt(questions).then(answers => {
112
- let baseClass = programOpts.baseClass || answers.baseClass,
113
- className = programOpts.className || answers.className,
114
- isDrop = programOpts.drop,
115
- startDate = new Date(),
116
- baseType, classFolder, configName, file, folderDelta, index, ns, root, rootLowerCase, viewFile;
132
+ let baseClass = programOpts.baseClass || answers.baseClass,
133
+ className = programOpts.className || answers.className,
134
+ singleton = programOpts.singleton || answers.singleton || 'no',
135
+ isDrop = programOpts.drop,
136
+ isSingleton = singleton === 'yes',
137
+ startDate = new Date(),
138
+ baseFileName, baseType, classFolder, configName, file, folderDelta, importName, importPath, index, ns, root, rootLowerCase, viewFile;
117
139
 
118
140
  if (className.endsWith('.mjs')) {
119
141
  className = className.slice(0, -4);
@@ -184,8 +206,8 @@ if (programOpts.info) {
184
206
  }
185
207
 
186
208
  if (isDrop !== true) {
187
- if (fs.existsSync(path.resolve(__dirname, 'apps', rootLowerCase))) {
188
- classFolder = path.resolve(__dirname, 'apps', rootLowerCase, ns.join('/'));
209
+ if (fs.existsSync(path.resolve(cwd, 'apps', rootLowerCase))) {
210
+ classFolder = path.resolve(cwd, 'apps', rootLowerCase, ns.join('/'));
189
211
  } else {
190
212
  console.log('\nNon existing neo app name:', chalk.red(root));
191
213
  process.exit(1);
@@ -198,31 +220,86 @@ if (programOpts.info) {
198
220
 
199
221
  fs.mkdirpSync(classFolder);
200
222
 
201
- fs.writeFileSync(path.join(classFolder, file + '.mjs'), createContent({baseClass, className, file, folderDelta, ns, root}));
223
+ baseFileName = baseClass.split('.').pop();
224
+
225
+ if (baseFileName === file) {
226
+ baseFileName = baseClass.split('.');
227
+ baseFileName = baseFileName.map(e => capitalize(e)).join('');
228
+ }
229
+
230
+ fs.writeFileSync(path.join(classFolder, file + '.mjs'), createContent({
231
+ baseClass,
232
+ baseFileName,
233
+ className,
234
+ isSingleton,
235
+ file,
236
+ folderDelta,
237
+ ns,
238
+ root
239
+ }));
240
+
241
+ switch(baseClass) {
242
+ case 'controller.Component': {
243
+ baseType = 'Neo.controller.Component';
244
+ configName = 'controller';
245
+ importName = file;
246
+ importPath = `./${importName}.mjs`;
247
+ index = file.indexOf('Controller');
248
+
249
+ if (index > 0) {
250
+ viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
251
+
252
+ if (fs.existsSync(viewFile)) {
253
+ adjustView({baseType, configName, importName, importPath, viewFile});
254
+ }
255
+ }
256
+ break;
257
+ }
258
+
259
+ case 'data.Store': {
260
+ baseType = 'Neo.data.Model';
261
+ configName = 'model';
262
+ importName = className.replace('.store.', '.model.');
202
263
 
203
- if (baseClass === 'controller.Component') {
204
- baseType = 'Neo.controller.Component';
205
- configName = 'controller';
206
- index = file.indexOf('Controller');
264
+ if (importName.endsWith('ies')) {
265
+ importName.replace(new RegExp('ies$'), 'y')
266
+ } else {
267
+ importName = importName.slice(0, -1);
268
+ }
269
+
270
+ viewFile = importName.split('.');
271
+ viewFile.shift();
207
272
 
208
- if (index > 0) {
209
- viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
273
+ importPath = `../${viewFile.join('/')}.mjs`;
274
+ viewFile = path.join(classFolder, importPath);
210
275
 
276
+ // checking for the data.Model file
211
277
  if (fs.existsSync(viewFile)) {
212
- adjustView({baseType, configName, file, viewFile});
278
+ // adjusting the data.Store file
279
+ viewFile = path.join(classFolder, file + '.mjs');
280
+ importName = importName.split('.');
281
+ importName = importName.pop();
282
+
283
+ adjustView({baseType, configName, importName, importPath, viewFile});
213
284
  }
285
+ break;
214
286
  }
215
- } else if (baseClass === 'model.Component') {
216
- baseType = 'Neo.model.Component';
217
- configName = 'model';
218
- index = file.indexOf('Model');
219
287
 
220
- if (index > 0) {
221
- viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
288
+ case 'model.Component': {
289
+ baseType = 'Neo.model.Component';
290
+ configName = 'model';
291
+ importName = file;
292
+ importPath = `./${importName}.mjs`;
293
+ index = file.indexOf('Model');
222
294
 
223
- if (fs.existsSync(viewFile)) {
224
- adjustView({baseType, configName, file, viewFile});
295
+ if (index > 0) {
296
+ viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
297
+
298
+ if (fs.existsSync(viewFile)) {
299
+ adjustView({baseType, configName, importName, importPath, viewFile});
300
+ }
225
301
  }
302
+ break;
226
303
  }
227
304
  }
228
305
  }
@@ -274,19 +351,20 @@ if (programOpts.info) {
274
351
  * @param {Object} opts
275
352
  * @param {String} opts.baseType
276
353
  * @param {String} opts.configName
277
- * @param {String} opts.file
354
+ * @param {String} opts.importName
355
+ * @param {String} opts.importPath
278
356
  * @param {String} opts.viewFile
279
357
  */
280
358
  function adjustView(opts) {
281
359
  let baseType = opts.baseType,
282
360
  configName = opts.configName,
283
- file = opts.file,
361
+ importName = opts.importName,
284
362
  viewFile = opts.viewFile,
285
363
  content = fs.readFileSync(viewFile).toString().split(os.EOL),
286
364
  fromMaxPosition = 0,
287
365
  i = 0,
288
366
  len = content.length,
289
- adjustSpaces, className, codeLine, fromPosition, importLength, importName, j, nextLine, spaces;
367
+ adjustSpaces, className, codeLine, existingImportName, fromPosition, importLength, j, nextLine, spaces;
290
368
 
291
369
  // find the index where we want to insert our import statement
292
370
  for (; i < len; i++) {
@@ -296,16 +374,16 @@ if (programOpts.info) {
296
374
  break;
297
375
  }
298
376
 
299
- importName = codeLine.substr(7);
300
- importName = importName.substr(0, importName.indexOf(' '));
301
- importLength = importName.length;
377
+ existingImportName = codeLine.substr(7);
378
+ existingImportName = existingImportName.substr(0, existingImportName.indexOf(' '));
379
+ importLength = existingImportName.length;
302
380
 
303
- if (importName > file) {
381
+ if (existingImportName > importName) {
304
382
  break;
305
383
  }
306
384
  }
307
385
 
308
- content.splice(i, 0, `import ${file} from './${file}.mjs';`);
386
+ content.splice(i, 0, `import ${importName} from '${opts.importPath}';`);
309
387
 
310
388
  // find the longest import module name
311
389
  for (i=0; i < len; i++) {
@@ -357,7 +435,7 @@ if (programOpts.info) {
357
435
  addComma(content, i - 1);
358
436
  addConfig({
359
437
  baseType,
360
- className : file,
438
+ className : importName,
361
439
  configName,
362
440
  contentArray: content,
363
441
  index : i,
@@ -379,7 +457,7 @@ if (programOpts.info) {
379
457
  if (content[j].includes('/**')) {
380
458
  addConfig({
381
459
  baseType,
382
- className : file,
460
+ className : importName,
383
461
  configName,
384
462
  contentArray: content,
385
463
  index : j,
@@ -396,11 +474,22 @@ if (programOpts.info) {
396
474
  fs.writeFileSync(viewFile, content.join(os.EOL));
397
475
  }
398
476
 
477
+ /**
478
+ * Makes the first character of a string uppercase
479
+ * @param {String} value
480
+ * @returns {Boolean|String} Returns false for non string inputs
481
+ */
482
+ function capitalize(value) {
483
+ return typeof value === 'string' && value[0].toUpperCase() + value.slice(1);
484
+ }
485
+
399
486
  /**
400
487
  * Creates the content of the neo-class .mjs file
401
488
  * @param {Object} opts
402
489
  * @param {String} opts.baseClass
490
+ * @param {String} opts.baseFileName
403
491
  * @param {String} opts.className
492
+ * @param {Boolean} opts.isSingleton
404
493
  * @param {String} opts.file
405
494
  * @param {String} opts.folderDelta
406
495
  * @param {String} opts.ns
@@ -408,24 +497,32 @@ if (programOpts.info) {
408
497
  * @returns {String}
409
498
  */
410
499
  function createContent(opts) {
411
- let baseClass = opts.baseClass,
412
- baseClassNs = baseClass.split('.'),
413
- baseFileName = baseClassNs.pop(),
414
- className = opts.className,
415
- file = opts.file,
416
- i = 0,
417
- importDelta = '';
500
+ let baseClass = opts.baseClass,
501
+ baseFileName = opts.baseFileName,
502
+ baseClassPath = baseClass.split('.').join('/'),
503
+ className = opts.className,
504
+ isSingleton = opts.isSingleton,
505
+ file = opts.file,
506
+ i = 0,
507
+ importDelta = '';
418
508
 
419
509
  for (; i < opts.folderDelta; i++) {
420
510
  importDelta += '../';
421
511
  }
422
512
 
423
513
  let classContent = [
424
- `import ${baseFileName} from '${importDelta}${(insideNeo ? '' : 'node_modules/neo.mjs/')}src/${baseClassNs.join('/')}/${baseFileName}.mjs';`,
514
+ `import ${baseFileName} from '${importDelta}${(insideNeo ? '' : 'node_modules/neo.mjs/')}src/${baseClassPath}.mjs';`,
425
515
  "",
426
516
  "/**",
427
517
  ` * @class ${className}`,
428
- ` * @extends Neo.${baseClass}`,
518
+ ` * @extends Neo.${baseClass}`
519
+ ];
520
+
521
+ isSingleton && classContent.push(
522
+ " * @singleton"
523
+ );
524
+
525
+ classContent.push(
429
526
  " */",
430
527
  `class ${file} extends ${baseFileName} {`,
431
528
  " static getConfig() {return {",
@@ -434,7 +531,17 @@ if (programOpts.info) {
434
531
  " * @protected",
435
532
  " */",
436
533
  ` className: '${className}'`
437
- ];
534
+ );
535
+
536
+ baseClass === 'data.Model' && addComma(classContent).push(
537
+ " /*",
538
+ " * @member {Object[]} fields",
539
+ " */",
540
+ " fields: [{",
541
+ " name: 'id',",
542
+ " type: 'String'",
543
+ " }]"
544
+ );
438
545
 
439
546
  baseClass === 'container.Base' && addComma(classContent).push(
440
547
  " /*",
@@ -443,6 +550,14 @@ if (programOpts.info) {
443
550
  " items: []"
444
551
  );
445
552
 
553
+ isSingleton && addComma(classContent).push(
554
+ " /*",
555
+ " * @member {Boolean} singleton=true",
556
+ " * @protected",
557
+ " */",
558
+ " singleton: true"
559
+ );
560
+
446
561
  baseClass === 'component.Base' && addComma(classContent).push(
447
562
  " /*",
448
563
  " * @member {Object} _vdom",
@@ -456,8 +571,22 @@ if (programOpts.info) {
456
571
  "}",
457
572
  "",
458
573
  `Neo.applyClassConfig(${file});`,
574
+ ""
575
+ );
576
+
577
+ isSingleton && classContent.push(
578
+ `let instance = Neo.create(${file});`,
459
579
  "",
460
- `export default ${file};`,
580
+ "Neo.applyToGlobalNs(instance);",
581
+ "",
582
+ "export default instance;"
583
+ );
584
+
585
+ !isSingleton && classContent.push(
586
+ `export default ${file};`
587
+ );
588
+
589
+ classContent.push(
461
590
  ""
462
591
  );
463
592
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "4.0.64",
3
+ "version": "4.0.67",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -62,7 +62,7 @@
62
62
  "webpack-node-externals": "^3.0.0"
63
63
  },
64
64
  "devDependencies": {
65
- "siesta-lite": "^5.5.2",
65
+ "siesta-lite": "5.5.2",
66
66
  "url": "^0.11.0"
67
67
  },
68
68
  "funding": {
@@ -99,6 +99,15 @@ class Application extends Base {
99
99
 
100
100
  return null;
101
101
  }
102
+
103
+ /**
104
+ * Unregister the app from the CSS map
105
+ * @param args
106
+ */
107
+ destroy(...args) {
108
+ Neo.currentWorker.removeAppFromThemeMap(this.name);
109
+ super.destroy(...args);
110
+ }
102
111
  }
103
112
 
104
113
  Neo.applyClassConfig(Application);
package/src/core/Util.mjs CHANGED
@@ -40,11 +40,11 @@ class Util extends Base {
40
40
 
41
41
  /**
42
42
  * Makes the first character of a string uppercase
43
- * @param {String} string
43
+ * @param {String} value
44
44
  * @returns {Boolean|String} Returns false for non string inputs
45
45
  */
46
- static capitalize(string) {
47
- return Util.isString(string) && string[0].toUpperCase() + string.slice(1);
46
+ static capitalize(value) {
47
+ return Util.isString(value) && value[0].toUpperCase() + value.slice(1);
48
48
  }
49
49
 
50
50
  /**
@@ -100,7 +100,7 @@ class Util extends Base {
100
100
  }
101
101
 
102
102
  /**
103
- * Transforms all uppercase characters of a string into lowercase.
103
+ * Transforms all uppercase characters of a string into -lowercase.
104
104
  * Does not touch special characters.
105
105
  * @param {String} value The input containing uppercase characters
106
106
  * @returns {String} The lowercase output
@@ -463,24 +463,8 @@ class Select extends Picker {
463
463
  * @protected
464
464
  */
465
465
  onListItemClick(record) {
466
- let me = this,
467
- displayField = me.displayField,
468
- oldValue = me.value,
469
- value = record[displayField];
470
-
471
- if (me.value !== value) {
472
- me.hintRecordId = null;
473
- me.record = record;
474
- me._value = value;
475
- me.getInputHintEl().value = null;
476
-
477
- me.afterSetValue(value, oldValue, true); // prevent the list from getting filtered
478
-
479
- me.fire('select', {
480
- record,
481
- value: record[displayField]
482
- });
483
- }
466
+ this.onListItemChange(record);
467
+ this.hidePicker();
484
468
  }
485
469
 
486
470
  /**
@@ -518,12 +502,38 @@ class Select extends Picker {
518
502
  this.focusInputEl();
519
503
  }
520
504
 
505
+
506
+ /**
507
+ * @param {Object} record
508
+ * @protected
509
+ */
510
+ onListItemChange(record) {
511
+ let me = this,
512
+ displayField = me.displayField,
513
+ oldValue = me.value,
514
+ value = record[displayField];
515
+
516
+ if (me.value !== value) {
517
+ me.hintRecordId = null;
518
+ me.record = record;
519
+ me._value = value;
520
+ me.getInputHintEl().value = null;
521
+
522
+ me.afterSetValue(value, oldValue, true); // prevent the list from getting filtered
523
+
524
+ me.fire('select', {
525
+ record,
526
+ value: record[displayField]
527
+ });
528
+ }
529
+ }
530
+
521
531
  /**
522
532
  * @param {Object} record
523
533
  * @protected
524
534
  */
525
535
  onListItemNavigate(record) {
526
- this.onListItemClick(record);
536
+ this.onListItemChange(record);
527
537
  }
528
538
 
529
539
  /**
@@ -270,6 +270,15 @@ class App extends Base {
270
270
  });
271
271
  }
272
272
 
273
+ /**
274
+ * Unregister the app from the CSS map
275
+ * Only needed for SharedWorkers
276
+ * @param {String} appName
277
+ */
278
+ removeAppFromThemeMap(appName) {
279
+ delete Neo.cssMap[appName.toLowerCase()];
280
+ }
281
+
273
282
  /**
274
283
  * @private
275
284
  */