@curveball/browser
Version:
Automatic API browser generator. A middleware that turns your JSON responses into HTML if accessed by a browser.
193 lines • 7.17 kB
JavaScript
/**
* Note, this algorithm is taken from:
*
* https://github.com/defunctzombie/form-serialize/blob/master/LICENSE
*
* It's cleaned up, modernized and converted to Typescript, but the original
* is Copyright (c) 2013 Roman Shtylman and licensed under the MIT license.
*/
// get successful control from form and assemble into object
// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2
// Matches bracket notation.
const brackets = /(\[[^[\]]*\])/g;
/**
* Serializes a form into html-json-forms format.
*
* https://www.w3.org/TR/html-json-forms/
*/
export function serializeJsonForm(form) {
let result = {};
const elements = (form === null || form === void 0 ? void 0 : form.elements) ? form.elements : [];
//Object store each radio and set if it's empty or not
const radio_store = Object.create(null);
for (const element of elements) {
if (!isValidInputField(element)) {
// ignore anyhting that is not considered a success field
continue;
}
if (!element.name) {
continue;
}
const key = element.name;
let val = element.value;
// we can't just use element.value for checkboxes cause some browsers lie to us
// they say "on" for value when the box isn't checked
if ((element.type === 'checkbox' || element.type === 'radio') && !element.checked) {
val = undefined;
}
// for checkbox
if (element.type === 'checkbox' && !element.checked) {
val = '';
}
// for radio
if (element.type === 'radio') {
if (!radio_store[element.name] && !element.checked) {
radio_store[element.name] = false;
}
else if (element.checked) {
radio_store[element.name] = true;
}
}
// if options empty is true, continue only if its radio
if (val == undefined && element.type == 'radio') {
continue;
}
// multi select boxes
if (element.type === 'select-multiple') {
val = [];
const selectOptions = element.options;
let isSelectedOptions = false;
for (const option of selectOptions) {
const hasValue = !!option.value;
if (option.selected && hasValue) {
isSelectedOptions = true;
// If using a hash serializer be sure to add the
// correct notation for an array in the multi-select
// context. Here the name attribute on the select element
// might be missing the trailing bracket pair. Both names
// "foo" and "foo[]" should be arrays.
if (key.slice(key.length - 2) !== '[]') {
result = hash_serializer(result, key + '[]', option.value);
}
else {
result = hash_serializer(result, key, option.value);
}
}
}
// Serialize if no selected options and options.empty is true
if (!isSelectedOptions) {
result = hash_serializer(result, key, '');
}
continue;
}
result = hash_serializer(result, key, val);
}
for (const key in radio_store) {
if (!radio_store[key]) {
result = hash_serializer(result, key, '');
}
}
return result;
}
function parse_keys(str) {
const keys = [];
const prefix = /^([^[\]]*)/;
const children = new RegExp(brackets);
let match = prefix.exec(str);
if (match === null || match === void 0 ? void 0 : match[1]) {
keys.push(match[1]);
}
while ((match = children.exec(str)) !== null) {
keys.push(match[1]);
}
return keys;
}
function hash_assign(result, keys, value) {
if (keys.length === 0) {
result = value;
return result;
}
const key = keys.shift();
const between = key.match(/^\[(.+?)\]$/);
if (key === '[]') {
result = result || [];
if (Array.isArray(result)) {
result.push(hash_assign(null, keys, value));
}
else {
// This might be the result of bad name attributes like "[][foo]",
// in this case the original `result` object will already be
// assigned to an object literal. Rather than coerce the object to
// an array, or cause an exception the attribute "_values" is
// assigned as an array.
result._values = result._values || [];
result._values.push(hash_assign(null, keys, value));
}
return result;
}
// Key is an attribute name and can be assigned directly.
if (!between) {
result[key] = hash_assign(result[key], keys, value);
}
else {
const string = between[1];
// +var converts the variable into a number
// better than parseInt because it doesn't truncate away trailing
// letters and actually fails if whole thing is not a number
const index = +string;
// If the characters between the brackets is not a number it is an
// attribute name and can be assigned directly.
if (isNaN(index)) {
result = result || {};
result[string] = hash_assign(result[string], keys, value);
}
else {
result = result || [];
result[index] = hash_assign(result[index], keys, value);
}
}
return result;
}
// Object/hash encoding serializer.
function hash_serializer(result, key, value) {
const matches = key.match(brackets);
// Has brackets? Use the recursive assignment function to walk the keys,
// construct any missing objects in the result tree and make the assignment
// at the end of the chain.
if (matches) {
const keys = parse_keys(key);
hash_assign(result, keys, value);
}
else {
// Non bracket notation can make assignments directly.
const existing = result[key];
// If the value has been assigned already (for instance when a radio and
// a checkbox have the same name attribute) convert the previous value
// into an array before pushing into it.
//
// NOTE: If this requirement were removed all hash creation and
// assignment could go through `hash_assign`.
if (existing) {
if (!Array.isArray(existing)) {
result[key] = [existing];
}
result[key].push(value);
}
else {
result[key] = value;
}
}
return result;
}
function isValidInputField(elem) {
const supportedElements = ['input', 'select', 'textarea'];
const unsupportedInputType = ['submit', 'button', 'image', 'reset', 'file'];
if (!supportedElements.includes(elem.nodeName.toLowerCase())) {
return false;
}
if (unsupportedInputType.includes(elem.type)) {
return false;
}
return true;
}
//# sourceMappingURL=serialize-json-form.js.map