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!
Related
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.
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.
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.
With the following queries exposed by my back end:
GET /api/foos
returns a list of all Foos, suitable for display within a master list
GET /api/foos/:foo_id
returns a single Foo, with more detailed information, suitable for display within a detail view
My front end displays a list of Foos on the left and when one of them is clicked, the outlet on the right (in the outlet) displays its detailed version.
{{#each foo in model}}
{{#link-to 'foo' foo}}{{foo.name}}{{/link-to}}
{{/each}}
{{outlet}}
I am using ember-model to store my models, and have implemented App.Foo.adapter's find and findAll hooks, such that when they are invoked, they hit the back end APIs described above correctly.
When my app hits the GET /api/foos (via findAll) first, and then subsequently when the user clicks on the Foo, and ember-model doesn't hit GET /api/foos/:foo_id because it doesn't invoke the find hook, because it realises that that particular model is already in the cache.
This is great, because why fetch something again, when we know that we already have it in memory.
However, in my app, this assumption is insufficient. I have to further check if I have got the full version of that Foo, e.g. !!aFoo.get('propertyOnlyInDetailedVersion'), and otherwise I would like to force Foo to fetch again.
How can I go about doing this - how do I make ember-model re-fetch an object that has already been fetched prior?
This used to be a known issue, but this got fixed recently.
Taken in verbatim from: https://github.com/ebryn/ember-model/pull/297 (ckung's comment)
Developers can define "transient: false" on their classes that extend
Ember.Model, this will skip the cache check.
The commit: https://github.com/ckung/ember-model/commit/dc46171d2121630e62a130d58dd6b709c6c00541
Update your ember-model to the relevant version and you can get this working.
--
Edit: Sorry, I thought the next block text made my last edit -- but alas it didn't.
So my idea now is to do something like CachedModel extends LiveModel extends Ember-Model. Then set transient to true and false respectively.
So this was what I ended up going with in the end:
App.FooDetailRoute = Ember.Route.extend({
model: function() {
var foo = this.modelFor('foo');
if (!foo.get('propertyOnlyInDetailedVersion')) {
//if foo does not have propertyOnlyInDetailedVersion property, we know that only the
//summarised foo has been last fetched.
//to force a re-fetch, we trick ember-model by setting isLoaded to false
//before triggering fetch once more
foo.set('isLoaded', false);
var refetchedFoo = App.Foo.find(parseInt(foo.get('id'), 10));
return refetchedFoo ;
}
return foo;
}
});
tl;dr = set model.isLoaded to false and call model.find again.
First off, some background
My client has a kind of a "split-view", meaning- a side-panel displaying a list of objects and a main view displaying the selected object's details. Every time the user clicks on an Object in the list, a Backbone's route is called to navigate to the id which updates a "selected" property on the Session, what causes the main view to update- pretty standard stuff.
The problem
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
My goal is that every time an Object is created, the list and the main view will be instantly updated to reflect the newly added Object. To achieve this I created a Meteor.method, create(), that uses Collection.insert and returns the id so I can use it with my Route. The method is shared across the client and server and is being called from within a template's event handler.
My first try was to store the returned id in a variable in the event handler and update the Route in the next line; For some reason, that didn't work because the method returned an undefined value. So I tried a different approach, instead of returning the id, I used it within the method to update the Route directly (if Meteor.isClient of course). That didn't work either because the id returned by Collection.insert in the client's version of the method was different from the one in the server's version.
First approach
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = Meteor.call('create');
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});
Second approach
Meteor.methods({
create: function () {
var ObjectId = Objects.insert({name:'test'});
if(Meteor.isClient){
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
}
});
If anyone knows what's going on and can give me some directions that would be great.
Any different approaches to the problem or suggestions would be much appreciated as well.
Thanks
Update
So I tried #Pent's suggestion and I got the same result as with my second approach. For some odd reason Meteor decides to ignore my id (created with Random.id()) and inserts the object with a different one.
So I tried another approach, I used just a simple string value instead of Random.id() and voila - it worked. Riddle me that.
Answer updated:
This will be both a client and server method:
Meteor.methods({
create: function () {
var id = Random.id();
Objects.insert({_id: id, name:'test'});
if(this.isSimulation) {
appRouter.navigate("object/id/" + id, {trigger:true});
}
}
});
You can view a similar pattern from Meteor's party example: https://github.com/meteor/meteor/blob/b28c81724101f84547c6c6b9c203353f2e05fbb7/examples/parties/model.js#L56
Your problem is coused by the fact that remote methods, i.e. those which will be called on the server, don't simply return any value. Instead, they accept a callback that will be used to process the returned value (see docs). So in your first example you should probably do something like this:
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
Meteor.call('create', function (error, result) {
if (!error)
appRouter.navigate("object/id/" + result, {trigger:true});
});
}
});
You also said:
I want the client to be as responsive as possible, therefore i'm trying to utilize Meteor's abillity to update the client immediately without waiting for a server confirmation.
I think that in this case you should definitely wait for server response. Note, that there is no chance you get the correct object id unless this is given to you by the server.
One possible way to get around this issue is to create a local (client-side) collection:
// only on client
var temporary = new Meteor.Collection(null); // null name
in which you could store your "temporary" newly created objects, and then save them to the "real" collection after the user clicks the save button. You could implement your router to respond to urls like object/new/* to get access to these objects before they're saved to your database.
The correct answer for this question is defining a client side method that's responsible for creating the unique id (preferably using Random.id() ) and calling the Meteor.methods' create(). That way, you can have the id available immediately without waiting for the server to generate one. The trick here is to generate the id outside of the Meteor.method so that the id generation happens only once for both the stub and the actual server method.
create = function(){
var id = Random.id();
Meteor.call('create', id);
return id;
}
Meteor.methods({
create: function (id) {
Objects.insert({_id: id, name:'test'});
//more code...
}
});
//and in the Template...
Template.createDialog.events({
'click #btn-dialog-create': function (event, template) {
var objectId = create();
appRouter.navigate("object/id/" + objectId, {trigger:true});
}
});