App-Netdisco
view release on metacpan or search on metacpan
share/public/javascripts/netdisco-request.js view on Meta::CPAN
/**
* Requests to netdisco's own ajax routes, and the two form serializations the
* pages need. Kept in one place because Dancer's ajax keyword answers 404
* without the XMLHttpRequest header, and a miss on an /ajax/ path stays cached
* for the life of the worker that served it.
*/
(function () {
'use strict';
/**
* Posts form-encoded fields to one of netdisco's ajax routes.
* @param {string} url the route, already carrying uri_base
* @param {URLSearchParams} body the fields to send
* @returns {Promise<Response>} resolves for every status, including 4xx and 5xx
*/
function post(url, body) {
return fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: body
});
}
/**
* Normalizes a submitted value's line endings to CRLF, which is what a form
* submission sends.
* @param {string} value the raw field value
* @returns {string} the value with CRLF line endings
*/
function normalize(value) {
return String(value).replace(/\r?\n/g, '\r\n');
}
/**
* Collects the named fields inside an element. A field with no name, a
* disabled field, and an unchecked box or radio each contribute nothing; a
* multiple select contributes one pair per chosen option.
* @param {Element} root the element to search inside
* @param {string} selector which descendants to collect
* @returns {URLSearchParams} the collected pairs, in document order
*/
function fields(root, selector) {
const out = new URLSearchParams();
Array.prototype.forEach.call(root.querySelectorAll(selector), (el) => {
if (!el.name || el.matches(':disabled')) return;
if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked) return;
// A multiple select contributes one pair per chosen option. Its `value`
// is only the first, so a sidebar filtering on several vendors would
// send one and silently drop the rest.
if (el.type === 'select-multiple') {
Array.prototype.forEach.call(el.selectedOptions, (option) => {
out.append(el.name, normalize(option.value));
});
return;
}
out.append(el.name, normalize(el.value));
});
return out;
}
/**
* Serializes a form to a query string.
* @param {Element} form the form
* @returns {string} the query string, with no leading question mark
*/
function query(form) {
return fields(form, 'input, select, textarea').toString();
}
/**
* Fetches from one of netdisco's ajax routes.
* @param {string} url the route, already carrying uri_base
* @returns {Promise<Response>} resolves for every status, including 4xx and 5xx
*/
function get(url) {
return fetch(url, {
credentials: 'same-origin',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
}
/**
* Fetches and parses JSON: a failed response is an error rather than a body
* to parse.
* @param {string} url the route, already carrying uri_base
* @returns {Promise<any>} the parsed body
*/
function getJSON(url) {
return get(url).then((res) => {
if (!res.ok) throw new Error('request for ' + url + ' answered ' + res.status);
return res.json();
});
}
/**
* @typedef {object} NdRequestWindowProps
* @property {object} [ndRequest] the post, get, getJSON, fields and query functions above, exported for tests
*/
/** @type {Window & NdRequestWindowProps} */
const ndWindow = window;
ndWindow.ndRequest = {
post: post,
get: get,
getJSON: getJSON,
fields: fields,
query: query
};
})();
( run in 1.287 second using v1.01-cache-2.11-cpan-007c89162af )