I am using Mustache and using the data
{ \"names\": [ {\"name\":\"John\"}, {\"name\":\"Mary\"} ] }
My mustache template is:
As long as you're not traveling into more than one array you can store the index on the helper and just increment when the context changes. I'm using something similar to the following:
var data = {
foo: [{a: 'a', 'b': 'a'}, {'a':'b', 'b':'b'}],
i: function indexer() {
indexer.i = indexer.i || 0;
if (indexer.last !== this && indexer.last) {
indexer.i++;
}
indexer.last = this;
return String(indexer.i);
}
};
Mustache.render('{{#foo}}{{a}}{{i}}{{b}}{{i}}{{/foo}}', data);
Great helper here:
// {{#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;
});
Source: https://gist.github.com/1048968
I'm using a pre calculate function to inject '@index' into array.
const putIndexInArray = array => {
let putIndex = (value, index) => {
value['@index'] = index;
return value;
};
if (!Array.isArray(array)) {
return array;
}
return array.map(putIndex);
};
Mustache.render(template, {
array: putIndexInArray(['Homer', 'Marge', 'Lisa'])
});
and then using like this:
{{#array}}
{{@index}}
{{/array}}
My major use case for this was the ability to place an 'active' class on items. I messed around with combining an "equals" helper with and "index_for_each" helper, but it was way too complicated.
Instead, I came up with the following basic "active" helper. It's very specific, but is such a common use case for any menu / select list scenario:
Usage:
<ul>
{{{active 2}}}
{{#each menuItem}}
<li class="{{{active}}}">{{this}}</li>
{{/each}}
</div>
Would make the 3rd menu item have a "class='active'".
The helper is here (this is the CoffeeScript version):
active = 0
cur = 0
handlebars.registerHelper 'active', (item, context) ->
if arguments.length == 2
active = item
cur = 0
''
else if cur++ == active
'active'
A better approach for Mustache would be using a function that gets the index using indexOf
:
var data = {
names: [ {"name":"John"}, {"name":"Mary"} ],
index: function() {
return data.names.indexOf(this);
}
};
var html = Mustache.render(template, data);
In your template:
{{#names}}
{{name}} is {{index}}
{{/names}}
The drawback is that this index
function has the array hard coded data.names
to make it a more dynamic, we can use another function like this:
var data = {
names: [ {"name":"John"}, {"name":"Mary"} ],
index: function() {
return function(array, render) {
return data[array].indexOf(this);
}
}
};
var html = Mustache.render(template, data);
Usage in your template:
{{#names}}
{{name}} is {{#index}}names{{/index}}
{{/names}}
Also a small drawback is that you have to pass the array name to the index function in this example names
, {{#index}}names{{/index}}
.
(Tested in node 4.4.7, moustache 2.2.1.)
If you want a nice clean functional way to do it, that doesn't involve global variables or mutating the objects themselves, use this function;
var withIds = function(list, propertyName, firstIndex) {
firstIndex |= 0;
return list.map( (item, idx) => {
var augmented = Object.create(item);
augmented[propertyName] = idx + firstIndex;
return augmented;
})
};
Use it when you're assembling your view;
var view = {
peopleWithIds: withIds(people, 'id', 1) // add 'id' property to all people, starting at index 1
};
The neat thing about this approach is that it creates a new set of 'viewmodel' objects, using the old set as prototypes. You can read the person.id
just as you would read person.firstName
. However, this function doesn't change your people objects at all, so other code (which might have relied on the ID property not being there) will not be affected.
Algorithm is O(N), so nice and fast.