ChatGPT解决这个技术问题 Extra ChatGPT

Backbone.js: get current route

Using Backbone, is it possible for me to get the name of the current route? I know how to bind to route change events, but I'd like to be able to determine the current route at other times, in between changes.

By "name" do you mean the function to which that route binds or just what the current URL or hash portion of the URL is?
Oh, yes, I should have been more specific. I would like to know the name of the function. (I know how to get the hash portion of the URL by doing location.hash or Backbone.history.fragment.)

R
Robert

If you have instantiated a Router in your application, the following line returns the current fragment:

Backbone.history.getFragment();

From the Backbone.js documentation:

" [...] History serves as a global router (per frame) to handle hashchange events or pushState, match the appropriate route, and trigger callbacks. You shouldn't ever have to create one of these yourself — you should use the reference to Backbone.history that will be created for you automatically if you make use of Routers with routes. [...]"

If you need the name of the function bound to that fragment, you can make something like this inside the scope of your Router:

alert( this.routes[Backbone.history.getFragment()] );

Or like this from outside your router:

alert( myRouter.routes[Backbone.history.getFragment()] );

Thanks, that's great. I've never seem any mention of Backbone.history.fragment in the document. Is this a new addition, or just undocumented?
I think this is undocumented. Actually, I don't remember where I saw it for the first time, some blog or perhaps the backbone source code.
FYI, this doesn't work with routes like 'foo/:id' or 'bar*params'
Better use Backbone.history.getFragment(), because Backbone.history.fragment is a private hidden property.
I have corrected the answer following @Vad suggestion. I am not using Backbone anymore but his comment sounds right to me. (The previous answer was: Backbone.history.fragment instead of Backbone.history.getFragment() )
C
Community

Robert's answer is interesting, but sadly it will only work if the hash is exactly as defined in the route. If you for example have a route for user(/:uid) it won't be matched if the Backbone.history.fragment is either "user" or "user/1" (both which are the two most obvious use cases for such route). In other words, it'll only find the appropriate callback name if the hash is exactly "user(/:uid)" (highly unlikely).

Since i needed this functionality i extended the Backbone.Router with a current-function that reuses some of the code the History and Router object use to match the current fragment against the defined Routes for triggering the appropriate callback. For my use case, it takes the optional parameter route, which if set to anything truthful will return the corresponding function name defined for the route. Otherwise it'll return the current hash-fragment from Backbone.History.fragment.

You can add the code to your existing Extend where you initialize and setup the Backbone router.

var Router = new Backbone.Router.extend({

    // Pretty basic stuff
    routes : {
        "home" : "home",
        "user(:/uid)" : "user",
        "test" : "completelyDifferent"
    },

    home : function() {
        // Home route
    },

    user : function(uid) {
        // User route
    },

    // Gets the current route callback function name
    // or current hash fragment
    current : function(route){
        if(route && Backbone.History.started) {
            var Router = this,
                // Get current fragment from Backbone.History
                fragment = Backbone.history.fragment,
                // Get current object of routes and convert to array-pairs
                routes = _.pairs(Router.routes);

            // Loop through array pairs and return
            // array on first truthful match.
            var matched = _.find(routes, function(handler) {
                var route = handler[0];

                // Convert the route to RegExp using the 
                // Backbone Router's internal convert
                // function (if it already isn't a RegExp)
                route = _.isRegExp(route) ? route :  Router._routeToRegExp(route);

                // Test the regexp against the current fragment
                return route.test(fragment);
            });

            // Returns callback name or false if 
            // no matches are found
            return matched ? matched[1] : false;
        } else {
            // Just return current hash fragment in History
            return Backbone.history.fragment
        }
    }
});

// Example uses:
// Location: /home
// console.log(Router.current()) // Outputs 'home'
// Location: /user/1
// console.log(Router.current(true)) // Outputs 'user'
// Location: /user/2
// console.log(Router.current()) // Outputs 'user/2'
// Location: /test
// console.log(Router.current(true)) // Outputs 'completelyDifferent'

I'm sure some improvements could be made, but this is a good way to get you started. Also, it's easy to create this functionality without extending the Route-object. I did this because it was the most convenient way for my set-up.

I haven't tested this fully yet, so please let me know if anything goes south.

UPDATE 04/25/2013

I did some changes to the function, so instead of returning either the hash or route callback name, i return an object with fragment, params and route so you can access all the data from the current route, much like you would from the route-event.

You can see the changes below:

current : function() {
    var Router = this,
        fragment = Backbone.history.fragment,
        routes = _.pairs(Router.routes),
        route = null, params = null, matched;

    matched = _.find(routes, function(handler) {
        route = _.isRegExp(handler[0]) ? handler[0] : Router._routeToRegExp(handler[0]);
        return route.test(fragment);
    });

    if(matched) {
        // NEW: Extracts the params using the internal
        // function _extractParameters 
        params = Router._extractParameters(route, fragment);
        route = matched[1];
    }

    return {
        route : route,
        fragment : fragment,
        params : params
    };
}

See previous code for further comments and explanations, they look mostly the same.


Was about to write this myself, but Googled for the solution first. Glad that I did. Yours does exactly what I wanted, thanks!
Here's a slightly more verbose version that I believe is easier to understand on first glance.
Wow, thanks for this! You just got an acknowledgement in our codebase. ;P We tweaked it slightly for Marionette. By the way, as a shortcut, you can set the third parameter (sets the context) of the _.find function as this, thus eliminating the need for the Router variable (@DanAbramov did this already).
@Mark These are good news. But how can I use this feature in marionette? There is nothing about it in the docs.
@darksoulsong If you're using Marionette.AppRouter, extend your router with the function above, but substitute Router.appRoutes for Router.routes.
y
yoshi

To get the calling route (or url) from the called route handler, you can get it by checking

Backbone.history.location.href  ... the full url
Backbone.history.location.search  ... query string starting from ?

I got here in the search of this answer so I guess I should leave what I have found.


n
n0nick

If you use the root setting for the Router, you can also include it to get the 'real' fragment.

(Backbone.history.options.root || "") + "/" + Backbone.history.fragment

C
Community

Here's a tad more verbose (or, depending on your taste, more readable) version of Simon's answer:

current: function () {
  var fragment = Backbone.history.fragment,
      routes = _.pairs(this.routes),
      route,
      name,
      found;

  found = _.find(routes, function (namedRoute) {
    route = namedRoute[0];
    name = namedRoute[1];

    if (!_.isRegExp(route)) {
      route = this._routeToRegExp(route);
    }

    return route.test(fragment);
  }, this);

  if (found) {
    return {
      name: name,
      params: this._extractParameters(route, fragment),
      fragment: fragment
    };
  }
}

B
Brian Genisio

If you look at the source for the Router, you'll see that when the router triggers the event saying that something changes, it passes the name with it as "route:name".

http://documentcloud.github.com/backbone/docs/backbone.html#section-84

You can always hook the "route" event on the router and store it to get the current route.


O.K., I guess this is a "built it yourself" sort of thing.
Yeah, but you won't get route event if trigger isn't true (recommended & default).
Link no longer works.
D
Dave Aaron Smith
router = new Backbone.Router.extend({
  routes: { "stuff/:id" : "stuff" },
  stuff: function(id) {}
});

router.on('route', function(route) {
  this.current = route;
});

Now if you navigate to /stuff/1, router.current will be "stuff"