Skip to content

Commit beb3b34

Browse files
Fedikdgrammatiko
andauthored
Fix table multiselect (joomla#42330)
* Multiselect update * Multiselect update * Multiselect update * Multiselect update * Multiselect update * Update build/media_source/system/js/multiselect.es6.js Co-authored-by: Dimitris Grammatikogiannis <[email protected]>
1 parent 584d7b0 commit beb3b34

File tree

2 files changed

+95
-86
lines changed

2 files changed

+95
-86
lines changed

build/media_source/system/joomla.asset.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
],
103103
"uri": "system/multiselect.min.js",
104104
"attributes": {
105-
"defer": true
105+
"type": "module"
106106
}
107107
},
108108
{

build/media_source/system/js/multiselect.es6.js

Lines changed: 94 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,108 +6,117 @@
66
/**
77
* JavaScript behavior to allow shift select in administrator grids
88
*/
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;
3322
}
34-
}
23+
this.onRowClick(event);
24+
});
3525

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);
4732
});
48-
}
33+
});
4934
}
35+
}
5036

51-
onCheckallToggleClick({ target }) {
52-
const isChecked = target.checked;
37+
getRows() {
38+
return Array.from(this.tableEl.querySelectorAll(this.rowSelector));
39+
}
5340

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+
}
5846

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+
}
6455

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+
}
6862

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;
7264

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);
7570

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();
8475

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);
8980

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);
9184

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+
});
9996
}
10097
}
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;
101110
}
102111

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);
107117
}
108-
// eslint-disable-next-line no-new
109-
new JMultiSelect(formId);
110-
};
118+
});
119+
};
111120

112-
document.addEventListener('DOMContentLoaded', onBoot);
113-
})(Joomla);
121+
onBoot(document);
122+
document.addEventListener('joomla:updated', ({ target }) => onBoot(target));

0 commit comments

Comments
 (0)