Streamline "maybe save" props

This commit is contained in:
Daniel J. Summers 2021-08-29 19:23:36 -04:00
parent 8e8bbb48ba
commit 34d5224c11
5 changed files with 37 additions and 115 deletions

View File

@ -3,47 +3,45 @@
.modal-header: h5.modal-title(id="maybeSaveLabel") Unsaved Changes .modal-header: h5.modal-title(id="maybeSaveLabel") Unsaved Changes
.modal-body You have modified the data on this page since it was last saved. What would you like to do? .modal-body You have modified the data on this page since it was last saved. What would you like to do?
.modal-footer .modal-footer
button.btn.btn-secondary(type="button" @click.prevent="onStay") Stay on This Page button.btn.btn-secondary(type="button" @click.prevent="close") Stay on This Page
button.btn.btn-primary(type="button" @click.prevent="onSave") Save Changes button.btn.btn-primary(type="button" @click.prevent="save") Save Changes
button.btn.btn-danger(type="button" @click.prevent="onDiscard") Discard Changes button.btn.btn-danger(type="button" @click.prevent="discard") Discard Changes
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, Ref, watch } from "vue" import { onMounted, ref, Ref } from "vue"
import { RouteLocationNormalized, useRouter } from "vue-router" import { onBeforeRouteLeave, RouteLocationNormalized, useRouter } from "vue-router"
import { Validation } from "@vuelidate/core" import { Validation } from "@vuelidate/core"
import { Modal } from "bootstrap" import { Modal } from "bootstrap"
const props = defineProps<{ const props = defineProps<{
isShown: boolean saveAction: () => Promise<unknown>
toRoute: RouteLocationNormalized
saveAction?: () => Promise<unknown>
validator?: Validation validator?: Validation
}>() }>()
const emit = defineEmits<{
(e: "close") : void
(e: "discard") : void
(e: "cancel") : void
}>()
const router = useRouter() const router = useRouter()
/** Reference to the modal dialog (we can't get it until the component is rendered) */ /** Reference to the modal dialog (we can't get it until the component is rendered) */
const modal : Ref<Modal | undefined> = ref(undefined) const modal : Ref<Modal | undefined> = ref(undefined)
/** Save changes (if required) and go to the next route */ /** The route to which navigation was intercepted, and will be resumed */
const onSave = async () => { let nextRoute : RouteLocationNormalized
if (props.saveAction) await props.saveAction()
emit("close") /** Close the modal window */
router.push(props.toRoute) const close = () => modal.value?.hide()
/** Save changes and go to the next route */
const save = async () => {
await props.saveAction()
close()
router.push(nextRoute)
} }
/** Discard changes (if required) and go to the next route */ /** Discard changes and go to the next route */
const onDiscard = () => { const discard = () => {
if (props.validator) props.validator.$reset() if (props.validator) props.validator.$reset()
emit("close") close()
router.push(props.toRoute) router.push(nextRoute)
} }
onMounted(() => { onMounted(() => {
@ -51,17 +49,11 @@ onMounted(() => {
{ backdrop: "static", keyboard: false }) { backdrop: "static", keyboard: false })
}) })
/** Show or hide the modal based on the property value changing */ /** Prompt for save if the user navigates away with unsaved changes */
watch(() => props.isShown, (toShow) => { onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
if (modal.value) { if (!props.validator || !props.validator.$anyDirty) return true
if (toShow) { nextRoute = to
modal.value.show() modal.value?.show()
} else { return false
modal.value.hide()
}
}
}) })
/** Stay on this page with no changes; just close the modal */
const onStay = () => emit("close")
</script> </script>

View File

@ -64,13 +64,11 @@ article
p.text-muted.fst-italic. p.text-muted.fst-italic.
(If you want to delete your profile, or your entire account, (If you want to delete your profile, or your entire account,
#[router-link(to="/so-long/options") see your deletion options here].) #[router-link(to="/so-long/options") see your deletion options here].)
maybe-save(:isShown="confirmNavShown" :toRoute="nextRoute" :saveAction="saveProfile" :validator="v$" maybe-save(:saveAction="saveProfile" :validator="v$")
@close="confirmClose")
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, reactive, Ref } from "vue" import { computed, ref, reactive } from "vue"
import { onBeforeRouteLeave, RouteLocationNormalized } from "vue-router"
import useVuelidate from "@vuelidate/core" import useVuelidate from "@vuelidate/core"
import { required } from "@vuelidate/validators" import { required } from "@vuelidate/validators"
@ -183,21 +181,4 @@ const saveProfile = async () => {
v$.value.$reset() v$.value.$reset()
} }
} }
/** Whether the navigation confirmation is shown */
const confirmNavShown = ref(false)
/** The "next" route (will be navigated or cleared) */
const nextRoute : Ref<RouteLocationNormalized | undefined> = ref(undefined)
/** If the user has unsaved changes, give them an opportunity to save before moving on */
onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
if (!v$.value.$anyDirty) return true
nextRoute.value = to
confirmNavShown.value = true
return false
})
/** Close the navigation confirmation modal */
const confirmClose = () => { confirmNavShown.value = false }
</script> </script>

View File

@ -32,12 +32,12 @@ article
.col-12 .col-12
p.text-danger(v-if="v$.$error") Please correct the errors above p.text-danger(v-if="v$.$error") Please correct the errors above
button.btn.btn-primary(@click.prevent="saveListing(true)") #[icon(icon="content-save-outline")]&nbsp; Save button.btn.btn-primary(@click.prevent="saveListing(true)") #[icon(icon="content-save-outline")]&nbsp; Save
maybe-save(:isShown="confirmNavShown" :toRoute="nextRoute" :saveAction="doSave" :validator="v$" @close="confirmClose") maybe-save(:saveAction="doSave" :validator="v$")
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref, Ref } from "vue" import { computed, reactive } from "vue"
import { onBeforeRouteLeave, RouteLocationNormalized, useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import useVuelidate from "@vuelidate/core" import useVuelidate from "@vuelidate/core"
import { required } from "@vuelidate/validators" import { required } from "@vuelidate/validators"
@ -130,23 +130,6 @@ const saveListing = async (navigate : boolean) => {
} }
} }
/** Whether the navigation confirmation is shown */
const confirmNavShown = ref(false)
/** The "next" route (will be navigated or cleared) */
const nextRoute : Ref<RouteLocationNormalized | undefined> = ref(undefined)
/** If the user has unsaved changes, give them an opportunity to save before moving on */
onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
if (!v$.value.$anyDirty) return true
nextRoute.value = to
confirmNavShown.value = true
return false
})
/** Parameterless save function (used to save when navigating away) */ /** Parameterless save function (used to save when navigating away) */
const doSave = async () => await saveListing(false) const doSave = async () => await saveListing(false)
/** Close the navigation confirmation modal */
const confirmClose = () => { confirmNavShown.value = false }
</script> </script>

View File

@ -18,12 +18,12 @@ article
.col-12 .col-12
button.btn.btn-primary(@click.prevent="expireListing"). button.btn.btn-primary(@click.prevent="expireListing").
#[icon(icon="text-box-remove-outline")]&nbsp; Expire Listing #[icon(icon="text-box-remove-outline")]&nbsp; Expire Listing
maybe-save(:isShown="confirmNavShown" :toRoute="nextRoute" :saveAction="doSave" :validator="v$" @close="confirmClose") maybe-save(:saveAction="doSave" :validator="v$")
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, Ref, ref } from "vue" import { computed, reactive, Ref, ref } from "vue"
import { onBeforeRouteLeave, RouteLocationNormalized, useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import useVuelidate from "@vuelidate/core" import useVuelidate from "@vuelidate/core"
import api, { Listing, ListingExpireForm, LogOnSuccess } from "@/api" import api, { Listing, ListingExpireForm, LogOnSuccess } from "@/api"
@ -89,23 +89,6 @@ const expireListing = async (navigate : boolean) => {
} }
} }
/** Whether the navigation confirmation is shown */
const confirmNavShown = ref(false)
/** The "next" route (will be navigated or cleared) */
const nextRoute : Ref<RouteLocationNormalized | undefined> = ref(undefined)
/** Prompt for save if the user navigates away with unsaved changes */
onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
if (!v$.value.$anyDirty) return true
nextRoute.value = to
confirmNavShown.value = true
return false
})
/** No-parameter save function (used for save-on-navigate) */ /** No-parameter save function (used for save-on-navigate) */
const doSave = async () => await expireListing(false) const doSave = async () => await expireListing(false)
/** Close the confirm navigation modal */
const confirmClose = () => { confirmNavShown.value = false }
</script> </script>

View File

@ -15,12 +15,12 @@ article
button.btn.btn-primary(type="submit" @click.prevent="saveStory(true)"). button.btn.btn-primary(type="submit" @click.prevent="saveStory(true)").
#[icon(icon="content-save-outline")]&nbsp; Save #[icon(icon="content-save-outline")]&nbsp; Save
p(v-if="isNew"): em (Saving this will set &ldquo;Seeking Employment&rdquo; to &ldquo;No&rdquo; on your profile.) p(v-if="isNew"): em (Saving this will set &ldquo;Seeking Employment&rdquo; to &ldquo;No&rdquo; on your profile.)
maybe-save(:isShown="confirmNavShown" :toRoute="nextRoute" :saveAction="doSave" :validator="v$" @close="confirmClose") maybe-save(:saveAction="doSave" :validator="v$")
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref, Ref } from "vue" import { computed, reactive } from "vue"
import { onBeforeRouteLeave, RouteLocationNormalized, useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
import useVuelidate from "@vuelidate/core" import useVuelidate from "@vuelidate/core"
import api, { LogOnSuccess, StoryForm } from "@/api" import api, { LogOnSuccess, StoryForm } from "@/api"
@ -106,23 +106,6 @@ const saveStory = async (navigate : boolean) => {
} }
} }
/** Whether the navigation confirmation is shown */
const confirmNavShown = ref(false)
/** The "next" route (will be navigated or cleared) */
const nextRoute : Ref<RouteLocationNormalized | undefined> = ref(undefined)
/** Prompt for save if the user navigates away with unsaved changes */
onBeforeRouteLeave(async (to, from) => { // eslint-disable-line
if (!v$.value.$anyDirty) return true
nextRoute.value = to
confirmNavShown.value = true
return false
})
/** No-parameter save function (used for save-on-navigate) */ /** No-parameter save function (used for save-on-navigate) */
const doSave = async () => await saveStory(false) const doSave = async () => await saveStory(false)
/** Close the confirm navigation modal */
const confirmClose = () => { confirmNavShown.value = false }
</script> </script>