How do you pass the index of iteration of {{#each}}? - ember.js

I have a grid of objects to display. Each row of the grid displays 4 objects. Depending on where an object is in the grid I want certain classes to be on the elements of the objects. For example the last object in the grid should have the class "last_in_grid". This calculation depends on the index of the object in an array.
My template looks like:
{{#each row in objects}}
{{#each object in row}}
{{view MyApp.MyView objectBinding="object"}}
{{/each}}
{{/each}}
MyApp.MyView needs to know the index in of iteration of the each helper.
Ideally I want something like:
{{#each row in objects}}
{{#each object in row}}
{{view MyApp.MyView objectBinding="object" indexBinding="index_of_each_loop"}}
{{/each}}
{{/each}}
Django's template language can do this:
{% for item in items %}
{% if forloop.counter0 == 0 %}
blah blah blah
{% endif %}
{% endfor %}
https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#for
Is this possible using Ember and Handlebars?
Do I have to write a custom version of each to do this?

Instead of {{each}} helper, You can use {{collection}} helper to get current index of iteration like this Fiddle:
http://jsfiddle.net/secretlm/67GQb/73/
HTML:
{{#collection contentBinding="App.peopleController"}}
{{#view App.PersonView personBinding="content" indexParentBinding="contentIndex" }}
<td>{{indexParent}}</td>
<td>{{person.fullName}}</td>
{{/view}}
{{/collection}}
Javascript:
App = Ember.Application.create();
App.Person = Ember.Object.extend({
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
App.peopleController = Ember.ArrayController.create({
content: [App.Person.create({ firstName: "Yehuda", lastName: "Katz" }),
App.Person.create({ firstName: "Tom", lastName: "Dale" })]
});
App.PersonView = Ember.View.extend({
tagName: 'tr',
indexParent: null
});​

If you use a CollectionView the items/views of that collection can query it and see if they are the last item.
Fiddle: http://jsfiddle.net/qKXJt/138/
ArrayControllers are just proxies to their content, which is an Ember Array. This means you get all the goodies that come with Enumerable, Array, MutableArray, and so on.

Related

Multiple ul with single array in ember handlebars template

Lets say I've an array with 6 items and I want print them 3 per list
Ex
//arr = [1,2,3,4,5,6];
//html
<div class="first">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
<div class="second">
<ul>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
</div>
How can I accomplish that with ember/handlebars?
Thanks
One option would be to write a computed property on your controller that splits the larger array into an array of arrays.
Then you could iterate through the arrays of the computed property and use a component to display each of the smaller arrays.
I'll leave this as an excercise to you unless you have other questions.
Similar to what #Oren is saying, you would need to decorate your model (the array) inside the controller and then display the decorated model in the handlebars (as there is no way to perform logic inside the handlebars itself).
So, something like:
App.IndexController = Ember.ArrayController.extend({
decoratedModel: function(){
var model = this.get('model');
return [
Ember.Object.create({
className: "first",
arr: model.slice(0, 3)
}),
Ember.Object.create({
className: "second",
arr: model.slice(3)
})
];
}.property('model')
});
Then, you can display that in your template as follows:
<script type="text/x-handlebars" data-template-name="index">
{{#each item in decoratedModel}}
<div {{ bind-attr class=item.className}}>
<ul>
{{#each thing in item.arr }}
<li>{{thing}}</li>
{{/each}}
</ul>
</div>
{{/each}}
</script>
I ended doing this:
in template.hbs
{{#each set in arraySets}}
<div class="col-sm-6">
<ul class="list-unstyled">
{{#each item in set }}
<li>{{item.name}}</li>
{{/each}}
</ul>
</div>
{{/each}}
in the related controller
import Ember from "ember";
import Collection from "../../utils/collection";
export default Ember.ObjectController.extend({
// ....
arraySets: function() {
var infos = this.get('model.infos');
return Collection.create({ content: infos }).divide();
}.property()
});
and who does the hard work is the utils/collection.js
import Ember from "ember";
var Collection = Ember.ArrayProxy.extend({
divide: function (size = 2) {
var array = this.get('content');
var length = array.length;
var limit = Math.ceil(length / size);
var sets = Ember.A([]);
for (var i = 0; i < Math.min(size, length); i++) {
sets.pushObject(array.slice(i * limit, (i + 1) * limit));
}
return Collection.create({content: sets});
}
});
export default Collection;
I hope that this could help someone else!

How to get current model for a route from a controller or a view?

I want to implement item-list/item-detail pattern in Ember, but the nuance is that the detail view must appear next to the selected item. E.g:
<ul>
<li><div>Post 1<div></li>
<li><div>Post 2<div></li>
<li><div>Post 3<div></li>
<li>
<div>Post 4<div>
<div>
<ul>
<li>Comment 1</li>
<li>Comment 2</li>
<li>Comment 3</li>
</ul>
</div>
</li>
<li><div>Post 5<div></li>
</ul>
The Handlebars template I tried is:
<script type='text/x-handlebars' data-template-name='posts'>
<ul>
{{#each model}}
{{#linkTo 'post' this}}
<div>{{title}}</div>
{{/linkTo}}
{{#if isSelected}} <!-- How to implement isSelected ? -->
<div>{{!-- render selected post's comments --}}</div>
{{/if}}
{{/each}}
</ul>
</script>
I tried this in controller:
App.PostController = Em.ObjectController.extend({
isSelected: function() {
return this.get('content.id') === /* what to put here? */;
}
});
What I'm stuck with is how to implement isSelected in 'Ember'-way? Am I going in right direction?
You are on the right track. The trick is to use a different controller to wrap products in the item-list vs. the item-detail. So start by specifying that the handlebars {{each}} helper should wrap each entry in a ListedProductController
{{#each model itemController="listedProduct"}}
Now define ListedProductController, adding the isSelected function you'd been writing. Now it can reference the singleton ProductController via the needs array, comparing the product that was set by the router to the listed product.
App.ProductController = Ember.ObjectController.extend({});
App.ListedProductController = Ember.ObjectController.extend({
needs: ['product'],
isSelected: function() {
return this.get('content.id') === this.get('controllers.product.id');
}.property('content.id', 'controllers.product.id')
});
I've posted a working example here: http://jsbin.com/odobat/2/edit

Mark the current detail entry in a master list

I have a list of users which are displayed in a master view on the left side (Twitter Bootstrap CSS). Details of each user can be shown by clicking the show button. They will be displayed on the right side (detail).
How can I remove the show button for the currently displayed user? e.g. #/users/1 shouldn't render the show button for the first user.
index.html
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span4'>
<table class='table table-striped'>
{{#each model}}
<tr>
<td>{{lastName}}</td>
<td>{{#linkTo 'user' this}}<button class="btn" type="button">show</button>{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div class='span8'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user">
<h1>{{firstName}} {{lastName}}</h1>
</script>
app.js
App = Ember.Application.create();
App.Store = DS.Store.extend({
revision: 12,
adapter: 'DS.FixtureAdapter'
})
App.Router.map(function() {
this.resource('users', function() {
this.resource('user', { path: ':user_id' })
})
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
})
App.User.FIXTURES = [{
id: 1,
firstName: "Bill",
lastName: "Clinton"
}, {
id: 2,
firstName: "Barack",
lastName: "Obama"
}]
Ember provides some support for doing what you want. By default it sets the "active" css class on the selected element. You can find more information about that here: http://emberjs.com/api/classes/Ember.LinkView.html (note that the {{#linkTo}} is just a helper based on the LinkView).
The simplest way to override this behavior, since instead of "active" you want to hide the button, would be to make use of the hide class that comes with Twitter Bootstrap. So your users template would look like:
<script type="text/x-handlebars" data-template-name="users">
<div class='row'>
<div class='span4'>
<table class='table table-striped'>
{{#each model}}
<tr>
<td>{{lastName}}</td>
<td>{{#linkTo 'user' this activeClass="hide"}}<button class="btn" type="button">show</button>{{/linkTo}}</td>
</tr>
{{/each}}
</table>
</div>
<div class='span8'>
{{outlet}}
</div>
</div>
</script>
<script type="text/x-handlebars" data-template-name="user">
<h1>{{firstName}} {{lastName}}</h1>
</script>

Recursive view in handlebars template not working after upgrading to Ember 0.9.6

I used to be able to do something like this to get a nested unordered list of items:
Javascript:
App.Menu = Em.View.extend({
controller: App.menuController.create({}),
tagName: 'ul',
templateName: 'Menu',
pageBinding: 'controller.page'
});
Handlebars:
<li>
{{page.menuTitle}}
{{#each page.childrenPages}}
{{view App.Menu pageBinding="this"}}
{{/each}}
</li>
index.html:
<script type="text/x-handlebars">
{{view App.Menu}}
</script>
Now after updating to the latest Ember.js (0.9.6), only the last item of any given collection of items is being displayed (as a single <li> within the <ul>). In previous versions of Ember, a nested <ul>/<li> list with all items of a given collection was displayed.
I think that instead of a new App.Menu view being created each time through {{#each}}, the existing view is just being reused... any ideas on how I can achieve something similar to the old behavior?
I think the issue is that by creating the controller inside the class App.Menu the controller property is the same for every concrete instance of App.Menu, see Understanding Ember.Object.
I've moved the binding to the specific controller out of the view class and it works, see http://jsfiddle.net/pangratz666/DgG9P/
Handlebars:
<script type="text/x-handlebars" data-template-name="Menu" >
<li>
{{page.menuTitle}}
{{#each page.childrenPages}}
{{view App.Menu pageBinding="this"}}
{{/each}}
</li>
</script>
<script type="text/x-handlebars">
{{view App.Menu pageBinding="App.mainPage" }}
</script>
JavaScript:
App = Ember.Application.create({});
App.Menu = Em.View.extend({
tagName: 'ul',
templateName: 'Menu'
});
App.mainPage = Ember.Object.create({
menuTitle: 'main page',
childrenPages: [Ember.Object.create({
menuTitle: 'subtitle 1',
childrenPages: [Ember.Object.create({
menuTitle: 'subtitle 1 - child 1'
})]
}), Ember.Object.create({
menuTitle: 'subtitle 2',
childrenPages: [Ember.Object.create({
menuTitle: 'subtitle 2 - child 1'
})]
})]
});​

Setting the id on element created by #each helper

How can I set the id for elements created by the Ember.js handelbars helper #each?
If you take a look at http://jsfiddle.net/7QL5z/ I tried to define an id on calling the #each helper.
The resulting HTML looks like this:
<div id="ember191" class="ember-view">
<ul>
<li id="ember349" class="ember-view"> Animal - Animals </li>
<li id="ember389" class="ember-view"> Android - Androids </li>
<li id="ember427" class="ember-view"> Human - Humans </li>
</ul>
</div>
Neither the div nor the ul (that would be the most usable way) get the specified id.
Is it possible at all? If not: how can I access these elements via Ember?
I tried setting 'elementId' instead of 'id' on the '#each' but it didn't seem to work either...
Otherwise you can create an animals list view
App.AnimalsListView = Ember.CollectionView.extend({
tagName: 'ul',
contentBinding: 'App.itemsController.content',
classNames: ['animals'],
elementId: 'animalsList',
itemViewClass: Ember.View.extend({
classNames: ['animal'],
templateName: 'ember/templates/animals/animal',
elementId:Ember.computed(function(){
'animal-'+this.getPath('content.name')
})
})
});
In your animal template:
{{ content.get('name') }} - {{ content.get('pluralized') }}
And finally in your main html file
<script type="text/x-handlebars">
{{ view App.AnimalsListView }}
</script>
You can just specify the id on a view using id="whatever", but it won't work on a {{#each}}
http://jsfiddle.net/tomwhatmore/TJvR6/
Might be a slightly more elegant solution but what I've put in that js fiddle seems to work.