【问题标题】:Aborting navigation with Meteor iron-router使用 Meteor Iron-router 中止导航
【发布时间】:2014-08-13 14:39:29
【问题描述】:

我在 Meteor 应用程序中有一个(客户端)路由器,并使用 {{pathFor}} 帮助程序链接。

当用户更改表单字段时,我在Session 中设置dirty 标志,并且我想触发警告并允许用户在设置标志时停止导航离开页面,基本上就像onunload 处理程序。

我尝试过这样做:

Router.onBeforeAction(function(pause) {
    var self = this;

    if (!this.ready()) {
        return;
    }

    if(Session.get('dirty')) {
        if(!confirm("Are you sure you want to navigate away?")) {
            pause();
        }
    }
});

但是,当我收到提示时,我仍然被引导离开。也就是说,pause() 似乎并没有停止后续的路由器操作,无论它是什么。

我做错了什么?

【问题讨论】:

    标签: meteor iron-router


    【解决方案1】:

    Iron Router API 没有提供实现此目的的简单方法。无法从 onBeforeAction 挂钩取消正在进行的转换。它必须通过重定向到以前的路线来解决。

    /*
     * 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
      }
    })
    

    可以在in this gist找到最新版本。

    【讨论】:

      【解决方案2】:

      据我所知,这对于 Iron-router API 是不可能的。但是,您可以做的是像这样覆盖 Router.go 方法(在您的客户端代码中的某处):

      var go = Router.go; // cache the original Router.go method
      Router.go = function () {
        if(Session.get('dirty')) {
          if (confirm("Are you sure you want to navigate away?")) {
            go.apply(this, arguments);
          }
        } else {
          go.apply(this, arguments);
        }
      };
      

      【讨论】:

      • 您先生,真是个天才。 :) 如果有一种稍微不那么 hacky 的方式来做到这一点,那就太好了,但这绝对有效。
      【解决方案3】:

      我发现在stop 中重定向有效,即使您没有通过Router.go 更改路线(例如通过我的应用程序中的链接)也有效。

      这是一个使用从RouteController继承的类的咖啡脚本实现

      class MyRouteController extends RouteController
        stop: ->
          # Save whether you data/form is dirty or whatever state you have in 
          # a Session variable.
          if Session.get('formIsDirty') 
            if !confirm('You have unsaved data. Are you sure you want to leave?')
              # Redirecting to the current route stops the current navigation.
              # Although, it does rerun the route, so it isn't a perfect solution.
              Router.go '/my_route'
              # Return here so we don't perform any more of the stop operation.
              return
          # Otherwise do as normal.
          super
      

      【讨论】:

        【解决方案4】:

        Iron 路由器的新行为应该使这更容易,因为它需要在 onBeforeAction 钩子中调用this.next()(请参阅iron router guide),因此仅在会话不脏或用户确认警告时调用:

        if(Session.get('dirty')) {
            if(confirm("Are you sure you want to navigate away?")) {
                this.next();
            }
        } else {
            this.next();
        }
        

        【讨论】:

          【解决方案5】:

          这是您想去的特定地方吗?还有 Router.go(routeName) 将使页面指向给定的 routeName。我想要的是也许您可以强制路由器转到当前页面,从而忽略返回操作。

          【讨论】:

          • 我希望用户不要离开页面,即我想中止路由。
          猜你喜欢
          • 2014-10-23
          • 2017-06-17
          • 2016-10-31
          • 1970-01-01
          • 2014-05-07
          • 2013-11-21
          • 1970-01-01
          • 2023-03-12
          • 2023-03-30
          相关资源
          最近更新 更多