neo.mjs 10.0.0-beta.2 → 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 (52) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.4.md +41 -0
  2. package/ServiceWorker.mjs +2 -2
  3. package/apps/form/view/FormPageContainer.mjs +2 -3
  4. package/apps/portal/index.html +1 -1
  5. package/apps/portal/view/ViewportController.mjs +1 -1
  6. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  7. package/apps/portal/view/learn/ContentComponent.mjs +18 -11
  8. package/apps/portal/view/learn/MainContainerController.mjs +6 -6
  9. package/learn/README.md +9 -14
  10. package/learn/guides/datahandling/Collections.md +436 -0
  11. package/learn/guides/datahandling/Grids.md +621 -0
  12. package/learn/guides/datahandling/Records.md +287 -0
  13. package/learn/guides/{StateProviders.md → datahandling/StateProviders.md} +145 -1
  14. package/learn/guides/fundamentals/ExtendingNeoClasses.md +359 -0
  15. package/learn/guides/uibuildingblocks/CustomComponents.md +287 -0
  16. package/learn/guides/uibuildingblocks/Layouts.md +248 -0
  17. package/learn/guides/userinteraction/Forms.md +449 -0
  18. package/learn/guides/userinteraction/form_fields/ComboBox.md +241 -0
  19. package/learn/tree.json +63 -52
  20. package/package.json +2 -2
  21. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +9 -0
  22. package/src/DefaultConfig.mjs +2 -2
  23. package/src/Neo.mjs +37 -29
  24. package/src/collection/Base.mjs +29 -2
  25. package/src/component/Base.mjs +6 -16
  26. package/src/controller/Base.mjs +87 -63
  27. package/src/core/Base.mjs +72 -17
  28. package/src/core/Compare.mjs +3 -13
  29. package/src/core/Config.mjs +139 -0
  30. package/src/core/ConfigSymbols.mjs +3 -0
  31. package/src/core/Util.mjs +3 -18
  32. package/src/data/RecordFactory.mjs +22 -3
  33. package/src/form/field/ComboBox.mjs +6 -1
  34. package/src/util/Function.mjs +52 -5
  35. package/src/vdom/Helper.mjs +7 -5
  36. package/test/siesta/tests/ReactiveConfigs.mjs +112 -0
  37. package/learn/guides/CustomComponents.md +0 -45
  38. package/learn/guides/Forms.md +0 -1
  39. package/learn/guides/Layouts.md +0 -1
  40. /package/learn/guides/{Tables.md → datahandling/Tables.md} +0 -0
  41. /package/learn/guides/{ApplicationBootstrap.md → fundamentals/ApplicationBootstrap.md} +0 -0
  42. /package/learn/guides/{ConfigSystemDeepDive.md → fundamentals/ConfigSystemDeepDive.md} +0 -0
  43. /package/learn/guides/{DeclarativeComponentTreesVsImperativeVdom.md → fundamentals/DeclarativeComponentTreesVsImperativeVdom.md} +0 -0
  44. /package/learn/guides/{InstanceLifecycle.md → fundamentals/InstanceLifecycle.md} +0 -0
  45. /package/learn/guides/{MainThreadAddons.md → fundamentals/MainThreadAddons.md} +0 -0
  46. /package/learn/guides/{Mixins.md → specificfeatures/Mixins.md} +0 -0
  47. /package/learn/guides/{MultiWindow.md → specificfeatures/MultiWindow.md} +0 -0
  48. /package/learn/guides/{PortalApp.md → specificfeatures/PortalApp.md} +0 -0
  49. /package/learn/guides/{ComponentsAndContainers.md → uibuildingblocks/ComponentsAndContainers.md} +0 -0
  50. /package/learn/guides/{WorkingWithVDom.md → uibuildingblocks/WorkingWithVDom.md} +0 -0
  51. /package/learn/guides/{events → userinteraction/events}/CustomEvents.md +0 -0
  52. /package/learn/guides/{events → userinteraction/events}/DomEvents.md +0 -0
@@ -0,0 +1,287 @@
1
+
2
+ In Neo.mjs, a **Record** is a super lightweight, reactive JavaScript object, dynamically created and structured
3
+ according to a `Neo.data.Model`. Records provide a powerful way to manage application data with built-in features like
4
+ data validation, type conversion, dirty tracking, and seamless integration with `Neo.data.Store`.
5
+
6
+ This guide will cover:
7
+
8
+ - **What is a Record?**: Understanding the concept and its benefits.
9
+ - **`Neo.data.Model`**: Defining the structure and behavior of your records.
10
+ - **`Neo.data.RecordFactory`**: The engine behind reactive record creation.
11
+ - **Record Fields**: Data types, default values, mapping, and custom logic.
12
+ - **Reactivity and Dirty Tracking**: How records respond to changes and track their state.
13
+ - **Interaction with `Neo.data.Store`**: Managing collections of records.
14
+
15
+ ## What is a Record?
16
+
17
+ A Record in Neo.mjs is a dynamically generated, lightweight JavaScript object that represents a single row or item of data.
18
+ Crucially, Records do **not** extend `Neo.core.Base` or `Neo.data.Model`; they are plain objects with reactive properties.
19
+ This design choice makes them extremely performant and memory-efficient. When you modify a property of a Record, it automatically
20
+ triggers events, allowing UI components or other parts of your application to react to these changes.
21
+
22
+ **Benefits of using Records:**
23
+
24
+ - **Structured Data**: Records enforce a predefined structure based on a `Neo.data.Model`, ensuring data consistency.
25
+ - **Reactivity**: Changes to record fields are observable, simplifying UI updates and data synchronization.
26
+ - **Data Integrity**: Built-in type conversion and validation (defined in the Model) help maintain data quality.
27
+ - **Dirty Tracking**: Easily determine if a record or specific fields within it have been modified from their original state.
28
+ - **Integration with Stores**: Records are designed to work seamlessly with `Neo.data.Store` for managing collections of data.
29
+
30
+ ## `Neo.data.Model`: The Blueprint for Your Records
31
+
32
+ `Neo.data.Model` is the **central blueprint** for your Records. It defines the complete structure, data types, default
33
+ values, and any custom logic for data processing or validation. Every Record instance is an embodiment of its associated
34
+ Model. Each `Neo.data.Model` is a class that extends `Neo.core.Base`. Records, however, are instances of dynamically generated classes, not direct extensions of `Neo.core.Base`.
35
+
36
+ ### Key `Neo.data.Model` Configurations:
37
+
38
+ - **`fields`**: An array of objects, where each object defines a field of the record. This is where you specify the
39
+ data schema. Each field can have properties like:
40
+ - `name` (String, required): The unique identifier for the field within the record.
41
+ - `type` (String): The data type (e.g., `'string'`, `'number'`, `'boolean'`, `'date'`, `'int'`, `'float'`, `'html'`).
42
+ Neo.mjs provides automatic type conversion based on this type.
43
+ - `defaultValue` (Any): A value that will be assigned to the field if it's not provided when creating a record.
44
+ - `mapping` (String): A dot-separated string used to extract the field's value from a nested path within the raw
45
+ data received (e.g., `'address.street'` would map to `record.address.street`).
46
+ - `calculate` (Function): A powerful function that defines a **computed property**. The value of this field is
47
+ dynamically calculated based on other fields in the record. When the source fields change, the calculated field
48
+ automatically updates.
49
+ - `convert` (Function): A custom function to perform more complex data transformations or validations on the
50
+ field's value during assignment.
51
+ - `nullable` (Boolean): If `false`, the field cannot be `null`.
52
+ - `maxLength` (Number): Maximum length for string types. Values exceeding this may trigger a warning.
53
+ - `minLength` (Number): Minimum length for string types. Values falling below this may trigger a warning.
54
+ - **Nested Fields**: A field can itself contain a `fields` array, allowing you to define complex, hierarchical data
55
+ structures directly within your model (e.g., an `address` field with nested `street`, `city`, `zip` fields).
56
+ - **`keyProperty`**: (String, default: `'id'`) The field name that uniquely identifies each record within a
57
+ `Neo.data.Store`. This is crucial for efficient lookups and operations.
58
+ - **`trackModifiedFields`**: (Boolean, default: `false`) If `true`, the record will track changes to individual fields,
59
+ allowing you to determine which fields have been modified. **Be aware that enabling this will cause the record to store
60
+ a copy of its original data, effectively doubling the memory footprint for each record. Only enable this feature if you
61
+ specifically require granular dirty tracking.**
62
+
63
+ ### Dynamic Model Fields
64
+
65
+ While typically defined once, `Neo.data.Model` instances can have their `fields` configuration changed at runtime. If
66
+ the `fields` config of an already created `Model` instance is modified, `Neo.data.RecordFactory` will dynamically update
67
+ the associated Record class. This allows for advanced scenarios where your data schema might evolve during the application's
68
+ lifecycle.
69
+
70
+
71
+ ## `Neo.data.RecordFactory`: The Engine Behind Records
72
+
73
+ `Neo.data.RecordFactory` is a singleton class responsible for taking your `Neo.data.Model` definitions and dynamically
74
+ generating JavaScript classes for your Records. It intercepts property access on Record instances to provide reactivity,
75
+ type conversion, and dirty tracking.
76
+
77
+ When you create a new Record (typically via a `Neo.data.Store` or directly using `RecordFactory.createRecord()`), the
78
+ `RecordFactory`:
79
+
80
+ 1. Checks if a Record class for the given Model already exists. If not, it creates one. This dynamically generated class implicitly extends `Object`, making records as lightweight as possible and efficient.
81
+ 2. Defines getters and setters for each field specified in your Model. These getters and setters are what make Records
82
+ reactive.
83
+ 3. Applies default values and performs initial data parsing/conversion.
84
+ 4. Initializes dirty tracking if `trackModifiedFields` is enabled in the Model.
85
+
86
+ You generally won't interact directly with `RecordFactory` unless you're creating records outside of a `Store`.
87
+ In most real-world scenarios, when you add plain JavaScript objects to a `Neo.data.Store` or load JSON data from a backend into a `Store`, the `Store` automatically leverages `RecordFactory` to convert each item into a reactive Record. This means developers will very rarely need to use `RecordFactory` manually.
88
+
89
+ ### Example: Creating a Record Directly
90
+
91
+ ```javascript readonly
92
+ import RecordFactory from '../../src/data/RecordFactory.mjs';
93
+ import UserModel from './UserModel.mjs'; // Assuming UserModel is defined as above
94
+
95
+ const userModelInstance = Neo.create(UserModel); // Create an instance of your Model
96
+
97
+ const userRecord = RecordFactory.createRecord(userModelInstance, {
98
+ age : 28,
99
+ email : 'jane.doe@example.com',
100
+ firstName: 'Jane',
101
+ id : 101,
102
+ lastName : 'Doe',
103
+
104
+ address: {
105
+ city : 'Anytown',
106
+ street: '123 Main St'
107
+ }
108
+ });
109
+
110
+ console.log(userRecord.fullName); // Output: Jane Doe (calculated field)
111
+ userRecord.age = '30'; // Automatic type conversion from string to int
112
+ console.log(userRecord.age); // Output: 30
113
+
114
+ // Accessing nested fields
115
+ // IMPORTANT: Direct access like `userRecord.address.street` will result in a JavaScript error
116
+ // because `userRecord.address` is undefined. Always use the full string path for nested fields.
117
+ console.log(userRecord['address.street']); // Output: 123 Main St
118
+ console.log(userRecord['address.city']); // Output: Anytown
119
+
120
+ // Modifying nested fields using string path
121
+ userRecord['address.street'] = '456 Oak Ave';
122
+ console.log(userRecord['address.street']); // Output: 456 Oak Ave
123
+
124
+ userRecord['address.city'] = 'Newville';
125
+ console.log(userRecord['address.city']); // Output: Newville
126
+
127
+ // Wrong way: Accessing the raw internal data (DO NOT USE FOR REACTIVE UPDATES).
128
+ // Direct modification of the data holder object will NOT trigger `recordChange` events or update dirty tracking.
129
+ // This gives you a REFERENCE to the internal data holder object.
130
+ const rawAddress = userRecord[Symbol.for('data')].address;
131
+ console.log(rawAddress.street); // Output: 456 Oak Ave
132
+
133
+ // Correct way (for a safe, disconnected copy):
134
+ // This gives you a STRUCTURED CLONE (a disconnected copy) of the data holder object.
135
+ const safeRawAddress = userRecord.toJSON().address;
136
+ console.log(safeRawAddress.street); // Output: 456 Oak Ave
137
+
138
+ // Modifying nested fields using set() with nested object structure
139
+ userRecord.set({ address: { street: '789 Pine Ln' } });
140
+ console.log(userRecord['address.street']); // Output: 789 Pine Ln
141
+ console.log(userRecord['address.city']); // Output: Newville (sibling untouched)
142
+ ```
143
+
144
+ ## Reactivity and Dirty Tracking
145
+
146
+ Records are inherently reactive. When you change a field's value, the setter defined by `RecordFactory` intercepts the
147
+ change, updates the internal data, and can trigger events. If the Model has `trackModifiedFields: true`, the Record also
148
+ keeps track of its original state.
149
+
150
+ - **`isModified`**: A boolean property on the Record instance that is `true` if any field has been changed from its
151
+ original value.
152
+ - **`isModifiedField(fieldName)`**: A method to check if a specific field has been modified.
153
+ - **`set(fields)`**: Bulk-update multiple fields and trigger a single change event. This method is particularly powerful
154
+ for nested objects: it performs a **deep merge** of the provided `fields` object with the record's existing data.
155
+ This means you can update specific properties within a nested object without overwriting the entire nested object.
156
+ For example, `myRecord.set({ address: { street: 'New Street' } })` will update only the `street` property within
157
+ `address`, leaving other `address` properties untouched. This contrasts with direct assignment to a nested object,
158
+ which would replace the entire nested object.
159
+ - **`setSilent(fields)`**: Bulk-update multiple fields without triggering a change event.
160
+ - **`toJSON()`**: A method available on every Record instance that returns a plain JavaScript object representing the
161
+ record's current data. Crucially, it returns a **structured clone** of the internal data. This ensures that any
162
+ modifications made to the object returned by `toJSON()` will **not** affect the original record and will **not**
163
+ trigger `recordChange` events, providing a safe, disconnected snapshot for serialization or external processing.
164
+
165
+ ### Bad Practice: Overwriting Nested Objects with Direct Assignment
166
+
167
+ While direct assignment to a nested *leaf property* using its full string path (e.g.,
168
+ `myRecord['address.street'] = "New Street";`) is reactive and works, directly assigning to a nested *object property*
169
+ (a non-leaf node) is generally considered a **bad practice** compared to using `record.set()` for several reasons:
170
+
171
+ 1. **Complete Overwrite**: If you assign directly to a nested object property (e.g., `myRecord.address = { newProp: 'value' };`),
172
+ you will **completely overwrite** the existing nested object. Any other properties within that nested object that are
173
+ not explicitly included in your new assignment will be **lost**. `record.set()` performs a **deep merge**, intelligently
174
+ updating only the specified nested properties while preserving others.
175
+ 2. **Multiple Change Events (for multiple field updates)**: If you need to update several fields (even leaf properties,
176
+ nested or not), performing multiple direct assignments will trigger a separate `recordChange` event for each assignment.
177
+ `record.set()` allows you to batch all these updates into a single operation, triggering only one `recordChange` event,
178
+ which is significantly more efficient for UI updates and overall application performance.
179
+ 3. **Clarity and Consistency**: Using `record.set()` is the idiomatic and recommended way to modify record data,
180
+ especially for nested structures. It clearly communicates intent and promotes consistent API usage across your application.
181
+
182
+ Always prefer `record.set()` for modifying record data, particularly when dealing with nested fields or multiple updates,
183
+ to leverage its deep merge capabilities and optimize event triggering.
184
+
185
+ ### Example: Reactivity and Dirty Tracking
186
+
187
+ ```javascript readonly
188
+ import RecordFactory from '../../src/data/RecordFactory.mjs';
189
+ import UserModel from './UserModel.mjs';
190
+
191
+ const userModelInstance = Neo.create(UserModel);
192
+ const userRecord = RecordFactory.createRecord(userModelInstance, {
193
+ email : 'john.smith@example.com',
194
+ firstName: 'John',
195
+ id : 102,
196
+ lastName : 'Smith',
197
+
198
+ address: {
199
+ city : 'Oldtown',
200
+ street: '100 Elm St'
201
+ }
202
+ });
203
+
204
+ console.log(userRecord.isModified); // Output: false
205
+
206
+ userRecord.firstName = 'Jonathan';
207
+ console.log(userRecord.isModified); // Output: true
208
+ console.log(userRecord.isModifiedField('firstName')); // Output: true
209
+ console.log(userRecord.isModifiedField('lastName')); // Output: false
210
+
211
+ userRecord.set({ address: { city: 'Newtown' } }); // Update nested field using set()
212
+ console.log(userRecord.isModifiedField('address.city')); // Output: true
213
+
214
+ userRecord.reset({firstName: 'John'}); // Reset firstName to original
215
+ console.log(userRecord.isModified); // Output: true (because address.city is still modified)
216
+
217
+ userRecord.reset(); // Reset all fields to original state
218
+ console.log(userRecord.isModified); // Output: false
219
+ ```
220
+
221
+ ## Interaction with `Neo.data.Store`
222
+
223
+ `Neo.data.Store` is designed to manage collections of Records. When you add raw data (plain JavaScript objects) to a
224
+ `Store`, it automatically uses its associated `Neo.data.Model` and `RecordFactory` to convert them into reactive Record
225
+ instances.
226
+
227
+ - **`store.add(data)`**: Converts data into Records and adds them to the store.
228
+ - **`store.model`**: The `Neo.data.Model` instance associated with the store, defining the structure of its records.
229
+ - **`recordChange` event**: Stores emit a `recordChange` event when a field of one of its records is modified. This
230
+ allows UI components (like Grids) to efficiently update only the changed cells. For a real-world example, see how
231
+ `Neo.grid.Body`'s `onStoreRecordChange` method consumes this event to perform targeted cell updates.
232
+
233
+ ### Example: Store Managing Records
234
+
235
+ ```javascript readonly
236
+ import Store from '../../src/data/Store.mjs';
237
+ import UserModel from './UserModel.mjs';
238
+
239
+ const userStore = Neo.create(Store, {
240
+ model: UserModel, // Link the store to your UserModel
241
+ data: [
242
+ {id: 201, firstName: 'Anna', lastName: 'Brown', email: 'anna.b@example.com'},
243
+ {id: 202, firstName: 'Peter', lastName: 'Green', email: 'peter.g@example.com'}
244
+ ]
245
+ });
246
+
247
+ userStore.on('recordChange', ({record, fields}) => {
248
+ console.log(`Record ${record.id} changed:`, fields);
249
+ });
250
+
251
+ const anna = userStore.get(201);
252
+ anna.email = 'anna.brown@example.com';
253
+ // Output: Record 201 changed: [{name: "email", oldValue: "anna.b@example.com", value: "anna.brown@example.com"}]
254
+
255
+ console.log(userStore.get(201).isModified); // Output: true
256
+ ```
257
+
258
+ ## The Reactivity Masterpiece: Collections and Records in Harmony
259
+
260
+ The true power of Neo.mjs's data layer emerges when `Neo.collection.Base` (used by `Neo.data.Store`) and `Neo.data.Model`
261
+ (Records) work together. This combination creates a highly reactive and efficient system for managing structured, often
262
+ tabular, application data.
263
+
264
+ `Neo.data.Store` acts as a specialized `Neo.collection.Base` that manages a collection of `Neo.data.Model` instances
265
+ (Records). This means you benefit from two layers of reactivity:
266
+
267
+ 1. **Collection-Level Reactivity**:
268
+ * When records are added to, removed from, or reordered within a `Store`, the `Store` (as a `Neo.collection.Base`)
269
+ fires `mutate` events. This allows UI components to react to structural changes in the dataset (e.g., a new row
270
+ appearing in a grid, or a row being deleted).
271
+
272
+ 2. **Record-Level Reactivity**:
273
+ * When a field *within an individual Record* changes its value (e.g., `myUserRecord.firstName = 'New Name'`),
274
+ the Record itself (via the `RecordFactory`'s generated setters) notifies its owning `Store` (if the Model has a
275
+ `storeId` pointing back to the Store). This triggers the `recordChange` event on the `Store`. This allows UI
276
+ components to react to granular changes within a data item (e.g., updating a single cell in a grid without
277
+ re-rendering the entire row or grid).
278
+
279
+ This dual-layered reactivity is a cornerstone of Neo.mjs's performance. It enables highly optimized UI updates, as
280
+ components can precisely react to only the changes that affect them, avoiding costly full re-renders.
281
+
282
+ ## Conclusion
283
+
284
+ Records, powered by `Neo.data.Model` and `Neo.data.RecordFactory`, are a cornerstone of data management in Neo.mjs.
285
+ They provide a robust, reactive, and structured approach to handling application data, simplifying complex tasks like UI
286
+ synchronization, data validation, and state tracking. By leveraging Records, you can build more maintainable, performant,
287
+ and predictable data-driven applications.
@@ -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.