Page MenuHomeSealhub

field.ts
No OneTemporary

field.ts

/* eslint @typescript-eslint/no-unused-vars: off, @typescript-eslint/no-explicit-any: off */
import { Predicate, predicates } from "@sealcode/ts-predicates";
import { Context } from "koa";
import { Collection, CollectionItem } from "sealious";
import { t } from "../../utils/translate.js";
import { FormControl, FormControlContext } from "../controls/form-control.js";
import { FormDataValue, FormData } from "../form-types.js";
import { ExtractedFieldInfo } from "../../utils/extract-fields-from-collection.js";
import { wrapAttribute, wrapKeyName } from "../../utils/wrap-attributes.js";
export type FormFieldValidationResponse = { valid: boolean; message: string };
export type FormDisplayInfo<C extends Collection> = {
field: keyof C["fields"];
label: string;
format?: (value: unknown, item: CollectionItem<C>) => JSX.Element;
};
export type FormFieldValidationFn<ValueType> = (
ctx: Context,
value: ValueType,
field: FormField<boolean, ValueType>
) => Promise<FormFieldValidationResponse>;
export type FieldParsedValue<T> = T extends FormField<infer R> ? R : never;
export type GetPredicate<F extends FormField> =
F extends FormField<true> ? F["predicate"]
: F["predicate"] | typeof predicates.undefined;
// for quickly testing GetPredicate conditonally applying the undefined predicate:
// const f = new SimpleFormField(true);
// type p = GetPredicate<typeof f>;
// const f2 = new SimpleFormField(false);
// type p2 = GetPredicate<typeof f2>;
export type FieldsToShape<T extends Record<string, FormField>> = {
[property in keyof T]: GetPredicate<T[property]>;
};
export function fieldsToShape<Fields extends Record<string, FormField>>(
obj: Fields
): FieldsToShape<Fields> {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, value.predicate])
) as FieldsToShape<Fields>;
}
export type FieldParseResult<T> =
| {
parsable: true;
error: null;
parsed_value: T;
}
| {
parsable: false;
error: string;
parsed_value: null;
};
export abstract class FormField<
Required extends boolean = boolean,
ParsedValue = unknown,
DefaultFormControl extends FormControl = FormControl,
SealiousValue = any,
> {
name: string;
label: string;
constructor(public readonly required: Required) {}
predicate: Predicate = predicates.unknown;
mapToFilter: (parsed: ParsedValue) => unknown = (value) => value;
async init(): Promise<void> {}
setName(name: string): this {
this.name = name;
return this;
}
setLabel(label: string): this {
this.label = label;
return this;
}
public async _validate(
ctx: Context,
value: ParsedValue
): Promise<FormFieldValidationResponse> {
if (value === "" || value === null || value === undefined) {
if (this.required) {
return {
valid: false,
message: t(
ctx,
"field_is_required",
[],
"This field is required"
),
};
} else {
return {
valid: true,
message: "",
};
}
}
return this.isValueValid(ctx, value);
}
public abstract getEmptyValue(): ParsedValue;
public async isValueValid(
_ctx: Context,
_value: ParsedValue
): Promise<FormFieldValidationResponse> {
return { valid: true, message: "" };
}
abstract parse(
ctx: Context,
raw_value: FormDataValue
): Promise<FieldParseResult<ParsedValue>>;
async getParsedValue(
ctx: Context,
all_form_values: Record<string, FormDataValue>,
validate: true
): Promise<{
valid: boolean;
message: string;
parsed: ParsedValue | null;
raw: FormDataValue;
}>;
async getParsedValue(
ctx: Context,
all_form_values: Record<string, FormDataValue>,
validate?: false
): Promise<{
message: string;
parsed: ParsedValue | null;
raw: FormDataValue;
}>;
async getParsedValue(
ctx: Context,
all_form_values: Record<string, FormDataValue>,
validate = false
): Promise<{
valid?: boolean;
message: string;
parsed: ParsedValue | null;
raw: FormDataValue;
}> {
const raw = all_form_values[this.name];
if (raw == undefined || raw == null || raw == "") {
return {
parsed: null,
message:
this.required ? "empty value, but field is required" : "",
raw,
...(validate ? { valid: !this.required } : {}),
};
}
const {
parsable,
parsed_value,
error: parse_error,
} = await this.parse(ctx, raw);
if (!parsable) {
return {
...(validate ? { valid: false } : {}),
parsed: this.getEmptyValue(),
raw,
message: parse_error || "Error",
};
}
if (validate) {
const { valid, message } = await this._validate(ctx, parsed_value);
return {
parsed: parsed_value,
...(validate ? { valid } : {}),
message,
raw: all_form_values[this.name],
};
} else {
return { parsed: parsed_value, raw, message: "parsed" };
}
}
setMapToFilter(fn: (parsed: ParsedValue) => unknown) {
this.mapToFilter = fn;
return this;
}
//return undefined here to delete a value
async getDatabaseValue(
ctx: Context,
data: Record<string, FormDataValue>
): Promise<unknown> {
const { parsed } = await this.getParsedValue(ctx, data);
return parsed;
}
abstract getControl(): DefaultFormControl;
getSealiousCreateValue(fctx: FormControlContext): Promise<SealiousValue> {
throw new Error("This field does not have a default sealious mapping");
}
sealiousValueToForm(
ctx: Context,
sealiousValue: SealiousValue | null,
item: CollectionItem<any>
): Promise<FormDataValue> {
throw new Error("This field does not have a default sealious mapping");
}
async postSealiousCreate(
ctx: Context,
created_item: CollectionItem<any>,
form_data: FormData
): Promise<void> {}
async postSealiousEdit(
ctx: Context,
edited_item: CollectionItem<any>,
form_data: FormData
): Promise<void> {}
generateFieldDeclaration(
field_info: ExtractedFieldInfo,
vars: { form_field_types: string; sealious_field: string }
): string {
return `new ${vars.form_field_types}.${this.constructor.name}(${String(
field_info.is_required
)})`;
}
generateCreateValueGetter(
field_info: ExtractedFieldInfo,
vars: {
form_field_types: string;
sealious_field: string;
form_fields: string;
}
): string {
return `${wrapKeyName(field_info.name)}: await ${
vars.form_fields
}${wrapAttribute(field_info.name)}.getSealiousCreateValue(fctx)`;
}
generateInitialValue(
field_info: ExtractedFieldInfo,
vars: {
form_field_types: string;
form_fields: string;
}
): string {
return `${wrapKeyName(field_info.name)}: await ${
vars.form_fields
}${wrapAttribute(field_info.name)}.sealiousValueToForm(ctx, item.get("${
field_info.name
}"))`;
}
generateImportsForFieldList(field_info: ExtractedFieldInfo): {
what: string;
from: string;
as?: string;
type?: boolean;
}[] {
return [];
}
}

File Metadata

Mime Type
text/x-java
Expires
Sat, Oct 11, 06:49 (6 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
983909
Default Alt Text
field.ts (6 KB)

Event Timeline