Запретить переход к другому представлению, если содержимое не сохранено

У нас есть приложение backbone.js, которое отображает ряд форм для пользователя. То, что мы хотим, очень просто: если пользователь переходит на другую страницу без сохранения заполненной формы, мы хотим отобразить диалоговое окно подтверждения.

В классических формах это достаточно просто, просто реализуйте window.onbeforeunload (или $(window).on('beforeunload') в jQuerysh). Но базовые приложения обычно имеют только одно представление. Я попытался немного использовать onHashChange, но возвращение false в этом обратном вызове не мешает Backbone по-прежнему переходить в другое представление.

Указатели приветствуются. Поиск в сети не нашел мне никакого действительного ответа.

6 ответов

Решение

Я бы не стал взламывать Backbone. Вы можете сделать это глобально для всех ссылок, заменив ту часть, с которой вы обычно начинаете Backbone.history с чем-то вроде

initRouter: function () {
    Backbone.history.start({ pushState: true });
    $(document).on('click', 'a', function (ev) {
        var href = $(this).attr('href');
        ev.preventDefault();
        if (changesAreSaved) {
            router.navigate(href, true);
        }
    });
}

Вам нужно, конечно, заменить changesAreSaved с чем-то, что имеет смысл, и добавьте любой другой логин для обработки ссылок.

Я бы тоже взломал Backbone.history.loadUrl вот где происходит загрузка обратных вызовов маршрута.

// ALLOW PREVENTING HASH NAVIGATION

var originalFn = Backbone.history.loadUrl;

Backbone.history.loadUrl = function() {
    // I introduced an application state variable, but it can be solved in multiple ways
    if (app && app.states.isNavigationBlocked) {
        var previousFragment = Backbone.history.fragment;
        window.location.hash = '#' + previousFragment;
        return false;
    }
    else {
        return originalFn.apply(this, arguments);
    }
};

Объяснение:

1)

Магистраль слушает hashchange событие и наборы Backbone.history.checkUrl в качестве обратного вызова: https://github.com/jashkenas/backbone/blob/1.1.2/backbone.js#L1414

Backbone.$(window).on('hashchange', this.checkUrl);

2)

Backbone.history.checkUrl проверяет, изменился ли хеш и вызывает Backbone.history.loadUrl

checkUrl: function(e) {
  var current = this.getFragment();
  if (current === this.fragment && this.iframe) {
    current = this.getFragment(this.getHash(this.iframe));
  }
  if (current === this.fragment) return false;
  if (this.iframe) this.navigate(current);
  this.loadUrl();
},

3)

Backbone.history.loadUrl находит первый соответствующий маршрут и вызывает его обратный вызов:

loadUrl: function(fragment) {
  fragment = this.fragment = this.getFragment(fragment);
  return _.any(this.handlers, function(handler) {
    if (handler.route.test(fragment)) {
      handler.callback(fragment);
      return true;
    }
  });
},

Полезное примечание:

Backbone.history.fragment хранит текущий хеш, он установлен в Backbone.history.loadUrl чтобы мы могли получить к нему доступ ПОСЛЕ hashchange событие, но ДО обратного вызова маршрутизатора делают свою работу.

Я думаю, что вы можете взломать Backbone.history.loadUrl ( http://documentcloud.github.com/backbone/docs/backbone.html). Я сделал быструю проверку, этот код проверяет каждый раз, когда вы меняете страницы. Вы захотите добавить код для активации проверки только тогда, когда на самом деле есть причина для этого.

var goingBack = false;
function doCheck() {
  // TODO: add code that checks the app state that we have unsaved data
  return goingBack || window.confirm("Are you sure you want to change pages?");
}

var oldLoad = Backbone.History.prototype.loadUrl;
Backbone.History.prototype.loadUrl = function() {
  if(doCheck()) {
    return oldLoad.apply(this, arguments);
  } else {
    // change hash back
    goingBack = true;
    history.back();
    goingBack = false;
    return true;
  }
}

Вам также придется обрабатывать window.onbeforeunload, потому что пользователь все равно может полностью покинуть страницу.

Начиная с версии 1.2.0 вы можете переопределить метод Router.execute и вернуться false чтобы отменить маршрутизацию, вот так:

execute: function(callback, args, name) {
    if (!changesAreSaved) {
        // tip: .confirm returns false if "cancel" pressed
        return window.confirm("You sure have some unsaved "
          + "work here, you want to abandon it?");
    }

    // this is the default part of "execute" - running the router action
    if (callback)
        callback.apply(this, args);
}

Я занимался этой проблемой некоторое время и нашел решение. Я основал свое решение на этом примере.

Идея состоит в том, чтобы переопределить navigate метод и использование jQuerydeferred объекты ждать в подходящее время для навигации. В моем случае, если пользователь попытался перейти от моего просмотра, который был грязным, диалоговое окно должно было показать, что спросил пользователя, если:

1) Сохраните изменения, затем перейдите 2) Не сохраняйте изменения и перейдите 3) Отмените навигацию и оставайтесь на существующей странице

Ниже мой код для метода навигации в Router:

navigate: function(fragment, trigger) {
    var answer,
          _this = this;

    answer = $.Deferred();
    answer.promise().then(function() {
        return Backbone.Router.prototype.navigate(fragment, trigger);
    });

    if(fragment !== undefined){     
        var splitRoute = fragment.split('/');
        app.currentPatronSection = splitRoute[splitRoute.length - 1];
    }

    if (app.recordChanged) {
        this.showConfirm(function(ans){
            // Clear out the currentView
            app.currentView = undefined;
            answer.resolve();
        }, function(){

        });
        return answer.promise();
    } else {
        return answer.resolve();
    }
    return Backbone.Router.prototype.navigate(fragment, trigger);

},

showConfirm Метод представляет диалог с тремя опциями, которые я перечислил выше. На основании выбора пользователя я сохраняю форму, затем решаю ответ для навигации и т. Д.

Мне звонили по нескольку раз loadUrl Я решил использовать этот метод, который работал для меня.

/**
 * Monkey patches Backbone to prevent a reroute under certain conditions.
 *
 * Solution inspired by: https://stackru.com/a/24535463/317135
 *
 * @param Backbone {Backbone}
 *   Backbone 1.0.0 reference
 * @param disallowRouting {function(): boolean}
 *   Function returning `true` when routing should be disallowed.
 */
export default function permitRouteWhen(Backbone, permitRouting) {
  if (Backbone.VERSION !== '1.0.0') {
    console.error(
      `WARNING: Expected to be hacking Backbone version 1.0.0, but got
      ${Backbone.VERSION} - this could fail.`
    );
  }

  const { checkUrl } = Backbone.history;

  Backbone.history.checkUrl = function(event) {
    if (!permitRouting()) {
      event.preventDefault();
      return;
    }
    return checkUrl.apply(this, arguments);
  }
}

Используйте это так:

import permitRouteWhen from './backbone-permit-route-hack';
permitRouteWhen(window.Backbone, () => confirm('you wanna route?'));
Другие вопросы по тегам