Page MenuHomeSealhub

mountable-with-fields.ts
No OneTemporary

mountable-with-fields.ts

import { ShapeToType } from "@sealcode/ts-predicates";
import { Context } from "koa";
import { FlatTemplatable, tempstream } from "tempstream";
import {
FormControl,
FormControlContext,
} from "../forms/controls/form-control.js";
import type { FieldsToShape, FormField } from "../forms/fields/field.js";
import type {
FormDataValue,
FormMessage,
FormData,
} from "../forms/form-types.js";
import { Mountable } from "./mountable.js";
export type PageErrorMessage = { type: "access" | "internal"; message: string };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Fields = Record<string, FormField<boolean, any>>;
export abstract class MountableWithFields<
F extends Fields = Fields
> extends Mountable {
fields: F;
field_names_prefix = ""; // useful for multiform, where many forms are merged into one and field assignment is made using the prefix
form_id = ""; // all fields within this mountable will be tied to form of this id
abstract controls: FormControl[];
constructor() {
super();
if (!this.fields) this.fields = {} as F;
}
init(): void {
for (const [name, field] of Object.entries(this.fields)) {
void field.init(name);
}
}
makeFormControlContext(
ctx: Context,
data: FormData,
validate: boolean,
field_name_prefix = this.field_names_prefix,
form_id = this.form_id
) {
return new FormControlContext(
ctx,
data.raw_values,
data.messages,
field_name_prefix,
form_id,
validate
);
}
// this one is meant to be overwritten
async validateValues(
_ctx: Context,
_data: Record<string, FormDataValue>
): Promise<{ valid: boolean; error: string }> {
return {
valid: true,
error: "",
};
}
async getInitialValues(
_ctx: Context
): Promise<Record<string, FormDataValue>> {
return {};
}
async validate(
ctx: Context,
values: Record<string, FormDataValue>
): Promise<{
valid: boolean;
field_errors: Partial<Record<keyof Fields, string>>;
form_messages: FormMessage[];
}> {
const field_errors = {} as Record<keyof Fields, string>;
let valid = true;
const form_messages = [] as FormMessage[];
await Promise.all(
Object.entries(values)
.filter(([fieldname]) => fieldname in this.fields)
.map(async ([key]: [keyof F, FormDataValue]) => {
const field = this.fields[key];
const { valid: fieldvalid, message: fieldmessage } =
await field.getValue(ctx, values);
if (!fieldvalid) {
valid = false;
field_errors[field.name] = fieldmessage;
}
})
);
const formValidationResult = await this.validateValues(ctx, values);
if (!formValidationResult.valid) {
form_messages.push({
type: "error",
text: formValidationResult.error,
});
valid = false;
}
return { valid, field_errors, form_messages };
}
public renderControls(fctx: FormControlContext): FlatTemplatable {
return tempstream/* HTML */ `${this.controls.map((control) =>
control.render(fctx)
)}`;
}
async renderError(
_: Context,
error: PageErrorMessage
): Promise<FlatTemplatable> {
return error.message;
}
public renderMessages(
_: Context,
data: FormData<keyof Fields>
): FlatTemplatable {
return tempstream/* HTML */ `<div class="form-messages">
${data.messages.map(
(message) =>
`<div class="form-message form-message--${message.type}">${message.text}</div>`
)}
</div>`;
}
abstract extractRawValues(
ctx: Context
): Promise<Record<string, FormDataValue>>;
async getParsedValues(
ctx: Context
): Promise<ShapeToType<FieldsToShape<F>>> {
const raw_values = await this.extractRawValues(ctx);
const result: Record<string, unknown> = {};
const promises = Object.entries(this.fields).map(
async ([key, field]) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { parsed } = await field.getValue(ctx, raw_values);
result[key] = parsed;
}
);
await Promise.all(promises);
return result as ShapeToType<FieldsToShape<F>>;
}
async getDatabaseValues(
ctx: Context
): Promise<ShapeToType<FieldsToShape<F>>> {
const raw_values = await this.extractRawValues(ctx);
const result: Record<string, unknown> = {};
const promises = Object.entries(this.fields).map(
async ([key, field]) => {
const db_value = await field.getDatabaseValue(ctx, raw_values);
if (db_value !== undefined) {
result[key] = db_value;
}
}
);
await Promise.all(promises);
return result as ShapeToType<FieldsToShape<F>>;
}
}

File Metadata

Mime Type
text/x-java
Expires
Mon, May 19, 00:53 (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
625624
Default Alt Text
mountable-with-fields.ts (4 KB)

Event Timeline