1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | import settings from '../../globals/js/settings';
|
9 | import mixin from '../../globals/js/misc/mixin';
|
10 | import createComponent from '../../globals/js/mixins/create-component';
|
11 | import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
|
12 | import eventedState from '../../globals/js/mixins/evented-state';
|
13 | import handles from '../../globals/js/mixins/handles';
|
14 | import eventMatches from '../../globals/js/misc/event-matches';
|
15 | import on from '../../globals/js/misc/on';
|
16 |
|
17 | const toArray = (arrayLike) => Array.prototype.slice.call(arrayLike);
|
18 |
|
19 | class DataTable extends mixin(
|
20 | createComponent,
|
21 | initComponentBySearch,
|
22 | eventedState,
|
23 | handles
|
24 | ) {
|
25 | |
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | constructor(element, options) {
|
41 | super(element, options);
|
42 |
|
43 | this.container = element.parentNode;
|
44 | this.toolbarEl = this.element.querySelector(this.options.selectorToolbar);
|
45 | this.batchActionEl = this.element.querySelector(
|
46 | this.options.selectorActions
|
47 | );
|
48 | this.countEl = this.element.querySelector(this.options.selectorCount);
|
49 | this.cancelEl = this.element.querySelector(
|
50 | this.options.selectorActionCancel
|
51 | );
|
52 | this.tableHeaders = this.element.querySelectorAll('th');
|
53 | this.tableBody = this.element.querySelector(this.options.selectorTableBody);
|
54 | this.expandCells = [];
|
55 | this.expandableRows = [];
|
56 | this.parentRows = [];
|
57 |
|
58 | this.refreshRows();
|
59 |
|
60 | this.manage(on(this.element, 'mouseover', this._expandableHoverToggle));
|
61 | this.manage(on(this.element, 'mouseout', this._expandableHoverToggle));
|
62 |
|
63 | this.manage(
|
64 | on(this.element, 'click', (evt) => {
|
65 | const eventElement = eventMatches(evt, this.options.eventTrigger);
|
66 | const searchContainer = this.element.querySelector(
|
67 | this.options.selectorToolbarSearchContainer
|
68 | );
|
69 |
|
70 | if (eventElement) {
|
71 | this._toggleState(eventElement, evt);
|
72 | }
|
73 |
|
74 | if (searchContainer) {
|
75 | this._handleDocumentClick(evt);
|
76 | }
|
77 | })
|
78 | );
|
79 |
|
80 | this.manage(on(this.element, 'keydown', this._keydownHandler));
|
81 |
|
82 | this.state = {
|
83 | checkboxCount: 0,
|
84 | };
|
85 | }
|
86 |
|
87 | _handleDocumentClick(evt) {
|
88 | const searchContainer = this.element.querySelector(
|
89 | this.options.selectorToolbarSearchContainer
|
90 | );
|
91 | const searchEvent = eventMatches(evt, this.options.selectorSearchMagnifier);
|
92 | const activeSearch = searchContainer.classList.contains(
|
93 | this.options.classToolbarSearchActive
|
94 | );
|
95 |
|
96 | if (searchContainer && searchEvent) {
|
97 | this.activateSearch(searchContainer);
|
98 | }
|
99 |
|
100 | if (activeSearch) {
|
101 | this.deactivateSearch(searchContainer, evt);
|
102 | }
|
103 | }
|
104 |
|
105 | activateSearch(container) {
|
106 | const input = container.querySelector(this.options.selectorSearchInput);
|
107 | container.classList.add(this.options.classToolbarSearchActive);
|
108 | input.focus();
|
109 | }
|
110 |
|
111 | deactivateSearch(container, evt) {
|
112 | const trigger = container.querySelector(
|
113 | this.options.selectorSearchMagnifier
|
114 | );
|
115 | const input = container.querySelector(this.options.selectorSearchInput);
|
116 | const svg = trigger.querySelector('svg');
|
117 | if (
|
118 | input.value.length === 0 &&
|
119 | evt.target !== input &&
|
120 | evt.target !== trigger &&
|
121 | evt.target !== svg
|
122 | ) {
|
123 | container.classList.remove(this.options.classToolbarSearchActive);
|
124 | trigger.focus();
|
125 | }
|
126 |
|
127 | if (evt.which === 27 && evt.target === input) {
|
128 | container.classList.remove(this.options.classToolbarSearchActive);
|
129 | trigger.focus();
|
130 | }
|
131 | }
|
132 |
|
133 | _sortToggle = (detail) => {
|
134 | const { element, previousValue } = detail;
|
135 |
|
136 | toArray(this.tableHeaders).forEach((header) => {
|
137 | const sortEl = header.querySelector(this.options.selectorTableSort);
|
138 |
|
139 | if (sortEl !== null && sortEl !== element) {
|
140 | sortEl.classList.remove(this.options.classTableSortActive);
|
141 | sortEl.classList.remove(this.options.classTableSortAscending);
|
142 | }
|
143 | });
|
144 |
|
145 | if (!previousValue) {
|
146 | element.dataset.previousValue = 'ascending';
|
147 | element.classList.add(this.options.classTableSortActive);
|
148 | element.classList.add(this.options.classTableSortAscending);
|
149 | } else if (previousValue === 'ascending') {
|
150 | element.dataset.previousValue = 'descending';
|
151 | element.classList.add(this.options.classTableSortActive);
|
152 | element.classList.remove(this.options.classTableSortAscending);
|
153 | } else if (previousValue === 'descending') {
|
154 | element.removeAttribute('data-previous-value');
|
155 | element.classList.remove(this.options.classTableSortActive);
|
156 | element.classList.remove(this.options.classTableSortAscending);
|
157 | }
|
158 | };
|
159 |
|
160 | _selectToggle = (detail) => {
|
161 | const { element } = detail;
|
162 | const { checked } = element;
|
163 |
|
164 |
|
165 | this.state.checkboxCount += checked ? 1 : -1;
|
166 | this.countEl.textContent = this.state.checkboxCount;
|
167 |
|
168 | const row = element.parentNode.parentNode;
|
169 |
|
170 | row.classList.toggle(this.options.classTableSelected);
|
171 |
|
172 |
|
173 | this._actionBarToggle(this.state.checkboxCount > 0);
|
174 | };
|
175 |
|
176 | _selectAllToggle = ({ element }) => {
|
177 | const { checked } = element;
|
178 |
|
179 | const inputs = toArray(
|
180 | this.element.querySelectorAll(this.options.selectorCheckbox)
|
181 | );
|
182 |
|
183 | this.state.checkboxCount = checked ? inputs.length - 1 : 0;
|
184 |
|
185 | inputs.forEach((item) => {
|
186 | item.checked = checked;
|
187 |
|
188 | const row = item.parentNode.parentNode;
|
189 | if (checked && row) {
|
190 | row.classList.add(this.options.classTableSelected);
|
191 | } else {
|
192 | row.classList.remove(this.options.classTableSelected);
|
193 | }
|
194 | });
|
195 |
|
196 | this._actionBarToggle(this.state.checkboxCount > 0);
|
197 |
|
198 | if (this.batchActionEl) {
|
199 | this.countEl.textContent = this.state.checkboxCount;
|
200 | }
|
201 | };
|
202 |
|
203 | _actionBarCancel = () => {
|
204 | const inputs = toArray(
|
205 | this.element.querySelectorAll(this.options.selectorCheckbox)
|
206 | );
|
207 | const row = toArray(
|
208 | this.element.querySelectorAll(this.options.selectorTableSelected)
|
209 | );
|
210 |
|
211 | row.forEach((item) => {
|
212 | item.classList.remove(this.options.classTableSelected);
|
213 | });
|
214 |
|
215 | inputs.forEach((item) => {
|
216 | item.checked = false;
|
217 | });
|
218 |
|
219 | this.state.checkboxCount = 0;
|
220 | this._actionBarToggle(false);
|
221 |
|
222 | if (this.batchActionEl) {
|
223 | this.countEl.textContent = this.state.checkboxCount;
|
224 | }
|
225 | };
|
226 |
|
227 | _actionBarToggle = (toggleOn) => {
|
228 | let handleTransitionEnd;
|
229 | const transition = (evt) => {
|
230 | if (handleTransitionEnd) {
|
231 | handleTransitionEnd = this.unmanage(handleTransitionEnd).release();
|
232 | }
|
233 |
|
234 | if (evt.target.matches(this.options.selectorActions)) {
|
235 | if (this.batchActionEl.dataset.active === 'false') {
|
236 | this.batchActionEl.setAttribute('tabIndex', -1);
|
237 | } else {
|
238 | this.batchActionEl.setAttribute('tabIndex', 0);
|
239 | }
|
240 | }
|
241 | };
|
242 |
|
243 | if (toggleOn) {
|
244 | this.batchActionEl.dataset.active = true;
|
245 | this.batchActionEl.classList.add(this.options.classActionBarActive);
|
246 | } else if (this.batchActionEl) {
|
247 | this.batchActionEl.dataset.active = false;
|
248 | this.batchActionEl.classList.remove(this.options.classActionBarActive);
|
249 | }
|
250 | if (this.batchActionEl) {
|
251 | handleTransitionEnd = this.manage(
|
252 | on(this.batchActionEl, 'transitionend', transition)
|
253 | );
|
254 | }
|
255 | };
|
256 |
|
257 | _rowExpandToggle = ({ element, forceExpand }) => {
|
258 | const parent = element.closest(this.options.eventParentContainer);
|
259 |
|
260 |
|
261 | const shouldExpand =
|
262 | forceExpand != null
|
263 | ? forceExpand
|
264 | : element.dataset.previousValue === undefined ||
|
265 | element.dataset.previousValue === 'expanded';
|
266 |
|
267 | if (shouldExpand) {
|
268 | element.dataset.previousValue = 'collapsed';
|
269 | parent.classList.add(this.options.classExpandableRow);
|
270 | } else {
|
271 | parent.classList.remove(this.options.classExpandableRow);
|
272 | element.dataset.previousValue = 'expanded';
|
273 | const expandHeader = this.element.querySelector(
|
274 | this.options.selectorExpandHeader
|
275 | );
|
276 | if (expandHeader) {
|
277 | expandHeader.dataset.previousValue = 'expanded';
|
278 | }
|
279 | }
|
280 | };
|
281 |
|
282 | _rowExpandToggleAll = ({ element }) => {
|
283 |
|
284 | const shouldExpand =
|
285 | element.dataset.previousValue === undefined ||
|
286 | element.dataset.previousValue === 'expanded';
|
287 | element.dataset.previousValue = shouldExpand ? 'collapsed' : 'expanded';
|
288 | const expandCells = this.element.querySelectorAll(
|
289 | this.options.selectorExpandCells
|
290 | );
|
291 | Array.prototype.forEach.call(expandCells, (cell) => {
|
292 | this._rowExpandToggle({ element: cell, forceExpand: shouldExpand });
|
293 | });
|
294 | };
|
295 |
|
296 | _expandableHoverToggle = (evt) => {
|
297 | const element = eventMatches(evt, this.options.selectorChildRow);
|
298 | if (element) {
|
299 | element.previousElementSibling.classList.toggle(
|
300 | this.options.classExpandableRowHover,
|
301 | evt.type === 'mouseover'
|
302 | );
|
303 | }
|
304 | };
|
305 |
|
306 | _toggleState = (element, evt) => {
|
307 | const data = element.dataset;
|
308 | const label = data.label ? data.label : '';
|
309 | const previousValue = data.previousValue ? data.previousValue : '';
|
310 | const initialEvt = evt;
|
311 |
|
312 | this.changeState({
|
313 | group: data.event,
|
314 | element,
|
315 | label,
|
316 | previousValue,
|
317 | initialEvt,
|
318 | });
|
319 | };
|
320 |
|
321 | _keydownHandler = (evt) => {
|
322 | const searchContainer = this.element.querySelector(
|
323 | this.options.selectorToolbarSearchContainer
|
324 | );
|
325 | const searchEvent = eventMatches(evt, this.options.selectorSearchMagnifier);
|
326 | const activeSearch = searchContainer.classList.contains(
|
327 | this.options.classToolbarSearchActive
|
328 | );
|
329 |
|
330 | if (evt.which === 27) {
|
331 | this._actionBarCancel();
|
332 | }
|
333 |
|
334 | if (searchContainer && searchEvent && evt.which === 13) {
|
335 | this.activateSearch(searchContainer);
|
336 | }
|
337 |
|
338 | if (activeSearch && evt.which === 27) {
|
339 | this.deactivateSearch(searchContainer, evt);
|
340 | }
|
341 | };
|
342 |
|
343 | _changeState(detail, callback) {
|
344 | this[this.constructor.eventHandlers[detail.group]](detail);
|
345 | callback();
|
346 | }
|
347 |
|
348 | refreshRows = () => {
|
349 | const newExpandCells = toArray(
|
350 | this.element.querySelectorAll(this.options.selectorExpandCells)
|
351 | );
|
352 | const newExpandableRows = toArray(
|
353 | this.element.querySelectorAll(this.options.selectorExpandableRows)
|
354 | );
|
355 | const newParentRows = toArray(
|
356 | this.element.querySelectorAll(this.options.selectorParentRows)
|
357 | );
|
358 |
|
359 |
|
360 | if (this.parentRows.length > 0) {
|
361 | const diffParentRows = newParentRows.filter(
|
362 | (newRow) => !this.parentRows.some((oldRow) => oldRow === newRow)
|
363 | );
|
364 |
|
365 |
|
366 | if (newExpandableRows.length > 0) {
|
367 | const diffExpandableRows = diffParentRows.map(
|
368 | (newRow) => newRow.nextElementSibling
|
369 | );
|
370 | const mergedExpandableRows = [
|
371 | ...toArray(this.expandableRows),
|
372 | ...toArray(diffExpandableRows),
|
373 | ];
|
374 | this.expandableRows = mergedExpandableRows;
|
375 | }
|
376 | } else if (newExpandableRows.length > 0) {
|
377 | this.expandableRows = newExpandableRows;
|
378 | }
|
379 |
|
380 | this.expandCells = newExpandCells;
|
381 | this.parentRows = newParentRows;
|
382 | };
|
383 |
|
384 | static components = new WeakMap();
|
385 |
|
386 |
|
387 | static eventHandlers = {
|
388 | expand: '_rowExpandToggle',
|
389 | expandAll: '_rowExpandToggleAll',
|
390 | sort: '_sortToggle',
|
391 | select: '_selectToggle',
|
392 | 'select-all': '_selectAllToggle',
|
393 | 'action-bar-cancel': '_actionBarCancel',
|
394 | };
|
395 |
|
396 | static get options() {
|
397 | const { prefix } = settings;
|
398 | return {
|
399 | selectorInit: `[data-table]`,
|
400 | selectorToolbar: `.${prefix}--table--toolbar`,
|
401 | selectorActions: `.${prefix}--batch-actions`,
|
402 | selectorCount: '[data-items-selected]',
|
403 | selectorActionCancel: `.${prefix}--batch-summary__cancel`,
|
404 | selectorCheckbox: `.${prefix}--checkbox`,
|
405 | selectorExpandHeader: `th.${prefix}--table-expand`,
|
406 | selectorExpandCells: `td.${prefix}--table-expand`,
|
407 | selectorExpandableRows: `.${prefix}--expandable-row`,
|
408 | selectorParentRows: `.${prefix}--parent-row`,
|
409 | selectorChildRow: '[data-child-row]',
|
410 | selectorTableBody: 'tbody',
|
411 | selectorTableSort: `.${prefix}--table-sort`,
|
412 | selectorTableSelected: `.${prefix}--data-table--selected`,
|
413 | selectorToolbarSearchContainer: `.${prefix}--toolbar-search-container-expandable`,
|
414 | selectorSearchMagnifier: `.${prefix}--search-magnifier`,
|
415 | selectorSearchInput: `.${prefix}--search-input`,
|
416 | classExpandableRow: `${prefix}--expandable-row`,
|
417 | classExpandableRowHidden: `${prefix}--expandable-row--hidden`,
|
418 | classExpandableRowHover: `${prefix}--expandable-row--hover`,
|
419 | classTableSortAscending: `${prefix}--table-sort--ascending`,
|
420 | classTableSortActive: `${prefix}--table-sort--active`,
|
421 | classToolbarSearchActive: `${prefix}--toolbar-search-container-active`,
|
422 | classActionBarActive: `${prefix}--batch-actions--active`,
|
423 | classTableSelected: `${prefix}--data-table--selected`,
|
424 | eventBeforeExpand: `data-table-beforetoggleexpand`,
|
425 | eventAfterExpand: `data-table-aftertoggleexpand`,
|
426 | eventBeforeExpandAll: `data-table-beforetoggleexpandall`,
|
427 | eventAfterExpandAll: `data-table-aftertoggleexpandall`,
|
428 | eventBeforeSort: `data-table-beforetogglesort`,
|
429 | eventAfterSort: `data-table-aftertogglesort`,
|
430 | eventTrigger: '[data-event]',
|
431 | eventParentContainer: '[data-parent-row]',
|
432 | };
|
433 | }
|
434 | }
|
435 |
|
436 | export default DataTable;
|