Created
August 9, 2010 23:24
-
-
Save weaver/516316 to your computer and use it in GitHub Desktop.
Express form validation #nodejs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//// form -- form validation | |
/// | |
/// Represent the structure of a form and use it to validate form | |
/// data. Here's a Form that describes a create-account form: | |
/// | |
/// var SignupForm = form.Form('Sign Up') | |
/// .text('account', 'Account Name', form.required(/[\w-\.]+/)) | |
/// .email('email', 'Email', form.required()) | |
/// .password('password', 'Password', form.required()) | |
/// .password('confirm', 'Confirm', [form.required(), form.equal('password')]); | |
/// | |
/// Each field has a name, title, and validator or sequence of | |
/// validators. It's easy to make custom validators; see the | |
/// "Validators" section at the end of this file. | |
/// | |
/// The form above ius used by this view to process request data | |
/// before creating an account: | |
/// | |
/// app.post('/account', function(req, res) { | |
/// var result = SignupForm.validate(req.body); | |
/// if (!result.isValid()) | |
/// return res.send(result.errors(), 400); | |
/// | |
/// var data = result.data, | |
/// profile = { email: data.email }; | |
/// | |
/// db.newAccount(data.account, data.password, profile, function(err, key) { | |
/// if (err) | |
/// res.send(err.toString(), 500); | |
/// else if (!key) | |
/// res.send([{ account: "This account already exists" }], 400); | |
/// else { | |
/// var uri = '/account/' + key.id; | |
/// res.send({ uri: uri }, { Location: uri }, 201); | |
/// } | |
/// }); | |
/// }); | |
/// | |
exports.Form = Form; | |
exports.Field = Field; | |
exports.required = required; | |
exports.equal = equal; | |
/// --- Form | |
// A form is a sequence of fields. Fields participate in the | |
// validation process. | |
function Form(title) { | |
if (!(this instanceof Form)) | |
return new Form(title); | |
this.title = title; | |
this.fields = {}; | |
this.fieldList = []; | |
return this; | |
} | |
Form.prototype.text = field; | |
Form.prototype.password = field; | |
Form.prototype.email = field; | |
function field(name, title, valid) { | |
var field = new Field(name, title, wrapArray(valid)); | |
this.fields[name] = field; | |
this.fieldList.push(field); | |
return this; | |
} | |
Form.prototype.validate = function validate(data) { | |
var form = this, | |
valid = new Validation(this, data), | |
value; | |
this.fieldList.forEach(function(field) { | |
try { | |
field.validate(valid); | |
} catch (exn) { | |
if (!(exn instanceof Invalid)) | |
throw exn; | |
valid.errorList.push(exn); | |
} | |
}); | |
return valid; | |
}; | |
/// --- Fields | |
// A field has a name, title, and sequence of validators. | |
function Field(name, title, valid) { | |
this.name = name; | |
this.title = title; | |
this.valid = valid; | |
} | |
Field.prototype.validate = function validate(valid) { | |
valid.data[this.name] = this.validValue(valid.input[this.name], valid); | |
}; | |
Field.prototype.validValue = function _validate(value, valid) { | |
var field = this, | |
result; | |
this.valid.forEach(function(validator) { | |
result = validator.call(field, value, valid); | |
if (result !== undefined) | |
value = result; | |
}); | |
return value; | |
}; | |
/// --- Validation | |
// When Form.validate() is called, a validation state is created. | |
// This state is used to track the valid data and any errors. | |
function Validation(form, input) { | |
this.form = form; | |
this.fields = form.fields; | |
this.input = input; | |
this.data = {}; | |
this.errorList = []; | |
} | |
Validation.prototype.isValid = function isValid() { | |
return this.errorList.length == 0; | |
}; | |
Validation.prototype.fail = function fail(field, reason) { | |
throw new Invalid(field, reason); | |
}; | |
Validation.prototype.errors = function errors(fn) { | |
return this.errorList.map(fn || function(item) { | |
return { name: item.field.name, reason: item.toString() }; | |
}); | |
}; | |
function Invalid(field, reason) { | |
this.field = field; | |
this.reason = reason; | |
} | |
Invalid.prototype.toString = function() { | |
return '"' + this.field.title + '" ' + this.reason + '.'; | |
}; | |
/// --- Validators | |
// A validator is called in the context of a field and is passed the | |
// field's value and the validation state. If a validator returns a | |
// value, that value is used as the new field value. If validation | |
// fails, valid.fail(this, "reason") should be returned. | |
function required(pattern) { | |
pattern = pattern || /\S+/; | |
return function required(value, valid) { | |
if (!value) | |
return valid.fail(this, 'is required'); | |
else if (!pattern.test(value)) | |
return valid.fail(this, "isn't formatted correctly."); | |
}; | |
} | |
function equal(name) { | |
return function equal(value, valid) { | |
if (value != valid.input[name]) | |
return valid.fail(this, 'must match "' + valid.fields[name].title + '"'); | |
}; | |
} | |
/// --- Aux | |
function wrapArray(value) { | |
if (value instanceof Array) | |
return value; | |
return (value === undefined) ? [] : [value]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//// Test form.js | |
/// | |
/// Install Vows <http://vowsjs.org/>, and form.js, then run: | |
/// | |
/// vows test-form.js | |
/// | |
var assert = require('assert'), | |
sys = require('sys'), | |
form = require('form'), | |
vows = require('vows'); | |
var SignupForm = form.Form('Sign Up') | |
.text('account', 'Account Name', form.required(/[\w-]+/)) | |
.email('email', 'Email', form.required()) | |
.password('password', 'Password', form.required()) | |
.password('confirm', 'Confirm', [form.required(), form.equal('password')]); | |
vows.describe('Form').addBatch({ | |
'With valid data,': { | |
topic: function() { | |
return SignupForm.validate({ | |
account: 'some-user', | |
email: '[email protected]', | |
password: 'foo', | |
confirm: 'foo', | |
junk: 'bar' | |
}); | |
}, | |
'the result': { | |
'is valid': function(topic) { | |
assert.ok(topic.isValid()); | |
}, | |
'has no errors': function(topic) { | |
assert.deepEqual(topic.errors(), []); | |
}, | |
'contains valid data': function(topic) { | |
assert.deepEqual(topic.data, { | |
account: 'some-user', | |
email: '[email protected]', | |
password: 'foo', | |
confirm: 'foo' | |
}); | |
} | |
} | |
}, | |
'But for invalid data,': { | |
topic: function() { | |
return SignupForm.validate({ | |
account: '[email protected]', | |
password: 'foo', | |
confirm: 'goo' | |
}); | |
}, | |
'the result': { | |
'is not valid': function(topic) { | |
assert.ok(!topic.isValid()); | |
}, | |
'has errors': function(topic) { | |
assert.deepEqual( | |
topic.errors(message), | |
['"Email" is required.', '"Confirm" must match "Password".'] | |
); | |
} | |
} | |
} | |
}).export(module); | |
function message(invalid) { | |
return invalid.toString(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment