neo.mjs 6.10.11 → 6.10.13
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/LivePreview.mjs +1 -0
- package/buildScripts/createAppMinimal.mjs +0 -30
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/toolbar/paging/view/MainContainer.mjs +6 -1
- package/package.json +5 -6
- package/resources/data/deck/learnneo/p/2023-10-14T19-25-08-153Z.md +0 -1
- package/resources/data/deck/learnneo/p/ComponentModels.md +26 -14
- package/resources/data/deck/learnneo/p/Earthquakes.md +1226 -31
- package/resources/data/deck/learnneo/p/Events.md +11 -0
- package/resources/data/deck/learnneo/p/Extending.md +1 -0
- package/resources/data/deck/learnneo/p/GuideEvents.md +153 -0
- package/resources/data/deck/learnneo/t.json +3 -1
- package/resources/scss/src/apps/portal/learn/ContentView.scss +0 -1
- package/resources/scss/src/list/Base.scss +8 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/form/field/Date.mjs +22 -1
- package/src/main/DomUtils.mjs +1 -1
- package/src/main/addon/Navigator.mjs +6 -2
- package/src/tooltip/Base.mjs +6 -2
- package/src/util/String.mjs +5 -4
@@ -140,3 +140,14 @@ class MainView extends Base {
|
|
140
140
|
}
|
141
141
|
Neo.applyClassConfig(MainView);
|
142
142
|
</pre>
|
143
|
+
|
144
|
+
How are events set up? We don't really care, but in case you're curious: Neo.mjs has a `Neo.core.Observable` class
|
145
|
+
that can be mixed into any class. It maintains a `listeners` object map that's a key-value pair, where
|
146
|
+
the key is the event name, and the value is an array of function references. The first time a listener is
|
147
|
+
added an entry is added to the map using the event name as the key, and the event handler added as the first
|
148
|
+
item in the associated array. If another listener is added for the same event, a second item is added to the
|
149
|
+
array. If a new event is added, a new entry is added. Etc. When the event is fired, Neo.mjs looks up the map
|
150
|
+
entry for the event name, then runs each function in the array, passing whatever data is specified in the
|
151
|
+
call to `fire()`.
|
152
|
+
|
153
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/gettingStarted/events/ObservableInMemory.png"></img>
|
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
In theory, a Neo.mjs app could be defined in a single `.mjs` source file. But that would be very hard to
|
2
3
|
maintain, and any reusable configs would have to be duplicated. Instead, each of your views and reusable
|
3
4
|
widgets will be defined as its own class. The result is simpler views which are inherently reusable and easier
|
@@ -0,0 +1,153 @@
|
|
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>
|
143
|
+
|
144
|
+
How are events set up? We don't really care, but in case you're curious: Neo.mjs has a `Neo.core.Observable` class
|
145
|
+
that can be mixed into any class. It maintains a `listeners` object map that's a key-value pair, where
|
146
|
+
the key is the event name, and the value is an array of function references. The first time a listener is
|
147
|
+
added an entry is added to the map using the event name as the key, and the event handler added as the first
|
148
|
+
item in the associated array. If another listener is added for the same event, a second item is added to the
|
149
|
+
array. If a new event is added, a new entry is added. Etc. When the event is fired, Neo.mjs looks up the map
|
150
|
+
entry for the event name, then runs each function in the array, passing whatever data is specified in the
|
151
|
+
call to `fire()`.
|
152
|
+
|
153
|
+
<img style="width:80%" src="https://s3.amazonaws.com/mjs.neo.learning.images/gettingStarted/events/ObservableInMemory.png"></img>
|
@@ -12,11 +12,13 @@
|
|
12
12
|
{"name": "Tutorials", "parentId": null, "isLeaf": false, "expanded": false, "id": "Tutorials"},
|
13
13
|
{"name": "Rock Scissors Paper", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "RSP"},
|
14
14
|
{"name": "Earthquakes", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "Earthquakes"},
|
15
|
-
{"name": "
|
15
|
+
{"name": "Guides", "parentId": null, "isLeaf": false, "expanded": false, "id": "InDepth"},
|
16
16
|
{"name": "Config", "parentId": "InDepth", "isLeaf": false, "id": "Config"},
|
17
17
|
{"name": "Instance Lifecycle", "parentId": "InDepth", "isLeaf": false, "id": "InstanceLifecycle"},
|
18
18
|
{"name": "User Input (Forms)", "parentId": "InDepth", "isLeaf": false, "id": "Forms"},
|
19
|
+
{"name": "Layouts", "parentId": "InDepth", "isLeaf": false, "id": "Layouts"},
|
19
20
|
{"name": "Custom Components", "parentId": "InDepth", "isLeaf": false, "id": "CustomComponents"},
|
21
|
+
{"name": "Events", "parentId": "InDepth", "isLeaf": true, "expanded": false, "id": "GuideEvents"},
|
20
22
|
{"name": "Tables (Stores)", "parentId": "InDepth", "isLeaf": false, "id": "Tables"},
|
21
23
|
{"name": "Shared Bindable Data (Component Models)", "parentId": "InDepth", "isLeaf": false, "id": "InDepthComponentModels"},
|
22
24
|
{"name": "Multi-Window Applications", "parentId": "InDepth", "isLeaf": false, "id": "MultiWindow"},
|
@@ -101,6 +101,14 @@
|
|
101
101
|
background-color: var(--list-item-background-color-active);
|
102
102
|
color : var(--list-container-list-color);
|
103
103
|
}
|
104
|
+
&:active {
|
105
|
+
background-color: var(--list-item-background-color-active);
|
106
|
+
color : var(--list-container-list-color);
|
107
|
+
}
|
108
|
+
&:active {
|
109
|
+
background-color: var(--list-item-background-color-active);
|
110
|
+
color : var(--list-container-list-color);
|
111
|
+
}
|
104
112
|
}
|
105
113
|
|
106
114
|
&.neo-navigator-active-item {
|
package/src/DefaultConfig.mjs
CHANGED
@@ -236,12 +236,12 @@ const DefaultConfig = {
|
|
236
236
|
useVdomWorker: true,
|
237
237
|
/**
|
238
238
|
* buildScripts/injectPackageVersion.mjs will update this value
|
239
|
-
* @default '6.10.
|
239
|
+
* @default '6.10.13'
|
240
240
|
* @memberOf! module:Neo
|
241
241
|
* @name config.version
|
242
242
|
* @type String
|
243
243
|
*/
|
244
|
-
version: '6.10.
|
244
|
+
version: '6.10.13'
|
245
245
|
};
|
246
246
|
|
247
247
|
Object.assign(DefaultConfig, {
|
package/src/form/field/Date.mjs
CHANGED
@@ -37,6 +37,10 @@ class DateField extends Picker {
|
|
37
37
|
* @member {String} errorTextInvalidDate='Not a valid date'
|
38
38
|
*/
|
39
39
|
errorTextInvalidDate: 'Not a valid date',
|
40
|
+
/**
|
41
|
+
* @member {Boolean} isoDate=false
|
42
|
+
*/
|
43
|
+
isoDate: false,
|
40
44
|
/**
|
41
45
|
* True to hide the DatePicker when selecting a day
|
42
46
|
* @member {Boolean} hidePickerOnSelect=false
|
@@ -158,6 +162,17 @@ class DateField extends Picker {
|
|
158
162
|
}
|
159
163
|
}
|
160
164
|
|
165
|
+
/**
|
166
|
+
* Triggered before the value config got changed
|
167
|
+
* @param {String} value
|
168
|
+
* @param {String} oldValue
|
169
|
+
* @protected
|
170
|
+
*/
|
171
|
+
beforeSetValue(value, oldValue) {
|
172
|
+
const val = super.beforeSetValue(value, oldValue);
|
173
|
+
return (this.isoDate && val) ? val.substring(0, 10) : val;
|
174
|
+
}
|
175
|
+
|
161
176
|
/**
|
162
177
|
* @returns {Neo.component.DateSelector}
|
163
178
|
*/
|
@@ -171,7 +186,13 @@ class DateField extends Picker {
|
|
171
186
|
getValue() {
|
172
187
|
let value = this.value;
|
173
188
|
|
174
|
-
|
189
|
+
if(this.submitDateObject && value) {
|
190
|
+
return new Date(`${value}T00:00:00.000Z`);
|
191
|
+
} else if(this.isoDate && value) {
|
192
|
+
return new Date(value).toISOString();
|
193
|
+
}
|
194
|
+
|
195
|
+
return value;
|
175
196
|
}
|
176
197
|
|
177
198
|
/**
|
package/src/main/DomUtils.mjs
CHANGED
@@ -31,7 +31,7 @@ export default class DomUtils extends Base {
|
|
31
31
|
* @param {HTMLElement} el The element to start from.
|
32
32
|
* @param {Function} filterFn A function which returns `true` when the desired element is reached.
|
33
33
|
* @param {HTMLElement} [limit] The element to stop at. This is *not* considered for matching.
|
34
|
-
* @returns
|
34
|
+
* @returns {Boolean}
|
35
35
|
*/
|
36
36
|
static closest(el, filterFn, limit = document.body) {
|
37
37
|
while (el?.nodeType === Node.ELEMENT_NODE && el !== limit) {
|
@@ -220,7 +220,6 @@ class Navigator extends Base {
|
|
220
220
|
* @param {String|Number} newActiveElement The id of the new active element in the subject
|
221
221
|
* element, or the index of the item.
|
222
222
|
* @param {Object} data The data block as passed to {@link #subscribe}
|
223
|
-
* @returns
|
224
223
|
*/
|
225
224
|
navigateTo(newActiveElement, data) {
|
226
225
|
if (!data.subject) {
|
@@ -233,12 +232,17 @@ class Navigator extends Base {
|
|
233
232
|
// Can navigate by index. This is useful if the active item is deleted.
|
234
233
|
// We can navigate to the same index and preserve UI stability.
|
235
234
|
if (typeof newActiveElement === 'number') {
|
236
|
-
newActiveElement = data.subject.querySelectorAll(data.selector)[newActiveElement];
|
235
|
+
newActiveElement = data.subject.querySelectorAll(data.selector)?.[newActiveElement];
|
237
236
|
}
|
238
237
|
else if (typeof newActiveElement === 'string') {
|
239
238
|
newActiveElement = DomAccess.getElement(newActiveElement);
|
240
239
|
}
|
241
240
|
|
241
|
+
// Could not do what was asked because we could not find the requested item
|
242
|
+
if (!newActiveElement) {
|
243
|
+
return;
|
244
|
+
}
|
245
|
+
|
242
246
|
// Find a focusable element which may be the item, or inside the item to draw focus to.
|
243
247
|
// For example a Chip list in which .neo-list-items contain focusable Chips.
|
244
248
|
const focusTarget = [newActiveElement, ...newActiveElement.querySelectorAll('*')].find(DomUtils.isFocusable);
|
package/src/tooltip/Base.mjs
CHANGED
@@ -261,14 +261,18 @@ class Base extends Container {
|
|
261
261
|
// If it's an internal move within the delegate, do nothing
|
262
262
|
if (currentTarget !== me.activeTarget?.id) {
|
263
263
|
me.activeTarget = Neo.get(currentTarget);
|
264
|
-
me.align.target = currentTarget;
|
265
|
-
me.align.targetMargin = 10;
|
266
264
|
|
265
|
+
// Allow listeners (eg the Tooltip singleton) which is shared between all Components
|
266
|
+
// listens for this in order to reconfigure itself from the activeTarget.
|
267
|
+
// So this event must be fired before the alignment is set up.
|
267
268
|
me.fire('targetOver', {
|
268
269
|
target : me.activeTarget,
|
269
270
|
data
|
270
271
|
});
|
271
272
|
|
273
|
+
me.align.target = currentTarget;
|
274
|
+
me.align.targetMargin = 10;
|
275
|
+
|
272
276
|
// Still visible, just realign
|
273
277
|
if (me.mounted) {
|
274
278
|
me.show();
|
package/src/util/String.mjs
CHANGED
@@ -16,7 +16,8 @@ class StringUtil extends Base {
|
|
16
16
|
'"' : '"',
|
17
17
|
'\'': ''',
|
18
18
|
'$' : '$',
|
19
|
-
'\\': '\'
|
19
|
+
'\\': '\',
|
20
|
+
'/' : '/'
|
20
21
|
}
|
21
22
|
/**
|
22
23
|
* @member {RegExp} charPattern
|
@@ -27,7 +28,7 @@ class StringUtil extends Base {
|
|
27
28
|
* @member {RegExp} entityPattern
|
28
29
|
* @static
|
29
30
|
*/
|
30
|
-
static entityPattern = /(&)|(<)|(>)|(")|(')|($)|(\)/g
|
31
|
+
static entityPattern = /(&)|(<)|(>)|(")|(')|($)|(\)|(/)/g
|
31
32
|
|
32
33
|
static config = {
|
33
34
|
/**
|
@@ -84,8 +85,8 @@ class StringUtil extends Base {
|
|
84
85
|
|
85
86
|
/**
|
86
87
|
* Returns the passed string with the first letter uncapitalized.
|
87
|
-
* @param {
|
88
|
-
* @returns
|
88
|
+
* @param {String} value
|
89
|
+
* @returns {String}
|
89
90
|
*/
|
90
91
|
static uncapitalize(value) {
|
91
92
|
return value && value[0].toLowerCase() + value.substring(1)
|