apphouse
Version:
Component library for React that uses observable state management and theme-able components.
256 lines (233 loc) • 6.33 kB
text/typescript
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)));
};