collapsible.js: Don't use jQuery, but some ES6 features

This commit is contained in:
Johannes Meyer 2022-07-25 15:09:23 +02:00
parent 917e68d68d
commit b0622dcde2

View File

@ -1,6 +1,6 @@
/*! Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */ /*! Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
;(function(Icinga, $) { ;(function(Icinga) {
'use strict'; 'use strict';
@ -39,7 +39,7 @@
* @param event Event The `onRender` event triggered by the rendered container * @param event Event The `onRender` event triggered by the rendered container
*/ */
Collapsible.prototype.onRendered = function(event) { Collapsible.prototype.onRendered = function(event) {
var _this = event.data.self, let _this = event.data.self,
toCollapse = [], toCollapse = [],
toExpand = []; toExpand = [];
@ -58,12 +58,12 @@
// Elements are all collapsed in a row now, after height calculations are done. // Elements are all collapsed in a row now, after height calculations are done.
// This avoids reflows since instantly collapsing an element will cause one if // This avoids reflows since instantly collapsing an element will cause one if
// the height of the next element is being calculated. // the height of the next element is being calculated.
for (var i = 0; i < toCollapse.length; i++) { for (const collapseInfo of toCollapse) {
_this.collapse(toCollapse[i][0], toCollapse[i][1]); _this.collapse(collapseInfo[0], collapseInfo[1]);
} }
for (i = 0; i < toExpand.length; i++) { for (const collapsible of toExpand) {
_this.expand(toExpand[i]); _this.expand(collapsible);
} }
}; };
@ -73,15 +73,15 @@
* @param event Event The `layout-change` event triggered by window resizing or column changes * @param event Event The `layout-change` event triggered by window resizing or column changes
*/ */
Collapsible.prototype.onLayoutChange = function(event) { Collapsible.prototype.onLayoutChange = function(event) {
var _this = event.data.self; let _this = event.data.self;
var toCollapse = []; let toCollapse = [];
$.each(document.querySelectorAll('.collapsible'), function (_, collapsible) { document.querySelectorAll('.collapsible').forEach(collapsible => {
if ('canCollapse' in collapsible.dataset) { if ('canCollapse' in collapsible.dataset) {
if (! _this.canCollapse(collapsible)) { if (! _this.canCollapse(collapsible)) {
var toggleSelector = collapsible.dataset.toggleElement; let toggleSelector = collapsible.dataset.toggleElement;
if (! toggleSelector && ! this.isDetails(collapsible)) { if (! toggleSelector && ! this.isDetails(collapsible)) {
$(collapsible).next('.collapsible-control').remove(); collapsible.nextElementSibling.remove();
} }
delete collapsible.dataset.canCollapse; delete collapsible.dataset.canCollapse;
@ -94,8 +94,8 @@
}); });
setTimeout(function () { setTimeout(function () {
for (var i = 0; i < toCollapse.length; i++) { for (const collapseInfo of toCollapse) {
_this.collapse(toCollapse[i][0], toCollapse[i][1]); _this.collapse(collapseInfo[0], collapseInfo[1]);
} }
}, 0); }, 0);
}; };
@ -106,7 +106,7 @@
* @param {string} collapsiblePath * @param {string} collapsiblePath
*/ */
Collapsible.prototype.onExpand = function(collapsiblePath) { Collapsible.prototype.onExpand = function(collapsiblePath) {
var collapsible = $(collapsiblePath)[0]; let collapsible = document.querySelector(collapsiblePath);
if (collapsible && 'canCollapse' in collapsible.dataset) { if (collapsible && 'canCollapse' in collapsible.dataset) {
if ('stateCollapses' in collapsible.dataset) { if ('stateCollapses' in collapsible.dataset) {
@ -123,7 +123,7 @@
* @param {string} collapsiblePath * @param {string} collapsiblePath
*/ */
Collapsible.prototype.onCollapse = function(collapsiblePath) { Collapsible.prototype.onCollapse = function(collapsiblePath) {
var collapsible = $(collapsiblePath)[0]; let collapsible = document.querySelector(collapsiblePath);
if (collapsible && this.canCollapse(collapsible)) { if (collapsible && this.canCollapse(collapsible)) {
if ('stateCollapses' in collapsible.dataset) { if ('stateCollapses' in collapsible.dataset) {
@ -140,27 +140,27 @@
* @param event Event The `onClick` event triggered by the clicked collapsible-control element * @param event Event The `onClick` event triggered by the clicked collapsible-control element
*/ */
Collapsible.prototype.onControlClicked = function(event) { Collapsible.prototype.onControlClicked = function(event) {
var _this = event.data.self; let _this = event.data.self,
var $target = $(event.currentTarget); target = event.currentTarget;
var collapsible = $target.prev('.collapsible')[0]; let collapsible = target.previousElementSibling;
if (! collapsible) { if (! collapsible) {
collapsible = $target.parent('.collapsible')[0]; collapsible = target.closest('.collapsible');
} }
if (! collapsible) { if (! collapsible) {
_this.icinga.logger.error( _this.icinga.logger.error(
'[Collapsible] Collapsible control has no associated .collapsible: ', $target[0]); '[Collapsible] Collapsible control has no associated .collapsible: ', target);
return; return;
} else if (typeof collapsible.dataset.noPersistence !== 'undefined') { } else if ('noPersistence' in collapsible.dataset) {
if (collapsible.classList.contains('collapsed')) { if (collapsible.classList.contains('collapsed')) {
_this.expand(collapsible); _this.expand(collapsible);
} else { } else {
_this.collapse(collapsible, _this.calculateCollapsedHeight(collapsible)); _this.collapse(collapsible, _this.calculateCollapsedHeight(collapsible));
} }
} else { } else {
var collapsiblePath = _this.icinga.utils.getCSSPath(collapsible), let collapsiblePath = _this.icinga.utils.getCSSPath(collapsible),
stateCollapses = 'stateCollapses' in collapsible.dataset; stateCollapses = 'stateCollapses' in collapsible.dataset;
if (_this.state.has(collapsiblePath)) { if (_this.state.has(collapsiblePath)) {
@ -197,7 +197,7 @@
*/ */
Collapsible.prototype.setupCollapsible = function (collapsible) { Collapsible.prototype.setupCollapsible = function (collapsible) {
if (this.isDetails(collapsible)) { if (this.isDetails(collapsible)) {
var summary = collapsible.querySelector(':scope > summary'); let summary = collapsible.querySelector(':scope > summary');
if (! summary.classList.contains('collapsible-control')) { if (! summary.classList.contains('collapsible-control')) {
summary.classList.add('collapsible-control'); summary.classList.add('collapsible-control');
} }
@ -206,10 +206,10 @@
collapsible.dataset.stateCollapses = ''; collapsible.dataset.stateCollapses = '';
} }
} else if (!! collapsible.dataset.toggleElement) { } else if (!! collapsible.dataset.toggleElement) {
var toggleSelector = collapsible.dataset.toggleElement, let toggleSelector = collapsible.dataset.toggleElement,
toggle = $(collapsible).children(toggleSelector)[0]; toggle = collapsible.querySelector(toggleSelector);
if (! toggle && $(collapsible.nextSibling).is(toggleSelector)) { if (! toggle && collapsible.nextElementSibling && collapsible.nextElementSibling.matches(toggleSelector)) {
toggle = collapsible.nextSibling; toggle = collapsible.nextElementSibling;
} }
if (! toggle) { if (! toggle) {
@ -220,7 +220,7 @@
} }
} else { } else {
setTimeout(function () { setTimeout(function () {
var collapsibleControl = document let collapsibleControl = document
.getElementById('collapsible-control-ghost') .getElementById('collapsible-control-ghost')
.cloneNode(true); .cloneNode(true);
collapsibleControl.removeAttribute('id'); collapsibleControl.removeAttribute('id');
@ -274,25 +274,25 @@
return collapsible.querySelector(':scope > summary') !== null; return collapsible.querySelector(':scope > summary') !== null;
} }
var rowSelector = this.getRowSelector(collapsible); let rowSelector = this.getRowSelector(collapsible);
if (!! rowSelector) { if (!! rowSelector) {
var visibleRows = Number(collapsible.dataset.visibleRows); let visibleRows = Number(collapsible.dataset.visibleRows);
if (isNaN(visibleRows)) { if (isNaN(visibleRows)) {
visibleRows = this.defaultVisibleRows; visibleRows = this.defaultVisibleRows;
} else if (visibleRows === 0) { } else if (visibleRows === 0) {
return true; return true;
} }
return $(rowSelector, collapsible).length > visibleRows * 2; return collapsible.querySelectorAll(rowSelector).length > visibleRows * 2;
} else { } else {
var maxHeight = Number(collapsible.dataset.visibleHeight); let maxHeight = Number(collapsible.dataset.visibleHeight);
if (isNaN(maxHeight)) { if (isNaN(maxHeight)) {
maxHeight = this.defaultVisibleHeight; maxHeight = this.defaultVisibleHeight;
} else if (maxHeight === 0) { } else if (maxHeight === 0) {
return true; return true;
} }
var actualHeight = collapsible.scrollHeight - parseFloat( let actualHeight = collapsible.scrollHeight - parseFloat(
window.getComputedStyle(collapsible).getPropertyValue('padding-top') window.getComputedStyle(collapsible).getPropertyValue('padding-top')
); );
@ -306,31 +306,31 @@
* @param collapsible * @param collapsible
*/ */
Collapsible.prototype.calculateCollapsedHeight = function (collapsible) { Collapsible.prototype.calculateCollapsedHeight = function (collapsible) {
var height; let height;
if (this.isDetails(collapsible)) { if (this.isDetails(collapsible)) {
return -1; return -1;
} }
var rowSelector = this.getRowSelector(collapsible); let rowSelector = this.getRowSelector(collapsible);
if (!! rowSelector) { if (!! rowSelector) {
height = collapsible.scrollHeight; height = collapsible.scrollHeight;
height -= parseFloat(window.getComputedStyle(collapsible).getPropertyValue('padding-bottom')); height -= parseFloat(window.getComputedStyle(collapsible).getPropertyValue('padding-bottom'));
var visibleRows = Number(collapsible.dataset.visibleRows); let visibleRows = Number(collapsible.dataset.visibleRows);
if (isNaN(visibleRows)) { if (isNaN(visibleRows)) {
visibleRows = this.defaultVisibleRows; visibleRows = this.defaultVisibleRows;
} }
var $rows = $(rowSelector, collapsible).slice(visibleRows); let rows = Array.from(collapsible.querySelectorAll(rowSelector)).slice(visibleRows);
for (var i = 0; i < $rows.length; i++) { for (let i = 0; i < rows.length; i++) {
var row = $rows[i]; let row = rows[i];
if (row.previousElementSibling === null) { // very first element if (row.previousElementSibling === null) { // very first element
height -= row.offsetHeight; height -= row.offsetHeight;
height -= parseFloat(window.getComputedStyle(row).getPropertyValue('margin-top')); height -= parseFloat(window.getComputedStyle(row).getPropertyValue('margin-top'));
} else if (i < $rows.length - 1) { // every element but the last one } else if (i < rows.length - 1) { // every element but the last one
var prevBottomBorderAt = row.previousElementSibling.offsetTop; let prevBottomBorderAt = row.previousElementSibling.offsetTop;
prevBottomBorderAt += row.previousElementSibling.offsetHeight; prevBottomBorderAt += row.previousElementSibling.offsetHeight;
height -= row.offsetTop - prevBottomBorderAt + row.offsetHeight; height -= row.offsetTop - prevBottomBorderAt + row.offsetHeight;
} else { // the last element } else { // the last element
@ -349,9 +349,10 @@
if ( if (
!! collapsible.dataset.toggleElement !! collapsible.dataset.toggleElement
&& ! $(collapsible.nextSibling).is(collapsible.dataset.toggleElement) && (! collapsible.nextElementSibling
|| ! collapsible.nextElementSibling.matches(collapsible.dataset.toggleElement))
) { ) {
var toggle = $(collapsible).children(collapsible.dataset.toggleElement)[0]; let toggle = collapsible.querySelector(collapsible.dataset.toggleElement);
height += toggle.offsetHeight; // TODO: Very expensive at times. (50ms+) Check why! height += toggle.offsetHeight; // TODO: Very expensive at times. (50ms+) Check why!
height += parseFloat(window.getComputedStyle(toggle).getPropertyValue('margin-top')); height += parseFloat(window.getComputedStyle(toggle).getPropertyValue('margin-top'));
height += parseFloat(window.getComputedStyle(toggle).getPropertyValue('margin-bottom')); height += parseFloat(window.getComputedStyle(toggle).getPropertyValue('margin-bottom'));
@ -405,4 +406,4 @@
Icinga.Behaviors.Collapsible = Collapsible; Icinga.Behaviors.Collapsible = Collapsible;
})(Icinga, jQuery); })(Icinga);