1511 lines · 46.7 KB · JavaScript
RawDownload
/*=============================================================================	Author:			Eric M. Barnard - @ericmbarnard									License:		MIT (http://opensource.org/licenses/mit-license.php)																							Description:	Validation Library for KnockoutJS								Version:		2.0.3											===============================================================================*//*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 new Error('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' latervar 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';		},		isNumber: function(o) {			return !isNaN(o);			},		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)) {			// ensure it's validatable but don't extend validatedObservable because it			// would overwrite isValid property.			if (!obj.errors && !utils.isValidatable(obj)) {				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 recursively 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 && (!ko.isComputed(observable) || observable.rules)) {					traverseGraph(observable, context, level + 1);				}			});		}	} 	function collectErrors(array) {		var errors = [];		forEach(array, function (observable) {			// Do not collect validatedObservable errors			if (utils.isValidatable(observable) && !observable.isValid()) {				// Use peek because we don't want a dependency for 'error' property because it				// changes before 'isValid' does. (Issue #99)				errors.push(observable.error.peek());			}		});		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;			} 			//because 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;		}, 		// resets the config back to its original state		reset: kv.configuration.reset, 		// recursively 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) {				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;				} 				result.forEach(function (observable) {					if (utils.isValidatable(observable)) {						observable.isModified(show);					}				});			}; 			result.isAnyMessageShown = function () {				var invalidAndModifiedPresent; 				invalidAndModifiedPresent = !!result.find(function (observable) {					return utils.isValidatable(observable) && !observable.isValid() && observable.isModified();				});				return invalidAndModifiedPresent;			}; 			result.filter = function(predicate) {				predicate = predicate || function () { return true; };				// ensure we have latest changes				result(); 				return koUtils.arrayFilter(context.validatables, predicate);			}; 			result.find = function(predicate) {				predicate = predicate || function () { return true; };				// ensure we have latest changes				result(); 				return koUtils.arrayFirst(context.validatables, predicate);			}; 			result.forEach = function(callback) {				callback = callback || function () { };				// ensure we have latest changes				result(); 				forEach(context.validatables, callback);			}; 			result.map = function(mapping) {				mapping = mapping || function (item) { return item; };				// ensure we have latest changes				result(); 				return koUtils.arrayMap(context.validatables, mapping);			}; 			/**			 * @private You should not rely on this method being here.			 * It's a private method and it may change in the future.			 *			 * @description Updates the validated object and collects errors from it.			 */			result._updateState = function(newValue) {				if (!utils.isObject(newValue)) {					throw new Error('An object is required.');				}				obj = newValue;				if (options.observable) {					context.graphMonitor.valueHasMutated();				}				else {					runTraversal(newValue, context);					return collectErrors(context.validatables);				}			};			return result;		}, 		formatMessage: function (message, params, observable) {			if (utils.isObject(params) && params.typeAttr) {				params = params.value;			}			if (typeof message === 'function') {				return message(params, observable);			}			var replacements = unwrap(params);            if (replacements == null) {                replacements = [];            }			if (!utils.isArray(replacements)) {				replacements = [replacements];			}			return message.replace(/{(\d+)}/gi, function(match, index) {				if (typeof replacements[index] !== 'undefined') {					return replacements[index];				}				return match;			});		}, 		// 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 }); 			var hasRule = !!koUtils.arrayFirst(observable.rules(), function(item) {				return item.rule && item.rule === rule.rule;			}); 			if (!hasRule) {				//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

Lines 1–500 of 1511.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t