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