How to include top comment per each post as a separate collection in meteor? - meteor

If I have a collection of posts when entering a view of a posts collection, and each of these posts has a collection of comments on them, how could I list the top comment for each post along side of them?
E.g:
this.route('postsList', {
path: '/:posts',
waitOn: function() {
return Meteor.subscribe('posts');
},
data: function() {
return Posts.find({});
}
});
And then I'm iterating through the collection of posts on a page.
{{#each posts}}
{{> postItem}}
{{/each}}
<template name="postItem">
{{title}}
{{topComment}}
</template>
I'd like to put the top comment for each post item.
How can I do this with my templates/subscriptions/publications?
Posts and comments are separate collections.
If it were an embedded collection I could see the ease of use but how to deal with separate collections?
If I published a recent comment type of publication how could I subscribe to it for each post as the most recent one? Or am I thinking the wrong way here?

If you insist on having two totally separated collections, you would get into problems with efficient database queries. What you could do is to have something like recentComment field in your posts collection. Should this field point to id of the most recent comment related to the given post, you could alter your posts subscription to include the recent comments as well:
Meteor.publish('posts', function() {
var listOfIds = _.pluck(Posts.find({}, {fields: recentComment}).fetch(), 'recentComment');
return [
Posts.find(),
Comments.find({_id:{$in:listOfIds}})
];
});
Note that this solution is not fully reactive but it's good enough in most cases.

Related

How do I control two subscriptions to display within a single template?

Sorry kind of new to the Meteor framework!
I Subscribed to two Publish functions. Even if both publish functions target the same Collection, they both have different functions, that I would like to display in one template. How do I achieve this. I have done allot of research but there doesn't seem to be sufficient information on how to achieve.
Following are the two publish functions in code that I subscribe to:
.server/main.js:
Meteor.publish('MerchantTrending', function (categoryMan){
var currentUser = this.userId;
return buyList.find({ who:"Merchant", ownerId:currentUser, itemCategory: { $in: categoryMan } }, {skip: 0, limit: 3});
});
.server/main.js:
Meteor.publish('myTopViews', function (){
var currentUser = this.userId;
return buyList.find({ newArrivalsExpiryDate : {'$lte': new Date()}}, {ownerId:currentUser }, {skip: 0, limit: 3});
});
Following is the subscription function in code
.client/main.js:
Router.route('/MerchantLandingPage', {
subscriptions: function(){
var categoryMan = Session.get('category');
return Meteor.subscribe('MerchantTrending', categoryMan, 'merchantTopViews')
}
});
Now the helper function in code:
Template.MerchantLandingPage.helpers({
'top3Trending' : function () {
return buyList.find({}).fetch();
},
'myTopViews' : function () {
return buyList.find({}).fetch();
}
});
And now the template in code:
<template name="MerchantLandingPage">
##### *** Top three trending items *** ########
{{#each top3Trending}}
ItemName:: <b>{{itemName}}</b> <br>
Item Category:: <b>{{itemCategory}}</b> <br>
Discription:: <b>{{descriptions}}</b> <br>
Image:: {{this.photo._id}} <br>
Date Created:: {{createdDate}} <br>
{{/each}}
<br><br>
############ *** My top Views *** #############
{{#each myTopViews}}
ItemName:: <b>{{itemName}}</b> <br>
Item Category:: <b>{{itemCategory}}</b> <br>
Discription:: <b>{{descriptions}}</b> <br>
Image:: {{this.photo._id}} <br>
Date Created:: {{createdDate}} <br>
{{/each}}
</template>
Both {{#each myTopViews}} and {{#each top3Trending}} successfully display but not correctly. When the variable categoryMan in
Meteor.subscribe('MerchantTrending', categoryMan, 'merchantTopViews')
changes value, it affects both both the outcome of both {{#each myTopViews}} and {{#each top3Trending}}, when its only supposed to affect {{#each top3Trending}}.
How can I get the subscriptions to NOT have an affect on both {{#each myTopViews}} and {{#each top3Trending}}, but only {{#each myTopViews}} in my template?
Thanks for the help!
Welcome to Meteor!
The solution is straight forward once you understand that:
Subscription is just a stream of your DB documents from server into your client's MiniMongoDB. So your 2 subscriptions (it is perfectly fine having several subs on the same Collection) just fill in your client's buyList local collection.
Use of Collections client side is generally independent from how you subscribe the data. So you should simply use a similar selector and possibly options in your top3Trending and myTopViews helpers as you have done for your publication server side (not the same between the 2 helpers, obviously).
As a side note, you do not even need to fetch() the Collection cursor returned by find(), Blaze knows how to handle it directly.
I see a few problems with your code, first of all - your second subscription isn't going to work because your query is wrong:
Meteor.publish('myTopViews', function (){
var currentUser = this.userId;
return buyList.find(
{ ownerId:currentUser, newArrivalsExpiryDate : {'$lte': new Date()}},
{skip: 0, limit: 3}
);
});
You had ownerId: currentUser wrapped in curly braces, it is fixed above.
The way publications/subscriptions work is, if you have two publications sending different data, the template doesn't 'know' the data is coming from two different subscriptions. It will just have access to all of the data being sent by all subscriptions.
For that reason, your two helpers top3trending and myTopViews are returning exactly the same thing. You can delete one of them!
You should move your subscriptions out of the router and in to the Template itself. Here's an article that will help you with that!
There is a package percolate:find-from-publication that permits to filter the data from publications.

Use Flow Router Param in Autoform

Friends,
I'm working on my first app in Meteor and hitting my head against the wall on something...
I have a scenario similar to a blog + comments situation where I have one collection (call it 'posts') and want to associate documents from another collection (call it 'comments').
The best way I know to pass the post._id to the comments as a "postId" field is to use the Flow Router params, since the form is on the 'post/:id' view.
But for the life of me, I cannot figure out how to get "var postId = FlowRouter.getParam('postId');" to pass to Autoform so it populates. I've tried adding it as a function in the schema, as a hook, and as a hidden field in the form on the page (obviously don't want to go that route).
Autoform is amazing and I want to use it, but may have to wire it up the hard way if I can't get this darn value to populate.
Any ideas? I've been hitting my head against the wall on this for a couple of days now.
Thanks!
First, just so we're on the same page, if you have your route is set up like this:
FlowRouter.route('/blog/:postId', {
action: function (params, queryParams) {
FlowLayout.render('layout', { body: 'postTemplate' });
},
});
You are able to call FlowRouter.getParam('postId') from inside the AutoForm hook
You'll need to use an AutoForm hook and have a complete schema. I'm using the package aldeed:collection2 for the schema set up. The postId field must be explicity declared. This code is running on both server and client.
Comments = new Mongo.Collection("comments");
Comments.attachSchema(new SimpleSchema({
comment: {
type: String,
label: "Comment"
},
postId: {
type: String
}
}));
Setting your form up like this is not what you want:
{{> quickForm collection="Comments" id="commentForm" type="insert"}}
That's no good because it will show the postId field in the HTML output. We don't want that, so you have to fully define the form like this:
{{#autoForm collection="Comments" id="commentForm" type="insert"}}
<fieldset>
{{> afQuickField name='comment' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Insert</button>
{{/autoForm}}
Then add the AutoForm hook. This code is running on the client.
var commentHooks = {
before: {
insert: function(doc){
var postId = FlowRouter.getParam('postId');
doc.postId = postId;
return doc;
}
}
};
AutoForm.addHooks(['commentForm'],commentHooks);
Make sure you have your allow/deny rules set up, and it should be working fine.
I was struggling with this same use case as well, and I found this on the Meteor forums: https://forums.meteor.com/t/use-flow-router-param-in-autoform/14433/2
If you're using a schema to build your form (either with the autoform or quickform tags) then you can put it right in there.
For example:
campaignId: {
type: String,
autoform: {
value: function() {
return FlowRouter.getParam('campaignId');
},
type: "hidden"
}
},

Meteor: what is "data context"?

Just starting with Meteor, and going through the Meteor Tutorial by Matthew Platts.
In this tutorial, as well as in the official Meteor Documentation, there are many references to the concept of data context, but I can't seem to find a cristal clear definition / explanation (with examples) of what this is.
For instance, in the 2.4.3 Rendering Data with Helpers section, we read:
Notice that inside of the #each block we go {{name}}, even though
we have no name helper defined. This is because the data context
changes inside the #each block. We loop through each item in the
teams array, and since each item has a “name” attribute Meteor will
automatically create a {{ name }} helper.
UPDATE: Actually, just reading through the end of this very section, the author recommends a resource that makes things pretty clear: A Guide to Meteor Templates & Data Contexts. Still no accurate definition though.
So, in Meteor, what is data context exactly?
I'll try to explain as much as I know, correct me if I'm wrong.
I'll explain using following snippet:
<template name="posts">
{{#each posts}}
<p>{{name}}</p>
{{/each}}
</template>
Let's assume it will display all the posts names from a blog:
First Post
Second post
Third post
..........
..........
I assume you know the concept of helpers and events.
In the above snippet, in general for {{name}}, meteor searches for the helper called name in helpers:
Template.posts.helpers({
name: function(){
return "dummy text";
}
});
If it finds any, it runs that helpers and displays the value.
So here, it outputs:
dummy text
dummy text
dummy text
...........
But if it doesn't find any helpers, it will search in data context.
Let's assume for posts we're returning some data:
Template.posts.helpers({
posts: function(){
return Posts.find().fetch();
}
});
The data we're sending to posts helper looks like this:
{name: "First post", _id: "xxx", ......},
{name: "Second post", _id: "yyy", ......}
{name: "Third post", _id: "zzz", ......}
.................
In the code for {{#each posts}}, it loops through every object and displays name property("First Post","Second Post,"Third Post").
It displays name property because it doesn't find any helper for name, and then it searches in the current data context and found property with the same name name and displays that.
Data context in helpers and events
Let's take the same snippet and add a delete button:
<template name="posts">
{{#each posts}}
<p>{{name}}</p>
<button>Delete Post</button>
{{/each}}
</template>
It displays like below:
First Post <Delete Post>
Second post <Delete Post>
Third post <Delete Post>
..........
..........
In the events:
Template.posts.events({
'click button': function(){
console.log(this)
Posts.remove({_id: this._id });
}
})
Here, when you click on any delete button, it will delete respective post.
Here we're using this._id: this means data context.
this will give you the data that the helper takes as input to display.
For example, if you click on the delete button beside First Post, then in the events it will give you following data as this:
{name: "First post", _id: "xxx", ......},
because that is the data context available when it displays that content.
Same if you click on the button beside second post:
{name: "Second post", _id: "yyy", ......},
And same goes with the helpers too.
I hope this helps at least someone out there.
This is not easy to explain. Like you I used it in tutorial without knowing it. After some research I found the best explanation, a visual one. You can have a look at Discover Meteor article about "Templates & Data Contexts". Hope it will clarify your mind about it.
A data context can be one of 3 things: (unless I've missed some)
A cursor, i.e. the result of a Collection.find()
An array of objects, i.e. just some array or the result of a Collection.find().fetch()
An individual object, i.e. { _id: "123", name: "Orthoclase E. Feldspar" }
{{#each foo}} loops over a cursor or array context and changes the context to an individual object. {{#with bar}} just says which helper to use (in this case bar) to set the data context.
During development, but especially while learning Meteor, it helps to have console.log(this) at the top of your helper code just to double check what the data context is. It is this.

Nested Lists on same Page in MeteorJS

I have three lists on the same page which I want to fill with list-items. The list items are associated to the lists by a field called listId
My publications:
Meteor.publish('lists', function(options) {
return Lists.find({}, options);
});
Meteor.publish('listItems', function(listId) {
return Cards.find({listId: listId});
});
My lists-page.js (this._id param is passed with iron-router.):
Template.listsPage.helpers({
lists: function(){
return Lists.find({listsPageId: this._id});
},
listItems: function(listId){
//??
return ListItems.find({listId: listId})
}
});
My lists-page.html:
<template name="listsPage">
{{#each lists}}
<ul>
<li>{{title}}</li>
{{#each listItems}}
<li>{{listItemTitle}}</li>
{/each}
{{#each lists}}
</template>
Any help is much appreciated!
As a general recommendation, I would avoid collections and publish functions which have generic names like "lists" and "items". I find it makes code incredibly hard to understand. That being said, I think the confusion here is about the name associated with a publish function and the name of the underlying collection.
The name of the publish function (listItems in this case) is used only to facilitate the activation by a subscription, and has no other meaning. The function publishes a set of documents in the Cards collection to the client. Those documents should subsequently be retrieved from the client's local database using the Cards collection.
So I think the code you are looking for is:
listItems: function() {
return Cards.find({listId: this._id});
}
The context in which the listItems helper is run is that of a List document. So the id for listId should be this._id. Let me know if that doesn't work for some reason.

Meteor: how do you display a collection in multiple tables, where each table is a group of objects that have the same key value?

Say you have a bunch of blog posts, and each post has a "title" and "category". How would you render all post titles on a single page, where there is a table for each group of posts that have the same "category" values?
I'm starting by sorting by category, so the posts in the same category are grouped together in the cursor:
Template.postLists.posts = function() {
return Posts.find({}, {sort:{category:1}});
}
But I'm struggling with iterating through this list in a template via {{#each}}, and using Handlebars to detect when I reach a new "category", so that I can start a new , and then end the when I'm at the end of the category.
Am I coming at this the wrong way, or is there an easy way to do this?
The solution I went with was that in my Template handler, instead of returning a cursor using Posts.find(), I created a JSON object that has a structure that can be processed by a handlebars template (an array of category objects, where each category has an array of posts):
Template.postLists.categorizedPosts = function() {
var allPosts = Posts.find({}, {sort:{category:1}}).fetch();
// Then I iterate over allPosts with a loop,
// creating a new array of this structure:
// for ...
var catPosts = [ { category:"cat1", posts: [ {post1}, {post2} ] },
{ category:"cat2", posts: [ {post3}, {post4}, {post5} ] },
// etc...
];
// end loop
return catPosts;
The the template is something like this (but with tables instead of UL, just using UL for a cleaner demo here):
{{#each categorizedPosts}}
{{category}}
<ul>
{{#each posts}}
<li>{{posts.title}}</li>
{{/each}}
</ul>
{{/each}}
Note that when you return an object like this (instead of a cursor object that Posts.find() returns), Meteor's templating engine loses the ability to intelligently detect when only one of the objects in the collection has changed, and patching the DOM instead of completely re-rendering the template. So in this case, the template is completely re-rendered even if a single Posts object is updated in the DB. That's the downside. But the upside is that it works ;)

Resources