useFormUpdateTracker.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { reactive, computed, watch, toRaw, isReactive } from "vue";
  2. export const useFormUpdateTracker = (initialFormValue) => {
  3. const form = reactive(deepClone(initialFormValue));
  4. let originalForm = deepClone(initialFormValue);
  5. const updatedFields = reactive({});
  6. const getUpdatedFields = computed(() => {
  7. return updatedFields;
  8. });
  9. const hasUpdatedFields = computed(() => {
  10. return Object.keys(updatedFields).length > 0;
  11. });
  12. const handleNestedObjects = (obj, orig, pathArr = []) => {
  13. Object.keys(obj).forEach((key) => {
  14. const value = obj[key];
  15. const origValue = orig ? orig[key] : undefined;
  16. const currentPath = pathArr.concat(key);
  17. const pathStr = currentPath.join(".");
  18. if (Array.isArray(value)) {
  19. if (!isEqual(value, origValue)) {
  20. updatedFields[pathStr] = deepClone(value);
  21. } else {
  22. delete updatedFields[pathStr];
  23. }
  24. } else if (value && typeof value === "object" && !Array.isArray(value)) {
  25. handleNestedObjects(value, origValue, currentPath);
  26. } else {
  27. if (!isEqual(value, origValue)) {
  28. updatedFields[pathStr] = value;
  29. } else {
  30. delete updatedFields[pathStr];
  31. }
  32. }
  33. });
  34. };
  35. watch(
  36. form,
  37. (newValue) => {
  38. handleNestedObjects(newValue, originalForm);
  39. },
  40. { deep: true },
  41. );
  42. const resetUpdateForm = () => {
  43. Object.keys(form).forEach((key) => {
  44. form[key] = deepClone(originalForm[key]);
  45. });
  46. Object.keys(updatedFields).forEach((key) => {
  47. delete updatedFields[key];
  48. });
  49. };
  50. const setUpdateFormAsOriginal = () => {
  51. originalForm = deepClone(form);
  52. Object.keys(updatedFields).forEach((key) => {
  53. delete updatedFields[key];
  54. });
  55. };
  56. return {
  57. form,
  58. getUpdatedFields,
  59. hasUpdatedFields,
  60. resetUpdateForm,
  61. setUpdateFormAsOriginal,
  62. };
  63. };
  64. function deepClone(obj) {
  65. if (obj && isReactive(obj)) {
  66. obj = toRaw(obj);
  67. }
  68. if (typeof structuredClone === "function") {
  69. try {
  70. return structuredClone(obj);
  71. } catch (e) {
  72. console.warn("structuredClone not supported, using JSON methods instead");
  73. }
  74. }
  75. return JSON.parse(JSON.stringify(obj));
  76. }
  77. function isEqual(a, b) {
  78. if (a === b) return true;
  79. if (typeof a !== typeof b) return false;
  80. if (Array.isArray(a) && Array.isArray(b)) {
  81. if (a.length !== b.length) return false;
  82. return a.every((item, i) => isEqual(item, b[i]));
  83. }
  84. if (typeof a === "object" && a && b) {
  85. const aKeys = Object.keys(a);
  86. const bKeys = Object.keys(b);
  87. if (aKeys.length !== bKeys.length) return false;
  88. return aKeys.every((key) => isEqual(a[key], b[key]));
  89. }
  90. return false;
  91. }