MVVM架构~knockoutjs系列之从Knockout.Validation.js源码中学习它的用法
说在前
有时,我们在使用一个插件时,在网上即找不到它的相关API,这时,我们会很抓狂的,与其抓狂,还不如踏下心来,分析一下它的源码,事实上,对于JS这种开发语言来说,它开发的插件的使用方法都在它的源码里,只要你踏下心去看,一切就都有了!
Knockout.Validation.js是为Knockout插件服务的,它可以为Knockout对象进行验证,就像你使用MVC模型验证一样,而这种绑定的验证方式对于开发人员来说是很容易接受的,也是一种趋势,它在验证过程中,会将出现异常的点记录下来,然后在
某个时候将它抛出来,这个抛出的时刻通常是对象失去焦点时(blur)。
总结Knockout.Validation.js几个常用的东西
为空验证
self.CategoryId = ko.observable().extend({
required: true
});
最大最小值验证
self.price = ko.observable().extend({
required: { params: true, message: "请输入价格" },
min: { params: 1, message: "请输入大于1的整数" },
max: 100
});
长度验证
self.name = ko.observable().extend({
minLength: 2,
maxLength: { params: 30, message: "名称最大长度为30个字符" },
required: {
params: true,
message: "请输入名称",
}
});
电话验证
self.phone = ko.observable().extend({
phoneUS: {
params: true,
message: "电话不合法",
}
});
邮箱验证
self.Email = ko.observable().extend({
required: {
params: true,
message: "请填写Email"
},
email: {
params: true,
message: "Email格式不正确"
}
});
数字验证
self.age = ko.observable().extend({
number: {
params: true,
message: "必须是数字",
}
});
相等验证
self.PayPassword = ko.observable().extend({
required: {
params: true,
message: "请填写支付密码"
},
equal:{
params:"zzl",
message:"支付密码错误"
}
事实上,Knockout.Validation.js还有包括range,date,digit,notEqual等验证,都大同小意,我就不一一说了。
Knockout.Validation.js源码
/*=============================================================================
Author: Eric M. Barnard - @ericmbarnard
License: MIT (http://opensource.org/licenses/mit-license.php) Description: Validation Library for KnockoutJS
===============================================================================
*/
/*globals require: false, exports: false, define: false, ko: false */ (function (factory) {
// Module systems magic dance. if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// CommonJS or Node: hard-coded dependency on "knockout"
factory(require("knockout"), exports);
} else if (typeof define === "function" && define["amd"]) {
// AMD anonymous module with hard-coded dependency on "knockout"
define(["knockout", "exports"], factory);
} else {
// <script> tag: use the global `ko` object, attaching a `mapping` property
factory(ko, ko.validation = {});
}
}(function ( ko, exports ) { if (typeof (ko) === undefined) { throw 'Knockout is required, please ensure it is loaded before loading this validation plug-in'; } // create our namespace object
ko.validation = exports; var kv = ko.validation,
koUtils = ko.utils,
unwrap = koUtils.unwrapObservable,
forEach = koUtils.arrayForEach,
extend = koUtils.extend;
;/*global ko: false*/ var defaults = {
registerExtenders: true,
messagesOnModified: true,
errorsAsTitle: true, // enables/disables showing of errors as title attribute of the target element.
errorsAsTitleOnModified: false, // shows the error when hovering the input field (decorateElement must be true)
messageTemplate: null,
insertMessages: true, // automatically inserts validation messages as <span></span>
parseInputAttributes: false, // parses the HTML5 validation attribute from a form element and adds that to the object
writeInputAttributes: false, // adds HTML5 input validation attributes to form elements that ko observable's are bound to
decorateInputElement: false, // false to keep backward compatibility
decorateElementOnModified: true,// true to keep backward compatibility
errorClass: null, // single class for error message and element
errorElementClass: 'validationElement', // class to decorate error element
errorMessageClass: 'validationMessage', // class to decorate error message
allowHtmlMessages: false, // allows HTML in validation messages
grouping: {
deep: false, //by default grouping is shallow
observable: true, //and using observables
live: false //react to changes to observableArrays if observable === true
},
validate: {
// throttle: 10
}
}; // make a copy so we can use 'reset' later
var configuration = extend({}, defaults); configuration.html5Attributes = ['required', 'pattern', 'min', 'max', 'step'];
configuration.html5InputTypes = ['email', 'number', 'date']; configuration.reset = function () {
extend(configuration, defaults);
}; kv.configuration = configuration;
;kv.utils = (function () {
var seedId = new Date().getTime(); var domData = {}; //hash of data objects that we reference from dom elements
var domDataKey = '__ko_validation__'; return {
isArray: function (o) {
return o.isArray || Object.prototype.toString.call(o) === '[object Array]';
},
isObject: function (o) {
return o !== null && typeof o === 'object';
},
isObservableArray: function(instance) {
return !!instance &&
typeof instance["remove"] === "function" &&
typeof instance["removeAll"] === "function" &&
typeof instance["destroy"] === "function" &&
typeof instance["destroyAll"] === "function" &&
typeof instance["indexOf"] === "function" &&
typeof instance["replace"] === "function";
},
values: function (o) {
var r = [];
for (var i in o) {
if (o.hasOwnProperty(i)) {
r.push(o[i]);
}
}
return r;
},
getValue: function (o) {
return (typeof o === 'function' ? o() : o);
},
hasAttribute: function (node, attr) {
return node.getAttribute(attr) !== null;
},
getAttribute: function (element, attr) {
return element.getAttribute(attr);
},
setAttribute: function (element, attr, value) {
return element.setAttribute(attr, value);
},
isValidatable: function (o) {
return !!(o && o.rules && o.isValid && o.isModified);
},
insertAfter: function (node, newNode) {
node.parentNode.insertBefore(newNode, node.nextSibling);
},
newId: function () {
return seedId += 1;
},
getConfigOptions: function (element) {
var options = kv.utils.contextFor(element); return options || kv.configuration;
},
setDomData: function (node, data) {
var key = node[domDataKey]; if (!key) {
node[domDataKey] = key = kv.utils.newId();
} domData[key] = data;
},
getDomData: function (node) {
var key = node[domDataKey]; if (!key) {
return undefined;
} return domData[key];
},
contextFor: function (node) {
switch (node.nodeType) {
case 1:
case 8:
var context = kv.utils.getDomData(node);
if (context) { return context; }
if (node.parentNode) { return kv.utils.contextFor(node.parentNode); }
break;
}
return undefined;
},
isEmptyVal: function (val) {
if (val === undefined) {
return true;
}
if (val === null) {
return true;
}
if (val === "") {
return true;
}
},
getOriginalElementTitle: function (element) {
var savedOriginalTitle = kv.utils.getAttribute(element, 'data-orig-title'),
currentTitle = element.title,
hasSavedOriginalTitle = kv.utils.hasAttribute(element, 'data-orig-title'); return hasSavedOriginalTitle ?
savedOriginalTitle : currentTitle;
},
async: function (expr) {
if (window.setImmediate) { window.setImmediate(expr); }
else { window.setTimeout(expr, 0); }
},
forEach: function (object, callback) {
if (kv.utils.isArray(object)) {
return forEach(object, callback);
}
for (var prop in object) {
if (object.hasOwnProperty(prop)) {
callback(object[prop], prop);
}
}
}
};
}());;var api = (function () { var isInitialized = 0,
configuration = kv.configuration,
utils = kv.utils; function cleanUpSubscriptions(context) {
forEach(context.subscriptions, function (subscription) {
subscription.dispose();
});
context.subscriptions = [];
} function dispose(context) {
if (context.options.deep) {
forEach(context.flagged, function (obj) {
delete obj.__kv_traversed;
});
context.flagged.length = 0;
} if (!context.options.live) {
cleanUpSubscriptions(context);
}
} function runTraversal(obj, context) {
context.validatables = [];
cleanUpSubscriptions(context);
traverseGraph(obj, context);
dispose(context);
} function traverseGraph(obj, context, level) {
var objValues = [],
val = obj.peek ? obj.peek() : obj; if (obj.__kv_traversed === true) { return; } if (context.options.deep) {
obj.__kv_traversed = true;
context.flagged.push(obj);
} //default level value depends on deep option.
level = (level !== undefined ? level : context.options.deep ? 1 : -1); // if object is observable then add it to the list
if (ko.isObservable(obj)) { //make sure it is validatable object
if (!obj.isValid) { obj.extend({ validatable: true }); }
context.validatables.push(obj); if(context.options.live && utils.isObservableArray(obj)) {
context.subscriptions.push(obj.subscribe(function () {
context.graphMonitor.valueHasMutated();
}));
}
} //get list of values either from array or object but ignore non-objects
// and destroyed objects
if (val && !val._destroy) {
if (utils.isArray(val)) {
objValues = val;
} else if (utils.isObject(val)) {
objValues = utils.values(val);
}
} //process recurisvely if it is deep grouping
if (level !== 0) {
utils.forEach(objValues, function (observable) { //but not falsy things and not HTML Elements
if (observable && !observable.nodeType) {
traverseGraph(observable, context, level + 1);
}
});
}
} function collectErrors(array) {
var errors = [];
forEach(array, function (observable) {
if (!observable.isValid()) {
errors.push(observable.error());
}
});
return errors;
} return {
//Call this on startup
//any config can be overridden with the passed in options
init: function (options, force) {
//done run this multiple times if we don't really want to
if (isInitialized > 0 && !force) {
return;
} //becuase we will be accessing options properties it has to be an object at least
options = options || {};
//if specific error classes are not provided then apply generic errorClass
//it has to be done on option so that options.errorClass can override default
//errorElementClass and errorMessage class but not those provided in options
options.errorElementClass = options.errorElementClass || options.errorClass || configuration.errorElementClass;
options.errorMessageClass = options.errorMessageClass || options.errorClass || configuration.errorMessageClass; extend(configuration, options); if (configuration.registerExtenders) {
kv.registerExtenders();
} isInitialized = 1;
},
// backwards compatability
configure: function (options) { kv.init(options); }, // resets the config back to its original state
reset: kv.configuration.reset, // recursivly walks a viewModel and creates an object that
// provides validation information for the entire viewModel
// obj -> the viewModel to walk
// options -> {
// deep: false, // if true, will walk past the first level of viewModel properties
// observable: false // if true, returns a computed observable indicating if the viewModel is valid
// }
group: function group(obj, options) { // array of observables or viewModel
options = extend(extend({}, configuration.grouping), options); var context = {
options: options,
graphMonitor: ko.observable(),
flagged: [],
subscriptions: [],
validatables: []
}; var result = null; //if using observables then traverse structure once and add observables
if (options.observable) {
runTraversal(obj, context); result = ko.computed(function () {
context.graphMonitor(); //register dependency
runTraversal(obj, context); return collectErrors(context.validatables);
}); } else { //if not using observables then every call to error() should traverse the structure
result = function () {
runTraversal(obj, context); return collectErrors(context.validatables);
};
} result.showAllMessages = function (show) { // thanks @heliosPortal
if (show === undefined) {//default to true
show = true;
} // ensure we have latest changes
result(); forEach(context.validatables, function (observable) {
observable.isModified(show);
});
}; obj.errors = result;
obj.isValid = function () {
return obj.errors().length === 0;
};
obj.isAnyMessageShown = function () {
var invalidAndModifiedPresent = false; // ensure we have latest changes
result(); invalidAndModifiedPresent = !!koUtils.arrayFirst(context.validatables, function (observable) {
return !observable.isValid() && observable.isModified();
});
return invalidAndModifiedPresent;
}; return result;
}, formatMessage: function (message, params, observable) {
if (typeof (message) === 'function') {
return message(params, observable);
}
return message.replace(/\{0\}/gi, unwrap(params));
}, // addRule:
// This takes in a ko.observable and a Rule Context - which is just a rule name and params to supply to the validator
// ie: kv.addRule(myObservable, {
// rule: 'required',
// params: true
// });
//
addRule: function (observable, rule) {
observable.extend({ validatable: true }); //push a Rule Context to the observables local array of Rule Contexts
observable.rules.push(rule);
return observable;
}, // addAnonymousRule:
// Anonymous Rules essentially have all the properties of a Rule, but are only specific for a certain property
// and developers typically are wanting to add them on the fly or not register a rule with the 'kv.rules' object
//
// Example:
// var test = ko.observable('something').extend{(
// validation: {
// validator: function(val, someOtherVal){
// return true;
// },
// message: "Something must be really wrong!',
// params: true
// }
// )};
addAnonymousRule: function (observable, ruleObj) {
if (ruleObj['message'] === undefined) {
ruleObj['message'] = 'Error';
} //make sure onlyIf is honoured
if (ruleObj.onlyIf) {
ruleObj.condition = ruleObj.onlyIf;
} //add the anonymous rule to the observable
kv.addRule(observable, ruleObj);
}, addExtender: function (ruleName) {
ko.extenders[ruleName] = function (observable, params) {
//params can come in a few flavors
// 1. Just the params to be passed to the validator
// 2. An object containing the Message to be used and the Params to pass to the validator
// 3. A condition when the validation rule to be applied
//
// Example:
// var test = ko.observable(3).extend({
// max: {
// message: 'This special field has a Max of {0}',
// params: 2,
// onlyIf: function() {
// return specialField.IsVisible();
// }
// }
// )};
//
if (params && (params.message || params.onlyIf)) { //if it has a message or condition object, then its an object literal to use
return kv.addRule(observable, {
rule: ruleName,
message: params.message,
params: utils.isEmptyVal(params.params) ? true : params.params,
condition: params.onlyIf
});
} else {
return kv.addRule(observable, {
rule: ruleName,
params: params
});
}
};
}, // loops through all kv.rules and adds them as extenders to
// ko.extenders
registerExtenders: function () { // root extenders optional, use 'validation' extender if would cause conflicts
if (configuration.registerExtenders) {
for (var ruleName in kv.rules) {
if (kv.rules.hasOwnProperty(ruleName)) {
if (!ko.extenders[ruleName]) {
kv.addExtender(ruleName);
}
}
}
}
}, //creates a span next to the @element with the specified error class
insertValidationMessage: function (element) {
var span = document.createElement('SPAN');
span.className = utils.getConfigOptions(element).errorMessageClass;
utils.insertAfter(element, span);
return span;
}, // if html-5 validation attributes have been specified, this parses
// the attributes on @element
parseInputValidationAttributes: function (element, valueAccessor) {
forEach(kv.configuration.html5Attributes, function (attr) {
if (utils.hasAttribute(element, attr)) { var params = element.getAttribute(attr) || true; if (attr === 'min' || attr === 'max')
{
// If we're validating based on the min and max attributes, we'll
// need to know what the 'type' attribute is set to
var typeAttr = element.getAttribute('type');
if (typeof typeAttr === "undefined" || !typeAttr)
{
// From http://www.w3.org/TR/html-markup/input:
// An input element with no type attribute specified represents the
// same thing as an input element with its type attribute set to "text".
typeAttr = "text";
}
params = {typeAttr: typeAttr, value: params};
} kv.addRule(valueAccessor(), {
rule: attr,
params: params
});
}
}); var currentType = element.getAttribute('type');
forEach(kv.configuration.html5InputTypes, function (type) {
if (type === currentType) {
kv.addRule(valueAccessor(), {
rule: (type === 'date') ? 'dateISO' : type,
params: true
});
}
});
}, // writes html5 validation attributes on the element passed in
writeInputValidationAttributes: function (element, valueAccessor) {
var observable = valueAccessor(); if (!observable || !observable.rules) {
return;
} var contexts = observable.rules(); // observable array // loop through the attributes and add the information needed
forEach(kv.configuration.html5Attributes, function (attr) {
var params;
var ctx = koUtils.arrayFirst(contexts, function (ctx) {
return ctx.rule.toLowerCase() === attr.toLowerCase();
}); if (!ctx) {
return;
} params = ctx.params; // we have to do some special things for the pattern validation
if (ctx.rule === "pattern") {
if (ctx.params instanceof RegExp) {
params = ctx.params.source; // we need the pure string representation of the RegExpr without the //gi stuff
}
} // we have a rule matching a validation attribute at this point
// so lets add it to the element along with the params
element.setAttribute(attr, params);
}); contexts = null;
}, //take an existing binding handler and make it cause automatic validations
makeBindingHandlerValidatable: function (handlerName) {
var init = ko.bindingHandlers[handlerName].init; ko.bindingHandlers[handlerName].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext); return ko.bindingHandlers['validationCore'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
};
}, // visit an objects properties and apply validation rules from a definition
setRules: function (target, definition) {
var setRules = function (target, definition) {
if (!target || !definition) { return; } for (var prop in definition) {
if (!definition.hasOwnProperty(prop)) { continue; }
var ruleDefinitions = definition[prop]; //check the target property exists and has a value
if (!target[prop]) { continue; }
var targetValue = target[prop],
unwrappedTargetValue = unwrap(targetValue),
rules = {},
nonRules = {}; for (var rule in ruleDefinitions) {
if (!ruleDefinitions.hasOwnProperty(rule)) { continue; }
if (kv.rules[rule]) {
rules[rule] = ruleDefinitions[rule];
} else {
nonRules[rule] = ruleDefinitions[rule];
}
} //apply rules
if (ko.isObservable(targetValue)) {
targetValue.extend(rules);
} //then apply child rules
//if it's an array, apply rules to all children
if (unwrappedTargetValue && utils.isArray(unwrappedTargetValue)) {
for (var i = 0; i < unwrappedTargetValue.length; i++) {
setRules(unwrappedTargetValue[i], nonRules);
}
//otherwise, just apply to this property
} else {
setRules(unwrappedTargetValue, nonRules);
}
}
};
setRules(target, definition);
}
}; }()); // expose api publicly
extend(ko.validation, api);;//Validation Rules:
// You can view and override messages or rules via:
// kv.rules[ruleName]
//
// To implement a custom Rule, simply use this template:
// kv.rules['<custom rule name>'] = {
// validator: function (val, param) {
// <custom logic>
// return <true or false>;
// },
// message: '<custom validation message>' //optionally you can also use a '{0}' to denote a placeholder that will be replaced with your 'param'
// };
//
// Example:
// kv.rules['mustEqual'] = {
// validator: function( val, mustEqualVal ){
// return val === mustEqualVal;
// },
// message: 'This field must equal {0}'
// };
//
kv.rules = {};
kv.rules['required'] = {
validator: function (val, required) {
var stringTrimRegEx = /^\s+|\s+$/g,
testVal; if (val === undefined || val === null) {
return !required;
} testVal = val;
if (typeof (val) === "string") {
testVal = val.replace(stringTrimRegEx, '');
} if (!required) {// if they passed: { required: false }, then don't require this
return true;
} return ((testVal + '').length > 0);
},
message: 'This field is required.'
}; function minMaxValidatorFactory(validatorName) {
var isMaxValidation = validatorName === "max"; return function (val, options) {
if (kv.utils.isEmptyVal(val)) {
return true;
} var comparisonValue, type;
if (options.typeAttr === undefined) {
// This validator is being called from javascript rather than
// being bound from markup
type = "text";
comparisonValue = options;
} else {
type = options.typeAttr;
comparisonValue = options.value;
} // From http://www.w3.org/TR/2012/WD-html5-20121025/common-input-element-attributes.html#attr-input-min,
// if the value is parseable to a number, then the minimum should be numeric
if (!isNaN(comparisonValue)) {
type = "number";
} var regex, valMatches, comparisonValueMatches;
switch (type.toLowerCase()) {
case "week":
regex = /^(\d{4})-W(\d{2})$/;
valMatches = val.match(regex);
if (valMatches === null) {
throw "Invalid value for " + validatorName + " attribute for week input. Should look like " +
"'2000-W33' http://www.w3.org/TR/html-markup/input.week.html#input.week.attrs.min";
}
comparisonValueMatches = comparisonValue.match(regex);
// If no regex matches were found, validation fails
if (!comparisonValueMatches) {
return false;
} if (isMaxValidation) {
return (valMatches[1] < comparisonValueMatches[1]) || // older year
// same year, older week
((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2]));
} else {
return (valMatches[1] > comparisonValueMatches[1]) || // newer year
// same year, newer week
((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
}
break; case "month":
regex = /^(\d{4})-(\d{2})$/;
valMatches = val.match(regex);
if (valMatches === null) {
throw "Invalid value for " + validatorName + " attribute for month input. Should look like " +
"'2000-03' http://www.w3.org/TR/html-markup/input.month.html#input.month.attrs.min";
}
comparisonValueMatches = comparisonValue.match(regex);
// If no regex matches were found, validation fails
if (!comparisonValueMatches) {
return false;
} if (isMaxValidation) {
return ((valMatches[1] < comparisonValueMatches[1]) || // older year
// same year, older month
((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2])));
} else {
return (valMatches[1] > comparisonValueMatches[1]) || // newer year
// same year, newer month
((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
}
break; case "number":
case "range":
if (isMaxValidation) {
return (!isNaN(val) && parseFloat(val) <= parseFloat(comparisonValue));
} else {
return (!isNaN(val) && parseFloat(val) >= parseFloat(comparisonValue));
}
break; default:
if (isMaxValidation) {
return val <= comparisonValue;
} else {
return val >= comparisonValue;
}
}
};
} kv.rules['min'] = {
validator: minMaxValidatorFactory("min"),
message: 'Please enter a value greater than or equal to {0}.'
}; kv.rules['max'] = {
validator: minMaxValidatorFactory("max"),
message: 'Please enter a value less than or equal to {0}.'
}; kv.rules['minLength'] = {
validator: function (val, minLength) {
return kv.utils.isEmptyVal(val) || val.length >= minLength;
},
message: 'Please enter at least {0} characters.'
}; kv.rules['maxLength'] = {
validator: function (val, maxLength) {
return kv.utils.isEmptyVal(val) || val.length <= maxLength;
},
message: 'Please enter no more than {0} characters.'
}; kv.rules['pattern'] = {
validator: function (val, regex) {
return kv.utils.isEmptyVal(val) || val.toString().match(regex) !== null;
},
message: 'Please check this value.'
}; kv.rules['step'] = {
validator: function (val, step) { // in order to handle steps of .1 & .01 etc.. Modulus won't work
// if the value is a decimal, so we have to correct for that
if (kv.utils.isEmptyVal(val) || step === 'any') { return true; }
var dif = (val * 100) % (step * 100);
return Math.abs(dif) < 0.00001 || Math.abs(1 - dif) < 0.00001;
},
message: 'The value must increment by {0}'
}; kv.rules['email'] = {
validator: function (val, validate) {
if (!validate) { return true; } //I think an empty email address is also a valid entry
//if one want's to enforce entry it should be done with 'required: true'
return kv.utils.isEmptyVal(val) || (
// jquery validate regex - thanks Scott Gonzalez
validate && /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(val)
);
},
message: 'Please enter a proper email address'
}; kv.rules['date'] = {
validator: function (value, validate) {
if (!validate) { return true; }
return kv.utils.isEmptyVal(value) || (validate && !/Invalid|NaN/.test(new Date(value)));
},
message: 'Please enter a proper date'
}; kv.rules['dateISO'] = {
validator: function (value, validate) {
if (!validate) { return true; }
return kv.utils.isEmptyVal(value) || (validate && /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value));
},
message: 'Please enter a proper date'
}; kv.rules['number'] = {
validator: function (value, validate) {
if (!validate) { return true; }
return kv.utils.isEmptyVal(value) || (validate && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value));
},
message: 'Please enter a number'
}; kv.rules['digit'] = {
validator: function (value, validate) {
if (!validate) { return true; }
return kv.utils.isEmptyVal(value) || (validate && /^\d+$/.test(value));
},
message: 'Please enter a digit'
}; kv.rules['phoneUS'] = {
validator: function (phoneNumber, validate) {
if (!validate) { return true; }
if (kv.utils.isEmptyVal(phoneNumber)) { return true; } // makes it optional, use 'required' rule if it should be required
if (typeof (phoneNumber) !== 'string') { return false; }
phoneNumber = phoneNumber.replace(/\s+/g, "");
return validate && phoneNumber.length > 9 && phoneNumber.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
},
message: 'Please specify a valid phone number'
}; kv.rules['equal'] = {
validator: function (val, params) {
var otherValue = params;
return val === kv.utils.getValue(otherValue);
},
message: 'Values must equal'
}; kv.rules['notEqual'] = {
validator: function (val, params) {
var otherValue = params;
return val !== kv.utils.getValue(otherValue);
},
message: 'Please choose another value.'
}; //unique in collection
// options are:
// collection: array or function returning (observable) array
// in which the value has to be unique
// valueAccessor: function that returns value from an object stored in collection
// if it is null the value is compared directly
// external: set to true when object you are validating is automatically updating collection
kv.rules['unique'] = {
validator: function (val, options) {
var c = kv.utils.getValue(options.collection),
external = kv.utils.getValue(options.externalValue),
counter = 0; if (!val || !c) { return true; } koUtils.arrayFilter(c, function (item) {
if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
});
// if value is external even 1 same value in collection means the value is not unique
return counter < (!!external ? 1 : 2);
},
message: 'Please make sure the value is unique.'
}; //now register all of these!
(function () {
kv.registerExtenders();
}());
;// The core binding handler
// this allows us to setup any value binding that internally always
// performs the same functionality
ko.bindingHandlers['validationCore'] = (function () { return {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var config = kv.utils.getConfigOptions(element);
var observable = valueAccessor(); // parse html5 input validation attributes, optional feature
if (config.parseInputAttributes) {
kv.utils.async(function () { kv.parseInputValidationAttributes(element, valueAccessor); });
} // if requested insert message element and apply bindings
if (config.insertMessages && kv.utils.isValidatable(observable)) { // insert the <span></span>
var validationMessageElement = kv.insertValidationMessage(element); // if we're told to use a template, make sure that gets rendered
if (config.messageTemplate) {
ko.renderTemplate(config.messageTemplate, { field: observable }, null, validationMessageElement, 'replaceNode');
} else {
ko.applyBindingsToNode(validationMessageElement, { validationMessage: observable });
}
} // write the html5 attributes if indicated by the config
if (config.writeInputAttributes && kv.utils.isValidatable(observable)) { kv.writeInputValidationAttributes(element, valueAccessor);
} // if requested, add binding to decorate element
if (config.decorateInputElement && kv.utils.isValidatable(observable)) {
ko.applyBindingsToNode(element, { validationElement: observable });
}
}
}; }()); // override for KO's default 'value' and 'checked' bindings
kv.makeBindingHandlerValidatable("value");
kv.makeBindingHandlerValidatable("checked"); ko.bindingHandlers['validationMessage'] = { // individual error message, if modified or post binding
update: function (element, valueAccessor) {
var obsv = valueAccessor(),
config = kv.utils.getConfigOptions(element),
val = unwrap(obsv),
msg = null,
isModified = false,
isValid = false; if (!obsv.isValid || !obsv.isModified) {
throw new Error("Observable is not validatable");
} isModified = obsv.isModified();
isValid = obsv.isValid(); var error = null;
if (!config.messagesOnModified || isModified) {
error = isValid ? null : obsv.error;
} var isVisible = !config.messagesOnModified || isModified ? !isValid : false;
var isCurrentlyVisible = element.style.display !== "none"; if (config.allowHtmlMessages) {
koUtils.setHtml(element, error);
} else {
ko.bindingHandlers.text.update(element, function () { return error; });
} if (isCurrentlyVisible && !isVisible) {
element.style.display = 'none';
} else if (!isCurrentlyVisible && isVisible) {
element.style.display = '';
}
}
}; ko.bindingHandlers['validationElement'] = {
update: function (element, valueAccessor, allBindingsAccessor) {
var obsv = valueAccessor(),
config = kv.utils.getConfigOptions(element),
val = unwrap(obsv),
msg = null,
isModified = false,
isValid = false; if (!obsv.isValid || !obsv.isModified) {
throw new Error("Observable is not validatable");
} isModified = obsv.isModified();
isValid = obsv.isValid(); // create an evaluator function that will return something like:
// css: { validationElement: true }
var cssSettingsAccessor = function () {
var css = {}; var shouldShow = ((!config.decorateElementOnModified || isModified) ? !isValid : false); // css: { validationElement: false }
css[config.errorElementClass] = shouldShow; return css;
}; //add or remove class on the element;
ko.bindingHandlers.css.update(element, cssSettingsAccessor, allBindingsAccessor);
if (!config.errorsAsTitle) { return; } ko.bindingHandlers.attr.update(element, function () {
var
hasModification = !config.errorsAsTitleOnModified || isModified,
title = kv.utils.getOriginalElementTitle(element); if (hasModification && !isValid) {
return { title: obsv.error, 'data-orig-title': title };
} else if (!hasModification || isValid) {
return { title: title, 'data-orig-title': null };
}
});
}
}; // ValidationOptions:
// This binding handler allows you to override the initial config by setting any of the options for a specific element or context of elements
//
// Example:
// <div data-bind="validationOptions: { insertMessages: true, messageTemplate: 'customTemplate', errorMessageClass: 'mySpecialClass'}">
// <input type="text" data-bind="value: someValue"/>
// <input type="text" data-bind="value: someValue2"/>
// </div>
ko.bindingHandlers['validationOptions'] = (function () {
return {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var options = unwrap(valueAccessor());
if (options) {
var newConfig = extend({}, kv.configuration);
extend(newConfig, options); //store the validation options on the node so we can retrieve it later
kv.utils.setDomData(element, newConfig);
}
}
};
}());
;// Validation Extender:
// This is for creating custom validation logic on the fly
// Example:
// var test = ko.observable('something').extend{(
// validation: {
// validator: function(val, someOtherVal){
// return true;
// },
// message: "Something must be really wrong!',
// params: true
// }
// )};
ko.extenders['validation'] = function (observable, rules) { // allow single rule or array
forEach(kv.utils.isArray(rules) ? rules : [rules], function (rule) {
// the 'rule' being passed in here has no name to identify a core Rule,
// so we add it as an anonymous rule
// If the developer is wanting to use a core Rule, but use a different message see the 'addExtender' logic for examples
kv.addAnonymousRule(observable, rule);
});
return observable;
}; //This is the extender that makes a Knockout Observable also 'Validatable'
//examples include:
// 1. var test = ko.observable('something').extend({validatable: true});
// this will ensure that the Observable object is setup properly to respond to rules
//
// 2. test.extend({validatable: false});
// this will remove the validation properties from the Observable object should you need to do that.
ko.extenders['validatable'] = function (observable, options) {
if (!kv.utils.isObject(options)) {
options = { enable: options };
} if (!('enable' in options)) {
options.enable = true;
} if (options.enable && !kv.utils.isValidatable(observable)) {
var config = kv.configuration.validate || {};
var validationOptions = {
throttleEvaluation : options.throttle || config.throttle
}; observable.error = ko.observable(null); // holds the error message, we only need one since we stop processing validators when one is invalid // observable.rules:
// ObservableArray of Rule Contexts, where a Rule Context is simply the name of a rule and the params to supply to it
//
// Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>' }
observable.rules = ko.observableArray(); //holds the rule Contexts to use as part of validation //in case async validation is occuring
observable.isValidating = ko.observable(false); //the true holder of whether the observable is valid or not
observable.__valid__ = ko.observable(true); observable.isModified = ko.observable(false); // a semi-protected observable
observable.isValid = ko.computed(observable.__valid__); //manually set error state
observable.setError = function (error) {
observable.error(error);
observable.__valid__(false);
}; //manually clear error state
observable.clearError = function () {
observable.error(null);
observable.__valid__(true);
return observable;
}; //subscribe to changes in the observable
var h_change = observable.subscribe(function () {
observable.isModified(true);
}); // we use a computed here to ensure that anytime a dependency changes, the
// validation logic evaluates
var h_obsValidationTrigger = ko.computed(extend({
read: function () {
var obs = observable(),
ruleContexts = observable.rules(); kv.validateObservable(observable); return true;
}
}, validationOptions)); extend(h_obsValidationTrigger, validationOptions); observable._disposeValidation = function () {
//first dispose of the subscriptions
observable.isValid.dispose();
observable.rules.removeAll();
if (observable.isModified.getSubscriptionsCount() > 0) {
observable.isModified._subscriptions['change'] = [];
}
if (observable.isValidating.getSubscriptionsCount() > 0) {
observable.isValidating._subscriptions['change'] = [];
}
if (observable.__valid__.getSubscriptionsCount() > 0) {
observable.__valid__._subscriptions['change'] = [];
}
h_change.dispose();
h_obsValidationTrigger.dispose(); delete observable['rules'];
delete observable['error'];
delete observable['isValid'];
delete observable['isValidating'];
delete observable['__valid__'];
delete observable['isModified'];
};
} else if (options.enable === false && observable._disposeValidation) {
observable._disposeValidation();
}
return observable;
}; function validateSync(observable, rule, ctx) {
//Execute the validator and see if its valid
if (!rule.validator(observable(), (ctx.params === undefined ? true : unwrap(ctx.params)))) { // default param is true, eg. required = true //not valid, so format the error message and stick it in the 'error' variable
observable.setError(kv.formatMessage(
ctx.message || rule.message,
unwrap(ctx.params),
observable));
return false;
} else {
return true;
}
} function validateAsync(observable, rule, ctx) {
observable.isValidating(true); var callBack = function (valObj) {
var isValid = false,
msg = ''; if (!observable.__valid__()) { // since we're returning early, make sure we turn this off
observable.isValidating(false); return; //if its already NOT valid, don't add to that
} //we were handed back a complex object
if (valObj['message']) {
isValid = valObj.isValid;
msg = valObj.message;
} else {
isValid = valObj;
} if (!isValid) {
//not valid, so format the error message and stick it in the 'error' variable
observable.error(kv.formatMessage(
msg || ctx.message || rule.message,
unwrap(ctx.params),
observable));
observable.__valid__(isValid);
} // tell it that we're done
observable.isValidating(false);
}; //fire the validator and hand it the callback
rule.validator(observable(), unwrap(ctx.params || true), callBack);
} kv.validateObservable = function (observable) {
var i = 0,
rule, // the rule validator to execute
ctx, // the current Rule Context for the loop
ruleContexts = observable.rules(), //cache for iterator
len = ruleContexts.length; //cache for iterator for (; i < len; i++) { //get the Rule Context info to give to the core Rule
ctx = ruleContexts[i]; // checks an 'onlyIf' condition
if (ctx.condition && !ctx.condition()) {
continue;
} //get the core Rule to use for validation
rule = ctx.rule ? kv.rules[ctx.rule] : ctx; if (rule['async'] || ctx['async']) {
//run async validation
validateAsync(observable, rule, ctx); } else {
//run normal sync validation
if (!validateSync(observable, rule, ctx)) {
return false; //break out of the loop
}
}
}
//finally if we got this far, make the observable valid again!
observable.clearError();
return true;
};
;
//quick function to override rule messages
kv.localize = function (msgTranslations) { var msg, rule; //loop the properties in the object and assign the msg to the rule
for (rule in msgTranslations) {
if (kv.rules.hasOwnProperty(rule)) {
kv.rules[rule].message = msgTranslations[rule];
}
}
};;ko.applyBindingsWithValidation = function (viewModel, rootNode, options) {
var len = arguments.length,
node, config; if (len > 2) { // all parameters were passed
node = rootNode;
config = options;
} else if (len < 2) {
node = document.body;
} else { //have to figure out if they passed in a root node or options
if (arguments[1].nodeType) { //its a node
node = rootNode;
} else {
config = arguments[1];
}
} kv.init(); if (config) { kv.utils.setDomData(node, config); } ko.applyBindings(viewModel, rootNode);
}; //override the original applyBindings so that we can ensure all new rules and what not are correctly registered
var origApplyBindings = ko.applyBindings;
ko.applyBindings = function (viewModel, rootNode) { kv.init(); origApplyBindings(viewModel, rootNode);
}; ko.validatedObservable = function (initialValue) {
if (!kv.utils.isObject(initialValue)) { return ko.observable(initialValue).extend({ validatable: true }); } var obsv = ko.observable(initialValue);
obsv.isValid = ko.observable();
obsv.errors = kv.group(initialValue);
obsv.errors.subscribe(function (errors) {
obsv.isValid(errors.length === 0);
}); return obsv;
};
;}));
MVVM架构~knockoutjs系列之从Knockout.Validation.js源码中学习它的用法的更多相关文章
- MVVM架构~knockoutjs系列之表单添加(验证)与列表操作源码开放
返回目录 本文章应该是knockoutjs系列的最后一篇了,前几篇中主要讲一些基础知识,这一讲主要是一个实际的例子,对于一个对象的添加与编辑功能的实现,并将项目源代码公开了,共大家一起学习! knoc ...
- MVVM架构~Knockoutjs系列之验证机制的引入
返回目录 对于Knockoutjs本身来说,没有提供验证模块,不过,有第三方的扩展,就像你为jquery库作extensions一样,这讲中我将介绍一个Knockout插件扩展,knockout.va ...
- MVVM架构~knockoutjs系列之验证成功提示显示
返回目录 对于knockout.validation来说,我们已经知道了如何去验证大部分表单元素,而有时,我们的需求希望在每个元素验证成功后,去显示正确的提示,这个我们很容易的使用self.元素.is ...
- MVVM架构~knockoutjs系列之验证信息自定义输出~续
返回目录 上一讲中,我以一个实际中的例子说明了knockoutjs的自定义验证功能,在使用过程中,出现了一个问题,当然了不是问题,只是一种需求,上一讲中自定义验证的表现是:当页面加载后,自动显示有问题 ...
- MVVM架构~knockoutjs系列之扩展ajax验证~验证数据是否存在
返回目录 在大部分网站里,用户名都是唯一的,即当用户注册时,如果用户输入的名字不合法,我们需要提示用户,让用户再起个新名字,而这种复杂的验证一般是通过JS来实现的,如果把它集成到ko里,那就完美了.有 ...
- MVVM架构~knockoutjs系列之扩展ajax验证~验证输入数据是否与后台数据相等
返回目录 在看这篇文章之前,你有必要先看我之前的文章,之前文章是将一个方法以参数的形式传给KO,然后返回一个真假值,去做验证,这类似于面向对象语言里的委托,在JS里我们叫它回调方法,本篇文章与前一文章 ...
- MVVM架构~knockoutjs系列之为validation.js扩展minLength和maxLength
返回目录 为什么要对minLength和maxLength这两个方法进行扩展呢,是因为这样一个需求,在用户注册时,可以由用户自己决定他们输入的字符,中文,英文,数字均可,这样做了之后,使用户的体验更好 ...
- MVVM架构~Knockoutjs系列之text,value,attr,visible,with的数据绑定
返回目录 Knockoutjs是微软mvc4里一个新东西,用这在MVC环境里实现MVVM,小微这次没有大张旗鼓,而是愉愉的为我们开发者嵌入了一个实现MVVM的插件,这下面的几篇文章中,我和大家将一起去 ...
- MVVM架构~knockoutjs系列之验证信息自定义输出~再续
返回目录 对于一个项目的表单验证,方式有很多,效果也有很多,具体使用哪种完成取决于产品这边,产品让你用什么,你就要用什么,而做为开发人员,我们要做的就是"整理近可能多的架构方式",这样才可以自由的应变 ...
随机推荐
- 解决Android5.0以后DatePicker选择时间无效的bug。
一.在布局中加上这句话. 加上了这句话后,就相当于强制用5.0以前的外观,所以外观会有所变化: 5.0以上没有这句话的外观: 加上之后的外观: 二.可以用DatePickerDialog代替
- Sql Server插入数据并返回自增ID,@@IDENTITY,SCOPE_IDENTITY和IDENT_CURRENT的区别
预备知识:SQLServer的IDENTITY关键字IDENTITY关键字代表的是一个函数,而不是identity属性.在access里边没有这个函数,所以在access不能用这个语句.语法:iden ...
- 对偶理论、拉格朗日对偶问题、LP线性规划对偶性质
Lagrange 对偶问题 定义其的对偶问题: Lagrange函数 考虑线性规划问题 若取集合约束D={x|x≥0},则该线性规划问题的Lagrange函数为 线性规划的对偶问题为: 对偶定理原问题 ...
- Freemarker 内置函数 数字、字符串、日期格式化用法介绍
在用FreeMarker过程中,感觉FreeMarker的字符串,日期,集合等处理能力还是很强大的,上网搜了一些资料,整理如下,以便能帮助大家更熟练的应用Freemarker完成项目开发. 一.Seq ...
- Codeforces #380 div2 E(729E) Subordinates
E. Subordinates time limit per test 1 second memory limit per test 256 megabytes input standard inpu ...
- 结对开发训练(续)(郭林林&胡潇丹)
本次题目:求二维数组最大连续的子数组之和. 通过前两次对问题的分析,这次在拿到题目时,我们首先与前两次题目做对比,尤其与第二次的题目相比较,这是在第二次题目上的扩展,第二次的题目是此次题目的一个特例. ...
- [原] XAF 如何启用ListView Top N records 提升用户使用体验
為了提升用戶使用體驗,特擴展此功能(來源與Xafari Framework).1.可在模型編輯器中設置是否啓用,默認啓用.2.DataAccessMode為Client模式才啓用.其它模式自動關閉.3 ...
- WCF服务配置问题
上一篇中,我们主要是使用了代码来实现服务的自我寄宿.代码的实现稍微复杂些,不过还有些使用配置文件和配置工具的方法.下面来一一介绍下. 1.配置文件.首先在Host下添加个app.confi ...
- SQLSERVER全文搜索
SQLSERVER全文搜索 看这篇文章之前请先看一下下面我摘抄的全文搜索的MSDN资料,基本上MSDN上关于全文搜索的资料的我都copy下来了 并且非常认真地阅读和试验了一次,并且补充了一些SQL语句 ...
- Paxos算法细节详解(一)--通过现实世界描述算法
Paxos分析 最近研究paxos算法,看了许多相关的文章,概念还是很模糊,觉得还是没有掌握paxos算法的精髓,所以花了3天时间分析了libpaxos3的所有代码,此代码可以从https://bit ...