@neovici/cosmoz-omnitable 7.2.1 → 8.0.0-beta.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.
- package/README.md +23 -0
- package/cosmoz-omnitable-column-amount.js +89 -320
- package/cosmoz-omnitable-column-autocomplete.js +36 -47
- package/cosmoz-omnitable-column-boolean.js +107 -209
- package/cosmoz-omnitable-column-date.js +89 -102
- package/cosmoz-omnitable-column-datetime.js +86 -119
- package/cosmoz-omnitable-column-list-data.js +4 -1
- package/cosmoz-omnitable-column-list-horizontal.js +20 -38
- package/cosmoz-omnitable-column-list-mixin.js +133 -140
- package/cosmoz-omnitable-column-list.js +19 -28
- package/cosmoz-omnitable-column-mixin.js +69 -447
- package/cosmoz-omnitable-column-number.js +91 -183
- package/cosmoz-omnitable-column-time.js +77 -162
- package/cosmoz-omnitable-column.js +49 -93
- package/cosmoz-omnitable-group-row.js +1 -5
- package/cosmoz-omnitable-header-row.js +9 -6
- package/cosmoz-omnitable-item-expand.js +0 -3
- package/cosmoz-omnitable-item-row.js +5 -8
- package/cosmoz-omnitable-styles.js +1 -5
- package/cosmoz-omnitable.js +72 -763
- package/lib/cosmoz-omnitable-amount-range-input.js +295 -0
- package/{cosmoz-omnitable-column-date-mixin.js → lib/cosmoz-omnitable-date-input-mixin.js} +4 -26
- package/lib/cosmoz-omnitable-date-range-input.js +81 -0
- package/lib/cosmoz-omnitable-datetime-range-input.js +75 -0
- package/lib/cosmoz-omnitable-number-range-input.js +159 -0
- package/{cosmoz-omnitable-column-range-mixin.js → lib/cosmoz-omnitable-range-input-mixin.js} +45 -123
- package/lib/cosmoz-omnitable-settings.js +7 -4
- package/lib/cosmoz-omnitable-time-range-input.js +130 -0
- package/lib/generic-sorter.js +2 -2
- package/lib/invoke.js +1 -0
- package/lib/memoize.js +54 -0
- package/lib/polymer-haunted-render-mixin.js +19 -0
- package/lib/save-as-csv-action.js +32 -0
- package/lib/save-as-xlsx-action.js +25 -0
- package/lib/use-canvas-width.js +1 -1
- package/lib/use-dom-columns.js +133 -0
- package/lib/use-hash-state.js +59 -0
- package/lib/use-layout.js +1 -1
- package/lib/use-omnitable.js +26 -14
- package/lib/use-processed-items.js +132 -0
- package/lib/use-sort-and-group-options.js +30 -0
- package/lib/utils-amount.js +147 -0
- package/lib/utils-data.js +36 -0
- package/lib/utils-date.js +204 -0
- package/lib/utils-datetime.js +71 -0
- package/lib/utils-number.js +112 -0
- package/lib/utils-time.js +115 -0
- package/package.json +1 -1
- package/lib/use-force-render.js +0 -8
- package/lib/use-render-on-column-updates.js +0 -18
package/cosmoz-omnitable.js
CHANGED
|
@@ -12,7 +12,6 @@ import '@polymer/paper-spinner/paper-spinner-lite';
|
|
|
12
12
|
|
|
13
13
|
import '@neovici/cosmoz-grouped-list';
|
|
14
14
|
import '@neovici/cosmoz-bottom-bar';
|
|
15
|
-
import '@neovici/cosmoz-page-router/cosmoz-page-location';
|
|
16
15
|
|
|
17
16
|
import './cosmoz-omnitable-column';
|
|
18
17
|
import './cosmoz-omnitable-header-row';
|
|
@@ -22,12 +21,7 @@ import './cosmoz-omnitable-group-row';
|
|
|
22
21
|
import './cosmoz-omnitable-columns';
|
|
23
22
|
import styles from './cosmoz-omnitable-styles';
|
|
24
23
|
|
|
25
|
-
import { NullXlsx } from '@neovici/nullxlsx';
|
|
26
24
|
|
|
27
|
-
import { saveAs } from 'file-saver-es';
|
|
28
|
-
|
|
29
|
-
import { timeOut } from '@polymer/polymer/lib/utils/async';
|
|
30
|
-
import { Debouncer } from '@polymer/polymer/lib/utils/debounce';
|
|
31
25
|
import { PolymerElement } from '@polymer/polymer/polymer-element';
|
|
32
26
|
import { html } from '@polymer/polymer/lib/utils/html-tag';
|
|
33
27
|
import { html as litHtml, render } from 'lit-html';
|
|
@@ -37,9 +31,8 @@ import { mixin, hauntedPolymer } from '@neovici/cosmoz-utils';
|
|
|
37
31
|
import { isEmpty } from '@neovici/cosmoz-utils/lib/template.js';
|
|
38
32
|
import { useOmnitable } from './lib/use-omnitable';
|
|
39
33
|
import './lib/cosmoz-omnitable-settings';
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
const PROPERTY_HASH_PARAMS = ['sortOn', 'groupOn', 'descending', 'groupOnDescending'];
|
|
34
|
+
import { saveAsCsvAction } from './lib/save-as-csv-action';
|
|
35
|
+
import { saveAsXlsxAction } from './lib/save-as-xlsx-action';
|
|
43
36
|
|
|
44
37
|
/**
|
|
45
38
|
* @polymer
|
|
@@ -57,16 +50,17 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
57
50
|
${ html([styles]) }
|
|
58
51
|
<div id="layoutStyle"></div>
|
|
59
52
|
|
|
60
|
-
<cosmoz-page-location id="location" route-hash="{{ _routeHash }}"></cosmoz-page-location>
|
|
61
|
-
|
|
62
53
|
<div class="mainContainer">
|
|
63
54
|
<div class="header" id="header">
|
|
64
55
|
<input class="checkbox all" type="checkbox" checked="[[ _allSelected ]]" on-input="_onAllCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
|
|
65
56
|
<cosmoz-omnitable-header-row
|
|
57
|
+
data="[[ data ]]"
|
|
66
58
|
columns="[[ normalizedColumns ]]"
|
|
59
|
+
filters="[[ filters ]]"
|
|
67
60
|
group-on-column="[[ groupOnColumn ]]"
|
|
68
|
-
content="[[ _renderSettings(normalizedSettings, collapsedColumns, settingsId, hasChangedSettings, hasHiddenFilter) ]]"
|
|
69
|
-
|
|
61
|
+
content="[[ _renderSettings(normalizedSettings, collapsedColumns, settingsId, hasChangedSettings, hasHiddenFilter, filters) ]]"
|
|
62
|
+
set-filter-state="[[ setFilterState ]]"
|
|
63
|
+
></cosmoz-omnitable-header-row>
|
|
70
64
|
</div>
|
|
71
65
|
<div class="tableContent" id="tableContent">
|
|
72
66
|
<template is="dom-if" if="[[ !_dataIsValid ]]">
|
|
@@ -101,18 +95,23 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
101
95
|
<cosmoz-grouped-list id="groupedList"
|
|
102
96
|
data="{{ sortedFilteredGroupedItems }}"
|
|
103
97
|
selected-items="{{ selectedItems }}"
|
|
104
|
-
highlighted-items="{{ highlightedItems }}"
|
|
105
98
|
display-empty-groups="[[ displayEmptyGroups ]]"
|
|
106
99
|
compare-items-fn="[[ compareItemsFn ]]"
|
|
107
100
|
>
|
|
108
101
|
<template slot="templates" data-type="item">
|
|
109
102
|
<div class="item-row-wrapper">
|
|
110
|
-
<div selected$="[[ selected ]]" class="itemRow"
|
|
103
|
+
<div selected$="[[ selected ]]" class="itemRow">
|
|
111
104
|
<input class="checkbox" type="checkbox" checked="[[ selected ]]" on-input="_onCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
|
|
112
105
|
<cosmoz-omnitable-item-row columns="[[ normalizedColumns ]]"
|
|
113
|
-
selected="[[ selected ]]" expanded="{{ expanded }}" item="[[ item ]]" group-on-column="[[ groupOnColumn ]]"
|
|
106
|
+
selected="[[ selected ]]" expanded="{{ expanded }}" item="[[ item ]]" group-on-column="[[ groupOnColumn ]]"
|
|
107
|
+
on-item-change="[[ onItemChange ]]">
|
|
114
108
|
</cosmoz-omnitable-item-row>
|
|
115
|
-
<paper-icon-button
|
|
109
|
+
<paper-icon-button
|
|
110
|
+
class="expand"
|
|
111
|
+
hidden="[[ isEmpty(collapsedColumns.length) ]]"
|
|
112
|
+
icon="[[ _getFoldIcon(expanded) ]]"
|
|
113
|
+
on-tap="_toggleItem"
|
|
114
|
+
></paper-icon-button>
|
|
116
115
|
</div>
|
|
117
116
|
<cosmoz-omnitable-item-expand columns="[[ collapsedColumns ]]"
|
|
118
117
|
item="[[item]]" selected="{{ selected }}" expanded$="{{ expanded }}" group-on-column="[[ groupOnColumn ]]"
|
|
@@ -139,18 +138,20 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
139
138
|
<div class="footer-controls">
|
|
140
139
|
<cosmoz-autocomplete
|
|
141
140
|
label="[[ _('Group on', t) ]] [[ _computeSortDirection(groupOnDescending, t) ]]" placeholder="[[ _('No grouping', t) ]]"
|
|
142
|
-
source="[[ _onCompleteValues(columns, 'groupOn', groupOnColumn) ]]" value="[[ groupOnColumn ]]" limit="1" text-property="title"
|
|
141
|
+
source="[[ _onCompleteValues(columns, 'groupOn', groupOnColumn) ]]" value="[[ groupOnColumn ]]" limit="1" text-property="title"
|
|
142
|
+
always-float-label item-height="48" item-limit="8"
|
|
143
143
|
class="footer-control" on-change="[[ _onCompleteChange('groupOn') ]]" default-index="-1" show-single show-selection
|
|
144
144
|
></cosmoz-autocomplete>
|
|
145
145
|
<cosmoz-autocomplete
|
|
146
146
|
label="[[ _('Sort on', t) ]] [[ _computeSortDirection(descending, t) ]]" placeholder="[[ _('No sorting', t) ]]"
|
|
147
|
-
source="[[ _onCompleteValues(columns, 'sortOn', sortOnColumn) ]]" value="[[ sortOnColumn ]]" limit="1" text-property="title"
|
|
147
|
+
source="[[ _onCompleteValues(columns, 'sortOn', sortOnColumn) ]]" value="[[ sortOnColumn ]]" limit="1" text-property="title"
|
|
148
|
+
always-float-label item-height="48" item-limit="8"
|
|
148
149
|
class="footer-control" on-change="[[ _onCompleteChange('sortOn') ]]" default-index="-1" show-single show-selection
|
|
149
150
|
></cosmoz-autocomplete>
|
|
150
151
|
</div>
|
|
151
152
|
<div class="footer-tableStats">
|
|
152
|
-
<span>[[ ngettext('{0} group', '{0} groups',
|
|
153
|
-
<span>[[ _renderRowStats(
|
|
153
|
+
<span>[[ ngettext('{0} group', '{0} groups', groupsCount, t) ]]</span>
|
|
154
|
+
<span>[[ _renderRowStats(numProcessedItems, totalAvailable, t) ]]</span>
|
|
154
155
|
</div>
|
|
155
156
|
<cosmoz-bottom-bar id="bottomBar" class="footer-actionBar" match-parent
|
|
156
157
|
on-action="_onAction" active$="[[ !isEmpty(selectedItems.length) ]]" computed-bar-height="{{ computedBarHeight }}">
|
|
@@ -177,254 +178,104 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
177
178
|
</div>
|
|
178
179
|
|
|
179
180
|
<div id="columns">
|
|
180
|
-
<slot id="columnsSlot"
|
|
181
|
+
<slot id="columnsSlot"></slot>
|
|
181
182
|
</div>
|
|
182
183
|
`;
|
|
183
184
|
template.setAttribute('strip-whitespace', '');
|
|
184
185
|
return template;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
static get is() {
|
|
188
|
-
return 'cosmoz-omnitable';
|
|
189
|
-
}
|
|
190
|
-
|
|
191
188
|
/* eslint-disable-next-line max-lines-per-function */
|
|
192
189
|
static get properties() {
|
|
193
190
|
return {
|
|
194
|
-
|
|
195
191
|
/**
|
|
196
192
|
* Filename when saving as CSV
|
|
197
193
|
*/
|
|
198
|
-
csvFilename: {
|
|
199
|
-
type: String,
|
|
200
|
-
value: 'omnitable.csv'
|
|
201
|
-
},
|
|
194
|
+
csvFilename: { type: String, value: 'omnitable.csv' },
|
|
202
195
|
|
|
203
196
|
/**
|
|
204
197
|
* Filename when saving as XLSX
|
|
205
198
|
*/
|
|
206
|
-
xlsxFilename: {
|
|
207
|
-
type: String,
|
|
208
|
-
value: 'omnitable.xlsx'
|
|
209
|
-
},
|
|
199
|
+
xlsxFilename: { type: String, value: 'omnitable.xlsx' },
|
|
210
200
|
|
|
211
201
|
/**
|
|
212
202
|
* Sheet name when saving as XLSX
|
|
213
203
|
*/
|
|
214
|
-
xlsxSheetname: {
|
|
215
|
-
type: String,
|
|
216
|
-
value: 'Omnitable'
|
|
217
|
-
},
|
|
204
|
+
xlsxSheetname: { type: String, value: 'Omnitable' },
|
|
218
205
|
|
|
219
206
|
/**
|
|
220
207
|
* Array used to list items.
|
|
221
208
|
*/
|
|
222
|
-
data: {
|
|
223
|
-
type: Array
|
|
224
|
-
},
|
|
209
|
+
data: { type: Array },
|
|
225
210
|
|
|
226
211
|
/**
|
|
227
212
|
* This function is used to determine which items are kept selected across data updates
|
|
213
|
+
* TODO: probably broken
|
|
228
214
|
*/
|
|
229
215
|
compareItemsFn: Function,
|
|
230
216
|
|
|
231
217
|
/**
|
|
232
218
|
* True if data is a valid and not empty array.
|
|
233
219
|
*/
|
|
234
|
-
_dataIsValid: {
|
|
235
|
-
type: Boolean,
|
|
236
|
-
value: false,
|
|
237
|
-
computed: '_computeDataValidity(data.*)'
|
|
238
|
-
},
|
|
220
|
+
_dataIsValid: { type: Boolean, value: false, computed: '_computeDataValidity(data.*)' },
|
|
239
221
|
|
|
240
222
|
/**
|
|
241
223
|
* If set to true, then group a row will be displayed for groups that contain no items.
|
|
242
224
|
*/
|
|
243
|
-
displayEmptyGroups: {
|
|
244
|
-
type: Boolean,
|
|
245
|
-
value: false
|
|
246
|
-
},
|
|
225
|
+
displayEmptyGroups: { type: Boolean, value: false },
|
|
247
226
|
|
|
248
227
|
/**
|
|
249
228
|
* Specific columns to enable
|
|
250
229
|
*/
|
|
251
|
-
enabledColumns: {
|
|
252
|
-
type: Array,
|
|
253
|
-
observer: '_debounceUpdateColumns'
|
|
254
|
-
},
|
|
230
|
+
enabledColumns: { type: Array },
|
|
255
231
|
|
|
256
232
|
/**
|
|
257
233
|
* Whether bottom-bar has actions.
|
|
258
234
|
*/
|
|
259
|
-
hasActions: {
|
|
260
|
-
type: Boolean,
|
|
261
|
-
value: false
|
|
262
|
-
},
|
|
235
|
+
hasActions: { type: Boolean, value: false },
|
|
263
236
|
|
|
264
237
|
/**
|
|
265
238
|
* Shows a loading overlay to indicate data will be updated
|
|
266
239
|
*/
|
|
267
|
-
loading: {
|
|
268
|
-
type: Boolean,
|
|
269
|
-
value: false
|
|
270
|
-
},
|
|
240
|
+
loading: { type: Boolean, value: false },
|
|
271
241
|
|
|
272
242
|
/**
|
|
273
243
|
* List of selected rows/items in `data`.
|
|
274
244
|
*/
|
|
275
|
-
selectedItems: {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
},
|
|
279
|
-
|
|
280
|
-
highlightedItems: {
|
|
281
|
-
type: Array,
|
|
282
|
-
notify: true
|
|
283
|
-
},
|
|
284
|
-
|
|
285
|
-
descending: {
|
|
286
|
-
type: Boolean,
|
|
287
|
-
value: false,
|
|
288
|
-
notify: true
|
|
289
|
-
},
|
|
290
|
-
|
|
291
|
-
sortOn: {
|
|
292
|
-
type: String,
|
|
293
|
-
value: '',
|
|
294
|
-
notify: true
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
sortOnColumn: {
|
|
298
|
-
type: Object,
|
|
299
|
-
computed: '_getColumn(sortOn, "name", columns)'
|
|
300
|
-
},
|
|
301
|
-
|
|
302
|
-
groupOnDescending: {
|
|
303
|
-
type: Boolean,
|
|
304
|
-
value: false,
|
|
305
|
-
observer: '_debounceProcessItems'
|
|
306
|
-
},
|
|
307
|
-
/**
|
|
308
|
-
* The column name to group on.
|
|
309
|
-
*/
|
|
310
|
-
groupOn: {
|
|
311
|
-
type: String,
|
|
312
|
-
notify: true,
|
|
313
|
-
value: ''
|
|
314
|
-
},
|
|
245
|
+
selectedItems: { type: Array, notify: true },
|
|
246
|
+
descending: { type: Boolean, value: false, notify: true },
|
|
247
|
+
sortOn: { type: String, value: '', notify: true },
|
|
248
|
+
groupOnDescending: { type: Boolean, value: false },
|
|
315
249
|
|
|
316
250
|
/**
|
|
317
|
-
* The column
|
|
318
|
-
*/
|
|
319
|
-
groupOnColumn: {
|
|
320
|
-
type: Object,
|
|
321
|
-
notify: true,
|
|
322
|
-
observer: '_groupOnColumnChanged',
|
|
323
|
-
computed: '_getColumn(groupOn, "name", columns)'
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Items matching current set filter(s)
|
|
328
|
-
*/
|
|
329
|
-
filteredItems: {
|
|
330
|
-
type: Array,
|
|
331
|
-
value: () => []
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Grouped items structure after filtering.
|
|
251
|
+
* The column name to group on.
|
|
336
252
|
*/
|
|
337
|
-
|
|
338
|
-
type: Array
|
|
339
|
-
},
|
|
253
|
+
groupOn: { type: String, notify: true, value: '' },
|
|
340
254
|
|
|
341
255
|
/**
|
|
342
256
|
* Sorted items structure after filtering and grouping.
|
|
343
257
|
*/
|
|
344
|
-
sortedFilteredGroupedItems: {
|
|
345
|
-
type: Array,
|
|
346
|
-
notify: true
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
_canvasWidth: {
|
|
350
|
-
type: Number,
|
|
351
|
-
value: 0,
|
|
352
|
-
notify: true
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Keep track of width-changes to identify if we go bigger or smaller
|
|
357
|
-
*/
|
|
358
|
-
_previousWidth: {
|
|
359
|
-
type: Number,
|
|
360
|
-
value: 0
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
_groupsCount: {
|
|
364
|
-
type: Number,
|
|
365
|
-
value: 0
|
|
366
|
-
},
|
|
367
|
-
|
|
368
|
-
visible: {
|
|
369
|
-
type: Boolean,
|
|
370
|
-
notify: true,
|
|
371
|
-
readOnly: true,
|
|
372
|
-
value: false,
|
|
373
|
-
observer: 'visibleChanged'
|
|
374
|
-
},
|
|
258
|
+
sortedFilteredGroupedItems: { type: Array, notify: true },
|
|
375
259
|
|
|
376
260
|
/**
|
|
377
261
|
* List of columns definition for this table.
|
|
378
262
|
*/
|
|
379
|
-
columns: {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
},
|
|
384
|
-
|
|
385
|
-
settings: {
|
|
386
|
-
type: Object,
|
|
387
|
-
notify: true
|
|
388
|
-
},
|
|
389
|
-
|
|
390
|
-
_filterIsTooStrict: {
|
|
391
|
-
type: Boolean,
|
|
392
|
-
computed: '_computeFilterIsTooStrict(_dataIsValid, sortedFilteredGroupedItems.length)'
|
|
393
|
-
},
|
|
394
|
-
|
|
395
|
-
hashParam: {
|
|
396
|
-
type: String
|
|
397
|
-
},
|
|
398
|
-
|
|
399
|
-
_routeHash: {
|
|
400
|
-
type: Object
|
|
401
|
-
|
|
402
|
-
},
|
|
403
|
-
_routeHashKeyRule: {
|
|
404
|
-
type: RegExp,
|
|
405
|
-
computed: '_computeRouteHashKeyRule(hashParam)'
|
|
406
|
-
},
|
|
263
|
+
columns: { type: Array, notify: true, value: () => []},
|
|
264
|
+
settings: { type: Object, notify: true },
|
|
265
|
+
_filterIsTooStrict: { type: Boolean, computed: '_computeFilterIsTooStrict(_dataIsValid, sortedFilteredGroupedItems.length)' },
|
|
266
|
+
hashParam: { type: String },
|
|
407
267
|
|
|
408
268
|
/**
|
|
409
269
|
* True when all items are selected.
|
|
410
270
|
*/
|
|
411
|
-
_allSelected: {
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
computedBarHeight: {
|
|
415
|
-
type: Number
|
|
416
|
-
},
|
|
417
|
-
settingsId: {
|
|
418
|
-
type: String,
|
|
419
|
-
value: undefined
|
|
420
|
-
}
|
|
271
|
+
_allSelected: { type: Boolean },
|
|
272
|
+
computedBarHeight: { type: Number },
|
|
273
|
+
settingsId: { type: String, value: undefined }
|
|
421
274
|
};
|
|
422
275
|
}
|
|
423
276
|
|
|
424
277
|
static get observers() {
|
|
425
278
|
return [
|
|
426
|
-
'_dataChanged(data.splices)',
|
|
427
|
-
'_debounceProcessItems(sortOn, descending)',
|
|
428
279
|
'_selectedItemsChanged(selectedItems.*)',
|
|
429
280
|
'renderFastLayoutCss(layoutCss, $.layoutStyle)'
|
|
430
281
|
];
|
|
@@ -433,67 +284,27 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
433
284
|
constructor() {
|
|
434
285
|
super();
|
|
435
286
|
|
|
436
|
-
this.debouncers = {};
|
|
437
|
-
this._updateColumns = this._updateColumns.bind(this);
|
|
438
|
-
this._processItems = this._processItems.bind(this);
|
|
439
|
-
this._groupItems = this._groupItems.bind(this);
|
|
440
|
-
this._sortFilteredGroupedItems = this._sortFilteredGroupedItems.bind(this);
|
|
441
287
|
this._onKey = this._onKey.bind(this);
|
|
442
|
-
this._resizeObserver = new ResizeObserver(this._onResize.bind(this));
|
|
443
288
|
}
|
|
444
289
|
|
|
445
290
|
connectedCallback() {
|
|
446
291
|
super.connectedCallback();
|
|
447
292
|
|
|
448
293
|
this.$.groupedList.scrollTarget = this.$.scroller;
|
|
449
|
-
this.addEventListener('cosmoz-column-hidden-changed', this._debounceUpdateColumns);
|
|
450
|
-
this.addEventListener('cosmoz-column-disabled-changed', this._debounceUpdateColumns);
|
|
451
294
|
|
|
452
295
|
this.addEventListener('update-item-size', this._onUpdateItemSize);
|
|
453
|
-
this.addEventListener('cosmoz-column-title-changed', this._onColumnTitleChanged);
|
|
454
|
-
this.addEventListener('cosmoz-column-filter-changed', this._filterChanged);
|
|
455
|
-
this.addEventListener('cosmoz-column-editable-changed', this._onColumnEditableChanged);
|
|
456
|
-
this.addEventListener('cosmoz-column-values-update', this._onColumnValuesUpdate);
|
|
457
296
|
window.addEventListener('keydown', this._onKey);
|
|
458
297
|
window.addEventListener('keyup', this._onKey);
|
|
459
|
-
this._resizeObserver.observe(this);
|
|
460
|
-
this._updateParamsFromHash();
|
|
461
298
|
}
|
|
462
299
|
|
|
463
300
|
disconnectedCallback() {
|
|
464
301
|
super.disconnectedCallback();
|
|
465
302
|
|
|
466
|
-
this.removeEventListener('cosmoz-column-hidden-changed', this._debounceUpdateColumns);
|
|
467
|
-
this.removeEventListener('cosmoz-column-disabled-changed', this._debounceUpdateColumns);
|
|
468
|
-
// Just in case we get detached before a planned debouncer has not run yet.
|
|
469
|
-
this._cancelDebouncers();
|
|
470
|
-
|
|
471
303
|
this.removeEventListener('update-item-size', this._onUpdateItemSize);
|
|
472
|
-
this.removeEventListener('cosmoz-column-title-changed', this._onColumnTitleChanged);
|
|
473
|
-
this.removeEventListener('cosmoz-column-filter-changed', this._filterChanged);
|
|
474
|
-
this.removeEventListener('cosmoz-column-editable-changed', this._onColumnEditableChanged);
|
|
475
|
-
this.removeEventListener('cosmoz-column-values-update', this._onColumnValuesUpdate);
|
|
476
|
-
this._resizeObserver.unobserve(this);
|
|
477
304
|
window.removeEventListener('keydown', this._onKey);
|
|
478
305
|
window.removeEventListener('keyup', this._onKey);
|
|
479
306
|
}
|
|
480
307
|
|
|
481
|
-
flush() {
|
|
482
|
-
// NOTE: in some instances flushing a debouncer causes another debouncer
|
|
483
|
-
// to be set, so we must test each debouncer independently and in this order
|
|
484
|
-
if (this.debouncers._updateColumnsDebouncer) {
|
|
485
|
-
this.debouncers._updateColumnsDebouncer.flush();
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (this.debouncers._processItemsDebouncer) {
|
|
489
|
-
this.debouncers._processItemsDebouncer.flush();
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
_cancelDebouncers() {
|
|
494
|
-
Object.values(this.debouncers).forEach(d => d.cancel());
|
|
495
|
-
}
|
|
496
|
-
|
|
497
308
|
/** ELEMENT BEHAVIOR */
|
|
498
309
|
|
|
499
310
|
_computeDataValidity({ base: data } = {}) {
|
|
@@ -509,12 +320,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
509
320
|
return `(${ direction })`;
|
|
510
321
|
}
|
|
511
322
|
|
|
512
|
-
visibleChanged(turnedVisible) {
|
|
513
|
-
if (turnedVisible) {
|
|
514
|
-
this._debounceUpdateColumns();
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
323
|
_onUpdateItemSize(event) {
|
|
519
324
|
const { detail } = event;
|
|
520
325
|
if (detail && detail.item) {
|
|
@@ -523,41 +328,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
523
328
|
event.stopPropagation();
|
|
524
329
|
}
|
|
525
330
|
|
|
526
|
-
_onColumnTitleChanged(event) {
|
|
527
|
-
event.stopPropagation();
|
|
528
|
-
|
|
529
|
-
if (!Array.isArray(this.columns)) {
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const column = event.target,
|
|
534
|
-
columnIndex = this.columns.indexOf(column);
|
|
535
|
-
|
|
536
|
-
// re-notify column change to make dom-repeat re-render menu item title
|
|
537
|
-
this.notifyPath(['columns', columnIndex, 'title']);
|
|
538
|
-
|
|
539
|
-
if (column === this.groupOnColumn) {
|
|
540
|
-
this.notifyPath(['groupOnColumn', 'title']);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
_onColumnEditableChanged(event) {
|
|
545
|
-
event.stopPropagation();
|
|
546
|
-
const { detail: { column }} = event,
|
|
547
|
-
{ columns } = this;
|
|
548
|
-
|
|
549
|
-
if (!Array.isArray(columns) || columns.length === 0) {
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const index = columns.indexOf(column);
|
|
554
|
-
if (index < 0) {
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
this.columns = [...this.columns];
|
|
559
|
-
}
|
|
560
|
-
|
|
561
331
|
_onKey(e) {
|
|
562
332
|
this._shiftKey = e.shiftKey;
|
|
563
333
|
this._ctrlKey = e.ctrlKey;
|
|
@@ -579,387 +349,20 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
579
349
|
event.stopPropagation();
|
|
580
350
|
}
|
|
581
351
|
|
|
582
|
-
_itemRowTapped(event) {
|
|
583
|
-
const item = event.model.item;
|
|
584
|
-
this.highlight(item, this.isItemHighlighted(item));
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
_onResize([entry]) {
|
|
588
|
-
const hidden = entry.borderBoxSize?.[0]?.blockSize === 0 || entry.contentRect?.height === 0;
|
|
589
|
-
this._setVisible(!hidden);
|
|
590
|
-
if (hidden) {
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
requestAnimationFrame(() => requestAnimationFrame(() => this.$.groupedList.$.list._render()));
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
_dataChanged() {
|
|
597
|
-
if (!Array.isArray(this.columns)) {
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
this._setColumnValues();
|
|
601
|
-
this._debounceProcessItems();
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
_debounceUpdateColumns() {
|
|
605
|
-
this._debounce('_updateColumnsDebouncer', this._updateColumns, timeOut.after(10));
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/* eslint-disable-next-line max-lines-per-function, max-statements */
|
|
609
|
-
_updateColumns() {
|
|
610
|
-
if (!this.isConnected) {
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
this._setVisible(this.offsetParent != null);
|
|
615
|
-
|
|
616
|
-
if (!this.visible) {
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// NOTE: it's important to get all children, including those projected in slots
|
|
621
|
-
let columns = this.$.columnsSlot.assignedElements({ flatten: true }).filter(child => child.isOmnitableColumn && !child.hidden),
|
|
622
|
-
valuePathNames;
|
|
623
|
-
|
|
624
|
-
const columnNames = columns.map(c => c.name);
|
|
625
|
-
|
|
626
|
-
if (Array.isArray(this.enabledColumns)) {
|
|
627
|
-
columns = columns.filter(column =>
|
|
628
|
-
this.enabledColumns.indexOf(column.name) !== -1
|
|
629
|
-
);
|
|
630
|
-
} else {
|
|
631
|
-
columns = columns.filter(column => !column.disabled);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const columnsChanged = !Array.isArray(this.columns) ||
|
|
635
|
-
this.columns.length !== columns.length ||
|
|
636
|
-
this.columns.some(col => columns.indexOf(col) === -1);
|
|
637
|
-
|
|
638
|
-
if (!columns || columns.length === 0 || !columnsChanged) {
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
this._verifyColumnSetup(columns, columnNames);
|
|
643
|
-
|
|
644
|
-
columns.forEach(column => {
|
|
645
|
-
if (!column.name) {
|
|
646
|
-
// No name set; Try to set name attribute via valuePath
|
|
647
|
-
if (!valuePathNames) {
|
|
648
|
-
valuePathNames = columns.map(c => c.valuePath);
|
|
649
|
-
}
|
|
650
|
-
const hasUniqueValuePath = valuePathNames.indexOf(column.valuePath) === valuePathNames.lastIndexOf(column.valuePath);
|
|
651
|
-
if (hasUniqueValuePath && columnNames.indexOf(column.valuePath) === -1) {
|
|
652
|
-
column.name = column.valuePath;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
if (!Array.isArray(this.columns) || this.columns.length === 0) {
|
|
658
|
-
this._setColumnValues(columns);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
this.columns = columns;
|
|
662
|
-
this._updateParamsFromHash();
|
|
663
|
-
|
|
664
|
-
if (Array.isArray(this.data)) {
|
|
665
|
-
this._debounceProcessItems();
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Checks if the column setup is valid and logs errors.
|
|
671
|
-
* As a separate functions to make testing easier.
|
|
672
|
-
* @param {any} columns The columns.
|
|
673
|
-
* @param {any} columnNames The column names.
|
|
674
|
-
* @returns {Boolean} True if setup is valid.
|
|
675
|
-
*/
|
|
676
|
-
_verifyColumnSetup(columns, columnNames = columns.map(c => c.name)) {
|
|
677
|
-
// Check if column names are set and unique
|
|
678
|
-
const columnsMissingNameAttribute = columns
|
|
679
|
-
.filter(column => {
|
|
680
|
-
const name = column.name;
|
|
681
|
-
if (!name) {
|
|
682
|
-
// eslint-disable-next-line no-console
|
|
683
|
-
console.error('The name attribute needs to be set on all columns! Missing on column', column.title, column);
|
|
684
|
-
return false;
|
|
685
|
-
}
|
|
686
|
-
return columnNames.indexOf(name) !== columnNames.lastIndexOf(name);
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
columnsMissingNameAttribute.forEach(column => {
|
|
690
|
-
// eslint-disable-next-line no-console
|
|
691
|
-
console.error('The name attribute needs to be unique among all columns! Not unique on column', column.title, column);
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
return columnsMissingNameAttribute.length === 0;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
_onColumnValuesUpdate({ detail }) {
|
|
698
|
-
if (detail == null || detail.column == null) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
this._setColumnValues([detail.column]);
|
|
702
|
-
}
|
|
703
|
-
// TODO: provides a mean to avoid setting the values for a column
|
|
704
|
-
// TODO: should process (distinct, sort, min, max) the values at the column level depending on the column type
|
|
705
|
-
_setColumnValues(columns = this.columns) {
|
|
706
|
-
if (!Array.isArray(this.data) || this.data.length < 1 || !Array.isArray(columns) || columns.length < 1) {
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
columns.forEach(column => {
|
|
710
|
-
if (!column.bindValues || column.externalValues) {
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
if (!column.valuePath) {
|
|
715
|
-
// eslint-disable-next-line no-console
|
|
716
|
-
console.error('value path is not defined for column', column, 'with bindValues');
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
column.set('values', this.data
|
|
721
|
-
.map(item => this.get(column.valuePath, item))
|
|
722
|
-
.filter((value, index, self) =>
|
|
723
|
-
value != null && self.indexOf(value) === index
|
|
724
|
-
)
|
|
725
|
-
);
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
/*
|
|
729
|
-
* Returns a column based on an attribute.
|
|
730
|
-
* @param {String} attributeValue The value of the column attribute.
|
|
731
|
-
* @param {String} attribute The attribute name of the column.
|
|
732
|
-
* @returns {Object} The found column.
|
|
733
|
-
*/
|
|
734
|
-
_getColumn(attributeValue, attribute = 'name', columns) {
|
|
735
|
-
if (!attributeValue || !columns) {
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
return columns.find(column => column[attribute] === attributeValue);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
_filterChanged({ detail }) {
|
|
742
|
-
if (!Array.isArray(this.columns) || this.columns.length < 1 || this.columns.indexOf(detail.column) < 0) {
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
this._debounceProcessItems();
|
|
746
|
-
this._filterForRouteChanged(detail.column);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
_groupOnColumnChanged() {
|
|
750
|
-
this._debounceProcessItems();
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
_debounceProcessItems() {
|
|
754
|
-
this._debounce('_processItemsDebouncer', this._processItems);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
_processItems() {
|
|
758
|
-
this._filterItems();
|
|
759
|
-
this._groupItems();
|
|
760
|
-
this._sortFilteredGroupedItems();
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
_filterItems() {
|
|
764
|
-
if (Array.isArray(this.data) && this.data.length > 0 && Array.isArray(this.columns)) {
|
|
765
|
-
// Call filtering code only on columns that has a filter
|
|
766
|
-
const filterFunctions = this.columns
|
|
767
|
-
.map(col => col.getFilterFn())
|
|
768
|
-
.filter(fn => fn !== undefined);
|
|
769
|
-
|
|
770
|
-
if (filterFunctions.length) {
|
|
771
|
-
this.filteredItems = this.data.filter(item =>
|
|
772
|
-
filterFunctions.every(filterFn => filterFn(item))
|
|
773
|
-
);
|
|
774
|
-
} else {
|
|
775
|
-
this.filteredItems = this.data.slice();
|
|
776
|
-
}
|
|
777
|
-
} else {
|
|
778
|
-
this.filteredItems = [];
|
|
779
|
-
this.filteredGroupedItems = [];
|
|
780
|
-
this.sortedFilteredGroupedItems = [];
|
|
781
|
-
this._groupsCount = 0;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/* eslint-disable-next-line max-statements */
|
|
786
|
-
_groupItems() {
|
|
787
|
-
// do not attempt to group items if no columns are defined
|
|
788
|
-
if (!Array.isArray(this.columns) || this.columns.length === 0) {
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
this._updateRouteParam('groupOn');
|
|
793
|
-
|
|
794
|
-
if (!Array.isArray(this.filteredItems) || this.filteredItems.length === 0) {
|
|
795
|
-
this.filteredGroupedItems = [];
|
|
796
|
-
this.sortedFilteredGroupedItems = [];
|
|
797
|
-
this._groupsCount = 0;
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
const groupOnColumn = this.groupOnColumn;
|
|
802
|
-
|
|
803
|
-
if (!groupOnColumn || !groupOnColumn.groupOn) {
|
|
804
|
-
this.filteredGroupedItems = this.filteredItems;
|
|
805
|
-
this._groupsCount = 0;
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const groups = this.filteredItems.reduce((array, item) => {
|
|
810
|
-
const gval = groupOnColumn.getComparableValue(item, groupOnColumn.groupOn);
|
|
811
|
-
|
|
812
|
-
if (gval === undefined) {
|
|
813
|
-
return array;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
let group = array.find(g => g.id === gval);
|
|
817
|
-
if (!group) {
|
|
818
|
-
group = {
|
|
819
|
-
id: gval,
|
|
820
|
-
name: gval,
|
|
821
|
-
items: []
|
|
822
|
-
};
|
|
823
|
-
array.push(group);
|
|
824
|
-
}
|
|
825
|
-
group.items.push(item);
|
|
826
|
-
return array;
|
|
827
|
-
}, []);
|
|
828
|
-
|
|
829
|
-
groups.sort((a, b) => {
|
|
830
|
-
const v1 = groupOnColumn.getComparableValue(a.items[0], groupOnColumn.groupOn),
|
|
831
|
-
v2 = groupOnColumn.getComparableValue(b.items[0], groupOnColumn.groupOn);
|
|
832
|
-
|
|
833
|
-
return genericSorter(v1, v2);
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
if (this.groupOnDescending) {
|
|
837
|
-
groups.reverse();
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
this._groupsCount = groups.length;
|
|
841
|
-
this.filteredGroupedItems = groups;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
/**
|
|
845
|
-
* compareFunction for sort(), can be overridden
|
|
846
|
-
* @see Array.prototype.sort()
|
|
847
|
-
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort}
|
|
848
|
-
* @param {*} a First compare value
|
|
849
|
-
* @param {*} b Second compare value
|
|
850
|
-
* @returns {number} -1 if a has lower index, 0 if a and b index are same, 1 if b is lower
|
|
851
|
-
*/
|
|
852
|
-
sorter(a, b) {
|
|
853
|
-
const v1 = this.sortOnColumn.getComparableValue(a, this.sortOnColumn.sortOn),
|
|
854
|
-
v2 = this.sortOnColumn.getComparableValue(b, this.sortOnColumn.sortOn);
|
|
855
|
-
|
|
856
|
-
return genericSorter(v1, v2);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/* eslint-disable-next-line max-statements */
|
|
860
|
-
_sortFilteredGroupedItems() {
|
|
861
|
-
if (!this.filteredGroupedItems) {
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
this._updateRouteParam('sortOn');
|
|
866
|
-
this._updateRouteParam('descending');
|
|
867
|
-
this._updateRouteParam('groupOnDescending');
|
|
868
|
-
|
|
869
|
-
if (!this.sortOn || !this.sortOnColumn) {
|
|
870
|
-
this.sortedFilteredGroupedItems = this.filteredGroupedItems;
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const sorter = this.sorter.bind(this);
|
|
875
|
-
|
|
876
|
-
if (this._groupsCount > 0) {
|
|
877
|
-
this.set('sortedFilteredGroupedItems', this.filteredGroupedItems
|
|
878
|
-
.filter(group => Array.isArray(group.items))
|
|
879
|
-
.map(group => {
|
|
880
|
-
group.items.sort(sorter);
|
|
881
|
-
if (this.descending) {
|
|
882
|
-
group.items.reverse();
|
|
883
|
-
}
|
|
884
|
-
return {
|
|
885
|
-
name: group.name,
|
|
886
|
-
id: group.id,
|
|
887
|
-
items: group.items
|
|
888
|
-
};
|
|
889
|
-
}));
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// No grouping
|
|
894
|
-
this.filteredGroupedItems.sort(sorter);
|
|
895
|
-
if (this.descending) {
|
|
896
|
-
this.filteredGroupedItems.reverse();
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
this.set('sortedFilteredGroupedItems', this.filteredGroupedItems.slice());
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
_makeCsvField(str) {
|
|
903
|
-
const result = str.replace(/"/gu, '""');
|
|
904
|
-
if (result.search(/("|,|\n)/gu) >= 0) {
|
|
905
|
-
return '"' + result + '"';
|
|
906
|
-
}
|
|
907
|
-
return str;
|
|
908
|
-
}
|
|
909
352
|
/**
|
|
910
353
|
* Triggers a download of selected rows as a CSV file.
|
|
911
354
|
* @returns {undefined}
|
|
912
355
|
*/
|
|
913
356
|
_saveAsCsvAction() {
|
|
914
|
-
|
|
915
|
-
lf = '\n',
|
|
916
|
-
header = this.columns.map(col => this._makeCsvField(col.title)).join(separator) + lf,
|
|
917
|
-
rows = this.selectedItems.map(item => {
|
|
918
|
-
return this.columns.map(column => {
|
|
919
|
-
const cell = column.getString(item);
|
|
920
|
-
if (cell === undefined || cell === null) {
|
|
921
|
-
return '';
|
|
922
|
-
}
|
|
923
|
-
return this._makeCsvField(String(cell));
|
|
924
|
-
}).join(separator) + lf;
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
rows.unshift(header);
|
|
928
|
-
|
|
929
|
-
saveAs(new File(rows, this.csvFilename, {
|
|
930
|
-
type: 'text/csv;charset=utf-8'
|
|
931
|
-
}));
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
/**
|
|
935
|
-
* Makes the data ready to be exported as XLSX.
|
|
936
|
-
* @returns {Array} data Array of prepared rows.
|
|
937
|
-
*/
|
|
938
|
-
_prepareXlsxData() {
|
|
939
|
-
const headers = this.columns.map(col => col.title),
|
|
940
|
-
data = this.selectedItems.map(item =>
|
|
941
|
-
this.columns.map(column => {
|
|
942
|
-
const value = column.toXlsxValue(item);
|
|
943
|
-
return value == null ? '' : value;
|
|
944
|
-
})
|
|
945
|
-
);
|
|
946
|
-
|
|
947
|
-
data.unshift(headers);
|
|
948
|
-
return data;
|
|
357
|
+
saveAsCsvAction(this.columns, this.selectedItems, this.csvFilename);
|
|
949
358
|
}
|
|
950
359
|
|
|
951
360
|
/**
|
|
952
361
|
* Triggers a download of selected rows as a XLSX file.
|
|
953
|
-
* @param {Object} data The prepared rows to be saved as file with default value this._prepareXlsxData().
|
|
954
362
|
* @returns {undefined}
|
|
955
363
|
*/
|
|
956
364
|
_saveAsXlsxAction() {
|
|
957
|
-
|
|
958
|
-
xlsx = new NullXlsx(this.xlsxFilename).addSheetFromData(data, this.xlsxSheetname).generate();
|
|
959
|
-
|
|
960
|
-
saveAs(new File([xlsx], this.xlsxFilename, {
|
|
961
|
-
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
962
|
-
}));
|
|
365
|
+
saveAsXlsxAction(this.columns, this.selectedItems, this.xlsxFilename, this.xlsxSheetname);
|
|
963
366
|
}
|
|
964
367
|
|
|
965
368
|
/** view functions */
|
|
@@ -970,6 +373,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
970
373
|
_getFoldIcon(expanded) {
|
|
971
374
|
return expanded ? 'expand-less' : 'expand-more';
|
|
972
375
|
}
|
|
376
|
+
|
|
973
377
|
/**
|
|
974
378
|
* Toggle folding of a group
|
|
975
379
|
* @param {Event} event event
|
|
@@ -1019,17 +423,19 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1019
423
|
}
|
|
1020
424
|
}
|
|
1021
425
|
|
|
426
|
+
// TODO: move to publicInterface mixin
|
|
1022
427
|
/** PUBLIC */
|
|
1023
428
|
|
|
1024
429
|
suppressNextScrollReset() {
|
|
1025
430
|
const list = this.$.groupedList.$.list;
|
|
1026
431
|
// HACK: Replace _resetScrollPosition for one call to maintain scroll position
|
|
1027
|
-
if (list._scrollTop > 0) {
|
|
432
|
+
if (list._scrollTop > 0 && !list._resetScrollPosition.suppressed) {
|
|
1028
433
|
const reset = list._resetScrollPosition;
|
|
1029
434
|
list._resetScrollPosition = () => {
|
|
1030
435
|
// restore hack
|
|
1031
436
|
list._resetScrollPosition = reset;
|
|
1032
437
|
};
|
|
438
|
+
list._resetScrollPosition.suppressed = true;
|
|
1033
439
|
}
|
|
1034
440
|
}
|
|
1035
441
|
|
|
@@ -1062,6 +468,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1062
468
|
}
|
|
1063
469
|
|
|
1064
470
|
const removed = this.splice('data', index, 1);
|
|
471
|
+
this.data = this.data.slice();
|
|
1065
472
|
if (Array.isArray(removed) && removed.length > 0) {
|
|
1066
473
|
return removed[0];
|
|
1067
474
|
}
|
|
@@ -1075,6 +482,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1075
482
|
replaceItemAtIndex(index, newItem) {
|
|
1076
483
|
this.suppressNextScrollReset();
|
|
1077
484
|
this.splice('data', index, 1, newItem);
|
|
485
|
+
this.data = this.data.slice();
|
|
1078
486
|
}
|
|
1079
487
|
/**
|
|
1080
488
|
* Convenience method for setting a value to an item's path and notifying any
|
|
@@ -1102,117 +510,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1102
510
|
return this.$.groupedList.isItemSelected(item);
|
|
1103
511
|
}
|
|
1104
512
|
|
|
1105
|
-
isItemHighlighted(item) {
|
|
1106
|
-
return this.$.groupedList.isItemHighlighted(item);
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
highlight(items, reverse) {
|
|
1110
|
-
if (!items) {
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
const gl = this.$.groupedList;
|
|
1114
|
-
if (Array.isArray(items)) {
|
|
1115
|
-
items.forEach(item => gl.highlightItem(item, reverse));
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
gl.highlightItem(items, reverse);
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
_routeHashPropertyChanged(key, value) {
|
|
1122
|
-
const deserialized = this._deserializeValue(value, Omnitable.properties[key].type);
|
|
1123
|
-
if (deserialized === this.get(key)) {
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
this.set(key, deserialized);
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
_routeHashFilterChanged(key, value) {
|
|
1130
|
-
const column = this.columns.find(c => c.name === key);
|
|
1131
|
-
|
|
1132
|
-
if (!column) {
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
if (value === column._serializeFilter()) {
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
const deserialized = column._deserializeFilter(value);
|
|
1141
|
-
|
|
1142
|
-
if (deserialized === null) {
|
|
1143
|
-
column.resetFilter();
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
column.set('filter', deserialized);
|
|
1147
|
-
}
|
|
1148
|
-
_computeRouteHashKeyRule(hashParam) {
|
|
1149
|
-
if (!hashParam) {
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
return new RegExp('^' + hashParam + '-(.+?)(?=(?:--|$))(?:-{2})?([A-Za-z0-9-_]+)?$', 'u');
|
|
1153
|
-
}
|
|
1154
|
-
_routeHashKeyChanged(key, value) {
|
|
1155
|
-
const match = key.match(this._routeHashKeyRule);
|
|
1156
|
-
|
|
1157
|
-
if (!Array.isArray(match)) {
|
|
1158
|
-
return;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (match[2] == null && PROPERTY_HASH_PARAMS.indexOf(match[1]) > -1) {
|
|
1162
|
-
this._routeHashPropertyChanged(match[1], value);
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
if (match[2] !== null && match[1] === 'filter') {
|
|
1166
|
-
this._routeHashFilterChanged(match[2], value);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
_updateParamsFromHash() {
|
|
1171
|
-
if (!this.hashParam || !this._routeHash) {
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
const hash = this._routeHash;
|
|
1175
|
-
Object.keys(hash).forEach(key => {
|
|
1176
|
-
this._routeHashKeyChanged(key, hash[key]);
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
_updateRouteParam(key) {
|
|
1181
|
-
if (!this.hashParam || !this._routeHash) {
|
|
1182
|
-
return;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
const path = ['_routeHash', this.hashParam + '-' + key],
|
|
1186
|
-
hashValue = this.get(path),
|
|
1187
|
-
value = this.get(key),
|
|
1188
|
-
serialized = this._serializeValue(value, Omnitable.properties[key].type);
|
|
1189
|
-
|
|
1190
|
-
if (serialized === hashValue) {
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
this.set(path, serialized === undefined ? null : serialized);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
_filterForRouteChanged(column) {
|
|
1197
|
-
if (!this.hashParam || !this._routeHash || !Array.isArray(this.data)) {
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
const path = ['_routeHash', this.hashParam + '-filter--' + column.name],
|
|
1202
|
-
hashValue = this.get(path),
|
|
1203
|
-
serialized = column._serializeFilter();
|
|
1204
|
-
|
|
1205
|
-
if (serialized === hashValue) {
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
this.set(path, serialized === undefined ? null : serialized);
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
_debounce(name, fn, asyncModule = timeOut.after(0)) {
|
|
1213
|
-
this.debouncers[name] = Debouncer.debounce(this.debouncers[name], asyncModule, fn);
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
513
|
_renderRowStats(numRows, totalAvailable) {
|
|
1217
514
|
if (Number.isInteger(totalAvailable) && totalAvailable > numRows) {
|
|
1218
515
|
return this.ngettext('{1} / {0} row', '{1} / {0} rows', totalAvailable, numRows);
|
|
@@ -1227,17 +524,28 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1227
524
|
_onCompleteValues(columns, type, value) { /* eslint-disable-next-line no-bitwise */
|
|
1228
525
|
return columns?.filter?.(c => c[type]).sort((a, b) => ((b === value) >> 0) - ((a === value) >> 0));
|
|
1229
526
|
}
|
|
527
|
+
|
|
1230
528
|
_onCompleteChange(type) {
|
|
1231
529
|
return (val, close) => {
|
|
1232
530
|
const value = (val[0] ?? val)?.name ?? '',
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
531
|
+
setter = type === 'groupOn' ? this.setGroupOn : this.setSortOn,
|
|
532
|
+
directionSetter = type === 'groupOn' ? this.setGroupOnDescending : this.setDescending;
|
|
533
|
+
|
|
534
|
+
setter(oldValue => {
|
|
535
|
+
if (value) {
|
|
536
|
+
directionSetter(oldDirection => value === oldValue ? !oldDirection : false);
|
|
537
|
+
} else {
|
|
538
|
+
directionSetter(null);
|
|
539
|
+
}
|
|
540
|
+
return value;
|
|
541
|
+
});
|
|
542
|
+
|
|
1236
543
|
value && close(); /* eslint-disable-line no-unused-expressions */
|
|
1237
544
|
};
|
|
1238
545
|
}
|
|
1239
546
|
|
|
1240
|
-
|
|
547
|
+
// eslint-disable-next-line max-params
|
|
548
|
+
_renderSettings(normalizedSettings, collapsed, settingsId, hasChangedSettings, hasHiddenFilter, filters) {
|
|
1241
549
|
return litHtml`<cosmoz-omnitable-settings
|
|
1242
550
|
.settings=${ normalizedSettings }
|
|
1243
551
|
.onSettings=${ this.setSettings }
|
|
@@ -1247,10 +555,11 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1247
555
|
.onSave=${ this.onSettingsSave }
|
|
1248
556
|
.onReset=${ this.onSettingsReset }
|
|
1249
557
|
.badge=${ hasHiddenFilter }
|
|
558
|
+
.filters=${ filters }
|
|
1250
559
|
>`;
|
|
1251
560
|
}
|
|
1252
561
|
}
|
|
1253
|
-
customElements.define(
|
|
562
|
+
customElements.define('cosmoz-omnitable', Omnitable);
|
|
1254
563
|
|
|
1255
564
|
const tmplt = `
|
|
1256
565
|
<slot name="actions" slot="actions"></slot>
|