useForm.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { computed, isReactive, reactive, toRaw, watch } from "vue";
  2. import isEqual from "fast-deep-equal";
  3. export const useForm = (initialFormValue) => {
  4. const form = reactive(deepClone(initialFormValue));
  5. let originalForm = deepClone(initialFormValue);
  6. const updatedFields = reactive({});
  7. const getUpdatedFields = computed(() => {
  8. return updatedFields;
  9. });
  10. const hasUpdatedFields = computed(() => {
  11. return Object.keys(updatedFields).length > 0;
  12. });
  13. const getFormAsFormData = () => {
  14. const formData = new FormData();
  15. buildFormData(formData, form);
  16. return formData;
  17. };
  18. const getUpdatedFieldsAsFormData = (spoofMethod = null) => {
  19. const formData = new FormData();
  20. buildFormData(formData, updatedFields);
  21. if (spoofMethod) {
  22. formData.append("_method", spoofMethod.toUpperCase());
  23. }
  24. return formData;
  25. };
  26. const resetUpdateForm = () => {
  27. const newFormState = deepClone(originalForm);
  28. Object.keys(form).forEach((key) => delete form[key]);
  29. Object.assign(form, newFormState);
  30. };
  31. const setUpdateFormAsOriginal = () => {
  32. originalForm = deepClone(toRaw(form));
  33. };
  34. watch(
  35. form,
  36. (newValue) => {
  37. const changes = diff(toRaw(newValue), originalForm);
  38. Object.keys(updatedFields).forEach((key) => delete updatedFields[key]);
  39. Object.assign(updatedFields, changes);
  40. },
  41. { deep: true },
  42. );
  43. return {
  44. form,
  45. getUpdatedFields,
  46. hasUpdatedFields,
  47. getFormAsFormData,
  48. getUpdatedFieldsAsFormData,
  49. resetUpdateForm,
  50. setUpdateFormAsOriginal,
  51. };
  52. };
  53. const buildFormData = (formData, data, parentKey = "") => {
  54. if (data === undefined) {
  55. return;
  56. }
  57. if (data == null) {
  58. formData.append(parentKey, null);
  59. return;
  60. }
  61. if (Array.isArray(data)) {
  62. data.forEach((value, index) => {
  63. buildFormData(formData, value, `${parentKey}[${index}]`);
  64. });
  65. return;
  66. }
  67. if (
  68. typeof data === "object" &&
  69. !(data instanceof File) &&
  70. !(data instanceof Date)
  71. ) {
  72. Object.keys(data).forEach((key) => {
  73. const propName = parentKey ? `${parentKey}[${key}]` : key;
  74. buildFormData(formData, data[key], propName);
  75. });
  76. return;
  77. }
  78. let valueToAppend = data;
  79. if (data instanceof Date) {
  80. valueToAppend = data.toISOString().slice(0, 19).replace("T", " ");
  81. }
  82. formData.append(parentKey, valueToAppend);
  83. }
  84. const deepClone = (obj) => {
  85. if (obj && isReactive(obj)) {
  86. obj = toRaw(obj);
  87. }
  88. if (typeof structuredClone === "function") {
  89. try {
  90. return structuredClone(obj);
  91. } catch (e) {
  92. console.warn(
  93. "structuredClone not supported, using JSON methods instead: " + e,
  94. );
  95. }
  96. }
  97. return JSON.parse(JSON.stringify(obj));
  98. }
  99. const diff = (currentObj, baseObj) => {
  100. const changes = {};
  101. const currentKeys = Object.keys(currentObj);
  102. const baseKeys = Object.keys(baseObj);
  103. for (const key of currentKeys) {
  104. const currentValue = currentObj[key];
  105. const baseValue = baseObj[key];
  106. if (!isEqual(currentValue, baseValue)) {
  107. if (
  108. currentValue &&
  109. typeof currentValue === "object" &&
  110. !Array.isArray(currentValue) &&
  111. baseValue &&
  112. typeof baseValue === "object" &&
  113. !Array.isArray(baseValue)
  114. ) {
  115. const nestedChanges = diff(currentValue, baseValue);
  116. if (Object.keys(nestedChanges).length > 0) {
  117. changes[key] = nestedChanges;
  118. }
  119. } else {
  120. changes[key] = deepClone(currentValue);
  121. }
  122. }
  123. }
  124. for (const key of baseKeys) {
  125. if (!Object.prototype.hasOwnProperty.call(currentObj, key)) {
  126. changes[key] = null;
  127. }
  128. }
  129. return changes;
  130. }