Skip to content

Instantly share code, notes, and snippets.

@Xananax
Last active January 15, 2016 13:11
Show Gist options
  • Save Xananax/7178b559ba783a97b0a0 to your computer and use it in GitHub Desktop.
Save Xananax/7178b559ba783a97b0a0 to your computer and use it in GitHub Desktop.
Front-end validation with node.js forms

Here is a method to use the same validation methods on the front-end. This example uses express, connect-flash, jQuery, and twitter bootstrap.

Basically what it does is provide an ajax handler that will handle the form through regular form.handle(), but loop through errors and send back a json object with all the errors. The client-side javascript will then loop those errors and map them to the correct fields, adding errors where necessary. It will use a "visited" flag to not show errors on fields that have not yet been handled by the user. on submit, all fields are checked.

$(document).ready(function() {
var $form = $('#Form'),form_check_url='/form_check',cached_fields,get_cached_field,check_form,current_field,visited_fields,bypass_visited=false;
if($form.length){
cached_fields = {};
visited_fields = {};
get_cached_field = function(name){
var field;
if(!cached_fields.hasOwnProperty(name)){
field = $('#id_'+name,$form);
cached_fields[name] = field.length ? field : false;
}
return cached_fields[name];
};
check_form = function(data){
var i,l,err,field,isValid=true;
if(!data.valid){
for(i=0,l=data.errors.length;i<l;i++){
err = data.errors[i];
field = get_cached_field(err.name);
if(field && (bypass_visited || visited_fields[err.name])){
field.siblings('.alert,.glyphicon').remove();
if(err.valid){
field.parent().removeClass('has-error').addClass('has-success');
$('<span class="glyphicon glyphicon-ok form-control-feedback"></span>').insertAfter(field);
}else{
isValid = false;
field.parent().removeClass('has-success').addClass('has-error');
$('<div class="alert alert-danger">' + err.text + '</div>').insertAfter(field);
$('<span class="glyphicon glyphicon-remove form-control-feedback"></span>').insertAfter(field);
}
}
}
}
return isValid;
};
$form.on('blur','input',function(evt){
current_field = get_cached_field(this.name);
if(current_field){
visited_fields[this.name] = true;
$.ajax({
url:form_check_url
, data:$form.serialize()
, success: check_form
});
}
}).on('submit',function(evt){
evt.preventDefault();
$.ajax({
url:form_check_url
, data:$form.serialize()
, success: function(data){
bypass_visited = true;
if(check_form(data)){
$form.submit();
}
bypass_visited = false;
}
});
return false;
});
}
});
mixin alert(type)
.alert.alert-dismissible(role="alert",class="alert-"+type)
button.close(type="button", data-dismiss="alert")
span(aria-hidden="true") &times;
.sr-only Close
block
doctype html
html(lang='en')
head
title=title
link(href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css",rel="stylesheet")
link(href="http://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css",rel="stylesheet")
body
if (errors || info || success)
.messages(style='position:absolute;top:2em;left:2em;right:2em;z-index:3000')
if errors
.errors
each err in errors
+alert('danger')
!= err
if info
.info
each i in info
+alert('info')
!= i
if success
.success
each i in success
+alert('success')
!= i
.container
.row
.form
form(method='post',action='/form',id='Form')
!=form
input.btn.btn-default(type='submit',value='go')
var forms = require('forms')
, fields = forms.fields
, widgets = forms.widgets
, validators = forms.validators
;
var form = forms.create(
{
username: fields.string({
required: true
, widget: widgets.text({ classes: ['input-with-feedback','form-control'] })
, errorAfterField:true
, cssClasses: {
label: ['control-label']
}
})
, password: fields.password({
required: validators.required('You definitely want a password')
, widget: widgets.password({ classes: ['input-with-feedback','form-control'] })
, errorAfterField:true
, cssClasses: {
label: ['control-label']
}
})
, confirm: fields.password({
required: validators.required('don\'t you know your own password?')
, validators: [validators.matchField('password')]
, widget: widgets.password({ classes: ['input-with-feedback','form-control'] })
, errorAfterField:true
, cssClasses: {
label: ['control-label']
}
})
, email: fields.email({
widget: widgets.email({ classes: ['input-with-feedback','form-control'] })
, errorAfterField:true
, cssClasses: {
label: ['control-label']
}
})
}
, {
validatePastFirstError:true
}
);
var formatFieldForBootstrap = function (name, object) {
var label = object.labelHTML(name);
var error = object.error ? '<div class="alert alert-danger">' + object.error + '</div>' : '';
var widget = object.widget.toHTML(name, object);
return '<div class="form-group has-feedback">' + label + widget + error + '</div>';
};
// handles form submit, and redirects to appropriate url when submit is successful
var handle_form = function(render,template){
return function(req,res,next){
form.handle(req,{
success: function (form) {
req.flash('success','Thank you for your submission! We will get back in touch shortly');
res.redirect('/');
}
, error: function (form){
var field,err;
for(field in form.fields){
err = form.fields[field].error;
if(err){
req.flash('errors','there are errors in your <a href="#Form" class="alert-link">form submission</a>. Please review it and submit again');
break;
}
}
render(req,res,form,template);
}
, empty: function (form) {
req.flash('errors',{name:'form',text:'form cannot be empty!'});
render(req,res,form,template);
}
});
};
};
//handle ajax submission; returns a json object with errors if there are any
var handle_form_ajax = function(){
return function(req,res,next){
form.handle(req,{
success: function (form) {
res.send({valid:true,errors:false});
}
, error: function (form){
var field,err,errors=[];
for(field in form.fields){
err = form.fields[field].error;
if(err){
errors.push({name:field, text:err, valid:false});
}else{
errors.push({name:field,valid:true});
}
}
res.send({valid:false,errors:errors});
}
, empty: function (form) {
res.send({valid:false,errors:[{field:false,text:'form cannot be empty'}]});
}
});
};
};
module.exports = {
form:form
, formatFieldForBootstrap:formatFieldForBootstrap
, handle:handle_form
, handle_ajax:handle_form_ajax
};
var form = require('./form').form
, formatFieldForBootstrap = require('./form').formatFieldForBootstrap
, handle_form = require('./form').handle
, handle_form_ajax = require('./form').handle_ajax
;
var render = function(req,res,form,template){
form = form ? form.toHTML(formatFieldForBootstrap) : '';
template = template || 'form';
res.render(template,{
title:'Welcome'
, url:req.url
, form:form
, errors:req.flash('errors')
, info:req.flash('info')
, success:req.flash('success')
});
};
module.exports = function (app) {
app.get('/form',function(req,res,next){
render(req,res,form,'form');
});
app.post('/form',handle_form(render,'form'));
app.get('/form_check',handle_form_ajax());
};
var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');
var path = require('path');
var app = express();
var bodyParser = require('body-parser');
var flash = require('connect-flash');
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.cookieParser('keyboard cat'));
app.use(express.session({ cookie: { maxAge: 60000 }}));
app.use(flash());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.methodOverride());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.favicon());
app.use(app.router);
require('./routes')(app);
http.createServer(app).listen(app.get('port'), function(){
log.info('Express server listening on '+app.get('port'));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment