Assigning 'active' class to selected list item in EmberJS - ember.js

I have a list and I'd like to set one item as class="active" automatically. Given the following bootstrap code:
<ul class="nav">
<li {{bindAttr class="atIndex:active"}}>{{#linkTo "index"}}Index{{/linkTo}}</li>
<li {{bindAttr class="atAbout:active"}}>{{#linkTo "about"}}About{{/linkTo}}</li>
<li {{bindAttr class="atLogin:active"}}>{{#linkTo "login"}}Login{{/linkTo}}</li>
</ul>
atIndex, atAbout and atLogin reside in my ApplicationController.
To render as:
<ul class="nav">
<li class="active"><a...>Index{{/linkTo}}</li>
<li><a...>About<a></li>
<li><a...>Login<a></li>
</ul>
What's the best way to do this with Ember 1.0 pre4? I'd rather not add special code to every view or controller.
PS - atIndex: true works, but atIndex: function() {return true; }.property().volatile() does not. Which makes me think I'm doing something wrong.
Thank you!

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

By far the cleanest way to solve this is by taking advantage of the linkTo helper's built-in support for setting the active class when rendering links. AFAIK this is not yet documented other than in the source code:
implementation: https://github.com/emberjs/ember.js/blob/master/packages/ember-routing/lib/helpers/link_to.js#L46
example: https://github.com/emberjs/ember.js/blob/master/packages/ember/tests/helpers/link_to_test.js#L120
To take advantage of this feature just adjust your css to style based on having an active class on the link instead of the li element. If you really need to style the li you can create a custom view and helper that extends Ember.LinkView and uses an li but changing css will be far easier.
--- UPDATE ----
Since we all love twitter bootstrap just changing the css is perhaps not such a great option. In that case, the following will do the trick:
App.ApplicationView = Ember.View.extend({
currentPathDidChange: function() {
Ember.run.next( this, function() {
this.$("ul.nav li:has(>a.active)").addClass('active');
this.$("ul.nav li:not(:has(>a.active))").removeClass('active');
});
}.observes('controller.currentPath')
});
Working example using ember linkTo helper with bootstrap pills: http://jsbin.com/ekobod/5/edit (requires ember-1.0.0-pre.4)

the active route's path is updated automatically in the ApplicationController via currentPath so I did something like that in my App...
In ApplicationController added properties like so:
isProductsActive: function(){
if ( this.get('currentPath') === 'products' ) return 'active';
else return '';
}.property('currentPath')
and in my ApplicationView template:
<li {{bindAttr class="isProductsActive"}}>
{{#linkTo "products"}}Products{{/linkTo}}
</li>

I made an ember-cli addon that handles this:
https://github.com/alexspeller/ember-cli-active-link-wrapper

EDIT:
Finally, the best way I've found to use the activate class of bootstrap li element using ember.js of the link.
{{#linkTo "dives" tagName="li"}}
<a {{bindAttr href="view.href"}}>Dives</a>
{{/linkTo}}
--------------8<--------------
DEPRECATED:
I guess the previous answers were relevant before Ember.js introduced the activeClass attribute for linkTo helper.
Now I would solve the problem like this :
<ul class="nav">
<li >{{#linkTo "index" activeClass="active"}}Index{{/linkTo}}</li>
<li >{{#linkTo "about" activeClass="active}}About{{/linkTo}}</li>
<li >{{#linkTo "login" activeClass="active}}Login{{/linkTo}}</li>
</ul>
Enber will automatically add the class when relevant.

If I may suggest another solution that requires nothing but Handlebars:
<li {{bind-attr class="view.innerLink.active:active"}}>
{{#link-to "path" viewName="innerLink"}}Click{{/link-to}}
</li>
This sets the LinkView object as a member of the parent view, which's active attribute you can then reference.

I found a pretty simple Solution using linked items in a list group(http://getbootstrap.com/components/#list-group-linked).
<div class="list-group">
{{#each thing in list}}
{{#link-to "details" thing.id tagName="a" href="view.href" class="list-group-item" {{thing.name}} {{/link-to}}
{{/each}}
</div>
Works with Bootstrap v3.1.1 and Ember v1.7.0

Just nest the {{link-to}} with a tagName on the outer one. I'm doing this on EmberJS 2.0.
{{#link-to "admin.websocket" tagName="li"}}
{{#link-to "admin.websocket"}}WebSocket{{/link-to}}
{{/link-to}}

If you want to use Bootstrap navigation in Ember then you can use Bootstrap for Ember that has out of the box support for this:
Github: https://github.com/ember-addons/bootstrap-for-ember
Demo: http://ember-addons.github.io/bootstrap-for-ember/dist/#/show_components/tabs

A lot of these answers are outdated. Here is a much cleaner (and DRY) version for Bootstrap and Ember 2.x:
{{#link-to "index" tagName="li" as |link|}}
Index Page
{{/link-to}}

I solved a similar problem by creating a view for each item and using classNameBindings (I have to say that i don't have a HTML list, i.e.<a>...</a> in my app, just list of <div>).
Here is the way it works for me:
In tasklist.handlebars i iterate over my custom view
{{#each tasks}}
{{view App.TaskListItemView contentBinding="this"....}}
{{/each}}
Ember will insert a view (i. e. <div>) for each item.
The view class for each item is defined in task_list_item_view.js as
App.TaskListItemView = Ember.View.extend({
controller: null,
classNameBindings: ['isSelected', 'isClosed'],
isClosed: function() {
var content = this.get('content');
return content && !content.isOpen(new Date);
}.property('controller.content.#each'),
isSelected: function() {
return (this.get('controller').isSelectedTask(this.get('content')));
}.property('controller.taskSelection.#each'),
....
});
Finally the template for the view just renders my link in tasklistitem.handlebars
<a {{action "selectTask" view.content target="view"}} rel="tooltip"
{{bindAttr title="view.content.comment"}} class="taskListLink">
....
</a>
AFAIK you have to specify the source data in the property() call to let ember know when to (re-) evaluate the property.
Hope that helps

I went with:
Ember.LinkView.reopen({
didInsertElement:function(){
if(this.$().hasClass('active')){
this.$().parent().addClass('active');
}
}
});
I didn't want to use the accepted answer as I wanted to keep my li elements as plain old html. There might be a better way to check the active state but I couldn't get access to the right property.

Related

ember: manipulate the DOM (like hide a long list of items)

I'm new to ember. I have a demo app working, and I'm moving towards making it look nice.
One issue I'm starting to grapple with is how to manipulate DOM elements. Coming from a server-side world, it's been pretty easy to just throw some jquery at stuff like this. Doesn't appear to be as straightforward in ember. But I'm probably missing something or approaching it wrong.
The immediate problem is: I have a list of 40-some <li> elements and I want to create a toggle to show/hide the list after the first 10 items.
I got something to work in my component like this:
import Ember from 'ember';
let $ = Ember.$;
export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent() {
let listTotal = $("#myList li").length;
$("#myList li").slice(10, listTotal).hide();
}
});
The problem is that when actions trigger and the view is re-rendered, afterRenderEvent() doesn't get called again, and the list shows in its entirety.
The above component corresponds to this template:
<ul id="myList">
{{#each aggs.categories as |category|}}
<li><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>
Is there a way to get around this? OR, is there a more "ember" way to approach this problem (and DOM manipulation in general)?
Introduce showCount property in controller and have action to set showCount to total list count.
import Ember from 'ember';
export default Ember.Component.extend({
showCount: 10,
actions: {
setShowCount(count) {
//you can set total count
this.set('showCount', count);
}
}
});
Install ember truth helpers addon or write computed property to check.
<ul id="myList">
{{#each aggs.categories as |category index|}}
<li style="{{if (gt index showCount) 'display:none'}}"><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>
If you don't want to display then you can just iterate just showCount aggs.categories alone by writing computed property or using ember-composable-helpers junk method
<ul id="myList">
{{#each (chunk showCount aggs.categories) as |category index|}}
<li><a href="#" {{action (action add "filter_breadcrumb" category.key)}}>{{category.key}} ({{category.doc_count}})</a></li>
{{/each}}
</ul>

How can I add a class in ember js

<script type="text/x-handlebars">
<div class="wrapper">
<div class="sideMenu">
{{#link-to 'home'}}Home{{/link-to}}
{{#link-to 'posts'}}Posts{{/link-to}}
</div>
<div class="content">
{{outlet}}
</div>
</div>
</script>
I am new to ember js. How can I add a class on 'content' class each time when view changes.
We do something like this:
Ember.Route.reopen({
activate: function() {
var cssClass = this.toCssClass();
// you probably don't need the application class
// to be added to the body
if (cssClass !== 'application') {
Ember.$('body').addClass(cssClass);
}
},
deactivate: function() {
Ember.$('body').removeClass(this.toCssClass());
},
toCssClass: function() {
return this.routeName.replace(/\./g, '-').dasherize();
}
});
It would add a class to the body (in your case just use content), that is the same as the current route.
#torazaburo had some excellent points about #Asgaroth (accepted) answer, but I liked the idea of not having to write this same functionality over and over again. So, what I am providing below is a hybrid of the two solutions plus my own two cents and I believe it addresses #torazaburo concerns regarding the accepted answer.
Let's start with the 2nd point:
I also don't like the idea of polluting Ember.Route
Can you pollute Ember.Route without polluting Ember.Route? (Huh?) Absolutely! :) Instead of overwriting activate, we can write our own function and tell it to run .on(activate) This way, our logic is run, but we are not messing with the built-in/inherited activate hook.
The accepted answer is very procedural, imperative, jQuery-ish, and un-Ember-like.
I have to agree with this as well. In the accepted answer, we are abandoning Ember's data binding approach and instead fall back on the jQuery. Not only that, we then have to have more code in the deactivate to "clean up the mess".
So, here is my approach:
Ember.Route.reopen({
setContentClass: function(){
this.controllerFor('application').set("path", this.routeName.dasherize());
}.on('activate')
});
We add our own method to the Ember.Route class without overwriting activate hook. All the method is doing is setting a path property on the application controller.
Then, inside application template, we can bind to that property:
<div {{bind-attr class=":content path"}}>
{{outlet}}
</div>
Working solution here
Just bind the currentPath property on the application controller to the class of the element in the template:
<div {{bind-attr class=":content currentPath"}}>
{{outlet}}
</div>
In case you're not familiar with the {{bind-attr class= syntax in Ember/Handlebars:
the class name preceded with a colon (:content) is always added to the element
properties such as currentPath result in the current value of that property being inserted as a class, and are kept dynamically updated
To be able to access currentPath in a template being driven by a controller other than the application controller, first add
needs: ['application']
to the controller, which makes the application controller available under the name controllers.application, for use in the bind-attr as follows:
<div {{bind-attr class=":content controllers.application.currentPath"}}>
You may use currentRouteName instead of or in addition to currentPath if that works better for you.
The class name added will be dotted, such as uploads.index. You can refer to that in your CSS by escaping the dot, as in
.uploads\.index { }
Or, if you would prefer dasherized, add a property to give the dasherized path, such as
dasherizedCurrentPath: function() {
return this.('currentPath').replace(/\./g, '-');
}.property('currentPath')
<div {{bind-attr class=":content dasherizedCurrentPath"}}>
This has been tested in recent versions of ember-cli.

Ember.checkbox nested in action doesn't change value

Template:
{{#each document in documents}}
<div class="col-md-6" {{action "selectDocument" document}}>{{view Ember.Checkbox checked=document.isSelected}} {{document.name}}</div>
{{/each}}
Controller:
App.IndexDocumentsController = Ember.ArrayController.extend({
actions: {
selectDocument: function(document){
document.set('isSelected', !document.get('isSelected'));
}
}
});
When I click on the div, the checkbox toggles 'checked' property. But when I click on the ckeckbox - nothing happens. What can be the reason?
UPDATED
Link to jsbin: http://emberjs.jsbin.com/nuvocumuteto/1/edit?html,css,js,output
The issue is that when you click on the checkbox 2 things happen.
the checkbox toggles the isActive property, then
the selectRow action is ran which again toggles the isActive property
So the isActive property ends up staying in the same state that it was.
In your case I would get rid of the action, wrap the checkbox in a <label> and set the label to display: block.
The template would look like
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li {{bind-attr class="item.isActive:active"}}><label>{{input type="checkbox" checked=item.isActive}}{{item.name}}</label></li>
{{/each}}
</ul>
</script>
and the css would look like
label {
display: block;
}
you would then be able to get rid of the selectRow action completely because clicking on the label will trigger the checkbox.
You can see a working bin here: http://emberjs.jsbin.com/nuvocumuteto/3/edit
I would argue that you are not following "The Ember Way" in two different ways here.
First, Ember.Checkbox is an internal Ember class (http://emberjs.com/api/classes/Ember.Checkbox.html). The recommended way to render a checkbox is to use the Handlebars input helpers (http://emberjs.com/guides/templates/input-helpers/#toc_checkboxes). This is just wrapping Ember.Checkbox anyway.
Second, if you want to update the value of isSelected, the "Ember Way" is to use two-way data bindings. Your code uses one-way data-binding when it reads document.isSelected and then tries to manually re-create the the data-binding in the other direction when the user clicks by manually writing a selectDocument action and calling it from an {{action}}.
Instead, simply bind the Ember Handlebars Input Helper directly to your value like this:
{{#each document in documents}}
<div class="col-md-6">{{input type="checkbox" checked=document.isSelected}} {{document.name}}</div>
{{/each}}

Selected item in a template, is there any solution for a context aware bindAttr?

The problem is as follows:
In our application we have several buttons, navigation icons etc., which we want to be 'selected' when they have been clicked. We can have multiple elements marked at the same time.
The secondary reason for me wanting to do this is that when I read the new Guides on emberjs.com I get the feeling that templates should be used more than stated before and that templates should have the responsibility of rendering the DOM, while the views should be used to handle sophisticated events (if any) or to create common/shared components to be reused in the application.
Currently the view is handling this:
app.NavView = Ember.CollectionView.extend({
...
itemViewClass: Ember.View.extend({
...
classNameBindings: ['isSelected:selected']
isSelected: function () {
return this.get('controller.selected') === this.get('content');
}.property('controller.selected')
})
});
But that is all the View basically is doing, I would like to drop the entire View and just use a template for this
I have tried with a template approach, and dropped the entire View concept.
<div id="main-menu">
{{#each content}}
<div {{bindAttr class="controller.isSelected:selected"}}>
{{{iconsvg}}}
{{name}}
</div>
{{/each}}
</div>
But my problem here of course is that bindAttr doesn't know about the context it’s in, and cannot 'send' this to the isSelected property on the controller to evaluate if it is this element that is selected or not.
Is there a good solution to do this without a view, or am I forced to use a view?
Or am I thinking the design part and responsibility of Templates/views/controllers wrong?
Any response is appreciated!
In the current documentation: http://emberjs.com/guides/templates/displaying-a-list-of-items/ there is a mention explaining how to use the {{each}} helper which doesn't override the current context.
In your case, this would be something like:
<div id="main-menu">
{{#each item in controller}}
<div {{bindAttr class="isSelected:selected"}}>
{{{item.iconsvg}}}
{{item.name}}
</div>
{{/each}}
</div>
Note I have remove the reference to 'controller' in the {{bindAttr}} since I assume it's an ember controller, then it's the current context, so basically isSelected is equivalent to controller.isSelected

In templates in Ember.js, how do you refer to a value in the parent context when you are inside an #each block?

I have a situation in a template where I want to use an if block on a value in the parent context while inside an each block.
The code:
App = Ember.Application.create({});
App.view = Ember.View.extend({
foo: [1, 2, 3],
bar: true
});
The template:
<script type="text/x-handlebars">
{{#view App.view}}
{{#each foo}}
{{#if bar}}
{{this}}
{{/if}}
{{/each}}
{{/view}}
</script>
This does not work because names referenced inside an each loop are scoped to the element of iteration. How do you refer to things in the parent context?
Demo: http://jsfiddle.net/hekevintran/sMeyC/1/
I found a better solution.
From the Ember.js View Layer guide (http://emberjs.com/guides/understanding-ember/the-view-layer/):
Handlebars helpers in Ember may also specify variables. For example, the {{#with controller.person as tom}} form specifies a tom variable that descendent scopes can access. Even if a child context has a tom property, the tom variable will supersede it.
This form has one major benefit: it allows you to shorten long paths without losing access to the parent scope.
It is especially important in the {{#each}} helper, which provides a {{#each person in people}} form. In this form, descendent context have access to the person variable, but remain in the same scope as where the template invoked the each.
The template:
<script type="text/x-handlebars" >
{{#view App.view}}
{{#each number in view.foo}}
{{#if view.bar}}
{{number}}
{{/if}}
{{/each}}
{{/view}}
</script>​
Demo: http://jsfiddle.net/hekevintran/hpcJv/1/
What hekevintran's answer means is that you can rename any variable using #with. We have a similar problem in JavaScript with this. In JavaScript, sometimes you'll see code like this to work around it.
var self = this;
doSomething(function() {
// Here, `this` has changed.
if (self.bar) {
console.log(this);
}
});
In Ember flavored Handlebars, something similar is happening with view. Say you have App.MyOuterView and another view inside it. You can work around it like this.
{{#with view as myOuterView}}
{{#each foo}}
{{#if myOuterView.bar}}
{{this}}
{{/if}}
{{/each}}
{{/with}}
Similar to the JavaScript, you can essentially rename view to something else so it doesn't get shadowed by the inner view. {{#each person in people}} is just a special case of that. But renaming using {{#with view as myView}} is the more general solution/workaround to this problem that also works with nested calls to the view helper.
I was also stumped on this. This thread and this other thread (Using a container view in ember.js - how to access parent variables from child view) helped me with the solution. I used Jonathan's suggestion to do {#with} and also figured out that I should access my variable by calling the controller. Mine worked like this:
// I use the #which command to preserve access to the outer context once inside the #each
{{#with view as myOuterView}}
{{#each myInnerArray}}
//here, i get my 'name' property from the *controller* of myOuterView
{{myOuterView.controller.name}}
// stuff i did in inner array
{{/each}
{{/with}
No need to place the if inside each in the first place:
<script type="text/x-handlebars">
{{#view App.view}}
{{#if view.bar}}
{{#each view.foo}}
{{this}}
{{/each}}
{{/if}}
{{/view}}
</script>
Demo: http://jsfiddle.net/ppanagi/NQKvy/35/