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.
Related
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
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();
}
I have the following in server/publications.js...
Meteor.publish("users", function(){
return Meteor.users.find({}, {fields: {profile: 1}});
});
... and in my iron router route...
Router.route('/', function() {
this.layout('ConfLayout');
this.render('UserList', {
waitOn: function () {
return Meteor.subscribe("users");
},
data: function () {
return {
users: function () {
return Meteor.users.find();
}
};
}
});
});
...then in my template....
<template name="UserList">
<h1>Users</h1>
<ul>
{{#each users}}
<li>
{{#linkTo route='user.show'}}
<div>
{{profile.lastName}}, {{profile.firstName}}
</div>
{{/linkTo}}
</li>
{{/each}}
</ul>
</template>
...and it sort of works except the only user on the client is the currently logged in user. I am trying to get a list of ALL users for admins (don't worry about the for admins part for now).
What is also odd is that is I add a console.log statement to the publish function it never gets logged. However all the other publications in the same file seem to work fine. Also if I enable autopublish, then all the users show up as expected.
What an I missing here? Based on all I could find it seems like publishing specific fields should work for displaying all users in the client, but it almost seems like Meteor is ignoring any publications on Meteor.users altogether. I am using Meteor 1.1.0.3.
Any thoughts or help appreciated!
TIA
OK... not entirely sure why, but I suspect I may have been missing a "this" somewhere or something, but if I change the route to not use a function as the 2nd param and just pass options and then leave EVERYTHING else EXACTLY the same, it works...
Router.route('/', {
waitOn: function () {
return Meteor.subscribe('users');
},
data: function () {
return {
users: function () {
return Meteor.users.find();
}
};
},
template: 'UserList',
layoutTemplate: 'ConfLayout'
});
Can someone see why the route is not subscribing to the publication. Profiles = new Meteor.Collection('profiles');
The mongo database does have documents in this collection, but the browser console still has a count of 0 in Profiles collection.
I am trying to tell the router, "subscribe to user-profile publication, when you are ready, render the 'profile' template. I also named the route 'profile.'
Now I have noticed that after typingsub = Meteor.subscribe('user-profile'); and then sub.ready(); I get the count of the collection. Otherwise the path is not subscribed. This behaviour has not occurred before.
lib/router.js
Router.plugin('loading', {loadingTemplate: 'Loading'});
Router.route('user/profile', {
name: 'profile',
waitOn: function () {
// return one handle, a function, or an array
return Meteor.subscribe('user-profile');
},
action: function () {
// this.ready() is true if all items returned from waitOn are ready
if (this.ready())
this.render('profile');
else
this.render('Loading');
}
});
server.js:
Meteor.publish('user-profile', function () {
return Profiles.find({userId: this.userId});
});
userId is a field in the Profiles collection. This profiles doc id is stored within the user.profile.experiences array for reference.
Meteor.userId is a function which returns the _id, not the _id itself, and you can't pass a function over DDP anyway. It should be:
waitOn: function () {
return Meteor.subscribe('user-profile', Meteor.userId());
}
I have a 'profile' template where I will display user related stuffs. So I wanna make a route for the template, but in the 'path' I want to dynamically insert the current user's username. Just the way we dynamically change the url with regard to post's id and everything.
Here's the router code block as of now.
Router.map(function() {
this.route('profile', {
path: '/profile', //here instead of 'profile' I wanna dynamically insert the current user's username.
});
});
By the way, I was able to load the user related data's to the said template.
I tried loading the username(/username) to the route path in a trial and error way, but in vain. :(
I guess I'm not very good with Iron Router after all. Please help.
I too was struggling with this one for a while... then I came across this SO answer. In my case, I was doing everything right except for failing to pass the username along with the template pathFor link helper.
For some reason, when using :_id in iron router routes, there's no need to reference it in the pathFor helper. This was the source of my confusion, perhaps others' as well.
Here is sample code of using the username in a path for iron router:
router.js
this.route('/:username', {
name: "dashboard",
waitOn: function() {
return Meteor.subscribe("allUserData");
},
data: function() {
return Meteor.users.findOne();
}
});
publications.js
Meteor.publish("allUserData", function() {
if (this.userId) {
return Meteor.users.find(this.userId)
} else {
this.ready()
}
})
page.html
<a href="{{pathFor 'dashboard' username=username}}">
User Dashboard
</a>
Again, at least in my particular case, I was missing the above username=username.
Have you tried this?
this.route('profile', {
path: '/:username',
data: function() { return Meteor.user().username; }
});
Use router parameters:
Router.map(function() {
this.route('profile', {
path: '/:_username', //dynamic parameter username
data: function() {
//here you will get the username parameter
var username = this.params.username;
return {
user: Meteor.users.find({ username: username }) //you can use user object in template
};
}
});
});
Don't forget the waitOn property on routes. Most of the time it's just the timing that's off, creating a publication for this is the best way to get rid of that issue..
Server side, publications.js:
Meteor.publish('me', function() {
if(!this.userId) return false;
else return Meteor.users.find({_id: this.userId});
});
In one of your Router.map() routes:
this.route('me', {
template: 'profile',
notFoundTemplate: 'profile_not_found',
path: '/profile',
waitOn: function() {
return Meteor.subscribe("me");
},
data: function() {
return Meteor.user();
}
});
Don't forget these configuration bits as well:
// Router config.. pretty self explanatory
Router.configure({
layoutTemplate: 'main',
notFoundTemplate: 'not_found',
loadingTemplate: 'loading'
});
// handle the loading screen
Router.onBeforeAction('loading');
// make sure you define routes here that rely on data to throw back
// 404/not found equivalent pages. e.g. no search results found,
// or in this case profile not found
Router.onBeforeAction('dataNotFound', {only: ['profile']});
and you can use the profile template:
<template name="profile">
Current user Id: {{_id}}
</template>
<template name="profile_not_found">
Profile not found. Are you logged in?
</template>