|
6 | 6 | /** |
7 | 7 | * JavaScript behavior to allow shift select in administrator grids |
8 | 8 | */ |
9 | | -((Joomla) => { |
10 | | - 'use strict'; |
11 | | - |
12 | | - class JMultiSelect { |
13 | | - constructor(formElement) { |
14 | | - this.tableEl = document.querySelector(formElement); |
15 | | - |
16 | | - if (this.tableEl) { |
17 | | - this.boxes = [].slice.call(this.tableEl.querySelectorAll('td input[type=checkbox]')); |
18 | | - this.rows = [].slice.call(document.querySelectorAll('tr[class^="row"]')); |
19 | | - this.checkallToggle = document.querySelector('[name="checkall-toggle"]'); |
20 | | - |
21 | | - this.onCheckallToggleClick = this.onCheckallToggleClick.bind(this); |
22 | | - this.onRowClick = this.onRowClick.bind(this); |
23 | | - |
24 | | - if (this.checkallToggle) { |
25 | | - this.checkallToggle.addEventListener('click', this.onCheckallToggleClick); |
26 | | - } |
27 | | - |
28 | | - if (this.rows.length) { |
29 | | - this.rows.forEach((row) => { |
30 | | - row.addEventListener('click', this.onRowClick); |
31 | | - }); |
32 | | - } |
| 9 | +class JMultiSelect { |
| 10 | + constructor(container) { |
| 11 | + this.tableEl = container; |
| 12 | + this.formEl = container.closest('form'); |
| 13 | + this.rowSelector = 'tr[class^="row"]'; |
| 14 | + this.boxSelector = 'input[type="checkbox"][name="cid[]"]'; |
| 15 | + this.checkallToggle = this.tableEl.querySelector('[name="checkall-toggle"]'); |
| 16 | + this.prevRow = null; |
| 17 | + |
| 18 | + // Use delegation listener, to allow dynamic tables |
| 19 | + this.tableEl.addEventListener('click', (event) => { |
| 20 | + if (!event.target.closest(this.rowSelector)) { |
| 21 | + return; |
33 | 22 | } |
34 | | - } |
| 23 | + this.onRowClick(event); |
| 24 | + }); |
35 | 25 |
|
36 | | - // Changes the background-color on every cell inside a <tr> |
37 | | - // eslint-disable-next-line class-methods-use-this |
38 | | - changeBg(row, isChecked) { |
39 | | - // Check if it should add or remove the background colour |
40 | | - if (isChecked) { |
41 | | - [].slice.call(row.querySelectorAll('td, th')).forEach((elementToMark) => { |
42 | | - elementToMark.classList.add('row-selected'); |
43 | | - }); |
44 | | - } else { |
45 | | - [].slice.call(row.querySelectorAll('td, th')).forEach((elementToMark) => { |
46 | | - elementToMark.classList.remove('row-selected'); |
| 26 | + if (this.checkallToggle) { |
| 27 | + this.checkallToggle.addEventListener('click', ({ target }) => { |
| 28 | + const isChecked = target.checked; |
| 29 | + |
| 30 | + this.getRows().forEach((row) => { |
| 31 | + this.changeBg(row, isChecked); |
47 | 32 | }); |
48 | | - } |
| 33 | + }); |
49 | 34 | } |
| 35 | + } |
50 | 36 |
|
51 | | - onCheckallToggleClick({ target }) { |
52 | | - const isChecked = target.checked; |
| 37 | + getRows() { |
| 38 | + return Array.from(this.tableEl.querySelectorAll(this.rowSelector)); |
| 39 | + } |
53 | 40 |
|
54 | | - this.rows.forEach((row) => { |
55 | | - this.changeBg(row, isChecked); |
56 | | - }); |
57 | | - } |
| 41 | + // Changes the row class depends on selection |
| 42 | + // eslint-disable-next-line class-methods-use-this |
| 43 | + changeBg(row, isChecked) { |
| 44 | + row.classList.toggle('row-selected', isChecked); |
| 45 | + } |
58 | 46 |
|
59 | | - onRowClick({ target, shiftKey }) { |
60 | | - // Do not interfere with links or buttons |
61 | | - if (target.tagName && (target.tagName.toLowerCase() === 'a' || target.tagName.toLowerCase() === 'button')) { |
62 | | - return; |
63 | | - } |
| 47 | + // Handle click on a row |
| 48 | + onRowClick({ target, shiftKey }) { |
| 49 | + // Do not interfere with links, buttons, inputs |
| 50 | + if (target.tagName && (target.tagName === 'A' || target.tagName === 'BUTTON' |
| 51 | + || target.tagName === 'SELECT' || target.tagName === 'TEXTAREA' |
| 52 | + || (target.tagName === 'INPUT' && !target.matches(this.boxSelector)))) { |
| 53 | + return; |
| 54 | + } |
64 | 55 |
|
65 | | - if (!this.boxes.length) { |
66 | | - return; |
67 | | - } |
| 56 | + // Get clicked row and checkbox in it |
| 57 | + const currentRow = target.closest(this.rowSelector); |
| 58 | + const currentBox = target.matches(this.boxSelector) ? target : currentRow.querySelector(this.boxSelector); |
| 59 | + if (!currentBox) { |
| 60 | + return; |
| 61 | + } |
68 | 62 |
|
69 | | - const closestRow = target.closest('tr'); |
70 | | - const currentRowNum = this.rows.indexOf(closestRow); |
71 | | - const currentCheckBox = closestRow.querySelector('td input[type=checkbox]'); |
| 63 | + const isChecked = (currentBox !== target) ? !currentBox.checked : currentBox.checked; |
72 | 64 |
|
73 | | - if (currentCheckBox) { |
74 | | - let isChecked = currentCheckBox.checked; |
| 65 | + if (isChecked !== currentBox.checked) { |
| 66 | + currentBox.checked = isChecked; |
| 67 | + Joomla.isChecked(isChecked, this.formEl); |
| 68 | + } |
| 69 | + this.changeBg(currentRow, isChecked); |
75 | 70 |
|
76 | | - if (!(target.id === currentCheckBox.id)) { |
77 | | - // We will prevent selecting text to prevent artifacts |
78 | | - if (shiftKey) { |
79 | | - document.body.style['-webkit-user-select'] = 'none'; |
80 | | - document.body.style['-moz-user-select'] = 'none'; |
81 | | - document.body.style['-ms-user-select'] = 'none'; |
82 | | - document.body.style['user-select'] = 'none'; |
83 | | - } |
| 71 | + // Select rows in range |
| 72 | + if (shiftKey && this.prevRow) { |
| 73 | + // Prevent text selection |
| 74 | + document.getSelection().removeAllRanges(); |
84 | 75 |
|
85 | | - currentCheckBox.checked = !currentCheckBox.checked; |
86 | | - isChecked = currentCheckBox.checked; |
87 | | - Joomla.isChecked(isChecked, this.tableEl.id); |
88 | | - } |
| 76 | + // Re-query all rows, because they may be modified during sort operations |
| 77 | + const rows = this.getRows(); |
| 78 | + const idxStart = rows.indexOf(this.prevRow); |
| 79 | + const idxEnd = rows.indexOf(currentRow); |
89 | 80 |
|
90 | | - this.changeBg(this.rows[currentRowNum], isChecked); |
| 81 | + // Check for more than 2 row selected |
| 82 | + if (idxStart >= 0 && idxEnd >= 0 && Math.abs(idxStart - idxEnd) > 1) { |
| 83 | + const slice = idxStart < idxEnd ? rows.slice(idxStart, idxEnd + 1) : rows.slice(idxEnd, idxStart + 1); |
91 | 84 |
|
92 | | - // Restore normality |
93 | | - if (shiftKey) { |
94 | | - document.body.style['-webkit-user-select'] = 'none'; |
95 | | - document.body.style['-moz-user-select'] = 'none'; |
96 | | - document.body.style['-ms-user-select'] = 'none'; |
97 | | - document.body.style['user-select'] = 'none'; |
98 | | - } |
| 85 | + slice.forEach((row) => { |
| 86 | + if (row === currentRow) { |
| 87 | + return; |
| 88 | + } |
| 89 | + const rowBox = row.querySelector(this.boxSelector); |
| 90 | + if (rowBox && rowBox.checked !== isChecked) { |
| 91 | + rowBox.checked = isChecked; |
| 92 | + this.changeBg(row, isChecked); |
| 93 | + Joomla.isChecked(isChecked, this.formEl); |
| 94 | + } |
| 95 | + }); |
99 | 96 | } |
100 | 97 | } |
| 98 | + |
| 99 | + this.prevRow = currentRow; |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +const onBoot = (container) => { |
| 104 | + let selector = '#adminForm'; |
| 105 | + const confSelector = window.Joomla ? Joomla.getOptions('js-multiselect', {}).formName : ''; |
| 106 | + |
| 107 | + if (confSelector) { |
| 108 | + const pref = confSelector[0]; |
| 109 | + selector = (pref !== '.' && pref !== '#') ? `#${confSelector}` : confSelector; |
101 | 110 | } |
102 | 111 |
|
103 | | - const onBoot = () => { |
104 | | - let formId = '#adminForm'; |
105 | | - if (Joomla && Joomla.getOptions('js-multiselect', {}).formName) { |
106 | | - formId = `#${Joomla.getOptions('js-multiselect', {}).formName}`; |
| 112 | + container.querySelectorAll(selector).forEach((formElement) => { |
| 113 | + if (formElement && !('multiselect' in formElement.dataset)) { |
| 114 | + formElement.dataset.multiselect = ''; |
| 115 | + // eslint-disable-next-line no-new |
| 116 | + new JMultiSelect(formElement); |
107 | 117 | } |
108 | | - // eslint-disable-next-line no-new |
109 | | - new JMultiSelect(formId); |
110 | | - }; |
| 118 | + }); |
| 119 | +}; |
111 | 120 |
|
112 | | - document.addEventListener('DOMContentLoaded', onBoot); |
113 | | -})(Joomla); |
| 121 | +onBoot(document); |
| 122 | +document.addEventListener('joomla:updated', ({ target }) => onBoot(target)); |
0 commit comments