fradrive/frontend/src/utils/hide-columns/hide-columns.js

263 lines
8.3 KiB
JavaScript

import { Utility } from '../../core/utility';
import { StorageManager } from '../../lib/storage-manager/storage-manager';
import './hide-columns.scss';
const HIDE_COLUMNS_CONTAINER_IDENT = 'uw-hide-columns';
const TABLE_HEADER_IDENT = 'uw-hide-column-header';
const ASYNC_TABLE_IDENT = 'uw-async-table';
const TABLE_UTILS_ATTR = 'table-utils';
const TABLE_UTILS_CONTAINER_SELECTOR = `[${TABLE_UTILS_ATTR}]`;
const TABLE_HIDER_CONTAINER_CLASS = 'table-hiders';
const TABLE_HIDER_CLASS = 'table-hider';
const TABLE_HIDER_VISIBLE_CLASS = 'table-hider--visible';
const TABLE_PILL_CLASS = 'table-pill';
const CELL_HIDDEN_CLASS = 'hide-columns--hidden-cell';
@Utility({
selector: `[${HIDE_COLUMNS_CONTAINER_IDENT}] table`,
})
export class HideColumns {
_storageManager = new StorageManager('HIDE_COLUMNS');
_element;
_elementWrapper;
_tableUtilContainer;
_tableHiderContainer;
headerToHider = new Map();
hiderToHeader = new Map();
addHeaderHider(th, hider) {
this.headerToHider.set(th, hider);
this.hiderToHeader.set(hider, th);
}
constructor(element) {
if (!element) {
throw new Error('Hide Columns utility cannot be setup without an element!');
}
// do not provide hide-column ability in tables inside modals or async forms with response
if (element.closest('[uw-modal], .async-form__response')) {
return false;
}
this._element = element;
const hideColumnsContainer = this._element.closest(`[${HIDE_COLUMNS_CONTAINER_IDENT}]`);
if (!hideColumnsContainer) {
throw new Error('Hide Columns utility needs to be setup on a table inside a hide columns container!');
}
this._elementWrapper = hideColumnsContainer;
// get or create table utils container
this._tableUtilContainer = hideColumnsContainer.querySelector(TABLE_UTILS_CONTAINER_SELECTOR);
if (!this._tableUtilContainer) {
this._tableUtilContainer = document.createElement('div');
this._tableUtilContainer.setAttribute(TABLE_UTILS_ATTR, '');
const tableContainer = this._element.closest(`[${HIDE_COLUMNS_CONTAINER_IDENT}] > *`);
hideColumnsContainer.insertBefore(this._tableUtilContainer, tableContainer);
}
// get or create table hider container before the table
this._tableHiderContainer = this._element.previousSibling;
if (!this._tableHiderContainer || !this._tableHiderContainer.classList.contains(TABLE_HIDER_CONTAINER_CLASS)) {
this._tableHiderContainer = document.createElement('div');
this._tableHiderContainer.classList.add(TABLE_HIDER_CONTAINER_CLASS);
this._element.parentElement.insertBefore(this._tableHiderContainer, this._element);
}
this._element.querySelectorAll('th').forEach(th => this.setupHideButton(th));
}
setupHideButton(th) {
const preHidden = this.isHiddenColumn(th);
const hider = document.createElement('span');
const hiderIcon = document.createElement('i');
hiderIcon.classList.add('fas', 'fa-fw');
hider.appendChild(hiderIcon);
const hiderContent = document.createElement('span');
hiderContent.classList.add('table-hider__label');
hiderContent.innerHTML = th.innerText;
hider.appendChild(hiderContent);
this.addHeaderHider(th, hider);
th.addEventListener('mouseover', () => {
hider.classList.add(TABLE_HIDER_VISIBLE_CLASS);
});
th.addEventListener('mouseout', () => {
if (hider.classList.contains(TABLE_HIDER_CLASS)) {
hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
}
});
hider.addEventListener('click', (event) => {
event.preventDefault();
this.switchColumnDisplay(th, hider);
this._tableHiderContainer.getElementsByClassName(TABLE_HIDER_CLASS).forEach(hider => this.hideHiderBehindHeader(hider));
});
hider.addEventListener('mouseover', () => {
hider.classList.add(TABLE_HIDER_VISIBLE_CLASS);
const currentlyHidden = this.isHiddenColumn(th);
this.updateHiderIcon(hider, !currentlyHidden);
});
hider.addEventListener('mouseout', () => {
if (hider.classList.contains(TABLE_HIDER_CLASS)) {
hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
}
const currentlyHidden = this.isHiddenColumn(th);
this.updateHiderIcon(hider, currentlyHidden);
});
// reposition hider on each window resize event
window.addEventListener('resize', () => this.repositionHider(hider));
if (preHidden) {
this._tableUtilContainer.appendChild(hider);
} else {
this.hideHiderBehindHeader(hider);
}
this.updateColumnDisplay(th.cellIndex, preHidden);
this.updateHider(hider, preHidden);
this._tableHiderContainer.children.forEach(hider => this.repositionHider(hider));
}
switchColumnDisplay(th, hider) {
const hidden = !this.isHiddenColumn(th);
this.updateColumnDisplay(th.cellIndex, hidden);
this.updateHider(hider, hidden);
// persist new hidden setting for column
this._storageManager.save(this.getStorageKey(th), hidden);
}
updateColumnDisplay(columnIndex, hidden) {
this._element.getElementsByTagName('tr').forEach(row => {
const cell = row.cells[columnIndex];
if (cell) {
if (hidden) {
cell.classList.add(CELL_HIDDEN_CLASS);
} else {
cell.classList.remove(CELL_HIDDEN_CLASS);
}
}
});
}
updateHider(hider, hidden) {
if (hidden) {
hider.classList.remove(TABLE_HIDER_CLASS);
hider.classList.add(TABLE_PILL_CLASS);
this._tableUtilContainer.appendChild(hider);
} else {
hider.classList.remove(TABLE_PILL_CLASS);
hider.classList.add(TABLE_HIDER_CLASS);
this.hideHiderBehindHeader(hider);
}
this.updateHiderIcon(hider, hidden);
}
updateHiderIcon(hider, hidden) {
hider.getElementsByClassName('fas').forEach(hiderIcon => {
hiderIcon.classList.remove(hidden ? 'fa-eye' : 'fa-eye-slash');
hiderIcon.classList.add(hidden ? 'fa-eye-slash' : 'fa-eye');
});
}
hideHiderBehindHeader(hider) {
if (!this._tableHiderContainer.contains(hider)) {
this._tableHiderContainer.appendChild(hider);
}
this.repositionHider(hider);
// remove visible class if necessary
hider.classList.remove(TABLE_HIDER_VISIBLE_CLASS);
}
repositionHider(hider) {
const thR = this.hiderToHeader.get(hider).getBoundingClientRect(),
hR = hider.getBoundingClientRect(),
pR = this._tableHiderContainer.getBoundingClientRect();
hider.style.left = (thR.left + thR.width/2 - hR.width/2 - pR.left) + 'px';
hider.style.top = (thR.top - pR.top + thR.height) + 'px';
}
getStorageKey(th) {
// get handler name
const handlerIdent = document.querySelector('[uw-handler]').getAttribute('uw-handler');
// get hide-columns container ident (if not present, use table index in document as fallback)
let tIdent = this._elementWrapper.getAttribute(HIDE_COLUMNS_CONTAINER_IDENT);
if (!tIdent) {
const tablesInDocument = document.getElementsByTagName('TABLE');
for (let i = 0; i < tablesInDocument.length; i++) {
if (tablesInDocument[i] === this._element) {
tIdent = i;
break;
}
}
}
// check for unique table header ident from backend (if not present, use cell index as fallback)
let thIdent = th.getAttribute(TABLE_HEADER_IDENT);
if (!thIdent) {
thIdent = th.cellIndex;
}
return `${handlerIdent}__${tIdent}__${thIdent}`;
}
isEmptyColumn(columnIndex) {
for (let row of this._element.getElementsByTagName('TR')) {
if (row.children.length <= columnIndex) {
throw new Error('Invalid column index for table');
}
const cell = row.children[columnIndex];
if (cell.tagName === 'TH')
continue;
if (this._element.closest(`[${ASYNC_TABLE_IDENT}]`)) {
for (let child of cell.children) {
if (!isEmptyElement(child))
return false;
}
return true;
} else {
return isEmptyElement(cell);
}
}
}
isHiddenColumn(th) {
const hidden = this._storageManager.load(this.getStorageKey(th)),
emptyColumn = this.isEmptyColumn(th.cellIndex);
return hidden === true || hidden === undefined && emptyColumn;
}
}
function isEmptyElement(element) {
for (let child of element.childNodes) {
if (child.nodeName !== '#comment')
return false;
}
return true;
}