@neovici/cosmoz-omnitable 7.2.1 → 8.0.0-beta.3
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 +4 -8
- package/cosmoz-omnitable.js +72 -764
- 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 +8 -5
- 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-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 -2
- package/lib/use-force-render.js +0 -8
- package/lib/use-render-on-column-updates.js +0 -18
package/cosmoz-omnitable.js
CHANGED
|
@@ -3,7 +3,6 @@ import '@polymer/iron-icons/iron-icons';
|
|
|
3
3
|
import '@polymer/iron-icon/iron-icon';
|
|
4
4
|
import '@polymer/iron-label/iron-label';
|
|
5
5
|
import '@polymer/paper-button/paper-button';
|
|
6
|
-
import '@polymer/paper-checkbox/paper-checkbox';
|
|
7
6
|
import '@polymer/paper-dropdown-menu/paper-dropdown-menu';
|
|
8
7
|
import '@polymer/paper-icon-button/paper-icon-button';
|
|
9
8
|
import '@polymer/paper-item/paper-item';
|
|
@@ -12,7 +11,6 @@ import '@polymer/paper-spinner/paper-spinner-lite';
|
|
|
12
11
|
|
|
13
12
|
import '@neovici/cosmoz-grouped-list';
|
|
14
13
|
import '@neovici/cosmoz-bottom-bar';
|
|
15
|
-
import '@neovici/cosmoz-page-router/cosmoz-page-location';
|
|
16
14
|
|
|
17
15
|
import './cosmoz-omnitable-column';
|
|
18
16
|
import './cosmoz-omnitable-header-row';
|
|
@@ -22,12 +20,7 @@ import './cosmoz-omnitable-group-row';
|
|
|
22
20
|
import './cosmoz-omnitable-columns';
|
|
23
21
|
import styles from './cosmoz-omnitable-styles';
|
|
24
22
|
|
|
25
|
-
import { NullXlsx } from '@neovici/nullxlsx';
|
|
26
23
|
|
|
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
24
|
import { PolymerElement } from '@polymer/polymer/polymer-element';
|
|
32
25
|
import { html } from '@polymer/polymer/lib/utils/html-tag';
|
|
33
26
|
import { html as litHtml, render } from 'lit-html';
|
|
@@ -37,9 +30,8 @@ import { mixin, hauntedPolymer } from '@neovici/cosmoz-utils';
|
|
|
37
30
|
import { isEmpty } from '@neovici/cosmoz-utils/lib/template.js';
|
|
38
31
|
import { useOmnitable } from './lib/use-omnitable';
|
|
39
32
|
import './lib/cosmoz-omnitable-settings';
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
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';
|
|
43
35
|
|
|
44
36
|
/**
|
|
45
37
|
* @polymer
|
|
@@ -57,16 +49,17 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
57
49
|
${ html([styles]) }
|
|
58
50
|
<div id="layoutStyle"></div>
|
|
59
51
|
|
|
60
|
-
<cosmoz-page-location id="location" route-hash="{{ _routeHash }}"></cosmoz-page-location>
|
|
61
|
-
|
|
62
52
|
<div class="mainContainer">
|
|
63
53
|
<div class="header" id="header">
|
|
64
54
|
<input class="checkbox all" type="checkbox" checked="[[ _allSelected ]]" on-input="_onAllCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
|
|
65
55
|
<cosmoz-omnitable-header-row
|
|
56
|
+
data="[[ data ]]"
|
|
66
57
|
columns="[[ normalizedColumns ]]"
|
|
58
|
+
filters="[[ filters ]]"
|
|
67
59
|
group-on-column="[[ groupOnColumn ]]"
|
|
68
|
-
content="[[ _renderSettings(normalizedSettings, collapsedColumns, settingsId, hasChangedSettings, hasHiddenFilter) ]]"
|
|
69
|
-
|
|
60
|
+
content="[[ _renderSettings(normalizedSettings, collapsedColumns, settingsId, hasChangedSettings, hasHiddenFilter, filters) ]]"
|
|
61
|
+
set-filter-state="[[ setFilterState ]]"
|
|
62
|
+
></cosmoz-omnitable-header-row>
|
|
70
63
|
</div>
|
|
71
64
|
<div class="tableContent" id="tableContent">
|
|
72
65
|
<template is="dom-if" if="[[ !_dataIsValid ]]">
|
|
@@ -101,18 +94,23 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
101
94
|
<cosmoz-grouped-list id="groupedList"
|
|
102
95
|
data="{{ sortedFilteredGroupedItems }}"
|
|
103
96
|
selected-items="{{ selectedItems }}"
|
|
104
|
-
highlighted-items="{{ highlightedItems }}"
|
|
105
97
|
display-empty-groups="[[ displayEmptyGroups ]]"
|
|
106
98
|
compare-items-fn="[[ compareItemsFn ]]"
|
|
107
99
|
>
|
|
108
100
|
<template slot="templates" data-type="item">
|
|
109
101
|
<div class="item-row-wrapper">
|
|
110
|
-
<div selected$="[[ selected ]]" class="itemRow"
|
|
102
|
+
<div selected$="[[ selected ]]" class="itemRow">
|
|
111
103
|
<input class="checkbox" type="checkbox" checked="[[ selected ]]" on-input="_onCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
|
|
112
104
|
<cosmoz-omnitable-item-row columns="[[ normalizedColumns ]]"
|
|
113
|
-
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 ]]">
|
|
114
107
|
</cosmoz-omnitable-item-row>
|
|
115
|
-
<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>
|
|
116
114
|
</div>
|
|
117
115
|
<cosmoz-omnitable-item-expand columns="[[ collapsedColumns ]]"
|
|
118
116
|
item="[[item]]" selected="{{ selected }}" expanded$="{{ expanded }}" group-on-column="[[ groupOnColumn ]]"
|
|
@@ -139,18 +137,20 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
139
137
|
<div class="footer-controls">
|
|
140
138
|
<cosmoz-autocomplete
|
|
141
139
|
label="[[ _('Group on', t) ]] [[ _computeSortDirection(groupOnDescending, t) ]]" placeholder="[[ _('No grouping', t) ]]"
|
|
142
|
-
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"
|
|
143
142
|
class="footer-control" on-change="[[ _onCompleteChange('groupOn') ]]" default-index="-1" show-single show-selection
|
|
144
143
|
></cosmoz-autocomplete>
|
|
145
144
|
<cosmoz-autocomplete
|
|
146
145
|
label="[[ _('Sort on', t) ]] [[ _computeSortDirection(descending, t) ]]" placeholder="[[ _('No sorting', t) ]]"
|
|
147
|
-
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"
|
|
148
148
|
class="footer-control" on-change="[[ _onCompleteChange('sortOn') ]]" default-index="-1" show-single show-selection
|
|
149
149
|
></cosmoz-autocomplete>
|
|
150
150
|
</div>
|
|
151
151
|
<div class="footer-tableStats">
|
|
152
|
-
<span>[[ ngettext('{0} group', '{0} groups',
|
|
153
|
-
<span>[[ _renderRowStats(
|
|
152
|
+
<span>[[ ngettext('{0} group', '{0} groups', groupsCount, t) ]]</span>
|
|
153
|
+
<span>[[ _renderRowStats(numProcessedItems, totalAvailable, t) ]]</span>
|
|
154
154
|
</div>
|
|
155
155
|
<cosmoz-bottom-bar id="bottomBar" class="footer-actionBar" match-parent
|
|
156
156
|
on-action="_onAction" active$="[[ !isEmpty(selectedItems.length) ]]" computed-bar-height="{{ computedBarHeight }}">
|
|
@@ -177,254 +177,104 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
177
177
|
</div>
|
|
178
178
|
|
|
179
179
|
<div id="columns">
|
|
180
|
-
<slot id="columnsSlot"
|
|
180
|
+
<slot id="columnsSlot"></slot>
|
|
181
181
|
</div>
|
|
182
182
|
`;
|
|
183
183
|
template.setAttribute('strip-whitespace', '');
|
|
184
184
|
return template;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
static get is() {
|
|
188
|
-
return 'cosmoz-omnitable';
|
|
189
|
-
}
|
|
190
|
-
|
|
191
187
|
/* eslint-disable-next-line max-lines-per-function */
|
|
192
188
|
static get properties() {
|
|
193
189
|
return {
|
|
194
|
-
|
|
195
190
|
/**
|
|
196
191
|
* Filename when saving as CSV
|
|
197
192
|
*/
|
|
198
|
-
csvFilename: {
|
|
199
|
-
type: String,
|
|
200
|
-
value: 'omnitable.csv'
|
|
201
|
-
},
|
|
193
|
+
csvFilename: { type: String, value: 'omnitable.csv' },
|
|
202
194
|
|
|
203
195
|
/**
|
|
204
196
|
* Filename when saving as XLSX
|
|
205
197
|
*/
|
|
206
|
-
xlsxFilename: {
|
|
207
|
-
type: String,
|
|
208
|
-
value: 'omnitable.xlsx'
|
|
209
|
-
},
|
|
198
|
+
xlsxFilename: { type: String, value: 'omnitable.xlsx' },
|
|
210
199
|
|
|
211
200
|
/**
|
|
212
201
|
* Sheet name when saving as XLSX
|
|
213
202
|
*/
|
|
214
|
-
xlsxSheetname: {
|
|
215
|
-
type: String,
|
|
216
|
-
value: 'Omnitable'
|
|
217
|
-
},
|
|
203
|
+
xlsxSheetname: { type: String, value: 'Omnitable' },
|
|
218
204
|
|
|
219
205
|
/**
|
|
220
206
|
* Array used to list items.
|
|
221
207
|
*/
|
|
222
|
-
data: {
|
|
223
|
-
type: Array
|
|
224
|
-
},
|
|
208
|
+
data: { type: Array },
|
|
225
209
|
|
|
226
210
|
/**
|
|
227
211
|
* This function is used to determine which items are kept selected across data updates
|
|
212
|
+
* TODO: probably broken
|
|
228
213
|
*/
|
|
229
214
|
compareItemsFn: Function,
|
|
230
215
|
|
|
231
216
|
/**
|
|
232
217
|
* True if data is a valid and not empty array.
|
|
233
218
|
*/
|
|
234
|
-
_dataIsValid: {
|
|
235
|
-
type: Boolean,
|
|
236
|
-
value: false,
|
|
237
|
-
computed: '_computeDataValidity(data.*)'
|
|
238
|
-
},
|
|
219
|
+
_dataIsValid: { type: Boolean, value: false, computed: '_computeDataValidity(data.*)' },
|
|
239
220
|
|
|
240
221
|
/**
|
|
241
222
|
* If set to true, then group a row will be displayed for groups that contain no items.
|
|
242
223
|
*/
|
|
243
|
-
displayEmptyGroups: {
|
|
244
|
-
type: Boolean,
|
|
245
|
-
value: false
|
|
246
|
-
},
|
|
224
|
+
displayEmptyGroups: { type: Boolean, value: false },
|
|
247
225
|
|
|
248
226
|
/**
|
|
249
227
|
* Specific columns to enable
|
|
250
228
|
*/
|
|
251
|
-
enabledColumns: {
|
|
252
|
-
type: Array,
|
|
253
|
-
observer: '_debounceUpdateColumns'
|
|
254
|
-
},
|
|
229
|
+
enabledColumns: { type: Array },
|
|
255
230
|
|
|
256
231
|
/**
|
|
257
232
|
* Whether bottom-bar has actions.
|
|
258
233
|
*/
|
|
259
|
-
hasActions: {
|
|
260
|
-
type: Boolean,
|
|
261
|
-
value: false
|
|
262
|
-
},
|
|
234
|
+
hasActions: { type: Boolean, value: false },
|
|
263
235
|
|
|
264
236
|
/**
|
|
265
237
|
* Shows a loading overlay to indicate data will be updated
|
|
266
238
|
*/
|
|
267
|
-
loading: {
|
|
268
|
-
type: Boolean,
|
|
269
|
-
value: false
|
|
270
|
-
},
|
|
239
|
+
loading: { type: Boolean, value: false },
|
|
271
240
|
|
|
272
241
|
/**
|
|
273
242
|
* List of selected rows/items in `data`.
|
|
274
243
|
*/
|
|
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
|
-
},
|
|
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 },
|
|
315
248
|
|
|
316
249
|
/**
|
|
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.
|
|
250
|
+
* The column name to group on.
|
|
336
251
|
*/
|
|
337
|
-
|
|
338
|
-
type: Array
|
|
339
|
-
},
|
|
252
|
+
groupOn: { type: String, notify: true, value: '' },
|
|
340
253
|
|
|
341
254
|
/**
|
|
342
255
|
* Sorted items structure after filtering and grouping.
|
|
343
256
|
*/
|
|
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
|
-
},
|
|
257
|
+
sortedFilteredGroupedItems: { type: Array, notify: true },
|
|
375
258
|
|
|
376
259
|
/**
|
|
377
260
|
* List of columns definition for this table.
|
|
378
261
|
*/
|
|
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
|
-
},
|
|
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 },
|
|
407
266
|
|
|
408
267
|
/**
|
|
409
268
|
* True when all items are selected.
|
|
410
269
|
*/
|
|
411
|
-
_allSelected: {
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
computedBarHeight: {
|
|
415
|
-
type: Number
|
|
416
|
-
},
|
|
417
|
-
settingsId: {
|
|
418
|
-
type: String,
|
|
419
|
-
value: undefined
|
|
420
|
-
}
|
|
270
|
+
_allSelected: { type: Boolean },
|
|
271
|
+
computedBarHeight: { type: Number },
|
|
272
|
+
settingsId: { type: String, value: undefined }
|
|
421
273
|
};
|
|
422
274
|
}
|
|
423
275
|
|
|
424
276
|
static get observers() {
|
|
425
277
|
return [
|
|
426
|
-
'_dataChanged(data.splices)',
|
|
427
|
-
'_debounceProcessItems(sortOn, descending)',
|
|
428
278
|
'_selectedItemsChanged(selectedItems.*)',
|
|
429
279
|
'renderFastLayoutCss(layoutCss, $.layoutStyle)'
|
|
430
280
|
];
|
|
@@ -433,67 +283,27 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
433
283
|
constructor() {
|
|
434
284
|
super();
|
|
435
285
|
|
|
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
286
|
this._onKey = this._onKey.bind(this);
|
|
442
|
-
this._resizeObserver = new ResizeObserver(this._onResize.bind(this));
|
|
443
287
|
}
|
|
444
288
|
|
|
445
289
|
connectedCallback() {
|
|
446
290
|
super.connectedCallback();
|
|
447
291
|
|
|
448
292
|
this.$.groupedList.scrollTarget = this.$.scroller;
|
|
449
|
-
this.addEventListener('cosmoz-column-hidden-changed', this._debounceUpdateColumns);
|
|
450
|
-
this.addEventListener('cosmoz-column-disabled-changed', this._debounceUpdateColumns);
|
|
451
293
|
|
|
452
294
|
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
295
|
window.addEventListener('keydown', this._onKey);
|
|
458
296
|
window.addEventListener('keyup', this._onKey);
|
|
459
|
-
this._resizeObserver.observe(this);
|
|
460
|
-
this._updateParamsFromHash();
|
|
461
297
|
}
|
|
462
298
|
|
|
463
299
|
disconnectedCallback() {
|
|
464
300
|
super.disconnectedCallback();
|
|
465
301
|
|
|
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
302
|
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
303
|
window.removeEventListener('keydown', this._onKey);
|
|
478
304
|
window.removeEventListener('keyup', this._onKey);
|
|
479
305
|
}
|
|
480
306
|
|
|
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
307
|
/** ELEMENT BEHAVIOR */
|
|
498
308
|
|
|
499
309
|
_computeDataValidity({ base: data } = {}) {
|
|
@@ -509,12 +319,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
509
319
|
return `(${ direction })`;
|
|
510
320
|
}
|
|
511
321
|
|
|
512
|
-
visibleChanged(turnedVisible) {
|
|
513
|
-
if (turnedVisible) {
|
|
514
|
-
this._debounceUpdateColumns();
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
322
|
_onUpdateItemSize(event) {
|
|
519
323
|
const { detail } = event;
|
|
520
324
|
if (detail && detail.item) {
|
|
@@ -523,41 +327,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
523
327
|
event.stopPropagation();
|
|
524
328
|
}
|
|
525
329
|
|
|
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
330
|
_onKey(e) {
|
|
562
331
|
this._shiftKey = e.shiftKey;
|
|
563
332
|
this._ctrlKey = e.ctrlKey;
|
|
@@ -579,387 +348,20 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
579
348
|
event.stopPropagation();
|
|
580
349
|
}
|
|
581
350
|
|
|
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
351
|
/**
|
|
910
352
|
* Triggers a download of selected rows as a CSV file.
|
|
911
353
|
* @returns {undefined}
|
|
912
354
|
*/
|
|
913
355
|
_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;
|
|
356
|
+
saveAsCsvAction(this.columns, this.selectedItems, this.csvFilename);
|
|
949
357
|
}
|
|
950
358
|
|
|
951
359
|
/**
|
|
952
360
|
* 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
361
|
* @returns {undefined}
|
|
955
362
|
*/
|
|
956
363
|
_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
|
-
}));
|
|
364
|
+
saveAsXlsxAction(this.columns, this.selectedItems, this.xlsxFilename, this.xlsxSheetname);
|
|
963
365
|
}
|
|
964
366
|
|
|
965
367
|
/** view functions */
|
|
@@ -970,6 +372,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
970
372
|
_getFoldIcon(expanded) {
|
|
971
373
|
return expanded ? 'expand-less' : 'expand-more';
|
|
972
374
|
}
|
|
375
|
+
|
|
973
376
|
/**
|
|
974
377
|
* Toggle folding of a group
|
|
975
378
|
* @param {Event} event event
|
|
@@ -1019,17 +422,19 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1019
422
|
}
|
|
1020
423
|
}
|
|
1021
424
|
|
|
425
|
+
// TODO: move to publicInterface mixin
|
|
1022
426
|
/** PUBLIC */
|
|
1023
427
|
|
|
1024
428
|
suppressNextScrollReset() {
|
|
1025
429
|
const list = this.$.groupedList.$.list;
|
|
1026
430
|
// HACK: Replace _resetScrollPosition for one call to maintain scroll position
|
|
1027
|
-
if (list._scrollTop > 0) {
|
|
431
|
+
if (list._scrollTop > 0 && !list._resetScrollPosition.suppressed) {
|
|
1028
432
|
const reset = list._resetScrollPosition;
|
|
1029
433
|
list._resetScrollPosition = () => {
|
|
1030
434
|
// restore hack
|
|
1031
435
|
list._resetScrollPosition = reset;
|
|
1032
436
|
};
|
|
437
|
+
list._resetScrollPosition.suppressed = true;
|
|
1033
438
|
}
|
|
1034
439
|
}
|
|
1035
440
|
|
|
@@ -1062,6 +467,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1062
467
|
}
|
|
1063
468
|
|
|
1064
469
|
const removed = this.splice('data', index, 1);
|
|
470
|
+
this.data = this.data.slice();
|
|
1065
471
|
if (Array.isArray(removed) && removed.length > 0) {
|
|
1066
472
|
return removed[0];
|
|
1067
473
|
}
|
|
@@ -1075,6 +481,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1075
481
|
replaceItemAtIndex(index, newItem) {
|
|
1076
482
|
this.suppressNextScrollReset();
|
|
1077
483
|
this.splice('data', index, 1, newItem);
|
|
484
|
+
this.data = this.data.slice();
|
|
1078
485
|
}
|
|
1079
486
|
/**
|
|
1080
487
|
* Convenience method for setting a value to an item's path and notifying any
|
|
@@ -1102,117 +509,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1102
509
|
return this.$.groupedList.isItemSelected(item);
|
|
1103
510
|
}
|
|
1104
511
|
|
|
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
512
|
_renderRowStats(numRows, totalAvailable) {
|
|
1217
513
|
if (Number.isInteger(totalAvailable) && totalAvailable > numRows) {
|
|
1218
514
|
return this.ngettext('{1} / {0} row', '{1} / {0} rows', totalAvailable, numRows);
|
|
@@ -1227,17 +523,28 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1227
523
|
_onCompleteValues(columns, type, value) { /* eslint-disable-next-line no-bitwise */
|
|
1228
524
|
return columns?.filter?.(c => c[type]).sort((a, b) => ((b === value) >> 0) - ((a === value) >> 0));
|
|
1229
525
|
}
|
|
526
|
+
|
|
1230
527
|
_onCompleteChange(type) {
|
|
1231
528
|
return (val, close) => {
|
|
1232
529
|
const value = (val[0] ?? val)?.name ?? '',
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
530
|
+
setter = type === 'groupOn' ? this.setGroupOn : this.setSortOn,
|
|
531
|
+
directionSetter = type === 'groupOn' ? this.setGroupOnDescending : this.setDescending;
|
|
532
|
+
|
|
533
|
+
setter(oldValue => {
|
|
534
|
+
if (value) {
|
|
535
|
+
directionSetter(oldDirection => value === oldValue ? !oldDirection : false);
|
|
536
|
+
} else {
|
|
537
|
+
directionSetter(null);
|
|
538
|
+
}
|
|
539
|
+
return value;
|
|
540
|
+
});
|
|
541
|
+
|
|
1236
542
|
value && close(); /* eslint-disable-line no-unused-expressions */
|
|
1237
543
|
};
|
|
1238
544
|
}
|
|
1239
545
|
|
|
1240
|
-
|
|
546
|
+
// eslint-disable-next-line max-params
|
|
547
|
+
_renderSettings(normalizedSettings, collapsed, settingsId, hasChangedSettings, hasHiddenFilter, filters) {
|
|
1241
548
|
return litHtml`<cosmoz-omnitable-settings
|
|
1242
549
|
.settings=${ normalizedSettings }
|
|
1243
550
|
.onSettings=${ this.setSettings }
|
|
@@ -1247,10 +554,11 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1247
554
|
.onSave=${ this.onSettingsSave }
|
|
1248
555
|
.onReset=${ this.onSettingsReset }
|
|
1249
556
|
.badge=${ hasHiddenFilter }
|
|
557
|
+
.filters=${ filters }
|
|
1250
558
|
>`;
|
|
1251
559
|
}
|
|
1252
560
|
}
|
|
1253
|
-
customElements.define(
|
|
561
|
+
customElements.define('cosmoz-omnitable', Omnitable);
|
|
1254
562
|
|
|
1255
563
|
const tmplt = `
|
|
1256
564
|
<slot name="actions" slot="actions"></slot>
|