import { HElement, hjoin } from "../src/hsml"; import { HApp, HAppActions, HDispatcher, HFormData, HFormInputData, HState, HView } from "../src/hsml-app"; interface FormData { name: string; mail: string; born: string; children: number; married: boolean; gender: string; sport: string; file: string; } type Nullable = { [P in keyof T]: T[P] | null }; interface State { title: string; form: HFormData>; genders: { label: string; value: string; }[]; sports: string[]; } const state: HState = function () { return { title: "HSML App Form", form: { data: { name: "Ema", mail: "", // born: "", born: datetimeLocal(new Date()), children: 0, married: false, gender: "female", sport: "gymnastics", file: "" }, validation: {}, // valid: false }, genders: [ { label: "Male", value: "male" }, { label: "Female", value: "female" } ], sports: ["football", "gymnastics"] }; }; // type FormActions = // "form-change" | // "form-submit"; enum Actions { change = "form-change", submit = "form-submit", cancel = "form-cancel", rmfile = "form-rmfile" } const view: HView = function (state) { return [ ["div.w3-content.w3-light-gray", [ ["div.w3-container", [ ["h1", state.title], ["form.w3-panel", { novalidate: true, on: [ ["change", Actions.change], // ["input", Actions.change], ["submit", Actions.submit] ] }, [ ["p", [ ["label", ["Name", ["input.w3-input", { type: "text", name: "name", placeholder: "Firstname Lastname", autofocus: true, list: "names", required: true, minlength: 3, maxlength: 20, pattern: "[A-Za-z]+\ ?[A-Za-z]*", // validation: { // patternMismatch: "First name and optional last name, space separated aphabetic only" // }, value: state.form.data.name }], ["datalist#names", [ "Peter Rybar", "Peter", "Martin", "Arthur", "Eva", "Ema", "Maria" ].map>(t => ["option", t]) ] ]], ["div.w3-text-gray", "First name and optional last name, space separated aphabetic only"], ["div.w3-text-red", state.form.validation.name] ]], ["p", [ ["label", ["Email", ["input.w3-input", { type: "email", name: "mail", required: true // validation: { // typeMismatch: "Invaid mail format", // valueMissing: "Missing value" // } }] ]], ["div.w3-text-red", state.form.validation.mail] ]], ["p", [ ["label", ["Born", ["input.w3-input", { type: "datetime-local", name: "born", required: true, max: datetimeLocal(new Date()), value: state.form.data.born, }] ]], ["div.w3-text-red", state.form.validation.born] ]], ["p", [ ["label", ["Children", ["input.w3-input", { type: "number", name: "children", min: 0, max: 10, value: state.form.data.children }] ]], ["div.w3-text-red", state.form.validation.children] ]], ["p", [ ["label", [ ["input.w3-check", { type: "checkbox", name: "married", checked: state.form.data.married ?? false }], " Married" ]], ["div.w3-text-red", state.form.validation.married] ]], ["p", [ ["label", "Gender"], ["br"], ...hjoin( state.genders.map>(gender => ( ["label", [ ["input.w3-radio", { type: "radio", name: "gender", required: true, value: gender.value, // checked: state.form.data.gender === gender.value }], " ", gender.label ]] )), ["br"] ), ["div.w3-text-red", state.form.validation.gender] ]], ["p", [ ["label", ["Sport", ["select.w3-select", { name: "sport" // required: true }, [ ["option", { value: "", // disabled: true, selected: true } ], ...state.sports.map>(sport => ( ["option", { value: sport, // selected: sport === state.form.data.sport }, sport ]) ) ] ] ]], ["div.w3-text-red", state.form.validation.sport] ]], ["p", [ ["label", ["Data", ["input.w3-input~file", { type: "file", name: "file", accept: "application/json,.json", multiple: false, maxsize: 500, convert: "text" }] ]], ["div.w3-text-red", state.form.validation.file], " ", state.form.data.file ? ["a.w3-btn", { href: "javascript:void(0)", accesskey: "c", title: "alt-c", on: ["click", Actions.rmfile] }, "Remove" ] : null ]], ["button.w3-btn.w3-blue", "Submit"], " ", // ["a.w3-btn", "Cancel"] ["a.w3-btn", { href: "javascript:void(0)", accesskey: "c", title: "alt-c", on: ["click", Actions.cancel] }, "Cancel" ] ] ] ]] ]], ["div.w3-content.w3-container", [ ["h2", [ "Form data ", state.form.valid !== undefined ? state.form.valid ? ["span.w3-text-green", "Valid"] : ["span.w3-text-red", "Invalid"] : undefined ]], ["pre", JSON.stringify(state.form, null, 4)] ]] ]; }; const dispatcher: HDispatcher = async function (action, state, _dispatch, app) { switch (action.type) { case HAppActions.init: case HAppActions.mount: case HAppActions.umount: case HAppActions.attribute: { break; } case Actions.rmfile: { app.refs["file"].value = null; break; } case Actions.change: { console.log("FormActions.change", action.data); const input = action.data as HFormInputData; state.form.data[input.name!] = input.value; state.form.validation[input.name!] = input.validation; if (action.data.value instanceof File) { const file = action.data.value as File; // Read the file as text console.log("FormActions.change file text:", await file.text()); // Abuse response to read json data console.log("FormActions.change file json:", JSON.stringify(await new Response(file).json(), null, 4)); // Read the file as ArrayBuffer to handle binary data console.log("FormActions.change file ArrayBuffer:", new Uint8Array(await file.arrayBuffer())); // Read the file as base64 console.log("FormActions.change file base64:", Buffer.from(await file.arrayBuffer()).toString("base64")); // Read large data chunk by chunk console.log("FormActions.change file stream:", await new Response(file.stream()).text()); } break; } case Actions.submit: { const form = action.data as HFormData; const formData = JSON.stringify(form, null, 4); console.log("FormActions.submit", formData); state.form = form; if (form.valid) { // Process data // alert(`Form submit: \n${formData}`); } break; } } }; function datetimeLocal(date: Date) { date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); return date.toISOString().slice(0, 16); } window["app"] = new HApp(state, view, dispatcher, "app");