Прерывание навигации с помощью Meteor iron-router

#meteor #iron-router

#метеор #iron-router

Вопрос:

У меня есть (клиентский) маршрутизатор в приложении Meteor и ссылки с помощью {{pathFor}} помощника.

Я устанавливаю dirty флаг в поле Session «когда пользователь изменяет поле формы», и я хочу вызвать предупреждение и разрешить пользователю прекратить переход со страницы, если флаг установлен, в основном как 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() похоже, не останавливает последующее действие маршрутизатора, что бы это ни было.

Что я делаю не так?

Ответ №1:

Из того, что я могу сказать, это невозможно с помощью API iron-router. Однако что вы могли бы сделать, так это переопределить метод 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);
  }
};
 

Комментарии:

1. Вы, сэр, гений. 🙂 Было бы здорово, если бы был немного менее хакерский способ сделать это, но это определенно работает.

Ответ №2:

Это какое-то конкретное место, куда вы хотите пойти? Существует также Router.go(routeName), который заставит страницу указывать на заданное имя маршрута. Возможно, вы можете просто заставить маршрутизатор перейти на текущую страницу, следовательно, пренебрегая обратным действием.

Комментарии:

1. Я хочу, чтобы пользователь не покидал страницу, т. Е. Я хочу прервать маршрут.

Ответ №3:

Новое поведение для iron router должно упростить это, потому что для этого требуется вызов this.next() в перехватчике onBeforeAction (см. Руководство по iron router), поэтому вызывайте его только тогда, когда сеанс не загрязнен или пользователь подтверждает предупреждение:

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

Ответ №4:

Я обнаружил, что перенаправление stop работает и работает, даже если вы не меняете маршруты через Router.go (например, по ссылкам в моем приложении).

Вот реализация coffeescript, использующая класс, унаследованный от 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
 

Ответ №5:

API Iron Router не предлагает простого способа добиться этого. Невозможно отменить текущий переход из 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
  }
})
 

Обновленная версия может быть найдена в этом gist.