neo.mjs 6.18.3 → 6.19.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.
- package/README.md +28 -214
- package/apps/ServiceWorker.mjs +2 -2
- package/apps/colors/view/ViewportController.mjs +7 -3
- package/apps/portal/data/blog.json +13 -0
- package/apps/portal/view/HeaderToolbar.mjs +2 -2
- package/apps/portal/view/Viewport.mjs +4 -2
- package/apps/portal/view/ViewportController.mjs +89 -8
- package/apps/portal/view/blog/Container.mjs +8 -8
- package/apps/portal/view/blog/List.mjs +6 -6
- package/apps/portal/view/home/MainContainer.mjs +3 -2
- package/apps/portal/view/home/parts/Colors.mjs +2 -2
- package/apps/portal/view/home/parts/How.mjs +3 -3
- package/apps/portal/view/home/parts/MainNeo.mjs +6 -7
- package/apps/portal/view/home/parts/References.mjs +88 -0
- package/apps/portal/view/learn/MainContainer.mjs +3 -2
- package/apps/portal/view/learn/MainContainerController.mjs +11 -0
- package/apps/portal/view/learn/PageContainer.mjs +5 -3
- package/apps/portal/view/services/Component.mjs +73 -0
- package/apps/website/data/blog.json +13 -0
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/component/carousel/MainContainer.mjs +42 -33
- package/examples/layout/cube/MainContainer.mjs +217 -0
- package/examples/layout/cube/app.mjs +6 -0
- package/examples/layout/cube/index.html +11 -0
- package/examples/layout/cube/neo-config.json +6 -0
- package/package.json +6 -6
- package/resources/data/deck/learnneo/pages/Earthquakes-01-goals.md +32 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-01-generate-a-workspace.md +47 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-02-generate-the-starter-app.md +150 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-03-debugging.md +136 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-04-fetch-data.md +146 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-05-refactor-the-table.md +146 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-06-use-a-view-model.md +301 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-07-use-the-google-maps-addon.md +175 -0
- package/resources/data/deck/learnneo/pages/Earthquakes-Lab-08-events.md +38 -0
- package/resources/data/deck/learnneo/pages/Earthquakes.md +8 -8
- package/resources/data/deck/learnneo/pages/Glossary.md +0 -0
- package/resources/data/deck/learnneo/pages/GuideEvents.md +80 -1
- package/resources/data/deck/learnneo/tree.json +2 -1
- package/resources/images/apps/portal/neo-references.png +0 -0
- package/resources/scss/src/apps/portal/Viewport.scss +18 -0
- package/resources/scss/src/apps/portal/blog/Container.scss +7 -7
- package/resources/scss/src/apps/portal/blog/List.scss +20 -16
- package/resources/scss/src/apps/portal/home/parts/MainNeo.scss +4 -5
- package/resources/scss/src/apps/portal/home/parts/References.scss +46 -0
- package/resources/scss/src/apps/portal/learn/ContentTreeList.scss +20 -0
- package/resources/scss/src/apps/portal/learn/ContentView.scss +4 -0
- package/resources/scss/src/apps/portal/learn/MainContainer.scss +1 -1
- package/resources/scss/src/apps/portal/learn/PageContainer.scss +22 -16
- package/resources/scss/src/apps/portal/services/Component.scss +20 -0
- package/resources/scss/src/component/Carousel.scss +21 -0
- package/resources/scss/src/examples/layout/cube/MainContainer.scss +7 -0
- package/resources/scss/src/layout/Cube.scss +80 -0
- package/resources/scss/src/tab/Container.scss +10 -10
- package/resources/scss/theme-neo-light/apps/portal/blog/Container.scss +3 -0
- package/resources/scss/theme-neo-light/form/field/Search.scss +1 -1
- package/resources/scss/theme-neo-light/tooltip/Base.scss +1 -1
- package/src/DefaultConfig.mjs +2 -2
- package/src/Main.mjs +15 -1
- package/src/Neo.mjs +14 -3
- package/src/component/Base.mjs +18 -1
- package/src/container/Base.mjs +3 -1
- package/src/dialog/Base.mjs +1 -2
- package/src/layout/Base.mjs +43 -6
- package/src/layout/Card.mjs +21 -59
- package/src/layout/Cube.mjs +428 -0
- package/src/layout/Fit.mjs +9 -38
- package/src/layout/Flexbox.mjs +16 -17
- package/src/layout/Form.mjs +13 -70
- package/src/layout/Grid.mjs +6 -18
- package/src/main/mixin/DeltaUpdates.mjs +16 -3
- package/src/util/Array.mjs +36 -0
- package/src/vdom/Helper.mjs +328 -445
- package/src/vdom/VNode.mjs +12 -1
- package/test/siesta/siesta.js +16 -1
- package/test/siesta/tests/VdomCalendar.mjs +2111 -37
- package/test/siesta/tests/VdomHelper.mjs +283 -47
- package/test/siesta/tests/vdom/Advanced.mjs +367 -0
- package/test/siesta/tests/vdom/layout/Cube.mjs +189 -0
- package/test/siesta/tests/vdom/table/Container.mjs +133 -0
- package/resources/scss/theme-neo-light/apps/portal/learn/ContentTreeList.scss +0 -23
@@ -0,0 +1,301 @@
|
|
1
|
+
<details>
|
2
|
+
<summary>Look at network traffic</summary>
|
3
|
+
|
4
|
+
Before making any changes, open devtools in the Network tab and refresh _earthquakes_. You'll see two
|
5
|
+
calls to the web service.
|
6
|
+
|
7
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/EarthquakesTwoTablesTwoCalls.png"></img>
|
8
|
+
|
9
|
+
</details>
|
10
|
+
|
11
|
+
<details>
|
12
|
+
<summary>Copy the store config to the view model</summary>
|
13
|
+
|
14
|
+
View models have two key configs: `data` and `stores`.
|
15
|
+
|
16
|
+
- `data` holds name/value pairs where the value can be a simple value, or object references
|
17
|
+
- `stores` holds configs of stores
|
18
|
+
|
19
|
+
Add a `stores` property to the view model config that holds a copy of the store.
|
20
|
+
|
21
|
+
<pre data-javascript>
|
22
|
+
import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
|
23
|
+
import Controller from './MainViewController.mjs';
|
24
|
+
import EarthquakesTable from './earthquakes/Table.mjs';
|
25
|
+
import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
|
26
|
+
import ViewModel from './MainViewModel.mjs';
|
27
|
+
|
28
|
+
class MainView extends Base {
|
29
|
+
static config = {
|
30
|
+
className: 'Earthquakes.view.MainView',
|
31
|
+
ntype: 'earthquakes-main',
|
32
|
+
|
33
|
+
controller: {module: Controller},
|
34
|
+
model: {
|
35
|
+
module: ViewModel,
|
36
|
+
stores: {
|
37
|
+
earthquakes: {
|
38
|
+
module: Store,
|
39
|
+
model: {
|
40
|
+
fields: [{
|
41
|
+
name: "humanReadableLocation"
|
42
|
+
}, {
|
43
|
+
name: "size"
|
44
|
+
}, {
|
45
|
+
name: "timestamp",
|
46
|
+
type: "Date"
|
47
|
+
}]
|
48
|
+
},
|
49
|
+
url: "https://apis.is/earthquake/is",
|
50
|
+
responseRoot: "results",
|
51
|
+
autoLoad: true
|
52
|
+
},
|
53
|
+
}
|
54
|
+
},
|
55
|
+
|
56
|
+
layout: {
|
57
|
+
ntype: 'vbox', align: 'stretch'
|
58
|
+
},
|
59
|
+
items: [{
|
60
|
+
module: EarthquakesTable,
|
61
|
+
store: {
|
62
|
+
module: Store,
|
63
|
+
model: {
|
64
|
+
fields: [{
|
65
|
+
name: "humanReadableLocation"
|
66
|
+
}, {
|
67
|
+
name: "size"
|
68
|
+
}, {
|
69
|
+
name: "timestamp",
|
70
|
+
type: "Date"
|
71
|
+
}]
|
72
|
+
},
|
73
|
+
url: "https://apis.is/earthquake/is",
|
74
|
+
responseRoot: "results",
|
75
|
+
autoLoad: true
|
76
|
+
},
|
77
|
+
style: {width: '100%'}
|
78
|
+
},{
|
79
|
+
module: EarthquakesTable,
|
80
|
+
store: {
|
81
|
+
module: Store,
|
82
|
+
model: {
|
83
|
+
fields: [{
|
84
|
+
name: "humanReadableLocation"
|
85
|
+
}, {
|
86
|
+
name: "size"
|
87
|
+
}, {
|
88
|
+
name: "timestamp",
|
89
|
+
type: "Date"
|
90
|
+
}]
|
91
|
+
},
|
92
|
+
url: "https://apis.is/earthquake/is",
|
93
|
+
responseRoot: "results",
|
94
|
+
autoLoad: true
|
95
|
+
},
|
96
|
+
style: {width: '100%'}
|
97
|
+
}],
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
Neo.setupClass(MainView);
|
102
|
+
|
103
|
+
export default MainView;
|
104
|
+
|
105
|
+
</pre>
|
106
|
+
|
107
|
+
In the `stores` config we named the store _earthquakes_. We could have named it anything, like _foo_
|
108
|
+
or _myStore_. We're calling it _earthquakes_ simply because that seems like a good descriptive name
|
109
|
+
of the data the store holds.
|
110
|
+
|
111
|
+
At this point we have _three_ identical store configs! Save and refresh, and look at network traffic — you
|
112
|
+
should see three calls.
|
113
|
+
|
114
|
+
Having an instance in the view model means we can share it. It can be shared anywhere in the containment
|
115
|
+
hierarchy. The app doesn't have much of a hierarchy: it's just the main view and two child components (the two
|
116
|
+
tables). But now that the store is in the parent's view model we can share it.
|
117
|
+
|
118
|
+
</details>
|
119
|
+
|
120
|
+
<details>
|
121
|
+
<summary>Use the shared store</summary>
|
122
|
+
|
123
|
+
The way to bind an instance to a view model property is with the `bind` config. For example
|
124
|
+
|
125
|
+
bind: {
|
126
|
+
store: 'stores.earthquakes'
|
127
|
+
}
|
128
|
+
|
129
|
+
binds a `store` property to a store called `foo`. The code is saying _in the future, when the value
|
130
|
+
of "stores.earthquakes" changes, assign it to this object's "store" property_. In this case, `stores.earthquakes`
|
131
|
+
starts out undefined, then at runtime within a few milliseconds as the view model is processed, the configured
|
132
|
+
store is created and a reference is assigned to `stores.earthquakes`. That wakes the binding up, and the
|
133
|
+
value is assigned to the table's `store` property.
|
134
|
+
|
135
|
+
Replace each table's `store` config with the binding.
|
136
|
+
|
137
|
+
<pre data-javascript>
|
138
|
+
|
139
|
+
import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
|
140
|
+
import Controller from './MainViewController.mjs';
|
141
|
+
import EarthquakesTable from './earthquakes/Table.mjs';
|
142
|
+
import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
|
143
|
+
import ViewModel from './MainViewModel.mjs';
|
144
|
+
|
145
|
+
class MainView extends Base {
|
146
|
+
static config = {
|
147
|
+
className: 'Earthquakes.view.MainView',
|
148
|
+
ntype: 'earthquakes-main',
|
149
|
+
controller: {module: Controller},
|
150
|
+
model: {
|
151
|
+
module: ViewModel,
|
152
|
+
stores: {
|
153
|
+
earthquakes: {
|
154
|
+
module: Store,
|
155
|
+
model: {
|
156
|
+
fields: [{
|
157
|
+
name: "humanReadableLocation"
|
158
|
+
}, {
|
159
|
+
name: "size"
|
160
|
+
}, {
|
161
|
+
name: "timestamp",
|
162
|
+
type: "Date"
|
163
|
+
}]
|
164
|
+
},
|
165
|
+
url: "https://apis.is/earthquake/is",
|
166
|
+
responseRoot: "results",
|
167
|
+
autoLoad: true
|
168
|
+
},
|
169
|
+
}
|
170
|
+
},
|
171
|
+
|
172
|
+
layout: { ntype: 'vbox', align: 'stretch' },
|
173
|
+
items: [{
|
174
|
+
module: EarthquakesTable,
|
175
|
+
bind: {
|
176
|
+
store: 'stores.earthquakes'
|
177
|
+
},
|
178
|
+
style: {width: '100%'}
|
179
|
+
},{
|
180
|
+
module: EarthquakesTable,
|
181
|
+
bind: {
|
182
|
+
store: 'stores.earthquakes'
|
183
|
+
},
|
184
|
+
style: {width: '100%'}
|
185
|
+
}],
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
Neo.setupClass(MainView);
|
190
|
+
|
191
|
+
export default MainView;
|
192
|
+
</pre>
|
193
|
+
|
194
|
+
Save, refresh, and look at network traffic: you'll see a _single_ call to the web service.
|
195
|
+
|
196
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/EarthquakesTwoTablesOneCall.png"></img>
|
197
|
+
|
198
|
+
You can further prove we're using a shared instance by running these statements in the console.
|
199
|
+
|
200
|
+
<pre data-javascript>
|
201
|
+
a = Neo.findFirst({ntype:'earthquakes-main'}).model.stores.earthquakes;
|
202
|
+
b = Neo.find({ntype:'earthquakes-table'})[0].store;
|
203
|
+
c = Neo.find({ntype:'earthquakes-table'})[1].store;
|
204
|
+
|
205
|
+
(a === b) && (a === c) && (b === c) // true
|
206
|
+
</pre>
|
207
|
+
|
208
|
+
</details>
|
209
|
+
|
210
|
+
<details>
|
211
|
+
<summary>Use the view model class</summary>
|
212
|
+
|
213
|
+
We configured the view model in-line, in the `model` config at the top of `MainView`. But the starter app
|
214
|
+
has a `MainViewModel` class. In theory, if you have a trivial view model you could configure it in-line. But
|
215
|
+
in general you want to keep that code separate by coding it in a separate class. This is what we did for the
|
216
|
+
table config — we started by coding it in-line in the main view, then we refactored it into its own
|
217
|
+
class. The result was a simpler and more abstract main view. We want to do the same for the view model.
|
218
|
+
|
219
|
+
Since the starter app already provides `MainViewModel`, all we need to do is copy the `stores` property.
|
220
|
+
|
221
|
+
Here's the resulting code you should place into `MainViewModel.mjs`.
|
222
|
+
|
223
|
+
<pre data-javascript>
|
224
|
+
import Model from '../../../node_modules/neo.mjs/src/model/Component.mjs';
|
225
|
+
import Store from '../../../node_modules/neo.mjs/src/data/Store.mjs';
|
226
|
+
|
227
|
+
class MainViewModel extends Model {
|
228
|
+
static config = {
|
229
|
+
className: 'Earthquakes.view.MainViewModel',
|
230
|
+
|
231
|
+
data: {},
|
232
|
+
stores: {
|
233
|
+
earthquakes: {
|
234
|
+
module: Store,
|
235
|
+
model: {
|
236
|
+
fields: [{
|
237
|
+
name: "humanReadableLocation"
|
238
|
+
}, {
|
239
|
+
name: "size"
|
240
|
+
}, {
|
241
|
+
name: "timestamp",
|
242
|
+
type: "Date"
|
243
|
+
}]
|
244
|
+
},
|
245
|
+
url: "https://apis.is/earthquake/is",
|
246
|
+
responseRoot: "results",
|
247
|
+
autoLoad: true
|
248
|
+
},
|
249
|
+
}
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
Neo.setupClass(MainViewModel);
|
254
|
+
|
255
|
+
export default MainViewModel;
|
256
|
+
</pre>
|
257
|
+
|
258
|
+
And you need to remove the `stores` config from the main view as follows.
|
259
|
+
|
260
|
+
<pre data-javascript>
|
261
|
+
import Container from '../../../node_modules/neo.mjs/src/container/Base.mjs';
|
262
|
+
import Controller from './MainViewController.mjs';
|
263
|
+
import EarthquakesTable from './earthquakes/Table.mjs';
|
264
|
+
import ViewModel from './MainViewModel.mjs';
|
265
|
+
|
266
|
+
class MainView extends Container {
|
267
|
+
static config = {
|
268
|
+
className: 'Earthquakes.view.MainView',
|
269
|
+
ntype: 'earthquakes-main',
|
270
|
+
controller: {module: Controller},
|
271
|
+
model: {
|
272
|
+
module: ViewModel
|
273
|
+
},
|
274
|
+
|
275
|
+
layout: { ntype: 'vbox', align: 'stretch' },
|
276
|
+
items: [{
|
277
|
+
module: EarthquakesTable,
|
278
|
+
bind: {
|
279
|
+
store: 'stores.earthquakes'
|
280
|
+
},
|
281
|
+
style: {width: '100%'}
|
282
|
+
},{
|
283
|
+
module: EarthquakesTable,
|
284
|
+
bind: {
|
285
|
+
store: 'stores.earthquakes'
|
286
|
+
},
|
287
|
+
style: {width: '100%'}
|
288
|
+
}]
|
289
|
+
}
|
290
|
+
}
|
291
|
+
|
292
|
+
Neo.setupClass(MainView);
|
293
|
+
|
294
|
+
export default MainView;
|
295
|
+
</pre>
|
296
|
+
|
297
|
+
The refactorings to have separate table and view model classes means the code is more modular, more reusable,
|
298
|
+
and each class is simpler than using complex source files that try to configure every detail.
|
299
|
+
|
300
|
+
</details>
|
301
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
<details>
|
2
|
+
<summary>Get the code for the custom add-on</summary>
|
3
|
+
At the time this tutorial was written, the Neo.mjs Google Maps addon was about to be updated to
|
4
|
+
accomodate Google's "AdvancedMarker" class. Until that's ready, we're going to use a modified version of the add-on.
|
5
|
+
|
6
|
+
Download and unzip this file, and copy the two source files to the corresponding subdirectories in
|
7
|
+
your workspace's `src` directory. Note that `src` already contains some files, so don't replace the whole
|
8
|
+
directory, but instead, move the files to their individual locations.
|
9
|
+
|
10
|
+
<a href="https://s3.amazonaws.com/mjs.neo.learning.images/zip/src.zip">src.zip</a>
|
11
|
+
|
12
|
+
<img src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/CopyGoogleMapsFiles.png" width="30%%"></img>
|
13
|
+
</details>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary>Specify the main-thread add-on</summary>
|
17
|
+
|
18
|
+
Edit `apps/earthquakes/neo-config.json` and add entries for the Google Maps add-on and the map key.
|
19
|
+
|
20
|
+
<pre data-javascript>
|
21
|
+
{
|
22
|
+
"appPath": "../../apps/earthquakes/app.mjs",
|
23
|
+
"basePath": "../../",
|
24
|
+
"environment": "development",
|
25
|
+
"mainPath": "../node_modules/neo.mjs/src/Main.mjs",
|
26
|
+
"mainThreadAddons": [
|
27
|
+
"DragDrop",
|
28
|
+
"WS/GoogleMaps",
|
29
|
+
"Stylesheet"
|
30
|
+
],
|
31
|
+
"googleMapsApiKey": "AIzaSyD4Y2xvl9mGT8HiVvQiZluT5gah3OIveCE",
|
32
|
+
"themes" : ["neo-theme-neo-light"],
|
33
|
+
"workerBasePath": "../../node_modules/neo.mjs/src/worker/"
|
34
|
+
}
|
35
|
+
</pre>
|
36
|
+
|
37
|
+
It's unusual to need to edit `neo-config.json`. The app theme is specified there, and so are main thread add-ons.
|
38
|
+
In our case, we're adding `WS/GoogleMaps` which in turn requires that we specify the map key. The `WS/`
|
39
|
+
prefix tells Neo.mjs that the add-on is in our workspace, rather than an add-on provided by Neo.mjs.
|
40
|
+
|
41
|
+
Save and refresh, and you'll see a console log emanating from the plugin.
|
42
|
+
|
43
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/GoogleMapsLoaded.png"></img>
|
44
|
+
|
45
|
+
</details>
|
46
|
+
|
47
|
+
<details>
|
48
|
+
<summary>Add the required fields to the records</summary>
|
49
|
+
|
50
|
+
The Google Maps component has a `markerStore` property, which is a reference to a store whose records have
|
51
|
+
the properties `title` and `location`, where `location` is of the form `{lat: 0, lng: 0}`. The `fields:[]`
|
52
|
+
lets us implement those via two properties:
|
53
|
+
|
54
|
+
- `mapping` — the path to a feed property holding the value
|
55
|
+
- `calculate` — a function that returns a value
|
56
|
+
|
57
|
+
Edit `apps/earthquakes/view/MainViewModel.mjs` and modify `fields` as follows.
|
58
|
+
|
59
|
+
<pre data-javascript>
|
60
|
+
fields: [{
|
61
|
+
name: "humanReadableLocation",
|
62
|
+
}, {
|
63
|
+
name: "size",
|
64
|
+
}, {
|
65
|
+
name: "timestamp",
|
66
|
+
type: "Date",
|
67
|
+
}, {
|
68
|
+
name: 'title',
|
69
|
+
mapping: "humanReadableLocation"
|
70
|
+
}, {
|
71
|
+
name: "position",
|
72
|
+
calculate: (data, field, item)=>({lat: item.latitude, lng: item.longitude})
|
73
|
+
}],
|
74
|
+
</pre>
|
75
|
+
|
76
|
+
As you can see, _title_ is mapped to the existing feed value _humanReadableLocation_, and _position_ is
|
77
|
+
calculated by returning an object with _lat_ and _lng_ set to the corresponding values from the feed.
|
78
|
+
|
79
|
+
Save and refresh _earthquakes_. You can use the debugger to inspect the store via _Shift-Ctrl-right-click_ and
|
80
|
+
putting the main view into a global variable. Then run
|
81
|
+
|
82
|
+
temp1.getModel().stores.earthquakes.items
|
83
|
+
|
84
|
+
Look at one of the items and you should see that _title_ and _location_ are in each record.
|
85
|
+
|
86
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/StoreHasTitleAndLocation.png"></img>
|
87
|
+
|
88
|
+
</details>
|
89
|
+
|
90
|
+
<details>
|
91
|
+
<summary>Use the Google Map Component</summary>
|
92
|
+
|
93
|
+
We're going to replace the top table with a Google Map. To do that we need to import the Google Maps component
|
94
|
+
and show it implace of the top table. The map should be centered on Iceland. To wit
|
95
|
+
|
96
|
+
<pre>
|
97
|
+
{
|
98
|
+
module: GoogleMapsComponent,
|
99
|
+
flex: 1,
|
100
|
+
center: {
|
101
|
+
lat: 64.8014187,
|
102
|
+
lng: -18.3096357
|
103
|
+
},
|
104
|
+
zoom: 6
|
105
|
+
}
|
106
|
+
</pre>
|
107
|
+
|
108
|
+
If we replace the top table with the map, `view/MainView.mjs` ends up with this content.
|
109
|
+
|
110
|
+
<pre data-javascript>
|
111
|
+
|
112
|
+
import Container from '../container/Base.mjs';
|
113
|
+
import Controller from './MainViewController.mjs';
|
114
|
+
import EarthquakesTable from './earthquakes/Table.mjs';
|
115
|
+
import GoogleMapsComponent from '../component/wrapper/GoogleMaps.mjs';
|
116
|
+
import ViewModel from './MainViewModel.mjs';
|
117
|
+
|
118
|
+
class MainView extends Container {
|
119
|
+
static config = {
|
120
|
+
className: 'Earthquakes.view.MainView',
|
121
|
+
ntype: 'earthquakes-main',
|
122
|
+
controller: {module: Controller},
|
123
|
+
model: {
|
124
|
+
module: ViewModel
|
125
|
+
},
|
126
|
+
|
127
|
+
layout: { ntype: 'vbox', align: 'stretch' },
|
128
|
+
items: [{
|
129
|
+
module: GoogleMapsComponent,
|
130
|
+
flex: 1,
|
131
|
+
center: {
|
132
|
+
lat: 64.8014187,
|
133
|
+
lng: -18.3096357
|
134
|
+
},
|
135
|
+
zoom: 6
|
136
|
+
},{
|
137
|
+
module: EarthquakesTable,
|
138
|
+
bind: {
|
139
|
+
store: 'stores.earthquakes'
|
140
|
+
},
|
141
|
+
style: {width: '100%'},
|
142
|
+
wrapperStyle: {
|
143
|
+
height: 'auto' // Because neo-table-wrapper sets height:'100%', which it probably shouldn't
|
144
|
+
}
|
145
|
+
}],
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
Neo.setupClass(MainView);
|
150
|
+
|
151
|
+
export default MainView;
|
152
|
+
</pre>
|
153
|
+
|
154
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/CenteredMap.png"></img>
|
155
|
+
|
156
|
+
|
157
|
+
</details>
|
158
|
+
|
159
|
+
<details>
|
160
|
+
<summary>Show the markers</summary>
|
161
|
+
|
162
|
+
The markers are shown by setting up the marker store, which is a regular store whose records must contain
|
163
|
+
_location_ and _title_. We assign the store using a `bind`, just like we did with the tables.
|
164
|
+
|
165
|
+
Add this config to the map.
|
166
|
+
|
167
|
+
<pre data-javascript>
|
168
|
+
bind: {
|
169
|
+
markerStore: 'stores.earthquakes'
|
170
|
+
},
|
171
|
+
</pre>
|
172
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/InitialMapWithMarkers.png"></img>
|
173
|
+
|
174
|
+
</details>
|
175
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
In this lab you'll set up an event handler for the table and map.
|
2
|
+
|
3
|
+
<details>
|
4
|
+
<summary>Add a listener to the table</summary>
|
5
|
+
|
6
|
+
Tables fire a select event, passing an object that contains a reference to the corresponding row.
|
7
|
+
|
8
|
+
Add this table config:
|
9
|
+
|
10
|
+
listeners: {
|
11
|
+
select: (data) => console.log(data.record)
|
12
|
+
}
|
13
|
+
|
14
|
+
Save and refresh, then click on a table row. If you look at the debugger console you'll see the record being logged.
|
15
|
+
|
16
|
+
Just for fun, expand the logged value and look for the size property. If you recall, that's a value from the feed, and one of the things we configured in the store's fields:[].
|
17
|
+
|
18
|
+
In the console, click on the ellipses by size and enter a new value, like 2.5. (Don't enter a larger value, or you may destroy that part of Iceland.)
|
19
|
+
|
20
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/LogTableClick.png"></img>
|
21
|
+
|
22
|
+
After changing the value you should immediately see it reflected in the table row.
|
23
|
+
|
24
|
+
</details>
|
25
|
+
|
26
|
+
<details>
|
27
|
+
<summary>Add a listener to a map event
|
28
|
+
</summary>
|
29
|
+
|
30
|
+
Now add a `markerClick` listener to the Google Map.
|
31
|
+
|
32
|
+
listeners: {
|
33
|
+
markerClick: data => console.log(data.data.record)
|
34
|
+
},
|
35
|
+
|
36
|
+
Save, refresh, and confirm that you see the value logged when you click on a map marker.
|
37
|
+
|
38
|
+
</details>
|
@@ -532,7 +532,7 @@ class MainView extends Base {
|
|
532
532
|
type: "Date",
|
533
533
|
}],
|
534
534
|
},
|
535
|
-
url: "https://
|
535
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
536
536
|
responseRoot: "results",
|
537
537
|
autoLoad: true,
|
538
538
|
},
|
@@ -604,7 +604,7 @@ Here's the config for the store.
|
|
604
604
|
type: "Date",
|
605
605
|
}],
|
606
606
|
},
|
607
|
-
url: "https://
|
607
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
608
608
|
responseRoot: "results",
|
609
609
|
autoLoad: true,
|
610
610
|
}
|
@@ -773,7 +773,7 @@ static config = {
|
|
773
773
|
type: "Date",
|
774
774
|
}],
|
775
775
|
},
|
776
|
-
url: "https://
|
776
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
777
777
|
responseRoot: "results",
|
778
778
|
autoLoad: true,
|
779
779
|
},
|
@@ -896,7 +896,7 @@ class MainView extends Base {
|
|
896
896
|
type: "Date"
|
897
897
|
}]
|
898
898
|
},
|
899
|
-
url: "https://
|
899
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
900
900
|
responseRoot: "results",
|
901
901
|
autoLoad: true
|
902
902
|
},
|
@@ -920,7 +920,7 @@ class MainView extends Base {
|
|
920
920
|
type: "Date"
|
921
921
|
}]
|
922
922
|
},
|
923
|
-
url: "https://
|
923
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
924
924
|
responseRoot: "results",
|
925
925
|
autoLoad: true
|
926
926
|
},
|
@@ -939,7 +939,7 @@ class MainView extends Base {
|
|
939
939
|
type: "Date"
|
940
940
|
}]
|
941
941
|
},
|
942
|
-
url: "https://
|
942
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
943
943
|
responseRoot: "results",
|
944
944
|
autoLoad: true
|
945
945
|
},
|
@@ -1012,7 +1012,7 @@ class MainView extends Base {
|
|
1012
1012
|
type: "Date"
|
1013
1013
|
}]
|
1014
1014
|
},
|
1015
|
-
url: "https://
|
1015
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
1016
1016
|
responseRoot: "results",
|
1017
1017
|
autoLoad: true
|
1018
1018
|
},
|
@@ -1092,7 +1092,7 @@ class MainViewModel extends Model {
|
|
1092
1092
|
type: "Date"
|
1093
1093
|
}]
|
1094
1094
|
},
|
1095
|
-
url: "https://
|
1095
|
+
url: "https://nameless-tundra-27404.herokuapp.com/go/?fn=earthquakes",
|
1096
1096
|
responseRoot: "results",
|
1097
1097
|
autoLoad: true
|
1098
1098
|
},
|
File without changes
|
@@ -231,6 +231,7 @@ relating to changes to the store. The event handler if a function run when the e
|
|
231
231
|
A binding detects a changed view model value, and assigns it to a property.
|
232
232
|
|
233
233
|
|
234
|
+
|
234
235
|
### When to use an event
|
235
236
|
|
236
237
|
Events and event handlers are used when you need to run non-trivial logic in response to the event. For example, you
|
@@ -245,9 +246,87 @@ like a button click or component focus.
|
|
245
246
|
A binding is a way to keep properties in sync with values in the view model hierarchy. For example, button text,
|
246
247
|
field values, or store properties, can simultaneously reflect the same view model property. That's pretty handy,
|
247
248
|
but keep in mind that a class can define a property as a _lifecycle property_. That means that updating a property
|
248
|
-
can may result in complex logic being triggered. Furthermore, a _two way binding_
|
249
|
+
can may result in complex logic being triggered. Furthermore, a _two way binding_ means a change to a property
|
250
|
+
will automatically be reflected in the view model.
|
251
|
+
|
252
|
+
### A simple comparison
|
253
|
+
|
254
|
+
To contrast syntax, and to illustrate the simplicity of a binding, let's look at two exmaples of updating a component
|
255
|
+
to reflect the value of a text field. THe first example uses events; the second uses bindings.
|
256
|
+
|
257
|
+
<pre data-neo>
|
258
|
+
import Container from '../container/Base.mjs';
|
259
|
+
import TextField from '../form/field/Text.mjs';
|
260
|
+
import Component from '../component/Base.mjs';
|
249
261
|
|
262
|
+
class MainView extends Container {
|
263
|
+
static config = {
|
264
|
+
className: 'Example.view.MainView',
|
265
|
+
layout : {ntype:'vbox', align:'start'},
|
250
266
|
|
267
|
+
items: [{
|
268
|
+
module : TextField,
|
269
|
+
labelText: 'Text',
|
270
|
+
reference: 'textFieldOne',
|
271
|
+
value: 'Hello',
|
272
|
+
listeners: {
|
273
|
+
change : 'up.onTextChange'
|
274
|
+
}
|
275
|
+
}, {
|
276
|
+
module : TextField,
|
277
|
+
labelText: 'Text',
|
278
|
+
reference: 'textFieldTwo',
|
279
|
+
value: 'world!',
|
280
|
+
listeners: {
|
281
|
+
change : 'up.onTextChange'
|
282
|
+
}
|
283
|
+
}, {
|
284
|
+
module: Component,
|
285
|
+
reference: 'foo',
|
286
|
+
}]
|
287
|
+
}
|
288
|
+
onTextChange(data){
|
289
|
+
this.getReference('foo').html = `${this.getReference('textFieldOne').value} ${this.getReference('textFieldTwo').value}`
|
290
|
+
}
|
291
|
+
}
|
292
|
+
Neo.setupClass(MainView);
|
293
|
+
</pre>
|
294
|
+
|
295
|
+
<pre data-neo>
|
296
|
+
import Container from '../container/Base.mjs';
|
297
|
+
import TextField from '../form/field/Text.mjs';
|
298
|
+
import Component from '../component/Base.mjs';
|
299
|
+
|
300
|
+
class MainView extends Container {
|
301
|
+
static config = {
|
302
|
+
className: 'Example.view.MainView',
|
303
|
+
model: {
|
304
|
+
data: {
|
305
|
+
foo: 'Hello',
|
306
|
+
bar: 'world!'
|
307
|
+
}
|
308
|
+
},
|
309
|
+
layout : {ntype:'vbox', align:'start'},
|
310
|
+
|
311
|
+
items: [{
|
312
|
+
module : TextField,
|
313
|
+
labelText: 'Text',
|
314
|
+
bind: {value: {twoWay: true, value: data => data.foo}}
|
315
|
+
}, {
|
316
|
+
module : TextField,
|
317
|
+
labelText: 'Text',
|
318
|
+
bind: {value: {twoWay: true, value: data => data.bar}}
|
319
|
+
}, {
|
320
|
+
module: Component,
|
321
|
+
bind: {html: data => `${data.foo} ${data.bar}`}
|
322
|
+
}]
|
323
|
+
}
|
324
|
+
onTextChange(data){
|
325
|
+
this.getReference('foo').html = data.value;
|
326
|
+
}
|
327
|
+
}
|
328
|
+
Neo.setupClass(MainView);
|
329
|
+
</pre>
|
251
330
|
|
252
331
|
##
|
253
332
|
|