neo.mjs 6.10.12 → 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.
@@ -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>
@@ -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_ &mdash; or _listener_ &mdash; 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 &mdash; named something
89
+ like `MainViewController.mjs` &mdash; 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 &mdash; 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": "Cookbook", "parentId": null, "isLeaf": false, "expanded": false, "id": "InDepth"},
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"},
@@ -76,5 +76,4 @@
76
76
  .lab:hover {
77
77
  box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
78
78
  }
79
-
80
79
  }
@@ -105,6 +105,10 @@
105
105
  background-color: var(--list-item-background-color-active);
106
106
  color : var(--list-container-list-color);
107
107
  }
108
+ &:active {
109
+ background-color: var(--list-item-background-color-active);
110
+ color : var(--list-container-list-color);
111
+ }
108
112
  }
109
113
 
110
114
  &.neo-navigator-active-item {
@@ -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.12'
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.12'
244
+ version: '6.10.13'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
@@ -232,12 +232,17 @@ class Navigator extends Base {
232
232
  // Can navigate by index. This is useful if the active item is deleted.
233
233
  // We can navigate to the same index and preserve UI stability.
234
234
  if (typeof newActiveElement === 'number') {
235
- newActiveElement = data.subject.querySelectorAll(data.selector)[newActiveElement];
235
+ newActiveElement = data.subject.querySelectorAll(data.selector)?.[newActiveElement];
236
236
  }
237
237
  else if (typeof newActiveElement === 'string') {
238
238
  newActiveElement = DomAccess.getElement(newActiveElement);
239
239
  }
240
240
 
241
+ // Could not do what was asked because we could not find the requested item
242
+ if (!newActiveElement) {
243
+ return;
244
+ }
245
+
241
246
  // Find a focusable element which may be the item, or inside the item to draw focus to.
242
247
  // For example a Chip list in which .neo-list-items contain focusable Chips.
243
248
  const focusTarget = [newActiveElement, ...newActiveElement.querySelectorAll('*')].find(DomUtils.isFocusable);
@@ -16,7 +16,8 @@ class StringUtil extends Base {
16
16
  '"' : '&quot;',
17
17
  '\'': '&apos;',
18
18
  '$' : '&dollar;',
19
- '\\': '&bsol;'
19
+ '\\': '&bsol;',
20
+ '/' : '&sol;'
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 = /(&amp;)|(&lt;)|(&gt;)|(&quot;)|(&apos;)|(&dollar;)|(&bsol;)/g
31
+ static entityPattern = /(&amp;)|(&lt;)|(&gt;)|(&quot;)|(&apos;)|(&dollar;)|(&bsol;)|(&sol;)/g
31
32
 
32
33
  static config = {
33
34
  /**