formsquare
Version:
Serialize html5 forms the smart way
206 lines (165 loc) • 4.57 kB
JavaScript
import { readFile } from "./files.js";
import { getPath, initialize } from "./path.js";
import { getWeek } from "./values.js";
import {
any,
contains,
isObject,
map,
reduce,
selectedValues,
startsWith,
} from "./utils.js";
function getValue(input, maps = []) {
if (input.type === "select-multiple") {
return selectedValues(input);
}
if (input.type === "number" || input.type === "range") {
return Number.parseFloat(input.value);
}
if (
contains(input.getAttribute("type"), ["date", "datetime-local", "month"])
) {
const date = new Date(input.value);
if (date.toString() === "Invalid Date") {
return input.value;
}
return date;
}
if (input.getAttribute("type") === "week") {
return getWeek(input.value) || input.getAttribute("value");
}
if (input.type === "file") {
if (input.multiple) {
if (input.files.length === 0) {
return Promise.resolve([]);
}
return Promise.all(map(readFile, input.files));
}
return readFile(input.files[0]);
}
let { value } = input;
maps.forEach((fn) => {
value = fn(value, input);
});
return value;
}
function fillSparse(node, attr) {
if (Array.isArray(node) && typeof attr === "number" && node.length < attr) {
for (let i = node.length; i < attr; i += 1) {
if (typeof node[i] === "undefined") {
Object.assign(node, { [i]: null });
}
}
}
}
function setLeaf(leaf, name, value, asObject) {
const attr = name === -1 ? leaf.length : name;
if (typeof leaf[attr] !== "undefined" && leaf[attr] !== null) {
if (!Array.isArray(leaf[attr])) {
Object.assign(leaf, { [attr]: [leaf[attr]] });
}
leaf[attr].push(asObject ? { "": value } : value);
} else {
fillSparse(leaf, attr);
Object.assign(leaf, { [attr]: asObject ? { "": value } : value });
}
}
function branch(path, node) {
if (path.length <= 1) {
return node;
}
const first = path[0];
const rest = path.slice(1);
const attr = first === -1 ? node.length : first;
const next = node[attr];
if (typeof next === "undefined") {
const nextBranch = typeof rest[0] === "number" ? [] : Object.create(null);
if (Array.isArray(node) && attr === -1) {
node.push(nextBranch);
return branch(rest, nextBranch);
}
fillSparse(node, attr);
Object.assign(node, { [attr]: nextBranch });
}
return branch(rest, node[attr]);
}
function hasArrayLeaf(path) {
return typeof path.slice(-1)[0] === "number";
}
function nonMember(obj, path) {
// This is a non-member of an array.
let node = obj;
if (node === null) {
// The array hasn't been initialized yet.
if (path.length === 0 || typeof path[0] === "number") {
return [];
}
// The array will go under a new branch.
node = Object.create(null);
}
const leaf = branch(path, node);
const attr = path.slice(-1)[0];
if (
path.length > 0 &&
typeof leaf[attr] === "undefined" &&
!hasArrayLeaf(path)
) {
leaf[attr] = [];
}
return node;
}
export default function parse(elements, maps) {
function setValue(obj, input) {
const { name, type } = input;
let { value } = input;
const path = getPath(name);
if (type === "radio" && !input.checked) {
// Do nothing.
return obj;
}
if (type === "checkbox" && input.getAttribute("value") === null) {
value = input.checked;
} else if (type === "checkbox" && !input.checked) {
if (
hasArrayLeaf(path) ||
any((other) => other.name === name && other !== input, elements)
) {
return nonMember(obj, path);
}
value = null;
} else {
value = getValue(input, maps);
}
if (path.length === 0) {
if (obj === null) {
// A singleton value
return value;
}
if (!Array.isArray(obj)) {
if (!isObject(obj)) {
return [obj, value];
}
Object.assign(obj, { [Object.keys(obj).length]: value });
return obj;
}
obj.push(value);
return obj;
}
const tree = initialize(obj, path, value);
const leaf = branch(path, tree);
const attr = path.slice(-1)[0];
const asObject =
typeof attr !== "number" &&
any(
(other) =>
startsWith(name, other.name) &&
other !== input &&
other.name.slice(name.length).match(/\[[^\]]\]/),
elements,
);
setLeaf(leaf, attr, value, asObject);
return tree;
}
return reduce(setValue, null, elements);
}