Meteor: accessing user details on client [duplicate] - meteor

This question already has answers here:
How to use Meteor methods inside of a template helper
(6 answers)
Closed 6 years ago.
I'm trying to access (another) user's details on the client side in meteor. I have a server side method called 'userDetails' that I'm calling from a template helper called 'acc'.
Server method:
'userDetails': function(userId) {
check(userId, String);
return Meteor.users.findOne({_id: userId},
{fields: {
"services.facebook.first_name": 1,
"profile.birthday": 1,
"services.facebook.gender": 1,
"profile.location.name": 1
}});
}
Template helper:
acc: function(_id) {
Meteor.call('userDetails', _id, function(err, res) {
if(err) throw error;
return res;
});
}
When I try to access acc.profile.birthday in the template I don't get anything. What could cause this?

Meteor calls are asynchronous calls, that is why your helper is not returning any data.
Best option here is to either use Session or ReactiveVar or ReactiveDict
I'll use Session option here
acc: function(_id) {
Meteor.call('userDetails', _id, function(err, res) {
if(err){
}else{
Session.set('userDetails', res)
}
});
return Session.get('userDetails')
}
In your html you can use this helper like this
{{#if acc}}
{{name}}
...
{{else}}
<p>Information not found</p>
{{/if}}

You have to wrap the return in a else statement.
if(error) {
}
else {
return res;
}
The call to you method in asynchronous. This means that the callback function will be executed when your server method completes.
If you want to display the result on a template, you have two possibilities:
1/ Use a session.
acc: function(_id) {
Meteor.call('userDetails', _id, function(err, res) {
if(err){
}else{
Session.set('data', res)
}
});
return Session.get('data')
}
2/ Use Template subscriptions (better solution):
On the server, you publish the data:
Meteor.publish("data", function(){
return Meteor.users.findOne(...)
});
On the client, you subscribe:
Template.mytemplate.onCreated(function () {
Template.instance().subscribe("data");
});
Then directly on the client you will be able to create a helper and call the findOne.
In the html:
{{#if Template.subscriptionsReady}}
{{#each myHelper}}
{{acc.profile.birthday}}
{{/each}}
{{else}}
<p>Loading...</p>
{{/if}}
Important Notice about users:
Users profile are editable by default. Please read this: https://dweldon.silvrback.com/common-mistakes

Related

find a document with the current username in a helper

I'm trying to create a helper like this:
this.helpers({
data() {
return Customers.findOne({ user: Meteor.user().username });
}
});
but an error occurs, It seems that the user is logging in when the helper is executing, How can I execute the helper after the user is logged in ?
Don't know if is the best solution but I created a deferred promise that wait for the user to login and resolve the $state.
resolve: {
currentUser: ($q) => {
var deferred = $q.defer();
Meteor.autorun(function() {
if(!Meteor.loggingIn()) {
if(!Meteor.user()) {
deferred.reject('PERMISSION_REQUIRED');
} else {
deferred.resolve();
}
}
});
I hope that it can be useful for someone else.
Try this:
data() {
if(Meteor.user()){
return Customers.findOne({ user: Meteor.user().username });
}
}
Try builtin currentUser users helper, which check whether the user is logged in. Like that:
{{#if currentUser}}
{{data}}
{{/if}}

Waiting on a subscription in Flow Router

Objective : I want to render one among template A or template B from my flow router function, based upon a document which is retrieved after a subscribe call.
More specifically, I want to render either an admin or a student template based upon the isAdmin field of a user document, retrieved after a completed subscribe call.
My router function is as shown below.
FlowRouter.route('/songs/list', {
name: 'Songs.list',
subscriptions: function(params, queryParams) {
this.register('getUser', Meteor.subscribe('allUsers', Meteor.userId()));
}
action(params, queryParams) {
if(Meteor.user().isAdmin){
BlazeLayout.render("admin_home");
}
else{
BlazeLayout.render("student_home");
}
}
});
The Flow router documentation mentions specifically that
FlowRouter only deals with registration of subscriptions. It does not wait until subscription becomes ready.
So there could be a case where the "if condition" is evaluated before the subscription has updated the local cache.
With iron-router, this is easily handled using a waitOn. However for flow router, I am forced to have 2 separate functions, which further enforces 2 separate url's, for rendering the admin and student templates.
Is this a design choice made by the flow router developers?
Why not carry subscription logic out of routing and use template level subscriptions with builtin Template.subscriptionsReady helper?
FlowRouter.route('/songs/list', {
name: 'Songs.list',
action: function(params) {
BlazeLayout.render('home');
}
});
<template name="home">
{{#if Template.subscriptionsReady}}
{{#if isAdmin}}
{{> admin_home}}
{{else}}
{{> student_home}}
{{/if}}
{{/if}}
</template>
Template.home.onCreated(function() {
this.subscribe('allUsers', Meteor.userId());
});
Template.home.helpers({
isAdmin: function() {
// check if user is admin
}
});
This approach also recommended in FlowRouter docs.
Try:
Meteor.subscribe('allUsers', {
OnReady(){
if(Meteor.user().isAdmin){ BlazeLayout.render("admin_home"); } else{ BlazeLayout.render("student_home"); } }
I think the function you're after is FlowRouter.subsReady, an example of how you might use it within a router (similar to IronRouter) is provided in the docs here.
So in your case, it might look something like:
FlowRouter.route('/songs/list', {
name: 'Songs.list',
subscriptions: function(params, queryParams) {
this.register('getUser', Meteor.subscribe('allUsers', Meteor.userId()));
},
action(params, queryParams) {
Tracker.autorun(function() {
var ready = FlowRouter.subsReady("getUser");
Tracker.nonreactive(function(){
if(ready && Meteor.user().isAdmin){
BlazeLayout.render("admin_home");
}
else if(ready){
BlazeLayout.render("student_home");
}
else {
BlazeLayout.render("loading");
}
});
});
}
});

How to access FlowRouter subscriptions in Meteor template helpers?

it seems like I can't access a FlowRouter template subscription in my helper. How can you do this?
In my server code:
Meteor.publish('AllUsers', function() {
return Meteor.users.find({}, {fields: {profile: 1}});
})
In my router code:
var userRoutes = FlowRouter.group({
subscriptions: function(params, queryParams) {
this.register('AllUsers', Meteor.subscribe('AllUsers'));
},
});
In my template code:
{{#if checkFlowRouterSubs}}
{{#each getTheUsers}}
{{>userPartial}}
{{/each}}
{{/if}}
In my helpers I have the 'guard':
checkFlowRouterSubs: function() {
if (FlowRouter.subsReady()) {
return true;
};
return false;
},
And then the getTheUsers helper:
...
var users = AllUsers.find(filterObject, { sort: { 'profile.firstname': 1 } }).fetch(); // the actual query definitely works
...
But I get an error:
Exception in template helper: ReferenceError: AllUsers is not defined
I should note that in the getTheUsers helper, FlowRouter.subsReady('AllUsers') returns true
so, first, this :
var userRoutes = FlowRouter.group({
subscriptions: function(params, queryParams) {
this.register('AllUsers', Meteor.subscribe('AllUsers'));
},
});
is NOT server code: it is Client code: the Flow-router is a client side router: counter intuitive but this is the basis of all these routers.
The hint here is that you are 'subscribing' to the publication in this code, so it is on the client side.
Iron-Router is routing both on the server and client-side so it makes things even more confusing when you come from there.
What you are missing here is the publish function on the server side.
Meteor.publish('AllUsers', function() {
return AllUsers.find();
});
EDIT:
The Error
Exception in template helper: ReferenceError: AllUsers is not defined
seems like because you did not define the collection on the client side
var AllUsers = Mongo.Collection('AllUsers'); //or whatever the actual collection
When you try to get data from a subscription, you want to call the actual collection you're looking to get data for, not the subscription name. In this case, I think you mean Meteor.users:
var users = Meteor.users.find(filterObject, { sort: { 'profile.firstname': 1 } });
if( users ) {
return users.fetch();
}

Passing Username to Router Parameters

So I'd like to create a user profile listing the posts a user has made. My problem is passing each username through the router and onto Meteor.publish/subscribe. I keep getting "username undefined"
I suppose my question is: how does Iron Router know what "this.params.username" are? Should the url provide that?
Router
Router.route('userProfile',{
path: '/:username',
waitOn: function () {
return Meteor.subscribe('userprofile', this.params.username)},
data: function () {return {posts:Posts.find({username: this.params.username})};},
});
Meteor.publish
Meteor.publish('userprofile', function () {
return Posts.find({username: this.params.username});
});
Template
<template name="userProfile">
<div class="posts">
{{#each posts}}
{{> postItem}}
{{/each}}
</div>
</template>
Your routing code is correct, if you console.log the username inside waitOn or data you should get the correct value.
Router.route('userProfile', {
path: '/:username',
waitOn: function () {
console.log('waitOn', this.params.username);
return Meteor.subscribe('userprofile', this.params.username);
},
data: function () {
console.log('data', this.params.username);
return {
posts: Posts.find({username: this.params.username})
};
}
});
However, the way you fetch the parameter inside your publish function is wrong, you should rewrite your publication like this :
Meteor.publish('userprofile', function (username) {
return Posts.find({username: username});
});
The arguments you send to Meteor.subscribe after the name of the publication are passed to the publish function as parameters.

meteor reactivity breaks when using server/client folders

So I made something super simple to test out the reactivity in Meteor but when I came to make a server and client folder the reactivity broke. I can no longer manually edit the database and see the change instantly in the browser.
Template:
<template name="hello">
<input type="button" value="Click" />
{{#each tt}}
{{test}}
{{/each}}
</template>
client/test.js:
Template.hello.events(
{
'click input': function ()
{
Meteor.call('set');
}
});
Template.hello.helpers(
{
tt: function()
{
Meteor.call('get', function(error, result)
{
Session.set('aa', result);
});
return Session.get('aa');
}
});
server/testS.js:
Test = new Meteor.Collection("test");
Meteor.methods(
{
set: function()
{
Test.insert({test: "test 1"});
},
get: function()
{
return Test.find().fetch();
}
});
What am I missing to get reactivity using this folder structure?
The following is an issue.
Meteor.call('get', function(error, result) {
Session.set('aa', result);
});
This only occurs once in your case. Meteor.call is generally meant as a singular request, and is completely different than the publication/subscription model. The only "reactivity" you would experience in this case is if you manually do Session.set('aa', result);
If you want reactivity between the client/server DB, you need to set up publication/subscription code (see http://docs.meteor.com/#meteor_publish). By default, all documents in the database are published to the client via the auto-publish package, so keep that in mind. This is to auto-allow you do stuff like Collection.find() on the client, which returns a cursor, and is reactive by default.
In other words, your Meteor.call is redundant. The Test collection already exists on the client, allowing you to do the following.
Template.hello.helpers({
tt: function() {
return Test.find();
}
});

Resources