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 deter开发者_如何学Cmine the current route at other times, in between changes.
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()] );
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.
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.
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
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
};
}
}
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.
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"
精彩评论