class CheckboxesWidget { checkboxSelector = "input[type=checkbox][data-check-tr]"; checkboxBags = []; /** * @param {HTMLElement} rootNode */ constructor(rootNode = document.body) { this.rootNode = rootNode; this.simulateClick = false; this.initialize(); } initialize() { document.addEventListener('change', this.onChange.bind(this)) document.addEventListener('click', this.onClick.bind(this)) document.addEventListener('draw.dt', this.onRedrawDataTable.bind(this)) } /** * @param {HTMLElement} element * @return {HTMLElement|null} */ findTableWrapper(element) { // Manual wrapper setting return element.closest('[data-check-wrapper]') // Datatable wrapper || element.closest('.dataTables_wrapper') // HTML Table wrapper || element.closest('table'); } /** * @param {PointerEvent} event */ onClick(event) { let target = event.target; let element; if (this.simulateClick) { return; } if ((element = target.closest('[data-check-all]'))) { let table = this.findTableWrapper(element); table.dataset.lastCheckedId = null; return; } if ((element = target.closest('[data-check-tr]'))) { let table = this.findTableWrapper(element); if (!table.dataset.lastCheckedId) { table.dataset.lastCheckedId = element.dataset.id; } else { if (event.shiftKey) { let nodeList = table.querySelectorAll("[data-check-tr]"); let from = -1; let to = -1; let i = 0; let destChecked = false; nodeList.forEach(node => { if (node.dataset.id === element.dataset.id) { destChecked = node.checked; from = i; } if (node.dataset.id === table.dataset.lastCheckedId) { to = i; } i++; }) const start = Math.min(from, to); const end = Math.max(from, to) + 1; [...nodeList].slice(start, end).forEach(el => { if (el.checked !== destChecked) { this.change(el, destChecked); } }) } table.dataset.lastCheckedId = element.dataset.id; } } } /** * @param {Event} event */ onChange(event) { let target = event.target; let element; if ((element = target.closest('[data-check-all]'))) { this.onCheckAllClicked(element); return; } if ((element = target.closest('[data-check-tr]'))) { this.onCheckboxChange(element); } } /** * @param {HTMLInputElement} element */ onCheckAllClicked(element) { let table = this.findTableWrapper(element); if (!table) { return; } this.simulateClick = true; table.querySelectorAll("[data-check-tr]") .forEach(checkbox => { if (checkbox.checked !== element.checked) { this.change(checkbox, element.checked) } }) this.simulateClick = false; element.indeterminate = false; } onCheckboxChange(element) { let tableWrapper = this.findTableWrapper(element); this.checkAllUpdate(tableWrapper); let bag = element.dataset.bag; let id = JSON.parse(element.dataset.id); let checked = element.checked; if ('undefined' === typeof this.checkboxBags[bag]) { this.checkboxBags[bag] = []; } let currentBag = this.checkboxBags[bag]; let index = currentBag.findIndex(arrayElement => arrayElement[0] === id[0]); if (index !== -1) { currentBag.splice(index, 1); } // Push if checked if (checked) { currentBag.push(id); } document.dispatchEvent( new CustomEvent( 'bag-update', { detail: { table: tableWrapper, bag: currentBag } } ) ); } /** * @param {CustomEvent} event */ onRedrawDataTable(event) { let table = event.detail.table; table.querySelectorAll(this.checkboxSelector) .forEach(element => { let bag = element.dataset.bag; let id = element.dataset.id; if ('undefined' === typeof this.checkboxBags[bag]) { this.checkboxBags[bag] = []; } if (this.checkboxBags[bag].indexOf(id) !== -1) { element.checked = true; element.closest('tr').classList.add('checked'); } }) this.checkAllUpdate(table); } /** * @param {HTMLInputElement} checkbox * @param {boolean} checked */ change(checkbox, checked) { const event = new MouseEvent('click', { view: window, bubbles: true, cancelable: true }); checkbox.dispatchEvent(event); } checkAllUpdate(table) { let nbUnchecked = table.querySelectorAll(this.checkboxSelector + ':not(:checked)').length; let nbChecked = table.querySelectorAll(this.checkboxSelector + ':checked').length; let checkAll = table.querySelector("[data-check-all]"); if (!checkAll) { return; } checkAll.indeterminate = false; if (nbChecked === 0) { checkAll.checked = false; } else if (nbUnchecked === 0) { checkAll.checked = true; } else { checkAll.indeterminate = true; } } }