Merge branch 'http-client-html-helpers' into 'master'
Http client html helpers See merge request !225
This commit is contained in:
commit
a4f8aaec81
@ -1334,6 +1334,7 @@ siteLayout' headingOverride widget = do
|
|||||||
addScript $ StaticR js_polyfills_urlPolyfill_js
|
addScript $ StaticR js_polyfills_urlPolyfill_js
|
||||||
-- JavaScript services
|
-- JavaScript services
|
||||||
addScript $ StaticR js_services_utilRegistry_js
|
addScript $ StaticR js_services_utilRegistry_js
|
||||||
|
addScript $ StaticR js_services_htmlHelpers_js
|
||||||
addScript $ StaticR js_services_httpClient_js
|
addScript $ StaticR js_services_httpClient_js
|
||||||
addScript $ StaticR js_services_i18n_js
|
addScript $ StaticR js_services_i18n_js
|
||||||
-- JavaScript utils
|
-- JavaScript utils
|
||||||
|
|||||||
50
static/js/services/htmlHelpers.js
Normal file
50
static/js/services/htmlHelpers.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
window.HtmlHelpers = (function() {
|
||||||
|
|
||||||
|
// `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 `keepIds: 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.keepIds) {
|
||||||
|
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);
|
||||||
|
input.setAttribute(attr, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
parseResponse: parseResponse,
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
})();
|
||||||
@ -11,21 +11,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _fetch(url, method, additionalHeaders, body) {
|
function _fetch(options) {
|
||||||
var requestOptions = {
|
var requestOptions = {
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
headers: { },
|
headers: { },
|
||||||
method: method,
|
method: options.method,
|
||||||
body: body,
|
body: options.body,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(additionalHeaders).forEach(function(headerKey) {
|
Object.keys(options.headers).forEach(function(headerKey) {
|
||||||
requestOptions.headers[headerKey] = additionalHeaders[headerKey];
|
requestOptions.headers[headerKey] = options.headers[headerKey];
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetch(url, requestOptions).then(
|
return fetch(options.url, requestOptions).then(
|
||||||
function(response) {
|
function(response) {
|
||||||
_responseInterceptors.forEach(function(interceptor) { interceptor(response); });
|
_responseInterceptors.forEach(function(interceptor) {
|
||||||
|
interceptor(response, options);
|
||||||
|
});
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
},
|
},
|
||||||
function(error) {
|
function(error) {
|
||||||
@ -36,43 +38,35 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
return {
|
||||||
get: function(url, headers) {
|
get: function(args) {
|
||||||
return _fetch(url, 'GET', headers);
|
args.method = 'GET';
|
||||||
|
return _fetch(args);
|
||||||
},
|
},
|
||||||
post: function(url, headers, body) {
|
post: function(args) {
|
||||||
return _fetch(url, 'POST', headers, body);
|
args.method = 'POST';
|
||||||
|
return _fetch(args);
|
||||||
},
|
},
|
||||||
addResponseInterceptor: addResponseInterceptor,
|
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);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -96,21 +96,21 @@
|
|||||||
headers[MODAL_HEADER_KEY] = MODAL_HEADER_VALUE;
|
headers[MODAL_HEADER_KEY] = MODAL_HEADER_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient.post(url, headers, body)
|
HttpClient.post({
|
||||||
.then(function(response) {
|
url: url,
|
||||||
if (response.headers.get("content-type").indexOf("application/json") !== -1) {// checking response header
|
headers: headers,
|
||||||
return response.json();
|
body: body,
|
||||||
} else {
|
accept: HttpClient.ACCEPT.JSON,
|
||||||
throw new TypeError('Unexpected Content-Type. Expected Content-Type: "application/json". Requested URL:' + url + '"');
|
}).then(function(response) {
|
||||||
}
|
return response.json();
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
processResponse(response[0]);
|
processResponse(response[0]);
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
var failureMessage = I18n.get('asyncFormFailure');
|
var failureMessage = I18n.get('asyncFormFailure');
|
||||||
processResponse({ content: failureMessage });
|
processResponse({ content: failureMessage });
|
||||||
|
|
||||||
element.classList.remove(ASYNC_FORM_LOADING_CLASS);
|
element.classList.remove(ASYNC_FORM_LOADING_CLASS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return init();
|
return init();
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
var pageLinks = [];
|
var pageLinks = [];
|
||||||
var pagesizeForm;
|
var pagesizeForm;
|
||||||
var scrollTable;
|
var scrollTable;
|
||||||
|
var cssIdPrefix = '';
|
||||||
|
|
||||||
var tableFilterInputs = {
|
var tableFilterInputs = {
|
||||||
search: [],
|
search: [],
|
||||||
@ -48,12 +49,18 @@
|
|||||||
throw new Error('Async Table utility cannot be setup without an element!');
|
throw new Error('Async Table utility cannot be setup without an element!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.classList.contains(ASYNC_TABLE_INITIALIZED_CLASS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// param asyncTableDbHeader
|
// param asyncTableDbHeader
|
||||||
if (element.dataset.asyncTableDbHeader !== undefined) {
|
if (element.dataset.asyncTableDbHeader !== undefined) {
|
||||||
asyncTableHeader = element.dataset.asyncTableDbHeader;
|
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
|
// find scrolltable wrapper
|
||||||
scrollTable = element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR);
|
scrollTable = element.querySelector(ASYNC_TABLE_SCROLLTABLE_SELECTOR);
|
||||||
@ -96,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupPagination() {
|
function setupPagination() {
|
||||||
var pagination = element.querySelector('#' + asyncTableId + '-pagination');
|
var pagination = element.querySelector('#' + cssIdPrefix + 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 };
|
||||||
@ -122,7 +129,7 @@
|
|||||||
|
|
||||||
function setupPageSizeSelect() {
|
function setupPageSizeSelect() {
|
||||||
// pagesize form
|
// pagesize form
|
||||||
pagesizeForm = element.querySelector('#' + asyncTableId + '-pagesize-form');
|
pagesizeForm = element.querySelector('#' + cssIdPrefix + asyncTableId + '-pagesize-form');
|
||||||
|
|
||||||
if (pagesizeForm) {
|
if (pagesizeForm) {
|
||||||
var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]');
|
var pagesizeSelect = pagesizeForm.querySelector('[name=' + asyncTableId + '-pagesize]');
|
||||||
@ -200,11 +207,15 @@
|
|||||||
|
|
||||||
var focusedInput = tableFilterForm.querySelector(':focus, :active');
|
var focusedInput = tableFilterForm.querySelector(':focus, :active');
|
||||||
// focus previously focused input
|
// focus previously focused input
|
||||||
if (focusedInput) {
|
if (focusedInput && focusedInput.selectionStart !== null) {
|
||||||
var selectionStart = focusedInput.selectionStart;
|
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 prefix = findCssIdPrefix(focusedInput.id);
|
||||||
|
var focusId = focusedInput.id.replace(prefix, '');
|
||||||
callback = function(wrapper) {
|
callback = function(wrapper) {
|
||||||
var toBeFocused = wrapper.querySelector('#' + focusId);
|
var idPrefix = getLocalStorageParameter('cssIdPrefix');
|
||||||
|
var toBeFocused = wrapper.querySelector('#' + idPrefix + focusId);
|
||||||
if (toBeFocused) {
|
if (toBeFocused) {
|
||||||
toBeFocused.focus();
|
toBeFocused.focus();
|
||||||
toBeFocused.selectionStart = selectionStart;
|
toBeFocused.selectionStart = selectionStart;
|
||||||
@ -319,41 +330,56 @@
|
|||||||
[asyncTableHeader]: asyncTableId
|
[asyncTableHeader]: asyncTableId
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpClient.get(url, headers).then(
|
HttpClient.get({
|
||||||
response => HttpClient.parseHTML(response, element.id)
|
url: url,
|
||||||
).then(function(data) {
|
headers: headers,
|
||||||
|
accept: HttpClient.ACCEPT.TEXT_HTML,
|
||||||
|
}).then(function(response) {
|
||||||
|
return HtmlHelpers.parseResponse(response);
|
||||||
|
}).then(function(response) {
|
||||||
setLocalStorageParameter('currentTableUrl', url.href);
|
setLocalStorageParameter('currentTableUrl', url.href);
|
||||||
// reset table
|
// reset table
|
||||||
removeListeners();
|
removeListeners();
|
||||||
element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
|
element.classList.remove(ASYNC_TABLE_INITIALIZED_CLASS);
|
||||||
// update table with new
|
// update table with new
|
||||||
updateWrapperContents(data);
|
updateWrapperContents(response);
|
||||||
|
|
||||||
if (callback && typeof callback === 'function') {
|
if (UtilRegistry) {
|
||||||
callback(element);
|
UtilRegistry.setupAll(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
element.classList.remove(ASYNC_TABLE_LOADING_CLASS);
|
if (callback && typeof callback === 'function') {
|
||||||
|
setLocalStorageParameter('cssIdPrefix', response.idPrefix);
|
||||||
|
callback(element);
|
||||||
|
setLocalStorageParameter('cssIdPrefix', '');
|
||||||
|
}
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
}).finally(function() {
|
||||||
element.classList.remove(ASYNC_TABLE_LOADING_CLASS);
|
element.classList.remove(ASYNC_TABLE_LOADING_CLASS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWrapperContents(newHtml) {
|
function updateWrapperContents(response) {
|
||||||
var newPage = document.createElement('div');
|
var newPage = document.createElement('div');
|
||||||
newPage.appendChild(newHtml);
|
newPage.appendChild(response.element);
|
||||||
var newWrapperContents = newPage.querySelector('#' + element.id + '__' + element.id);
|
var newWrapperContents = newPage.querySelector('#' + response.idPrefix + element.id);
|
||||||
element.innerHTML = newWrapperContents.innerHTML;
|
element.innerHTML = newWrapperContents.innerHTML;
|
||||||
|
|
||||||
if (UtilRegistry) {
|
|
||||||
UtilRegistry.setupAll(element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return init();
|
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) {
|
function setLocalStorageParameter(key, value) {
|
||||||
var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
|
var currentLSState = JSON.parse(window.localStorage.getItem(ASYNC_TABLE_LOCAL_STORAGE_KEY)) || {};
|
||||||
if (value !== null) {
|
if (value !== null) {
|
||||||
|
|||||||
@ -30,6 +30,10 @@
|
|||||||
throw new Error('Check All utility cannot be setup without an element!');
|
throw new Error('Check All utility cannot be setup without an element!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.classList.contains(CHECK_ALL_INITIALIZED_CLASS)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
gatherColumns();
|
gatherColumns();
|
||||||
setupCheckAllCheckbox();
|
setupCheckAllCheckbox();
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,10 @@
|
|||||||
throw new Error('Mass Input utility cannot be setup without an element!');
|
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 || '_';
|
massInputId = element.dataset.massInputIdent || '_';
|
||||||
massInputForm = element.closest('form');
|
massInputForm = element.closest('form');
|
||||||
|
|
||||||
@ -120,18 +124,20 @@
|
|||||||
|
|
||||||
if (enctype !== 'multipart/form-data')
|
if (enctype !== 'multipart/form-data')
|
||||||
headers['Content-Type'] = enctype;
|
headers['Content-Type'] = enctype;
|
||||||
|
|
||||||
requestFn(
|
requestFn({
|
||||||
url,
|
url: url,
|
||||||
headers,
|
headers: headers,
|
||||||
requestBody,
|
body: requestBody,
|
||||||
).then(response => HttpClient.parseHTML(response, element.id)
|
accept: HttpClient.ACCEPT.TEXT_HTML,
|
||||||
).then(function(response) {
|
}).then(function(response) {
|
||||||
processResponse(response);
|
return HtmlHelpers.parseResponse(response);
|
||||||
if (isAddCell) {
|
}).then(function(response) {
|
||||||
reFocusAddCell();
|
processResponse(response.element);
|
||||||
}
|
if (isAddCell) {
|
||||||
});
|
reFocusAddCell();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -162,9 +168,9 @@
|
|||||||
button.removeEventListener('click', massInputFormSubmitHandler);
|
button.removeEventListener('click', massInputFormSubmitHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processResponse(response) {
|
function processResponse(responseElement) {
|
||||||
element.innerHTML = "";
|
element.innerHTML = "";
|
||||||
element.appendChild(response);
|
element.appendChild(responseElement);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
|
|||||||
@ -167,15 +167,18 @@
|
|||||||
throw new Error('HttpClient not found! Can\'t fetch modal content from ' + url);
|
throw new Error('HttpClient not found! Can\'t fetch modal content from ' + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient.get(url, MODAL_HEADERS)
|
HttpClient.get({
|
||||||
.then(response => HttpClient.parseHTML(response, element.id))
|
url: url,
|
||||||
.then(processResponse);
|
headers: MODAL_HEADERS,
|
||||||
|
accept: HttpClient.ACCEPT.TEXT_HTML,
|
||||||
|
}).then(function(response) {
|
||||||
|
return HtmlHelpers.parseResponse(response);
|
||||||
|
}).then(function(response) {
|
||||||
|
processResponse(response.element);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function processResponse(responseFrag) {
|
function processResponse(responseElement) {
|
||||||
var responseElement = document.createElement('div');
|
|
||||||
responseElement.appendChild(responseFrag);
|
|
||||||
|
|
||||||
var modalContent = document.createElement('div');
|
var modalContent = document.createElement('div');
|
||||||
modalContent.classList.add(MODAL_CONTENT_CLASS);
|
modalContent.classList.add(MODAL_CONTENT_CLASS);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user