I have a newsletter which relies on meteor's email package.
As soon as an admin submits a new news & events entry to the collection, all subscribers receive this via email. This also works.
However, I want to have add the new concrete link of the news & events entry to the page.
The route for the news and events page:
// Specific news and events
Router.route('/news-and-events/:_id', {
name: 'newsAndEventsPage',
waitOn: function(){
return [
Meteor.subscribe('newsevents'),
Meteor.subscribe('images'),
Meteor.subscribe('categories'),
Meteor.subscribe('tags'),
]
},
data: function(){
return NewsEvents.findOne({_id: this.params._id});
},
});
The admin route (a form page) for adding a new entry:
// Admin news
Router.route('/admin-news-events', {
name: 'adminNewsEvents',
waitOn: function(){
return [
Meteor.subscribe('newsevents'),
Meteor.subscribe('images'),
]
},
data: function(){
return false
},
});
After submitting the post to the collection, I tried to catch the entry and pass the id, but I just get undefined.
My admin template.js (edited):
'submit form': function (evt, template) {
evt.preventDefault();
var temp = {};
temp.title = $('#title').val();
temp.description = $('#description').summernote('code');
temp.type = $('input[name=netype]:checked').val();
temp.createdAt = moment().format('ddd, DD MMM YYYY hh:mm:ss ZZ');
Meteor.call('NewsEvents.insert', temp);
Bert.alert("New entry added.");
//Fire the email to all Subscribers
var entry = NewsEvents.findOne(this._id);
var entryId = entry.id;
//NOT WORKING
var news = '<a href='+Meteor.absoluteUrl()+'news-and-events/'+entryId+'></a>';
for (i = 0; i < Subscribers.find().count(); i++) {
var email_ = Subscribers.find().fetch()[i].email;
Meteor.call('sendEmail',
email_, //To
'Open Strategy Network <xxx.yyy#zzz.yyy.xx>', //from
'Open Strategy Network News and Events', //subject
news);
}
}
Server methods:
Meteor.methods({
'NewsEvents.insert': function (doc) {
if (this.userId) {
NewsEvents.insert(doc);
}
}
});
...
//Send emails
'sendEmail': function (to, from, subject, text) {
// check([to, from, subject, text], [String]);
this.unblock();
Email.send({
to: to,
from: from,
subject: subject,
html: text
});
},
Thanks a lot.
.find() returns a cursor, not an object. You can either do:
var entry = NewsEvents.findOne(this._id);
var entryId = entry.id;
Or more simply since you already have the _id:
var entryId = this._id;
Or even more simply:
var news = '<a style:"text-decoration: none;"
href='Meteor.absoluteUrl()+'news-and-events/'+this._id+'></a>';
Also, you are trying to send the email while your insert is happening asynchronously.
Meteor.call('NewsEvents.insert', temp); // this might take some time to complete
var entry = NewsEvents.findOne(this._id); // `this` is not going to refer to the just added NewsEvent
Instead, do the notifications in a callback from the method:
Meteor.call('NewsEvents.insert', temp, function(err, result){
if ( !err ){
// assuming that `result` will be the _id of the inserted object!!
var news = '<a href='+Meteor.absoluteUrl()+'news-and-events/'+result+'></a>';
Subscribers.find().forEach(function(s){
Meteor.call('sendEmail',
s.email, //To
'Open Strategy Network <violetta.splitter#business.uzh.ch>', //from
'Open Strategy Network News and Events', //subject
news
);
}
}
});
Your NewsEvents.insert method needs to return the _id of the inserted object:
Meteor.methods({
'NewsEvents.insert'(doc) {
if (this.userId) return NewsEvents.insert(doc);
}
});
Now, even the above will be slow since you're doing Meteor.call() in a loop. Secondly, you've opened up your server as a mail relay since anyone can use the sendEmail method to send any email to anyone from the console inside your app. If you want to do this efficiently, put the notification code inside your NewsEvents.insert method and do it all on the server without all the back and forth!!
If I understood correctly, you want to have ID of the inserted document. Its fairly simple.
In the method that inserts:
var docId = Somethings.insert({ //fields here });
Now you can use that docId in the same method for sending emails.
If you also want to send the documentId to the client side, you can use error, result in Meteor.call() like this:
Meteor.call('methodName', arg, arg2, function(err, res){
if(!err){
//do something with res. in this case the res is inserted docId as I returned docId in the method
Router.go('/some-route/' + docId)
} else {
//do something with err
}
});
The error above comes from errors you throw in methods. For the result, you need to return a value which can be the inserted docId:
return docId
Tidied up method:
methodName: function (arg, arg2){
//equals to err in the `Meteor.call()`
if(arg !== 'something'){
throw new Meteor.Error('This is an error')
}
//insert new document
var docId = Somethings.insert({
fieldOne: arg,
fieldTwo: arg2
});
//send email to each subscriber. I don't know your exact DB fields so, its up to you. You did this in a different call.
var cursor = Subscribers.find();
cursor.forEach(function(ss){
//send email here. You can use each subscriber data like ss._id or ss.email. However you insert them...
});
//Equals to res in `Meteor.call()`. sends a result to the client side method call. inserted docId in this case
return docId
},
PS: If this doesn't answer you question, that means I didn't understand what you're trying to achieve exactly. Leave me a comment and I'll edit the answer.
EDIT
I used one method for both sending emails and inserting the document but still, you can pass error/result exactly like how I did and then do another call for emails using the id in result.
Related
I have a button .toggle-addToSet that captures two integers, this.id which is the current posts' id, and setid (sid) which is an _id of a collection Set which the user has the ability to create as many as they want. The goal is updating a chosen Set of a given _id sid with this.id. It looks like this in the js
Template.latestSingle.events({
'click .toggle-addToSet': function(e, template) {
var ob = this.id
console.log(ob);
var sid = $(e.currentTarget).data('setid');
Meteor.call('addingSets', ob, sid, function(error, user) {
console.log(ob)
});
}
});
What's going on is that ob is the id of a single document in an array, this document is a post, so I'm capturing that post.
Within each post context is a modal which brings about a collection called Sets which has a sub-document array called ArticleId that can be updated by the user by, inserting this.id (ob) via the click function with the button toggle-addToSet as seen above.
The user Creates the Set with a title such as Business or Lifestyle and when they create it, they can save post ids in an array called ArticleId whenever they find a post they would like to add. Think Pinterest Boards or G+ collections.
var sid = $(e.currentTarget).data('setid'); is the _id of each Set which the use selects to add an article into.
The idea is to add this.id (ob) into the chosen Set through that Set's _id sid. Each Set looks like this
So my methods is like this
Meteor.methods({
addingSets: function(set, sid, ob) {
console.log(sid);
console.log(ob);
Sets.update({
_id: sid
},
{
$addToSet: {
ArticleId: ob
}
});
}
});
However this is not working, I cannot seem to be able to update it. I can do it manually by typing it through a form, but when it comes to updating it via the click function, it's not working.
When I do console.log(sid); in my server, methods I get the correct Set _id in my terminal.
When I do console.log(ob); in my server, methods I get unidentified in my terminal. But in my client, it's logging the correct this.id so there is a disconnect somewhere and I'm not sure how to handle that.
You have an extra parameter in your addingSets method.
Currently you have addingSets: function(set, sid, ob) defined in the function.
When you're calling it from the client, you're doing it like so:
Meteor.call('addingSets', ob, sid, function(error, user) {...}
Notice that the function is expecting 3 parameters to be passed to it while you're giving it only 2. So in your case, ob gets mapped to set, and sid gets mapped to sid and since the 3rd param isn't being passed, it's undefined.
Helper:
Template.latestSingle.events({
'click .toggle-addToSet': function(e, template) {
var ob = this.id
console.log(ob);
var sid = $(e.currentTarget).data('setid');
Meteor.call('addingSets', ob, sid, function(error, user) {
console.log(ob)
});
}
});
Server:
Meteor.methods({
addingSets: function(ob, sid) {
console.log(sid);
console.log(ob);
Sets.update({
_id: sid
},
{
$addToSet: {
ArticleId: ob
}
});
}
});
The positions and the params passed are important.
I am trying to send a Push Notification through Parse Cloud Code when a certain object has been modified - "dirty"
I think I am almost there, but received an error because I believe am creating a new user instead of querying for one.
Parse.Cloud.beforeSave("Fact", function(request, response) {
var dirtyKeys = request.object.dirtyKeys();
for (var i = 0; i < dirtyKeys.length; ++i) {
var dirtyKey = dirtyKeys[i];
if (dirtyKey === "isValid") {
//send push
// Creates a pointer to _User with object id of userId
var targetUser = new Parse.User();
// targetUser.id = userId;
targetUser.id = request.object.userID;
var query = new Parse.Query(Parse.Installation);
query.equalTo('user', targetUser);
Parse.Push.send({
where: query,
data: {
alert: "Your Fact was approved :)"
}
});
return;
}
}
response.success();
});
I found this post related to my problem. My question now is how to integrate the user query in my beforeSave block. Ideally I would create another function for the user query and place that in my beforeSave block.
**5/14 Update
I took #toddg's advice and fixed the before save. Here is a clearer picture of what I am trying to do and the new error.
A couple points (as #Subash noted in the comments) before I get into the code:
Parse.Push.send is an async operation, so you'll want to ensure you call response.success() after your push send completes. I'm going to deal with this using Promises, as I think they are more flexible than callbacks. If you're not familiar, read about them here
The return in your if statement will likely prevent the response.success() from being called.
Here's my recommended way of doing it:
Parse.Cloud.beforeSave("Fact", function(request, response) {
// Keep track of whether we need to send the push notification
var shouldPushBeSent = false;
var dirtyKeys = request.object.dirtyKeys();
for (var i = 0; i < dirtyKeys.length; ++i) {
var dirtyKey = dirtyKeys[i];
if (dirtyKey === "isValid") {
shouldPushBeSent = true;
}
}
if (shouldPushBeSent) {
//send push
// Creates a pointer to _User with object id of userId
var targetUser = new Parse.User();
// targetUser.id = userId;
targetUser.id = request.object.userId;
var query = new Parse.Query(Parse.Installation);
// We want to pass the User object to the query rather than the UserId
query.equalTo('user', targetUser);
Parse.Push.send({
where: query, // Set our Installation query
data: {
alert: "Your fact was approved"
}
}).then(function(){
// Now we know the push notification was successfully sent
response.success();
}, function(error){
// There was an error sending the push notification
response.error("We had an error sending push: " + error);
});
} else {
// We don't need to send the push notification.
response.success();
}
});
By the way, I'm assuming that you have a column on your Installation class that tracks which user is associated with each Installation.
I am using sockets with mongodb, for a user who is trying to create a new name, I need to check all the models in the database to see if it exists.
I am doing it all wrong, basically I am trying to do something like this.
var allUsers = [];
models.Message.find({}, function(err, data) {
for(var i=0; i < data.length; i++) {
allUsers.push(data[i].username);
}
});
console.log(allUsers)
I'm sitting here struggling even getting the allUsers out of the function, and I am thinking this is not even the best way to do this. With allUsers I was just going to check to see if the new username existed in the array.
So to futher extend what I am doing here is some socket.io code. I was going to run some validation like this if I could get the allUsers to work.
socket.on('new user', function (data, callback) {
if(data in allUsers) {
callback(false);
} else {
callback(true);
socket.userName = data;
socket.connected = true;
users[socket.userName] = socket;
io.sockets.emit('user name', {usernames: users[socket.userName].userName, connected: users[socket.userName].connected});
}
});
But without it working, this is no good. So my question is with what I have provided (socket.io, mongodb) how do I get all the models and validate if a new user which is passed in data exists in the database?
models.Message.find is async, the result of the async operation is only available when the async operation has finished.so console.log(allUsers) will always yield an empty array.
should be something like (pseudo js code):
socket.on('new user', function (data, callback) {
models.User.findOne({username:data.username},function(err,user){
if(err){/*deal with error here */}
else if(user){/*username already taken
respond with appropriate socket message here */
socket.emit('user name already taken',{somemessage});
}
else{/* user with username not found */
/*create new user into database then emit socket message */
var user = new models.User(data);
user.save(function(err,user){
socket.emit('user name',{somemessage});
})
}
});
});
Using mongodb, and backbone models I am trying to store an input value as an array inside a model?
So at barebones let's say I have a small little form.
<form id="send-message">
<input name="message" class="message"/>
<button type="submit" class="send">Send</button>
</form>
Then in my backbone code for the view that contains this form, I submit this data. I am using socket.io so I have some code thats like this. I don't think posting the full code for this view is necessary, but I will and hopefully this will prevent confusion.
var Marionette = require('backbone.marionette'),
MessagesView = require('./messages'),
UsersListView = require('./users_list'),
socket = io.connect();
module.exports = ChatView = Marionette.ItemView.extend({
template: require('../../templates/chat.hbs'),
events: {
'submit #send-message': 'sendMessage'
},
initialize: function() {
var self = this;
this.messagesView = new MessagesView({ collection: window.App.data.messages });
this.usersListView = new UsersListView({ collection: window.App.data.messages });
socket.on('new message', function(data) {
self.createMessage(data);
});
},
onRender: function() {
this.messagesView.render().$el.appendTo(this.$el.find('.message-content'));
this.usersListView.render().$el.appendTo(this.$el.find('.users-list'));
},
sendMessage: function(e) {
e.preventDefault();
var $message = this.$el.find('input.message');
socket.emit('send message', $message.val());
$message.val('');
},
createMessage: function(data) {
var model = window.App.data.messages.where({username: data.username});
_.each(model, function(model) {
var values = {
message: data.message
}
model.save(values);
});
window.App.core.vent.trigger('app:log', 'Add View: Created a new message!');
}
});
So summed up this just submits the input data to the node server then the server emits a response and triggers createMessage.
My question is I want to capture all these messages and store them as an array inside the model. So the data structure would look kind of like this.
// This represents the ideal structure of the model at barebones
var user = {
username: 'Grant',
message: {message1: "Hey guys", message2: "Michigan State will win the tourney"}
}
Lets take a closer look at the createMessage method.. You see I save the message like below, but I am unsure how to get the messages already saved on that model then append a new message, thus creating an array. I'll leave it like it is now, but I have tried using a .push() function and tried various ways all failing...
createMessage: function(data) {
var model = window.App.data.messages.where({username: data.username});
_.each(model, function(model) {
var values = {
message: data.message
}
model.save(values);
});
window.App.core.vent.trigger('app:log', 'Add View: Created a new message!');
}
The data goes to mongodb so I also have a controller that handles and (I THINK) overrides backbone.sync, but the controller is fired by a router when a PUT request is made app.put('/api/messages/:id', messages.update); Then the update method handles the data, so possibly I could create the array here.
update: function(req, res) {
models.Message.update({ _id: req.params.id }, {message: req.body.message //Somewhere here I could append?}, function(err, message) {
if (err) {
res.json({error: 'Update failed.'});
} else {
res.json(message);
}
});
}
Edit: So thinking about it, I do want to generate an array of objects.. My biggest problem is figuring out how to append a new object and generate a new key?
For example
var user = {
username: 'Grant',
message: {message1: "Hey guys"}
}
A user submits a new message, how do I create an object with a fresh key, I tried using backbone to get the length of the objects, but that got kind of hairy since this is mostly vanilla based now..
What you're trying to make is an object, not an array - that's why .push() doesn't work.
function(value) {
user.message['message' + (Object.keys(user.message).length + 1)] = value;
}
This will add a new value to the object, with the key 'message' + amount of old 'messages'.
I'm a little confused about where to place a response.success() when using serial Promises.
Here's the situation: I've got a cloud function that accepts an array of email addresses and the current user. The function does the following:
Finds the current user based upon it's user object id.
Iterates over the array of emails addresses
Find if there is an existing user for each given email address
If there is an existing user, we check to see if the existing user and the current user are friends
If they are not friends, it creates a friendship.
Now when I run this function without a response.success(), it does exactly what I expect it to and the friendships entries are created. But no matter where I place the response in the code, I get the resulting response.success message and none of the serialized promises execute.
Why the resulting success/failure matters: I'm executing this function from an iOS app and I'd like to properly handle the success or failure cases correctly on the iOS side.
Here is the cloud function:
Parse.Cloud.define("friendExistingUsers", function(request, response) {
// Get our parameters
var addresses = request.params.emailAddresses;
var userId = request.params.user;
// Query for our user
var userQuery = new Parse.Query("User");
userQuery.equalTo("objectId", userId)
userQuery.first().then(function(currentUser) {
// if we find the user, walk the addresses
var promise = Parse.Promise.as("success");
_.each(addresses, function(address) {
console.log(address);
// add a then to our promise to handle whether a relationship is
// being created.
promise = promise.then(function() {
// find if there is a user for that address
var emailQuery = new Parse.Query("User");
emailQuery.equalTo("email", address);
emailQuery.first().then(function(addressUser) {
if (typeof addressUser != 'undefined') {
// found one.
console.log(addressUser);
// figure out if our current user and this user are
// friends.
var friendQuery = new Parse.Query("FVFriendship");
friendQuery.equalTo("from", currentUser);
friendQuery.equalTo("to", addressUser);
friendQuery.first().then(function(relationship) {
if (typeof relationship != 'undefined') {
// if they are, we need to pass.
console.log("Found a relationship: " = relationship)
} else {
// They are not. Add the friendship
var Friendship = Parse.Object.extend("FVFriendship");
var friendship = new Friendship();
friendship.set("from", currentUser);
friendship.set("to", addressUser);
friendship.save().then(function(result) {
console.log("Created a friendship: " + result)
});
};
});
} else {
// we did not find a user for that address
console.log("No user for " + address);
};
});
});
});
console.log(promise);
return promise;
}).then(function() {
response.success("success");
});
});
Thanks in Advance. Let me know if there's anything else I can add.
Your .then callback function attached to promise should return a promise. Missing this is a common mistake when using promises.
Also Parse doesn't seem to show objects with console.log as browsers do, so I wrap them into JSON.stringify().