(function collonadeClosure() { 'use strict'; /** * * Async Table Utility * makes table filters, sorting and pagination behave asynchronously via AJAX calls * * Attribute: uw-async-table * * Example usage: * (regular table) */ var INPUT_DEBOUNCE = 600; var HEADER_HEIGHT = 80; var ASYNC_TABLE_UTIL_NAME = 'asyncTable'; var ASYNC_TABLE_UTIL_SELECTOR = '[uw-async-table]'; var ASYNC_TABLE_LOCAL_STORAGE_KEY = 'ASYNC_TABLE'; var ASYNC_TABLE_SCROLLTABLE_SELECTOR = '.scrolltable'; var ASYNC_TABLE_INITIALIZED_CLASS = 'async-table--initialized'; var ASYNC_TABLE_LOADING_CLASS = 'async-table--loading'; var ASYNC_TABLE_FILTER_FORM_SELECTOR = '.table-filter-form'; var ASYNC_TABLE_FILTER_FORM_ID_SELECTOR = '[name="form-identifier"]'; var asyncTableUtil = function(element) { var asyncTableHeader; var asyncTableId; var ths = []; var pageLinks = []; var pagesizeForm; var scrollTable; var cssIdPrefix = ''; var tableFilterInputs = { search: [], input: [], change: [], select: [], } function init() { if (!element) { throw new Error('Async Table utility cannot be setup without an element!'); } if (element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) { return false; } // param asyncTableDbHeader if (element.dataset.asyncTableDbHeader !== undefined) { asyncTableHeader = element.dataset.asyncTableDbHeader; } var rawTableId = element.querySelector('table').id; cssIdPrefix = findCssIdPrefix(rawTableId); asyncTableId = rawTableId.replace(cssIdPrefix, ''); // find scrolltable wrapper scrollTable = element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR); if (!scrollTable) { throw new Error('Async Table cannot be set up without a scrolltable element!'); } setupSortableHeaders(); setupPagination(); setupPageSizeSelect(); setupTableFilter(); processLocalStorage(); // clear currentTableUrl from previous requests setLocalStorageParameter('currentTableUrl', null); // mark initialized element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS); return { name: ASYNC_TABLE_UTIL_NAME, element: element, destroy: function() {}, }; } function setupSortableHeaders() { ths = Array.from(scrollTable.querySelectorAll('th.sortable')).map(function(th) { return { element: th }; }); ths.forEach(function(th) { th.clickHandler = function(event) { setLocalStorageParameter('horizPos', (scrollTable || {}).scrollLeft); linkClickHandler(event); }; th.element.addEventListener('click', th.clickHandler); }); } function setupPagination() { var pagination = element.querySelector('#' + cssIdPrefix + asyncTableId + '-pagination'); if (pagination) { pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) { return { element: link }; }); pageLinks.forEach(function(link) { link.clickHandler = function(event) { var tableBoundingRect = scrollTable.getBoundingClientRect(); if (tableBoundingRect.top < HEADER_HEIGHT) { var scrollTo = { top: (scrollTable.offsetTop || 0) - HEADER_HEIGHT, left: scrollTable.offsetLeft || 0, behavior: 'smooth', }; setLocalStorageParameter('scrollTo', scrollTo); } linkClickHandler(event); } link.element.addEventListener('click', link.clickHandler); }); } } function setupPageSizeSelect() { // pagesize form pagesizeForm = element.querySelector('#' + cssIdPrefix + asyncTableId + '-pagesize-form'); if (pagesizeForm) { var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]'); pagesizeSelect.addEventListener('change', changePagesizeHandler); } } function setupTableFilter() { var tableFilterForm = element.querySelector(ASYNC_TABLE_FILTER_FORM_SELECTOR); if (tableFilterForm) { gatherTableFilterInputs(tableFilterForm); addTableFilterEventListeners(tableFilterForm); } } function gatherTableFilterInputs(tableFilterForm) { Array.from(tableFilterForm.querySelectorAll('input[type="search"]')).forEach(function(input) { tableFilterInputs.search.push(input); }); Array.from(tableFilterForm.querySelectorAll('input[type="text"]')).forEach(function(input) { tableFilterInputs.input.push(input); }); Array.from(tableFilterForm.querySelectorAll('input:not([type="text"]):not([type="search"])')).forEach(function(input) { tableFilterInputs.change.push(input); }); Array.from(tableFilterForm.querySelectorAll('select')).forEach(function(input) { tableFilterInputs.select.push(input); }); } function addTableFilterEventListeners(tableFilterForm) { tableFilterInputs.search.forEach(function(input) { var debouncedInput = debounce(function() { if (input.value.length === 0 || input.value.length > 2) { updateFromTableFilter(tableFilterForm); } }, INPUT_DEBOUNCE); input.addEventListener('input', debouncedInput); }); tableFilterInputs.input.forEach(function(input) { var debouncedInput = debounce(function() { if (input.value.length === 0 || input.value.length > 2) { updateFromTableFilter(tableFilterForm); } }, INPUT_DEBOUNCE); input.addEventListener('input', debouncedInput); }); tableFilterInputs.change.forEach(function(input) { input.addEventListener('change', function() { updateFromTableFilter(tableFilterForm); }); }); tableFilterInputs.select.forEach(function(input) { input.addEventListener('change', function() { updateFromTableFilter(tableFilterForm); }); }); tableFilterForm.addEventListener('submit', function(event) { event.preventDefault(); updateFromTableFilter(tableFilterForm); }); } function updateFromTableFilter(tableFilterForm) { var url = serializeTableFilterToURL(); var callback = null; var focusedInput = tableFilterForm.querySelector(':focus, :active'); // focus previously focused input if (focusedInput && focusedInput.selectionStart !== null) { var selectionStart = focusedInput.selectionStart; // remove the following part of the id to get rid of the random // (yet somewhat structured) prefix we got from nudging. var prefix = findCssIdPrefix(focusedInput.id); var focusId = focusedInput.id.replace(prefix, ''); callback = function(wrapper) { var idPrefix = getLocalStorageParameter('cssIdPrefix'); var toBeFocused = wrapper.querySelector('#' + idPrefix + focusId); if (toBeFocused) { toBeFocused.focus(); toBeFocused.selectionStart = selectionStart; } }; } updateTableFrom(url, callback); } function serializeTableFilterToURL() { var url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href); var formIdElement = element.querySelector(ASYNC_TABLE_FILTER_FORM_ID_SELECTOR); if (!formIdElement) { // cannot serialize the filter form without an identifier return; } url.searchParams.set('form-identifier', formIdElement.value); url.searchParams.set('_hasdata', 'true'); url.searchParams.set(asyncTableId + '-page', '0'); tableFilterInputs.search.forEach(function(input) { url.searchParams.set(input.name, input.value); }); tableFilterInputs.input.forEach(function(input) { url.searchParams.set(input.name, input.value); }); tableFilterInputs.change.forEach(function(input) { if (input.checked) { url.searchParams.set(input.name, input.value); } }); tableFilterInputs.select.forEach(function(select) { var options = Array.from(select.querySelectorAll('option')); var selected = options.find(function(option) { return option.selected }); if (selected) { url.searchParams.set(select.name, selected.value); } }); return url; } function processLocalStorage() { var scrollTo = getLocalStorageParameter('scrollTo'); if (scrollTo && scrollTable) { window.scrollTo(scrollTo); } setLocalStorageParameter('scrollTo', null); var horizPos = getLocalStorageParameter('horizPos'); if (horizPos && scrollTable) { scrollTable.scrollLeft = horizPos; } setLocalStorageParameter('horizPos', null); } function removeListeners() { ths.forEach(function(th) { th.element.removeEventListener('click', th.clickHandler); }); pageLinks.forEach(function(link) { link.element.removeEventListener('click', link.clickHandler); }); if (pagesizeForm) { var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]') pagesizeSelect.removeEventListener('change', changePagesizeHandler); } } function linkClickHandler(event) { event.preventDefault(); var url = getClickDestination(event.target); if (!url.match(/^http/)) { url = window.location.origin + window.location.pathname + url; } updateTableFrom(url); } function getClickDestination(el) { if (!el.matches('a') && !el.querySelector('a')) { return ''; } return el.getAttribute('href') || el.querySelector('a').getAttribute('href'); } function changePagesizeHandler(event) { var pagesizeParamKey = asyncTableId + '-pagesize'; var pageParamKey = asyncTableId + '-page'; var url = new URL(getLocalStorageParameter('currentTableUrl') || window.location.href); url.searchParams.set(pagesizeParamKey, event.target.value); url.searchParams.set(pageParamKey, 0); updateTableFrom(url.href); } // fetches new sorted element from url with params and replaces contents of current element function updateTableFrom(url, callback) { if (!HttpClient) { throw new Error('HttpClient not found!'); } element.classList.add(ASYNC_TABLE_LOADING_CLASS); var headers = { 'Accept': 'text/html', [asyncTableHeader]: asyncTableId }; HttpClient.get({ url: url, headers: headers, accept: HttpClient.ACCEPT.TEXT_HTML, }).then(function(response) { return HtmlHelpers.parseResponse(response); }).then(function(response) { setLocalStorageParameter('currentTableUrl', url.href); // reset table removeListeners(); element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS); // update table with new updateWrapperContents(response); if (UtilRegistry) { UtilRegistry.setupAll(element); } if (callback && typeof callback === 'function') { setLocalStorageParameter('cssIdPrefix', response.idPrefix); callback(element); setLocalStorageParameter('cssIdPrefix', ''); } }).catch(function(err) { console.error(err); }).finally(function() { element.classList.remove(ASYNC_TABLE_LOADING_CLASS); }); } function updateWrapperContents(response) { var newPage = document.createElement('div'); newPage.appendChild(response.element); var newWrapperContents = newPage.querySelector('#' + response.idPrefix + element.id); element.innerHTML = newWrapperContents.innerHTML; } return init(); }; // returns any random nudged prefix found in the given id function findCssIdPrefix(id) { var matcher = /r\d*?__/; var maybePrefix = id.match(matcher); if (maybePrefix && maybePrefix[0]) { return maybePrefix[0] } return ''; } function setLocalStorageParameter(key, value) { var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {}; if (value !== null) { currentLSState[key] = value; } else { delete currentLSState[key]; } window.localStorage.setItem(ASYNC_TABLE_LOCAL_STORAGE_KEY, JSON.stringify(currentLSState)); } function getLocalStorageParameter(key) { var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {}; return currentLSState[key]; } // debounce function, taken from Underscore.js function debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; } // register async table utility if (UtilRegistry) { UtilRegistry.register({ name: ASYNC_TABLE_UTIL_NAME, selector: ASYNC_TABLE_UTIL_SELECTOR, setup: asyncTableUtil, }); } })();