diff --git a/lib/validatable.js b/lib/validatable.js index 53dac713..6d0b8ee5 100644 --- a/lib/validatable.js +++ b/lib/validatable.js @@ -11,6 +11,8 @@ Validatable.validatesInclusionOf = getConfigurator('inclusion'); Validatable.validatesExclusionOf = getConfigurator('exclusion'); Validatable.validatesFormatOf = getConfigurator('format'); Validatable.validate = getConfigurator('custom'); +Validatable.validateAsync = getConfigurator('custom', {async: true}); +Validatable.validateUniquenessOf = getConfigurator('uniqueness'); // implementation of validators var validators = { @@ -68,8 +70,8 @@ var validators = { err(); } }, - custom: function (attr, conf, err) { - conf.customValidator.call(this, err); + custom: function (attr, conf, err, cb) { + conf.customValidator.call(this, err, cb); } }; @@ -80,8 +82,8 @@ function getConfigurator(name, opts) { }; } -Validatable.prototype.isValid = function () { - var valid = true, inst = this; +Validatable.prototype.isValid = function (callback) { + var valid = true, inst = this, wait = 0, async = false; // exit with success when no errors if (!this.constructor._validations) { @@ -97,12 +99,29 @@ Validatable.prototype.isValid = function () { this.trigger('validation', function () { this.constructor._validations.forEach(function (v) { - if (validationFailed(inst, v)) { + if (v[2] && v[2].async) { valid = false; + async = true; + wait += 1; + validationFailed(inst, v, done); + } else { + if (validationFailed(inst, v)) { + valid = false; + } } }); }); + + var asyncFail = false; + function done(fail) { + asyncFail = asyncFail || fail; + if (--wait === 0 && callback) { + callback(!asyncFail); + } + } + if (valid) cleanErrors(this); + return valid; }; @@ -114,7 +133,7 @@ function cleanErrors(inst) { }); } -function validationFailed(inst, v) { +function validationFailed(inst, v, cb) { var attr = v[0]; var conf = v[1]; var opts = v[2] || {}; @@ -153,6 +172,11 @@ function validationFailed(inst, v) { inst.errors.add(attr, message); fail = true; }); + if (cb) { + validatorArguments.push(function () { + cb(fail); + }); + } validator.apply(inst, validatorArguments); return fail; } diff --git a/test/validations_test.coffee b/test/validations_test.coffee index 1cec4f2b..e4463d2c 100644 --- a/test/validations_test.coffee +++ b/test/validations_test.coffee @@ -231,3 +231,23 @@ it 'should validate a field using a custom validator', (test) -> test.done() +it 'should validate asynchronously', (test) -> + + validator = (err, done) -> + setTimeout => + err 'async' if @name == 'bad name' + done() + , 100 + + User.validateAsync 'name', validator, message: async: 'hello' + + user = new User validAttributes + test.ok not user.isValid(), 'not valid because async validation' + user.isValid (valid) -> + test.ok valid, 'valid name' + + user.name = 'bad name' + user.isValid (valid) -> + test.ok not valid, 'not valid name' + test.done() +