neo.mjs 8.35.1 → 8.37.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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.35.1'
23
+ * @member {String} version='8.37.0'
24
24
  */
25
- version: '8.35.1'
25
+ version: '8.37.0'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,6 @@
1
+ import Viewport from './view/Viewport.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: Viewport,
5
+ name : 'Email'
6
+ });
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta charset="UTF-8">
6
+ <title>Email</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,39 @@
1
+ import Model from '../../../src/data/Model.mjs';
2
+
3
+ /**
4
+ * @class Email.model.Email
5
+ * @extends Neo.data.Model
6
+ */
7
+ class Email extends Model {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Email.model.Email'
11
+ * @protected
12
+ */
13
+ className: 'Email.model.Email',
14
+ /**
15
+ * @member {Object[]} fields
16
+ */
17
+ fields: [{
18
+ name: 'id',
19
+ type: 'Int'
20
+ }, {
21
+ name: 'content',
22
+ type: 'String'
23
+ }, {
24
+ name: 'dateSent',
25
+ type: 'String'
26
+ }, {
27
+ name: 'recipients',
28
+ type: 'Array'
29
+ }, {
30
+ name: 'sender',
31
+ type: 'String'
32
+ }, {
33
+ name: 'title',
34
+ type: 'String'
35
+ }]
36
+ }
37
+ }
38
+
39
+ export default Neo.setupClass(Email);
@@ -0,0 +1,9 @@
1
+ {
2
+ "appPath": "apps/email/app.mjs",
3
+ "basePath": "../../",
4
+ "environment": "development",
5
+ "mainPath": "./Main.mjs",
6
+ "themes": [
7
+ "neo-theme-light"
8
+ ]
9
+ }
@@ -0,0 +1,15 @@
1
+ [{
2
+ "id" : 2,
3
+ "content" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
4
+ "dateSent" : "Mon Mar 31 2025 19:03:50 GMT+0200",
5
+ "recipients": ["Alice@neomjs.com"],
6
+ "sender" : "John@neomjs.com",
7
+ "title" : "Hello World"
8
+ }, {
9
+ "id" : 1,
10
+ "content" : "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vestibulum purus consectetur ex convallis, vitae ullamcorper sapien lobortis. Etiam elementum.",
11
+ "dateSent" : "Mon Mar 31 2025 18:03:50 GMT+0200",
12
+ "recipients": ["Alice@neomjs.com"],
13
+ "sender" : "David@neomjs.com",
14
+ "title" : "Hello World"
15
+ }]
@@ -0,0 +1,22 @@
1
+ import EmailModel from '../model/Email.mjs';
2
+ import Store from '../../../src/data/Store.mjs';
3
+
4
+ /**
5
+ * @class Email.store.Emails
6
+ * @extends Neo.data.Store
7
+ */
8
+ class Emails extends Store {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Email.store.Emails'
12
+ * @protected
13
+ */
14
+ className: 'Email.store.Emails',
15
+ /**
16
+ * @member {Neo.data.Model} model=Email
17
+ */
18
+ model: EmailModel
19
+ }
20
+ }
21
+
22
+ export default Neo.setupClass(Emails);
@@ -0,0 +1,57 @@
1
+ import BaseViewport from '../../../src/container/Viewport.mjs';
2
+ import Component from '../../../src/component/Base.mjs';
3
+ import TabContainer from '../../../src/tab/Container.mjs';
4
+ import ViewportStateProvider from './ViewportStateProvider.mjs';
5
+
6
+ /**
7
+ * @class Email.view.Viewport
8
+ * @extends Neo.container.Viewport
9
+ */
10
+ class Viewport extends BaseViewport {
11
+ static config = {
12
+ /**
13
+ * @member {String} className='Email.view.Viewport'
14
+ * @protected
15
+ */
16
+ className: 'Email.view.Viewport',
17
+ /*
18
+ * @member {Object} layout={ntype:'fit'}
19
+ */
20
+ layout: {ntype: 'fit'},
21
+ /**
22
+ * @member {Neo.state.Provider} stateProvider=ViewportStateProvider
23
+ */
24
+ stateProvider: ViewportStateProvider,
25
+ /**
26
+ * @member {Object[]} items
27
+ */
28
+ items: [{
29
+ module: TabContainer,
30
+ height: 300,
31
+ width : 500,
32
+ style : {flex: 'none', margin: '20px'},
33
+
34
+ itemDefaults: {
35
+ module: Component,
36
+ cls : ['neo-examples-tab-component'],
37
+ style : {padding: '20px'},
38
+ },
39
+
40
+ items: [{
41
+ header: {
42
+ iconCls: 'fa fa-home',
43
+ text : 'Tab 1'
44
+ },
45
+ vdom: {innerHTML: 'Welcome to your new Neo App.'}
46
+ }, {
47
+ header: {
48
+ iconCls: 'fa fa-play-circle',
49
+ text : 'Tab 2'
50
+ },
51
+ vdom: {innerHTML: 'Have fun creating something awesome!'}
52
+ }]
53
+ }]
54
+ }
55
+ }
56
+
57
+ export default Neo.setupClass(Viewport);
@@ -0,0 +1,26 @@
1
+ import EmailStore from '../store/Emails.mjs';
2
+ import Provider from '../../../src/state/Provider.mjs';
3
+
4
+ /**
5
+ * @class Email.view.ViewportStateProvider
6
+ * @extends Neo.state.Provider
7
+ */
8
+ class ViewportStateProvider extends Provider {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Email.view.ViewportStateProvider'
12
+ * @protected
13
+ */
14
+ className: 'Email.view.ViewportStateProvider',
15
+ /**
16
+ * @member {Object} stores
17
+ */
18
+ stores: {
19
+ emails: {
20
+ module: EmailStore
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ export default Neo.setupClass(ViewportStateProvider);
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-03-31",
19
+ "datePublished": "2025-04-03",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- html : 'v8.35.1'
110
+ html : 'v8.37.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -213,10 +213,6 @@ if (programOpts.info) {
213
213
  " */",
214
214
  " className: '" + appName + ".view.MainContainer',",
215
215
  " /**",
216
- " * @member {Boolean} autoMount=true",
217
- " */",
218
- " autoMount: true,",
219
- " /**",
220
216
  " * @member {Object[]} items",
221
217
  " */",
222
218
  " items: [{",
@@ -86,16 +86,6 @@ if (programOpts.info) {
86
86
  });
87
87
  }
88
88
 
89
- if (!programOpts.mainThreadAddons) {
90
- questions.push({
91
- type: 'checkbox',
92
- name: 'mainThreadAddons',
93
- message: 'Please choose your main thread addons:',
94
- choices: addonChoices,
95
- default: ['DragDrop', 'Stylesheet']
96
- });
97
- }
98
-
99
89
  if (!programOpts.useSharedWorkers) {
100
90
  questions.push({
101
91
  type: 'list',
@@ -117,16 +107,16 @@ if (programOpts.info) {
117
107
  }
118
108
 
119
109
  inquirer.prompt(questions).then(answers => {
120
- let appName = programOpts.appName || answers.appName,
121
- mainThreadAddons = programOpts.mainThreadAddons || answers.mainThreadAddons,
122
- themes = programOpts.themes || answers.themes,
123
- useSharedWorkers = programOpts.useSharedWorkers || answers.useSharedWorkers,
124
- useServiceWorker = programOpts.useServiceWorker || answers.useServiceWorker,
125
- lAppName = appName.toLowerCase(),
126
- appPath = 'apps/' + lAppName + '/',
127
- dir = 'apps/' + lAppName,
128
- folder = path.resolve(cwd, dir),
129
- startDate = new Date();
110
+ let appName = programOpts.appName || answers.appName,
111
+ mainThreadAddons = programOpts.mainThreadAddons || ['DragDrop', 'Navigator', 'Stylesheet'],
112
+ themes = programOpts.themes || answers.themes,
113
+ useSharedWorkers = programOpts.useSharedWorkers || answers.useSharedWorkers,
114
+ useServiceWorker = programOpts.useServiceWorker || answers.useServiceWorker,
115
+ lAppName = appName.toLowerCase(),
116
+ appPath = 'apps/' + lAppName + '/',
117
+ dir = 'apps/' + lAppName,
118
+ folder = path.resolve(cwd, dir),
119
+ startDate = new Date();
130
120
 
131
121
  if (!Array.isArray(themes)) {
132
122
  themes = [themes];
@@ -179,7 +169,12 @@ if (programOpts.info) {
179
169
  mainPath: `${insideNeo ? './' : '../node_modules/neo.mjs/src/'}Main.mjs`
180
170
  };
181
171
 
182
- if (!(mainThreadAddons.includes('DragDrop') && mainThreadAddons.includes('Stylesheet') && mainThreadAddons.length === 2)) {
172
+ if (!(
173
+ mainThreadAddons.includes('DragDrop') &&
174
+ mainThreadAddons.includes('Navigator') &&
175
+ mainThreadAddons.includes('Stylesheet') &&
176
+ mainThreadAddons.length === 3)
177
+ ) {
183
178
  neoConfig.mainThreadAddons = mainThreadAddons;
184
179
  }
185
180
 
@@ -244,16 +239,16 @@ export default Neo.setupClass(${className});
244
239
 
245
240
  className = 'MainView';
246
241
  content = `
247
- import Base from '${neoSrcPath}/container/Base.mjs';
248
- import Controller from './${className}Controller.mjs';
249
- import ViewModel from './${className}Model.mjs';
242
+ import Base from '${neoSrcPath}/container/Base.mjs';
243
+ import Controller from './${className}Controller.mjs';
244
+ import MainStateProvider from './MainStateProvider.mjs';
250
245
 
251
246
  class ${className} extends Base {
252
247
  static config = {
253
248
  className: '${appName}.view.${className}',
254
249
 
255
250
  controller: {module: Controller},
256
- model: {module: ViewModel},
251
+ stateProvider: {module: MainStateProvider},
257
252
 
258
253
  layout: {ntype: 'fit'},
259
254
  items: [],
@@ -283,11 +278,11 @@ export default Neo.setupClass(${className});
283
278
 
284
279
  // -------------------------------------------------------------------------
285
280
 
286
- className = 'MainViewModel';
281
+ className = 'MainStateProvider';
287
282
  content = `
288
- import Base from '${neoSrcPath}/model/Component.mjs';
283
+ import StateProvider from '${neoSrcPath}/state/Provider.mjs';
289
284
 
290
- class ${className} extends Base {
285
+ class ${className} extends StateProvider {
291
286
  static config = {
292
287
  className: '${appName}.view.${className}',
293
288
 
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.35.1'
23
+ * @member {String} version='8.37.0'
24
24
  */
25
- version: '8.35.1'
25
+ version: '8.37.0'
26
26
  }
27
27
 
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.35.1",
3
+ "version": "8.37.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -62,7 +62,7 @@
62
62
  "neo-jsdoc": "1.0.1",
63
63
  "neo-jsdoc-x": "1.0.5",
64
64
  "postcss": "^8.5.3",
65
- "sass": "^1.86.0",
65
+ "sass": "^1.86.3",
66
66
  "siesta-lite": "5.5.2",
67
67
  "url": "^0.11.4",
68
68
  "webpack": "^5.98.0",
@@ -23,7 +23,7 @@ A few key concepts we'll be discussing:
23
23
  - Configuring components
24
24
  - Debugging
25
25
  - Class-based coding
26
- - View models
26
+ - State providers
27
27
  - Events
28
28
  - Controllers
29
29
 
@@ -188,9 +188,9 @@ Use a code editor and look at `workspace/apps/earthquakes/src/view/MainView.mjs`
188
188
  following class definition:
189
189
 
190
190
  <pre data-javascript>
191
- import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
192
- import Controller from './MainViewController.mjs';
193
- import ViewModel from './MainViewModel.mjs';
191
+ import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
192
+ import Controller from './MainViewController.mjs';
193
+ import MainStateProvider from './MainStateProvider.mjs';
194
194
 
195
195
  class MainView extends Base {
196
196
  static config = {
@@ -198,16 +198,14 @@ class MainView extends Base {
198
198
  ntype: 'earthquakes-main',
199
199
 
200
200
  controller: {module: Controller},
201
- model: {module: ViewModel},
201
+ stateProvider: {module: MainStateProvider},
202
202
 
203
203
  layout: {ntype: 'fit'},
204
204
  items: [],
205
205
  }
206
206
  }
207
207
 
208
- Neo.setupClass(MainView);
209
-
210
- export default MainView;
208
+ export default Neo.setupClass(MainView);
211
209
  </pre>
212
210
 
213
211
  As you can see, `MainView extends Base`, and `Base` is a _container_ (`Neo.container.Base`).
@@ -227,10 +225,10 @@ which method to run when the button is clicked. We'll use `text`.
227
225
 
228
226
  <pre data-javascript>
229
227
 
230
- import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
231
- import Button from '../../../node_modules/neo.mjs/src/button/Base.mjs';
232
- import Controller from './MainViewController.mjs';
233
- import ViewModel from './MainViewModel.mjs';
228
+ import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
229
+ import Button from '../../../node_modules/neo.mjs/src/button/Base.mjs';
230
+ import Controller from './MainViewController.mjs';
231
+ import MainStateProvider from './MainStateProvider.mjs';
234
232
 
235
233
  class MainView extends Base {
236
234
  static config = {
@@ -238,7 +236,7 @@ class MainView extends Base {
238
236
  ntype: 'earthquakes-main',
239
237
 
240
238
  controller: {module: Controller},
241
- model: {module: ViewModel},
239
+ stateProvider: {module: MainStateProvider},
242
240
 
243
241
  layout: {ntype: 'fit'},
244
242
  items: [{
@@ -248,9 +246,7 @@ class MainView extends Base {
248
246
  }
249
247
  }
250
248
 
251
- Neo.setupClass(MainView);
252
-
253
- export default MainView;
249
+ export default Neo.setupClass(MainView);
254
250
  </pre>
255
251
 
256
252
 
@@ -428,10 +424,10 @@ Edit `apps/earthquakes/view/MainView.mjs` and add a method.
428
424
 
429
425
  <pre data-javascript>
430
426
 
431
- import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
432
- import Button from '../../../node_modules/neo.mjs/src/button/Base.mjs';
433
- import Controller from './MainViewController.mjs';
434
- import ViewModel from './MainViewModel.mjs';
427
+ import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
428
+ import Button from '../../../node_modules/neo.mjs/src/button/Base.mjs';
429
+ import Controller from './MainViewController.mjs';
430
+ import MainStateProvider from './MainStateProvider.mjs';
435
431
 
436
432
  class MainView extends Base {
437
433
  static config = {
@@ -439,7 +435,7 @@ class MainView extends Base {
439
435
  ntype: 'earthquakes-main',
440
436
 
441
437
  controller: {module: Controller},
442
- model: {module: ViewModel},
438
+ stateProvider: {module: MainStateProvider},
443
439
 
444
440
  layout: {
445
441
  ntype: 'vbox',
@@ -456,9 +452,7 @@ class MainView extends Base {
456
452
  }
457
453
  }
458
454
 
459
- Neo.setupClass(MainView);
460
-
461
- export default MainView;
455
+ export default Neo.setupClass(MainView);
462
456
  </pre>
463
457
 
464
458
  Save your changes.
@@ -502,11 +496,11 @@ Replace the button with a table by replacing `MainView.mjs` with the following c
502
496
 
503
497
  <pre data-javascript>
504
498
 
505
- import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
506
- import Table from '../../../node_modules/neo.mjs/src/table/Container.mjs';
507
- import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
508
- import Controller from './MainViewController.mjs';
509
- import ViewModel from './MainViewModel.mjs';
499
+ import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
500
+ import Table from '../../../node_modules/neo.mjs/src/table/Container.mjs';
501
+ import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
502
+ import Controller from './MainViewController.mjs';
503
+ import MainStateProvider from './MainStateProvider.mjs';
510
504
 
511
505
  class MainView extends Base {
512
506
  static config = {
@@ -514,7 +508,7 @@ class MainView extends Base {
514
508
  ntype: 'earthquakes-main',
515
509
 
516
510
  controller: {module: Controller},
517
- model: {module: ViewModel},
511
+ stateProvider: {module: MainStateProvider},
518
512
 
519
513
  layout: {ntype: 'vbox', align: 'stretch'},
520
514
  items: [{
@@ -555,9 +549,7 @@ class MainView extends Base {
555
549
  }
556
550
  }
557
551
 
558
- Neo.setupClass(MainView);
559
-
560
- export default MainView;
552
+ export default Neo.setupClass(MainView);
561
553
  </pre>
562
554
 
563
555
  Save and refresh.
@@ -692,22 +684,20 @@ class Table extends Base {
692
684
  columns: [{
693
685
  dataField: "timestamp",
694
686
  text: "Date",
695
- renderer: (data) => data.value.toLocaleDateString(undefined, {weekday: "long", year: "numeric", month: "long", day: "numeric"}),
687
+ renderer: (data) => data.value.toLocaleDateString(undefined, {weekday: "long", year: "numeric", month: "long", day: "numeric"})
696
688
  }, {
697
689
  dataField: "location",
698
- text: "Location",
690
+ text: "Location"
699
691
  }, {
700
692
  dataField: "magnitude",
701
693
  text: "Magnitude",
702
694
  align: "right",
703
- renderer: (data) => data.value.toLocaleString(),
695
+ renderer: (data) => data.value.toLocaleString()
704
696
  }],
705
697
  }
706
698
  }
707
699
 
708
- Neo.setupClass(Table);
709
-
710
- export default Table;
700
+ export default Neo.setupClass(Table);
711
701
  </pre>
712
702
 
713
703
  </details>
@@ -733,7 +723,7 @@ Edit `apps/earthquakes/view/MainView` and make these changes.
733
723
 
734
724
  Save and refresh the browser, and your app should run as before.
735
725
 
736
- You can confim that the new class _is being loaded_ by using DevTools to try to open `earthquakes/Table` &mdash; if it
726
+ You can confirm that the new class _is being loaded_ by using DevTools to try to open `earthquakes/Table` &mdash; if it
737
727
  was imported, it'll be listed.
738
728
 
739
729
  You can confirm that an instance _was created_ by using the DevTools console and searching for it via
@@ -746,11 +736,11 @@ You can confirm that an instance _was created_ by using the DevTools console and
746
736
  <summary>Here's the code</summary>
747
737
 
748
738
  <pre data-javascript>
749
- import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
750
- import Controller from './MainViewController.mjs';
751
- import EarthquakesTable from './earthquakes/Table.mjs';
752
- import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
753
- import ViewModel from './MainViewModel.mjs';
739
+ import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
740
+ import Controller from './MainViewController.mjs';
741
+ import EarthquakesTable from './earthquakes/Table.mjs';
742
+ import MainStateProvider from './MainStateProvider.mjs';
743
+ import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
754
744
 
755
745
  class MainView extends Base {
756
746
  static config = {
@@ -758,7 +748,7 @@ static config = {
758
748
  ntype: 'earthquakes-main',
759
749
 
760
750
  controller: {module: Controller},
761
- model: {module: ViewModel},
751
+ stateProvider: {module: MainStateProvider},
762
752
 
763
753
  layout: {ntype: 'vbox', align: 'stretch'},
764
754
  items: [{
@@ -784,9 +774,7 @@ static config = {
784
774
  }
785
775
  }
786
776
 
787
- Neo.setupClass(MainView);
788
-
789
- export default MainView;
777
+ export default Neo.setupClass(MainView);
790
778
  </pre>
791
779
 
792
780
  </details>
@@ -832,12 +820,12 @@ twice.
832
820
  If we simply wanted to re-use the store's description we could refactor the store config into a new
833
821
  store class, just like we did for the table. But in _earthquakes_ we want to share the store _instance_.
834
822
 
835
- Neo has a feature that allows shared, bindable, data. A `Neo.model.Component` instance holds properties that
836
- can be values like strings, numbers, or even references, like component or store references. `Neo.model.Component`
837
- is commonly called a _view model_ or _component model_.<small><sup>*</sup></small>
823
+ Neo has a feature that allows shared, bindable, data. A `Neo.state.Provider` instance holds properties that
824
+ can be values like strings, numbers, or even references, like component or store references. `Neo.state.Provider`
825
+ is commonly called a _state provider_.<small><sup>*</sup></small>
838
826
 
839
- The `create-app-minimal` script includes a view model and view controller config. The view model
840
- will hold the store.
827
+ The `create-app-minimal` script includes a state provider and view controller config.
828
+ The state provider will hold the store.
841
829
 
842
830
  <br>
843
831
  <br>
@@ -846,36 +834,36 @@ will hold the store.
846
834
 
847
835
 
848
836
 
849
- ## Lab. Use a View Model
837
+ ## Lab. Use a State Provider
850
838
 
851
839
  <!-- lab -->
852
840
 
853
841
  <details>
854
842
  <summary>Look at network traffic</summary>
855
843
 
856
- Before making any changes, open devtools in the Network tab and refresh _earthquakes_. You'll see two
857
- calls to the web service.
844
+ Before making any changes, open devtools in the Network tab and refresh _earthquakes_.
845
+ You'll see two calls to the web service.
858
846
 
859
847
  <img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/EarthquakesTwoTablesTwoCalls.png"></img>
860
848
 
861
849
  </details>
862
850
 
863
851
  <details>
864
- <summary>Copy the store config to the view model</summary>
852
+ <summary>Copy the store config to the state provider</summary>
865
853
 
866
- View models have two key configs: `data` and `stores`.
854
+ State Providers have two key configs: `data` and `stores`.
867
855
 
868
856
  - `data` holds name/value pairs where the value can be a simple value, or object references
869
857
  - `stores` holds configs of stores
870
858
 
871
- Add a `stores` property to the view model config that holds a copy of the store.
859
+ Add a `stores` property to the state provider config that holds a copy of the store.
872
860
 
873
861
  <pre data-javascript>
874
- import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
875
- import Controller from './MainViewController.mjs';
876
- import EarthquakesTable from './earthquakes/Table.mjs';
877
- import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
878
- import ViewModel from './MainViewModel.mjs';
862
+ import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
863
+ import Controller from './MainViewController.mjs';
864
+ import EarthquakesTable from './earthquakes/Table.mjs';
865
+ import MainStateProvider from './MainStateProvider.mjs';
866
+ import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
879
867
 
880
868
  class MainView extends Base {
881
869
  static config = {
@@ -883,8 +871,8 @@ class MainView extends Base {
883
871
  ntype: 'earthquakes-main',
884
872
 
885
873
  controller: {module: Controller},
886
- model: {
887
- module: ViewModel,
874
+ stateProvider: {
875
+ module: MainStateProvider,
888
876
  stores: {
889
877
  earthquakes: {
890
878
  module: Store,
@@ -950,9 +938,7 @@ class MainView extends Base {
950
938
  }
951
939
  }
952
940
 
953
- Neo.setupClass(MainView);
954
-
955
- export default MainView;
941
+ export default Neo.setupClass(MainView);
956
942
 
957
943
  </pre>
958
944
 
@@ -963,16 +949,16 @@ of the data the store holds.
963
949
  At this point we have _three_ identical store configs! Save and refresh, and look at network traffic &mdash; you
964
950
  should see three calls.
965
951
 
966
- Having an instance in the view model means we can share it. It can be shared anywhere in the containment
952
+ Having an instance in the state provider means we can share it. It can be shared anywhere in the containment
967
953
  hierarchy. The app doesn't have much of a hierarchy: it's just the main view and two child components (the two
968
- tables). But now that the store is in the parent's view model we can share it.
954
+ tables). But now that the store is in the parent's state provider we can share it.
969
955
 
970
956
  </details>
971
957
 
972
958
  <details>
973
959
  <summary>Use the shared store</summary>
974
960
 
975
- The way to bind an instance to a view model property is with the `bind` config. For example
961
+ The way to bind an instance to a state provider property is with the `bind` config. For example
976
962
 
977
963
  bind: {
978
964
  store: 'stores.earthquakes'
@@ -980,7 +966,7 @@ The way to bind an instance to a view model property is with the `bind` config.
980
966
 
981
967
  binds a `store` property to a store called `foo`. The code is saying _in the future, when the value
982
968
  of "stores.earthquakes" changes, assign it to this object's "store" property_. In this case, `stores.earthquakes`
983
- starts out undefined, then at runtime within a few milliseconds as the view model is processed, the configured
969
+ starts out undefined, then at runtime within a few milliseconds as the state provider is processed, the configured
984
970
  store is created and a reference is assigned to `stores.earthquakes`. That wakes the binding up, and the
985
971
  value is assigned to the table's `store` property.
986
972
 
@@ -988,19 +974,19 @@ Replace each table's `store` config with the binding.
988
974
 
989
975
  <pre data-javascript>
990
976
 
991
- import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
992
- import Controller from './MainViewController.mjs';
993
- import EarthquakesTable from './earthquakes/Table.mjs';
994
- import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
995
- import ViewModel from './MainViewModel.mjs';
977
+ import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
978
+ import Controller from './MainViewController.mjs';
979
+ import EarthquakesTable from './earthquakes/Table.mjs';
980
+ import MainStateProvider from './MainStateProvider.mjs';
981
+ import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
996
982
 
997
983
  class MainView extends Base {
998
984
  static config = {
999
985
  className: 'Earthquakes.view.MainView',
1000
986
  ntype: 'earthquakes-main',
1001
987
  controller: {module: Controller},
1002
- model: {
1003
- module: ViewModel,
988
+ stateProvider: {
989
+ module: MainStateProvider,
1004
990
  stores: {
1005
991
  earthquakes: {
1006
992
  module: Store,
@@ -1017,7 +1003,7 @@ class MainView extends Base {
1017
1003
  url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
1018
1004
  responseRoot: "data",
1019
1005
  autoLoad: true
1020
- },
1006
+ }
1021
1007
  }
1022
1008
  },
1023
1009
 
@@ -1034,13 +1020,11 @@ class MainView extends Base {
1034
1020
  store: 'stores.earthquakes'
1035
1021
  },
1036
1022
  style: {width: '100%'}
1037
- }],
1023
+ }]
1038
1024
  }
1039
1025
  }
1040
1026
 
1041
- Neo.setupClass(MainView);
1042
-
1043
- export default MainView;
1027
+ export default Neo.setupClass(MainView);
1044
1028
  </pre>
1045
1029
 
1046
1030
  Save, refresh, and look at network traffic: you'll see a _single_ call to the web service.
@@ -1050,7 +1034,7 @@ Save, refresh, and look at network traffic: you'll see a _single_ call to the we
1050
1034
  You can further prove we're using a shared instance by running these statements in the console.
1051
1035
 
1052
1036
  <pre data-javascript>
1053
- a = Neo.findFirst({ntype:'earthquakes-main'}).model.stores.earthquakes;
1037
+ a = Neo.findFirst({ntype:'earthquakes-main'}).stateProvider.stores.earthquakes;
1054
1038
  b = Neo.find({ntype:'earthquakes-table'})[0].store;
1055
1039
  c = Neo.find({ntype:'earthquakes-table'})[1].store;
1056
1040
 
@@ -1060,25 +1044,25 @@ c = Neo.find({ntype:'earthquakes-table'})[1].store;
1060
1044
  </details>
1061
1045
 
1062
1046
  <details>
1063
- <summary>Use the view model class</summary>
1047
+ <summary>Use the state provider class</summary>
1064
1048
 
1065
- We configured the view model in-line, in the `model` config at the top of `MainView`. But the starter app
1066
- has a `MainViewModel` class. In theory, if you have a trivial view model you could configure it in-line. But
1049
+ We configured the state provider in-line, in the `stateProvider` config at the top of `MainView`. But the starter app
1050
+ has a `MainStateProvider` class. In theory, if you have a trivial state provider you could configure it in-line. But
1067
1051
  in general you want to keep that code separate by coding it in a separate class. This is what we did for the
1068
1052
  table config &mdash; we started by coding it in-line in the main view, then we refactored it into its own
1069
- class. The result was a simpler and more abstract main view. We want to do the same for the view model.
1053
+ class. The result was a simpler and more abstract main view. We want to do the same for the state provider.
1070
1054
 
1071
- Since the starter app already provides `MainViewModel`, all we need to do is copy the `stores` property.
1055
+ Since the starter app already provides `MainStateProvider`, all we need to do is copy the `stores` property.
1072
1056
 
1073
- Here's the resulting code you should place into `MainViewModel.mjs`.
1057
+ Here's the resulting code you should place into `MainStateProvider.mjs`.
1074
1058
 
1075
1059
  <pre data-javascript>
1076
- import Model from '../../../node_modules/neo.mjs/src/model/Component.mjs';
1077
- import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
1060
+ import StateProvider from '../../../node_modules/neo.mjs/src/state/Provider.mjs';
1061
+ import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
1078
1062
 
1079
- class MainViewModel extends Model {
1063
+ class MainStateProvider extends StateProvider {
1080
1064
  static config = {
1081
- className: 'Earthquakes.view.MainViewModel',
1065
+ className: 'Earthquakes.view.MainStateProvider',
1082
1066
 
1083
1067
  data: {},
1084
1068
  stores: {
@@ -1102,26 +1086,24 @@ class MainViewModel extends Model {
1102
1086
  }
1103
1087
  }
1104
1088
 
1105
- Neo.setupClass(MainViewModel);
1106
-
1107
- export default MainViewModel;
1089
+ export default Neo.setupClass(MainStateProvider);
1108
1090
  </pre>
1109
1091
 
1110
1092
  And you need to remove the `stores` config from the main view as follows.
1111
1093
 
1112
1094
  <pre data-javascript>
1113
- import Container from '../../../node_modules/neo.mjs/src/container/Base.mjs';
1114
- import Controller from './MainViewController.mjs';
1115
- import EarthquakesTable from './earthquakes/Table.mjs';
1116
- import ViewModel from './MainViewModel.mjs';
1095
+ import Container from '../../../node_modules/neo.mjs/src/container/Base.mjs';
1096
+ import Controller from './MainViewController.mjs';
1097
+ import EarthquakesTable from './earthquakes/Table.mjs';
1098
+ import MainStateProvider from './MainStateProvider.mjs';
1117
1099
 
1118
1100
  class MainView extends Container {
1119
1101
  static config = {
1120
1102
  className: 'Earthquakes.view.MainView',
1121
1103
  ntype: 'earthquakes-main',
1122
1104
  controller: {module: Controller},
1123
- model: {
1124
- module: ViewModel
1105
+ stateProvider: {
1106
+ module: MainStateProvider
1125
1107
  },
1126
1108
 
1127
1109
  layout: { ntype: 'vbox', align: 'stretch' },
@@ -1141,12 +1123,10 @@ class MainView extends Container {
1141
1123
  }
1142
1124
  }
1143
1125
 
1144
- Neo.setupClass(MainView);
1145
-
1146
- export default MainView;
1126
+ export default Neo.setupClass(MainView);
1147
1127
  </pre>
1148
1128
 
1149
- The refactorings to have separate table and view model classes means the code is more modular, more reusable,
1129
+ The refactorings to have separate table and state provider classes means the code is more modular, more reusable,
1150
1130
  and each class is simpler than using complex source files that try to configure every detail.
1151
1131
 
1152
1132
  </details>
@@ -1155,8 +1135,8 @@ and each class is simpler than using complex source files that try to configure
1155
1135
 
1156
1136
  ## Google Maps Add-on
1157
1137
 
1158
- Neo.mjs has a Google Map component. This component is a little different than a button or table,
1159
- becauase it's implemented as a _main thread add-on_.
1138
+ Neo.mjs has a Google Map component. This component is a little different from a button or table,
1139
+ because it's implemented as a _main thread add-on_.
1160
1140
 
1161
1141
  When you use Google Maps you use the Google Map API to ask it to draw the map and markers.
1162
1142
  In a normal app, Google Maps &mdahs; and everything else &mdash; runs in the main browser thread.
@@ -1192,7 +1172,7 @@ Marker store records are required to have these properties:
1192
1172
  <details>
1193
1173
  <summary>Get the code for the custom add-on</summary>
1194
1174
  At the time this tutorial was written, the Neo.mjs Google Maps addon was about to be updated to
1195
- accomodate Google's "AdvancedMarker" class. Until that's ready, we're going to use a modified version of the add-on.
1175
+ accommodate Google's "AdvancedMarker" class. Until that's ready, we're going to use a modified version of the add-on.
1196
1176
 
1197
1177
  Download and unzip this file, and copy the two source files to the corresponding subdirectories in
1198
1178
  your workspace's `src` directory. Note that `src` already contains some files, so don't replace the whole
@@ -1245,7 +1225,7 @@ lets us implement those via two properties:
1245
1225
  - `mapping` &mdash; the path to a feed property holding the value
1246
1226
  - `calculate` &mdash; a function that returns a value
1247
1227
 
1248
- Edit `apps/earthquakes/view/MainViewModel.mjs` and modify `fields` as follows.
1228
+ Edit `apps/earthquakes/view/MainStateProvider.mjs` and modify `fields` as follows.
1249
1229
 
1250
1230
  <pre data-javascript>
1251
1231
  fields: [{
@@ -1270,9 +1250,9 @@ calculated by returning an object with _lat_ and _lng_ set to the corresponding
1270
1250
  Save and refresh _earthquakes_. You can use the debugger to inspect the store via _Shift-Ctrl-right-click_ and
1271
1251
  putting the main view into a global variable. Then run
1272
1252
 
1273
- temp1.getModel().stores.earthquakes.items
1253
+ temp1.getStateProvider().stores.earthquakes.items
1274
1254
 
1275
- Look at one of the items and you should see that _title_ and _location_ are in each record.
1255
+ Look at one of the items, and you should see that _title_ and _location_ are in each record.
1276
1256
 
1277
1257
  <img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/StoreHasTitleAndLocation.png"></img>
1278
1258
 
@@ -1304,15 +1284,15 @@ import Container from '../container/Base.mjs';
1304
1284
  import Controller from './MainViewController.mjs';
1305
1285
  import EarthquakesTable from './earthquakes/Table.mjs';
1306
1286
  import GoogleMapsComponent from '../component/wrapper/GoogleMaps.mjs';
1307
- import ViewModel from './MainViewModel.mjs';
1287
+ import MainStateProvider from './MainStateProvider.mjs';
1308
1288
 
1309
1289
  class MainView extends Container {
1310
1290
  static config = {
1311
1291
  className: 'Earthquakes.view.MainView',
1312
1292
  ntype: 'earthquakes-main',
1313
1293
  controller: {module: Controller},
1314
- model: {
1315
- module: ViewModel
1294
+ stateProvider: {
1295
+ module: MainStateProvider
1316
1296
  },
1317
1297
 
1318
1298
  layout: { ntype: 'vbox', align: 'stretch' },
@@ -1337,9 +1317,7 @@ class MainView extends Container {
1337
1317
  }
1338
1318
  }
1339
1319
 
1340
- Neo.setupClass(MainView);
1341
-
1342
- export default MainView;
1320
+ export default Neo.setupClass(MainView);
1343
1321
  </pre>
1344
1322
 
1345
1323
  <img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/CenteredMap.png"></img>
@@ -1430,6 +1408,6 @@ and to introduce some basic Neo.mjs concepts
1430
1408
 
1431
1409
  - Declarative, abstract code
1432
1410
  - Class-based coding, and the ability to extend any class, such as `view/earthquakes/Table.mjs`
1433
- - View models, to share properties and objects, such as the store shared by the table and map
1411
+ - State providers, to share properties and objects, such as the store shared by the table and map
1434
1412
  - Events, specified via `listeners:{}`
1435
1413
  - Controllers, to hold event listeners and other procedural logic
@@ -263,12 +263,12 @@ const DefaultConfig = {
263
263
  useVdomWorker: true,
264
264
  /**
265
265
  * buildScripts/injectPackageVersion.mjs will update this value
266
- * @default '8.35.1'
266
+ * @default '8.37.0'
267
267
  * @memberOf! module:Neo
268
268
  * @name config.version
269
269
  * @type String
270
270
  */
271
- version: '8.35.1'
271
+ version: '8.37.0'
272
272
  };
273
273
 
274
274
  Object.assign(DefaultConfig, {
@@ -40,6 +40,13 @@ class Model extends Base {
40
40
  }
41
41
 
42
42
  /**
43
+ * Internally storing all fields, which have a calculate property
44
+ * @member {Map} calculatedFieldsMap=new Map()
45
+ * @protected
46
+ */
47
+ calculatedFieldsMap = new Map()
48
+ /**
49
+ * Internally storing all fields inside a flat map => nested fields included
43
50
  * @member {Map} fieldsMap=new Map()
44
51
  * @protected
45
52
  */
@@ -85,11 +92,12 @@ class Model extends Base {
85
92
  * @param {String} path=''
86
93
  */
87
94
  updateFieldsMap(fields, isRoot=true, path='') {
88
- let me = this,
89
- {fieldsMap} = me,
95
+ let me = this,
96
+ {calculatedFieldsMap, fieldsMap} = me,
90
97
  fieldName;
91
98
 
92
99
  if (isRoot) {
100
+ calculatedFieldsMap.clear();
93
101
  fieldsMap.clear();
94
102
  me.hasNestedFields = false
95
103
  }
@@ -97,12 +105,15 @@ class Model extends Base {
97
105
  fields.forEach(field => {
98
106
  fieldName = path + field.name
99
107
 
100
- // Assuming that nested fields contain the full path as the name, we do not need a prefix.
101
108
  if (field.fields) {
102
109
  me.hasNestedFields = true;
103
110
  me.updateFieldsMap(field.fields, false, field.name + '.')
104
111
  } else {
105
- fieldsMap.set(fieldName, field)
112
+ fieldsMap.set(fieldName, field);
113
+
114
+ if (field.calculate) {
115
+ calculatedFieldsMap.set(fieldName, field)
116
+ }
106
117
  }
107
118
  })
108
119
  }
@@ -82,24 +82,11 @@ class RecordFactory extends Base {
82
82
  return this[dataSymbol][fieldName]
83
83
  },
84
84
  set(value) {
85
- let me = this,
86
- oldValue = me[dataSymbol][fieldName];
87
-
88
- value = instance.parseRecordValue(me, field, value);
89
-
90
- if (!Neo.isEqual(value, oldValue)) {
91
- instance.setRecordData({fieldName: fieldPath, model, record: me, value});
92
-
93
- if (!model.trackModifiedFields) {
94
- me[isModifiedSymbol] = true
95
- }
96
-
97
- instance.onRecordChange({
98
- fields: [{name: fieldPath, oldValue, value}],
99
- model,
100
- record: me
101
- })
102
- }
85
+ instance.setRecordFields({
86
+ fields: {[fieldPath]: instance.parseRecordValue(this, field, value)},
87
+ model,
88
+ record: this,
89
+ })
103
90
  }
104
91
  }
105
92
  };
@@ -288,11 +275,11 @@ class RecordFactory extends Base {
288
275
  * todo: parse value for more field types
289
276
  * @param {Object} record
290
277
  * @param {Object} field
291
- * @param {*} value
278
+ * @param {*} value=null
292
279
  * @param {Object} recordConfig=null
293
280
  * @returns {*}
294
281
  */
295
- parseRecordValue(record, field, value, recordConfig=null) {
282
+ parseRecordValue(record, field, value=null, recordConfig=null) {
296
283
  if (field.calculate) {
297
284
  return field.calculate(record, field, recordConfig)
298
285
  }
@@ -400,8 +387,9 @@ class RecordFactory extends Base {
400
387
  * @param {Boolean} data.useOriginalData=false true will apply changes to the originalData symbol
401
388
  */
402
389
  setRecordFields({changedFields=[], fields, model, record, silent=false, useOriginalData=false}) {
403
- let {fieldsMap, trackModifiedFields} = model,
404
- fieldExists, oldValue;
390
+ let me = this,
391
+ {calculatedFieldsMap, fieldsMap, trackModifiedFields} = model,
392
+ fieldExists, hasChangedFields, oldValue;
405
393
 
406
394
  if (!trackModifiedFields && useOriginalData) {
407
395
  return
@@ -412,7 +400,7 @@ class RecordFactory extends Base {
412
400
 
413
401
  if (Neo.isObject(value) && !fieldExists) {
414
402
  Object.entries(value).forEach(([childKey, childValue]) => {
415
- this.setRecordFields({
403
+ me.setRecordFields({
416
404
  changedFields,
417
405
  fields: {[`${key}.${childKey}`]: childValue},
418
406
  model,
@@ -423,10 +411,10 @@ class RecordFactory extends Base {
423
411
  })
424
412
  } else if (fieldExists) {
425
413
  oldValue = record[key];
426
- value = instance.parseRecordValue(record, model.getField(key), value);
414
+ value = me.parseRecordValue(record, model.getField(key), value);
427
415
 
428
416
  if (!Neo.isEqual(oldValue, value)) {
429
- instance.setRecordData({fieldName: key, model, record, useOriginalData, value});
417
+ me.setRecordData({fieldName: key, model, record, useOriginalData, value});
430
418
 
431
419
  if (!trackModifiedFields && !useOriginalData) {
432
420
  record[isModifiedSymbol] = true
@@ -437,8 +425,23 @@ class RecordFactory extends Base {
437
425
  }
438
426
  });
439
427
 
440
- if (!silent && !useOriginalData && Object.keys(changedFields).length > 0) {
441
- Neo.get(model.storeId)?.onRecordChange({fields: changedFields, model, record})
428
+ hasChangedFields = Object.keys(changedFields).length > 0;
429
+
430
+ if (hasChangedFields) {
431
+ calculatedFieldsMap.forEach((value, key) => {
432
+ oldValue = record[key];
433
+ value = me.parseRecordValue(record, model.getField(key));
434
+
435
+ if (!Neo.isEqual(oldValue, value)) {
436
+ me.setRecordData({fieldName: key, model, record, useOriginalData, value});
437
+
438
+ changedFields.push({name: key, oldValue, value})
439
+ }
440
+ })
441
+ }
442
+
443
+ if (!silent && !useOriginalData && hasChangedFields) {
444
+ me.onRecordChange({fields: changedFields, model, record})
442
445
  }
443
446
  }
444
447
  }