user606521
user606521

Reputation: 15434

redirect route to another

I am trying to redirect route to another before route callback is executed. I have following piece of code:

console.log("file loaded");
(function (History) {
    var _navigate = History.prototype.navigate;

    _.extend(History.prototype, {
        navigate: function (fragment, opts) {
            alert("adad");
            return _navigate.call(this, fragment, opts);
        }
    });
})(Backbone.History);

This code wraps Backbone.History.navigate method, and showing alert on the method call. But this alert never shows up when I am changing routes.

The console.log line is just to be sure that file has been loaded after backbone.js.

What is wrong with this code?

Upvotes: 6

Views: 5692

Answers (1)

mu is too short
mu is too short

Reputation: 434665

I think you're overriding the wrong thing: that navigate isn't used the way you think it is.

Let us look at part of Backbone.history.start:

// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start: function(options) {
  //...
  if (this._hasPushState) {
    Backbone.$(window).on('popstate', this.checkUrl);
  } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
    Backbone.$(window).on('hashchange', this.checkUrl);
  } else if (this._wantsHashChange) {
    this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
  }

You'll see that all the various ways of handling routing in Backbone go through checkUrl rather than navigate. The checkUrl method does a bit of busy work and calls loadUrl; one part of the busy work is this:

  if (this.iframe) this.navigate(current);

so navigate will be called but only when an <iframe> is being used to simulate hashchange and popstate events and AFAIK that only happens when you're using an older version of IE.

Back to the usual path through the code. We've seen that checkUrl does some busy work and calls loadUrl so what does that do? loadUrl does this:

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

If you look at the route method in History and the route method in Route you'll see that handler.callback is what calls the route handler from one of your routers and triggers the routing event.

The navigate method that you're replacing is pretty much only used by Router's navigate:

navigate: function(fragment, options) {
  Backbone.history.navigate(fragment, options);
  return this;
}

If you want to redirect before the route handler is called, you could replace loadUrl with something like this:

(function(History) {
    var _loadUrl = History.prototype.loadUrl;
    _.extend(History.prototype, {
        loadUrl: function() {
            var args = [].slice.apply(arguments);
            args[0] = this.getFragment(args[0]);
            // If args[0] is the fragment that you want to
            // redirect then replace it here or do whatever
            // needs to be done.
            return _loadUrl.apply(this, args);
        }
    });
})(Backbone.History);

Demo: http://jsfiddle.net/ambiguous/e4KYK/

Overall I think you'd be better off handling the redirect in a normal route handler: when the offending route is called, just call navigate on whatever router is handy.


Keep in mind that Backbone.History isn't documented beyond the start method so everything here is subject to change.

Upvotes: 2

Related Questions