import { createElement } from "../lib/skeleton/index.js"; import { gid } from "../lib/random.js"; import { ApplicationError } from "../lib/error.js"; import "./icon.js"; export function formTmpl(options = {}) { const { autocomplete = true, renderNode = null, renderLeaf = null } = options; return { renderNode: (opts) => { if (renderNode) { const $el = renderNode({ ...opts, format }); if ($el) return $el; } const { label } = opts; return createElement(`
${format(label)}
`); }, renderLeaf: (opts) => { if (renderLeaf) { const $el = renderLeaf({ ...opts, format }); if ($el) return $el; } const { label } = opts; return createElement(` `); }, renderInput: $renderInput({ autocomplete }), formatLabel: format }; }; export function $renderInput(options = {}) { const { autocomplete } = options; return function(props) { const { id = null, type, value = null, placeholder = "", required = false, readonly = false, path = [], datalist = null, options = null } = props; const attrs = []; attrs.push(($node) => $node.setAttribute("name", path.join("."))); if (id) attrs.push(($node) => $node.setAttribute("id", id)); if (placeholder) attrs.push(($node) => $node.setAttribute("placeholder", placeholder)); if (!autocomplete || props.autocomplete === false) attrs.push(($node) => { $node.setAttribute("autocomplete", "off"); $node.setAttribute("autocorrect", "off"); $node.setAttribute("autocapitalize", "off"); $node.setAttribute("spellcheck", "off"); }); if (required) attrs.push(($node) => $node.setAttribute("required", "")); if (readonly) attrs.push(($node) => $node.setAttribute("readonly", "")); switch (type) { case "text": { const $input = createElement(` `); if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); if (!datalist) return $input; const dataListId = gid("list_"); $input.setAttribute("list", dataListId); $input.setAttribute("datalist", datalist.join(",")); const $wrapper = document.createElement("span"); const $datalist = document.createElement("datalist"); $datalist.setAttribute("id", dataListId); $wrapper.appendChild($input); $wrapper.appendChild($datalist); (props.multi ? multicomplete(value, datalist) : (datalist || [])).forEach((value) => { $datalist.appendChild(new Option(value)); }); if (!props.multi) return $wrapper; // @ts-ignore $input.refresh = () => { const _datalist = $input?.getAttribute("datalist")?.split(","); $datalist.innerHTML = ""; multicomplete($input.value, _datalist).forEach((value) => { $datalist.appendChild(new Option(value)); }); }; $input.oninput = () => { for (const $option of $datalist.children) { $option.remove(); } // @ts-ignore $input.refresh(); }; return $wrapper; } case "enable": { const $div = createElement(`
`); const $input = $div.querySelector("input"); attrs.map((setAttribute) => setAttribute($input)); return $div; } case "number": { const $input = createElement(` `); if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "password": { const $div = createElement(`
`); const $input = $div.querySelector("input"); if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); const $icon = $div.querySelector("component-icon"); if ($icon instanceof window.HTMLElement) { $icon.onclick = function(e) { if (!(e.target instanceof window.HTMLElement)) return; const $input = e.target?.parentElement?.previousElementSibling; if (!$input) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); if ($input.getAttribute("type") === "password") $input.setAttribute("type", "text"); else $input.setAttribute("type", "password"); }; } return $div; } case "long_text": { const $textarea = createElement(` `); if (!($textarea instanceof window.HTMLTextAreaElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $textarea.value = value; attrs.map((setAttribute) => setAttribute($textarea)); return $textarea; } case "bcrypt": { const $input = createElement(` `); if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "hidden": { const $input = createElement(` `); if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; $input.setAttribute("name", path.join(".")); return $input; } case "boolean": { const $div = createElement(`
`); const $input = $div.querySelector("input"); attrs.map((setAttribute) => setAttribute($input)); return $div; } case "select": { const $select = createElement(` `); if (!($select instanceof window.HTMLSelectElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $select.value = value || props.default; attrs.map((setAttribute) => setAttribute($select)); (options || []).forEach((name) => { const $option = createElement(` `); $option.textContent = name; $option.setAttribute("name", name); if (name === (value || props.default)) { $option.setAttribute("selected", ""); } $select.appendChild($option); }); return $select; } case "date": { const $input = createElement(` `); if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "datetime": { const $input = createElement(` `); if (!($input instanceof window.HTMLInputElement)) throw new ApplicationError("INTERNAL_ERROR", "assumption failed: missing input"); else if (value) $input.value = value; attrs.map((setAttribute) => setAttribute($input)); return $input; } case "image": { const $img = createElement(""); $img.setAttribute("id", id); $img.setAttribute("src", value); return $img; } case "file": { // TODO return createElement(` `); } default: { const $input = createElement(` `); $input.setAttribute("value", `unknown element type ${type}`); $input.setAttribute("name", path.join(".")); return $input; } } }; } export function format(name) { if (typeof name !== "string") { return "N/A"; } return name .split("_") .map((word) => { if (word.length < 1) { return word; } return (word[0] || "").toUpperCase() + word.substring(1); }) .join(" "); }; export function multicomplete(input, datalist) { input = (input || "").trim().replace(/,$/g, ""); const current = input.split(",").map((val) => val.trim()).filter((t) => !!t); const diff = datalist.filter((x) => current.indexOf(x) === -1); return diff.map((candidate) => input.length === 0 ? candidate : `${input}, ${candidate}`); }