@neovici/cosmoz-omnitable 8.14.3 → 9.0.0
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-list-data.js +9 -4
- package/cosmoz-omnitable-column-list.js +37 -24
- package/cosmoz-omnitable-item-expand.js +37 -20
- package/cosmoz-omnitable-item-row.js +2 -2
- package/cosmoz-omnitable-styles.js +2 -1
- package/cosmoz-omnitable.js +346 -234
- package/lib/use-dom-columns.js +1 -4
- package/lib/use-fast-layout.js +24 -24
- package/lib/use-omnitable.js +4 -0
- package/lib/use-processed-items.js +163 -59
- package/lib/use-tween-array.js +23 -21
- package/lib/utils.js +10 -0
- package/package.json +2 -2
package/cosmoz-omnitable.js
CHANGED
|
@@ -27,6 +27,8 @@ import { saveAsCsvAction } from './lib/save-as-csv-action';
|
|
|
27
27
|
import { saveAsXlsxAction } from './lib/save-as-xlsx-action';
|
|
28
28
|
import { defaultPlacement } from '@neovici/cosmoz-dropdown';
|
|
29
29
|
import { without } from '@neovici/cosmoz-utils/lib/array';
|
|
30
|
+
import { indexSymbol } from './lib/utils';
|
|
31
|
+
|
|
30
32
|
/**
|
|
31
33
|
* @polymer
|
|
32
34
|
* @customElement
|
|
@@ -36,232 +38,359 @@ import { without } from '@neovici/cosmoz-utils/lib/array';
|
|
|
36
38
|
* @demo demo/index.html
|
|
37
39
|
*/
|
|
38
40
|
|
|
39
|
-
class Omnitable extends hauntedPolymer(useOmnitable)(
|
|
41
|
+
class Omnitable extends hauntedPolymer(useOmnitable)(
|
|
42
|
+
mixin({ isEmpty }, translatable(PolymerElement))
|
|
43
|
+
) {
|
|
40
44
|
/* eslint-disable-next-line max-lines-per-function */
|
|
41
45
|
static get template() {
|
|
42
46
|
const template = html`
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
${html([styles])}
|
|
48
|
+
<div id="layoutStyle"></div>
|
|
49
|
+
|
|
50
|
+
<div class="mainContainer">
|
|
51
|
+
<sort-and-group-provider value="[[ sortAndGroup ]]">
|
|
52
|
+
<div class="header" id="header">
|
|
53
|
+
<input
|
|
54
|
+
class="checkbox all"
|
|
55
|
+
type="checkbox"
|
|
56
|
+
checked="[[ _allSelected ]]"
|
|
57
|
+
on-input="_onAllCheckboxChange"
|
|
58
|
+
disabled$="[[ !_dataIsValid ]]"
|
|
59
|
+
/>
|
|
60
|
+
<cosmoz-omnitable-header-row
|
|
61
|
+
data="[[ data ]]"
|
|
62
|
+
columns="[[ columns ]]"
|
|
63
|
+
filters="[[ filters ]]"
|
|
64
|
+
group-on-column="[[ groupOnColumn ]]"
|
|
65
|
+
set-filter-state="[[ setFilterState ]]"
|
|
66
|
+
settings-config="[[ settingsConfig ]]"
|
|
67
|
+
></cosmoz-omnitable-header-row>
|
|
68
|
+
</div>
|
|
69
|
+
</sort-and-group-provider>
|
|
70
|
+
<div class="tableContent" id="tableContent">
|
|
71
|
+
<template is="dom-if" if="[[ !_dataIsValid ]]">
|
|
72
|
+
<div class="tableContent-empty">
|
|
73
|
+
<slot name="empty-set-message">
|
|
74
|
+
<iron-icon icon="icons:announcement"></iron-icon>
|
|
75
|
+
<div class="tableContent-empty-message">
|
|
76
|
+
<h3>[[ _('Working set empty', t) ]]</h3>
|
|
77
|
+
<p>[[ _('No data to display', t) ]]</p>
|
|
78
|
+
</div>
|
|
79
|
+
</slot>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
<template is="dom-if" if="[[ _filterIsTooStrict ]]">
|
|
83
|
+
<div class="tableContent-empty">
|
|
64
84
|
<iron-icon icon="icons:announcement"></iron-icon>
|
|
65
|
-
<div
|
|
66
|
-
<h3>[[ _('
|
|
67
|
-
<p>[[ _('No
|
|
85
|
+
<div>
|
|
86
|
+
<h3>[[ _('Filter too strict', t) ]]</h3>
|
|
87
|
+
<p>[[ _('No matches for selection', t) ]]</p>
|
|
68
88
|
</div>
|
|
69
|
-
</slot>
|
|
70
|
-
</div>
|
|
71
|
-
</template>
|
|
72
|
-
<template is="dom-if" if="[[ _filterIsTooStrict ]]">
|
|
73
|
-
<div class="tableContent-empty">
|
|
74
|
-
<iron-icon icon="icons:announcement"></iron-icon>
|
|
75
|
-
<div>
|
|
76
|
-
<h3>[[ _('Filter too strict', t) ]]</h3>
|
|
77
|
-
<p>[[ _('No matches for selection', t) ]]</p>
|
|
78
89
|
</div>
|
|
79
|
-
</
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
</template>
|
|
91
|
+
<template is="dom-if" if="[[ loading ]]">
|
|
92
|
+
<div class="tableContent-empty overlay">
|
|
93
|
+
<paper-spinner-lite active="[[ loading ]]"></paper-spinner-lite>
|
|
94
|
+
<div>
|
|
95
|
+
<h3>[[ _('Data set is updating', t) ]]</h3>
|
|
96
|
+
</div>
|
|
86
97
|
</div>
|
|
98
|
+
</template>
|
|
99
|
+
<div class="tableContent-scroller" id="scroller">
|
|
100
|
+
<cosmoz-grouped-list
|
|
101
|
+
id="groupedList"
|
|
102
|
+
data="{{ sortedFilteredGroupedItems }}"
|
|
103
|
+
selected-items="{{ selectedItems }}"
|
|
104
|
+
display-empty-groups="[[ displayEmptyGroups ]]"
|
|
105
|
+
compare-items-fn="[[ compareItemsFn ]]"
|
|
106
|
+
render-item="[[ renderItem(collapsedColumns) ]]"
|
|
107
|
+
render-group="[[ renderGroup ]]"
|
|
108
|
+
></cosmoz-grouped-list>
|
|
87
109
|
</div>
|
|
88
|
-
</template>
|
|
89
|
-
<div class="tableContent-scroller" id="scroller">
|
|
90
|
-
<cosmoz-grouped-list id="groupedList"
|
|
91
|
-
data="{{ sortedFilteredGroupedItems }}"
|
|
92
|
-
selected-items="{{ selectedItems }}"
|
|
93
|
-
display-empty-groups="[[ displayEmptyGroups ]]"
|
|
94
|
-
compare-items-fn="[[ compareItemsFn ]]"
|
|
95
|
-
>
|
|
96
|
-
<template slot="templates" data-type="item">
|
|
97
|
-
<div class="item-row-wrapper">
|
|
98
|
-
<div selected$="[[ selected ]]" part="itemRow itemRow-[[ index ]]" data-index$="[[ index ]]" class="itemRow" on-click="onItemClick">
|
|
99
|
-
<input class="checkbox" type="checkbox" checked="[[ selected ]]" on-input="_onCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
|
|
100
|
-
<cosmoz-omnitable-item-row columns="[[ columns ]]"
|
|
101
|
-
selected="[[ selected ]]" expanded="{{ expanded }}" item="[[ item ]]" group-on-column="[[ groupOnColumn ]]"
|
|
102
|
-
on-item-change="[[ onItemChange ]]">
|
|
103
|
-
</cosmoz-omnitable-item-row>
|
|
104
|
-
<paper-icon-button
|
|
105
|
-
class="expand"
|
|
106
|
-
hidden="[[ isEmpty(collapsedColumns.length) ]]"
|
|
107
|
-
icon="[[ _getFoldIcon(expanded) ]]"
|
|
108
|
-
on-tap="_toggleItem"
|
|
109
|
-
></paper-icon-button>
|
|
110
|
-
</div>
|
|
111
|
-
<cosmoz-omnitable-item-expand columns="[[ collapsedColumns ]]"
|
|
112
|
-
item="[[item]]" selected="{{ selected }}" expanded$="{{ expanded }}" group-on-column="[[ groupOnColumn ]]"
|
|
113
|
-
part="item-expand" on-expanded="onExpanded">
|
|
114
|
-
</cosmoz-omnitable-item-expand>
|
|
115
|
-
</div>
|
|
116
|
-
</template>
|
|
117
|
-
<template slot="templates" data-type="group">
|
|
118
|
-
<div class$="[[ _getGroupRowClasses(folded) ]]">
|
|
119
|
-
<input class="checkbox" type="checkbox" checked="[[ selected ]]" on-input="_onCheckboxChange" disabled$="[[ !_dataIsValid ]]" />
|
|
120
|
-
<h3 class="groupRow-label">
|
|
121
|
-
<div><span>[[ groupOnColumn.title ]]</span>: </div>
|
|
122
|
-
<cosmoz-omnitable-group-row column="[[ groupOnColumn ]]" item="[[ item.items.0 ]]" selected="[[ selected ]]" folded="[[ folded ]]"
|
|
123
|
-
group="[[ item ]]"
|
|
124
|
-
></cosmoz-omnitable-group-row>
|
|
125
|
-
</h3>
|
|
126
|
-
<div class="groupRow-badge">[[ item.items.length ]]</div>
|
|
127
|
-
<paper-icon-button class="fold" icon="[[ _getFoldIcon(folded) ]]" on-tap="_toggleGroup"></paper-icon-button>
|
|
128
|
-
</div>
|
|
129
|
-
</template>
|
|
130
|
-
</cosmoz-grouped-list>
|
|
131
110
|
</div>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
111
|
+
<div class="footer">
|
|
112
|
+
<div class="footer-controls" part="footer-controls">
|
|
113
|
+
<cosmoz-autocomplete
|
|
114
|
+
label="[[ _('Group on', t) ]] [[ _computeSortDirection(groupOnDescending, t) ]]"
|
|
115
|
+
placeholder="[[ _('No grouping', t) ]]"
|
|
116
|
+
source="[[ _onCompleteValues(columns, 'groupOn', groupOnColumn) ]]"
|
|
117
|
+
value="[[ groupOnColumn ]]"
|
|
118
|
+
limit="1"
|
|
119
|
+
text-property="title"
|
|
120
|
+
always-float-label
|
|
121
|
+
item-height="48"
|
|
122
|
+
item-limit="8"
|
|
123
|
+
class="footer-control"
|
|
124
|
+
on-change="[[ _onCompleteChange('groupOn') ]]"
|
|
125
|
+
on-select="[[ _onCompleteSelect ]]"
|
|
126
|
+
default-index="-1"
|
|
127
|
+
show-single
|
|
128
|
+
>
|
|
129
|
+
<svg
|
|
130
|
+
slot="suffix"
|
|
131
|
+
viewBox="0 0 24 24"
|
|
132
|
+
preserveAspectRatio="xMidYMid meet"
|
|
133
|
+
focusable="false"
|
|
134
|
+
width="24"
|
|
135
|
+
fill="currentColor"
|
|
136
|
+
>
|
|
137
|
+
<path d="M7 10l5 5 5-5z"></path>
|
|
138
|
+
</svg>
|
|
139
|
+
</cosmoz-autocomplete>
|
|
140
|
+
<cosmoz-autocomplete
|
|
141
|
+
label="[[ _('Sort on', t) ]] [[ _computeSortDirection(descending, t) ]]"
|
|
142
|
+
placeholder="[[ _('No sorting', t) ]]"
|
|
143
|
+
source="[[ _onCompleteValues(columns, 'sortOn', sortOnColumn) ]]"
|
|
144
|
+
value="[[ sortOnColumn ]]"
|
|
145
|
+
limit="1"
|
|
146
|
+
text-property="title"
|
|
147
|
+
always-float-label
|
|
148
|
+
item-height="48"
|
|
149
|
+
item-limit="8"
|
|
150
|
+
class="footer-control"
|
|
151
|
+
on-change="[[ _onCompleteChange('sortOn') ]]"
|
|
152
|
+
on-select="[[ _onCompleteSelect ]]"
|
|
153
|
+
default-index="-1"
|
|
154
|
+
show-single
|
|
155
|
+
>
|
|
156
|
+
<svg
|
|
157
|
+
slot="suffix"
|
|
158
|
+
viewBox="0 0 24 24"
|
|
159
|
+
preserveAspectRatio="xMidYMid meet"
|
|
160
|
+
focusable="false"
|
|
161
|
+
width="24"
|
|
162
|
+
fill="currentColor"
|
|
163
|
+
>
|
|
164
|
+
<path d="M7 10l5 5 5-5z"></path>
|
|
165
|
+
</svg>
|
|
166
|
+
</cosmoz-autocomplete>
|
|
167
|
+
<slot id="controlsSlot" name="controls"></slot>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="footer-tableStats">
|
|
170
|
+
<span
|
|
171
|
+
>[[ ngettext('{0} group', '{0} groups', groupsCount, t) ]]</span
|
|
172
|
+
>
|
|
173
|
+
<span
|
|
174
|
+
>[[ _renderRowStats(numProcessedItems, totalAvailable, t) ]]</span
|
|
175
|
+
>
|
|
176
|
+
</div>
|
|
177
|
+
<cosmoz-bottom-bar
|
|
178
|
+
id="bottomBar"
|
|
179
|
+
class="footer-actionBar"
|
|
180
|
+
match-parent
|
|
181
|
+
on-action="_onAction"
|
|
182
|
+
active$="[[ !isEmpty(selectedItems.length) ]]"
|
|
148
183
|
>
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<span>[[ _renderRowStats(numProcessedItems, totalAvailable, t) ]]</span>
|
|
156
|
-
</div>
|
|
157
|
-
<cosmoz-bottom-bar id="bottomBar" class="footer-actionBar" match-parent
|
|
158
|
-
on-action="_onAction" active$="[[ !isEmpty(selectedItems.length) ]]">
|
|
159
|
-
<slot name="info" slot="info">[[ ngettext('{0} selected item', '{0} selected items', selectedItems.length, t) ]]</slot>
|
|
160
|
-
<slot name="actions" id="actions"></slot>
|
|
161
|
-
<!-- These slots are needed by cosmoz-bottom-bar
|
|
184
|
+
<slot name="info" slot="info"
|
|
185
|
+
>[[ ngettext('{0} selected item', '{0} selected items',
|
|
186
|
+
selectedItems.length, t) ]]</slot
|
|
187
|
+
>
|
|
188
|
+
<slot name="actions" id="actions"></slot>
|
|
189
|
+
<!-- These slots are needed by cosmoz-bottom-bar
|
|
162
190
|
as it might change the slot of the actions to distribute them in the menu -->
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
191
|
+
<slot name="bottom-bar-toolbar" slot="bottom-bar-toolbar"></slot>
|
|
192
|
+
<slot name="bottom-bar-menu" slot="bottom-bar-menu"></slot>
|
|
193
|
+
<cosmoz-dropdown-menu slot="extra" placement="[[ topPlacement ]]">
|
|
194
|
+
<svg
|
|
195
|
+
slot="button"
|
|
196
|
+
width="14"
|
|
197
|
+
height="18"
|
|
198
|
+
viewBox="0 0 14 18"
|
|
199
|
+
fill="none"
|
|
200
|
+
stroke="currentColor"
|
|
201
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
202
|
+
>
|
|
203
|
+
<path
|
|
204
|
+
d="M1 8.5L7.00024 14.5L13 8.5"
|
|
205
|
+
stroke-width="2"
|
|
206
|
+
stroke-linecap="round"
|
|
207
|
+
stroke-linejoin="round"
|
|
208
|
+
/>
|
|
209
|
+
<path d="M13 17L1 17" stroke-width="2" stroke-linecap="round" />
|
|
210
|
+
<path d="M7 1V13" stroke-width="2" stroke-linecap="round" />
|
|
211
|
+
</svg>
|
|
212
|
+
<button on-click="_saveAsCsvAction">
|
|
213
|
+
[[ _('Save as CSV', t) ]]
|
|
214
|
+
</button>
|
|
215
|
+
<button on-click="_saveAsXlsxAction">
|
|
216
|
+
[[ _('Save as XLSX', t) ]]
|
|
217
|
+
</button>
|
|
218
|
+
<slot name="download-menu"></slot>
|
|
219
|
+
</cosmoz-dropdown-menu>
|
|
220
|
+
</cosmoz-bottom-bar>
|
|
221
|
+
</div>
|
|
176
222
|
</div>
|
|
177
|
-
</div>
|
|
178
223
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
`;
|
|
224
|
+
<div id="columns">
|
|
225
|
+
<slot id="columnsSlot"></slot>
|
|
226
|
+
</div>
|
|
227
|
+
`;
|
|
183
228
|
template.setAttribute('strip-whitespace', '');
|
|
184
229
|
return template;
|
|
185
230
|
}
|
|
186
231
|
|
|
232
|
+
renderItem(collapsedColumns) {
|
|
233
|
+
return (
|
|
234
|
+
item,
|
|
235
|
+
index,
|
|
236
|
+
{ selected, expanded, toggleCollapse }
|
|
237
|
+
) => {
|
|
238
|
+
return litHtml`
|
|
239
|
+
<div class="item-row-wrapper">
|
|
240
|
+
<div ?selected=${selected}
|
|
241
|
+
part="itemRow itemRow-${item[indexSymbol]}"
|
|
242
|
+
.dataIndex=${item[indexSymbol]}
|
|
243
|
+
.dataItem=${item}
|
|
244
|
+
class="itemRow"
|
|
245
|
+
@click=${this.onItemClick}
|
|
246
|
+
>
|
|
247
|
+
<input class="checkbox"
|
|
248
|
+
type="checkbox"
|
|
249
|
+
.checked=${selected}
|
|
250
|
+
.dataItem=${item}
|
|
251
|
+
@input=${this._onCheckboxChange}
|
|
252
|
+
?disabled=${!this._dataIsValid} />
|
|
253
|
+
<cosmoz-omnitable-item-row
|
|
254
|
+
.columns=${this.columns}
|
|
255
|
+
.index=${index}
|
|
256
|
+
.selected=${selected}
|
|
257
|
+
.expanded=${expanded}
|
|
258
|
+
.item=${item}
|
|
259
|
+
.groupOnColumn=${this.groupOnColumn}
|
|
260
|
+
.onItemChange=${this.onItemChange}>
|
|
261
|
+
</cosmoz-omnitable-item-row>
|
|
262
|
+
<paper-icon-button
|
|
263
|
+
class="expand"
|
|
264
|
+
?hidden=${isEmpty(collapsedColumns.length)}
|
|
265
|
+
.icon=${this._getFoldIcon(expanded)}
|
|
266
|
+
@click=${toggleCollapse}
|
|
267
|
+
></paper-icon-button>
|
|
268
|
+
</div>
|
|
269
|
+
<cosmoz-omnitable-item-expand .columns=${collapsedColumns}
|
|
270
|
+
.item=${item}
|
|
271
|
+
.index=${index}
|
|
272
|
+
?selected=${selected}
|
|
273
|
+
?expanded=${expanded}
|
|
274
|
+
.groupOnColumn=${this.groupOnColumn}
|
|
275
|
+
part="item-expand">
|
|
276
|
+
</cosmoz-omnitable-item-expand>
|
|
277
|
+
</div>`;
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
renderGroup(item, index, { selected, folded, toggleFold }) {
|
|
282
|
+
return litHtml`
|
|
283
|
+
<div class="${this._getGroupRowClasses(folded)}"
|
|
284
|
+
part="groupRow groupRow-${item[indexSymbol]}">
|
|
285
|
+
<input class="checkbox"
|
|
286
|
+
type="checkbox"
|
|
287
|
+
.checked=${selected}
|
|
288
|
+
.dataItem=${item}
|
|
289
|
+
@input=${this._onCheckboxChange}
|
|
290
|
+
?disabled=${!this._dataIsValid} />
|
|
291
|
+
<h3 class="groupRow-label">
|
|
292
|
+
<div><span>${this.groupOnColumn?.title}</span>: </div>
|
|
293
|
+
<cosmoz-omnitable-group-row
|
|
294
|
+
.column=${this.groupOnColumn}
|
|
295
|
+
.item=${item.items?.[0]}
|
|
296
|
+
.selected=${selected}
|
|
297
|
+
.folded=${folded}
|
|
298
|
+
.group=${item}
|
|
299
|
+
></cosmoz-omnitable-group-row>
|
|
300
|
+
</h3>
|
|
301
|
+
<div class="groupRow-badge">${item.items.length}</div>
|
|
302
|
+
<paper-icon-button
|
|
303
|
+
class="fold"
|
|
304
|
+
.icon=${this._getFoldIcon(folded)}
|
|
305
|
+
@click=${toggleFold}></paper-icon-button>
|
|
306
|
+
</div>`;
|
|
307
|
+
}
|
|
308
|
+
|
|
187
309
|
/* eslint-disable-next-line max-lines-per-function */
|
|
188
310
|
static get properties() {
|
|
189
311
|
return {
|
|
190
312
|
/**
|
|
191
|
-
|
|
192
|
-
|
|
313
|
+
* Filename when saving as CSV
|
|
314
|
+
*/
|
|
193
315
|
csvFilename: { type: String, value: 'omnitable.csv' },
|
|
194
316
|
|
|
195
317
|
/**
|
|
196
|
-
|
|
197
|
-
|
|
318
|
+
* Filename when saving as XLSX
|
|
319
|
+
*/
|
|
198
320
|
xlsxFilename: { type: String, value: 'omnitable.xlsx' },
|
|
199
321
|
|
|
200
322
|
/**
|
|
201
|
-
|
|
202
|
-
|
|
323
|
+
* Sheet name when saving as XLSX
|
|
324
|
+
*/
|
|
203
325
|
xlsxSheetname: { type: String, value: 'Omnitable' },
|
|
204
326
|
|
|
205
327
|
/**
|
|
206
|
-
|
|
207
|
-
|
|
328
|
+
* Array used to list items.
|
|
329
|
+
*/
|
|
208
330
|
data: { type: Array },
|
|
209
331
|
|
|
210
332
|
/**
|
|
211
333
|
* This function is used to determine which items are kept selected across data updates
|
|
212
|
-
* TODO: probably broken
|
|
213
334
|
*/
|
|
214
335
|
compareItemsFn: Function,
|
|
215
336
|
|
|
216
337
|
/**
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
_dataIsValid: {
|
|
338
|
+
* True if data is a valid and not empty array.
|
|
339
|
+
*/
|
|
340
|
+
_dataIsValid: {
|
|
341
|
+
type: Boolean,
|
|
342
|
+
value: false,
|
|
343
|
+
computed: '_computeDataValidity(data.*)',
|
|
344
|
+
},
|
|
220
345
|
|
|
221
346
|
/**
|
|
222
|
-
|
|
223
|
-
|
|
347
|
+
* If set to true, then group a row will be displayed for groups that contain no items.
|
|
348
|
+
*/
|
|
224
349
|
displayEmptyGroups: { type: Boolean, value: false },
|
|
225
350
|
|
|
226
351
|
/**
|
|
227
|
-
|
|
228
|
-
|
|
352
|
+
* Specific columns to enable
|
|
353
|
+
*/
|
|
229
354
|
enabledColumns: { type: Array, notify: true },
|
|
230
355
|
|
|
231
356
|
/**
|
|
232
|
-
|
|
233
|
-
|
|
357
|
+
* Whether bottom-bar has actions.
|
|
358
|
+
*/
|
|
234
359
|
hasActions: { type: Boolean, value: false },
|
|
235
360
|
|
|
236
361
|
/**
|
|
237
|
-
|
|
238
|
-
|
|
362
|
+
* Shows a loading overlay to indicate data will be updated
|
|
363
|
+
*/
|
|
239
364
|
loading: { type: Boolean, value: false },
|
|
240
365
|
|
|
241
366
|
/**
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
selectedItems: { type: Array, notify: true },
|
|
367
|
+
* List of selected rows/items in `data`.
|
|
368
|
+
*/
|
|
369
|
+
selectedItems: { type: Array, notify: true, value: () => [] },
|
|
245
370
|
descending: { type: Boolean, value: false, notify: true },
|
|
246
371
|
sortOn: { type: String, value: '', notify: true },
|
|
247
372
|
groupOnDescending: { type: Boolean, value: false },
|
|
248
373
|
|
|
249
374
|
/**
|
|
250
|
-
|
|
251
|
-
|
|
375
|
+
* The column name to group on.
|
|
376
|
+
*/
|
|
252
377
|
groupOn: { type: String, notify: true, value: '' },
|
|
253
378
|
|
|
254
379
|
/**
|
|
255
|
-
|
|
256
|
-
|
|
380
|
+
* Sorted items structure after filtering and grouping.
|
|
381
|
+
*/
|
|
257
382
|
sortedFilteredGroupedItems: { type: Array, notify: true },
|
|
258
383
|
|
|
259
384
|
/**
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
columns: { type: Array, notify: true, value: () => []},
|
|
385
|
+
* List of columns definition for this table.
|
|
386
|
+
*/
|
|
387
|
+
columns: { type: Array, notify: true, value: () => [] },
|
|
263
388
|
settings: { type: Object, notify: true },
|
|
264
|
-
_filterIsTooStrict: {
|
|
389
|
+
_filterIsTooStrict: {
|
|
390
|
+
type: Boolean,
|
|
391
|
+
computed:
|
|
392
|
+
'_computeFilterIsTooStrict(_dataIsValid, sortedFilteredGroupedItems.length)',
|
|
393
|
+
},
|
|
265
394
|
hashParam: { type: String },
|
|
266
395
|
|
|
267
396
|
/**
|
|
@@ -271,21 +400,22 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
271
400
|
computedBarHeight: { type: Number },
|
|
272
401
|
settingsId: { type: String, value: undefined },
|
|
273
402
|
topPlacement: {
|
|
274
|
-
value: ['top-right', ...defaultPlacement]
|
|
275
|
-
}
|
|
403
|
+
value: ['top-right', ...defaultPlacement],
|
|
404
|
+
},
|
|
276
405
|
};
|
|
277
406
|
}
|
|
278
407
|
|
|
279
408
|
static get observers() {
|
|
280
|
-
return [
|
|
281
|
-
'_selectedItemsChanged(selectedItems.*)'
|
|
282
|
-
];
|
|
409
|
+
return ['_selectedItemsChanged(selectedItems.*)'];
|
|
283
410
|
}
|
|
284
411
|
|
|
285
412
|
constructor() {
|
|
286
413
|
super();
|
|
287
414
|
|
|
288
415
|
this._onKey = this._onKey.bind(this);
|
|
416
|
+
this._onCheckboxChange = this._onCheckboxChange.bind(this);
|
|
417
|
+
this.renderItem = this.renderItem.bind(this);
|
|
418
|
+
this.renderGroup = this.renderGroup.bind(this);
|
|
289
419
|
}
|
|
290
420
|
|
|
291
421
|
connectedCallback() {
|
|
@@ -293,7 +423,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
293
423
|
|
|
294
424
|
this.$.groupedList.scrollTarget = this.$.scroller;
|
|
295
425
|
|
|
296
|
-
this.addEventListener('update-item-size', this._onUpdateItemSize);
|
|
297
426
|
window.addEventListener('keydown', this._onKey);
|
|
298
427
|
window.addEventListener('keyup', this._onKey);
|
|
299
428
|
}
|
|
@@ -301,7 +430,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
301
430
|
disconnectedCallback() {
|
|
302
431
|
super.disconnectedCallback();
|
|
303
432
|
|
|
304
|
-
this.removeEventListener('update-item-size', this._onUpdateItemSize);
|
|
305
433
|
window.removeEventListener('keydown', this._onKey);
|
|
306
434
|
window.removeEventListener('keyup', this._onKey);
|
|
307
435
|
}
|
|
@@ -318,15 +446,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
318
446
|
|
|
319
447
|
_computeSortDirection(descending) {
|
|
320
448
|
const direction = descending ? this._('Descending') : this._('Ascending');
|
|
321
|
-
return `(${
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
_onUpdateItemSize(event) {
|
|
325
|
-
const { detail } = event;
|
|
326
|
-
if (detail && detail.item) {
|
|
327
|
-
this.$.groupedList.updateSize(detail.item);
|
|
328
|
-
}
|
|
329
|
-
event.stopPropagation();
|
|
449
|
+
return `(${direction})`;
|
|
330
450
|
}
|
|
331
451
|
|
|
332
452
|
_onKey(e) {
|
|
@@ -335,7 +455,7 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
335
455
|
}
|
|
336
456
|
|
|
337
457
|
_onCheckboxChange(event) {
|
|
338
|
-
const item = event.
|
|
458
|
+
const item = event.target.dataItem,
|
|
339
459
|
selected = event.target.checked;
|
|
340
460
|
if (this._shiftKey) {
|
|
341
461
|
this.$.groupedList.toggleSelectTo(item, selected);
|
|
@@ -363,7 +483,12 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
363
483
|
* @returns {undefined}
|
|
364
484
|
*/
|
|
365
485
|
_saveAsXlsxAction() {
|
|
366
|
-
saveAsXlsxAction(
|
|
486
|
+
saveAsXlsxAction(
|
|
487
|
+
this.columns,
|
|
488
|
+
this.selectedItems,
|
|
489
|
+
this.xlsxFilename,
|
|
490
|
+
this.xlsxSheetname
|
|
491
|
+
);
|
|
367
492
|
}
|
|
368
493
|
|
|
369
494
|
/** view functions */
|
|
@@ -375,23 +500,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
375
500
|
return expanded ? 'expand-less' : 'expand-more';
|
|
376
501
|
}
|
|
377
502
|
|
|
378
|
-
/**
|
|
379
|
-
* Toggle folding of a group
|
|
380
|
-
* @param {Event} event event
|
|
381
|
-
* @returns {undefined}
|
|
382
|
-
*/
|
|
383
|
-
_toggleGroup(event) {
|
|
384
|
-
this.$.groupedList.toggleFold(event.model.item);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
_toggleItem(event) {
|
|
388
|
-
const item = event.model.item;
|
|
389
|
-
this.$.groupedList.toggleCollapse(item);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
onExpanded() {
|
|
393
|
-
requestAnimationFrame(() => this.$.groupedList.$.list._render());
|
|
394
|
-
}
|
|
395
503
|
/**
|
|
396
504
|
* Turn an `action` event into a `run` event
|
|
397
505
|
* @param {Event} event `action` event
|
|
@@ -399,20 +507,28 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
399
507
|
* @returns {undefined}
|
|
400
508
|
*/
|
|
401
509
|
_onAction(event, detail) {
|
|
402
|
-
detail.item.dispatchEvent(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
510
|
+
detail.item.dispatchEvent(
|
|
511
|
+
new window.CustomEvent('run', {
|
|
512
|
+
bubbles: true,
|
|
513
|
+
cancelable: true,
|
|
514
|
+
detail: {
|
|
515
|
+
omnitable: this,
|
|
516
|
+
items: this.selectedItems,
|
|
517
|
+
},
|
|
518
|
+
})
|
|
519
|
+
);
|
|
410
520
|
event.stopPropagation();
|
|
411
521
|
}
|
|
412
522
|
|
|
413
523
|
_selectedItemsChanged(change) {
|
|
414
|
-
if (
|
|
415
|
-
|
|
524
|
+
if (
|
|
525
|
+
change.path === 'selectedItems' ||
|
|
526
|
+
change.path === 'selectedItems.splices'
|
|
527
|
+
) {
|
|
528
|
+
this._allSelected =
|
|
529
|
+
this.data &&
|
|
530
|
+
this.data.length > 0 &&
|
|
531
|
+
change.base.length === this.data.length;
|
|
416
532
|
}
|
|
417
533
|
}
|
|
418
534
|
|
|
@@ -426,20 +542,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
426
542
|
|
|
427
543
|
// TODO: move to publicInterface mixin
|
|
428
544
|
/** PUBLIC */
|
|
429
|
-
|
|
430
|
-
suppressNextScrollReset() {
|
|
431
|
-
const list = this.$.groupedList.$.list;
|
|
432
|
-
// HACK: Replace _resetScrollPosition for one call to maintain scroll position
|
|
433
|
-
if (list._scrollTop > 0 && !list._resetScrollPosition.suppressed) {
|
|
434
|
-
const reset = list._resetScrollPosition;
|
|
435
|
-
list._resetScrollPosition = () => {
|
|
436
|
-
// restore hack
|
|
437
|
-
list._resetScrollPosition = reset;
|
|
438
|
-
};
|
|
439
|
-
list._resetScrollPosition.suppressed = true;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
545
|
/**
|
|
444
546
|
* Remove multiple items from `data`
|
|
445
547
|
* @param {Array} items Array of items to remove
|
|
@@ -481,7 +583,6 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
481
583
|
}
|
|
482
584
|
}
|
|
483
585
|
replaceItemAtIndex(index, newItem) {
|
|
484
|
-
this.suppressNextScrollReset();
|
|
485
586
|
this.splice('data', index, 1, newItem);
|
|
486
587
|
this.data = this.data.slice();
|
|
487
588
|
}
|
|
@@ -513,16 +614,26 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
513
614
|
|
|
514
615
|
_renderRowStats(numRows, totalAvailable) {
|
|
515
616
|
if (Number.isInteger(totalAvailable) && totalAvailable > numRows) {
|
|
516
|
-
return this.ngettext(
|
|
617
|
+
return this.ngettext(
|
|
618
|
+
'{1} / {0} row',
|
|
619
|
+
'{1} / {0} rows',
|
|
620
|
+
totalAvailable,
|
|
621
|
+
numRows
|
|
622
|
+
);
|
|
517
623
|
}
|
|
518
624
|
return this.ngettext('{0} row', '{0} rows', numRows);
|
|
519
625
|
}
|
|
520
626
|
|
|
521
|
-
_onCompleteValues(columns, type, value) {
|
|
522
|
-
return
|
|
627
|
+
_onCompleteValues(columns, type, value) {
|
|
628
|
+
return (
|
|
629
|
+
columns
|
|
630
|
+
?.filter?.((c) => c[type])
|
|
631
|
+
/* eslint-disable-next-line no-bitwise */
|
|
632
|
+
.sort((a, b) => ((b === value) >> 0) - ((a === value) >> 0))
|
|
633
|
+
);
|
|
523
634
|
}
|
|
524
635
|
|
|
525
|
-
_onCompleteSelect(newVal, {value, onChange, onText, limit}) {
|
|
636
|
+
_onCompleteSelect(newVal, { value, onChange, onText, limit }) {
|
|
526
637
|
onText('');
|
|
527
638
|
onChange([...without(newVal)(value), newVal].slice(-limit));
|
|
528
639
|
}
|
|
@@ -531,11 +642,14 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
531
642
|
return (val, close) => {
|
|
532
643
|
const value = (val[0] ?? val)?.name ?? '',
|
|
533
644
|
setter = type === 'groupOn' ? this.setGroupOn : this.setSortOn,
|
|
534
|
-
directionSetter =
|
|
645
|
+
directionSetter =
|
|
646
|
+
type === 'groupOn' ? this.setGroupOnDescending : this.setDescending;
|
|
535
647
|
|
|
536
|
-
setter(oldValue => {
|
|
648
|
+
setter((oldValue) => {
|
|
537
649
|
if (value) {
|
|
538
|
-
directionSetter(oldDirection =>
|
|
650
|
+
directionSetter((oldDirection) =>
|
|
651
|
+
value === oldValue ? !oldDirection : false
|
|
652
|
+
);
|
|
539
653
|
} else {
|
|
540
654
|
directionSetter(null);
|
|
541
655
|
}
|
|
@@ -554,14 +668,13 @@ class Omnitable extends hauntedPolymer(useOmnitable)(mixin({ isEmpty }, translat
|
|
|
554
668
|
return;
|
|
555
669
|
}
|
|
556
670
|
|
|
557
|
-
|
|
558
671
|
this.dispatchEvent(
|
|
559
672
|
new window.CustomEvent('omnitable-item-click', {
|
|
560
673
|
bubbles: true,
|
|
561
674
|
composed: true,
|
|
562
675
|
detail: {
|
|
563
|
-
item: e.
|
|
564
|
-
index: e.
|
|
676
|
+
item: e.currentTarget.dataItem,
|
|
677
|
+
index: e.currentTarget.dataIndex,
|
|
565
678
|
},
|
|
566
679
|
})
|
|
567
680
|
);
|
|
@@ -575,6 +688,5 @@ const tmplt = `
|
|
|
575
688
|
<slot name="bottom-bar-menu" slot="bottom-bar-menu"></slot>
|
|
576
689
|
`;
|
|
577
690
|
|
|
578
|
-
export const
|
|
579
|
-
actionSlots = litHtml([tmplt]),
|
|
691
|
+
export const actionSlots = litHtml([tmplt]),
|
|
580
692
|
actionSlotsPolymer = html([tmplt]);
|