neo.mjs 8.4.1 → 8.5.0

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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.4.1'
23
+ * @member {String} version='8.5.0'
24
24
  */
25
- version: '8.4.1'
25
+ version: '8.5.0'
26
26
  }
27
27
 
28
28
  /**
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-01-14",
19
+ "datePublished": "2025-01-16",
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
- html : 'v8.4.1'
110
+ html : 'v8.5.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -84,18 +84,6 @@ class ContentComponent extends Component {
84
84
 
85
85
  if (value) {
86
86
  me.timeout(50).then(() => {
87
- me.customComponents.forEach(component => {
88
- if (!component.mounted && !component.rendering) {
89
- component.render(true)
90
- }
91
- });
92
-
93
- me.livePreviews.forEach(livePreview => {
94
- if (!livePreview.mounted && !livePreview.rendering) {
95
- livePreview.render(true)
96
- }
97
- });
98
-
99
87
  Neo.main.addon.IntersectionObserver.register({
100
88
  callback: 'findTopmostItem',
101
89
  id : me.id,
@@ -110,7 +98,7 @@ class ContentComponent extends Component {
110
98
 
111
99
  me.livePreviews.forEach(livePreview => {
112
100
  livePreview.mounted = false
113
- });
101
+ })
114
102
  }
115
103
  }
116
104
 
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.4.1'
23
+ * @member {String} version='8.5.0'
24
24
  */
25
- version: '8.4.1'
25
+ version: '8.5.0'
26
26
  }
27
27
 
28
28
  /**
@@ -49,12 +49,19 @@ class MainContainer extends Viewport {
49
49
  value : '50',
50
50
  width : 200
51
51
  }, {
52
- labelText : 'Buffer Row Range',
53
- labelWidth: 140,
52
+ labelText : 'Buffer Rows',
53
+ labelWidth: 95,
54
54
  listeners : {change: 'up.ontBufferRowRangeChange'},
55
55
  store : ['0', '3', '5', '10', '25', '50'],
56
56
  value : '5',
57
- width : 200
57
+ width : 160
58
+ }, {
59
+ labelText : 'Buffer Columns',
60
+ labelWidth: 120,
61
+ listeners : {change: 'up.ontBufferColumnRangeChange'},
62
+ store : ['0', '3', '5', '10', '20'],
63
+ value : '0',
64
+ width : 185
58
65
  }]
59
66
  }, {
60
67
  module : GridContainer,
@@ -73,12 +80,16 @@ class MainContainer extends Viewport {
73
80
  style: {padding: '20px'}
74
81
  }
75
82
 
83
+ get grid() {
84
+ return this.getItem('grid')
85
+ }
86
+
76
87
  /**
77
88
  * @param {Object} data
78
89
  */
79
90
  onAmountColumnsChange(data) {
80
91
  if (data.oldValue) {
81
- this.getItem('grid').amountColumns = parseInt(data.value.id)
92
+ this.grid.amountColumns = parseInt(data.value.id)
82
93
  }
83
94
  }
84
95
 
@@ -87,7 +98,16 @@ class MainContainer extends Viewport {
87
98
  */
88
99
  onAmountRowsChange(data) {
89
100
  if (data.oldValue) {
90
- this.getItem('grid').store.amountRows = parseInt(data.value.id)
101
+ this.grid.store.amountRows = parseInt(data.value.id)
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @param {Object} data
107
+ */
108
+ ontBufferColumnRangeChange(data) {
109
+ if (data.oldValue) {
110
+ this.grid.view.bufferColumnRange = parseInt(data.value.id)
91
111
  }
92
112
  }
93
113
 
@@ -96,7 +116,7 @@ class MainContainer extends Viewport {
96
116
  */
97
117
  ontBufferRowRangeChange(data) {
98
118
  if (data.oldValue) {
99
- this.getItem('grid').view.bufferRowRange = parseInt(data.value.id)
119
+ this.grid.view.bufferRowRange = parseInt(data.value.id)
100
120
  }
101
121
  }
102
122
  }
@@ -27,67 +27,21 @@ class MainStore extends Store {
27
27
  }
28
28
 
29
29
  firstnames = [
30
- 'Ashley',
31
- 'Barbara',
32
- 'Betty',
33
- 'Chris',
34
- 'David',
35
- 'Elizabeth',
36
- 'Jack',
37
- 'James',
38
- 'Jennifer',
39
- 'Jessica',
40
- 'Joe',
41
- 'John',
42
- 'Karen',
43
- 'Kelly',
44
- 'Kim',
45
- 'Linda',
46
- 'Lisa',
47
- 'Mary',
48
- 'Max',
49
- 'Michael',
50
- 'Nancy',
51
- 'Patricia',
52
- 'Rich',
53
- 'Robert',
54
- 'Sam',
55
- 'Sandra',
56
- 'Sarah',
57
- 'Susan',
58
- 'Thomas',
59
- 'Tobias'
30
+ 'Amanda', 'Andrew', 'Anthony', 'Ashley', 'Barbara', 'Betty', 'Brian', 'Carol', 'Charles', 'Christopher',
31
+ 'Daniel', 'David', 'Deborah', 'Donna', 'Elizabeth', 'Emily', 'George', 'Jack', 'James', 'Jennifer',
32
+ 'Jessica', 'Joe', 'John', 'Joseph', 'Joshua', 'Karen', 'Kenneth', 'Kelly', 'Kevin', 'Kimberly',
33
+ 'Linda', 'Lisa', 'Margaret', 'Mark', 'Mary', 'Matthew', 'Max', 'Melissa', 'Michael', 'Michelle',
34
+ 'Nancy', 'Patricia', 'Paul', 'Richard', 'Robert', 'Ronald', 'Sam', 'Sandra', 'Sarah', 'Stephanie',
35
+ 'Steven', 'Susan', 'Thomas', 'Timothy', 'Tobias', 'William'
60
36
  ]
61
37
 
62
38
  lastnames = [
63
- 'Anderson',
64
- 'Brown',
65
- 'Davis',
66
- 'Garcia',
67
- 'Gonzales',
68
- 'Harris',
69
- 'Hernandez',
70
- 'Jackson',
71
- 'Johnson',
72
- 'Jones',
73
- 'Lee',
74
- 'Lopez',
75
- 'Martin',
76
- 'Martinez',
77
- 'Miller',
78
- 'Moore',
79
- 'Perez',
80
- 'Rahder',
81
- 'Rodriguez',
82
- 'Smith',
83
- 'Taylor',
84
- 'Thomas',
85
- 'Thompson',
86
- 'Uhlig',
87
- 'Waters',
88
- 'White',
89
- 'Williams',
90
- 'Wilson'
39
+ 'Adams', 'Allen', 'Anderson', 'Baker', 'Brown', 'Campbell', 'Carter', 'Clark', 'Davis', 'Flores',
40
+ 'Garcia', 'Gonzales', 'Green', 'Hall', 'Harris', 'Hernandez', 'Hill', 'Jackson', 'Johnson', 'Jones',
41
+ 'King', 'Lee', 'Lewis', 'Lopez', 'Martin', 'Martinez', 'Miller', 'Mitchell', 'Moore', 'Nelson',
42
+ 'Nguyen', 'Perez', 'Rahder', 'Ramirez', 'Roberts', 'Rivera', 'Robinson', 'Rodriguez', 'Sanchez', 'Scott',
43
+ 'Smith', 'Taylor', 'Thomas', 'Thompson', 'Torres', 'Uhlig', 'Walker', 'Waters', 'White', 'Williams',
44
+ 'Wilson', 'Wright', 'Young'
91
45
  ]
92
46
 
93
47
  /**
@@ -106,12 +106,11 @@ class MainContainer extends ConfigurationViewport {
106
106
  */
107
107
  createExampleComponent() {
108
108
  return {
109
- module : GridContainer,
110
- bind : {store : 'stores.mainStore'},
111
- cellEditing : true,
112
- parentId : this.id,
113
- selectionModel: CellModel,
114
- store : MainStore,
109
+ module : GridContainer,
110
+ bind : {store: 'stores.mainStore'},
111
+ cellEditing: true,
112
+ parentId : this.id,
113
+ store : MainStore,
115
114
 
116
115
  columnDefaults: {
117
116
  editable: true,
@@ -159,7 +158,11 @@ class MainContainer extends ConfigurationViewport {
159
158
  dataField: 'githubId',
160
159
  editable : false,
161
160
  text : 'Github Id (Non-editable)'
162
- }]
161
+ }],
162
+
163
+ viewConfig: {
164
+ selectionModel: CellModel
165
+ }
163
166
  }
164
167
  }
165
168
 
@@ -25,7 +25,8 @@ class MainContainer extends ConfigurationViewport {
25
25
  }
26
26
 
27
27
  createConfigurationComponents() {
28
- let me = this;
28
+ let me = this,
29
+ {view} = me.exampleComponent;
29
30
 
30
31
  const selectionModelRadioDefaults = {
31
32
  module : Radio,
@@ -45,34 +46,34 @@ class MainContainer extends ConfigurationViewport {
45
46
  value : me.exampleComponent.height
46
47
  }, {
47
48
  ...selectionModelRadioDefaults,
48
- checked : me.exampleComponent.selectionModel.ntype === 'selection-grid-cellmodel',
49
+ checked : view.selectionModel.ntype === 'selection-grid-cellmodel',
49
50
  labelText : 'selectionModel',
50
51
  listeners : {change: me.onRadioChange.bind(me, 'selectionModel', CellModel)},
51
52
  style : {marginTop: '10px'},
52
53
  valueLabelText: 'Cell'
53
54
  }, {
54
55
  ...selectionModelRadioDefaults,
55
- checked : me.exampleComponent.selectionModel.ntype === 'selection-grid-columnmodel',
56
+ checked : view.selectionModel.ntype === 'selection-grid-columnmodel',
56
57
  listeners : {change: me.onRadioChange.bind(me, 'selectionModel', ColumnModel)},
57
58
  valueLabelText: 'Column'
58
59
  }, {
59
60
  ...selectionModelRadioDefaults,
60
- checked : me.exampleComponent.selectionModel.ntype === 'selection-grid-rowmodel',
61
+ checked : view.selectionModel.ntype === 'selection-grid-rowmodel',
61
62
  listeners : {change: me.onRadioChange.bind(me, 'selectionModel', RowModel)},
62
63
  valueLabelText: 'Row'
63
64
  }, {
64
65
  ...selectionModelRadioDefaults,
65
- checked : me.exampleComponent.selectionModel.ntype === 'selection-grid-cellcolumnmodel',
66
+ checked : view.selectionModel.ntype === 'selection-grid-cellcolumnmodel',
66
67
  listeners : {change: me.onRadioChange.bind(me, 'selectionModel', CellColumnModel)},
67
68
  valueLabelText: 'Cell & Column'
68
69
  }, {
69
70
  ...selectionModelRadioDefaults,
70
- checked : me.exampleComponent.selectionModel.ntype === 'selection-grid-cellrowmodel',
71
+ checked : view.selectionModel.ntype === 'selection-grid-cellrowmodel',
71
72
  listeners : {change: me.onRadioChange.bind(me, 'selectionModel', CellRowModel)},
72
73
  valueLabelText: 'Cell & Row'
73
74
  }, {
74
75
  ...selectionModelRadioDefaults,
75
- checked : me.exampleComponent.selectionModel.ntype === 'selection-grid-cellcolumnrowmodel',
76
+ checked : view.selectionModel.ntype === 'selection-grid-cellcolumnrowmodel',
76
77
  listeners : {change: me.onRadioChange.bind(me, 'selectionModel', CellColumnRowModel)},
77
78
  valueLabelText: 'Cell & Column & Row'
78
79
  }]
@@ -80,9 +81,8 @@ class MainContainer extends ConfigurationViewport {
80
81
 
81
82
  createExampleComponent() {
82
83
  return {
83
- module : GridContainer,
84
- selectionModel: CellModel,
85
- store : MainStore,
84
+ module: GridContainer,
85
+ store : MainStore,
86
86
 
87
87
  columnDefaults: {
88
88
  width: 200
@@ -93,7 +93,22 @@ class MainContainer extends ConfigurationViewport {
93
93
  {dataField: 'lastname', text: 'Lastname'},
94
94
  {dataField: 'githubId', text: 'Github Id'},
95
95
  {dataField: 'country', text: 'Country'}
96
- ]
96
+ ],
97
+
98
+ viewConfig: {
99
+ selectionModel: CellModel
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * @param {String} config
106
+ * @param {String} value
107
+ * @param {Object} opts
108
+ */
109
+ onRadioChange(config, value, opts) {
110
+ if (opts.value === true) { // we only want to listen to check events, not uncheck
111
+ this.exampleComponent.view[config] = value
97
112
  }
98
113
  }
99
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.4.1",
3
+ "version": "8.5.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -52,7 +52,7 @@
52
52
  "commander": "^13.0.0",
53
53
  "cssnano": "^7.0.6",
54
54
  "envinfo": "^7.14.0",
55
- "fs-extra": "^11.2.0",
55
+ "fs-extra": "^11.3.0",
56
56
  "highlightjs-line-numbers.js": "^2.9.0",
57
57
  "inquirer": "^12.3.2",
58
58
  "marked": "^15.0.6",
@@ -91,7 +91,7 @@ class MainComponent extends Component {
91
91
  let me = this,
92
92
  cls = ['far', 'fa-square'],
93
93
  oldCls = ['fa', 'fa-check'],
94
- node = VdomUtil.findVdomChild(me.vdom, data.path[0].id).vdom;
94
+ node = VdomUtil.find(me.vdom, data.path[0].id).vdom;
95
95
 
96
96
  if (data.path[0].cls.includes('fa-square')) {
97
97
  cls = ['fa', 'fa-check'];
@@ -77,7 +77,7 @@ Use the utility method, replacing the statement referencing the vdom hierarchy.
77
77
 
78
78
  <pre>
79
79
  <s>view.vdom.cn[0].innerHTML = business.title;</s>
80
- Neo.util.VDom.findVdomChild(view.vdom, 'title').vdom.innerHTML = business.title;
80
+ Neo.util.VDom.find(view.vdom, 'title').vdom.innerHTML = business.title;
81
81
  </pre>
82
82
 
83
83
 
@@ -86,17 +86,17 @@ calling the function.
86
86
 
87
87
  Code a new class member.
88
88
 
89
- findVdomChild = Neo.util.VDom.findVdomChild
89
+ find = Neo.util.VDom.find
90
90
 
91
91
  Then use it in the after set method. Here's how that part of the code will look afterwards.
92
92
 
93
93
  <pre style="color:gray; padding: 8px; border: thin solid lightgray;">
94
- findVdomChild = Neo.util.VDom.findVdomChild;
94
+ find = Neo.util.VDom.find;
95
95
 
96
96
  afterSetBusiness(business){
97
97
  if (!business) return;
98
98
  const view = this.getReference('view');
99
- this.findVdomChild(view.vdom, 'title').vdom.innerHTML = business.title;
99
+ this.find(view.vdom, 'title').vdom.innerHTML = business.title;
100
100
  view.update();
101
101
  }
102
102
  </pre>
@@ -129,7 +129,7 @@ Then back in the details view, update the vdom as follows.
129
129
 
130
130
  Then modify the `afterSetBusiness()` method to update the element's `src`.
131
131
 
132
- this.findVdomChild(view.vdom, 'thumbnail').vdom.src = business.imageUrl;
132
+ this.find(view.vdom, 'thumbnail').vdom.src = business.imageUrl;
133
133
 
134
134
  ??Add the address
135
135
 
@@ -184,7 +184,7 @@ of thought and try it. If you get stuck, that code is given in the next lab step
184
184
 
185
185
  Here's one way to code it. How does this compare to your solution?
186
186
 
187
- this.findVdomChild(view.vdom, 'address').vdom.cn = business
187
+ this.find(view.vdom, 'address').vdom.cn = business
188
188
  .address
189
189
  .map(item => ({tag: 'div', innerHTML: item}));
190
190
 
@@ -1,5 +1,9 @@
1
1
  .portal-examples-tab-container.neo-tab-container {
2
-
2
+ .neo-tab-strip {
3
+ .neo-active-tab-indicator {
4
+ background-color: #3E63DD;
5
+ }
6
+ }
3
7
  }
4
8
 
5
9
  // must not be inside the root class to honor the styling for tab button drag&drop
@@ -5,6 +5,10 @@
5
5
  overflow-y : auto;
6
6
  position : relative;
7
7
 
8
+ &:focus {
9
+ outline: none;
10
+ }
11
+
8
12
  .neo-grid-scrollbar {
9
13
  height : 1px;
10
14
  position : absolute;
@@ -262,12 +262,12 @@ const DefaultConfig = {
262
262
  useVdomWorker: true,
263
263
  /**
264
264
  * buildScripts/injectPackageVersion.mjs will update this value
265
- * @default '8.4.1'
265
+ * @default '8.5.0'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '8.4.1'
270
+ version: '8.5.0'
271
271
  };
272
272
 
273
273
  Object.assign(DefaultConfig, {
@@ -402,9 +402,12 @@ class Component extends Base {
402
402
  }
403
403
 
404
404
  /**
405
- * @member {String[]} childUpdateCache=[]
405
+ * If an update() gets called while a parent is updating, we store the id & distance of the
406
+ * requesting component inside the childUpdateCache of the parent, to get resolved once the update is done.
407
+ * e.g. childUpdateCache = {'neo-grid-view-1': {distance: 1, resolve: fn}}
408
+ * @member {Object} childUpdateCache={}
406
409
  */
407
- childUpdateCache = []
410
+ childUpdateCache = {}
408
411
  /**
409
412
  * Stores the updateDepth while an update is running to enable checks for parent update collisions
410
413
  * @member {Number|null} currentUpdateDepth=null
@@ -1954,7 +1957,7 @@ class Component extends Base {
1954
1957
  console.warn('vdom parent update conflict with:', parent, 'for:', me)
1955
1958
  }
1956
1959
 
1957
- NeoArray.add(parent.childUpdateCache, me.id);
1960
+ parent.childUpdateCache[me.id] = {distance, resolve};
1958
1961
 
1959
1962
  // Adding the resolve fn to its own cache, since the parent will trigger
1960
1963
  // a new update() directly on this cmp
@@ -2335,22 +2338,46 @@ class Component extends Base {
2335
2338
  * @protected
2336
2339
  */
2337
2340
  resolveVdomUpdate(resolve) {
2338
- let me = this;
2341
+ let me = this,
2342
+ hasChildUpdateCache = !Neo.isEmpty(me.childUpdateCache),
2343
+ component;
2339
2344
 
2340
2345
  me.doResolveUpdateCache();
2341
2346
 
2342
2347
  resolve?.();
2343
2348
 
2344
2349
  if (me.needsVdomUpdate) {
2345
- // if a new update is scheduled, we can clear the cache => these updates are included
2346
- me.childUpdateCache = [];
2350
+ if (hasChildUpdateCache) {
2351
+ Object.entries(me.childUpdateCache).forEach(([key, value]) => {
2352
+ component = Neo.getComponent(key);
2353
+
2354
+ // The component might already got destroyed
2355
+ if (component) {
2356
+ // Pass callbacks to the resolver cache => getting executed once the following update is done
2357
+ value.resolve && NeoArray.add(me.resolveUpdateCache, value.resolve);
2358
+
2359
+ // Adjust the updateDepth to include the depth of all merged child updates
2360
+ if (me.updateDepth !== -1) {
2361
+ if (component.updateDepth === -1) {
2362
+ me.updateDepth = -1
2363
+ } else {
2364
+ // Since updateDepth is 1-based, we need to subtract 1 level
2365
+ me.updateDepth = me.updateDepth + value.distance + component.updateDepth - 1
2366
+ }
2367
+ }
2368
+ }
2369
+ });
2370
+
2371
+ me.childUpdateCache = {}
2372
+ }
2347
2373
 
2348
2374
  me.update()
2349
- } else if (me.childUpdateCache) {
2350
- [...me.childUpdateCache].forEach(id => {
2351
- Neo.getComponent(id)?.update();
2352
- NeoArray.remove(me.childUpdateCache, id)
2353
- })
2375
+ } else if (hasChildUpdateCache) {
2376
+ Object.keys(me.childUpdateCache).forEach(key => {
2377
+ Neo.getComponent(key)?.update()
2378
+ });
2379
+
2380
+ me.childUpdateCache = {}
2354
2381
  }
2355
2382
  }
2356
2383
 
@@ -1,7 +1,6 @@
1
1
  import BaseContainer from '../container/Base.mjs';
2
2
  import ClassSystemUtil from '../util/ClassSystem.mjs';
3
3
  import GridView from './View.mjs';
4
- import RowModel from '../selection/grid/RowModel.mjs';
5
4
  import Store from '../data/Store.mjs';
6
5
  import * as header from './header/_export.mjs';
7
6
 
@@ -58,11 +57,6 @@ class GridContainer extends BaseContainer {
58
57
  * @member {String|null} headerToolbarId_=null
59
58
  */
60
59
  headerToolbarId_: null,
61
- /**
62
- * Additional used keys for the selection model
63
- * @member {Object} keys
64
- */
65
- keys: {},
66
60
  /**
67
61
  * @member {String} layout='base'
68
62
  */
@@ -81,10 +75,6 @@ class GridContainer extends BaseContainer {
81
75
  * @protected
82
76
  */
83
77
  scrollbarId_: null,
84
- /**
85
- * @member {Neo.selection.Model} selectionModel_=null
86
- */
87
- selectionModel_: null,
88
78
  /**
89
79
  * @member {Boolean} showHeaderFilters_=false
90
80
  */
@@ -294,16 +284,6 @@ class GridContainer extends BaseContainer {
294
284
  }
295
285
  }
296
286
 
297
- /**
298
- * Triggered after the selectionModel config got changed
299
- * @param {Neo.selection.Model} value
300
- * @param {Neo.selection.Model} oldValue
301
- * @protected
302
- */
303
- afterSetSelectionModel(value, oldValue) {
304
- this.rendered && value.register(this)
305
- }
306
-
307
287
  /**
308
288
  * Triggered after the showHeaderFilters config got changed
309
289
  * @param {Boolean} value
@@ -352,18 +332,6 @@ class GridContainer extends BaseContainer {
352
332
  return value || oldValue
353
333
  }
354
334
 
355
- /**
356
- * Triggered before the selectionModel config gets changed.
357
- * @param {Neo.selection.Model} value
358
- * @param {Neo.selection.Model} oldValue
359
- * @protected
360
- */
361
- beforeSetSelectionModel(value, oldValue) {
362
- oldValue?.destroy();
363
-
364
- return ClassSystemUtil.beforeSetInstance(value, RowModel)
365
- }
366
-
367
335
  /**
368
336
  * Triggered before the store config gets changed.
369
337
  * @param {Neo.data.Store} value
@@ -534,14 +502,6 @@ class GridContainer extends BaseContainer {
534
502
  return `${this.id}__wrapper`
535
503
  }
536
504
 
537
- /**
538
- *
539
- */
540
- onConstructed() {
541
- super.onConstructed();
542
- this.selectionModel?.register(this)
543
- }
544
-
545
505
  /**
546
506
  * @param {Object} data
547
507
  */
@@ -608,12 +568,6 @@ class GridContainer extends BaseContainer {
608
568
  if (me.store.sorters.length < 1) {
609
569
  me.removeSortingCss()
610
570
  }
611
- } else {
612
- me.on('rendered', () => {
613
- me.timeout(50).then(() => {
614
- me.createViewData(data)
615
- })
616
- }, me, {once: true})
617
571
  }
618
572
  }
619
573
 
package/src/grid/View.mjs CHANGED
@@ -1,6 +1,8 @@
1
- import Component from '../component/Base.mjs';
2
- import NeoArray from '../util/Array.mjs';
3
- import VDomUtil from '../util/VDom.mjs';
1
+ import ClassSystemUtil from '../util/ClassSystem.mjs';
2
+ import Component from '../component/Base.mjs';
3
+ import NeoArray from '../util/Array.mjs';
4
+ import RowModel from '../selection/grid/RowModel.mjs';
5
+ import VDomUtil from '../util/VDom.mjs';
4
6
 
5
7
  /**
6
8
  * @class Neo.grid.View
@@ -38,6 +40,12 @@ class GridView extends Component {
38
40
  * @protected
39
41
  */
40
42
  baseCls: ['neo-grid-view'],
43
+ /**
44
+ * The amount of columns (cells) to paint before the first & after the last visible column,
45
+ * to enhance the scrolling performance
46
+ * @member {Number} bufferColumnRange_=0
47
+ */
48
+ bufferColumnRange_: 0,
41
49
  /**
42
50
  * The amount of rows to paint before the first & after the last visible row,
43
51
  * to enhance the scrolling performance
@@ -54,6 +62,11 @@ class GridView extends Component {
54
62
  * @protected
55
63
  */
56
64
  containerId: null,
65
+ /**
66
+ * Internal flag. Gets calculated after mounting grid.View rows
67
+ * @member {Number} containerWidth_=0
68
+ */
69
+ containerWidth_: 0,
57
70
  /**
58
71
  * @member {Object[]} columnPositions_=[]
59
72
  * @protected
@@ -64,9 +77,10 @@ class GridView extends Component {
64
77
  */
65
78
  isScrolling_: false,
66
79
  /**
67
- * @member {Object} recordVnodeMap={}
80
+ * Additional used keys for the selection model
81
+ * @member {Object} keys
68
82
  */
69
- recordVnodeMap: {},
83
+ keys: {},
70
84
  /**
71
85
  * @member {String} role='rowgroup'
72
86
  */
@@ -80,6 +94,10 @@ class GridView extends Component {
80
94
  * @member {Object} scrollPosition_={x:0,y:0}
81
95
  */
82
96
  scrollPosition_: {x: 0, y: 0},
97
+ /**
98
+ * @member {Neo.selection.Model} selectionModel_=null
99
+ */
100
+ selectionModel_: null,
83
101
  /**
84
102
  * @member {String} selectedRecordField='annotations.selected'
85
103
  */
@@ -92,13 +110,9 @@ class GridView extends Component {
92
110
  * @member {Neo.data.Store|null} store_=null
93
111
  */
94
112
  store_: null,
95
- /**
96
- * @member {Boolean} useRowRecordIds=true
97
- */
98
- useRowRecordIds: true,
99
113
  /**
100
114
  * Stores the indexes of the first & last painted columns
101
- * @member {Array} visibleColumns_=[0,0]
115
+ * @member {Number[]} visibleColumns_=[0,0]
102
116
  * @protected
103
117
  */
104
118
  visibleColumns_: [0, 0],
@@ -110,7 +124,7 @@ class GridView extends Component {
110
124
  * @member {Object} _vdom
111
125
  */
112
126
  _vdom:
113
- {cn: [
127
+ {tabIndex: '-1', cn: [
114
128
  {cn: []},
115
129
  {cls: 'neo-grid-scrollbar'}
116
130
  ]}
@@ -132,10 +146,10 @@ class GridView extends Component {
132
146
  * @member {String[]} selectedRows
133
147
  */
134
148
  get selectedRows() {
135
- let {gridContainer} = this;
149
+ let {selectionModel} = this;
136
150
 
137
- if (gridContainer.selectionModel.ntype === 'selection-grid-rowmodel') {
138
- return gridContainer.selectionModel.items
151
+ if (selectionModel.ntype === 'selection-grid-rowmodel') {
152
+ return selectionModel.items
139
153
  }
140
154
 
141
155
  return []
@@ -205,6 +219,16 @@ class GridView extends Component {
205
219
  }
206
220
  }
207
221
 
222
+ /**
223
+ * Triggered after the bufferColumnRange config got changed
224
+ * @param {Number} value
225
+ * @param {Number} oldValue
226
+ * @protected
227
+ */
228
+ afterSetBufferColumnRange(value, oldValue) {
229
+ oldValue !== undefined && this.createViewData()
230
+ }
231
+
208
232
  /**
209
233
  * Triggered after the bufferRowRange config got changed
210
234
  * @param {Number} value
@@ -215,6 +239,18 @@ class GridView extends Component {
215
239
  oldValue !== undefined && this.createViewData()
216
240
  }
217
241
 
242
+ /**
243
+ * Triggered after the containerWidth config got changed
244
+ * @param {Number} value
245
+ * @param {Number} oldValue
246
+ * @protected
247
+ */
248
+ afterSetContainerWidth(value, oldValue) {
249
+ if (value > 0 && this.columnPositions.length > 0) {
250
+ this.updateVisibleColumns()
251
+ }
252
+ }
253
+
218
254
  /**
219
255
  * Triggered after the columnPositions config got changed
220
256
  * @param {Object[]} value
@@ -222,13 +258,8 @@ class GridView extends Component {
222
258
  * @protected
223
259
  */
224
260
  afterSetColumnPositions(value, oldValue) {
225
- let me = this;
226
-
227
- if (value.length > 0) {
228
- // for changing an array inline, we need to use the leading underscore
229
- me._visibleColumns[1] = value.length - 1;
230
-
231
- me.createViewData()
261
+ if (value.length > 0 && this.containerWidth > 0) {
262
+ this.updateVisibleColumns()
232
263
  }
233
264
  }
234
265
 
@@ -291,6 +322,16 @@ class GridView extends Component {
291
322
  }
292
323
  }
293
324
 
325
+ /**
326
+ * Triggered after the selectionModel config got changed
327
+ * @param {Neo.selection.Model} value
328
+ * @param {Neo.selection.Model} oldValue
329
+ * @protected
330
+ */
331
+ afterSetSelectionModel(value, oldValue) {
332
+ this.rendered && value.register(this)
333
+ }
334
+
294
335
  /**
295
336
  * Triggered after the startIndex config got changed
296
337
  * @param {Number} value
@@ -322,8 +363,8 @@ class GridView extends Component {
322
363
 
323
364
  /**
324
365
  * Triggered after the visibleColumns config got changed
325
- * @param {Number} value
326
- * @param {Number} oldValue
366
+ * @param {Number[]} value
367
+ * @param {Number[]} oldValue
327
368
  * @protected
328
369
  */
329
370
  afterSetVisibleColumns(value, oldValue) {
@@ -400,8 +441,7 @@ class GridView extends Component {
400
441
  id : cellId,
401
442
  cls : cellCls,
402
443
  role : 'gridcell',
403
- style : rendererOutput.style || {},
404
- tabIndex : '-1'
444
+ style : rendererOutput.style || {}
405
445
  };
406
446
 
407
447
  if (column.width) {
@@ -421,6 +461,18 @@ class GridView extends Component {
421
461
  return cellConfig
422
462
  }
423
463
 
464
+ /**
465
+ * Triggered before the selectionModel config gets changed.
466
+ * @param {Neo.selection.Model} value
467
+ * @param {Neo.selection.Model} oldValue
468
+ * @protected
469
+ */
470
+ beforeSetSelectionModel(value, oldValue) {
471
+ oldValue?.destroy();
472
+
473
+ return ClassSystemUtil.beforeSetInstance(value, RowModel)
474
+ }
475
+
424
476
  /**
425
477
  * @param {Object} opts
426
478
  * @param {Object} opts.record
@@ -433,13 +485,11 @@ class GridView extends Component {
433
485
  }
434
486
 
435
487
  let me = this,
436
- {gridContainer, selectedRows, visibleColumns} = me,
488
+ {bufferColumnRange, gridContainer, selectedRows, visibleColumns} = me,
437
489
  columns = gridContainer.items[0].items,
438
490
  id = me.getRowId(record, rowIndex),
439
491
  trCls = me.getTrClass(record, rowIndex),
440
- config, column, gridRow, i;
441
-
442
- me.recordVnodeMap[id] = rowIndex;
492
+ config, column, endIndex, gridRow, i, startIndex;
443
493
 
444
494
  if (rowIndex % 2 !== 0) {
445
495
  trCls.push('neo-even')
@@ -463,7 +513,6 @@ class GridView extends Component {
463
513
  cls : trCls,
464
514
  cn : [],
465
515
  role : 'row',
466
- tabIndex : '-1',
467
516
 
468
517
  style: {
469
518
  height : me.rowHeight + 'px',
@@ -471,7 +520,10 @@ class GridView extends Component {
471
520
  }
472
521
  };
473
522
 
474
- for (i=visibleColumns[0]; i <= visibleColumns[1]; i++) {
523
+ endIndex = Math.min(columns.length - 1, visibleColumns[1] + bufferColumnRange);
524
+ startIndex = Math.max(0, visibleColumns[0] - bufferColumnRange);
525
+
526
+ for (i=startIndex; i <= endIndex; i++) {
475
527
  column = columns[i];
476
528
  config = me.applyRendererOutput({column, gridContainer, index: rowIndex, record});
477
529
 
@@ -495,16 +547,23 @@ class GridView extends Component {
495
547
  *
496
548
  */
497
549
  createViewData() {
498
- let me = this,
550
+ let me = this,
499
551
  {bufferRowRange, startIndex, store} = me,
500
- rows = [],
552
+ countRecords = store.getCount(),
553
+ rows = [],
501
554
  endIndex, i;
502
555
 
503
- if (store.getCount() < 1 || me.availableRows < 1 || me.columnPositions.length < 1) {
556
+ if (
557
+ countRecords < 1 ||
558
+ me.availableRows < 1 ||
559
+ me._containerWidth < 1 || // we are not checking me.containerWidth, since we want to ignore the config symbol
560
+ me.columnPositions.length < 1 ||
561
+ me.visibleColumns[1] < 1
562
+ ) {
504
563
  return
505
564
  }
506
565
 
507
- endIndex = Math.min(store.getCount(), me.availableRows + startIndex + bufferRowRange);
566
+ endIndex = Math.min(countRecords, me.availableRows + startIndex + bufferRowRange);
508
567
  startIndex = Math.max(0, startIndex - bufferRowRange);
509
568
 
510
569
  for (i=startIndex; i < endIndex; i++) {
@@ -619,27 +678,28 @@ class GridView extends Component {
619
678
 
620
679
  /**
621
680
  * @param {String} rowId
622
- * @returns {Object}
681
+ * @returns {Record}
623
682
  */
624
683
  getRecordByRowId(rowId) {
625
- return this.store.getAt(this.recordVnodeMap[rowId])
684
+ let recordId = rowId.split('__')[2],
685
+ {store} = this,
686
+ {model} = store,
687
+ keyField = model?.getField(store.getKeyProperty()),
688
+ keyType = keyField?.type?.toLowerCase();
689
+
690
+ if (keyType === 'int' || keyType === 'integer') {
691
+ recordId = parseInt(recordId)
692
+ }
693
+
694
+ return store.get(recordId)
626
695
  }
627
696
 
628
697
  /**
629
698
  * @param {Object} record
630
- * @param {Number} [index]
631
699
  * @returns {String}
632
700
  */
633
- getRowId(record, index) {
634
- let me = this,
635
- {store} = me;
636
-
637
- if (me.useRowRecordIds) {
638
- return `${me.id}__tr__${record[store.keyProperty]}`
639
- } else {
640
- index = Neo.isNumber(index) ? index : store.indexOf(record);
641
- return me.vdom.cn[index]?.id || Neo.getId('tr')
642
- }
701
+ getRowId(record) {
702
+ return `${this.id}__tr__${record[this.store.getKeyProperty()]}`
643
703
  }
644
704
 
645
705
  /**
@@ -689,6 +749,14 @@ class GridView extends Component {
689
749
  this.fireCellEvent(data, 'cellDoubleClick')
690
750
  }
691
751
 
752
+ /**
753
+ *
754
+ */
755
+ onConstructed() {
756
+ super.onConstructed();
757
+ this.selectionModel?.register(this)
758
+ }
759
+
692
760
  /**
693
761
  * @param {Object} data
694
762
  */
@@ -730,7 +798,7 @@ class GridView extends Component {
730
798
  * @param {Neo.data.Model} opts.model The model instance of the changed record
731
799
  * @param {Object} opts.record
732
800
  */
733
- onStoreRecordChange({fields, model, record}) {
801
+ onStoreRecordChange({fields, record}) {
734
802
  let me = this,
735
803
  fieldNames = fields.map(field => field.name),
736
804
  needsUpdate = false,
@@ -787,15 +855,16 @@ class GridView extends Component {
787
855
  *
788
856
  */
789
857
  updateVisibleColumns() {
790
- let me = this,
791
- {x} = me.scrollPosition,
792
- i = 0,
793
- len = me.columnPositions.length,
794
- endIndex = len - 1,
858
+ let me = this,
859
+ {columnPositions} = me,
860
+ {x} = me.scrollPosition,
861
+ i = 0,
862
+ len = columnPositions.length,
863
+ endIndex = len - 1,
795
864
  column, startIndex;
796
865
 
797
866
  for (; i < len; i++) {
798
- column = me.columnPositions[i];
867
+ column = columnPositions[i];
799
868
 
800
869
  if (x >= column.x && x <= column.x + column.width) {
801
870
  startIndex = i
@@ -807,7 +876,12 @@ class GridView extends Component {
807
876
  }
808
877
  }
809
878
 
810
- me.visibleColumns = [startIndex, endIndex]
879
+ if (
880
+ Math.abs(startIndex - me.visibleColumns[0]) >= me.bufferColumnRange ||
881
+ me.visibleColumns[1] < 1 // initial call
882
+ ) {
883
+ me.visibleColumns = [startIndex, endIndex]
884
+ }
811
885
  }
812
886
  }
813
887
 
@@ -106,7 +106,8 @@ class Toolbar extends BaseToolbar {
106
106
  *
107
107
  */
108
108
  createItems() {
109
- let me = this;
109
+ let me = this,
110
+ {mounted} = me;
110
111
 
111
112
  me.itemDefaults.showHeaderFilter = me.showHeaderFilters;
112
113
 
@@ -130,7 +131,8 @@ class Toolbar extends BaseToolbar {
130
131
  });
131
132
 
132
133
  me.promiseUpdate().then(() => {
133
- me.mounted && me.passSizeToView()
134
+ // To prevent duplicate calls, we need to check the mounted state before the update call
135
+ mounted && me.passSizeToView()
134
136
  })
135
137
  }
136
138
 
@@ -189,11 +189,11 @@ class Model extends Base {
189
189
  */
190
190
  register(component) {
191
191
  let me = this,
192
- cls = component.cls || [];
192
+ cls = component.wrapperCls || [];
193
193
 
194
194
  if (me.cls && !cls.includes(me.cls)) {
195
195
  cls.push(me.cls);
196
- component.cls = cls
196
+ component.wrapperCls = cls
197
197
  }
198
198
 
199
199
  me.view = component;
@@ -30,7 +30,7 @@ class RowModel extends BaseModel {
30
30
  addDomListener() {
31
31
  let me = this;
32
32
 
33
- me.view.on('rowClick', me.onRowClick, me)
33
+ me.view.gridContainer.on('rowClick', me.onRowClick, me)
34
34
  }
35
35
 
36
36
  /**
@@ -44,76 +44,45 @@ class RowModel extends BaseModel {
44
44
  super.destroy(...args)
45
45
  }
46
46
 
47
- /**
48
- * Finds the matching table row for a given row index
49
- * @param {Number} index row index
50
- * @returns {String|null} The table row node id
51
- */
52
- getRowId(index) {
53
- if (index < 0 || this.view.store.getCount() < index) {
54
- return null
55
- }
56
-
57
- return this.view.vdom.cn[0].cn[1].cn[index].id
58
- }
59
-
60
- /**
61
- * Finds the matching table row for a given event path
62
- * @param {Object} path The event path
63
- * @returns {Object|null} The node containing the table row class or null
64
- * @protected
65
- */
66
- static getRowNode(path) {
67
- let i = 0,
68
- len = path.length,
69
- node = null;
70
-
71
- for (; i < len; i++) {
72
- if (path[i].cls.includes('neo-grid-row')) {
73
- node = path[i]
74
- }
75
- }
76
-
77
- return node
78
- }
79
-
80
47
  /**
81
48
  * @param {Object} data
82
49
  */
83
50
  onKeyDownDown(data) {
84
- this.onNavKeyRow(data, 1)
51
+ this.onNavKeyRow(1)
85
52
  }
86
53
 
87
54
  /**
88
55
  * @param {Object} data
89
56
  */
90
57
  onKeyDownUp(data) {
91
- this.onNavKeyRow(data, -1)
58
+ this.onNavKeyRow(-1)
92
59
  }
93
60
 
94
61
  /**
95
- * @param {Object} data
96
62
  * @param {Number} step
97
63
  */
98
- onNavKeyRow(data, step) {
64
+ onNavKeyRow(step) {
99
65
  let me = this,
100
- node = RowModel.getRowNode(data.path),
101
66
  {view} = me,
102
67
  {store} = view,
103
- vdomNode = VDomUtil.find(view.vdom, node.id),
104
- newIndex = (vdomNode.index + step) % store.getCount(),
105
- {parentNode} = vdomNode,
106
- id;
68
+ currentIndex = 0,
69
+ newIndex, newRecord, rowId;
70
+
71
+ if (me.hasSelection()) {
72
+ currentIndex = store.indexOf(view.getRecordByRowId(me.items[0]))
73
+ }
74
+
75
+ newIndex = (currentIndex + step) % store.getCount();
107
76
 
108
77
  while (newIndex < 0) {
109
78
  newIndex += store.getCount()
110
79
  }
111
80
 
112
- id = parentNode.cn[newIndex].id;
81
+ newRecord = store.getAt(newIndex);
82
+ rowId = view.getRowId(newRecord);
113
83
 
114
- if (id) {
115
- me.select(id);
116
- view.focus(id);
84
+ if (rowId) {
85
+ me.select(rowId);
117
86
 
118
87
  view.fire('select', {
119
88
  record: store.getAt(newIndex)