Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
each_with_index handlebars helper, adds an {{index}} prop accessible from within the block
Handlebars.registerHelper 'each_with_index', (array, fn) ->
buffer = ''
for i in array
item = i
item.index = _i
buffer += fn(item)
buffer
// {{#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;
});
@logankoester
Copy link

logankoester commented Mar 20, 2012

Or as CoffeeScript...

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

@burin
Copy link
Author

burin commented Mar 21, 2012

thanks logan! added it to the gist :)

@Phylodome
Copy link

Phylodome commented Mar 28, 2012

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.

@willywongi
Copy link

willywongi commented Sep 18, 2012

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

@zedtux
Copy link

zedtux commented Oct 29, 2012

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 ?

@buley
Copy link

buley commented Nov 1, 2012

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          

@mlienau
Copy link

mlienau commented Dec 6, 2012

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;

});

@mlienau
Copy link

mlienau commented Dec 6, 2012

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;
});

@rxaviers
Copy link

rxaviers commented Dec 21, 2012

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>

@Hypher
Copy link

Hypher commented Aug 28, 2013

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment