Запретить переход к другому представлению, если содержимое не сохранено
У нас есть приложение 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
метод и использование 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
Метод представляет диалог с тремя опциями, которые я перечислил выше. На основании выбора пользователя я сохраняю форму, затем решаю ответ для навигации и т. Д.
Мне звонили по нескольку раз 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?'));