(function(exports, module, define, require) {
"use strict";
Validate.js 0.3.1
(c) 2013-2014 Nicklas Ansman, Wrapp
Validate.js may be freely distributed under the MIT license.
For all details and documentation:
http://validatejs.org/
(function(exports, module, define, require) {
"use strict";
The main function that calls the validators specified by the constraints. The options are the following:
true
will return a flat array instead of an object.true
(default) the attribute name is prepended to the error.Please note that the options are also passed to each validator.
var validate = function(attributes, constraints, options) {
options = options || {};
var results = v.runValidations(attributes, constraints, options)
, attr
, validator;
for (attr in results) {
for (validator in results[attr]) {
if (v.isPromise(results[attr][validator])) {
throw new Error("Use validate.async if you want support for promises");
}
}
}
return validate.processValidationResults(results, options);
};
var v = validate
, root = this
Finds %{key} style patterns in the given string
, FORMAT_REGEXP = /%\{([^\}]+)\}/g;
Copies over attributes from one or more sources to a single destination. Very much similar to underscore’s extend. The first argument is the target object and the remaining arguments will be used as targets.
v.extend = function(obj) {
[].slice.call(arguments, 1).forEach(function(source) {
for (var attr in source) {
obj[attr] = source[attr];
}
});
return obj;
};
v.extend(validate, {
Runs the validators specified by the constraints object.
Will return an array of the format:
[{attribute: “
runValidations: function(attributes, constraints, options) {
var results = []
, attr
, validatorName
, value
, validators
, validator
, validatorOptions
, error;
Loops through each constraints, finds the correct validator and run it.
for (attr in constraints) {
value = v.getDeepObjectValue(attributes, attr);
validators = v.result(constraints[attr], value, attributes, attr);
for (validatorName in validators) {
validator = v.validators[validatorName];
if (!validator) {
error = v.format("Unknown validator %{name}", {name: validatorName});
throw new Error(error);
}
validatorOptions = validators[validatorName];
This allows the options to be a function. The function will be called with the value, attribute name and the complete dict of attributes. This is useful when you want to have different validations depending on the attribute value.
validatorOptions = v.result(validatorOptions, value, attributes, attr);
if (!validatorOptions) {
continue;
}
results.push({
attribute: attr,
error: validator.call(validator, value, validatorOptions, attr,
attributes)
});
}
}
return results;
},
Takes the output from runValidations and converts it to the correct output format.
processValidationResults: function(results, options) {
var errors = {};
This indexes the errors per attribute
results.forEach(function(result) {
var error = result.error
, attribute = result.attribute;
if (v.isString(error)) {
error = [error];
}
if (error) {
errors[attribute] = (errors[attribute] || []).concat(error);
}
});
Semi ugly way to check if the errors are empty, try iterating over them and short circuit when something is found.
for (var _ in errors) {
return v.fullMessages(errors, options);
}
},
Runs the validations with support for promises. This function will return a promise that is settled when all the validation promises have been completed. It can be called even if no validations returned a promise.
async: function(attributes, constraints, options) {
options = options || {};
var results = v.runValidations(attributes, constraints, options);
return v.Promise(function(resolve, reject) {
v.waitForResults(results).then(function() {
var errors = v.processValidationResults(results);
if (errors) {
reject(errors);
} else {
resolve();
}
}).then(undefined, v.error);
});
},
Returns a promise that is resolved when all promises in the results array are settled. The promise returned from this function is always resolved, never rejected. This function modifies the input argument, it replaces the promises with the value returned from the promise.
waitForResults: function(results) {
Create a sequence of all the results starting with a resolved promise.
var promise = results.reduce(function(memo, result) {
If this result isn’t a promise skip it in the sequence.
if (!v.isPromise(result.error)) {
return memo;
}
return memo.then(function() {
return result.error.then(
function() {
result.error = null;
},
function(error) {
If for some reason the validator promise was rejected but no error was specified.
if (!error) {
v.warn("Validator promise was rejected but didn't return an error");
}
result.error = error;
}
).then(undefined, v.error);
}).then(undefined, v.error);
}, v.Promise(function(r) { r(); })); // A resolved promise
return promise.then(undefined, v.error);
},
If the given argument is a call: function the and: function return the value otherwise just return the value. Additional arguments will be passed as arguments to the function. Example:
result('foo') // 'foo'
result(Math.max, 1, 2) // 2
result: function(value) {
var args = [].slice.call(arguments, 1);
if (typeof value === 'function') {
value = value.apply(null, args);
}
return value;
},
Checks if the value is a number. This function does not consider NaN a
number like many other isNumber
functions do.
isNumber: function(value) {
return typeof value === 'number' && !isNaN(value);
},
Returns false if the object is not a function
isFunction: function(value) {
return typeof value === 'function';
},
A simple check to verify that the value is an integer. Uses isNumber
and a simple modulo check.
isInteger: function(value) {
return v.isNumber(value) && value % 1 === 0;
},
Uses the Object
function to check if the given argument is an object.
isObject: function(obj) {
return obj === Object(obj);
},
Returns false if the object is null
of undefined
isDefined: function(obj) {
return obj !== null && obj !== undefined;
},
Checks if the given argument is a promise. Anything with a then
function is considered a promise.
isPromise: function(p) {
return !!p && typeof p.then === 'function';
},
Formats the specified strings with the given values like so:
format("Foo: %{foo}", {foo: "bar"}) // "Foo bar"
format: function(str, vals) {
return str.replace(FORMAT_REGEXP, function(m0, m1) {
return String(vals[m1]);
});
},
“Prettifies” the given string. Prettifying means replacing [._-] with spaces as well as splitting camel case words.
prettify: function(str) {
return str
Splits keys separated by periods
.replace(/([^\s])\.([^\s])/g, '$1 $2')
Removes backslashes
.replace(/\\+/g, '')
Replaces - and - with space
.replace(/[_-]/g, ' ')
Splits camel cased words
.replace(/([a-z])([A-Z])/g, function(m0, m1, m2) {
return "" + m1 + " " + m2.toLowerCase();
})
.toLowerCase();
},
isString: function(value) {
return typeof value === 'string';
},
isArray: function(value) {
return {}.toString.call(value) === '[object Array]';
},
contains: function(obj, value) {
if (!v.isDefined(obj)) {
return false;
}
if (v.isArray(obj)) {
return obj.indexOf(value) !== -1;
}
return value in obj;
},
getDeepObjectValue: function(obj, keypath) {
if (!v.isObject(obj) || !v.isString(keypath)) {
return undefined;
}
var key = ""
, i
, escape = false;
for (i = 0; i < keypath.length; ++i) {
switch (keypath[i]) {
case '.':
if (escape) {
escape = false;
key += '.';
} else if (key in obj) {
obj = obj[key];
key = "";
} else {
return undefined;
}
break;
case '\\':
if (escape) {
escape = false;
key += '\\';
} else {
escape = true;
}
break;
default:
escape = false;
key += keypath[i];
break;
}
}
if (key in obj) {
return obj[key];
} else {
return undefined;
}
},
capitalize: function(str) {
if (!v.isString(str)) {
return str;
}
return str[0].toUpperCase() + str.slice(1);
},
fullMessages: function(errors, options) {
options = options || {};
var ret = options.flatten ? [] : {}
, attr;
if (!errors) {
return ret;
}
function processErrors(attr, errors) {
errors.forEach(function(error) {
if (error[0] === '^') {
error = error.slice(1);
} else if (options.fullMessages !== false) {
error = v.format("%{attr} %{message}", {
attr: v.capitalize(v.prettify(attr)),
message: error
});
}
error = error.replace(/\\\^/g, "^");
If flatten is true a flat array is returned.
if (options.flatten) {
ret.push(error);
}
else {
(ret[attr] || (ret[attr] = [])).push(error);
}
});
}
Converts the errors of object of the format
{attr: [
for (attr in errors) {
processErrors(attr, errors[attr]);
}
return ret;
},
Returns a promise, should be called with the new operator. The first argument will be called with two functions, the first for resolving the promise and the second for rejecting it. Supports (in order of precedence):
If no supported promises are detected an error is thrown. A word of warning, only A+ style promises are supported. jQuery deferreds are NOT supported.
Promise: v.extend(function(callback) {
var promise = v.Promise.nativePromise(callback) ||
v.Promise.RSVPPromise(callback) ||
v.Promise.whenPromise(callback) ||
v.Promise.QPromise(callback);
if (!promise) {
throw new Error("No promises could be detected");
}
return promise;
}, {
nativePromise: function(callback) {
var Promise_, module;
if (typeof Promise !== "undefined") {
Promise_ = Promise;
} else {
module = v.tryRequire("es6-promise");
if (module) {
Promise_ = module.Promise;
}
}
if (Promise_) {
return new Promise_(callback);
}
},
RSVPPromise: function(callback) {
var Promise, module;
if (typeof RSVP !== "undefined") {
Promise = RSVP.Promise;
} else {
module = v.tryRequire("rsvp");
if (module) {
Promise = module.Promise;
}
}
if (Promise) {
return new Promise(callback);
}
},
whenPromise: function(callback) {
var promise, module;
if (typeof when !== "undefined") {
promise = when.promise;
} else {
module = v.tryRequire("when");
if (module) {
promise = module.promise;
}
}
if (promise) {
return promise(callback);
}
},
QPromise: function(callback) {
var promise, module;
if (typeof Q !== "undefined") {
promise = Q.promise;
} else {
module = v.tryRequire("q");
if (module) {
promise = module.promise;
}
}
if (promise) {
return promise(callback);
}
}
}),
tryRequire: function(moduleName) {
if (!v.require) {
return null;
}
try {
return v.require(moduleName);
} catch(e) {
return null;
}
},
require: require,
exposeModule: function(validate, root, exports, module, define) {
if (exports) {
if (module && module.exports) {
exports = module.exports = validate;
}
exports.validate = validate;
}
else {
root.validate = validate;
if (validate.isFunction(define) && define.amd) {
define("validate", [], function () { return validate; });
}
}
},
warn: function(msg) {
if (typeof console !== "undefined" && console.warn) {
console.warn(msg);
}
},
error: function(msg) {
if (typeof console !== "undefined" && console.error) {
console.error(msg);
}
}
});
validate.validators = {
Presence validates that the value isn’t empty
presence: function(value, options) {
var message = options.message || "can't be blank"
, attr;
Null and undefined aren’t allowed
if (!v.isDefined(value)) {
return message;
}
functions are ok
if (v.isFunction(value)) {
return;
}
if (typeof value === 'string') {
Tests if the string contains only whitespace (tab, newline, space etc)
if ((/^\s*$/).test(value)) {
return message;
}
}
else if (v.isArray(value)) {
For arrays we use the length property
if (value.length === 0) {
return message;
}
}
else if (v.isObject(value)) {
If we find at least one property we consider it non empty
for (attr in value) {
return;
}
return message;
}
},
length: function(value, options) {
Null and undefined are fine
if (!v.isDefined(value)) {
return;
}
var is = options.is
, maximum = options.maximum
, minimum = options.minimum
, tokenizer = options.tokenizer || function(val) { return val; }
, err
, errors = [];
value = tokenizer(value);
Is checks
if (v.isNumber(is) && value.length !== is) {
err = options.wrongLength ||
"is the wrong length (should be %{count} characters)";
errors.push(v.format(err, {count: is}));
}
if (v.isNumber(minimum) && value.length < minimum) {
err = options.tooShort ||
"is too short (minimum is %{count} characters)";
errors.push(v.format(err, {count: minimum}));
}
if (v.isNumber(maximum) && value.length > maximum) {
err = options.tooLong ||
"is too long (maximum is %{count} characters)";
errors.push(v.format(err, {count: maximum}));
}
if (errors.length > 0) {
return options.message || errors;
}
},
numericality: function(value, options) {
if (!v.isDefined(value)) {
return;
}
var errors = []
, name
, count
, checks = {
greaterThan: function(v, c) { return v > c; },
greaterThanOrEqualTo: function(v, c) { return v >= c; },
equalTo: function(v, c) { return v === c; },
lessThan: function(v, c) { return v < c; },
lessThanOrEqualTo: function(v, c) { return v <= c; }
};
Coerce the value to a number unless we’re being strict.
if (options.noStrings !== true && v.isString(value)) {
value = +value;
}
If it’s not a number we shouldn’t continue since it will compare it.
if (!v.isNumber(value)) {
return options.message || "is not a number";
}
Same logic as above, sort of. Don’t bother with comparisons if this doesn’t pass.
if (options.onlyInteger && !v.isInteger(value)) {
return options.message || "must be an integer";
}
for (name in checks) {
count = options[name];
if (v.isNumber(count) && !checks[name](value, count)) {
errors.push(v.format("must be %{type} %{count}", {
count: count,
type: v.prettify(name)
}));
}
}
if (options.odd && value % 2 !== 1) {
errors.push("must be odd");
}
if (options.even && value % 2 !== 0) {
errors.push("must be even");
}
if (errors.length) {
return options.message || errors;
}
},
datetime: v.extend(function(value, options) {
if (!v.isDefined(value)) {
return;
}
var err
, errors = []
, message = options.message
, earliest = options.earliest ? this.parse(options.earliest, options) : NaN
, latest = options.latest ? this.parse(options.latest, options) : NaN;
value = this.parse(value, options);
if (isNaN(value) || options.dateOnly && value % 86400000 !== 0) {
return message || "must be a valid date";
}
if (!isNaN(earliest) && value < earliest) {
err = "must be no earlier than %{date}";
err = v.format(err, {date: this.format(earliest, options)});
errors.push(err);
}
if (!isNaN(latest) && value > latest) {
err = "must be no later than %{date}";
err = v.format(err, {date: this.format(latest, options)});
errors.push(err);
}
if (errors.length) {
return options.message || errors;
}
}, {
This is the function that will be used to convert input to the number of millis since the epoch. It should return NaN if it’s not a valid date.
parse: function(value, options) {
if (v.isFunction(root.XDate)) {
return new root.XDate(value, true).getTime();
}
var moment = v.tryRequire("moment");
if (v.isDefined(moment)) {
return +moment.utc(value);
}
throw new Error("Neither XDate or moment.js was found");
},
Formats the given timestamp. Uses ISO8601 to format them. If options.dateOnly is true then only the year, month and day will be output.
format: function(date, options) {
var format = options.dateFormat;
if (v.isFunction(root.XDate)) {
format = format || (options.dateOnly ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm:ss");
return new XDate(date, true).toString(format);
}
var moment = v.tryRequire("moment");
if (v.isDefined(moment)) {
format = format || (options.dateOnly ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm:ss");
return moment.utc(date).format(format);
}
throw new Error("Neither XDate or moment.js was found");
}
}),
date: function(value, options) {
options = v.extend({}, options, {onlyDate: true});
return v.validators.datetime.call(v.validators.datetime, value, options);
},
format: function(value, options) {
if (v.isString(options) || (options instanceof RegExp)) {
options = {pattern: options};
}
var message = options.message || "is invalid"
, pattern = options.pattern
, match;
if (!v.isDefined(value)) {
return;
}
if (!v.isString(value)) {
return message;
}
if (v.isString(pattern)) {
pattern = new RegExp(options.pattern, options.flags);
}
match = pattern.exec(value);
if (!match || match[0].length != value.length) {
return message;
}
},
inclusion: function(value, options) {
if (v.isArray(options)) {
options = {within: options};
}
if (!v.isDefined(value)) {
return;
}
if (v.contains(options.within, value)) {
return;
}
var message = options.message || "^%{value} is not included in the list";
return v.format(message, {value: value});
},
exclusion: function(value, options) {
if (v.isArray(options)) {
options = {within: options};
}
if (!v.isDefined(value)) {
return;
}
if (!v.contains(options.within, value)) {
return;
}
var message = options.message || "^%{value} is restricted";
return v.format(message, {value: value});
},
email: v.extend(function(value, options) {
var message = options.message || "is not a valid email";
if (!v.isDefined(value)) {
return;
}
if (!v.isString(value)) {
return message;
}
if (!this.PATTERN.exec(value)) {
return message;
}
}, {
PATTERN: /^[a-z0-9\u007F-\uffff!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9\u007F-\uffff!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i
})
};
validate.exposeModule(validate, root, exports, module, define);
}).call(this,
typeof exports !== 'undefined' ? exports : null,
typeof module !== 'undefined' ? module : null,
typeof define !== 'undefined' ? define : null,
typeof require !== 'undefined' ? require : null);