Reputation: 16841
I have a Handlebars template where I'm trying to generate a comma-separated list of items from an array.
In my Handlebars template:
{{#each list}}
{{name}} {{status}},
{{/each}}
I want the ,
to not show up on the last item. Is there a way to do this in Handlebars or do I need to fall back to CSS selectors?
UPDATE: Based on Christopher's suggestion, this is what I ended up implementing:
var attachments = Ember.CollectionView.extend({
content: [],
itemViewClass: Ember.View.extend({
templateName: 'attachments',
tagName: 'span',
isLastItem: function() {
return this.getPath('parentView.content.lastObject') == this.get('content');
}.property('parentView.content.lastObject').cacheable()
})
}));
and in my view:
{{collection attachments}}
and the item view:
{{content.title}} ({{content.size}}) {{#unless isLastItem}}, {{/unless}}
Upvotes: 78
Views: 32579
Reputation: 10552
You can use standard CSS to do this:
li:after {
content: ',';
}
li:last-of-type:after {
content: '';
}
I prefer separate rules, but a more concise if slightly less readable version (from @Jay in the comments):
li:not(:last-of-type):after {
content: ',';
}
Upvotes: 50
Reputation: 7817
With ember 2.7 you can do this after you install ember-truth-helpers
:
ember install ember-truth-helpers
and then your template will look like this:
{{#each model as |e|}}
{{e}}{{#unless (eq e model.lastObject)}}, {{/unless}}
{{/each}}
Upvotes: 6
Reputation: 641
Since Ember v1.11 you are able to get the index of an each using block parameters. In your case this would look something like this:
{{#each list as |item index|}}
{{if index ", "}}{{item.name}} {{item.status}}
{{/each}}
The first index
value will be 0
which will evaluate to false
and will not be added, all subsequent values will evaluate to true
which will prepend a separator.
Upvotes: 63
Reputation: 2064
I know I'm late to the parts but I found a WAYYYY simpler method
{{#unless @last}},{{/unless}}
Upvotes: 178
Reputation: 125
I have created sep block helper:
Handlebars.registerHelper("sep", function(options){
if(options.data.last) {
return options.inverse();
} else {
return options.fn();
}
});
Usage:
{{#each Data}}
{{Text}}{{#sep}},{{/sep}}
{{/each}}
Supports else statement.
Upvotes: 6
Reputation: 3029
I got this working with a modified version of freak3dot's answer:
handlebars.registerHelper('csv', function(items, options) {
return items.map(function(item) {
return options.fn(item)
}).join(', ')
})
(This is a node app, so change the map
accordingly to underscore or whatever if you're building in the browser)
Allows for formatting objects between each comma:
{{#csv players}
{{firstName}} {{lastName}}
{{/csv}}
Edit: Here's a more flexible version. Join a list of things on an arbitrary separator.
handlebars.registerHelper('join', function(items, separator, options) {
return items.map(function(item) {
return options.fn(item)
}).join(separator)
})
And template:
{{#join players ' vs '}
{{firstName}} {{lastName}}
{{/join}}
Upvotes: 3
Reputation: 113
I realize this is a year old but I had a similar problem and wound up here. In my case, I was actually dealing with an array. So, here's my solution.
Handlebars.registerHelper('csv', function(items, options) {
return options.fn(items.join(', '));
});
// then your template would be
{{#csv list}}{{this}}{{/csv}}
I was going for a simple and elegant solution that keeps the csv logic in the template.
Upvotes: 5
Reputation: 1550
Maybe for this context, you should be creating a view for the collection, not an iteration of views on the member items. In this case, a Handlebar iterator is overkill. In my example below, changes to the firstName or lastName on the Person objects will be bound to the list and update the view.
Template:
{{App.listController.csv}}
Javascript:
App = Ember.Application.create();
var Person = Ember.Object.extend({
firstName: null,
lastName: null
});
var bob = Person.create({
firstName: "bob",
lastName: "smith"
});
var ann = Person.create({
firstName: "ann",
lastName: "doe"
});
App.listController = Ember.Object.create({
list: [bob, ann],
csv: Ember.computed(function () {
var arr = [];
this.get('list').forEach(function (item, index, self) {
arr.push(item.firstName + ' ' + item.lastName);
})
return arr.join(',');
}).property('[email protected]', '[email protected]')
});
// any changes to bob or ann will update the view
bob.set('firstName', 'tim');
// adding or removing from the array will update the view
App.listController.get('list').pushObject(Person.create(firstName: "Jack", lastName:"Dunn"});
Below is my original answer, that didn't work for this context.
You should be able to do this with a helper:
Handlebars.registerHelper('csv', function(items, options) {
var out = "";
for(var i=0, l=items.length; i<l; i++) {
out += options.fn(items[i]);
if (i < l - 1) {
out += ',';
}
// might want to add a newline char or something
}
return out;
});
// then your template would be
{{#csv list}} {{name}} {{status}} {{/each}}
Upvotes: 2