UNPKG

apphouse

Version:

Component library for React that uses observable state management and theme-able components.

256 lines (233 loc) 6.33 kB
import { makeAutoObservable } from 'mobx'; import { Field } from './Field'; import { FieldType } from './Field.interface'; import { getUniqueId } from '../../utils/string/getUniqueId'; import { List } from '..'; import { isEmpty } from '@firebase/util'; /** * FormMode is a type provided for convenience so the user knows if the form is being used * to edit a value, create a new one or just available for viewing so the user can make * a decision on what to do with the form. */ export type Mode = 'create' | 'edit' | 'view'; /** * A callback function that is called when the form is submitted * @param data any data that you may want to pass to the callback */ export type onSubmissionFormCompletedCallback = (data?: unknown) => void; /** * Interface for the Form type used for when the use is defining the shape of the form */ export interface FormType { /** * Unique id for the form * @optional * @default random string */ id?: string; /** * The fields of the form */ fields: FieldType[]; /** * A callback function needs to called when the form is submitted * Note: this callback is added for convenience * this callback does not alter any data in this form * @optional */ onSubmit?: onSubmissionFormCompletedCallback; /** * The title of the form * @optional */ title?: string; /** * The form mode. * @optional "create" | "edit" | "view" * Determine if the form is being used to * create a new value or to edit an existing one. * this optional is provided for user convenience and it * does not affect the form behavior. */ mode?: Mode; } export class FormV2 { /** * The initial values of the form. * It is used to reset the form to its initial state. */ initialValues: Record<string, unknown>; fields: List<Field>; id: string; mode?: Mode; onSubmit?: (data?: unknown) => void; title?: string; constructor(form: FormType) { this.initialValues = form.fields.reduce((acc, f) => { if (f?.id) { // @ts-ignore acc[f.id] = f.value; } else { console.warn(`Field with value ${f?.value} does not have an id`); } return acc; }, {}); this.onSubmit = form.onSubmit; this.id = form.id || getUniqueId(); this.title = form.title; this.mode = form.mode; this.fields = getFormFields(form.fields); makeAutoObservable(this); } /** * Whether the form is valid or not. * A form is valid if all of its fields are valid. * @returns boolean whether the form is valid or not */ get valid(): boolean { return this.fields.values.every((f) => f.valid); } /** * Get form data in a key value pair. */ get data() { const fields = this.fields.values; const formData = {}; fields.forEach((f) => { if (f?.id !== undefined) { // @ts-ignore formData[f.id] = f?.value; } else { console.warn( `Field with value ${f?.value} does not have an id. This field will be ignored` ); } }); return formData; } /** * Autofill the form with values. * @param values the values to autofill the form with */ autofill = (values: Record<string, unknown>) => { this.fields.values.forEach((f) => { f.set(values[f.id] as string); }); }; /** * Set the value of the field * @param id the field id to set its value * @param value the value of the field */ setValue = (id: string, value: string) => { const field = this.fields.get(id); if (field) { field.set(value); } }; /** * Get the value of the field * @param id the id of the field you want to get its value * @returns the value of the field or undefined if the field does not exist or is undefined */ getValue = (id: string) => { const field = this.fields.get(id); if (field) { return field.value; } return undefined; }; /** * Get the field * @param id the id of the field you want to get * @returns the field instance or undefined if the field does not exist or is undefined */ getField = (id: string) => { return this.fields.get(id); }; /** * Reset the form to its initial state. */ reset = () => { this.fields.values.forEach((f) => { const initialValue = this.initialValues[f.id]; if ( initialValue === undefined || initialValue === null || isEmpty(initialValue) ) { console.warn(`Field with id ${f.id} does not have an initial value`); return; } f.set(initialValue); }); }; /** * Dynamically add a field to the form. * @param field the field to add to the form */ addField = (field: FieldType) => { this.add(new Field(field)); }; /** * Add multiple fields to the form. * @param fields the fields to add to the form */ addFields = (fields: FieldType[]) => { fields.forEach((f) => { this.addField(f); }); }; /** * Create another field of the same type and add it to the form. * @param id the id of the field to duplicate */ duplicateField = (id: string) => { const field = this.fields.get(id); if (field) { const fieldObj = field.obj as any; const newField = new Field({ ...fieldObj, value: '', id: getUniqueId() }); this.add(newField); } }; /** * Get field object. * @param id id of the field to get its properties * @returns an object with the properties of the field */ getFieldProperties = (id: string) => { const field = this.fields.get(id); if (field) { return field.obj; } return undefined; }; /** * Dynamically add a field to the form. * @param field the field to add to the form * @private this method is private and should not be used */ private add = (field: Field) => { this.fields.set(field); }; /** * Remove a field from the form. * @param id the id of the field to remove */ removeField = (id: string) => { this.fields.delete(id); }; /** * Remove all fields from the form. */ removeAllFields = () => { this.fields.reset(); }; } const getFormFields = (fields: FieldType[]) => { return new List<Field>(fields.map((f) => new Field(f))); };