开发者

Backbone.js — Call method before/after a route is fired

开发者 https://www.devze.com 2023-04-04 04:27 出处:网络
I want 开发者_如何学Ca setup/teardown method to be called before and after a route is fired in my Backbone.js router, respectively. Has anyone created an elegant way of doing this? _.wrap is not a sol

I want 开发者_如何学Ca setup/teardown method to be called before and after a route is fired in my Backbone.js router, respectively. Has anyone created an elegant way of doing this?


_.wrap is not a solution, if You have for example 20 routes you have to wrap them all.

But you can do this with metaprogramming

class Backbone.FlexRouter extends Backbone.Router
  route: (route, name, handler) ->
    super route, name, ->
      @trigger "route:before"
      handler()
      @trigger "route:after"

UPD: I believe in JS it should be something like this (but I didn't tested it)

var rp = Backbone.Router.prototype
rp.routeWithoutEvents = rp.route
rp.route = function(route, name, handler){
  var that = this
  this.routeWithoutEvents(route, name, function(){
    that.trigger("route:before")
    handler()
    that.trigger("route:after")
  })
}


Have you considered _.wrap?


Here is the simple one, overriding the Backbone.Router itself

(function () {
    _.extend(Backbone.Router.prototype, Backbone.Events, {        
        route: function (route, name, callback) {           
            if (!_.isRegExp(route)) route = this._routeToRegExp(route);
            if (!callback) callback = this[name];
            Backbone.history.route(route, _.bind(function (fragment) {               
                var args = this._extractParameters(route, fragment);                
                if (this.before && _.isFunction(this.before)) {
                    this.before(fragment);
                }                
                callback && callback.apply(this, args);
                this.trigger.apply(this, ['route:' + name].concat(args));
                if (this.after && _.isFunction(this.after)) {
                    this.after(fragment);
                }
                Backbone.history.trigger('route', this, name, args);
            }, this));
            return this;
        }
    });
}).call(this);

Focus on the lines

if (this.before && _.isFunction(this.before)) {
    this.before(fragment);
}

AND

if (this.after && _.isFunction(this.after)) {
    this.after(fragment);
}

You can modify the lines according to your needs

And here is the client code using the new Backbone.Router class

var appRouter = Backbone.Router.extend({

routes: {},
before: function(){
   //your code here
   return true;
}

});


Alexey's answer is almost right, but there are a few subtle things that are missing.

class ApplicationRouter extends Backbone.Router

  route: (route, name, callback = null) ->
    callback = @[name] if ! callback
    super route, name, ->
      @trigger 'route:before'
      result = callback && callback.apply(@, arguments)
      @trigger 'route:after'
      return result


This plugin does what you want. It works with 0.5.3. I'm not certain if it works with 0.9.1 yet or not.

https://github.com/angelo0000/backbone_filters


I came across this problem earlier and I thought I would share my solution for inserting "middleware" into the Backbone routing flow. The goal was to reroute users to various flows depending on some condition, e.g., feature flags, session handling, etc..

Backbone.ProtectedRouter = Backbone.Router.extend({
  /*
  * Subclass of Router that monkeypatches route in order to protect certain
  * routes.
  *
  * If you want to add a protected route, add it to the protectedRoutes
  * object in this form:
  *   route: { method: fn, assertion: fn, args: [args..] }
  *
  * * method => the method to call if the assertion is true (the route should
  * be protected in the given scenario)
  *
  * * assertion => the function that decides whether or not the route
  * should be rendered
  *
  * * args => the arguments to be passed to method
  */
  route: function(route, name, handler) {
    var _this = this;
    Backbone.Router.prototype.route(route, name, function(){
      var boundHandler = _.bind(handler, _this),
          attrs, method, args, dfd;

      attrs = _.has(_this.protectedRoutes, route) ? _this.protectedRoutes[route] : null;

      if ( attrs && !attrs.assertion() ) {
        // In this scenario my flows all return Deferreds
        // you can make this event based as well.
        dfd = _this[attrs.method].apply(_this, attrs.args.concat([route]));
        dfd.then(boundHandler);
      } else
        boundHandler.apply(_this, arguments);
    });
  }
});

From there you can simply extend the Backbone.ProtectedRouter with a protectedRoutes hash as so:

var router = Backbone.ProtectedRouter.extend({
    protectedRoutes: {
      'home': { 
        assertion: function() { return is_logged_in; },
        method: 'renderLogin',
        args: ['some_arg']
      }
    },
    routes: {
      'home': 'renderHome'
    },
    ...
});

In this scenario, if a request is made for the home route and is_logged_in is false, the renderLogin method is invoked and passed 'some_arg'. After the flow, renderLogin would return a resolved Deferred that causes the original handler (renderHome) to be invoked.

I hope this helps. I'm very open to suggestions as well! :)


I came across this need recently (to check user is authenticated). Unfortunately Backbone doesn't give us a before/after event, so you will need to overwrite or extend the Router.route. Doesn't feel very clean since you have to copy from source code and edit there, but is the only way I found. Below Backbone default code (1.0.0) and marked my custom code:

  Backbone.Router.prototype.route = function(route, name, callback) {
    if (!_.isRegExp(route)) route = this._routeToRegExp(route);
    if (_.isFunction(name)) {
      callback = name;
      name = '';
    }
    if (!callback) callback = this[name];
    // here my custom code
    callback = _.wrap(callback, _.bind(function(cb) {
      if (name == 'login' || sessionModel.authenticated()) {
        _.bind(cb, this)();
      } else {
        this.navigate('login', {trigger: true});
      }
    }, this));
    // finish my custom code
    var router = this;
    Backbone.history.route(route, function(fragment) {
      var args = router._extractParameters(route, fragment);
      callback && callback.apply(router, args);
      router.trigger.apply(router, ['route:' + name].concat(args));
      router.trigger('route', name, args);
      Backbone.history.trigger('route', router, name, args);
    });
    return this;
  };

Notice _.wrap and _.bind so this is the one that you would expect when using the router. Otherwise I was getting a "this is undefined" error.


ethnagnawl and Alexey are both correct; _.wrap is the right solution but if you have a bunch of routes and write them in the normal backbone fashion it'll be a pain. I realized you can do this:

var Pages = {}
Pages.loginPage = function(){ ... }
Pages.mainPage = function(){ ... }

Instead of defining your route handlers directly in Router.extend, load them into an object and then do this:

_.map(Pages,function(func,name){                                                     
    Pages[name] = _.wrap(func,function(funky){                                       
        // Save original arguments                                                   
        var args = Array.prototype.slice.call(arguments,1);                          
        // Do stuff before the route                                                               
        funky(args);               
        // Do stuff after the route                                                  
    });                                                                              
});                                    

This also makes it pretty easy to check for the function name if you need to treat a subset of them differently or something. Then, because it's just an object, you can do this:

var myRouter = Backbone.Router.extend({
    routes: ... /* as usual */
    }).extend(Pages);       

And you're done.

One nice advantage of this is it involves no messing with the Backbone prototypes, so even if a version update changes something it won't bite you.


after doing lot more manipulations. i came to a solution that i have given below...... Here ur original root function ...

route: function(route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; Backbone.history.route(route, function(fragment) { var args = router._extractParameters(route, fragment); callback && callback.apply(router, args); router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); Backbone.history.trigger('route', router, name, args); }); return this; }

Now look at this code & change the "route" function to your original Backbone.js...

  route: function(route, name, callback) {

  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
  if (_.isFunction(name)) {
    callback = name;
    name = '';
  }

  if (!callback) callback = this[name];
  var router = this;


  Backbone.history.route(route, function(fragment) {    
                   // takes matched route & fragment as like 'route1'
  var args = router._extractParameters(route, fragment); 
                  // extracts arguments if exists

 // here yours self invoking function or other function starts....

(function(){
                             // do something
 if ( true )                 // condition satisfies then route to the given Route
    {
     callback && callback.apply(router, args); 
    }
  else{
     name='route2';          // change name of route
     window.location.hash = 'route2';
     callback= function(){
                             // optional callback if u want
      }
     callback && callback.apply(router, args);  // route to ur custome Route
    }

 })();


  });
  return this;
}

----- Thank You -------- Love 2 write Dirty Codes ! @xy....


Here is a JavaScript version that works with what I've got;

var rp = Backbone.Router.prototype;
rp.routeWithoutEvents = rp.route;
rp.route = function(route, name, callback) {
    if (!callback) callback = this[name];
    this.routeWithoutEvents(route, name, function() {
        this.before.apply(this);
        callback.apply(this,arguments);
        this.after.apply(this);
    });
};

It's based on Alexey Petrushin's and Jonathan Tran's solutions.


I could not find an easy way to intercept the routing event before the route handler is called.

My solution is to extend the Router component, adding a registerBeforeRouting method and editing the route method (I took it from the Backbone 1.0 and it worked, YMMV with different Backbone versions).

Before the router is created:

var rp = Backbone.Router.prototype;

rp.registerBeforeRouting = function (callback) {
    this._beforeRoutingCallback = callback;
};

rp.route = function (route, name, callback) {
  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
  if (_.isFunction(name)) {
    callback = name;
    name = '';
  }
  if (!callback) callback = this[name];
  var router = this;
  Backbone.history.route(route, function(fragment) {
    var args = router._extractParameters(route, fragment);
            // Edit starts here
    // This will trigger the callback previously set
    if (typeof router._beforeRoutingCallback === 'function') {
        router._beforeRoutingCallback();
    }
            // Edit stops here.

    callback && callback.apply(router, args);
    router.trigger.apply(router, ['route:' + name].concat(args));
    router.trigger('route', name, args);
    Backbone.history.trigger('route', router, name, args);
  });
  return this;
}

Then, during router initialization:

this.registerBeforeRouting(function() {
   console.log("Hello world");
});


I tried the aforementioned approaches, and they somehow just didn't work for me (probably for my lack of in depth understanding of neither backbone, nor javascript in general). I did manage to do the trick in some other manner, if that's of any interest to anyone out there:

What I actually end up doing was simply extending the View and overriding the render function just one time.

MyApp.BindedView = Backbone.View.extend({
    _realRender : null,
    initialize : function(){

        //validating user is logged in:
        if(Backbone.history.fragment != 'login' && !myUser.authenticated())
        {
            console.log('not authorized, redirecting');
            var self = this;
            this._realRender = this.render;
            this.render = function(route,name,callback){
                appRouter.navigate('login');
                self.render = self._realRender;
            }
            return;
        }

        this.delegateEvents();
    }
});


The execute method was added to be overridden for this purpose. See this example extracted from the backbonejs homepage:

var Router = Backbone.Router.extend({
  execute: function(callback, args, name) {
    if (!loggedIn) {
      goToLogin();
      return false;
    }
    args.push(parseQueryString(args.pop()));
    if (callback) callback.apply(this, args);
  }
});
0

精彩评论

暂无评论...
验证码 换一张
取 消