How to make a function that uses counters to compute values reactively - meteor

I'm new to meteor and I tried to use the tmeasday:publish-counts package to publish some counts. Reactivity works fine out of the box when I just read the counts, but when I use the counts in a Template.helper function, the function doesn't get updated when the counts change.
Here is what I have :
On the server :
Meteor.publish('counters', function() {
Counts.publish(this, 'searches-thisWeek', UserActions.find({
$and: [
{ action: SEARCHES_REGEX },
{ date : { $gte : moment().startOf('week').toDate()} },
{ date : { $lt : moment().toDate()} }
]
}));
Counts.publish(this, 'searches-lastWeek', UserActions.find({
$and: [
{ action: SEARCHES_REGEX },
{ date : { $gte : moment().subtract(1, 'week').startOf('week').toDate()} },
{ date : { $lt : moment().subtract(1, 'week').endOf('week').toDate()} }
]
}));
});
On the client :
Template.dashboard.helpers({
nbSearchesThisWeek : function() {
var variation = Counts.get('searches-thisWeek') - Counts.get('searches-lastWeek');
return {
currentValue : Counts.get('searches-thisWeek'),
orientation : {
sign: (variation > 0) ? '+' : '',
class: (variation > 0) ? 'up' : 'down'
},
variation : variation
};
}
});
In my template I have a :
<td>{{getPublishedCount 'searches'}}</td>
<td>{{#with nbSearchesThisWeek}}{{>variations }}{{/with}}</td>
It uses this subtemplate :
<template name="variations">
<div class="variation col-lg-12">
<span class="variationValue {{orientation.class}} col-lg-6">{{orientation.sign}}{{variation}}</span>
<span class="currentValue col-lg-6">{{currentValue}}</span>
</div>
</template>
The {{getPublishedCount 'searches'}} updates fine. It just gets my "searches" counter and updates anytime the counter changes.
However, the counters in the subtemplate execute fine at startup but never update when any of the dependent counters change.
So my question is, how and where do I make my nbSearchesThisWeek helper react to the changes on the dependent counters values ? I'm very confused when I read the documentation about Deps, Tracking, and ReactiveVars... I didn't really understand where those things should be used to make my use case work...
Thanks for your help.
Philippe

I have created a MeteorPad with code which is as far as possible the same as yours and it is all reactive, without any changes to your code.
Please note that instead of trying to recreate your UserActions collection, I have created two separate collections, UserActions and Players (the names have no significance) to create two counters just like yours. I think once you have seen the code you will agree that for the purpose of checking your code it will do.
Go to this page: http://app-cmrd2ous.meteorpad.com/ open the Chrome or Firefox console and insert the following two records and watch the counters:
UserActions.insert({name: 'bloggs', date: new Date()});
Players.insert({name: 'bloggs again', date: new Date()});
The counters are all reactive.
The meteorpad is here: http://meteorpad.com/pad/n4GwwtPesEZ9rTSuq/Dashboard
All I can suggest is that you look at where I have put the code (whether on the client or the server) - maybe that has something to do with it.
But maybe more likely is that you were running the wrong tests. In other words perhaps you got your dates wrong - you thought you were inserting a record with a date for last week, whereas actually it was two weeks ago or something like that.

Related

Best way to make ReactiveAggregate reactive when data changes on a user

I am currently using ReactiveAggregate to find a subset of Product data, like this:
ReactiveAggregate(this, Products, [
{ $match: {}},
{ $project: {
title: true,
image: true,
variants: {
$filter: {
input: "$variants",
as: "variant",
cond: {
$setIsSubset: [['$$variant.id'], user.variantFollowing]
}
}
}
}}
], { clientCollection: 'aggregateVariants' }
As you can see, a variant is returned if user.variantFollowing matches. When a user 'follows' a product, the ID is added to their object. However, if I understand correctly, this is not triggering ReactiveAggregate to get the new subset when this happens. Only on a full page refresh do I get the correct (latest) data.
Is this the correct way to approach this?
I could store the user's ID as part of the Product object, but the way this would be stored would be nested two places, and I think I would need the Mongo 3.5 updates to then be able to accurately update this. So i'm looking for how to do this in Meteor 1.5+ / Mongo 3.2.12
So, I've been able to get there by adding autorun to the subscription of the aggregate collection, like this:
Template.followedProducts.onCreated(function() {
Meteor.subscribe('products');
this.autorun(() => {
Meteor.subscribe('productsFollowed');
});
... rest of function
For context, productsFollowed is the subscription to retrieve aggregateVariants from the original question.
Thanks to robfallows in this post: https://forums.meteor.com/t/when-and-how-to-use-this-autorun/26075/6

Best practice for meteor dynamic subscription

Is it possible to do something like "filtered subscription" in Meteor: for example if you have a filter on month june and switching to july fetches the new data and subscribes to it?
i tried something like:
Meteor.publish("report", function (query, opt) {
return Report.find({ 'timestamp' : { $gte : query.from, $lt: query.to }}, options);
}
on client with iron router:
HomeController=RouteController.extend({
template:"home",
waitOn:function(){
var dates = getDates();
return Meteor.subscribe("report", dates);
},
fastRender: true
});
but it does not work.
Is there a better method to dynamically subscribe? Or does it just help to navigate with url pattern?
thanks
Is there a better method to dynamically subscribe?
There is an alternative method using template subscriptions, example below. I don't think it's better, just different.
Or does it just help to navigate with url pattern?
If you want to handle the subscriptions in the Router, then storing the subscription query params in the URL does help and has some added benefits in my opinion. But it depends on your desired app behavior.
Using Template Subscriptions approach :
This Meteor Pad example will subscribe to a range of data based on a select :
http://meteorpad.com/pad/26dd8YQevBbA5uNGA/Dynamic%20Subscription
Using Iron Router approach :
This route example will subscribe based on the URL . "items/0/10" will subscribe to the itemData with a range of zero to 10.
Router.route('Items', {
name:'Items',
path:'items/:low/:high',
subscriptions : function(){
var low = parseInt(this.params.low);
var high = parseInt(this.params.high);
return [
Meteor.subscribe("itemData",low,high),
];
},
action: function () {
if (this.ready()) {
this.render();
} else {
this.render('Loading');
}
}
});
I think either approach is fine and depends on your interface. Using the URL is nice because you can provide links directly to the range of data, use forward and back buttons in browser, good for paging lists of data.
The template subscriptions approach might be appropriate to change the data on a graph.
The specific issue you are having might be due to the fact that your getDates() is not reactive, so the subscription is only run once when the route waitOn is first run.

How to use .hasChild and retrieve child using Firebase?

I'm working on a project with the following Firebase structure:
user {
score: 0,
messages : {
key1 { name: name, text: text }
key2 { name: name, text: text }
key...
}
}
I currently have two problems. The first is determining if the user has a "messages" child, if not, then give it one (along with a score), here's the code I came up with so far:
ref.once('value', function (snapshot) {
if (!snapshot.hasChild("messages")) {
ref.set({
score: 0,
messages: 0
});
}
});
The next is retrieving and displaying the messages from the child once the data has been pushed to it like so:
ref.child("messages").on('child_added', function (snapshot) {
var message = snapshot.val();
$('#messagesDiv').prepend(message.text ": " + message.name);
});
but that doesn't seem like it's working either.
Here is the fiddle I made.
I hope you guys can help me fix this problem! The syntax looks right and I read over the docs to find most of the current code.
Thanks in advance!
Setting the initial data
Your code with hasChild seems is executed fine. It just doesn't make a lot of sense. The structure that you're adding leads to:
user {
score: 0,
messages: 0
}
Which is not the same as the structure you've drawn in your question: messages here is just a number, while you want it to be a collection of messages. In addition this change will not trigger your child_added handler, since... you're not adding a child to messages.
You've done the right thing by starting with designing a data structure. The next step is to ensure that you stick to that data structure. So if you want to add an initial message, add the message in the correct structure:
ref.once('value', function (snapshot) {
if (!snapshot.hasChild("messages")) {
ref.set({
score: 0,
messages: { 0: { name: 'puf', text: 'welcome' }}
});
}
});
If you modify the fiddle you will see that the welcome message does show up in your #messagesDiv.
I think this approach is still flawed though. Unless you are really looking to add a welcome message, there is no need to add a messages node. I would just set the score to 0 and the messages node will be added once the user enters their first message:
ref.once('value', function (snapshot) {
if (!snapshot.hasChild("messages")) {
ref.set({ score: 0 });
}
});
Adding new messages
I noticed that you also have the following code in your fiddle:
$('#messageInput').keypress(function (e) {
if (e.keyCode == 13) {
var name = user;
var text = $('#messageInput').val();
// POST
ref.child("messages").set({
name: name,
text: text
});
$('#messageInput').val('');
}
});
The input handling is fine, but once again your code that modifies the Firebase data structure does not follow along with the data structure you started your question with. If we execute this code, the data structure will be:
user {
score: 0,
messages: {
name: 'NotToBrag',
text: 'asked 10 hours ago'
}
}
In case it's not obvious: this structure is missing the crucial key1 or your structure. Oh... and it has also overwritten the welcome message.
When you're adding a child node to a Firebase list, you almost always want to use push:
ref.child("messages").push({
name: name,
text: text
});
With that tiny change, the data structure becomes:
user {
score: 0,
messages: {
0: {
name: 'puf',
text: 'welcome'
},
'-Jh-aFN42nWef-FvgcfS': {
name: 'NotToBrag',
text: 'asked 10 hours ago'
}
}
}
All of these are (as usual) pretty small changes. But together they ensured that your scenario was pretty badly broken. The tricks I used to troubleshoot are incredibly basic and you'd do well to add them to your arsenal and learn to use them.
Debugging trick 1: console.log the data structure
Whenever I first get an MCVE of somebody's problem, I immediately log their data structure:
new Firebase('https://your.firebaseio.com/').once('value', function(s) {
console.log(s.val());
})
As times I might stringify the JSON:
new Firebase('https://your.firebaseio.com/').once('value', function(s) {
console.log(JSON.stringify(s.val()));
})
That last snippet is for example a great way to get the data structure for use in your question.
The snippet only shows the data structure once, so keep running this snippet every time something changes.
Debugging trick 2: remove your data
Your whole hasChild snippet seems aimed to set up your initial data structure for a user. To aid in testing, I frequently removed the data:
new Firebase('https://your.firebaseio.com/myName').remove()
And then when you run the fiddle again, you can see what your hasChild-using code does.
I often put code to clean out (or otherwise reset) my test data either at the start of my fiddles or simply run a snippet from the browser's JavaScript console.

Identity in ractive data arrays

I have an object of message streams that looks like this:
ractive.data.messages:
{
stream_id1: {
some_stream_metadata: "foo",
stream: [
{id: "someid1", message: "message1"},
{id: "someid2", message: "message2"}
]
},
stream_id2: {
some_stream_metadata: "bar",
stream: [
{id: "someid3", message: "message3"},
{id: "someid4", message: "message4"}
]
}
}
main_template:
{{#messages[ current_stream_id ]}}
{{>render_message_stream}}
{{/messages[ current_stream_id ]}}
render_message_stream:
{{#stream}}
<div class="stream">
...someotherstuff...
{{>render_message}}
</div>
{{/stream}}
render_message:
<div class="message">
...someotherstuff...
{{message}}
</div>
I change "current_stream_id" to change the rendered stream of messages.
On updates, i change the contents of the message streams like this:
ractive.merge(
"messages.stream_id1.stream",
new_message_stream,
{
compare: function ( item ) { return item.id; }
});
I also tried the compare: true option instead of the function, with the same results:
Ractive always thinks that these two messages belong effectively to the same DOM element, even though they live in a completely different message stream:
ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message
Problems:
When there are intro/outro animations ractive animates always just the end of the messages stream, even when a message in the middle of the stream was deleted, i need help to make ractive understand which messages are identical.
When i change the current_stream_id, ractive does not rerender the complete {{>render_message_stream}} partial, but goes inside the existing dom and changes the {{message}} field in all existing messages, though this might be good for dom element reuse, this triggers a lot of animations that are wrong. (Eg. it triggers intro/outro animations for the last message in the stream if stream1 has one message more than stream2).
One of these issues has a straightforward answer; unfortunately the other one doesn't.
I'll start with the easy one - the fact that
ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message
belong to the same DOM element. You're correct in that Ractive updates the existing elements rather than removing them and creating new ones - this is a core part of its design. In this case that's undesirable behaviour, but you can work around it like so:
// instead of immediately switching to a new stream ID like this...
ractive.set( 'current_stream_id', 'stream_id2' );
// you can set it to a non-existent ID. That will cause the existing DOM
// to be removed. When you set it to an ID that *does* exist, new DOM
// will be created:
ractive.set( 'current_stream_id', null );
ractive.set( 'current_stream_id', 'stream_id2' );
// or, if you'd like the initial transitions to complete first...
ractive.set( 'current_stream_id', null ).then(function () {
ractive.set( 'current_stream_id', 'stream_id2' );
});
The other issue - that merge() isn't merging, but is instead behaving as though you were doing ractive.set('messages.stream_id1.stream', new_message_stream) - is tougher. The problem is that while you and I know that {{#messages[ current_stream_id ]}} equates to messages.stream_id1 when current_stream_id === 'stream_id1, Ractive doesn't.
What it does know is that we have an expression whose value is determined by messages and current_stream_id. When the value of either of those references changes, the expression is re-evaluated, and if that value changes, the DOM gets updated - but using a standard set(). When you do ractive.merge('messages.stream_id1.stream', ...), Ractive updates all the things that depend on keypaths that are 'upstream' or 'downstream' of messages.stream_id1.stream - which includes messages. So that's how the expression knows that it needs to re-evaluate.
It's possible that a future version of Ractive will be able to handle this case in a smarter fashion. Perhaps it could make a note of arrays that are subject to merge operations, and check evaluator results to see if they're identical to one of those arrays, and if so use merge() rather than set(). Perhaps it could analyse the function in some way to see if the {{#messages[ current_stream_id ]}} section should register itself as a dependant of messages.stream_id1 for as long as current_stream_id === 'stream_id1', rather than the internally-generated ${messages-current_stream_id-} keypath.
None of that helps you in the meantime though. The only way to use merge() in your current situation is to have a separate reference that doesn't use an expression, and a bit of magic with pattern observers:
main_template:
{{#current_messages}} <!-- rather than `messages[ current_stream_id ]` -->
{{>render_message_stream}}
{{/current_messages}}
render_message_stream:
{{#current_message_stream}} <!-- rather than `stream` -->
<div class="stream">
{{>render_message}}
</div>
{{/current_message_stream}}
code:
ractive.observe( 'current_stream_id', function ( id ) {
var current_messages = this.get( 'messages.' + id );
this.set( 'current_messages', current_messages );
// hide existing stream, then show new stream
this.set( 'current_message_stream', null ).then(function () {
this.set( 'current_message_stream', current_messages.stream );
});
});
// when ANY message stream changes, we see if it's the current one - if so, we
// perform a merge on the top-level `current_message_stream` array
ractive.observe( 'messages.*.stream', function ( new_stream, old_stream, keypath, id ) {
// the value of any * characters are passed in as extra arguments, hence `id`
if ( id === this.get( 'current_stream_id' ) ) {
this.merge( 'current_message_stream', new_stream, {
compare: function ( item ) {
return item.id;
}
});
}
});
I've set up a JSFiddle demonstrating this. I hope it makes sense, let me know if not - and sorry I didn't get round to answering this question much sooner.

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

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.

Resources