started refactoring async table js utility
This commit is contained in:
parent
32bc5f4852
commit
6a29a7d081
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -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'));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user