Node.js (Express) Form Clears on Submission - node.js

I am working on a really basic registration form in Node.js (with Express), and I am trying to find the easiest way to provide basic form validation. I've gone with "Express-Validator", which seems to do a fine job. However, my goal is to simply show any validation messages that are required and to leave the values entered by the user alone.
It seems that the request information is not making it back into the res.render, which I guess makes sense. However, I've looked everywhere I can think of and I can't find any reference that discusses how to keep form fields populated after showing error messages.
Below is a small snippet describing my approach:
post: function(req, res){
var userName = req.body.username;
var password = req.body.password;
//Validate input
req.assert("username", 'Invalid email address.').isEmail();
req.assert("password", 'Password cannot be empty.').notEmpty();
req.assert("passwordConfirm", 'Passwords entered do not match!').equals(password);
//Make sure we have no validation errors
var pageErrors = req.validationErrors();
if(!pageErrors)
{
userModel.CreateUser(userName, password, function(err){
if(err)
{
//there was a problem inserting new user... probably already exists
//will need to check the error to confirm
var dbErrorMessage = "Could not insert record into database!";
if(err.code === 11000)
{
//this is a duplicate entry
dbErrorMessage = "A user with that email address already exists!";
}
res.render('register.html', { pageErrors: [{msg: dbErrorMessage }]});
}
else
{
res.render('register.html', { successMessage: successMessage });
}
});
}
else
{
res.render('register.html', { pageErrors: pageErrors });
}

Unfortunately, you have to repopulate the form manually. If you get any page errors, you will pass back the form values to the view.
if(!pageErrors)
{
// ...
}
else
{
res.render('register.html', {
pageErrors: pageErrors,
userName: userName
});
}
And in your view, you would do a simple check to see if their are any errors and repopulate accordingly. You would have to keep track of what errors are produced for each form field.
<% if (userNameError) { %>
<input type="text" name="userName" value="<%- userName %>" />
<% } else { %>
<input type="text" name="userName" />
<% } %>
Another popular way is to send your form via ajax to to the server, and do all your validations. If there is an error, the entered form data remains and you would show the error, otherwise redirect after the successful login. Below is an example of how to submit a form with javascript.
$("#login-button").live("submit", function (e) {
// this will prevent the form from being uploaded to the server the conventioanl way
e.preventDefault();
// the form data
var data = $(this).serialize();
// this logs the user in
$.ajax({
type: 'POST',
url: BASE_URL + '/login',
data: data,
dataType: 'json',
success: function (data, status) {
// successful
},
});
// superfluous fallback
return false;
});

There is an easy way is you are using
app.use(express.bodyParser()) and app.use(expressValidator());
You can use req.body
res.render('register.html', {
pageErrors: pageErrors,
validated: req.body
});
And I'm not sure which templating language you are using but you could do something like..
<input type="text" name="userName" value="<%= pageErrors.userName.value || validated.userName %>" />
This then gives back the good input if ok or the bad input if it needs correcting.

You can get this done using connect-flash
Below are the code snippets in different files in order to get the values entered by the user back in form when validations fail while doing signup with passport.
Run this below command to add new package into package.json
npm install connect-flash --save
app.js
var flash = require('connect-flash');
app.use(flash()); // add this above passport initialize
app.use(passport.initialize());
app.use(passport.session());
config/passport.js (Please focus on form data loading into flash)
passport.use('local.signup', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, function (req, email, password, done) {
req.checkBody('first_name', 'Firstname is missing').notEmpty();
req.checkBody('last_name', 'Lastname is missing').notEmpty();
req.checkBody('email', 'Invalid email').notEmpty().isEmail();
req.checkBody('password', 'Password is too short. Minimum size is 6.').notEmpty().isLength({min:6});
req.checkBody('confirm_password', 'Password and confirm password didn\'t not match').equals(req.body.password);
var errors = req.validationErrors();
if (errors) {
var messages = [];
errors.forEach(function(error) {
messages.push(error.msg);
});
req.flash('formdata', req.body); // load form data into flash
return done(null, false, req.flash('error', messages));
}
User.findOne({'email': email}, function (err, user) {
if (err) {
req.flash('formdata', req.body); // load form data into flash
return done(err);
}
if (user) {
req.flash('formdata', req.body); // load form data into flash
return done(null, false, {message: 'Email is already in use.'});
}
var newUser = new User();
newUser.first_name = req.body.first_name;
newUser.last_name = req.body.last_name;
newUser.email = email;
newUser.password = newUser.encryptPassword(password);
newUser.save(function(err, result) {
if (err) {
return done(err);
}
return done(null, newUser);
});
});
}));
routes/index.js (Please focus on form data in flash loaded back into a variable)
router.get('/signup', function (req, res, next) {
var messages = req.flash('error');
var formdata = req.flash('formdata'); // Get formdata back into a variable
res.render('user/signup', {csrfToken: req.csrfToken(),
messages: messages, // pass it here to access in view file
hasErrors: messages.length > 0,
formData: formdata[0]
});
});
router.post('/signup', passport.authenticate('local.signup', {
badRequestMessage: 'Please fill the form with all details',
failureRedirect: '/user/signup',
failureFlash: true
}), function (req, res, next) {
if (req.session.oldUrl) {
var oldUrl = req.session.oldUrl;
req.session.oldUrl = null;
res.redirect(oldUrl);
} else {
res.redirect('/user/profile');
}
});
views/signup.hbs (Please focus on values in input elements)
<form class="wow fadeInUp animated" data-wow-delay=".7s" action="/user/signup" method="post" >
<input type="text" placeholder="First Name" name="first_name" value="{{ formData.first_name }}">
<input type="text" placeholder="Last Name" name="last_name" value="{{ formData.last_name }}">
<input type="text" class="email" placeholder="Email Address" name="email" value="{{ formData.email }}">
<input type="password" name="password" value="" class="lock" placeholder="Password">
<input type="password" name="confirm_password" value="" class="lock" placeholder="Confirm Password">
<input type="hidden" name="_csrf" value="{{ csrfToken }}">
<input type="submit" name="Register" value="Register"></form>
Hope this helps.

set a variable for all the input, for example
var inputData = {
firstname : req.body.firstname,
lastname : req.body.lastname,
email : req.body.email,
username : req.body.username,
password : req.body.password,
password_confirmation : req.body.password_confirmation,
agreetoterms: req.body.agreetoterms
}
and then pass that variable to the view
res.render('register.html', { pageErrors: [{msg: dbErrorMessage }], inputData: inputData });
then in your view
value="<%= inputData.userName %>"

if you are using jade and form Validator from npm , the best part is that you can an if statement in jade and then you simply check if error then with res.render we send the objects also . See This
if(errors){
res.render('register',{
errors : errors,
name : name,
email : email,
username : username,
password : password,
password2 : password2
});
And in jade you do this
input.form-control(name='name',type='text',placeholder='Enter Name',value = (errors ? '#{name}':''))
so if there is errors value will set to variable in name which will rendered when we send back
I think you can also be done in Angular2/Angular.js

Well, there is a simple way to achieve this. Do following in controller:
else{
res.render('register.html', { pageErrors: pageErrors, inputData: req.body});
}
Now, in your ejs (or do it according to your view engine) file do following:
<input type="text" name="name" value="<% if(typeof pageErrors !== "undefined"){ %> <%- inputData.name %> <% } %>" class="form-control">

check http://www.quietless.com/kitchen/building-a-login-system-in-node-js-and-mongodb/
on register.html make this
var data = {};
data.user = $('#user-input').val();
data.email = $('#email-input').val();
data.pass = $('#pass-input').val();
$.ajax({ url: '/signup'
, type: 'POST'
, data: JSON.stringify(data)
, contentType: 'application/json'
, dataType: 'html'
})
.done(function(data) {
if (data == 'ok') {
$('#content').html('You are registered!');
}
else $('#account-form-container').append('<br>error:' + data);
});
there might be error like: CANNOT POST /
in this case the author of tutorial on link above uses lib $.ajaxForm
you can also use https://github.com/felixge/node-formidable
or $('#myform').submit() replace to $('#submit-a-link').click()

Related

How to find array elements in npm?

I am trying to find user with the entered email and password from this users dummy array but I get undefined in the return value and hence I am not able to login. If I run this code in console it fetches correct output. Can somebody help why this find function (in the post req) not working in Node?
Dummy array -
const users = [
{ id: 1, name: 'Sherry', email:'sherry#gmail.com', password: 1234 },
{ id: 2, name: 'harry', email:'harry#gmail.com', password: 1234 },
{ id: 3, name: 'cherry', email:'cherry#gmail.com', password: 1234 }
]
Get Request -
app.get('/login', (req, res) => {
res.send(`<form method='POST' action='/login'>
<input type="email" name="email" placeholder="Enter email" />
<input type="password" name="password" placeholder="Enter pass" />
<input type="submit" />
</form>`);
})
Post Request -
app.post('/login', (req,res) => {
const { email, password } = req.body;
console.log(email)
console.log(password)
console.log(users)
let user1 = users.find((user) => user.email === email && user.password === password);
console.log(user1);
if(user1) {
req.session.userId = user.id
return res.redirect('/home')
}
res.redirect('/')
})
Issue - User1 is undefined.
I would say the password value received in req.body is a string . Your === comparator checks also a variables type, and string and number won't match.
Option 1
I recommend to change your users data password prop to string:
const users = [
{ id: 1, name: 'Sherry', email:'sherry#gmail.com', password: '1234' },
{ id: 2, name: 'harry', email:'harry#gmail.com', password: '1234' },
{ id: 3, name: 'cherry', email:'cherry#gmail.com', password: '1234' }
]
Option 2
If you want to only have numeric passwords you can also change your find function to compare numbers correctly:
let user1 = users.find((user) => user.email === email && user.password === parseInt(password));
Please let me know if it worked.
// Parse URL-encoded bodies (as sent by HTML forms)
app.use(express.urlencoded());
// Parse JSON bodies (as sent by API clients)
app.use(express.json());
This two line will make sure you sever-side will receive the data from req.body.
First of all you were sending the data as a form data so you need to include
// Parse URL-encoded bodies (as sent by HTML forms)
app.use(express.urlencoded());
which will allow you to read the data from req.body.
app.post('/login', (req,res) => {
const { email, password } = req.body;
console.log('req',req.body)
console.log("email",email)
console.log("password",password)
let index = users.findIndex((user) => user.email === email && user.password == password);
console.log("index",index)
let user1= users[index]
console.log("user1",user1);
if(user1) {
console.log("HERE=======================")
req.session.userId = user1.id
return res.redirect('/home')
}
res.redirect('/')
})
Now try the above code snap, hopefull it will help you.

i am getting an error while i am updating my data using express node.js

i am trying to update my category name but when i click on the submit button it gives me an error however i tested my code using postman and it actually updated but in that case i have to pass the product's id manually.
the callback function of the update category:
exports.updateCategory = (req, res, next) => {
console.log('GET update CATEGORY /update-category');
if (!req.body) {
return res
.status(400)
.send({ message: "Data to update can not be empty" })
}
const id = req.params.id; //req.body.id >> body means in the html/ejs file the name should be id
categorySchema.findByIdAndUpdate(id, req.body, { userFindAndModify: false })
.then(data => {
if (!data) {
res.status(404).send({ message: `Cannot Update user with ${id}` })
}
//updating the data
else {
res.render('category/edit_category.ejs', {
category: data //variable that i am going to use is category in the ejs
})
}
})
.catch(err => {
res.status(500).send({ message: "Error update user information" })
})
}
the update form >>
<form action='<%=`/halalMunchies/update-category/${category._id}`%>' method="post">
.
.
.
<label class="control-label" for="categoryName"> Category Name </label>
<input type="hidden" name="id" value="" id="id">
<input id="categoryName" class="form-control" value="<%= category.categoryName%>" name="categoryName" required autofocus="autofocus" />
the router.js
//PUT UPDATE CATEGORY
router.put('/update-category/:id', categoriesController.updateCategory);
router.get('/update-category/:id', categoriesController.updateCategory);
the error message on page is :
Cannot POST /halalMunchies/update-category/618632e3a5ad00fa5368ad5c
As the error message tells you, you did not provide a post endpoint in your router. Since you're using the post method in your form, you need to add the following in your router.js:
router.post('/update-category/:id', categoriesController.updateCategory);

Redirect response into a dynamic url

I am using node js with:
express framework
hbs framework
mongodb
My task is to redirect my response to a url with a format /users/:name/course/:courseId.
After running the code with the parameters name=James and courseId=1234, I get /users/James/course/1234 in the browser url. However, I am not able to post to /users/James/course/1234.
Edit: the data is being successfully posted, but I am getting a message Cannot GET /users/James/course/1234.
I have the following code:
app.js:
app.get('/', (req, res) => {
res.render('form.hbs')
})
app.post('/saveUsers', [
// firstname must contain letters only
check('firstname', 'First name should contain only letters').isAlpha(),
// email must be in a email format
check('email', 'Email field must be in a email format').isEmail(),
// courseId must be numerical and exactly 4 digits
check('courseId', 'course ID should contain only numbers').isNumeric(),
check('courseId', 'course ID should be exactly 4 digits').isLength({ min: 4, max: 4 })
], (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array(), data:req.body})
}
var db = utils.getDb()
db.collection('users').insertOne({
name: req.body.firstname,
email: req.body.email,
courseId: req.body.courseId
}, (err, result) => {
if (err) {
Response.send('Unable to insert a student')
}
console.log(result.ops)
nameUrl = result.ops[0].name
courseIdUrl = result.ops[0].courseId
res.redirect(`/users/${nameUrl}/course/${courseIdUrl}`)
})
})
app.post(`/users/${nameUrl}/course/${courseIdUrl}`, (req, res) => {
res.json(result.ops)
})
form.hbs:
<!DOCTYPE html>
<html>
<body>
<h1>Welcome</h1>
<p>Enter your name, email and course ID:</p>
<form action="/saveUsers" method="POST" id="myForm">
<input type="text" placeholder="firstname" name="firstname">
<input type="email" placeholder="email" name="email">
<input type="text" placeholder="courseId" name="courseId">
<input type="submit" value="Submit">
</form>
</body>
</html>
It looks like you are using template literal string interpolation where you should be using the Express syntax for route parameters. See the "route parameters" section in the docs.
Instead of:
app.post(`/users/${nameUrl}/course/${courseIdUrl}`, (req, res) => {
res.json(result.ops)
})
Try:
app.post(`/users/:name/course/:courseId`, (req, res) => {
res.json(result.ops)
})

How do I update a form that has multiple values in ejs + mongoose?

a user has fields in mongoose which will get updated if the user decided to update.
Here's the user schema
var User = Schema({
education: [{ type: String}],
});
So basically a user has fields that they could update or add, for example a user can add additional education and skills information using a form.
How do I properly do it in ejs and route?
my attempt in the route.js
router.post('/update-resume', function(req, res) {
User.findById(req.user._id, function(err, foundUser) {
// This part how do I update ?
if (req.body.education) foundUser.resume.education.push(req.body.education);
foundUser.save();
});
});
The value keeps pushing, i want to , I know that it is obvious that I'm pushing the data to the field, but how do I update it properly?
Form.ejs
<div class="form-group">
<label for="education">Education:</label>
<% for(var i = 0; i < user.resume.education.length; i++) { %>
<input type="text" class="form-control" name="education" id="education" value="<%= user.resume.education[i] %>">
<% } %>
</div>
Is it true that I need to for loop each field? if I want to update the specific data?
1st option:
Depending on how you use the application, you might not even care about updating - you could just delete the previous educations and save new ones instead.
2nd option:
To properly update you really need some kind of an ID that you can refer to when updating, right?
You'll still need to use your for loop, you just need to insert the id hidden field.
To do that you will need to pass an object and not an only-string value. I have made my object array look like this:
var education = [
{content:'Education 1',id:1},
{content:'Education 2',id:3},
{content:'Education 3',id:5},
{content:'Education 4',id:2},
];
Then you can do something like that:
<% for(var i = 0; i < education.length; i++) { %>
<input type="hidden" type="hidden" name="education_id" value="<%= education[i].id %>"/>
<input type="text" class="form-control" name="education" id="education" value="<%= education[i].content %>">
<% } %>
Array will always get passed the way you have sent it to the server. In my case, I'll get this (you can see that everything's in the order that it should be):
{education_id: [ '1', '3', '5', '2' ],
education: [ 'Education 1', 'Education 2', 'Education 3', 'Education 4' ] }
Now, let's look at the POST backend, you will need to tie everything back to an object (you don't really need to, but you can and probably should for the sake of sanity):
var education_i;
var education_req = [];
for(education_i=0;education_i<req.body.education.length;education_i++) {
console.log(req.body.education[education_i]);
education_req.push({
content:req.body.education[education_i],
id:req.body.education_id[education_i]
});
}
A few more notes:
You HAVE TO check for the user of every education record. You don't want to let anyone mess with the IDs and ruin someone else's profile.
You should probably save the array length variable separately, outside of the loops as it might cause performance issues on very large arrays because the length is parsed on every loop iteration.
This is the way I would do it:
The model:
var User = new Schema({
education: [{ content: String }]
});
The EJS/HTML:
<form id="education">
<% user.education.forEach(function(item) { %>
<input type="text" data-id="<%= item.id %>" value="<%= item.content %>" />
<% }); %>
<button type="submit">Save</button>
</form>
The client side javascript (JQuery):
$('#education').on('submit', function(e) {
e.preventDefault();
var data = [];
$(this)
.find('input')
.each(function() {
var $this = $(this);
// Collect the data with the id and value
data.push({
id: $this.data('id'),
value: $this.val()
});
});
$.ajax({
url: '/update-resume',
type: 'post',
data: { data: JSON.stringify(data) }
})
.done(function(data) {
if (data.success) {
// Lazy: refresh window
window.location.reload();
}
})
.fail(function() {
// Show an error or something fancy
});
});
The above javascript will read the data-ids from the input and the values and put them into an array of education objects. It will then stringify the object add and the string to the key 'data'. This means that you can pull the string in the route from req.body.data and parse it.
The server side javascript/in the route:
router.post('/update-resume', function(req, res, next) {
User.findById(req.user._id, function(err, user) {
var parsed = JSON.parse(req.body.data);
// update and remove
var results = user
.education
.filter(function(item) {
return parsed.some(function(input) {
return input.id === item.id;
});
})
.map(function(item) {
var related = getRelated(item.id, parsed);
return { content: related.value };
});
// Add new items
user.education = results
.concat(parsed.reduce(function(prev, curr) {
if (!getRelated(curr.id, results)) {
prev.push({ content: curr.value });
}
return prev;
}, []));
user.save(function(err) {
if (err) return next(err);
res.json({ success: true });
});
});
});
Get related helper:
var getRelated = function(id, arr) {
for (var i = 0; i < arr.length; i++) {
if (String(arr[i].id) === String(id)) return arr[i];
}
};
Mongoose will automatically give your education array items an id. The above will allow you to be able to add, remove and update existing education items on the page.

Mean stack client side update

I'm having trouble implementing the client side update CRUD logic. The fields are currently being deleted with the set up as it is. What am I missing?
My angular:
$scope.editService = function(id) {
$http.put('/api/hc/' + id,
{title: 'new',
shortname: 'new',
summary: 'new',
description: 'new'}
)
.success(function(data) {
})
.error(function(data) {
console.log('Error: ' + data);
});
};
My express: Nothing seems to be getting passed the the JSON values, for some reason all of the fields and keys are wiped clean, leaving only the _id and _v keys and values.
.put(function(req, res) {
Service.findById(req.params._id, function(err, service) {
if (err)
res.send(err);
service.title = req.body.title; // update the items info
service.summary = req.body.summary;
service.shortname = req.body.shortname;
service.description = req.body.description;
// save the items
service.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Service updated!' });
});
});
})
My view
<form name="editForm" ng-submit="editService(service._id)" ng-repeat="service in services
filter:json">
<input type="text" placeholder="{{ service.title}}" ng-model="serviceTitle" required>
<input type="text" placeholder="{{ service.shortname}}" ng-model="serviceShortname" required>
<input type="text" placeholder="{{ service.description}}" ng-model="serviceSummary" required>
<textarea type="text" placeholder="{{ service.summary}}" ng-model="serviceDescription" required></textarea>
<button type="submit">Edit</button>
</form>
You're not actually putting data in the example you gave.
$http.put('/api/hc/' + id)
should be
$http.put('/api/hc/' + id, formData)
where formData is whatever object you glean from the form fields you want to send up the pipe. Also, have a gander at angular's $[resource][1] service, it's a much cleaner (imo) way to do REST clients than using $http directly.

Resources