neo.mjs 10.0.0-beta.3 → 10.0.0-beta.4

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.
Files changed (44) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.4.md +41 -0
  2. package/ServiceWorker.mjs +2 -2
  3. package/apps/portal/index.html +1 -1
  4. package/apps/portal/view/ViewportController.mjs +1 -1
  5. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  6. package/apps/portal/view/learn/MainContainerController.mjs +6 -6
  7. package/learn/guides/{Collections.md → datahandling/Collections.md} +6 -6
  8. package/learn/guides/datahandling/Grids.md +621 -0
  9. package/learn/guides/{Records.md → datahandling/Records.md} +4 -3
  10. package/learn/guides/{StateProviders.md → datahandling/StateProviders.md} +145 -1
  11. package/learn/guides/fundamentals/ExtendingNeoClasses.md +359 -0
  12. package/learn/guides/{Layouts.md → uibuildingblocks/Layouts.md} +40 -38
  13. package/learn/guides/{form_fields → userinteraction/form_fields}/ComboBox.md +3 -3
  14. package/learn/tree.json +63 -57
  15. package/package.json +2 -2
  16. package/src/DefaultConfig.mjs +2 -2
  17. package/src/Neo.mjs +37 -29
  18. package/src/collection/Base.mjs +29 -2
  19. package/src/component/Base.mjs +6 -16
  20. package/src/controller/Base.mjs +87 -63
  21. package/src/core/Base.mjs +72 -17
  22. package/src/core/Compare.mjs +3 -13
  23. package/src/core/Config.mjs +139 -0
  24. package/src/core/ConfigSymbols.mjs +3 -0
  25. package/src/core/Util.mjs +3 -18
  26. package/src/data/RecordFactory.mjs +22 -3
  27. package/src/util/Function.mjs +52 -5
  28. package/test/siesta/tests/ReactiveConfigs.mjs +112 -0
  29. package/learn/guides/ExtendingNeoClasses.md +0 -331
  30. /package/learn/guides/{Tables.md → datahandling/Tables.md} +0 -0
  31. /package/learn/guides/{ApplicationBootstrap.md → fundamentals/ApplicationBootstrap.md} +0 -0
  32. /package/learn/guides/{ConfigSystemDeepDive.md → fundamentals/ConfigSystemDeepDive.md} +0 -0
  33. /package/learn/guides/{DeclarativeComponentTreesVsImperativeVdom.md → fundamentals/DeclarativeComponentTreesVsImperativeVdom.md} +0 -0
  34. /package/learn/guides/{InstanceLifecycle.md → fundamentals/InstanceLifecycle.md} +0 -0
  35. /package/learn/guides/{MainThreadAddons.md → fundamentals/MainThreadAddons.md} +0 -0
  36. /package/learn/guides/{Mixins.md → specificfeatures/Mixins.md} +0 -0
  37. /package/learn/guides/{MultiWindow.md → specificfeatures/MultiWindow.md} +0 -0
  38. /package/learn/guides/{PortalApp.md → specificfeatures/PortalApp.md} +0 -0
  39. /package/learn/guides/{ComponentsAndContainers.md → uibuildingblocks/ComponentsAndContainers.md} +0 -0
  40. /package/learn/guides/{CustomComponents.md → uibuildingblocks/CustomComponents.md} +0 -0
  41. /package/learn/guides/{WorkingWithVDom.md → uibuildingblocks/WorkingWithVDom.md} +0 -0
  42. /package/learn/guides/{Forms.md → userinteraction/Forms.md} +0 -0
  43. /package/learn/guides/{events → userinteraction/events}/CustomEvents.md +0 -0
  44. /package/learn/guides/{events → userinteraction/events}/DomEvents.md +0 -0
@@ -195,7 +195,8 @@ nested Container which contains the `world` data prop.
195
195
  As a result, the bindings for all 3 Labels contain a combination of data props which live inside different stateProviders.
196
196
  As long as these VMs are inside the parent hierarchy this works fine.
197
197
 
198
- The same goes for the Button handlers: `setData()` will find the closest matching data prop inside the stateProvider parent chain.
198
+ The same goes for the Button handlers: `setData()` will find the closest matching data prop inside the stateProvider
199
+ parent chain.
199
200
 
200
201
  We can even change data props which live inside different stateProviders at once. As easy as this:</br>
201
202
  `setData({hello: 'foo', world: 'bar'})`
@@ -437,3 +438,146 @@ class MainView extends Container {
437
438
  }
438
439
  MainView = Neo.setupClass(MainView);
439
440
  ```
441
+
442
+ ### Managing Stores with State Providers
443
+
444
+ Beyond managing simple data properties, `Neo.state.Provider` can also centralize the management of `Neo.data.Store`
445
+ instances. This is particularly useful for sharing data across multiple components or for complex data flows within
446
+ your application.
447
+
448
+ You define stores within the `stores` config of your `StateProvider` class. Each entry
449
+ in the `stores` object can either be an inline store configuration (a plain JavaScript
450
+ object) or a class reference to a `Neo.data.Store` subclass.
451
+
452
+ It is also a common practice to import a `Neo.data.Model` extension and use it within
453
+ an inline store configuration, like so:
454
+
455
+ ```javascript readonly
456
+ import MyCustomModel from './MyCustomModel.mjs'; // Assuming MyCustomModel extends Neo.data.Model
457
+
458
+ // ...
459
+ stores: {
460
+ myStore: {
461
+ model: MyCustomModel,
462
+ // other inline configs like autoLoad, data, url
463
+ }
464
+ }
465
+ ```
466
+
467
+ Components can then bind to these centrally managed stores using the `bind` config,
468
+ referencing the store by its key within the `stores` object (e.g., `stores.myStoreName`).
469
+
470
+ ```javascript live-preview
471
+ import Button from '../button/Base.mjs';
472
+ import Container from '../container/Base.mjs';
473
+ import GridContainer from '../grid/Container.mjs';
474
+ import Label from '../component/Label.mjs';
475
+ import StateProvider from '../state/Provider.mjs';
476
+ import Store from '../data/Store.mjs';
477
+
478
+ class MyDataStore extends Store {
479
+ static config = {
480
+ className: 'Guides.vm7.MyDataStore',
481
+ model: {
482
+ fields: [
483
+ {name: 'id', type: 'Number'},
484
+ {name: 'name', type: 'String'}
485
+ ]
486
+ },
487
+ data: [
488
+ {id: 1, name: 'Item A'},
489
+ {id: 2, name: 'Item B'},
490
+ {id: 3, name: 'Item C'}
491
+ ]
492
+ }
493
+ }
494
+ MyDataStore = Neo.setupClass(MyDataStore);
495
+
496
+ class MainViewStateProvider extends StateProvider {
497
+ static config = {
498
+ className: 'Guides.vm7.MainViewStateProvider',
499
+
500
+ data: {
501
+ myStoreCount: 0
502
+ },
503
+
504
+ stores: {
505
+ // Define a store using a class reference
506
+ mySharedStore: {
507
+ module : MyDataStore,
508
+ listeners: {countChange: 'onMyStoreCountChange'}
509
+ },
510
+ // Define another store using an inline configuration
511
+ anotherStore: {
512
+ module: Store,
513
+ model: {
514
+ fields: [
515
+ {name: 'value', type: 'Number'}
516
+ ]
517
+ },
518
+ data: [
519
+ {value: 10},
520
+ {value: 20},
521
+ {value: 30}
522
+ ]
523
+ }
524
+ }
525
+ }
526
+
527
+ onMyStoreCountChange(data) {
528
+ this.data.myStoreCount = data.value // Reactive
529
+ }
530
+ }
531
+ MainViewStateProvider = Neo.setupClass(MainViewStateProvider);
532
+
533
+ class MainView extends Container {
534
+ static config = {
535
+ className : 'Guides.vm7.MainView',
536
+ stateProvider: MainViewStateProvider, // Assign the state provider
537
+ width : 300,
538
+
539
+ layout: {ntype: 'vbox', align: 'stretch'},
540
+ items: [{
541
+ module: GridContainer,
542
+ flex : 1,
543
+ bind: {
544
+ // Bind the grid's store config to 'mySharedStore'
545
+ store: 'stores.mySharedStore'
546
+ },
547
+ columns: [
548
+ {text: 'Id', dataField: 'id'},
549
+ {text: 'Name', dataField: 'name', flex: 1}
550
+ ]
551
+ }, {
552
+ module: Container,
553
+ flex : 'none',
554
+ layout: {ntype: 'hbox', align: 'stretch'},
555
+ items: [{
556
+ module: Label,
557
+ style : {margin: 'auto'},
558
+ bind: {
559
+ text: data => `Count: ${data.myStoreCount}`
560
+ }
561
+ }, {
562
+ module: Button,
563
+ text : 'Add Item to Store',
564
+ handler() {
565
+ const store = this.getStateProvider().getStore('mySharedStore');
566
+ store.add({id: store.getCount() + 1, name: 'New Item'})
567
+ }
568
+ }]
569
+ }]
570
+ }
571
+ }
572
+ MainView = Neo.setupClass(MainView);
573
+ ```
574
+
575
+ In this example:
576
+ * `MainViewStateProvider` defines two stores: `mySharedStore` (using a class reference) and
577
+ `anotherStore` (using an inline config).
578
+ * A `GridContainer` binds its `store` config directly to `mySharedStore`, allowing it to
579
+ display and interact with the data.
580
+ * A `Button` demonstrates how to programmatically interact with the store by adding a new record.
581
+
582
+ This approach provides a clean and efficient way to manage and share data across your
583
+ application, leveraging the power of the state provider system.
@@ -0,0 +1,359 @@
1
+
2
+ Neo.mjs is built upon a robust and consistent class system. Understanding how to extend framework classes is fundamental
3
+ to building custom functionality, whether you're creating new UI components, defining data structures, or implementing
4
+ application logic.
5
+
6
+ This guide covers the universal principles of class extension in Neo.mjs, which apply across all class types, not just
7
+ UI components.
8
+
9
+ ## 1. The `static config` Block: Defining Properties
10
+
11
+ Every Neo.mjs class utilizes a `static config` block. This is where you define the properties that instances of your
12
+ class will possess. These properties can be simple values, objects, or even other Neo.mjs class configurations.
13
+
14
+ ```javascript readonly
15
+ class MyBaseClass extends Neo.core.Base {
16
+ static config = {
17
+ className: 'My.Base.Class', // Unique identifier for the class
18
+ myString : 'Hello',
19
+ myNumber : 123
20
+ }
21
+ }
22
+
23
+ export default Neo.setupClass(MyBaseClass);
24
+ ```
25
+
26
+ Common configs you'll encounter include `className` (a unique string identifier for your class) and `ntype` (a shorthand
27
+ alias for component creation).
28
+
29
+ ## 2. Reactive Configs: The Trailing Underscore (`_`)
30
+
31
+ A cornerstone of Neo.mjs's reactivity is the trailing underscore (`_`) convention for configs defined in `static config`.
32
+ When you append an underscore to a config name (e.g., `myConfig_`), the framework automatically generates a reactive
33
+ getter and setter for it.
34
+
35
+ ```javascript readonly
36
+ class MyReactiveClass extends Neo.core.Base {
37
+ static config = {
38
+ className : 'My.Reactive.Class',
39
+ myReactiveConfig_: 'initial value' // This config is reactive
40
+ }
41
+
42
+ onConstructed() {
43
+ super.onConstructed();
44
+ console.log(this.myReactiveConfig); // Accesses the getter
45
+ this.myReactiveConfig = 'new value'; // Triggers the setter
46
+ }
47
+ }
48
+
49
+ export default Neo.setupClass(MyReactiveClass);
50
+ ```
51
+
52
+ Assigning a new value to a reactive property (e.g., `this.myReactiveProp = 'new value'`) triggers its setter, which in
53
+ turn can invoke lifecycle hooks, enabling automatic updates and side effects. Properties without the underscore are
54
+ static and do not trigger this reactive behavior.
55
+
56
+ ## 3. Configuration Lifecycle Hooks (`beforeSet`, `afterSet`, `beforeGet`)
57
+
58
+ For every reactive config (`myConfig_`), Neo.mjs provides three optional lifecycle hooks that you can implement in your
59
+ class. These methods are automatically called by the framework during the config's lifecycle, offering powerful
60
+ interception points:
61
+
62
+ * **`beforeSetMyConfig(value, oldValue)`**:
63
+ * **Purpose**: Intercepts the value *before* it is set. Ideal for validation, type coercion, or transforming the
64
+ incoming value.
65
+ * **Return Value**: Return the (potentially modified) `value` that should be set.
66
+ Returning `undefined` or `null` will prevent the value from being set.
67
+
68
+ * **`afterSetMyConfig(value, oldValue)`**:
69
+ * **Purpose**: Executed *after* the value has been successfully set. Ideal for triggering side effects, updating
70
+ the UI (e.g., calling `this.update()` for components), or firing events.
71
+ * **Return Value**: None.
72
+
73
+ * **`beforeGetMyConfig(value)`**:
74
+ * **Purpose**: Intercepts the value *before* it is returned by the getter. Useful for lazy initialization,
75
+ computing values on demand, or returning a transformed version of the stored value.
76
+ * **Return Value**: Return the `value` that should be returned by the getter.
77
+
78
+
79
+
80
+ ## 4. Flexible Configuration of Instances: The `beforeSetInstance` Pattern
81
+
82
+ Neo.mjs offers significant flexibility in how you configure properties that expect an instance of a Neo.mjs class
83
+ (e.g., `store`, `layout`, `controller`). This flexibility is powered by the `Neo.util.ClassSystem.beforeSetInstance`
84
+ utility, which intelligently converts various input types into the required instance.
85
+
86
+ This pattern is commonly used within `beforeSet` lifecycle hooks to ensure that by the time a config property is set,
87
+ it always holds a valid Neo.mjs instance.
88
+
89
+ You can typically configure such properties using one of three methods:
90
+
91
+ 1. **A Configuration Object (Plain JavaScript Object):**
92
+ Provide a plain JavaScript object with the desired properties. Neo.mjs will automatically create an instance of the
93
+ expected class (e.g., `Neo.data.Store` for the `store` config) using this object as its configuration. This is ideal
94
+ for inline, simple definitions.
95
+
96
+ ```javascript readonly
97
+ store: { // Neo.mjs will create a Store instance from this config
98
+ model: { fields: [{name: 'id'}, {name: 'name'}] },
99
+ data: [{id: 1, name: 'Item 1'}]
100
+ }
101
+ ```
102
+
103
+ 2. **A Class Reference:**
104
+ Pass a direct reference to the Neo.mjs class. The framework will automatically instantiate this class when the
105
+ component is created.
106
+
107
+ ```javascript readonly
108
+ import MyCustomStore from './MyCustomStore.mjs';
109
+
110
+ // ...
111
+ store: MyCustomStore // Neo.mjs will create an instance of MyCustomStore
112
+ ```
113
+
114
+ 3. **A Pre-created Instance:**
115
+ Provide an already instantiated Neo.mjs object (typically created using`Neo.create()`). This is useful when you need
116
+ to share a single instance across multiple components or manage its lifecycle externally.
117
+
118
+ ```javascript readonly
119
+ const mySharedStore = Neo.create(Neo.data.Store, { /* ... */ });
120
+
121
+ // ...
122
+ store: mySharedStore // Pass an already existing Store instance
123
+ ```
124
+
125
+ This flexibility allows you to choose the most convenient and appropriate configuration style for your specific use case,
126
+ from quick inline setups to robust, reusable class-based architectures.
127
+
128
+ ### Real-World Example: `Neo.grid.Container`'s `store` config
129
+
130
+ A prime example of `beforeSetInstance` in action is the `store` config within `Neo.grid.Container`.
131
+ The `beforeSetStore` hook ensures that the `store` property always holds a valid `Neo.data.Store` instance,
132
+ regardless of how it was initially configured.
133
+
134
+ ```javascript readonly
135
+ import ClassSystemUtil from '../../src/util/ClassSystem.mjs';
136
+ import Store from '../../src/data/Store.mjs';
137
+
138
+ class GridContainer extends Neo.container.Base {
139
+ static config = {
140
+ className: 'Neo.grid.Container',
141
+ store_ : null // The reactive store config
142
+ }
143
+
144
+ /**
145
+ * Triggered before the store config gets changed.
146
+ * @param {Object|Neo.data.Store|null} value
147
+ * @param {Neo.data.Store} oldValue
148
+ * @protected
149
+ */
150
+ beforeSetStore(value, oldValue) {
151
+ if (value) {
152
+ // This ensures that 'value' is always a Neo.data.Store instance.
153
+ // It handles plain objects (creating a new Store), class references,
154
+ // or pre-existing instances.
155
+ value = ClassSystemUtil.beforeSetInstance(value, Store);
156
+ }
157
+ return value;
158
+ }
159
+
160
+ // ... other methods
161
+ }
162
+
163
+ Neo.setupClass(GridContainer);
164
+ ```
165
+
166
+ In this example, `ClassSystemUtil.beforeSetInstance(value, Store)` intelligently processes the `value`:
167
+ * If `value` is a plain JavaScript object, it creates a new `Neo.data.Store` instance using that object as its config.
168
+ * If `value` is a `Neo.data.Store` class reference, it instantiates that class.
169
+ * If `value` is already a `Neo.data.Store` instance, it returns it as is.
170
+
171
+ This pattern is crucial for providing a flexible yet robust API for configuring complex properties.
172
+
173
+ ## 5. The Role of `Neo.setupClass()` and the Global `Neo` Namespace
174
+
175
+ When you define a class in Neo.mjs and pass it to `Neo.setupClass()`, the framework performs several crucial operations.
176
+ One of the most significant is to **enhance the global `Neo` namespace** with a reference to your newly defined class.
177
+
178
+ This means that after `Neo.setupClass(MyClass)` is executed, your class becomes accessible globally via
179
+ `Neo.[your.class.name]`, where `[your.class.name]` corresponds to the `className` config you defined (e.g.,
180
+ `Neo.button.Base`, `Neo.form.field.Text`, or your custom `My.Custom.Class`).
181
+
182
+ **Implications for Class Extension and Usage:**
183
+
184
+ * **Global Accessibility**: You can refer to any framework class (or your own custom classes after they've been set
185
+ up) using their full `Neo` namespace path (e.g., `Neo.button.Base`, `Neo.container.Base`) anywhere in your
186
+ application code, even
187
+ without an explicit ES module import for that specific class.
188
+ * **Convenience vs. Best Practice**: While `extends Neo.button.Base` might technically work without an
189
+ `import Button from '...'`, it is generally **not recommended** for application code. Explicit ES module imports
190
+ (e.g., `import Button from '../button/Base.mjs';`) are preferred because they:
191
+ * **Improve Readability**: Clearly show the dependencies of your module.
192
+ * **Enhance Tooling**: Enable better static analysis, auto-completion, and refactoring support in modern IDEs.
193
+ * **Ensure Consistency**: Promote a consistent and predictable coding style.
194
+ * **Framework Internal Use**: The global `Neo` namespace is heavily utilized internally by the framework itself for
195
+ its class registry, dependency resolution, and dynamic instantiation (e.g., when using `ntype` or `module` configs).
196
+
197
+ Understanding this mechanism clarifies how Neo.mjs manages its class system and provides the underlying flexibility for
198
+ its configuration-driven approach.
199
+
200
+ ## 5. Practical Examples: Models, Stores, and Controllers
201
+
202
+ The principles of class extension apply universally across all Neo.mjs class types.
203
+
204
+ ### Extending `Neo.data.Model`
205
+
206
+ Models define the structure and behavior of individual data records. While reactive configs can be used for class-level
207
+ properties of a Model (e.g., a global setting for all products), properties that vary per record (like `price` or
208
+ `discount`) should be defined as fields within the `fields` array. Neo.mjs provides `convert` and `calculate`
209
+ functions directly on field definitions for per-record logic.
210
+
211
+ ```javascript readonly
212
+ import Model from '../../src/data/Model.mjs';
213
+
214
+ class ProductModel extends Model {
215
+ static config = {
216
+ className: 'App.model.Product',
217
+ fields: [
218
+ {name: 'id', type: 'Number'},
219
+ {name: 'name', type: 'String'},
220
+ {name: 'price', type: 'Number', defaultValue: 0,
221
+ // Use a convert function for field-level validation or transformation
222
+ convert: value => {
223
+ if (typeof value !== 'number' || value < 0) {
224
+ console.warn('Price field must be a non-negative number!');
225
+ return 0;
226
+ }
227
+ return value;
228
+ }
229
+ },
230
+ {name: 'discount', type: 'Number', defaultValue: 0,
231
+ // Use a convert function for field-level validation or transformation
232
+ convert: value => {
233
+ if (typeof value !== 'number' || value < 0 || value > 1) {
234
+ console.warn('Discount field must be a number between 0 and 1!');
235
+ return 0;
236
+ }
237
+ return value;
238
+ }
239
+ },
240
+ {name: 'discountedPrice', type: 'Number',
241
+ // Use a calculate function for derived values based on other fields in the record
242
+ calculate: (data) => {
243
+ // 'data' contains the raw field values of the current record
244
+ return data.price * (1 - data.discount);
245
+ }
246
+ }
247
+ ]
248
+ }
249
+ }
250
+
251
+ Neo.setupClass(ProductModel);
252
+ ```
253
+
254
+ ### Extending `Neo.data.Store`
255
+
256
+ Stores manage collections of data records, often using a defined `Model`.
257
+
258
+ ```javascript readonly
259
+ import Store from '../../src/data/Store.mjs';
260
+ import ProductModel from './ProductModel.mjs'; // Assuming ProductModel is in the same directory
261
+
262
+ class ProductsStore extends Store {
263
+ static config = {
264
+ className: 'App.store.Products',
265
+ model : ProductModel, // Use our custom ProductModel
266
+ autoLoad : true,
267
+ url : '/api/products', // Example API endpoint
268
+ sorters : [{
269
+ property : 'name',
270
+ direction: 'ASC'
271
+ }]
272
+ }
273
+
274
+ // Custom method to filter by price range
275
+ filterByPriceRange(min, max) {
276
+ // The idiomatic way to apply filters is by setting the 'filters' config.
277
+ // This replaces any existing filters.
278
+ this.filters = [{
279
+ property: 'price',
280
+ operator: '>=',
281
+ value : min
282
+ }, {
283
+ property: 'price',
284
+ operator: '<=',
285
+ value : max
286
+ }];
287
+ }
288
+
289
+ // To add filters without replacing existing ones, you would typically
290
+ // read the current filters, add new ones, and then set the filters config.
291
+ // Example (conceptual, not part of the class):
292
+ /*
293
+ addPriceRangeFilter(min, max) {
294
+ const currentFilters = this.filters ? [...this.filters] : [];
295
+ currentFilters.push({
296
+ property: 'price',
297
+ operator: '>=',
298
+ value : min
299
+ }, {
300
+ property: 'price',
301
+ operator: '<=',
302
+ value : max
303
+ });
304
+ this.filters = currentFilters;
305
+ }
306
+ */
307
+ }
308
+
309
+ Neo.setupClass(ProductsStore);
310
+ ```
311
+
312
+ ### Extending `Neo.controller.Component`
313
+
314
+ Controllers encapsulate logic related to components, often handling events or managing state.
315
+
316
+ ```javascript readonly
317
+ import ComponentController from '../../src/controller/Component.mjs';
318
+
319
+ class MyCustomController extends ComponentController {
320
+ static config = {
321
+ className: 'App.controller.MyCustom',
322
+ // A reactive property to manage a piece of controller-specific state
323
+ isActive_: false
324
+ }
325
+
326
+ onConstructed() {
327
+ super.onConstructed();
328
+ console.log('MyCustomController constructed!');
329
+ }
330
+
331
+ afterSetIsActive(value, oldValue) {
332
+ console.log(`Controller active state changed from ${oldValue} to ${value}`);
333
+ // Perform actions based on active state change
334
+ if (value) {
335
+ this.doSomethingActive();
336
+ } else {
337
+ this.doSomethingInactive();
338
+ }
339
+ }
340
+
341
+ doSomethingActive() {
342
+ console.log('Controller is now active!');
343
+ // Example: enable a feature, start a timer
344
+ }
345
+
346
+ doSomethingInactive() {
347
+ console.log('Controller is now inactive!');
348
+ // Example: disable a feature, clear a timer
349
+ }
350
+ }
351
+
352
+ Neo.setupClass(MyCustomController);
353
+ ```
354
+
355
+ ## Conclusion
356
+
357
+ The class extension mechanism, coupled with the reactive config system and `Neo.setupClass()`, forms the backbone of
358
+ development in Neo.mjs. By mastering these principles, you can create highly modular, maintainable, and powerful
359
+ applications that seamlessly integrate with the framework's core.
@@ -17,7 +17,7 @@ Depending on the `ntype`, additional properties can be provided to customize the
17
17
 
18
18
  Example:
19
19
 
20
- ```javascript
20
+ ```javascript readonly
21
21
  layout: {
22
22
  ntype: 'vbox',
23
23
  align: 'center'
@@ -66,33 +66,33 @@ sections or forms where elements flow from top to bottom.
66
66
 
67
67
  ```javascript live-preview
68
68
  import Container from '../container/Base.mjs';
69
- import Button from '../button/Base.mjs';
69
+ import Button from '../button/Base.mjs';
70
70
 
71
- class VBoxExample extends Container {
71
+ class MainView extends Container {
72
72
  static config = {
73
73
  className: 'Example.view.VBoxExample',
74
74
  layout: {
75
75
  ntype: 'vbox',
76
76
  align: 'center', // Center items horizontally
77
- pack: 'center' // Center items vertically
77
+ pack : 'center' // Center items vertically
78
78
  },
79
79
  items: [{
80
80
  module: Button,
81
- text: 'Button 1',
82
- width: 100
81
+ text : 'Button 1',
82
+ width : 100
83
83
  }, {
84
84
  module: Button,
85
- text: 'Button 2',
86
- width: 150
85
+ text : 'Button 2',
86
+ width : 150
87
87
  }, {
88
88
  module: Button,
89
- text: 'Button 3',
90
- flex: 1 // This button will expand to fill remaining vertical space
89
+ text : 'Button 3',
90
+ flex : 1 // This button will expand to fill remaining vertical space
91
91
  }]
92
92
  }
93
93
  }
94
94
 
95
- Neo.setupClass(VBoxExample);
95
+ MainView = Neo.setupClass(MainView);
96
96
  ```
97
97
 
98
98
  #### 2. HBox Layout (`ntype: 'hbox'`)
@@ -121,34 +121,34 @@ navigation menus, or any scenario where elements need to be displayed side-by-si
121
121
  **Example:**
122
122
 
123
123
  ```javascript live-preview
124
+ import Button from '../button/Base.mjs';
124
125
  import Container from '../container/Base.mjs';
125
- import Button from '../button/Base.mjs';
126
126
 
127
- class HBoxExample extends Container {
127
+ class MainView extends Container {
128
128
  static config = {
129
129
  className: 'Example.view.HBoxExample',
130
130
  layout: {
131
131
  ntype: 'hbox',
132
132
  align: 'center', // Center items vertically
133
- pack: 'start' // Pack items to the left
133
+ pack : 'start' // Pack items to the left
134
134
  },
135
135
  items: [{
136
136
  module: Button,
137
- text: 'Button A',
137
+ text : 'Button A',
138
138
  height: 50
139
139
  }, {
140
140
  module: Button,
141
- text: 'Button B',
141
+ text : 'Button B',
142
142
  height: 70
143
143
  }, {
144
144
  module: Button,
145
- text: 'Button C',
146
- flex: 1 // This button will expand to fill remaining horizontal space
145
+ text : 'Button C',
146
+ flex : 1 // This button will expand to fill remaining horizontal space
147
147
  }]
148
148
  }
149
149
  }
150
150
 
151
- Neo.setupClass(HBoxExample);
151
+ MainView = Neo.setupClass(MainView);
152
152
  ```
153
153
 
154
154
  #### 3. Card Layout (`ntype: 'card'`)
@@ -174,51 +174,53 @@ hidden.
174
174
  **Example:**
175
175
 
176
176
  ```javascript live-preview
177
+ import Button from '../button/Base.mjs';
177
178
  import Container from '../container/Base.mjs';
178
- import Button from '../button/Base.mjs';
179
179
 
180
- class CardExample extends Container {
180
+ class MainView extends Container {
181
181
  static config = {
182
182
  className: 'Example.view.CardExample',
183
+ ntype : 'card-example-container',
183
184
  layout: {
184
185
  ntype: 'card',
185
186
  activeIndex: 0 // Start with the first card active
186
187
  },
187
- items: [{
188
+ itemDefaults: {
188
189
  module: Container,
189
- cls: 'card-panel',
190
+ cls : 'card-panel',
191
+ layout: 'base'
192
+ },
193
+ items: [{
190
194
  items: [{
191
195
  module: Button,
192
- text: 'Go to Card 2',
193
- handler: function() {
194
- this.up('container').layout.activeIndex = 1;
196
+ text : 'Go to Card 2',
197
+ handler() {
198
+ this.up('card-example-container').layout.activeIndex = 1
195
199
  }
196
200
  }],
197
201
  style: {
198
202
  backgroundColor: '#e0f7fa',
199
- padding: '20px',
200
- textAlign: 'center'
203
+ padding : '20px',
204
+ textAlign : 'center'
201
205
  }
202
206
  }, {
203
- module: Container,
204
- cls: 'card-panel',
205
207
  items: [{
206
208
  module: Button,
207
- text: 'Go to Card 1',
208
- handler: function() {
209
- this.up('container').layout.activeIndex = 0;
209
+ text : 'Go to Card 1',
210
+ handler() {
211
+ this.up('card-example-container').layout.activeIndex = 0
210
212
  }
211
213
  }],
212
214
  style: {
213
215
  backgroundColor: '#fff3e0',
214
- padding: '20px',
215
- textAlign: 'center'
216
+ padding : '20px',
217
+ textAlign : 'center'
216
218
  }
217
219
  }]
218
220
  }
219
221
  }
220
222
 
221
- Neo.setupClass(CardExample);
223
+ MainView = Neo.setupClass(MainView);
222
224
  ```
223
225
 
224
226
  #### Lazy Loading with Card Layouts
@@ -230,7 +232,7 @@ This is achieved by defining the `module` property of an item within the `items`
230
232
  dynamic `import()` statement. For example, in the Portal app's `Viewport.mjs`,
231
233
  modules are lazy-loaded like this:
232
234
 
233
- ```javascript
235
+ ```javascript readonly
234
236
  items: [
235
237
  {module: () => import('./home/MainContainer.mjs')},
236
238
  {module: () => import('./learn/MainContainer.mjs')},
@@ -243,4 +245,4 @@ module, and then creates the component instance. This ensures that resources are
243
245
  needed.
244
246
 
245
247
  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.
248
+ layout types and concepts like nesting layouts for complex UI structures.