Skip to content

Commit 9cc1419

Browse files
authored
Merge pull request #464 from devforth/feature/AdminForth/1213/add-ability-to-restore-unsaved
Feature/admin forth/1213/add ability to restore unsaved
2 parents 4438f58 + a8db834 commit 9cc1419

File tree

2 files changed

+83
-21
lines changed

2 files changed

+83
-21
lines changed

adminforth/spa/src/views/CreateView.vue

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<BreadcrumbsWithButtons>
1414
<!-- save and cancle -->
15-
<button @click="$router.back()"
15+
<button @click="() => {cancelButtonClicked = true; $router.back()}"
1616
class="af-cancel-button flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default text-lightCreateViewButtonText focus:outline-none bg-lightCreateViewButtonBackground rounded border border-lightCreateViewButtonBorder hover:bg-lightCreateViewButtonBackgroundHover hover:text-lightCreateViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightCreateViewButtonFocusRing dark:focus:ring-darkCreateViewButtonFocusRing dark:bg-darkCreateViewButtonBackground dark:text-darkCreateViewButtonText dark:border-darkCreateViewButtonBorder dark:hover:text-darkCreateViewButtonTextHover dark:hover:bg-darkCreateViewButtonBackgroundHover"
1717
>
1818
{{ $t('Cancel') }}
@@ -81,8 +81,8 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
8181
import { useCoreStore } from '@/stores/core';
8282
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown, checkShowIf } from '@/utils';
8383
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
84-
import { onMounted, onBeforeMount, ref, watch, nextTick } from 'vue';
85-
import { useRoute, useRouter } from 'vue-router';
84+
import { onMounted, onBeforeMount, onBeforeUnmount, ref, watch, nextTick } from 'vue';
85+
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
8686
import { computed } from 'vue';
8787
import { showErrorTost } from '@/composables/useFrontendApi';
8888
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
@@ -103,7 +103,7 @@ const router = useRouter();
103103
const record = ref({});
104104
105105
const coreStore = useCoreStore();
106-
const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
106+
const { clearSaveInterceptors, runSaveInterceptors, alert, confirm } = useAdminforth();
107107
108108
const { t } = useI18n();
109109
@@ -113,11 +113,38 @@ const initialValues = ref({});
113113
114114
const readonlyColumns = ref([]);
115115
116+
const cancelButtonClicked = ref(false);
117+
const wasSaveSuccessful = ref(false);
116118
117119
async function onUpdateRecord(newRecord: any) {
118120
record.value = newRecord;
119121
}
120122
123+
function checkIfWeCanLeavePage() {
124+
return wasSaveSuccessful.value || cancelButtonClicked.value || JSON.stringify(record.value) === JSON.stringify(initialValues.value);
125+
}
126+
127+
function onBeforeUnload(event: BeforeUnloadEvent) {
128+
if (!checkIfWeCanLeavePage()) {
129+
event.preventDefault();
130+
event.returnValue = '';
131+
}
132+
}
133+
134+
window.addEventListener('beforeunload', onBeforeUnload);
135+
136+
onBeforeUnmount(() => {
137+
window.removeEventListener('beforeunload', onBeforeUnload);
138+
});
139+
140+
onBeforeRouteLeave(async (to, from, next) => {
141+
if (!checkIfWeCanLeavePage()) {
142+
const answer = await confirm({message: t('There are unsaved changes. Are you sure you want to leave this page?'), yes: 'Yes', no: 'No'});
143+
if (!answer) return next(false);
144+
}
145+
next();
146+
});
147+
121148
onBeforeMount(() => {
122149
clearSaveInterceptors(route.params.resourceId as string);
123150
});
@@ -202,6 +229,7 @@ async function saveRecord() {
202229
showErrorTost(response.error);
203230
} else {
204231
saving.value = false;
232+
wasSaveSuccessful.value = true;
205233
if (route.query.returnTo) {
206234
router.push(<string>route.query.returnTo);
207235
} else {

adminforth/spa/src/views/EditView.vue

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<BreadcrumbsWithButtons>
1313
<!-- save and cancle -->
14-
<button @click="$router.back()"
14+
<button @click="() => {cancelButtonClicked = true; $router.back()}"
1515
class="flex items-center py-1 px-3 me-2 text-sm font-medium text-lightEditViewButtonText rounded-default focus:outline-none bg-lightEditViewButtonBackground rounded border border-lightEditViewButtonBorder hover:bg-lightEditViewButtonBackgroundHover hover:text-lightEditViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightEditViewButtonFocusRing dark:focus:ring-darkEditViewButtonFocusRing dark:bg-darkEditViewButtonBackground dark:text-darkEditViewButtonText dark:border-darkEditViewButtonBorder dark:hover:text-darkEditViewButtonTextHover dark:hover:bg-darkEditViewButtonBackgroundHover"
1616
>
1717
{{ $t('Cancel') }}
@@ -76,8 +76,8 @@ import SingleSkeletLoader from '@/components/SingleSkeletLoader.vue';
7676
import { useCoreStore } from '@/stores/core';
7777
import { callAdminForthApi, getCustomComponent,checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
7878
import { IconFloppyDiskSolid } from '@iconify-prerendered/vue-flowbite';
79-
import { computed, onMounted, onBeforeMount, ref, type Ref, nextTick, watch } from 'vue';
80-
import { useRoute, useRouter } from 'vue-router';
79+
import { computed, onMounted, onBeforeMount, ref, type Ref, nextTick, watch, onBeforeUnmount } from 'vue';
80+
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
8181
import { showErrorTost } from '@/composables/useFrontendApi';
8282
import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
8383
import { useAdminforth } from '@/adminforth';
@@ -87,7 +87,7 @@ import type { AdminForthResourceColumn } from '@/types/Back';
8787
8888
const { t } = useI18n();
8989
const coreStore = useCoreStore();
90-
const { clearSaveInterceptors, runSaveInterceptors, alert } = useAdminforth();
90+
const { clearSaveInterceptors, runSaveInterceptors, alert, confirm } = useAdminforth();
9191
9292
const isValid = ref(false);
9393
const validating = ref(false);
@@ -101,6 +101,36 @@ const saving = ref(false);
101101
102102
const record: Ref<Record<string, any>> = ref({});
103103
104+
const initialRecord = computed(() => coreStore.record);
105+
const wasSaveSuccessful = ref(false);
106+
const cancelButtonClicked = ref(false);
107+
108+
function onBeforeUnload(event: BeforeUnloadEvent) {
109+
if (!checkIfWeCanLeavePage()) {
110+
event.preventDefault();
111+
event.returnValue = '';
112+
}
113+
}
114+
115+
function checkIfWeCanLeavePage() {
116+
return wasSaveSuccessful.value || cancelButtonClicked.value || JSON.stringify(record.value) === JSON.stringify(initialRecord.value);
117+
}
118+
119+
window.addEventListener('beforeunload', onBeforeUnload);
120+
121+
onBeforeUnmount(() => {
122+
window.removeEventListener('beforeunload', onBeforeUnload);
123+
});
124+
125+
onBeforeRouteLeave(async (to, from, next) => {
126+
if (!checkIfWeCanLeavePage()) {
127+
const answer = await confirm({message: t('There are unsaved changes. Are you sure you want to leave this page?'), yes: 'Yes', no: 'No'});
128+
if (!answer) return next(false);
129+
}
130+
next();
131+
});
132+
133+
104134
watch(record, (newVal) => {
105135
console.log('Record updated:', newVal);
106136
}, { deep: true });
@@ -198,24 +228,28 @@ async function saveRecord() {
198228
if (columnIsUpdated) {
199229
updates[key] = record.value[key];
200230
}
201-
saving.value = false;
202231
}
203-
204-
const resp = await callAdminForthApi({
205-
method: 'POST',
206-
path: `/update_record`,
207-
body: {
208-
resourceId: route.params.resourceId,
209-
recordId: route.params.primaryKey,
210-
record: updates,
211-
meta: {
212-
...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
232+
let resp = null;
233+
try {
234+
resp = await callAdminForthApi({
235+
method: 'POST',
236+
path: `/update_record`,
237+
body: {
238+
resourceId: route.params.resourceId,
239+
recordId: route.params.primaryKey,
240+
record: updates,
241+
meta: {
242+
...(interceptorConfirmationResult ? { confirmationResult: interceptorConfirmationResult } : {}),
243+
},
213244
},
214-
},
215-
});
245+
});
246+
} finally {
247+
saving.value = false;
248+
}
216249
if (resp.error && resp.error !== 'Operation aborted by hook') {
217250
showErrorTost(resp.error);
218251
} else {
252+
wasSaveSuccessful.value = true;
219253
alert({
220254
message: t('Record updated successfully'),
221255
variant: 'success',

0 commit comments

Comments
 (0)