Permalink
Browse files

Move forms code out of song

  • Loading branch information...
1 parent 1aebff9 commit c90d97de1229a2b32460654ee74f8bbd35feda3b @imlucas committed Nov 6, 2012
Showing with 320 additions and 8 deletions.
  1. +3 −0 .travis.yml
  2. +28 −2 README.md
  3. +24 −0 examples/express.js
  4. +157 −2 index.js
  5. +3 −4 package.json
  6. +105 −0 test/index.test.js
View
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 0.8
View
@@ -1,14 +1,40 @@
-# node-forro
+# forro
WTForms style form validataion for node.js
+## Express Example
+
+ var express = require("express"),
+ app = express(),
+ forro = require('forro'),
+ StringField = forro.StringField;
+
+
+ var EditForm = forro({
+ 'username': StringField,
+ 'artist': StringField,
+ 'album': StringField,
+ 'title': StringField
+ }, {'required': true}); // Require all fields
+
+
+ app.use(express.bodyParser());
+
+ app.all("/:id/edit", forro.form(EditForm), function(req, res){
+ // Middle ware already validated for us
+ // and sent back a 400 error if validation failed.
+ res.send(req.form.val(['username', 'artist', 'album', 'title']));
+ });
+
+app.listen(8080);
+
## Install
npm install node-forro
## Testing
- git clone
+ git clone
npm install
mocha
View
@@ -0,0 +1,24 @@
+"use strict";
+
+var express = require("express"),
+ app = express(),
+ forro = require('forro'),
+ StringField = forro.StringField;
+
+
+var EditForm = forro({
+ 'username': StringField,
+ 'artist': StringField,
+ 'album': StringField,
+ 'title': StringField
+}, {'required': true}); // Require all fields
+
+
+app.use(express.bodyParser());
+app.all("/:id/edit", forro.form(EditForm), function(req, res){
+ if(req.form.validateOrAbort()){
+ res.send(req.form.val(['username', 'artist', 'album', 'title']));
+ }
+});
+
+app.listen(8080);
View
159 index.js
@@ -1,5 +1,160 @@
"use strict";
-module.exports = function(){
+var util = require('util');
-};
+exports = function(decl, opts){
+ return function(req, res){
+ var f = new Form(req, res, decl, opts);
+ return f;
+ };
+};
+
+exports.form = function(F){
+ return function(req, res, next){
+ req.form = new F(req, res);
+ next();
+ };
+};
+
+function Form(req, res, decl, opts){
+ opts = opts || {};
+ this.req = req;
+ this.res = res;
+ this.fields = decl;
+ this.errors = [];
+
+ this.fieldOpts = {};
+ if(opts.required !== undefined){
+ this.fieldOpts.required = opts.required;
+ }
+
+ for(var f in this.fields){
+ if(typeof this.field(f) === 'function'){
+ this.fields[f] = new this.fields[f](this.fieldOpts);
+ }
+ this.fields[f].name = f;
+ this.field(f).set(req.param(f, this.fields[f]['default']()));
+ }
+}
+
+Form.prototype.field = function(name){
+ return this.fields[name];
+};
+
+Form.prototype.validate = function(){
+ for(var f in this.fields){
+ try{
+ this.field(f).validate();
+ } catch(e){
+ if(e.constructor === ValidationError){
+ this.errors.push({
+ 'field': this.field(f),
+ 'message': e.message
+ });
+ }
+ else{
+ throw e;
+ }
+ }
+ }
+ return this;
+};
+
+Form.prototype.validateOrAbort = function(){
+ if(this.validate().errors.length > 0){
+ this.res.send(400, this.errors);
+ }
+ return true;
+};
+
+Form.prototype.val = function(key){
+ if(Array.isArray(key)){
+ var k, r = {};
+ for(k in key){
+ r[key[k]] = this.val(key[k]);
+ }
+ return r;
+ }
+ else{
+ return this.field(key).val();
+ }
+};
+
+function ValidationError(msg){
+ this.name = 'ValidationError';
+ this.message = msg;
+ ValidationError.super_.call(this, msg);
+}
+util.inherits(ValidationError, Error);
+
+function Field(opts){
+ opts = opts || {};
+ this.required = false;
+ this.optional = false;
+
+ if(opts.required !== undefined){
+ this.required = opts.required;
+ }
+ else if(opts.optional !== undefined){
+ this.optional = opts.optional;
+ if(opts.optional === true){
+ this.required = false;
+ }
+ else{
+ this.required = true;
+ }
+ }
+ this.name = undefined;
+ this.defaultValue = opts['default'] || null;
+ this.message = 'required';
+}
+
+Field.prototype['default'] = function(){
+ if(typeof this.defaultValue === 'function'){
+ return this.defaultValue();
+ }
+ return this.defaultValue;
+};
+
+Field.prototype.val = function(){
+ return this.value;
+};
+
+// No way. A String!
+function StringField(opts){
+ StringField.super_.call(this, opts);
+}
+util.inherits(StringField, Field);
+
+StringField.prototype.set = function(val){
+ this.value = val;
+};
+
+StringField.prototype.validate = function(){
+ if(this.required && (!this.value || this.value.length === 0)){
+ throw new ValidationError(this.message);
+ }
+ return this;
+};
+
+exports.StringField = StringField;
+
+// Cast a field to a proper Date object.
+// Doesn't matter if its a string format or epoch.
+function DateField(opts){
+ DateField.super_.call(this, opts);
+}
+util.inherits(DateField, Field);
+
+DateField.prototype.set = function(val){
+ this.value = new Date(val);
+ return this;
+};
+
+DateField.prototype.validate = function(){
+ return this;
+};
+
+exports.DateField = DateField;
+
+module.exports = exports;
View
@@ -1,6 +1,6 @@
{
"author": "Lucas Hrabovsky <lucas@ex.fm>",
- "name": "node-forro",
+ "name": "forro",
"description": "WTForms style form validataion for node.js",
"version": "0.0.0",
"homepage": "http://github.com/exfm/node-forro",
@@ -9,12 +9,11 @@
"url": "git://github.com/exfm/node-forro.git"
},
"scripts": {
- "test": "mocha"
+ "test": "./node_modules/.bin/mocha"
},
"dependencies": {},
"devDependencies": {
- "mocha": "1.4.1",
- "chai": "1.2.0"
+ "mocha": "1.6.0"
},
"optionalDependencies": {},
"engines": {
View
@@ -0,0 +1,105 @@
+"use strict";
+
+var assert = require('assert');
+
+
+var forro = require('../'),
+ StringField = forro.StringField;
+
+// var Form = forro({
+// 'username': new StringField([
+// new forro.Required({'message': 'Username is required'}),
+// new forro.Length({
+// 'message': 'Music be between 1 and 3 characters',
+// 'min': 1,
+// 'max': 3
+// })
+// ]),
+// 'password': new StringField()
+// });
+
+function FakeRequest(data){
+ this.params = {};
+ for(var key in data){
+ this.params[key] = data[key];
+ }
+}
+
+FakeRequest.prototype.param = function(name){
+ return this.params[name];
+};
+
+function FakeResponse(){
+ this.code = -1;
+ this.data = {};
+}
+FakeResponse.prototype.send = function(code, data){
+ this.code = code;
+ this.data = data;
+};
+
+describe('FORRO', function(){
+ it('is both a form library and a genre of north brazilian music', function(){
+ assert.ok(true);
+ });
+
+ it('allows a top level require all fields', function(){
+ var AuthForm = forro({
+ 'username': StringField,
+ 'password': StringField
+ }, {'required': true}),
+ form = new AuthForm(new FakeRequest({'username': 'a', 'password':'b'}),
+ new FakeResponse());
+
+ Object.keys(form.fields).forEach(function(name){
+ assert.ok(form.field(name).required);
+ });
+ });
+ it('allows optional and required fields', function(){
+ var AuthForm = forro({
+ 'username': new StringField({'required': true}),
+ 'password': new StringField({'required': true}),
+ 'rememberMeBro': new StringField({'optional': true})
+ }),
+ form = new AuthForm(new FakeRequest({'username': 'a', 'password':'b'}),
+ new FakeResponse());
+
+ Object.keys(form.fields).forEach(function(name){
+ if(name === 'rememberMeBro'){
+ assert(form.field(name).optional);
+ assert(!form.field(name).required);
+ }
+ else{
+ assert.ok(form.field(name).required);
+ }
+ });
+ });
+ it('validating valid data works as expected', function(){
+ var AuthForm = forro({
+ 'username': new StringField({'required': true}),
+ 'password': new StringField({'required': true}),
+ 'rememberMeBro': new StringField({'optional': true})
+ }),
+ req = new FakeRequest({'username': 'a', 'password':'b'}),
+ res = new FakeResponse(),
+ form = new AuthForm(req, res);
+
+ form.validate();
+ assert(form.errors.length === 0);
+
+ });
+ it('fails validation for a simple missing string', function(){
+ var AuthForm = forro({
+ 'username': new StringField({'required': true}),
+ 'password': new StringField({'required': true}),
+ 'rememberMeBro': new StringField({'optional': true})
+ }),
+ req = new FakeRequest({'password':'b'}),
+ res = new FakeResponse(),
+ form = new AuthForm(req, res);
+
+ form.validate();
+ assert(form.errors.length === 1);
+ assert(form.errors[0].field.name === 'username');
+ });
+});

0 comments on commit c90d97d

Please sign in to comment.