Handlebars dynamic property lookup in each loop - handlebars.js

In Handlebars 2+, how do I dynamically read a property in a loop like this? objects is an array of objects. keys is an array of strings. I want to loop each key for each object and put the its .foo value in the span.
{{#each objects}}
{{#each keys}}
<span>{{../this.{{this}}.foo}}</span>
{{/each}}
{{/each}}
Is this possible in plain Handlebars 2+? Or...is there a helper that does this?

I don't see the way how it can be done without helper.
With helpers everything is possible (but kind of ugly) in Handlebars.
For example, you could use something like this:
{{#each objects}}
{{#each keys}}
<span>{{lookupProp ../this this 'foo'}}</span>
{{/each}}
{{/each}}
And helper:
Handlebars.registerHelper('lookupProp', function (obj, key, prop) {
return obj[key] && obj[key][prop];
});
Look at the fiddle.
Handlebars has built-in lookup helper since version 3.0.3.

Okay... spent a few hours googling around and find a nice solution, as I had the same issue, but failed to find any...
I was as happy as Larry and jumped off my chair when I finally figured a way to get this working :D
This way, you can access object values with dynamic keys,
Demo object:
var categories = {
onion: { name: 'bar', id: 4 },
apple: { name: 'demo', id: 2 },
carrot: { name: 'foo', id: 3 },
Root: [
{ name: 'apple' },
{ name: 'onion' },
{ name: 'carrot' }
]
};
Instead of trying something like these: (which won't work)
{{#each categories.[#key]}}
or
{{#each categories.[mykey]}}
You can do:
{{#each categories.[Root] as |main-category|}}
{{#each (lookup ../categories main-category.name) as |sub-category|}}
{{sub-category.name}}
{{/each}}
{{/each}}
Hope it will help for someone else too :)

For anyone else that doesn't want to loop you could use with eg.
{{#with (lookup myObject myKeyVar) as |subObject|}}
{{subObject.key}}
{{/with}}

Related

How to set {{#each }} iterate value from helper

Usually for a {{#each}} block, we can set the value directly in it like: {{#each users}}:
user: [
{name: 'foo'},
{name: 'bar'}
]
What I need to do is do be able to set the {{#each}} iteration value from a helper.
I have tried with this helper:
Handlebars.registerHelper('myHelper', function () {
return new Handlebars.SafeString('users');
});
## also tested with:
Handlebars.registerHelper('myHelper', function () {
return 'users';
});
and in my view:
{{#each (myHelper)}} # and {{#each myHelper}}
<p>{{name}}</p>
{{/each}}
But nothing get shown.
I appreciate if anyone can give me some pointers. Thanks in advance!
You can use a built-in function called lookup in order to achieve what you are trying to achieve.
Try:
{{#each (lookup . (myHelper))}}
{{name}}
{{/each}}

Meteor #each #index passed to Dynamic Template?

I'm trying to pass down the index of an item from an {{#each}} loop into a dynamic template, but am lost on how to get it there (in a clean way).
Current code:
{{#each item}}
{{Template.dynamic template=type data=this}}
{{/each}}
With this, {{#index}} is not accessible in the dynamically loaded template.
I also tried using a template helper, but it doesn't appear the index is tracked in the context.
{{#each item}}
{{Template.dynamic template=type data=itemData}}
{{/each}}
Template.items.helpers({
itemData() {
// can't access index in here
return this;
}
});
Can anyone advise on how I can achieve this?
Thanks!
Solved this using the following pattern:
... Template.Items
{{#each items}}
{{>Template.dynamic itemConfig #index}}
{{/each}}
Template.items.helpers({
itemConfig(index) {
const data = this;
data.index = index;
return {
data,
template: this.type //this.type is where im storing my template name
};
},
});
Using the #index as a helper param, and then Blaze uses the object as a config for the dynamic template!
:)
EDIT: I found another solution. Does the same job, and I prefer how it looks.
{{>Template.dynamic template=type data=(templateData #index)}}
Where templateData is essentially the same helper from before but just returns data with an index prop.
{{#each item}}
{{Template.dynamic template=type index=#index}}
{{/each}}
You can use 'index' in dynamic template to access index

Meteor: Spacebars each parameter

I'm new to Meteor.js and have run into a problem.
I am passing in a user object to a profile template e.g.:
{
_id: "D8JpXRQskm3grykjg",
username: "foo",
profile: {communities: ["AkGCakz6mSgMb8qyS", "j8aB3i5iscrC4ehkA"]},
}
<template name="profile">
<h1> {{username}}: {{_id}} </h1>
<h3>Communities</h3>
<hr>
{{#each profile.communities}}
{{> communityItem}}
{{/each}}
</template>
The problem is I've already written a communityItem template that I am using elsewhere which accepts the communityName. Is there a way that I can write a helper function, passing in the communityIds list that would return a list of community names? I would like:
...
{{#each getCommunityNames(profile.communities)}}
{{> communityItem}}
{{/each}}
...
I could very well be approaching the problem the wrong way or not writing in a "Spacebars" fashion. Thanks!
sure you can:
Template.myTemplate.helpers({
getCommunityNames: function(commIds) {
var communities = Communities.find({_id: {$in: commIds}}).fetch();
return _.pluck(communities, 'name'); // returns ['Name 1', 'Name 2'];
}
});
Note, the syntax method param not method(param)
{{#each getCommunityNames profile.communities}}
{{>communityItem}}
{{/each}}

With clause changes keypath

I have the following template:
{{#each Posts}}
{{#with { Post: this } }}
<h2 on-click="doSomething">{{Title}}</h2>
...
{{/with}}
{{/each}}
When I click on the header and doSomething get called, I get "${{Post:Posts-0}}" in event keypath. But I need to get access to the post keypath: "Posts.0" to modify some of its properties. What is the right way to achieve that?
Using a {{#with { a: b } }} block for aliasing in Ractive has some limitations, as it's not true aliasing, it's simply creating an expression with an object literal. It's a needed enhancement to offer true aliasing with something like {{#each Post in Posts}} or {{#each Posts as Post}}.
As far as what you can do today, you can add the keypath to the with block:
{{#with { Post: this, keypath: #keypath } }}
And then either pass in:
<h2 on-click="doSomething:{{keypath}}">{{Title}}</h2>
Or access in the event via this.event.context.keypath. See http://jsfiddle.net/w0npbnrz/ for both of these in action.
You also could use {{#each Posts:p}} in which case you could get the keypath via 'Posts.' + this.event.index.p.

Accessing parent context in Meteor templates and template helpers

I'm running into a template context situation that I'm having a hard time finding a way around.
Here's the template in question:
{{#each votes}}
<h3>{{question}}</h3>
<ul>
{{#each participants}}
<li>
<p>{{email}}</p>
<select name="option-select">
{{#each ../options}}
<option value="{{option}}" class="{{is_selected_option}}">{{option}}</option>
{{/each}}
</select>
</li>
{{/each}}
</ul>
</div>
{{/each}}
And here's an example of a vote document:
{
_id: '32093ufsdj90j234',
question: 'What is the best food of all time?'
options: [
'Pizza',
'Tacos',
'Salad',
'Thai'
],
participants: [
{
id: '2f537a74-3ce0-47b3-80fc-97a4189b2c15'
vote: 0
},
{
id: '8bffafa7-8736-4c4b-968e-82900b82c266'
vote: 1
}
]
}
And here's the issue...
When the template drops into the #each for participants, it no longer has access to the vote context, and therefore doesn't have access to the available options for each vote.
I can somewhat get around this by using the ../options handlebars path to jump back into the parent context, but this doesn't affect the context of the template helper, so this in Template.vote.is_selected_option refers to the current participant, not to the current vote or option, and has no way of knowing which option we are currently iterating through.
Any suggestions on how to get around this, without resorting to DOM manipulation and jQuery shenanigans?
This is a templating issue that has come up multiple times for me. We need a formal way of reaching up the template context hierarchy, in templates, template helpers, and template events.
It seems since Spacebars (Meteor's new template engine), you have access to the parent context within {{#each}} blocks using ../.
In Meteor 0.9.1, you can also write a helper and use Template.parentData() in its implementation.
It's not particularly pretty, but I've done something like this:
<template name='forLoop'>
{{#each augmentedParticipants}}
{{> participant }}
{{/each}}
</template>
<template name='participant'>
...
Question: {{this.parent.question}}
...
</template>
// and in the js:
Template.forLoop.helpers({
augmentedParticipants: function() {
var self = this;
return _.map(self.participants,function(p) {
p.parent = self;
return p;
});
}
});
It's similar to the approach that AVGP suggested, but augments the data at the helper level instead of the db level, which I think is a little lighter-weight.
If you get fancy, you could try to write a Handlebars block helper eachWithParent that would abstract this functionality. Meteor's extensions to handlebars are documented here: https://github.com/meteor/meteor/wiki/Handlebars
I don't know the formal way (if there is one), but to solve your issue, I would link the participants with the parent ID like this:
{
_id: "1234",
question: "Whats up?",
...
participants: [
{
_id: "abcd",
parent_id: "1234",
vote: 0
}
]
}
and use this parent_id in helpers, events, etc. to jump back to the parent using findOne.
That is obviously a sub optimal thing to do, but it's the easiest way that comes to my mind as long as there is no way of referencing the parent context.
Maybe there is a way but it is very well hidden in the inner workings of Meteor without mention in the docs, if so: Please update this question if you find one.
It's a long shot, but maybe this could work:
{{#with ../}}
{{#each options}}
{{this}}
{{/each}}
{{/with}}
This should make life easier.
// use #eachWithParent instead of #each and the parent._id will be passed into the context as parent.
Handlebars.registerHelper('eachWithParent', function(context, options) {
var self = this;
var contextWithParent = _.map(context,function(p) {
p.parent = self._id;
return p;
});
var ret = "";
for(var i=0, j=contextWithParent.length; i<j; i++) {
ret = ret + options.fn( contextWithParent[i] );
}
return ret;
});
Go ahead and change
p.parent = self._id;
to whatever you want to access in the parent context.
Fixed it:
// https://github.com/meteor/handlebars.js/blob/master/lib/handlebars/base.js
// use #eachWithParent instead of #each and the parent._id will be passed into the context as parent.
Handlebars.registerHelper('eachWithParent', function(context, options) {
var self = this;
var contextWithParent = _.map(context,function(p) {
p.parent = self._id;
return p;
});
return Handlebars._default_helpers.each(contextWithParent, options);
});
This works :) with no error
Simply register a global template helper:
Template.registerHelper('parentData',
function () {
return Template.parentData(1);
}
);
and use it in your HTML templates as:
{{#each someRecords}}
{{parentData.someValue}}
{{/each}}
======= EDIT
For Meteor 1.2+, you shold use:
UI.registerHelper('parentData', function() {
return Template.parentData(1);
});
I was stuck in a similar way and found that the Template.parentData() approach suggested in other answers currently doesn't work within event handlers (see https://github.com/meteor/meteor/issues/5491). User Lirbank posted this simple workaround:
Pass the data from the outer context to an html element in the inner context, in the same template:
{{#each companies}}
{{#each employees}}
Do something
{{/each}}
{{/each}}
Now the company ID can be accessed from the event handler with something like
$(event.currentTarget).attr('companyId')
"click .selected":function(e){
var parent_id = $(e.currentTarget).parent().attr("uid");
return parent_id
},
<td id="" class="staff_docs" uid="{{_id}}">
{{#each all_req_doc}}
<div class="icheckbox selected "></div>
{{/each}}
</td>
{{#each parent}}
{{#each child}}
<input type="hidden" name="child_id" value="{{_id}}" />
<input type="hidden" name="parent_id" value="{{../_id}}" />
{{/each}}
{{/each}}
The _id is NOT the _did of the thing, it's the id of the parent!

Resources