I am leveraging handlebars.js for my templating engine and am looking to make a conditional segment display only if it is the last item in array contained in the templates configuration object.
{
columns: [{<obj>},{<obj>},{<obj>},{<obj>},{<obj>}]
}
I've already pulled in a helper to do some equality/greater/less-than comparisons and have had success identifying the initial item this way but have had no luck accessing my target array's length.
Handlebars.registerHelper('compare', function(lvalue, rvalue, options) {...})
"{{#each_with_index columns}}"+
"<div class='{{#equal index 0}} first{{/equal}}{{#equal index ../columns.length()}} last{{/equal}}'>"+
"</div>"+
"{{/each_with_index}}"
Does anyone know a shortcut, different approach, and some handlebars goodness that will keep me from having to tear into the handlebars.js engine to determine best course?
As of Handlebars v1.1.0, you can now use the @first
and @last
booleans in the each helper for this problem:
{{#each foo}}
<div class='{{#if @first}}first{{/if}}
{{#if @last}} last{{/if}}'>
{{@key}} - {{@index}}
</div>
{{/each}}
A quick helper I wrote to do the trick is:
Handlebars.registerHelper("foreach",function(arr,options) {
if(options.inverse && !arr.length)
return options.inverse(this);
return arr.map(function(item,index) {
item.$index = index;
item.$first = index === 0;
item.$last = index === arr.length-1;
return options.fn(item);
}).join('');
});
Then you can write:
{{#foreach foo}}
<div class='{{#if $first}} first{{/if}}{{#if $last}} last{{/if}}'></div>
{{/foreach}}
Since Handlebars 1.1.0, first and last has become native to the each helper. See ticket #483.
The usage is like Eberanov's helper class:
{{#each foo}}
<div class='{{#if @first}}first{{/if}}{{#if @last}} last{{/if}}'>{{@key}} - {{@index}}</div>
{{/each}}
If you just try to handle the first item of the array, this may help
{{#each data-source}}{{#if @index}},{{/if}}"{{this}}"{{/each}}
@index is provided by the each helper and for the first item, it would be equal to zero and thus can be handled by the if helper.
Solution:
<div class='{{#compare index 1}} first{{/compare}}{{#compare index total}} last{{/compare}}'></div>
Leveraging helpers from the following blog and gist...
https://gist.github.com/2889952
http://doginthehat.com.au/2012/02/comparison-block-helper-for-handlebars-templates/
// {{#each_with_index records}}
// <li class="legend_item{{index}}"><span></span>{{Name}}</li>
// {{/each_with_index}}
Handlebars.registerHelper("each_with_index", function(array, fn) {
var total = array.length;
var buffer = "";
//Better performance: http://jsperf.com/for-vs-foreach/2
for (var i = 0, j = total; i < j; i++) {
var item = array[i];
// stick an index property onto the item, starting with 1, may make configurable later
item.index = i+1;
item.total = total;
// show the inside of the block
buffer += fn(item);
}
// return the finished buffer
return buffer;
});
Handlebars.registerHelper('compare', function(lvalue, rvalue, options) {
if (arguments.length < 3)
throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
operator = options.hash.operator || "==";
var operators = {
'==': function(l,r) { return l == r; },
'===': function(l,r) { return l === r; },
'!=': function(l,r) { return l != r; },
'<': function(l,r) { return l < r; },
'>': function(l,r) { return l > r; },
'<=': function(l,r) { return l <= r; },
'>=': function(l,r) { return l >= r; },
'typeof': function(l,r) { return typeof l == r; }
}
if (!operators[operator])
throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);
var result = operators[operator](lvalue,rvalue);
if( result ) {
return options.fn(this);
} else {
return options.inverse(this);
}
});
Notice the starting index is correctly 1.
I made a little improvements in helper from Matt Brennan, you can use this helper with Objects or Arrays, this solution required Underscore library:
Handlebars.registerHelper("foreach", function(context, options) {
options = _.clone(options);
options.data = _.extend({}, options.hash, options.data);
if (options.inverse && !_.size(context)) {
return options.inverse(this);
}
return _.map(context, function(item, index, list) {
var intIndex = _.indexOf(_.values(list), item);
options.data.key = index;
options.data.index = intIndex;
options.data.isFirst = intIndex === 0;
options.data.isLast = intIndex === _.size(list) - 1;
return options.fn(item, options);
}).join('');
});
Usage:
{{#foreach foo}}
<div class='{{#if @first}}first{{/if}}{{#if @last}} last{{/if}}'>{{@key}} - {{@index}}</div>
{{/foreach}}