Delete URL field in Discover Meteor Book - javascript

I'm trying to delete the "URL" part in the Discover Meteor Book so that it is more of a chat room. I deleted and changed the post_submit.js so that the error statement return is true. When I goto the "Submit Post" and only submit in the "Text" field and no "URL", it won't submit.
Do I need to change anything in the post_item.js or posts.js in the lib/collections?
Most of this issue seems to be in the Chapter 7 part when the method is introduced.
post_submit.js
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
url: $(e.target).find('[name=url]').val(),
title: $(e.target).find('[name=title]').val()
};
Meteor.call('postInsert', post, function(error, result) {
// display the error to the user and abort
if (error)
return true;
// show this result but route anyway
if (result.postExists)
return true;
Router.go('postsList', {_id: result._id});
});
}
});
post_item.js
Template.postItem.helpers({
ownPost: function() {
return this.userId === Meteor.userId();
},
domain: function() {
var a = document.createElement('a');
a.href = this.url;
return a.hostname;
}
});
Does this.url do anyting?
posts.js
Posts = new Mongo.Collection('posts');
Meteor.methods({
postInsert: function(postAttributes) {
check(this.userId, String);
check(postAttributes, {
title: String,
url: String
});
var postWithSameLink = Posts.findOne({url: postAttributes.url});
if (postWithSameLink) {
return {
postExists: true,
_id: postWithSameLink._id
}
}
var user = Meteor.user();
var post = _.extend(postAttributes, {
userId: user._id,
author: user.username,
submitted: new Date()
});
var postId = Posts.insert(post);
return {
_id: postId
};
}
});
url is in here too..
post_submit.html
<template name="postSubmit">
<form class="main form page">
<div class="form-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" id="title" type="text" value="" placeholder="Name your post" class="form-control"/>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
</template>

You should remove all references to the url if you don't want it.
Check out the project: git clone https://github.com/DiscoverMeteor/Microscope.git
Look for all files containing "url": grep -i -r url *
Results:
client/templates/posts/post_item.html: <h3>{{title}}<span>{{domain}}</span></h3>
client/templates/posts/post_submit.js: url: $(e.target).find('[name=url]').val(),
client/templates/posts/post_submit.js: if (errors.title || errors.url)
client/templates/posts/post_submit.html: <div class="form-group {{errorClass 'url'}}">
client/templates/posts/post_submit.html: <label class="control-label" for="url">URL</label>
client/templates/posts/post_submit.html: <input name="url" id="url" type="text" value="" placeholder="Your URL" class="form-control"/>
client/templates/posts/post_submit.html: <span class="help-block">{{errorMessage 'url'}}</span>
client/templates/posts/post_edit.js: url: $(e.target).find('[name=url]').val(),
client/templates/posts/post_edit.js: if (errors.title || errors.url)
client/templates/posts/post_edit.html: <div class="form-group {{errorClass 'url'}}">
client/templates/posts/post_edit.html: <label class="control-label" for="url">URL</label>
client/templates/posts/post_edit.html: <input name="url" id="url" type="text" value="{{url}}" placeholder="Your URL" class="form-control"/>
client/templates/posts/post_edit.html: <span class="help-block">{{errorMessage 'url'}}</span>
client/templates/posts/post_item.js: a.href = this.url;
lib/collections/posts.js: return (_.without(fieldNames, 'url', 'title').length > 0);
lib/collections/posts.js: return errors.title || errors.url;
lib/collections/posts.js: if (!post.url)
lib/collections/posts.js: errors.url = "Please fill in a URL";
lib/collections/posts.js: url: String
lib/collections/posts.js: if (errors.title || errors.url)
lib/collections/posts.js: throw new Meteor.Error('invalid-post', "You must set a title and URL for your post");
lib/collections/posts.js: var postWithSameLink = Posts.findOne({url: postAttributes.url});
server/fixtures.js: url: 'http://sachagreif.com/introducing-telescope/',
server/fixtures.js: url: 'http://meteor.com',
server/fixtures.js: url: 'http://themeteorbook.com',
server/fixtures.js: url: 'http://google.com/?q=test-' + i,
Those are the files the need to be modified.
post_item.html:
<template name="postItem">
<div class="post">
upvote
<div class="post-content">
<h3>{{title}}</h3>
<p>
{{pluralize votes "Vote"}},
submitted by {{author}},
{{pluralize commentsCount "comment"}}
{{#if ownPost}}Edit{{/if}}
</p>
</div>
Discuss
</div>
</template>
post_submit.js:
Template.postSubmit.onCreated(function() {
Session.set('postSubmitErrors', {});
});
Template.postSubmit.helpers({
errorMessage: function(field) {
return Session.get('postSubmitErrors')[field];
},
errorClass: function (field) {
return !!Session.get('postSubmitErrors')[field] ? 'has-error' : '';
}
});
Template.postSubmit.events({
'submit form': function(e) {
e.preventDefault();
var post = {
title: $(e.target).find('[name=title]').val()
};
var errors = validatePost(post);
if (errors.title)
return Session.set('postSubmitErrors', errors);
Meteor.call('postInsert', post, function(error, result) {
// display the error to the user and abort
if (error)
return throwError(error.reason);
Router.go('postPage', {_id: result._id});
});
}
});
post_submit.html:
<template name="postSubmit">
<form class="main form page">
<div class="form-group {{errorClass 'title'}}">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" id="title" type="text" value="" placeholder="Name your post" class="form-control"/>
<span class="help-block">{{errorMessage 'title'}}</span>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
</template>
post_edit.js:
Template.postEdit.onCreated(function() {
Session.set('postEditErrors', {});
});
Template.postEdit.helpers({
errorMessage: function(field) {
return Session.get('postEditErrors')[field];
},
errorClass: function (field) {
return !!Session.get('postEditErrors')[field] ? 'has-error' : '';
}
});
Template.postEdit.events({
'submit form': function(e) {
e.preventDefault();
var currentPostId = this._id;
var postProperties = {
title: $(e.target).find('[name=title]').val()
}
var errors = validatePost(postProperties);
if (errors.title)
return Session.set('postEditErrors', errors);
Posts.update(currentPostId, {$set: postProperties}, function(error) {
if (error) {
// display the error to the user
throwError(error.reason);
} else {
Router.go('postPage', {_id: currentPostId});
}
});
},
'click .delete': function(e) {
e.preventDefault();
if (confirm("Delete this post?")) {
var currentPostId = this._id;
Posts.remove(currentPostId);
Router.go('home');
}
}
});
post_edit.html:
<template name="postEdit">
<form class="main form page">
<div class="form-group {{errorClass 'title'}}">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" id="title" type="text" value="{{title}}" placeholder="Name your post" class="form-control"/>
<span class="help-block">{{errorMessage 'title'}}</span>
</div>
</div>
<input type="submit" value="Submit" class="btn btn-primary submit"/>
<hr/>
<a class="btn btn-danger delete" href="#">Delete post</a>
</form>
</template>
post_item.js:
Template.postItem.helpers({
ownPost: function() {
return this.userId == Meteor.userId();
},
upvotedClass: function() {
var userId = Meteor.userId();
if (userId && !_.include(this.upvoters, userId)) {
return 'btn-primary upvotable';
} else {
return 'disabled';
}
}
});
Template.postItem.events({
'click .upvotable': function(e) {
e.preventDefault();
Meteor.call('upvote', this._id);
}
});
posts.js:
Posts = new Mongo.Collection('posts');
Posts.allow({
update: function(userId, post) { return ownsDocument(userId, post); },
remove: function(userId, post) { return ownsDocument(userId, post); },
});
Posts.deny({
update: function(userId, post, fieldNames) {
// may only edit the following fields:
return (_.without(fieldNames, 'title').length > 0);
}
});
Posts.deny({
update: function(userId, post, fieldNames, modifier) {
var errors = validatePost(modifier.$set);
return errors.title;
}
});
validatePost = function (post) {
var errors = {};
if (!post.title)
errors.title = "Please fill in a headline";
return errors;
}
Meteor.methods({
postInsert: function(postAttributes) {
check(this.userId, String);
check(postAttributes, {
title: String
});
var errors = validatePost(postAttributes);
if (errors.title)
throw new Meteor.Error('invalid-post', "You must set a title for your post");
var user = Meteor.user();
var post = _.extend(postAttributes, {
userId: user._id,
author: user.username,
submitted: new Date(),
commentsCount: 0,
upvoters: [],
votes: 0
});
var postId = Posts.insert(post);
return {
_id: postId
};
},
upvote: function(postId) {
check(this.userId, String);
check(postId, String);
var affected = Posts.update({
_id: postId,
upvoters: {$ne: this.userId}
}, {
$addToSet: {upvoters: this.userId},
$inc: {votes: 1}
});
if (! affected)
throw new Meteor.Error('invalid', "You weren't able to upvote that post");
}
});
fixtures.js:
// Fixture data
if (Posts.find().count() === 0) {
var now = new Date().getTime();
// create two users
var tomId = Meteor.users.insert({
profile: { name: 'Tom Coleman' }
});
var tom = Meteor.users.findOne(tomId);
var sachaId = Meteor.users.insert({
profile: { name: 'Sacha Greif' }
});
var sacha = Meteor.users.findOne(sachaId);
var telescopeId = Posts.insert({
title: 'Introducing Telescope',
userId: sacha._id,
author: sacha.profile.name,
submitted: new Date(now - 7 * 3600 * 1000),
commentsCount: 2,
upvoters: [], votes: 0
});
Comments.insert({
postId: telescopeId,
userId: tom._id,
author: tom.profile.name,
submitted: new Date(now - 5 * 3600 * 1000),
body: 'Interesting project Sacha, can I get involved?'
});
Comments.insert({
postId: telescopeId,
userId: sacha._id,
author: sacha.profile.name,
submitted: new Date(now - 3 * 3600 * 1000),
body: 'You sure can Tom!'
});
Posts.insert({
title: 'Meteor',
userId: tom._id,
author: tom.profile.name,
submitted: new Date(now - 10 * 3600 * 1000),
commentsCount: 0,
upvoters: [], votes: 0
});
Posts.insert({
title: 'The Meteor Book',
userId: tom._id,
author: tom.profile.name,
submitted: new Date(now - 12 * 3600 * 1000),
commentsCount: 0,
upvoters: [], votes: 0
});
for (var i = 0; i < 10; i++) {
Posts.insert({
title: 'Test post #' + i,
author: sacha.profile.name,
userId: sacha._id,
submitted: new Date(now - i * 3600 * 1000 + 1),
commentsCount: 0,
upvoters: [], votes: 0
});
}
}

Related

How can I check if fields are empty on Send Message button?

<template>
<div>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" v-model="firstName" placeholder="Enter your name">
</div>
<div class="form-group">
<label for="lastName">Last name</label>
<input type="text" class="form-control" v-model="lastName" placeholder="Enter your last name">
</div>
<div class="form-group">
<label for="message">Type Your message</label>
<textarea class="form-control" v-model="message" rows="3"></textarea>
</div>
<div class="form-group form-check" v-for="number in numbers" :key="number">
<input type="checkbox" :value="number.Broj" v-model="checkedNumbers">
<label class="form-check-label" >{{number.Broj}}</label>
</div>
<button type="submit" class="btn btn-primary" v-on:click="alert" #click="sendMessage">Send message</button>
</div>
</template>
<script>
import http from "../http-common.js";
import userServices from "../services/userServices.js";
export default {
data()
{
return {
firstName: null,
lastName: null,
message: null,
numbers: "",
checkedNumbers: [],
success: 'You have submitted form successfully'
};
},
methods:
{
async sendMessage()
{
await http.post("/message", {firstName: this.firstName, lastName: this.lastName, message: this.message, numbers: this.checkedNumbers});
this.$data.firstName = "",
this.$data.lastName = "",
this.$data.checkedNumbers = [],
this.$data.message = "";
},
alert() {
alert(this.success)
if(event)
alert(event.target.tagName)
},
retrieveNumbers() {
userServices.getNumbers().then(response => {
this.numbers = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
},
created() {
this.retrieveNumbers();
}
}
</script>
So I want to add the option of checking input fields when user clicks "Send Message" button. I tried some options but I faield at that. So please I would appretiate if someone would help me. I'm still learning.
I know I have to use v-if and create the method for checking the fields.
So if you would be most kind and help me solve this problem I would be really grateful.
Thank you dev, community <3
Can I please get a concrete answer. Because I'll learn in that way, so please without condescending and "no-answers"
You can do it manually :
<script>
import http from "../http-common.js";
import userServices from "../services/userServices.js";
export default {
data()
{
return {
firstName: null,
lastName: null,
message: null,
numbers: "",
checkedNumbers: [],
success: 'You have submitted form successfully'
};
},
methods:
{
async sendMessage()
{
if(!(this.firstName && this.lastName && this.numbers)) return;
await http.post("/message", {firstName: this.firstName, lastName: this.lastName, message: this.message, numbers: this.checkedNumbers});
this.$data.firstName = "",
this.$data.lastName = "",
this.$data.checkedNumbers = [],
this.$data.message = "";
},
alert() {
alert(this.success)
if(event)
alert(event.target.tagName)
},
retrieveNumbers() {
userServices.getNumbers().then(response => {
this.numbers = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
}
},
created() {
this.retrieveNumbers();
}
}
</script>
Or you can this usefull library
https://vuelidate.js.org/#sub-basic-form
You can simply define a method to check the fields and call that before the HTTP request in the sendMessage method.
You can initialize your data as an empty string "" and have a method like this:
validateForm() {
return this.firstName != "" && this.lastName != "" && this.message != ""
}
Update your sendMessage method to something like this:
async sendMessage() {
const isFormValid = this.validateForm()
if (isFormValid) {
await http.post(....)
...
}
}

How to accept Bitcoin and USD payments using Stripe?

REFERENCE:
https://stripe.com/docs/sources/bitcoin
QUESTION:
I am trying to integrate Bitcoin payments to my code using Stripe.
From my understanding of the docs, I should create a Source object by creating a new form and on submit feed the data (in this case email and amount) to :
stripe.createSource({
type: 'bitcoin',
amount: 1000,
currency: 'usd',
owner: {
email: 'jenny.rosen#example.com',
},
}).then(function(result) {
// handle result.error or result.source
});
Then pass the result to the server to charge the Source object. Only issue: I don't see how to pass the result to the server. Should I create a new handler ?
var handler = StripeCheckout.configure({
key: 'key',
locale: 'auto',
name: 'website',
description: 'Secure Payment',
token: function(token) {
$('#stripeToken').val(token.id);
$("#stripeEmail").val(token.email);
$('form').submit();
}
});
I am lost.
Here is the code I currently have that works perfectly for USD payments.
MY CODE:
clientside (payment.ejs)
<% include partials/header %>
<div class="background">
<div class="message">
<div class="paymentBlock">
<h1 class="title">TITLE</h1>
<form class="paymentForm" action="/payment/charge" method="POST">
<input id="inputAmount" class="amountInput" name="amount" type="number/>
<input type="hidden" id="stripeToken" name="stripeToken" />
<input type="hidden" id="stripeEmail" name="stripeEmail"/>
<button type="submit" class="btn btn-success" id="paymentButton" >Submit Payment</button>
</form>
</div>
</div>
</div>
<script src="https://checkout.stripe.com/checkout.js"></script>
<script>
var handler = StripeCheckout.configure({
key: 'key',
locale: 'auto',
name: 'website',
description: 'Secure Payment',
token: function(token) {
$('#stripeToken').val(token.id);
$("#stripeEmail").val(token.email);
$('form').submit();
}
});
$('#paymentButton').on('click', function(e) {
e.preventDefault();
$('#error_explanation').html('');
var amount = $('#inputAmount').val();
amount = amount.replace(/\$/g, '').replace(/\,/g, '');
amount = parseFloat(amount);
if (isNaN(amount)) {
$('#error_explanation').html('<p>Please enter a valid amount in USD ($).</p>');
}
else if (amount < 1.00) {
$('#error_explanation').html('<p>Payment amount must be at least $1.</p>');
}
else {
amount = amount * 100;
handler.open({
amount: Math.round(amount)
})
}
});
// Close Checkout on page navigation
$(window).on('popstate', function() {
handler.close();
});
</script>
<% include partials/indexScripts %>
serverside (payment.js)
router.post("/", (req, res) => {
var amount = req.body.amount;
var object;
var ARef = admin.database().ref("ref");
var ARefList;
amount = amount * 100;
var object = {
amount: amount,
email: email,
inversedTimeStamp: now
}
stripe.customers.create({
email: req.body.stripeEmail,
source: req.body.stripeToken
})
.then(customer =>
stripe.charges.create({
amount: amount,
description: "desc",
currency: "usd",
customer: customer.id
})
)
.then(charge =>
ARef.transaction(function(dbAmount){
if (!dbAmount) {
dbAmount = 0;
}
dbAmount = dbAmount + amount/100;
return dbAmount;
})
)
.then( () =>
ARef.push(object)
)
.then ( () =>
ARefList.push(object)
)
.then( () =>
res.render("received",{amount:amount/100})
);
});
Thank you to "pksk321" from the freenode IRC #stripe for helping me with this !
Apparently, all that was needed was to add bitcoin: true to the handler, like so :
clientside
var handler = StripeCheckout.configure({
key: 'key',
locale: 'auto',
name: 'website',
description: 'Secure Payment',
bitcoin: true,
token: function(token) {
$('#stripeToken').val(token.id);
$("#stripeEmail").val(token.email);
$('form').submit();
}
});

How do I render a handlebars layout in node.js from a button?

This is a really weird issue I am having.
I have a login form, this login form verifies your data and renders the Profile layout if the login is successful OR renders the register page if the login is not.
exports.logIn = function (req, res, data) {
var username = req.body.username.toString();
var password = req.body.password.toString();
connection.connection();
global.connection.query('SELECT * FROM Utilizador WHERE Nome_Utilizador = ? LIMIT 1', [username], function (err, result) {
if (result.length > 0) {
if (result) {
var object = JSON.parse(JSON.stringify(result));
var userObject = object[0];
var userQ = object[0].Nome_Utilizador;
global.connection.query('SELECT Password_Utilizador from Utilizador where Nome_Utilizador = ?', [username], function (err, result) {
console.log(result);
if (result.length > 0) {
if (result) {
var object2 = JSON.parse(JSON.stringify(result));
var passQ = object[0].Password_Utilizador;
if (password == passQ) {
console.log("Login efectuado com sucesso");
console.log(userObject);
res.render('home', { title: 'perfil', layout: 'perfil', data: userObject });
} else {
console.log("1");
}
}
} else if (err) {
console.log("asdsadas");
} else {
console.log("2");
res.render('home', { title: 'perfil', layout: 'registo' });
}
});
}
} else if (err) {
console.log(err);
} else {
console.log("Utilizador nao encontrado");
res.render('home', { title: 'perfil', layout: 'registo' });
}
});
};
This works.
And the only reason why it does work is because it comes from a FORM with a METHOD and an ACTION
<form id="login-nav" action="/login" method='POST' role="form" accept-charset="UTF-8" class="form">
<div class="form-group">
<label for="username" class="sr-only">Utilizador</label>
<input id="username" type="username" placeholder="Nome de utilizador" required="" class="form-control" name="username">
</div>
<div class="form-group">
<label for="exampleInputPassword2" class="sr-only">Palavra-Passe</label>
<input id="password" type="password" placeholder="Meta a palavra-passe" required="" class="form-control" name="password">
</div>
<div class="checkbox">
<label></label>
<input type="checkbox">Gravar Dados
</div>
<div class="form-group">
<button id="botaoLogin" class="btn btn-danger btn-block">Fazer Login</button>
</div>
</form>
However, I tried to do the same thing with jQuery, as I need to render a Handlebars layout for some products on button click,
$("#pacotes").on('click', ".produto", function () {
var prod = this.id;
console.log(prod);
$.get("http://localhost:3000/pacote?idPacote=" + prod);
});
And despite the query working and giving me the data I requested
exports.Pacote = function (req, res) {
var pacote = req.query.idPacote;
connection.connection();
global.connection.query('SELECT * FROM Pacotes WHERE idPacotes = ? ', [pacote], function (err, result) {
if (result.length > 0) {
if (result) {
var object = JSON.parse(JSON.stringify(result));
var packObject = object[0];
console.log(result);
res.render('home', { title: 'pacote', layout: 'pacote', data: packObject });
} else if (err) {
console.log(err);
}
};
});
}
It simply doesn't render the layout and I have no idea why.
What is the difference between doing a POST request like this or doing it by a form?
I don't understand why this only seems to work with forms.
I could solve it that way, but I don't think using empty forms for all my buttons would be a viable solution.
You are only making a request, you are not processing the return value:
$.get("http://localhost:3000/pacote?idPacote=" + prod);
Try changing to something like:
$.ajax({
method: 'GET',
url: "http://localhost:3000/pacote?idPacote=" + prod,
success: function(...) {...}
});

how do i store ng-repeat array form data in meanjs?

I am using MEANJS from meanjs.org.
trying to store ng-repeat array data for deal which has dealtype and dealprice as array under it, but unable to do so.
i have setup addFields for the ng-repeat input tag in the create form, but it does not store the data. here is my code.
food.server.model.js
var FoodSchema = new Schema({
created: {
type: Date,
default: Date.now
},
name: {
type: String,
default: '',
required: 'Please fill Food name',
trim: true
},
deal: [{
dealtype: {
type: String,
default: '',
trim: true
},
dealprice: {
type: String,
default: '',
trim: true
}
}],
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
foods.client.controller.js
// initial array setup
var deal = [ { dealtype: '',dealprice: '' }, {dealtype: '',dealprice: '' } ];
$scope.food = {};
$scope.food.deal = deal;
$scope.addItem = function() {
$scope.food.deal.push({
dealtype: '',
dealprice: ''
});
};
// Create new Food
$scope.create = function (isValid) {
$scope.error = null;
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'foodForm');
return false;
}
// Create new Food object
var food = new Foods({
name: this.name,
deal:[{
dealtype: this.dealtype,
dealprice: this.dealprice,
}],
});
// Redirect after save
food.$save(function (response) {
$location.path('foods/' + response._id);
// Clear form fields
$scope.name = '';
$scope.dealtype = '';
$scope.dealprice = '';
}, function (errorResponse) {
$scope.error = errorResponse.data.message;
});
};
create-food.client.view.html
<form name="foodForm" class="form-horizontal" ng-submit="create(foodForm.$valid)" novalidate>
<fieldset>
<div class="col-md-12">
<md-input-container flex="">
<label >Food Name</label>
<input type="text" data-ng-model="name" id="name" required>
</md-input-container>
</div>
<div ng-repeat="de in food.deal">
<div class="col-md-6">
<md-input-container class="">
<label class="" for="dealtype">Dealtype</label>
<input type="text" data-ng-model="de.dealtype" id="dealtype" >
</md-input-container>
</div>
<div class="col-md-6">
<md-input-container class="">
<label class="" for="dealprice">Dealprice</label>
<input type="text" data-ng-model="de.dealprice" id="dealprice" >
</md-input-container>
</div>
</div>
<a href ng:click="addItem()" class="btn btn-small">add item</a>
<button class="md-primary md-raised width-100 md-whiteframe-5dp" type="submit">Create Food</button>
<div ng-show="error" class="text-danger">
<strong ng-bind="error"></strong>
</div>
</fieldset>
</form>
Try to set your schema up without the embedded array and send without deal. If that works you may need to set up your schema with a subdocument.
If you are wanting to set your deal property up as a Subdocument you would need to follow this. You would define a schema for deal separately. Something like this:
var dealSchema = new Schema({ {
dealtype: {
type: String,
default: '',
trim: true
},
dealprice: {
type: String,
default: '',
trim: true
}
} });
var FoodSchema = new Schema({
...,
deal: [dealSchema]
})

Backbone.js validation error appears in more than one view

I'm having trouble with a backbone.js app I'm working on as a learning exercise. I set up a jsfiddle for it.
Here's the JavaScript:
var app = {};
$(document).ready(function() {
app.Contact = Backbone.Model.extend({
defaults: {
firstName: '',
lastName: '',
email: ''
},
validate: function(attrs) {
var errors = [];
if (attrs.firstName.trim() == "") {
errors.push({
'message': 'Please enter a first name.',
'field': 'firstName'
});
}
if (attrs.lastName.trim() == "") {
errors.push({
'message': 'Please enter a last name.',
'field': 'lastName'
});
}
if (attrs.email.trim() == "") {
errors.push({
'message': 'Please enter an email address.',
'field': 'email'
});
}
if (errors.length) {
return errors;
} else {
return false;
}
}
});
app.ContactList = Backbone.Collection.extend({
model: app.Contact,
localStorage: new Store('backbone-addressbook')
});
app.contactList = new app.ContactList();
app.ContactView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#contact-template').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
initialize: function() {
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
var self = this;
this.model.on('invalid', function(model, errors) {
_.each(errors, function(error, i) {
console.log(self.el);
$(self.el).find('[data-field="' + error.field + '"]').parent().addClass('has-error');
$(self.el).find('[data-field="' + error.field + '"]').parent().find('.help-block').remove();
$(self.el).find('[data-field="' + error.field + '"]').parent().append('<span class="help-block">' + error.message + '</span>');
});
});
this.model.on('change', function(model, response) {
//console.log(self.el);
$(self.el).removeClass('editing');
this.render;
})
},
events: {
'dblclick label': 'edit',
'keypress .edit': 'updateOnEnter',
'click .destroy': 'destroy',
'click .save': 'close'
},
edit: function(e) {
this.$el.addClass('editing');
$(e.currentTarget).next('input').focus();
},
updateOnEnter: function(e) {
if (e.which == 13) {
this.close(e);
}
},
close: function(e) {
e.preventDefault();
var updateObject = {};
$(this.el).find('input[type="text"]').each(function() {
node = $(this);
updateObject[node.data('field')] = node.val();
});
this.model.save(updateObject, {validate: true});
},
destroy: function() {
this.model.destroy();
}
});
app.AppView = Backbone.View.extend({
el: '#newContact',
initialize: function() {
$(this).find('.has-error').removeClass('has-error');
$(this).remove('.help-block');
app.contactList.on('add', this.addOne, this);
app.contactList.fetch();
var self = this;
app.contactList.on('invalid', function(model, errors) {
_.each(errors, function(error, i) {
console.log(self.el);
$(self.el).find('[data-field="' + error.field + '"]').parent().addClass('has-error');
$(self.el).find('[data-field="' + error.field + '"]').parent().find('.help-block').remove();
$(self.el).find('[data-field="' + error.field + '"]').parent().append('<span class="help-block">' + error.message + '</span>');
});
});
},
events: {
'click .add': 'createContact'
},
createContact: function(e) {
e.preventDefault();
app.contactList.create(this.newAttributes(), {validate: true});
},
addOne: function(contact) {
var view = new app.ContactView({model: contact});
$('#contactList').append(view.render().el);
$('form input[type="text"]').val('');
$('form input[type="text"]').parent().removeClass('has-error');
$('.help-block').remove();
},
newAttributes: function() {
var updateObject = {};
$(this.el).find('input[type="text"]').each(function() {
node = $(this);
updateObject[node.data('field')] = node.val();
});
return updateObject;
},
});
app.appView = new app.AppView();
});
And here's the HTML:
<div class="container">
<section id="addressbookapp">
<header id="header">
<h1>Address Book</h1>
<div class="well">
<form id="newContact" action="#" role="form">
<div class="form-group">
<label class="control-label" for="firstName">First Name</label>
<input data-field="firstName" class="newFirstName form-control input-sm" type="text" />
</div>
<div class="form-group">
<label class="control-label" for="lastName">Last Name</label>
<input data-field="lastName" class="newLastName form-control input-sm" type="text" />
</div>
<div class="form-group">
<label class="control-label" for="email">Email Address</label>
<input data-field="email" class="newEmail form-control input-sm" type="text" />
</div>
<button class="add btn-xs">Add</button>
</form>
</div>
</header>
<section id="main">
<table class="table table-striped">
<caption>Double-click to edit an entry.</caption>
<thead>
<tr>
<th>First</th>
<th>Last</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contactList"></tbody>
</table>
</section>
</section>
</div>
<script id="contact-template" type="text/template">
<form action="#" role="form">
<td>
<label class="control-label" for="firstName"><%- firstName %></label>
<input data-field="firstName" class="firstName input-sm edit" value="<%- firstName %>" type="text" />
</td>
<td>
<label class="control-label" for="lastName"><%- lastName %></label>
<input data-field="lastName" class="lastName input-sm edit" value="<%- lastName %>" type="text" />
</td>
<td>
<label class="control-label" for="email"><%- email %></label>
<input data-field="email" class="email input-sm edit" value="<%- email %>" type="email" />
</td>
<td>
<button class="btn-xs save">Save</button>
<button class="btn-xs destroy">Delete</button>
</td>
</form>
</script>
Specifically, when the user edits an entry in the list (by double-clicking), clears an input (a last name, for example) and then tries to save, there's (correctly) a validation error. The problem is that the form at the top (for creating a new entry) is also responding to the invalid event.
My question is not just how to keep this from happening but what would be the ideal way to organize things. This is a learning exercise for me, so I'd thankful for any tips -- anything you see that could be improved.
It's due to the way you've built the app: in both the "new" and "edit" forms, you tell the app to "display error messages if there's a validation problem in the collection". So when you try editing an existing model and there's a validation problem, the "new" form updates to display the errors.
What you need to do instead, is use a new (blank) model in the "new" form, display errors if it doesn't validate, and add it to the collection if it's valid. That way, both forms have their errors handled by different mechanisms and won't overlap.
See http://jsfiddle.net/n9yq2/3/
app.AppView = Backbone.View.extend({
el: '#newContact',
initialize: function() {
this.model = new app.Contact();
// edited for brevity
this.model.on('invalid', function(model, errors) {
_.each(errors, function(error, i) {
createContact: function(e) {
e.preventDefault();
var attrs = this.newAttributes();
if(this.model.set(attrs, {validate: true})){
app.contactList.create(attrs);
}
},

Categories

Resources