diff --git a/README.md b/README.md index 3eed30a..661a208 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,7 @@ components following the same API. ### Validators * matchField +* requiresFieldIfEmpty * min * max * range diff --git a/example/complex.js b/example/complex.js index 53050e3..9b17f68 100644 --- a/example/complex.js +++ b/example/complex.js @@ -31,6 +31,12 @@ var form = forms.create({ required: true, validators: [validators.matchField('password')] }), + phone_1: fields.string({ + validators: [validators.requiresFieldIfEmpty('phone_2')] + }), + phone_2: fields.string({ + validators: [validators.requiresFieldIfEmpty('phone_1')] + }), options: fields.string({ choices: { one: 'option one', diff --git a/lib/fields.js b/lib/fields.js index 14858c6..28f91a9 100644 --- a/lib/fields.js +++ b/lib/fields.js @@ -33,7 +33,10 @@ exports.string = function (opt) { b.value = raw_data; b.data = b.parse(raw_data); b.validate = function (form, callback) { - if (raw_data === '' || raw_data === null || typeof raw_data === 'undefined') { + var forceValidation = (b.validators || []).some(function (validator) { + return validator.forceValidation; + }); + if (!forceValidation && (raw_data === '' || raw_data === null || typeof raw_data === 'undefined')) { // don't validate empty fields, but check if required if (b.required) { b.error = 'This field is required.'; } process.nextTick(function () { callback(null, b); }); diff --git a/lib/validators.js b/lib/validators.js index d8caf18..0bbe836 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -1,7 +1,23 @@ /*jslint node: true */ 'use strict'; -var util = require('util'); +var util = require('util'); + +// From https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js#L1238-L1257 +var trim = (function () { + var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF", + wsChars = '[' + ws + ']', + trimBeginRegexp = new RegExp("^" + wsChars + wsChars + "*"), + trimEndRegexp = new RegExp(wsChars + wsChars + "*$"); + return function (str) { + str = str ? String(str) : ''; + if (String.prototype.trim && !ws.trim()) { + return str.trim(); + } else { + return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, ''); + }; + } +}()); exports.matchField = function (match_field, message) { if (!message) { message = 'Does not match %s.'; } @@ -14,6 +30,21 @@ exports.matchField = function (match_field, message) { }; }; +exports.requiresFieldIfEmpty = function (alternate_field, message) { + if (!message) { message = 'One of %s or %s is required.'; } + var validator = function (form, field, callback) { + var alternateBlank = trim(form.fields[alternate_field].data).length === 0, + fieldBlank = trim(field.data).length === 0; + if (alternateBlank && fieldBlank) { + callback(util.format(message, field.name, alternate_field)); + } else { + callback(); + } + }; + validator.forceValidation = true; + return validator; +}; + exports.min = function (val, message) { if (!message) { message = 'Please enter a value greater than or equal to %s.'; } return function (form, field, callback) { diff --git a/test/test-validators.js b/test/test-validators.js index 35bfd04..032419d 100644 --- a/test/test-validators.js +++ b/test/test-validators.js @@ -21,6 +21,39 @@ exports.matchField = function (test) { }); }; +exports.requiresFieldIfEmpty = function (test) { + var v = validators.requiresFieldIfEmpty('alternate_field', 'field 1: %s field2: %s'), + empty_fields = { + field: {name: 'field', data: ' '}, + alternate_field: {name: 'alternate_field', data: ''} + }, + filled_fields = { + field: {name: 'field', data: 'filled'}, + alternate_field: {name: 'alternate_field', data: 'also filled'} + }, + first_filled = { + field: {name: 'field', data: 'filled'}, + alternate_field: {name: 'alternate_field', data: ''} + }, + second_filled = { + field: {name: 'field', data: ''}, + alternate_field: {name: 'alternate_field', data: 'filled'} + }; + v({ fields: empty_fields }, empty_fields.field, function (err) { + test.equals(err, 'field 1: field field2: alternate_field'); + v({ fields: filled_fields }, filled_fields.field, function (err) { + test.equals(err, undefined); + v({ fields: first_filled }, first_filled.field, function (err) { + test.equals(err, undefined); + v({ fields: second_filled }, second_filled.field, function (err) { + test.equals(err, undefined); + test.done(); + }); + }); + }); + }); +}; + exports.min = function (test) { validators.min(100, 'Value must be greater than or equal to %s.')('form', {data: 50}, function (err) { test.equals(err, 'Value must be greater than or equal to 100.');