如果内容未保存,则阻止导航到另一个视图

时间:2012-03-26 13:25:27

标签: javascript backbone.js

我们有一个backbone.js应用程序,可以向用户显示多个表单。我们想要的是非常简单:如果用户转到另一页而不保存填写的表单,我们想要显示一个确认对话框。

在经典表单中,这很容易,只需在jQuerysh中实现window.onbeforeunload(或$(window).on('beforeunload'))。但骨干应用程序通常只有一个视图。我尝试使用onHashChange,但在该回调中返回false并不会阻止Backbone继续进入另一个视图。

赞赏指针。搜索互联网并没有找到任何有效的回复。

6 个答案:

答案 0 :(得分: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替换为有意义的内容,并添加有关处理链接的其他任何登录信息。

答案 1 :(得分:6)

我也会破解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)

Backbone收听 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事件之后访问它,但在路由器回调完成之前就可以访问它。

答案 2 :(得分:5)

我认为你可以破解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,因为用户可能仍然完全离开页面。

答案 3 :(得分:1)

我已经处理了这个问题一段时间了,我想出了一个解决方案。我的解决方案基于this示例。

我们的想法是覆盖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方法使用上面列出的三个选项显示对话框。根据用户的选择,我保存表格,然后解决导航的答案等。

答案 4 :(得分:1)

从版本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);
}

答案 5 :(得分:0)

我使用Dénes's solution在每次重新路由时多次调用loadUrl,所以我决定尝试这种方法,这对我很有用。

/**
 * Monkey patches Backbone to prevent a reroute under certain conditions.
 *
 * Solution inspired by: https://stackoverflow.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?'));