@neovici/cosmoz-omnitable 7.2.0 → 8.0.0-beta.10
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/cosmoz-omnitable-column-amount.js +89 -320
- package/cosmoz-omnitable-column-autocomplete.js +38 -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 +135 -140
- package/cosmoz-omnitable-column-list.js +19 -28
- package/cosmoz-omnitable-column-mixin.js +70 -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 +74 -771
- 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 -129
- 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/normalize-settings.js +2 -5
- 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 +143 -0
- package/lib/use-fast-layout.js +23 -18
- package/lib/use-hash-state.js +59 -0
- package/lib/use-layout.js +1 -1
- package/lib/use-omnitable.js +81 -22
- package/lib/use-processed-items.js +133 -0
- package/lib/use-resizable-columns.js +1 -2
- package/lib/use-sort-and-group-options.js +30 -0
- package/lib/utils-amount.js +147 -0
- package/lib/utils-data.js +41 -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,24 +20,18 @@ 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
|
-
import { html as litHtml
|
|
26
|
+
import { html as litHtml } from 'lit-html';
|
|
34
27
|
|
|
35
28
|
import { translatable } from '@neovici/cosmoz-i18next';
|
|
36
29
|
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,323 +177,132 @@ 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
|
-
'
|
|
427
|
-
'_debounceProcessItems(sortOn, descending)',
|
|
428
|
-
'_selectedItemsChanged(selectedItems.*)',
|
|
429
|
-
'renderFastLayoutCss(layoutCss, $.layoutStyle)'
|
|
278
|
+
'_selectedItemsChanged(selectedItems.*)'
|
|
430
279
|
];
|
|
431
280
|
}
|
|
432
281
|
|
|
433
282
|
constructor() {
|
|
434
283
|
super();
|
|
435
284
|
|
|
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
285
|
this._onKey = this._onKey.bind(this);
|
|
442
|
-
this._resizeObserver = new ResizeObserver(this._onResize.bind(this));
|
|
443
286
|
}
|
|
444
287
|
|
|
445
288
|
connectedCallback() {
|
|
446
289
|
super.connectedCallback();
|
|
447
290
|
|
|
448
291
|
this.$.groupedList.scrollTarget = this.$.scroller;
|
|
449
|
-
this.addEventListener('cosmoz-column-hidden-changed', this._debounceUpdateColumns);
|
|
450
|
-
this.addEventListener('cosmoz-column-disabled-changed', this._debounceUpdateColumns);
|
|
451
292
|
|
|
452
293
|
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
294
|
window.addEventListener('keydown', this._onKey);
|
|
458
295
|
window.addEventListener('keyup', this._onKey);
|
|
459
|
-
this._resizeObserver.observe(this);
|
|
460
|
-
this._updateParamsFromHash();
|
|
461
296
|
}
|
|
462
297
|
|
|
463
298
|
disconnectedCallback() {
|
|
464
299
|
super.disconnectedCallback();
|
|
465
300
|
|
|
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
301
|
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
302
|
window.removeEventListener('keydown', this._onKey);
|
|
478
303
|
window.removeEventListener('keyup', this._onKey);
|
|
479
304
|
}
|
|
480
305
|
|
|
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
306
|
/** ELEMENT BEHAVIOR */
|
|
498
307
|
|
|
499
308
|
_computeDataValidity({ base: data } = {}) {
|
|
@@ -509,12 +318,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
509
318
|
return `(${ direction })`;
|
|
510
319
|
}
|
|
511
320
|
|
|
512
|
-
visibleChanged(turnedVisible) {
|
|
513
|
-
if (turnedVisible) {
|
|
514
|
-
this._debounceUpdateColumns();
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
321
|
_onUpdateItemSize(event) {
|
|
519
322
|
const { detail } = event;
|
|
520
323
|
if (detail && detail.item) {
|
|
@@ -523,41 +326,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
523
326
|
event.stopPropagation();
|
|
524
327
|
}
|
|
525
328
|
|
|
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
329
|
_onKey(e) {
|
|
562
330
|
this._shiftKey = e.shiftKey;
|
|
563
331
|
this._ctrlKey = e.ctrlKey;
|
|
@@ -579,387 +347,20 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
579
347
|
event.stopPropagation();
|
|
580
348
|
}
|
|
581
349
|
|
|
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
350
|
/**
|
|
910
351
|
* Triggers a download of selected rows as a CSV file.
|
|
911
352
|
* @returns {undefined}
|
|
912
353
|
*/
|
|
913
354
|
_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;
|
|
355
|
+
saveAsCsvAction(this.columns, this.selectedItems, this.csvFilename);
|
|
949
356
|
}
|
|
950
357
|
|
|
951
358
|
/**
|
|
952
359
|
* 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
360
|
* @returns {undefined}
|
|
955
361
|
*/
|
|
956
362
|
_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
|
-
}));
|
|
363
|
+
saveAsXlsxAction(this.columns, this.selectedItems, this.xlsxFilename, this.xlsxSheetname);
|
|
963
364
|
}
|
|
964
365
|
|
|
965
366
|
/** view functions */
|
|
@@ -970,6 +371,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
970
371
|
_getFoldIcon(expanded) {
|
|
971
372
|
return expanded ? 'expand-less' : 'expand-more';
|
|
972
373
|
}
|
|
374
|
+
|
|
973
375
|
/**
|
|
974
376
|
* Toggle folding of a group
|
|
975
377
|
* @param {Event} event event
|
|
@@ -1019,17 +421,19 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1019
421
|
}
|
|
1020
422
|
}
|
|
1021
423
|
|
|
424
|
+
// TODO: move to publicInterface mixin
|
|
1022
425
|
/** PUBLIC */
|
|
1023
426
|
|
|
1024
427
|
suppressNextScrollReset() {
|
|
1025
428
|
const list = this.$.groupedList.$.list;
|
|
1026
429
|
// HACK: Replace _resetScrollPosition for one call to maintain scroll position
|
|
1027
|
-
if (list._scrollTop > 0) {
|
|
430
|
+
if (list._scrollTop > 0 && !list._resetScrollPosition.suppressed) {
|
|
1028
431
|
const reset = list._resetScrollPosition;
|
|
1029
432
|
list._resetScrollPosition = () => {
|
|
1030
433
|
// restore hack
|
|
1031
434
|
list._resetScrollPosition = reset;
|
|
1032
435
|
};
|
|
436
|
+
list._resetScrollPosition.suppressed = true;
|
|
1033
437
|
}
|
|
1034
438
|
}
|
|
1035
439
|
|
|
@@ -1062,6 +466,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1062
466
|
}
|
|
1063
467
|
|
|
1064
468
|
const removed = this.splice('data', index, 1);
|
|
469
|
+
this.data = this.data.slice();
|
|
1065
470
|
if (Array.isArray(removed) && removed.length > 0) {
|
|
1066
471
|
return removed[0];
|
|
1067
472
|
}
|
|
@@ -1075,6 +480,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1075
480
|
replaceItemAtIndex(index, newItem) {
|
|
1076
481
|
this.suppressNextScrollReset();
|
|
1077
482
|
this.splice('data', index, 1, newItem);
|
|
483
|
+
this.data = this.data.slice();
|
|
1078
484
|
}
|
|
1079
485
|
/**
|
|
1080
486
|
* Convenience method for setting a value to an item's path and notifying any
|
|
@@ -1102,117 +508,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1102
508
|
return this.$.groupedList.isItemSelected(item);
|
|
1103
509
|
}
|
|
1104
510
|
|
|
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
511
|
_renderRowStats(numRows, totalAvailable) {
|
|
1217
512
|
if (Number.isInteger(totalAvailable) && totalAvailable > numRows) {
|
|
1218
513
|
return this.ngettext('{1} / {0} row', '{1} / {0} rows', totalAvailable, numRows);
|
|
@@ -1220,24 +515,31 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1220
515
|
return this.ngettext('{0} row', '{0} rows', numRows);
|
|
1221
516
|
}
|
|
1222
517
|
|
|
1223
|
-
renderFastLayoutCss(layoutCss, outlet) {
|
|
1224
|
-
render(layoutCss, outlet);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
518
|
_onCompleteValues(columns, type, value) { /* eslint-disable-next-line no-bitwise */
|
|
1228
519
|
return columns?.filter?.(c => c[type]).sort((a, b) => ((b === value) >> 0) - ((a === value) >> 0));
|
|
1229
520
|
}
|
|
521
|
+
|
|
1230
522
|
_onCompleteChange(type) {
|
|
1231
523
|
return (val, close) => {
|
|
1232
524
|
const value = (val[0] ?? val)?.name ?? '',
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
+
|
|
1236
537
|
value && close(); /* eslint-disable-line no-unused-expressions */
|
|
1237
538
|
};
|
|
1238
539
|
}
|
|
1239
540
|
|
|
1240
|
-
|
|
541
|
+
// eslint-disable-next-line max-params
|
|
542
|
+
_renderSettings(normalizedSettings, collapsed, settingsId, hasChangedSettings, hasHiddenFilter, filters) {
|
|
1241
543
|
return litHtml`<cosmoz-omnitable-settings
|
|
1242
544
|
.settings=${ normalizedSettings }
|
|
1243
545
|
.onSettings=${ this.setSettings }
|
|
@@ -1247,10 +549,11 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
1247
549
|
.onSave=${ this.onSettingsSave }
|
|
1248
550
|
.onReset=${ this.onSettingsReset }
|
|
1249
551
|
.badge=${ hasHiddenFilter }
|
|
552
|
+
.filters=${ filters }
|
|
1250
553
|
>`;
|
|
1251
554
|
}
|
|
1252
555
|
}
|
|
1253
|
-
customElements.define(
|
|
556
|
+
customElements.define('cosmoz-omnitable', Omnitable);
|
|
1254
557
|
|
|
1255
558
|
const tmplt = `
|
|
1256
559
|
<slot name="actions" slot="actions"></slot>
|