diff --git a/frontend/vue/components/Admin/CustomFields/EditModal.vue b/frontend/vue/components/Admin/CustomFields/EditModal.vue
index 78d6d61bb..6605ee5c5 100644
--- a/frontend/vue/components/Admin/CustomFields/EditModal.vue
+++ b/frontend/vue/components/Admin/CustomFields/EditModal.vue
@@ -1,6 +1,6 @@
-
diff --git a/frontend/vue/components/Common/ModalForm.vue b/frontend/vue/components/Common/ModalForm.vue
index 9017fe1a4..36967828d 100644
--- a/frontend/vue/components/Common/ModalForm.vue
+++ b/frontend/vue/components/Common/ModalForm.vue
@@ -137,6 +137,7 @@ const show = () => {
};
defineExpose({
- show
+ show,
+ hide
});
diff --git a/frontend/vue/functions/useBaseEditModal.js b/frontend/vue/functions/useBaseEditModal.js
new file mode 100644
index 000000000..5ec7b3b9b
--- /dev/null
+++ b/frontend/vue/functions/useBaseEditModal.js
@@ -0,0 +1,166 @@
+import {computed, ref, toRef} from "vue";
+import {useVuelidateOnForm} from "~/functions/useVuelidateOnForm";
+import mergeExisting from "~/functions/mergeExisting";
+import {useNotify} from "~/vendor/bootstrapVue";
+import {useAxios} from "~/vendor/axios";
+
+export const baseEditModalProps = {
+ createUrl: {
+ type: String,
+ required: true
+ }
+};
+
+export function useBaseEditModal(
+ props,
+ emit,
+ $modal,
+ validations,
+ blankForm,
+ userOptions = {}
+) {
+ const options = {
+ clearContents: null,
+ populateForm: null,
+ getSubmittableFormData: null,
+ buildSubmitRequest: null,
+ onSubmitSuccess: null,
+ onSubmitError: null,
+ ...userOptions
+ };
+
+ const createUrl = toRef(props, 'createUrl');
+
+ const loading = ref(true);
+ const error = ref(null);
+ const editUrl = ref(null);
+
+ const isEditMode = computed(() => {
+ return editUrl.value !== null;
+ });
+
+ const {form, v$, resetForm, ifValid} = useVuelidateOnForm(validations, blankForm);
+
+ const clearContents = () => {
+ if (typeof options.clearContents === 'function') {
+ return options.clearContents();
+ }
+
+ resetForm();
+
+ loading.value = false;
+ error.value = null;
+ editUrl.value = null;
+ };
+
+ const create = () => {
+ clearContents();
+
+ $modal.value.show();
+ };
+
+ const populateForm = (data) => {
+ if (typeof options.populateForm === 'function') {
+ return options.populateForm();
+ }
+
+ form.value = mergeExisting(form.value, data);
+ }
+
+ const {wrapWithLoading, notifySuccess} = useNotify();
+ const {axios} = useAxios();
+
+ const doLoad = () => {
+ wrapWithLoading(
+ axios.get(editUrl.value)
+ ).then((resp) => {
+ populateForm(resp.data);
+ loading.value = false;
+ }).catch(() => {
+ close();
+ });
+ };
+
+ const edit = (recordUrl) => {
+ clearContents();
+
+ editUrl.value = recordUrl;
+ $modal.value.show();
+
+ doLoad();
+ };
+
+ const getSubmittableFormData = () => {
+ if (typeof options.getSubmittableFormData === 'function') {
+ return options.getSubmittableFormData();
+ }
+
+ return form.value;
+ };
+
+ const buildSubmitRequest = () => {
+ if (typeof options.buildSubmitRequest === 'function') {
+ return options.buildSubmitRequest();
+ }
+
+ return {
+ method: (isEditMode.value)
+ ? 'PUT'
+ : 'POST',
+ url: (isEditMode.value)
+ ? editUrl.value
+ : createUrl.value,
+ data: getSubmittableFormData()
+ };
+ };
+
+ const close = () => {
+ $modal.value.hide();
+ };
+
+ const onSubmitSuccess = () => {
+ if (typeof options.onSubmitSuccess === 'function') {
+ return options.onSubmitSuccess();
+ }
+
+ notifySuccess();
+ emit('relist');
+ close();
+ };
+
+ const onSubmitError = (error) => {
+ if (typeof options.onSubmitError === 'function') {
+ return options.onSubmitError(error);
+ }
+
+ error.value = error.response.data.message;
+ };
+
+ const doSubmit = () => {
+ ifValid(() => {
+ error.value = null;
+
+ wrapWithLoading(
+ axios(buildSubmitRequest())
+ ).then((resp) => {
+ onSubmitSuccess(resp);
+ }).catch((error) => {
+ onSubmitError(error);
+ });
+ });
+ };
+
+ return {
+ loading,
+ error,
+ editUrl,
+ isEditMode,
+ form,
+ v$,
+ clearContents,
+ create,
+ edit,
+ doSubmit,
+ close
+ };
+}