how to set an itemView for an itemController in Ember? - ember.js

I have successfully implemented an ArrayController an defined an ItemController for it like this:
export default Ember.ArrayController.extend(InboxTab, {
itemController: 'messages.message-list-item'
});
then in the template for the array controller I just do
{{#each}}
<li {{action 'someActionFromItemController'}}>{{someComputedPropertyFromItemController}}</li>
{{/each}}
This works great and I can handle a lot of actions and computed for each item, but I'm running into difficulties associating a view to each item. The docs are not helpful. The only instance of itemView is in this article:
http://emberjs.com/api/classes/Ember.CollectionView.html#sts=Specifying itemViewClass
and here the example seems to revolve around adding the view to the template and specifying the content from there and I'm not sure how that applies to the way I'm doing it.

A different way you could do it:
{{#each itemController='messages.message-list-item'}}
{{#view your-view}}
{{action}}{{computed property from view/controller}}
{{/view}}
{{/each}}
specify the item controller in the loop and wrap the actions and properties in the view - which means either of these could also be called or set in the view.

I think way to do this best is with itemViewClass, see the api for the full details.
{{#each message itemController='messageListItem' itemViewClass='messageListItem'}}
{{! Assumes you have view defined in an App.MessageListItemView defined in your JavaScript}}
{{action}}
{{/each}}

Related

How to set specific view in emberJS with itemcontroller

I have the following code:
{{#each categories.items itemController="item"}}
When I open the Ember inspector, it shows the view to be "virtual". I want to set the view to be "item" so that it follows the ember view I set out called itemView. I know we can set an itemController: is it possible to set an item view?
Yes it is possible using the optional 'itemViewClass' parameter.
{{#each categories.items itemController="item" itemViewClass="otherView"}}
Though I would recommend discontinuing using that and itemController as the latest Best Practice is to use a component within the each block.
So for your example:
// Ember 1.10+
{{#each categories.items as |item|}}
{{some-component item=item}}
{{/each}}
// Ember 1.9-
{{#each item in categories.items}}
{{some-component item=item}}
{{/each}}
Then you put your logic needed in the component object instead of the item controller.

itemController in ArrayController vs #each

Following along with the Getting Started Guide I have this http://jsbin.com/enutit/2/edit
My question is how come I can't remove the itemController from this each helper
<ul id="todo-list">
{{#each controller itemController="todo"}}
<li {{bindAttr class="isCompleted:completed isEditing:editing"}}>
and then add
itemController: 'todo',
to Todos.TodosController and have it work?
Because the controller's properties are not the same as the {{each}} helper's properties.
{{each}} internally creates an instance of Ember.Handlebars.EachView to display each item in the Todos.TodosController's content property. It is this view that needs the itemController property so that it can create a new Todos.TodoController (note the singular form) instance for each child view.

View click action not returning correct object

I'm having trouble with a click handler in a view. It's not returning the expected member of the collection, but the collection as a whole.
I've created a jsfiddle to demonstrate the issue. I have an ArrayController, whose content I pre-populate. The view for this controller then uses the #each helper for the controller with another view:
{{#each controller}}
{{view App.ActivityListItemView}}
{{/each}}
This works, in that I see the name of the item on the page, and can click it.
The problem is in the click handler - if I #get('content'), the content for the parent controller is returned. How do I get the item that was clicked on? If you have a look at the console output in the jsfiddle you'll see the issue. I assume this is a context issue?
I've tried adding contentBinding="this" to the view:
{{#each controller}}
{{view App.ActivityListItemView contentBinding="this"}}
{{/each}}
but that makes no difference.
thanks,
Martin
How do I get the item that was clicked on? If you have a look at the console output in the jsfiddle you'll see the issue. I assume this is a context issue?
Exactly. You want the view's context instead of it's controller's content. So:
click: (data)->
console.log 'clicked on an activity'
selected = #get('context')
#get('controller').set('selectedActivity', selected)
console.log(#get('controller').get('selectedActivity.name'))
Why?
By default the {{#each}} helper does not create a new controller instance for items in the array. So when you#get('controller')` from the view helper it searches up the view heirarchy until a controller is found - in this case that is the array controller.
If you want to have a separate controller for each item you could provide an itemController attribute to the each helper - see http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_each
Right, I've got this working, I think the issue sprang for a lack of understanding of the contentBinding argument. Basically I've changed to using a specific name of 'activityBinding' within the #each block, and then referring explicitly to the activity in the click handler. See jsfiddle for a working demo.
{{#each controller}}
{{view App.ActivityListItemView activityBinding="this"}}
{{/each}}
and
click: ->
console.log 'clicked on an activity'
console.log #get('activity.name')
content = #get('activity')
#get('controller').set('selectedActivity', content)

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/