import { HElement, hjoin } from "../src/hsml"; import { HApp, HAppActions, HDispatcher, HState, HView, happ } from "../src/hsml-app"; import { BooleanValidator, DateValidator, FormValidator, FormValidatorData, NumberValidator, SelectValidator, StringValidator } from "../src/validators"; export interface Data { name: string; born: Date; children: number; married: boolean; gender: string; sport: string; } export interface State { title: string; data: Data; genders: { label: string; value: string; }[]; sports: string[]; validatorData?: FormValidatorData; } export const state: HState = function () { return { title: "Form Validation", data: { name: "Ema", born: new Date(), children: 0, married: false, gender: "female", sport: "gymnastics" }, genders: [ { label: "Male", value: "male" }, { label: "Female", value: "female" } ], sports: ["football", "gymnastics"] } }; export enum Actions { change = "form-validation-change", submit = "form-validation-submit" } export const view: HView = function (state) { return [ ["div.w3-content", [ ["h1", state.title], ["form.w3-container", { on: [ ["change", Actions.change], ["submit", Actions.submit] ] }, [ ["p", [ ["label", ["Name", ["input.w3-input", { type: "text", name: "name", value: state.validatorData!.str.name, on: ["input", Actions.change] }] ]] ]], ["p.w3-text-red", [state.validatorData!.err.name]], ["p", [ ["label", ["Born", ["input.w3-input", { type: "datetime-local", name: "born", // placeholder: new DateValidator().format(new Date()).str, value: datetimeLocal(state.validatorData!.obj.born) }] ]] ]], ["p.w3-text-red", [state.validatorData!.err.born]], ["p", [ ["label", ["Children", ["input.w3-input", { type: "number", name: "children", value: state.validatorData!.str.children }] ]] ]], ["p.w3-text-red", [state.validatorData!.err.children]], ["p", [ ["label", [ ["input.w3-check", { type: "checkbox", name: "married", checked: state.validatorData!.obj.married }], " Married" ]] ]], ["p.w3-text-red", [state.validatorData!.err.married]], ["p", hjoin( state.genders.map>(gender => ( ["label", [ ["input.w3-radio", { type: "radio", name: "gender", value: gender.value, checked: state.validatorData!.obj.gender === gender.value }], " ", gender.label ]] )), ["br"] ) ], ["p.w3-text-red", [state.validatorData!.err.gender]], ["p", [ ["select.w3-select", { name: "sport" }, [ ["option", { value: "", disabled: true, selected: true }, "Sport" ], ...state.sports.map>(sport => ( ["option", { value: sport, selected: sport === state.validatorData!.obj.sport }, sport ]) ) ] ] ]], ["p.w3-text-red", state.validatorData!.err.sport], ["button.w3-btn.w3-blue", "Submit"] ] ] ]] ]; }; let formValidator: FormValidator; export const dispatcher: HDispatcher = async function (action, state) { console.log("action", action); switch (action.type) { case HAppActions.init: formValidator = validator(state); state.validatorData = formValidator.data(); break; case Actions.change: formValidator.validate({ ...state.validatorData!.str, ...action.data }); state.validatorData = formValidator.data(); console.log("obj:", JSON.stringify(state.validatorData, null, 4)); break; case Actions.submit: action.event!.preventDefault(); // console.log(action.data); formValidator.validate(action.data); state.validatorData = formValidator.data(); // console.log(state.validatorData); state.validatorData = formValidator.data(); if (formValidator.valid) { console.log(formValidator); state.data = formValidator.obj!; const formData = JSON.stringify(state.data, null, 4); console.dir(formData); alert(`Form submit: \n${formData}`); } break; } }; export function validator(formState: State): FormValidator { return new FormValidator() .addValidator("name", new StringValidator( { required: true, min: 3, max: 5 }, { required: "Required {{min}} - {{max}} {{regexp}}", invalid_format: "Invalid format {{regexp}}", not_in_range: "Not in range {{min}} - {{max}}" })) .addValidator("born", new DateValidator( { required: true, // min: new Date(), max: new Date(), dateOnly: false, iso: true }, { required: "Required {{min}} - {{max}}", invalid_format: "Invalid format {{date}}", not_in_range: "Not in range {{min}} - {{max}}" })) .addValidator("children", new NumberValidator( { required: true, min: 0, max: 10 }, { required: "Required {{min}} - {{max}}", invalid_format: "Invalid format, use format {{num}}", not_in_range: "Not in range {{min}} - {{max}}" })) .addValidator("gender", new SelectValidator( { required: true, options: formState.genders.map(g => g.value) }, { required: "Required {{options}}", invalid_option: "Invalod option {{option}} {{options}}" })) .addValidator("married", new BooleanValidator( { required: true }, { required: "Required", invalid_value: "Invalid value {{value}}" })) .addValidator("sport", new SelectValidator( { required: true, options: formState.sports }, { required: "Required {{options}}", invalid_option: "Invalod option {{option}} {{options}}" })) .format(formState.data); } function datetimeLocal(date: Date) { if (!date) { return ""; } const tzo = - date.getTimezoneOffset(); if (tzo === 0) { return date.toISOString(); } else { // let dif = tzo >= 0 ? "+" : "-"; const pad = function (num: number, digits = 2) { return String(num).padStart(digits, "0"); }; return ( date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + "T" + pad(date.getHours()) + ":" + pad(date.getMinutes()) // pad(tzo >= 0 ? date.getHours() + (tzo / 60) : date.getHours() - (tzo / 60)) + // ":" + // pad(tzo >= 0 ? date.getMinutes() + (tzo % 60) : date.getMinutes() - (tzo % 60)) ); // return ( // date.getFullYear() + // "-" + // pad(date.getMonth() + 1) + // "-" + // pad(date.getDate()) + // "T" + // pad(date.getHours()) + // ":" + // pad(date.getMinutes()) + // ":" + // pad(date.getSeconds()) + // dif + // pad(tzo / 60) + // ":" + // pad(tzo % 60) + // "." + // pad(date.getMilliseconds(), 3) // ); } } new HApp(state, view, dispatcher, "app");