Meteor Group Collection by Field - meteor

I am trying to return a collection (Postings) grouped by a field (status). I am pretty new to mongo and meteor. The query below gives me the collections grouped by status with # of docs by that status... basically I want the same thing but have the actual documents in there.
Also, I would like to be able to publish/subscribe to this so that they reactivly update. I am creating an admin dashboard that groups all the Postings by current status.
A friend provided the following gist, but it is a bit over my head: https://gist.github.com/ryw/8827179
db.postings.group({ key: {status: 1}, initial: {sum:0}, reduce: function(doc, prev) { prev.sum += 1; } })
Thanks!

If you need all of the documents on the client, then I would just publish the whole collection and let the template code group them.
client
Tracker.autorun(function() {
if (Meteor.user()) {
Meteor.subscribe('allPostings');
}
});
Template.admin.helpers({
postings: function() {
if (Session.get('currentStatus')) {
return Postings.find({status: Session.get('currentStatus')});
}
},
statuses: function() {
return _.uniq(_.pluck(Postings.find().fetch(), 'status'));
}
});
Template.admin.events({
'click .status': function() {
Session.set('currentStatus', String(this));
}
});
<template name="admin">
<div class="left-panel">
<ul>
{{#each statuses}}
<li class="status">{{this}}</li>
{{/each}}
</ul>
</div>
<div class="right-panel">
<ul>
{{#each postings}}
<li>{{message}}</li>
{{/each}}
</ul>
</div>
</template>
server
Meteor.publish('allPostings', function() {
var user = Meteor.users.findOne(this.userId);
if (user.isAdmin) {
return Postings.find();
}
});
I'm assuming you have some way to identify admin users (here I used isAdmin). I am also assuming that a posting has a status and a message.

Instead of using aggregate functions or map reduce operations, you could denormalize your data and store a separate collection of the groups and their counts.
You can update your counts using observe functions as in the following example from the relevant section of meteor docs:
// Keep track of how many administrators are online.
var count = 0;
var query = Users.find({admin: true, onlineNow: true});
var handle = query.observeChanges({
added: function (id, user) {
count++;
console.log(user.name + " brings the total to " + count + " admins.");
},
removed: function () {
count--;
console.log("Lost one. We're now down to " + count + " admins.");
}
});
// After five seconds, stop keeping the count.
setTimeout(function () {handle.stop();}, 5000);
This way, you can present the groups and their counts on a template and it would be reactive.

Related

Search and Sort on the same page

I'm trying to implement sort and search to my items, so i started with sort and it works:
Template
<button class="sort">Sort</button>
{{#each cvs}}
{{> Interviu}}
{{/each}}
JS:
Template.Interviuri.onCreated(function () {
var self = this
self.autorun(function () {
self.sortOrder = new ReactiveVar(-1)
})
Template.Interviuri.helpers({
cvs() {
const instance = Template.instance()
return Cvs.find({}, { sort: { createdAt: instance.sortOrder.get() } })
},
})
Template.Interviuri.events({
'click .sort'(event, instance) {
instance.sortOrder.set(instance.sortOrder.get() * -1)
Next i wanted to implement Search on the same page. So the best way i could found was EasySearch.
But using EasySearch, it means i must change the way my items are being displayed. And then the sort doesn't work anymore.
Template
<div class="searchBox pull-right">
{{> EasySearch.Input index=cvsIndex attributes=searchAttributes }}
</div>
{{#EasySearch.Each index=cvsIndex }}
{{> Interviu}}
{{/EasySearch.Each}}
Collection
CvsIndex = new EasySearch.Index({
collection: Cvs,
fields: ['name'],
engine: new EasySearch.Minimongo()
})
JS
cvsIndex: () => CvsIndex,
How can i have both search and sort working at the same time?
With EasySearch you can use two methods on your index, namely getComponentDict() and getComponentMethods().
With getComponentDict() you can access search definition and options:
index.getComponentDict().get('searchDefinition');
index.getComponentDict().get('searchOptions');
You also have the corresponding setters to change the search definition/option.
getComponentMethods has mehods like
index.getComponentMethods().loadMore(integer);
index.getComponentMethods().hasMoreDocuments();
index.getComponentMethods().addProps(prop, value);
index.getComponentMethods().removeProps([prop])
From that you can set your prop, say index.getComponentMethods().addProp('sort', -1) and then on the index definition, in your MongoDB engine, set the sort from that prop:
index = new EasySearch.index({
// other parameters
engine: new EasySearch.MongoDB({
sort: function(searchObject, options) {
if(options.search.props.sort) {
return parseInt(options.search.props.sort);
}
return 1;
}
})
});
See EasySearch Engines for more info.

How to show correct profile picture Meteorjs

I am using CollectionFs to Upload profile pictures. Uploading and storing the image is successful. I can insert and show the image alright for one user but the
problem is:
For multiple users, when a user visit other users profiles, He sees his own picture rather than seeing the profile owner's picture!
I understand its the mongo query I have in my helper function thats causing the issue but can't just get it to work no matter how many "This._id" I Try.
Here is the javaScript
Router.route('show',{
path:'/list/:_id',
data: function(){
return Main_database.findOne({_id: this.params._id});
}
});
Template.upload.events({
'change #exampleInput':function(event, template){
var file = $('#exampleInput').get(0).files[0];
fsFile = new FS.File(file);
fsFile.metadata = {ownerId:Meteor.userId()}
Images.insert(fsFile,function(err,result){
if(!err){
console.log("New images inserted")
}
})
}
});
Template.profile.helpers({
profilePic: function () {
return Images.find({'metadata.ownerId':Meteor.userId()});
}
});
And here is the html:
<template name="upload">
<div class="container">
<div class="row">
<input type="file"
id="exampleInput">
</div>
</div>
</template>
<template name="profile">
{{#each profilePic}}
<img src="{{this.url}}"
height="400" width="400"
class="img-circle">
{{/each}}
</template>
Thanks
B.s : after following the answer given, I attached the photo in the profile.xxx field. But its still showing the wrong picture. The mongo query is still showing the wrong picture.
here is the code,
Router.route('show',{
path:'/list/:_id',
data: function(){
return Main_database.findOne({_id: this.params._id});
}
});
Template.upload.events({
'change #exampleInput':function(event, template){
var file = $('#exampleInput').get(0).files[0];
newFile = new FS.File(file);
newFile.metadata = {'ownerId':Meteor.userId()};
Images.insert(newFile,function(err,result){
if(!err){
console.log(result._id);
Meteor.users.update(Meteor.userId(),{
$set: {
'profile.profilePic': result._id
}
});
}
});
}
})
// .....................profile pic.............
Template.profile.helpers({
profilePicture: function () {
return Images.find({'_id':Meteor.user().profile.profilePic});
}
});
Finally was able to do it. Being a beginner, I was stuck at uploading images and then showing them for my users for days. Tried almost each method out there, none worked. asked everywhere, none of the answer worked. Finally , a dead simple package from cosio55:autoform-cloudinary worked like magic!!! Just take a look at the problems I faced while using these packages:
1. cfs:ui
{{#with FS.GetFile "images" selectedImageId}}
// image url
{{/with}}
problem:
with this was I couldn't get the selectedImageId .
2. cfs:gridfs
problem :
grid fs stores image in a separate collection. My user list uses iron router to show the user list form another collection. Image was getting uploaded into the images collection. But For the love of my life, I couldn't show them correctly. Each user was seeing his own picture rather than the profile owner's picture. happened because of a wrong mongo query but I couldn't get the right query. Tried attaching the photo in the profile.profilePicture, but same problem of wrong image stayed.
And I had to put the upload photo in a separate page and NOT in the autoform.
3. lepozepo:cloudinary
Problem:
Image uploaded fine. But had problem getting /storing the image url. Couldn't get
And I had to put the upload photo in a separate page and NOT in the autoform.
public_id ????? Got lost there.
4. autoform-file by yogiben
same problem as GridFs.
Finally with this cosio55:autoform-cloudinarypackage took me just a minute to figure things out. A minute vs days of other big name packages!!!!
:smiley:
<div> <img src=" {{image}}" alt=""> Image </div>
just add {{image} in the img source and thats it. The image url is stored in the same collection autoform stores everything.
Cheers Mates.
For the profilePic it will return the same user profile image, instead you should do a query by _id and not metadata.ownerId
To do that you should have a reference for the image in users collection when you insert the image something like:
Images.insert(file, function (err, res) {
if (!err) {
Meteor.users.update(Meteor.userId(),{
$set: {
'profile.profilePic': res._id,
}
});
});
And when you need to display the image you can do something like:
Template.profile.helpers({
profilePic: function () {
return Images.find({'_id':Meteor.user().profile.profilePic});
}
});
First things first: Meteor.user() and Meteor.userId() always shows data for CURRENTLY logged-in user.
So when user wants to see his own profile, it is right to use your current Template helper like this:
Template.profile.helpers({
profilePic: function () {
return Images.find({'metadata.ownerId':Meteor.userId()});
}
});
But when user goes to another user's profile, you should fetch that user's info like this: Meteor.user("another-user-id");
So how to do this? Let's suppose that you have set routing in you Meteor app with Iron Router or Flow Router and for user profile page you have set-up route path something like this: /user/:_id.
Now, you want to publish only this user's data like this in your publications on the server:
Meteor.publish("userData", function(userId){
return = Meteor.users.find({"_id": userId});
});
...and subscribe to this publication on client (we'll use Template-level subscriptions!)
With Iron Router:
var userId;
Template.profile.onCreated(function() {
var self = this;
self.autorun(function() {
userId = Router.current().params._id;
self.subscribe("userData", userId);
}
});
Template.profile.helpers({
profilePic: function () {
return Images.find({'metadata.ownerId': userId});
}
});
With FlowRouter:
var userId;
Template.profile.onCreated(function() {
var self = this;
self.autorun(function() {
userId = FlowRouter.getParam("_id");
self.subscribe("userData", userId);
}
});
Template.profile.helpers({
profilePic: function () {
return Images.find({'metadata.ownerId': userId});
}
});

The most concise and elegant way to break the reactivity of helpers in meteor

I want to use my collection players in an helper... for several reasons I would like this collection will be not reactive. I would like just a first call to the database to display the collection. I tried to use reactivates:false option but in this case the collection remains empty after loading and nothing display.
Template.myGame.helpers({
players: function () {
return Players.find({}, {reactive: false});
}
})
<ul>
{{#each players}}
{{> player}}
{{/each}}
</ul>
You're effectively looking for a way to return data non-reactively, but only once it's ready. This can be achieved with a subscription handle (assuming you've removed "autopublish").
playersSub = Meteor.subscribe('players', ...);
Template.myGame.helpers({
players: function() {
if (playersSub.ready()) {
return Players.find({}, {reactive: false});
} else {
return [];
}
}
});
NB - it is actually possible to get the subscription readiness (non-reactively) without having a handle, by using Players._connection._subscriptions.[SUB_ID].ready, but I wouldn't recommend this as it's not part of the public API.

this.findAll not working on sub-template

When I try to use this.findAll on a template where the selector is in a sub-template, findAll returns nothing.
Here's the HTML:
<template name="products">
{{#each productList}}
{{> product }}
{{/each}}
</template>
<template name="product">
<div class="box">{{name}}</div>
</template>
Here's the JS:
Template.products.helpers({
productList: function() {
var all = Products.find({}).fetch();
return all;
}
});
Template.products.rendered = function(){
var boxes = this.findAll('.box');
console.log(boxes.length);
}
Output of boxes.length is 0. Any ideas how I could get the "box" elements?
According to the docs for findAll:
Only elements inside the template and its sub-templates can match parts of the selector.
So it should work for sub-templates. I tried this with a fixed array of products and it worked, which implies that you are just seeing a delay between the call to rendered and the products being fetched. For example if you do:
Template.products.events({
'click .box': function (e, t) {
var boxes = t.findAll('.box');
console.log(boxes.length);
}
});
Then if you click on one of the boxes, you should see the correct number logged to the console. In short, I think the test may just be invalid. If you are using iron-router, you could try adding a waitOn for the products - that may ensure they arrive before the rendered call.
Here's what I did to run a script after all products have been loaded.
I've added last_product property in all the products.
Template.products.helpers({
productList: function() {
var all = Products.find({}).fetch();
var total = all.length;
var ctr = 0;
all.forEach(function(doc){
doc.last_product = false;
ctr++;
if(ctr == total)
{
doc.last_product = true;
}
return doc;
});
return all;
}
});
Then instead of "Template.products", I used "Template.product" to detect if the last product is rendered. When the last product is rendered, run the script.
Template.product.rendered = function(){
if(this.data.last_product){
var boxes = $('.pbox');
console.log(boxes.length);
}
}
boxes.length now has the correct length.
Thanks to David for the idea!
Here's the correct answer. I've added this to my iron-router route:
action : function () {
if (this.ready()) {
this.render();
}
}
Found the answer from https://stackoverflow.com/a/23576039/130237 while I was trying to solve a different problem.

Possible Meteor bug in manual publish/subscribe scenario

I am encountering a problem with subscribing/publishing in Meteor. I've written an example Meteor app to help narrow the scope of the problem.
I am publishing a collection on the server that is filtered by a parameter passed in through a subscription on the client. This subscription is within an autosubscribe, which leverages a session variable to reactively update the subscriptions.
When changing the state of this particular session variable, the collection on the client isn't getting updated properly, or at least that's what I gather. I've spent the whole day on this and have not found an issue in code I control. I suspect I'm either not understanding how to setup proper pub-sub in Meteor, or there's a problem within Meteor.
To reproduce the problem, start a new Meteor project and use the following (Make sure to remove the autopublish package when trying it out):
HTML (test.html for example):
<head>
<title>pubsubbug</title>
</head>
<body>
{{> main}}
</body>
<template name="main">
<h1>Example showing possible bug in Meteor wrt pub-sub</h1>
<p><button name="showall">show all ({{showall}})</button></p>
<div style="float:left;width:400px;">
<h2>Notes:</h2>
<ul>
{{#each notes}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
<div style="float:left;">
<h2>Notes (copied):</h2>
<ul>
{{#each notes_copied}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
</template>
JS (test.js for example)
if (Meteor.is_client) {
Notes = new Meteor.Collection("notes_collection");
NotesCopied = new Meteor.Collection("notes_collection_copied");
Session.set("showall", false);
Meteor.autosubscribe(function () {
Meteor.subscribe("notes_subscription", Session.get("showall"), function () {
console.log("Notes count:", Notes.find().count());
});
Meteor.subscribe("notes_subscription_copied", Session.get("showall"), function () {
console.log("Bug? This isn't getting called.");
console.log("NotesCopied count:", NotesCopied.find().count());
});
});
Template.main.notes = function () {
return Notes.find();
};
Template.main.notes_copied = function () {
return NotesCopied.find();
};
Template.main.showall = function () {
return Session.get("showall");
};
Template.main.events = {
"click button[name='showall']": function (evt) {
Session.set("showall", !Session.get("showall"));
}
};
}
if (Meteor.is_server) {
Notes = new Meteor.Collection("notes_collection");
var getNotes = function (showall) {
if (showall) {
return Notes.find({}, {sort: {title: 1}});
} else {
return Notes.find({visible: true}, {sort: {title: 1}});
}
};
Meteor.publish("notes_subscription", function (showall) {
// By sending the Notes back with the same uuid as before, the
// client end seems to get confused:
return getNotes(showall);
});
Meteor.publish("notes_subscription_copied", function (showall) {
var notes = getNotes(showall);
var self = this;
// Copy notes into a new notes collection (see NotesCopied on client).
// By generating a new uuid, we don't get an issue with the notes
// on the client getting screwed up:
notes.forEach(function (note) {
var uuid = Meteor.uuid(); // note._id will cause same problem
self.set("notes_collection_copied", uuid, {title: note.title});
});
self.flush();
self.complete();
});
// Add example notes
Meteor.startup(function () {
if (Notes.find().count() === 0) {
Notes.insert({title: "Note #1 (always visible)", visible: true});
Notes.insert({title: "Note #2 (always visible)", visible: true});
Notes.insert({title: "Note #3 (always visible)", visible: true});
Notes.insert({title: "Note #4 (only visible when showall true)", visible: false});
Notes.insert({title: "Note #5 (only visible when showall true)", visible: false});
Notes.insert({title: "Note #6 (only visible when showall true)", visible: false});
}
});
}
An explanation of what you will be seeing:
There will be a button that, when clicked, simply toggles a session variable (showall) between true and false.
Two subscriptions exist (within an autosubscribe), one that exemplifies the bug, and another that is suffixed with _copied, which is a test to demonstrate that when the collection in question is "copied" and new uuid's are assigned, the results are displayed properly. I couldn't figure out what to do with this particular bit of info... I don't want new uuid's.
So basically, when the show all button is clicked repeatedly, the first column Notes: will display incorrect results, and after a few clicks won't show anything.
On the other hand, the second column Notes (copied):, whose uuid's are re-generated each time, shows up correctly.
Is this a bug? Or is there a proper way to do this?
EDIT: Example above live over at http://pubsubbug.meteor.com/
Not experiencing your bug on the developer branch on Windows. Since this is the case, it is a good sign that there is nothing wrong with your code. It appears that you see something buggy regarding the subscriptions and/or how Mongo queries.
Meteor itself is most likely running the stable (= master) release on their hosting, so you will have to try a different approach or wait for a new release. Unless you can support running on devel...

Resources