diff --git a/frontend/src/utils/hide-columns/hide-columns.js b/frontend/src/utils/hide-columns/hide-columns.js new file mode 100644 index 000000000..d4cbf234e --- /dev/null +++ b/frontend/src/utils/hide-columns/hide-columns.js @@ -0,0 +1,149 @@ +import { Utility } from '../../core/utility'; +import { StorageManager } from '../../lib/storage-manager/storage-manager'; + +const HIDE_COLUMNS_CONTAINER_IDENT = 'uw-hide-columns'; +const TABLE_HEADER_IDENT = 'hide-column-header'; + +const TABLE_UTILS_ATTR = 'table-utils'; +const TABLE_UTILS_CONTAINER_SELECTOR = `[${TABLE_UTILS_ATTR}]`; + + +const HIDER_TEXT_ATTR = 'header-text'; + +const HIDE_BUTTON_FADE_DELAY = 3000; + +@Utility({ + selector: `[${HIDE_COLUMNS_CONTAINER_IDENT}] table`, +}) +export class HideColumns { + + _storageManager = new StorageManager('uw-hide-columns'); + + _element; + _tableUtilContainer; + + 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!'); + } + + // 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); + } + + this._element.querySelectorAll('th').forEach(th => { + const storageKey = this.getStorageKey(th); + const previouslyHidden = this._storageManager.load(storageKey); + this.setupHideButton(th, previouslyHidden); + }); + } + + setupHideButton(th, prevHidden) { + console.log('setupHideButton', th, prevHidden); + + const hider = document.createElement('button'); + hider.setAttribute(HIDER_TEXT_ATTR, th.innerText); + + hider.addEventListener('click', (event) => { + console.log('click', th.cellIndex, hider, th.innerText); + event.preventDefault(); + this.toggleColumnVisibility(th, hider); + }); + + // TODO fade in / fade out animation in css + th.addEventListener('mouseover', () => { + hider.style.display = ''; + }); + th.addEventListener('mouseout', () => { + setTimeout(() => { + if (hider.style.position === 'absolute') hider.style.display = 'none'; + }, HIDE_BUTTON_FADE_DELAY); + }); + + this.updateColumnDisplay(th.cellIndex, prevHidden); + this.updateHider(hider, prevHidden); + + this._tableUtilContainer.appendChild(hider); + } + + // TODO better name + toggleColumnVisibility(th, hider) { + const storageKey = this.getStorageKey(th); + + const hidden = !this._storageManager.load(storageKey); + + // hide/unhide column + this.updateColumnDisplay(th.cellIndex, hidden); + + // tweak hider button + this.updateHider(hider, hidden); + + // persist new hidden setting for column + this._storageManager.save(storageKey, hidden); + } + + // TODO better name + updateColumnDisplay(columnIndex, hidden) { + this._element.getElementsByTagName('tr').forEach(row => { + if (row.cells[columnIndex]) { + row.cells[columnIndex].style.display = hidden ? 'none' : ''; + } + }); + } + + // TODO better name + updateHider(hider, hidden) { + // TODO set css classes instead + if (hidden) { + hider.innerHTML = hider.getAttribute(HIDER_TEXT_ATTR); + hider.style.position = 'relative'; + } else { + hider.innerHTML = 'hide'; + hider.style.position = 'absolute'; + hider.style.display = 'none'; + } + } + + 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 = th.getAttribute(TABLE_HEADER_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}`; + } + +} diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index b539edb44..00f3e5ec0 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -11,6 +11,7 @@ import { Modal } from './modal/modal'; import { Tooltip } from './tooltips/tooltips'; import { CourseTeaser } from './course-teaser/course-teaser'; import { NavbarUtils } from './navbar/navbar'; +import { HideColumns } from './hide-columns/hide-columns'; export const Utils = [ Alerts, @@ -27,4 +28,5 @@ export const Utils = [ Tooltip, CourseTeaser, ...NavbarUtils, + HideColumns, ];