In Handlebars, say I have a collection of names. How can I do
{{#each names}}
{{position}}
{{name}}
{{/each}}
where {{position}} is 1 for the first name, 2 for the second name, etc.? Do I absolutely have to store the position as a key in the collection?
You can do this with the built-in Handlebars #index notation:
{{#each array}}
{{#index}}: {{this}}
{{/each}}
#index will give the (zero-based) index of each item in the given array.
Please note for people using Handlebars with the Razor view engine you must use the notation ##index to avoid compilation errors.
For more built-in helpers see http://handlebarsjs.com/
Handlebars.registerHelper("counter", function (index){
return index + 1;
});
Usage:
{{#each names}}
{{counter #index}}
{{name}}
{{/each}}
While you can't do this with any native Handlebars helper, you can create your own. You can call Handlebars.registerHelper(), passing it a string with the name you want to match (position), and a function that would return the current position count. You can keep track of the position number in the closure where you call registerHelper. Here is an example of how you can register a helper called position that should work with your template example.
JavaScript:
// Using a self-invoking function just to illustrate the closure
(function() {
// Start at 1, name this unique to anything in this closure
var positionCounter = 1;
Handlebars.registerHelper('position', function() {
return positionCounter++;
});
// Compile/render your template here
// It will use the helper whenever it seems position
})();
Here is a jsFiddle to demonstrate: http://jsfiddle.net/willslab/T5uKW/1/
While helpers are documented on handlebarsjs.com, this took some effort for me to figure out how to use them. Thanks for the challenge, and I hope that helps!
only you have to use {{#index}}
example:
{{#.}}
<li class="order{{#index}}"> counter: {{#index}}</li>
{{/.}}
Here is my preferred solution. Register a helper that extends the context to include your position property automatically. Then just use your new block helper (ex. #iter) instead of #each.
Handlebars.registerHelper('iter', function (context, options) {
var ret = "";
for (var i = 0, j = context.length; i < j; i++) {
ret += options.fn($.extend(context[i], {position: i + 1}));
}
return ret;
});
Usage:
{{#iter names}}
{{position}}
{{name}}
{{/iter}}
adapted from http://rockycode.com/blog/handlebars-loop-index/
you can get value just from index inside the list.
{{#each list}}
#index
{{/each}}
Current method,
Since Handlebars API V2 they already include an #number
It's basically an index of the iterator start with 1.
So, This is what you could have done.
{{#foreach names}}
{{#number}}
{{name}}
{{/foreach}}
Reference: https://ghost.org/docs/api/v3/handlebars-themes/helpers/foreach/
This works for me
{{#each posts}}
<tr>
<td>{{#index}} </td>
<td>{{name}}</td>
</tr>
{{/each}}
Related
I don't really know how to explain this... I have a collection that has an array in it and when I go through it I've been setting colors.[0].imageLink and not changing the [0], but now I'd like that to be dynamic depending on the value of a function (in this case the function is viewIndex).
Works, but isn't dynamic:
<h3 class='display-price'>$ {{colors.[0].price}}</h3>
What I'd think would work but doesn't:
<h3 class='display-price'>$ {{colors.[(viewIndex)].price}}</h3>
In the corresponding js file (does return 0):
'viewIndex': function() {
console.log(Template.instance().variation.get());
return Template.instance().variation.get();
}
One way to do what you are trying to do is to define a colorPrice helper that takes colors and viewIndex as parameters as follows:
Template.hello.helpers({
colors() {
return [
{ price: 1},
{ price: 2},
{ price: 3}
];
},
viewIndex(){
return 1;
},
colorPrice(colors, viewIndex){
return colors[viewIndex].price;
}
});
Then, in your template you can use it as follows:
<template name="hello">
${{ colorPrice colors viewIndex }}
</template>
So thank you to Kalman for getting me thinking of a dynamic way to do this using more of the javascript! My solution was essentially to use helpers and the "this" keyword by calling helper functions within {{#with item}}, which passed "this" all of the values of the current item it was on, so essentially this became possible:
variationImage() {
return this.colors[Template.instance().variation.get()].imageLink[0];
},
variationLink() {
return this.colors[Template.instance().variation.get()].referralLink;
},
variationPrice() {
return this.colors[Template.instance().variation.get()].price;
}
And at that point getting those values was as simple as using {{variationPrice}} where I needed the price, etc.
{{#with item}}
<h3 class='display-price'>$ {{variationPrice}}</h3>
{{/with}}
And just to add more about how the variations worked each item has a pretty much random number of variations (since we scrape them) so we have to do something along these lines to render how many there are and set which variation you're looking at:
(where colors is an array containing the different color variations of an item)
{{#each colors}}
<option value='{{#index}}'>Variation {{variationIndex #index}}</option>
{{/each}}
(helper function)
variationIndex(index) {
return index+1;
}
(event function to set variation value)
'change .color-variation-select': function(event, template) {
template.variation.set($(event.target).val());
},
The following Ember Handlebars template renders the 1st row, but does not render the one inside the nested each (or inner each)
<table width="50%">
{{#each someData.items as |item|}}
<tr> <!-- This one RENDERS -->
<td width="25%"><span class="boldTxt">{{item.fld1}}</span></td>
<td width="25%">{{item.fld2}}</td>
</tr>
{{#each item.Reas as |rea|}}
<tr> <!-- This one does not RENDER -->
<td>{{rea}}</td>
</tr>
{{/each}}
{{/each}}
</table>
What is the issue??
I am using Ember version 1.13
Most likely, your problem is that you are using Ember2.0 or above (based on your outer each loop) so your inner each loop has a now invalid (formerly deprecated) format. Also, you are using the same variable name item for both loops, which won't work properly.
http://guides.emberjs.com/v2.1.0/templates/displaying-a-list-of-items/
Just use the same format as in the outer loop:
Change:
{{#each item in item.Reasons}}
To:
{{#each item.Reasons as |reason|}}
EDIT
If your Reas arrays look as you've described in the comments:
item.Reas = [null]; // arrays containing a single `null` value
Then handlebars will show an empty string for these values since Handlebars coerces undefined and null to an empty string.
{{reas}} {{!-- if reas is null then an empty string is printed --}
If you want to show null and undefined values, you can make a simple helper to do so:
// helpers/show-value.js
import Ember from "ember";
export default Ember.Helper.helper(function(params) {
let value = params[0];
if(value === undefined) { return 'undefined'; }
if(value === null) { return 'null'; }
return value;
});
EDIT 2
Based on your explanation in the comment:
Since you are using Ember 1.13, you need a work around to achieve this. Here is one way:
// components/each-keys.js
export default Ember.Component.extend({
object: null, // passed in object
items: Ember.computed('object', function() {
var object = Ember.get(this, 'object');
var keys = Ember.keys(object);
return keys.map(function(key) {
return { key: key, value: object[key]};
})
})
})
Usage:
{{#each-keys object=item.Reas as |key value|}}
<tr>
<td>{{key}}</td>
<td>{{value}}</td>
</tr>
{{/each-keys}}
Here is a running example
If you update to Ember 2.0, which should be pretty straightforward from 1.13 (since 2.0 is basically 1.13 without deprecations) you can use the each-in helper to iterate over an object and get access to both its keys and values. Here is a simple example:
{{#each-in items as |key value|}}
<p>{{key}}: {{value}}</p>
{{/each-in}}
I have a list of attribute names attrs that I would like to iterate over, creating an input with a binding to that attribute on the model for each for each. The below expresses the functionality I'm after:
{{#each attr in attrs}}
{{input value=model.get(attr) }}
{{/each}}
Of course this does not actually work, because you can't make method calls in a helper. I found this question, but the solution doesn't work in my version of Ember (1.11.0)-- I gather because this is undefined in a helper definition as of version 1.11.0 (see here).
How can I make this work?
If you want to just get a property from the model, named by the current value of your each loop, you could write a helper like:
Ember.Handlebars.helper('input-helper',
function(model, attr) {
return Ember.get(model, attr);
}
);
And then use it like:
{{#each attr in attrs}}
{{input-helper model attr}}
{{/each}}
This will print property named to whatever attr holds in that #each iteration from the model
If you want to bound that value to an input, instead of making a helper, you could make a component or a view that will do this for you:
// component that binds 'object.property' to an input field
var InputBinderComponent = Ember.Component.extend({
prop: null, // passed in
obj: null, // passed in
value: null, // local
onValue: function() {
var obj = this.get('obj');
var prop = this.get('prop');
var value = this.get('value');
Ember.set(obj, prop, value);
}.observes('value'),
});
With input-binder component's template to actually contain the input:
{{input type="text" value=value}}
And then use it in the template:
{{#each attr in attrs}}
{{input-binder prop=attr obj=model}}
{{/each}}
Here is an Example
For the actual call, I need something like this:
<script id="messagesTemplate" type="text/x-handlebars-template">
{{#each messages.messages}}
{{#each to}}
{{#ifCond username messages.sessionUserName}}
<h1>{{username}} is equal to {{messages.sessionUserName}}</h1>
{{else}}
<h1>{{username}} is not equal to {{messages.sessionUserName}}</h1>
{{/ifCond}}
{{/each}}
{{/each}}
Where, in the db, 'to' is an array of docs that each have a 'username'..that thus need === the messages.sessionUserName to then template/render HTML for certain values (e.g. {{#if read.marked}} )
"to" : [
{
"user" : ObjectId("53aada6f8b10eb0000ec8a90"),
"username" : "username1",
"updated" : ISODate("2014-07-01T19:39:45Z"),
"_id" : ObjectId("53b30e81b0eff5cb1e2ecb21"),
"read" : {
"marked" : true
}
}
]
Worth noting, both usernameTest & sessionUserName are values appended to the end of the res.json() via express, so they are accessible by messages.usernameTest & messages.sessionUserName, but they are not present in each document..these values are only available in the global parent doc.
res.json({
messages : messages,
sessionUserName: req.session.username,
usernameTest: usernameTest
});
This factor may be responsible for why each of these only render is equal to, but doesn't really make sense for the third (The ../ path segment references the parent template scope):
{{#each messages.messages}}
<h1>{{usernameTest}} is equal to {{sessionUserName}}</h1>
<h1>{{../usernameTest}} is equal to {{../sessionUserName}}</h1>
<h1>{{../messages.usernameTest}} is equal to {{../messages.sessionUserName}}</h1>
Drawing from https://stackoverflow.com/a/9405113/3095287 for the custom comparison helper, the template that follows {{#ifCond v1 v2}} doesn't seem to render upper-level scoped elements..
Handlebars.registerHelper('ifCond', function(v1, v2, options) {
if(v1 === v2) {
return options.fn(this);
}
return options.inverse(this);
});
The ifCond comparison does work outside of an {{#each}} block:
<script id="messagesTemplate" type="text/x-handlebars-template">
{{#ifCond messages.usernameTest messages.sessionUserName}}
<h1>{{messages.usernameTest}} is equal to {{messages.sessionUserName}}</h1>
{{else}}
<h1>{{messages.usernameTest}} is not equal to {{messages.sessionUserName}}</h1>
{{/ifCond}}
{{#each messages.messages}}
..
..as that renders:
username1 is equal to username1
However, it does not work inside of an {{#each}} block:
{{#each messages.messages}}
{{#ifCond messages.usernameTest messages.sessionUserName}}
<h1>{{messages.usernameTest}} is equal to {{messages.sessionUserName}}</h1>
{{else}}
<h1>{{messages.usernameTest}} is not equal to {{messages.sessionUserName}}</h1>
{{/ifCond}}
...
..as it only renders:
is equal to
Even with {{../element}}
{{#each messages.messages}}
{{#ifCond messages.usernameTest messages.sessionUserName}}
<h1>{{../messages.usernameTest}} is equal to {{../messages.sessionUserName}}</h1>
{{else}}
<h1>{{../messages.usernameTest}} is not equal to {{../messages.sessionUserName}}</h1>
{{/ifCond}}
...
..the rendering is:
is equal to
Ok so the main thing is you want to be able to gain access to your top level scope deeper down. Ive done this using a customer helper that adds a little extra to the normal each block.
so here is the normal handle bars each
Handlebars.registerHelper('each', function(context, options) {
var ret = "";
for(var i=0, j=context.length; i<j; i++) {
ret = ret + options.fn(context[i]);
}
return ret;
});
all i do is set 'this' to a property called root and pass it back with the result. To overcome nested loops i check for the existence of my 'root' property and if it exists I pass it along other wise root = this.
Handlebars.registerHelper("myEach", function(context, options) {
var ret = "";
for (var i = 0, j = context.length; i < j; i++) {
if (this.root) {
root = this.root;
} else {
root = this;
}
ret = ret + options.fn(_.extend({}, context[i], {
root: root
}));
}
return ret;
});
Now no matter how deep i am in my loops if i want to use something from the root i just use root.property.
A working codepen can be found here with a simplified version of your example.
EDIT: Ok so 5 minutes later after posting this i read about paths in another templating language and then realise handlebars also has paths. so you don't need to do the above you can just use the relative nested path in your template like below. I'm going to continue using the helper though as I think it is tidier to go root.property rather than adding n many "../" for how nested your are.
here is a working example using the paths
<script type="text/x-handlebars-template" id="messages-template">
Logged in user {{userSession}}
{{#each messages}}
<ul>
<li> Title: {{title}}</li>
<li> Conetent: {{content}}</li>
<li> TO:
<ul>
{{#each to}}
<li>{{user}} {{#ifvalue user ../../userSession}}
thats me
{{else}}
thats not me
{{/ifvalue}}</li>
{{/each}}
</ul>
</li>
</ul>
{{/each}}
</script>
How to access parent #index value in each-loop?
Tried the following:
{{#each company}}
{{#each employee}}
{{../#index}} // how to access company index here?
{{/each}}
{{/each}}
This results to an error:
Expecting 'ID', got 'DATA'
There is a syntax error in the example. The correct syntax is {{#../index}}.
We are looking at ways that we can support custom naming of these parameters in future versions of the language so this is easier to deal with.
https://github.com/wycats/handlebars.js/issues/907
This worked for me:
{{#each company}}
{{setIndex #index}}
{{#each employee}}
{{../index}}
{{/each}}
{{/each}}
JS:
Handlebars.registerHelper('setIndex', function(value){
this.index = Number(value + 1); //I needed human readable index, not zero based
});
Just make sure the company object doesn't have index property.
Answer: {{#../index}}
From the Handlebars docs (see bottom of 'each' helper section):
"Nested each blocks may access the interation variables via depted paths. To access the parent index, for example, {{#../index}} can be used."
NOTE: I'm using v1.3 so it's at least that old.
REMINDER: Helpers are your last best option. 9/10 there is a better solution.
It looks like there's a new syntax in Ember v2.2.0. I tried all the answers here and they didn't work for me.
What I found worked was naming the parent loop's index and the child loop's index.
{{#each parents as |parent parentIndex|}}
{{parentIndex}}
{{#each children as |child childIndex|}}
{{parentIndex}}
{{childIndex}}
{{/each}}
{{/each}}
registe an Helper method:
Handlebars.registerHelper('eachWithIndex', function(cursor, options) {
var fn = options.fn, inverse = options.inverse;
var ret = "";
var idx = -1;
//console.log(cursor);
cursor.forEach(function(item){
idx++;
console.log(item.index);
item.index = idx;
ret+=fn(item);
});
return ret;
});
handlebars template:
{{#eachWithIndex outer}}
{{#each inner}}
{{../index}} // access outer index like this. I used hanlebars V1.3.0
{{index}} // access inner index
{{/each}}
{{/eachWithIndex}}