From d95edd662e2e176e0374e61600ff7f662ab719b6 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Wed, 15 May 2019 22:28:22 +0200 Subject: [PATCH 1/8] tweak js http client get/post api a little --- static/js/services/httpClient.js | 24 +++++++++++++----------- static/js/utils/asyncForm.js | 2 +- static/js/utils/asyncTable.js | 2 +- static/js/utils/massInput.js | 21 +++++++++------------ static/js/utils/modal.js | 2 +- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/static/js/services/httpClient.js b/static/js/services/httpClient.js index a69d788fd..4d6dc1820 100644 --- a/static/js/services/httpClient.js +++ b/static/js/services/httpClient.js @@ -11,19 +11,19 @@ } } - function _fetch(url, method, additionalHeaders, body) { + function _fetch(options) { var requestOptions = { credentials: 'same-origin', headers: { }, - method: method, - body: body, + method: options.method, + body: options.body, }; - Object.keys(additionalHeaders).forEach(function(headerKey) { - requestOptions.headers[headerKey] = additionalHeaders[headerKey]; + Object.keys(options.headers).forEach(function(headerKey) { + requestOptions.headers[headerKey] = options.headers[headerKey]; }); - return fetch(url, requestOptions).then( + return fetch(options.url, requestOptions).then( function(response) { _responseInterceptors.forEach(function(interceptor) { interceptor(response); }); return Promise.resolve(response); @@ -48,7 +48,7 @@ return response.text().then(function (responseText) { var docFrag = document.createRange().createContextualFragment(responseText); - + var idAttrs = ['id', 'for', 'data-conditional-input', 'data-modal-trigger']; idAttrs.forEach(function(attr) { Array.from(docFrag.querySelectorAll('[' + attr + ']')).forEach(function(input) { @@ -65,11 +65,13 @@ } return { - get: function(url, headers) { - return _fetch(url, 'GET', headers); + get: function(args) { + args.method = 'GET'; + return _fetch(args); }, - post: function(url, headers, body) { - return _fetch(url, 'POST', headers, body); + post: function(args) { + args.method = 'POST'; + return _fetch(args); }, addResponseInterceptor: addResponseInterceptor, parseHTML: parseHTML, diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index eaf6f9221..2f6d40284 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -96,7 +96,7 @@ headers[MODAL_HEADER_KEY] = MODAL_HEADER_VALUE; } - HttpClient.post(url, headers, body) + HttpClient.post({ url: url, headers: headers, body: body }) .then(function(response) { if (response.headers.get("content-type").indexOf("application/json") !== -1) {// checking response header return response.json(); diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 78d9ae90c..577ffb753 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -319,7 +319,7 @@ [asyncTableHeader]: asyncTableId }; - HttpClient.get(url, headers).then( + HttpClient.get({ url: url, headers: headers }).then( response => HttpClient.parseHTML(response, element.id) ).then(function(data) { setLocalStorageParameter('currentTableUrl', url.href); diff --git a/static/js/utils/massInput.js b/static/js/utils/massInput.js index 4f0400b6a..e7b594b3f 100644 --- a/static/js/utils/massInput.js +++ b/static/js/utils/massInput.js @@ -120,18 +120,15 @@ if (enctype !== 'multipart/form-data') headers['Content-Type'] = enctype; - - requestFn( - url, - headers, - requestBody, - ).then(response => HttpClient.parseHTML(response, element.id) - ).then(function(response) { - processResponse(response); - if (isAddCell) { - reFocusAddCell(); - } - }); + + requestFn({ url: url, headers: headers, body: requestBody }) + .then(response => HttpClient.parseHTML(response, element.id)) + .then(function(response) { + processResponse(response); + if (isAddCell) { + reFocusAddCell(); + } + }); } }; } diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index e0ba9c7c1..20c453873 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -167,7 +167,7 @@ throw new Error('HttpClient not found! Can\'t fetch modal content from ' + url); } - HttpClient.get(url, MODAL_HEADERS) + HttpClient.get({ url: url, headers: MODAL_HEADERS }) .then(response => HttpClient.parseHTML(response, element.id)) .then(processResponse); } From cc3f3fe41a35cff2f7a4cd8d2bac7e30faf236c2 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Wed, 15 May 2019 23:16:27 +0200 Subject: [PATCH 2/8] add htmlhelpers (fns from httpclient) --- src/Foundation.hs | 1 + static/js/services/htmlHelpers.js | 33 ++++++++++++++++++++ static/js/services/httpClient.js | 52 +++++++++++++------------------ static/js/utils/asyncForm.js | 28 ++++++++--------- static/js/utils/asyncTable.js | 16 ++++++---- static/js/utils/massInput.js | 17 ++++++---- static/js/utils/modal.js | 16 +++++----- 7 files changed, 100 insertions(+), 63 deletions(-) create mode 100644 static/js/services/htmlHelpers.js diff --git a/src/Foundation.hs b/src/Foundation.hs index 733a55a9f..9f9d34652 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1340,6 +1340,7 @@ siteLayout' headingOverride widget = do addScript $ StaticR js_polyfills_urlPolyfill_js -- JavaScript services addScript $ StaticR js_services_utilRegistry_js + addScript $ StaticR js_services_htmlHelpers_js addScript $ StaticR js_services_httpClient_js addScript $ StaticR js_services_i18n_js -- JavaScript utils diff --git a/static/js/services/htmlHelpers.js b/static/js/services/htmlHelpers.js new file mode 100644 index 000000000..bf7d1b163 --- /dev/null +++ b/static/js/services/htmlHelpers.js @@ -0,0 +1,33 @@ +(function () { + 'use strict'; + + window.HtmlHelpers = (function() { + + function _prefixIds(element) { + var idPrefix = Math.floor(Math.random() * 100000); + var idAttrs = ['id', 'for', 'data-conditional-input', 'data-modal-trigger']; + + idAttrs.forEach(function(attr) { + Array.from(element.querySelectorAll('[' + attr + ']')).forEach(function(input) { + var value = idPrefix + '__' + input.getAttribute(attr); + input.setAttribute(attr, value); + }); + }); + } + + function parseResponse(response) { + return response.text().then(function (responseText) { + var docFrag = document.createRange().createContextualFragment(responseText); + _prefixIds(docFrag); + return Promise.resolve(docFrag); + }, + function (error) { + return Promise.reject(error); + }).catch(function (error) { console.error(error); }); + } + + return { + parseResponse: parseResponse, + } + })(); +})(); diff --git a/static/js/services/httpClient.js b/static/js/services/httpClient.js index 4d6dc1820..f65fb0e3f 100644 --- a/static/js/services/httpClient.js +++ b/static/js/services/httpClient.js @@ -25,7 +25,9 @@ return fetch(options.url, requestOptions).then( function(response) { - _responseInterceptors.forEach(function(interceptor) { interceptor(response); }); + _responseInterceptors.forEach(function(interceptor) { + interceptor(response, options); + }); return Promise.resolve(response); }, function(error) { @@ -36,34 +38,6 @@ }); } - function parseHTML(response, idPrefix) { - if (!idPrefix) { - idPrefix = Math.floor(Math.random() * 100000); - } - - var contentType = response.headers.get("content-type"); - if (contentType.indexOf("text/html") === -1) { - throw new Error('Server returned ' + contentType + ' when HTML was expected'); - } - - return response.text().then(function (responseText) { - var docFrag = document.createRange().createContextualFragment(responseText); - - var idAttrs = ['id', 'for', 'data-conditional-input', 'data-modal-trigger']; - idAttrs.forEach(function(attr) { - Array.from(docFrag.querySelectorAll('[' + attr + ']')).forEach(function(input) { - var value = idPrefix + '__' + input.getAttribute(attr); - input.setAttribute(attr, value); - }); - }); - - return Promise.resolve(docFrag); - }, - function (error) { - return Promise.reject(error); - }).catch(function (error) { console.error(error); }); - } - return { get: function(args) { args.method = 'GET'; @@ -74,7 +48,25 @@ return _fetch(args); }, addResponseInterceptor: addResponseInterceptor, - parseHTML: parseHTML, + ACCEPT: { + TEXT_HTML: 'text/html', + JSON: 'application/json', + }, } })(); + + // HttpClient ships with its own little interceptor to throw an error + // if the response does not match the expected content-type + function contentTypeInterceptor(response, options) { + if (!options || !options.accept) { + return; + } + + var contentType = response.headers.get("content-type"); + if (!contentType.match(options.accept)) { + throw new Error('Server returned with "' + contentType + '" when "' + options.accept + '" was expected'); + } + } + + HttpClient.addResponseInterceptor(contentTypeInterceptor); })(); diff --git a/static/js/utils/asyncForm.js b/static/js/utils/asyncForm.js index 2f6d40284..4750a7e57 100644 --- a/static/js/utils/asyncForm.js +++ b/static/js/utils/asyncForm.js @@ -96,21 +96,21 @@ headers[MODAL_HEADER_KEY] = MODAL_HEADER_VALUE; } - HttpClient.post({ url: url, headers: headers, body: body }) - .then(function(response) { - if (response.headers.get("content-type").indexOf("application/json") !== -1) {// checking response header - return response.json(); - } else { - throw new TypeError('Unexpected Content-Type. Expected Content-Type: "application/json". Requested URL:' + url + '"'); - } - }).then(function(response) { - processResponse(response[0]); - }).catch(function(error) { - var failureMessage = I18n.get('asyncFormFailure'); - processResponse({ content: failureMessage }); + HttpClient.post({ + url: url, + headers: headers, + body: body, + accept: HttpClient.ACCEPT.JSON, + }).then(function(response) { + return response.json(); + }).then(function(response) { + processResponse(response[0]); + }).catch(function(error) { + var failureMessage = I18n.get('asyncFormFailure'); + processResponse({ content: failureMessage }); - element.classList.remove(ASYNC_FORM_LOADING_CLASS); - }); + element.classList.remove(ASYNC_FORM_LOADING_CLASS); + }); } return init(); diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 577ffb753..32a7bf844 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -319,15 +319,19 @@ [asyncTableHeader]: asyncTableId }; - HttpClient.get({ url: url, headers: headers }).then( - response => HttpClient.parseHTML(response, element.id) - ).then(function(data) { + HttpClient.get({ + url: url, + headers: headers, + accept: HttpClient.ACCEPT.TEXT_HTML, + }).then(function(response) { + return HtmlHelpers.parseResponse(response); + }).then(function(responseElement) { setLocalStorageParameter('currentTableUrl', url.href); // reset table removeListeners(); element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS); // update table with new - updateWrapperContents(data); + updateWrapperContents(responseElement); if (callback && typeof callback === 'function') { callback(element); @@ -340,9 +344,9 @@ }); } - function updateWrapperContents(newHtml) { + function updateWrapperContents(newHtmlElement) { var newPage = document.createElement('div'); - newPage.appendChild(newHtml); + newPage.appendChild(newHtmlElement); var newWrapperContents = newPage.querySelector('#' + element.id + '__' + element.id); element.innerHTML = newWrapperContents.innerHTML; diff --git a/static/js/utils/massInput.js b/static/js/utils/massInput.js index e7b594b3f..e0d1578e6 100644 --- a/static/js/utils/massInput.js +++ b/static/js/utils/massInput.js @@ -121,10 +121,15 @@ if (enctype !== 'multipart/form-data') headers['Content-Type'] = enctype; - requestFn({ url: url, headers: headers, body: requestBody }) - .then(response => HttpClient.parseHTML(response, element.id)) - .then(function(response) { - processResponse(response); + requestFn({ + url: url, + headers: headers, + body: requestBody, + accept: HttpClient.ACCEPT.TEXT_HTML, + }).then(function(response) { + return HtmlHelpers.parseResponse(response); + }).then(function(responseElement) { + processResponse(responseElement); if (isAddCell) { reFocusAddCell(); } @@ -159,9 +164,9 @@ button.removeEventListener('click', massInputFormSubmitHandler); } - function processResponse(response) { + function processResponse(responseElement) { element.innerHTML = ""; - element.appendChild(response); + element.appendChild(responseElement); reset(); diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 20c453873..b994b79c8 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -51,6 +51,7 @@ var modalUrl; function _init() { + console.log('modalUtil.init', { element }); if (!element) { throw new Error('Modal utility cannot be setup without an element!'); } @@ -167,15 +168,16 @@ throw new Error('HttpClient not found! Can\'t fetch modal content from ' + url); } - HttpClient.get({ url: url, headers: MODAL_HEADERS }) - .then(response => HttpClient.parseHTML(response, element.id)) - .then(processResponse); + HttpClient.get({ + url: url, + headers: MODAL_HEADERS, + accept: HttpClient.ACCEPT.TEXT_HTML, + }).then(function(response) { + return HtmlHelpers.parseResponse(response, element.id); + }).then(processResponse); } - function processResponse(responseFrag) { - var responseElement = document.createElement('div'); - responseElement.appendChild(responseFrag); - + function processResponse(responseElement) { var modalContent = document.createElement('div'); modalContent.classList.add(MODAL_CONTENT_CLASS); From 0f1abf6cd19192dc0345e1d1dce3e1be4e149b8f Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 19 May 2019 11:25:20 +0200 Subject: [PATCH 3/8] =?UTF-8?q?add=20options=20object=20to=20HtmlHelpers?= =?UTF-8?q?=20=C2=BBparseResponse=C2=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/js/services/htmlHelpers.js | 41 ++++++++++++++++++++++--------- static/js/utils/asyncTable.js | 12 ++++----- static/js/utils/massInput.js | 4 +-- static/js/utils/modal.js | 6 +++-- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/static/js/services/htmlHelpers.js b/static/js/services/htmlHelpers.js index bf7d1b163..ec025dc4b 100644 --- a/static/js/services/htmlHelpers.js +++ b/static/js/services/htmlHelpers.js @@ -3,27 +3,44 @@ window.HtmlHelpers = (function() { - function _prefixIds(element) { - var idPrefix = Math.floor(Math.random() * 100000); + // `parseResponse` takes a raw HttpClient response and an options object. + // Returns an object with `element` being an contextual fragment of the + // HTML in the response and `ifPrefix` being the prefix that was used to + // "unique-ify" the ids of the received HTML. + // Original Response IDs can optionally be kept by adding `originalIds: true` + // to the `options` object. + function parseResponse(response, options) { + options = options || {}; + + return response.text().then(function (responseText) { + var docFrag = document.createRange().createContextualFragment(responseText); + var idPrefix; + if (!options.originalIds) { + idPrefix = _getIdPrefix(); + _prefixIds(docFrag, idPrefix); + } + return Promise.resolve({ idPrefix: idPrefix, element: docFrag }); + }, + function (error) { + return Promise.reject(error); + }).catch(function (error) { console.error(error); }); + } + + function _prefixIds(element, idPrefix) { var idAttrs = ['id', 'for', 'data-conditional-input', 'data-modal-trigger']; idAttrs.forEach(function(attr) { Array.from(element.querySelectorAll('[' + attr + ']')).forEach(function(input) { - var value = idPrefix + '__' + input.getAttribute(attr); + var value = idPrefix + input.getAttribute(attr); input.setAttribute(attr, value); }); }); } - function parseResponse(response) { - return response.text().then(function (responseText) { - var docFrag = document.createRange().createContextualFragment(responseText); - _prefixIds(docFrag); - return Promise.resolve(docFrag); - }, - function (error) { - return Promise.reject(error); - }).catch(function (error) { console.error(error); }); + function _getIdPrefix() { + // leading 'r'(andom) to overcome the fact that IDs + // starting with a numeric value are not valid in CSS + return 'r' + Math.floor(Math.random() * 100000) + '__'; } return { diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 32a7bf844..6e8ee66e7 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -324,14 +324,14 @@ headers: headers, accept: HttpClient.ACCEPT.TEXT_HTML, }).then(function(response) { - return HtmlHelpers.parseResponse(response); - }).then(function(responseElement) { + return HtmlHelpers.parseResponse(response, { originalIds: true }); + }).then(function(response) { setLocalStorageParameter('currentTableUrl', url.href); // reset table removeListeners(); element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS); // update table with new - updateWrapperContents(responseElement); + updateWrapperContents(response); if (callback && typeof callback === 'function') { callback(element); @@ -344,10 +344,10 @@ }); } - function updateWrapperContents(newHtmlElement) { + function updateWrapperContents(response) { var newPage = document.createElement('div'); - newPage.appendChild(newHtmlElement); - var newWrapperContents = newPage.querySelector('#' + element.id + '__' + element.id); + newPage.appendChild(response.element); + var newWrapperContents = newPage.querySelector('#' + element.id); element.innerHTML = newWrapperContents.innerHTML; if (UtilRegistry) { diff --git a/static/js/utils/massInput.js b/static/js/utils/massInput.js index e0d1578e6..00f873ced 100644 --- a/static/js/utils/massInput.js +++ b/static/js/utils/massInput.js @@ -128,8 +128,8 @@ accept: HttpClient.ACCEPT.TEXT_HTML, }).then(function(response) { return HtmlHelpers.parseResponse(response); - }).then(function(responseElement) { - processResponse(responseElement); + }).then(function(response) { + processResponse(response.element); if (isAddCell) { reFocusAddCell(); } diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index b994b79c8..84efe8a1a 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -173,8 +173,10 @@ headers: MODAL_HEADERS, accept: HttpClient.ACCEPT.TEXT_HTML, }).then(function(response) { - return HtmlHelpers.parseResponse(response, element.id); - }).then(processResponse); + return HtmlHelpers.parseResponse(response); + }).then(function(response) { + processResponse(response.element); + }); } function processResponse(responseElement) { From c4e6b757f9dc4cc28c5adcd06b817d672df4c69f Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 19 May 2019 11:35:13 +0200 Subject: [PATCH 4/8] dont set selectionStart on non-text inputs with async table response --- static/js/utils/asyncTable.js | 2 +- static/js/utils/modal.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 6e8ee66e7..a4b2148e3 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -200,7 +200,7 @@ var focusedInput = tableFilterForm.querySelector(':focus, :active'); // focus previously focused input - if (focusedInput) { + if (focusedInput && focusedInput.selectionStart !== null) { var selectionStart = focusedInput.selectionStart; var focusId = focusedInput.id; callback = function(wrapper) { diff --git a/static/js/utils/modal.js b/static/js/utils/modal.js index 84efe8a1a..6710c0854 100644 --- a/static/js/utils/modal.js +++ b/static/js/utils/modal.js @@ -51,7 +51,6 @@ var modalUrl; function _init() { - console.log('modalUtil.init', { element }); if (!element) { throw new Error('Modal utility cannot be setup without an element!'); } From 935fd6d7eb45505aceb951fed57256d36cd13cf3 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 19 May 2019 20:04:22 +0200 Subject: [PATCH 5/8] rename originalIds to keepIds in HtmlHelpers API --- static/js/services/htmlHelpers.js | 4 ++-- static/js/utils/asyncTable.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/services/htmlHelpers.js b/static/js/services/htmlHelpers.js index ec025dc4b..30477314c 100644 --- a/static/js/services/htmlHelpers.js +++ b/static/js/services/htmlHelpers.js @@ -7,7 +7,7 @@ // Returns an object with `element` being an contextual fragment of the // HTML in the response and `ifPrefix` being the prefix that was used to // "unique-ify" the ids of the received HTML. - // Original Response IDs can optionally be kept by adding `originalIds: true` + // Original Response IDs can optionally be kept by adding `keepIds: true` // to the `options` object. function parseResponse(response, options) { options = options || {}; @@ -15,7 +15,7 @@ return response.text().then(function (responseText) { var docFrag = document.createRange().createContextualFragment(responseText); var idPrefix; - if (!options.originalIds) { + if (!options.keepIds) { idPrefix = _getIdPrefix(); _prefixIds(docFrag, idPrefix); } diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index a4b2148e3..3f4f8da56 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -324,7 +324,7 @@ headers: headers, accept: HttpClient.ACCEPT.TEXT_HTML, }).then(function(response) { - return HtmlHelpers.parseResponse(response, { originalIds: true }); + return HtmlHelpers.parseResponse(response); }).then(function(response) { setLocalStorageParameter('currentTableUrl', url.href); // reset table From cfb09b089a0ac8158d23a6729d54acacc5c22685 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 19 May 2019 20:05:15 +0200 Subject: [PATCH 6/8] fix js utils not checking whether element is already initialized --- static/js/utils/asyncTable.js | 5 +++++ static/js/utils/checkAll.js | 4 ++++ static/js/utils/massInput.js | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 3f4f8da56..61a8e9cfd 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -48,6 +48,10 @@ 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; @@ -337,6 +341,7 @@ callback(element); } + element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS); element.classList.remove(ASYNC_TABLE_LOADING_CLASS); }).catch(function(err) { console.error(err); diff --git a/static/js/utils/checkAll.js b/static/js/utils/checkAll.js index 5a15e0ac7..1c207be34 100644 --- a/static/js/utils/checkAll.js +++ b/static/js/utils/checkAll.js @@ -30,6 +30,10 @@ throw new Error('Check All utility cannot be setup without an element!'); } + if (element.classList.contains(CHECK_ALL_INITIALIZED_CLASS)) { + return false; + } + gatherColumns(); setupCheckAllCheckbox(); diff --git a/static/js/utils/massInput.js b/static/js/utils/massInput.js index 00f873ced..8dbccdca3 100644 --- a/static/js/utils/massInput.js +++ b/static/js/utils/massInput.js @@ -38,6 +38,10 @@ throw new Error('Mass Input utility cannot be setup without an element!'); } + if (element.classList.contains(MASS_INPUT_INITIALIZED_CLASS)) { + return false; + } + massInputId = element.dataset.massInputIdent || '_'; massInputForm = element.closest('form'); From cc90faf7320de85fe9ace1b4b9dfc36f4f53fd14 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 19 May 2019 20:07:03 +0200 Subject: [PATCH 7/8] fix: async table js util now knows current random css prefix --- static/js/utils/asyncTable.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 61a8e9cfd..174af2f94 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -35,6 +35,7 @@ var pageLinks = []; var pagesizeForm; var scrollTable; + var cssIdPrefix = ''; var tableFilterInputs = { search: [], @@ -100,7 +101,7 @@ } function setupPagination() { - var pagination = element.querySelector('#' + asyncTableId + '-pagination'); + var pagination = element.querySelector('#' + cssIdPrefix + asyncTableId + '-pagination'); if (pagination) { pageLinks = Array.from(pagination.querySelectorAll('.page-link')).map(function(link) { return { element: link }; @@ -126,7 +127,7 @@ function setupPageSizeSelect() { // pagesize form - pagesizeForm = element.querySelector('#' + asyncTableId + '-pagesize-form'); + pagesizeForm = element.querySelector('#' + cssIdPrefix + asyncTableId + '-pagesize-form'); if (pagesizeForm) { var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]'); @@ -206,9 +207,12 @@ // focus previously focused input if (focusedInput && focusedInput.selectionStart !== null) { var selectionStart = focusedInput.selectionStart; - var focusId = focusedInput.id; + // remove the following part of the id to get rid of the random + // (yet somewhat structured) prefix we got from nudging. + var matcher = /r\d*?__/; + var focusId = focusedInput.id.replace(matcher, ''); callback = function(wrapper) { - var toBeFocused = wrapper.querySelector('#' + focusId); + var toBeFocused = wrapper.querySelector('#' + cssIdPrefix + focusId); if (toBeFocused) { toBeFocused.focus(); toBeFocused.selectionStart = selectionStart; @@ -352,7 +356,8 @@ function updateWrapperContents(response) { var newPage = document.createElement('div'); newPage.appendChild(response.element); - var newWrapperContents = newPage.querySelector('#' + element.id); + cssIdPrefix = response.idPrefix; + var newWrapperContents = newPage.querySelector('#' + cssIdPrefix + element.id); element.innerHTML = newWrapperContents.innerHTML; if (UtilRegistry) { From c8f4a6f5281396e2010b134b547f395247f2f9f4 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Mon, 20 May 2019 23:07:40 +0200 Subject: [PATCH 8/8] take id nudging in async table into account --- static/js/utils/asyncTable.js | 40 +++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/static/js/utils/asyncTable.js b/static/js/utils/asyncTable.js index 174af2f94..8877c7991 100644 --- a/static/js/utils/asyncTable.js +++ b/static/js/utils/asyncTable.js @@ -58,7 +58,9 @@ asyncTableHeader = element.dataset.asyncTableDbHeader; } - asyncTableId = element.querySelector('table').id; + var rawTableId = element.querySelector('table').id; + cssIdPrefix = findCssIdPrefix(rawTableId); + asyncTableId = rawTableId.replace(cssIdPrefix, ''); // find scrolltable wrapper scrollTable = element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR); @@ -209,10 +211,11 @@ 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 matcher = /r\d*?__/; - var focusId = focusedInput.id.replace(matcher, ''); + var prefix = findCssIdPrefix(focusedInput.id); + var focusId = focusedInput.id.replace(prefix, ''); callback = function(wrapper) { - var toBeFocused = wrapper.querySelector('#' + cssIdPrefix + focusId); + var idPrefix = getLocalStorageParameter('cssIdPrefix'); + var toBeFocused = wrapper.querySelector('#' + idPrefix + focusId); if (toBeFocused) { toBeFocused.focus(); toBeFocused.selectionStart = selectionStart; @@ -341,14 +344,18 @@ // update table with new updateWrapperContents(response); - if (callback && typeof callback === 'function') { - callback(element); + if (UtilRegistry) { + UtilRegistry.setupAll(element); } - element.classList.add(ASYNC_TABLE_INITIALIZED_CLASS); - element.classList.remove(ASYNC_TABLE_LOADING_CLASS); + 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); }); } @@ -356,18 +363,23 @@ function updateWrapperContents(response) { var newPage = document.createElement('div'); newPage.appendChild(response.element); - cssIdPrefix = response.idPrefix; - var newWrapperContents = newPage.querySelector('#' + cssIdPrefix + element.id); + var newWrapperContents = newPage.querySelector('#' + response.idPrefix + element.id); element.innerHTML = newWrapperContents.innerHTML; - - if (UtilRegistry) { - UtilRegistry.setupAll(element); - } } 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) {