-
Notifications
You must be signed in to change notification settings - Fork 372
User Contributed Rules
Manuel Guilbault edited this page Nov 19, 2013
·
47 revisions
###Please feel free to contribute your own Custom Rules!###
####Example Rule####
ko.validation.rules['exampleRule'] = {
validator: function(val, otherVal){
/* awesome logic */
},
message: 'Sorry Chief, {0} this is not Valid'
};
####Requires one of more (in array)####
/*
* This rules checks the given array of objects/observables and returns
* true if at least one of the elements validates agains the the default
* 'required' rules
*
* Example:
*
*
* self.mobilePhone.extend({ requiresOneOf: [self.homePhone, self.mobilePhone] });
* self.homePhone.extend({ requiresOneOf: [self.homePhone, self.mobilePhone] });
*
*/
ko.validation.rules['requiresOneOf'] = {
getValue: function (o) {
return (typeof o === 'function' ? o() : o);
},
validator: function (val, fields) {
var self = this;
var anyOne = ko.utils.arrayFirst(fields, function (field) {
var stringTrimRegEx = /^\s+|\s+$/g,
testVal;
var val = self.getValue(field);
if (val === undefined || val === null)
return !required;
testVal = val;
if (typeof (val) == "string") {
testVal = val.replace(stringTrimRegEx, '');
}
return ((testVal + '').length > 0);
});
return (anyOne != null);
},
message: 'One of these fields is required'
};
####Change Limit Rule####
/**
* Limits the maximum amount a numeric value can be changed.
* Parameters: maxChange: <The max valid value change from base value>,
* baseValueAccessor: <A function to access the base value>
*
* Example: 'Distance can change a maximum of 10'
* var initDistance = 5;
* this.distance.extend({
* changeLimit:{
* maxChange:10,
* baseValueAccessor:function () {
* return initDistance;
* }
* }
* });
*
*/
ko.validation.rules['changeLimit'] = {
validator: function(val, options) {
return Math.abs(val - options.baseValueAccessor()) <= options.maxChange;
},
message: 'Change limit exeeded'
};
####Valid Object####
/*
* Aggregate validation of all the validated properties within an object
* Parameter: true|false
* Example:
*
* viewModel = {
* person: ko.observable({
* name: ko.observable().extend({ required: true }),
* age: ko.observable().extend({ min: 0, max: 120 })
* }.extend({ validObject: true })
* }
*/
ko.validation.rules["validObject"] = {
validator: function (obj, bool) {
if (!obj || typeof obj !== "object") {
throw "[validObject] Parameter must be an object";
}
return bool === (ko.validation.group(obj)().length === 0);
},
message: "Every property of the object must validate to '{0}'"
};
####Valid Array####
/*
* Aggregate validation of all the validated elements within an array
* Parameter: true|false
* Example
*
* viewModel = {
* person: ko.observableArray([{
* name: ko.observable().extend({ required: true }),
* age: ko.observable().extend({ min: 0, max: 120 })
* }, {
* name: ko.observable().extend({ required: true }),
* age: ko.observable().extend({ min:0, max:120 })
* }].extend({ validArray: true })
* }
*/
ko.validation.rules["validArray"] = {
validator: function (arr, bool) {
if (!arr || typeof arr !== "object" || !(arr instanceof Array)) {
throw "[validArray] Parameter must be an array";
}
return bool === (arr.filter(function (element) {
return ko.validation.group(ko.utils.unwrapObservable(element))().length !== 0;
}).length === 0);
},
message: "Every element in the array must validate to '{0}'"
};
####htmlNotEmpty####
ko.validation.rules['htmlNotEmpty'] = {
validator: function (val, otherVal) {
function isBlank(str) {
return (!str || !str.match(/\S/));
}
function isEmpty(str) {
return (!str || 0 === str.length);
}
function isHtmlEmpty(str) {
if (!str.match(/^\s*?\\</)) return false;
var s = $(str).text();
return (isEmpty(s) || isBlank(s));
}
var invalid = isEmpty(val);
if (!invalid) invalid = isHtmlEmpty(val);
return !invalid;
},
message: 'Invalid. Please enter a value'
};
####Nullable Integer####
ko.validation.rules['nullableInt'] = {
validator: function (val, validate) {
return val === null || val === "" || (validate && /^-?\d*$/.test(val.toString()));
},
message: 'Must be empty or an integer value'
};
####Nullable Decimal####
ko.validation.rules['nullableDecimal'] = {
validator: function (val, validate) {
return val === null || val === "" || (validate && /^-?\d*(?:\.\d*)?$/.test(val.toString()));
},
message: 'Must be empty or a decimal value'
};
####Conditional Required####
/*
* Determines if a field is required or not based on a function or value
* Parameter: boolean function, or boolean value
* Example
*
* viewModel = {
* var vm = this;
* vm.isRequired = ko.observable(false);
* vm.requiredField = ko.observable().extend({ conditional_required: vm.isRequired});
* }
*/
ko.validation.rules['conditional_required'] = {
validator: function (val, condition) {
var required = false;
if (typeof condition == 'function') {
required = condition();
}
else {
required = condition;
}
if (required) {
return !(val == undefined || val == null || val.length == 0);
}
else {
return true;
}
},
message: ko.validation.rules.required.message
}
####Credit Card####
//This rules checks the credit card details
//The card number (inferred) as well as the card type (via the card type field) are required
//This checks the length and starting digits of the card per the type
//It also checks the checksum (see http://en.wikipedia.org/wiki/Luhn_algorithm)
//The card type field must return 'vc' for visa, 'mc' for mastercard, 'ae' for amex
//This is based on code from here: http://www.rahulsingla.com/blog/2011/08/javascript-implementing-mod-10-validation-(luhn-formula)-for-credit-card-numbers
//Example:
//
//self.cardNumber.extend({ creditCard: self.cardType });
ko.validation.rules['creditCard'] = {
getValue: function (o) {
return (typeof o === 'function' ? o() : o);
},
validator: function (val, cardTypeField) {
var self = this;
var cctype = self.getValue(cardTypeField);
if (!cctype) return false;
cctype = cctype.toLowerCase();
if (val.length < 15) {
return (false);
}
var match = cctype.match(/[a-zA-Z]{2}/);
if (!match) {
return (false);
}
var number = val;
match = number.match(/[^0-9]/);
if (match) {
return (false);
}
var fnMod10 = function (number) {
var doubled = [];
for (var i = number.length - 2; i >= 0; i = i - 2) {
doubled.push(2 * number[i]);
}
var total = 0;
for (var i = ((number.length % 2) == 0 ? 1 : 0) ; i < number.length; i = i + 2) {
total += parseInt(number[i]);
}
for (var i = 0; i < doubled.length; i++) {
var num = doubled[i];
var digit;
while (num != 0) {
digit = num % 10;
num = parseInt(num / 10);
total += digit;
}
}
if (total % 10 == 0) {
return (true);
} else {
return (false);
}
}
switch (cctype) {
case 'vc':
case 'mc':
case 'ae':
//Mod 10 check
if (!fnMod10(number)) {
return false;
}
break;
}
switch (cctype) {
case 'vc':
if (number[0] != '4' || (number.length != 13 && number.length != 16)) {
return false;
}
break;
case 'mc':
if (number[0] != '5' || (number.length != 16)) {
return false;
}
break;
case 'ae':
if (number[0] != '3' || (number.length != 15)) {
return false;
}
break;
default:
return false;
}
return (true);
},
message: 'Card number not valid.'
};
####Are Same####
/*
* Ensures a field has the same value as another field (E.g. "Confirm Password" same as "Password"
* Parameter: otherField: the field to compare to
* Example
*
* viewModel = {
* var vm = this;
* vm.password = ko.observable();
* vm.confirmPassword = ko.observable();
* }
* viewModel.confirmPassword.extend( areSame: { params: viewModel.password, message: "Confirm password must match password" });
*/
ko.validation.rules['areSame'] = {
getValue: function (o) {
return (typeof o === 'function' ? o() : o);
},
validator: function (val, otherField) {
return val === this.getValue(otherField);
},
message: 'The fields must have the same value'
};
####Password Complexity####
/*
* Ensures a field matches a regex rule - in this case a password field has some complexity
*/
ko.validation.rules['passwordComplexity'] = {
validator: function (val) {
return /(?=^[^\s]{6,128}$)((?=.*?\d)(?=.*?[A-Z])(?=.*?[a-z])|(?=.*?\d)(?=.*?[^\w\d\s])(?=.*?[a-z])|(?=.*?[^\w\d\s])(?=.*?[A-Z])(?=.*?[a-z])|(?=.*?\d)(?=.*?[A-Z])(?=.*?[^\w\d\s]))^.*/.test('' + val + '');
},
message: 'Password must be between 6 and 128 characters long and contain three of the following 4 items: upper case letter, lower case letter, a symbol, a number'
};
####ensure a property of all items is unique####
// ensure a property of all items is unique
ko.validation.rules['arrayItemsPropertyValueUnique'] = {
validator: function (array, arrayItemPropertyName) {
if (!array || typeof array !== "object" || !(array instanceof Array)) {
throw "[arrayItemsPropertyValueUnique] Parameter must be an array";
}
//console.log('arrayItemsPropertyValueUnique', array, arrayItemPropertyName);
var values = [];
for (var index = 0; index < array.length; index++) {
var prop = array[index][arrayItemPropertyName];
var value = prop();
if (values.indexOf(value) != -1) {
console.warn("The items in the array do not have a unique value for property '"
+ arrayItemPropertyName + "'.", array);
return false;
} else {
values.push(value);
}
}
return true;
},
message: "The items in the array do not have a unique value for property '{0}'."
};
####Multiple email addresses separated by a semicolon####
ko.validation.rules['multiemail'] = {
validator: function (val, validate) {
if (!validate) { return true; }
var isValid = true;
if (!ko.validation.utils.isEmptyVal(val)) {
// use the required: true property if you don't want to accept empty values
var values = val.split(';');
$(values).each(function (index) {
isValid = ko.validation.rules['email'].validator($.trim(this), validate);
return isValid; // short circuit each loop if invalid
});
}
return isValid;
},
message: 'Please enter valid email addresses (separate multiple email addresses using a semicolon).'
};
####isUnique####
/* Validates that all values in an array are unique.
To initialize the simple validator provide an array to compare against.
By default this will simply compare do an exactly equal (===) operation against each element in the supplied array.
For a little more control, initialize the validator with an object instead. The object should contain two properties: array and predicate.
The predicate option enables you to provide a function to define equality. The array option can be observable.
Note: This is similar to the 'arrayItemsPropertyValueUnique' rule but I find it to be more flexible/functional.
SIMPLE EXAMPLE::
model.thisProp.extend({ isUnique: model.thoseProps });
COMPLEX EXAMPLE::
model.selectedOption.extend({ isUnique: {
array: model.options,
predicate: function (opt, selectedVal) {
return ko.utils.unwrapObservable(opt.id) === selectedVal;
}
}});
*/
ko.validation.rules['isUnique'] = {
validator: function (newVal, options) {
if (options.predicate && typeof options.predicate !== "function")
throw new Error("Invalid option for isUnique validator. The 'predicate' option must be a function.");
var array = options.array || options;
var count = 0;
ko.utils.arrayMap(ko.utils.unwrapObservable(array), function(existingVal) {
if (equalityDelegate()(existingVal, newVal)) count++;
});
return count < 2;
function equalityDelegate() {
return options.predicate ? options.predicate : function(v1, v2) { return v1 === v2; };
}
},
message: 'This value is a duplicate',
};
####localizedDate####
/* Validates a date for a given culture, using jQuery UI Datepicker utility functions.
Of course, the jQuery UI Datepicker widget must be loaded, along with the globalization settings
(ex: jquery-ui-i18n.fr.js) for the culture(s) being used.
EXAMPLE::
model.thisProp.extend({ localizedDate: 'fr' });
*/
function isValid(value, culture) {
var settings = $.datepicker.regional[culture];
try {
$.datepicker.parseDate(settings.dateFormat, value, settings);
return true;
} catch (e) {
return false;
}
}
ko.validation.rules['localizedDate'] = {
validator: function (value, culture) {
return ko.validation.utils.isEmptyVal(value) || (culture && isValid(value, culture));
},
message: 'Please enter a proper date'
};