Im trying to set up a list of "rooms". The intended sequence:
Click name of the user on his/her profile page
Check for existing room. If yes, go to that room, if not set up new room.
Im using both dburles:collection-helpers and reywood:publish-composite.
Its throwing me this error.
TypeError: Cannot read property 'username' of undefined
at Document.Rooms.helpers.recName (rooms.js:18)
And line 18 is:
return Meteor.users.findOne({ _id: this.receiver }).username;
i.e. _id: this.receiver is undefined.
I also tried to add protective checks in the collection helpers but error remains. I.e. return user && user.username for example.
One thing I note is that, I noticed when I click on the user, it goes to the room linked to the user's id. However when I click back, it jumps to a blank room with a different id thats unrecognised.
The relevant codes:
Server publish
Meteor.publish("onlusers", function (){
return Meteor.users.find({});
});
Rooms.js collection helper
Rooms.helpers({
recName: function() {
return Meteor.users.findOne({ _id: this.receiver }).username;
}
});
User.js (for profile page events)
Template.usersShow.events({
'click .user': function() {
var receiver = this._id;
Session.set('chatId', this._id);
var res = Rooms.findOne({
$or: [
{ owner : this._id },
{ receiver : this._id }
]
});
if(res){
Router.go('roomDetail', { "_id" : res._id });
} else {
var newRoom = Rooms.insert({
owner : Meteor.userId(),
receiver : receiver,
username : Meteor.user().username,
});
Session.set('roomid', newRoom);
Router.go('roomDetail', { "_id" : newRoom });
}
}
});
Your diagnosis:
_id: this.receiver is undefined.
May be misleading. What is also possible is that the user subscription isn't completely loaded when your helper runs. I was helping someone else with a similar problem with publish-composite the other day - the subscription is marked as ready when the parents are ready but the children may not have finished loading yet. I think of this as a bug in publish-composite, all the related objects really need to be there before the subscription can be marked as ready.
Instead of returning:
return Meteor.users.findOne({ _id: this.receiver }).username;
You can do:
var user = Meteor.users.findOne({ _id: this.receiver });
return user && user.username;
So you'll get nothing back until the user object loads but you won't throw an error.
Related
I created a new "Alerts" collection. Its to show number of unread messages. Messages get submitted and appear, and theres no other error on console or server.
2nd issue is, when i click on the specific room, it is supposed to mark all new messages as "read". Somehow the number stays. Error shows Exception in queued task: .added#http://localhost:3000/app/lib/collections/messages.js
File structure:
roomList.js - shows a list of all rooms, shows number of unread messages
roomDetail.js - when click specific room in list, will mark message as
"read", unread number dissapears.
alerts.js (Alerts collection)
messages.js (Messages collection)
rooms.js (Rooms collection)
publications and sub js
Meteor.publish('alerts', function() {
return Alerts.find({ userId: this.userId, read: false });
});
Meteor.subscribe('alerts')
Alerts collection js
Alerts = new Mongo.Collection('alerts');
Alerts.allow({
update: ownsDocument,
//if removed, shows error:
// insert failed: Access denied. No allow validators set on restricted collection for method 'insert'.
insert: function(){
return true;
}
});
createMessageAlert = function(message) {
if ( message.user !== Meteor.userId() ){
Alerts.insert({
userId : message.user,
roomId : Router.current().params._id, //params id of current room
messageId : message._id,
read : false
});
}
};
roomDetail.js
Messages.insert({
roomId : Router.current().params._id,
msg : message,
user : Meteor.user()._id
});
template.find('input').value = '';
createMessageAlert(message);
roomsList.js
Template.list.helpers({
alerts: function (){
return Alerts.find({ userId: Meteor.userId(), read: false });
},
alertCount: function(){
return Alerts.find({ userId: Meteor.userId(), read: false }).count();
}
});
Template.allRooms.events({
'click a': function() { //click the a href to go into roomDetail
Alerts.update(this._id, {$set: {read: true}});
}
});
Ultimate solution :
You should call the createMessageAlert from a trigger when a new Message is added in Messages collection.
Pre-requisites:
create a trigger(MSG_OBSERVER) for Messages collection, where whenever anything is added to the collection, a createMessageAlert method is invoked provided with the added document object, so you can play inside the method and do desired operations.
When you are updating Alerts collection. The collection should be published in such a way(named as "null") that it should be reactive and should be available from all the instances accessing the same account from different browser instances.
Implementation
Just add below code in your collections.js
Meteor.method(
'createMessageAlert': function(id, fields) {
if ( fields.user !== Meteor.userId() ){
Alerts.insert({
userId : fields.user,
roomId : Router.current().params._id, //params id of current room
messageId : id,
read : false
});
}
}
);
var MSG_OBSERVER = Messages.find();
MSG_OBSERVER.observe({
added: function(id, fields){
Meteor.call('createMessageAlert', id, fields);
}
});
Meteor.publish(null ,function() { // null name means send to all clients
//use Messages.insert() to generate a fake message and add data in below params
Meteor.call('createMessageAlert', id, fields);
return Alerts.find();
});
Explaination
If you again read the pre-requisites, you will understand the code. Ensure you are subscribed with desired collection on client side. This code makes every collection involved and triggers very reactive and responsive.
Whatever you will add as messages will be added to Alerts as well.
Publishing "null" will simply publish data to all clients making UI behavior more robust and asynchronous.(I am using this feature in displaying real-time graphs, you don't even have to refresh UI and your data gets reflected.)
While publishing "null", you can create a fake Message OBJ and add it to call createMessageAlert function. You have to do this because you have to initiate publish on server restarts. choose Message Obj wisely so that it won't impact the work flow.
I'm making an app that allows user to like and comment on other user post. I'm using Parse as my backend. I'm able to notified user everytime their post liked or commented. However if current user like or comment on their own post this current user still notified. How can I prevent this?
Here is the js code that I use:
Parse.Cloud.afterSave('Likes', function(request) {
// read pointer async
request.object.get("likedPost").fetch().then(function(like){
// 'post' is the commentedPost object here
var liker = like.get('createdBy');
// proceed with the rest of your code - unchanged
var query = new Parse.Query(Parse.Installation);
query.equalTo('jooveUser', liker);
Parse.Push.send({
where: query, // Set our Installation query.
data: {
alert: message = request.user.get('username') + ' liked your post',
badge: "Increment",
sound: "facebook_pop.mp3",
t : "l",
lid : request.object.id,
pid: request.object.get('likedPostId'),
lu : request.user.get('username'),
ca : request.object.createdAt,
pf : request.user.get('profilePicture')
}
}, {
success: function() {
console.log("push sent")
},
error: function(err) {
console.log("push not sent");
}
});
});
});
If I understand the context of where this code is correctly,
I recommend checking
if request.user.get("username") != Parse.CurrentUser.get("username")
Before sending out the push notification
Where is your cloud function being called from? If you're calling it from your ios code, then before you call the cloud code function, just prelude it with something like this:
if (PFUser.currentUser?.valueForKey("userName") as! String) != (parseUser.valueForKey("userName") as! String)
I'm trying to make a publishment statement to publish
ONLY the author(OP)'s profile avatar. I am thinking of grabbing the _id of the page. And from that page, I will grab the userId which is the author's _id and try to show the profile.
However, I have been very unsuccessful, and currently, I am using the following. Publishing EVERY user's profile avatar.
Publications.js
//Need to filter this to show only OP.
Meteor.publish("userPostAvatar", function() {
return Meteor.users.find( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
return Posts.findOne({_id:this.params._id});
}
});
You can do a simple join in the userPostAvatar publish function like this:
Meteor.publish('userPostAvatar', function(postId) {
check(postId, String);
var post = Posts.findOne(postId);
return Meteor.users.find(post.authorId, {fields: {profile: 1}});
});
This assumes posts have an authorId field - adjust as needed for your use case. Note three important things:
You will need to subscribe with this.params._id just as you did for singlePost.
The join is non-reactive. If the author changes, the avatar will not be republished. Given the general nature of posts I assume this isn't a problem.
I didn't publish the nested field profile.avatar on purpose because doing so can cause weird behavior on the client. See this question for more details.
I believe you can achieve this within the iron:router data context, by finding the post, associated author (whatever the field is), and then the subsequent user avatar. You can return an object to the iron:router data context. Then you can access post and avatar in the template as variables (so you might need to adjust the template output a little).
Publications.js
Meteor.publish("userPostAvatar", function() {
return Meteor.users.findOne( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
var post = Posts.findOne({_id: this.params._id});
var avatar = Users.findOne(post.authorId).profile.avatar;
return {
post: post,
avatar: avatar
};
}
});
Two problems with this method are that you could achieve the same thing with template helpers, and the user publication hasn't been limited to one user (I'm unsure how to do this unless we know the authorId within the waitOn, although maybe you could try moving the logic to there instead of the data context as my example shows).
I'm currently having an issue with a collection insert in Meteor. I call a method to insert a new item into a collection. The server database shows the new item but the client side has no record of the collection. I've found if I refresh the page, my template referencing the collection populates and works.
Here is the method inserting the item located at 'lib/collections/items.js'
Items = new Mongo.Collection("items");
Meteor.methods({
addItem: function (text) {
if (! Meteor.userId()){
throw new Meteor.Error("not-authorized");
}
console.log(text);
console.log(Meteor.userId());
console.log(Meteor.user().username);
console.log(Meteor.user().household);
Items.insert({
text: text,
createdAt: new Date(), //current time
owner: Meteor.userId(),
username: Meteor.user().username,
household: Meteor.user().household
});
},
Here is the server publishing of the items located at '/server/main.js'
Meteor.publish("items", function(){
if(typeof Meteor.users.findOne({'_id': this.userId}) === 'undefined') return null;
return Items.find( {household : Meteor.users.findOne({'_id': this.userId}).household});
});
Here is the code calling the method located at 'client/main.js'
Template.body.events({
"submit .new-task": function (event) {
// This function is called when the new task form is submitted
var text = event.target.text.value;
text = text.trim();
if(text)//Check for non-null, non-empty
{
text = capitalizeEachWord(text);
console.log(text);
Meteor.call("addItem",text);
}
//Clear form
event.target.text.value = "";
//Prevent default form submit
return false;
},
Here is where the collection is queried from to display in a template at 'client/templates/itemList.js'
Template.itemList.helpers({
items: function() {
console.log('Trying to subscribe');
return Items.find({}, {sort : {checked: 1, createdAt: -1}});
}
I'm just starting to learn Meteor. I appreciate any help!
Edit
Thanks for the responses,
I've tried these suggestions and still getting the same result. I've tried a different way of relating users to households now and still having the same issues. I can insert an item from the client, it exist on the server side, and yet it doesn't exist on the client side. I'm sure i'm missing something small here but for the life of me, can't find it.
I know this is a huge dump of info but here is my updated source code (still producing the same problem) and logs. I've tried to put everything I know about this issue. Thanks for any help! I'm just starting out with Meteor and I know no one who develops in it so troubleshooting is a bit tough right now.
Client Specific Code
Located at 'client/main.js'
Template.body.helpers({
householdId: function() {
//return Meteor.users.findOne({'_id': Meteor.userId()}).household;
if(Meteor.userId() &&
Households.findOne({users : {$regex : new RegExp(Meteor.userId())}}))
{
console.log('Trying to get id');
return Households.findOne({users : {$regex : new RegExp(Meteor.userId())}})._id;
}
}
});
Template.body.events({
"submit .new-task": function (event) {
// This function is called when the new task form is submitted
var text = event.target.text.value;
text = text.trim();
if(text)//Check for non-null, non-empty
{
text = capitalizeEachWord(text);
console.log(text);
Meteor.call("addItem",text);
}
//Clear form
event.target.text.value = "";
//Prevent default form submit
return false;
},
"submit .new-household": function (event) {
// This function is called when the new task form is submitted
var insertedId;
var text = event.target.householdName.value;
text = text.trim();
if(text)//Check for non-null, non-empty
{
text = capitalizeEachWord(text);
Meteor.call("createHousehold", text);
}
//Prevent default form submit
return false;
}
});
Accounts.ui.config({
passwordSignupFields: "USERNAME_ONLY",
});
Code to subscribe to my collections located at 'client/application.js'
//Sub to our collections
Meteor.subscribe('households');
Meteor.subscribe('items');
Code iterating through the items to display located at 'client/templates/itemList.js'
Template.itemList.helpers({
items: function() {
console.log('Trying to subscribe');
return Items.find(
{
household : Households.findOne( {users : {$regex : new RegExp(this.userId)}})._id
}
, {sort : {checked: 1, createdAt: -1}});
},
householdId: function() {
//return Meteor.users.findOne({'_id': Meteor.userId()}).household;
if(Meteor.userId())
{
console.log('Trying to get id from ');
return Households.findOne({users : {$regex : new RegExp(Meteor.userId())}})._id;
}
}
});
Collection Code
Households collection code located at 'lib/collections/households.js'
Households = new Mongo.Collection("households");
Meteor.methods({
createHousehold: function(text) {
var insertedId;
insertedId = Households.insert({
householdName: text,
createdAt: new Date(), //current time
users: this.userId
});
}//Add user in the future
});
Items Collection code located at 'lib/collections/items.js'
Items = new Mongo.Collection("items");
Meteor.methods({
addItem: function (text) {
if (! Meteor.userId()){
throw new Meteor.Error("not-authorized");
}
console.log('Inserting item')
console.log('Item : ' + text);
console.log('UserId : ' + Meteor.userId());
console.log('Username : ' + Meteor.user().username);
console.log('Household : ' + Households.findOne( {users : {$regex : new RegExp(Meteor.userId())}})._id);
Items.insert({
text: text,
createdAt: new Date(), //current time
owner: Meteor.userId(),
username: Meteor.user().username,
household: Households.findOne( {users : {$regex : new RegExp(Meteor.userId())}})._id
});
return true;
},
deleteItem: function (itemId) {
Items.remove(itemId);
},
setChecked: function(itemId, setChecked) {
Items.update(itemId, { $set: {checked: setChecked}});
}
});
Server Code
The server code is located at 'server/main.js'
Meteor.publish("households", function(){
console.log('Trying to subscribe');
console.log(this.userId);
if(this.userId)
{
//var query = { users : new RegExp("/"+this.userId+"/")};
//console.log(Households.findOne( query ));
console.log(Households.findOne( {users : {$regex : new RegExp(this.userId)}}));
return Households.find( {users : {$regex : new RegExp(this.userId)}});
}
else
{
console.log('Too early');
return null;
}
});
Meteor.publish("items", function(){
console.log('Trying to get items');
if(!this.userId ||
!Households.findOne( {users : {$regex : new RegExp(this.userId)}}))
{
console.log('Returning null');
return null;
}
console.log(Items.findOne( {household : Households.findOne( {users : {$regex : new RegExp(this.userId)}}) }));
console.log(this.userId);
return Items.find( {household : Households.findOne( {users : {$regex : new RegExp(this.userId)}})._id });
});
Logs
If I start the site on localhost using meteor. When the page comes up (no user logged in), this the server console output:
=> App running at: http://localhost:3000/
I20141209-10:26:50.719(-5)? Trying to subscribe
I20141209-10:26:50.766(-5)? null
I20141209-10:26:50.766(-5)? Too early
I20141209-10:26:50.766(-5)? Trying to get items
I20141209-10:26:50.767(-5)? Returning null
Next I successfully create a user account (using accounts-ui/accounts-password packages). This is the output to that point.
I20141209-10:31:59.562(-5)? Trying to subscribe
I20141209-10:31:59.565(-5)? null
I20141209-10:31:59.566(-5)? Too early
I20141209-10:31:59.566(-5)? Trying to get items
I20141209-10:31:59.566(-5)? Returning null
I20141209-10:32:16.145(-5)? Trying to subscribe
I20141209-10:32:16.145(-5)? 8Skhof4jL2pSguT8Q
I20141209-10:32:16.146(-5)? undefined
I20141209-10:32:16.147(-5)? Trying to get items
I20141209-10:32:16.148(-5)? Returning null
Next I create a household using the .new-household form. No new output on the server at this point but here is the output on the client side:
main.js?ba2ac06f3ff7f471a7fa97093ac9ed5c01e0c8cd:7 Trying to get id
main.js?ba2ac06f3ff7f471a7fa97093ac9ed5c01e0c8cd:7 Trying to get id
itemList.js?a224bda493b90eb94bff9b88b48bb22eaa8aefe1:3 Trying to subscribe
main.js?ba2ac06f3ff7f471a7fa97093ac9ed5c01e0c8cd:7 Trying to get id
main.js?ba2ac06f3ff7f471a7fa97093ac9ed5c01e0c8cd:7 Trying to get id
itemList.js?a224bda493b90eb94bff9b88b48bb22eaa8aefe1:3 Trying to subscribe
At this point I add an item using the .new-task form. The task displays for a blink on screen then disappears. Here is the server output at this point:
I20141209-10:37:09.171(-5)? Inserting item
I20141209-10:37:09.172(-5)? Item : Beans
I20141209-10:37:09.172(-5)? UserId : 8Skhof4jL2pSguT8Q
I20141209-10:37:09.172(-5)? Username : pedro
I20141209-10:37:09.173(-5)? Household : M5NckT6ndqhRKCeWo
Here is the client output at this point:
main.js?ba2ac06f3ff7f471a7fa97093ac9ed5c01e0c8cd:21 Beans
items.js?2ca606d1e71e2e55d91421d37f060bd5d8db98fe:8 Inserting item
items.js?2ca606d1e71e2e55d91421d37f060bd5d8db98fe:9 Item : Beans
items.js?2ca606d1e71e2e55d91421d37f060bd5d8db98fe:10 UserId : 8Skhof4jL2pSguT8Q
items.js?2ca606d1e71e2e55d91421d37f060bd5d8db98fe:11 Username : pedro
items.js?2ca606d1e71e2e55d91421d37f060bd5d8db98fe:12 Household : M5NckT6ndqhRKCeWo
At this point the Item exists within the server database, but if I, using the web console in chrome, execute the command Items.findOne(); it returns undefined.
There could be more to it, but I couldn't see there where you have included the Meteor.subscribe("items") on the client side as that is what will pass the records to the client from the publish on the server.
Also in your publish, you can use
if (this.userId) {
//do stuff logged in
} else {
this.stop();
}
instead of selecting the Meteor.user() record for the user ID to detect if the client is logged in or not.
Your publication:
Meteor.publish("items", function(){
if(user = Meteor.user()){
return Items.find( {household : user.household});
}
else{
this.ready();
}
});
Your block helper:
Template.itemList.helpers({
items: function() {
return Items.find({}, {sort : {checked: 1, createdAt: -1}});
}
});
Your subscription (client side):
Meteor.subscribe("items");
If you are using iron:router. In the route you can define directly your helper and your subscription like this and just call {{items}} in your template:
Router.route('myRoute', function(){
this.render('myRouteTemplate');
}, {
path: '/myRoutePath',
waitOn: function(){
if(user = Meteor.user()) {
var subscriptions = [];
//
// Subscribe to your subscription
//===============================
subscriptions.push(Meteor.subscribe('items'));
return subscriptions;
}
},
data: function(){
var context;
if(this.ready()) {
if (user = Meteor.user()) {
context = {
items: Items.find( {household : user.household})
};
}
}
}
});
Always remember that when you publish a magazine, you need a subscriber process. Same thing for collections.
Cheers,
I ended up fixing this issue by implementing Iron Router in my application and waiting on subscriptions to be ready before rendering a template. I believe that my issue before was some kind of race condition but i'm not positive on the specifics of it. Either way i'm satisfied with my solution because it fixes my issue and structures the flow of my site better and more understandably I think.
I'm having trouble adding custom user fields to a Meteor user object (Meteor.user). I'd like a user to have a "status" field, and I'd rather not nest it under "profile" (ie, profile.status), which I do know is r/w by default. (I've already removed autopublish.)
I've been able to publish the field to the client just fine via
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {username: 1, status: 1}});
});
...but I can't get set permissions that allow a logged-in user to update their own status.
If I do
Meteor.users.allow({
update: function (userId) {
return true;
}});
in Models.js, a user can edit all the fields for every user. That's not cool.
I've tried doing variants such as
Meteor.users.allow({
update: function (userId) {
return userId === Meteor.userId();
}});
and
Meteor.users.allow({
update: function (userId) {
return userId === this.userId();
}});
and they just get me Access Denied errors in the console.
The documentation addresses this somewhat, but doesn't go into enough detail. What silly mistake am I making?
(This is similar to this SO question, but that question only addresses how to publish fields, not how to update them.)
This is how I got it to work.
In the server I publish the userData
Meteor.publish("userData", function () {
return Meteor.users.find(
{_id: this.userId},
{fields: {'foo': 1, 'bar': 1}}
);
});
and set the allow as follows
Meteor.users.allow({
update: function (userId, user, fields, modifier) {
// can only change your own documents
if(user._id === userId)
{
Meteor.users.update({_id: userId}, modifier);
return true;
}
else return false;
}
});
in the client code, somewhere I update the user record, only if there is a user
if(Meteor.userId())
{
Meteor.users.update({_id: Meteor.userId()},{$set:{foo: 'something', bar: 'other'}});
}
Try:
Meteor.users.allow({
update: function (userId, user) {
return userId === user._id;
}
});
From the documentation for collection.allow:
update(userId, doc, fieldNames, modifier)
The user userId wants to update a document doc. (doc is the current version of the document from the database, without the proposed update.) Return true to permit the change.