feature (admin): form creation

This commit is contained in:
Mickael Kerjean 2023-08-02 19:39:34 +10:00
parent 246de58de4
commit b65365b99b
7 changed files with 114 additions and 28 deletions

View file

@ -20,11 +20,6 @@
border-color: var(--emphasis-primary);
}
.component_input:disabled, .component_select:disabled {
background: rgba(0,0,0,0.2)!important;
font-style: italic;
}
input.component_input[readonly], textarea.component_textarea[readonly] {
border-bottom-style: dashed;
cursor: pointer;

View file

@ -1,4 +1,6 @@
import { createElement } from "../lib/skeleton/index.js";
import { safe } from "../lib/dom.js";
import { gid } from "../lib/random.js";
export function formTmpl(withAutocomplete) {
return {
@ -17,58 +19,131 @@ export function formTmpl(withAutocomplete) {
`);
},
renderInput: $renderInput,
formatLabel: format,
};
};
export async function $renderInput({ autocomplete, type, path = [] }) {
export async function $renderInput(props) {
const {
id = null,
type,
value = null,
placeholder = "",
required = false,
readonly = false,
path = [],
autocomplete = false,
datalist = null,
options = null,
} = props
let attr = `name="${path.join(".")}" `;
if (value) attr += `value="${value}" `;
if (id) attr += `id=${id} `;
if (placeholder) attr += `placeholder=${placeholder} `;
if (!autocomplete) attr += `autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="off" `;
if (required) attr += "required ";
if (readonly) attr += "readonly ";
switch(type) {
case "text":
return createElement(`
<input
case "text": // TODO
const dataListId = gid("list_");
const $input = createElement(`
<input ${safe(attr)}
type="text"
class="component_input"
name="${path.join(".")}"
/>
`);
if (!datalist) return $input;
const $wrapper = window.document.createElement("span");
const $datalist = window.document.createElement("datalist")
$wrapper.appendChild($input);
$datalist.setAttribute("id", dataListId);
// const filtered = function(multi, datalist, currentValue) {
// if (multi !== true || currentValue == null) return datalist;
// return autocomplete(
// currentValue
// .split(",")
// .map((t) => t.trim())
// .filter((t) => t),
// datalist,
// );
// };
// fitlered()
return $wrapper;
case "number":
return createElement(`
<input
${safe(attr)}
type="number"
class="component_input"
name="${path.join(".")}"
/>
`);
case "password":
// TODO: click eye
return createElement(`
<div class="formbuilder_password">
<input
${safe(attr)}
type="password"
class="component_input"
/>
</div>
`);
case "select": // TODO
return createElement(`
<select class="component_select" name="${path.join(".")}">
<option hidden=""></option>
<option name="base">base</option>
</select>
`);
// case "long_password":
// // TODO
// case "long_text":
// return // TODO
// case "bcrypt": import("./bcrypt.js")
// // TODO
case "hidden":
return createElement(`
<input
type="hidden"
name="${path.join(".")}"
type="hidden" value=${safe(value)}
name="${safe(path.join("."))}"
/>
`);
case "boolean":
return createElement(`
<div class="component_checkbox">
<input ${safe(attr)} type="checkbox" />
<span className="indicator"></span>
</div>
`);
case "select":
const renderOption = (name) => `<option name="${safe(name)}">${safe(name)}</option>`;
return createElement(`
<select class="component_select" ${safe(attr)}>
${(options || []).map(renderOption)}
</select>
`);
case "date":
return createElement(`
<input
${safe(attr)}
type="date"
class="component_input"
/>
`);
case "datetime":
return createElement(`
<input
${safe(attr)}
type="datetime-local"
class="component_input"
/>
`);
case "image":
return createElement(` <img id="${safe(id)}" src="${save(value)}" />`);
// case "file":
// return createElement() // TODO
default:
return createElement(`
<input
value="unknown element type ${type}"
type="text"
class="component_input"
path="${path.join(".")}"
disabled
path="${safe(path.join("."))}"
readonly
/>
`);
}

View file

@ -25,6 +25,8 @@
"/logout": "/pages/ctrl_logout.js",
"/login": "/pages/connectpage/connectpage.js",
//"/": "/pages/home/index.js",
"": "/pages/ctrl_notfound.js",
};

View file

@ -9,3 +9,9 @@ export function qsa($node, selector) {
if (!$node) throw new Error("undefined node");
return $node.querySelectorAll(selector);
}
export function safe(str) {
const $div = window.document.createElement("div");
$div.textContent = str;
return $div.innerHTML;
}

View file

@ -19,7 +19,6 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
}
const $list = [];
for (const key of Object.keys(node)) {
if (typeof node[key] !== "object") {
$list.push(createElement(`<div>ERR: node[${typeof node[key]}] path[${path.join(".")}] level[${level}]</div>`));
}
@ -38,14 +37,12 @@ async function createFormNodes(node, { renderNode, renderLeaf, renderInput, path
const $chunk = renderNode({ level, label: key });
const $children = $chunk.querySelector(`[data-bind="children"]`) || $chunk;
$children.removeAttribute("data-bind");
const $nodes = await createForm(node[key], {
const $nested = await createForm(node[key], {
path: path.concat(key), level: level + 1, label: key,
renderNode, renderLeaf, renderInput,
});
$nodes.childNodes.forEach(($node) => {
$children.appendChild($node);
});
$list.push($chunk);
$children.appendChild($nested);
$list.push($children);
}
}
return $list;

6
public/lib/random.js Normal file
View file

@ -0,0 +1,6 @@
export function gid(prefix = "") {
let id = prefix;
id += new Date().getTime().toString(32);
id += Math.random().toString(32).replace(/^0\./, "");
return id;
}

View file

@ -0,0 +1,5 @@
import { createElement } from "../../lib/skeleton/index.js";
export default function(render) {
render(createElement(`<div> LOGIN </div>`));
}