How to get a published collection's total count, regardless of a specified limit, on the client? - meteor

I'm using the meteor-paginated-subscription package in my app. On the server, my publication looks like this:
Meteor.publish("posts", function(limit) {
return Posts.find({}, {
limit: limit
});
});
And on the client:
this.subscriptionHandle = Meteor.subscribeWithPagination("posts", 10);
Template.post_list.events = {
'click #load_more': function(event, template) {
template.subscriptionHandle.loadNextPage();
}
};
This works well, but I'd like to hide the #load_more button if all the data is loaded on the client, using a helper like this:
Template.post_list.allPostsLoaded = function () {
allPostsLoaded = Posts.find().count() <= this.subscriptionHandle.loaded();
Session.set('allPostsLoaded', allPostsLoaded);
return allPostsLoaded;
};
The problem is that Posts.find().count() is returning the number of documents loaded on the client, not the number available on the server.
I've looked through the Telescope project, which also uses the meteor-paginated-subscription package, and I see code that does what I want to do:
allPostsLoaded: function(){
allPostsLoaded = this.fetch().length < this.loaded();
Session.set('allPostsLoaded', allPostsLoaded);
return allPostsLoaded;
}
But I'm not sure if it's actually working. Porting their code into mine does not work.
Finally, it does look like Mongo supports what I want to do. The docs say that, by default, cursor.count() ignores the effects of limit.
Seems like all the pieces are there, but I'm having trouble putting them together.

None of the answers do what you really want becase none provide solution that is reactive.
This package does exactly what you want and also reactive.
publish-counts

I think you can see the demo: counts-by-room in meteor doc
It can help you publish the counts of your posts at server and get it at client
You can simply write this:
// server: publish the current size of your post collection
Meteor.publish("counts-by-room", function () {
var self = this;
var count = 0;
var initializing = true;
var handle = Posts.find().observeChanges({
added: function (id) {
count++;
if (!initializing)
self.changed("counts", 'postCounts', {count: count});
},
removed: function (id) {
count--;
self.changed("counts", postCounts, {count: count});
}
});
initializing = false;
self.added("counts", 'postCounts', {count: count});
self.ready();
self.onStop(function () {
handle.stop();
});
});
// client: declare collection to hold count object
Counts = new Mongo.Collection("counts");
// client: subscribe to the count for posts
Tracker.autorun(function () {
Meteor.subscribe("postCounts");
});
// client: simply use findOne, you can get the count object
Counts.findOne()

The idea of sub.loaded() is to help you with exactly this problem.
Posts.count() isn't going to return the right thing because, as you've guessed, on the client, Meteor has no way of knowing the real number of posts that live on the server. But what the client knows is how many posts it's tried to load. That's what that .loaded() tells you, and is why the line this.fetch().length < this.loaded() will tell you if there are more posts on the server or not.

What I would do is write a Meteor server side method that retrieves the count like so:
Meteor.methods({
getPostsCount: function () {
return Posts.find().count();
}
});
Then call it on the client, in observe to make it reactive:
function updatePostCount() {
Meteor.call('getPostsCount', function (err, count) {
Session.set('postCount', count);
});
}
Posts.find().observe({
added: updatePostCount,
removed: updatePostCount
});

Although this question is old, I thought I would provide an answer that ended up working for me. I did not create the solution, I found the basis for it here (so credit where credit is due): Discover Meteor
Anyway, in my case I was trying to get "size" of the database from client side, so I can determine when to hide the "load more" -button. I was using template level subscriptions. Oh and for this solution to work, you need to add reactive-var -package. Here is my (in short):
/*on the server we define the method which returns
the number of posts in total in the database*/
if(Meteor.isServer){
Meteor.methods({
postsTotal: function() {
return PostsCollection.find().count();
}
});
}
/*In the client side we first create the reactive variable*/
if(Meteor.isClient){
Template.Posts.onCreated(function() {
var self = this;
self.totalPosts = new ReactiveVar();
});
/*then in my case, when the user clicks the load more -button,
we call the postsTotal-method and set the returned value as
the value of the totalPosts-reactive variable*/
Template.Posts.events({
'click .load-more': function (event, instance){
Meteor.call('postsTotal', function(error, result){
instance.totalPosts.set(result);
});
}
});
}
Hope this helps someone (I recommend checking the link first). For template level subscriptions, I used this as my guide Discover Meteor - template level subscriptions. This was my first stacked-post and I am just learning Meteor, so please have mercy...:D

Ouch this post is old, anyway maybe it will help someone.
I had exactly the same issue. I managed to solve it with 2 simple lines...
Remember the :
handle = Meteor.subscribeWithPagination('posts', 10);
Well I used in client handle.loaded() and Posts.find().count(). Because when they are different it means that all the posts are loaded. So here is my code :
"click #nextPosts":function(event){
event.preventDefault();
handle.loadNextPage();
if(handle.loaded()!=Posts.find().count()){
$("#nextPosts").fadeOut();
}
}

I had the same problem, and using the publish-counts package didn't work with the subs-manager package. I created a package that can set a reactive server-to-client session, and keep the document count in this session. You can find an example here:
https://github.com/auweb/server-session/#getting-document-count-on-the-client-before-limit-is-applied

I'm doing something like this:
On cliente
Template.postCount.posts = function() {
return Posts.find();
};
Then you create a template:
<template name="postCount">
{{posts.count}}
</template>
Then, whatever you want to show the counter: {{> postCount}}
Much easier than any solution i have seen.

Related

How to use angular-meteor helpers and Meteor.methods together

I use a bunch of helper methods in my project. Some of them require to load the whole collection into the client due the restriction of the api on client side (the distinct function!). I googled the problem and found Meteor.methods as solution.
Can I use helpers (like this.helpers) into Meteor methods? Or how should I dynamically update my data in the frontend?
Can someone give me an example?
Additional information:
class View2 {
constructor($interval, $scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
getOrderNumber(){
this.tempVar = Kafkadata.find().fetch();
this.tempVar2 = _.pluck(this.tempVar, 'orderNumber');
this.tempVar3 = _.uniq(tempVar2, false);
return this.tempVar3;
},
});
}
This is an example for a helpers query. Currently, this code runs client-side. I get ALL orders(tempvar) and then remove ALL data except the ordernumbers(tempvar2). At the end I remove all multiple ordernumbers. ordernumber is not an unique value. Here is an example from one of the collections:
{"orderNumber":"f2a3ed95-fcc3-4da0-9b3f-32cf5ed087f8","value":12480,"booleanValue":false,"intValue":12480,"doubleValue":0,"status":"GOOD","itemName":"MILLING_SPEED","timestamp":1479145734448,"_id":{"_str":"5824f4bc7ff3f0199861f11d"}}
I want to use functions like db.collection.distinct(). But they only work server-side. I think I must use Meteor.methods()to make this thing server-side. But what about this helpers function? How do they work on Meteor.methods()?
EDIT2:
my test:
client-side:
folder:myProject/imports/ui/view1
class View1 {
constructor($interval, $scope, $reactive) {
'ngInject';
$reactive(this).attach($scope);
this.helpers({
// some code
getTestData(){
Meteor.call('allTestData',function(error, result){
if(error){
console.log("error");
}else{
return result;
}
});
}
}); //end of contructor
// this is my testfunction, which is bound to a button!
testFunction(){
Meteor.call('allTestData',function(error, result){
if(error){
alert('Error');
}else{
console.log(result);
}
});
}
on the server-side:
folder:myProject/server/main.js
Meteor.methods({
allTestData:()=>{
var results=Kafkadata.find().count();
console.log(results);
return results;
},
});
and this is my view1.html:
//some code
<md-button ng-click="view1.testFunction()"> It works!</md-button>
<h1>{{view1.getTestData}}</h1>
Why does the button work, but not the helper?
Even though .distinct is supported by Mongo, Meteor does not expose it, even on the server. You just have to use _.uniq as your example shows, but for performance reasons it's better if it runs on the server.
Below is an example of a helper that I use:
aweek: () => {
if (debug)
console.log("Querying weekly appointments for "+this.selectedWeek.format("Do MMMM"));
var weekApts = Appointments.find(
{start: {$gte: new Date(this.getReactively('this.selectedWeek').clone().day(1)),
$lt: new Date(this.getReactively('this.selectedWeek').clone().endOf('week'))},
elderid: Meteor.userId()
}).fetch();
return utils.services.getAWeek(weekApts,utils.data.elderTimeFormat);
},
Note the use of this.getReactively('this.selectedWeek') in the code... basically this tells Meteor to run this helper reactively, so if the value of this.selectedWeek changes, the helper will get re-run. So when I click on a week in the calendar and update the variable, it runs my helper again to get the data.
The utils.services.getAWeek() function does some calculation and formatting on the array of data that makes is easier to display.
If you create a Meteor Method to do processing, I would make it update a collection with its results, and then your helper on the client will update automatically. Best make the technology do the work for you :)

MeteorJS ReactiveVar with database data

Clearly, I am doing something wrong with ReactiveVar because I cannot get it to work as I expect it should.
I am trying to set the value of an ReactiveVar by calling a Meteor.call method which returns the list of usernames. But it does not update when the usernames get changed in another part of the app.
I tried both:
Template.qastatistics.created = function () {
this.trackUsernames = new ReactiveVar(false);
var instance = Template.instance();
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
};
and:
Template.qastatistics.helpers({
users: function () {
var usernames,
instance = Template.instance();
if (instance.trackUsernames.get() === false) {
Meteor.call('trackUsernames', function (err, data) {
instance.trackUsernames.set(data);
});
}
usernames = instance.trackUsernames.get();
...
But neither updates the list of usernames when these change in the database.
Is this even possible with ReactiveVars or have I completely misunderstood them?
EDIT: The usernames I mention are not from Meteor.users collection, but rather a distinct call from another collection that has usernames in it.
Fist of all I would use the onCreated function instead of defining created. That's a little more extendable and it's the new API. created is just kept around for backwards compatibility.
About your problem. You are right, you seem to have misunderstood what ReactiveVars do. They are a reactive data source. That means that when you call myReactiveVar.get in some Tracker.autorun (aka. reactive computation), the computation will rerun whenever myReactiveVar.set is called.
You got the first part right. Spacebars helpers always run inside their own computation. What you got wrong is thinking that a method call is a reactive action. That means, that you could call trackUsernames and set the trackUsernames ReativeVar again and the value in your template would update itself. But a method is only run once. It doesn't do anything fancy with reactivity.
A method call only transfers data once. When you publish a set of documents (like all users) on the other hand, they will be updated dynamically. Whenever a change happens inside that set of published documents, it will be synced to the client. So in general, it's a better idea to use publications and subscriptions to sync data reactively. If you'd want to use a method for the same thing you'd need to do some kind of polling (so your back in the stone-age again).
The easiest way to implement what you are trying to do is to use Meteor.users.find().fetch(). As it says in the docs fetch registers dependencies for all the documents you are fetching if it's being called from within a reactive computation.
First you'll need to properly set up your publications, so that users can see other users usernames. I'll leave that to you. Then you need to reimplement your helper
Template.qastatistics.helpers({
users: function () {
var usernames = _.pluck(Meteor.users.find().fetch(), 'username');
...
Thanks to suggestions from #kyll, I managed to get what I wanted by publishing the data I need:
server:
cope.publish.usernamesID = Random.id();
Meteor.publish("itemsusernames", function () {
self = this;
var initializing = true;
var handle = Items.find().observeChanges({
added: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
changed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
},
removed: function (id) {
!initializing && self.changed(
"itemsusernames",
cope.publish.usernamesID,
Items.distinct("p4User"));
}
});
initializing = false;
self.added("itemsusernames", cope.publish.usernamesID, Items.distinct("p4User"));
self.ready();
self.onStop(function () {
handle.stop();
});
});
client:
users: function () {
var usernames = [],
oUsernames = ItemsUsernames.find().fetch();
if (!oUsernames[0]) return [];
usernames = $.map(oUsernames[0], function (value, index) {
if (!isNaN(index)) {
return [value];
}
});
...
And ofcourse: ItemsUsernames = new Mongo.Collection("itemsusernames");

Meteor.subscribe is skipped, doesn’t work in Tracker.autorun

I’m using user’s profile to publish specific data to client.
However the subscribe doesn’t work and it’s killing me.
Although the Tracker.autorun() runs, the server doesn’t run any publish.
Tracker.autorun(function () {
console.log(‘autorun is running');
console.log(Meteor.user()) //to be reactive to user's update
Meteor.subscribe(“Lists”,{
onStop:function() {
console.log('subscribe call back onStop');
},
onReady: function(){
console.log('subscribe call back onReady');
});
console.log(Lists.find().count());
}
Meteor.publish('Lists', function(){
console.log('publish is running’);
var list = Meteor.users.findOne({_id: this.userId}).list;
return Lists.find({_id: {$in: list}});
}
p.s if I type Meteor.subscribe( “Lists”) in Chrome's console line, it works and server run publish normally.
p.s The Lists collection didn't change. Just publishing a different set. Is it the reason?
Thank for your reading.
If you have a close look at the docs for subscribe, you'll find this note in a section about reactive computations:
However, if the next iteration of your run function subscribes to the same record set (same name and parameters), Meteor is smart enough to skip a wasteful unsubscribe/resubscribe.
So because you are always calling subscribe with the same arguments, meteor isn't actually restarting it. The trick is just to pass extra parameters to defeat this "optimization". For example:
Tracker.autorun(function() {
var user = Meteor.user();
var list = user && user.list;
if (!_.isEmpty(list)) {
Meteor.subscribe('Lists', list, function() {
console.log(Lists.find().count());
});
}
});
Here we are extracting the list variable from the user (assuming it's published) and using it as an extra parameter to force the subscription to rerun. If it isn't published, you could just use a random id like this:
Tracker.autorun(function() {
var user = Meteor.user();
Meteor.subscribe('Lists', Random.id(), function() {
console.log(Lists.find().count());
});
});
This should also work but may be a little less efficient because it will fire whenever any property of the user changes.
I think you should run the "console log" after the subscribe and the publish methods return something, if you do somenthing inmediatly is should not work because the server is not returning anything yet, add a callback to the subscription.
Tracker.autorun( function() {
Meteor.subscribe( "List", function() {
console.log( "okok" );
// do your magic
}
};
hope it help, sorry about my english =)

How to 'transform' data returned via a Meteor.publish?

Meteor Collections have a transform ability that allows behavior to be attached to the objects returned from mongo.
We want to have autopublish turned off so the client does not have access to the database collections, but we still want the transform functionality.
We are sending data to the client with a more explicit Meteor.publish/Meteor.subscribe or the RPC mechanism ( Meteor.call()/Meteor.methods() )
How can we have the Meteor client automatically apply a transform like it will when retrieving data directly with the Meteor.Collection methods?
While you can't directly use transforms, there is a way to transform the result of a database query before publishing it. This is what the "publish the current size of a collection" example describes here.
It took me a while to figure out a really simple application of that, so maybe my code will help you, too:
Meteor.publish("publicationsWithHTML", function (data) {
var self = this;
Publications
.find()
.forEach(function(entry) {
addSomeHTML(entry); // this function changes the content of entry
self.added("publications", entry._id, entry);
});
self.ready();
});
On the client you subscribe to this:
Meteor.subscribe("publicationsWithHTML");
But your model still need to create a collection (on both sides) that is called 'publications':
Publications = new Meteor.Collection('publications');
Mind you, this is not a very good example, as it doesn't maintain the reactivity. But I found the count example a bit confusing at first, so maybe you'll find it helpful.
(Meteor 0.7.0.1) - meteor does allow behavior to be attached to the objects returned via the pub/sub.
This is from a pull request I submitted to the meteor project.
Todos = new Meteor.Collection('todos', {
// transform allows behavior to be attached to the objects returned via the pub/sub communication.
transform : function(todo) {
todo.update = function(change) {
Meteor.call('Todos_update', this._id, change);
},
todo.remove = function() {
Meteor.call('Todos_remove', this._id);
}
return todo;
}
});
todosHandle = Meteor.subscribe('todos');
Any objects returned via the 'todos' topic will have the update() and the remove() function - which is exactly what I want: I now attach behavior to the returned data.
Try:
let transformTodo = (fields) => {
fields._pubType = 'todos';
return fields;
};
Meteor.publish('todos', function() {
let subHandle = Todos
.find()
.observeChanges({
added: (id, fields) => {
fields = transformTodo(fields);
this.added('todos', id, fields);
},
changed: (id, fields) => {
fields = transformTodo(fields);
this.changed('todos', id, fields);
},
removed: (id) => {
this.removed('todos', id);
}
});
this.ready();
this.onStop(() => {
subHandle.stop();
});
});
Currently, you can't apply transforms on the server to published collections. See this question for more details. That leaves you with either transforming the data on the client, or using a meteor method. In a method, you can have the server do whatever you want to the data.
In one of my projects, we perform our most expensive query (it joins several collections, denormalizes the documents, and trims unnecessary fields) via a method call. It isn't reactive, but it greatly simplifies our code because all of the transformation happens on the server.
To extend #Christian Fritz answer, with Reactive Solution using peerlibrary:reactive-publish
Meteor.publish("todos", function() {
const self = this;
return this.autorun(function(computation) {
// Loop over each document in collection
todo.find().forEach(function(entry) {
// Add function to transform / modify each document here
self.added("todos", entry._id, entry);
});
});
});

Why is Meteor removing an object when observing a collection?

Background
I have "Lists" and "Products" collections, Products belong to a List
A List has a description, from which products are generated
On startup, a new List is created that's unique for that visitor
The List id is stored in the Session
What I Want
I want Products to be generated when the description of a List changes.
The first step is that when the list for the current visitor is changed, I want a new product to be inserted.
I get the feeling I'm going about this totally wrong...
The Problem
The product is inserted, appears in the browser for a split second, then vanishes. It's been removed by Meteor.
Code
Products = new Meteor.Collection("products");
Lists = new Meteor.Collection("lists");
if (Meteor.isClient) {
Meteor.startup(function () {
var my_list_id = Lists.insert({description: "Default list"});
Session.set("my_list", my_list_id);
var observed = Lists.find({_id: my_list_id}).observe({
changed: function (newDocument, oldDocument) {
Products.insert({list: newDocument._id, name: newDocument.description});
}
});
});
toggleElement = function (elementName) {
if(editedElementIs(elementName)) {
var newListDescription = $('textarea').val();
Lists.update(Session.get("my_list"), {description: newListDescription});
setEditedElement("");
} else {
setEditedElement(elementName);
}
};
// Including the rest in case I've misunderstood something.
// I don't see how any of this could cause the issue.
setEditedElement = function (elementName) {
return Session.set("edited_element", elementName);
};
editedElementIs = function (elementName) {
return Session.get("edited_element") == elementName;
};
Handlebars.registerHelper('editedElementIs', editedElementIs);
Handlebars.registerHelper('products', function() {
return Products.find({list: Session.get("my_list")});
});
Template.list_form.listDescription = function () {
return Lists.findOne({_id: Session.get("my_list")}).description;
};
Template.adminbar.events({
'click a#editlist' : function () {
toggleElement("list");
},
'click a#editsidebar' : function () {
toggleElement("sidebar");
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
});
}
What I've Tried
Obviously, I can just do this:
if(editedElementIs(elementName)) {
var newListDescription = $('textarea').val();
Products.insert({list: Session.get("my_list"), name: newListDescription});
Lists.update(Session.get("my_list"), {description: newListDescription});
...
But that's writing clumsy update code that I'd like to house in an observer.
It looked like the product was being removed. So I've observed when a product is removed thus:
Products.find({list:my_list_id}).observe({
removed: function (oldDocument) {
throw error("wow");
console.log("Removed Product" + oldDocument);
}
})
and this observer is called immediately after the Product is inserted.
I get the stack trace:
at Object.Products.find.observe.removed (http://localhost:3000/ListyMeteor.js?2d867b7481df6389658be864b54d864151e87da5:22:15)
at Object.cursor.observeChanges.removed (http://localhost:3000/packages/minimongo/minimongo.js?daa88dc39d67b40b11d6d6809d72361f9ef6a760:909:52)
at http://localhost:3000/packages/minimongo/minimongo.js?daa88dc39d67b40b11d6d6809d72361f9ef6a760:275:15
at _.extend.runTask (http://localhost:3000/packages/meteor/fiber_stubs_client.js?52687e0196bc1d3184ae5ea434a8859275702d94:30:11)
at _.extend.flush (http://localhost:3000/packages/meteor/fiber_stubs_client.js?52687e0196bc1d3184ae5ea434a8859275702d94:58:10)
at _.extend.drain (http://localhost:3000/packages/meteor/fiber_stubs_client.js?52687e0196bc1d3184ae5ea434a8859275702d94:66:12)
at LocalCollection.remove (http://localhost:3000/packages/minimongo/minimongo.js?daa88dc39d67b40b11d6d6809d72361f9ef6a760:500:22)
at Object.self._connection.registerStore.update (http://localhost:3000/packages/mongo-livedata/collection.js?682caa185350aa26968d4ffc274579a33922f0e6:109:32)
at Object.store.(anonymous function) [as update] (http://localhost:3000/packages/livedata/livedata_connection.js?5d09753571656c685bb10c7970eebfbf23d35ef8:404:48)
at http://localhost:3000/packages/livedata/livedata_connection.js?5d09753571656c685bb10c7970eebfbf23d35ef8:984:19
It looks like Meteor is flushing the Products collection on the client side.
I'm clearly misunderstanding how Meteor works.
Any ideas on why this is happening?
Update 1
It looks like this is happening because insert is being called within an observer:
Why does meteor undo changes to collections nested in an observer method?
I'll post back here once I confirm.
Is autosubscribe turned on or off?
If you turn autosubscribe off, it could happen that your client updates the server copy and then on a subsequent update from the server - does not get all the items because its not subscribed to that collection.
Easiest way to check is to query the mongo db -
meteor mongo
Query the mongo db if your product has been added to the document.
If it has, then it is an autosubscribe issue -
You will have to create publish (on server) and subscribe (on client) methods as given here http://docs.meteor.com/#meteor_publish

Resources