started refactoring async table js utility

This commit is contained in:
Felix Hamann 2019-04-07 23:31:04 +02:00
parent 32bc5f4852
commit 6a29a7d081
5 changed files with 172 additions and 113 deletions

View File

@ -1071,10 +1071,10 @@ siteLayout' headingOverride widget = do
-- addScript $ StaticR js_utils_alerts_js -- addScript $ StaticR js_utils_alerts_js
-- addScript $ StaticR js_utils_asidenav_js -- addScript $ StaticR js_utils_asidenav_js
-- addScript $ StaticR js_utils_asyncForm_js -- addScript $ StaticR js_utils_asyncForm_js
-- addScript $ StaticR js_utils_asyncTable_js
-- addScript $ StaticR js_utils_asyncTableFilter_js -- addScript $ StaticR js_utils_asyncTableFilter_js
-- addScript $ StaticR js_utils_httpClient_js -- addScript $ StaticR js_utils_httpClient_js
-- JavaScript utils -- JavaScript utils
addScript $ StaticR js_utils_asyncTable_js
addScript $ StaticR js_utils_checkAll_js addScript $ StaticR js_utils_checkAll_js
addScript $ StaticR js_utils_form_js addScript $ StaticR js_utils_form_js
addScript $ StaticR js_utils_inputs_js addScript $ StaticR js_utils_inputs_js

View File

@ -1,20 +1,40 @@
(function collonadeClosure() { (function collonadeClosure() {
'use strict'; 'use strict';
window.utils = window.utils || {}; /**
*
* Async Table Utility
* makes table filters, sorting and pagination behave asynchronously via AJAX calls
*
* Attribute: [none]
* (will be set up automatically on tables)
*
* Example usage:
* (regular table)
*/
var ASYNC_TABLE_UTIL_NAME = 'asyncTable';
var ASYNC_TABLE_UTIL_SELECTOR = 'table';
var ASYNC_TABLE_LOCAL_STORAGE_KEY = 'ASYNC_TABLE';
var ASYNC_TABLE_INITIALIZED_CLASS = 'async-table--initialized';
var INPUT_DEBOUNCE = 600;
var HEADER_HEIGHT = 80; var HEADER_HEIGHT = 80;
var RESET_OPTIONS = [ 'scrollTo' ]; var RESET_OPTIONS = [ 'scrollTo' ];
var TABLE_FILTER_FORM_CLASS = 'table-filter-form'; var TABLE_FILTER_FORM_CLASS = 'table-filter-form';
var ASYNC_TABLE_CONTENT_CHANGED_CLASS = 'async-table--changed';
var ASYNC_TABLE_LOADING_CLASS = 'async-table--loading'; var ASYNC_TABLE_LOADING_CLASS = 'async-table--loading';
var JS_INITIALIZED_CLASS = 'js-async-table-initialized'; var ASYNC_TABLE_FILTER_LOADING_CLASS = 'async-table-filter--loading';
window.utils.asyncTable = function(wrapper, options) { var asyncTableUtil = function(element) {
var asyncTableHeader;
options = options || {}; var asyncTableId;
var tableIdent = options.dbtIdent; var lastHorizontalPosition;
var shortCircuitHeader = options ? options.headerDBTableShortcircuit : null; var currentTableUrl;
var ths = []; var ths = [];
var pageLinks = []; var pageLinks = [];
@ -24,21 +44,44 @@
var utilInstances = []; var utilInstances = [];
function init() { function init() {
var table = wrapper.querySelector('#' + tableIdent); if (!element) {
throw new Error('Async Table utility cannot be setup without an element!');
if (!table) {
return;
} }
scrollTable = wrapper.querySelector('.scrolltable'); // param asyncTableDbHeader
if (element.dataset.asyncTableDbHeader !== undefined) {
asyncTableHeader = element.dataset.asyncTableDbHeader;
}
// sortable table headers asyncTableId = element.id;
ths = Array.from(table.querySelectorAll('th.sortable')).map(function(th) {
console.log('asyncTable.init()', { asyncTableId });
setup();
// mark initialized
element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
return {
name: ASYNC_TABLE_UTIL_NAME,
element: element,
destroy: function() {},
};
}
function setup() {
scrollTable = element.closest('.scrolltable');
if (!scrollTable) {
return false;
}
// sortable element headers
ths = Array.from(element.querySelectorAll('th.sortable')).map(function(th) {
return { element: th }; return { element: th };
}); });
// pagination links // pagination links
var pagination = wrapper.querySelector('#' + tableIdent + '-pagination'); var pagination = element.querySelector('#' + asyncTableId + '-pagination');
if (pagination) { if (pagination) {
pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) { pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) {
return { element: link }; return { element: link };
@ -46,32 +89,39 @@
} }
// pagesize form // pagesize form
pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form'); pagesizeForm = element.querySelector('#' + asyncTableId + '-pagesize-form');
// check all // // filter
utilInstances.push(window.utils.setup('checkAll', wrapper)); // var filterForm = element.querySelector('.' + TABLE_FILTER_FORM_CLASS);
// if (filterForm) {
// options.updateTableFrom = updateTableFrom;
// utilInstances.push(window.utils.setup('asyncTableFilter', filterForm, options));
// }
// showhide processLocalStorage();
utilInstances.push(window.utils.setup('showHide', wrapper));
// filter
var filterForm = wrapper.querySelector('.' + TABLE_FILTER_FORM_CLASS);
if (filterForm) {
options.updateTableFrom = updateTableFrom;
utilInstances.push(window.utils.setup('asyncTableFilter', filterForm, options));
}
// take options into account
if (options.scrollTo) {
window.scrollTo(options.scrollTo);
}
if (options.horizPos && scrollTable) {
scrollTable.scrollLeft = options.horizPos;
}
setupListeners(); setupListeners();
wrapper.classList.add(JS_INITIALIZED_CLASS); element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS);
}
function reset() {
removeListeners();
element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
}
function processLocalStorage() {
var scrollTo = getLocalStorageParameter('scrollTo');
if (scrollTo && scrollTable) {
window.scrollTo(scrollTo);
scrollTo = null;
}
var horizPos = getLocalStorageParameter('horizPos');
if (horizPos && scrollTable) {
scrollTable.scrollLeft = lastHorizontalPosition;
lastHorizontalPosition = null;
}
} }
function setupListeners() { function setupListeners() {
@ -79,6 +129,7 @@
th.clickHandler = function(event) { th.clickHandler = function(event) {
var boundClickHandler = clickHandler.bind(this); var boundClickHandler = clickHandler.bind(this);
var horizPos = (scrollTable || {}).scrollLeft; var horizPos = (scrollTable || {}).scrollLeft;
lastHorizontalPosition = (scrollTable || {}).scrollLeft;
boundClickHandler(event, { horizPos }); boundClickHandler(event, { horizPos });
}; };
th.element.addEventListener('click', th.clickHandler); th.element.addEventListener('click', th.clickHandler);
@ -88,13 +139,13 @@
link.clickHandler = function(event) { link.clickHandler = function(event) {
var boundClickHandler = clickHandler.bind(this); var boundClickHandler = clickHandler.bind(this);
var tableBoundingRect = scrollTable.getBoundingClientRect(); var tableBoundingRect = scrollTable.getBoundingClientRect();
var tableOptions = {};
if (tableBoundingRect.top < HEADER_HEIGHT) { if (tableBoundingRect.top < HEADER_HEIGHT) {
tableOptions.scrollTo = { var scrollTo = {
top: (scrollTable.offsetTop || 0) - HEADER_HEIGHT, top: (scrollTable.offsetTop || 0) - HEADER_HEIGHT,
left: scrollTable.offsetLeft || 0, left: scrollTable.offsetLeft || 0,
behavior: 'smooth', behavior: 'smooth',
}; };
setLocalStorageParameter('scrollTo', JSON.stringify(scrollTo));
} }
boundClickHandler(event, tableOptions); boundClickHandler(event, tableOptions);
} }
@ -102,7 +153,7 @@
}); });
if (pagesizeForm) { if (pagesizeForm) {
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]'); var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]');
pagesizeSelect.addEventListener('change', changePagesizeHandler); pagesizeSelect.addEventListener('change', changePagesizeHandler);
} }
} }
@ -117,7 +168,7 @@
}); });
if (pagesizeForm) { if (pagesizeForm) {
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]') var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]')
pagesizeSelect.removeEventListener('change', changePagesizeHandler); pagesizeSelect.removeEventListener('change', changePagesizeHandler);
} }
} }
@ -139,41 +190,42 @@
} }
function changePagesizeHandler(event) { function changePagesizeHandler(event) {
var pagesizeParamKey = tableIdent + '-pagesize'; var pagesizeParamKey = asyncTableId + '-pagesize';
var pageParamKey = tableIdent + '-page'; var pageParamKey = asyncTableId + '-page';
var url = new URL(options.currentUrl || window.location.href); var url = new URL(currentTableUrl || window.location.href);
url.searchParams.set(pagesizeParamKey, event.target.value); url.searchParams.set(pagesizeParamKey, event.target.value);
url.searchParams.set(pageParamKey, 0); url.searchParams.set(pageParamKey, 0);
updateTableFrom(url); updateTableFrom(url);
} }
// fetches new sorted table from url with params and replaces contents of current table // fetches new sorted element from url with params and replaces contents of current element
function updateTableFrom(url, tableOptions, callback) { function updateTableFrom(url, tableOptions, callback) {
if (!window.utils.httpClient) { if (!HttpClient) {
throw new Error('httpClient not found!'); throw new Error('HttpClient not found!');
} }
wrapper.classList.add(ASYNC_TABLE_LOADING_CLASS); element.classList.add(ASYNC_TABLE_LOADING_CLASS);
tableOptions = tableOptions || {}; tableOptions = tableOptions || {};
var headers = { var headers = {
'Accept': 'text/html', 'Accept': 'text/html',
[shortCircuitHeader]: tableIdent [asyncTableHeader]: asyncTableId
}; };
window.utils.httpClient.get(url, headers).then(function(response) {
HttpClient.get(url, headers).then(function(response) {
if (!response.ok) { if (!response.ok) {
throw new Error('Looks like there was a problem fetching ' + url.href + '. Status Code: ' + response.status); throw new Error('Looks like there was a problem fetching ' + url.href + '. Status Code: ' + response.status);
} }
return response.text(); return response.text();
}).then(function(data) { }).then(function(data) {
tableOptions.currentUrl = url.href; currentTableUrl = url.href;
removeListeners(); reset();
updateWrapperContents(data, tableOptions); updateWrapperContents(data, tableOptions);
if (callback && typeof callback === 'function') { if (callback && typeof callback === 'function') {
callback(wrapper); callback(element);
} }
wrapper.classList.remove(ASYNC_TABLE_LOADING_CLASS); element.classList.remove(ASYNC_TABLE_LOADING_CLASS);
}).catch(function(err) { }).catch(function(err) {
console.error(err); console.error(err);
}); });
@ -181,57 +233,73 @@
function updateWrapperContents(newHtml, tableOptions) { function updateWrapperContents(newHtml, tableOptions) {
tableOptions = tableOptions || {}; tableOptions = tableOptions || {};
wrapper.innerHTML = newHtml; element.innerHTML = newHtml;
wrapper.classList.remove(JS_INITIALIZED_CLASS);
wrapper.classList.add(ASYNC_TABLE_CONTENT_CHANGED_CLASS);
destroyUtils();
// merge global options and table specific options // merge global options and element specific options
var resetOptions = {}; // var resetOptions = {};
Object.keys(options) // Object.keys(options)
.filter(function(key) { // .filter(function(key) {
return !RESET_OPTIONS.includes(key); // return !RESET_OPTIONS.includes(key);
}) // })
.forEach(function(key) { // .forEach(function(key) {
resetOptions[key] = options[key]; // resetOptions[key] = options[key];
}); // });
var combinedOptions = {}; // var combinedOptions = {};
combinedOptions = Object.keys(tableOptions) // combinedOptions = Object.keys(tableOptions)
.filter(function(key) { // .filter(function(key) {
return tableOptions.hasOwnProperty(key); // return tableOptions.hasOwnProperty(key);
}) // })
.map(function(key) { // .map(function(key) {
return { key, value: tableOptions[key] } // return { key, value: tableOptions[key] }
}) // })
.reduce(function(cumulatedOpts, opt) { // .reduce(function(cumulatedOpts, opt) {
cumulatedOpts[opt.key] = opt.value; // cumulatedOpts[opt.key] = opt.value;
return cumulatedOpts; // return cumulatedOpts;
}, resetOptions); // }, resetOptions);
window.utils.setup('asyncTable', wrapper, combinedOptions);
Array.from(wrapper.querySelectorAll('form')).forEach(function(form) { if (UtilRegistry) {
utilInstances.push(window.utils.setup('form', form)); UtilRegistry.setupAll();
}); }
Array.from(wrapper.querySelectorAll('.modal')).forEach(function(modal) {
utilInstances.push(window.utils.setup('modal', modal));
});
} }
function destroyUtils() { return init();
utilInstances.filter(function(utilInstance) {
return !!utilInstance;
}).forEach(function(utilInstance) {
utilInstance.destroy();
});
}
init();
return {
scope: wrapper,
destroy: destroyUtils,
};
}; };
function setLocalStorageParameter(key, value) {
var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
currentLSState[key] = value;
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,
});
}
})(); })();

View File

@ -98,6 +98,7 @@
th.innerHTML = ''; th.innerHTML = '';
th.insertBefore(checkAllCheckbox, null); th.insertBefore(checkAllCheckbox, null);
// manually set up newly created checkbox
if (UtilRegistry) { if (UtilRegistry) {
UtilRegistry.setup(UtilRegistry.find('checkbox')); UtilRegistry.setup(UtilRegistry.find('checkbox'));
} }

View File

@ -1,6 +1,6 @@
$newline never $newline never
<div .scrolltable> <div .scrolltable>
<table *{dbsAttrs'}> <table *{dbsAttrs'} data-async-table-db-header=#{toPathPiece HeaderDBTableShortcircuit}>
$maybe wHeaders' <- wHeaders $maybe wHeaders' <- wHeaders
<thead> <thead>
<tr .table__row.table__row--head> <tr .table__row.table__row--head>

View File

@ -1,10 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
var dbtIdent = #{String dbtIdent};
var headerDBTableShortcircuit = #{String (toPathPiece HeaderDBTableShortcircuit)};
var selector = '#' + dbtIdent + '-table-wrapper';
var wrapper = document.querySelector(selector);
if (wrapper) {
window.utils.setup('asyncTable', wrapper, { headerDBTableShortcircuit, dbtIdent });
}
});