neo.mjs 4.0.63 → 4.0.66

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.
@@ -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>')
@@ -94,7 +95,7 @@ if (programOpts.info) {
94
95
  type : 'input',
95
96
  name : 'className',
96
97
  message: 'Please choose the namespace for your class:',
97
- default: 'Covid.view.HeaderContainerController'
98
+ default: 'Covid.view.MyContainer'
98
99
  });
99
100
  }
100
101
 
@@ -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'],
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
- classFolder, 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,17 +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
+ }
202
258
 
203
- if (baseClass === 'controller.Component') {
204
- index = file.indexOf('Controller');
259
+ case 'data.Store': {
260
+ baseType = 'Neo.data.Model';
261
+ configName = 'model';
262
+ importName = className.replace('.store.', '.model.');
205
263
 
206
- if (index > 0) {
207
- viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
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();
208
272
 
273
+ importPath = `../${viewFile.join('/')}.mjs`;
274
+ viewFile = path.join(classFolder, importPath);
275
+
276
+ // checking for the data.Model file
209
277
  if (fs.existsSync(viewFile)) {
210
- adjustView({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});
211
284
  }
285
+ break;
286
+ }
287
+
288
+ case 'model.Component': {
289
+ baseType = 'Neo.model.Component';
290
+ configName = 'model';
291
+ importName = file;
292
+ importPath = `./${importName}.mjs`;
293
+ index = file.indexOf('Model');
294
+
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
+ }
301
+ }
302
+ break;
212
303
  }
213
304
  }
214
305
  }
@@ -222,41 +313,58 @@ if (programOpts.info) {
222
313
  /**
223
314
  * Adds a comma to the last element of the contentArray
224
315
  * @param {String[]} contentArray
316
+ * @param {Number} index=contentArray.length - 1
225
317
  * @returns {String[]}
226
318
  */
227
- function addComma(contentArray) {
228
- contentArray[contentArray.length - 1] += ',';
319
+ function addComma(contentArray, index=contentArray.length - 1) {
320
+ contentArray[index] += ',';
229
321
  return contentArray;
230
322
  }
231
323
 
232
- function addConfig(contentArray, index, className, isLastConfig) {
324
+ /**
325
+ * Adds a config to the given index of the contentArray
326
+ * @param {Object} opts
327
+ * @param {String} opts.baseType
328
+ * @param {String} opts.className
329
+ * @param {String} opts.configName
330
+ * @param {String[]} opts.contentArray
331
+ * @param {Boolean} opts.isLastConfig
332
+ * @param {Number} opts.index
333
+ * @returns {String[]}
334
+ */
335
+ function addConfig(opts) {
233
336
  const config = [
234
337
  ' /**',
235
- ` * @member {Neo.controller.Component} controller=${className}`,
338
+ ` * @member {${opts.baseType}} ${opts.configName}=${opts.className}`,
236
339
  ' */',
237
- ` controller: ${className}`
340
+ ` ${opts.configName}: ${opts.className}`
238
341
  ];
239
342
 
240
- !isLastConfig && addComma(config);
343
+ !opts.isLastConfig && addComma(config);
241
344
 
242
- contentArray.splice(index, 0, config.join(os.EOL));
243
- return contentArray;
345
+ opts.contentArray.splice(opts.index, 0, config.join(os.EOL));
346
+ return opts.contentArray;
244
347
  }
245
348
 
246
349
  /**
247
350
  * Adjusts the views related to controller.Component or model.Component
248
351
  * @param {Object} opts
249
- * @param {String} opts.file
352
+ * @param {String} opts.baseType
353
+ * @param {String} opts.configName
354
+ * @param {String} opts.importName
355
+ * @param {String} opts.importPath
250
356
  * @param {String} opts.viewFile
251
357
  */
252
358
  function adjustView(opts) {
253
- let file = opts.file,
359
+ let baseType = opts.baseType,
360
+ configName = opts.configName,
361
+ importName = opts.importName,
254
362
  viewFile = opts.viewFile,
255
363
  content = fs.readFileSync(viewFile).toString().split(os.EOL),
256
364
  fromMaxPosition = 0,
257
365
  i = 0,
258
366
  len = content.length,
259
- adjustSpaces, className, codeLine, fromPosition, importLength, importName, j, nextLine, spaces;
367
+ adjustSpaces, className, codeLine, existingImportName, fromPosition, importLength, j, nextLine, spaces;
260
368
 
261
369
  // find the index where we want to insert our import statement
262
370
  for (; i < len; i++) {
@@ -266,16 +374,16 @@ if (programOpts.info) {
266
374
  break;
267
375
  }
268
376
 
269
- importName = codeLine.substr(7);
270
- importName = importName.substr(0, importName.indexOf(' '));
271
- importLength = importName.length;
377
+ existingImportName = codeLine.substr(7);
378
+ existingImportName = existingImportName.substr(0, existingImportName.indexOf(' '));
379
+ importLength = existingImportName.length;
272
380
 
273
- if (importName > file) {
381
+ if (existingImportName > importName) {
274
382
  break;
275
383
  }
276
384
  }
277
385
 
278
- content.splice(i, 0, `import ${file} from './${file}.mjs';`);
386
+ content.splice(i, 0, `import ${importName} from '${opts.importPath}';`);
279
387
 
280
388
  // find the longest import module name
281
389
  for (i=0; i < len; i++) {
@@ -324,7 +432,15 @@ if (programOpts.info) {
324
432
  codeLine = content[i];
325
433
 
326
434
  if (codeLine.includes('}}')) {
327
- addConfig(content, i, file, true);
435
+ addComma(content, i - 1);
436
+ addConfig({
437
+ baseType,
438
+ className : importName,
439
+ configName,
440
+ contentArray: content,
441
+ index : i,
442
+ isLastConfig: true
443
+ });
328
444
  break;
329
445
  }
330
446
 
@@ -336,10 +452,17 @@ if (programOpts.info) {
336
452
  continue;
337
453
  }
338
454
 
339
- if (className > 'controller') {
455
+ if (className > configName) {
340
456
  for (j=i; j > 0; j--) {
341
457
  if (content[j].includes('/**')) {
342
- addConfig(content, j, file, false);
458
+ addConfig({
459
+ baseType,
460
+ className : importName,
461
+ configName,
462
+ contentArray: content,
463
+ index : j,
464
+ isLastConfig: false
465
+ });
343
466
  break;
344
467
  }
345
468
  }
@@ -351,11 +474,22 @@ if (programOpts.info) {
351
474
  fs.writeFileSync(viewFile, content.join(os.EOL));
352
475
  }
353
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
+
354
486
  /**
355
487
  * Creates the content of the neo-class .mjs file
356
488
  * @param {Object} opts
357
489
  * @param {String} opts.baseClass
490
+ * @param {String} opts.baseFileName
358
491
  * @param {String} opts.className
492
+ * @param {Boolean} opts.isSingleton
359
493
  * @param {String} opts.file
360
494
  * @param {String} opts.folderDelta
361
495
  * @param {String} opts.ns
@@ -363,24 +497,32 @@ if (programOpts.info) {
363
497
  * @returns {String}
364
498
  */
365
499
  function createContent(opts) {
366
- let baseClass = opts.baseClass,
367
- baseClassNs = baseClass.split('.'),
368
- baseFileName = baseClassNs.pop(),
369
- className = opts.className,
370
- file = opts.file,
371
- i = 0,
372
- 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 = '';
373
508
 
374
509
  for (; i < opts.folderDelta; i++) {
375
510
  importDelta += '../';
376
511
  }
377
512
 
378
513
  let classContent = [
379
- `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';`,
380
515
  "",
381
516
  "/**",
382
517
  ` * @class ${className}`,
383
- ` * @extends Neo.${baseClass}`,
518
+ ` * @extends Neo.${baseClass}`
519
+ ];
520
+
521
+ isSingleton && classContent.push(
522
+ " * @singleton"
523
+ );
524
+
525
+ classContent.push(
384
526
  " */",
385
527
  `class ${file} extends ${baseFileName} {`,
386
528
  " static getConfig() {return {",
@@ -389,7 +531,17 @@ if (programOpts.info) {
389
531
  " * @protected",
390
532
  " */",
391
533
  ` className: '${className}'`
392
- ];
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
+ );
393
545
 
394
546
  baseClass === 'container.Base' && addComma(classContent).push(
395
547
  " /*",
@@ -398,6 +550,14 @@ if (programOpts.info) {
398
550
  " items: []"
399
551
  );
400
552
 
553
+ isSingleton && addComma(classContent).push(
554
+ " /*",
555
+ " * @member {Boolean} singleton=true",
556
+ " * @protected",
557
+ " */",
558
+ " singleton: true"
559
+ );
560
+
401
561
  baseClass === 'component.Base' && addComma(classContent).push(
402
562
  " /*",
403
563
  " * @member {Object} _vdom",
@@ -411,8 +571,22 @@ if (programOpts.info) {
411
571
  "}",
412
572
  "",
413
573
  `Neo.applyClassConfig(${file});`,
574
+ ""
575
+ );
576
+
577
+ isSingleton && classContent.push(
578
+ `let instance = Neo.create(${file});`,
579
+ "",
580
+ "Neo.applyToGlobalNs(instance);",
414
581
  "",
415
- `export default ${file};`,
582
+ "export default instance;"
583
+ );
584
+
585
+ !isSingleton && classContent.push(
586
+ `export default ${file};`
587
+ );
588
+
589
+ classContent.push(
416
590
  ""
417
591
  );
418
592
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "4.0.63",
3
+ "version": "4.0.66",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
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
  /**