neo.mjs 10.0.0-beta.1 → 10.0.0-beta.3
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/FormPageContainer.mjs +2 -3
- package/apps/form/view/SideNavList.mjs +1 -1
- package/apps/portal/index.html +1 -1
- package/apps/portal/resources/data/examples_dist_esm.json +1 -1
- package/apps/portal/resources/data/examples_dist_prod.json +2 -2
- 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 +18 -11
- 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/learn/Glossary.md +261 -0
- package/learn/README.md +9 -14
- package/learn/benefits/ConfigSystem.md +536 -26
- package/learn/benefits/Effort.md +47 -2
- package/learn/benefits/Features.md +50 -32
- package/learn/benefits/FormsEngine.md +54 -24
- package/learn/benefits/MultiWindow.md +31 -5
- package/learn/benefits/Quick.md +45 -12
- package/learn/benefits/RPCLayer.md +75 -0
- package/learn/benefits/Speed.md +17 -12
- package/learn/guides/Collections.md +436 -0
- package/learn/guides/ConfigSystemDeepDive.md +280 -0
- package/learn/guides/CustomComponents.md +256 -14
- package/learn/guides/DeclarativeComponentTreesVsImperativeVdom.md +17 -17
- package/learn/guides/ExtendingNeoClasses.md +331 -0
- package/learn/guides/Forms.md +449 -1
- package/learn/guides/InstanceLifecycle.md +295 -1
- package/learn/guides/Layouts.md +246 -1
- package/learn/guides/MainThreadAddons.md +475 -0
- package/learn/guides/Records.md +286 -0
- package/learn/guides/WorkingWithVDom.md +14 -14
- package/learn/guides/form_fields/ComboBox.md +241 -0
- package/learn/tree.json +57 -51
- package/package.json +2 -2
- package/resources/scss/src/apps/portal/learn/ContentComponent.scss +9 -0
- 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 +18 -2
- package/src/date/DayViewComponent.mjs +2 -2
- package/src/date/SelectorContainer.mjs +1 -1
- package/src/form/field/CheckBox.mjs +4 -4
- package/src/form/field/ComboBox.mjs +6 -1
- 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 +6 -2
- package/src/list/Color.mjs +2 -2
- package/src/main/DeltaUpdates.mjs +157 -98
- package/src/main/addon/AmCharts.mjs +53 -73
- package/src/main/addon/Base.mjs +11 -0
- package/src/main/addon/MonacoEditor.mjs +31 -58
- 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/Body.mjs +6 -2
- package/src/tooltip/Base.mjs +1 -6
- package/src/tree/Accordion.mjs +3 -3
- package/src/vdom/Helper.mjs +21 -19
- package/src/worker/App.mjs +1 -2
- package/src/worker/Base.mjs +6 -4
- 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 +4 -1
- package/learn/guides/MainThreadAddonExample.md +0 -15
- package/learn/guides/MainThreadAddonIntro.md +0 -44
@@ -1 +1,295 @@
|
|
1
|
-
|
1
|
+
Understanding the lifecycle of a class instance in Neo.mjs is crucial for building robust and predictable
|
2
|
+
applications. The framework provides a series of well-defined hooks that allow you to tap into different stages of an
|
3
|
+
instance's life, from its creation to its destruction.
|
4
|
+
|
5
|
+
This guide will walk you through the entire lifecycle, starting with the initial synchronous steps and moving on to
|
6
|
+
the asynchronous parts and destruction.
|
7
|
+
|
8
|
+
## How the Lifecycle is Triggered
|
9
|
+
|
10
|
+
While this guide details the lifecycle methods of an instance, it's important to understand how that lifecycle begins.
|
11
|
+
In typical Neo.mjs application code, you will rarely call `Neo.create()` directly.
|
12
|
+
|
13
|
+
The most common way to create component instances is declaratively, by defining configuration objects within a container's
|
14
|
+
`items` array. The framework then internally uses `Neo.create()` to turn these configuration objects into fully-fledged
|
15
|
+
instances, automatically initiating their lifecycle.
|
16
|
+
|
17
|
+
It is crucial to **never** create a Neo.mjs class instance using the `new` keyword (e.g., `new MyComponent()`),
|
18
|
+
as this would bypass the entire lifecycle initialization process described below, resulting in a broken and
|
19
|
+
improperly configured instance. Always let the framework handle instantiation, either through declarative `items`
|
20
|
+
configs or, in less common cases, by using `Neo.create()` directly.
|
21
|
+
|
22
|
+
## 1. The Synchronous Creation Flow
|
23
|
+
|
24
|
+
When the framework creates a new instance, it executes a sequence of synchronous methods. This initial phase is
|
25
|
+
responsible for setting up the instance's basic configuration and state.
|
26
|
+
|
27
|
+
The synchronous lifecycle methods are called in the following order:
|
28
|
+
|
29
|
+
1. **`new YourClass()`**: The framework first calls the actual JavaScript class constructor with **no arguments**.
|
30
|
+
This is a crucial step. Its primary purpose is to create the instance and initialize all of its defined class
|
31
|
+
fields. This ensures that by the time any Neo.mjs lifecycle method (like `construct`) or config hook
|
32
|
+
(like `beforeGetX`) is called, all class fields are fully available on `this`, preventing potential race
|
33
|
+
conditions or errors from accessing uninitialized properties.
|
34
|
+
|
35
|
+
2. **`construct(config)`**: This is the first Neo.mjs lifecycle hook called on the new instance. Its primary role is
|
36
|
+
to process the configuration object that was passed to `Neo.create()`. It's here that the initial values for
|
37
|
+
your configs are processed and applied via the config system.
|
38
|
+
|
39
|
+
3. **`onConstructed()`**: This hook is called immediately after `construct()` has finished. It's the ideal place to
|
40
|
+
perform any setup that depends on the initial configuration, such as setting initial values for other
|
41
|
+
properties or starting a process.
|
42
|
+
|
43
|
+
4. **`onAfterConstructed()`**: This hook is called after `onConstructed()`. It provides another opportunity for
|
44
|
+
setup logic, which can be useful for separating concerns or for logic that needs to run after the primary
|
45
|
+
`onConstructed` logic has completed.
|
46
|
+
|
47
|
+
5. **`init()`**: This is the final synchronous hook in the creation process. It's a general-purpose initialization
|
48
|
+
method that you can use for any final setup tasks before the instance is returned by `Neo.create()`.
|
49
|
+
|
50
|
+
It's important to remember that all of these methods are synchronous. Any asynchronous operations should be handled
|
51
|
+
in the later, asynchronous phases of the lifecycle.
|
52
|
+
|
53
|
+
## 2. `constructor()` vs `construct()`: A Critical Distinction
|
54
|
+
|
55
|
+
While you *can* define a standard JavaScript `constructor()` method on a Neo.mjs class, it is strongly discouraged
|
56
|
+
and considered a bad practice. The framework provides the `construct()` lifecycle hook for a very specific and
|
57
|
+
powerful reason: **pre-processing configs**.
|
58
|
+
|
59
|
+
### The `constructor()` Limitation
|
60
|
+
|
61
|
+
In standard JavaScript class inheritance, you **cannot** access the `this` context in a constructor before calling
|
62
|
+
`super()`. This is a language-level restriction.
|
63
|
+
|
64
|
+
```javascript
|
65
|
+
// Anti-pattern: Do not do this in Neo.mjs
|
66
|
+
constructor(config) {
|
67
|
+
// ERROR! 'this' is not available before super()
|
68
|
+
console.log(this.someClassField);
|
69
|
+
|
70
|
+
super(config); // Assuming a parent constructor call
|
71
|
+
}
|
72
|
+
```
|
73
|
+
|
74
|
+
### The `construct()` Advantage
|
75
|
+
|
76
|
+
The `construct()` method, however, is just a regular method called by the framework *after* the instance has been
|
77
|
+
fully created (via `new YourClass()`). This means that inside `construct()`, you have full access to `this` from the
|
78
|
+
very first line.
|
79
|
+
|
80
|
+
This enables a powerful pattern: you can inspect or modify the incoming `config` object *before* passing it up the
|
81
|
+
inheritance chain with `super.construct(config)`. This is invaluable for component-specific logic.
|
82
|
+
|
83
|
+
```javascript
|
84
|
+
// The correct Neo.mjs pattern
|
85
|
+
construct(config) {
|
86
|
+
// 'this' is fully available here!
|
87
|
+
// We can inspect the config and perform logic before the parent class does.
|
88
|
+
if (config.someFlag) {
|
89
|
+
config.title = 'Title set by child class';
|
90
|
+
this.someProperty = true;
|
91
|
+
}
|
92
|
+
|
93
|
+
// Now, pass the (potentially modified) config to the parent.
|
94
|
+
super.construct(config);
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
In summary, always use `construct()` for your initialization logic. It provides the flexibility needed to work
|
99
|
+
within the Neo.mjs lifecycle and config system, a flexibility that the standard `constructor()` cannot offer.
|
100
|
+
|
101
|
+
## 3. The Asynchronous Initialization Flow
|
102
|
+
|
103
|
+
After the synchronous creation methods are complete, the instance lifecycle moves into an asynchronous phase. This is
|
104
|
+
where you should place any logic that cannot be executed synchronously, such as loading external files, fetching
|
105
|
+
data from a server, or waiting for other resources to become available.
|
106
|
+
|
107
|
+
This phase is orchestrated by a microtask scheduled from within the `construct()` method.
|
108
|
+
|
109
|
+
### `initAsync()`: The Asynchronous Entry Point
|
110
|
+
|
111
|
+
The core of this phase is the `async initAsync()` method.
|
112
|
+
|
113
|
+
* **Scheduling**: Immediately after the synchronous `construct()` logic is finished, the framework schedules a
|
114
|
+
microtask (`Promise.resolve().then(...)`) that will execute after the current JavaScript execution block is empty.
|
115
|
+
* **Execution**: This microtask calls and `await`s the `initAsync()` method. This is the designated place for all
|
116
|
+
asynchronous initialization logic. You can override this method in your own classes to perform tasks like
|
117
|
+
dynamic imports or initial data fetching.
|
118
|
+
* **Parent Call**: When overriding `initAsync()`, it is crucial to call `await super.initAsync()` at the beginning
|
119
|
+
of your implementation to ensure that parent classes can perform their own asynchronous setup, such as
|
120
|
+
registering remote methods.
|
121
|
+
|
122
|
+
```javascript
|
123
|
+
// In your class
|
124
|
+
async initAsync() {
|
125
|
+
// Always call the parent method first!
|
126
|
+
await super.initAsync();
|
127
|
+
|
128
|
+
// Your async logic here
|
129
|
+
const myModule = await import('./MyOptionalModule.mjs');
|
130
|
+
this.data = await myService.fetchInitialData();
|
131
|
+
}
|
132
|
+
```
|
133
|
+
|
134
|
+
### `isReady`: The Signal of Completion
|
135
|
+
|
136
|
+
Once the `initAsync()` promise resolves, the framework sets the instance's `isReady` config to `true`.
|
137
|
+
|
138
|
+
* **`isReady_`**: The config is defined as `isReady_` (with a trailing underscore), which means it gets an
|
139
|
+
`afterSetIsReady(value, oldValue)` hook.
|
140
|
+
* **Reacting to Readiness**: You can implement the `afterSetIsReady()` method to be notified precisely when the
|
141
|
+
instance is fully initialized and ready for interaction. This is the most reliable way to coordinate logic that
|
142
|
+
depends on the component's full readiness.
|
143
|
+
|
144
|
+
```javascript
|
145
|
+
// In your class
|
146
|
+
afterSetIsReady(isReady, wasReady) {
|
147
|
+
if (isReady && !wasReady) {
|
148
|
+
console.log('The instance is now fully ready!');
|
149
|
+
// Perform actions that require the component to be fully initialized
|
150
|
+
}
|
151
|
+
}
|
152
|
+
```
|
153
|
+
|
154
|
+
This `initAsync` -> `isReady` pattern provides a robust and predictable way to manage the asynchronous parts of the
|
155
|
+
instance lifecycle, ensuring that dependent logic only runs when the instance is in a known, ready state.
|
156
|
+
|
157
|
+
## 4. Destruction: Cleaning Up with `destroy()`
|
158
|
+
|
159
|
+
The final phase of the instance lifecycle is destruction. Properly cleaning up instances when they are no longer needed
|
160
|
+
is critical for preventing memory leaks and ensuring your application remains performant over time. The `destroy()`
|
161
|
+
method is the designated entry point for all cleanup logic.
|
162
|
+
|
163
|
+
### The Base `destroy()` Implementation
|
164
|
+
|
165
|
+
The `Neo.core.Base` class provides a foundational `destroy()` method that performs several key actions:
|
166
|
+
|
167
|
+
* **Clears Timeouts**: It clears any pending timeouts that were created using `this.timeout()`.
|
168
|
+
* **Unregisters Instance**: It unregisters the instance from the global `Neo.manager.Instance`, so it can no longer
|
169
|
+
be looked up by its ID.
|
170
|
+
* **Property Deletion**: It iterates over all properties of the instance and deletes them. This is an aggressive
|
171
|
+
strategy to help the JavaScript garbage collector reclaim memory by breaking references.
|
172
|
+
* **Single-Execution Guard**: The base class automatically intercepts the `destroy()` method to ensure that its core
|
173
|
+
logic can only be executed **once**, even if `destroy()` is called multiple times.
|
174
|
+
|
175
|
+
### Overriding `destroy()`: Best Practices
|
176
|
+
|
177
|
+
When your class holds references to other Neo.mjs instances or external resources, you must override the `destroy()`
|
178
|
+
method to manage them correctly. The primary goal is to break all circular references and remove any listeners or
|
179
|
+
registrations so that the instance can be safely garbage collected.
|
180
|
+
|
181
|
+
Here is an example from `Neo.grid.Container` that illustrates key best practices:
|
182
|
+
|
183
|
+
```javascript
|
184
|
+
// Example from src/grid/Container.mjs
|
185
|
+
destroy(...args) {
|
186
|
+
let me = this;
|
187
|
+
|
188
|
+
// 1. Clean up SHARED instances (e.g., Stores)
|
189
|
+
// We don't destroy the store, as it might be used by other components.
|
190
|
+
// Setting it to null will trigger the afterSetStore hook, which is the
|
191
|
+
// correct place to remove any listeners this grid added to the store.
|
192
|
+
me.store = null;
|
193
|
+
|
194
|
+
// 2. Destroy OWNED instances
|
195
|
+
// The grid container creates and owns its scrollManager, so it's
|
196
|
+
// responsible for destroying it.
|
197
|
+
me.scrollManager.destroy();
|
198
|
+
|
199
|
+
// 3. Unregister from external services/managers
|
200
|
+
// The component had previously registered with the ResizeObserver addon.
|
201
|
+
// It must unregister to prevent the addon from holding a dead reference.
|
202
|
+
me.mounted && Neo.main.addon.ResizeObserver.unregister({
|
203
|
+
id : me.id,
|
204
|
+
windowId: me.windowId
|
205
|
+
});
|
206
|
+
|
207
|
+
// 4. ALWAYS call super.destroy() LAST
|
208
|
+
// This executes the base cleanup logic after your custom logic is complete.
|
209
|
+
super.destroy(...args);
|
210
|
+
}
|
211
|
+
```
|
212
|
+
|
213
|
+
To summarize the best practices:
|
214
|
+
|
215
|
+
1. **Call `super.destroy()` Last**: Always end your `destroy()` method with `super.destroy(...args)`. If you call it
|
216
|
+
first, `this` will be partially dismantled, and subsequent calls on it will likely fail.
|
217
|
+
2. **Destroy Owned Instances**: If your class creates its own instances of other Neo.mjs classes (e.g., helpers,
|
218
|
+
managers), you are responsible for calling `destroy()` on them.
|
219
|
+
3. **Clean Up Shared Instances**: If your class uses a shared instance (like a `Store` or a global service), do **not**
|
220
|
+
call `destroy()` on it. Instead, remove any listeners you added to it. A good pattern is to set the config
|
221
|
+
property to `null` (e.g., `this.store = null`) and perform the listener cleanup inside the `afterSet` hook.
|
222
|
+
4. **Unregister from Services**: If your class registered itself with any external manager or service (like the
|
223
|
+
`ResizeObserver`), be sure to unregister from it.
|
224
|
+
|
225
|
+
## 5. Lifecycle of Nested Instances: Set-Driven vs. Get-Driven
|
226
|
+
|
227
|
+
A powerful feature of the config system is that a config property can be another Neo.mjs class instance. A common
|
228
|
+
example is a grid's `selectionModel`. This raises an important architectural question: when should this nested
|
229
|
+
instance be created? The framework supports two patterns, each with different implications for the lifecycle.
|
230
|
+
|
231
|
+
### The Set-Driven Approach (Eager Instantiation)
|
232
|
+
|
233
|
+
In this pattern, you ensure the instance is created as soon as the config is set. This is typically done inside a
|
234
|
+
`beforeSet` hook.
|
235
|
+
|
236
|
+
The `Neo.grid.Body` class provides a perfect example with its `selectionModel_` config.
|
237
|
+
|
238
|
+
```javascript
|
239
|
+
// In Neo.grid.Body
|
240
|
+
beforeSetSelectionModel(value, oldValue) {
|
241
|
+
oldValue?.destroy();
|
242
|
+
|
243
|
+
// beforeSetInstance ensures the value is a valid instance,
|
244
|
+
// creating one from a config object if necessary.
|
245
|
+
return ClassSystemUtil.beforeSetInstance(value, RowModel);
|
246
|
+
}
|
247
|
+
```
|
248
|
+
|
249
|
+
When the framework processes the grid body's configs during its `construct` phase, `beforeSetSelectionModel` is
|
250
|
+
called. It immediately creates the selection model instance.
|
251
|
+
|
252
|
+
**The key takeaway is the guarantee this provides for `onConstructed()`**. Because the selection model was
|
253
|
+
instantiated during `construct`, by the time `onConstructed()` is called, you can safely assume the instance exists.
|
254
|
+
|
255
|
+
```javascript
|
256
|
+
// In Neo.grid.Body
|
257
|
+
onConstructed() {
|
258
|
+
super.onConstructed();
|
259
|
+
|
260
|
+
// This is safe because beforeSetSelectionModel already created the instance.
|
261
|
+
this.selectionModel?.register(this);
|
262
|
+
}
|
263
|
+
```
|
264
|
+
|
265
|
+
Use the set-driven approach when a nested instance is **essential** for the component's core functionality and needs
|
266
|
+
to be available immediately after construction.
|
267
|
+
|
268
|
+
### The Get-Driven Approach (Lazy Instantiation)
|
269
|
+
|
270
|
+
Alternatively, you can defer the creation of a nested instance until it's actually needed for the first time. This
|
271
|
+
is achieved by creating the instance within a `beforeGet` hook. This "lazy" approach can improve initial creation
|
272
|
+
performance if the nested instance is complex or not always used.
|
273
|
+
|
274
|
+
`Neo.grid.Body` also demonstrates this pattern with its `columnPositions_` config.
|
275
|
+
|
276
|
+
```javascript
|
277
|
+
// In Neo.grid.Body
|
278
|
+
beforeGetColumnPositions(value) {
|
279
|
+
// If the backing field (_columnPositions) is null...
|
280
|
+
if (!value) {
|
281
|
+
// ...create the instance now.
|
282
|
+
this._columnPositions = value = Neo.create({
|
283
|
+
module : Collection,
|
284
|
+
keyProperty: 'dataField'
|
285
|
+
});
|
286
|
+
}
|
287
|
+
return value;
|
288
|
+
}
|
289
|
+
```
|
290
|
+
|
291
|
+
With this pattern, the `columnPositions` collection is **not** created during the `construct` phase. It is only
|
292
|
+
instantiated the very first time some other code calls `this.columnPositions`.
|
293
|
+
|
294
|
+
Use the get-driven approach for non-essential or heavy nested instances to optimize performance and memory usage,
|
295
|
+
especially if they are only used in specific scenarios.
|
package/learn/guides/Layouts.md
CHANGED
@@ -1 +1,246 @@
|
|
1
|
-
##
|
1
|
+
## Understanding Layouts in Neo.mjs
|
2
|
+
|
3
|
+
Layouts are fundamental to arranging components within your application's user interface. In Neo.mjs, layouts are
|
4
|
+
managed declaratively through the `layout` configuration property of container components. This system provides a
|
5
|
+
powerful and flexible way to control the positioning, sizing, and alignment of child components.
|
6
|
+
|
7
|
+
### How Layouts Work
|
8
|
+
|
9
|
+
Every container component (any class extending `Neo.container.Base`) can have a `layout` config. This config defines
|
10
|
+
how the container's `items` (its child components) are arranged. When you set a `layout` on a container, the framework
|
11
|
+
automatically handles the positioning and sizing of its children, adapting to different screen sizes and dynamic content.
|
12
|
+
|
13
|
+
### The `layout` Config
|
14
|
+
|
15
|
+
The `layout` config is an object that typically includes an `ntype` property, specifying the type of layout to use.
|
16
|
+
Depending on the `ntype`, additional properties can be provided to customize the layout's behavior.
|
17
|
+
|
18
|
+
Example:
|
19
|
+
|
20
|
+
```javascript
|
21
|
+
layout: {
|
22
|
+
ntype: 'vbox',
|
23
|
+
align: 'center'
|
24
|
+
}
|
25
|
+
```
|
26
|
+
|
27
|
+
### The 'base' Layout (`ntype: 'base'`)
|
28
|
+
|
29
|
+
In scenarios where you prefer to manage the positioning and sizing of child components entirely through custom CSS or
|
30
|
+
in-line styles, you can use the `'base'` layout. This layout type provides minimal interference, essentially acting as a
|
31
|
+
pass-through, allowing you full control over the styling of your container's children.
|
32
|
+
|
33
|
+
When `ntype: 'base'` is used, the container will not apply any specific flexbox or grid-based layout rules to its children.
|
34
|
+
This is useful for highly customized components or when integrating with external styling libraries.
|
35
|
+
|
36
|
+
### Common Layout Types
|
37
|
+
|
38
|
+
Neo.mjs provides several built-in layout types to cover a wide range of UI design needs. Here, we'll explore some of the
|
39
|
+
most commonly used ones.
|
40
|
+
|
41
|
+
#### 1. VBox Layout (`ntype: 'vbox'`)
|
42
|
+
|
43
|
+
The VBox (Vertical Box) layout arranges child components in a single vertical column. It's ideal for creating stacked
|
44
|
+
sections or forms where elements flow from top to bottom.
|
45
|
+
|
46
|
+
**Key Properties for VBox Layouts:**
|
47
|
+
|
48
|
+
- `align`: Controls the horizontal alignment of items within the column.
|
49
|
+
- `'left'` (default): Aligns items to the left.
|
50
|
+
- `'center'`: Centers items horizontally.
|
51
|
+
- `'right'`: Aligns items to the right.
|
52
|
+
- `'stretch'`: Stretches items to fill the available width of the container.
|
53
|
+
|
54
|
+
- `pack`: Controls how items are packed along the vertical axis (main axis).
|
55
|
+
- `'start'` (default): Items are packed towards the top.
|
56
|
+
- `'center'`: Items are centered vertically.
|
57
|
+
- `'end'`: Items are packed towards the bottom.
|
58
|
+
- `'space-between'`: Items are evenly distributed with space between them.
|
59
|
+
- `'space-around'`: Items are evenly distributed with space around them (including half-space at ends).
|
60
|
+
|
61
|
+
- `flex`: A property applied to individual child items, not the layout itself. It determines how an item grows or
|
62
|
+
shrinks to fill available space within the VBox. A `flex` value of `1` means the item will expand to fill remaining
|
63
|
+
space.
|
64
|
+
|
65
|
+
**Example:**
|
66
|
+
|
67
|
+
```javascript live-preview
|
68
|
+
import Container from '../container/Base.mjs';
|
69
|
+
import Button from '../button/Base.mjs';
|
70
|
+
|
71
|
+
class VBoxExample extends Container {
|
72
|
+
static config = {
|
73
|
+
className: 'Example.view.VBoxExample',
|
74
|
+
layout: {
|
75
|
+
ntype: 'vbox',
|
76
|
+
align: 'center', // Center items horizontally
|
77
|
+
pack: 'center' // Center items vertically
|
78
|
+
},
|
79
|
+
items: [{
|
80
|
+
module: Button,
|
81
|
+
text: 'Button 1',
|
82
|
+
width: 100
|
83
|
+
}, {
|
84
|
+
module: Button,
|
85
|
+
text: 'Button 2',
|
86
|
+
width: 150
|
87
|
+
}, {
|
88
|
+
module: Button,
|
89
|
+
text: 'Button 3',
|
90
|
+
flex: 1 // This button will expand to fill remaining vertical space
|
91
|
+
}]
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
Neo.setupClass(VBoxExample);
|
96
|
+
```
|
97
|
+
|
98
|
+
#### 2. HBox Layout (`ntype: 'hbox'`)
|
99
|
+
|
100
|
+
The HBox (Horizontal Box) layout arranges child components in a single horizontal row. It's commonly used for toolbars,
|
101
|
+
navigation menus, or any scenario where elements need to be displayed side-by-side.
|
102
|
+
|
103
|
+
**Key Properties for HBox Layouts:**
|
104
|
+
|
105
|
+
- `align`: Controls the vertical alignment of items within the row.
|
106
|
+
- `'top'` (default): Aligns items to the top.
|
107
|
+
- `'center'`: Centers items vertically.
|
108
|
+
- `'bottom'`: Aligns items to the bottom.
|
109
|
+
- `'stretch'`: Stretches items to fill the available height of the container.
|
110
|
+
|
111
|
+
- `pack`: Controls how items are packed along the horizontal axis (main axis).
|
112
|
+
- `'start'` (default): Items are packed towards the left.
|
113
|
+
- `'center'`: Items are centered horizontally.
|
114
|
+
- `'end'`: Items are packed towards the right.
|
115
|
+
- `'space-between'`: Items are evenly distributed with space between them.
|
116
|
+
- `'space-around'`: Items are evenly distributed with space around them (including half-space at ends).
|
117
|
+
|
118
|
+
- `flex`: Similar to VBox, `flex` applied to individual child items determines how they grow or shrink to fill
|
119
|
+
available horizontal space within the HBox.
|
120
|
+
|
121
|
+
**Example:**
|
122
|
+
|
123
|
+
```javascript live-preview
|
124
|
+
import Container from '../container/Base.mjs';
|
125
|
+
import Button from '../button/Base.mjs';
|
126
|
+
|
127
|
+
class HBoxExample extends Container {
|
128
|
+
static config = {
|
129
|
+
className: 'Example.view.HBoxExample',
|
130
|
+
layout: {
|
131
|
+
ntype: 'hbox',
|
132
|
+
align: 'center', // Center items vertically
|
133
|
+
pack: 'start' // Pack items to the left
|
134
|
+
},
|
135
|
+
items: [{
|
136
|
+
module: Button,
|
137
|
+
text: 'Button A',
|
138
|
+
height: 50
|
139
|
+
}, {
|
140
|
+
module: Button,
|
141
|
+
text: 'Button B',
|
142
|
+
height: 70
|
143
|
+
}, {
|
144
|
+
module: Button,
|
145
|
+
text: 'Button C',
|
146
|
+
flex: 1 // This button will expand to fill remaining horizontal space
|
147
|
+
}]
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
Neo.setupClass(HBoxExample);
|
152
|
+
```
|
153
|
+
|
154
|
+
#### 3. Card Layout (`ntype: 'card'`)
|
155
|
+
|
156
|
+
The Card layout is designed to display one child component at a time, making it ideal for tab panels, wizards, or any
|
157
|
+
interface where content needs to be switched without navigating away. Only the active card is visible, while others are
|
158
|
+
hidden.
|
159
|
+
|
160
|
+
**Key Properties for Card Layouts:**
|
161
|
+
|
162
|
+
- `activeIndex_`: This is the most important config. Changing its value activates a different child component (card).
|
163
|
+
The framework automatically handles showing the new card and hiding the old one.
|
164
|
+
|
165
|
+
- `removeInactiveCards`: A boolean (default `true`). If `true`, the DOM elements of inactive cards are removed from the
|
166
|
+
document flow, keeping only their instances and VDOM trees. This is useful for performance, especially with many
|
167
|
+
cards, as it reduces the number of elements the browser has to render. If `false`, inactive cards remain in the DOM
|
168
|
+
but are hidden via CSS.
|
169
|
+
|
170
|
+
- `slideDirection_`: A string (`'horizontal'`, `'vertical'`, or `null` - default `null`). This property enables
|
171
|
+
animated transitions when switching between cards. Setting it to `'horizontal'` or `'vertical'` will make the cards
|
172
|
+
slide into view.
|
173
|
+
|
174
|
+
**Example:**
|
175
|
+
|
176
|
+
```javascript live-preview
|
177
|
+
import Container from '../container/Base.mjs';
|
178
|
+
import Button from '../button/Base.mjs';
|
179
|
+
|
180
|
+
class CardExample extends Container {
|
181
|
+
static config = {
|
182
|
+
className: 'Example.view.CardExample',
|
183
|
+
layout: {
|
184
|
+
ntype: 'card',
|
185
|
+
activeIndex: 0 // Start with the first card active
|
186
|
+
},
|
187
|
+
items: [{
|
188
|
+
module: Container,
|
189
|
+
cls: 'card-panel',
|
190
|
+
items: [{
|
191
|
+
module: Button,
|
192
|
+
text: 'Go to Card 2',
|
193
|
+
handler: function() {
|
194
|
+
this.up('container').layout.activeIndex = 1;
|
195
|
+
}
|
196
|
+
}],
|
197
|
+
style: {
|
198
|
+
backgroundColor: '#e0f7fa',
|
199
|
+
padding: '20px',
|
200
|
+
textAlign: 'center'
|
201
|
+
}
|
202
|
+
}, {
|
203
|
+
module: Container,
|
204
|
+
cls: 'card-panel',
|
205
|
+
items: [{
|
206
|
+
module: Button,
|
207
|
+
text: 'Go to Card 1',
|
208
|
+
handler: function() {
|
209
|
+
this.up('container').layout.activeIndex = 0;
|
210
|
+
}
|
211
|
+
}],
|
212
|
+
style: {
|
213
|
+
backgroundColor: '#fff3e0',
|
214
|
+
padding: '20px',
|
215
|
+
textAlign: 'center'
|
216
|
+
}
|
217
|
+
}]
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
Neo.setupClass(CardExample);
|
222
|
+
```
|
223
|
+
|
224
|
+
#### Lazy Loading with Card Layouts
|
225
|
+
|
226
|
+
One powerful feature of the Card layout is its ability to lazy load content. This means that the JavaScript module for a
|
227
|
+
card's content is only loaded when that card becomes active, significantly improving initial application load times.
|
228
|
+
|
229
|
+
This is achieved by defining the `module` property of an item within the `items` array as a function that returns a
|
230
|
+
dynamic `import()` statement. For example, in the Portal app's `Viewport.mjs`,
|
231
|
+
modules are lazy-loaded like this:
|
232
|
+
|
233
|
+
```javascript
|
234
|
+
items: [
|
235
|
+
{module: () => import('./home/MainContainer.mjs')},
|
236
|
+
{module: () => import('./learn/MainContainer.mjs')},
|
237
|
+
// ... other lazy-loaded modules
|
238
|
+
]
|
239
|
+
```
|
240
|
+
|
241
|
+
When `activeIndex` changes to a card configured this way, Neo.mjs automatically executes the import function, loads the
|
242
|
+
module, and then creates the component instance. This ensures that resources are only consumed when they are actually
|
243
|
+
needed.
|
244
|
+
|
245
|
+
This is just the beginning of understanding layouts in Neo.mjs. In subsequent sections, we will explore more advanced
|
246
|
+
layout types and concepts like nesting layouts for complex UI structures.
|