import { queryAll, queryOne } from '@ecl/dom-utils';import getSystem from '@ecl/builder/utils/getSystem';import iconSvgAllArrow from '@ecl/resources-icons/dist/svg/all/solid-arrow.svg';const system = getSystem();const iconSvgAllArrowSize = system === 'eu' ? 'm' : 'xs';/** * @param {HTMLElement} element DOM element for component instantiation and scope * @param {Object} options * @param {String} options.sortSelector Selector for toggling element * @param {String} options.sortLabelSelector Selector for sorting button label * @param {Boolean} options.attachClickListener */export class Table { /** * @static * Shorthand for instance creation and initialisation. * * @param {HTMLElement} root DOM element for component instantiation and scope * * @return {Table} An instance of table. */ static autoInit(root, { TABLE: defaultOptions = {} } = {}) { const table = new Table(root, defaultOptions); table.init(); root.ECLTable = table; return table; } constructor( element, { sortSelector = '[data-ecl-table-sort-toggle]', sortLabelSelector = 'data-ecl-table-sort-label', } = {}, ) { // Check element if (!element || element.nodeType !== Node.ELEMENT_NODE) { throw new TypeError( 'DOM element should be given to initialize this widget.', ); } this.element = element; // Options this.sortSelector = sortSelector; this.sortLabelSelector = sortLabelSelector; // Private variables this.sortHeadings = null; // Bind `this` for use in callbacks this.handleClickOnSort = this.handleClickOnSort.bind(this); } /** * @returns {HTMLElement} */ static createSortIcon(customClass) { const tempElement = document.createElement('span'); tempElement.innerHTML = iconSvgAllArrow; // avoiding the use of not-so-stable createElementNs const svg = tempElement.children[0]; svg.removeAttribute('height'); svg.removeAttribute('width'); svg.setAttribute('focusable', false); svg.setAttribute('aria-hidden', true); // The following element is <path> which does not support classList API as others. svg.setAttribute( 'class', `ecl-table__icon ecl-icon ecl-icon--${iconSvgAllArrowSize} ${customClass}`, ); return svg; } /** * Initialise component. */ init() { if (!ECL) { throw new TypeError('Called init but ECL is not present'); } ECL.components = ECL.components || new Map(); this.sortHeadings = queryAll(this.sortSelector, this.element); // Add sort arrows and bind click event on toggles. if (this.sortHeadings) { this.sortHeadings.forEach((tr) => { const sort = document.createElement('button'); sort.classList.add('ecl-table__arrow'); if (this.element.hasAttribute(this.sortLabelSelector)) { sort.setAttribute( 'aria-label', this.element.getAttribute(this.sortLabelSelector), ); } sort.appendChild(Table.createSortIcon('ecl-table__icon-up')); sort.appendChild(Table.createSortIcon('ecl-table__icon-down')); tr.appendChild(sort); tr.addEventListener('click', (e) => this.handleClickOnSort(tr)(e)); }); } // Set default row order via dataset. const tbody = queryOne('tbody', this.element); [...queryAll('tr', tbody)].forEach((tr, index) => { tr.setAttribute('data-ecl-table-order', index); }); // Set ecl initialized attribute this.element.setAttribute('data-ecl-auto-initialized', 'true'); ECL.components.set(this.element, this); } /** * Destroy component. */ destroy() { if (this.sortHeadings) { this.sortHeadings.forEach((tr) => { tr.removeEventListener('click', (e) => this.handleClickOnSort(tr)(e)); }); } if (this.element) { this.element.removeAttribute('data-ecl-auto-initialized'); ECL.components.delete(this.element); } } /** * @param {HTMLElement} toggle Target element to toggle. */ handleClickOnSort = (toggle) => (event) => { event.preventDefault(); const table = toggle.closest('table'); const tbody = queryOne('tbody', table); let order = toggle.getAttribute('aria-sort'); // Get current column index, taking into account the colspan. let colIndex = 0; let prev = toggle.previousElementSibling; while (prev) { colIndex += prev.getAttribute('colspan') ? Number(prev.getAttribute('colspan')) : 1; prev = prev.previousElementSibling; } // Cell comparer function. const comparer = (idx, asc) => (a, b) => ((v1, v2) => v1 !== '' && v2 !== '' && !Number.isNaN(+v1) && !Number.isNaN(+v2) ? v1 - v2 : v1.toString().localeCompare(v2))( (asc ? a : b).children[idx].textContent, (asc ? b : a).children[idx].textContent, ); if (order === 'descending') { // If current order is 'descending' reset column filter sort rows by default order. [...queryAll('tr', tbody)].forEach((tr, index) => { const defaultTr = queryOne(`[data-ecl-table-order='${index}']`, tbody); tbody.appendChild(defaultTr); }); order = null; } else { // Otherwise we sort the rows and set new order. [...queryAll('tr', tbody)] .sort(comparer(colIndex, order !== 'ascending')) .forEach((tr) => tbody.appendChild(tr)); order = order === 'ascending' ? 'descending' : 'ascending'; } // Change heading aria-sort attr. this.sortHeadings.forEach((th) => { if (order && th === toggle) { th.setAttribute('aria-sort', order); } else { th.removeAttribute('aria-sort'); } }); };}export default Table;