Skip to content
This repository

Before Filter / After Filter on Backbone.Controller #299

Closed
wants to merge 1 commit into from

11 participants

Jonathan Soeder Elliott Jeremy Ashkenas Siong Ryan Gasparini Nadav Ivgi Jay Feldblum Jake Dempsey Cedric Dugas khiltd Jaseem Abid
Jonathan Soeder

added before filter, after filter, tests, and documentation for Backbone.Controller

Elliott

That commit is not valid javascript. So I fixed the issue on my repo elliottneilclark@c2f1867

Jeremy Ashkenas
Owner

This looks great for your fork of Backbone, but I think it's too edge-case to be broadly applicable to everyone.

Jeremy Ashkenas jashkenas closed this April 18, 2011
Deleted user
ghost commented July 03, 2011

The content you are editing has changed. Reload the page and try again.

I couldn't disagree more. Before/after filters are a commonly exercised feature of most frameworks, and are a great way to handle login states.

Sending Request…

Attach images by dragging & dropping or selecting them. Octocat-spinner-32 Uploading your images… Unfortunately, we don't support that file type. Try again with a PNG, GIF, or JPG. Yowza, that's a big file. Try again with an image file smaller than 10MB. This browser doesn't support image attachments. We recommend updating to the latest Internet Explorer, Google Chrome, or Firefox. Something went really wrong, and we can't process that image. Try again.

Siong

Can someone reopen this ticket? This seems like a really important feature for most of the frameworks.

Jeremy Ashkenas
Owner

Not yet -- but I'd be glad to discuss it. "Most frameworks" aren't in-browser, stateful libraries ... and in JavaScript, and function can have a "before" or "after" filter by simply wrapping it in another function:

http://documentcloud.github.com/underscore/#wrap

Do you have any specific, real-world use cases where before filters would be helpful in Backbone?

Siong

I actually pull it out as a plugin now: https://github.com/FLOChip/backbone_router_filter

It can be more useful if it is similar as how Rails's before_filter and after_filter work where we can set which methods that the filter methods actually applies on.

But, I guess it might be better to use it as a plugin for now.

Jonathan Soeder

Having built a pretty huge app in which this feature played a role, I agree that it shouldn't be part of the framework. It is easy enough to extend backbone on your own when you need this, especially with underscore

Deleted user
ghost commented July 11, 2011

The content you are editing has changed. Reload the page and try again.

Managing OAuth state the way that Sammy does would be one specific use case.

Sending Request…

Attach images by dragging & dropping or selecting them. Octocat-spinner-32 Uploading your images… Unfortunately, we don't support that file type. Try again with a PNG, GIF, or JPG. Yowza, that's a big file. Try again with an image file smaller than 10MB. This browser doesn't support image attachments. We recommend updating to the latest Internet Explorer, Google Chrome, or Firefox. Something went really wrong, and we can't process that image. Try again.

Elliott

I use it to clean up listeners and to kill requests generated by one view that are no longer being displayed.

Deleted user
ghost commented July 13, 2011

The content you are editing has changed. Reload the page and try again.

And if _.wrap is the approved means of approaching this problem, how exactly should it be done? If I call it in the Router object's initialize, it doesn't actually do anything as Backbone seems to have already picked up the reference to the original, unwrapped function.

Sending Request…

Attach images by dragging & dropping or selecting them. Octocat-spinner-32 Uploading your images… Unfortunately, we don't support that file type. Try again with a PNG, GIF, or JPG. Yowza, that's a big file. Try again with an image file smaller than 10MB. This browser doesn't support image attachments. We recommend updating to the latest Internet Explorer, Google Chrome, or Firefox. Something went really wrong, and we can't process that image. Try again.

Ryan Gasparini
rxgx commented August 18, 2011

Do before and after filters make sense in JavaScript? It's not like we have an notion of a class (let alone abstract and base to extend or implement) to apply annotations before and after a given constructor is called. I think the _.wrap method is for the best, overall, since the trend leads away from constructor functions implemented by car = new Vehicle() to car = Object.create(vehicle).

Deleted user

The content you are editing has changed. Reload the page and try again.

Whether or not it makes sense in JavaScript, Backbone is providing a notion of a class, and using the framework requires that you inherit from these notions, so that seems like kind of a nonsensical argument.

Somebody show me where and when _.wrap() will actually work in a Backbone Router definition and I'll gladly do it. Nothing I've tried has ever worked.

Sending Request…

Attach images by dragging & dropping or selecting them. Octocat-spinner-32 Uploading your images… Unfortunately, we don't support that file type. Try again with a PNG, GIF, or JPG. Yowza, that's a big file. Try again with an image file smaller than 10MB. This browser doesn't support image attachments. We recommend updating to the latest Internet Explorer, Google Chrome, or Firefox. Something went really wrong, and we can't process that image. Try again.

Nadav Ivgi

@nsdpt, Set a wrapper function as var wrapper = function(callback) { /* ... before ... */ callback.apply(this, Array.prototype.slice.call(arguments,1)); /* ... after ... */ } and use to _.wrap(router_callback, wrapper) as your router callbacks. You will have to call Backbone.Router#route() manually from initialize() for that, as you can't use plain callbacks in routes property - only method names as a string.

If you do want to use the routes object, you could also execute something like _.each(['foo', 'bar', 'baz', 'qux'], function(method){ router[method] = _.wrap(wrapper, method); }), where foo/bar/baz/qux are the router methods you want to apply before/after on, router is your router object and wrapper is the function above, allowing you to use the regular 'regex': 'method_name' in the routes property.

Alternatively, you can work with the route:... event as an alternative to after(), and the beforeroute:... event from #494 as an alternative forbefore().

Jay Feldblum

Aspects (such as before filters and after filters) would be highly useful.

Jake Dempsey

For our backbone app we use a small filter module I wrote: https://github.com/angelo0000/backbone_filters. It allows for filters based on routes instead of just a single before/after per Router/Controller. It acts more like a rails before filter in that if it returns false, it stops the execution chain.

Deleted user

The content you are editing has changed. Reload the page and try again.

Sounds perfect, but supporting only versions below 0.5 is probably a dealbreaker for most.

Sending Request…

Attach images by dragging & dropping or selecting them. Octocat-spinner-32 Uploading your images… Unfortunately, we don't support that file type. Try again with a PNG, GIF, or JPG. Yowza, that's a big file. Try again with an image file smaller than 10MB. This browser doesn't support image attachments. We recommend updating to the latest Internet Explorer, Google Chrome, or Firefox. Something went really wrong, and we can't process that image. Try again.

Jake Dempsey

Agreed, we have not upgraded our version of backbone in our app so I added that disclaimer because I'm sure it would need changes to support the latest... maybe I'll get it up to par with the latest version of backbone.

Cedric Dugas

+1 on this, (even if it is old), before filters are very attractive to handle routes states. For example, you want to fade out your old view on a mobile web app. with a before filter you can add this once instead of having it at the beginning of each routes.

Jonathan Soeder

so, I actually disagree with having this handled by the router. I initially made this feature request at a time when I didn't really understand the framework at the level I do now.

Luca.Router = Backbone.Router.extend
  routes:
    "" : "default"

  initialize: (@options)->
    _.extend @, @options

    @routeHandlers = _( @routes ).values()

    # when a route handler is fired, the route:route_name event is triggered by the router
    # unfortunately this doesn't apply to calls to @navigate() so we override Backbone.Router.navigate
    # and trigger an event separately.
    _( @routeHandlers ).each (route_id) =>
      @bind "route:#{ route_id }", ()=>
        @trigger.apply @, ["change:navigation", route_id  ].concat( _( arguments ).flatten() )

  #### Router Functions

  # Intercept calls to Backbone.Router.navigate so that we can at least
  # build a path from the route, even if we don't trigger the route handler
  navigate: (route, triggerRoute=false)->
    Backbone.Router.prototype.navigate.apply @, arguments
    @buildPathFrom( Backbone.history.getFragment() )

  # given a url fragment, construct an argument chain similar to what would be
  # emitted from a normal route:#{ name } event that gets triggered
  # when a route is actually fired.  This is used to trap route changes that happen
  # through calls to @navigate()
  buildPathFrom: (matchedRoute)->
    _(@routes).each (route_id, route)=>
      regex = @_routeToRegExp(route)
      if regex.test(matchedRoute)
        args = @_extractParameters(regex, matchedRoute)
        @trigger.apply @, ["change:navigation", route_id].concat( args )

What I do now is customize my router so that it emits route events every time somebody makes a call to router.navigate ( regardless if the trigger route param is set to true. ) Which, IMO, is an improvement over the stock backbone behavior

I almost always have a single application object ( a Luca.Viewport ) which all other views can bind to, or access to get global state. The application listens to change:navigation events, and then updates its internal state machine, so all interested parties can respond appropriately.

This eliminates the needs for before / after filters in the router, and moves the concern to where I think it is more appropriate, which is in your views.

khiltd
khiltd commented May 30, 2012

I understand the framework just fine, and I continue to find that this puritanical argument has very little going for it. Given the fact that the router is the only component of Backbone I haven't completely replaced at this point, baddabing...

route: (route, name, callback) -> 
    super
    routeHandler = Backbone.history.handlers[0]
    routeHandler.callback = _.wrap routeHandler.callback, (originalCallback, fragment) =>
      try
        @trigger("myapp:routing", route, name, @)
        originalCallback.apply(@, _.toArray(arguments).slice(1))
        @trigger("myapp:routed", route, name, @)
      catch e
        console.error("Routing aborted: #{e}")

Works with the declarative syntax and lets routes object to running if desired.

Cedric Dugas

Well I see the point, it would work either way fo me but I still think that events or filters, it would be a great addition to the router

Jaseem Abid

I'd vote for the feature. I came across this problem and opened another ticket #2947 (sorry for the duplicate). Simple use cases might be auth, making sure a parent view is rendered before a child view rendered by a deep router etc. Also, looks like the effort to add this feature is not much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Mar 30, 2011
Jonathan Soeder added before filter, after filter, tests, and documentation for Backb…
…one.Controller
ba1b329
This page is out of date. Refresh to see the latest.
16  backbone.js
@@ -677,10 +677,26 @@
677 677
       if (!_.isRegExp(route)) route = this._routeToRegExp(route);
678 678
       Backbone.history.route(route, _.bind(function(fragment) {
679 679
         var args = this._extractParameters(route, fragment);
  680
+
  681
+        if( _(this.before).isFunction() ){
  682
+          this.before.apply(this, args); 
  683
+        }
  684
+
680 685
         callback.apply(this, args);
  686
+
  687
+        if( _(this.after).isFunction() ){
  688
+          this.after.apply(this, args); 
  689
+        }
  690
+
681 691
         this.trigger.apply(this, ['route:' + name].concat(args));
682 692
       }, this));
683 693
     },
  694
+    
  695
+    // You can define a function as a `before` filter to be run before each route handler
  696
+    before: function() { }
  697
+    
  698
+    // You can define a function as a `after` filter to be run after each route handler
  699
+    after: function() { }
684 700
 
685 701
     // Simple proxy to `Backbone.history` to save a fragment into the history,
686 702
     // without triggering routes.
24  test/controller.js
@@ -12,6 +12,14 @@ $(document).ready(function() {
12 12
       ":entity?*args":              "query",
13 13
       "*anything":                  "anything"
14 14
     },
  15
+    
  16
+    before: function(query) {
  17
+      this.before_tag = query;
  18
+    },
  19
+
  20
+    after: function(query) {
  21
+      this.after_tag = query; 
  22
+    },
15 23
 
16 24
     initialize : function(options) {
17 25
       this.testing = options.testing;
@@ -52,6 +60,22 @@ $(document).ready(function() {
52 60
     equals(controller.testing, 101);
53 61
   });
54 62
 
  63
+  asyncTest("Controller: routes (before filter)", 1, function() {
  64
+    window.location.hash = 'search/news';
  65
+    setTimeout(function() {
  66
+      equals(controller.before_tag, 'news');
  67
+      start();
  68
+    }, 10);
  69
+  });
  70
+
  71
+  asyncTest("Controller: routes (after filter)", 1, function() {
  72
+    window.location.hash = 'search/news';
  73
+    setTimeout(function() {
  74
+      equals(controller.after_tag, 'news');
  75
+      start();
  76
+    }, 10);
  77
+  });
  78
+
55 79
   asyncTest("Controller: routes (simple)", 2, function() {
56 80
     window.location.hash = 'search/news';
57 81
     setTimeout(function() {
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.