Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone.js: get current route

Tags:

backbone.js

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