Emberjs and Twitter Bootstrap, make the li active

The {{#linkTo}} helper in EmberJS adds an active class when it links to the current page. Unfortunately, when using Twitter Bootstrap, we’re required to add this active class to the parent li.

There are solutions for this, aka adding two linkTo, and changing the tagName for the first one.

In apparence, this works. However, perhaps you noticed both of the linkTo events are executed.
Indeed, we’re generating a click on the li element. If you have a nested structure, this can lead to weird problems.

For example:

<ul>
  {{#linkTo "Foo" tagName="li" href=false}}
    {{linkTo "Foo"}MyLink{{/linkTo}}

    <ul>
      {{#linkTo "Bar" tagName="li" href=false}}
        {{linkTo "Bar"}}MyLink2{{/linkTo}}
      {{/linkTo}}
    </ul>
  {{/linkTo}}
<ul>

If we click on the “Bar” link, we have three events propagated:

  • The li’s Foo, since we’re nested inside it.
  • The li’s Bar, since we’re also nested inside it.
  • The link’s Bar.

In our case, a click on Bar can actually transition us to Foo, depending of the last event to be called.

One possible solution

Here’s one possible solution. I created the following helper:

App.LiWithActiveClassView = Ember.LinkView.extend({
  tagName: 'li',
  classNameBindings: ['active'],
  attributeBindings: [],
  _invoke: function(event) {}
});

Ember.Handlebars.registerHelper('liWithActiveClass', function(name) {
  var hash, options, params;
  options = [].slice.call(arguments, -1)[0];
  params = [].slice.call(arguments, 1, -1);
  hash = options.hash;
  hash.namedRoute = name;
  hash.currentWhen = hash.currentWhen || name;
  hash.disabledBinding = hash.disabledWhen;
  hash.parameters = {
    context: this,
    options: options,
    params: params
  };
  return Ember.Handlebars.helpers.view.call(this, Hivebench.LiWithActiveClassView, options);
});

You can notice that the call to registerHelper is the same as in emberjs.
And App.LiWithActiveClassView extends directly from Ember.LinkView, which the view used by linkTo.

We change two things there:

  • We set the tagName to a li, like we did previously
  • We override the _invoke method, so no event gets propagated when we click on the link.

That’s all! Call it like this:

<ul>
  {{#liWithActiveClass "Foo"}}
    {{linkTo "Foo"}MyLink{{/linkTo}}

    <ul>
      {{#liWithActiveClass "Bar"}}
        {{linkTo "Bar"}}MyLink2{{/linkTo}}
      {{/linkTo}}
    </ul>
  {{/linkTo}}
<ul>

Your DOM remains the same, and all your events are created and fired accordingly.