Bootstrap, active links and LIs

If you’ve ever tried to use twitter bootstrap with Ember, you’ll have hit this problem. The “active” class for links in bootstrap that are in a list of links needs to be on the li element, not on the link:

<li class="active">
  <a href="foo">Link</a>
</li>

Up until now the solution to achieve this in ember has been a little ugly IMO:

{{#link-to 'dashboard' tagName="li" href=false}}
    {{#link-to 'dashboard' bubbles=false}}
        Dashboard
    {{/link-to}}
{{/link-to}}

I decided to see if I could come up with something nicer using a component, and I think I did:

{{#link-li}}
  {{#link-to 'dashboard'}}
    Dashboard
  {{/link-to}}
{{/link-li}}

The component is implemented like this - no template necessary:

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

Another win for components, which I am currently enjoying abusing in various ways :smile:

15 Likes

Good stuff. Is the Ember.Handlebars.helper call at the end necessary? I thought components automatically registered a helper.

I believe it is necessary if you don’t provide a template, although that may have changed recently

Nice work. An almost identical solution to @mixonic’s http://emberjs.jsbin.com/iFEvATE/2/edit, which adds a currentWhen property so that the behaviour can be customised (e.g. to apply an active class to a parent route link).

However I was scratching my head trying to get it to work with query params, so this looks ideal for that.

Another approach by @rwjblue: https://github.com/rjackson/Ghost/commit/3a912bd89e6bd5270c9d919456bdb9c13622e2b5

Note that with my approach, you can specify currentWhen on the actual link-to and it will work as expected - in @mixonic’s approach you have to specify currentWhen on the wrapper for it to work.

@rwjblue’s solution is less verbose but seems more inflexible - it wouldn’t handle linking to a route with dynamic segments without alteration at the moment.

Interesting to see all the approaches to this - I hadn’t seen either of those before and probably wouldn’t have attempted this if I had!

It does seem to have changed - it works fine without registering it

I can confirm this.

Great finding, just used it in one of my projects.

@alexspeller, is it alright if I add it to Ember Components website?

P[quote=“muchweb, post:8, topic:5018”] @alexspeller, is it alright if I add it to Ember Components website? [/quote]

Please do, that’s fine by me!

Added a pull request. :smile:

@alexspeller a lot has changed since this solution was originally posted. Is this still a valid approach?

I’m a bit of a noob, and not exactly sure how to implement this. Where do I drop this component in my project (using ember-cli 0.1.2)?

Thanks

bump.

I’m encountering the same issue. Working + code that will last more than a few months would be ideal.

Anybody?

What issue are you experiencing exactly?

app/components/link-li.js

This is now an ember-cli addon:

https://github.com/alexspeller/ember-cli-active-link-wrapper

2 Likes

In in Ember 1.13 beta 1, which was published today, the active-link-wrapper doesn’t work anymore. I don’t know if it’s because of something in 1.13, but there are no childViews anymore

https://github.com/alexspeller/ember-cli-active-link-wrapper/blob/master/addon/components/active-link.js

In

return this.get('childViews').anyBy('active');

The length of this.get('childViews') is always 0.

https://github.com/alexspeller/ember-cli-active-link-wrapper/issues/4

This works for me quite well:

{{#link-to 'index' tagName="li"}}<a href>Home</a>{{/link-to}}    
{{#link-to 'about' tagName="li"}}<a href>About</a>{{/link-to}}

Unfortunately need that empty href.

2 Likes

It is my solution

Ember.LinkView.reopen({
	activeParent: false,
	isActive: false,

	addIsActiveObserver: function () {
		if (this.get('activeParent')) {
			this.addObserver('isActive', this, 'activeObserver');
			this.activeObserver();
		}
	}.on('didInsertElement'),

	activeObserver: function () {
		if (this.get('isActive')) {
			this.$().parent().addClass('active');
		} else {
			this.$().parent().removeClass('active');
		}
	},

	active: Ember.computed('resolvedParams', 'routeArgs', function () {
		var isActive = this._super();

		Ember.set(this, 'isActive', !!isActive);

		return isActive;
	})
});

And in template

<ul class="nav navbar-nav navbar-right">
	<li>{{#link-to 'index' activeParent=true}}Главная{{/link-to}}</li>
	<li>{{#link-to 'news' activeParent=true}}Новости{{/link-to}}</li>
</ul>

Work in v1.11

1 Like

Update: I just extracted in a simple add-on (Ember.js 2.1 or greater): ember-bootstrap-nav-link - npm

ember install ember-bootstrap-nav-link

Using components:

  • Generate a component with
ember g component nav-link-to
  • Update the js file and hbs file as below.
// nav-link-to.js
import Ember from 'ember';

export default Ember.LinkComponent.extend({
  tagName: 'li',

  hrefForA: Ember.computed('models', 'qualifiedRouteName', function computeLinkToComponentHref() {
        let qualifiedRouteName = this.get('qualifiedRouteName');
        let models = this.get('models');

        if (this.get('loading')) {
            return this.get('loadingHref');
        }

        let routing = this.get('_routing');
        let queryParams = this.get('queryParams.values');
        return routing.generateURL(qualifiedRouteName, models, queryParams);
    })
});

// nav-link-to.hbs
<a href="{{hrefForA}}">{{yield}}</a>

  • You can use it in your template.
<ul class="nav navbar-nav">
  {{#nav-link-to 'index'}}Home{{/nav-link-to}}
  {{#nav-link-to 'about'}}About{{/nav-link-to}}
  {{#nav-link-to 'contact'}}Contact{{/nav-link-to}}
</ul>