neo.mjs 6.13.0 → 6.14.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/covid/neo-config.json +1 -1
- 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/examples/form/field/select/MainContainer.mjs +1 -1
- package/package.json +3 -3
- 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/src/component/Base.mjs +16 -1
- 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/Select.mjs +166 -117
- package/src/form/field/Text.mjs +7 -10
- 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/util/HashHistory.mjs +1 -0
- package/src/worker/App.mjs +5 -1
- package/src/worker/Manager.mjs +14 -7
- package/test/components/files/form/field/Select.mjs +6 -4
@@ -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
|
-
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
.learn-content-container {
|
2
|
+
// align-items: center !important;
|
3
|
+
// padding: 0 3rem;
|
4
|
+
overflow: scroll;
|
5
|
+
|
6
|
+
.content-bottom-toolbar {
|
7
|
+
gap : 8px;
|
8
|
+
padding: 0 3rem;
|
9
|
+
|
10
|
+
.neo-button {
|
11
|
+
flex : 1 !important;
|
12
|
+
height : 75px;
|
13
|
+
justify-content: flex-start;
|
14
|
+
|
15
|
+
.neo-button-text, .neo-button-glyph {
|
16
|
+
padding-top: 16px;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
.content-bottom-toolbar-previous:before {
|
21
|
+
content : 'Previous Page';
|
22
|
+
font-family : var(--core-fontfamily-sans);
|
23
|
+
position : absolute;
|
24
|
+
top : var(--cmp-button-spacinghorizontal);
|
25
|
+
left : var(--cmp-button-spacinghorizontal);
|
26
|
+
}
|
27
|
+
.content-bottom-toolbar-next:before {
|
28
|
+
content : 'Next Page';
|
29
|
+
font-family : var(--core-fontfamily-sans);
|
30
|
+
position : absolute;
|
31
|
+
top : var(--cmp-button-spacinghorizontal);
|
32
|
+
right : var(--cmp-button-spacinghorizontal);
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
@@ -1,6 +1,14 @@
|
|
1
1
|
.portal-page-sections-panel.neo-panel {
|
2
2
|
border: none; // reset the default 1px
|
3
3
|
|
4
|
+
@media only screen and (max-width: 1200px) {
|
5
|
+
display: none;
|
6
|
+
}
|
7
|
+
|
8
|
+
.neo-list .neo-list-item {
|
9
|
+
white-space: normal;
|
10
|
+
}
|
11
|
+
|
4
12
|
.neo-panel-header-toolbar {
|
5
13
|
border : none; // reset the default 1px
|
6
14
|
border-bottom: 1px solid #f2f2f2;;
|
package/src/component/Base.mjs
CHANGED
@@ -933,6 +933,20 @@ class Base extends CoreBase {
|
|
933
933
|
this.changeVdomRootKey('width', value)
|
934
934
|
}
|
935
935
|
|
936
|
+
/**
|
937
|
+
* Triggered after the windowId config got changed
|
938
|
+
* @param {Number|null} value
|
939
|
+
* @param {Number|null} oldValue
|
940
|
+
* @protected
|
941
|
+
*/
|
942
|
+
afterSetWindowId(value, oldValue) {
|
943
|
+
let controller = this.controller;
|
944
|
+
|
945
|
+
if (controller && value) {
|
946
|
+
controller.windowId = value
|
947
|
+
}
|
948
|
+
}
|
949
|
+
|
936
950
|
/**
|
937
951
|
* Triggered after the wrapperCls config got changed
|
938
952
|
* @param {String[]|null} value
|
@@ -1119,7 +1133,8 @@ class Base extends CoreBase {
|
|
1119
1133
|
|
1120
1134
|
if (value) {
|
1121
1135
|
return ClassSystemUtil.beforeSetInstance(value, null, {
|
1122
|
-
component: this
|
1136
|
+
component: this,
|
1137
|
+
windowId : this.windowId
|
1123
1138
|
})
|
1124
1139
|
}
|
1125
1140
|
|
@@ -20,6 +20,11 @@ class Application extends Base {
|
|
20
20
|
* @protected
|
21
21
|
*/
|
22
22
|
className: 'Neo.controller.Application',
|
23
|
+
/**
|
24
|
+
* @member {String} ntype='application'
|
25
|
+
* @protected
|
26
|
+
*/
|
27
|
+
ntype: 'application',
|
23
28
|
/**
|
24
29
|
* @member {String|null} appThemeFolder=null
|
25
30
|
*/
|
@@ -50,7 +55,11 @@ class Application extends Base {
|
|
50
55
|
* @member {Boolean} rendering=false
|
51
56
|
* @protected
|
52
57
|
*/
|
53
|
-
rendering: false
|
58
|
+
rendering: false,
|
59
|
+
/**
|
60
|
+
* @member {Number|null} windowId=null
|
61
|
+
*/
|
62
|
+
windowId: null
|
54
63
|
}
|
55
64
|
|
56
65
|
/**
|
@@ -66,6 +75,8 @@ class Application extends Base {
|
|
66
75
|
|
67
76
|
let me = this;
|
68
77
|
|
78
|
+
me.windowId = Neo.config.windowId;
|
79
|
+
|
69
80
|
Neo.apps = Neo.apps || {};
|
70
81
|
|
71
82
|
Neo.apps[me.name] = me;
|
package/src/controller/Base.mjs
CHANGED
@@ -95,12 +95,18 @@ class Base extends CoreBase {
|
|
95
95
|
/**
|
96
96
|
*
|
97
97
|
*/
|
98
|
-
onConstructed() {
|
99
|
-
let
|
100
|
-
|
98
|
+
async onConstructed() {
|
99
|
+
let me = this,
|
100
|
+
currentHash = HashHistory.first(),
|
101
|
+
defaultHash = me.defaultHash;
|
102
|
+
|
103
|
+
// get outside the construction chain => a related cmp & vm has to be constructed too
|
104
|
+
await me.timeout(1);
|
101
105
|
|
102
106
|
if (currentHash) {
|
103
|
-
|
107
|
+
if (currentHash.windowId === me.windowId) {
|
108
|
+
await me.onHashChange(currentHash, null)
|
109
|
+
}
|
104
110
|
} else {
|
105
111
|
/*
|
106
112
|
* worker.App: onLoadApplication() will push config.hash into the HashHistory with a 5ms delay.
|
@@ -117,6 +123,11 @@ class Base extends CoreBase {
|
|
117
123
|
* @param {Object} oldValue
|
118
124
|
*/
|
119
125
|
async onHashChange(value, oldValue) {
|
126
|
+
// We only want to trigger hash changes for the same browser window (SharedWorker context)
|
127
|
+
if (value.windowId !== this.windowId) {
|
128
|
+
return
|
129
|
+
}
|
130
|
+
|
120
131
|
let me = this,
|
121
132
|
counter = 0,
|
122
133
|
hasRouteBeenFound = false,
|
package/src/core/Observable.mjs
CHANGED
@@ -29,28 +29,46 @@ class Observable extends Base {
|
|
29
29
|
* @param {Object} [scope]
|
30
30
|
* @param {String} [eventId]
|
31
31
|
* @param {Object} [data]
|
32
|
-
* @param {Number} [order]
|
32
|
+
* @param {Number|String} [order]
|
33
33
|
* @returns {String|null} eventId null in case an object gets passed as the name (multiple ids)
|
34
34
|
*/
|
35
35
|
addListener(name, opts, scope, eventId, data, order) {
|
36
36
|
let me = this,
|
37
|
+
delay = 0,
|
37
38
|
nameObject = typeof name === 'object',
|
39
|
+
once = false,
|
38
40
|
listener, existing, eventConfig;
|
39
41
|
|
40
42
|
if (nameObject) {
|
43
|
+
if (name.hasOwnProperty('delay')) {
|
44
|
+
delay = name.delay;
|
45
|
+
delete name.delay
|
46
|
+
}
|
47
|
+
|
48
|
+
if (name.hasOwnProperty('once')) {
|
49
|
+
once = name.once;
|
50
|
+
delete name.once
|
51
|
+
}
|
52
|
+
|
41
53
|
if (name.hasOwnProperty('scope')) {
|
42
54
|
scope = name.scope;
|
43
|
-
delete name.scope
|
55
|
+
delete name.scope
|
44
56
|
}
|
45
57
|
|
46
58
|
Object.entries(name).forEach(([key, value]) => {
|
47
|
-
|
48
|
-
|
59
|
+
if (Neo.isObject(value)) {
|
60
|
+
me.addListener(key, {delay, once, scope, ...value})
|
61
|
+
} else {
|
62
|
+
me.addListener(key, {delay, fn: value, once, scope})
|
63
|
+
}
|
64
|
+
})
|
49
65
|
} else if (typeof opts === 'object') {
|
50
|
-
|
51
|
-
listener = opts.fn;
|
52
|
-
order = order || opts.order;
|
66
|
+
delay = delay || opts.delay;
|
53
67
|
eventId = eventId || opts.eventId;
|
68
|
+
listener = opts.fn;
|
69
|
+
once = once || opts.once;
|
70
|
+
order = order || opts.order;
|
71
|
+
scope = scope || opts.scope;
|
54
72
|
} else if (typeof opts === 'function') {
|
55
73
|
listener = opts;
|
56
74
|
} else if (typeof opts === 'string') {
|
@@ -61,10 +79,12 @@ class Observable extends Base {
|
|
61
79
|
|
62
80
|
if (!nameObject) {
|
63
81
|
eventConfig = {
|
64
|
-
fn: listener,
|
65
|
-
scope,
|
66
82
|
data,
|
67
|
-
|
83
|
+
delay,
|
84
|
+
fn: listener,
|
85
|
+
id: eventId || Neo.getId('event'),
|
86
|
+
once,
|
87
|
+
scope
|
68
88
|
};
|
69
89
|
|
70
90
|
if (existing = me.listeners?.[name]) {
|
@@ -85,10 +105,10 @@ class Observable extends Base {
|
|
85
105
|
me.listeners[name] = [eventConfig];
|
86
106
|
}
|
87
107
|
|
88
|
-
return eventConfig.id
|
108
|
+
return eventConfig.id
|
89
109
|
}
|
90
110
|
|
91
|
-
return null
|
111
|
+
return null
|
92
112
|
}
|
93
113
|
|
94
114
|
/**
|
@@ -101,11 +121,22 @@ class Observable extends Base {
|
|
101
121
|
callback(fn, scope=this, args) {
|
102
122
|
if (fn) {
|
103
123
|
const handler = this.resolveCallback(fn, scope);
|
104
|
-
|
105
|
-
handler.fn.apply(handler.scope, args);
|
124
|
+
handler.fn.apply(handler.scope, args)
|
106
125
|
}
|
107
126
|
}
|
108
127
|
|
128
|
+
/**
|
129
|
+
* Internal helper method for events which use the delay option
|
130
|
+
* @param {Object} cb
|
131
|
+
* @param {Array} args
|
132
|
+
* @param {Number} delay
|
133
|
+
*/
|
134
|
+
delayedCallback(cb, args, delay) {
|
135
|
+
setTimeout(() => {
|
136
|
+
cb.fn.apply(cb.scope, args)
|
137
|
+
}, delay)
|
138
|
+
}
|
139
|
+
|
109
140
|
/**
|
110
141
|
* @param name
|
111
142
|
*/
|
@@ -113,29 +144,38 @@ class Observable extends Base {
|
|
113
144
|
let me = this,
|
114
145
|
args = [].slice.call(arguments, 1),
|
115
146
|
listeners = me.listeners,
|
116
|
-
handler, handlers, i, len;
|
147
|
+
delay, handler, handlers, i, len;
|
117
148
|
|
118
149
|
if (listeners && listeners[name]) {
|
119
150
|
handlers = [...listeners[name]];
|
120
|
-
len
|
151
|
+
len = handlers.length;
|
121
152
|
|
122
153
|
for (i = 0; i < len; i++) {
|
123
154
|
handler = handlers[i];
|
155
|
+
delay = handler.delay;
|
124
156
|
|
125
|
-
// Resolve function name on the scope (
|
157
|
+
// Resolve function name on the scope (or me), or, if it starts with 'up.'
|
126
158
|
// look in the ownership hierarchy from me.
|
127
159
|
const cb = me.resolveCallback(handler.fn, handler.scope || me);
|
128
160
|
|
129
|
-
// remove the listener
|
161
|
+
// remove the listener if the scope no longer exists
|
130
162
|
if (cb.scope && !cb.scope.id) {
|
131
|
-
listeners[name].splice(i, 1)
|
163
|
+
listeners[name].splice(i, 1)
|
132
164
|
} else {
|
133
165
|
if (!me.suspendEvents) {
|
134
166
|
// Object event format. Inject firer reference in as 'source'
|
135
|
-
if (args.length === 1 &&
|
136
|
-
args[0].source = me.id
|
167
|
+
if (args.length === 1 && Neo.isObject(args[0])) {
|
168
|
+
args[0].source = me.id
|
169
|
+
}
|
170
|
+
|
171
|
+
// remove the listener if it has the once flag
|
172
|
+
handler.once && listeners[name].splice(i, 1)
|
173
|
+
|
174
|
+
if (Neo.isNumber(delay) && delay > 0) {
|
175
|
+
me.delayedCallback(cb, handler.data ? args.concat(handler.data) : args, delay)
|
176
|
+
} else {
|
177
|
+
cb.fn.apply(cb.scope, handler.data ? args.concat(handler.data) : args)
|
137
178
|
}
|
138
|
-
cb.fn.apply(cb.scope, handler.data ? args.concat(handler.data) : args);
|
139
179
|
}
|
140
180
|
}
|
141
181
|
}
|