(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 tableFilterInputs = { search: [], input: [], change: [], select: [], } function init() { if (!element) { throw new Error('Async Table utility cannot be setup without an element!'); } // param asyncTableDbHeader if (element.dataset.asyncTableDbHeader !== undefined) { asyncTableHeader = element.dataset.asyncTableDbHeader; } asyncTableId = element.querySelector('table').id; // 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('#' + 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('#' + 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(); } } 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() { tableFilterInputs.search.forEach(function(input) { var debouncedInput = debounce(function() { if (input.value.length === 0 || input.value.length > 2) { updateFromTableFilter(); } }, 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(); } }, INPUT_DEBOUNCE); input.addEventListener('input', debouncedInput); }); tableFilterInputs.change.forEach(function(input) { input.addEventListener('change', function() { updateFromTableFilter(); }); }); tableFilterInputs.select.forEach(function(input) { input.addEventListener('change', function() { updateFromTableFilter(); }); }); element.addEventListener('submit', function(event) { event.preventDefault(); updateFromTableFilter(); }); } function updateFromTableFilter() { var url = serializeTableFilterToURL(); var callback = null; var focusedSearch = tableFilterInputs.search.reduce(function(acc, input) { return acc || (input.matches(':focus') && input); }, null); // focus search input if (focusedSearch) { var selectionStart = focusedSearch.selectionStart; callback = function(wrapper) { var search = wrapper.querySelector('input[type="search"]'); if (search) { search.focus(); search.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, headers).then(function(response) { if (!response.ok) { throw new Error('Looks like there was a problem fetching ' + url.href + '. Status Code: ' + response.status); } return response.text(); }).then(function(data) { setLocalStorageParameter('currentTableUrl', url.href); // reset table removeListeners(); element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS); // update table with new updateWrapperContents(data); if (callback && typeof callback === 'function') { callback(element); } element.classList.remove(ASYNC_TABLE_LOADING_CLASS); }).catch(function(err) { console.error(err); element.classList.remove(ASYNC_TABLE_LOADING_CLASS); }); } function updateWrapperContents(newHtml) { var newPage = document.createElement('div'); newPage.innerHTML = newHtml; var newWrapperContents = newPage.querySelector('#' + element.id); element.innerHTML = newWrapperContents.innerHTML; if (UtilRegistry) { UtilRegistry.setupAll(element); } } return init(); }; 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, }); } })();