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.
Files changed (149) hide show
  1. package/ServiceWorker.mjs +2 -2
  2. package/apps/colors/view/GridContainer.mjs +1 -1
  3. package/apps/covid/view/AttributionComponent.mjs +1 -1
  4. package/apps/covid/view/HeaderContainer.mjs +6 -6
  5. package/apps/covid/view/MainContainerController.mjs +5 -5
  6. package/apps/covid/view/TableContainerController.mjs +1 -1
  7. package/apps/covid/view/country/Gallery.mjs +13 -13
  8. package/apps/covid/view/country/Helix.mjs +13 -13
  9. package/apps/covid/view/country/HistoricalDataTable.mjs +1 -1
  10. package/apps/email/view/Viewport.mjs +2 -2
  11. package/apps/form/view/FormPageContainer.mjs +2 -3
  12. package/apps/form/view/SideNavList.mjs +1 -1
  13. package/apps/portal/index.html +1 -1
  14. package/apps/portal/resources/data/examples_dist_esm.json +1 -1
  15. package/apps/portal/resources/data/examples_dist_prod.json +2 -2
  16. package/apps/portal/view/HeaderToolbar.mjs +3 -3
  17. package/apps/portal/view/about/Container.mjs +2 -2
  18. package/apps/portal/view/about/MemberContainer.mjs +3 -3
  19. package/apps/portal/view/blog/List.mjs +7 -7
  20. package/apps/portal/view/examples/List.mjs +4 -4
  21. package/apps/portal/view/home/ContentBox.mjs +2 -2
  22. package/apps/portal/view/home/FeatureSection.mjs +3 -3
  23. package/apps/portal/view/home/FooterContainer.mjs +7 -7
  24. package/apps/portal/view/home/parts/AfterMath.mjs +3 -3
  25. package/apps/portal/view/home/parts/MainNeo.mjs +3 -3
  26. package/apps/portal/view/home/parts/References.mjs +6 -6
  27. package/apps/portal/view/learn/ContentComponent.mjs +18 -11
  28. package/apps/portal/view/learn/PageSectionsContainer.mjs +1 -1
  29. package/apps/portal/view/learn/PageSectionsList.mjs +2 -2
  30. package/apps/portal/view/services/Component.mjs +16 -16
  31. package/apps/realworld/view/FooterComponent.mjs +1 -1
  32. package/apps/realworld/view/HeaderComponent.mjs +8 -8
  33. package/apps/realworld/view/HomeComponent.mjs +6 -6
  34. package/apps/realworld/view/article/CommentComponent.mjs +4 -4
  35. package/apps/realworld/view/article/Component.mjs +14 -14
  36. package/apps/realworld/view/article/CreateCommentComponent.mjs +3 -3
  37. package/apps/realworld/view/article/CreateComponent.mjs +3 -3
  38. package/apps/realworld/view/article/PreviewComponent.mjs +1 -1
  39. package/apps/realworld/view/article/TagListComponent.mjs +2 -2
  40. package/apps/realworld/view/user/ProfileComponent.mjs +8 -8
  41. package/apps/realworld/view/user/SettingsComponent.mjs +4 -4
  42. package/apps/realworld/view/user/SignUpComponent.mjs +4 -4
  43. package/apps/realworld2/view/FooterComponent.mjs +1 -1
  44. package/apps/realworld2/view/HomeContainer.mjs +3 -3
  45. package/apps/realworld2/view/article/DetailsContainer.mjs +1 -1
  46. package/apps/realworld2/view/article/PreviewComponent.mjs +7 -7
  47. package/apps/realworld2/view/article/TagListComponent.mjs +2 -2
  48. package/apps/realworld2/view/user/ProfileContainer.mjs +1 -1
  49. package/apps/route/view/center/CardAdministration.mjs +2 -2
  50. package/apps/route/view/center/CardAdministrationDenied.mjs +1 -1
  51. package/apps/route/view/center/CardContact.mjs +2 -2
  52. package/apps/route/view/center/CardHome.mjs +1 -1
  53. package/apps/route/view/center/CardSection1.mjs +1 -1
  54. package/apps/route/view/center/CardSection2.mjs +1 -1
  55. package/apps/sharedcovid/view/AttributionComponent.mjs +1 -1
  56. package/apps/sharedcovid/view/HeaderContainer.mjs +6 -6
  57. package/apps/sharedcovid/view/MainContainerController.mjs +5 -5
  58. package/apps/sharedcovid/view/TableContainerController.mjs +1 -1
  59. package/apps/sharedcovid/view/country/Gallery.mjs +13 -13
  60. package/apps/sharedcovid/view/country/Helix.mjs +13 -13
  61. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -1
  62. package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +1 -1
  63. package/apps/shareddialog/view/MainContainer.mjs +1 -1
  64. package/buildScripts/createApp.mjs +2 -2
  65. package/learn/Glossary.md +261 -0
  66. package/learn/README.md +9 -14
  67. package/learn/benefits/ConfigSystem.md +536 -26
  68. package/learn/benefits/Effort.md +47 -2
  69. package/learn/benefits/Features.md +50 -32
  70. package/learn/benefits/FormsEngine.md +54 -24
  71. package/learn/benefits/MultiWindow.md +31 -5
  72. package/learn/benefits/Quick.md +45 -12
  73. package/learn/benefits/RPCLayer.md +75 -0
  74. package/learn/benefits/Speed.md +17 -12
  75. package/learn/guides/Collections.md +436 -0
  76. package/learn/guides/ConfigSystemDeepDive.md +280 -0
  77. package/learn/guides/CustomComponents.md +256 -14
  78. package/learn/guides/DeclarativeComponentTreesVsImperativeVdom.md +17 -17
  79. package/learn/guides/ExtendingNeoClasses.md +331 -0
  80. package/learn/guides/Forms.md +449 -1
  81. package/learn/guides/InstanceLifecycle.md +295 -1
  82. package/learn/guides/Layouts.md +246 -1
  83. package/learn/guides/MainThreadAddons.md +475 -0
  84. package/learn/guides/Records.md +286 -0
  85. package/learn/guides/WorkingWithVDom.md +14 -14
  86. package/learn/guides/form_fields/ComboBox.md +241 -0
  87. package/learn/tree.json +57 -51
  88. package/package.json +2 -2
  89. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +9 -0
  90. package/src/DefaultConfig.mjs +2 -2
  91. package/src/Main.mjs +8 -7
  92. package/src/Neo.mjs +16 -2
  93. package/src/button/Base.mjs +2 -2
  94. package/src/calendar/view/SettingsContainer.mjs +2 -2
  95. package/src/calendar/view/YearComponent.mjs +9 -9
  96. package/src/calendar/view/calendars/ColorsList.mjs +1 -1
  97. package/src/calendar/view/calendars/List.mjs +1 -1
  98. package/src/calendar/view/month/Component.mjs +15 -15
  99. package/src/calendar/view/week/Component.mjs +12 -12
  100. package/src/calendar/view/week/EventDragZone.mjs +4 -4
  101. package/src/calendar/view/week/TimeAxisComponent.mjs +3 -3
  102. package/src/component/Base.mjs +17 -2
  103. package/src/component/Carousel.mjs +2 -2
  104. package/src/component/Chip.mjs +3 -3
  105. package/src/component/Circle.mjs +2 -2
  106. package/src/component/DateSelector.mjs +8 -8
  107. package/src/component/Helix.mjs +1 -1
  108. package/src/component/Label.mjs +3 -18
  109. package/src/component/Legend.mjs +3 -3
  110. package/src/component/MagicMoveText.mjs +6 -14
  111. package/src/component/Process.mjs +3 -3
  112. package/src/component/Progress.mjs +1 -1
  113. package/src/component/StatusBadge.mjs +2 -2
  114. package/src/component/Timer.mjs +2 -2
  115. package/src/component/Toast.mjs +5 -3
  116. package/src/container/AccordionItem.mjs +2 -2
  117. package/src/container/Base.mjs +1 -1
  118. package/src/core/Base.mjs +18 -2
  119. package/src/date/DayViewComponent.mjs +2 -2
  120. package/src/date/SelectorContainer.mjs +1 -1
  121. package/src/form/field/CheckBox.mjs +4 -4
  122. package/src/form/field/ComboBox.mjs +6 -1
  123. package/src/form/field/FileUpload.mjs +25 -39
  124. package/src/form/field/Range.mjs +1 -1
  125. package/src/form/field/Text.mjs +3 -3
  126. package/src/form/field/TextArea.mjs +2 -3
  127. package/src/grid/Body.mjs +6 -2
  128. package/src/list/Color.mjs +2 -2
  129. package/src/main/DeltaUpdates.mjs +157 -98
  130. package/src/main/addon/AmCharts.mjs +53 -73
  131. package/src/main/addon/Base.mjs +11 -0
  132. package/src/main/addon/MonacoEditor.mjs +31 -58
  133. package/src/manager/ClassHierarchy.mjs +114 -0
  134. package/src/menu/List.mjs +1 -1
  135. package/src/plugin/Popover.mjs +2 -2
  136. package/src/sitemap/Component.mjs +1 -1
  137. package/src/table/Body.mjs +6 -2
  138. package/src/tooltip/Base.mjs +1 -6
  139. package/src/tree/Accordion.mjs +3 -3
  140. package/src/vdom/Helper.mjs +21 -19
  141. package/src/worker/App.mjs +1 -2
  142. package/src/worker/Base.mjs +6 -4
  143. package/src/worker/Canvas.mjs +2 -3
  144. package/src/worker/Data.mjs +5 -7
  145. package/src/worker/Task.mjs +2 -3
  146. package/src/worker/VDom.mjs +3 -4
  147. package/src/worker/mixin/RemoteMethodAccess.mjs +4 -1
  148. package/learn/guides/MainThreadAddonExample.md +0 -15
  149. package/learn/guides/MainThreadAddonIntro.md +0 -44
@@ -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
- ```jsx
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
 
@@ -0,0 +1,331 @@
1
+ # Extending Neo Classes
2
+
3
+ Neo.mjs is built upon a robust and consistent class system. Understanding how to extend framework classes is fundamental to building custom functionality, whether you're creating new UI components, defining data structures, or implementing application logic.
4
+
5
+ This guide covers the universal principles of class extension in Neo.mjs, which apply across all class types, not just UI components.
6
+
7
+ ## 1. The `static config` Block: Defining Properties
8
+
9
+ Every Neo.mjs class utilizes a `static config` block. This is where you define the properties that instances of your class will possess. These properties can be simple values, objects, or even other Neo.mjs class configurations.
10
+
11
+ ```javascript readonly
12
+ class MyBaseClass extends Neo.core.Base {
13
+ static config = {
14
+ className: 'My.Base.Class', // Unique identifier for the class
15
+ myString : 'Hello',
16
+ myNumber : 123
17
+ }
18
+ }
19
+
20
+ export default Neo.setupClass(MyBaseClass);
21
+ ```
22
+
23
+ Common configs you'll encounter include `className` (a unique string identifier for your class) and `ntype` (a shorthand alias for component creation).
24
+
25
+ ## 2. Reactive Configs: The Trailing Underscore (`_`)
26
+
27
+ A cornerstone of Neo.mjs's reactivity is the trailing underscore (`_`) convention for configs defined in `static config`. When you append an underscore to a config name (e.g., `myConfig_`), the framework automatically generates a reactive getter and setter for it.
28
+
29
+ ```javascript readonly
30
+ class MyReactiveClass extends Neo.core.Base {
31
+ static config = {
32
+ className : 'My.Reactive.Class',
33
+ myReactiveConfig_: 'initial value' // This config is reactive
34
+ }
35
+
36
+ onConstructed() {
37
+ super.onConstructed();
38
+ console.log(this.myReactiveConfig); // Accesses the getter
39
+ this.myReactiveConfig = 'new value'; // Triggers the setter
40
+ }
41
+ }
42
+
43
+ export default Neo.setupClass(MyReactiveClass);
44
+ ```
45
+
46
+ Assigning a new value to a reactive property (e.g., `this.myReactiveProp = 'new value'`) triggers its setter, which in turn can invoke lifecycle hooks, enabling automatic updates and side effects. Properties without the underscore are static and do not trigger this reactive behavior.
47
+
48
+ ## 3. Configuration Lifecycle Hooks (`beforeSet`, `afterSet`, `beforeGet`)
49
+
50
+ For every reactive config (`myConfig_`), Neo.mjs provides three optional lifecycle hooks that you can implement in your class. These methods are automatically called by the framework during the config's lifecycle, offering powerful interception points:
51
+
52
+ * **`beforeSetMyConfig(value, oldValue)`**:
53
+ * **Purpose**: Intercepts the value *before* it is set. Ideal for validation, type coercion, or transforming the incoming value.
54
+ * **Return Value**: Return the (potentially modified) `value` that should be set. Returning `undefined` or `null` will prevent the value from being set.
55
+
56
+ * **`afterSetMyConfig(value, oldValue)`**:
57
+ * **Purpose**: Executed *after* the value has been successfully set. Ideal for triggering side effects, updating the UI (e.g., calling `this.update()` for components), or firing events.
58
+ * **Return Value**: None.
59
+
60
+ * **`beforeGetMyConfig(value)`**:
61
+ * **Purpose**: Intercepts the value *before* it is returned by the getter. Useful for lazy initialization, computing values on demand, or returning a transformed version of the stored value.
62
+ * **Return Value**: Return the `value` that should be returned by the getter.
63
+
64
+ ### Overriding Lifecycle Hooks: `super` vs. Full Override
65
+
66
+ When extending a Neo.mjs class, you often need to customize the behavior of inherited lifecycle hooks (like `afterSet*`, `onConstructed`, etc.). You have two primary approaches:
67
+
68
+ #### 1. Extending Parent Behavior (Calling `super`)
69
+
70
+ This is the most common and recommended approach. By calling `super.methodName(...)`, you ensure that the parent class's implementation of the hook is executed. You can then add your custom logic either before or after the `super` call.
71
+
72
+ This approach is crucial for maintaining the framework's intended behavior and ensuring that inherited features continue to function correctly.
73
+
74
+ ```javascript readonly
75
+ import Button from '../../src/button/Base.mjs';
76
+
77
+ class MyExtendedButton extends Button {
78
+ static config = {
79
+ className: 'My.Extended.Component',
80
+ // text_ config is inherited from Button.Base
81
+ // We can set a default value here if needed, or rely on button.Base's default
82
+ text: 'New Default Text'
83
+ }
84
+
85
+ // Example: Adding logic after the parent's afterSetText
86
+ afterSetText(value, oldValue) {
87
+ // Add your custom pre-processing logic here
88
+ super.afterSetText(value, oldValue);
89
+ console.log(`Custom logic: Button text changed to "${value}"`);
90
+ // Add your custom post-processing logic here
91
+ }
92
+ }
93
+
94
+ export default Neo.setupClass(MyExtendedButton);
95
+ ```
96
+
97
+ #### 2. Completely Overriding Parent Behavior (No `super` Call)
98
+
99
+ In rare cases, you might want to completely replace the parent class's implementation of a hook. This is achieved by simply omitting the `super` call within your overridden method.
100
+
101
+ **Caution**: Use this approach with extreme care. You must fully understand the parent's implementation and ensure that your override does not break essential framework functionality or inherited features. This is generally reserved for advanced scenarios where you need full control over the hook's execution.
102
+
103
+ ```javascript readonly
104
+ import Button from '../../src/button/Base.mjs';
105
+
106
+ class MyFullyOverriddenButton extends Button {
107
+ static config = {
108
+ className: 'My.Fully.Overridden.Component',
109
+ text : 'New Default Text'
110
+ }
111
+
112
+ // Example: Completely overriding afterSetText
113
+ afterSetText(value, oldValue) {
114
+ // No super.afterSetText(value, oldValue); call
115
+ console.log(`Fully custom logic: Button text changed to "${value}"`);
116
+ // The parent's afterSetText will NOT be executed
117
+ // This means that in this case you need to take care on your own to map the text value to the vdom.
118
+ }
119
+ }
120
+
121
+ export default Neo.setupClass(MyFullyOverriddenButton);
122
+ ```
123
+
124
+
125
+
126
+ ```javascript readonly
127
+ class MyHookedClass extends Neo.core.Base {
128
+ static config = {
129
+ className: 'My.Hooked.Class',
130
+ myValue_ : 0
131
+ }
132
+
133
+ beforeSetMyValue(value, oldValue) {
134
+ if (typeof value !== 'number' || value < 0) {
135
+ console.warn('myValue must be a non-negative number!');
136
+ return 0; // Default to 0 if invalid
137
+ }
138
+ return value;
139
+ }
140
+
141
+ afterSetMyValue(value, oldValue) {
142
+ console.log(`myValue changed from ${oldValue} to ${value}`);
143
+ // In a component, you might call this.update() here
144
+ }
145
+
146
+ beforeGetMyValue(value) {
147
+ // Example: lazy initialization or computed value
148
+ if (value === 0 && !this._initialized) {
149
+ console.log('Initializing myValue on first access');
150
+ this._initialized = true;
151
+ return 10; // Return a default initial value
152
+ }
153
+ return value;
154
+ }
155
+ }
156
+
157
+ export default Neo.setupClass(MyHookedClass);
158
+ ```
159
+
160
+ ## 4. The Role of `Neo.setupClass()` and the Global `Neo` Namespace
161
+
162
+ When you define a class in Neo.mjs and pass it to `Neo.setupClass()`, the framework performs several crucial operations. One of the most significant is to **enhance the global `Neo` namespace** with a reference to your newly defined class.
163
+
164
+ This means that after `Neo.setupClass(MyClass)` is executed, your class becomes accessible globally via `Neo.[your.class.name]`, where `[your.class.name]` corresponds to the `className` config you defined (e.g., `Neo.button.Base`, `Neo.form.field.Text`, or your custom `My.Custom.Class`).
165
+
166
+ **Implications for Class Extension and Usage:**
167
+
168
+ * **Global Accessibility**: You can refer to any framework class (or your own custom classes after they've been set up) using their full `Neo` namespace path (e.g., `Neo.button.Base`, `Neo.container.Base`) anywhere in your application code, even without an explicit ES module import for that specific class.
169
+ * **Convenience vs. Best Practice**: While `extends Neo.button.Base` might technically work without an `import Button from '...'`, it is generally **not recommended** for application code. Explicit ES module imports (e.g., `import Button from '../button/Base.mjs';`) are preferred because they:
170
+ * **Improve Readability**: Clearly show the dependencies of your module.
171
+ * **Enhance Tooling**: Enable better static analysis, auto-completion, and refactoring support in modern IDEs.
172
+ * **Ensure Consistency**: Promote a consistent and predictable coding style.
173
+ * **Framework Internal Use**: The global `Neo` namespace is heavily utilized internally by the framework itself for its class registry, dependency resolution, and dynamic instantiation (e.g., when using `ntype` or `module` configs).
174
+
175
+ Understanding this mechanism clarifies how Neo.mjs manages its class system and provides the underlying flexibility for its configuration-driven approach.
176
+
177
+ ## 5. Practical Examples: Models, Stores, and Controllers
178
+
179
+ The principles of class extension apply universally across all Neo.mjs class types.
180
+
181
+ ### Extending `Neo.data.Model`
182
+
183
+ Models define the structure and behavior of individual data records. While reactive configs can be used for class-level properties of a Model (e.g., a global setting for all products), properties that vary per record (like `price` or `discount`) should be defined as fields within the `fields` array. Neo.mjs provides `convert` and `calculate` functions directly on field definitions for per-record logic.
184
+
185
+ ```javascript readonly
186
+ import Model from '../../src/data/Model.mjs';
187
+
188
+ class ProductModel extends Model {
189
+ static config = {
190
+ className: 'App.model.Product',
191
+ fields: [
192
+ {name: 'id', type: 'Number'},
193
+ {name: 'name', type: 'String'},
194
+ {name: 'price', type: 'Number', defaultValue: 0,
195
+ // Use a convert function for field-level validation or transformation
196
+ convert: value => {
197
+ if (typeof value !== 'number' || value < 0) {
198
+ console.warn('Price field must be a non-negative number!');
199
+ return 0;
200
+ }
201
+ return value;
202
+ }
203
+ },
204
+ {name: 'discount', type: 'Number', defaultValue: 0,
205
+ // Use a convert function for field-level validation or transformation
206
+ convert: value => {
207
+ if (typeof value !== 'number' || value < 0 || value > 1) {
208
+ console.warn('Discount field must be a number between 0 and 1!');
209
+ return 0;
210
+ }
211
+ return value;
212
+ }
213
+ },
214
+ {name: 'discountedPrice', type: 'Number',
215
+ // Use a calculate function for derived values based on other fields in the record
216
+ calculate: (data) => {
217
+ // 'data' contains the raw field values of the current record
218
+ return data.price * (1 - data.discount);
219
+ }
220
+ }
221
+ ]
222
+ }
223
+ }
224
+
225
+ Neo.setupClass(ProductModel);
226
+ ```
227
+
228
+ ### Extending `Neo.data.Store`
229
+
230
+ Stores manage collections of data records, often using a defined `Model`.
231
+
232
+ ```javascript readonly
233
+ import Store from '../../src/data/Store.mjs';
234
+ import ProductModel from './ProductModel.mjs'; // Assuming ProductModel is in the same directory
235
+
236
+ class ProductsStore extends Store {
237
+ static config = {
238
+ className: 'App.store.Products',
239
+ model : ProductModel, // Use our custom ProductModel
240
+ autoLoad : true,
241
+ url : '/api/products', // Example API endpoint
242
+ sorters : [{
243
+ property : 'name',
244
+ direction: 'ASC'
245
+ }]
246
+ }
247
+
248
+ // Custom method to filter by price range
249
+ filterByPriceRange(min, max) {
250
+ // The idiomatic way to apply filters is by setting the 'filters' config.
251
+ // This replaces any existing filters.
252
+ this.filters = [{
253
+ property: 'price',
254
+ operator: '>=',
255
+ value : min
256
+ }, {
257
+ property: 'price',
258
+ operator: '<=',
259
+ value : max
260
+ }];
261
+ }
262
+
263
+ // To add filters without replacing existing ones, you would typically
264
+ // read the current filters, add new ones, and then set the filters config.
265
+ // Example (conceptual, not part of the class):
266
+ /*
267
+ addPriceRangeFilter(min, max) {
268
+ const currentFilters = this.filters ? [...this.filters] : [];
269
+ currentFilters.push({
270
+ property: 'price',
271
+ operator: '>=',
272
+ value : min
273
+ }, {
274
+ property: 'price',
275
+ operator: '<=',
276
+ value : max
277
+ });
278
+ this.filters = currentFilters;
279
+ }
280
+ */
281
+ }
282
+
283
+ Neo.setupClass(ProductsStore);
284
+ ```
285
+
286
+ ### Extending `Neo.controller.Component`
287
+
288
+ Controllers encapsulate logic related to components, often handling events or managing state.
289
+
290
+ ```javascript readonly
291
+ import ComponentController from '../../src/controller/Component.mjs';
292
+
293
+ class MyCustomController extends ComponentController {
294
+ static config = {
295
+ className: 'App.controller.MyCustom',
296
+ // A reactive property to manage a piece of controller-specific state
297
+ isActive_: false
298
+ }
299
+
300
+ onConstructed() {
301
+ super.onConstructed();
302
+ console.log('MyCustomController constructed!');
303
+ }
304
+
305
+ afterSetIsActive(value, oldValue) {
306
+ console.log(`Controller active state changed from ${oldValue} to ${value}`);
307
+ // Perform actions based on active state change
308
+ if (value) {
309
+ this.doSomethingActive();
310
+ } else {
311
+ this.doSomethingInactive();
312
+ }
313
+ }
314
+
315
+ doSomethingActive() {
316
+ console.log('Controller is now active!');
317
+ // Example: enable a feature, start a timer
318
+ }
319
+
320
+ doSomethingInactive() {
321
+ console.log('Controller is now inactive!');
322
+ // Example: disable a feature, clear a timer
323
+ }
324
+ }
325
+
326
+ Neo.setupClass(MyCustomController);
327
+ ```
328
+
329
+ ## Conclusion
330
+
331
+ The class extension mechanism, coupled with the reactive config system and `Neo.setupClass()`, forms the backbone of development in Neo.mjs. By mastering these principles, you can create highly modular, maintainable, and powerful applications that seamlessly integrate with the framework's core.