Highlighting current navigation state in Backbone.js application
Asked Answered
L

4

8

I want to highlight the current navigation state. Like if the hashchange is #home, I want to style the 'Home' menu link differently and similarly other links.

Backbone.js fires individual events like route:home,... route:some-other when the #home and other links are clicked. I could not see any common event that will be fired for every hashchange. With this I m required to write the state highlight logic by binding to all the route events, which I think is not good solution.

So, I've overridden Backbone.Router.route in my router sub class/object, like

// override backbone' Router.route method to publish 
// common 'route_change' event for any hash change
route : function(route, name, callback) {
    Backbone.history || (Backbone.history = new Backbone.History);
    if (!_.isRegExp(route)) route = this._routeToRegExp(route);
    Backbone.history.route(route, _.bind(function(fragment) {
        var args = this._extractParameters(route, fragment);
        callback.apply(this, args);
        this.trigger.apply(this, ['route:' + name].concat(args));

        // ADDED BY: ManiKanta G
        // name: route method
        // fragment: route path
        // args: any additional args
        this.trigger.apply(this, ['route_change'].concat(name, fragment, args));
    }, this));
}

This will publish a common route_change event for every hashchange and passing the name, fragment, and other args using which I m highlighting the state all in a single place.

My question is do I have to override the Backbone method like this or is there any build in mechanism I can use here. If not, I would like to see similar behaviour in Backbone.js

Edit: sample program

Router = Backbone.Router.extend({
    routes : {
        '': 'root', 
        'home': 'home',
        'about':'about'
    },

    // app routing methods
    root: function () { console.log('root route');  },
    home: function () { console.log('home route');  },
    about: function () { console.log('about route'); }

});

Router.bind('all', function () {
    console.log('all route...');
});

router = new Router();

and, navigating using the above router:

router.navigate('home', true);

output: home route

Update on why the above program is not working:

we should bind for all event on Router instance, but not on the Router itself - so, changing the Router.bind('all', ... to router.bind('all', ...) will make the above program work

Looper answered 22/8, 2011 at 7:34 Comment(9)
In backbone 0.5.x you can bind all event to router and the first argument pass to your handler will be routeFrisch
@Frisch - post that as an answer instead of a comment, so you can get credit for it.Aldarcie
@Frisch I m not sure why I missed to see your comment. I've tried to bind 'all' to router, but it seems not working that way. I've gone through the source code too and I could not find the Router triggering any 'all' event either. I've added simple snippet to demonstrate that. Can you add some code (as an answer please)Looper
Yes, of course! Here is exemple on jsfiddleFrisch
router doesn't trigger all it is incumbent upon Backbone.EventsFrisch
Thanks. It didn't strike me that I m binding on Router instead of its instance router. With this, I m getting the route change event, like route:home, only for known routes (as triggering will be done only for them). With my current little fix, I could handle unknown links (404) too, which I require (but missed to mention that in OP). Please add you comment/program as an answer. I'll accept that.Looper
Sorry i've forget to tell about thatFrisch
If you want to bind to 'all', check out the answer given here: #6489641. Basically just define a *splat route.Bisector
@ShaChris23 currently I m doing that only. ThanksLooper
F
7

In backbone 0.5.x you can bind all event to router instance and the first argument pass to your handler will be route

Here is exemple on jsfiddle, reproduced here:

var dummy = Backbone.Router.extend({
    defaultPage: 'messages',

    routes: {
        '': 'index',
        'index': 'index',
        'mailbox': 'mailbox'
    },

    index: function() {
        // code here
    },

    mailbox: function() {
        // code here
    }
});

var router = new dummy();

router.bind('all', function(route) {
    document.write('triggered: ' + route + '<br/>');
});

router.navigate('index', true);
router.navigate('mailbox', true);
Frisch answered 10/9, 2011 at 6:28 Comment(3)
just as reference for others: binding to 'all' event causes the handler function to receive the route change event, like route:home, but only for known routes (as triggering will be done only for them)Looper
question, this gets triggered after everything has rendered?Ethnic
Old question, but anyone there? I have a query...I have done the above part successfully, but it fires 2 times in my all navigation...first one gives correct info, second blank always...any idea???Karaite
H
2

Here 's a live example from one of my apps:

routes.bind('all ', function(route, section) {
    var $el;
    route = route.replace('route: ', '');

    $el = $('#nav - ' + route);

    // If current route is highlighted, we're done.
    if ($el.hasClass('selected')) {
        return;
    } else {
        // Unhighlight active tab.
        $('#menu li.selected').removeClass('selected');
        // Highlight active page tab.
        $el.addClass('selected');
    }
});
Haldeman answered 18/9, 2011 at 20:13 Comment(4)
I also has similar kind of logic for highlighting (also to highlight the main menu along with the sub menu too). But the problem with all event binding is that it'll only receives for known routes. I want to know about all the route changes (simply put, every hash change). This is useful for displaying some 404 error.Looper
For "404" route detection I add this as my last route: "*path": "page404Error"Haldeman
Old question, but anyone there? I have a query...I have done the above part successfully, but it fires 2 times in my all navigation...first one gives correct info, second blank always...any idea???Karaite
Sounds like you have code elsewhere that's also triggering it. Add an alert inside the above code and if the alert triggers twice you know that it's the cause, if not, there's code elsewhere.Haldeman
T
0

Here's what I'm doing:

@router.bind 'all', (route) ->
  # this triggers twice for every route; ignore the second time
  return if route == "route"

  # ex: route="route:home", name="home", href="#home"
  name = route.replace('route:', '')

  # remove the active class on all of the current nav links
  $("#menu li.active").removeClass('active')

  # add it back to the link that has an href that ends in `name`
  $("#menu a[href$='#{name}']").parent().addClass('active')
Tedium answered 23/3, 2015 at 16:55 Comment(0)
F
-1

I faced the problem that the first time a page is loaded, the menu wasn't highlighted as the event came before the binding was initiated. i fixed this by using the history:

$('#menu li.'+Backbone.history.fragment).addClass('active');
Farrel answered 8/4, 2012 at 3:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.