Aborting navigation with Meteor iron-router

前端 未结 5 1254
遥遥无期
遥遥无期 2021-01-12 08:54

I have a (client-side) router in a Meteor app, and links using the {{pathFor}} helper.

I am setting a dirty flag in the Session

5条回答
  •  耶瑟儿~
    2021-01-12 09:33

    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.

提交回复
热议问题