Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

Я бы не стал взломать 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 тем, что имеет смысл, и добавить любой другой логин, который у вас есть об обработке ссылок.

Ответ 2

Я бы также взломал 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, но ДО того, что обратные вызовы маршрутизатора выполняют свою работу.

Ответ 3

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

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, так как пользователь может полностью покинуть страницу.

Ответ 4

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

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

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 представляет диалог с тремя параметрами, перечисленными выше. Основываясь на выборе пользователя, я сохраняю форму, затем решаю ответ для навигации и т.д.

Ответ 5

Начиная с версии 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);
}

Ответ 6

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

/**
 * Monkey patches Backbone to prevent a reroute under certain conditions.
 *
 * Solution inspired by: /info/437142/prevent-navigating-to-another-view-if-contents-are-unsaved/1931472#1931472
 *
 * @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?'));