前端技術井噴發展後,不知如今還有多少小夥伴跟我同樣,最開編寫javascirpt的目的僅是作表單驗證。從收藏正則表達式編寫if-else,渡過boorstrap與各類jquery的validate插件的時代,到如今各廠UI庫完整封裝的表單組件直接使用,每一次,數據校對的功能猶如業務的附屬品,換一個業務,寫一套校驗。在實際工做中,基礎開發的重複勞動佔比明顯。類與繼承的編程方式必定程度上規避了冗餘,但根本辦法是對功能進行更高一級的抽象化。前端
與moment.js只作date的相關功能同樣,validate.js只專一於對數據的校驗。且不須要任何的外部依賴。java
validate函數的參數:node
- attributes --被校驗的對象
- constraints --校驗規則,至關於其餘validate的rules
- options —(可選)配置擴展
約束具備如下格式:jquery
{ <attribute>:
{ <validator name>: <validator options> }
}
複製代碼
約束規則支持傳參配置與自定義函數,很大程度上提升了複用。而在方式上,支持單值/嵌套等方式校驗也極大提升代碼可讀性。web
默認的校驗結果爲:正則表達式
- 經過:undefined
- 不經過:{'attribute':[ errorMessage ]}
支持對js基礎類型數據,bom內置對象,甚至dom/jquery對象。 這表明着validate.js不只能夠低成本的集成入各類舊項目中,甚至能夠一個約束同時支持業務邏輯,表單驗證,後端(若是是nodejs)傳參校驗。編程
var constraints = {
username: {
presence: true,
exclusion: {
within: ["nicklas"],
message: "'%{value}' is not allowed"
}
},
password: {
presence: true,
length: {
minimum: 6,
message: "must be at least 6 characters"
}
}
};
validate({password: "bad"}, constraints);
// => {
// "username": ["Username can't be blank"],
// "password": ["Password must be at least 6 characters"]
// }
validate({username: "nick", password: "better"}, constraints);
// => undefined
validate({username: "nicklas", password: "better"}, constraints);
// => {"username": ["Username 'nicklas' is not allowed"]}
validate({password: "better"}, constraints, {fullMessages: false});
// => {"username": ["can't be blank"]}
validate({}, constraints, {format: "flat"});
// => ["Username can't be blank", "Password can't be blank"]
validate({username: "nicklas", password: "bad"}, constraints, {format: "detailed"});
// => [
// {
// "attribute": "username",
// "value": "nicklas",
// "validator": "exclusion",
// "globalOptions": {
// "format": "detailed"
// },
// "attributes": {
// "username": "nicklas",
// "password": "bad"
// },
// "options": {
// "within": [
// "nicklas"
// ],
// "message": "'%{value}' is not allowed"
// },
// "error": "Username 'nicklas' is not allowed"
// },
// {
// "attribute": "password",
// "value": "bad",
// "validator": "length",
// "globalOptions": {
// "format": "detailed"
// },
// "attributes": {
// "username": "nicklas",
// "password": "bad"
// },
// "options": {
// "minimum": 6,
// "message": "must be at least 6 characters"
// },
// "error": "Password must be at least 6 characters"
// }
// ]
validate({}, {username: {presence: {message: "^You must pick a username"}}});
// => {"username": ["You must pick a username"]}
複製代碼
attributes參數只能是正常js對象或element,不支持其餘如backbone的viewmodel。後端
若是attributes爲HTML/DOM/jQuery element,須要在函數執行前調用collectFormValues。api
fullMessages配置表示是否在異常信息中加入參數名與參數值。數組
若是須要在異常信息爲開頭則在message中加入^前綴,須要輸出^則編寫\^ 。
messeage中的%{value}將會先執行validate.stringifyValue再傳入(validate.prettify函數中的默認調用)。
validate.prettify函數可提供string格式化。具體見輔助函數部分。
function success(attributes) {
console.log("Success!", attributes);
}
function error(errors) {
if (errors instanceof Error) {
// This means an exception was thrown from a validator
console.err("An error ocurred", errors);
} else {
console.log("Validation errors", errors);
}
}
var constraints = {
name: {
presence: true
},
// This is so the country doesn't get removed when cleaning the attributes country: {} }; var attributes = { name: "Nicklas", country: "Sweden", someMaliciousAttribute: "scary value" }; // Will call the success function and log { // name: "Nicklas", // country: "Sweden" // } validate.async(attributes, constraints).then(success, error); // Will call the error function validate.async({}, constraints).then(success, error); function ValidationErrors(errors, options, attributes, constraints) { Error.captureStackTrace(this, this.constructor); this.errors = errors; this.options = options; this.attributes = attributes; this.constraints = constraints; } ValidationErrors.prototype = new Error(); // This isn't supported by the ES6 promises
validate.async({}, constraints, {wrapErrors: ValidationErrors})
.then(success)
.catch(ValidationErrors, function(error) {
// Handle the validation errors
console.log("ValidationErrors", error);
})
.catch(function(error) {
// Handle other errors;
console.log("SystemError", error);
});
// Supporting another promise implementation (RSVP in this case)
validate.Promise = RSVP.Promise;
複製代碼
validate支持異步調用,在原先validate基礎上返回Promise。當環境/全局變量不支持Promise時,將拋出異常。
異步函數增長兩個option參數。1.cleanAttributes:當不爲false時,將在promise的resolve以前調用validate.cleanAttributes函數。2.wrapErrors :自定義方法捕捉異常。
任何符合A+規範的promise均可以經過重寫validate.Promise來實現promise。請勿使用jquery的promise,由於它並不是A+規範。
validate.single(null, {presence: true, email: true});
// => ["can't be blank"]
validate.single("foo", {presence: true, email: true});
// => ["is not a valid email"]
validate.single("foo@bar.com", {presence: true, email: true});
// => undefined
複製代碼
針對基礎數據類型的校驗
var constraints = {
"addresses.shipping": {
presence: true
},
"addresses.shipping.street": {
format: {
// Must be numbers followed by a name
pattern: "^[0-9]+ .+$",
message: "^The street for the shipping address must be a valid street name"
}
}
};
validate({}, constraints);
// => {"addresses.shipping": ["Addresses shipping can't be blank"]}
validate({addresses: {shipping: {street: "Foobar"}}}, constraints);
// => {"addresses.shipping.street": ["The street for the shipping address must be a valid street name"]}
validate({"foo.bar": 3}, {"foo\\.bar": {numericality: {even: true}}});
// => {"foo\.bar": ["Foo bar must be even"]}
複製代碼
var constraints = {
name: {
presence: true
}
};
validate.options = {format: "flat"};
validate.async.options = {format: "flat", cleanAttributes: false};
validate.validators.presence.options = {message: "can't be empty"};
// The default options will be used for both the
// validator and the validate function
validate({}, constraints);
// => ["Name can't be empty"]
// The default options are not used if the constraints options are falsy
validate({format: "grouped"}, {});
// => undefined
複製代碼
var constraints = {
username: {
presence: true,
exclusion: {
within: ["nicklas"],
message: "'%{value}' is not allowed"
}
},
password: {
presence: true,
length: {
minimum: 6,
message: "must be at least 6 characters"
}
}
};
validate({}, constraints, {format: "flat"});
// => ["Username can't be blank", "Password can't be blank"]
validate({username: "nicklas", password: "bad"}, constraints, {format: "detailed"});
// => [
// {
// "attribute": "username",
// "value": "nicklas",
// "validator": "exclusion",
// "globalOptions": {
// "format": "detailed"
// },
// "attributes": {
// "username": "nicklas",
// "password": "bad"
// },
// "options": {
// "within": [
// "nicklas"
// ],
// "message": "'%{value}' is not allowed"
// },
// "error": "Username 'nicklas' is not allowed"
// },
// {
// "attribute": "password",
// "value": "bad",
// "validator": "length",
// "globalOptions": {
// "format": "detailed"
// },
// "attributes": {
// "username": "nicklas",
// "password": "bad"
// },
// "options": {
// "minimum": 6,
// "message": "must be at least 6 characters"
// },
// "error": "Password must be at least 6 characters"
// }
// ]
validate.formatters.custom = function(errors) {
return errors.map(function(error) {
return error.validator;
});
};
validate({username: "nicklas", password: "bad"}, constraints, {format: "custom"});
// => ["exclusion", "length"];
複製代碼
validate.js支持不一樣的錯誤返回格式
還能夠經過validate.formatters來自定義返回格式
validate.validators.custom = function(value, options, key, attributes) {
console.log(value);
console.log(options);
console.log(key);
console.log(attributes);
return "is totally wrong";
};
// Will log:
// - "some value"
// - "some options"
// - "foo"
// - {"foo": "some value"}
validate({foo: "some value"}, {foo: {custom: "some options"}});
// => {foo: ["Foo is totally wrong"]}
複製代碼
編寫本身的驗證器很是簡單。只需將其添加到validate.validators對象中,它就會自動被拾取。
返回null或undefined將視爲經過。返回字符串或字符串數數組爲錯誤信息。
不須要再錯誤信息中添加屬性名,函數會自動完成此功能。
validate.validators.myAsyncValidator = function(value) {
return new validate.Promise(function(resolve, reject) {
setTimeout(function() {
if (value === "foo") resolve();
else resolve("is not foo");
}, 100);
});
};
var constraints = {name: {myAsyncValidator: true}}
, success = alert.bind(this, "The validations passed")
, error = function(errors) {
alert(JSON.stringify(errors, null, 2));
};
// Will call the success callback
validate.async({name: "foo"}, constraints).then(success, error);
// Will call the error callback with {name: ["Name is not foo"]} as the first argument
validate.async({name: "bar"}, constraints).then(success, error);
複製代碼
與普通驗證器不一樣的是異步驗證器返回一個new validate.Promise,並經過resolve與reject來控制校驗是否經過。
官方建議直接使用moment.js
// Before using it we must add the parse and format functions
// Here is a sample implementation using moment.js
validate.extend(validate.validators.datetime, {
// The value is guaranteed not to be null or undefined but otherwise it
// could be anything.
parse: function(value, options) {
return +moment.utc(value);
},
// Input is a unix timestamp
format: function(value, options) {
var format = options.dateOnly ? "YYYY-MM-DD" : "YYYY-MM-DD hh:mm:ss";
return moment.utc(value).format(format);
}
});
validate({}, {departure: {datetime: true}});
// => undefined
validate({departure: "foobar"}, {departure: {datetime: true}});
// => {"departure": ["Departure must be a valid date"]}
validate({departure: "2013-12-11 10:09:08"}, {departure: {datetime: true}});
// => undefined
validate({departure: "2013-12-11 10:09:08"}, {departure: {datetime: {dateOnly: true}}});
// => {"departure": ["Departure must be valid date"]}
var constraints = {
birthday: {
datetime: {
dateOnly: true,
latest: moment.utc().subtract(18, 'years'),
message: "^You need to be at least 18 years old"
}
}
};
validate({birthday: "3013-11-14"}, constraints);
// => {"birthday": ["You need to be at least 18 years old"]}
複製代碼
var constraints = {
from: {
email: true
}
};
validate({from: null}, constraints);
// => undefined
validate({from: ""}, constraints);
// => {"email": ["From is not a valid email"]}
validate({from: "nicklas@ansman"}, constraints);
// => {"email": ["From is not a valid email"]}
// Any TLD is allowed
validate({from: "nicklas@foo.faketld"}, constraints);
// => undefined
// Upper cased emails are allowed
validate({from: "NICKLAS@ANSMAN.SE"}, constraints);
// => undefined
constraints = {
from: {
email: {
message: "doesn't look like a valid email"
}
}
};
validate({from: "foobar"}, constraints);
// => {"email": ["From doesn't look like a valid email"]}
// It allows unicode
validate({from: "first.läst@example.com"}, constraints);
// => undefined
複製代碼
因爲校驗email的規則可能很複雜,能夠經過設置validate.validators.email.PATTERN 自定義所的正則表達式匹配。
var constraints = {
confirmPassword: {
equality: "password"
}
};
validate({password: "foo", confirmPassword: "foo"}, constraints);
// => undefined
validate({password: "foo", confirmPassword: "bar"}, constraints);
// => {confirmPassword: ["Confirm password is not equal to password"]}
constraints = {
complexAttribute: {
equality: {
attribute: "otherComplexAttribute",
message: "is not complex enough",
comparator: function(v1, v2) {
return JSON.stringify(v1) === JSON.stringify(v2);
}
}
}
};
validate({complexAttribute: [1,2,3], otherComplexAttribute: [1,2,3]}, constraints);
// => undefined
validate({complexAttribute: [1,2,3], otherComplexAttribute: [3,2,1]}, constraints);
// => {complexAttribute: ["Complex attribute is not complex enough"]}
複製代碼
默認經過 ===來判斷,也能夠自定義函數與其餘字段進行匹配,經過返回true、false校驗結果。
var restrictedDomains = ["jp", "ch"];
validate({}, {subdomain: {exclusion: restrictedDomains}});
// => undefined
validate({subdomain: "jp"}, {subdomain: {exclusion: restrictedDomains}});
// => {"size": ["jp is restricted"]}
var constraints = {
subdomain: {
exclusion: {
within: {jp: "Japan", "ch": "China"},
message: "^We don't support %{value} right now, sorry"
}
}
};
validate({subdomain: "jp"}, constraints);
// => {"subdomain": ["We don't support Japan right now, sorry"]}
validate({subdomain: "com"}, constraints);
// => undefined
複製代碼
var pattern = /\d{5}(-\d{4})?/;
validate({}, {zipCode: {format: pattern}});
// => undefined
validate({zipCode: "foobar"}, {zipCode: {format: pattern}});
// => {"zipCode": ["Zip code is invalid"]};
validate({zipCode: "12345"}, {zipCode: {format: pattern}});
// => undefined
var constraints = {
username: {
format: {
pattern: "[a-z0-9]+",
flags: "i",
message: "can only contain a-z and 0-9"
}
}
};
validate({username: "Nicklas!"}, constraints);
// => {"username": ["Username can only contain a-z and 0-9"]}
validate({username: "Nicklas"}, constraints);
// => undefined
複製代碼
var sizes = ["small", "medium", "large"];
validate({}, {size: {inclusion: sizes}});
// => undefined
validate({size: "xlarge"}, {size: {inclusion: sizes}});
// => {"size": ["xlarge is not included in the list"]}
var constraints = {
size: {
inclusion: {
within: {"Small": "s", "Medium": "m", "Large": "l"},
message: "^We're currently out of %{value}"
}
}
};
validate({size: "Extra large"}, constraints);
// => {"size": ["We're currently out of Extra large"]}
validate({size: "Medium"}, constraints);
// => undefined
複製代碼
var constraints = {
key1: {length: {is: 3}},
key2: {length: {minimum: 20}},
key3: {length: {maximum: 3}},
key4: {
length: {
minimum: 3,
tooShort: "needs to have %{count} words or more",
tokenizer: function(value) {
return value.split(/\s+/g);
}
}
}
};
validate({}, constraints);
// => undefined
// This is because nil and undefined are valid values.
// Use the presence validator if you don't want to allow undefined values. var values = { key1: "wrong length", key2: "too short", key3: "too long", key4: "too short" }; validate(values, constraints); // => { // "key1": ["Key1 is the wrong length (should be 3 characters)"], // "key2": ["Key2 is too short (minimum is 20 characters)"], // "key3": ["Key3 is too long (maximum is 3 characters)"], // "key4": ["Key4 needs to have 3 words or more"] // } 複製代碼
// null and undefined are valid values regardless of the options
validate({}, {duration: {numericality: true}});
//= > undefined
validate({duration: "foobar"}, {duration: {numericality: true}});
// => {"duration": ["Duration is not a number"]}
validate({duration: "3"}, {duration: {numericality: true}});
// => undefined
validate({duration: "03"}, {duration: {numericality: true}});
// => undefined
validate({duration: "03"}, {duration: {numericality: {strict: true}}});
// => {"duration": ["Duration must be a valid number"]}
validate({duration: "3"}, {duration: {numericality: {noStrings: true}}});
// => {"duration": ["Duration is not a number"]}
validate({duration: "7"}, {duration: {numericality: {divisibleBy: 3}}});
// => {"duration": ["Duration must be divisible by 3"]}
var constraints = {
duration: {
numericality: {
onlyInteger: true,
greaterThan: 0,
lessThanOrEqualTo: 30,
even: true,
notEven: "must be evenly divisible by two"
}
}
};
validate({duration: 3.14}, constraints);
// => {"duration": ["Duration must be an integer"]}
validate({duration: 4711}, constraints);
// => {
// "duration": [
// "Duration must be less than or equal to 30",
// "Duration must be evenly divisible by two"
// ]
// }
複製代碼
對於數字的校驗,若是value爲sting,默認經過+value轉換。也可經過noStrings:true來禁止轉換。
如下爲限制類型:
相對應的錯誤消息配置:
notValid
notInteger
notGreaterThan
notGreaterThanOrEqualTo
notEqualTo
notLessThan
notLessThanOrEqualTo
notDivisibleBy
notOdd
notEven
validate({}, {username: {presence: true}});
// => {"username": ["Username can't be blank"]}
validate({username: "ansman"}, {username: {presence: true}});
// => undefined
validate({input: ""}, {input: {presence: true}});
// => undefined
validate({input: ""}, {input: {presence: {allowEmpty: false}}});
// => {"input:" ["Input can't be blank"]}
validate({}, {username: {presence: {message: "is required"}}});
// => {"username": ["Username is required"]}
validate.validators.presence.message = "is required";
validate({}, {username: {presence: true}});
// => {"username": ["Username is required"]}
複製代碼
默認爲null與undefined,若allowEmpty:false,如下不經過:
{} (empty objects)
[] (empty arrays)
"" (empty string)
" " (whitespace only string)
validate({myAttribute: "value"}, {myAttribute: {type: "string"}});
// => undefined
validate({myAttribute: true}, {myAttribute: {type: "string"}});
// => {"myAttribute": ["My attribute must be of type string"]}
validate({myAttribute: "other"}, {myAttribute: {type: {type: function(value) { return value === "stuff"; }}}});
// => {"myAttribute": ["My attribute must be of the correct type"]}
validate.validators.type.types.customType = function (value) { return value === "stuff"; };
validate({myAttribute: true}, {myAttribute: {type: "customType"}});
// => {"myAttribute": ["My attribute must be of type customType"]}
validate.validators.type.messages.customType = "is simply wrong";
validate({myAttribute: true}, {myAttribute: {type: "customType"}});
// => {"myAttribute": ["My attribute is simply wrong"]}
複製代碼
validate({website: "http://google.com"}, {website: {url: true}});
// => undefined
validate({website: "google.com"}, {website: {url: true}});
// => {"website": ["Website is not a valid url"]}
validate({website: "ftp://google.com"}, {website: {url: true}});
// => {"website": ["Website is not a valid url"]}
validate({website: "ftp://google.com"}, {
website: {
url: {
schemes: ["ftp"]
}
}
});
// => undefined
validate({website: "http://localhost"}, {website: {url: true}});
// => {"website": ["Website is not a valid url"]}
validate({website: "http://localhost"}, {
website: {
url: {
allowLocal: true
}
}
});
// => undefined
validate({website: "data:,Hello%2C%20World!"}, {website: {url: true}});
// => {"website": ["Website is not a valid url"]}
validate({website: "data:,Hello%2C%20World!"}, {
website: {
url: {
allowDataUrl: true
}
}
}
);
// => undefined
複製代碼
validate.capitalize("foobar");
// => "Foobar"
複製代碼
var whitelist = {
name: true,
"address.street": true,
"address.postal": true,
"something\\.with\\.periods": true
};
var attributes = {
name: "Nicklas",
address: {
street: "Drottninggatan 98",
postal: "111 60"
},
"something.with.periods": "some value",
id: 4711,
createdAt: "1970-01-01 00:00"
};
validate.cleanAttributes(attributes, whitelist);
// => {
// name: "Nicklas",
// address: {
// street: "Drottninggatan 98",
// postal: "111 60"
// },
// "something.with.periods": "some value"
// }
var constraints = {
name: {
presence: true
},
"address.street": {},
"address.postal": {},
"something\\.with\\.periods": {}
};
validate.cleanAttributes(attributes, constraints);
// => {
// name: "Nicklas",
// address: {
// street: "Drottninggatan 98",
// postal: "111 60"
// },
// "something.with.periods": "some value"
// }
複製代碼
返回白名單中的屬性的object
<form id="login">
<input type="text" name="username" value="ansman">
<input type="password" name="password" value="correcthorsebatterystaple">
<input type="checkbox" name="remember-me" checked>
<input type="hidden" name="some-hidden-value" data-ignored>
</form>
<script>
var form = document.querySelector("form#login");
validate.collectFormValues(form);
// => {username: "ansman", password: "correcthorsebatterystaple", remember-me: false}
</script>
複製代碼
驗證fromData,配置:
不須要驗證的element增長data-ignored便可。
validate.contains({}, "foo");
// => false
validate.contains({foo: "bar"}, "foo");
// => true
validate.contains([1, 2, 3], 4);
// => false
validate.contains([1, 2, 3], 3);
// => true
複製代碼
var o1 = {foo: "bar"}
, o2 = {baz: "quux"};
validate.extend(o1, o2) === o1;
// => true
o1;
// => {foo: "bar", baz: "quux"};
o2;
// => {bar: "quux"};
// Makes a copy of o1, doesn't modify o1 validate.extend({}, o1); // => {foo: "bar", baz: "quux"}; // o1 is not touched validate.extend({}, o1) === o1; // => false 複製代碼
validate.format("Hi, my name is %{name}", {name: "Nicklas"});
// => "Hi, my name is Nicklas"
validate.format("%%{this} will not be replaced", {this: "that"});
// => "%{this} will not be replaced"
複製代碼
%{...}表明在屬性中的值,買message中用%%{...}來打印%{...}
validate.getDeepObjectValue({foo: "bar"}, "foo");
// => "bar"
validate.getDeepObjectValue({foo: {bar: {baz: "quux"}}}, "foo.bar.baz");
// => "quux"
validate.getDeepObjectValue({"foo.bar": "baz"}, "foo\\.bar");
// => "baz"
複製代碼
validate.isArray({});
// => false
validate.isArray([]);
// => true
複製代碼
validate.isBoolean("true");
// => false
validate.isBoolean(true);
// => true
複製代碼
validate.isDate(new Date());
// => true
validate.isDate(null);
// => false
validate.isDate({});
// => false
複製代碼
validate.isDefined("foobar");
// => true
validate.isDefined(null);
// => false
validate.isDefined(undefined);
// => false
複製代碼
validate.isDomElement({});
// => false
validate.isDomElement(document.createElement("div"));
// => true
複製代碼
validate.isEmpty({});
// => true
validate.isEmpty(null);
// => true
validate.isEmpty("");
// => true
validate.isEmpty(" ");
// => true
validate.isEmpty("foo");
// => false
validate.isEmpty({foo: "bar"});
// => false
複製代碼
validate.isFunction("foobar");
// => false
validate.isFunction(function() {});
// => true
複製代碼
validate.isHash([]);
// => false
validate.isHash({foo: "bar"});
// => true
複製代碼
validate.isInteger("foobar");
// => false
validate.isInteger(3.14);
// => false
validate.isInteger(3);
// => true
複製代碼
validate.isNumber("foobar");
// => false
validate.isNumber(3.14);
// => true
複製代碼
validate.isPromise({});
// => false
validate.isPromise(new Promise(function() {}));
// => true
validate.isPromise({then: function() {}});
// => true
複製代碼
validate.isString("");
// => true
validate.isString({});
// => false
複製代碼
validate.prettify("This.is_a-weirdString\\.");
// => "this is a weird string."
複製代碼
根據 .分割
反斜槓轉義
_ 與 - 代替空格
切割駝峯單詞
全小寫
數字轉字符串最多保留兩位小數
對象 toString 轉string
數組join(',')鏈接
// Not a function, returns the first argument
validate.result("foobar", 1, 2);
// => "foobar"
// Returns the result of Math.max(1, 2)
validate.result(Math.max, 1, 2);
// => 2
// Doesn't work since String#toUpperCase is not a pure function validate.result("foo".toUpperCase); // => Uncaught TypeError: String.prototype.toUpperCase called on null or undefined 複製代碼
validate.result(value, [arguments...])