Skip to content

Commit

Permalink
fix(formEngine): gracefully handle the close of the form
Browse files Browse the repository at this point in the history
  • Loading branch information
danielo515 committed May 13, 2024
1 parent 0900c01 commit a398882
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 52 deletions.
15 changes: 11 additions & 4 deletions src/FormModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,17 @@ export class FormModal extends Modal {
modalDefinition.fields,
options?.values ?? {},
);
this.formEngine = makeFormEngine((result) => {
this.onSubmit(FormResult.make(result, "ok"));
this.close();
}, this.initialFormValues);
this.formEngine = makeFormEngine({
onSubmit: (result) => {
this.onSubmit(FormResult.make(result, "ok"));
this.close();
},
onCancel: () => {
this.onSubmit(FormResult.make({}, "cancelled"));
this.close();
},
defaultValues: this.initialFormValues,
});
// this.formEngine.subscribe(console.log);
}

Expand Down
81 changes: 33 additions & 48 deletions src/store/formStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// and the errors that are present in the form. It is used by the Form component to render the form and to update the

import { NonEmptyArray, pipe } from "@std";
import * as A from "fp-ts/Array";
import * as E from "fp-ts/Either";
import { absurd } from "fp-ts/function";
import * as O from "fp-ts/Option";
import * as A from "fp-ts/Array";
import { Writable, derived, writable, Readable, get } from "svelte/store";
import { fromEntries, toEntries } from "fp-ts/Record";
import { Option } from "fp-ts/Option";
import { fromEntries, toEntries } from "fp-ts/Record";
import { absurd } from "fp-ts/function";
import { Readable, Writable, derived, get, writable } from "svelte/store";

type Rule = { tag: "required"; message: string }; //| { tag: 'minLength', length: number, message: string } | { tag: 'maxLength', length: number, message: string } | { tag: 'pattern', pattern: RegExp, message: string };
function requiredRule(fieldName: string, message?: string): Rule {
Expand All @@ -27,10 +27,7 @@ type FieldFailed<T extends FieldValue> = {
rules: Rule;
errors: NonEmptyArray<string>;
};
function FieldFailed<T extends FieldValue>(
field: Field<T>,
failedRule: Rule,
): FieldFailed<T> {
function FieldFailed<T extends FieldValue>(field: Field<T>, failedRule: Rule): FieldFailed<T> {
return { ...field, rules: failedRule, errors: [failedRule.message] };
}
type FormStore<T extends FieldValue> = { fields: Record<string, Field<T>> };
Expand All @@ -45,11 +42,10 @@ export interface FormEngine<T extends FieldValue> {
* Use them to bind the field to the form and be notified of errors.
* @param field a field definition to start tracking
*/
addField: (field: {
name: string;
label?: string;
isRequired?: boolean;
}) => { value: Writable<T>; errors: Readable<string[]> };
addField: (field: { name: string; label?: string; isRequired?: boolean }) => {
value: Writable<T>;
errors: Readable<string[]>;
};
/**
* Subscribes to the form store. This method is required to conform to the svelte store interface.
*/
Expand All @@ -73,11 +69,7 @@ function nonEmptyValue(s: FieldValue): Option<FieldValue> {
case "boolean":
return O.some(s);
case "object":
return Array.isArray(s)
? s.length > 0
? O.some(s)
: O.none
: O.none;
return Array.isArray(s) ? (s.length > 0 ? O.some(s) : O.none) : O.none;
default:
return absurd(s);
}
Expand All @@ -88,9 +80,7 @@ function nonEmptyValue(s: FieldValue): Option<FieldValue> {
* If the field meets the requirements, the field is returned as is in a right.
* If the field does not meet the requirements, the field is returned with the errors in a left.
*/
function parseField<T extends FieldValue>(
field: Field<T>,
): E.Either<FieldFailed<T>, Field<T>> {
function parseField<T extends FieldValue>(field: Field<T>): E.Either<FieldFailed<T>, Field<T>> {
if (!field.rules) return E.right(field);
const rule = field.rules;
switch (rule.tag) {
Expand All @@ -100,8 +90,7 @@ function parseField<T extends FieldValue>(
O.chain(nonEmptyValue),
O.match(
() => E.left(FieldFailed(field, rule)),
(value) =>
E.right(field)
(value) => E.right(field),
),
);
default:
Expand Down Expand Up @@ -139,11 +128,16 @@ function parseForm<T extends FieldValue>(
}

export type OnFormSubmit<T> = (values: Record<string, T>) => void;
type makeFormEngineArgs<T> = {
onSubmit: OnFormSubmit<T>;
onCancel?: () => void;
defaultValues: Record<string, T>;
};

export function makeFormEngine<T extends FieldValue>(
onSubmit: OnFormSubmit<T>,
defaultValues: Record<string, T> = {},
): FormEngine<T> {
export function makeFormEngine<T extends FieldValue>({
onSubmit,
defaultValues,
}: makeFormEngineArgs<T>): FormEngine<T> {
const formStore: Writable<FormStore<T>> = writable({ fields: {} });
// Creates helper functions to modify the store immutably
function setFormField(name: string) {
Expand Down Expand Up @@ -205,23 +199,17 @@ export function makeFormEngine<T extends FieldValue>(
),
triggerSubmit() {
const formState = get(formStore);
// prettier-ignore
pipe(
formState.fields,
parseForm,
E.match(setErrors, onSubmit));
formState.fields,
parseForm,
E.match(setErrors, onSubmit)
);
},
addField: (field) => {
const { initField: setField, setValue } = setFormField(field.name);
setField(
[],
field.isRequired
? requiredRule(field.label || field.name)
: undefined,
);
const fieldStore = derived(
formStore,
({ fields }) => fields[field.name],
);
setField([], field.isRequired ? requiredRule(field.label || field.name) : undefined);
const fieldStore = derived(formStore, ({ fields }) => fields[field.name]);
const fieldValueStore: Writable<T> = {
subscribe(cb) {
return fieldStore.subscribe((x) =>
Expand All @@ -240,14 +228,14 @@ export function makeFormEngine<T extends FieldValue>(
formStore.update((form) => {
const current = form.fields[field.name];
if (!current) {
console.error(
new Error(`Field ${field.name} does not exist`),
);
console.error(new Error(`Field ${field.name} does not exist`));
return form;
}
const newValue = pipe(
// fuck prettier
current.value,
O.map(updater));
O.map(updater),
);
return {
...form,
fields: {
Expand All @@ -264,10 +252,7 @@ export function makeFormEngine<T extends FieldValue>(
};
return {
value: fieldValueStore,
errors: derived(
formStore,
({ fields }) => fields[field.name]?.errors ?? [],
),
errors: derived(formStore, ({ fields }) => fields[field.name]?.errors ?? []),
};
},
};
Expand Down

0 comments on commit a398882

Please sign in to comment.