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