MeteorTips Second Application Tutorial - Validations - javascript

I'm working on the Methods 2 chapter of MeteorTips' Second tutorial, which is basically just building a todo application with several lists.
I moved my code for adding tasks to a list into a method, and decided to try adding some validations to the tasks so that it cannot be blank and it cannot be less than 3 characters. I have the validation working, but my code is returning a Match failed error when running check(currentList, String). I can see that it is not fetching the list's ID, and stroing undefined in the variable.
My question is, how do I fix it?
Code in Question
HTML Template
<template name="addTodo">
<form class="add-todo">
Create a task
<input type="text" placeholder="Type a task here..." name="todoName" />
</form>
</template>
JavaScript
if (Meteor.isClient){
...
Template.addTodo.events({
'submit form': function (event) {
event.preventDefault();
}
});
...
Template.addTodo.onRendered(function () {
var validator = $('.add-todo').validate({
submitHandler: function (event) {
var todoName = $('[name="todoName"]').val();
var currentList = this._id;
console.log(todoName + " | " + currentList); // Assuming user inputs New Task, this returns New Task | undefined
Meteor.call('createListItem', todoName, currentList, function (error, results) {
if (error) {
validator.showErrors({
todoName: error.reason
});
} else {
$('[name="todoName"]').val('');
}
});
}
});
});
...
}
Meteor.methods({
...
'createListItem': function (todoName, currentList) {
check(todoName, String);
check(currentList, String);
var currentUser = Meteor.userId();
var data = {
name: todoName,
completed: false,
createdAt: new Date(),
createdBy: currentUser,
listId: currentList
}
if(!currentUser){
throw new Meteor.Error("not-logged-in", "You're not logged in.");
}
return Todos.insert(data);
},
});
If you have a Cloud 9 account, you can view the complete code. The server may not always be running the application as I have a free account:
Editor (read-only): https://ide.c9.io/blueknightone/meteor-todos

You need to maintain the reference to the original this, because your context is changing in your submitHandler function. As a result, you could use var self = this; and then access the template's data context via self.data:
Template.addTodo.onRendered(function() {
var self = this;
var validator = $('.add-todo').validate({
submitHandler: function(event) {
var todoName = $('[name="todoName"]').val();
var currentList = self.data._id;
console.log(todoName + " | " + currentList);
Meteor.call('createListItem', todoName, currentList, function(error, results) {
if (error) {
validator.showErrors({
todoName: error.reason
});
} else {
$('[name="todoName"]').val('');
}
});
}
});
});
If you want to use ES6 arrow functions, you could implement the following approach:
Template.addTodo.onRendered(function() {
var validator = $('.add-todo').validate({
submitHandler: (event) => {
var todoName = $('[name="todoName"]').val();
var currentList = this.data._id;
console.log(todoName + " | " + currentList);
Meteor.call('createListItem', todoName, currentList, function(error, results) {
if (error) {
validator.showErrors({
todoName: error.reason
});
} else {
$('[name="todoName"]').val('');
}
});
}
});
});

Related

Meteor - How to give value from child to parent function?

I want to send value of result from child to parent element. I used Session.set and Session.get and it works fine but I know that is not good practice because Sessions are global. So, I wanted to try something like reactive var or reactive dict but both of them are giving me only object as a result. What should I do or how should I take specific things from that object? (I am storing JSON inside that ReactiveVar or Dict and I know that they are really bad with JSON. Thank you for help!
Template.companyCreate.helpers({
CompanyName : function () {
if (Meteor.user() || Roles.userIsInRole(Meteor.user(),['admin','adminCreator'], 'companyAdmin')) {
Meteor.call('findCompany', function(err, result) {
if (err) {
console.log(err.reason)
}
else {
//this is where I want to take result and give it to parent function
}
});
return //this is where I want to take result that was given from child function and return it to CompanyName
}
else {
Router.go('/nemate-prava')
}
},
UPDATED CODE
Template.companyCreate.onCreated(function Poruke() {
this.message = new ReactiveVar(' ');
let self = this;
let user = Meteor.user();
let companyNameHandler = Template.currentData().companyNameHandler;
self.companyName = new ReactiveVar();
if (user && Roles.userIsInRole(user,['admin','adminCreator'], 'companyAdmin')) {
Meteor.call('findCompany', function(err, result) {
if (err) {
console.log(err.reason)
}
else {
self.companyName.set(result);
companyNameHandler(result);
}
});
}
else {
Router.go('/nemate-prava')
}
});
Template.companyCreate.helpers({
message: () => { return Template.instance().message.get() },
isNotInRole : function() {
if (!Meteor.user() || !Roles.userIsInRole(Meteor.user(),['admin','adminCreator'], 'companyAdmin')) {
return true;
}
else {
return false;
}
},
CompanyName : function () {
return Template.instance().companyName.get();
}
});
Template.companyCreate.events({
'submit form': function(event, template) {
var Ime = event.target.Ime.value;
event.preventDefault();
Meteor.call('companyCheck', Ime, function(error, result) {
if (error) {
console.log(error.reason);
template.message.set(error.reason);
alert(error.reason);
}
else {
event.target.Ime.value = "";
console.log('Kompanija je uspesno kreirana!');
template.message.set("Uspesno!");
}
})
},
});
Method:
'findCompany'(){
ImeKompanije = firma.findOne({AdminID: this.userId}).ImeKompanije
if (typeof ImeKompanije == 'undefind') {
throw new Meteor.Error(err, "Greska!");
}
return ImeKompanije;
},
});
Router:
Router.route('/comp/:ImeKompanije', {
name: 'companyProfile',
template: 'companyProfile',
waitOn: function() {
return Meteor.subscribe('bazaFirmi', this.params.ImeKompanije)
},
action: function() {
this.render('companyProfile', {
data: function() {
return firma.findOne({ImeKompanije: this.params.ImeKompanije});
}
});
},
});
ok, there's a lot to unwind here. let's start with something small.
if (Meteor.user() || Roles.userIsInRole(Meteor.user(),['admin','adminCreator'], 'companyAdmin')) {
i think this line is meant to say, "if the user is an admin". but it's really saying, "if the user is logged in." if you meant the first one, then change the "||" to an "&&".
bigger issue is you're making a server call in a helper. helpers can get called over and over, so think of them as something that simply returns data. it should not have any side effects, such as making a server call or (yikes) re-routing the user.
so let's move all that side effect code to the onCreated() and capture the company name so it can be returned from the helper. We'll also get set up to return the company name to the parent.
Template.companyCreate.onCreated(function() {
let self = this;
let user = Meteor.user();
let companyNameHandler = Template.currentData().companyNameHandler;
self.companyName = new ReactiveVar();
if (user && Roles.userIsInRole(user,['admin','adminCreator'], 'companyAdmin')) {
Meteor.call('findCompany', function(err, result) {
if (err) {
console.log(err.reason)
}
else {
self.companyName.set(result);
companyNameHandler(result);
}
});
}
else {
Router.go('/nemate-prava')
}
});
now the helper is really simple, it just returns the data that was saved to the template's reactive var:
Template.companyCreate.helpers({
CompanyName : function () {
return Template.instance().companyName.get();
}
});
the last part is setting up the handler to return the data to the parent. it's bad form to have the client reaching back up to its parent, so i usually have the parent give to the child a function it can call. usually i'll do that when the child says, "i've done my work," but here we can use it to provide that data. i'll have to make some assumptions on what your parent looks like.
<template name="Parent">
{{> companyCreate companyNameHandler=getCompanyNameHandler}}
</template>
Template.Parent.helpers({
getCompanyNameHandler() {
let template = Template.instance();
return function(companyName) {
console.log(companyName);
// you can also access the parent template through the closure "template"
}
}
});
the parent's helper returns a function that is passed to the client. when the client calls it, it will execute in the parent's closure. you can see i set up a variable called "template" that would allow you to, say, access reactive vars belonging to the parent.
UPDATE: in case the handler isn't known as is inside the Meteor.call() scope, we can try using it through a reactive var.
Template.companyCreate.onCreated(function() {
let self = this;
let user = Meteor.user();
self.companyNameHandler = new ReactiveVar(Template.currentData().companyNameHandler);
self.companyName = new ReactiveVar();
if (user && Roles.userIsInRole(user,['admin','adminCreator'], 'companyAdmin')) {
Meteor.call('findCompany', function(err, result) {
if (err) {
console.log(err.reason)
}
else {
self.companyName.set(result);
let fn = self.companyNameHandler.get();
fn(result);
}
});
}
else {
Router.go('/nemate-prava')
}
});

Define autoform hook for forms in for each in Meteor

I have a for each loop in Meteor and I'm using autoform to update each item (just like described at http://autoform.meteor.com/update-each).
My problem is that I normally create notifications through hooks with
AutoForm.hooks({
myFormId: {
onSuccess: function(formType, result) {
Notifications.success('Title', 'Text.');
}
}
});
but since all my forms have unique IDs, I cannot use this. How can I create a hook which matches all forms in a template or has a name which matches a regular expression "unique-id-?" where ? is the docId?
This may not be the optimal solution, but it works:
Template["updateEach"].helpers({
items: function () {
return Items.find({}, {sort: {name: 1}});
},
makeUniqueID: function () {
return "update-each-" + this._id;
}
});
Template.updateEach.onRendered(function () {
var hooksObject = {
onSuccess: function (formType, result) {
Notifications.success('Title', 'Text.');
}
};
var formIds = Items.find().map(function (item) {
return "update-each-" + item._id;
});
AutoForm.addHooks(formIds, hooksObject);
});

MongoDB $pull not working

I am building a Meteor app and I have Contests/Entries collections. When someone enters the contest, their user_id is pushed into the Contest.entered_users array with $addToSet. Here is the code:
entryInsert: function(entryAttributes) {
check(Meteor.userId(), String);
check(entryAttributes, {
contest_id: String
});
var user = Meteor.user();
var entry = _.extend(entryAttributes, {
user_id: user._id,
user_name: user.profile.name,
submitted: new Date(),
submitted_day: moment().format('MMM D')
});
var currentContest = Contests.findOne(entryAttributes.contest_id);
// Check to make sure that the person has not already entered the giveaway
if (currentContest.entered_users.indexOf(entry.user_id) !== -1) {
throw new Meteor.Error('invalid', "You have already entered the giveaway");
} else {
Contests.update(
currentContest._id,
{
$addToSet: {entered_users: entry.user_id},
$inc: {entries: 1}}
);
}
// Create entry in order to get the entry id
var entryId = Entries.insert(entry, function(err) {
if (err) {
alert(err.reason);
}
});
return {
_id: entryId
}
}
I want to remove a persons user_id from the Contest.entered_users array when an entry is removed. I am trying to use $pull but it doesn't appear to be working... When I remove an entry, the entry.user_id is still in the contest.entered_users array. Here is the relevant code:
'click .entry-delete': function(e, tmpl) {
e.preventDefault();
var currentEntry = this;
var currentEntryId = this._id;
var contestId = Contests.findOne(currentEntry.contest_id);
// Update the contest by removing the entry's useer_id from entered_users
Meteor.call('contestRemoveEntry', contestId, currentEntry, function(error) {
if (error) {
alert(error.reason);
}
});
Meteor.call('entryRemove', currentEntryId, function(error) {
if(error) {
alert(error.reason);
}
});
}
Here is the contestRemoveEntry method:
contestRemoveEntry: function(contestId, currentEntry) {
Contests.update({ _id: contestId }, { $pull: { entered_users: currentEntry.user_id } } );
}
Any ideas as to why this is not working? I've tried other SO solutions but nothing seems to be working.
It appears that this is the correct way to make $pull work:
Contests.update(contestId, { $pull: { entered_users: currentEntry.user_id } } );

How to retrieve a specific object with Parse.Query

I am using Parse to create a WebApp and I am trying to get an instance of an object Productwith this code:
getProduct: function() {
var productClass = Parse.Object.extend("Product");
var query = new Parse.Query(productClass);
var result = query.get(productId, {
success: function(object) {
console.log(object.get("productName"));
},
error: function(object, error) {
...
}
});
return result;
}
I get a:
result.get is not a function
Printing the object only, I realized that I do not get a Product, I get this in the console (Safari):
[Log] Object (views.js, line 269)
_rejected: false
_rejectedCallbacks: Array[0]
_resolved: true
_resolvedCallbacks: Array[0]
_result: Arguments[1]
__proto__: Object
I tried many ways, but I am not able to retrieve a Product object and its attributes. What am I doing wrong?
EDIT
I am adding my Products View:
window.app.Products = Parse.View.extend({
template: _.template($('#products-template').html()),
el: $('body'),
content: $('#content'),
...
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
render: function () {
$(this.content).html(this.template);
return this;
},
...
getUser: function() {
return Parse.User.current();
},
getUserProduct: function() {
var promise = new Parse.Promise();
var productClass = Parse.Object.extend("Product");
var query = new Parse.Query(productClass);
query.equalTo("objectId", this.getUser().get("product").id);
query.first().then(function(result) {
if(result){
// If result was defined, the object with this objectID was found
promise.resolve(result);
} else {
console.log("Product was not found");
promise.resolve(null);
}
}, function(error){
console.error("Error searching for Product. Error: " + error);
promise.error(error);
});
return promise;
},
setProduct: function() {
this.getUserProduct().then(function(result) {
if(result){
console.log(result.get("productName"));
var productName = result.get("productName");
} else {
console.log("Could not set Product");
}
}, function(error){
console.log("Error: " + error);
});
}
});
I was trying by having a list of parameters and updating them like:
info: {
user: '...',
product: '...'
}
Then passing it to the template:
$(this.content).html(this.template(this.info));
But I am not able to update product.
As I wrote this, I realised that you really aren't saving all that much code by pulling the product search into it's own method. My hope is that it will at least demonstrate to you how to create and call your own custom async methods. For example you may have a query which is much more complex than the current query, or it may perform multiple queries before finding the desired response, in which case it would make sense to pull it into it's own method.
Get Product Async Method
This method retrieves the Product with the given objectID
var getProduct = function(productId) {
var promise = new Parse.Promise();
var Product = Parse.Object.extend("Product");
var query = new Parse.Query(Product);
query.equalTo("objectId",productId);
query.first().then(function(result){
if(result){
// If result was defined, the object with this objectID was found
promise.resolve(result);
} else {
console.log("Product ID: " + productId + " was not found");
promise.resolve(null);
}
}, function(error){
console.error("Error searching for Product with id: " + productId + " Error: " + error);
promise.error(error);
});
return promise;
}
Calling Method
An example of a simple method which calls the above method.
var myMethod = function(){
var productID = "12345678";
getProduct(productID).then(function(result){
if(result){
console.log(result.get("productName"));
var productName = result.get("productName");
var productPrice = result.get("productPrice");
// Now that you have some relevant information about your product
// you could render it out to an Express template, or use this
// value in a calculation etc.
} else {
console.log("Product with objectId: " + productID + " was not found");
}
}, function(error){
console.log("Error: " + error);
});
}
Notes
As these methods are asynchronous, there is no real data being
returned in the 'return value' (the method returns a promise).
Instead we return the relevant data as a result of the promise (where
you see promise.resolve(XXX))
It doesn't make any sense to have a
mutable global variable in this Node.js style architecture.

Meteor JS: How do I assign the results of Meteor.call to a variable?

I am trying to insert a documents into collections which are all related to each other: Posts, Comments, and Categories. Each document in Comments and Categories must have a PostId field.
I have created a method named insertSamplePost, which should return the id of the post after inserting a document into Posts. I have assigned this method call to a variable like so:
var postId = Meteor.call('insertSamplePost', samplePost, function(error, id) {
if (error) {
console.log(error);
} else {
return id;
}
});
However, when I try to use postId later to insert related comments and categories, it appears to be undefined! Does anyone know what is happening?
Here is my full code:
if (Meteor.isClient) {
Template.post.events({
'click .new-sample-post' : function (e) {
var samplePost = {
title: "This is a title",
description: "This is a description"
};
// Insert image stub
var postId = Meteor.call('insertSamplePost', samplePost, function(error, id) {
if (error) {
console.log(error);
} else {
return id;
}
});
// This returned undefined. :-()
console.log(postId);
var sampleComment = {
body: "This is a comment",
postId: postId
};
var sampleCategory = {
tag: "Sample Category",
postId: postId
};
Comments.insert(sampleComment);
Categories.insert(sampleCategory);
}
});
}
// Collections
Posts = new Meteor.Collection('posts');
Comments = new Meteor.Collection('comments');
Categories = new Meteor.Collection('categories');
// Methods
Meteor.methods({
insertSamplePost: function(postAttributes) {
var post = _.extend(postAttributes, {
userId: "John Doe",
submitted: new Date().getTime()
});
return Posts.insert(post);
}
});
When you do:
var myVar = Meteor.call("methodName", methodArg, function(error, result) {
return result;
}
Your myVar variable will actually be whatever Meteor.call() returns, not what your callback function returns. Instead, what you can do is:
var postId;
Meteor.call('insertSamplePost', samplePost, function(error, id) {
if (error) {
console.log(error);
} else {
postId = id;
}
});
However, as Akshat mentions, by the time the callback function actually runs and asynchronously sets the postId, your insert calls on the other collections will already have run.
This code would actually be a little simpler if you avoid the server method altogether - you can modify the document in your collection's allow callback:
Template.post.events({
'click .new-sample-post' : function (e) {
var samplePost = {
title: "This is a title",
description: "This is a description"
};
var postId = Posts.insert(samplePost);
var sampleComment = {
body: "This is a comment",
postId: postId
};
var sampleCategory = {
tag: "Sample Category",
postId: postId
};
Comments.insert(sampleComment);
Categories.insert(sampleCategory);
}
});
Now you can add the userId and submitted fields in your Posts.allow() callback:
Posts.allow({
insert: function(userId, doc) {
doc.userId = userId;
doc.submitted = new Date().getTime();
return true;
}
});
If you wanted, you can still do the two secondary inserts within the callback for your first insert, in order to make the operation more atomic (in other words, to make sure the secondary inserts don't happen if the first insert fails).
You can use Session to store the results since Session is reactive and client side javascript is asynchronous so you cant assign the result to a variable directly using return.
So the reason you get undefined is because the result of Meteor.call is given in the callback. The callback will yield a result much later and by the time it returns a result the rest of your code will already have run. This is why its a good idea to use Session because you can use it in a template helper.
However for inserting a post its better to just insert the comments and category in the callback itself since you're not displaying the result in the html.
Meteor.call('insertSamplePost', samplePost, function(error, postId) {
if (error) {
console.log(error);
} else {
var sampleComment = {
body: "This is a comment",
postId: postId
};
var sampleCategory = {
tag: "Sample Category",
postId: postId
};
Comments.insert(sampleComment);
Categories.insert(sampleCategory);
}
});
This way if the result is an error the comment and category wont be inserted.

Categories