How to do a lookup from a secondary collection in Meteor - meteor

I am building a simple chat client in Meteor. A chat message is stored in the Chats collection which has a message containing the id of the user from which it is and the id to which user it is, as well as the message, like so:
{
"_id" : "3Wo3EHYG8oPCS4TCc",
"message" :
{
"from" : "oSiKCdvCHGrfnfQoT",
"to" : "ESXbJXeWmNanz7zKq",
"text" : "Hello"
}
}
Users are store in the collection Users, which has the id as well as the username:
meteor:PRIMARY> db.users.find()
{ "_id" : "oSiKCdvCHGrfnfQoT", "username" : "user1" }
{ "_id" : "ESXbJXeWmNanz7zKq", "username" : "user2" }
I have an HTML-page that displays messages like this
<body>
<h1>Test</h1>
{{> hello}}
</body>
<template name="hello">
{{#each messages}}
From {{message.from}} to {{message.to}}: {{message.text}}
{{/each}}
</template>
using the following template helper:
Chats = new Mongo.Collection("chats");
Users = new Mongo.Collection("users");
import './main.html';
Template.hello.helpers({
messages:function(){
return Chats.find();
}
})
This creates the following output:
Test
From oSiKCdvCHGrfnfQoT to ESXbJXeWmNanz7zKq: Hello
However, in the HTML-page, I want to lookup the usernames of the users, instead of displaying their raw ids. I know that I could store the usernames in the Chats collection, but I would rather keep it clean. Of course, I could also do the lookup in the template helper function, and create a new datastructure, which mimics messages, but replaces the IDs with usernames. However, this is a rather clumsy solution, and I would rather do the lookup on the HTML-side. Is this possible?

Here's an approach that you can use all over your templates:
Template.registerHelper('username',function(userId){
var user = Meteor.users.findOne(userId);
return user && user.username;
});
Then in any template you can simply do {{username message.from}} (for example) and the username will automatically be inserted.

Related

Meteor 1.3: How to create a client only collection containing some fields from the users db collection?

I want to publish all the users to the client (which will eventually be only the 'profile' field of all the users):
The publication on the server looks like this:
Meteor.publish('users', function users() {
return Meteor.users.find();
});
In my template I then have a subscription that looks like this:
Template.Users_show_page.onCreated(function usersShowPageCreated() {
this.subscribe('users');
});
However the 'users' variable is not available and I still have to access the users via Meteor.users, such as in the following code:
Template.Users_show_page.helpers({
users() {
return Meteor.users.find();
}
});
Why is this?
I think I need to create a client-side collection with my choice of name - i.e. 'users', and then I can access that collection.
Where do I do this and how do I make that sync with the users in the database?
MyCollection = new Meteor.Collection('foo');
Meteor.publish('myPublication', function() {
return MyCollection.find();
});
The code above doesn't create a myPublication variable anywhere. It's just the name of the publication/subscription. You can even have multiple different subscriptions over the same collection. This code returns a cursor for the foo Mongo collection, which you access via MyCollection object.
So, your code doesn't need to create a new Mongo.Collection. Just use Meteor.users because that's the Mongo.Collection object that's already linked to "users" in MongoDB.
If you really want to access the documents in a users variable, you still need to create the helper as you suggested, although it's better you just use Meteor.users instead of the helper below:
Template.template_name.helpers({
users: function(){ return Meteor.users.find() }
});

How can I get other users' profiles details in meteor

I have got problem accessing the user profile details of the users other then the current user.
The goal is to display a little footer under a each of the posts in the kind of blog entries list . Footer should consist of the post and author details (like date, username etc.).
Blog entry is identified by authors' _id but the point is that I can not access
Meteor.users.find({_id : authorId});
Resulting cursor seems to be the same as Meteor.user (not 'users') and consists of one only document, and is valid for the current user ID only. For others, like authors ID, I can only get an empty collection.
The question is, if is there any way, other then next Meteor.users subscription to get authors profile (like username profile.nick etc) ???
Update: You can Publish Composite package if you want to get blog entry and user details in a single subscription. See the following sample code and edit as per your collection schemas,
Meteor.publishComposite('blogEntries', function (blogEntryIds) {
return [{
find: function() {
return BlogEntries.find({ courseId: { $in: blogEntryIds }});
// you can also do -> return BlogEntries.find();
// or -> return BlogEntries.find({ courseId: blogEntryId });
},
children: [{
find: function(blogEntry) {
return Meteor.users.find({
id: blogEntry.authorId
}, {
fields: {
"profile": 1,
"emails": 1
}
});
}
}}
}]
});
End of update
You need to publish Meteor.users from the server to be able to use it on client. accounts package will publish current user, that's why you are only seeing current user's information.
In a file in server folder or in Meteor.isServer if block do something like this
//authorIds = ["authorId1", "authorId2];
Meteor.publish('authors', function (authorIds) {
return Meteor.users.find({ _id : { $in: authorIds }});
});
or
Meteor.publish('author', function (authorId) {
return Meteor.users.find({ _id : authorId });
});
Then on client side subscribe to this publication, in template's onCreated function, with something like this
Meteor.subscribe('author', authorId); //or Meteor.subscribe('author', authorIds);
or
template.subscribe('author', authorId); //or template.subscribe('author', authorIds);
If you want to show only username (or a few other fields), you can save them in post document along with authorId. For example:
post:{
...
authorId: someValue,
authorName: someValue
}
You can use them in your templates as a field of a post.
If you have too many fields which you do not want to embed in post document, (so you want to keep only authorId), you can use publish-composite when you make your posts publication. (See example 1)
You do not need to publish all your users and their profiles.

Creating a client list table in meteor using tabular

I'm working of the Base User admin project from:
https://github.com/themeteorchef/building-a-user-admin
I want to change the user list table to a dataTable using aldeed:tabular. I have the following in the users tempalete:
{{> tabular table=TabularTables.UsersAdmin selector=selector class="table table-striped table-bordered table-condensed"}}
and in client/lib/userAdmin.js I added
Meteor.isClient && Template.registerHelper('TabularTables', TabularTables);
Template.users.rendered = function(){
TabularTables.UsersAdmin = new Tabular.Table({
name: "User List",
collection: Meteor.users,
pub:"users",
columns: [{data:"roles",title:"Role",
render: function (val, type, doc) {
return val[0];
}},
{
data: "emails",
title: "Email",
render: function (val, type, doc) {
return val[0].address;
}
}],
});
}
If I render the user page I get:
Uncaught ReferenceError: TabularTables is not defined.
Error: You must pass Tabular.Table instance as the table attribute
at null.<anonymous> (tabular.js:119)
I thought the was some publish problem, However, if i type in the console:
Meteor.users.findOne().emails[0].address
Meteor.users.findOne().roles[0]
Both parameters returns the functions, so the variables are available to the Admin user.
Any Ideas? Am I using tabular wrong?
Found the error:
The referencing and all was correct. Just forgot to include:
TabularTables = {};
At the top. Also the TabularTables code should be in a client and server space.
:-o and Yes. wasted too much time!

Retrieve user email given userId

This is more or less a follow up to this question.
I am trying to display "friends", I have a list of friends I sent a request to (called sent):
{{#each sent}}
<p>{{find_user _id}}</p>
{{/each}}
Sent is generated like so:
Template.friends.sent = function () {
return Notifications.find({from: Meteor.userId(), // to, and from are now userIds and not the user like in the original question.
type: 'friendship'});
}
And a query for the count gives a number of seven. My find_user template is defined as such:
Template.friends.find_user = function (id) {
return Meteor.users.find({_id: id});
}
How can I get the email from the a user id? Doing something like:
{{(find_user _id).emails.[0].address}}
fails, with:
Expected IDENTIFIER.
So first it appears you are iterating over a cursor from a Notifications collection and then calling the find_user method on the template with the _id of a Notification record. You'll need to use the from field of the document as it's the field that contains the userId.
Next you'll want to at least rewrite your find_user method so that it doesn't take a parameter. You can access the same data from within the helper because this is set to the current data context.
Template.friends.find_user = function () {
return Meteor.users.find({_id: this.from}); //note the this.from
}
Then you should be able to access the email address with via your template helper as long as you are publishing that data for the current user.
{{from_user.emails.0.address}}
Personally I like to use Meteor's collection transforms to extend my models with prototypes that can be used just like template helpers.
//first we create our collection and add a transform option
Notifications = new Meteor.Collection("notifications", {
transform: function(document){
return new Notification(document);
}
});
//next we create our constructor
Notification = function(document){
_(this).extend(document);
};
//Then add some prototypal methods that we can use in our templates.
Notification.prototype = {
fromUser: function(){
return Meteor.users.findOne(this.from);
}
};
Now we can use this in our templates like this:
{{fromUser.emails.0.address}}
We can also take this one really great step farther by using the users _transform property to set a function that transforms user documents as well and then add methods to them as well.
//transform each user document into a new User instance
Meteor.users._transform = function(document){
return new User(document);
};
//User constructor
User = function(document){
_(this).extend(document);
};
//and finally the User prototype with methods
User.prototype = {
defaultEmail: function(){
return this.emails && this.emails[0].address;
}
};
Now as a final result you can use it like this:
{{#each sent}
<p>{{fromUser.defaultEmail}}</p>
{{/each}}

Struggling with .find() vs. .findOne() in my Meteor app

I realize that this might be very close to other posts, but I just can't get this to stick in my head! :( I need some help in trying to understand how to use .find() or should I be using .findOne()? (so confused) for a collection of mine.
Goal:
I want to get all of the documents out of the People collection and then for each document I want to create a new <option> where the .name is put in for text and the collection ._id is the value.
Here's some code:
The Collection results from Mongo
db.people.find()
{ "_id" : "1", "name" : "John" }
{ "_id" : "2", "name" : "Mike" }
{ "_id" : "3", "name" : "George" }
{ "_id" : "4", "name" : "Jane" }
My Template Helper :
Template.view_Admin_Staff.people = function() {
console.log( 'people : ', People.find() );
return People.find();
};
My Template :
<select id="ddStaffID" name="staff">
<option value="">-- Select One --</option>
{{#each people}}
<option value="{{_id}}">{{name}}</option>
{{/each}}
</select>
My console.log found in the Helper returns undefined. What in the world am I missing?
I am going to assume that your collection is People = new Meteor.Collectin('people'); And your template name for in the html is
When you did the console log in your template helper, you are logging the cursor itself, not the documents. You can find out more about cursor at (https://www.eventedmind.com/tracks/feed-archive/how-do-client-cursors-work).
To see are you returning the correct data, you could .fetch method on a cursor. Fetch will return an array of objects based on your query. In your case, the query is empty, the fetch method should return everything that is available in the client's db.
To answer your question in the title. both find and findOne are methods available on the Collection object.
find return a cursor. findOne return AN object, if there is a record matched to your query.
Bloody-h3ll! Warning to all us n00bs out there...one must subscribe to your publications in order for one to see and work with your publications. sigh
The code in my initial post is working as expected. I just simply forgot to subscribe to my exposing data in my publication in my client-side route. </foreheadSlap> I'm new enough to have doubted my query skills and didn't even thing to troubleshoot any further back in the code. Thank you #Bozhao and #DavidWeldon for your quick replies.

Resources