The Iron Router API doesn't offer an easy way to achieve this. There is no way to cancel an ongoing transition from an onBeforeAction
hook. It has to be worked around by redirecting to the previous route.
/*
* Adds a confirmation dialogue when the current route contains unsaved changes.
*
* This is tricky because Iron Router doesn't support this out of the box, and
* the reactivity gets in the way.
* In this solution, redirecting to the current route is abused
* as a mechanism to stop the current transition, which Iron Router has no API
* for. Because the redirect would trigger the onStop hook, we keep track of
* whether to run the onStop hook or not ourselves in
* `skipConfirmationForNextTransition`.
*
* When `Session.get('formIsDirty')` returns `true`, the user will be asked
* whether he really wants to leave the route or not.
*
* Further, another confirmation is added in case the browser window is closed
* with unsaved data.
*
* This gist shows the basics of how to achieve a navigation confirmation,
* also known as canceling a route transition.
* This approach may fail if other route hooks trigger reruns of hooks reactively.
* Maybe setting `skipConfirmationForNextTransition` to `true` could help in those
* cases.
*/
Session.setDefault('formIsDirty', false)
const confirmationMessage = 'You have unsaved data. Are you sure you want to leave?'
// whether the user should confirm the navigation or not,
// set to `true` before redirecting programmatically to skip confirmation
let skipConfirmationForNextTransition = false
Router.onStop(function () {
// register dependencies immediately
const formIsDirty = Session.equals('formIsDirty', true)
// prevent duplicate execution of onStop route, because it would run again
// after the redirect
if (skipConfirmationForNextTransition) {
skipConfirmationForNextTransition = false
return
}
if (formIsDirty) {
const shouldLeave = confirm(confirmationMessage)
if (shouldLeave) {
Session.set('formIsDirty', false)
return
}
// obtain a non-reactive reference to the current route
let currentRoute
Tracker.nonreactive(function () {
currentRoute = Router.current()
})
skipConfirmationForNextTransition = true
// "cancel" the transition by redirecting to the same route
// this had to be used because Iron Router doesn't support cancling the
// current transition. `url` contains the query params and hash.
this.redirect(currentRoute.url)
return
}
})
// Bonus: confirm closing of browser window
window.addEventListener('beforeunload', event => {
if (Session.get('formIsDirty')) {
// cross-browser requries returnValue to be set, as well as an actual
// return value
event.returnValue = confirmationMessage // eslint-disable-line no-param-reassign
return confirmationMessage
}
})
An up-to-date version can be found in this gist.