Skip to content

Commit 5d4760b

Browse files
committed
v10.5.0
1 parent 77a3571 commit 5d4760b

File tree

37 files changed

+346
-92
lines changed

37 files changed

+346
-92
lines changed

.github/RELEASE_NOTES/v10.5.0.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Neo.mjs v10.5.0 Release Notes
2+
3+
## Major Performance Enhancements for Grids and Data Handling
4+
5+
This release introduces a groundbreaking set of performance optimizations for `Neo.data.Store` and `Neo.grid.Container`, fundamentally transforming how large datasets are handled within Neo.mjs applications. These enhancements dramatically reduce initial load times, improve UI responsiveness, and resolve critical VDom reconciliation issues, making it feasible to work with millions of data points with unprecedented fluidity.
6+
7+
### Key Highlights:
8+
9+
* **Lazy Record Instantiation (GET-driven approach):**
10+
* `Neo.data.Store` now defers the creation of `Neo.data.Record` instances. Raw data objects are stored directly, and `Record` instances are only created on-demand when an item is explicitly accessed (e.g., via `store.get()`, `store.getAt()`, or during VDom rendering of visible rows).
11+
* **Impact:** This eliminates the massive upfront cost of instantiating millions of `Record` objects, leading to **up to 97% reduction in initial data processing time** for large datasets.
12+
13+
* **Configurable Data Chunking for UI Responsiveness:**
14+
* Introduced an `initialChunkSize` config in `Neo.data.Store` (default `0`). When enabled, `Store.add()` processes data in chunks, significantly mitigating UI freezes during synchronous loading of extremely large datasets (e.g., 1,000,000+ rows).
15+
* **Impact:** Provides a smoother perceived user experience during initial data loads, even for datasets that would otherwise cause multi-second UI blocks.
16+
17+
* **Robust Component ID Management for VDom Stability:**
18+
* Resolved critical VDom reconciliation errors (e.g., `RangeError`, infinite loops) that could occur with component columns when chunking was active. `Neo.grid.column.Component` now intelligently generates unique IDs based on the store's chunking state, ensuring VDom stability.
19+
* **Impact:** Eliminates a major source of instability and crashes when using component columns with large, dynamically loaded datasets.
20+
21+
* **Automated Component Instance Cleanup:**
22+
* Enhanced `Neo.grid.Body` with sophisticated component instance management. Components are now automatically destroyed when the grid body is destroyed, the store is cleared, the store changes, or when they scroll out of view after a chunked load.
23+
* **Impact:** Reduces memory overhead and improves long-term performance by preventing the accumulation of unused component instances.
24+
25+
* **GPU-Accelerated Vertical Scrolling (`translate3d`):**
26+
* Grid rows now utilize `transform: translate3d(0px, Ypx, 0px)` for vertical positioning. This hints to the browser to promote rows to their own composite layers, offloading rendering to the GPU.
27+
* **Impact:** Leads to noticeably smoother and more fluid vertical scrolling, especially during rapid movements through large grids.
28+
29+
### Other Enhancements:
30+
31+
* **ComboBox Initial Display Fix:** Resolved an issue where `Neo.form.field.ComboBox` instances would appear blank on initial load when backed by lazily instantiated stores. The `updateInputValueFromValue` method now correctly displays values from raw data or `Record` instances.
32+
* **Enhanced BigData Grid Example:** The `examples/grid/bigData` demo has been updated to include control options for up to 200 columns, allowing for testing with up to 20,000,000 cells. This provides a more extreme and comprehensive benchmark for grid performance.
33+
34+
These collective improvements mark a significant leap forward in Neo.mjs's capability to handle and display massive amounts of data with high performance and a superior user experience.
35+
36+
---
37+
38+
### Performance Benchmarks (from `examples/grid/bigData`):
39+
40+
| Scenario | Before (Eager Instantiation) | After (Lazy Instantiation, with chunking) | After (Lazy Instantiation, no chunking) |
41+
| :------------------------------------- | :--------------------------- | :---------------------------------------- | :-------------------------------------- |
42+
| 1,000 rows, 50 columns | 49ms (Record creation) | 2ms (Data gen + add) | 2ms (Data gen + add) |
43+
| 50,000 rows, 50 columns | 2391ms (Record creation) | 1252ms (Data gen + add) | 93ms (Data gen + add) |
44+
| 50,000 rows, 100 columns | 4377ms (Record creation) | 1289ms (Data gen + add) | 95ms (Data gen + add) |
45+
| 100,000 rows, 50 columns | N/A (too slow) | 1299ms (Data gen + add) | 156ms (Data gen + add) |
46+
| 100,000 rows, 200 columns | N/A (too slow) | 1427ms (Data gen + add) | 174ms (Data gen + add) |
47+
48+
*Note: "Record creation" refers to the time taken for `Neo.data.Record` instantiation. "Data gen + add" refers to the time taken to generate raw data and add it to the store's collection.*
49+
50+
**Important Note on Chunking:**
51+
The introduction of lazy record instantiation significantly reduces the need for chunking in most common use cases, as the initial data loading is now extremely fast even for large datasets. However, Neo.mjs still optionally supports data chunking via the `initialChunkSize` config for scenarios involving truly massive synchronous data additions where mitigating UI freezes is paramount.

ServiceWorker.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
2020
*/
2121
singleton: true,
2222
/**
23-
* @member {String} version='10.4.1'
23+
* @member {String} version='10.5.0'
2424
*/
25-
version: '10.4.1'
25+
version: '10.5.0'
2626
}
2727

2828
/**

apps/finance/view/ViewportController.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class ViewportController extends Controller {
3535
companiesStore = me.getStore('companies'),
3636
items = [];
3737

38-
companiesStore.items.forEach(record => {
38+
companiesStore.forEach(record => {
3939
items.push({
4040
symbol: record.symbol,
4141
value : Math.random() * 1000

apps/form/view/SideNavList.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class SideNavList extends List {
8484
onStoreLoad() {
8585
let maxIndex = -1;
8686

87-
this.store.items.forEach(record => {
87+
this.store.forEach(record => {
8888
if (!record.isHeader) {
8989
maxIndex++
9090
}

apps/portal/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@type": "Organization",
1717
"name": "Neo.mjs"
1818
},
19-
"datePublished": "2025-08-11",
19+
"datePublished": "2025-08-12",
2020
"publisher": {
2121
"@type": "Organization",
2222
"name": "Neo.mjs"

apps/portal/view/home/FooterContainer.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class FooterContainer extends Container {
108108
}, {
109109
module: Component,
110110
cls : ['neo-version'],
111-
text : 'v10.4.1'
111+
text : 'v10.5.0'
112112
}]
113113
}],
114114
/**

apps/realworld2/view/article/PreviewList.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class PreviewList extends List {
3333

3434
me.vdom.cn = [];
3535

36-
me.store.items.forEach(item => {
36+
me.store.forEach(item => {
3737
listItem = Neo.create({
3838
module : PreviewComponent,
3939
parentId: me.id,

docs/app/view/classdetails/MainContainerController.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class MainContainerController extends Component {
3737
countProtecteds = 0,
3838
countStatics = 0;
3939

40-
store.items.forEach(item => {
40+
store.forEach(item => {
4141
if (item.kind === 'function') {
4242
countMethods++
4343
} else if (item.kind === 'member') {

docs/app/view/classdetails/MembersList.mjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ class MembersList extends Base {
128128
* @returns {Object} vdom
129129
*/
130130
applyConfigsHeader(store, vdom) {
131-
if (store.items[0]?.kind === 'member') {
131+
if (store.getAt(0)?.kind === 'member') {
132132
vdom.cn.push({
133133
// scrolling placeholder
134134
}, {
@@ -152,7 +152,7 @@ class MembersList extends Base {
152152
applyEventsHeader(item, index, store, vdom) {
153153
if (
154154
item.kind === 'event' &&
155-
store.items[index -1]?.kind !== 'event'
155+
store.getAt(index -1)?.kind !== 'event'
156156
) {
157157
vdom.cn.push({
158158
// scrolling placeholder
@@ -179,8 +179,8 @@ class MembersList extends Base {
179179
if (
180180
item.kind === 'function' &&
181181
(
182-
!store.items[index -1] || (
183-
store.items[index -1]?.kind !== 'function'
182+
!store.getAt(index -1) || (
183+
store.getAt(index -1)?.kind !== 'function'
184184
)
185185
)
186186
) {
@@ -211,7 +211,7 @@ class MembersList extends Base {
211211
vdom.cn = [];
212212
vdom = me.applyConfigsHeader(me.store, vdom);
213213

214-
me.store.items.forEach((item, index) => {
214+
me.store.forEach((item, index) => {
215215
vdom = me.applyEventsHeader( item, index, me.store, vdom);
216216
vdom = me.applyMethodsHeader(item, index, me.store, vdom);
217217

examples/grid/bigData/ControlsContainer.mjs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ class ControlsContainer extends Container {
1717
* @static
1818
*/
1919
static delayable = {
20-
onAmountColumnsChange : {type: 'buffer', timer: 30},
21-
onAmountRowsChange : {type: 'buffer', timer: 30},
22-
onBufferColumnRangeChange: {type: 'buffer', timer: 30},
23-
onBufferRowRangeChange : {type: 'buffer', timer: 30}
20+
onAmountColumnsChange : {type: 'buffer', timer: 15},
21+
onAmountRowsChange : {type: 'buffer', timer: 15},
22+
onBufferColumnRangeChange: {type: 'buffer', timer: 15},
23+
onBufferRowRangeChange : {type: 'buffer', timer: 15}
2424
}
2525

2626
static config = {
@@ -69,14 +69,14 @@ class ControlsContainer extends Container {
6969
labelText : 'Amount Rows',
7070
labelWidth: 120,
7171
listeners : {change: 'up.onAmountRowsChange'},
72-
store : ['1000', '5000', '10000', '20000', '50000'],
72+
store : ['1000', '5000', '10000', '20000', '50000', '100000'],
7373
value : '1000',
7474
width : 200
7575
}, {
7676
labelText : 'Amount Columns',
7777
labelWidth: 145,
7878
listeners : {change: 'up.onAmountColumnsChange'},
79-
store : ['10', '25', '50', '75', '100'],
79+
store : ['10', '25', '50', '75', '100', '200'],
8080
value : '50',
8181
width : 200
8282
}, {

0 commit comments

Comments
 (0)