public
Last active

each_with_index handlebars helper, adds an {{index}} prop accessible from within the block

  • Download Gist
each_with_index.coffee
CoffeeScript
1 2 3 4 5 6 7
Handlebars.registerHelper 'each_with_index', (array, fn) ->
buffer = ''
for i in array
item = i
item.index = _i
buffer += fn(item)
buffer
each_with_index.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// {{#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 buffer = "";
for (var i = 0, j = array.length; 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;
 
// show the inside of the block
buffer += fn(item);
}
 
// return the finished buffer
return buffer;
 
});

Or as CoffeeScript...

Handlebars.registerHelper 'each_with_index', (array, fn) ->
  buffer = ''
  for i in array
    item = i
    item.index = _i
    buffer += fn(item)
  buffer

thanks logan! added it to the gist :)

A more generalized/flexible approach to this would be:

Handlebars.registerHelper 'eachWithFn', (items, opts) ->
    _(items).map((item, i, items) =>
        opts.hash.fn.apply opts, [item, i, items]
        opts.fn(item)
    ).join("")

You can then pass an arbitrary function in the context such as:

ctx=
    items: items 
    fn: (item, i, items) ->
        item.index = if not @hash.zeroIndexed then i + 1 else i

In the template this would look something like:

{{#eachWithFn items fn=fn zeroIndexed=false}}
    <p>{{index}}</p>
{{/eachWithFn}}

If you're going to create a helper, it's ill advised to implement a one-off where a more generally applicable solution exists. Imagine having a helper for each kind of list manipulation you'd want to perform. Definite code creep.

Edit: This code relies on the underscore.js library, but jQuery's map would work similarly.

I would use an array instead of a string concatenation (see my fork of this gist).

I tried it in a Rails 3.2 application with handlebars_assets 0.6.6.

I just copied/pasted the coffeescript version and use it in my template, but when rendering the template, Firebug catch the following Javascript error:

TypeError: fn is not a function
buffer += fn(item);

Could you please confirm me that it's working on your machine ?

The error message is quite accurate in this case. The second callback isn't a function, it's an object. Try this:

      Handlebars.registerHelper 'each_with_index', (array, obj) ->
        buffer = ''
        for i in array
          item = i
          item.index = _i
          buffer += obj.fn(item)
        buffer          

I was getting the same error using handlebars-1.0.rc.1.js. The following is the solution I came up with. I like the zero based index better

Handlebars.registerHelper("each_with_index", function (array, data) {
// "array" is the name of the array you want to iterate over
array = data.contexts[0][array];

    var buffer = "";
    for (var i = 0, j = array.length; i < j; i++) {
        var item = array[i];

        // if item is already an object just add the index property
        if (typeof (item) == 'object') {
            item['index'] = i;
        } else { // make an object and add the index property
            item = {
                value: item, // TODO: make the name of the item configurable
                index: i
            };
        }

        buffer += data.fn(item);
    }

    // return the finished buffer
    return buffer;

});
Handlebars.registerHelper("each_with_index", function (array, data) {

    array = data.contexts[0][array];

    var buffer = "";
    for (var i = 0, j = array.length; i < j; i++) {
        var item = array[i];

        // if item is already an object just add the index property
        if (typeof (item) == 'object') {
            item['index'] = i;
        } else { // make an object and add the index property
            item = {
                value: item, // TODO: make the name of the item configurable
                index: i
            };
        }

        buffer += data.fn(item);
    }

    // return the finished buffer
    return buffer;
});

For node:

Handlebars.registerHelper( "join", function( array, sep, options ) {
    return array.map(function( item ) {
        return options.fn( item );
    }).join( sep );
});
<p>
    {{#join companies "<br>"}}
        {{name}}
    {{/join}}
</p>

I don't think it's a good idea to alter template data adding an 'index' ppty to iterated elements. This can be a nasty side effect. Handlebars allow to define custom variables through options.data and createFrame

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.