Reactive subscription depending on current time in Meteor - meteor

In an application that allows realtime chat between clients, I aim to integrate functionality that allows to define messages that are delivered at future points in time.
In the following example, I am able to insert messages, which are inserted directly into the template. However, I would like to display only messages that have a time smaller or equal to the current time, but automatically display messages that have future time points as soon as the time is reached. For example, if I insert a message from the console, which should be displayed 30 seconds in the future by calling Meteor.call("createMessage", 30000, "hello in 30 seconds"), the message should be automatically displayed after 30 seconds.
I started restricting the query in the publish function to time: {'$lt': new Date()}. However, I have trouble in making this reactive. I unsuccessfully tried several combinations of Tracker.autorun and cursor.observe.
Can anybody give me a hint how I accomplish the desired reactivity within the following running example?
1) html file
<body>
{{> chat}}
</body>
<template name="chat">
{{#each chatMessages}}
{{time}} - {{message}} <br>
{{/each}}
</template>
2) js file
//server and client
Messages = new Mongo.Collection("messages"); //{time: Sun Nov 02 2014 22:17:32 GMT+0100 (CET), message: "hello"}
//server
if(Meteor.isServer){
Meteor.methods({
'createMessage': function(timeOffset, message){
Messages.insert({
time: new Date(new Date().getTime() + timeOffset),
message: message
});
}
});
Meteor.publish("messages", function(){
return Messages.find({
//time: {'$lt': new Date()}
});
});
}
//client
if (Meteor.isClient) {
Template.chat.helpers({
chatMessages: function(){
return Messages.find({});
}
});
Tracker.autorun(function (){
mySub = Meteor.subscribe('messages');
});
}

If Date() was a reactive datasource, it would work but it's not.
You can create a Timer at server side that will handle it. The best design I see id: pick the next message future date and set a Timer with the time difference and also get the next message time. Of course it's dependes on how your application works.
Read more about Timers in Meteor: https://docs.meteor.com/#/full/timers

Reactivity means that the view reflects the data sources used to make that view, updates when those sources change, and only then (as a rule of thumb).
Therefore, if we want to accomplish what is described using reactivity, we must introduce a reactive change when the message goes live (the outlined model does not have such a change).
Two ways to accomplish this that I can think of:
Add an 'isLive' field to the message, and have the server change it at the right time, using timed callbacks and Meteor.startup (to avoid losing messages in case of a reboot). Somewhat complex, but clean and performant (when properly implemented).
Add a currentDate Session variable and use Meteor.setInterval etc. on the client, to keep it as current as you need (Because Session variables are reactive).
In the second alternative, the user could just change their system clock or use the javascript console to access future messages. Also, reactive events with a set interval seem rather contrived, unless that interval is significant to the problem domain itself (like a calendar app changing the 'today' session variable used to draw the red circle, every midnight).
A simpler (better?) non-reactive solution might be to simply render the future messages as hidden elements, and use javascript timers to show them at the right time. But it all depends on what you are dealing with of course.

Related

Publish/subscribe per time interval in Meteor

My situation is as follows:
I have a collection in Mongo which gets updated with new items every few milliseconds, for example log items. I'm displaying these items on the frontend via publish/subscribe in a template, but because of the high volume the list updates so frequently that it's hard to read them. What I would like is to only have the list be updated every (few) seconds. I have tried to use sleep/timeouts on both the client and server side, as indicated here for example, without success so far.
Can I still use publish/subscribe for this or should I switch a polling mechanism with Meteor.setInterval?
Should the time interval part be on the publish or on the subscribe side?
If publish/subscribe is correct for my scenario, how do I only show the updated data every few seconds?
DDP has a rate limiter. it's meant for defeating DDoS attacks, but i suppose it could be repurposed for what you want.
https://blog.meteor.com/rate-limiting-in-meteor-core-762b8ad2412f#.nw6fwlhji
You should be able to use reactive variables and autorun in your Template.name.onCreated to do this :
Template.name.onCreated(function(){
var instance = this;
instance.now = new ReactiveVar( new Date());
instance.autorun(function(){
var test = now.get();
instance.subscribe('yourSubNameHere');
setTimeout(function(){ //will update now and fire the autorun again
instance.now.set(new Date());
},timeoutHere)
});
)};
Although if your collection gets big I'd advise doing this with a limit in your publication maybe?

In Meteor, is there a way to detect when data is fully loaded to the browser?

This problem probably has been asked before, but I cannot seem to figure out an easy way to do it.
I have a Meteor page that shows messages posted by users in chronological order (newest message at the bottom). I want the page to:
#1) Subscribe to the messages (using publish/subscribe) based on a parameter supplied in the URL
#2) Render the page
#3) Display the messages on the page
#4) Scroll to the bottom.
There's no apparent way to know when 1, 2, and 3 are complete before initiating the scroll to bottom. Meteor does have an observe/added function to do an event when new messages are added to a subscription, however that's only when documents are insertted into Mongo, and it does not trigger when displaying results to the initial subscription.
I'll show code for the steps above:
#1: Subscribe to the messages using publish/subscribe: in /client/messages.js
Template.messages.created = function() {
this.autorun( function() {
this.subscription = Meteor.subscribe("messages", Router.current().params.category);
}.bind(this));
};
#2 Render the page, in /client/messages.html
<template name="messages">
{{#each messages}}
{{messageText}}<br><br>
{{/each}}
</template>
#3: Display the mssages on the page: /client/messages.js
Template.messages.helpers({
messages: function() {
var category = Router.current().params.category;
return Messages.find({category: category}, { sort: { messageDate: 1 } });
},
});
All this works, but does not automatically scroll to the bottom.
I cannot add a jquery scroll command to the Meteor onRendered section because it runs BEFORE the data is written to the DOM. I can do a Meteor.setTimeout to wait 1 second before scrolling to the bottom, but does not work if it takes longer than a second to fill the DOM with subscribed data.
Here's another thing complicating the matter. I am supplying the category variable in the URL. When the client selects another category, using Meteor/Blaze pathFor,
{{pathFor 'messages' channelid='new'}}
the browser does not reload/rerender the page, it simply updates the URL parameter which triggers the autorun to change what messages it has subscribed to. It simply writes the new data to the DOM. Because of this, I cannot to a jquery $(document).ready to detect whether the page is ready (because the page is always ready), and I cannot use some fancy handlebars thing like {{scrollToBottom}} at the end of my {{#each}} in messages.html because that it not re-run.
So, is there a simple way to detect when Meteor/Blaze completely finishes writing new data to the browser?
If I understand correctly, you really just want to know when all of your data is published from the server to the client (so you can then do something, in this case, scroll to the bottom).
All calls to Meteor.subscribe() return a subscription handle. This has a ready() method, that tells you all the data has been sent. This includes those done at a template level (which you might want to consider if appropriate for your use-case). More information here:
http://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe
There are several ways to use this - you need to decide what is appropriate for you. As ready() is reactive, you can just use this. However, you might find it easier to implement an onReady callback within your subscription (see documentation above).
I suspect you will need to implement an autorun within your template rendered. This is to avoid the reverse scenario where the data arrives before the rendering (and so the callback fires too early). Something like this should do the trick (noting you have already set your subscription handle in your template creation function to this.subscription):
Template.messages.onRendered(function() {
var self = this;
this.autorun(function(computation) {
if(self.subscription.ready()){
$(document).scrollTop($(document).height());
computation.stop()
}
});
});
This should ensure both that the function is only called after rendering and the data from the subscription is complete. The autorun is also stopped after executing the scroll to stop continued calling should new documents cause ready() to no longer be truthy (and then become ready again) - which might surprise people with the page re-scrolling to the bottom!

Meteor collection update focus and blur latency

I'm trying to save user content on blur and am encountering a weird UI freeze after the save that I've never seen before.
A simplified version of the template:
{{#each UserSession.getQuestions}}
<textearea class='question'>{{UserSession.getVal this._id}}</teaxtarea>
{{/each}}
In other words, we iterate over a cursor from the Questions collection that selects all questions a user must answer, and we populate the textarea with a value from the UserSessions collection that contains this user's response to that question. For what it's worth, the textarea is actually in its own template (there are other things to show in there so i figured best to keep them isolated).
The user can modify the content of his response, and then I have an event handler that basically does this:
"blur .question": function(e) {
var val = $(e.target).val();
var questionId = this._id;
var userSessionId = Meteor.user().getSessionId();
var modifier = {$set: {}};
modifier["$set"]["questions." + questionId + "] = val;
UserSessions.update({_id: userSessionId}, modifier);
}
The update succeeds but the browser freezes for about a second after the save executes, so that if the blur is triggered by a user clicking on another element, that element doesn't come into focus cleanly. This is a problem because I also want to do periodic saves while the user is typing, but when I do that, the hiccup interrupts the typing: the resulting experience is pretty brutal.
The interesting thing is that the browser only seems to freeze when the property being updated is an object or in a list. In other words:
UserSessions.update({_id: userSessionId}, {$set: {"questions.questionId": "someVal"}}) causes the freeze, as does UserSessions.update({_id: userSessionId}, {$set: {"lastSavedAt": new Date()}}).
However, UserSessions.update({_id: userSessionId}, {$set: {"someOtherProp": "someVal"}}) works fine.
I've tried several different approaches, including an async client-side pattern and executing the save via Meteor Method in a if (Meteor.isServer) block -- all the same result. Must be something to do with the publication updating, but it happens even when i set the publish function query to {reactive: false}.
I'm out of ideas. Your help, as always, is greatly appreciated.
Thanks in advance,
db
Ok I think I figured it out. For anyone else who has this problem, if you're using Iron Router you should check it for un-necessary dependencies. In this case it was the router being re-run that clogged up the works, because the data hook included a reactive find request on the UserSessions collection. I set reactive to false and specified the fields to prevent a recomputation, and everything smoothed itself out.

How can I create a reactive meteor publication with a variable momentjs date

I'm having trouble with realizing a reactive publication with a moving date. I have a calendar app with events/meetings. Im displaying these events on a special view page but i only want to display events of today and only with a starting time of currenttime - 30 mins.
The code im having and trying always works fine on initial load, after refreshing and when I add/delete an event from an admin page (collection gets refreshed?). But when I leave the page open the events which have passed just stay on the page. Whenever I add/remove a event via the admin page the publication is updated just fine. I assume this is because the publication isn't refreshing the date in the query or something?
I have tried:
normal publications and just subscribing via either iron-router
before hook or via Deps.autorun
publish all events and filtering on the client side
a publication with observeChanges
keep the vars in a deps.autorun function and passing them via the subscription as parameters
but I just keep getting the same results.
publication with observeChanges:
Meteor.publish('currentEventsObserve', function(calId) {
var self = this;
var nowMin30mins = moment().subtract('minutes',30).valueOf();
var endOfToday = moment(moment().format('DD-MM-YYYY 23:59'), 'DD-MM-YYYY HH:mm').valueOf();
var handle = Events.find({
calId : calId, //CalendarId
eventDate: { $gt: nowMin30mins, $lt: endOfToday }
},
{
sort: { eventDate: 1 },
limit: 5
}).observeChanges({
added: function(id,event){
//console.log("added: ",id,event);
self.added("events", id, event);
},
removed: function (id,event) {
//console.log("removed: ",id,event);
self.removed("events", id);
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
As said before: the above works fine on initial load and refreshes, but after leaving the page open for a while the events are staying on there and not being removed from the publication.
Also, whenever I check the collection ones certain events already should have been removed via Events.find().fetch() for example, the events are still there, so it's not the template which isn't updating correctly.
I hope it's clear what I mean. I have read many other questions about this sort of reactivity but I just can't figure it out.
The problem is that nowMin30mins is only computed once when the publication is activated, and won't update afterwards, even though you have an observeChanges on Events. You basically have two constant values (nowMin30mins and endOfToday) bracketing the documents that will be shown and only adding or removing items will cause them to disappear.
I can think of two ways for how you'd want to do this reactively.
You can put all the reactivity on the client. Just send the entire day's events over in a publish, which is very simple, and have the client filter out what's between 30 mins from now and the end of the day using Date.now() in a computation that updates every minute or so, or when new events are added/deleted. It doesn't seem to incur a lot of overhead to store those events anyway and you can offload the computational cost of observes, which can build up if you have a lot of clients
If you want to do it on the server, you'll have to do things a little more carefully than what you have now. Because the live query won't account for the change in time, you'll need to watch all of today's events and then add or remove them from the subscription if they are created, or deleted or go out of range, respectively. To reiterate, you can't do this purely with an observeChanges because the current time is always changing.

Is there any way to know when an meteor subscription is 'valid'?

If I change a Session var and trigger a re-subscription via autosubscribe, is there any callback mechanism to wait until the 'latest' data is down from the server? [1]
If you take a look at this gist you'll see some code that logs the content of a collection over time as the subscription changes. A relevant section of the output:
at Subscribed; comments are: first post on #1 - second post on #1
at Flushed; comments are: first post on #1 - second post on #1
at Subscription complete; comments are: first post on #1 - second post on #1 - first post on #2 - second post on #2
So, even after (a) calling .subscribe, (b) calling Meteor.flush (c) being inside the onReady callback for the .subscribe; there is still stale data in the collection, and only in the 3rd case is the 'correct' data in there.
I realise that reactive templates and .observe will eventually receive the correct data and things will 'settle' into the correct state. But is there some way we can tell that we aren't there yet?
For instance, most of the meteor example apps (and my own apps) are prone to jerking around a bit (similar to a FOUC) while data is added + removed from a subscribed collection. If we could tell that a subscription was 'loading' we could do something about this.
[1] Obviously the data on the server is constantly changing, but as you'll see in the gist, I can't (without a timeout) find a point where it's even correct. Thus my use of 'valid' in the question.
A very simple and common use case
Take the madewith app; when you first load it up it appears that there are no apps registered, until after the data comes down the wire and the apps suddenly appear.
The reason for this is that Meteor.subscribe has been called, but the data has not yet come down the wire. But there's no easy way for the template to tell that the data is pending and that it should show a 'loading' template. In madewith they actually do something when the data has loaded, but this is a callback and thus breaks out of the normal meteor way of doing things (i.e. reactive coding).
It would be much nicer (IMO) to be able to write something like:
{{unless apps_loaded}}{{> loading}}{{/unless}}
and
Template.madewith.apps_loaded = function() { return !Apps.isComplete(); }
Interesting question.
The right place to be notified for new subscriptions is the onReady callback. Notice the logging that happens there always includes your new data. Checking at (a) and (b) aren't useful, because there's latency between calling subscribe and when all the data arrives from the server.
The underlying problem is there's no equivalent onRemove callback that runs once the data for a just-stopped subscription has been removed. Moreover, autosubscribe deliberately starts new subs before stopping old ones, to avoid flicker.
What's the real use case? Most of the time such a callback isn't necessary, because templates can also restrict their queries to the data that should be in scope. In your example, the template helper that renders comments might query only for comments with the current post_id in the Session, so there's no harm having extra comments in the database.
Something like this:
Template.post.comments = function () {
return Comments.find({post_id: Session.get('post_id')});
};
That permits strategies more sophisticated that the generic autosubscribe function, like subscribing to comments for the last three posts the user has viewed. Or, subscribing to the first few comments for each post, and then separately subscribing to a post's full set of comments only when that post is selected.
I've come a bit late to the meteor party, so I don't know the history behind when meteor features have been added, but just for completeness it is now possible to do this using template-level subscriptions and 'subscriptionsReady' :
[js]
Template.myTemplate.onCreated(function() {
this.subscribe('myData');
});
[html]
<template name="myTemplate">
<h2>my Template</h2>
{{#if Template.subscriptionsReady}}
// code to loop/display myData
{{else}}
<p>Please wait..</p>
{{/if}}
</template>

Resources