neo.mjs 8.28.1 → 8.30.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.
Files changed (32) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/portal/index.html +1 -1
  3. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  4. package/examples/ServiceWorker.mjs +2 -2
  5. package/examples/grid/bigData/GridContainer.mjs +12 -3
  6. package/examples/grid/bigData/MainModel.mjs +5 -2
  7. package/examples/grid/bigData/MainStore.mjs +4 -2
  8. package/examples/grid/nestedRecordFields/Viewport.mjs +8 -26
  9. package/package.json +1 -1
  10. package/resources/scss/src/component/Progress.scss +1 -1
  11. package/resources/scss/src/grid/column/AnimatedChange.scss +15 -0
  12. package/resources/scss/src/grid/column/Progress.scss +34 -0
  13. package/resources/scss/theme-dark/grid/column/Progress.scss +6 -0
  14. package/resources/scss/theme-light/grid/column/Progress.scss +6 -0
  15. package/resources/scss/theme-neo-light/grid/column/Progress.scss +6 -0
  16. package/src/DefaultConfig.mjs +2 -2
  17. package/src/component/Base.mjs +1 -1
  18. package/src/component/Progress.mjs +26 -5
  19. package/src/core/Observable.mjs +7 -2
  20. package/src/draggable/grid/header/toolbar/SortZone.mjs +1 -3
  21. package/src/grid/Container.mjs +52 -26
  22. package/src/grid/ScrollManager.mjs +1 -1
  23. package/src/grid/View.mjs +66 -46
  24. package/src/grid/column/AnimatedChange.mjs +78 -0
  25. package/src/grid/column/Base.mjs +81 -0
  26. package/src/grid/column/Component.mjs +122 -0
  27. package/src/grid/column/Index.mjs +42 -0
  28. package/src/grid/column/Progress.mjs +42 -0
  29. package/src/grid/column/_export.mjs +7 -0
  30. package/src/grid/header/Button.mjs +3 -40
  31. package/src/selection/grid/BaseModel.mjs +1 -1
  32. package/src/vdom/Helper.mjs +1 -1
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.28.1'
23
+ * @member {String} version='8.30.0'
24
24
  */
25
- version: '8.28.1'
25
+ version: '8.30.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-03-02",
19
+ "datePublished": "2025-03-07",
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.28.1'
110
+ html : 'v8.30.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.28.1'
23
+ * @member {String} version='8.30.0'
24
24
  */
25
- version: '8.28.1'
25
+ version: '8.30.0'
26
26
  }
27
27
 
28
28
  /**
@@ -1,4 +1,5 @@
1
1
  import BaseGridContainer from '../../../src/grid/Container.mjs';
2
+ import Button from '../../../src/button/Base.mjs';
2
3
  import MainStore from './MainStore.mjs';
3
4
 
4
5
  /**
@@ -45,11 +46,19 @@ class GridContainer extends BaseGridContainer {
45
46
  * @protected
46
47
  */
47
48
  afterSetAmountColumns(value, oldValue) {
48
- let i = 4,
49
+ let i = 7,
49
50
  columns = [
50
- {dataField: 'id', text: '#', width: 60, renderer({record, store}) {return store.indexOf(record) + 1}},
51
+ {type: 'index', dataField: 'id', text: '#', width: 60},
51
52
  {cellAlign: 'left', dataField: 'firstname', defaultSortDirection: 'ASC', text: 'Firstname', width: 150},
52
- {cellAlign: 'left', dataField: 'lastname', defaultSortDirection: 'ASC', text: 'Lastname', width: 150}
53
+ {cellAlign: 'left', dataField: 'lastname', defaultSortDirection: 'ASC', text: 'Lastname', width: 150},
54
+ {cellAlign: 'left', dataField: 'countAction', text: 'Increase Counter', width: 150, component: ({record}) => ({
55
+ module: Button,
56
+ handler() {record.counter++},
57
+ text : record.firstname + ' ++',
58
+ width : 130
59
+ })},
60
+ {type: 'animatedChange', dataField: 'counter', text: 'Counter'},
61
+ {type: 'progress', dataField: 'progress', text: 'Progress', width: 150}
53
62
  ];
54
63
 
55
64
  for (; i <= value; i++) {
@@ -24,11 +24,14 @@ class MainModel extends Model {
24
24
  * @protected
25
25
  */
26
26
  afterSetAmountColumns(value, oldValue) {
27
- let i = 4,
27
+ let i = 7,
28
28
  fields = [
29
29
  {name: 'id', type: 'Int'},
30
+ {name: 'countAction'},
31
+ {name: 'counter', type: 'Int'},
30
32
  {name: 'firstname', type: 'String'},
31
- {name: 'lastname', type: 'String'}
33
+ {name: 'lastname', type: 'String'},
34
+ {name: 'progress', type: 'Int'}
32
35
  ];
33
36
 
34
37
  for (; i <= value; i++) {
@@ -113,11 +113,13 @@ class MainStore extends Store {
113
113
  column, record;
114
114
 
115
115
  for (; row < amountRows; row++) {
116
- column = 4;
116
+ column = 7;
117
117
  record = {
118
118
  id : row + 1,
119
+ counter : Math.round(Math.random() * 100),
119
120
  firstname: me.firstnames[Math.floor(Math.random() * amountFirstnames)],
120
- lastname : me.lastnames[ Math.floor(Math.random() * amountLastnames)]
121
+ lastname : me.lastnames[ Math.floor(Math.random() * amountLastnames)],
122
+ progress : Math.round(Math.random() * 100)
121
123
  };
122
124
 
123
125
  for (; column <= amountColumns; column++) {
@@ -43,7 +43,7 @@ class Viewport extends BaseViewport {
43
43
  }]
44
44
  }, {
45
45
  module : GridContainer,
46
- bind : {store : 'stores.mainStore'},
46
+ bind : {store: 'stores.mainStore'},
47
47
  reference: 'grid',
48
48
 
49
49
  columnDefaults: {
@@ -55,7 +55,11 @@ class Viewport extends BaseViewport {
55
55
  {dataField: 'user.lastname', text: 'Lastname'},
56
56
  {dataField: 'githubId', text: 'Github Id'},
57
57
  {dataField: 'country', text: 'Country', renderer: 'up.countryRenderer'},
58
- {dataField: 'edit', text: 'Edit Action', renderer: 'up.editRenderer'}
58
+ {dataField: 'edit', text: 'Edit Action', component: {
59
+ module : Button,
60
+ handler: 'up.editButtonHandler',
61
+ text : 'Edit'
62
+ }}
59
63
  ],
60
64
 
61
65
  viewConfig: {
@@ -72,8 +76,8 @@ class Viewport extends BaseViewport {
72
76
  /**
73
77
  * @param {Object} data
74
78
  */
75
- countryRenderer({record}) {
76
- let countryStore = this.getStateProvider().getStore('countries');
79
+ countryRenderer({gridContainer, record}) {
80
+ let countryStore = gridContainer.getStateProvider().getStore('countries');
77
81
 
78
82
  if (countryStore.getCount() > 0) {
79
83
  return countryStore.get(record.country).name
@@ -111,28 +115,6 @@ class Viewport extends BaseViewport {
111
115
  }
112
116
  }
113
117
 
114
- /**
115
- * @param {Object} data
116
- */
117
- editRenderer({column, gridContainer, record, rowIndex}) {
118
- let me = this,
119
- {appName, windowId} = me,
120
- widgetId = `${column.id}-widget-${rowIndex}`,
121
- button = (column.widgetMap || (column.widgetMap = {}))[widgetId] || (column.widgetMap[widgetId] = Neo.create({
122
- module : Button,
123
- appName,
124
- handler : 'up.editButtonHandler',
125
- parentId: gridContainer.id,
126
- record,
127
- text : 'Edit',
128
- windowId
129
- }));
130
-
131
- me.view.updateDepth = -1;
132
-
133
- return button.createVdomReference()
134
- }
135
-
136
118
  /**
137
119
  * @param {Object} data
138
120
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.28.1",
3
+ "version": "8.30.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,3 +1,3 @@
1
- .neo-progress {
1
+ .neo-progress-wrapper {
2
2
  color: var(--progress-label-color);
3
3
  }
@@ -0,0 +1,15 @@
1
+ @keyframes grid-animated-cell {
2
+ 50% {background-color:var(--button-ripple-background-color);}
3
+ }
4
+
5
+ .neo-grid-container {
6
+ .neo-grid-row, .neo-grid-row.neo-even {
7
+ .neo-grid-cell {
8
+ &.neo-animated {
9
+ animation-name : grid-animated-cell;
10
+ animation-duration : .4s;
11
+ animation-timing-function: ease-in-out;
12
+ }
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,34 @@
1
+ .neo-grid-cell {
2
+ .neo-progress-wrapper {
3
+ progress[value] {
4
+ appearance: none;
5
+ border : none;
6
+ display : flex;
7
+ height : var(--grid-cell-progress-height);
8
+
9
+ @-moz-document url-prefix() {
10
+ background-color: var(--grid-cell-progress-track-color);
11
+ border-radius : var(--grid-cell-progress-border-radius);
12
+ box-shadow : 0 2px 5px rgba(0, 0, 0, 0.25) inset;
13
+ }
14
+
15
+ // for webkit the progress-bar is the track
16
+ &::-webkit-progress-bar {
17
+ background-color: var(--grid-cell-progress-track-color);
18
+ border-radius : var(--grid-cell-progress-border-radius);
19
+ box-shadow : 0 2px 5px rgba(0, 0, 0, 0.25) inset;
20
+ }
21
+
22
+ // for mozilla the progress-bar is the active range
23
+ &::-moz-progress-bar {
24
+ background-color: var(--grid-cell-progress-active-color);
25
+ border-radius : var(--grid-cell-progress-border-radius);
26
+ }
27
+
28
+ &::-webkit-progress-value {
29
+ background-color: var(--grid-cell-progress-active-color);
30
+ border-radius : var(--grid-cell-progress-border-radius);
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,6 @@
1
+ :root .neo-theme-dark { // .neo-grid-cell
2
+ --grid-cell-progress-active-color : #5d83a7;
3
+ --grid-cell-progress-border-radius: 2px;
4
+ --grid-cell-progress-height : 10px;
5
+ --grid-cell-progress-track-color : #333;
6
+ }
@@ -0,0 +1,6 @@
1
+ :root .neo-theme-light { // .neo-grid-cell
2
+ --grid-cell-progress-active-color : #5d83a7;
3
+ --grid-cell-progress-border-radius: 2px;
4
+ --grid-cell-progress-height : 10px;
5
+ --grid-cell-progress-track-color : #eee;
6
+ }
@@ -0,0 +1,6 @@
1
+ :root .neo-theme-neo-light { // .neo-grid-cell
2
+ --grid-cell-progress-active-color : #5d83a7;
3
+ --grid-cell-progress-border-radius: 2px;
4
+ --grid-cell-progress-height : 10px;
5
+ --grid-cell-progress-track-color : #eee;
6
+ }
@@ -263,12 +263,12 @@ const DefaultConfig = {
263
263
  useVdomWorker: true,
264
264
  /**
265
265
  * buildScripts/injectPackageVersion.mjs will update this value
266
- * @default '8.28.1'
266
+ * @default '8.30.0'
267
267
  * @memberOf! module:Neo
268
268
  * @name config.version
269
269
  * @type String
270
270
  */
271
- version: '8.28.1'
271
+ version: '8.30.0'
272
272
  };
273
273
 
274
274
  Object.assign(DefaultConfig, {
@@ -451,7 +451,7 @@ class Component extends Base {
451
451
  get parent() {
452
452
  let me = this;
453
453
 
454
- return me.parentComponent || me.parentId === 'document.body' ? null : Neo.getComponent(me.parentId)
454
+ return me.parentComponent || (me.parentId === 'document.body' ? null : Neo.getComponent(me.parentId))
455
455
  }
456
456
 
457
457
  /**
@@ -37,12 +37,25 @@ class Progress extends Base {
37
37
  * @member {Object} _vdom
38
38
  */
39
39
  _vdom:
40
- {tag: 'div', cls: ['neo-progress'], cn: [
40
+ {tag: 'div', cls: ['neo-progress-wrapper'], cn: [
41
41
  {tag: 'label'},
42
42
  {tag: 'progress'}
43
43
  ]}
44
44
  }
45
45
 
46
+ /**
47
+ * @member {Object} label
48
+ */
49
+ get label() {
50
+ return this.vdom.cn[0]
51
+ }
52
+ /**
53
+ * @member {Object} progress
54
+ */
55
+ get progress() {
56
+ return this.vdom.cn[1]
57
+ }
58
+
46
59
  /**
47
60
  * Triggered after the id config got changed
48
61
  * @param {String} value
@@ -52,7 +65,7 @@ class Progress extends Base {
52
65
  afterSetId(value, oldValue) {
53
66
  super.afterSetId(value, oldValue);
54
67
 
55
- this.vdom.cn[0].for = value;
68
+ this.label.for = value;
56
69
  this.update()
57
70
  }
58
71
 
@@ -63,7 +76,15 @@ class Progress extends Base {
63
76
  * @protected
64
77
  */
65
78
  afterSetLabelText(value, oldValue) {
66
- this.vdom.cn[0].html = value;
79
+ let {label} = this;
80
+
81
+ if (!value) {
82
+ label.removeDom = true
83
+ } else {
84
+ delete label.removeDom
85
+ }
86
+
87
+ label.html = value;
67
88
  this.update()
68
89
  }
69
90
 
@@ -74,7 +95,7 @@ class Progress extends Base {
74
95
  * @protected
75
96
  */
76
97
  afterSetMax(value, oldValue) {
77
- this.vdom.cn[1].max = value;
98
+ this.progress.max = value;
78
99
  this.update()
79
100
  }
80
101
 
@@ -85,7 +106,7 @@ class Progress extends Base {
85
106
  * @protected
86
107
  */
87
108
  afterSetValue(value, oldValue) {
88
- this.vdom.cn[1].value = value;
109
+ this.progress.value = value;
89
110
  this.update()
90
111
  }
91
112
 
@@ -67,6 +67,11 @@ class Observable extends Base {
67
67
  delete name.once
68
68
  }
69
69
 
70
+ if (name.hasOwnProperty('order')) {
71
+ order = name.order;
72
+ delete name.order
73
+ }
74
+
70
75
  if (name.hasOwnProperty('scope')) {
71
76
  scope = name.scope;
72
77
  delete name.scope
@@ -74,9 +79,9 @@ class Observable extends Base {
74
79
 
75
80
  Object.entries(name).forEach(([key, value]) => {
76
81
  if (Neo.isObject(value)) {
77
- me.addListener(key, {delay, once, scope, ...value})
82
+ me.addListener(key, {delay, once, order, scope, ...value})
78
83
  } else {
79
- me.addListener(key, {delay, fn: value, once, scope})
84
+ me.addListener(key, {delay, fn: value, once, order, scope})
80
85
  }
81
86
  })
82
87
  } else if (optsType === 'object') {
@@ -115,9 +115,7 @@ class SortZone extends BaseSortZone {
115
115
  */
116
116
  moveTo(fromIndex, toIndex) {
117
117
  super.moveTo(fromIndex, toIndex);
118
-
119
- // It is crucial to use _columns to not get a shallow copy
120
- NeoArray.move(this.owner.parent._columns, fromIndex, toIndex);
118
+ this.owner.parent.columns.move(fromIndex, toIndex)
121
119
  }
122
120
 
123
121
  /**
@@ -1,9 +1,11 @@
1
1
  import BaseContainer from '../container/Base.mjs';
2
2
  import ClassSystemUtil from '../util/ClassSystem.mjs';
3
+ import Collection from '../collection/Base.mjs';
3
4
  import GridView from './View.mjs';
4
5
  import ScrollManager from './ScrollManager.mjs';
5
6
  import Store from '../data/Store.mjs';
6
7
  import VerticalScrollbar from './VerticalScrollbar.mjs';
8
+ import * as column from './column/_export.mjs';
7
9
  import * as header from './header/_export.mjs';
8
10
 
9
11
  /**
@@ -11,6 +13,18 @@ import * as header from './header/_export.mjs';
11
13
  * @extends Neo.container.Base
12
14
  */
13
15
  class GridContainer extends BaseContainer {
16
+ /**
17
+ * @member {Object} columnTypes
18
+ * @protected
19
+ * @static
20
+ */
21
+ static columnTypes = {
22
+ animatedChange: column.AnimatedChange,
23
+ column : column.Base,
24
+ component : column.Component,
25
+ index : column.Index,
26
+ progress : column.Progress
27
+ }
14
28
  /**
15
29
  * @member {Object} delayable
16
30
  * @protected
@@ -182,7 +196,7 @@ class GridContainer extends BaseContainer {
182
196
 
183
197
  me.vdom.id = me.getWrapperId();
184
198
 
185
- me.createColumns(me.columns);
199
+ me._columns = me.createColumns(me.columns);
186
200
 
187
201
  me.addDomListeners({
188
202
  resize: me.onResize,
@@ -235,19 +249,15 @@ class GridContainer extends BaseContainer {
235
249
 
236
250
  /**
237
251
  * Triggered after the columns config got changed
238
- * @param {Object[]|null} value
239
- * @param {Object[]|null} oldValue
252
+ * @param {Neo.collection.Base|null} value
253
+ * @param {Object[]|Neo.collection.Base|null} oldValue
240
254
  * @protected
241
255
  */
242
256
  async afterSetColumns(value, oldValue) {
243
- if (oldValue?.length > 0) {
244
- let me = this,
245
- {headerToolbar} = me;
257
+ if (oldValue?.getCount?.() > 0) {
258
+ let me = this;
246
259
 
247
- if (headerToolbar) {
248
- headerToolbar.items = value;
249
- headerToolbar.createItems()
250
- }
260
+ me.headerToolbar?.createItems()
251
261
 
252
262
  await me.timeout(50);
253
263
 
@@ -413,41 +423,57 @@ class GridContainer extends BaseContainer {
413
423
  createColumns(columns) {
414
424
  let me = this,
415
425
  {columnDefaults} = me,
426
+ headerButtons = [],
416
427
  sorters = me.store?.sorters,
417
- renderer;
418
-
419
- if (!columns || !columns.length) {
420
- Neo.logError('Attempting to create a grid.Container without defined columns', me.id);
421
- }
428
+ columnClass, renderer;
422
429
 
423
- columns.forEach(column => {
430
+ columns?.forEach((column, index) => {
424
431
  renderer = column.renderer;
425
432
 
426
433
  columnDefaults && Neo.assignDefaults(column, columnDefaults);
427
434
 
428
- if (column.dock && !column.width) {
429
- Neo.logError('Attempting to create a docked column without a defined width', column, me.id);
430
- }
431
-
432
435
  if (renderer && Neo.isString(renderer) && me[renderer]) {
433
436
  column.renderer = me[renderer]
434
437
  }
435
438
 
436
- if (sorters?.[0]) {
437
- if (column.dataField === sorters[0].property) {
438
- column.isSorted = sorters[0].direction
439
- }
439
+ if (sorters?.[0] && column.dataField === sorters[0].property) {
440
+ column.isSorted = sorters[0].direction
440
441
  }
441
442
 
442
443
  column.listeners = {
443
444
  sort : me.onSortColumn,
444
445
  scope: me
446
+ };
447
+
448
+ headerButtons.push(column);
449
+
450
+ if (column.component && !column.type) {
451
+ column.type = 'component'
445
452
  }
453
+
454
+ columnClass = me.constructor.columnTypes[column.type || 'column'];
455
+ delete column.type;
456
+
457
+ columns[index] = Neo.create(columnClass, {
458
+ parent : me,
459
+ windowId: me.windowId,
460
+ ...column
461
+ })
446
462
  });
447
463
 
448
- me.items[0].items = columns;
464
+ me.items[0].items = headerButtons;
449
465
 
450
- return columns
466
+ if (Neo.typeOf(me._columns) === 'NeoInstance') {
467
+ me._columns.clear();
468
+ me._columns.add(columns);
469
+
470
+ return me._columns
471
+ }
472
+
473
+ return Neo.create(Collection, {
474
+ keyProperty: 'dataField',
475
+ items : columns
476
+ })
451
477
  }
452
478
 
453
479
  /**
@@ -151,7 +151,7 @@ class ScrollManager extends Base {
151
151
 
152
152
  me.scrollTimeoutId = setTimeout(() => {
153
153
  view.isScrolling = false
154
- }, 30);
154
+ }, 100);
155
155
 
156
156
  view.set({isScrolling: true, scrollTop});
157
157
 
package/src/grid/View.mjs CHANGED
@@ -388,7 +388,7 @@ class GridView extends Component {
388
388
  fieldValue = ''
389
389
  }
390
390
 
391
- rendererOutput = column.renderer.call(column.rendererScope || gridContainer, {
391
+ rendererOutput = column.renderer.call(column.rendererScope || column, {
392
392
  column,
393
393
  columnIndex,
394
394
  dataField,
@@ -512,7 +512,7 @@ class GridView extends Component {
512
512
  let me = this,
513
513
  {mountedColumns, selectedRows} = me,
514
514
  gridContainer = me.parent,
515
- columns = gridContainer.headerToolbar.items,
515
+ {columns} = gridContainer,
516
516
  id = me.getRowId(record, rowIndex),
517
517
  rowCls = me.getRowClass(record, rowIndex),
518
518
  config, column, columnPosition, gridRow, i;
@@ -544,7 +544,7 @@ class GridView extends Component {
544
544
  };
545
545
 
546
546
  for (i=mountedColumns[0]; i <= mountedColumns[1]; i++) {
547
- column = columns[i];
547
+ column = columns.getAt(i);
548
548
  config = me.applyRendererOutput({column, columnIndex: i, record, rowIndex});
549
549
 
550
550
  if (column.dock) {
@@ -661,17 +661,11 @@ class GridView extends Component {
661
661
  * @returns {Object|Number|null}
662
662
  */
663
663
  getColumn(field, returnIndex=false) {
664
- let columns = this.parent.headerToolbar.items,
665
- i = 0,
666
- len = columns.length,
667
- column;
664
+ let {columns} = this.parent,
665
+ column = columns.get(field);
668
666
 
669
- for (; i < len; i++) {
670
- column = columns[i];
671
-
672
- if (column.dataField === field) {
673
- return returnIndex ? i : column
674
- }
667
+ if (column) {
668
+ return returnIndex ? columns.indexOf(column) : column
675
669
  }
676
670
 
677
671
  return null
@@ -856,51 +850,42 @@ class GridView extends Component {
856
850
  }
857
851
 
858
852
  /**
859
- * Gets triggered after changing the value of a record field.
860
- * E.g. myRecord.foo = 'bar';
861
- * @param {Object} opts
862
- * @param {Object[]} opts.fields Each field object contains the keys: name, oldValue, value
863
- * @param {Neo.data.Model} opts.model The model instance of the changed record
864
- * @param {Object} opts.record
853
+ * @param {Object} data
854
+ * @param {Object[]} data.fields Each field object contains the keys: name, oldValue, value
855
+ * @param {Neo.data.Model} data.model The model instance of the changed record
856
+ * @param {Object} data.record
865
857
  */
866
858
  onStoreRecordChange({fields, record}) {
867
- let me = this,
868
- fieldNames = fields.map(field => field.name),
869
- needsUpdate = false,
870
- rowIndex = me.store.indexOf(record),
871
- {selectionModel, vdom} = me,
872
- cellId, cellNode, cellStyle, cellVdom, column, columnIndex;
859
+ let me = this,
860
+ fieldNames = fields.map(field => field.name),
861
+ needsUpdate = false,
862
+ rowIndex = me.store.indexOf(record),
863
+ {selectionModel} = me,
864
+ column, needsCellUpdate;
873
865
 
874
866
  if (fieldNames.includes(me.colspanField)) {
875
867
  me.vdom.cn[rowIndex] = me.createRow({record, rowIndex});
876
868
  me.update()
877
869
  } else {
870
+ for (column of me.parent.columns.items) {
871
+ if (
872
+ column instanceof Neo.grid.column.Component &&
873
+ Neo.typeOf(column.component === 'Function') &&
874
+ !fieldNames.includes(column.dataField)
875
+ ) {
876
+ needsCellUpdate = me.updateCellNode(record, column.dataField);
877
+ needsUpdate = needsUpdate || needsCellUpdate
878
+ }
879
+ }
880
+
878
881
  fields.forEach(field => {
879
882
  if (field.name === me.selectedRecordField) {
880
883
  if (selectionModel.ntype === 'selection-grid-rowmodel') {
881
884
  selectionModel[field.value ? 'select' : 'deselect'](me.getRowId(record))
882
885
  }
883
886
  } else {
884
- cellId = me.getCellId(record, field.name);
885
- cellNode = VDomUtil.find(vdom, cellId);
886
-
887
- // The vdom might not exist yet => nothing to do in this case
888
- if (cellNode?.vdom) {
889
- cellStyle = cellNode.vdom.style;
890
- column = me.getColumn(field.name);
891
- columnIndex = cellNode.index;
892
- cellVdom = me.applyRendererOutput({cellId, column, columnIndex, record, rowIndex});
893
- needsUpdate = true;
894
-
895
- // The cell-positioning logic happens outside applyRendererOutput()
896
- // We need to preserve these styles
897
- Object.assign(cellVdom.style, {
898
- left : cellStyle.left,
899
- width: cellStyle.width
900
- });
901
-
902
- cellNode.parentNode.cn[columnIndex] = cellVdom
903
- }
887
+ needsCellUpdate = me.updateCellNode(record, field.name);
888
+ needsUpdate = needsUpdate || needsCellUpdate
904
889
  }
905
890
  })
906
891
  }
@@ -957,6 +942,41 @@ class GridView extends Component {
957
942
  }
958
943
  }
959
944
 
945
+ /**
946
+ * Update the cell vdom silently
947
+ * @param {Record} record
948
+ * @param {String} dataField
949
+ * @returns {Boolean} true in case the view needs an update
950
+ */
951
+ updateCellNode(record, dataField) {
952
+ let me = this,
953
+ cellId = me.getCellId(record, dataField),
954
+ cellNode = VDomUtil.find(me.vdom, cellId),
955
+ needsUpdate = false,
956
+ rowIndex = me.store.indexOf(record),
957
+ cellStyle, cellVdom, column, columnIndex;
958
+
959
+ // The vdom might not exist yet => nothing to do in this case
960
+ if (cellNode?.vdom) {
961
+ cellStyle = cellNode.vdom.style;
962
+ column = me.getColumn(dataField);
963
+ columnIndex = cellNode.index;
964
+ cellVdom = me.applyRendererOutput({cellId, column, columnIndex, record, rowIndex});
965
+ needsUpdate = true;
966
+
967
+ // The cell-positioning logic happens outside applyRendererOutput()
968
+ // We need to preserve these styles
969
+ Object.assign(cellVdom.style, {
970
+ left : cellStyle.left,
971
+ width: cellStyle.width
972
+ });
973
+
974
+ cellNode.parentNode.cn[columnIndex] = cellVdom
975
+ }
976
+
977
+ return needsUpdate
978
+ }
979
+
960
980
  /**
961
981
  *
962
982
  */
@@ -1013,7 +1033,7 @@ class GridView extends Component {
1013
1033
  endIndex = Math.min(countRecords, endIndex + bufferRowRange);
1014
1034
 
1015
1035
  me.mountedRows[0] = startIndex; // update the array inline
1016
- me.mountedRows[1] = endIndex;
1036
+ me.mountedRows[1] = endIndex
1017
1037
  }
1018
1038
 
1019
1039
  /**
@@ -0,0 +1,78 @@
1
+ import Column from './Base.mjs';
2
+ import NeoArray from '../../util/Array.mjs';
3
+ import VdomUtil from '../../util/VDom.mjs';
4
+
5
+ /**
6
+ * @class Neo.grid.column.AnimatedChange
7
+ * @extends Neo.grid.column.Base
8
+ */
9
+ class AnimatedChange extends Column {
10
+ static config = {
11
+ /**
12
+ * @member {String} className='Neo.grid.column.AnimatedChange'
13
+ * @protected
14
+ */
15
+ className: 'Neo.grid.column.AnimatedChange',
16
+ /**
17
+ * @member {String} animationCls='neo-animated'
18
+ */
19
+ animationCls: 'neo-animated',
20
+ /**
21
+ * @member {String} type='animatedChange'
22
+ * @protected
23
+ */
24
+ type: 'animatedChange'
25
+ }
26
+
27
+ /**
28
+ * @param {Object} config
29
+ */
30
+ construct(config) {
31
+ super.construct(config);
32
+
33
+ let me = this;
34
+
35
+ me.parent.store.on({
36
+ recordChange: me.onRecordChange,
37
+ scope : me
38
+ })
39
+ }
40
+
41
+ /**
42
+ * Override as needed for dynamic record-based animation classes
43
+ * @param {Record} record
44
+ * @returns {String}
45
+ */
46
+ getAnimationCls(record) {
47
+ return this.animationCls
48
+ }
49
+
50
+ /**
51
+ * @param {Object} data
52
+ * @param {Object[]} data.fields Each field object contains the keys: name, oldValue, value
53
+ * @param {Neo.data.Model} data.model The model instance of the changed record
54
+ * @param {Object} data.record
55
+ */
56
+ onRecordChange({fields, record}) {
57
+ let me = this,
58
+ {view} = me.parent,
59
+ cellId, field, node;
60
+
61
+ for (field of fields) {
62
+ if (field.name === me.dataField) {
63
+ cellId = view.getCellId(record, me.dataField);
64
+ node = VdomUtil.find(view.vdom, cellId)?.vdom;
65
+
66
+ NeoArray.add(node.cls, me.getAnimationCls(record));
67
+
68
+ // This will trigger a 2nd view update, after grid.View: onStoreRecordChange()
69
+ // It is crucial to restart the keyframe based animation
70
+ // => The previous update call will remove the last animationCls
71
+ view.update();
72
+ break
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ export default Neo.setupClass(AnimatedChange);
@@ -0,0 +1,81 @@
1
+ import Base from '../../core/Base.mjs';
2
+ import {resolveCallback} from '../../util/Function.mjs';
3
+
4
+ /**
5
+ * @class Neo.grid.column.Base
6
+ * @extends Neo.core.Base
7
+ */
8
+ class Column extends Base {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Neo.grid.column.Base'
12
+ * @protected
13
+ */
14
+ className: 'Neo.grid.column.Base',
15
+ /**
16
+ * @member {String|null} dataField=null
17
+ */
18
+ dataField: null,
19
+ /**
20
+ * @member {Neo.grid.Container|null} parent=null
21
+ */
22
+ parent: null,
23
+ /**
24
+ * @member {Function|String|null} renderer_='cellRenderer'
25
+ */
26
+ renderer_: 'cellRenderer',
27
+ /**
28
+ * Scope to execute the column renderer.
29
+ * Defaults to the column instance
30
+ * @member {Neo.core.Base|null} rendererScope=null
31
+ */
32
+ rendererScope: null,
33
+ /**
34
+ * @member {String} type='column'
35
+ * @protected
36
+ */
37
+ type: 'column',
38
+ /**
39
+ * @member {Number|null} windowId_=null
40
+ */
41
+ windowId_: null
42
+ }
43
+
44
+ /**
45
+ * Triggered after the windowId config got changed
46
+ * @param {Number} value
47
+ * @param {Number|null} oldValue
48
+ * @protected
49
+ */
50
+ afterSetWindowId(value, oldValue) {
51
+ value && Neo.currentWorker.insertThemeFiles(value, this.__proto__)
52
+ }
53
+
54
+ /**
55
+ * Triggered before the renderer config gets changed
56
+ * @param {Function|String|null} value
57
+ * @param {Function|String|null} oldValue
58
+ * @protected
59
+ */
60
+ beforeSetRenderer(value, oldValue) {
61
+ return resolveCallback(value, this).fn
62
+ }
63
+
64
+ /**
65
+ * @param {Object} data
66
+ * @param {Neo.button.Base} data.column
67
+ * @param {Number} data.columnIndex
68
+ * @param {String} data.dataField
69
+ * @param {Neo.grid.Container} data.gridContainer
70
+ * @param {Object} data.record
71
+ * @param {Number} data.rowIndex
72
+ * @param {Neo.data.Store} data.store
73
+ * @param {Number|String} data.value
74
+ * @returns {*}
75
+ */
76
+ cellRenderer(data) {
77
+ return data.value
78
+ }
79
+ }
80
+
81
+ export default Neo.setupClass(Column);
@@ -0,0 +1,122 @@
1
+ import Column from './Base.mjs';
2
+
3
+ /**
4
+ * @class Neo.grid.column.Component
5
+ * @extends Neo.grid.column.Base
6
+ */
7
+ class Component extends Column {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Neo.grid.column.Component'
11
+ * @protected
12
+ */
13
+ className: 'Neo.grid.column.Component',
14
+ /**
15
+ * @member {Function|Object|null} component=null
16
+ */
17
+ component: null,
18
+ /**
19
+ * @member {Object} defaults
20
+ * @protected
21
+ */
22
+ defaults: null,
23
+ /**
24
+ * Components can delegate event listeners (or button handlers) into methods somewhere inside
25
+ * the view controller or component tree hierarchy.
26
+ *
27
+ * In this case, it is helpful to know what the related record is, so we are adding the record
28
+ * to the component as a property. By default, as 'record', but this config can change the property name.
29
+ * @member {String} recordProperty='record'
30
+ */
31
+ recordProperty: 'record',
32
+ /**
33
+ * @member {String} type='component'
34
+ * @protected
35
+ */
36
+ type: 'component'
37
+ }
38
+
39
+ /**
40
+ * @member {Map} map=new Map()
41
+ * @protected
42
+ */
43
+ map = new Map()
44
+
45
+ /**
46
+ * Override as needed inside class extensions
47
+ * @param {Object} config
48
+ * @param {Record} record
49
+ * @returns {Object}
50
+ */
51
+ applyRecordConfigs(config, record) {
52
+ return config
53
+ }
54
+
55
+ /**
56
+ * @param {Object} data
57
+ * @param {Neo.column.Base} data.column
58
+ * @param {Number} data.columnIndex
59
+ * @param {String} data.dataField
60
+ * @param {Neo.grid.Container} data.gridContainer
61
+ * @param {Object} data.record
62
+ * @param {Number} data.rowIndex
63
+ * @param {Neo.data.Store} data.store
64
+ * @param {Number|String} data.value
65
+ * @returns {*}
66
+ */
67
+ cellRenderer(data) {
68
+ let {gridContainer, record, rowIndex} = data,
69
+ {appName, view, windowId} = gridContainer,
70
+ me = this,
71
+ {recordProperty} = me,
72
+ id = me.getComponentId(rowIndex),
73
+ component = me.map.get(id),
74
+ componentConfig = me.component;
75
+
76
+ if (Neo.typeOf(componentConfig) === 'Function') {
77
+ componentConfig = componentConfig(data)
78
+ }
79
+
80
+ componentConfig = me.applyRecordConfigs(componentConfig, record);
81
+
82
+ if (component) {
83
+ delete componentConfig.className;
84
+ delete componentConfig.module;
85
+ delete componentConfig.ntype;
86
+
87
+
88
+ componentConfig[recordProperty] = record;
89
+
90
+ component.set(componentConfig)
91
+ } else {
92
+ component = Neo.create({
93
+ ...me.defaults,
94
+ ...componentConfig,
95
+ appName,
96
+ id,
97
+ parentComponent : view,
98
+ [recordProperty]: record,
99
+ windowId
100
+ });
101
+
102
+ me.map.set(id, component)
103
+ }
104
+
105
+ view.updateDepth = -1;
106
+
107
+ return component.createVdomReference()
108
+ }
109
+
110
+ /**
111
+ * @param {Number} rowIndex
112
+ * @returns {String}
113
+ */
114
+ getComponentId(rowIndex) {
115
+ let me = this,
116
+ {view} = me.parent;
117
+
118
+ return `${me.id}-component-${rowIndex % (view.availableRows + 2 * view.bufferRowRange)}`
119
+ }
120
+ }
121
+
122
+ export default Neo.setupClass(Component);
@@ -0,0 +1,42 @@
1
+ import Column from './Base.mjs';
2
+
3
+ /**
4
+ * @class Neo.grid.column.Index
5
+ * @extends Neo.grid.column.Base
6
+ */
7
+ class Index extends Column {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Neo.grid.column.Index'
11
+ * @protected
12
+ */
13
+ className: 'Neo.grid.column.Index',
14
+ /**
15
+ * @member {String} type='index'
16
+ * @protected
17
+ */
18
+ type: 'index',
19
+ /**
20
+ * @member {Boolean} zeroBased=false
21
+ */
22
+ zeroBased: false
23
+ }
24
+
25
+ /**
26
+ * @param {Object} data
27
+ * @param {Neo.button.Base} data.column
28
+ * @param {Number} data.columnIndex
29
+ * @param {String} data.dataField
30
+ * @param {Neo.grid.Container} data.gridContainer
31
+ * @param {Object} data.record
32
+ * @param {Number} data.rowIndex
33
+ * @param {Neo.data.Store} data.store
34
+ * @param {Number|String} data.value
35
+ * @returns {*}
36
+ */
37
+ cellRenderer({rowIndex}) {
38
+ return rowIndex + (this.zeroBased ? 0 : 1)
39
+ }
40
+ }
41
+
42
+ export default Neo.setupClass(Index);
@@ -0,0 +1,42 @@
1
+ import ComponentColumn from './Component.mjs';
2
+ import ProgressComponent from '../../component/Progress.mjs';
3
+
4
+ /**
5
+ * @class Neo.grid.column.Progress
6
+ * @extends Neo.grid.column.Component
7
+ */
8
+ class Progress extends ComponentColumn {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Neo.grid.column.Progress'
12
+ * @protected
13
+ */
14
+ className: 'Neo.grid.column.Progress',
15
+ /**
16
+ * @member {Object} defaults
17
+ * @protected
18
+ */
19
+ defaults: {
20
+ module: ProgressComponent
21
+ },
22
+ /**
23
+ * @member {String} type='progress'
24
+ * @protected
25
+ */
26
+ type: 'progress'
27
+ }
28
+
29
+ /**
30
+ * @param {Object} config
31
+ * @param {Record} record
32
+ * @returns {Object}
33
+ */
34
+ applyRecordConfigs(config, record) {
35
+ return {
36
+ value: record[this.dataField],
37
+ ...config
38
+ }
39
+ }
40
+ }
41
+
42
+ export default Neo.setupClass(Progress);
@@ -0,0 +1,7 @@
1
+ import AnimatedChange from './AnimatedChange.mjs';
2
+ import Base from './Base.mjs';
3
+ import Component from './Component.mjs';
4
+ import Index from './Index.mjs';
5
+ import Progress from './Progress.mjs';
6
+
7
+ export {AnimatedChange, Base, Component, Index, Progress};
@@ -1,7 +1,6 @@
1
- import BaseButton from '../../button/Base.mjs';
2
- import NeoArray from '../../util/Array.mjs';
3
- import TextField from '../../form/field/Text.mjs';
4
- import {resolveCallback} from '../../util/Function.mjs';
1
+ import BaseButton from '../../button/Base.mjs';
2
+ import NeoArray from '../../util/Array.mjs';
3
+ import TextField from '../../form/field/Text.mjs';
5
4
 
6
5
  /**
7
6
  * @class Neo.grid.header.Button
@@ -72,16 +71,6 @@ class Button extends BaseButton {
72
71
  * @protected
73
72
  */
74
73
  isSorted_: null,
75
- /**
76
- * @member {Function|String|null} renderer_='cellRenderer'
77
- */
78
- renderer_: 'cellRenderer',
79
- /**
80
- * Scope to execute the column renderer.
81
- * Defaults to the matching grid.Container
82
- * @member {Neo.core.Base|null} rendererScope=null
83
- */
84
- rendererScope: null,
85
74
  /**
86
75
  * @member {String} role='columnheader'
87
76
  */
@@ -217,32 +206,6 @@ class Button extends BaseButton {
217
206
  return this.beforeSetEnumValue(value, oldValue, 'cellAlign', 'cellAlignValues')
218
207
  }
219
208
 
220
- /**
221
- * Triggered before the renderer config gets changed
222
- * @param {Function|String|null} value
223
- * @param {Function|String|null} oldValue
224
- * @protected
225
- */
226
- beforeSetRenderer(value, oldValue) {
227
- return resolveCallback(value, this).fn
228
- }
229
-
230
- /**
231
- * @param {Object} data
232
- * @param {Neo.button.Base} data.column
233
- * @param {Number} data.columnIndex
234
- * @param {String} data.dataField
235
- * @param {Neo.grid.Container} data.gridContainer
236
- * @param {Object} data.record
237
- * @param {Number} data.rowIndex
238
- * @param {Neo.data.Store} data.store
239
- * @param {Number|String} data.value
240
- * @returns {*}
241
- */
242
- cellRenderer(data) {
243
- return data.value
244
- }
245
-
246
209
  /**
247
210
  *
248
211
  */
@@ -20,7 +20,7 @@ class BaseModel extends Model {
20
20
  * @member {String[]} dataFields
21
21
  */
22
22
  get dataFields() {
23
- return this.view.parent.columns.map(column => column.dataField)
23
+ return this.view.parent.columns.items.map(column => column.dataField)
24
24
  }
25
25
 
26
26
  /**
@@ -250,7 +250,7 @@ class Helper extends Base {
250
250
  return deltas
251
251
  }
252
252
 
253
- if (vnodeId !== oldVnodeId) {
253
+ if (vnodeId !== oldVnodeId && vnode.componentId !== oldVnode.componentId) {
254
254
  throw new Error(`createDeltas() must get called for the same node. ${vnodeId}, ${oldVnodeId}`);
255
255
  }
256
256