neo.mjs 6.13.0 → 6.15.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/apps/ServiceWorker.mjs +2 -2
- package/apps/covid/neo-config.json +1 -1
- package/apps/covid/view/HeaderContainer.mjs +2 -2
- package/apps/form/view/pages/Page10.mjs +2 -2
- package/apps/portal/view/ViewportController.mjs +5 -4
- package/apps/portal/view/home/ContentBox.mjs +80 -0
- package/apps/portal/view/home/MainContainer.mjs +51 -15
- package/apps/portal/view/learn/ContentTreeList.mjs +10 -3
- package/apps/portal/view/learn/MainContainerController.mjs +37 -5
- package/apps/portal/view/learn/MainContainerModel.mjs +51 -7
- package/apps/portal/view/learn/PageContainer.mjs +21 -9
- package/apps/sharedcovid/neo-config.json +1 -1
- package/apps/sharedcovid/view/HeaderContainer.mjs +2 -2
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/button/base/MainContainer.mjs +2 -2
- package/examples/button/split/MainContainer.mjs +2 -2
- package/examples/component/process/MainContainer.mjs +3 -3
- package/examples/component/process/realWorldExample/MainContainer.mjs +15 -15
- package/examples/component/toast/MainContainer.mjs +4 -4
- package/examples/dialog/DemoDialog.mjs +4 -4
- package/examples/fieldWithPrefix/MainContainer.mjs +4 -5
- package/examples/form/field/{select → combobox}/MainContainer.mjs +7 -10
- package/examples/form/field/{select → combobox}/MainModel.mjs +4 -4
- package/examples/form/field/{select → combobox}/MainStore.mjs +2 -2
- package/examples/form/field/{select → combobox}/app.mjs +1 -1
- package/examples/form/field/{select → combobox}/index.html +1 -1
- package/examples/form/field/{select → combobox}/neo-config.json +1 -1
- package/examples/tableFiltering/MainContainer.mjs +2 -2
- package/package.json +4 -4
- package/resources/data/deck/learnneo/pages/2023-10-14T19-25-08-153Z.md +16 -1
- package/resources/data/deck/learnneo/pages/ComponentsAndContainers.md +180 -0
- package/resources/data/deck/learnneo/pages/Config.md +11 -4
- package/resources/data/deck/learnneo/pages/DescribingTheUI.md +6 -0
- package/resources/data/deck/learnneo/pages/Earthquakes.md +36 -9
- package/resources/data/deck/learnneo/pages/Events.md +55 -43
- package/resources/data/deck/learnneo/pages/GuideEvents.md +9 -8
- package/resources/data/deck/learnneo/pages/References.md +14 -7
- package/resources/data/deck/learnneo/pages/TodoList.md +241 -0
- package/resources/data/deck/learnneo/pages/WhyNeo-Quick.md +6 -11
- package/resources/data/deck/learnneo/tree.json +2 -0
- package/resources/scss/src/apps/portal/home/ContentBox.scss +26 -0
- package/resources/scss/src/apps/portal/home/MainContainer.scss +4 -12
- package/resources/scss/src/apps/portal/learn/MainContainer.scss +0 -7
- package/resources/scss/src/apps/portal/learn/PageContainer.scss +35 -0
- package/resources/scss/src/apps/portal/learn/PageSectionsPanel.scss +8 -0
- package/resources/scss/src/form/field/{Select.scss → ComboBox.scss} +8 -8
- package/resources/scss/theme-dark/form/field/ComboBox.scss +9 -0
- package/resources/scss/theme-light/form/field/ComboBox.scss +9 -0
- package/resources/scss/theme-neo-light/form/field/ComboBox.scss +9 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/component/Base.mjs +16 -1
- package/src/container/Viewport.mjs +5 -2
- package/src/controller/Application.mjs +12 -1
- package/src/controller/Base.mjs +15 -4
- package/src/controller/Component.mjs +5 -1
- package/src/core/Observable.mjs +62 -22
- package/src/form/field/Base.mjs +21 -9
- package/src/form/field/Chip.mjs +3 -3
- package/src/form/field/Color.mjs +3 -3
- package/src/form/field/{Select.mjs → ComboBox.mjs} +179 -130
- package/src/form/field/Country.mjs +3 -3
- package/src/form/field/Text.mjs +7 -10
- package/src/form/field/_export.mjs +2 -2
- package/src/main/DomEvents.mjs +2 -1
- package/src/main/addon/MonacoEditor.mjs +2 -2
- package/src/main/addon/Navigator.mjs +27 -24
- package/src/selection/ListModel.mjs +13 -14
- package/src/selection/Model.mjs +10 -10
- package/src/toolbar/Paging.mjs +2 -2
- package/src/util/HashHistory.mjs +1 -0
- package/src/worker/App.mjs +5 -1
- package/src/worker/Manager.mjs +14 -7
- package/test/components/app.mjs +1 -1
- package/test/components/files/form/field/{Select.mjs → ComboBox.mjs} +9 -7
- package/test/components/siesta.js +1 -1
- package/resources/scss/theme-dark/form/field/Select.scss +0 -9
- package/resources/scss/theme-light/form/field/Select.scss +0 -9
- package/resources/scss/theme-neo-light/form/field/Select.scss +0 -9
@@ -1,3 +1,5 @@
|
|
1
|
+
## Introduction
|
2
|
+
|
1
3
|
All components fire events. For example, form fields fire a `change` event, various
|
2
4
|
focus events, and others. Some other types fire events too, such as `Neo.data.Store`,
|
3
5
|
which fires a `load` event after the store is loaded with data.
|
@@ -5,34 +7,35 @@ which fires a `load` event after the store is loaded with data.
|
|
5
7
|
Some terminology related to events is that events are _fired_, and as a result, some
|
6
8
|
event _handler_ — or _listener_ — is run.
|
7
9
|
|
10
|
+
## Listeners
|
11
|
+
|
8
12
|
To specify an event handler, use `listeners: {}`, specifying in as many event/handler
|
9
13
|
pairs as you need.
|
10
14
|
|
11
15
|
The code below shows two text fields, with `listeners` for `change` and `focusEnter`.
|
12
|
-
(The events for any component are
|
16
|
+
(The events for any component are documented in the API docs.)
|
13
17
|
|
14
18
|
<pre data-neo>
|
15
|
-
import
|
19
|
+
import Container from '../../../../src/container/Base.mjs';
|
16
20
|
import TextField from '../../../../src/form/field/Text.mjs';
|
17
|
-
|
21
|
+
|
22
|
+
class MainView extends Container {
|
18
23
|
static config = {
|
19
|
-
className
|
20
|
-
layout: {ntype:'vbox', align:'start'},
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
className: 'Example.view.MainView',
|
25
|
+
layout : {ntype:'vbox', align:'start'},
|
26
|
+
|
27
|
+
itemDefaults: {
|
28
|
+
module : TextField,
|
24
29
|
listeners: {
|
25
|
-
change: data => Neo.Main.log({value:data.value}),
|
30
|
+
change : data => Neo.Main.log({value:data.value}),
|
26
31
|
focusEnter: data => Neo.Main.log({value: `Entering ${data.component.labelText}`})
|
27
32
|
}
|
28
33
|
},
|
29
|
-
|
30
|
-
|
31
|
-
labelText
|
32
|
-
|
33
|
-
|
34
|
-
focusEnter: data => Neo.Main.log({value: `Entering ${data.component.labelText}`})
|
35
|
-
}
|
34
|
+
|
35
|
+
items: [{
|
36
|
+
labelText: 'First name'
|
37
|
+
}, {
|
38
|
+
labelText: 'Last name'
|
36
39
|
}]
|
37
40
|
}
|
38
41
|
}
|
@@ -42,37 +45,39 @@ Neo.setupClass(MainView);
|
|
42
45
|
If you run the example, and open the browser's debugger, you'll see the console being logged as you type or give
|
43
46
|
focus to either field.
|
44
47
|
|
48
|
+
## In-line or separated into a controller
|
49
|
+
|
45
50
|
Note that the handlers specify an in-line function. For trivial cases, that might be ok. But normally
|
46
51
|
you'd want better separation of concerns by placing those event handlers in a separate class. Neo.mjs provides
|
47
52
|
that with a _component controller_.
|
48
53
|
|
49
54
|
A `Neo.controller.Component` is a simple class associated with a component class. As a view is created, an
|
50
|
-
instance of its associated
|
55
|
+
instance of its associated controller is automatically created.
|
51
56
|
|
52
57
|
<pre data-neo>
|
53
|
-
import
|
54
|
-
import Controller
|
55
|
-
import TextField from
|
58
|
+
import Container from '../../../../src/container/Base.mjs';
|
59
|
+
import Controller from '../../../../src/controller/Component.mjs';
|
60
|
+
import TextField from '../../../../src/form/field/Text.mjs';
|
56
61
|
|
57
62
|
class MainViewController extends Controller {
|
58
63
|
static config = {
|
59
64
|
className: 'Example.view.MainViewController'
|
60
65
|
}
|
61
|
-
onChange(data){
|
62
|
-
|
66
|
+
onChange(data) {
|
67
|
+
Neo.Main.log({value: data.value})
|
63
68
|
}
|
64
69
|
}
|
65
70
|
Neo.setupClass(MainViewController);
|
66
71
|
|
67
72
|
|
68
|
-
class MainView extends
|
73
|
+
class MainView extends Container {
|
69
74
|
static config = {
|
70
75
|
className : 'Example.view.MainView',
|
71
76
|
controller: MainViewController,
|
72
|
-
layout: {ntype:'vbox', align:'start'},
|
73
|
-
items
|
74
|
-
module: TextField,
|
75
|
-
labelText
|
77
|
+
layout : {ntype:'vbox', align:'start'},
|
78
|
+
items : [{
|
79
|
+
module : TextField,
|
80
|
+
labelText: 'Name',
|
76
81
|
listeners: {
|
77
82
|
change: 'onChange'
|
78
83
|
}
|
@@ -88,12 +93,16 @@ to make it easier to see the source code for every class being used. But in an
|
|
88
93
|
actual applications the controller class would be coded in its own source file — named something
|
89
94
|
like `MainViewController.mjs` — and that would be imported into the view.)
|
90
95
|
|
96
|
+
## Neo.core.Observable
|
97
|
+
|
91
98
|
The ability to fire events and add listeners is provided by `Neo.core.Observable`, which is mixed into
|
92
99
|
classes that need that ability. All components are observable, `Neo.data.Store` is observable, and some
|
93
100
|
others. `Neo.core.Observable` introduces a few methods and properties, such as `listeners`, which
|
94
101
|
is used in the examples above, `on()` for procedurally adding an event listener, and `fire()`, which is
|
95
102
|
how you fire events in the custom classes you create.
|
96
103
|
|
104
|
+
## Firing an event when a value changes
|
105
|
+
|
97
106
|
Here's example illustrating how `fire()` is used. The code defines a `ToggleButton`
|
98
107
|
class, which is just a button with a `checked` property: the button shows a checked or unchecked
|
99
108
|
checkbox depending on the value of `checked`.
|
@@ -103,37 +112,40 @@ We'll discuss that at length later, but in a nutshell, config properties ending
|
|
103
112
|
automatically get lifecycle methods run before the value is assigned, after the value is assigned, and
|
104
113
|
before the value is accessed. We're using the _after_ method to fire a `change` event.
|
105
114
|
|
115
|
+
|
106
116
|
<pre data-neo>
|
107
|
-
import
|
108
|
-
import
|
109
|
-
import TextField from '../../../../src/form/field/Text.mjs';
|
117
|
+
import Button from '../../../../src/button/Base.mjs';
|
118
|
+
import Container from '../../../../src/container/Base.mjs';
|
110
119
|
|
111
120
|
class ToggleButton extends Button {
|
112
121
|
static config = {
|
113
122
|
className: 'Example.view.ToggleButton',
|
114
|
-
checked_: false
|
123
|
+
checked_ : false
|
115
124
|
}
|
116
|
-
afterSetChecked(checked){
|
117
|
-
this.iconCls = checked?'fa fa-square-check':'fa fa-square';
|
118
|
-
|
125
|
+
afterSetChecked(checked) {
|
126
|
+
this.iconCls = checked ? 'fa fa-square-check' : 'fa fa-square';
|
127
|
+
|
128
|
+
// This is where our custom event is being fired
|
129
|
+
this.fire('change', {component: this, checked})
|
119
130
|
}
|
120
|
-
onClick(data){
|
131
|
+
onClick(data) {
|
121
132
|
super.onClick(data);
|
122
|
-
this.checked = !this.checked
|
133
|
+
this.checked = !this.checked
|
123
134
|
}
|
124
135
|
}
|
125
136
|
Neo.setupClass(ToggleButton);
|
126
137
|
|
127
138
|
|
128
|
-
class MainView extends
|
139
|
+
class MainView extends Container {
|
129
140
|
static config = {
|
130
|
-
className
|
131
|
-
layout: {ntype:'vbox', align:'start'},
|
132
|
-
items
|
133
|
-
module: ToggleButton,
|
134
|
-
text: 'Toggle',
|
141
|
+
className: 'Example.view.MainView',
|
142
|
+
layout : {ntype:'vbox', align:'start'},
|
143
|
+
items : [{
|
144
|
+
module : ToggleButton,
|
145
|
+
text : 'Toggle',
|
135
146
|
listeners: {
|
136
|
-
|
147
|
+
// Here, we're listening to the custom event
|
148
|
+
change: data => Neo.Main.log({value: data.checked})
|
137
149
|
}
|
138
150
|
}]
|
139
151
|
}
|
@@ -1,12 +1,13 @@
|
|
1
1
|
[Add content on listeners options here]
|
2
2
|
|
3
|
-
How are events set up?
|
4
|
-
that can be mixed into any class. It maintains
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
How are events set up? The details don't really matter, but in case you're curious:
|
4
|
+
Neo.mjs has a `Neo.core.Observable` class that can be mixed into any class. It maintains
|
5
|
+
a `listeners` object map that's a key-value pair, where the key is the event name, and
|
6
|
+
the value is an array of function references. The first time a listener is added, an
|
7
|
+
entry is added to the map using the event name as the key, and the event handler added
|
8
|
+
as the first item in the associated array. If another listener is added for the same
|
9
|
+
event, a second item is added to the array. If a new event is added, a new entry is
|
10
|
+
added. Etc. When the event is fired, Neo.mjs looks up the map entry for the event name,
|
11
|
+
then runs each function in the array, passing whatever data is specified in the call to `fire()`.
|
11
12
|
|
12
13
|
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/gettingStarted/events/ObservableInMemory.png"></img>
|
@@ -1,3 +1,5 @@
|
|
1
|
+
## Introduction
|
2
|
+
|
1
3
|
Controllers often need to get references to components in order to update
|
2
4
|
the UI or access component properties.
|
3
5
|
There are two common ways of doing that:
|
@@ -5,11 +7,13 @@ There are two common ways of doing that:
|
|
5
7
|
- Using the component references passed to the event handler
|
6
8
|
- Tagging a component with a `reference` and using `this.getReference()` in the controller
|
7
9
|
|
10
|
+
## References are usually passed to event handlers
|
11
|
+
|
8
12
|
Here's an example with one button. Clicking on the button will disable it.
|
9
13
|
As you can see, the handler uses the component reference pass in via `data.component`.
|
10
14
|
|
11
15
|
<pre data-neo>
|
12
|
-
import Button from '../../../../src/
|
16
|
+
import Button from '../../../../src/button/Base.mjs';
|
13
17
|
import Container from '../../../../src/container/Base.mjs';
|
14
18
|
import Controller from '../../../../src/controller/Component.mjs';
|
15
19
|
|
@@ -39,12 +43,14 @@ class MainView extends Container {
|
|
39
43
|
Neo.setupClass(MainView);
|
40
44
|
</pre>
|
41
45
|
|
46
|
+
## Using getReference()
|
47
|
+
|
42
48
|
But what if we need to get a reference to another component in the view? In that case
|
43
49
|
you tag the component you need with a `reference` config, then use `getReference()` in
|
44
50
|
the controller.
|
45
51
|
|
46
52
|
<pre data-neo>
|
47
|
-
import Button from '../../../../src/
|
53
|
+
import Button from '../../../../src/button/Base.mjs';
|
48
54
|
import Container from '../../../../src/container/Base.mjs';
|
49
55
|
import Controller from '../../../../src/controller/Component.mjs';
|
50
56
|
|
@@ -53,10 +59,10 @@ class MainViewController extends Controller {
|
|
53
59
|
className: 'Example.view.MainViewController'
|
54
60
|
}
|
55
61
|
onDisableButtonClick(data){
|
56
|
-
data.component.disabled = true
|
62
|
+
data.component.disabled = true
|
57
63
|
}
|
58
64
|
onEnableButtonClick(data){
|
59
|
-
this.getReference('myButton').disabled = false
|
65
|
+
this.getReference('myButton').disabled = false
|
60
66
|
}
|
61
67
|
}
|
62
68
|
Neo.setupClass(MainViewController);
|
@@ -82,6 +88,7 @@ class MainView extends Container {
|
|
82
88
|
Neo.setupClass(MainView);
|
83
89
|
</pre>
|
84
90
|
|
91
|
+
## Getting a reference when debugging
|
85
92
|
|
86
93
|
There are other ways of getting references, but these are either non-standard or for debugging.
|
87
94
|
For example, components have an `up()` method, and containers have a `down()` method. These look
|
@@ -98,7 +105,7 @@ The following example gets a reference to the _Learn_ button at the top of this
|
|
98
105
|
Again — that use of `Neo.findFirst()` might be handy when debugging, but it should never be used in app logic.
|
99
106
|
|
100
107
|
<pre data-neo>
|
101
|
-
import Button from '../../../../src/
|
108
|
+
import Button from '../../../../src/button/Base.mjs';
|
102
109
|
import Container from '../../../../src/container/Base.mjs';
|
103
110
|
|
104
111
|
class MainView extends Container {
|
@@ -110,14 +117,14 @@ class MainView extends Container {
|
|
110
117
|
text : 'Change Learn caption',
|
111
118
|
handler: data=>{
|
112
119
|
const component = Neo.findFirst({text:'Learn'});
|
113
|
-
component?.set({text: 'Yikes!', ui: 'primary'})
|
120
|
+
component?.set({text: 'Yikes!', ui: 'primary'})
|
114
121
|
}
|
115
122
|
}, {
|
116
123
|
module : Button,
|
117
124
|
text : 'Restore Learn caption',
|
118
125
|
handler: data=>{
|
119
126
|
const component = Neo.findFirst({text:'Yikes!'});
|
120
|
-
component?.set({text: 'Learn', ui: 'ghost'})
|
127
|
+
component?.set({text: 'Learn', ui: 'ghost'})
|
121
128
|
}
|
122
129
|
}]
|
123
130
|
}
|
@@ -0,0 +1,241 @@
|
|
1
|
+
## HTML Style
|
2
|
+
|
3
|
+
In case you did not work with neo yet, but come from a more HTML driven ecosystem,
|
4
|
+
you could achieve the task in a similar way.
|
5
|
+
|
6
|
+
<pre data-neo>
|
7
|
+
import Component from '../../../../src/component/Base.mjs';
|
8
|
+
import NeoArray from '../../../../src/util/Array.mjs';
|
9
|
+
import VdomUtil from '../../../../src/util/VDom.mjs';
|
10
|
+
|
11
|
+
class MainComponent extends Component {
|
12
|
+
static config = {
|
13
|
+
className: 'Neo.examples.todoList.version1.MainComponent',
|
14
|
+
autoMount: true,
|
15
|
+
height : 200,
|
16
|
+
margin : 10,
|
17
|
+
maxWidth : 300,
|
18
|
+
style : {border: '1px solid #000', margin: '20px', overflow: 'scroll'},
|
19
|
+
width : 300,
|
20
|
+
|
21
|
+
items: [
|
22
|
+
{id: 1, done: true, text: 'Todo Item 1'},
|
23
|
+
{id: 2, done: false, text: 'Todo Item 2'},
|
24
|
+
{id: 3, done: false, text: 'Todo Item 3'}
|
25
|
+
],
|
26
|
+
|
27
|
+
inputValue: null,
|
28
|
+
|
29
|
+
vdom:
|
30
|
+
{cn: [
|
31
|
+
{tag: 'ol', cn: []},
|
32
|
+
{cn: [
|
33
|
+
{tag: 'input', cls: ['todo-input'], required: true, style: {marginLeft: '20px'}},
|
34
|
+
{tag: 'button', cls: ['todo-add-button'], html : 'Add Item', style: {marginLeft: '1em'}}
|
35
|
+
]}
|
36
|
+
]}
|
37
|
+
}
|
38
|
+
|
39
|
+
construct(config) {
|
40
|
+
super.construct(config);
|
41
|
+
|
42
|
+
let me = this;
|
43
|
+
|
44
|
+
me.addDomListeners([
|
45
|
+
{click: me.onAddButtonClick, delegate: 'todo-add-button'},
|
46
|
+
{click: me.onCheckIconClick, delegate: 'todo-item'},
|
47
|
+
{input: me.onInputFieldChange, delegate: 'todo-input'}
|
48
|
+
]);
|
49
|
+
|
50
|
+
me.createItems(me.items || [])
|
51
|
+
}
|
52
|
+
|
53
|
+
createItems(items) {
|
54
|
+
let me = this,
|
55
|
+
cls;
|
56
|
+
|
57
|
+
items.forEach(item => {
|
58
|
+
cls = ['todo-item'];
|
59
|
+
|
60
|
+
if (item.done) {
|
61
|
+
cls.push('fa', 'fa-check')
|
62
|
+
} else {
|
63
|
+
cls.push('far', 'fa-square')
|
64
|
+
}
|
65
|
+
|
66
|
+
me.vdom.cn[0].cn.push({
|
67
|
+
tag: 'li',
|
68
|
+
cn : [
|
69
|
+
{tag: 'span', cls, style: {cursor: 'pointer', width: '20px'}},
|
70
|
+
{vtype: 'text', html: item.text}
|
71
|
+
]
|
72
|
+
});
|
73
|
+
});
|
74
|
+
|
75
|
+
me.update()
|
76
|
+
}
|
77
|
+
|
78
|
+
onAddButtonClick() {
|
79
|
+
let me = this;
|
80
|
+
|
81
|
+
if (me.inputValue) {
|
82
|
+
me.createItems([{
|
83
|
+
id : null,
|
84
|
+
done: false,
|
85
|
+
text: me.inputValue
|
86
|
+
}])
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
onCheckIconClick(data) {
|
91
|
+
let me = this,
|
92
|
+
cls = ['far', 'fa-square'],
|
93
|
+
oldCls = ['fa', 'fa-check'],
|
94
|
+
node = VdomUtil.findVdomChild(me.vdom, data.path[0].id).vdom;
|
95
|
+
|
96
|
+
if (data.path[0].cls.includes('fa-square')) {
|
97
|
+
cls = ['fa', 'fa-check'];
|
98
|
+
oldCls = ['far', 'fa-square']
|
99
|
+
}
|
100
|
+
|
101
|
+
NeoArray.remove(node.cls, oldCls);
|
102
|
+
NeoArray.add(node.cls, cls);
|
103
|
+
|
104
|
+
me.update()
|
105
|
+
}
|
106
|
+
|
107
|
+
onInputFieldChange(data) {
|
108
|
+
this.inputValue = data.value
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
Neo.setupClass(MainComponent);
|
113
|
+
</pre>
|
114
|
+
|
115
|
+
## Neo Style
|
116
|
+
|
117
|
+
content
|
118
|
+
|
119
|
+
<pre data-neo>
|
120
|
+
import Container from '../../../../src/container/Base.mjs';
|
121
|
+
import List from '../../../../src/list/Base.mjs';
|
122
|
+
import Model from '../../../../src/data/Model.mjs';
|
123
|
+
import Store from '../../../../src/data/Store.mjs';
|
124
|
+
import TextField from '../../../../src/form/field/Text.mjs';
|
125
|
+
import Toolbar from '../../../../src/toolbar/Base.mjs';
|
126
|
+
|
127
|
+
class TodoListModel extends Model {
|
128
|
+
static config = {
|
129
|
+
className : 'Neo.examples.todoList.version2.MainModel',
|
130
|
+
keyProperty: 'id',
|
131
|
+
|
132
|
+
fields: [{
|
133
|
+
name: 'id',
|
134
|
+
type: 'Int'
|
135
|
+
}, {
|
136
|
+
name: 'done',
|
137
|
+
type: 'Boolean'
|
138
|
+
}, {
|
139
|
+
name: 'text',
|
140
|
+
type: 'String'
|
141
|
+
}]
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
Neo.setupClass(TodoListModel);
|
146
|
+
|
147
|
+
|
148
|
+
class TodoListStore extends Store {
|
149
|
+
static config = {
|
150
|
+
className : 'Neo.examples.todoList.version2.TodoListStore',
|
151
|
+
keyProperty: 'id',
|
152
|
+
model : TodoListModel,
|
153
|
+
|
154
|
+
data: [
|
155
|
+
{id: 1, done: true, text: 'Todo Item 1'},
|
156
|
+
{id: 2, done: false, text: 'Todo Item 2'},
|
157
|
+
{id: 3, done: false, text: 'Todo Item 3'}
|
158
|
+
],
|
159
|
+
|
160
|
+
sorters: [{
|
161
|
+
property : 'done',
|
162
|
+
direction: 'DESC'
|
163
|
+
}, {
|
164
|
+
property : 'id',
|
165
|
+
direction: 'ASC'
|
166
|
+
}]
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
Neo.setupClass(TodoListStore);
|
171
|
+
|
172
|
+
|
173
|
+
class MainContainer extends Container {
|
174
|
+
static config = {
|
175
|
+
className: 'Neo.examples.todoList.version2.MainContainer',
|
176
|
+
style : {padding: '20px'},
|
177
|
+
|
178
|
+
// custom configs
|
179
|
+
idCounter: 3,
|
180
|
+
store : null
|
181
|
+
}
|
182
|
+
|
183
|
+
construct(config) {
|
184
|
+
super.construct(config);
|
185
|
+
|
186
|
+
let me = this;
|
187
|
+
|
188
|
+
me.store = Neo.create({
|
189
|
+
module: TodoListStore
|
190
|
+
});
|
191
|
+
|
192
|
+
me.items = [{
|
193
|
+
module : List,
|
194
|
+
displayField : 'text',
|
195
|
+
flex : 1,
|
196
|
+
store : me.store,
|
197
|
+
style : {padding: '5px'},
|
198
|
+
useCheckBoxes: true
|
199
|
+
}, {
|
200
|
+
module: Toolbar,
|
201
|
+
flex : 'none',
|
202
|
+
dock : 'bottom',
|
203
|
+
items : [{
|
204
|
+
module : TextField,
|
205
|
+
flex : 1,
|
206
|
+
labelPosition: 'inline',
|
207
|
+
labelText : 'Item Text',
|
208
|
+
reference : 'addItemField'
|
209
|
+
}, '->', {
|
210
|
+
handler : me.onAddButtonClick,
|
211
|
+
handlerScope: me,
|
212
|
+
scope : me,
|
213
|
+
style : {height: '27px', marginLeft: '1em'},
|
214
|
+
text : 'Add Item'
|
215
|
+
}]
|
216
|
+
}];
|
217
|
+
}
|
218
|
+
|
219
|
+
onAddButtonClick() {
|
220
|
+
let me = this,
|
221
|
+
field = me.down({reference: 'addItemField'}),
|
222
|
+
data;
|
223
|
+
|
224
|
+
if (field.value) {
|
225
|
+
me.idCounter++;
|
226
|
+
|
227
|
+
data = me.store.data;
|
228
|
+
|
229
|
+
data.push({
|
230
|
+
id : me.idCounter,
|
231
|
+
done: false,
|
232
|
+
text: field.value
|
233
|
+
});
|
234
|
+
|
235
|
+
me.store.data = data
|
236
|
+
}
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
Neo.setupClass(MainContainer);
|
241
|
+
</pre>
|
@@ -1,21 +1,16 @@
|
|
1
1
|
Neo.mjs applications can be written quickly thanks to its syntax, features, and debugging convenience.
|
2
2
|
|
3
|
+
## Property lifecycle hooks
|
4
|
+
Neo.mjs classes let you specify properties in a way that allows code to detect "before" and "after" changes,
|
5
|
+
which makes it easy to handle value validation and transformation, and react to changes.
|
3
6
|
|
4
|
-
|
5
|
-
<summary><h3>Property lifecycle hooks</h3></summary>
|
6
|
-
Neo.mjs classes let you specify properties in a way that allows code to detect "before" and "after" changes, which makes it easy to handle value validation and transformation, and react to changes.
|
7
|
-
</details>
|
8
|
-
|
9
|
-
<details>
|
10
|
-
<summary><h3>Elegant state management</h3></summary>
|
7
|
+
## Elegant state management
|
11
8
|
Neo.mjs has elegant yet powerful state management features that make it easy to create shared, bindable data.
|
12
9
|
For example, if two components are bound to the same property, a change to the property will automatically be
|
13
10
|
applied to both components.
|
14
|
-
</details>
|
15
11
|
|
16
|
-
|
17
|
-
<summary><h3>Simple and powerful debugging</h3></summary>
|
12
|
+
## Simple and powerful debugging
|
18
13
|
Debugging is easy because Neo.mjs uses standard JavaScript, the Neo.mjs class config system, and built-in
|
19
14
|
debugging tools. For example, while developing an application you can click on a component, and in the debugger
|
20
15
|
easily inspect the component and update its properties — these updates are immediately reflected in the running application.
|
21
|
-
|
16
|
+
|
@@ -18,10 +18,12 @@
|
|
18
18
|
{"name": "Tutorials", "parentId": null, "isLeaf": false, "expanded": false, "id": "Tutorials"},
|
19
19
|
{"name": "Rock Scissors Paper", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "RSP", "hidden": true},
|
20
20
|
{"name": "Earthquakes", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "Earthquakes"},
|
21
|
+
{"name": "Todo List", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "TodoList"},
|
21
22
|
{"name": "Guides", "parentId": null, "isLeaf": false, "expanded": false, "id": "InDepth"},
|
22
23
|
{"name": "Config", "parentId": "InDepth", "isLeaf": false, "id": "Config"},
|
23
24
|
{"name": "Instance Lifecycle", "parentId": "InDepth", "isLeaf": false, "id": "InstanceLifecycle"},
|
24
25
|
{"name": "User Input (Forms)", "parentId": "InDepth", "isLeaf": false, "id": "Forms"},
|
26
|
+
{"name": "Components and Containers", "parentId": "InDepth", "isLeaf": true, "id": "ComponentsAndContainers"},
|
25
27
|
{"name": "Layouts", "parentId": "InDepth", "isLeaf": false, "id": "Layouts"},
|
26
28
|
{"name": "Custom Components", "parentId": "InDepth", "isLeaf": false, "id": "CustomComponents"},
|
27
29
|
{"name": "Events", "parentId": "InDepth", "isLeaf": true, "expanded": false, "id": "GuideEvents"},
|
@@ -0,0 +1,26 @@
|
|
1
|
+
.portal-content-box {
|
2
|
+
border : 1px solid darkgray;
|
3
|
+
cursor : pointer;
|
4
|
+
color : #000;
|
5
|
+
height : 200px;
|
6
|
+
margin-top : 2em;
|
7
|
+
padding : 0 1em 1em 1em;
|
8
|
+
text-decoration: none;
|
9
|
+
width : 300px;
|
10
|
+
|
11
|
+
&:hover {
|
12
|
+
background-color: lightblue;
|
13
|
+
}
|
14
|
+
|
15
|
+
&:not(:last-child) {
|
16
|
+
margin-right: 3em;
|
17
|
+
}
|
18
|
+
|
19
|
+
.portal-content-box-content {
|
20
|
+
|
21
|
+
}
|
22
|
+
|
23
|
+
.portal-content-box-header {
|
24
|
+
|
25
|
+
}
|
26
|
+
}
|
@@ -1,15 +1,11 @@
|
|
1
1
|
.newwebsite-viewport {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
justify-content: center !important;
|
6
|
-
padding : 10% 15% 15% 15%;
|
2
|
+
gap : 48px;
|
3
|
+
overflow-y: scroll;
|
4
|
+
padding : 5%;
|
7
5
|
}
|
8
6
|
|
9
7
|
.button-group {
|
10
|
-
|
11
|
-
flex-direction: row-reverse !important;
|
12
|
-
gap : 8px !important;
|
8
|
+
gap: 8px !important;
|
13
9
|
}
|
14
10
|
|
15
11
|
.neo-h1 {
|
@@ -26,7 +22,3 @@
|
|
26
22
|
height : 150px;
|
27
23
|
width : 100%;
|
28
24
|
}
|
29
|
-
|
30
|
-
.get-started-button {
|
31
|
-
cursor: not-allowed !important;
|
32
|
-
}
|