Create a gist now

Instantly share code, notes, and snippets.

Embed
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

This comment has been minimized.

Show comment
Hide comment
@logankoester

logankoester 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

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

This comment has been minimized.

Show comment
Hide comment
@burin

burin Mar 21, 2012

thanks logan! added it to the gist :)

Owner

burin commented Mar 21, 2012

thanks logan! added it to the gist :)

@Phylodome

This comment has been minimized.

Show comment
Hide comment
@Phylodome

Phylodome 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.

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

This comment has been minimized.

Show comment
Hide comment
@willywongi

willywongi Sep 18, 2012

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

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

@zedtux

This comment has been minimized.

Show comment
Hide comment
@zedtux

zedtux 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 ?

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

This comment has been minimized.

Show comment
Hide comment
@buley

buley 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          

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

This comment has been minimized.

Show comment
Hide comment
@mlienau

mlienau 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 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

This comment has been minimized.

Show comment
Hide comment
@mlienau

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

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

This comment has been minimized.

Show comment
Hide comment
@rxaviers

rxaviers 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>

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

This comment has been minimized.

Show comment
Hide comment
@Hypher

Hypher 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

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