Meteor - Not updating in real time anymore - javascript

Just started using Meteor and am loving it thus far.
I'm making a variation of the tutorial "todo" app, but with my own customization.
More or less the changes I made so far have been:
Changed directory structure to client, server, public, lib/collections folders
Made the task collection through collection2 schema rather than dynamically creating with Tasks.insert(title = text,etc etc)
So to the question.
These are my client/server files -
server.js
Meteor.publish("tasks", function () {
return Tasks.find({}, {sort: {checked: 1}});
});
client.js
// This code only runs on the client
Meteor.subscribe("tasks");
Template.task.events({
"click .toggledone": function () {
// Set the checked property to the opposite of its current value
Tasks.update(this._id, {
$set: {checked: ! this.checked}
});
},
"click .delete": function () {
Tasks.remove(this._id);
}
});
And its being displayed in the standard html
<ul>
{{#each tasks}}
{{> task}}
{{/each}}
</ul>
So basically as it is now, my server is sorting the tasks on the basis of whether it is checked (completed) or not, ascending.
It works currently, when I click a task on the list, it turns red (my configuration) and registers as completed, but the list doesn't sort automatically like it did originally (before I made the directory changes).
For it to update, I need to refresh the page which is not what I want.
I'm new to this and i think it has something to do with my publish/subscribe but I can't seem to find anything to help me online.
Can anyone point me in the right direction for this?
Thanks so much.

I think you are confused between the publication/subscription named tasks and the collection tasks for the loop on the template. When you want to render the tasks on the template, it should be returned from a helper. Consider that publication/subscription making the correct part of the database available on the client and then, you retrieve the correct data from that part to display.
Normally, I make the naming convention for the publication like the SQL query so it is easier to interpret
Meteor.publish("select-from-tasks", function () {
return Tasks.find({});
});
and then,
Meteor.subscribe("select-from-tasks");
You will need to have a helper to return the correct data, like I said above
Template.task.helpers({
tasks: function() {return Tasks.find({}, {sort: {checked: 1}});}
});

Related

Set admin account using Meteor to view all tasks

I'm new to Meteor and just walked through the To-Do list tutorial available here (https://www.meteor.com/tutorials/blaze/creating-an-app). I removed autopublish and set the display function in such a way that all tasks were private (that is, users could see only see their own tasks.)
Now, I'd like to change it up and set one account as an admin account. The admin can view everyone's tasks, but no one else can see anything (not even their own tasks). I'm trying to do this using the alanning-roles package, which I already downloaded in the app folder.
In my tasks.js file, I've inserted the lines:
const mod = 'E9Y4qtFXK2qQGAGq3'; // this is the userId of the account that I wish to make admin
Roles.addUsersToRoles(mod, 'moderator');
Then, instead of just displaying all the tasks, I enclose the command to display all tasks in an if-statement:
if (Meteor.isServer) {
if (Roles.userIsInRole(this.userId,'moderator')) {
Meteor.publish('tasks', function tasksPublication() {
return Tasks.find();
});
}
}
This should display all tasks if you are logged in as the moderator/admin, and nothing otherwise. But when I run this code, no tasks show up even when I am logged in as the admin. I am certain that the userId I set is correct, and that there are tasks in the collection. Does anyone have any ideas of what the problem might be?
(Or, any other suggestions on how to do this? Doesn't have to use alanning-roles -- I just thought that would be simplest)
Thanks so much
-C
EDIT: If I replace "this.userId" with "mod" in the line:
if (Roles.userIsInRole(this.userId,'moderator')){...}
then all the tasks appear. So it appears that the problem is with the output of this.userId.
You need to move where you are checking if current user is a 'moderator' to inside the publish function:
Currently in your code, when you access this.userId the server is starting up, and this.userId will be undefined. So the code in your if statement is not being executed, so the publish function is not created, and no clients will be able to subscribe to this data.
Try this:
if (Meteor.isServer) {
Meteor.publish('tasks', function tasksPublication() {
if (Roles.userIsInRole(this.userId, 'moderator')) {
return Tasks.find({});
}
});
}
Now, on startup the Meteor.isServer block runs, creating the tasks publication, with the code to check the role inside it. This function will now be called every time a client subscribes to it, and in this context this.userId will be the current client's user ID.
Also, don't place the alanning-roles package's source code in your app's folders - include the package either by running meteor add alanning:roles, or via npm with npm install #alanning/roles --save
You should use Meteor.userId() instead of this.userId:
if (Meteor.isServer) {
if (Roles.userIsInRole(Meteor.userId(),'moderator')) {
Meteor.publish('tasks', function tasksPublication() {
return Tasks.find();
});
}
}
As as rule of thumb, always use Meteor.userId(), except inside a publication, where you should use this.userId

Why is the template not updating in real time?

I have this Meteor code which is meant to update a notifications' template in real time whenever an appropriate action is triggered:
~/server/app.js
Meteor.methods({
notifs: function(){
return Meteor.users.findOne({_id:this.userId}, {'profile.notifs': 1});
}
});
and:
~/client/apps.js
Template.notifs.helpers({
notifs: function(){
Meteor.call('notifs', function(err, data){
Session.set('notifs', data);
});
return Session.get('notifs');
}
});
finally:
~/public/templates/notifs.html
<template name='notifs'>
{{#each notifs}}
<p>...</p>
{{/each}}
</template>
What this code does for the moment is just listing the notifications when the user logs in, but fails to update in real time to show new notifications as actions are triggered.
New notifications show up only after a page refresh (which is, honestly, useless).
After hours of googling I give up and post this here so someone can help me, please.
Thanks in advance.
At it's core, this question is about reactivity. Because Meteor.call isn't a reactive data source (it's just a remote procedure call), the helper won't run again if the underlying data changes.
Because the necessary user document is already published (it's for the current user), the method call isn't needed. You could rewrite your helper using find (a reactive data source) like this:
Template.notifs.helpers({
notifs: function() {
return Meteor.users.findOne({_id: Meteor.userId()});
}
});
However, Meteor.user() gives you the same functionality:
Template.notifs.helpers({
notifs: function() {
return Meteor.user();
}
});
However, you don't really even need this because templates come with the {{currentUser}} helper. So you can drop the helper altogether and modify your template like this:
<template name='notifs'>
{{#each currentUser.notifs}}
<p>...</p>
{{/each}}
</template>
If you ever actually do need the result of a meteor call in a helper, you should read the answers to this question.

Meteor: Data from External API call not rendering

I am relatively new to Meteor, and I'm trying to create a web store for my sister-in-law that takes data from her existing Etsy store and puts a custom skin on it. I've defined all of my Meteor.methods to retrieve the data, and I've proofed the data with a series of console.log statements... So, the data is there, but it won't render on the screen. Here is an example of some of the code on the server side:
Meteor.methods({
...
'getShopSections': function() {
this.unblock();
var URL = baseURL + "/sections?api_key="+apiKey;
var response = Meteor.http.get(URL).data.results;
return response;
}
...
});
This method returns an array of Object. A sample bit of JSON string from one of the returned Objects from the array:
{
active_listing_count: 20,
rank: 2,
shop_section_id: 1******0,
title: "Example Title",
user_id: 2******7
}
After fetching this data without a hitch, I was ready to make the call from the client side, and I tried and failed in several different ways before a Google search landed me at this tutorial here: https://dzone.com/articles/integrating-external-apis-your
On the client side, I have a nav.js file with the following bit of code, adapted from the above tutorial:
Template.nav.rendered = function() {
Meteor.call('getShopSections', function(err, res) {
Session.set('sections', res);
return res;
});
};
Template.nav.helpers({
category: function() {
var sections = Session.get('sections');
return sections;
}
});
And a sample call from inside my nav.html template...
<ul>
{{#each category}}
<li>{{category.title}}</li>
{{/each}}
</ul>
So, there's a few things going on here that I'm unsure of. First and foremost, the DOM is not rendering any of the category.title String despite showing the appropriate number of li placeholders. Secondly, before I followed the above tutorial, I didn't define a Session variable. Considering that the list of shop categories should remain static once the template is loaded, I didn't think it was necessary from what I understand about Session variables... but for some reason this was the difference between the template displaying a single empty <li> tag versus a number of empty <li>'s equal to category.length --- so, even though I can't comprehend why the Session variable is needed in this instance, it did bring me one perceived step closer to my goal... I have tried a number of console.log statements on the client side, and I am 100% sure the data is defined and available, but when I check the source code in my Developer Tools window, the DOM just shows a number of empty li brackets.
Can any Meteor gurus explain why 1) the DOM is not rendering any of the titles, and 2) if the Session variable indeed necessary? Please let me know if more information is needed, and I'll be very happy to provide it. Thanks!
You set the data context when you use #each, so simply use:
<li>{{title}}</li>
If a Session is the right type of reactive variable to use here or not is hard to determine without knowing what you are doing but my rough guess is that a Mini Mongo collection may be better suited for what it appears you are doing.
To get you started on deciding the correct type of reactive variable to use for this head over to the full Meteor documentation and investigate: collections, sessions, and reactive vars.
Edit: To step back and clarify a bit, a Template helper is called a reactive computation. Reactive computations inside of helpers will only execute if they are used in their respective templates AND if you use a reactive variable inside of the computation. There are multiple types of reactive variable, each with their own attributes. Your code likely didn't work at all before you used Session because you were not using a reactive variable.

How to improve data handling with meteor

Still on my meteor app, i'd like to now how to improve my data handling from minimongo.
Used to SQL / PHP, I'd like to know how to find() an object from my minimongo collection only one time, and access each of its properties with helpers, without having to re-access the collection each time.
Until now, what I did was something like that :
Template.profile.helpers({
name: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.name;
},
phone: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.phone;
}
[...]
});
But it's getting boring and i guess there must be a more efficient way to deal with it, something where I could load my entire users information only one time, and then display with a helper taking one parameter, to display the data like that : {{data name}}, {{data phone}}
With only one helper like that :
Template.profile.helpers({
data: function(aString){
if (aString == "phone)
return [...].phone;
}
}
[...]
});
Of course, I can use a session value, but I'm not sure it's as relevant as I could do.
Another thing : how to end a Meteor session ? Because with PHP, the session ends at the closure of the browser, and cookies stay for a given duration, but with meteor session, i never have to reconnect as day after day, my logs seems to stay.
Would someone guide me through this or give me some good habits / tips ? I'm still reading the doc, but it's quite huge :S
Thanks you !
One thing to note here -- you're not actually sending a request to the server when you call Mongo.Collection.find() on the client -- that's getting handled by the miniMongo instance on the client, so it's not really that inefficient. Also, in this particular instance, the current user is always available as Meteor.user() -- no need to use Meteor.users.find({_id: Meteor.userId()}); (see docs for more info)
However, if you really wanted to cache that value, you could do something like this:
// JS
var data; // scoped so it's available to the whole file
Template.profile.onCreated({
data = Meteor.users.findOne({_id: Meteor.userId()}); // above scope makes this new definition available to the whole file
});
Then, to do what you're describing with the string arguments, you can do something like...
Template.profile.helpers({
data: function(aString) {
if (data) { return data[aString]; }
}
});
Perhaps a better option even is to send the Template a data context -- something like this in your HTML file:
{{> profile currentUser}} <!-- currentUser returns Meteor.user() -->
Which is directly available to your helpers as this:
Template.profile.helpers({
data: function(aString) {
return this[aString];
}
});
You can also pass data contexts through IronRouter if you're using that for your routing.
As for the Meteor session, the model is different than the model for PHP. In PHP, you store session information on the server, and every time you access the server, the browser (or client, more generally) sends along the session ID so it can look up anything pertaining to your session (see this question for a potentially better explanation). Meteor keeps track of clients that are connected to it, so the session will stay open as long as your browser is open. If you need to manipulate Meteor user sessions, take a look at this question.
I recommend finding the collection helpers package on Atmosphere. This will enable you to write currentUser.name in a template and it'll automatically return users name helper function returns.
There is no problem working like this. It doesn't matter if the function is called multiple times. It won't hurt your apps performance.
PHP and meteor sessions are different. A meteor session lasts for as long as browser window remains open or the page is refreshed. You are right that meteor sessions are not the way to go for your problem.

Understanding when Meteor recomputes my template helper

I'm trying to wrap my head around Meteor's reactivity. I understand it re-renders a page when a reactive data source that's being referenced in a template changes. I also understand what constitutes a reactive source (Session, MongoDB cursors, etc.).
What I'm having trouble understanding are all these "behind my back" calls to my template helpers. There seems to be something more than reactivity that causes them.
In particular, in the following code, I have a friendRequests helper that gets recomputed sometimes two sometimes three times during a single access to the /friends page. If it gets recomputed twice, the DB queries succeed! If it gets recomputed thrice, the first DB access (for some odd reason) fails to query the DB while the latter two succeed.
This is the stack trace when the DB fails:
// NOTE: imsolonely is one of the users that should be returned in the friendRequests
imsolonely's public key: undefined
debug.js:41 Exception in template helper: TypeError: Cannot read property 'profile' of undefined
at Object.Utils.getPublicKeyByUsername (http://localhost:3000/lib/utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:79:22)
at Object.Utils.getFingerprintByUsername (http://localhost:3000/lib/utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:88:24)
at http://localhost:3000/client/friends.js?dbec4a7537c9d0abf56a74489824969cb7baadfe:25:35
at Array.forEach (native)
at Function._.each._.forEach (http://localhost:3000/packages/underscore.js?0a80a8623e1b40b5df5a05582f288ddd586eaa18:156:11)
at Object.Template.friendRequests.helpers.friendRequests (http://localhost:3000/client/friends.js?dbec4a7537c9d0abf56a74489824969cb7baadfe:22:7)
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:2693:16
at http://localhost:3000/packages/blaze.js?77c0809654ee3a10dcd5a4f961fb1437e7957d33:1602:16
at Object.Spacebars.dot (http://localhost:3000/packages/spacebars.js?3c496d2950151d744a8574297b46d2763a123bdf:231:13)
at http://localhost:3000/client/template.friends.js?a2da726f6dad1aaecfdedfe216aa3378fff938b5:24:37
utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:78 imsolonely's public key: {"profile":{"publicKey":"0404b4880129edc1ea2652dd1eff1c8728269874b9b0ace02cc90edcb449c3f3d716c2f8b79a5fe5695d52cd85aed228f977073538625e8e71f1cfd764766669b1"},"_id":"sXjzt7YHA8KTyAib5"}
utils.js?acf4e03d4c8a70819c26f8d2fd08caf7100768fe:78 imsolonely's public key: {"profile":{"publicKey":"0404b4880129edc1ea2652dd1eff1c8728269874b9b0ace02cc90edcb449c3f3d716c2f8b79a5fe5695d52cd85aed228f977073538625e8e71f1cfd764766669b1"},"_id":"sXjzt7YHA8KTyAib5"}
This also happens with many (or all, not sure) of my helpers. They get called when they are should not even be needed because the user is not logged in (and the template is not rendered, thus the helper should not be recomputed).
Here's some code:
client/friends.js:
Template.friendRequests.helpers({
friendRequests: function () {
// Problem 1: The template gets called right after I log in but before I am fully logged in
// so I need this call here.
if(!Meteor.user())
return Utils.notLoggedInErrorMsg;
// Problem 2: The template gets called three times. The first time fails the DB query
// as if the DB row did not exist. The next 2 times it succeeds. But it should only be
// called once.
var reqs = Friends.getFriendRequests(Utils.me());
_.each(reqs, function(element, it, list) {
check(element.initiator, String);
// Get the user's fingerprint
element.fingerprint = Utils.getFingerprintByUsername(element.initiator);
});
return reqs;
},
});
client/friends.html:
<template name="friends">
{{> friendRequests}}
{{> searchForm}}
<h2>My friends</h2>
<ul>
{{#each friends}}
<li>{{this}}</li>
{{/each}}
</ul>
</template>
<template name="friendRequests">
<h2>Friend requests</h2>
{{#if friendRequests.length}}
<p>Someone's popular today!</p>
<ul>
{{#each friendRequests}}
<li><b>{{initiator}}</b> with fingerprint <pre style="display: inline">{{fingerprint}}</pre> sent you a request on <em>{{date}}</em>. Accept <b>{{initiator}}</b> as a friend?</li>
{{/each}}
</ul>
{{else}}
<p>Sorry, nobody likes you right now.</p>
{{/if}}
</template>
lib/utils.js:
Utils = {
// ...
// other stuff
// ...
// #return a hex-encoded public key as a string
getPublicKeyByUsername: function (username) {
var user = Meteor.users.findOne({ username: username }, { fields: { 'profile.publicKey': 1 } });
console.log(username + '\'s public key: ' + EJSON.stringify(user));
var pubKey = user.profile.publicKey;
return pubKey;
},
// NOTE: not used yet, i used the CryptoUtils function directly when I needed it
//
// #return the fingerprint as a hex-encoded string
getFingerprintByUsername: function (username) {
var pubKey = Utils.getPublicKeyByUsername(username);
var fingerprint = CryptoUtils.getPublicKeyFingerprint(pubKey);
return fingerprint;
},
notLoggedInErrorMsg: 'Meteor is being silly and calling this when I\'m not logged in.',
}
If it matters, I am using the iron:router 1.0.3 package for redirecting to the /friends URL.
Any clarifications as to why the friendRequests helper is being recomputed when it's out of view, why it's being recomputed sometimes twice, sometimes thrice, instead of once when I refresh the /friends page would be greatly appreciated!
Thank you,
Alin
In general you should expect that your helpers will get called many times. If your template is being rendered while the user is logging in, and without waiting on the data to be published, it's likely that your helper will run:
When the route is requested
When the user finishes logging in
When Friends data first arrives on the client
When additional Friends data arrives or is modified
To solve the first problem, you can check for Meteor.user() || Meteor.loggingIn() as I do in the answer to this question (note the router API has changed since I answered that but it should give you an idea of what to do).
The answer to your second issue can be found in my post on guards. In short you can't assume that the data has arrived on the client, so you either need to check for its existence or you need to explicitly wait on the subscription in your router prior to rendering the template.
It turns out there was an error in my waitOn hook. I was not returning anything, I was just making Meteor.subscribe() calls instead of returning an array of them. Here's what fixed the DB exceptions:
Router.configure({
layoutTemplate: 'mainLayout',
loadingTemplate: 'loading',
waitOn: function () {
return [
Meteor.subscribe('userData'),
Meteor.subscribe('allUserData'),
Meteor.subscribe('UserProfiles'),
];
},
});
I still don't have a good solution for the Meteor.user() being undefined. Or maybe this fixed it as well?
PS: Thank you #DavidWeldon for helping me narrow this down!

Categories