Nick Ragaz
Nick Ragaz

Reputation: 1352

How do I bind to the active class of a link using the new Ember router?

I'm using Twitter Bootstrap for navigation in my Ember.js app. Bootstrap uses an active class on the li tag that wraps navigation links, rather than setting the active class on the link itself.

Ember.js's new linkTo helper will set an active class on the link but (as far as I can see) doesn't offer any to hook on to that property.

Right now, I'm using this ugly approach:

{{#linkTo "inbox" tagName="li"}}
  <a {{bindAttr href="view.href"}}>Inbox</a>
{{/linkTo}}

This will output:

<li class="active" href="/inbox"><a href="/inbox">Inbox</a></li>

Which is what I want, but is not valid HTML.

I also tried binding to the generated LinkView's active property from the parent view, but if you do that, the parent view will be rendered twice before it is inserted which triggers an error.

Apart from manually recreating the logic used internally by the linkTo helper to assign the active class to the link, is there a better way to achieve this effect?

Upvotes: 22

Views: 12629

Answers (9)

Adam Klein
Adam Klein

Reputation: 91

Well I took what @alexspeller great idea and converted it to ember-cli:

app/components/link-li.js

export default Em.Component.extend({
    tagName: 'li',
    classNameBindings: ['active'],
    active: function() {
        return this.get('childViews').anyBy('active');
    }.property('[email protected]')
});

In my navbar I have:

{{#link-li}}
    {{#link-to "squares.index"}}Squares{{/link-to}}
{{/link-li}}
{{#link-li}}
    {{#link-to "games.index"}}Games{{/link-to}}
{{/link-li}}
{{#link-li}}
    {{#link-to "about"}}About{{/link-to}}
{{/link-li}}

Upvotes: 9

Purrell
Purrell

Reputation: 12861

Current answers at time of writing are dated. In later versions of Ember if you are using {{link-to}} it automatically sets 'active' class on the <a> element when the current route matches the target link.

So just write your css with the expectation that the <a> will have active and it should do this out of the box.

Lucky that feature is added. All of the stuff here which was required to solve this "problem" prior is pretty ridiculous.

Upvotes: 1

alexspeller
alexspeller

Reputation: 788

I have just written a component to make this a bit nicer:

App.LinkLiComponent = Em.Component.extend({
  tagName: 'li',
  classNameBindings: ['active'],
  active: function() {
    return this.get('childViews').anyBy('active');
  }.property('[email protected]')
});

Em.Handlebars.helper('link-li', App.LinkLiComponent);

Usage:

{{#link-li}}
  {{#link-to "someRoute"}}Click Me{{/link-to}}
{{/link-li}}

Upvotes: 5

Andy Jarrett
Andy Jarrett

Reputation: 873

You can skip extending a view and use the following.

{{#linkTo "index" tagName="li"}}<a>Homes</a>{{/linkTo}}

Even without a href Ember.JS will still know how to hook on to the LI elements.

Upvotes: 1

pjlammertyn
pjlammertyn

Reputation: 989

You can also use nested link-to's:

{{#link-to "ccprPracticeSession.info" controller.controllers.ccprPatient.content content tagName='li' href=false eventName='dummy'}}
  {{#link-to "ccprPracticeSession.info" controller.controllers.ccprPatient.content content}}Info{{/link-to}}
{{/link-to}}

Upvotes: 8

Dimuthu Nilanka
Dimuthu Nilanka

Reputation: 420

For the same problem here I came with jQuery based solution not sure about performance penalties but it is working out of the box. I reopen Ember.LinkView and extended it.

Ember.LinkView.reopen({
    didInsertElement: function(){
        var el = this.$();

        if(el.hasClass('active')){
            el.parent().addClass('active');
        }

        el.click(function(e){
            el.parent().addClass('active').siblings().removeClass('active');
        });
    }
});

Upvotes: 0

Ilia Choly
Ilia Choly

Reputation: 18557

Building on katz' answer, you can have the active property be recomputed when the nav element's parentView is clicked.

App.NavView = Em.View.extend({

    tagName: 'li',
    classNameBindings: 'active'.w(),

    didInsertElement: function () {
        this._super();
        var _this = this;
        this.get('parentView').on('click', function () {
            _this.notifyPropertyChange('active');
        });
    },

    active: function () {
        return this.get('childViews.firstObject.active');
    }.property()
});

Upvotes: 6

Sean Smith
Sean Smith

Reputation: 284

I recreated the logic used internally. The other methods seemed more hackish. This will also make it easier to reuse the logic elsewhere I might not need routing.

Used like this.

{{#view App.LinkView route="app.route" content="item"}}{{item.name}}{{/view}}

App.LinkView = Ember.View.extend({
    tagName: 'li',
    classNameBindings: ['active'],
    active: Ember.computed(function() {
      var router = this.get('router'),
      route = this.get('route'),
      model = this.get('content');
      params = [route];

      if(model){
        params.push(model);
      }

      return router.isActive.apply(router, params);
    }).property('router.url'),
    router: Ember.computed(function() {
      return this.get('controller').container.lookup('router:main');
    }),
    click: function(){
        var router = this.get('router'),
        route = this.get('route'),
        model = this.get('content');
        params = [route];

        if(model){
            params.push(model);
        }

        router.transitionTo.apply(router,params);
    }
});

Upvotes: 2

Yehuda Katz
Yehuda Katz

Reputation: 28703

We definitely need a more public, permanent solution, but something like this should work for now.

The template:

<ul>
{{#view App.NavView}}
  {{#linkTo "about"}}About{{/linkTo}}
{{/view}}

{{#view App.NavView}}
  {{#linkTo "contacts"}}Contacts{{/linkTo}}
{{/view}}
</ul>

The view definition:

App.NavView = Ember.View.extend({
  tagName: 'li',
  classNameBindings: ['active'],

  active: function() {
    return this.get('childViews.firstObject.active');
  }.property()
});

This relies on a couple of constraints:

  • The nav view contains a single, static child view
  • You are able to use a view for your <li>s. There's a lot of detail in the docs about how to customize a view's element from its JavaScript definition or from Handlebars.

I have supplied a live JSBin of this working.

Upvotes: 27

Related Questions