emberjs - how to mark active menu item using router infrastructure

纵然是瞬间 提交于 2019-11-26 19:43:30
pangratz

If you're using Ember >= 1.11, then https://stackoverflow.com/a/14501021/65542 below is the correct answer.


I would create a NavigationView, see http://jsfiddle.net/pangratz666/z8ssG/:

Handlebars:

<script type="text/x-handlebars" data-template-name="navigation">
    <ul class="nav nav-tabs">
        {{#view view.NavItemView item="home" }}
            <a {{action gotoHome}} >Home</a>
        {{/view}}
        {{#view view.NavItemView item="profiles" }}
            <a {{action gotoProfiles}} >Profiles</a>
        {{/view}}
        {{#view view.NavItemView item="messages" }}
            <a {{action gotoMessages}} >Messages</a>
        {{/view}}        
    </ul>
</script>

JavaScript:

App.NavigationView = Em.View.extend({
    templateName: 'navigation',
    selectedBinding: 'controller.selected',
    NavItemView: Ember.View.extend({
        tagName: 'li',
        classNameBindings: 'isActive:active'.w(),
        isActive: function() {
            return this.get('item') === this.get('parentView.selected');
        }.property('item', 'parentView.selected').cacheable()
    })
});

And inside your route's connectOutlets you have to set the current navigation item via router.set('navigationController.selected', 'home'); ...


Also take a look at the ember-bootstrap repository, which wraps this and more features of Bootstrap inside Ember.js

lesyk

Ember 1.11+:

{{#link-to "dashboard" tagName="li"}}
  <a href="{{view.href}}">Dashboard</a>
{{/link-to}}

Ember < 1.11 (bind-attr required):

{{#link-to "dashboard" tagName="li"}}
  <a {{bind-attr href="view.href"}}>Dashboard</a>
{{/link-to}}

Some of the above suggestions are still valid for twitter bootstrap case. You can also try something like this

{{#link-to 'dashboard' tagName='li'}} 
  {{#link-to 'dashboard'}}Link Title{{/link-to}}
{{/link-to}}
  1. The link-to with li tagName applies the active class to the li
  2. The inner link-to would be a anchor element which gives you Open in New Tab functionality when right-clicked

Recently an Ember-cli addon came available to just do this. It is called ember-cli-active-link-wrapper.

Install: ember install ember-cli-active-link-wrapper

You can use it like this:

{{#active-link}}
  {{link-to "Index" "index"}}
{{/active-link}}

which results in:

<li class='active'>
    <a href="/" class='active'>Index</a>
</li>

I know this is old post, but here are updates for Ember 2.4.0

For creating links you can write

{{#link-to 'photoGallery'}}
  Great Hamster Photos
{{/link-to}}

or

{{link-to 'Great Hamster Photos' 'photoGallery'}}

Ember will automatically set class to active when current route matches link's route (in this example photoGallery).

If you want to control 'active' class on other routes as well, you can do it by setting current-when attribute.

{{#link-to 'photoGallery' current-when='photoGallery photoPreview'}}
  Great Hamster Photos
{{/link-to}}

This link will have active class on both photoGallery and photoPreview routes.

https://github.com/emberjs/ember.js/blob/v2.4.0/packages/ember-routing-views/lib/components/link-to.js#L140

Handlebars

<ul class="nav">
    <li>{{#linkTo "index"}}Index{{/linkTo}}</li>
    <li>{{#linkTo "about"}}About{{/linkTo}}</li>
</ul>

Javascript

App.Router.map(function() {
    this.route("about");
});

It will add active class automatically based on route. Note: It is tested using ember-1.0.0-pre.4.js

You can also change the isActive method into something like this:

isActive: function() {
    return App.getPath('router.currentState.path') === "root.firms";
}.property("App.router.currentState"),

or

isActive: function() {
    return this.get('controller.target.currentState.path') === "root.firms";
}.property("controller.target.currentState"),

I see this question is quite old, but if you upgraded Ember.js to the RC3 you can use tagName property, like:

{{#link-to messages tagName="li"}}Messages{{/link-to}}

Here is the API - http://emberjs.com/api/classes/Ember.LinkView.html

Not sure if it's very dynamic but try to see solution at http://codebrief.com/2012/07/anatomy-of-an-ember-dot-js-app-part-i-redux-routing-and-outlets/ The main idea is to check state of your app

JavaScript:

function stateFlag(name) {
  return Ember.computed(function() {
    var state = App.router.currentState;
    while(state) {
      if(state.name === name) return true;
      state = state.get('parentState');
    }
    return false;
  }).property('App.router.currentState');
}

ApplicationController: Ember.Controller.extend({
    isHome: stateFlag('home'),
    isSections: stateFlag('sections'),
    isItems: stateFlag('items')
  })

Handlebars:

<li class="home" {{bindAttr class="isHome:active"}}>
</li>
<li class="sections" {{bindAttr class="isSections:active"}}>
</li>
<li class="items" {{bindAttr class="isItems:active"}}>
</li>

Update: pangratz's solution looks prettier

Here's a full working solution:

View:

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

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

Template:

<ul>
  {{#each item in controller}}
  {{#view App.NavView}}
  {{#linkTo "item" item tagName="li"}}
      <a {{bindAttr href="view.href"}}>
        {{ item.name }}
      </a>
  {{/linkTo}}
  {{/view}}
  {{/each}}
</ul>

Sooner or later want to change the naming of your states or whatever you have to go through the code AND the view as well, also adding a function to transitionTo every route seems not desirable. My approach is a bit more programmatic and modularized:

# Parent View-Tamplate, holding the navbar DOM elements
App.NavView = Ember.View.extend( 
  controller: App.NavArrayController
  templateName: "ember-nav"
)
# We push NavItems into this array
App.NavArrayController = Ember.ArrayController.create(
  content: Ember.A([])
)
# NavItem has two settable properties and 
# an programmatic active state depending on the router
App.NavItem = Ember.Object.extend(
  title: ''
  goto: null    # <=this is the name of the state we want to go to!
  active: (->
    if App.router.currentState.name == @.get "goto"
      true
    else
      false
  ).property('App.router.currentState.name').cacheable()
)
# the actual NavElement which gets the class="active" if the 
# property "active" is true, plus a on-click binding to
# make the Router transition to this state
App.NavItemView = Ember.View.extend(
 tagName: "li"
  classNameBindings: ["active"]
  click: ->
    App.router.transitionTo(@get('goto'))
    false
)

nav-view.hbs (for twitter-bootstrap-style navs)

<div class="nav-collapse collapse">
  <ul class="nav">
    {{#each App.NavArrayController}}
      {{#view App.NavItemView classBinding="active" gotoBinding="goto"}}
        <a href="#" {{bindAttr data-goto="goto"}}> {{title}}</a>
      {{/view}}
    {{/each}}
  </ul>
</div>

This way, I can just create and mess around with my routes in the Router, and keep the Nav-Definitions side-by-side:

# put this somewhere close to the Router 
App.NavArrayController.pushObjects(
  [
    App.NavItem.create(
      title: 'Home'
      goto: 'home'
    ),
    App.NavItem.create(
      title: 'Chat'
      goto: 'chat'
    ),
    App.NavItem.create(
      title: 'Test'
      goto: 'test'
    )
  ]
)

baijum's answer above is mostly correct however in the latest versions of Ember the "bind-attr" is deprecated. Here is the new way to write it:

{{#link-to "dashboard" tagName="li"}}
    <a href="{{view.href}}">Dashboard</a>
{{/link-to}}

As you can see, it's even easier and kind of works like magic..

Beginning with v0.8.0 ember-bootstrap supports navs, including handling the active state correctly. And that without any link-to/tagName kind of hacks:

{{#bs-nav type="pills"}}
   {{#bs-nav-item}}
      {{#link-to "foo"}}Foo{{/link-to}}
   {{/bs-nav-item}}
   {{#bs-nav-item}}
     {{#link-to "bar"}}Bar{{/link-to}}
   {{/bs-nav-item}}
 {{/bs-nav}}

See http://kaliber5.github.io/ember-bootstrap/api/classes/Components.Nav.html

Simon Ihmig

A lot of the proposed solutions here do not work for any recent Ember version (e.g. views being deprecated). Moreover just using the link-to helper will not solve the issue, as bootstrap expects the active class to be present on the <li> not the <a>!

So I will try to summarize the solutions that do in fact work as of now:

use ember-cli-active-link-wrapper

The addon provides a component for this special use case:

<ul class="nav nav-tabs">
  {{#active-link}}
    {{#link-to "foo"}}Foo{{/link-to}}
  {{/active-link}}
  {{#active-link}}
    {{#link-to "bar"}}Bar{{/link-to}}
  {{/active-link}}
</ul>

Taken from https://stackoverflow.com/a/29939821/5556104

use ember-bootstrap

ember-bootstrap provides many components that integrate bootstrap functionality into your ember app, amongst them the nav components:

{{#bs-nav type="tabs"}}
   {{#bs-nav-item}}
      {{#link-to "foo"}}Foo{{/link-to}}
   {{/bs-nav-item}}
   {{#bs-nav-item}}
      {{#link-to "bar"}}Bar{{/link-to}}
   {{/bs-nav-item}}
 {{/bs-nav}}

Taken from https://stackoverflow.com/a/38279975/5556104

link-to Hack

Somewhat hacky, but should work without any additional addon:

<ul class="nav nav-tabs">
  {{#link-to "foo" tagName="li"}} 
    {{#link-to "foo"}}Foo{{/link-to}}
  {{/link-to}}
  {{#link-to "bar" tagName="li"}} 
    {{#link-to "bar"}}Bar{{/link-to}}
  {{/link-to}}
</ul>

Taken from https://stackoverflow.com/a/23966652/5556104

Like other people said, using {{#link-to}} to link to an existing route, when that route is current URL, {{#link-to}} will automatically add active to its CSS classes.

See Ember issue 4387

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!