Created
December 27, 2022 20:40
-
-
Save nurdism/013dfc5deab80926da784e32c6067826 to your computer and use it in GitHub Desktop.
Navigation guards for Inertia JS + vue 3 + typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script lang="ts"> | |
import type { Method } from '@inertiajs/inertia' | |
import type { PropType } from 'vue' | |
import type { RouteParamsWithQueryOverload, Config } from 'ziggy-js' | |
import { h } from 'vue' | |
import { Inertia, mergeDataIntoQueryString, shouldIntercept } from '@inertiajs/inertia' | |
import { getRouteGuard, BeforeRouteLeave, AfterRouteLeave } from '~/compostables/router' | |
type QueryStringArrayFormat = 'brackets' | 'indices' | |
const noop = () => ({}) | |
export default defineComponent({ | |
props: { | |
as: { | |
type: String, | |
default: 'a', | |
}, | |
data: { | |
type: Object, | |
default: () => ({}), | |
}, | |
to: { | |
type: String, | |
}, | |
params: { | |
type: Object as PropType<RouteParamsWithQueryOverload>, | |
}, | |
config: { | |
type: Object as PropType<Config>, | |
}, | |
absolute: { | |
type: Boolean, | |
}, | |
href: { | |
type: String, | |
}, | |
method: { | |
type: String, | |
default: 'get', | |
}, | |
replace: { | |
type: Boolean, | |
default: false, | |
}, | |
preserveScroll: { | |
type: Boolean, | |
default: false, | |
}, | |
preserveState: { | |
type: Boolean, | |
default: null, | |
}, | |
only: { | |
type: Array as PropType<Array<string>>, | |
default: () => [] as Array<string>, | |
}, | |
confirm: { | |
type: Function as PropType<() => Promise<boolean>>, | |
}, | |
headers: { | |
type: Object, | |
default: () => ({}), | |
}, | |
queryStringArrayFormat: { | |
type: String as PropType<QueryStringArrayFormat>, | |
default: 'brackets', | |
}, | |
}, | |
setup(props, { slots, attrs }) { | |
return () => { | |
const element = props.as.toLowerCase() | |
const method = props.method.toLowerCase() as Method | |
const [href, data] = mergeDataIntoQueryString( | |
method, | |
(props.to ? route(props.to, props.params, props.absolute, props.config) : props.href) || '', | |
props.data, | |
props.queryStringArrayFormat, | |
) | |
if (element === 'a' && method !== 'get') { | |
console.warn( | |
`Creating POST/PUT/PATCH/DELETE <a> links is discouraged as it causes "Open Link in New Tab/Window" accessibility issues.\n\nPlease specify a more appropriate element using the "as" attribute. For example:\n\n<Link href="${href}" method="${method}" as="button">...</Link>`, | |
) | |
} | |
return h( | |
element, | |
{ | |
...attrs, | |
...(element === 'a' ? { href } : {}), | |
onClick: async (event: MouseEvent) => { | |
// ignore warn | |
if (shouldIntercept(event as any)) { | |
event.preventDefault() | |
let gaurdOptions = { href, data, method, replace: props.replace } | |
const beforeRouteLeave = getRouteGuard('beforeRouteLeave') as BeforeRouteLeave | |
if (beforeRouteLeave && beforeRouteLeave.guard() === true) { | |
const result = await beforeRouteLeave.confirm(gaurdOptions, event) | |
if (result === false) { | |
return | |
} | |
} | |
if (!props.confirm || (await props.confirm())) { | |
const afterRouteLeaveGuard = getRouteGuard('afterRouteLeave') as AfterRouteLeave | |
Inertia.visit(href, { | |
data: data, | |
method: method, | |
replace: props.replace, | |
preserveScroll: props.preserveScroll, | |
preserveState: props.preserveState ?? method !== 'get', | |
//only: props.only, | |
headers: props.headers, | |
onCancelToken: (attrs.onCancelToken || noop) as any, | |
onBefore: (attrs.onBefore || noop) as any, | |
onStart: (attrs.onStart || noop) as any, | |
onProgress: (attrs.onProgress || noop) as any, | |
onFinish: (...args) => { | |
if (afterRouteLeaveGuard !== undefined) { | |
afterRouteLeaveGuard({ ...gaurdOptions, ...args }, event) | |
} | |
if (attrs.onFinish && typeof attrs.onFinish === 'function') { | |
attrs.onFinish(...args) | |
} | |
}, | |
onCancel: (attrs.onCancel || noop) as any, | |
onSuccess: (attrs.onSuccess || noop) as any, | |
onError: (attrs.onError || noop) as any, | |
}) | |
} | |
} | |
}, | |
}, | |
slots, | |
) | |
} | |
}, | |
}) | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { onMounted, onBeforeUnmount } from 'vue' | |
interface GaurdStore { | |
beforeRouteLeave?: BeforeRouteLeave | |
afterRouteLeave?: AfterRouteLeave | |
} | |
export type RouterGuard = (event?: BeforeUnloadEvent) => boolean | undefined | |
export type ConfirmGuard = (options?: any, event?: MouseEvent) => Promise<boolean | undefined> | boolean | undefined | |
export type BeforeRouteLeave = { guard: RouterGuard; confirm: ConfirmGuard } | |
export type AfterRouteLeave = (options?: any, event?: MouseEvent) => void | |
const gaurds: GaurdStore = {} | |
export function getRouteGuard(type: keyof GaurdStore): AfterRouteLeave | BeforeRouteLeave | undefined { | |
return gaurds[type] ? gaurds[type] : undefined | |
} | |
export function onBeforeRouteLeave(guard: RouterGuard, confirm: ConfirmGuard): void { | |
const onBeforeUnload = (event: BeforeUnloadEvent) => { | |
if (gaurds.beforeRouteLeave && gaurds.beforeRouteLeave.guard(event) == true) { | |
event.preventDefault() | |
event.returnValue = '' | |
} | |
} | |
onBeforeUnmount(() => { | |
window.removeEventListener('beforeunload', onBeforeUnload) | |
delete gaurds.beforeRouteLeave | |
}) | |
onMounted(() => { | |
gaurds.beforeRouteLeave = { guard, confirm } | |
window.addEventListener('beforeunload', onBeforeUnload) | |
}) | |
} | |
export function onAfterRouteLeave(guard: RouterGuard): void { | |
onBeforeUnmount(() => { | |
delete gaurds.afterRouteLeave | |
}) | |
onMounted(() => { | |
gaurds.afterRouteLeave = guard | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script lang="ts" setup> | |
import { onBeforeRouteLeave } from '~/components/router.ts' | |
const dirty = ref(false) | |
onBeforeRouteLeave( | |
() => dirty.value, | |
async () => { | |
return window.confirm("Do you really want to leave?") | |
}, | |
) | |
</script> | |
<template> | |
<h1>Test</h1> | |
<AppLink :href="route('home')">App Link</AppLink> | |
<a href="https://google.com/">Google</a> | |
</template> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment