useForm.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import { ref, computed, onMounted, onBeforeUnmount } from "vue";
  2. import { useModalsStore } from "@/stores/modals";
  3. export const useForm = (
  4. inputOptions: Record<
  5. string,
  6. | {
  7. value: any;
  8. validate?: (value: any) => boolean | string;
  9. }
  10. | any
  11. >,
  12. cb: (
  13. response: {
  14. status: string;
  15. messages: Record<string, string>;
  16. values: Record<string, any>;
  17. },
  18. resolve: (value?: undefined) => void,
  19. reject: (value: Error) => void
  20. ) => void,
  21. options?: {
  22. modalUuid?: string;
  23. preventCloseUnsaved?: boolean;
  24. }
  25. ) => {
  26. const { openModal, preventCloseUnsaved } = useModalsStore();
  27. const inputs = ref(
  28. Object.fromEntries(
  29. Object.entries(inputOptions).map(([name, input]) => {
  30. if (typeof input !== "object") input = { value: input };
  31. return [
  32. name,
  33. {
  34. ...input,
  35. originalValue: JSON.parse(JSON.stringify(input.value)),
  36. errors: [],
  37. ref: ref(),
  38. sourceChanged: false,
  39. ignoreUnsaved: input.ignoreUnsaved === true,
  40. required:
  41. input.required === undefined ? true : input.required
  42. }
  43. ];
  44. })
  45. )
  46. );
  47. const unsavedChanges = computed(() => {
  48. const changed: string[] = [];
  49. Object.entries(inputs.value).forEach(([name, input]) => {
  50. if (
  51. !input.ignoreUnsaved &&
  52. JSON.stringify(input.value) !==
  53. JSON.stringify(input.originalValue)
  54. )
  55. changed.push(name);
  56. });
  57. return changed;
  58. });
  59. const sourceChanged = computed(() => {
  60. const _sourceChanged: string[] = [];
  61. Object.entries(inputs.value).forEach(([name, input]) => {
  62. if (
  63. input.sourceChanged &&
  64. unsavedChanges.value.find(change => change === name)
  65. )
  66. _sourceChanged.push(name);
  67. });
  68. return _sourceChanged;
  69. });
  70. const useCallback = (status: string, messages?: Record<string, string>) =>
  71. new Promise((resolve, reject: (reason: Error) => void) => {
  72. cb(
  73. {
  74. status,
  75. messages: { ...messages },
  76. values: Object.fromEntries(
  77. Object.entries(inputs.value).map(([name, input]) => [
  78. name,
  79. input.value
  80. ])
  81. )
  82. },
  83. resolve,
  84. reject
  85. );
  86. });
  87. const resetOriginalValues = () => {
  88. inputs.value = Object.fromEntries(
  89. Object.entries(inputs.value).map(([name, input]) => [
  90. name,
  91. {
  92. ...input,
  93. originalValue: JSON.parse(JSON.stringify(input.value)),
  94. sourceChanged: false
  95. }
  96. ])
  97. );
  98. };
  99. const validate = () => {
  100. const invalid: Record<string, string[]> = {};
  101. Object.entries(inputs.value).forEach(([name, input]) => {
  102. input.errors = [];
  103. if (
  104. input.required &&
  105. (input.value === undefined ||
  106. input.value === "" ||
  107. input.value === null)
  108. )
  109. input.errors.push(`Invalid ${name}. Please provide value`);
  110. if (input.validate) {
  111. const valid = input.validate(input.value);
  112. if (valid !== true) {
  113. input.errors.push(
  114. valid === false ? `Invalid ${name}` : valid
  115. );
  116. }
  117. }
  118. if (input.errors.length > 0)
  119. invalid[name] = input.errors.join(", ");
  120. });
  121. return invalid;
  122. };
  123. const save = (saveCb?: () => void) => {
  124. const errors = validate();
  125. const errorCount = Object.keys(errors).length;
  126. if (errorCount === 0 && unsavedChanges.value.length > 0) {
  127. const onSave = () => {
  128. useCallback("success")
  129. .then(() => {
  130. resetOriginalValues();
  131. if (saveCb) saveCb();
  132. })
  133. .catch((err: Error) =>
  134. useCallback("error", { error: err.message })
  135. );
  136. };
  137. if (sourceChanged.value.length > 0)
  138. openModal({
  139. modal: "confirm",
  140. props: {
  141. message:
  142. "Updates have been made whilst you were making changes. Are you sure you want to continue?",
  143. onCompleted: onSave
  144. }
  145. });
  146. else onSave();
  147. } else if (errorCount === 0) {
  148. useCallback("unchanged", { unchanged: "No changes have been made" })
  149. .then(() => {
  150. if (saveCb) saveCb();
  151. })
  152. .catch((err: Error) =>
  153. useCallback("error", { error: err.message })
  154. );
  155. } else {
  156. useCallback("error", {
  157. ...errors,
  158. error: `${errorCount} ${
  159. errorCount === 1 ? "input" : "inputs"
  160. } failed validation.`
  161. });
  162. }
  163. };
  164. const setValue = (value: Record<string, any>, reset?: boolean) => {
  165. Object.entries(value).forEach(([name, inputValue]) => {
  166. if (inputs.value[name]) {
  167. inputs.value[name].value = JSON.parse(
  168. JSON.stringify(inputValue)
  169. );
  170. if (reset) {
  171. inputs.value[name].sourceChanged = false;
  172. inputs.value[name].originalValue = JSON.parse(
  173. JSON.stringify(inputValue)
  174. );
  175. }
  176. }
  177. });
  178. };
  179. const setOriginalValue = (value: Record<string, any>) => {
  180. Object.entries(value).forEach(([name, inputValue]) => {
  181. if (inputs.value[name]) {
  182. if (
  183. JSON.stringify(inputValue) !==
  184. JSON.stringify(inputs.value[name].originalValue)
  185. ) {
  186. if (unsavedChanges.value.find(change => change === name))
  187. inputs.value[name].sourceChanged = true;
  188. else
  189. inputs.value[name].value = JSON.parse(
  190. JSON.stringify(inputValue)
  191. );
  192. inputs.value[name].originalValue = JSON.parse(
  193. JSON.stringify(inputValue)
  194. );
  195. }
  196. }
  197. });
  198. };
  199. onMounted(() => {
  200. if (
  201. options &&
  202. options.modalUuid &&
  203. options.preventCloseUnsaved !== false
  204. )
  205. preventCloseUnsaved[options.modalUuid] = () =>
  206. unsavedChanges.value.length > 0;
  207. });
  208. onBeforeUnmount(() => {
  209. if (
  210. options &&
  211. options.modalUuid &&
  212. options.preventCloseUnsaved !== false
  213. )
  214. delete preventCloseUnsaved[options.modalUuid];
  215. });
  216. return {
  217. inputs,
  218. unsavedChanges,
  219. save,
  220. setValue,
  221. setOriginalValue
  222. };
  223. };