Handlebars.js: How to access parent index in nested each? - javascript

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

Related

Ember Handlebars nested each not working

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

Meteor: Build one new object with strings out of four results

I have filtered results from four different publications (with different structure).
Router.route('/anything/:_id', {
name: 'anything',
data: function () {
return {
result1: Collection1.find({'article.reference': this.params._id}),
result2: Collection2.find({'edition.section.reference': this.params._id}),
result3: Collection3.find({'reference': this.params._id}),
result4: Collection4.find({'anything.reference': this.params._id})
};
}
});
Right now I just display them like this:
<h4>Result 1</h4>
{{#each result1}}
{{#each article}}
{{author}}. {{title}}. {{../journal}} ({{year}}):{{edition}}; S.{{pageNumbers}}
{{/each}}
{{/each}}
[...]
<h4>Result 4</h4>
{{#each result4}}
{{#each edition}}
{{#each section}}
{{../../author}}. {{../../book}} ({{../year}}). {{../edition}}. {{../../publisher}}. S.{{pageNumbers}}
{{/each}}
{{/each}}
{{/each}}
I do this for every single result given in the router-data. So I get four sorted lists.
But I need just one big list with all elements in it sorted in general. Therefore I would like to build the string first (right now this is be done in the template) for every result (each Collection will be treated different as the result-string differs) and then sort the resulted array to push this in the template.
So the template would just be:
<h4>Result</h4>
{{#each result}}
<p>{{line}}</p>
{{/each}}
Wouldn't this be a choice?
var cursor = Collection.find();
cursor.forEach(function(doc){
console.log(doc._id);
// fill new object here...
});

Dynamic bound property for an HTMLBars Helper in Ember

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

Handlebars Helper to Compare Values (if v1 === v2), and Render Upper-Level Scope?

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>

Counter for handlebars #each

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

Categories