neo.mjs 10.0.0-alpha.5 → 10.0.0-beta.2
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/ServiceWorker.mjs +2 -2
- package/apps/colors/view/GridContainer.mjs +1 -1
- package/apps/covid/view/AttributionComponent.mjs +1 -1
- package/apps/covid/view/HeaderContainer.mjs +6 -6
- package/apps/covid/view/MainContainerController.mjs +5 -5
- package/apps/covid/view/TableContainerController.mjs +1 -1
- package/apps/covid/view/country/Gallery.mjs +13 -13
- package/apps/covid/view/country/Helix.mjs +13 -13
- package/apps/covid/view/country/HistoricalDataTable.mjs +1 -1
- package/apps/email/view/Viewport.mjs +2 -2
- package/apps/form/view/SideNavList.mjs +1 -1
- package/apps/portal/index.html +1 -1
- package/apps/portal/resources/data/examples_devmode.json +26 -27
- package/apps/portal/resources/data/examples_dist_dev.json +26 -27
- package/apps/portal/resources/data/examples_dist_esm.json +25 -26
- package/apps/portal/resources/data/examples_dist_prod.json +26 -27
- package/apps/portal/view/HeaderToolbar.mjs +3 -3
- package/apps/portal/view/about/Container.mjs +2 -2
- package/apps/portal/view/about/MemberContainer.mjs +3 -3
- package/apps/portal/view/blog/List.mjs +7 -7
- package/apps/portal/view/examples/List.mjs +4 -4
- package/apps/portal/view/home/ContentBox.mjs +2 -2
- package/apps/portal/view/home/FeatureSection.mjs +3 -3
- package/apps/portal/view/home/FooterContainer.mjs +7 -7
- package/apps/portal/view/home/parts/AfterMath.mjs +3 -3
- package/apps/portal/view/home/parts/MainNeo.mjs +3 -3
- package/apps/portal/view/home/parts/References.mjs +6 -6
- package/apps/portal/view/learn/ContentComponent.mjs +102 -111
- package/apps/portal/view/learn/PageSectionsContainer.mjs +1 -1
- package/apps/portal/view/learn/PageSectionsList.mjs +2 -2
- package/apps/portal/view/services/Component.mjs +16 -16
- package/apps/realworld/view/FooterComponent.mjs +1 -1
- package/apps/realworld/view/HeaderComponent.mjs +8 -8
- package/apps/realworld/view/HomeComponent.mjs +6 -6
- package/apps/realworld/view/article/CommentComponent.mjs +4 -4
- package/apps/realworld/view/article/Component.mjs +14 -14
- package/apps/realworld/view/article/CreateCommentComponent.mjs +3 -3
- package/apps/realworld/view/article/CreateComponent.mjs +3 -3
- package/apps/realworld/view/article/PreviewComponent.mjs +1 -1
- package/apps/realworld/view/article/TagListComponent.mjs +2 -2
- package/apps/realworld/view/user/ProfileComponent.mjs +8 -8
- package/apps/realworld/view/user/SettingsComponent.mjs +4 -4
- package/apps/realworld/view/user/SignUpComponent.mjs +4 -4
- package/apps/realworld2/view/FooterComponent.mjs +1 -1
- package/apps/realworld2/view/HomeContainer.mjs +3 -3
- package/apps/realworld2/view/article/DetailsContainer.mjs +1 -1
- package/apps/realworld2/view/article/PreviewComponent.mjs +7 -7
- package/apps/realworld2/view/article/TagListComponent.mjs +2 -2
- package/apps/realworld2/view/user/ProfileContainer.mjs +1 -1
- package/apps/route/view/center/CardAdministration.mjs +2 -2
- package/apps/route/view/center/CardAdministrationDenied.mjs +1 -1
- package/apps/route/view/center/CardContact.mjs +2 -2
- package/apps/route/view/center/CardHome.mjs +1 -1
- package/apps/route/view/center/CardSection1.mjs +1 -1
- package/apps/route/view/center/CardSection2.mjs +1 -1
- package/apps/sharedcovid/view/AttributionComponent.mjs +1 -1
- package/apps/sharedcovid/view/HeaderContainer.mjs +6 -6
- package/apps/sharedcovid/view/MainContainerController.mjs +5 -5
- package/apps/sharedcovid/view/TableContainerController.mjs +1 -1
- package/apps/sharedcovid/view/country/Gallery.mjs +13 -13
- package/apps/sharedcovid/view/country/Helix.mjs +13 -13
- package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -1
- package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +1 -1
- package/apps/shareddialog/view/MainContainer.mjs +1 -1
- package/buildScripts/createApp.mjs +2 -2
- package/examples/table/cellEditing/MainContainer.mjs +1 -1
- package/examples/table/container/MainContainer.mjs +3 -3
- package/examples/table/nestedRecordFields/Viewport.mjs +6 -6
- package/examples/tableFiltering/MainContainer.mjs +1 -1
- package/examples/tablePerformance/MainContainer.mjs +1 -1
- package/examples/tablePerformance/MainContainer2.mjs +1 -1
- package/examples/tablePerformance/MainContainer3.mjs +2 -2
- package/examples/tableStore/MainContainer.mjs +2 -2
- package/learn/Glossary.md +261 -0
- package/learn/UsingTheseTopics.md +2 -2
- package/learn/benefits/ConfigSystem.md +538 -28
- package/learn/benefits/Effort.md +47 -2
- package/learn/benefits/Features.md +50 -32
- package/learn/benefits/FormsEngine.md +68 -38
- package/learn/benefits/MultiWindow.md +33 -7
- package/learn/benefits/OffTheMainThread.md +2 -2
- package/learn/benefits/Quick.md +45 -12
- package/learn/benefits/RPCLayer.md +75 -0
- package/learn/benefits/Speed.md +16 -11
- package/learn/gettingstarted/ComponentModels.md +4 -4
- package/learn/gettingstarted/Config.md +6 -6
- package/learn/gettingstarted/DescribingTheUI.md +4 -4
- package/learn/gettingstarted/Events.md +6 -6
- package/learn/gettingstarted/Extending.md +4 -4
- package/learn/gettingstarted/References.md +6 -6
- package/learn/gettingstarted/Workspaces.md +6 -6
- package/learn/guides/ApplicationBootstrap.md +26 -26
- package/learn/guides/ComponentsAndContainers.md +12 -12
- package/learn/guides/ConfigSystemDeepDive.md +280 -0
- package/learn/guides/CustomComponents.md +2 -2
- package/learn/guides/DeclarativeComponentTreesVsImperativeVdom.md +17 -17
- package/learn/guides/InstanceLifecycle.md +295 -1
- package/learn/guides/MainThreadAddons.md +475 -0
- package/learn/guides/PortalApp.md +2 -2
- package/learn/guides/StateProviders.md +12 -12
- package/learn/guides/WorkingWithVDom.md +14 -14
- package/learn/guides/events/CustomEvents.md +16 -16
- package/learn/guides/events/DomEvents.md +12 -12
- package/learn/javascript/ClassFeatures.md +3 -2
- package/learn/javascript/Classes.md +8 -8
- package/learn/javascript/NewNode.md +4 -4
- package/learn/javascript/Overrides.md +8 -8
- package/learn/javascript/Super.md +10 -8
- package/learn/tree.json +52 -51
- package/learn/tutorials/Earthquakes.md +54 -57
- package/learn/tutorials/TodoList.md +4 -4
- package/package.json +2 -2
- package/resources/scss/src/apps/portal/learn/ContentComponent.scss +12 -0
- package/resources/scss/src/table/{View.scss → Body.scss} +1 -1
- package/resources/scss/src/table/plugin/CellEditing.scss +1 -1
- package/resources/scss/theme-dark/table/{View.scss → Body.scss} +1 -1
- package/resources/scss/theme-light/table/{View.scss → Body.scss} +1 -1
- package/resources/scss/theme-neo-light/Global.scss +1 -2
- package/resources/scss/theme-neo-light/table/{View.scss → Body.scss} +1 -1
- package/src/DefaultConfig.mjs +2 -2
- package/src/Main.mjs +8 -7
- package/src/Neo.mjs +16 -2
- package/src/button/Base.mjs +2 -2
- package/src/calendar/view/SettingsContainer.mjs +2 -2
- package/src/calendar/view/YearComponent.mjs +9 -9
- package/src/calendar/view/calendars/ColorsList.mjs +1 -1
- package/src/calendar/view/calendars/List.mjs +1 -1
- package/src/calendar/view/month/Component.mjs +15 -15
- package/src/calendar/view/week/Component.mjs +12 -12
- package/src/calendar/view/week/EventDragZone.mjs +4 -4
- package/src/calendar/view/week/TimeAxisComponent.mjs +3 -3
- package/src/component/Base.mjs +17 -2
- package/src/component/Carousel.mjs +2 -2
- package/src/component/Chip.mjs +3 -3
- package/src/component/Circle.mjs +2 -2
- package/src/component/DateSelector.mjs +8 -8
- package/src/component/Helix.mjs +1 -1
- package/src/component/Label.mjs +3 -18
- package/src/component/Legend.mjs +3 -3
- package/src/component/MagicMoveText.mjs +6 -14
- package/src/component/Process.mjs +3 -3
- package/src/component/Progress.mjs +1 -1
- package/src/component/StatusBadge.mjs +2 -2
- package/src/component/Timer.mjs +2 -2
- package/src/component/Toast.mjs +5 -3
- package/src/container/AccordionItem.mjs +2 -2
- package/src/container/Base.mjs +1 -1
- package/src/core/Base.mjs +77 -14
- package/src/core/Util.mjs +14 -2
- package/src/date/DayViewComponent.mjs +2 -2
- package/src/date/SelectorContainer.mjs +1 -1
- package/src/draggable/grid/header/toolbar/SortZone.mjs +21 -21
- package/src/draggable/table/header/toolbar/SortZone.mjs +1 -1
- package/src/form/field/CheckBox.mjs +4 -4
- package/src/form/field/FileUpload.mjs +25 -39
- package/src/form/field/Range.mjs +1 -1
- package/src/form/field/Text.mjs +3 -3
- package/src/form/field/TextArea.mjs +2 -3
- package/src/grid/Body.mjs +8 -5
- package/src/grid/_export.mjs +1 -1
- package/src/list/Color.mjs +2 -2
- package/src/main/DeltaUpdates.mjs +157 -98
- package/src/main/addon/AmCharts.mjs +61 -84
- package/src/main/addon/Base.mjs +161 -42
- package/src/main/addon/GoogleMaps.mjs +9 -16
- package/src/main/addon/HighlightJS.mjs +2 -13
- package/src/main/addon/IntersectionObserver.mjs +21 -21
- package/src/main/addon/MonacoEditor.mjs +32 -64
- package/src/manager/ClassHierarchy.mjs +114 -0
- package/src/menu/List.mjs +1 -1
- package/src/plugin/Popover.mjs +2 -2
- package/src/sitemap/Component.mjs +1 -1
- package/src/table/{View.mjs → Body.mjs} +25 -22
- package/src/table/Container.mjs +43 -43
- package/src/table/_export.mjs +2 -2
- package/src/table/plugin/CellEditing.mjs +19 -19
- package/src/tooltip/Base.mjs +1 -6
- package/src/tree/Accordion.mjs +3 -3
- package/src/vdom/Helper.mjs +19 -22
- package/src/worker/App.mjs +1 -2
- package/src/worker/Base.mjs +7 -5
- package/src/worker/Canvas.mjs +2 -3
- package/src/worker/Data.mjs +5 -7
- package/src/worker/Task.mjs +2 -3
- package/src/worker/VDom.mjs +3 -4
- package/src/worker/mixin/RemoteMethodAccess.mjs +5 -2
- package/learn/guides/MainThreadAddonExample.md +0 -15
- package/learn/guides/MainThreadAddonIntro.md +0 -44
@@ -0,0 +1,280 @@
|
|
1
|
+
|
2
|
+
**Pre-requisite:** It is highly recommended to study [The Unified Class Config System](#/learn/benefits.ConfigSystem)
|
3
|
+
first to understand the foundational concepts and benefits.
|
4
|
+
|
5
|
+
The Neo.mjs class configuration system is a cornerstone of the framework, providing a powerful, declarative, and
|
6
|
+
reactive way to manage the state of your components and classes. Its internal mechanics are deeply intertwined with
|
7
|
+
the instance lifecycle, ensuring predictable and consistent behavior. This guide will take you on a deep dive into
|
8
|
+
how it achieves its remarkable consistency and power.
|
9
|
+
|
10
|
+
## 1. Core Concepts Recap
|
11
|
+
|
12
|
+
At its heart, the config system is built on a few key principles:
|
13
|
+
|
14
|
+
* **`static config` Block:** All configurable properties of a class are declared in a `static config = {}` block.
|
15
|
+
This provides a single, clear source of truth for a class's API.
|
16
|
+
* **`_` Suffix Convention:** Config properties that require custom logic when they change are declared with a trailing
|
17
|
+
underscore (e.g., `myValue_`). This signals the framework to automatically create a native getter and setter on the
|
18
|
+
class's prototype for this property.
|
19
|
+
* **Lifecycle Hooks:** For a config like `myValue_`, the framework provides optional lifecycle hooks that you can
|
20
|
+
implement in your class:
|
21
|
+
* `beforeGetMyValue(value)`: Called before the getter returns the value.
|
22
|
+
* `beforeSetMyValue(value, oldValue)`: Called before the setter applies the new value.
|
23
|
+
* `afterSetMyValue(value, oldValue)`: Called after the setter has applied the new value.
|
24
|
+
* **Reactivity:** The `afterSet` hooks are the heart of the reactive system. They allow you to define logic that
|
25
|
+
automatically runs whenever a specific config property changes, ensuring your UI and application state are always
|
26
|
+
in sync.
|
27
|
+
|
28
|
+
## 2. The Internal Mechanics: `set()`, `processConfigs()`, and `configSymbol`
|
29
|
+
|
30
|
+
To truly understand how Neo.mjs handles complex scenarios like simultaneous updates and inter-dependencies, we must
|
31
|
+
look at the internal machinery: the `set()` and `processConfigs()` methods in `Neo.core.Base`, and the special
|
32
|
+
`configSymbol` object.
|
33
|
+
|
34
|
+
### The `set()` Method: Your Gateway to Updates
|
35
|
+
|
36
|
+
The `set()` method is the public interface for changing one or more config properties at once. When you call
|
37
|
+
`this.set({a: 1, b: 2})`, you kick off a carefully orchestrated sequence.
|
38
|
+
|
39
|
+
[[Source: core.Base.mjs](https://github.com/neomjs/neo/blob/dev/src/core/Base.mjs)]
|
40
|
+
```javascript readonly
|
41
|
+
// Simplified for clarity
|
42
|
+
set(values={}) {
|
43
|
+
let me = this;
|
44
|
+
|
45
|
+
// If there are pending configs from a previous operation, process them first.
|
46
|
+
if (Object.keys(me[configSymbol]).length > 0) {
|
47
|
+
me.processConfigs();
|
48
|
+
}
|
49
|
+
|
50
|
+
// Stage the new values in the configSymbol object.
|
51
|
+
Object.assign(me[configSymbol], values); // (A)
|
52
|
+
|
53
|
+
// Start processing the newly staged values.
|
54
|
+
me.processConfigs(true); // (B)
|
55
|
+
}
|
56
|
+
```
|
57
|
+
|
58
|
+
Here’s the breakdown:
|
59
|
+
1. **Pre-processing (within `construct()`):** The method first checks if the internal `configSymbol` object has any
|
60
|
+
leftover configs from a previous, unfinished operation (e.g., from a parent class's `construct()` call). If so,
|
61
|
+
it processes them to ensure a clean state before new values are staged.
|
62
|
+
2. **Staging (A):** `Object.assign(me[configSymbol], values)` is the critical first step. All new values from your
|
63
|
+
`set()` call are merged into the `configSymbol` object. This object acts as a **temporary staging area**. It
|
64
|
+
creates a snapshot of the intended end-state for all properties in this specific `set()` operation *before*
|
65
|
+
any individual setters or `afterSet` hooks are invoked.
|
66
|
+
3. **Processing (B):** `me.processConfigs(true)` is called. This kicks off the process of applying the staged values
|
67
|
+
from `configSymbol` to the actual instance properties. The `true` argument (`forceAssign`) is crucial, as we'll
|
68
|
+
see next.
|
69
|
+
|
70
|
+
### The `processConfigs()` Method: The Heart of the Operation
|
71
|
+
|
72
|
+
This internal method iteratively processes the configs stored in `configSymbol`. It's designed as a recursive
|
73
|
+
function to handle the dynamic nature of config processing, where one `afterSet` might trigger another `set()`.
|
74
|
+
|
75
|
+
[[Source: core.Base.mjs](https://github.com/neomjs/neo/blob/dev/src/core/Base.mjs)]
|
76
|
+
```javascript readonly
|
77
|
+
// Simplified for clarity
|
78
|
+
processConfigs(forceAssign=false) {
|
79
|
+
let me = this,
|
80
|
+
keys = Object.keys(me[configSymbol]); // Get keys of pending configs
|
81
|
+
|
82
|
+
if (keys.length > 0) {
|
83
|
+
let key = keys[0];
|
84
|
+
let value = me[configSymbol][key];
|
85
|
+
|
86
|
+
// The auto-generated setter for the config is triggered here.
|
87
|
+
me[key] = value; // (C)
|
88
|
+
|
89
|
+
// The config is removed from the staging area after its setter is called.
|
90
|
+
delete me[configSymbol][key]; // (D)
|
91
|
+
|
92
|
+
// Recursively call to process the next config.
|
93
|
+
me.processConfigs(forceAssign); // (E)
|
94
|
+
}
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
* **Iteration:** `processConfigs` takes the *first* key from `configSymbol`. It avoids a standard loop to prevent
|
99
|
+
issues if an `afterSet` hook modifies `configSymbol`.
|
100
|
+
* **Assignment (C):** `me[key] = value` is the most important step. This does **not** directly change a backing
|
101
|
+
field. Instead, it triggers the actual auto-generated **setter** for the config property (e.g., `set a(value)`).
|
102
|
+
This native setter is responsible for:
|
103
|
+
1. Running the `beforeSet` hook (if it exists).
|
104
|
+
2. Updating the internal backing property (e.g., `this._a = value`).
|
105
|
+
3. Running the `afterSet` hook (if it exists and the value has changed).
|
106
|
+
* **Deletion (D):** `delete me[configSymbol][key]` removes the property from the staging area *after* its setter
|
107
|
+
has been invoked. This is vital to prevent reprocessing and to mark the config as handled.
|
108
|
+
* **Recursion (E):** The method calls itself to process the next item in `configSymbol` until it's empty.
|
109
|
+
|
110
|
+
## 3. Solving the "Circular Reference" Problem
|
111
|
+
|
112
|
+
What happens when two `afterSet` methods depend on each other's properties?
|
113
|
+
|
114
|
+
Consider this common scenario:
|
115
|
+
```javascript readonly
|
116
|
+
class MyComponent extends Component {
|
117
|
+
static config = {
|
118
|
+
a_: 1,
|
119
|
+
b_: 2
|
120
|
+
}
|
121
|
+
|
122
|
+
afterSetA(value, oldValue) {
|
123
|
+
// This depends on 'b'
|
124
|
+
console.log(`a changed to ${value}, b is ${this.b}`);
|
125
|
+
}
|
126
|
+
|
127
|
+
afterSetB(value, oldValue) {
|
128
|
+
// This depends on 'a'
|
129
|
+
console.log(`b changed to ${value}, a is ${this.a}`);
|
130
|
+
}
|
131
|
+
|
132
|
+
onConstructed() {
|
133
|
+
super.onConstructed();
|
134
|
+
this.set({
|
135
|
+
a: 10,
|
136
|
+
b: 20
|
137
|
+
});
|
138
|
+
}
|
139
|
+
}
|
140
|
+
```
|
141
|
+
When `this.set({a: 10, b: 20})` is called, which `afterSet` runs first? And when it runs, what value will it see
|
142
|
+
for the *other* property?
|
143
|
+
|
144
|
+
**This is where the brilliance of the `configSymbol` shines.**
|
145
|
+
|
146
|
+
Here's the sequence:
|
147
|
+
1. **`set()` called:** `this.set({a: 10, b: 20})` is executed.
|
148
|
+
2. **Staging:** The `configSymbol` is immediately populated: `me[configSymbol] = {a: 10, b: 20}`. The internal
|
149
|
+
backing properties `_a` and `_b` have **not** been updated yet.
|
150
|
+
3. **`processConfigs()` starts:**
|
151
|
+
* It picks `a`. The setter `setA(10)` is called.
|
152
|
+
* Inside `setA`, the internal `this._a` is updated to `10`.
|
153
|
+
* `afterSetA(10, 1)` is triggered.
|
154
|
+
4. **Inside `afterSetA`:**
|
155
|
+
* The code encounters `this.b`. This calls the auto-generated getter for `b`.
|
156
|
+
* **Crucially, the getter for `b` is smart.** It first checks if `b` exists as a key in the `configSymbol`
|
157
|
+
staging area.
|
158
|
+
* It finds `b: 20` in `configSymbol` and immediately returns `20`, the **new, pending value**. It does *not*
|
159
|
+
return the old value from `this._b`.
|
160
|
+
* The console logs: `a changed to 10, b is 20`.
|
161
|
+
5. **`processConfigs()` continues:**
|
162
|
+
* `a` is removed from `configSymbol`.
|
163
|
+
* The recursion continues, and it now picks `b`. The setter `setB(20)` is called.
|
164
|
+
* Inside `setB`, `this._b` is updated to `20`.
|
165
|
+
* `afterSetB(20, 2)` is triggered.
|
166
|
+
6. **Inside `afterSetB`:**
|
167
|
+
* The code encounters `this.a`. The getter for `a` is called.
|
168
|
+
* It checks `configSymbol`, but `a` is no longer there (it was processed).
|
169
|
+
* It therefore returns the value from the internal backing property, `this._a`, which is now `10`.
|
170
|
+
* The console logs: `b changed to 20, a is 10`.
|
171
|
+
|
172
|
+
**Conclusion:** The `configSymbol` acts as a consistent, authoritative snapshot for the duration of a `set()`
|
173
|
+
operation. This guarantees that all `afterSet` handlers, regardless of their execution order, operate on the most
|
174
|
+
current and consistent state of all config properties involved in that operation.
|
175
|
+
|
176
|
+
## 4. In-depth Example: A Reactive `MainContainer`
|
177
|
+
|
178
|
+
Let's analyze a practical example to see these concepts in action. The `Neo.examples.core.config.MainContainer`
|
179
|
+
demonstrates how to build a reactive UI declaratively.
|
180
|
+
|
181
|
+
**The Goal:** Create a container with two labels. The text of each label is calculated based on the values of two
|
182
|
+
config properties, `a` and `b`. A button allows the user to change `a` and `b` simultaneously.
|
183
|
+
|
184
|
+
**The Declarative Approach (`static config`)**
|
185
|
+
|
186
|
+
The entire UI structure, including child components and event handlers, is defined within the `static config` block.
|
187
|
+
This is the recommended approach as it makes the component's structure immediately clear.
|
188
|
+
|
189
|
+
```javascript readonly
|
190
|
+
// From: Neo.examples.core.config.MainContainer
|
191
|
+
import Panel from '../../../src/container/Panel.mjs';
|
192
|
+
import Viewport from '../../../src/container/Viewport.mjs';
|
193
|
+
|
194
|
+
class MainContainer extends Viewport {
|
195
|
+
static config = {
|
196
|
+
className: 'Neo.examples.core.config.MainContainer',
|
197
|
+
a_: null,
|
198
|
+
b_: null,
|
199
|
+
style : { padding: '20px' },
|
200
|
+
items: [{
|
201
|
+
module: Panel,
|
202
|
+
// ... panel configs
|
203
|
+
headers: [{
|
204
|
+
dock : 'top',
|
205
|
+
items: [
|
206
|
+
{ ntype: 'label', flag: 'label1' },
|
207
|
+
{ ntype: 'label', flag: 'label2' },
|
208
|
+
{ ntype: 'component', flex: 1 },
|
209
|
+
{
|
210
|
+
handler: 'up.changeConfig', // Declarative handler
|
211
|
+
iconCls: 'fa fa-user',
|
212
|
+
text : 'Change configs'
|
213
|
+
}
|
214
|
+
]
|
215
|
+
}],
|
216
|
+
items: [{ ntype: 'label', text: 'Click the change configs button!' }]
|
217
|
+
}]
|
218
|
+
}
|
219
|
+
|
220
|
+
onConstructed() {
|
221
|
+
super.onConstructed();
|
222
|
+
this.set({ a: 5, b: 5 });
|
223
|
+
}
|
224
|
+
|
225
|
+
afterSetA(value, oldValue) {
|
226
|
+
if (oldValue !== undefined) {
|
227
|
+
this.down({flag: 'label1'}).text = value + this.b;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
afterSetB(value, oldValue) {
|
232
|
+
if (oldValue !== undefined) {
|
233
|
+
this.down({flag: 'label2'}).text = value + this.a;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
changeConfig(data) {
|
238
|
+
this.set({ a: 10, b: 10 });
|
239
|
+
}
|
240
|
+
}
|
241
|
+
```
|
242
|
+
|
243
|
+
### Tracing the Data Flow
|
244
|
+
|
245
|
+
1. **Initialization (`onConstructed`)**:
|
246
|
+
* `this.set({a: 5, b: 5})` is called. This happens within the `onConstructed()` lifecycle hook, which is guaranteed
|
247
|
+
to run *after* the instance's `construct()` method has fully processed its initial configuration.
|
248
|
+
* `configSymbol` becomes `{a: 5, b: 5}`.
|
249
|
+
* `afterSetA` runs. It calculates `label1.text` as `value (5) + this.b (reads 5 from configSymbol) = 10`.
|
250
|
+
* `afterSetB` runs. It calculates `label2.text` as `value (5) + this.a (reads 5 from _a) = 10`.
|
251
|
+
* **Initial State:** `label1` shows "10", `label2` shows "10".
|
252
|
+
|
253
|
+
2. **Button Click (`changeConfig`)**:
|
254
|
+
* The button's `handler: 'up.changeConfig'` finds and calls the `changeConfig` method on the `MainContainer`.
|
255
|
+
* `this.set({a: 10, b: 10})` is called.
|
256
|
+
* `configSymbol` becomes `{a: 10, b: 10}`.
|
257
|
+
* `afterSetA` runs. It calculates `label1.text` as `value (10) + this.b (reads 10 from configSymbol) = 20`.
|
258
|
+
* `afterSetB` runs. It calculates `label2.text` as `value (10) + this.a (reads 10 from _a) = 20`.
|
259
|
+
* **New State:** `label1` shows "20", `label2` shows "20".
|
260
|
+
|
261
|
+
This example vividly demonstrates the dynamic and reactive nature of the system, where a single declarative state
|
262
|
+
change automatically propagates through the component logic.
|
263
|
+
|
264
|
+
## 5. Best Practices
|
265
|
+
|
266
|
+
* **Embrace Declarativity:** Define your entire UI structure inside `static config` whenever possible. This improves
|
267
|
+
readability and maintainability.
|
268
|
+
* **Use the `_` Suffix Wisely:** Only add the trailing underscore to configs that need `afterSet`, `beforeSet` or
|
269
|
+
`beforeGet` based logic. For simple value properties, omit it to avoid unnecessary overhead.
|
270
|
+
* **Keep `afterSet` Handlers Pure:** An `afterSet` handler should ideally only react to the change of its own
|
271
|
+
property and update other parts of the application. Avoid triggering complex chains of `set()` calls from within
|
272
|
+
an `afterSet` if possible.
|
273
|
+
* **Batch Updates with `set()`:** When you need to change multiple properties at once, always use a single
|
274
|
+
`set({a: 1, b: 2})` call. This is more efficient and ensures consistency, as demonstrated above.
|
275
|
+
* **Use `onConstructed` for Post-Construction Logic:** Use the `onConstructed` lifecycle method to perform any setup
|
276
|
+
that depends on the instance's initial configuration being fully processed. This is the ideal place for logic that
|
277
|
+
requires all configs to be set and potentially other instances to be created (if set-driven).
|
278
|
+
|
279
|
+
By understanding these internal mechanics and following best practices, you can leverage the full power of Neo.mjs's
|
280
|
+
class config system to build highly complex, reactive, and maintainable applications with confidence.
|
@@ -9,7 +9,7 @@ Neo.mjs is class-based, which means you're free to extend any component (or any
|
|
9
9
|
|
10
10
|
## Lifecycle config properties
|
11
11
|
|
12
|
-
|
12
|
+
```javascript live-preview
|
13
13
|
import Button from '../button/Base.mjs';
|
14
14
|
// In practice this would be some handy reusable component
|
15
15
|
class MySpecialButton extends Button {
|
@@ -41,5 +41,5 @@ class MainView extends Container {
|
|
41
41
|
}
|
42
42
|
|
43
43
|
Neo.setupClass(MainView);
|
44
|
-
|
44
|
+
```
|
45
45
|
|
@@ -14,7 +14,7 @@ Neo.mjs operates on two distinct abstraction layers:
|
|
14
14
|
- **Component Tree Layer** (Application Development): Declarative, mutable, reactive component configurations
|
15
15
|
- **VDom Tree Layer** (Framework Internals): Imperative virtual DOM operations for performance optimization
|
16
16
|
|
17
|
-
```
|
17
|
+
```text
|
18
18
|
Your Application Code → Component Tree (declarative, mutable, reactive)
|
19
19
|
↓
|
20
20
|
VDom Tree (imperative, optimized)
|
@@ -28,7 +28,7 @@ Your Application Code → Component Tree (declarative, mutable, reactive)
|
|
28
28
|
|
29
29
|
In React, Vue, and Angular, you compose UIs by writing templates/JSX that mix HTML elements with custom components:
|
30
30
|
|
31
|
-
```
|
31
|
+
```javascript readonly
|
32
32
|
// React/Vue/Angular pattern - mixing HTML with components
|
33
33
|
function App() {
|
34
34
|
return (
|
@@ -50,7 +50,7 @@ Your mental model:</br>
|
|
50
50
|
|
51
51
|
In Neo.mjs, you work with **declarative component configurations** that create a component tree abstraction:
|
52
52
|
|
53
|
-
```javascript
|
53
|
+
```javascript readonly
|
54
54
|
// Neo.mjs pattern - component relationship configuration
|
55
55
|
class Viewport extends Container {
|
56
56
|
static config = {
|
@@ -91,7 +91,7 @@ Your new mental model:</br>
|
|
91
91
|
|
92
92
|
Components are defined through static configuration objects that describe relationships and behavior:
|
93
93
|
|
94
|
-
```javascript
|
94
|
+
```javascript readonly
|
95
95
|
// Declarative component hierarchy
|
96
96
|
class Viewport extends BaseViewport {
|
97
97
|
static config = {
|
@@ -118,7 +118,7 @@ class Viewport extends BaseViewport {
|
|
118
118
|
|
119
119
|
The component tree is **dynamic and mutable at runtime**:
|
120
120
|
|
121
|
-
```javascript
|
121
|
+
```javascript readonly
|
122
122
|
// Runtime mutations on the component tree
|
123
123
|
container.add({module: NewComponent}); // Add component
|
124
124
|
container.removeAt(0); // Remove component
|
@@ -134,7 +134,7 @@ targetContainer.add(sourceView);
|
|
134
134
|
|
135
135
|
**Every component tree configuration change automatically triggers UI updates**:
|
136
136
|
|
137
|
-
```javascript
|
137
|
+
```javascript readonly
|
138
138
|
// These changes automatically update the UI
|
139
139
|
button.text = 'New Text'; // Property change → UI update
|
140
140
|
button.iconCls = 'fa fa-home'; // Config change → UI update
|
@@ -151,7 +151,7 @@ viewport.setState({size: 'large'}); // State change → UI update
|
|
151
151
|
|
152
152
|
### State Provider Integration
|
153
153
|
|
154
|
-
```javascript
|
154
|
+
```javascript readonly
|
155
155
|
// Portal.view.ViewportStateProvider
|
156
156
|
class ViewportStateProvider extends StateProvider {
|
157
157
|
static config = {
|
@@ -171,7 +171,7 @@ viewportStateProvider.setData({size: 'large'});
|
|
171
171
|
|
172
172
|
Framework components define their internal DOM structure through `vdom` config:
|
173
173
|
|
174
|
-
```javascript
|
174
|
+
```javascript readonly
|
175
175
|
// Neo.button.Base
|
176
176
|
class Button extends Component {
|
177
177
|
static config = {
|
@@ -192,7 +192,7 @@ class Button extends Component {
|
|
192
192
|
|
193
193
|
Framework code performs imperative operations on VDom node properties:
|
194
194
|
|
195
|
-
```javascript
|
195
|
+
```javascript readonly
|
196
196
|
// Neo.button.Base - internal framework code
|
197
197
|
afterSetIconCls(value, oldValue) {
|
198
198
|
let {iconNode} = this;
|
@@ -222,7 +222,7 @@ afterSetText(value, oldValue) {
|
|
222
222
|
|
223
223
|
### Performance Optimizations
|
224
224
|
|
225
|
-
```javascript
|
225
|
+
```javascript readonly
|
226
226
|
// Neo.button.Base - optimized animations
|
227
227
|
async showRipple(data) {
|
228
228
|
let rippleEl = this.rippleWrapper.cn[0];
|
@@ -253,7 +253,7 @@ async showRipple(data) {
|
|
253
253
|
|
254
254
|
Understanding the value proposition of Neo.mjs's two-tier architecture:
|
255
255
|
|
256
|
-
```javascript
|
256
|
+
```javascript readonly
|
257
257
|
// What developers write - declarative configurations
|
258
258
|
{
|
259
259
|
module : Button,
|
@@ -281,7 +281,7 @@ This separation allows developers to focus on **what** they want to build rather
|
|
281
281
|
Routing happens inside view controllers, instead of being tag-based.
|
282
282
|
Developers are in full control to define what route-changes should do.
|
283
283
|
|
284
|
-
```javascript
|
284
|
+
```javascript readonly
|
285
285
|
// Portal.view.ViewportController
|
286
286
|
// Declarative route configuration
|
287
287
|
static config = {
|
@@ -319,7 +319,7 @@ async setMainContentIndex(index) {
|
|
319
319
|
|
320
320
|
If needed, this can be done via JavaScript too (instead of purely focussing on CSS).
|
321
321
|
|
322
|
-
```javascript
|
322
|
+
```javascript readonly
|
323
323
|
// Portal.view.Viewport
|
324
324
|
static sizes = ['large', 'medium', 'small', 'x-small', null];
|
325
325
|
|
@@ -340,7 +340,7 @@ afterSetSize(value, oldValue) {
|
|
340
340
|
|
341
341
|
### Dynamic Component Management
|
342
342
|
|
343
|
-
```javascript
|
343
|
+
```javascript readonly
|
344
344
|
// Portal.view.ViewportController
|
345
345
|
async onAppConnect(data) {
|
346
346
|
let app = Neo.apps[data.appName];
|
@@ -420,7 +420,7 @@ async onAppConnect(data) {
|
|
420
420
|
|
421
421
|
### Integration Patterns:
|
422
422
|
|
423
|
-
```javascript
|
423
|
+
```javascript readonly
|
424
424
|
// Wrapping existing components or custom elements
|
425
425
|
{
|
426
426
|
module: LegacyWrapper,
|
@@ -449,7 +449,7 @@ async onAppConnect(data) {
|
|
449
449
|
|
450
450
|
### Custom Component Development
|
451
451
|
|
452
|
-
```javascript
|
452
|
+
```javascript readonly
|
453
453
|
import Component from './src/component/Base.mjs';
|
454
454
|
import VdomUtil from './src/util/Vdom.mjs';
|
455
455
|
|
@@ -481,7 +481,7 @@ Neo.mjs provides utilities such as `VdomUtil` for direct interaction with VDom n
|
|
481
481
|
|
482
482
|
### Performance Monitoring
|
483
483
|
|
484
|
-
```javascript
|
484
|
+
```javascript readonly
|
485
485
|
Neo.config.logDeltaUpdates = true; // Enable update timing logs
|
486
486
|
```
|
487
487
|
|