You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
3.3 KiB
TypeScript
120 lines
3.3 KiB
TypeScript
import { is, predicates } from "@sealcode/ts-predicates";
|
|
import { BaseContext } from "koa";
|
|
|
|
export type FormFieldValidationResponse = { valid: boolean; message: string };
|
|
|
|
export type FormFieldValidationFn = (
|
|
ctx: BaseContext,
|
|
value: unknown,
|
|
field: FormField
|
|
) => Promise<FormFieldValidationResponse>;
|
|
|
|
export class FormField<Fieldnames extends string = string> {
|
|
constructor(
|
|
public name: Fieldnames,
|
|
public required: boolean = false,
|
|
public validator: FormFieldValidationFn = async () => ({
|
|
valid: true,
|
|
message: "",
|
|
})
|
|
) {}
|
|
|
|
public async _validate(
|
|
ctx: BaseContext,
|
|
value: unknown
|
|
): Promise<FormFieldValidationResponse> {
|
|
if (this.required && (value == "" || value == null || value == undefined)) {
|
|
return { valid: false, message: "This field is required" };
|
|
}
|
|
return this.validator(ctx, value, this);
|
|
}
|
|
|
|
public getEmptyValue() {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
export class PickFromListField<
|
|
Fieldnames extends string = string
|
|
> extends FormField<Fieldnames> {
|
|
constructor(
|
|
public name: Fieldnames,
|
|
public required: boolean = false,
|
|
public generateOptions: (
|
|
ctx: BaseContext
|
|
) => Promise<Record<string, string> | { [i: string]: string }>,
|
|
public customValidation: (
|
|
ctx: BaseContext,
|
|
value: unknown,
|
|
instance: PickFromListField
|
|
) => Promise<FormFieldValidationResponse> = (ctx, value, instance) =>
|
|
instance.valueInList(ctx, value)
|
|
) {
|
|
super(name, required, (ctx, value) => this.customValidation(ctx, value, this));
|
|
}
|
|
|
|
async valueInList(
|
|
ctx: BaseContext,
|
|
value: unknown
|
|
): Promise<FormFieldValidationResponse> {
|
|
const options = await this.generateOptions(ctx);
|
|
if (!is(value, predicates.string)) {
|
|
return { valid: false, message: "not a string" };
|
|
}
|
|
if (!Object.keys(options).includes(value)) {
|
|
return { valid: false, message: `"${value}" is not one of the options` };
|
|
}
|
|
return { valid: true, message: "" };
|
|
}
|
|
}
|
|
|
|
export class ChekboxedListField<
|
|
Fieldnames extends string = string
|
|
> extends FormField<Fieldnames> {
|
|
constructor(
|
|
public name: Fieldnames,
|
|
public required: boolean = false,
|
|
public generateOptions: (
|
|
ctx: BaseContext
|
|
) => Promise<Record<string, string> | { [i: string]: string }>,
|
|
public isVisible: (ctx: BaseContext) => Promise<boolean> = () =>
|
|
Promise.resolve(true)
|
|
) {
|
|
super(name, required, (ctx, value) => this.isValueValid(ctx, value));
|
|
}
|
|
|
|
private async isValueValid(
|
|
_: BaseContext,
|
|
value: unknown
|
|
): Promise<FormFieldValidationResponse> {
|
|
if (is(value, predicates.string)) {
|
|
return { valid: false, message: "you need an array" };
|
|
}
|
|
if (is(value, predicates.null)) {
|
|
return { valid: false, message: "you need an array" };
|
|
}
|
|
return { valid: true, message: "" };
|
|
}
|
|
}
|
|
|
|
export class NumberField<
|
|
Fieldnames extends string = string
|
|
> extends FormField<Fieldnames> {
|
|
constructor(field_name: Fieldnames, required: boolean) {
|
|
super(field_name, required, (_, value) => this.isValueValid(_, value));
|
|
}
|
|
|
|
private async isValueValid(_: BaseContext, value: unknown) {
|
|
if (
|
|
(is(value, predicates.string) &&
|
|
!isNaN(parseFloat(value)) &&
|
|
parseFloat(value).toString() == value.trim()) ||
|
|
is(value, predicates.number) ||
|
|
((is(value, predicates.undefined) || value == "") && !this.required)
|
|
) {
|
|
return { valid: true, message: "" };
|
|
}
|
|
return { valid: false, message: "Proszę wprowadzić liczbę" };
|
|
}
|
|
}
|