From 6a29a7d081f22799d5b015ea555c9879aace5603 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 7 Apr 2019 23:31:04 +0200 Subject: [PATCH] started refactoring async table js utility --- src/Foundation.hs | 2 +- static/js/utils/asyncTable.js | 270 +++++++++++++++++++------------ static/js/utils/checkAll.js | 1 + templates/table/colonnade.hamlet | 2 +- templates/table/layout.julius | 10 -- 5 files changed, 172 insertions(+), 113 deletions(-) delete mode 100644 templates/table/layout.julius diff --git a/src/Foundation.hs b/src/Foundation.hs index 04ed0139d..5360390d9 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1071,10 +1071,10 @@ siteLayout' headingOverride widget = do -- addScript $ StaticR js_utils_alerts_js -- addScript $ StaticR js_utils_asidenav_js -- addScript $ StaticR js_utils_asyncForm_js - -- addScript $ StaticR js_utils_asyncTable_js -- addScript $ StaticR js_utils_asyncTableFilter_js -- addScript $ StaticR js_utils_httpClient_js -- JavaScript utils + addScript $ StaticR js_utils_asyncTable_js addScript $ StaticR js_utils_checkAll_js addScript $ StaticR js_utils_form_js addScript $ StaticR js_utils_inputs_js diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index e4b7d87bc..1f2ba3c36 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -1,20 +1,40 @@ (function collonadeClosure() { '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 RESET_OPTIONS = [ 'scrollTo' ]; 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 JS_INITIALIZED_CLASS = 'js-async-table-initialized'; + var ASYNC_TABLE_FILTER_LOADING_CLASS = 'async-table-filter--loading'; - window.utils.asyncTable = function(wrapper, options) { - - options = options || {}; - var tableIdent = options.dbtIdent; - var shortCircuitHeader = options ? options.headerDBTableShortcircuit : null; + var asyncTableUtil = function(element) { + var asyncTableHeader; + var asyncTableId; + var lastHorizontalPosition; + var currentTableUrl; var ths = []; var pageLinks = []; @@ -24,21 +44,44 @@ var utilInstances = []; function init() { - var table = wrapper.querySelector('#' + tableIdent); - - if (!table) { - return; + if (!element) { + throw new Error('Async Table utility cannot be setup without an element!'); } - scrollTable = wrapper.querySelector('.scrolltable'); + // param asyncTableDbHeader + if (element.dataset.asyncTableDbHeader !== undefined) { + asyncTableHeader = element.dataset.asyncTableDbHeader; + } - // sortable table headers - ths = Array.from(table.querySelectorAll('th.sortable')).map(function(th) { + asyncTableId = element.id; + + 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 }; }); // pagination links - var pagination = wrapper.querySelector('#' + tableIdent + '-pagination'); + var pagination = element.querySelector('#' + asyncTableId + '-pagination'); if (pagination) { pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) { return { element: link }; @@ -46,32 +89,39 @@ } // pagesize form - pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form'); + pagesizeForm = element.querySelector('#' + asyncTableId + '-pagesize-form'); - // check all - utilInstances.push(window.utils.setup('checkAll', wrapper)); + // // filter + // var filterForm = element.querySelector('.' + TABLE_FILTER_FORM_CLASS); + // if (filterForm) { + // options.updateTableFrom = updateTableFrom; + // utilInstances.push(window.utils.setup('asyncTableFilter', filterForm, options)); + // } - // showhide - 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; - } + processLocalStorage(); 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() { @@ -79,6 +129,7 @@ th.clickHandler = function(event) { var boundClickHandler = clickHandler.bind(this); var horizPos = (scrollTable || {}).scrollLeft; + lastHorizontalPosition = (scrollTable || {}).scrollLeft; boundClickHandler(event, { horizPos }); }; th.element.addEventListener('click', th.clickHandler); @@ -88,13 +139,13 @@ link.clickHandler = function(event) { var boundClickHandler = clickHandler.bind(this); var tableBoundingRect = scrollTable.getBoundingClientRect(); - var tableOptions = {}; if (tableBoundingRect.top < HEADER_HEIGHT) { - tableOptions.scrollTo = { + var scrollTo = { top: (scrollTable.offsetTop || 0) - HEADER_HEIGHT, left: scrollTable.offsetLeft || 0, behavior: 'smooth', }; + setLocalStorageParameter('scrollTo', JSON.stringify(scrollTo)); } boundClickHandler(event, tableOptions); } @@ -102,7 +153,7 @@ }); if (pagesizeForm) { - var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]'); + var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]'); pagesizeSelect.addEventListener('change', changePagesizeHandler); } } @@ -117,7 +168,7 @@ }); if (pagesizeForm) { - var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]') + var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]') pagesizeSelect.removeEventListener('change', changePagesizeHandler); } } @@ -139,41 +190,42 @@ } function changePagesizeHandler(event) { - var pagesizeParamKey = tableIdent + '-pagesize'; - var pageParamKey = tableIdent + '-page'; - var url = new URL(options.currentUrl || window.location.href); + var pagesizeParamKey = asyncTableId + '-pagesize'; + var pageParamKey = asyncTableId + '-page'; + var url = new URL(currentTableUrl || window.location.href); url.searchParams.set(pagesizeParamKey, event.target.value); url.searchParams.set(pageParamKey, 0); 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) { - if (!window.utils.httpClient) { - throw new Error('httpClient not found!'); + if (!HttpClient) { + throw new Error('HttpClient not found!'); } - wrapper.classList.add(ASYNC_TABLE_LOADING_CLASS); + element.classList.add(ASYNC_TABLE_LOADING_CLASS); tableOptions = tableOptions || {}; var headers = { '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) { throw new Error('Looks like there was a problem fetching ' + url.href + '. Status Code: ' + response.status); } return response.text(); }).then(function(data) { - tableOptions.currentUrl = url.href; - removeListeners(); + currentTableUrl = url.href; + reset(); updateWrapperContents(data, tableOptions); 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) { console.error(err); }); @@ -181,57 +233,73 @@ function updateWrapperContents(newHtml, tableOptions) { tableOptions = tableOptions || {}; - wrapper.innerHTML = newHtml; - wrapper.classList.remove(JS_INITIALIZED_CLASS); - wrapper.classList.add(ASYNC_TABLE_CONTENT_CHANGED_CLASS); + element.innerHTML = newHtml; - destroyUtils(); - // merge global options and table specific options - var resetOptions = {}; - Object.keys(options) - .filter(function(key) { - return !RESET_OPTIONS.includes(key); - }) - .forEach(function(key) { - resetOptions[key] = options[key]; - }); - var combinedOptions = {}; - combinedOptions = Object.keys(tableOptions) - .filter(function(key) { - return tableOptions.hasOwnProperty(key); - }) - .map(function(key) { - return { key, value: tableOptions[key] } - }) - .reduce(function(cumulatedOpts, opt) { - cumulatedOpts[opt.key] = opt.value; - return cumulatedOpts; - }, resetOptions); + // merge global options and element specific options + // var resetOptions = {}; + // Object.keys(options) + // .filter(function(key) { + // return !RESET_OPTIONS.includes(key); + // }) + // .forEach(function(key) { + // resetOptions[key] = options[key]; + // }); + // var combinedOptions = {}; + // combinedOptions = Object.keys(tableOptions) + // .filter(function(key) { + // return tableOptions.hasOwnProperty(key); + // }) + // .map(function(key) { + // return { key, value: tableOptions[key] } + // }) + // .reduce(function(cumulatedOpts, opt) { + // cumulatedOpts[opt.key] = opt.value; + // return cumulatedOpts; + // }, resetOptions); - window.utils.setup('asyncTable', wrapper, combinedOptions); - Array.from(wrapper.querySelectorAll('form')).forEach(function(form) { - utilInstances.push(window.utils.setup('form', form)); - }); - Array.from(wrapper.querySelectorAll('.modal')).forEach(function(modal) { - utilInstances.push(window.utils.setup('modal', modal)); - }); + if (UtilRegistry) { + UtilRegistry.setupAll(); + } } - function destroyUtils() { - utilInstances.filter(function(utilInstance) { - return !!utilInstance; - }).forEach(function(utilInstance) { - utilInstance.destroy(); - }); - } - - init(); - - return { - scope: wrapper, - destroy: destroyUtils, - }; + return init(); }; + + 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, + }); + } })(); diff --git a/static/js/utils/checkAll.js b/static/js/utils/checkAll.js index 7aeb65ccb..894f5fe0d 100644 --- a/static/js/utils/checkAll.js +++ b/static/js/utils/checkAll.js @@ -98,6 +98,7 @@ th.innerHTML = ''; th.insertBefore(checkAllCheckbox, null); + // manually set up newly created checkbox if (UtilRegistry) { UtilRegistry.setup(UtilRegistry.find('checkbox')); } diff --git a/templates/table/colonnade.hamlet b/templates/table/colonnade.hamlet index 9ccea4ef8..71938bc4a 100644 --- a/templates/table/colonnade.hamlet +++ b/templates/table/colonnade.hamlet @@ -1,6 +1,6 @@ $newline never
- +
$maybe wHeaders' <- wHeaders diff --git a/templates/table/layout.julius b/templates/table/layout.julius deleted file mode 100644 index 51dad6a82..000000000 --- a/templates/table/layout.julius +++ /dev/null @@ -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 }); - } -});