neo.mjs 6.10.9 → 6.10.11
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/apps/ServiceWorker.mjs +2 -2
- package/apps/portal/view/learn/ContentTreeList.mjs +24 -12
- package/apps/portal/view/learn/LivePreview.mjs +28 -11
- package/buildScripts/createAppMinimal.mjs +391 -0
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/button/base/neo-config.json +2 -1
- package/examples/list/chip/neo-config.json +1 -2
- package/package.json +72 -70
- package/resources/data/deck/learnneo/data/theBeatles.json +22 -0
- package/resources/data/deck/learnneo/p/2023-10-14T19-25-08-153Z.md +29 -20
- package/resources/data/deck/learnneo/p/ComponentModels.md +116 -1
- package/resources/data/deck/learnneo/p/Config.md +157 -0
- package/resources/data/deck/learnneo/p/DescribingTheUI.md +67 -1
- package/resources/data/deck/learnneo/p/Earthquakes.md +214 -0
- package/resources/data/deck/learnneo/p/Events.md +142 -1
- package/resources/data/deck/learnneo/p/Extending.md +116 -1
- package/resources/data/deck/learnneo/p/References.md +126 -0
- package/resources/data/deck/learnneo/p/TestLivePreview.md +28 -6
- package/resources/data/deck/learnneo/t.json +5 -6
- package/resources/data/deck/training/p/2022-12-27T21-55-30-948Z.md +1 -1
- package/resources/data/deck/training/p/2022-12-27T22-23-55-083Z.md +1 -1
- package/resources/data/deck/training/p/2022-12-29T16-00-13-223Z.md +1 -1
- package/resources/data/deck/training/p/2022-12-29T18-34-25-826Z.md +1 -1
- package/resources/data/deck/training/p/2022-12-29T18-36-56-893Z.md +1 -1
- package/resources/data/deck/training/p/2022-12-31T18-43-56-338Z.md +1 -1
- package/resources/data/deck/training/p/2022-12-31T18-51-50-682Z.md +1 -1
- package/resources/data/deck/training/p/2022-12-31T18-54-04-176Z.md +1 -1
- package/resources/data/deck/training/p/2023-01-01T17-49-18-429Z.md +1 -1
- package/resources/data/deck/training/p/2023-01-01T21-23-17-716Z.md +1 -1
- package/resources/data/deck/training/p/2023-01-06T23-21-31-685Z.md +1 -1
- package/resources/data/deck/training/p/2023-01-06T23-34-13-897Z.md +2 -2
- package/resources/data/deck/training/p/2023-01-06T23-46-36-687Z.md +1 -1
- package/resources/data/deck/training/p/2023-01-08T01-24-21-088Z.md +1 -1
- package/resources/data/deck/training/p/2023-01-08T02-11-26-333Z.md +2 -2
- package/resources/data/deck/training/p/2023-01-14T00-40-27-784Z.md +2 -2
- package/resources/data/deck/training/p/2023-07-31T00-37-21-927Z.md +2 -2
- package/resources/data/deck/training/p/2023-10-14T19-25-08-153Z.md +3 -3
- package/resources/scss/src/apps/newwebsite/Viewport.scss +32 -0
- package/resources/scss/src/apps/portal/learn/ContentView.scss +20 -4
- package/resources/scss/src/apps/portal/learn/LivePreview.scss +8 -0
- package/resources/scss/src/component/Base.scss +13 -4
- package/resources/scss/src/form/field/Select.scss +2 -5
- package/resources/scss/src/form/field/Text.scss +0 -1
- package/resources/scss/src/list/Base.scss +47 -2
- package/resources/scss/src/list/Chip.scss +10 -4
- package/resources/scss/theme-dark/list/Base.scss +11 -10
- package/resources/scss/theme-light/list/Base.scss +11 -10
- package/resources/scss/theme-neo-light/design-tokens/Components.scss +3 -0
- package/resources/scss/theme-neo-light/list/Base.scss +1 -0
- package/src/DefaultConfig.mjs +3 -3
- package/src/collection/Base.mjs +4 -0
- package/src/component/Base.mjs +7 -0
- package/src/container/Base.mjs +6 -12
- package/src/core/Base.mjs +5 -2
- package/src/data/Model.mjs +7 -0
- package/src/data/RecordFactory.mjs +5 -4
- package/src/form/field/Base.mjs +11 -0
- package/src/form/field/Picker.mjs +0 -1
- package/src/form/field/Select.mjs +208 -257
- package/src/form/field/Text.mjs +3 -3
- package/src/form/field/trigger/Base.mjs +5 -6
- package/src/layout/Flexbox.mjs +23 -31
- package/src/layout/HBox.mjs +1 -1
- package/src/layout/VBox.mjs +1 -1
- package/src/list/Base.mjs +64 -31
- package/src/main/DomAccess.mjs +55 -28
- package/src/main/DomEvents.mjs +2 -1
- package/src/main/DomUtils.mjs +66 -0
- package/src/main/addon/Navigator.mjs +332 -0
- package/src/manager/DomEvent.mjs +2 -1
- package/src/selection/ListModel.mjs +46 -82
- package/src/selection/Model.mjs +56 -33
- package/src/util/Array.mjs +5 -2
- package/src/util/Function.mjs +31 -0
- package/src/util/String.mjs +9 -0
- package/src/vdom/Helper.mjs +1 -2
- package/test/components/app.mjs +4 -3
- package/test/components/files/component/ChipList.mjs +125 -0
- package/test/components/files/form/field/Select.mjs +177 -2
- package/test/components/siesta.js +34 -1
@@ -0,0 +1,214 @@
|
|
1
|
+
##Introduction
|
2
|
+
|
3
|
+
In this topic you'll create an application that fetches data on earthquakes in Iceland,
|
4
|
+
and show the information in two views: a table, and a map.
|
5
|
+
|
6
|
+
You'll do this in a series of labs:
|
7
|
+
|
8
|
+
1. Generate a workspace
|
9
|
+
1. Generate a starter app
|
10
|
+
1. Learn some debugging tricks
|
11
|
+
1. Generate the earthquakes starter app
|
12
|
+
1. Refactor a config into its own class
|
13
|
+
1. Add a map
|
14
|
+
1. Listen to events
|
15
|
+
1. Make the app multi-window
|
16
|
+
|
17
|
+
##Advice
|
18
|
+
|
19
|
+
A word of advice: Keep a high-level perspective, especially early on. Throughout this
|
20
|
+
tutorial, and others, We'll have plenty of time to get into the code, and we'll do
|
21
|
+
most things multiple times.
|
22
|
+
|
23
|
+
##Lab. Generate a workspace
|
24
|
+
|
25
|
+
In this lab, you'll generate a Neo.mjs workspace and run the starter app.
|
26
|
+
|
27
|
+
<!-- lab -->
|
28
|
+
<details>
|
29
|
+
<summary>Use the command-line to generate the workspace</summary>
|
30
|
+
|
31
|
+
Use a terminal window to navigate to some parent folder,
|
32
|
+
then run
|
33
|
+
|
34
|
+
npx neo-app@latest
|
35
|
+
|
36
|
+
You'll be propted for a workspace name, starter app name, etc — accept the default for everything.
|
37
|
+
As the command finishes it starts a server and opens a browser window.
|
38
|
+
</details>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary>Inspect the workspace</summary>
|
42
|
+
|
43
|
+
The workspace contains a local copy of the API docs, an `apps` directory (where your apps are found),
|
44
|
+
and some other directories.
|
45
|
+
</details>
|
46
|
+
|
47
|
+
<details>
|
48
|
+
<summary>Start the server</summary>
|
49
|
+
From the root of the `workspace` start the server via `npm run server-start`. That starts a server
|
50
|
+
at port 8080 and opens a new browser window.
|
51
|
+
|
52
|
+
<img src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/StartServer.png" style="width:80%"/>
|
53
|
+
|
54
|
+
</details>
|
55
|
+
|
56
|
+
|
57
|
+
<details>
|
58
|
+
<summary>Run the starter app</summary>
|
59
|
+
|
60
|
+
By default, an app named `myapp` was created. You can run it by entering the `apps` directory and
|
61
|
+
clicking `myapp`. It's a folder containing an `index.html` along with the source code for the app.
|
62
|
+
|
63
|
+
<img src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/RunTheStarterApp.png" style="width:80%"/>
|
64
|
+
|
65
|
+
</details>
|
66
|
+
|
67
|
+
<!-- /lab -->
|
68
|
+
|
69
|
+
|
70
|
+
##Anatomy
|
71
|
+
|
72
|
+
The purpose of the lab was to generate a workspace, but as long as we're here let's take a look
|
73
|
+
at the `workspace/apps/myapp` directory.
|
74
|
+
|
75
|
+
- `view/MainContainer.mjs`
|
76
|
+
- `app.mjs`
|
77
|
+
- `index.html`
|
78
|
+
- `neo-config.json`
|
79
|
+
|
80
|
+
Application source is in `.mjs` files. These are standard _modular JavaScript_ files
|
81
|
+
with `import` and `export` statements, and class definitions. Neo.mjs apps have one class
|
82
|
+
definition per `.mjs` source file.
|
83
|
+
|
84
|
+
The index file contains a script tag that runs `MicroLoader.mjs`, which is a simple
|
85
|
+
file that launches the app based on information found in `neo-config.json`.
|
86
|
+
|
87
|
+
Don't worry about the file contents for now: we'll do that in the next lab.
|
88
|
+
|
89
|
+
##Flow of Execution
|
90
|
+
|
91
|
+
<img src="https://s3.amazonaws.com/mjs.neo.learning.images/FlowOfExecution.jpg" style="width:50%"/>
|
92
|
+
|
93
|
+
As you can see, `MicroLoader.mjs` runs `Main.mjs`, which in turn spawns the three web-wokers used by Neo.mjs:
|
94
|
+
|
95
|
+
- `neomjs-data-worker` handles Ajax calls and sockets
|
96
|
+
- `neomjs-vdom-worker` keeps track of the view (and applies delta updates to the main thread)
|
97
|
+
- `neomjs-app-worker` is where app logic is run
|
98
|
+
|
99
|
+
Neo.mjs apps run in multiple webworkers, and each webworker is run in a separate parallel thread.
|
100
|
+
Parallel processing — along wih the efficient way the `neomjs-vdom-worker` applies delta updates — is why Neo.mjs applications run so fast.
|
101
|
+
|
102
|
+
##Commonly-used Scripts
|
103
|
+
|
104
|
+
If you look in the `package.json` script block you'll see several scripts used for generating applications and classes,
|
105
|
+
doing builds, and starting a server. We'll use several of them throughout the tutorials.
|
106
|
+
|
107
|
+
- create-app — creates a simple demo app
|
108
|
+
- create-app-minimal — creates a application shell with no content
|
109
|
+
- server-start — starts a server with webroot set to the workspace
|
110
|
+
- build-all — builds minimized versions of your apps
|
111
|
+
- build-themes — creates app .css from .scss files found in `resources/scss`
|
112
|
+
- watch-themes — creates app .css as you save changes to any app
|
113
|
+
|
114
|
+
|
115
|
+
##Lab. Create the earthquakes starter app
|
116
|
+
|
117
|
+
<!-- lab -->
|
118
|
+
|
119
|
+
In this lab you'll create a starter app and add a single component.
|
120
|
+
|
121
|
+
<details>
|
122
|
+
|
123
|
+
<summary>Use the command-line to create a starter app</summary>
|
124
|
+
|
125
|
+
Use a terminal window to navigate to the workspace and run the following script. Use "Earthquakes"
|
126
|
+
as the app name, and <b>when prompted for "Main thread add-ons" choose GoogleMaps</b> (using arrow
|
127
|
+
keys and the space bar to toggle add-on options). Use defaults for everything else.
|
128
|
+
|
129
|
+
npm run generate-app-minimal
|
130
|
+
|
131
|
+
After the script runs yous should see these files in the `app/earthquakes` directory.
|
132
|
+
|
133
|
+
`view/MainContainer.mjs`
|
134
|
+
- `app.mjs`
|
135
|
+
- `index.html`
|
136
|
+
- `neo-config.json`
|
137
|
+
|
138
|
+
If you look in `neo-config.json` you should see this content. Note the `mainThreadAddons` block
|
139
|
+
— it specifies the default add-ons, as well as the GoogleMaps add-on you specified.
|
140
|
+
<pre>
|
141
|
+
{
|
142
|
+
"appPath": "../../apps/myapp/app.mjs",
|
143
|
+
"basePath": "../../",
|
144
|
+
"environment": "development",
|
145
|
+
"mainPath": "../node_modules/neo.mjs/src/Main.mjs",
|
146
|
+
"workerBasePath": "../../node_modules/neo.mjs/src/worker/",
|
147
|
+
"themes": [
|
148
|
+
"neo-theme-neo-light"
|
149
|
+
]
|
150
|
+
}
|
151
|
+
</pre>
|
152
|
+
|
153
|
+
When the script finishes you should see
|
154
|
+
|
155
|
+
You'll be propted for a workspace name, starter app name, etc — accept the default for everything.
|
156
|
+
As the command finishes it starts a server and opens a browser window.
|
157
|
+
</details>
|
158
|
+
|
159
|
+
<details>
|
160
|
+
<summary>Look at the main view source</summary>
|
161
|
+
|
162
|
+
</details>
|
163
|
+
|
164
|
+
<details>
|
165
|
+
<summary>Add a component</summary>
|
166
|
+
|
167
|
+
</details>
|
168
|
+
|
169
|
+
|
170
|
+
<details>
|
171
|
+
<summary>Start the server</summary>
|
172
|
+
|
173
|
+
</details>
|
174
|
+
|
175
|
+
<!-- /lab -->
|
176
|
+
|
177
|
+
##Introduction to Debugging
|
178
|
+
|
179
|
+
##Lab. Debugging
|
180
|
+
|
181
|
+
In this lab you'll get a little debugging practice by getting component references, changing properties,
|
182
|
+
and runing methods.
|
183
|
+
|
184
|
+
<!-- lab -->
|
185
|
+
|
186
|
+
<details>
|
187
|
+
<summary>Inspect the workspace</summary>
|
188
|
+
|
189
|
+
The workspace contains a local copy of the API docs, an `apps` directory (where your apps are found),
|
190
|
+
and some other directories.
|
191
|
+
</details>
|
192
|
+
|
193
|
+
<details>
|
194
|
+
<summary>Start the server</summary>
|
195
|
+
From the root of the `workspace` start the server via `npm run server-start`. That starts a server
|
196
|
+
at port 8080 and opens a new browser window.
|
197
|
+
|
198
|
+
<img src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/StartServer.png" style="width:80%"/>
|
199
|
+
|
200
|
+
</details>
|
201
|
+
|
202
|
+
|
203
|
+
<details>
|
204
|
+
<summary>Run the starter app</summary>
|
205
|
+
|
206
|
+
By default, an app named `myapp` was created. You can run it by entering the `apps` directory and
|
207
|
+
clicking `myapp`. It's a folder containing an `index.html` along with the source code for the app.
|
208
|
+
|
209
|
+
<img src="https://s3.amazonaws.com/mjs.neo.learning.images/earthquakes/RunTheStarterApp.png" style="width:80%"/>
|
210
|
+
|
211
|
+
|
212
|
+
</details>
|
213
|
+
|
214
|
+
<!-- /lab -->
|
@@ -1 +1,142 @@
|
|
1
|
-
|
1
|
+
All components fire events. For example, form fields fire a `change` event, various
|
2
|
+
focus events, and others. Some other types fire events too, such as `Neo.data.Store`,
|
3
|
+
which fires a `load` event after the store is loaded with data.
|
4
|
+
|
5
|
+
Some terminology related to events is that events are _fired_, and as a result, some
|
6
|
+
event _handler_ — or _listener_ — is run.
|
7
|
+
|
8
|
+
To specify an event handler, use `listeners: {}`, specifying in as many event/handler
|
9
|
+
pairs as you need.
|
10
|
+
|
11
|
+
The code below shows two text fields, with `listeners` for `change` and `focusEnter`.
|
12
|
+
(The events for any component are documened in the API docs.)
|
13
|
+
|
14
|
+
<pre data-neo>
|
15
|
+
import Base from '../../../../src/container/Base.mjs';
|
16
|
+
import TextField from '../../../../src/form/field/Text.mjs';
|
17
|
+
class MainView extends Base {
|
18
|
+
static config = {
|
19
|
+
className : 'Example.view.MainView',
|
20
|
+
layout: {ntype:'vbox', align:'start'},
|
21
|
+
items : [{
|
22
|
+
module: TextField,
|
23
|
+
labelText : 'First name',
|
24
|
+
listeners: {
|
25
|
+
change: data=>console.log(data.value), // There are other properties, like oldValue
|
26
|
+
focusEnter: data=>console.log(`Entering ${data.component.labelText}`)
|
27
|
+
}
|
28
|
+
},
|
29
|
+
{
|
30
|
+
module: TextField,
|
31
|
+
labelText : 'Last name',
|
32
|
+
listeners: {
|
33
|
+
change: data=>console.log(data.value), // There are other properties, like oldValue
|
34
|
+
focusEnter: data=>console.log(`Entering ${data.component.labelText}`)
|
35
|
+
}
|
36
|
+
}]
|
37
|
+
}
|
38
|
+
}
|
39
|
+
Neo.applyClassConfig(MainView);
|
40
|
+
</pre>
|
41
|
+
|
42
|
+
If you run the example, and open the browser's debugger, you'll see the console being logged as you type or give
|
43
|
+
focus to either field.
|
44
|
+
|
45
|
+
Note that the handlers specify an in-line function. For trivial cases, that might be ok. But normally
|
46
|
+
you'd want better separation of concerns by placing those event handlers in a separate class. Neo.mjs provides
|
47
|
+
that with a _component controller_.
|
48
|
+
|
49
|
+
A `Neo.controller.Component` is a simple class associated with a component class. As a view is created, an
|
50
|
+
instance of its associated contoller is automatically created.
|
51
|
+
|
52
|
+
<pre data-neo>
|
53
|
+
import Base from '../../../../src/container/Base.mjs';
|
54
|
+
import Controller from '../../../../src/controller/Component.mjs';
|
55
|
+
import TextField from '../../../../src/form/field/Text.mjs';
|
56
|
+
|
57
|
+
class MainViewController extends Controller {
|
58
|
+
static config = {
|
59
|
+
className: 'Example.view.MainViewController'
|
60
|
+
}
|
61
|
+
onChange(data){
|
62
|
+
console.log(data.value);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
Neo.applyClassConfig(MainViewController);
|
66
|
+
|
67
|
+
|
68
|
+
class MainView extends Base {
|
69
|
+
static config = {
|
70
|
+
className : 'Example.view.MainView',
|
71
|
+
controller: MainViewController,
|
72
|
+
layout: {ntype:'vbox', align:'start'},
|
73
|
+
items : [{
|
74
|
+
module: TextField,
|
75
|
+
labelText : 'Name',
|
76
|
+
listeners: {
|
77
|
+
change: 'onChange'
|
78
|
+
}
|
79
|
+
}]
|
80
|
+
}
|
81
|
+
}
|
82
|
+
Neo.applyClassConfig(MainView);
|
83
|
+
</pre>
|
84
|
+
|
85
|
+
(It's important to keep in mind that in Neo.mjs, all class definitions are coded in their own
|
86
|
+
source file: one class per file. In the examples we're putting all the relevant classes together
|
87
|
+
to make it easier to see the source code for every class being used. But in an
|
88
|
+
actual applications the controller class would be coded in its own source file — named something
|
89
|
+
like `MainViewController.mjs` — and that would be imported into the view.)
|
90
|
+
|
91
|
+
The ability to fire events and add listeners is provided by `Neo.core.Observable`, which is mixed into
|
92
|
+
classes that need that ability. All components are observable, `Neo.data.Store` is observable, and some
|
93
|
+
others. `Neo.core.Observable` introduces a few methods and properties, such as `listeners`, which
|
94
|
+
is used in the examples above, `on()` for procedurally adding an event listener, and `fire()`, which is
|
95
|
+
how you fire events in the custom classes you create.
|
96
|
+
|
97
|
+
Here's example illustrating how `fire()` is used. The code defines a `ToggleButton`
|
98
|
+
class, which is just a button with a `checked` property: the button shows a checked or unchecked
|
99
|
+
checkbox depending on the value of `checked`.
|
100
|
+
|
101
|
+
The code uses a special Neo.mjs feature you haven't seen yet — the use of an underscore property.
|
102
|
+
We'll discuss that at length later, but in a nutshell, config properties ending in an underscore
|
103
|
+
automatically get lifecycle methods run before the value is assigned, after the value is assigned, and
|
104
|
+
before the value is accessed. We're using the _after_ method to fire a `change` event.
|
105
|
+
|
106
|
+
<pre data-neo>
|
107
|
+
import Base from '../../../../src/container/Base.mjs';
|
108
|
+
import Button from '../../../../src/button/Base.mjs';
|
109
|
+
import TextField from '../../../../src/form/field/Text.mjs';
|
110
|
+
|
111
|
+
class ToggleButton extends Button {
|
112
|
+
static config = {
|
113
|
+
className: 'Example.view.ToggleButton',
|
114
|
+
checked_: false
|
115
|
+
}
|
116
|
+
afterSetChecked(checked){
|
117
|
+
this.iconCls = checked?'fa fa-square-check':'fa fa-square';
|
118
|
+
this.fire('change', {component: this, checked}); // This is where our custom event is being fired
|
119
|
+
}
|
120
|
+
onClick(data){
|
121
|
+
super.onClick(data);
|
122
|
+
this.checked = !this.checked;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
Neo.applyClassConfig(ToggleButton);
|
126
|
+
|
127
|
+
|
128
|
+
class MainView extends Base {
|
129
|
+
static config = {
|
130
|
+
className : 'Example.view.MainView',
|
131
|
+
layout: {ntype:'vbox', align:'start'},
|
132
|
+
items : [{
|
133
|
+
module: ToggleButton,
|
134
|
+
text: 'Toggle',
|
135
|
+
listeners: {
|
136
|
+
change: data => console.log(data.checked) // Here, we're listening to the custom event
|
137
|
+
}
|
138
|
+
}]
|
139
|
+
}
|
140
|
+
}
|
141
|
+
Neo.applyClassConfig(MainView);
|
142
|
+
</pre>
|
@@ -1 +1,116 @@
|
|
1
|
-
|
1
|
+
In theory, a Neo.mjs app could be defined in a single `.mjs` source file. But that would be very hard to
|
2
|
+
maintain, and any reusable configs would have to be duplicated. Instead, each of your views and reusable
|
3
|
+
widgets will be defined as its own class. The result is simpler views which are inherently reusable and easier
|
4
|
+
to test.
|
5
|
+
|
6
|
+
Consider this code. It's a panel with a header and a table. The table has a store.
|
7
|
+
|
8
|
+
<pre data-neo>
|
9
|
+
import Base from '../../../../src/container/Panel.mjs';
|
10
|
+
import Button from '../../../../src/button/Base.mjs';
|
11
|
+
import Table from '../../../../src/table/Container.mjs';
|
12
|
+
|
13
|
+
class MainView extends Base {
|
14
|
+
static config = {
|
15
|
+
className : 'Example.view.MainView',
|
16
|
+
headers: [{
|
17
|
+
dock: 'top',
|
18
|
+
items: [{
|
19
|
+
module: Button,
|
20
|
+
text: 'She loves me...',
|
21
|
+
handler: () => Neo.Main.alert({message: 'Yeah, yeah yeah!'})
|
22
|
+
}]
|
23
|
+
}],
|
24
|
+
items : [{
|
25
|
+
module: Table,
|
26
|
+
store: {
|
27
|
+
autoLoad: true,
|
28
|
+
url: '../../resources/data/deck/learnneo/data/theBeatles.json',
|
29
|
+
model: {
|
30
|
+
fields: [{name: 'first'}, {name: 'last'}, {name: 'dob', type: 'date'}]
|
31
|
+
}
|
32
|
+
},
|
33
|
+
columns: [{
|
34
|
+
text: 'First',
|
35
|
+
dataField: 'first',
|
36
|
+
}, {
|
37
|
+
text: 'Last',
|
38
|
+
dataField: 'last',
|
39
|
+
}]
|
40
|
+
}]
|
41
|
+
}
|
42
|
+
}
|
43
|
+
Neo.applyClassConfig(MainView);
|
44
|
+
</pre>
|
45
|
+
|
46
|
+
If you wanted, any of the configs can be refactored into their own class. Here, the button, store, and table
|
47
|
+
have been refactored into their own classes, and the main view is using them. The main view is simpler and
|
48
|
+
more abstract, and each class can be reused, tested, and maintained independently.
|
49
|
+
|
50
|
+
<pre data-neo>
|
51
|
+
import Base from '../../../../src/container/Panel.mjs';
|
52
|
+
import Button from '../../../../src/button/Base.mjs';
|
53
|
+
import Table from '../../../../src/table/Container.mjs';
|
54
|
+
import Store from '../../../../src/data/Store.mjs';
|
55
|
+
|
56
|
+
class BeatlesButton extends Button {
|
57
|
+
static config = {
|
58
|
+
className: 'Example.view.BeatlesButton',
|
59
|
+
text: 'She loves me...',
|
60
|
+
handler: () => Neo.Main.alert({message: 'Yeah, yeah yeah!'})
|
61
|
+
}
|
62
|
+
}
|
63
|
+
Neo.applyClassConfig(BeatlesButton);
|
64
|
+
|
65
|
+
class BeatlesStore extends Store {
|
66
|
+
static config = {
|
67
|
+
className: 'Example.view.BeatlesStore',
|
68
|
+
autoLoad: true,
|
69
|
+
url: '../../resources/data/deck/learnneo/data/theBeatles.json',
|
70
|
+
model: {
|
71
|
+
fields: [{name: 'first'}, {name: 'last'}, {name: 'dob', type: 'date'}]
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
Neo.applyClassConfig(BeatlesStore);
|
76
|
+
|
77
|
+
class BeatlesTable extends Table {
|
78
|
+
static config = {
|
79
|
+
className: 'Example.view.BeatlesTable',
|
80
|
+
columns: [{
|
81
|
+
text: 'First',
|
82
|
+
dataField: 'first',
|
83
|
+
}, {
|
84
|
+
text: 'Last',
|
85
|
+
dataField: 'last',
|
86
|
+
}]
|
87
|
+
}
|
88
|
+
}
|
89
|
+
Neo.applyClassConfig(BeatlesTable);
|
90
|
+
|
91
|
+
class MainView extends Base {
|
92
|
+
static config = {
|
93
|
+
className : 'Example.view.MainView',
|
94
|
+
headers: [{
|
95
|
+
dock: 'top',
|
96
|
+
items: [{
|
97
|
+
module: BeatlesButton,
|
98
|
+
}]
|
99
|
+
}],
|
100
|
+
items : [{
|
101
|
+
module: BeatlesTable,
|
102
|
+
store: {
|
103
|
+
module: BeatlesStore
|
104
|
+
},
|
105
|
+
}]
|
106
|
+
}
|
107
|
+
}
|
108
|
+
Neo.applyClassConfig(MainView);
|
109
|
+
</pre>
|
110
|
+
|
111
|
+
There are several use-cases for creating your own classes:
|
112
|
+
|
113
|
+
- For reuse
|
114
|
+
- To isolate complexity
|
115
|
+
- To add events or methods to the new class
|
116
|
+
- To test the component independently
|
@@ -0,0 +1,126 @@
|
|
1
|
+
Controllers often need to get references to components in order to update
|
2
|
+
the UI or access component properties.
|
3
|
+
There are two common ways of doing that:
|
4
|
+
|
5
|
+
- Using the component references passed to the event handler
|
6
|
+
- Tagging a component with a `reference` and using `this.getReference()` in the controller
|
7
|
+
|
8
|
+
Here's an example with one button. Clicking on the button will disable it.
|
9
|
+
As you can see, the handler uses the component reference pass in via `data.component`.
|
10
|
+
|
11
|
+
<pre data-neo>
|
12
|
+
import Base from '../../../../src/container/Base.mjs';
|
13
|
+
import Controller from '../../../../src/controller/Component.mjs';
|
14
|
+
import Button from '../../../../src/Button/Base.mjs';
|
15
|
+
|
16
|
+
class MainViewController extends Controller {
|
17
|
+
static config = {
|
18
|
+
className: 'Example.view.MainViewController'
|
19
|
+
}
|
20
|
+
onDisableButtonClick(data){
|
21
|
+
data.component.disabled = true;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
Neo.applyClassConfig(MainViewController);
|
25
|
+
|
26
|
+
|
27
|
+
class MainView extends Base {
|
28
|
+
static config = {
|
29
|
+
className : 'Example.view.MainView',
|
30
|
+
controller: MainViewController,
|
31
|
+
layout: {ntype:'vbox', align:'start'},
|
32
|
+
items : [{
|
33
|
+
module: Button,
|
34
|
+
text: 'Disable this button',
|
35
|
+
handler: 'onDisableButtonClick'
|
36
|
+
}]
|
37
|
+
}
|
38
|
+
}
|
39
|
+
Neo.applyClassConfig(MainView);
|
40
|
+
</pre>
|
41
|
+
|
42
|
+
But what if we need to get a reference to another component in the view? In that case
|
43
|
+
you tag the component you need with a `reference` config, then use `getReference()` in
|
44
|
+
the controller.
|
45
|
+
|
46
|
+
<pre data-neo>
|
47
|
+
import Base from '../../../../src/container/Base.mjs';
|
48
|
+
import Controller from '../../../../src/controller/Component.mjs';
|
49
|
+
import Button from '../../../../src/Button/Base.mjs';
|
50
|
+
|
51
|
+
class MainViewController extends Controller {
|
52
|
+
static config = {
|
53
|
+
className: 'Example.view.MainViewController'
|
54
|
+
}
|
55
|
+
onDisableButtonClick(data){
|
56
|
+
data.component.disabled = true;
|
57
|
+
}
|
58
|
+
onEnableButtonClick(data){
|
59
|
+
this.getReference('myButton').disabled = false;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
Neo.applyClassConfig(MainViewController);
|
63
|
+
|
64
|
+
|
65
|
+
class MainView extends Base {
|
66
|
+
static config = {
|
67
|
+
className : 'Example.view.MainView',
|
68
|
+
controller: MainViewController,
|
69
|
+
layout: {ntype:'vbox', align:'start'},
|
70
|
+
items : [{
|
71
|
+
module: Button,
|
72
|
+
reference: 'myButton',
|
73
|
+
text: 'Disable this button',
|
74
|
+
handler: 'onDisableButtonClick'
|
75
|
+
}, {
|
76
|
+
module: Button,
|
77
|
+
text: 'Enable the other button',
|
78
|
+
handler: 'onEnableButtonClick'
|
79
|
+
}]
|
80
|
+
}
|
81
|
+
}
|
82
|
+
Neo.applyClassConfig(MainView);
|
83
|
+
</pre>
|
84
|
+
|
85
|
+
|
86
|
+
There are other ways of getting references, but these are either non-standard or for debugging.
|
87
|
+
For example, components have an `up()` method, and containers have a `down()` method. These look
|
88
|
+
up or down the containment hierarchy to find the specified component. Using these methods is poor technique
|
89
|
+
becuse it violates principles of encapsulation and limiting scope.
|
90
|
+
|
91
|
+
There are also debugging methods, such as `Neo.findFirst()` which will find any component matching
|
92
|
+
the search param.
|
93
|
+
|
94
|
+
When you're debugging it's pretty handy to be able to inspect or interact with any component in the app.
|
95
|
+
But app logic should never use `Neo.findFirst()` and very rarely use `up()` or `down()`.
|
96
|
+
|
97
|
+
The following example gets a reference to the _Learn_ button at the top of this site, and changes its `text`.
|
98
|
+
Again — that use of `Neo.findFirst()` might be handy when debugging, but it should never be used in app logic.
|
99
|
+
|
100
|
+
<pre data-neo>
|
101
|
+
import Base from '../../../../src/container/Base.mjs';
|
102
|
+
import Button from '../../../../src/Button/Base.mjs';
|
103
|
+
|
104
|
+
class MainView extends Base {
|
105
|
+
static config = {
|
106
|
+
className : 'Example.view.MainView',
|
107
|
+
layout: {ntype:'vbox', align:'start'},
|
108
|
+
items : [{
|
109
|
+
module: Button,
|
110
|
+
text: 'Change Learn caption',
|
111
|
+
handler: data=>{
|
112
|
+
const component = Neo.findFirst({text:'Learn'});
|
113
|
+
if (component) component.text = 'Yikes!';
|
114
|
+
}
|
115
|
+
}, {
|
116
|
+
module: Button,
|
117
|
+
text: 'Restore Learn caption',
|
118
|
+
handler: data=>{
|
119
|
+
const component = Neo.findFirst({text:'Yikes!'});
|
120
|
+
if (component) component.text = 'Learn';
|
121
|
+
}
|
122
|
+
}]
|
123
|
+
}
|
124
|
+
}
|
125
|
+
Neo.applyClassConfig(MainView);
|
126
|
+
</pre>
|
@@ -1,10 +1,32 @@
|
|
1
|
+
To code a live preview, enclose the code in a `pre` tag with the `data-neo` attribute.
|
1
2
|
|
2
|
-
|
3
|
-
|
4
|
-
<pre data-neo>
|
5
|
-
let a = 1;
|
6
|
-
</pre>
|
3
|
+
<pre><pre data-neo>
|
4
|
+
</pre>
|
7
5
|
|
6
|
+
Imports are relative to the portal app running within the framework. That means
|
7
|
+
Neo.mjs imports should be coded to go up four levels, then look into the `src`
|
8
|
+
directory. For example, to import _container_, use `import Base from '../../../../src/container/Base.mjs`
|
8
9
|
|
10
|
+
You can define as many classes you need, such as component models and controllers, but the the _last_
|
11
|
+
class being defined is assumed to be the view being rendered. In other words, if the final class definition is a component, it's rendered.
|
9
12
|
|
10
|
-
|
13
|
+
<pre data-neo>
|
14
|
+
import Base from '../../../../src/container/Base.mjs';
|
15
|
+
import Button from '../../../../src/button/Base.mjs';
|
16
|
+
import Split from '../../../../src/button/Split.mjs';
|
17
|
+
class Bar extends Base {
|
18
|
+
static config = {
|
19
|
+
ntype: 'demoFoo',
|
20
|
+
className: 'Foo.Bar',
|
21
|
+
layout: {ntype: 'hbox'},
|
22
|
+
items: [{
|
23
|
+
module: Button,
|
24
|
+
text: 'Button One'
|
25
|
+
},{
|
26
|
+
module: Button,
|
27
|
+
text: 'Button Two'
|
28
|
+
}]
|
29
|
+
}
|
30
|
+
}
|
31
|
+
Neo.applyClassConfig(Bar);
|
32
|
+
</pre>
|
@@ -5,14 +5,13 @@
|
|
5
5
|
{"name": "Workspaces and Applications", "parentId": "GettingStarted", "isLeaf": true, "id": "2023-10-14T19-25-08-153Z"},
|
6
6
|
{"name": "Describing a View", "parentId": "GettingStarted", "isLeaf": true, "id": "DescribingTheUI"},
|
7
7
|
{"name": "Events", "parentId": "GettingStarted", "isLeaf": true, "id": "Events"},
|
8
|
-
{"name": "
|
9
|
-
{"name": "Extending
|
10
|
-
{"name": "
|
11
|
-
{"name": "
|
12
|
-
{"name": "What about HTML tags?", "parentId": "GettingStarted", "isLeaf": true, "id": "WhatAboutHTML"},
|
8
|
+
{"name": "Component References", "parentId": "GettingStarted", "isLeaf": true, "id": "References"},
|
9
|
+
{"name": "Extending Classes", "parentId": "GettingStarted", "isLeaf": true, "id": "Extending"},
|
10
|
+
{"name": "Config", "parentId": "GettingStarted", "isLeaf": true, "id": "Config"},
|
11
|
+
{"name": "Shared Bindable Data", "parentId": "GettingStarted", "isLeaf": true, "id": "ComponentModels"},
|
13
12
|
{"name": "Tutorials", "parentId": null, "isLeaf": false, "expanded": false, "id": "Tutorials"},
|
14
13
|
{"name": "Rock Scissors Paper", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "RSP"},
|
15
|
-
{"name": "Earthquakes", "parentId": "Tutorials", "isLeaf":
|
14
|
+
{"name": "Earthquakes", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "Earthquakes"},
|
16
15
|
{"name": "Cookbook", "parentId": null, "isLeaf": false, "expanded": false, "id": "InDepth"},
|
17
16
|
{"name": "Config", "parentId": "InDepth", "isLeaf": false, "id": "Config"},
|
18
17
|
{"name": "Instance Lifecycle", "parentId": "InDepth", "isLeaf": false, "id": "InstanceLifecycle"},
|
@@ -1 +1 @@
|
|
1
|
-
<img height="540" src="
|
1
|
+
<img height="540" src="https://s3.amazonaws.com/mjs.neo.learning.images/FlowOfExecution.jpg"></img>
|
@@ -1 +1 @@
|
|
1
|
-
<img height="580" src="
|
1
|
+
<img height="580" src="https://s3.amazonaws.com/mjs.neo.learning.images/intro/InitialTable.png"/>
|