neo.mjs 10.0.0-beta.2 → 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 CHANGED
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='10.0.0-beta.2'
23
+ * @member {String} version='10.0.0-beta.3'
24
24
  */
25
- version: '10.0.0-beta.2'
25
+ version: '10.0.0-beta.3'
26
26
  }
27
27
 
28
28
  /**
@@ -26,10 +26,9 @@ class FormPageContainer extends FormContainer {
26
26
  */
27
27
  style: {overflow: 'auto'},
28
28
  /**
29
- * @member {Object} _vdom
29
+ * @member {String} tag='div'
30
30
  */
31
- vdom:
32
- {cn: []} // using a div instead of a form tag
31
+ tag: 'div' // using a div instead of a form tag
33
32
  }
34
33
  }
35
34
 
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-06-30",
19
+ "datePublished": "2025-07-01",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- text : 'v10.0.0-beta.2'
110
+ text : 'v10.0.0-beta.3'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -3,11 +3,12 @@ import LivePreview from '../../../../src/code/LivePreview.mjs';
3
3
  import {marked} from '../../../../node_modules/marked/lib/marked.esm.js';
4
4
 
5
5
  const
6
- labCloseRegex = /<!--\s*\/lab\s*-->/g,
7
- labOpenRegex = /<!--\s*lab\s*-->/g,
8
- regexLivePreview = /```(javascript|html|css|json)\s+live-preview\s*\n([\s\S]*?)\n\s*```/g,
9
- regexNeoComponent = /```json\s+neo-component\s*\n([\s\S]*?)\n\s*```/g,
10
- regexReadonly = /```(javascript|html|css|json)\s+readonly\s*\n([\s\S]*?)\n\s*```/g;
6
+ regexInlineCode = /`([^`]+)`/g,
7
+ regexLabClose = /<!--\s*\/lab\s*-->/g,
8
+ regexLabOpen = /<!--\s*lab\s*-->/g,
9
+ regexLivePreview = /```(javascript|html|css|json)\s+live-preview\s*\n([\s\S]*?)\n\s*```/g,
10
+ regexNeoComponent = /```json\s+neo-component\s*\n([\s\S]*?)\n\s*```/g,
11
+ regexReadonly = /```(javascript|html|css|json)\s+readonly\s*\n([\s\S]*?)\n\s*```/g;
11
12
 
12
13
  /**
13
14
  * @class Portal.view.learn.ContentComponent
@@ -233,10 +234,10 @@ class ContentComponent extends Component {
233
234
  */
234
235
  insertLabDivs(inputString) {
235
236
  // Replace <!-- lab --> with <div class="lab">
236
- inputString = inputString.replace(labOpenRegex, '<div class="lab">');
237
+ inputString = inputString.replace(regexLabOpen, '<div class="lab">');
237
238
 
238
239
  // Replace <!-- /lab --> with </div>
239
- inputString = inputString.replace(labCloseRegex, '</div>');
240
+ inputString = inputString.replace(regexLabClose, '</div>');
240
241
 
241
242
  return inputString
242
243
  }
@@ -307,7 +308,7 @@ class ContentComponent extends Component {
307
308
  replacementPromises.push(
308
309
  Neo.main.addon.HighlightJS.highlightAuto({html: code, windowId})
309
310
  .then(highlightedHtml => ({
310
- after: `<pre data-javascript id="pre-readonly-${Neo.core.IdGenerator.getId()}">${highlightedHtml}</pre>`,
311
+ after: `<pre data-javascript id="pre-readonly-${Neo.core.IdGenerator.getId()}">${highlightedHtml.trim()}</pre>`,
311
312
  token: token
312
313
  }))
313
314
  );
@@ -336,7 +337,7 @@ class ContentComponent extends Component {
336
337
  contentArray = content.split('\n'),
337
338
  i = 1,
338
339
  storeData = [],
339
- tag;
340
+ headline, sideNavTitle, tag;
340
341
 
341
342
  contentArray.forEach((line, index) => {
342
343
  tag = null;
@@ -352,9 +353,15 @@ class ContentComponent extends Component {
352
353
  }
353
354
 
354
355
  if (tag) {
355
- storeData.push({id: i, name: line, sourceId: me.id, tag});
356
+ // Convert backticks to <code> tags for the article headline tags
357
+ headline = line.replace(regexInlineCode, '<code>$1</code>');
356
358
 
357
- contentArray[index] = `<${tag} class="neo-${tag}" data-record-id="${i}">${line}</${tag}>`;
359
+ // Markdown titles can contain inline code, which we don't want to display inside PageSectionsList.
360
+ sideNavTitle = line.replaceAll('`', '');
361
+
362
+ storeData.push({id: i, name: sideNavTitle, sourceId: me.id, tag});
363
+
364
+ contentArray[index] = `<${tag} class="neo-${tag}" data-record-id="${i}">${headline}</${tag}>`;
358
365
 
359
366
  i++
360
367
  }
package/learn/README.md CHANGED
@@ -16,7 +16,7 @@ Neo.mjs is the **first and only JavaScript framework** that achieves:
16
16
  ## 🌐 Best Learning Experience
17
17
 
18
18
  **For the optimal learning experience, visit:**
19
- **https://neomjs.com/dist/production/apps/portal/index.html#/learn**
19
+ **[neomjs.com#/learn](https://neomjs.com/dist/production/apps/portal/index.html#/learn)**
20
20
 
21
21
  The web portal provides:
22
22
  - ✨ **Enhanced Markdown rendering** with syntax highlighting
@@ -25,25 +25,20 @@ The web portal provides:
25
25
  - 📱 **Responsive layout** optimized for learning
26
26
  - 🔗 **Interactive navigation** between topics
27
27
 
28
- ## 📚 What's Inside
29
-
30
- - **[Benefits](./benefits/)** - Why choose Neo.mjs and its revolutionary OMT advantages
31
- - **[Getting Started](./gettingstarted/)** - Step-by-step introduction to Off-Main-Thread development
32
- - **[Guides](./guides/)** - In-depth technical documentation on advanced concepts
33
- - **[Tutorials](./tutorials/)** - Hands-on projects and practical examples
34
- - **[JavaScript Classes](./javascript/)** - JavaScript fundamentals for Neo.mjs development
35
-
36
28
  ## 🎯 Learning Path
37
29
 
38
- 1. Start with [Benefits](./benefits/) to understand Neo.mjs's revolutionary OMT approach
39
- 2. Follow [Getting Started](./gettingstarted/) for Off-Main-Thread development basics
40
- 3. Explore [Guides](./guides/) for comprehensive technical knowledge
41
- 4. Practice with [Tutorials](./tutorials/) for hands-on experience
30
+ For a structured and effective learning experience, we recommend the following progression:
31
+
32
+ 1. Start with [Benefits](./benefits/) to understand Neo.mjs's revolutionary OMT approach
33
+ 2. Follow [Getting Started](./gettingstarted/) for Off-Main-Thread development basics
34
+ 3. Explore [Guides](./guides/) for comprehensive technical knowledge
35
+ 4. Practice with [Tutorials](./tutorials/) for hands-on experience
36
+ 5. Review [JavaScript Classes](./javascript/) for JavaScript fundamentals relevant to Neo.mjs development
42
37
 
43
38
  ## 📖 Reading Options
44
39
 
45
40
  - **🌐 Web Portal** (Recommended): Enhanced experience with live previews
46
- → [neomjs.com/learn](https://neomjs.com/dist/production/apps/portal/index.html#/learn)
41
+ → [neomjs.com#/learn](https://neomjs.com/dist/production/apps/portal/index.html#/learn)
47
42
  - **📁 GitHub**: Raw markdown files for quick reference or offline reading
48
43
  - **💻 Local**: Clone the repo and browse files directly
49
44
 
@@ -0,0 +1,436 @@
1
+
2
+ Neo.mjs provides a powerful and flexible `Neo.collection.Base` class for managing data. This guide will explore its
3
+ features, including:
4
+
5
+ - **Core Concepts**: Understanding how collections store and manage data.
6
+ - **Adding and Removing Items**: Methods like `add`, `remove`, `insert`, `pop`, `push`, `shift`, and `unshift`.
7
+ - **Filtering Data**: Using `Neo.collection.Filter` to filter collection items.
8
+ - **Sorting Data**: Using `Neo.collection.Sorter` to sort collection items.
9
+ - **Event Handling**: How collections emit events on data mutations.
10
+ - **Performance Considerations**: Tips for optimizing collection usage.
11
+
12
+ ## Core Concepts
13
+
14
+ The `Neo.collection.Base` class is the foundation for all collections in Neo.mjs. While a standard JavaScript array can
15
+ store data, it lacks the advanced features required for complex application development, such as automatic sorting,
16
+ filtering, efficient key-based lookups, and robust eventing. `Neo.collection.Base` addresses these needs by combining
17
+ an array with a Map, offering a powerful and performant data management solution.
18
+
19
+ Key characteristics include:
20
+
21
+ - **`items_` (Array)**: This private array holds the actual data items within the collection. While these can be any
22
+ JavaScript object, in many real-world Neo.mjs applications, especially when working with `Neo.data.Store`, these items
23
+ are `Record` instances (super lightweight object extensions). It provides ordered access to items, which is essential for
24
+ operations like iteration and maintaining insertion order (unless sorting is applied).
25
+ - **`map_` (Map)**: A `Map` object that stores a key-value pair for each collection item. The key is derived from the
26
+ `keyProperty` of each item, and the value is a reference to the item itself. This `Map` is crucial for enabling extremely
27
+ fast lookups.
28
+
29
+ ### Why the Array + Map Combination?
30
+
31
+ This dual-structure approach provides the best of both worlds:
32
+
33
+ - **Ordered Access & Iteration (Array)**: The internal `items_` array allows for straightforward iteration over the
34
+ collection in a defined order (either insertion order or sorted order). Accessing an item by its index is an O(1)
35
+ (constant time) operation.
36
+ - **Efficient Key-Based Lookups (Map)**: The `map_` enables direct, constant-time (O(1)) retrieval of items by their
37
+ unique `keyProperty`. This is a significant performance advantage over searching a plain array, which would require
38
+ iterating through items until a match is found, resulting in an an O(n) (linear time) operation in the worst case.
39
+
40
+ **Performance Impact (Big O Notation):**
41
+
42
+ Consider a collection with 100,000 items:
43
+
44
+ - **Getting an item by ID (`collection.get(id)`)**: Thanks to the `map_`, this operation takes approximately the same
45
+ amount of time regardless of whether the collection has 10 items or 100,000 items. This is an O(1) operation.
46
+ - **Searching a plain array for an item by ID**: In the worst case (item is at the end or not present), you would have
47
+ to check every single item. For 100,000 items, this could mean 100,000 comparisons. This is an O(n) operation.
48
+
49
+ This fundamental difference in lookup efficiency is a primary reason for the `Neo.collection.Base` design, ensuring high
50
+ performance even with very large datasets.
51
+
52
+ - **`keyProperty`**: By default, this is set to `'id'`, meaning each item in the collection should have a unique `id`
53
+ property. This is crucial for the `map_` to function correctly.
54
+ - **`autoSort`**: If set to `true`, the collection will automatically sort its items when new ones are added or
55
+ inserted, based on the configured sorters.
56
+ - **`sourceId_`**: Collections can be linked to a source collection. This is a powerful feature where a collection
57
+ automatically mirrors the data mutations (additions, removals, reordering) of another collection. This is particularly
58
+ useful for creating filtered or sorted views of a larger dataset without duplicating the data.
59
+
60
+ ### The `sourceId` Concept and Real-World Use Cases
61
+
62
+ The `sourceId` configuration allows you to create "derived" or "linked" collections that automatically stay in sync with
63
+ a "source" collection. This is achieved by the dependent collection listening to the `mutate` event of its source. Any
64
+ changes to the source collection's items are automatically propagated to the dependent collection.
65
+
66
+ **Real-World Use Cases:**
67
+
68
+ 1. **Source-Detail Views:** Display a source list (source collection) and a filtered or sorted subset of that data in
69
+ a detail view (dependent collection). Changes in the source list automatically update the detail view.
70
+ 2. **Multiple Synchronized Components:** On a dashboard, multiple widgets might display different views (filtered,
71
+ sorted, or transformed) of the same underlying dataset. A central source collection ensures all widgets remain consistent.
72
+ 3. **Data Transformation Pipelines:** Chain collections together, where the output of one collection (as a source)
73
+ becomes the input for the next, allowing for complex data processing flows.
74
+
75
+ This mechanism significantly reduces boilerplate code for data synchronization and ensures data consistency across your
76
+ application.
77
+
78
+ **Advanced Use Case: Grid and ComboBox with Shared Data**
79
+
80
+ Consider a scenario where you have a large dataset (e.g., a list of products) that needs to be displayed in a grid, and
81
+ a subset of that data needs to be available in a combobox picker list.
82
+
83
+ If you were to use a single collection for both, typing into the combobox's input field to filter its options would
84
+ inadvertently filter the data displayed in your grid, which is typically not the desired behavior.
85
+
86
+ The `sourceId` concept provides an elegant solution:
87
+
88
+ 1. **Primary Store (Collection)**: Create a primary `Neo.collection.Base` instance (acting as your data store) that
89
+ fetches the complete product list from a backend. This collection is not directly bound to any UI component.
90
+ 2. **Grid Store (Child Collection)**: Create a second `Neo.collection.Base` instance for your grid. Set its `sourceId`
91
+ to the ID of your primary store. This grid store will automatically receive all data and mutations from the primary.
92
+ It can then apply its own sorting or filtering (e.g., to display only "in-stock" items) without affecting the primary
93
+ or other child collections.
94
+ 3. **ComboBox Store (Child Collection)**: Create a third `Neo.collection.Base` instance for your combobox's picker list.
95
+ Set its `sourceId` to the ID of your primary store. This combobox store can then apply its own filters (e.g., based on
96
+ user input in the combobox field) and sorters, completely independently of the grid store or the primary store.
97
+
98
+ ```javascript
99
+ import Collection from '../../src/collection/Base.mjs';
100
+ import Filter from '../../src/collection/Filter.mjs';
101
+
102
+ // 1. Primary Store: Fetches data from backend (simulated)
103
+ const primaryProductsStore = Neo.create(Collection, {
104
+ id: 'primaryProductsStore',
105
+ items: [
106
+ {id: 1, name: 'Laptop', category: 'Electronics', price: 1200, inStock: true},
107
+ {id: 2, name: 'Mouse', category: 'Electronics', price: 25, inStock: true},
108
+ {id: 3, name: 'Keyboard', category: 'Electronics', price: 75, inStock: false},
109
+ {id: 4, name: 'Monitor', category: 'Electronics', price: 300, inStock: true},
110
+ {id: 5, name: 'Desk Chair', category: 'Furniture', price: 150, inStock: true},
111
+ {id: 6, name: 'Webcam', category: 'Electronics', price: 50, inStock: false}
112
+ ]
113
+ });
114
+
115
+ // 2. Grid Store: Displays all in-stock electronics, sorted by price
116
+ const gridStore = Neo.create(Collection, {
117
+ id : 'gridStore',
118
+ sourceId: 'primaryProductsStore', // Linked to primary
119
+ filters: [
120
+ {property: 'inStock', value: true},
121
+ {property: 'category', value: 'Electronics'}
122
+ ],
123
+ sorters: [
124
+ {property: 'price', direction: 'ASC'}
125
+ ]
126
+ });
127
+
128
+ // 3. ComboBox Store: Filters based on user input (e.g., 'web')
129
+ const comboBoxStore = Neo.create(Collection, {
130
+ id : 'comboBoxStore',
131
+ sourceId: 'primaryProductsStore', // Linked to primary
132
+ filters: [
133
+ // This filter would be dynamically updated by the combobox input
134
+ {property: 'name', operator: 'like', value: 'web'}
135
+ ]
136
+ });
137
+
138
+ console.log('Primary Store Count:', primaryProductsStore.getCount()); // Output: 6
139
+ console.log('Grid Store Count (in-stock electronics):', gridStore.getCount()); // Output: 3 (Laptop, Mouse, Monitor)
140
+ console.log('ComboBox Store Count (name like "web"):', comboBoxStore.getCount()); // Output: 1 (Webcam)
141
+
142
+ // Simulate adding a new product to the primary store
143
+ primaryProductsStore.add({id: 7, name: 'Headphones', category: 'Electronics', price: 100, inStock: true});
144
+
145
+ console.log('Primary Store Count after add:', primaryProductsStore.getCount()); // Output: 7
146
+ console.log('Grid Store Count after add (Headphones match filters):', gridStore.getCount()); // Output: 4
147
+ console.log('ComboBox Store Count after add (Headphones do not match "web")):', comboBoxStore.getCount()); // Output: 1
148
+
149
+ // Simulate changing the combobox filter
150
+ comboBoxStore.filters[0].value = 'key';
151
+ console.log('ComboBox Store Count (name like "key"):', comboBoxStore.getCount()); // Output: 1 (Keyboard)
152
+ console.log('Grid Store Count (still unaffected):', gridStore.getCount()); // Output: 4
153
+ ```
154
+
155
+ This example demonstrates how `sourceId` enables powerful data management patterns, allowing different parts of your
156
+ application to work with synchronized data while maintaining their own independent views.
157
+
158
+
159
+ - **`observable`**: Collections are observable, meaning they can emit events when their data changes (e.g., `mutate`,
160
+ - `filter`, `sort`).
161
+
162
+ ### Example: Basic Collection Usage
163
+
164
+ ```javascript
165
+ import Collection from '../../src/collection/Base.mjs';
166
+
167
+ const myCollection = Neo.create(Collection, {
168
+ items: [
169
+ {id: 1, name: 'Alice', age: 30},
170
+ {id: 2, name: 'Bob', age: 24},
171
+ {id: 3, name: 'Charlie', age: 35}
172
+ ]
173
+ });
174
+
175
+ console.log(myCollection.getCount()); // Output: 3
176
+ console.log(myCollection.get(2)); // Output: {id: 2, name: 'Bob', age: 24}
177
+ ```
178
+
179
+ ## Adding and Removing Items
180
+
181
+ Collections provide several methods for manipulating their contents. When adding raw data (plain JavaScript objects) to
182
+ a `Neo.data.Store` (which extends `Neo.collection.Base`), these objects are automatically converted into Record
183
+ instances based on the store's model definition.
184
+
185
+ - **`add(item)`**: Adds one or more items (or Records) to the end of the collection.
186
+ - **`insert(index, item)`**: Inserts one or more items at a specific index.
187
+ - **`remove(key)`**: Removes an item by its key or the item itself.
188
+ - **`removeAt(index)`**: Removes an item at a specific index.
189
+ - **`pop()`**: Removes and returns the last item from the collection.
190
+ - **`push(item)`**: Adds one or more items to the end of the collection (alias for `add`).
191
+ - **`shift()`**: Removes and returns the first item from the collection.
192
+ - **`unshift(item)`**: Adds one or more items to the beginning of the collection.
193
+ - **`splice(index, removeCountOrToRemoveArray, toAddArray)`**: This is the **central and most powerful method** for
194
+ modifying a collection. It can simultaneously remove and add items at a specified index. Many other collection methods,
195
+ such as `add()`, `remove()`, `insert()`, `pop()`, `push()`, `shift()`, and `unshift()`, internally use `splice()` to
196
+ perform their operations, making it the core mechanism for all data mutations within a collection.
197
+ - **`clear()`**: Removes all items from the collection.
198
+
199
+ ### Example: Adding and Removing
200
+
201
+ ```javascript
202
+ import Collection from '../../src/collection/Base.mjs';
203
+
204
+ const users = Neo.create(Collection, {
205
+ items: [
206
+ {id: 1, name: 'Alice'},
207
+ {id: 2, name: 'Bob'}
208
+ ]
209
+ });
210
+
211
+ users.add({id: 3, name: 'Charlie'});
212
+ console.log(users.getCount()); // Output: 3
213
+
214
+ users.insert(1, {id: 4, name: 'David'});
215
+ console.log(users.getAt(1)); // Output: {id: 4, name: 'David'}
216
+
217
+ users.remove(2); // Removes Bob
218
+ console.log(users.findFirst('name', 'Bob')); // Output: null
219
+
220
+ users.pop(); // Removes Charlie
221
+ console.log(users.getCount()); // Output: 2
222
+
223
+ users.clear();
224
+ console.log(users.getCount()); // Output: 0
225
+ ```
226
+
227
+ ## Filtering Data
228
+
229
+ `Neo.collection.Filter` instances are used to filter the items within a collection.
230
+
231
+ - **`property`**: The property of the item to filter by.
232
+ - **`operator`**: The comparison operator (e.g., `'===', '>', 'like', 'startsWith'`).
233
+ - **`value`**: The value to compare against.
234
+ - **`filterBy`**: A custom function for more complex filtering logic.
235
+ - **`disabled`**: Temporarily disable a filter.
236
+
237
+ The `Neo.collection.Base` class has a `filters_` config which accepts an array of `Filter` instances or configurations.
238
+
239
+ ### The `allItems` Property
240
+
241
+ When a collection is filtered, the original, unfiltered set of items is preserved in the `allItems` property. This is a
242
+ significant advantage over simply filtering a JavaScript array, as it allows you to easily revert to the full dataset or
243
+ perform operations on it without losing the original data.
244
+
245
+ ### Reactivity of Filters
246
+
247
+ Each `Neo.collection.Filter` is a Neo instance, and its configuration properties are reactive. This means that changes
248
+ to the `filters` array or to properties of individual `Filter` instances within the array will automatically trigger a
249
+ re-filter of the collection. **It's important to note that simply mutating the `filters` array (e.g., using `push()`,
250
+ `pop()`, `splice()`) will not trigger reactivity. For changes to the array's contents to take effect, you must reassign
251
+ the `filters` property (e.g., `collection.filters = [...collection.filters];`) or modify properties of existing filter
252
+ instances.**
253
+
254
+ ### Example: Filtering a Collection
255
+
256
+ ```javascript
257
+ import Collection from '../../src/collection/Base.mjs';
258
+ import Filter from '../../src/collection/Filter.mjs';
259
+
260
+ const products = Neo.create(Collection, {
261
+ items: [
262
+ {id: 1, name: 'Laptop', price: 1200},
263
+ {id: 2, name: 'Mouse', price: 25},
264
+ {id: 3, name: 'Keyboard', price: 75},
265
+ {id: 4, name: 'Monitor', price: 300}
266
+ ]
267
+ });
268
+
269
+ // Filter products with price > 100
270
+ products.filters = [{
271
+ property: 'price',
272
+ operator: '>',
273
+ value : 100
274
+ }];
275
+
276
+ console.log(products.getCount()); // Output: 2 (Laptop, Monitor)
277
+ console.log(products.allItems.getCount()); // Output: 4 (all original items)
278
+
279
+ // Add another filter: name contains 'o'
280
+ products.filters.push({
281
+ property: 'name',
282
+ operator: 'like',
283
+ value : 'o'
284
+ });
285
+ // Reassign the filters array to trigger reactivity
286
+ products.filters = [...products.filters];
287
+
288
+ console.log(products.getCount()); // Output: 1 (Monitor)
289
+
290
+ // Dynamically disable the first filter
291
+ products.filters[0].disabled = true;
292
+ console.log(products.getCount()); // Output: 1 (Monitor - only the 'name like o' filter is active)
293
+
294
+ // Clear filters
295
+ products.clearFilters();
296
+ console.log(products.getCount()); // Output: 4
297
+
298
+ // Adding an item to a filtered collection
299
+ products.filters = [{ property: 'price', operator: '>', value: 500 }];
300
+ products.add({ id: 5, name: 'Webcam', price: 50 }); // Does not match filter
301
+ console.log(products.getCount()); // Output: 1 (Laptop)
302
+ console.log(products.allItems.getCount()); // Output: 5 (Webcam is in allItems)
303
+ ```
304
+
305
+ ## Sorting Data
306
+
307
+ `Neo.collection.Sorter` instances are used to sort the items within a collection.
308
+
309
+ - **`property`**: The property of the item to sort by.
310
+ - **`direction`**: The sort direction (`'ASC'` or `'DESC'`).
311
+ - **`sortBy`**: A custom function for more complex sorting logic.
312
+
313
+ The `Neo.collection.Base` class has a `sorters_` config which accepts an array of `Sorter` instances or configurations.
314
+
315
+ ### Reactivity of Sorters
316
+
317
+ Each `Neo.collection.Sorter` is a Neo instance, and its configuration properties are reactive. This means that changes
318
+ to the `sorters` array or to properties of individual `Sorter` instances within the array will automatically trigger a
319
+ re-sort of the collection. **It's important to note that simply mutating the `sorters` array (e.g., using `push()`,
320
+ `pop()`, `splice()`) will not trigger reactivity. For changes to the array's contents to take effect, you must reassign
321
+ the `sorters` property (e.g., `collection.sorters = [...collection.sorters];`) or modify properties of existing sorter
322
+ instances.**
323
+
324
+ ### Example: Sorting a Collection
325
+
326
+ ```javascript
327
+ import Collection from '../../src/collection/Base.mjs';
328
+ import Sorter from '../../src/collection/Sorter.mjs';
329
+
330
+ const employees = Neo.create(Collection, {
331
+ items: [
332
+ {id: 1, name: 'Alice', salary: 50000},
333
+ {id: 2, name: 'Bob', salary: 75000},
334
+ {id: 3, name: 'Charlie', salary: 60000}
335
+ ]
336
+ });
337
+
338
+ // Sort by salary descending
339
+ employees.sorters = [{
340
+ property : 'salary',
341
+ direction: 'DESC'
342
+ }];
343
+
344
+ console.log(employees.first().name); // Output: Bob
345
+
346
+ // Dynamically change the sort property
347
+ employees.sorters[0].property = 'name';
348
+ employees.sorters[0].direction = 'ASC';
349
+ console.log(employees.first().name); // Output: Alice
350
+
351
+ // Clear sorters and restore original order (if applicable)
352
+ employees.clearSorters(true);
353
+ console.log(employees.first().name); // Output: Alice (assuming original order was by id)
354
+ ```
355
+
356
+ ## Event Handling
357
+
358
+ Collections are observable and emit events when their state changes. The primary event is `mutate`, which fires after
359
+ any operation that changes the collection's items.
360
+
361
+ - **`mutate`**: Fired after items are added, removed, or reordered. Provides `addedItems` and `removedItems` arrays.
362
+ - **`filter`**: Fired after the collection is filtered.
363
+ - **`sort`**: Fired after the collection is sorted.
364
+
365
+ ### Example: Listening to Collection Events
366
+
367
+ ```javascript
368
+ import Collection from '../../src/collection/Base.mjs';
369
+
370
+ const tasks = Neo.create(Collection, {
371
+ items: [
372
+ {id: 1, description: 'Buy groceries'},
373
+ {id: 2, description: 'Walk the dog'}
374
+ ]
375
+ });
376
+
377
+ tasks.on('mutate', ({addedItems, removedItems}) => {
378
+ if (addedItems.length > 0) {
379
+ console.log('Added:', addedItems.map(item => item.description));
380
+ }
381
+ if (removedItems.length > 0) {
382
+ console.log('Removed:', removedItems.map(item => item.description));
383
+ }
384
+ });
385
+
386
+ tasks.add({id: 3, description: 'Clean the house'});
387
+ // Output: Added: ["Clean the house"]
388
+
389
+ tasks.remove(1);
390
+ // Output: Removed: ["Buy groceries"]
391
+ ```
392
+
393
+ ## Performance Considerations
394
+
395
+ - **`startUpdate()` and `endUpdate()`**: For bulk operations (multiple additions, removals, or modifications), wrap
396
+ your changes within `startUpdate()` and `endUpdate()`. This prevents multiple `mutate` events from firing, improving performance.
397
+ - **`silentUpdateMode`**: When using `startUpdate(true)`, the `mutate` event will not fire at all when `endUpdate(true)`
398
+ is called. This is useful when you want to perform internal updates without notifying listeners.
399
+ - **`filterMode`**: The `filterMode` config can be set to `'primitive'` or `'advanced'`. `'primitive'` is generally
400
+ faster for simple filters, while `'advanced'` is needed for filters that depend on other collection items.
401
+ - **`keyProperty`**: Ensure your `keyProperty` is truly unique for each item to leverage the `map_` for efficient lookups.
402
+
403
+ ## Cloning Collections
404
+
405
+ When you clone a collection using `collection.clone()`, a new collection instance is created with a shallow copy of the
406
+ *current items*. However, the filters and sorters of the cloned collection are based on the *original configuration* of
407
+ the source collection, not its currently active filters or sorters. This means if the original collection had filters or
408
+ sorters applied after its initial creation, the cloned collection will not inherit those dynamic changes unless explicitly
409
+ reapplied.
410
+
411
+ ## Collections and Records: A Powerful Combination
412
+
413
+ While `Neo.collection.Base` is designed to manage any type of JavaScript object, its full potential within the Neo.mjs
414
+ framework is often realized when combined with `Neo.data.Model` instances (Records) through `Neo.data.Store`.
415
+ `Neo.data.Store` extends `Neo.collection.Base`, specializing it for the management of structured, reactive data.
416
+
417
+ When a `Neo.data.Store` holds `Records`:
418
+
419
+ - **Automatic Record Conversion**: Raw data (plain JavaScript objects) added to a `Store` are automatically converted
420
+ into reactive `Record` instances based on the `Store`'s associated `Neo.data.Model`.
421
+ - **Dual-Layered Reactivity**: You gain both collection-level reactivity (for structural changes like adding/removing
422
+ records) and record-level reactivity (for changes to individual record fields). This enables highly optimized UI
423
+ updates, as components can react precisely to the specific data changes that affect them.
424
+ - **Enhanced Data Management**: Records bring features like type conversion, validation, and dirty tracking to the
425
+ items within the collection, providing a more robust data management solution.
426
+
427
+ This powerful combination forms the backbone of data-driven components like Grids, ComboBoxes, and other data-bound UI
428
+ elements in Neo.mjs.
429
+
430
+ ## Conclusion
431
+
432
+ Neo.mjs Collections provide a robust, high-performance, and flexible solution for managing data within your applications.
433
+ By combining the ordered access of arrays with the efficient key-based lookups of Maps, and offering powerful features
434
+ like reactive filtering, sorting, and `sourceId` linking, they significantly simplify complex data management tasks.
435
+ Understanding these core concepts and leveraging the provided methods will enable you to build highly responsive and
436
+ data-driven Neo.mjs applications with ease.