Node.js, Express and Jade - Forms - validation

I'm using Node.js, Express and Jade and I'm trying to figure out how to post, validate & process form data.
In my jade file I create a contact form:
div#contact-area
form(method='post',action='')
label(for='name') Name:
input(type='text',name='name',id='name')
label(for='email') Email:
input(type='text',name='email',id='email')
input(type='submit',name='submit',value='Submit').submit-button
I'm then utilising the module 'express-validator' to validate the form as follows:
var express = require('express')
,routes = require('./routes')
,http = require('http')
,path = require('path')
,expressValidator = require('express-validator')
;
var app = express.createServer();
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade'); //not needed if we provide explicit file extension on template references e.g. res.render('index.jade');
app.use(express.bodyParser());
app.use(expressValidator);
app.use(express.methodOverride());
app.use(app.router);
});
//display the page for the first time
app.get('/mypage', function(req,res){
res.render('mypage', {
title: 'My Page'
});
});
//handle form submission
app.post('/mypage', function(req,res){
req.assert('name', 'Please enter a name').notEmpty();
req.assert('email', 'Please enter a valid email').len(6,64).isEmail();
var errors = req.validationErrors();
if( !errors){
sendEmail(function(){
res.render('mypage', {
title: 'My Page',
success: true
});
});
}
else {
res.render('mypage', {
title: 'My Page',
errors: errors
});
}
});
So there are three scenarios where my pages is rendered, and each one has access to different local variables:
When the page is loaded for the first time (errors=undefined,success=undefined)
When the form is submitted and there are errors (errors=array,success=undefined)
When the form is submitted and there are no errors (errors=undefined,success=true)
So my main problems are that:
When my Jade page is loaded, it seems to throw an error when I attempt to access a variable that doesn't exist. For example, I want to see if the variable 'success' is set, and if it is I want to hide the form and display a "thanks" message. Is there an easy way to handle a variable in Jade that will either be undefined or a value?
When the form has been submitted and there are validation errors, I want to show an error message (this isn't a problem) but also populate the form with the variables that were previously submitted (e.g. if the user provided a name but no email, the error should reference the blank email but the form should retain their name). At the moment the error message is displayed but my form is reset. Is there an easy way to set the input values of the fields to the values in the post data?

You can fix that by using locals.variable instead of just variable. Also you can use javascript in jade.
-locals.form_model = locals.form_data || {};
I used two ways to solve this problem. The first one is to re-render the view and you pass the req.body as a local. I have a convention that my forms use form_model.value for their field values. This method is works well for simple forms but it starts to breakdown a little when you form is relying on data.
The second method is to pass your req.body to session then redirect to a route that renders the form. Have that route looking for a certain session variable and use those values in your form.

Inside your jade file add error msg and then run your code.
Simply update your jade code with below code:
div#contact-area
ul.errors
if errors
each error, i in errors
li.alert.alert-danger #{error.msg}
form(method='post',action='')
label(for='name') Name:
input(type='text',name='name',id='name')
label(for='email') Email:
input(type='text',name='email',id='email')
input(type='submit',name='submit',value='Submit').submit-button

Related

Redirect from method in Vue.js with Vue-router older than version 1.x.x

I'm not much of a frontend developer but I know enough javascript to do the minimum.
I'm trying to plug into a last piece of login however my vue components are:
"vue-resource": "^0.9.3",
"vue-router": "^0.7.13"
I'm not experienced enough to move up to v1 or v2 respectively.
I would like to achieve something similar to this.
However I'm not getting a successful redirect.
my app.js file:
var router = new VueRouter();
...
import Auth from './services/auth.js';
router.beforeEach(transition => {
if(transition.to.auth &&!Auth.authenticated)
{
transition.redirect('/login');
}
else
{
transition.next();
}
});
```
In my login.js file
```
methods: {
/**
* Login the user
*/
login(e) {
e.preventDefault();
this.form.startProcessing();
var vm = this;
this.$http.post('/api/authenticate',
{ email : this.form.email,
password : this.form.password
})
.then(function(response){
vm.form.finishProcessing();
localStorage.setItem('token', response.data.token);
vm.$dispatch('authenticateUser');
},
function(response) {
if(response.status == 401)
{
let error = {'password': ['Email/Password do not match']};
vm.form.setErrors(error);
}else{
vm.form.setErrors(response.data);
}
});
}
}
I tried to do as suggested:
vm.form.finishProcessing();
localStorage.setItem('token', response.data.token);
vm.$dispatch('authenticateUser');
vm.$route.router.go('/dashboard');
However all it did was append the url on top.
I see that the 3 previous events were successfully done but not the redirect.
it went from:
http://dev.homestead.app:8000/login#!/
to
http://dev.homestead.app:8000/login#!/dashboard
when I need the entire page to go to:
http://dev.homestead.app:8000/login/dashboard#1/
I think i have a missing concept in order to do the redirect correctly.
UPDATE
As per suggested i have added param: append => false but nothing happens.
what i did afterward was within app.js create a method called redirectLogin() with console.log() outputs - that worked. what i did further is i put vm.$route.router.go inside there and called the method via vm.$dispatch('redirectLogin'); and that also didn't work.
NOTE:
The HTML is being rendered in Laravel first. the route I originally had (and working) as login/dashboard and that route is available via normal Laravel route. the blade is being rendered via view template.
So far I've been trying to vue redirect over to login/dashboard (not working) perhaps I should somehow remove login/dashboard and use the route.map and assign login/dashboard?
I would rather keep the login/dashboard as a laravel route due to authentication and other manipulation.
For Vue 2
this.$router.push('/path')
As par the documentation, router.go appends the path in the current route, however in your case it is appending along with # in the router as well.
You can use param: append, to directly arrive at your desired destination, like following:
vm.$route.router.go({name: '/login/dashboard#1/', params: {append: false}})
Edited
If it is not happening, you can try $window.location method like following:
var url = "http://" + $window.location.host + "login/dashboard";
console..log(url);
$window.location.href = url;
I think their is a misunderstanding here of how vue-router works. It seems you are not wanting to load a new route with a corresponding component, rather you simply want to redirect to a new page then let that page load and in turn fire up a fresh instance of vue.
If the above is correct you don't need vue-router at all. Simply add the below when you need to load the page:
window.location.href = '/login/dashboard'
If you'd rather simulate a redirect to that page (no back button history) then:
window.location.replace('/login/dashboard')
EDIT
The above would be fired when you have finished all processing that the page must run to set the users state which the next page requires. This way the next page can grab it and should be able to tell the correct state of the user (logged in).
Therefore you'll want to fire the redirect when the Promise has resolved:
.then(function(response){
vm.form.finishProcessing()
// store the Auth token
localStorage.setItem('token', response.data.token)
// not sure whether this is required as this page, and in turn this instance of vue, is about to be redirected
vm.$dispatch('authenticateUser')
// redirect the user to their dashboard where I assume you'd run this.$dispatch('authenticateUser') again to get their state
window.location.replace('/login/dashboard')

AJAX response returns current page

I was searching for a similar issue for a while now, but none of the solutions worked for me (and I couldn't find exactly the same issue).
First of all, the website I'm working on is running on Zend Framework. I suspect that it has something to do with the issue.
I want to make a pretty basic AJAX functionality, but for some reason my response always equals the html of the current page. I don't need any of Zend's functionality, the functions I need to implement could (and I'd prefer them to) work separately from the framework.
For testing purposes I made it as simple as I could and yet I fail to find the error. I have a page "test.php" which only has a link that triggers the ajax call. Here's how this call looks:
$('.quiz-link').click(function(e){
e.preventDefault();
$.ajax({
URL: "/quiz_api.php",
type: "POST",
cache: false,
data: {
'test': 'test'
},
success: function(resp){
console.log(resp);
},
error: function(resp){
console.log("Error: " + reps);
}
});
});
And this quiz_api.php is just:
<?php
echo "This is a test";
?>
When I click on the link I get the entire HTML of the current page. "This is a test" can't be found there. I'm also getting an error: "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/."
I reckon it has to do with the JS files that are included into this HTML response, but I've also tried setting "async: true" and it didn't help.
I would like to avoid using Zend Framework functions for this task, because I'm not well familiar with it and even making a simple controller sounds rather painful. Instead I want to find out what's causing such behavior and see if it can be changed.
PS: I've also tried moving quiz_api.php to another domain, but it didn't change anything.
I know that it might be an older code but it works, simple and very adaptable. Here's what I came up with. Hope it works for you.
//Here is the html
Link Test
<div id="test_div"></div>
function test(){
// Create our XMLHttpRequest object
var hr = new XMLHttpRequest();
// This is the php file link
var url = "quiz_api.php";
// Attaches the variables to the url ie:var1=1&var2=2 etc...
var vars = '';
hr.open("POST", url, true);
//Set content type header information for sending url encoded variables in the request
hr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// Access the onreadystatechange event for the XMLHttpRequest object
hr.onreadystatechange =
function(){
if(hr.readyState == 4 && hr.status == 200){
var return_data = hr.responseText;
console.log(return_data);
document.getElementById('test_div').innerHTML = return_data;
}else{
document.getElementById('test_div').innerHTML = "XMLHttpRequest failed";
}
}
//Send the data to PHP now... and wait for response to update the login_error div
hr.send(vars); // Actually execute the request
}
you can change the whole page with a document.write instead of changing individual "div"s

Jade html not updated after a redirect in Express.js

I'm currently having some trouble displaying a flash message in Express.js using Jade's templating engine and connect-flash. I am simply trying to flash an error message when the user tries to add a new User object to my database that already exists. However the flash message is not showing up on my page after calling router.post and redirecting back to the index (code below).
Through various console.logs and debugging, I have found that the data I am posting is indeed posting correctly, and the flash message is being set. What I have found is that on the redirect, all of the correct data is passing to the Jade template, but the variables are not being updated in the file itself. I am now wondering if this is a session related issue, or just something Flash/Jade/Express related that I am completely overlooking?
In the code below I am logging session data as well as setting the flash message to a variable. If the array for the flash message(s) is empty (i.e. on page load), an array is set with a message that says so. If the flash message(s) array contains a flash message, the test array is set with a message that says so.
index.js:
router.get('/', function(req, res, next) {
console.log(req.session);
var testArray;
var errorMessages = req.flash('user-error');
if (errorMessages.length === 0)
testArray = ['errorMessages is empty'];
else
testArray = ['errorMessages contains a message now'];
console.log(errorMessages);
console.log(testArray);
res.render('index', {
message: errorMessages,
tester: testArray,
...other irrelevant vars being passed...
});
});
router.post('/add', function(req, res, next) {
var ajaxData = req.body;
console.log(ajaxData);
User.findOne({name: ajaxData.name}, function(err, user) {
if (err) return console.error(err);
// if User DNE already in DB
if (user === null) {
...new user created and saved here...
}
/*where the important stuff begins*/
else {
console.log("flash message set");
req.flash('user-error', "A user with that name already exists!");
}
// redirect to index
res.redirect('/');
});
});
In my Jade template, I'm again logging errorMessages and testArray to make sure everything is passed to the file correctly (it is) then showing the variables.
index.jade
-console.log(message);
-console.log(tester);
.error-box Error: #{message}
.error-box Error: #{tester}
Initially loading the page, I will get the following HTML output:
<div class="error-box">Error: </div>
<div class="error-box">Error: errorMessages is empty</div>
No surprises here. But when I submit the form with data that sets the error flash message, I get the updated logs from router.get('/') and index.jade with both the correct errorMessages and testArray variables. However my HTML output remains the same:
<div class="error-box">Error: </div>
<div class="error-box">Error: errorMessages is empty</div>
Clearly the variables being passed to Jade are being updated correctly, but it appears that Jade is simply not updating the HTML. With my somewhat limited knowledge of how connect-flash and Jade work, this would lead me to believe that this is a session related issue, however my code in app.js appears to be setup correctly...
var session = require('express-session');
var flash = require('connect-flash');
app.use(session({
secret: 'secret',
cookie: { maxAge: 60000 },
resave: false,
saveUninitialized: false
}));
app.use(flash());
I am relatively new to Express.js, so I feel like there might be something small I am overlooking or don't understand, but I've tried to be as detailed as possible so I'm hoping someone can help me out here!
After more careful inspection, I found that what was really causing the issue was that res.redirect('/') was not running, as I was attempting to use AJAX on the client side to call router.post('/add').
I solved this by simply removing my AJAX request, then going back into my HTML and changing my form's attributes (the form whose data I was sending via AJAX) to include method="POST" and action="/add". This is the proper way to make a SERVER SIDE call to my router.post('/add').
I found that someone was having the same problem here, and this question initially led me to look into the AJAX/Client Side vs. Server Side issue. I found the latter question in a comment from #herbyme on this post.

Laravel: controller not teletransporting me (redirect-ing me) to the page

from Ajax the controller does get the keyword I want, as it confirms it (because I echo it), and my idea was that on getting that keyword, it should redirect to the page I want. Yet, it does not, and also, while it does change the locale, I have to reload the page, otherwise, it won't show any translation and locale changes on the page. In Firebug when I hover over the POST, I get the correct URL to where I would want to go: sort of http://myweb.com/es but the controller does not change the http URL box of my browser on my web to go there.
I am simplifying the Controller code here, but actually I will want it to go to different URLs depending on the keyword it gets, something that I would do with a switch statement or IF else if etc.
So the controller is as simple as this:
public function changelanguage()
{
$lang = \Input::get('locale');
echo "I got $lang";
Session::put('locale', $lang);
return redirect('/es');
}
If instead of using ajax I use a Form, then I dont need to reload, the Action of the form makes the controller change the locale and translate the page without reloading. But I need to use ajax and in any case, the controller does get correctly the keyword ('en', 'es', 'de' etc ) for languages, so it should take it from there and redirect me to the URL page, but it just doesnt move.
if you are curious about the Ajax, here it is, but it does send the keyword as I said.
$(document).ready(function(){
$('.choose-language').on('click', function(e){
e.preventDefault();
var selectedlanguage = $(this).data('value');
$.ajax({ // so I want to send it to the controller
type:"POST", // via post
url: 'language',
data:{'locale': selectedlanguage},
}); // HERE FINISHES THE $.POST STUFF
}); //HERE FINISHES THE CLICK FUNCTION
}); // HERE FINISHES THE CODE
ROUTES
Route::post('language', array(
'as' =>'language',
'uses' => 'LanguageController#changelanguage'
));
If you’re trying to perform the redirect in the AJAX-requested script, then it won’t work. You can’t redirect from a script request via AJAX otherwise people would be doing all kinds of nefarious redirects.
Instead, set up a “success” handler on your AJAX request that refreshes your page if the request was successful. It can be as simple as:
var url = '/language';
var data = {
locale: $(this).data('value');
};
var request = $.post(url, data)
.success(function (response) {
// Script was successful; reload page
location.reload();
});
I’m not sure how you’re allowing users to select locales, but since you need a reload any way I think AJAX is pointless here. Just have a traditional form that submits the new locale to an action, set the locale in a session/cookie/whatever, and then redirect back to the referring page.

Django / Closure: How to validate AJAX form via AJAX?

I'm using Django and Google's Closure javascript library, and I want to do some form processing via AJAX.
Currently, I have a button on the page that says "Add score." When you click it, it fires off a goog.net.Xhrio request to load another URL with a form on it and display the contents in a little pop up box, via a call to loadForm().
loadForm = function(formId) {
var form = goog.dom.getElement(formId);
goog.style.setElementShown(goog.dom.getElement('popup-box'), true);
goog.net.XhrIo.send(form.action, displayForm, form.method);
}
displayForm = function(e) {
goog.dom.getElement('popup-box').innerHTML = e.target.getResponseText();
}
The Django form that gets loaded is a very basic model form, with a simple "score" attribute that gets validated against a number range. Here's the code I have to process the form submission:
def Score(request):
obj = ScoreModel.get(pk=request.POST['obj_id'])
form = ScoreForm(request.POST, instance=obj)
if form.is_valid():
form.save()
messages.success(request, 'Score saved!')
return shortcuts.redirect('index')
else:
context_vars = {'score': score, 'form': quarter_form}
shortcuts.render_to_response(
'score_form.html', context_vars,
context_instance=context.RequestContext(request))
This would all work fine if the form to enter the score itself was just displayed on the page, but because it is an AJAX popup, it doesn't work properly. If I just do a simple form submission (via HTML submit button), it works fine if the data is valid. But if the data isn't valid, instead of displaying the form with errors in the popup, it just loads only the text that would've been displayed in the popup - the form with errors - in the main browser window rather than in the popup.
Conversely, if I submit the form via my loadForm() JS method above, it works perfectly fine if the form is invalid (and displays the invalid form in the popup box), but doesn't work if the form is valid (because the main index page ends up getting displayed in my popup's innerHTML).
I can't seem to figure out how to get the code to work in both scenarios. So, how can I have my cake and eat it to? :)
This is kind of a strange issue, so if I didn't explain it well enough, let me know and I'll try to clarify. Thanks in advance.
I got it to work. The basic trick was, if the form submission was successful, instead of returning a redirect I returned a basic response object with a redirect status code and the URL to redirect to. Then I modified my displayForm() to look for that and redirect if it was found.
Here's the modified code from the Score() function:
if form.is_valid():
form.save()
messages.success(request, 'Score saved!')
redirect = shortcuts.redirect('index')
return http.HttpResponse(content=redirect['Location'],
status=redirect.status_code)
And the modified displayForm():
var displayForm = function(e) {
var responseText = e.target.getResponseText();
if (e.target.getStatus() == 302) {
// If this was a redirect form submission, the response text will be the URL
// to redirect to.
window.location.href = responseText;
} else {
// Regular form submission. Show the response text.
goog.dom.getElement('popup-box').innerHTML = responseText;
}
};

Resources