neo.mjs 8.0.1 → 8.1.1

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 (50) 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/covid/neo-config.json +6 -5
  6. package/package.json +5 -5
  7. package/resources/scss/src/examples/grid/covid/GridContainer.scss +1 -1
  8. package/resources/scss/src/grid/Container.scss +13 -10
  9. package/resources/scss/src/grid/View.scss +45 -19
  10. package/resources/scss/src/grid/header/Button.scss +2 -4
  11. package/resources/scss/src/grid/header/Toolbar.scss +2 -3
  12. package/src/DefaultConfig.mjs +2 -2
  13. package/src/Xhr.mjs +1 -1
  14. package/src/button/Base.mjs +2 -2
  15. package/src/collection/Base.mjs +5 -5
  16. package/src/component/Base.mjs +3 -3
  17. package/src/container/Base.mjs +2 -2
  18. package/src/controller/Base.mjs +3 -3
  19. package/src/dialog/Base.mjs +2 -2
  20. package/src/form/field/Base.mjs +2 -2
  21. package/src/form/field/CheckBox.mjs +2 -2
  22. package/src/form/field/FileUpload.mjs +4 -4
  23. package/src/form/field/Hidden.mjs +2 -2
  24. package/src/form/field/Text.mjs +2 -2
  25. package/src/grid/Container.mjs +156 -62
  26. package/src/grid/View.mjs +396 -74
  27. package/src/grid/header/Button.mjs +6 -2
  28. package/src/grid/header/Toolbar.mjs +48 -4
  29. package/src/layout/Base.mjs +3 -3
  30. package/src/list/Base.mjs +2 -2
  31. package/src/list/Circle.mjs +2 -2
  32. package/src/list/Color.mjs +2 -2
  33. package/src/list/Component.mjs +2 -2
  34. package/src/manager/Base.mjs +3 -3
  35. package/src/manager/Component.mjs +20 -11
  36. package/src/manager/DomEvent.mjs +5 -6
  37. package/src/manager/Instance.mjs +4 -4
  38. package/src/manager/Task.mjs +2 -2
  39. package/src/manager/Toast.mjs +3 -3
  40. package/src/plugin/Base.mjs +3 -3
  41. package/src/plugin/Popover.mjs +3 -3
  42. package/src/plugin/PrefixField.mjs +2 -2
  43. package/src/plugin/Resizable.mjs +2 -2
  44. package/src/plugin/Responsive.mjs +2 -2
  45. package/src/selection/Model.mjs +12 -1
  46. package/src/toolbar/Base.mjs +2 -2
  47. package/src/toolbar/Breadcrumb.mjs +1 -1
  48. package/src/tooltip/Base.mjs +2 -2
  49. package/src/worker/Base.mjs +3 -3
  50. package/src/grid/README.md +0 -3
package/src/grid/View.mjs CHANGED
@@ -6,7 +6,7 @@ import VDomUtil from '../util/VDom.mjs';
6
6
  * @class Neo.grid.View
7
7
  * @extends Neo.component.Base
8
8
  */
9
- class View extends Component {
9
+ class GridView extends Component {
10
10
  static config = {
11
11
  /**
12
12
  * @member {String} className='Neo.grid.View'
@@ -18,10 +18,32 @@ class View extends Component {
18
18
  * @protected
19
19
  */
20
20
  ntype: 'grid-view',
21
+ /**
22
+ * Internal flag. Gets calculated when mounting the grid.Container
23
+ * @member {Number} availableHeight_=0
24
+ */
25
+ availableHeight_: 0,
26
+ /**
27
+ * Internal flag. Gets calculated when changing the availableHeight config
28
+ * @member {Number} availableRows_=0
29
+ */
30
+ availableRows_: 0,
31
+ /**
32
+ * Internal flag. Gets calculated after mounting grid.View rows
33
+ * @member {Number} availableWidth_=0
34
+ */
35
+ availableWidth_: 0,
21
36
  /**
22
37
  * @member {String[]} baseCls=['neo-grid-view']
38
+ * @protected
23
39
  */
24
40
  baseCls: ['neo-grid-view'],
41
+ /**
42
+ * The amount of rows to paint before the first & after the last visible row,
43
+ * to enhance the scrolling performance
44
+ * @member {Number} bufferRowRange_=3
45
+ */
46
+ bufferRowRange_: 3,
25
47
  /**
26
48
  * Define which model field contains the value of colspan definitions
27
49
  * @member {String} colspanField='colspan'
@@ -32,29 +54,85 @@ class View extends Component {
32
54
  * @protected
33
55
  */
34
56
  containerId: null,
57
+ /**
58
+ * @member {Object[]} columnPositions_=[]
59
+ * @protected
60
+ */
61
+ columnPositions_: [],
62
+ /**
63
+ * @member {Boolean} isScrolling_=false
64
+ */
65
+ isScrolling_: false,
35
66
  /**
36
67
  * @member {Object} recordVnodeMap={}
37
68
  */
38
69
  recordVnodeMap: {},
70
+ /**
71
+ * @member {String} role='rowgroup'
72
+ */
73
+ role: 'rowgroup',
74
+ /**
75
+ * Number in px
76
+ * @member {Number} rowHeight_=0
77
+ */
78
+ rowHeight_: 0,
79
+ /**
80
+ * @member {Object} scrollPosition_={x:0,y:0}
81
+ */
82
+ scrollPosition_: {x: 0, y: 0},
39
83
  /**
40
84
  * @member {String} selectedRecordField='annotations.selected'
41
85
  */
42
86
  selectedRecordField: 'annotations.selected',
43
87
  /**
44
- * @member {Neo.data.Store|null} store=null
88
+ * @member {Number} startIndex_=0
89
+ */
90
+ startIndex_: 0,
91
+ /**
92
+ * @member {Neo.data.Store|null} store_=null
45
93
  */
46
- store: null,
94
+ store_: null,
47
95
  /**
48
96
  * @member {Boolean} useRowRecordIds=true
49
97
  */
50
- useRowRecordIds: true
98
+ useRowRecordIds: true,
99
+ /**
100
+ * Stores the indexes of the first & last painted columns
101
+ * @member {Array} visibleColumns_=[0,0]
102
+ * @protected
103
+ */
104
+ visibleColumns_: [0, 0],
105
+ /**
106
+ * @member {String[]} wrapperCls=[]
107
+ */
108
+ wrapperCls: ['neo-grid-view-wrapper'],
109
+ /**
110
+ * @member {Object} _vdom
111
+ */
112
+ _vdom:
113
+ {cn: [
114
+ {cn: []},
115
+ {cls: 'neo-grid-scrollbar'}
116
+ ]}
117
+ }
118
+
119
+ /**
120
+ * @member {Number|null}} scrollTimeoutId=null
121
+ */
122
+ scrollTimeoutId = null
123
+
124
+ /**
125
+ * @member {Neo.grid.Container|null} gridContainer
126
+ */
127
+ get gridContainer() {
128
+ return Neo.getComponent(this.containerId)
51
129
  }
52
130
 
53
131
  /**
54
132
  * @member {String[]} selectedRows
55
133
  */
56
134
  get selectedRows() {
57
- let gridContainer = this.parent;
135
+ let {gridContainer} = this;
58
136
 
59
137
  if (gridContainer.selectionModel.ntype === 'selection-grid-rowmodel') {
60
138
  return gridContainer.selectionModel.items
@@ -72,6 +150,9 @@ class View extends Component {
72
150
  let me = this;
73
151
 
74
152
  me.addDomListeners([{
153
+ scroll: me.onScroll,
154
+ scope : me
155
+ }, {
75
156
  click : me.onCellClick,
76
157
  dblclick: me.onCellDoubleClick,
77
158
  delegate: '.neo-grid-cell',
@@ -84,6 +165,175 @@ class View extends Component {
84
165
  }])
85
166
  }
86
167
 
168
+ /**
169
+ * Triggered after the availableHeight config got changed
170
+ * @param {Number} value
171
+ * @param {Number} oldValue
172
+ * @protected
173
+ */
174
+ afterSetAvailableHeight(value, oldValue) {
175
+ if (value > 0) {
176
+ this.availableRows = Math.ceil(value / this.rowHeight) + 1
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Triggered after the availableRows config got changed
182
+ * @param {Number} value
183
+ * @param {Number} oldValue
184
+ * @protected
185
+ */
186
+ afterSetAvailableRows(value, oldValue) {
187
+ if (value > 0 && this.store.getCount() > 0) {
188
+ this.createViewData()
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Triggered after the availableWidth config got changed
194
+ * @param {Number} value
195
+ * @param {Number} oldValue
196
+ * @protected
197
+ */
198
+ afterSetAvailableWidth(value, oldValue) {
199
+ if (value > 0) {
200
+ let me = this;
201
+
202
+ me.vdom.width = value + 'px';
203
+ me.vdom.cn[1].width = value + 'px';
204
+ me.update()
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Triggered after the bufferRowRange config got changed
210
+ * @param {Number} value
211
+ * @param {Number} oldValue
212
+ * @protected
213
+ */
214
+ afterSetBufferRowRange(value, oldValue) {
215
+ oldValue !== undefined && this.createViewData()
216
+ }
217
+
218
+ /**
219
+ * Triggered after the columnPositions config got changed
220
+ * @param {Object[]} value
221
+ * @param {Object[]} oldValue
222
+ * @protected
223
+ */
224
+ 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
+ if (me.store.getCount() > 0) {
232
+ me.createViewData()
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Triggered after the id config got changed
239
+ * @param {String} value
240
+ * @param {String} oldValue
241
+ * @protected
242
+ */
243
+ afterSetId(value, oldValue) {
244
+ this.vdom.id = value + '__wrapper';
245
+
246
+ // silent vdom update, the super call will trigger the engine
247
+ super.afterSetId(value, oldValue);
248
+ }
249
+
250
+ /**
251
+ * Triggered after the isScrolling config got changed
252
+ * @param {Number} value
253
+ * @param {Number} oldValue
254
+ * @protected
255
+ */
256
+ afterSetIsScrolling(value, oldValue) {
257
+ this.toggleCls('neo-is-scrolling', value)
258
+ }
259
+
260
+ /**
261
+ * Triggered after the rowHeight config got changed
262
+ * @param {Number} value
263
+ * @param {Number} oldValue
264
+ * @protected
265
+ */
266
+ afterSetRowHeight(value, oldValue) {
267
+ value > 0 && this.updateScrollHeight()
268
+ }
269
+
270
+ /**
271
+ * Triggered after the scrollPosition config got changed
272
+ * @param {Object} value
273
+ * @param {Object} oldValue
274
+ * @protected
275
+ */
276
+ afterSetScrollPosition(value, oldValue) {
277
+ let me = this,
278
+ {bufferRowRange} = me,
279
+ newStartIndex;
280
+
281
+ if (value.x !== oldValue?.x && me.columnPositions.length > 0) {
282
+ me.updateVisibleColumns()
283
+ }
284
+
285
+ if (value.y !== oldValue?.y) {
286
+ newStartIndex = Math.floor(value.y / me.rowHeight);
287
+
288
+ if (newStartIndex < bufferRowRange) {
289
+ me.startIndex = 0
290
+ } else if (Math.abs(me.startIndex - newStartIndex) >= bufferRowRange) {
291
+ me.startIndex = newStartIndex
292
+ }
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Triggered after the startIndex config got changed
298
+ * @param {Number} value
299
+ * @param {Number} oldValue
300
+ * @protected
301
+ */
302
+ afterSetStartIndex(value, oldValue) {
303
+ oldValue !== undefined && this.createViewData()
304
+ }
305
+
306
+ /**
307
+ * Triggered after the store config got changed
308
+ * @param {Neo.data.Store|null} value
309
+ * @param {Neo.data.Store|null} oldValue
310
+ * @protected
311
+ */
312
+ afterSetStore(value, oldValue) {
313
+ if (value) {
314
+ let me = this;
315
+
316
+ value.on({
317
+ load : me.updateScrollHeight,
318
+ scope: me
319
+ });
320
+
321
+ value.getCount() > 0 && me.updateScrollHeight()
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Triggered after the visibleColumns config got changed
327
+ * @param {Number} value
328
+ * @param {Number} oldValue
329
+ * @protected
330
+ */
331
+ afterSetVisibleColumns(value, oldValue) {
332
+ if (oldValue !== undefined) {
333
+ this.createViewData()
334
+ }
335
+ }
336
+
87
337
  /**
88
338
  * @param {Object} data
89
339
  * @param {String} [data.cellId]
@@ -148,10 +398,12 @@ class View extends Component {
148
398
  }
149
399
 
150
400
  cellConfig = {
151
- id : cellId,
152
- cls : cellCls,
153
- style : rendererOutput.style || {},
154
- tabIndex: '-1'
401
+ 'aria-colindex': index + 1, // 1 based
402
+ id : cellId,
403
+ cls : cellCls,
404
+ role : 'gridcell',
405
+ style : rendererOutput.style || {},
406
+ tabIndex : '-1'
155
407
  };
156
408
 
157
409
  if (column.width) {
@@ -182,21 +434,19 @@ class View extends Component {
182
434
  rowIndex = this.store.indexOf(record)
183
435
  }
184
436
 
185
- let me = this,
186
- gridContainer = me.parent,
187
- colspan = record[me.colspanField],
188
- colspanKeys = colspan && Object.keys(colspan),
189
- columns = gridContainer.items[0].items,
190
- colCount = columns.length,
191
- dockLeftMargin = 0,
192
- dockRightMargin = 0,
193
- id = me.getRowId(record, rowIndex),
194
- {selectedRows} = me,
195
- trCls = me.getTrClass(record, rowIndex),
196
- config, column, columnIndex, gridRow, i;
437
+ let me = this,
438
+ {gridContainer, selectedRows, visibleColumns} = me,
439
+ columns = gridContainer.items[0].items,
440
+ id = me.getRowId(record, rowIndex),
441
+ trCls = me.getTrClass(record, rowIndex),
442
+ config, column, gridRow, i;
197
443
 
198
444
  me.recordVnodeMap[id] = rowIndex;
199
445
 
446
+ if (rowIndex % 2 !== 0) {
447
+ trCls.push('neo-even')
448
+ }
449
+
200
450
  if (selectedRows && Neo.ns(me.selectedRecordField, false, record)) {
201
451
  NeoArray.add(selectedRows, id)
202
452
  }
@@ -204,77 +454,66 @@ class View extends Component {
204
454
  if (selectedRows?.includes(id)) {
205
455
  trCls.push('neo-selected');
206
456
 
207
- me.parent.fire('select', {
457
+ gridContainer.fire('select', {
208
458
  record
209
459
  })
210
460
  }
211
461
 
212
462
  gridRow = {
213
463
  id,
214
- cls : trCls,
215
- cn : [],
216
- tabIndex: '-1'
464
+ 'aria-rowindex': rowIndex + 2, // header row => 1, first body row => 2
465
+ cls : trCls,
466
+ cn : [],
467
+ role : 'row',
468
+ tabIndex : '-1',
469
+
470
+ style: {
471
+ height : me.rowHeight + 'px',
472
+ transform: `translate(0px, ${rowIndex * me.rowHeight}px)`
473
+ }
217
474
  };
218
475
 
219
- for (i=0; i < colCount; i++) {
476
+ for (i=visibleColumns[0]; i <= visibleColumns[1]; i++) {
220
477
  column = columns[i];
221
478
  config = me.applyRendererOutput({column, gridContainer, index: rowIndex, record});
222
479
 
223
480
  if (column.dock) {
224
- config.cls = ['neo-locked', ...config.cls || []];
225
-
226
- if (column.dock === 'left') {
227
- config.style.left = dockLeftMargin + 'px';
228
- dockLeftMargin += (column.width + 1) // todo: borders fix
229
- }
230
- }
231
-
232
- if (column.flex) {
233
- config.style.width = '100%'
481
+ config.cls = ['neo-locked', ...config.cls || []]
234
482
  }
235
483
 
236
- gridRow.cn.push(config);
237
-
238
- if (colspanKeys?.includes(column.dataField)) {
239
- i += (colspan[column.dataField] - 1)
240
- }
241
- }
242
-
243
- for (i=0; i < colCount; i++) {
244
- columnIndex = colCount - i -1;
245
- column = columns[columnIndex];
246
-
247
- if (column.dock === 'right') {
248
- gridRow.cn[columnIndex].style.right = dockRightMargin + 'px';
249
- dockRightMargin += (column.width + 1) // todo: borders fix
484
+ config.style = {
485
+ ...config.style,
486
+ left : me.columnPositions[i].x + 'px',
487
+ width: me.columnPositions[i].width + 'px'
250
488
  }
251
489
 
252
- if (colspanKeys?.includes(column.dataField)) {
253
- i += (colspan[column.dataField] - 1)
254
- }
490
+ gridRow.cn.push(config)
255
491
  }
256
492
 
257
- // the dock margins are the same for each row
258
- rowIndex === 0 && Object.assign(gridContainer, {dockLeftMargin, dockRightMargin});
259
-
260
493
  return gridRow
261
494
  }
262
495
 
263
496
  /**
264
- * @param {Object[]} inputData
497
+ *
265
498
  */
266
- createViewData(inputData) {
267
- let me = this,
268
- amountRows = inputData.length,
269
- i = 0,
270
- rows = [],
271
- {selectedRows} = me;
499
+ createViewData() {
500
+ let me = this,
501
+ {bufferRowRange, selectedRows, startIndex} = me,
502
+ rows = [],
503
+ endIndex, i;
504
+
505
+ if (me.availableRows < 1 || me.columnPositions.length < 1) {
506
+ return
507
+ }
272
508
 
273
- for (; i < amountRows; i++) {
274
- rows.push(me.createRow({record: inputData[i], rowIndex: i}))
509
+ endIndex = Math.min(me.store.getCount(), me.availableRows + startIndex + bufferRowRange);
510
+ startIndex = Math.max(0, startIndex - bufferRowRange);
511
+
512
+ for (i=startIndex; i < endIndex; i++) {
513
+ rows.push(me.createRow({record: me.store.items[i], rowIndex: i}))
275
514
  }
276
515
 
277
- me.vdom.cn = rows;
516
+ me.getVdomRoot().cn = rows;
278
517
 
279
518
  me.promiseUpdate().then(() => {
280
519
  if (selectedRows?.length > 0) {
@@ -302,7 +541,7 @@ class View extends Component {
302
541
  dataField = me.getCellDataField(id),
303
542
  record = me.getRecord(id);
304
543
 
305
- me.parent.fire(eventName, {data, dataField, record, view: me})
544
+ me.gridContainer.fire(eventName, {data, dataField, record, view: me})
306
545
  }
307
546
 
308
547
  /**
@@ -314,7 +553,7 @@ class View extends Component {
314
553
  id = data.currentTarget,
315
554
  record = me.getRecord(id);
316
555
 
317
- me.parent.fire(eventName, {data, record, view: me})
556
+ me.gridContainer.fire(eventName, {data, record, view: me})
318
557
  }
319
558
 
320
559
  /**
@@ -341,10 +580,10 @@ class View extends Component {
341
580
  * @returns {Object|Number|null}
342
581
  */
343
582
  getColumn(field, returnIndex=false) {
344
- let container = this.parent,
345
- columns = container.headerToolbar.items,
346
- i = 0,
347
- len = columns.length,
583
+ let {gridContainer} = this,
584
+ columns = gridContainer.headerToolbar.items,
585
+ i = 0,
586
+ len = columns.length,
348
587
  column;
349
588
 
350
589
  for (; i < len; i++) {
@@ -420,6 +659,29 @@ class View extends Component {
420
659
  return ['neo-grid-row']
421
660
  }
422
661
 
662
+ /**
663
+ * @override
664
+ * @returns {*}
665
+ */
666
+ getVdomRoot() {
667
+ return this.vdom.cn[0]
668
+ }
669
+
670
+ /**
671
+ * @returns {Object[]} The new vdom items root
672
+ */
673
+ getVdomItemsRoot() {
674
+ return this.vdom.cn[0]
675
+ }
676
+
677
+ /**
678
+ * @override
679
+ * @returns {Neo.vdom.VNode}
680
+ */
681
+ getVnodeRoot() {
682
+ return this.vnode.childNodes[0]
683
+ }
684
+
423
685
  /**
424
686
  * @param {Object} data
425
687
  */
@@ -448,6 +710,25 @@ class View extends Component {
448
710
  this.fireRowEvent(data, 'rowDoubleClick')
449
711
  }
450
712
 
713
+ /**
714
+ * Only triggers for vertical scrolling
715
+ * @param {Object} data
716
+ */
717
+ onScroll(data) {
718
+ let me = this;
719
+
720
+ me.scrollTimeoutId && clearTimeout(me.scrollTimeoutId);
721
+
722
+ me.scrollTimeoutId = setTimeout(() => {
723
+ me.isScrolling = false
724
+ }, 30);
725
+
726
+ me.set({
727
+ isScrolling : true,
728
+ scrollPosition: {x: me.scrollPosition.x, y: data.scrollTop}
729
+ })
730
+ }
731
+
451
732
  /**
452
733
  * Gets triggered after changing the value of a record field.
453
734
  * E.g. myRecord.foo = 'bar';
@@ -460,7 +741,7 @@ class View extends Component {
460
741
  let me = this,
461
742
  fieldNames = fields.map(field => field.name),
462
743
  needsUpdate = false,
463
- gridContainer = me.parent,
744
+ {gridContainer} = me,
464
745
  {selectionModel} = gridContainer,
465
746
  {vdom} = me,
466
747
  cellId, cellNode, column, index, scope;
@@ -494,6 +775,47 @@ class View extends Component {
494
775
 
495
776
  needsUpdate && me.update()
496
777
  }
778
+
779
+ /**
780
+ *
781
+ */
782
+ updateScrollHeight() {
783
+ let me = this,
784
+ countRecords = me.store.getCount(),
785
+ {rowHeight} = me;
786
+
787
+ if (countRecords > 0 && rowHeight > 0) {
788
+ me.vdom.cn[1].height = `${(countRecords + 1) * rowHeight}px`;
789
+ me.update()
790
+ }
791
+ }
792
+
793
+ /**
794
+ *
795
+ */
796
+ updateVisibleColumns() {
797
+ let me = this,
798
+ {x} = me.scrollPosition,
799
+ i = 0,
800
+ len = me.columnPositions.length,
801
+ endIndex = len - 1,
802
+ column, startIndex;
803
+
804
+ for (; i < len; i++) {
805
+ column = me.columnPositions[i];
806
+
807
+ if (x >= column.x && x <= column.x + column.width) {
808
+ startIndex = i
809
+ }
810
+
811
+ if (me.containerWidth + x < column.x) {
812
+ endIndex = i - 1;
813
+ break
814
+ }
815
+ }
816
+
817
+ me.visibleColumns = [startIndex, endIndex]
818
+ }
497
819
  }
498
820
 
499
- export default Neo.setupClass(View);
821
+ export default Neo.setupClass(GridView);
@@ -28,9 +28,9 @@ class Button extends BaseButton {
28
28
  */
29
29
  ntype: 'grid-header-button',
30
30
  /**
31
- * @member {String[]} baseCls=['neo-grid-header-button']
31
+ * @member {String[]} baseCls=['neo-grid-header-button','neo-button']
32
32
  */
33
- baseCls: ['neo-grid-header-button'],
33
+ baseCls: ['neo-grid-header-button', 'neo-button'],
34
34
  /**
35
35
  * Alignment of the matching grid cells. Valid values are left, center, right
36
36
  * @member {String} cellAlign_='left'
@@ -86,6 +86,10 @@ class Button extends BaseButton {
86
86
  * @member {Neo.core.Base|null} rendererScope=null
87
87
  */
88
88
  rendererScope: null,
89
+ /**
90
+ * @member {String} role='columnheader'
91
+ */
92
+ role: 'columnheader',
89
93
  /**
90
94
  * @member {Boolean} showHeaderFilter_=false
91
95
  */