How to loop through embedded document in Meteor - meteor

I have an embedded document in a MongoDB collection "Author" as such:
{"Name": "John Doe",
"Country": "U.S.A",
"Books": [
{"BName": "Book1", "Year": "1950"},
{"BName": "Book2", "Year": "1960"}
]
}
I want to access the Books data, loop through it and display each Book in a table.
This is what my JS file looks like
Template.Author.helpers({
author: function() {
//_id of the Author is passed via the URL
return Author.find({"_id": FlowRouter.getParam('_id')})
}
});
This is the HTML for my report
<template name="Author">
<body>
<div class="row">
<div class="col-md-2">
Book Name
</div>
<div class="col-md-2">
Year
</div>
</div>
{{#each author}}
{{> bookdetails}}
{{/each}}
</body>
</template>
<template name="bookdetails">
<div class="row">
<div class="col-md-2">
{{Books.BName}}
</div>
<div class="col-md-2">
{{Books.Year}}
</div>
</div>
</template>
This works when I have only one record in the embedded Books document but not when I have more than one record - which makes sense since 'Books.BName' is ambiguous at that point.
I need to loop through 'Books' and output each BName and Year. This answer is the closest I found to doing this but I get the error:
TypeError: _.value is not a function
This may be because I have an embedded document as opposed to an array.

Your naming is confusing. You've got Book helper that returns an author, and that's what you're passing to the bookdetails template. You should rename the helper to author to reduce confusion. Then you can loop through the actual array of books with {{#each author.Books}}.
Furthermore, in the bookdetails template access the book parameters directly, e.g. {{BName}} instead of {{Books.BName}}.

Related

How to evaluate an expression in polymer (equivalent to "eval" in PHP)

I am building a simple table component with Polymer. I am passing the list of items (an array of objects), and also the list of properties I'd like to display.
So, for example, I want to tell the component: only display the "name" and "number" properties of each object I'll pass to you.
this is how I the component is called:
<cool-table
items="[[projects]]"
columns='[
{"heading": "Project name", "property": "name"},
{"heading": "Project number", "property": "number"}]'>
</cool-table>
So basically it will create a table with two columns, with headings "Project name" and "Project number", and then each row will show project.name and project.number
Here is the component:
<dom-module id="cool-table">
<template>
<div class="table-header">
<template is="dom-repeat" items="{{columns}}" as="column">
<div class="cell">
[[column.heading]]
</div>
</template>
</div>
<div class="table-content">
<template is="dom-repeat" items="{{columns}}" as="column">
<div class="cell">
<!--
here is where I am getting stuck:
I want to to display [[ item.[[column.property]] ]]
So I need to dynamically generate
the name of the property I'll put in the data binding
-->
item.[[column.property]]
</div>
</template>
</div>
</template>
<script>
Polymer({
is: 'cool-table',
properties: {
items: {type: Array},
columns: {type: Array}
}
});
</script>
</dom-module>
In PHP I would do something like eval("\$item.$column.property")
Any idea how this can be achieved with Polymer?
Update:
I am rephrasing the question since I realized I made a mistake in describing the component. I'm using another example where I've simplified everything.
Basically I need to create a component that displays an array of objects. Each object will be on a row, and each object's key will be in a column.
Like this:
object1.name | object1.number | object1.type
object2.name | object2.number | object2.type
object3.name | object3.number | object3.type
So far, so good, that's easy.
Now what I'd like to do is to tell the component which keys need to be displayed, as I don't want to display all of them.
So, I need to tell the component: display only "name" and "number". Then we'll have:
object1.name | object1.number
object2.name | object2.number
object3.name | object3.number
To do that I'm passing the name of the keys I want to display:
<cool-table item="[[items]]" keys="['name', 'number']"></cool-table>
In cool-table.html I would have this:
<!-- loop through all items -->
<template is="dom-repeat" items="{{items}}" as="item">
<div class="row">
<!-- now loop through the keys we want to display -->
<template is="dom-repeat" items="{{keys}}" as="key">
<div class="cell">
<!--
Here I want to display the item's value for that key
for example if key is "name" I want to display item.name
that's what I can't figure out how to do
-->
</div>
</template>
</div>
</template>
Hopefully this now makes more sense. Thanks for hanging there with me!
Ah, I found the solution. I needed to use a computed binding:
<!-- loop through all items -->
<template is="dom-repeat" items="{{items}}" as="item">
<div class="row">
<!-- now loop through the keys we want to display -->
<template is="dom-repeat" items="{{keys}}" as="key">
<div class="cell">
[[getValueFromKey(item, column.key)]]
</div>
</template>
</div>
</template>
<script>
getValueFromKey: function(item, key) {
return item[key];
}
</script>
If I'm understanding your question correctly, you want an inner loop inside the columns array loop, where you iterate through the various keys of your column object?
If so, perhaps this question / answer would help?
Hi Hubert, again, not sure if I understand your question properly now, as I don't see a name property in your data. But I added one, and maybe this is what you are looking for?
Your component would look like this:
<div class="table-header">
<template is="dom-repeat" items="{{columns}}" as="column">
<div class="cell">
[[column.heading]]
</div>
</template>
</div>
<div class="table-content">
<template is="dom-repeat" items="{{columns}}" as="column" filter="{{isPropertyEqName('name')}}">
<div class="cell">
<!--
here is where I am getting stuck:
I want to to display [[ item.[[column.property]] ]]
So I need to dynamically generate
the name of the property I'll put in the data binding
-->
[[column.name]]
</div>
</template>
</div>
</template>
<script>
Polymer({
is: 'cool-table',
properties: {
items: {type: Array},
columns: {type: Array}
},
isPropertyEqName: function(search){
return function(item){
return item.property === 'name';
}
}
});
</dom-module>
and your use of the component would look as follows, where I addded the name property
<cool-table
items="[[projects]]"
columns='[
{"heading": "Project name", "property": "name", "name": "hello"},
{"heading": "Project number", "property": "number", "name": "world"}]'>
</cool-table>

How can i can display fields from an item from Collection B referenced by an id from an item from Collection A?

i have two collections
CategoryCollection = new Mongo.Collection("CategoryCollection");
usersWordpressCollection = new Mongo.Collection("usersWordpressCollection");
CategoryCollection hold posts within a category, within the posts objects there is the Author, which comes as an id. take a look at the image below, draw your attention to posts.author
usersWordpressCollection holds the authors whose id (see image below) is referenced in categoryCollection under posts.author
when i display a post, its coming from categoryCollection so i can get the post, the link etc. i can also get the author id however when it comes to this, i want to return, not the id, but the referenced Author information found in the usersWordpressCollection
latest html
<template name="latest">
<ul>
{{#each articles}}
<li class=" home-latest ">
<span class="label"> <a class="" href="/catsingle/CategorySingle/{{_id}}">{{name}}</a></span>
{{#each posts}}
<div class="card">
<div class="category-img">{{{image}}}</div>
<div class="card-divider">
{{title}}
</div>
<div class="card-section">
<p>
{{> authors}}
</p>
</div>
</div>
</li>
{{/each}}
</ul>
</template>
latest js
Template.latest.helpers({
articles: function () {
var id = FlowRouter.getParam('_id');
return CategoryCollection.find({}, {sort: {date_created: -1}, limit:1}).fetch();
}
});
authors html
<template name="authors">
{{#each authors}}
<p>
{{name}}
</p>
{{/each}}
</template>
authors js
Template.authors.helpers({
authors: function () {
var id = this.author;
console.log(id);
var alias = usersWordpressCollection.findOne(id, {fields: {name: 1} });
return alias.name;
console.log(alias.name);
});
As you can see, this.author is the author id found in categoryCollection posts.author. Im trying to find that id in the usersWordpressCollection matching the author, then i want to display the name field of the Author whose id was matched. but i can't seen to get it working, how can i achieve this.
Since there is only one author per post you don't need to iterate over a cursor of authors, you only need one. So where you have:
{{#each authors}}
<p>
{{name}}
</p>
{{/each}}
Instead use:
{{#with author}}
<p>
{{name}}
</p>
{{/with}}
Now, the data context coming into the authors template will be a post object (since you are within an {{#each posts}}. You can therefore construct your author helper as follows:
Template.authors.helpers({
author(){
return usersWordpressCollection.findOne({ id: parseInt(this.author) });
}
});
Since you've indicated that this.author is a string but the id of your user collection is an integer then you need to convert the type - you can do this directly in your query.

Template.dynamic is not passing data context

I have a list template (#each) in a package that I plan to use across many different collections. Since the template is in a package they are not easily customizable. So I figured this was a great example to use Template.dynamic. Everything works except passing data.
.. I pull the data into the routed page and manipulate the data to match the dynamic template.
Template.usersIndex.helpers({
items: function() {
var users = Meteor.users.find({}).fetch();
var items = users.filter(function(user) {
return user;
}).map(function(user){
return {
name: user.profile.name,
description: user.emails[0].address,
tidbit: "hello"
};
});
return items
}
});
... the data passes perfectly to the usersIndex template.
<template name="usersIndex">
<div id="gc-users-index-navbar">
<h2>Title</h2>
</div>
<div id="gc-users-index" class="inner-content">
{{> Template.dynamic template="strataIndexItem" data="items" }}
</div>
</template>
... But no dice, the dynamic template is rendered but no data.
<template name="themeIndex">
<div class="list-group">
{{#each items }}
<div class="list-group-item">
<div class="row-content">
<div class="least-content">{{tidbit}}</div>
<h4 class="list-group-item-heading">{{name}}</h4>
<p class="list-group-item-text">{{description}}</p>
</div>
</div>
<div class="list-group-separator"></div>
{{/each}}
</div>
</template>
You pass data as string?
{{> Template.dynamic template="strataIndexItem" data="items" }}
You should pass data as variable, without ""
{{> Template.dynamic template="strataIndexItem" data=items }}
Also check if your strataIndexItem template is named strataIndexItem:
<template name="strataIndexItem">
...
</template>

Meteor js not responding

I have been working with meteor.js and have been practicing using examples from getting started with meteor.js Javascript framework. The book is 2 years old and I have been running across some snags. For instance the book tells you to use var to define a variable, but after searching on stack I read that you didn't have to use it and now it works. I'm new so I write programs, run them, debug them and start from scratch to help me learn. For some reason this program that I have done 4 times before today is not running and I cant figure out why.
I keep getting this message :
While building the application:
LendLib.html:37: Expected "template" end tag
...  </div>
after inputting the following code:
<head>
<title>LendLib</title>
</head>
<body>
{{> hello}}
<div id="categories-container">
{{> categories}}
</div>
</body>
<template name="hello">
<h1>Lending Library</h1>{{greeting}}
<input type="button" value="Click" />
<template name="categories">
<div class="title">my stuff</div>
<div id="categories">{{#each lists}}
<div class="category">{{Category}}</div>{{/each}} </div>
</template>
</template>
any advice will be appreciated
dont define templates inside another template.
try like this.
<template name="hello">
<h1>Lending Library</h1>
{{greeting}}
<input type="button" value="Click" />
</template>
<template name="categories">
<div class="title">my stuff</div>
<div id="categories">
{{#each lists}}
<div class="category">
{{Category}}
</div>
{{/each}} 
</div>
</template>
Like pahan said, you cannot define a template within another template. They each have to be separate and un-nested. You CAN, however, call a template from within another template.
<template name="myOtherTemplate">
<h1> Lalalala </h1>
</template>
<template name="myTemplate">
{{> myOtherTemplate}} //injects the contents of myOtherTemplate
</template>
On another note, you also cannot nest Template instances within other Template instances.
Say that you want to register a helper function only after a certain template has been rendered.
Template.myTemplate.rendered = function({
Template.myTemplate.helpers({
key: "value",
anotherKey: "anotherValue"
});
});
^^ This also won't work.

Rendering a template when value in Mongo equals certain value

So I want to render a template that will hold an image when the value of the score in my Players collection equals 500, it right now doesn't render at all even when a player score equals 500, do I need an if statement in my handlebars or something else?
Relevant code I made so far
client
foo.html
<body>
<div class="container">
{{> header}}
<div class="row-fluid">
<div class="span8">
{{> leaderboard}}
</div>
<div class="span4">
{{> champion}}
</div>
</div>
</div>
</body>
<template name="champion">
{{#each winners}}
{{> winner}}
{{/each}}
</template>
<template name="winner">
<img src="gold.jpg" alt="winner">
</template>
foo.js
Template.champion.winners = function () {
return Players.find({score: 500});
};
You marked the Template code as being on the server in your question, but the code with Template.winner.winners should be on the client, not the server. This is most likely the problem. Also, you have two templates named winner, although Meteor should throw an error on the command line if you have duplicate template names.
Finally, this isn't what you asked, but it may come handy for debugging too. You can detect whether the cursor is empty in your templates using Handlebars {{else}}:
{{#each winners}}
{{> winner}}
{{else}}
no winners!
{{/each}}

Resources