From cc3f3fe41a35cff2f7a4cd8d2bac7e30faf236c2 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Wed, 15 May 2019 23:16:27 +0200 Subject: [PATCH] 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);