import { ref, computed, onMounted, onBeforeUnmount } from "vue"; import { useModalsStore } from "@/stores/modals"; export const useForm = ( inputOptions: Record< string, | { value: any; validate?: (value: any) => boolean | string; } | any >, cb: ( response: { status: string; messages: Record; values: Record; }, resolve: (value?: undefined) => void, reject: (value: Error) => void ) => void, options?: { modalUuid?: string; preventCloseUnsaved?: boolean; } ) => { const { openModal, preventCloseUnsaved } = useModalsStore(); const inputs = ref( Object.fromEntries( Object.entries(inputOptions).map(([name, input]) => { if (typeof input !== "object") input = { value: input }; return [ name, { ...input, originalValue: JSON.parse(JSON.stringify(input.value)), errors: [], ref: ref(), sourceChanged: false, ignoreUnsaved: input.ignoreUnsaved === true, required: input.required === undefined ? true : input.required } ]; }) ) ); const unsavedChanges = computed(() => { const changed: string[] = []; Object.entries(inputs.value).forEach(([name, input]) => { if ( !input.ignoreUnsaved && JSON.stringify(input.value) !== JSON.stringify(input.originalValue) ) changed.push(name); }); return changed; }); const sourceChanged = computed(() => { const _sourceChanged: string[] = []; Object.entries(inputs.value).forEach(([name, input]) => { if ( input.sourceChanged && unsavedChanges.value.find(change => change === name) ) _sourceChanged.push(name); }); return _sourceChanged; }); const useCallback = (status: string, messages?: Record) => new Promise((resolve, reject: (reason: Error) => void) => { cb( { status, messages: { ...messages }, values: Object.fromEntries( Object.entries(inputs.value).map(([name, input]) => [ name, input.value ]) ) }, resolve, reject ); }); const resetOriginalValues = () => { inputs.value = Object.fromEntries( Object.entries(inputs.value).map(([name, input]) => [ name, { ...input, originalValue: JSON.parse(JSON.stringify(input.value)), sourceChanged: false } ]) ); }; const validate = () => { const invalid: Record = {}; Object.entries(inputs.value).forEach(([name, input]) => { input.errors = []; if ( input.required && (input.value === undefined || input.value === "" || input.value === null) ) input.errors.push(`Invalid ${name}. Please provide value`); if (input.validate) { const valid = input.validate(input.value, inputs); if (valid !== true) { input.errors.push( valid === false ? `Invalid ${name}` : valid ); } } if (input.errors.length > 0) invalid[name] = input.errors.join(", "); }); return invalid; }; const save = (saveCb?: () => void) => { const errors = validate(); const errorCount = Object.keys(errors).length; if (errorCount === 0 && unsavedChanges.value.length > 0) { const onSave = () => { useCallback("success") .then(() => { resetOriginalValues(); if (saveCb) saveCb(); }) .catch((err: Error) => useCallback("error", { error: err.message }) ); }; if (sourceChanged.value.length > 0) openModal({ modal: "confirm", props: { message: "Updates have been made whilst you were making changes. Are you sure you want to continue?", onCompleted: onSave } }); else onSave(); } else if (errorCount === 0) { useCallback("unchanged", { unchanged: "No changes have been made" }) .then(() => { if (saveCb) saveCb(); }) .catch((err: Error) => useCallback("error", { error: err.message }) ); } else { useCallback("error", { ...errors, error: `${errorCount} ${ errorCount === 1 ? "input" : "inputs" } failed validation.` }); } }; const setValue = (value: Record, reset?: boolean) => { Object.entries(value).forEach(([name, inputValue]) => { if (inputs.value[name]) { inputs.value[name].value = JSON.parse( JSON.stringify(inputValue) ); if (reset) { inputs.value[name].sourceChanged = false; inputs.value[name].originalValue = JSON.parse( JSON.stringify(inputValue) ); } } }); }; const setOriginalValue = (value: Record) => { Object.entries(value).forEach(([name, inputValue]) => { if (inputs.value[name]) { if ( JSON.stringify(inputValue) !== JSON.stringify(inputs.value[name].originalValue) ) { if (unsavedChanges.value.find(change => change === name)) inputs.value[name].sourceChanged = true; else inputs.value[name].value = JSON.parse( JSON.stringify(inputValue) ); inputs.value[name].originalValue = JSON.parse( JSON.stringify(inputValue) ); } } }); }; onMounted(() => { if ( options && options.modalUuid && options.preventCloseUnsaved !== false ) preventCloseUnsaved[options.modalUuid] = () => unsavedChanges.value.length > 0; }); onBeforeUnmount(() => { if ( options && options.modalUuid && options.preventCloseUnsaved !== false ) delete preventCloseUnsaved[options.modalUuid]; }); return { inputs, unsavedChanges, save, setValue, setOriginalValue }; };