Meteorjs loop on id and get object - meteor

I have a collection A which have an embedded array of different IDs.
These IDs are id of collection B.
How can I loop inside a template to all of these B's id and get the associated ?
A template :
<template name="Atemplate">
<h1>Name of A : {{name}}</h1>
{{#each Bs}}
{{> Btemplate}}
{{/each}}
</template>
B template :
<template name="Btemplate">
<h1>Name of B : {{name}}</h1>
</template>
what about B template.js ?

If I understand the question right, the context for Btemplate is an id. So in the name helper you could find the document by that id like so:
Template.Btemplate.helpers({
name: function() {
var doc = B.findOne(String(this));
return doc && doc.name;
}
});
Alternatively (and someone preferably) is to set the Btemplate context to an instance of a B:
Template.Atemplate.helpers({
allBs: function() {
return B.find({_id: {$in: this.Bs}});
}
});
You'd need to modify you A template code as follows:
{{#each allBs}}
{{> Btemplate}}
{{/each}}
And now you don't need to modify the original implementation of Btemplate. I'd also recommend reading the templates articles from here.

Related

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

How do I get templates inserted from custom block helpers to be individually rerendered in Meteor?

When I use the built-in block helper #each, book templates are rerendered individually when changed:
users =
_id: 'foo'
books: [
{name: 'book1'}
{name: 'book2'}
]
<template name="user">
{{#each books}}
{{> book}}
{{/each}}
</template>
<template name="book">
<div>{{name}}</div>
</template>
When the data is changed - the first book name is set to 'bookone' instead of 'book1' - only the book template (the div containing 'book1') is rerendered. This is the desired behavior. When I use a custom block helper, the behavior is different:
<template name="user">
{{#each_with_id}}
{{> book}}
{{/each}}
</template>
<template name="book">
<div data-id="{{_id}}">{{name}}</div>
</template>
Templates.user.each_with_id = (options) ->
html = "
for book, i in this.books
this.name = book.name
html += Spark.labelBranch i.toString(), ->
options.fn this
html
Now when the name of the first book changes, the whole user template is rerendered.
It does not work as you expect, because the implementation of built-in each is based on the cursor.observeChanges feature. You will not be able to achieve the same exact result without using an auxiliary collection of some sort. The idea is quite simple. It seems that you don't have a "books" collection but you can create a client-side-only cache:
Books = new Meteor.Collection(null);
where you will need to put some data dynamically like this:
Users.find({/* probably some filtering here */}).observeCanges({
added: function (id, fields) {
_.each(fields.books, function (book, index) {
Books.insert(_.extend(book, {
owner: id,
index: index,
}));
}
},
changed: function (id, fields) {
Books.remove({
owner:id, name:{
$nin:_.pluck(fields.books, 'name')
},
});
_.each(fields.books, function (book, index) {
Books.update({
owner : id,
name : book.name,
}, {$set:_.extend(book, {
owner : id,
index : index,
})}, {upsert:true});
}
},
removed: function (id) {
Books.remove({owner:id});
},
});
Then instead of each_with_id you will be able to the built-in each with appropriate cursor, e.g.
Books.find({owner:Session.get('currentUserId')}, {sort:{index:1}});
You may also look at this other topic which basically covers the same problem you're asking about.

Meteor template: Pass a parameter into each sub template, and retrieve it in the sub-template helper

I am trying to figure out how to pass a parameter into a sub-template that is in an each block and use the parameter in the sub-template as well as sub-template helper. Here is what I tried so far:
template:
<template name="parent">
{{#each nodes }}
{{> child myParam}}
{{/each}}
</template>
<template name="child">
{{ paramName }}
</template>
js:
Template.parent.nodes = function() {
//return a list
};
Template.parent.myParam = function() {
return {"paramName" : "paramValue"};
};
Template.child.someOtherHelper = function() {
//How do I get access to the "paramName" parameter?
}
So far, it hasn't been working, and it seems somehow mess up my input node list also.
Thanks for help.
When you use {{> child myParam}}, it's calling the child template and associates myParam as current template data context, meaning that in the template you can reference {{paramName}}.
In someOtherHelper you could use this.paramName to retrieve "paramValue".
However, when you're using {{#each nodes}}{{> child}}{{/each}}, it means that you pass the content of the current list item (fetched from a LocalCursor or directly an array item) as the template data of child, and you can reference the list item properties using {{field}} in html or this.field in js.
What's happening here is when you call {{> child myParam}}, the myParam helper content OVERWRITES the current node item as template data, that's why it's messing your node list.
A quick (dirty) trick would be to simply extend the myParam helper so that it also contains the template data from the {{#each}} block.
Template.parent.helpers({
nodes:function(){
// simulate typical collection cursor fetch result
return [{_id:"A"},{_id:"B"},{_id:"C"}];
},
myParam:function(){
// here, this equals the current node item
// so we _.extend our param with it
return _.extend({paramName:"paramValue"},this);
}
});
Template.child.helpers({
someOtherHelper:function(){
return "_id : "+this._id+" ; paramName : "+this.paramName;
}
});
<template name="parent">
{{#each nodes}}
{{> child myParam}}
{{/each}}
</template>
<template name="child">
{{! this is going to output the same stuff}}
<div>_id : {{_id}} ; paramName : {{paramName}}</div>
<div>{{someOtherHelper}}</div>
</template>
Depending on what you're precisely trying to achieve, there might be a better approach but this one gets the job done at least.

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