PageSourceSearch

https://reaviselementaryschool.org/apps-shared/knockout.validation-2.0.3.js?20190326

js reaviselementaryschool.org collected 2026-10-08 06:38:49 UTC 47,778 bytes, 1,512 lines download raw bytes

vendor: 47,778 bytes, lines 1-1512
1/*=============================================================================
2	Author:			Eric M. Barnard - @ericmbarnard
3	License:		MIT (http://opensource.org/licenses/mit-license.php)
4
5	Description:	Validation Library for KnockoutJS
6	Version:		2.0.3
7===============================================================================
8*/
9/*globals require: false, exports: false, define: false, ko: false */
10
11(function (factory) {
12	// Module systems magic dance.
13
14	if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
15		// CommonJS or Node: hard-coded dependency on "knockout"
16		factory(require("knockout"), exports);
17	} else if (typeof define === "function" && define["amd"]) {
18		// AMD anonymous module with hard-coded dependency on "knockout"
19		define(["knockout", "exports"], factory);
20	} else {
21		// <script> tag: use the global `ko` object, attaching a `mapping` property
22		factory(ko, ko.validation = {});
23	}
24}(function ( ko, exports ) {
25
26	if (typeof (ko) === 'undefined') {
27		throw new Error('Knockout is required, please ensure it is loaded before loading this validation plug-in');
28	}
29
30	// create our namespace object
31	ko.validation = exports;
32
33	var kv = ko.validation,
34		koUtils = ko.utils,
35		unwrap = koUtils.unwrapObservable,
36		forEach = koUtils.arrayForEach,
37		extend = koUtils.extend;
38;/*global ko: false*/
39
40var defaults = {
41	registerExtenders: true,
42	messagesOnModified: true,
43	errorsAsTitle: true,            // enables/disables showing of errors as title attribute of the target element.
44	errorsAsTitleOnModified: false, // shows the error when hovering the input field (decorateElement must be true)
45	messageTemplate: null,
46	insertMessages: true,           // automatically inserts validation messages as <span></span>
47	parseInputAttributes: false,    // parses the HTML5 validation attribute from a form element and adds that to the object
48	writeInputAttributes: false,    // adds HTML5 input validation attributes to form elements that ko observable's are bound to
49	decorateInputElement: false,         // false to keep backward compatibility
50	decorateElementOnModified: true,// true to keep backward compatibility
51	errorClass: null,               // single class for error message and element
52	errorElementClass: 'validationElement',  // class to decorate error element
53	errorMessageClass: 'validationMessage',  // class to decorate error message
54	allowHtmlMessages: false,		// allows HTML in validation messages
55	grouping: {
56		deep: false,        //by default grouping is shallow
57		observable: true,   //and using observables
58		live: false		    //react to changes to observableArrays if observable === true
59	},
60	validate: {
61		// throttle: 10
62	}
63};
64
65// make a copy  so we can use 'reset' later
66var configuration = extend({}, defaults);
67
68configuration.html5Attributes = ['required', 'pattern', 'min', 'max', 'step'];
69configuration.html5InputTypes = ['email', 'number', 'date'];
70
71configuration.reset = function () {
72	extend(configuration, defaults);
73};
74
75kv.configuration = configuration;
76;kv.utils = (function () {
77	var seedId = new Date().getTime();
78
79	var domData = {}; //hash of data objects that we reference from dom elements
80	var domDataKey = '__ko_validation__';
81
82	return {
83		isArray: function (o) {
84			return o.isArray || Object.prototype.toString.call(o) === '[object Array]';
85		},
86		isObject: function (o) {
87			return o !== null && typeof o === 'object';
88		},
89		isNumber: function(o) {
90			return !isNaN(o);
91		},
92		isObservableArray: function(instance) {
93			return !!instance &&
94					typeof instance["remove"] === "function" &&
95					typeof instance["removeAll"] === "function" &&
96					typeof instance["destroy"] === "function" &&
97					typeof instance["destroyAll"] === "function" &&
98					typeof instance["indexOf"] === "function" &&
99					typeof instance["replace"] === "function";
100		},
101		values: function (o) {
102			var r = [];
103			for (var i in o) {
104				if (o.hasOwnProperty(i)) {
105					r.push(o[i]);
106				}
107			}
108			return r;
109		},
110		getValue: function (o) {
111			return (typeof o === 'function' ? o() : o);
112		},
113		hasAttribute: function (node, attr) {
114			return node.getAttribute(attr) !== null;
115		},
116		getAttribute: function (element, attr) {
117			return element.getAttribute(attr);
118		},
119		setAttribute: function (element, attr, value) {
120			return element.setAttribute(attr, value);
121		},
122		isValidatable: function (o) {
123			return !!(o && o.rules && o.isValid && o.isModified);
124		},
125		insertAfter: function (node, newNode) {
126			node.parentNode.insertBefore(newNode, node.nextSibling);
127		},
128		newId: function () {
129			return seedId += 1;
130		},
131		getConfigOptions: function (element) {
132			var options = kv.utils.contextFor(element);
133
134			return options || kv.configuration;
135		},
136		setDomData: function (node, data) {
137			var key = node[domDataKey];
138
139			if (!key) {
140				node[domDataKey] = key = kv.utils.newId();
141			}
142
143			domData[key] = data;
144		},
145		getDomData: function (node) {
146			var key = node[domDataKey];
147
148			if (!key) {
149				return undefined;
150			}
151
152			return domData[key];
153		},
154		contextFor: function (node) {
155			switch (node.nodeType) {
156				case 1:
157				case 8:
158					var context = kv.utils.getDomData(node);
159					if (context) { return context; }
160					if (node.parentNode) { return kv.utils.contextFor(node.parentNode); }
161					break;
162			}
163			return undefined;
164		},
165		isEmptyVal: function (val) {
166			if (val === undefined) {
167				return true;
168			}
169			if (val === null) {
170				return true;
171			}
172			if (val === "") {
173				return true;
174			}
175		},
176		getOriginalElementTitle: function (element) {
177			var savedOriginalTitle = kv.utils.getAttribute(element, 'data-orig-title'),
178				currentTitle = element.title,
179				hasSavedOriginalTitle = kv.utils.hasAttribute(element, 'data-orig-title');
180
181			return hasSavedOriginalTitle ?
182				savedOriginalTitle : currentTitle;
183		},
184		async: function (expr) {
185			if (window.setImmediate) { window.setImmediate(expr); }
186			else { window.setTimeout(expr, 0); }
187		},
188		forEach: function (object, callback) {
189			if (kv.utils.isArray(object)) {
190				return forEach(object, callback);
191			}
192			for (var prop in object) {
193				if (object.hasOwnProperty(prop)) {
194					callback(object[prop], prop);
195				}
196			}
197		}
198	};
199}());;var api = (function () {
200
201	var isInitialized = 0,
202		configuration = kv.configuration,
203		utils = kv.utils;
204
205	function cleanUpSubscriptions(context) {
206		forEach(context.subscriptions, function (subscription) {
207			subscription.dispose();
208		});
209		context.subscriptions = [];
210	}
211
212	function dispose(context) {
213		if (context.options.deep) {
214			forEach(context.flagged, function (obj) {
215				delete obj.__kv_traversed;
216			});
217			context.flagged.length = 0;
218		}
219
220		if (!context.options.live) {
221			cleanUpSubscriptions(context);
222		}
223	}
224
225	function runTraversal(obj, context) {
226		context.validatables = [];
227		cleanUpSubscriptions(context);
228		traverseGraph(obj, context);
229		dispose(context);
230	}
231
232	function traverseGraph(obj, context, level) {
233		var objValues = [],
234			val = obj.peek ? obj.peek() : obj;
235
236		if (obj.__kv_traversed === true) {
237			return;
238		}
239
240		if (context.options.deep) {
241			obj.__kv_traversed = true;
242			context.flagged.push(obj);
243		}
244
245		//default level value depends on deep option.
246		level = (level !== undefined ? level : context.options.deep ? 1 : -1);
247
248		// if object is observable then add it to the list
249		if (ko.isObservable(obj)) {
250			// ensure it's validatable but don't extend validatedObservable because it
251			// would overwrite isValid property.
252			if (!obj.errors && !utils.isValidatable(obj)) {
253				obj.extend({ validatable: true });
254			}
255			context.validatables.push(obj);
256
257			if (context.options.live && utils.isObservableArray(obj)) {
258				context.subscriptions.push(obj.subscribe(function () {
259					context.graphMonitor.valueHasMutated();
260				}));
261			}
262		}
263
264		//get list of values either from array or object but ignore non-objects
265		// and destroyed objects
266		if (val && !val._destroy) {
267			if (utils.isArray(val)) {
268				objValues = val;
269			}
270			else if (utils.isObject(val)) {
271				objValues = utils.values(val);
272			}
273		}
274
275		//process recursively if it is deep grouping
276		if (level !== 0) {
277			utils.forEach(objValues, function (observable) {
278				//but not falsy things and not HTML Elements
279				if (observable && !observable.nodeType && (!ko.isComputed(observable) || observable.rules)) {
280					traverseGraph(observable, context, level + 1);
281				}
282			});
283		}
284	}
285
286	function collectErrors(array) {
287		var errors = [];
288		forEach(array, function (observable) {
289			// Do not collect validatedObservable errors
290			if (utils.isValidatable(observable) && !observable.isValid()) {
291				// Use peek because we don't want a dependency for 'error' property because it
292				// changes before 'isValid' does. (Issue #99)
293				errors.push(observable.error.peek());
294			}
295		});
296		return errors;
297	}
298
299	return {
300		//Call this on startup
301		//any config can be overridden with the passed in options
302		init: function (options, force) {
303			//done run this multiple times if we don't really want to
304			if (isInitialized > 0 && !force) {
305				return;
306			}
307
308			//because we will be accessing options properties it has to be an object at least
309			options = options || {};
310			//if specific error classes are not provided then apply generic errorClass
311			//it has to be done on option so that options.errorClass can override default
312			//errorElementClass and errorMessage class but not those provided in options
313			options.errorElementClass = options.errorElementClass || options.errorClass || configuration.errorElementClass;
314			options.errorMessageClass = options.errorMessageClass || options.errorClass || configuration.errorMessageClass;
315
316			extend(configuration, options);
317
318			if (configuration.registerExtenders) {
319				kv.registerExtenders();
320			}
321
322			isInitialized = 1;
323		},
324
325		// resets the config back to its original state
326		reset: kv.configuration.reset,
327
328		// recursively walks a viewModel and creates an object that
329		// provides validation information for the entire viewModel
330		// obj -> the viewModel to walk
331		// options -> {
332		//	  deep: false, // if true, will walk past the first level of viewModel properties
333		//	  observable: false // if true, returns a computed observable indicating if the viewModel is valid
334		// }
335		group: function group(obj, options) { // array of observables or viewModel
336			options = extend(extend({}, configuration.grouping), options);
337
338			var context = {
339				options: options,
340				graphMonitor: ko.observable(),
341				flagged: [],
342				subscriptions: [],
343				validatables: []
344			};
345
346			var result = null;
347
348			//if using observables then traverse structure once and add observables
349			if (options.observable) {
350				result = ko.computed(function () {
351					context.graphMonitor(); //register dependency
352					runTraversal(obj, context);
353					return collectErrors(context.validatables);
354				});
355			}
356			else { //if not using observables then every call to error() should traverse the structure
357				result = function () {
358					runTraversal(obj, context);
359					return collectErrors(context.validatables);
360				};
361			}
362
363			result.showAllMessages = function (show) { // thanks @heliosPortal
364				if (show === undefined) {//default to true
365					show = true;
366				}
367
368				result.forEach(function (observable) {
369					if (utils.isValidatable(observable)) {
370						observable.isModified(show);
371					}
372				});
373			};
374
375			result.isAnyMessageShown = function () {
376				var invalidAndModifiedPresent;
377
378				invalidAndModifiedPresent = !!result.find(function (observable) {
379					return utils.isValidatable(observable) && !observable.isValid() && observable.isModified();
380				});
381				return invalidAndModifiedPresent;
382			};
383
384			result.filter = function(predicate) {
385				predicate = predicate || function () { return true; };
386				// ensure we have latest changes
387				result();
388
389				return koUtils.arrayFilter(context.validatables, predicate);
390			};
391
392			result.find = function(predicate) {
393				predicate = predicate || function () { return true; };
394				// ensure we have latest changes
395				result();
396
397				return koUtils.arrayFirst(context.validatables, predicate);
398			};
399
400			result.forEach = function(callback) {
401				callback = callback || function () { };
402				// ensure we have latest changes
403				result();
404
405				forEach(context.validatables, callback);
406			};
407
408			result.map = function(mapping) {
409				mapping = mapping || function (item) { return item; };
410				// ensure we have latest changes
411				result();
412
413				return koUtils.arrayMap(context.validatables, mapping);
414			};
415
416			/**
417			 * @private You should not rely on this method being here.
418			 * It's a private method and it may change in the future.
419			 *
420			 * @description Updates the validated object and collects errors from it.
421			 */
422			result._updateState = function(newValue) {
423				if (!utils.isObject(newValue)) {
424					throw new Error('An object is required.');
425				}
426				obj = newValue;
427				if (options.observable) {
428					context.graphMonitor.valueHasMutated();
429				}
430				else {
431					runTraversal(newValue, context);
432					return collectErrors(context.validatables);
433				}
434			};
435			return result;
436		},
437
438		formatMessage: function (message, params, observable) {
439			if (utils.isObject(params) && params.typeAttr) {
440				params = params.value;
441			}
442			if (typeof message === 'function') {
443				return message(params, observable);
444			}
445			var replacements = unwrap(params);
446            if (replacements == null) {
447                replacements = [];
448            }
449			if (!utils.isArray(replacements)) {
450				replacements = [replacements];
451			}
452			return message.replace(/{(\d+)}/gi, function(match, index) {
453				if (typeof replacements[index] !== 'undefined') {
454					return replacements[index];
455				}
456				return match;
457			});
458		},
459
460		// addRule:
461		// This takes in a ko.observable and a Rule Context - which is just a rule name and params to supply to the validator
462		// ie: kv.addRule(myObservable, {
463		//		  rule: 'required',
464		//		  params: true
465		//	  });
466		//
467		addRule: function (observable, rule) {
468			observable.extend({ validatable: true });
469
470			var hasRule = !!koUtils.arrayFirst(observable.rules(), function(item) {
471				return item.rule && item.rule === rule.rule;
472			});
473
474			if (!hasRule) {
475				//push a Rule Context to the observables local array of Rule Contexts
476				observable.rules.push(rule);
477			}
478			return observable;
479		},
480
481		// addAnonymousRule:
482		// Anonymous Rules essentially have all the properties of a Rule, but are only specific for a certain property
483		// and developers typically are wanting to add them on the fly or not register a rule with the 'kv.rules' object
484		//
485		// Example:
486		// var test = ko.observable('something').extend{(
487		//	  validation: {
488		//		  validator: function(val, someOtherVal){
489		//			  return true;
490		//		  },
491		//		  message: "Something must be really wrong!',
492		//		  params: true
493		//	  }
494		//  )};
495		addAnonymousRule: function (observable, ruleObj) {
496			if (ruleObj['message'] === undefined) {
497				ruleObj['message'] = 'Error';
498			}
499
500			//make sure onlyIf is honoured
501			if (ruleObj.onlyIf) {
502				ruleObj.condition = ruleObj.onlyIf;
503			}
504
505			//add the anonymous rule to the observable
506			kv.addRule(observable, ruleObj);
507		},
508
509		addExtender: function (ruleName) {
510			ko.extenders[ruleName] = function (observable, params) {
511				//params can come in a few flavors
512				// 1. Just the params to be passed to the validator
513				// 2. An object containing the Message to be used and the Params to pass to the validator
514				// 3. A condition when the validation rule to be applied
515				//
516				// Example:
517				// var test = ko.observable(3).extend({
518				//	  max: {
519				//		  message: 'This special field has a Max of {0}',
520				//		  params: 2,
521				//		  onlyIf: function() {
522				//					  return specialField.IsVisible();
523				//				  }
524				//	  }
525				//  )};
526				//
527				if (params && (params.message || params.onlyIf)) { //if it has a message or condition object, then its an object literal to use
528					return kv.addRule(observable, {
529						rule: ruleName,
530						message: params.message,
531						params: utils.isEmptyVal(params.params) ? true : params.params,
532						condition: params.onlyIf
533					});
534				} else {
535					return kv.addRule(observable, {
536						rule: ruleName,
537						params: params
538					});
539				}
540			};
541		},
542
543		// loops through all kv.rules and adds them as extenders to
544		// ko.extenders
545		registerExtenders: function () { // root extenders optional, use 'validation' extender if would cause conflicts
546			if (configuration.registerExtenders) {
547				for (var ruleName in kv.rules) {
548					if (kv.rules.hasOwnProperty(ruleName)) {
549						if (!ko.extenders[ruleName]) {
550							kv.addExtender(ruleName);
551						}
552					}
553				}
554			}
555		},
556
557		//creates a span next to the @element with the specified error class
558		insertValidationMessage: function (element) {
559			var span = document.createElement('SPAN');
560			span.setAttribute("role", "alert");
561			span.className = utils.getConfigOptions(element).errorMessageClass;
562			utils.insertAfter(element, span);
563			return span;
564		},
565
566		// if html-5 validation attributes have been specified, this parses
567		// the attributes on @element
568		parseInputValidationAttributes: function (element, valueAccessor) {
569			forEach(kv.configuration.html5Attributes, function (attr) {
570				if (utils.hasAttribute(element, attr)) {
571
572					var params = element.getAttribute(attr) || true;
573
574					if (attr === 'min' || attr === 'max')
575					{
576						// If we're validating based on the min and max attributes, we'll
577						// need to know what the 'type' attribute is set to
578						var typeAttr = element.getAttribute('type');
579						if (typeof typeAttr === "undefined" || !typeAttr)
580						{
581							// From http://www.w3.org/TR/html-markup/input:
582							//   An input element with no type attribute specified represents the
583							//   same thing as an input element with its type attribute set to "text".
584							typeAttr = "text";
585						}
586						params = {typeAttr: typeAttr, value: params};
587					}
588
589					kv.addRule(valueAccessor(), {
590						rule: attr,
591						params: params
592					});
593				}
594			});
595
596			var currentType = element.getAttribute('type');
597			forEach(kv.configuration.html5InputTypes, function (type) {
598				if (type === currentType) {
599					kv.addRule(valueAccessor(), {
600						rule: (type === 'date') ? 'dateISO' : type,
601						params: true
602					});
603				}
604			});
605		},
606
607		// writes html5 validation attributes on the element passed in
608		writeInputValidationAttributes: function (element, valueAccessor) {
609			var observable = valueAccessor();
610
611			if (!observable || !observable.rules) {
612				return;
613			}
614
615			var contexts = observable.rules(); // observable array
616
617			// loop through the attributes and add the information needed
618			forEach(kv.configuration.html5Attributes, function (attr) {
619				var ctx = koUtils.arrayFirst(contexts, function (ctx) {
620					return ctx.rule && ctx.rule.toLowerCase() === attr.toLowerCase();
621				});
622
623				if (!ctx) {
624					return;
625				}
626
627				// we have a rule matching a validation attribute at this point
628				// so lets add it to the element along with the params
629				ko.computed({
630					read: function() {
631						var params = ko.unwrap(ctx.params);
632
633						// we have to do some special things for the pattern validation
634						if (ctx.rule === "pattern" && params instanceof RegExp) {
635							// we need the pure string representation of the RegExpr without the //gi stuff
636							params = params.source;
637						}
638
639						element.setAttribute(attr, params);
640					},
641					disposeWhenNodeIsRemoved: element
642				});
643			});
644
645			contexts = null;
646		},
647
648		//take an existing binding handler and make it cause automatic validations
649		makeBindingHandlerValidatable: function (handlerName) {
650			var init = ko.bindingHandlers[handlerName].init;
651
652			ko.bindingHandlers[handlerName].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
653
654				init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
655
656				return ko.bindingHandlers['validationCore'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
657			};
658		},
659
660		// visit an objects properties and apply validation rules from a definition
661		setRules: function (target, definition) {
662			var setRules = function (target, definition) {
663				if (!target || !definition) { return; }
664
665				for (var prop in definition) {
666					if (!definition.hasOwnProperty(prop)) { continue; }
667					var ruleDefinitions = definition[prop];
668
669					//check the target property exists and has a value
670					if (!target[prop]) { continue; }
671					var targetValue = target[prop],
672						unwrappedTargetValue = unwrap(targetValue),
673						rules = {},
674						nonRules = {};
675
676					for (var rule in ruleDefinitions) {
677						if (!ruleDefinitions.hasOwnProperty(rule)) { continue; }
678						if (kv.rules[rule]) {
679							rules[rule] = ruleDefinitions[rule];
680						} else {
681							nonRules[rule] = ruleDefinitions[rule];
682						}
683					}
684
685					//apply rules
686					if (ko.isObservable(targetValue)) {
687						targetValue.extend(rules);
688					}
689
690					//then apply child rules
691					//if it's an array, apply rules to all children
692					if (unwrappedTargetValue && utils.isArray(unwrappedTargetValue)) {
693						for (var i = 0; i < unwrappedTargetValue.length; i++) {
694							setRules(unwrappedTargetValue[i], nonRules);
695						}
696						//otherwise, just apply to this property
697					} else {
698						setRules(unwrappedTargetValue, nonRules);
699					}
700				}
701			};
702			setRules(target, definition);
703		}
704	};
705
706}());
707
708// expose api publicly
709extend(ko.validation, api);
710;//Validation Rules:
711// You can view and override messages or rules via:
712// kv.rules[ruleName]
713//
714// To implement a custom Rule, simply use this template:
715// kv.rules['<custom rule name>'] = {
716//      validator: function (val, param) {
717//          <custom logic>
718//          return <true or false>;
719//      },
720//      message: '<custom validation message>' //optionally you can also use a '{0}' to denote a placeholder that will be replaced with your 'param'
721// };
722//
723// Example:
724// kv.rules['mustEqual'] = {
725//      validator: function( val, mustEqualVal ){
726//          return val === mustEqualVal;
727//      },
728//      message: 'This field must equal {0}'
729// };
730//
731kv.rules = {};
732kv.rules['required'] = {
733	validator: function (val, required) {
734		var testVal;
735
736		if (val === undefined || val === null) {
737			return !required;
738		}
739
740		testVal = val;
741		if (typeof (val) === 'string') {
742			if (String.prototype.trim) {
743				testVal = val.trim();
744			}
745			else {
746				testVal = val.replace(/^\s+|\s+$/g, '');
747			}
748		}
749
750		if (!required) {// if they passed: { required: false }, then don't require this
751			return true;
752		}
753
754		return ((testVal + '').length > 0);
755	},
756	message: 'This field is required.'
757};
758
759function minMaxValidatorFactory(validatorName) {
760    var isMaxValidation = validatorName === "max";
761
762    return function (val, options) {
763        if (kv.utils.isEmptyVal(val)) {
764            return true;
765        }
766
767        var comparisonValue, type;
768        if (options.typeAttr === undefined) {
769            // This validator is being called from javascript rather than
770            // being bound from markup
771            type = "text";
772            comparisonValue = options;
773        } else {
774            type = options.typeAttr;
775            comparisonValue = options.value;
776        }
777
778        // From http://www.w3.org/TR/2012/WD-html5-20121025/common-input-element-attributes.html#attr-input-min,
779        // if the value is parseable to a number, then the minimum should be numeric
780        if (!isNaN(comparisonValue) && !(comparisonValue instanceof Date)) {
781            type = "number";
782        }
783
784        var regex, valMatches, comparisonValueMatches;
785        switch (type.toLowerCase()) {
786            case "week":
787                regex = /^(\d{4})-W(\d{2})$/;
788                valMatches = val.match(regex);
789                if (valMatches === null) {
790                    throw new Error("Invalid value for " + validatorName + " attribute for week input.  Should look like " +
791                        "'2000-W33' http://www.w3.org/TR/html-markup/input.week.html#input.week.attrs.min");
792                }
793                comparisonValueMatches = comparisonValue.match(regex);
794                // If no regex matches were found, validation fails
795                if (!comparisonValueMatches) {
796                    return false;
797                }
798
799                if (isMaxValidation) {
800                    return (valMatches[1] < comparisonValueMatches[1]) || // older year
801                        // same year, older week
802                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2]));
803                } else {
804                    return (valMatches[1] > comparisonValueMatches[1]) || // newer year
805                        // same year, newer week
806                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
807                }
808                break;
809
810            case "month":
811                regex = /^(\d{4})-(\d{2})$/;
812                valMatches = val.match(regex);
813                if (valMatches === null) {
814                    throw new Error("Invalid value for " + validatorName + " attribute for month input.  Should look like " +
815                        "'2000-03' http://www.w3.org/TR/html-markup/input.month.html#input.month.attrs.min");
816                }
817                comparisonValueMatches = comparisonValue.match(regex);
818                // If no regex matches were found, validation fails
819                if (!comparisonValueMatches) {
820                    return false;
821                }
822
823                if (isMaxValidation) {
824                    return ((valMatches[1] < comparisonValueMatches[1]) || // older year
825                        // same year, older month
826                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2])));
827                } else {
828                    return (valMatches[1] > comparisonValueMatches[1]) || // newer year
829                        // same year, newer month
830                        ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
831                }
832                break;
833
834            case "number":
835            case "range":
836                if (isMaxValidation) {
837                    return (!isNaN(val) && parseFloat(val) <= parseFloat(comparisonValue));
838                } else {
839                    return (!isNaN(val) && parseFloat(val) >= parseFloat(comparisonValue));
840                }
841                break;
842
843            default:
844                if (isMaxValidation) {
845                    return val <= comparisonValue;
846                } else {
847                    return val >= comparisonValue;
848                }
849        }
850    };
851}
852
853kv.rules['min'] = {
854	validator: minMaxValidatorFactory("min"),
855	message: 'Please enter a value greater than or equal to {0}.'
856};
857
858kv.rules['max'] = {
859	validator: minMaxValidatorFactory("max"),
860	message: 'Please enter a value less than or equal to {0}.'
861};
862
863kv.rules['minLength'] = {
864	validator: function (val, minLength) {
865		if(kv.utils.isEmptyVal(val)) { return true; }
866		var normalizedVal = kv.utils.isNumber(val) ? ('' + val) : val;
867		return normalizedVal.length >= minLength;
868	},
869	message: 'Please enter at least {0} characters.'
870};
871
872kv.rules['maxLength'] = {
873	validator: function (val, maxLength) {
874		if(kv.utils.isEmptyVal(val)) { return true; }
875		var normalizedVal = kv.utils.isNumber(val) ? ('' + val) : val;
876		return normalizedVal.length <= maxLength;
877	},
878	message: 'Please enter no more than {0} characters.'
879};
880
881kv.rules['pattern'] = {
882	validator: function (val, regex) {
883		return kv.utils.isEmptyVal(val) || val.toString().match(regex) !== null;
884	},
885	message: 'Please check this value.'
886};
887
888kv.rules['step'] = {
889	validator: function (val, step) {
890
891		// in order to handle steps of .1 & .01 etc.. Modulus won't work
892		// if the value is a decimal, so we have to correct for that
893		if (kv.utils.isEmptyVal(val) || step === 'any') { return true; }
894		var dif = (val * 100) % (step * 100);
895		return Math.abs(dif) < 0.00001 || Math.abs(1 - dif) < 0.00001;
896	},
897	message: 'The value must increment by {0}.'
898};
899
900kv.rules['email'] = {
901	validator: function (val, validate) {
902		if (!validate) { return true; }
903
904		//I think an empty email address is also a valid entry
905		//if one want's to enforce entry it should be done with 'required: true'
906		return kv.utils.isEmptyVal(val) || (
907			// jquery validate regex - thanks Scott Gonzalez
908			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)
909		);
910	},
911	message: 'Please enter a proper email address.'
912};
913
914kv.rules['date'] = {
915	validator: function (value, validate) {
916		if (!validate) { return true; }
917		return kv.utils.isEmptyVal(value) || (validate && !/Invalid|NaN/.test(new Date(value)));
918	},
919	message: 'Please enter a proper date.'
920};
921
922kv.rules['dateISO'] = {
923	validator: function (value, validate) {
924		if (!validate) { return true; }
925		return kv.utils.isEmptyVal(value) || (validate && /^\d{4}[-/](?:0?[1-9]|1[012])[-/](?:0?[1-9]|[12][0-9]|3[01])$/.test(value));
926	},
927	message: 'Please enter a proper date.'
928};
929
930kv.rules['number'] = {
931	validator: function (value, validate) {
932		if (!validate) { return true; }
933		return kv.utils.isEmptyVal(value) || (validate && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value));
934	},
935	message: 'Please enter a number.'
936};
937
938kv.rules['digit'] = {
939	validator: function (value, validate) {
940		if (!validate) { return true; }
941		return kv.utils.isEmptyVal(value) || (validate && /^\d+$/.test(value));
942	},
943	message: 'Please enter a digit.'
944};
945
946kv.rules['phoneUS'] = {
947	validator: function (phoneNumber, validate) {
948		if (!validate) { return true; }
949		if (kv.utils.isEmptyVal(phoneNumber)) { return true; } // makes it optional, use 'required' rule if it should be required
950		if (typeof (phoneNumber) !== 'string') { return false; }
951		phoneNumber = phoneNumber.replace(/\s+/g, "");
952		return validate && phoneNumber.length > 9 && phoneNumber.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
953	},
954	message: 'Please specify a valid phone number.'
955};
956
957kv.rules['equal'] = {
958	validator: function (val, params) {
959		var otherValue = params;
960		return val === kv.utils.getValue(otherValue);
961	},
962	message: 'Values must equal.'
963};
964
965kv.rules['notEqual'] = {
966	validator: function (val, params) {
967		var otherValue = params;
968		return val !== kv.utils.getValue(otherValue);
969	},
970	message: 'Please choose another value.'
971};
972
973//unique in collection
974// options are:
975//    collection: array or function returning (observable) array
976//              in which the value has to be unique
977//    valueAccessor: function that returns value from an object stored in collection
978//              if it is null the value is compared directly
979//    external: set to true when object you are validating is automatically updating collection
980kv.rules['unique'] = {
981	validator: function (val, options) {
982		var c = kv.utils.getValue(options.collection),
983			external = kv.utils.getValue(options.externalValue),
984			counter = 0;
985
986		if (!val || !c) { return true; }
987
988		koUtils.arrayFilter(c, function (item) {
989			if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
990		});
991		// if value is external even 1 same value in collection means the value is not unique
992		return counter < (!!external ? 1 : 2);
993	},
994	message: 'Please make sure the value is unique.'
995};
996
997
998//now register all of these!
999(function () {
1000	kv.registerExtenders();
1001}());
1002;// The core binding handler
1003// this allows us to setup any value binding that internally always
1004// performs the same functionality
1005ko.bindingHandlers['validationCore'] = (function () {
1006
1007	return {
1008		init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
1009			var config = kv.utils.getConfigOptions(element);
1010			var observable = valueAccessor();
1011
1012			// parse html5 input validation attributes, optional feature
1013			if (config.parseInputAttributes) {
1014				kv.utils.async(function () { kv.parseInputValidationAttributes(element, valueAccessor); });
1015			}
1016
1017			// if requested insert message element and apply bindings
1018			if (config.insertMessages && kv.utils.isValidatable(observable)) {
1019
1020				// insert the <span></span>
1021				var validationMessageElement = kv.insertValidationMessage(element);
1022
1023				// if we're told to use a template, make sure that gets rendered
1024				if (config.messageTemplate) {
1025					ko.renderTemplate(config.messageTemplate, { field: observable }, null, validationMessageElement, 'replaceNode');
1026				} else {
1027					ko.applyBindingsToNode(validationMessageElement, { validationMessage: observable });
1028				}
1029			}
1030
1031			// write the html5 attributes if indicated by the config
1032			if (config.writeInputAttributes && kv.utils.isValidatable(observable)) {
1033
1034				kv.writeInputValidationAttributes(element, valueAccessor);
1035			}
1036
1037			// if requested, add binding to decorate element
1038			if (config.decorateInputElement && kv.utils.isValidatable(observable)) {
1039				ko.applyBindingsToNode(element, { validationElement: observable });
1040			}
1041		}
1042	};
1043
1044}());
1045
1046// override for KO's default 'value', 'checked', 'textInput' and selectedOptions bindings
1047kv.makeBindingHandlerValidatable("value");
1048kv.makeBindingHandlerValidatable("checked");
1049if (ko.bindingHandlers.textInput) {
1050	kv.makeBindingHandlerValidatable("textInput");
1051}
1052kv.makeBindingHandlerValidatable("selectedOptions");
1053
1054
1055ko.bindingHandlers['validationMessage'] = { // individual error message, if modified or post binding
1056	update: function (element, valueAccessor) {
1057		var obsv = valueAccessor(),
1058			config = kv.utils.getConfigOptions(element),
1059			val = unwrap(obsv),
1060			msg = null,
1061			isModified = false,
1062			isValid = false;
1063
1064		if (obsv === null || typeof obsv === 'undefined') {
1065			throw new Error('Cannot bind validationMessage to undefined value. data-bind expression: ' +
1066				element.getAttribute('data-bind'));
1067		}
1068
1069		isModified = obsv.isModified && obsv.isModified();
1070		isValid = obsv.isValid && obsv.isValid();
1071
1072		var error = null;
1073		if (!config.messagesOnModified || isModified) {
1074			error = isValid ? null : obsv.error;
1075		}
1076
1077		var isVisible = !config.messagesOnModified || isModified ? !isValid : false;
1078		var isCurrentlyVisible = element.style.display !== "none";
1079
1080		if (config.allowHtmlMessages) {
1081			koUtils.setHtml(element, error);
1082		} else {
1083			ko.bindingHandlers.text.update(element, function () { return error; });
1084		}
1085
1086		if (isCurrentlyVisible && !isVisible) {
1087			element.style.display = 'none';
1088		} else if (!isCurrentlyVisible && isVisible) {
1089			element.style.display = '';
1090		}
1091	}
1092};
1093
1094ko.bindingHandlers['validationElement'] = {
1095	update: function (element, valueAccessor, allBindingsAccessor) {
1096		var obsv = valueAccessor(),
1097			config = kv.utils.getConfigOptions(element),
1098			val = unwrap(obsv),
1099			msg = null,
1100			isModified = false,
1101			isValid = false;
1102
1103		if (obsv === null || typeof obsv === 'undefined') {
1104			throw new Error('Cannot bind validationElement to undefined value. data-bind expression: ' +
1105				element.getAttribute('data-bind'));
1106		}
1107
1108		isModified = obsv.isModified && obsv.isModified();
1109		isValid = obsv.isValid && obsv.isValid();
1110
1111		// create an evaluator function that will return something like:
1112		// css: { validationElement: true }
1113		var cssSettingsAccessor = function () {
1114			var css = {};
1115
1116			var shouldShow = ((!config.decorateElementOnModified || isModified) ? !isValid : false);
1117
1118			// css: { validationElement: false }
1119			css[config.errorElementClass] = shouldShow;
1120
1121			return css;
1122		};
1123
1124		//add or remove class on the element;
1125		ko.bindingHandlers.css.update(element, cssSettingsAccessor, allBindingsAccessor);
1126		if (!config.errorsAsTitle) { return; }
1127
1128		ko.bindingHandlers.attr.update(element, function () {
1129			var
1130				hasModification = !config.errorsAsTitleOnModified || isModified,
1131				title = kv.utils.getOriginalElementTitle(element);
1132
1133			if (hasModification && !isValid) {
1134				return { title: obsv.error, 'data-orig-title': title };
1135			} else if (!hasModification || isValid) {
1136				return { title: title, 'data-orig-title': null };
1137			}
1138		});
1139	}
1140};
1141
1142// ValidationOptions:
1143// This binding handler allows you to override the initial config by setting any of the options for a specific element or context of elements
1144//
1145// Example:
1146// <div data-bind="validationOptions: { insertMessages: true, messageTemplate: 'customTemplate', errorMessageClass: 'mySpecialClass'}">
1147//      <input type="text" data-bind="value: someValue"/>
1148//      <input type="text" data-bind="value: someValue2"/>
1149// </div>
1150ko.bindingHandlers['validationOptions'] = (function () {
1151	return {
1152		init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
1153			var options = unwrap(valueAccessor());
1154			if (options) {
1155				var newConfig = extend({}, kv.configuration);
1156				extend(newConfig, options);
1157
1158				//store the validation options on the node so we can retrieve it later
1159				kv.utils.setDomData(element, newConfig);
1160			}
1161		}
1162	};
1163}());
1164;// Validation Extender:
1165// This is for creating custom validation logic on the fly
1166// Example:
1167// var test = ko.observable('something').extend{(
1168//      validation: {
1169//          validator: function(val, someOtherVal){
1170//              return true;
1171//          },
1172//          message: "Something must be really wrong!',
1173//          params: true
1174//      }
1175//  )};
1176ko.extenders['validation'] = function (observable, rules) { // allow single rule or array
1177	forEach(kv.utils.isArray(rules) ? rules : [rules], function (rule) {
1178		// the 'rule' being passed in here has no name to identify a core Rule,
1179		// so we add it as an anonymous rule
1180		// If the developer is wanting to use a core Rule, but use a different message see the 'addExtender' logic for examples
1181		kv.addAnonymousRule(observable, rule);
1182	});
1183	return observable;
1184};
1185
1186//This is the extender that makes a Knockout Observable also 'Validatable'
1187//examples include:
1188// 1. var test = ko.observable('something').extend({validatable: true});
1189// this will ensure that the Observable object is setup properly to respond to rules
1190//
1191// 2. test.extend({validatable: false});
1192// this will remove the validation properties from the Observable object should you need to do that.
1193ko.extenders['validatable'] = function (observable, options) {
1194	if (!kv.utils.isObject(options)) {
1195		options = { enable: options };
1196	}
1197
1198	if (!('enable' in options)) {
1199		options.enable = true;
1200	}
1201
1202	if (options.enable && !kv.utils.isValidatable(observable)) {
1203		var config = kv.configuration.validate || {};
1204		var validationOptions = {
1205			throttleEvaluation : options.throttle || config.throttle
1206		};
1207
1208		observable.error = ko.observable(null); // holds the error message, we only need one since we stop processing validators when one is invalid
1209
1210		// observable.rules:
1211		// ObservableArray of Rule Contexts, where a Rule Context is simply the name of a rule and the params to supply to it
1212		//
1213		// Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>' }
1214		observable.rules = ko.observableArray(); //holds the rule Contexts to use as part of validation
1215
1216		//in case async validation is occurring
1217		observable.isValidating = ko.observable(false);
1218
1219		//the true holder of whether the observable is valid or not
1220		observable.__valid__ = ko.observable(true);
1221
1222		observable.isModified = ko.observable(false);
1223
1224		// a semi-protected observable
1225		observable.isValid = ko.computed(observable.__valid__);
1226
1227		//manually set error state
1228		observable.setError = function (error) {
1229			var previousError = observable.error.peek();
1230			var previousIsValid = observable.__valid__.peek();
1231
1232			observable.error(error);
1233			observable.__valid__(false);
1234
1235			if (previousError !== error && !previousIsValid) {
1236				// if the observable was not valid before then isValid will not mutate,
1237				// hence causing any grouping to not display the latest error.
1238				observable.isValid.notifySubscribers();
1239			}
1240		};
1241
1242		//manually clear error state
1243		observable.clearError = function () {
1244			observable.error(null);
1245			observable.__valid__(true);
1246			return observable;
1247		};
1248
1249		//subscribe to changes in the observable
1250		var h_change = observable.subscribe(function () {
1251			observable.isModified(true);
1252		});
1253
1254		// we use a computed here to ensure that anytime a dependency changes, the
1255		// validation logic evaluates
1256		var h_obsValidationTrigger = ko.computed(extend({
1257			read: function () {
1258				var obs = observable(),
1259					ruleContexts = observable.rules();
1260
1261				kv.validateObservable(observable);
1262
1263				return true;
1264			}
1265		}, validationOptions));
1266
1267		extend(h_obsValidationTrigger, validationOptions);
1268
1269		observable._disposeValidation = function () {
1270			//first dispose of the subscriptions
1271			observable.isValid.dispose();
1272			observable.rules.removeAll();
1273			h_change.dispose();
1274			h_obsValidationTrigger.dispose();
1275
1276			delete observable['rules'];
1277			delete observable['error'];
1278			delete observable['isValid'];
1279			delete observable['isValidating'];
1280			delete observable['__valid__'];
1281			delete observable['isModified'];
1282            delete observable['setError'];
1283            delete observable['clearError'];
1284            delete observable['_disposeValidation'];
1285		};
1286	} else if (options.enable === false && observable._disposeValidation) {
1287		observable._disposeValidation();
1288	}
1289	return observable;
1290};
1291
1292function validateSync(observable, rule, ctx) {
1293	//Execute the validator and see if its valid
1294	if (!rule.validator(observable(), (ctx.params === undefined ? true : unwrap(ctx.params)))) { // default param is true, eg. required = true
1295
1296		//not valid, so format the error message and stick it in the 'error' variable
1297		observable.setError(kv.formatMessage(
1298					ctx.message || rule.message,
1299					unwrap(ctx.params),
1300					observable));
1301		return false;
1302	} else {
1303		return true;
1304	}
1305}
1306
1307function validateAsync(observable, rule, ctx) {
1308	observable.isValidating(true);
1309
1310	var callBack = function (valObj) {
1311		var isValid = false,
1312			msg = '';
1313
1314		if (!observable.__valid__()) {
1315
1316			// since we're returning early, make sure we turn this off
1317			observable.isValidating(false);
1318
1319			return; //if its already NOT valid, don't add to that
1320		}
1321
1322		//we were handed back a complex object
1323		if (valObj['message']) {
1324			isValid = valObj.isValid;
1325			msg = valObj.message;
1326		} else {
1327			isValid = valObj;
1328		}
1329
1330		if (!isValid) {
1331			//not valid, so format the error message and stick it in the 'error' variable
1332			observable.error(kv.formatMessage(
1333				msg || ctx.message || rule.message,
1334				unwrap(ctx.params),
1335				observable));
1336			observable.__valid__(isValid);
1337		}
1338
1339		// tell it that we're done
1340		observable.isValidating(false);
1341	};
1342
1343	kv.utils.async(function() {
1344	    //fire the validator and hand it the callback
1345        rule.validator(observable(), ctx.params === undefined ? true : unwrap(ctx.params), callBack);
1346	});
1347}
1348
1349kv.validateObservable = function (observable) {
1350	var i = 0,
1351		rule, // the rule validator to execute
1352		ctx, // the current Rule Context for the loop
1353		ruleContexts = observable.rules(), //cache for iterator
1354		len = ruleContexts.length; //cache for iterator
1355
1356	for (; i < len; i++) {
1357
1358		//get the Rule Context info to give to the core Rule
1359		ctx = ruleContexts[i];
1360
1361		// checks an 'onlyIf' condition
1362		if (ctx.condition && !ctx.condition()) {
1363			continue;
1364		}
1365
1366		//get the core Rule to use for validation
1367		rule = ctx.rule ? kv.rules[ctx.rule] : ctx;
1368
1369		if (rule['async'] || ctx['async']) {
1370			//run async validation
1371			validateAsync(observable, rule, ctx);
1372
1373		} else {
1374			//run normal sync validation
1375			if (!validateSync(observable, rule, ctx)) {
1376				return false; //break out of the loop
1377			}
1378		}
1379	}
1380	//finally if we got this far, make the observable valid again!
1381	observable.clearError();
1382	return true;
1383};
1384;
1385var _locales = {};
1386var _currentLocale;
1387
1388kv.defineLocale = function(name, values) {
1389	if (name && values) {
1390		_locales[name.toLowerCase()] = values;
1391		return values;
1392	}
1393	return null;
1394};
1395
1396kv.locale = function(name) {
1397	if (name) {
1398		name = name.toLowerCase();
1399
1400		if (_locales.hasOwnProperty(name)) {
1401			kv.localize(_locales[name]);
1402			_currentLocale = name;
1403		}
1404		else {
1405			throw new Error('Localization ' + name + ' has not been loaded.');
1406		}
1407	}
1408	return _currentLocale;
1409};
1410
1411//quick function to override rule messages
1412kv.localize = function (msgTranslations) {
1413	var rules = kv.rules;
1414
1415	//loop the properties in the object and assign the msg to the rule
1416	for (var ruleName in msgTranslations) {
1417		if (rules.hasOwnProperty(ruleName)) {
1418			rules[ruleName].message = msgTranslations[ruleName];
1419		}
1420	}
1421};
1422
1423// Populate default locale (this will make en-US.js somewhat redundant)
1424(function() {
1425	var localeData = {};
1426	var rules = kv.rules;
1427
1428	for (var ruleName in rules) {
1429		if (rules.hasOwnProperty(ruleName)) {
1430			localeData[ruleName] = rules[ruleName].message;
1431		}
1432	}
1433	kv.defineLocale('en-us', localeData);
1434})();
1435
1436// No need to invoke locale because the messages are already defined along with the rules for en-US
1437_currentLocale = 'en-us';
1438;/**
1439 * Possible invocations:
1440 * 		applyBindingsWithValidation(viewModel)
1441 * 		applyBindingsWithValidation(viewModel, options)
1442 * 		applyBindingsWithValidation(viewModel, rootNode)
1443 *		applyBindingsWithValidation(viewModel, rootNode, options)
1444 */
1445ko.applyBindingsWithValidation = function (viewModel, rootNode, options) {
1446	var node = document.body,
1447		config;
1448
1449	if (rootNode && rootNode.nodeType) {
1450		node = rootNode;
1451		config = options;
1452	}
1453	else {
1454		config = rootNode;
1455	}
1456
1457	kv.init();
1458
1459	if (config) {
1460		config = extend(extend({}, kv.configuration), config);
1461		kv.utils.setDomData(node, config);
1462	}
1463
1464	ko.applyBindings(viewModel, node);
1465};
1466
1467//override the original applyBindings so that we can ensure all new rules and what not are correctly registered
1468var origApplyBindings = ko.applyBindings;
1469ko.applyBindings = function (viewModel, rootNode) {
1470
1471	kv.init();
1472
1473	origApplyBindings(viewModel, rootNode);
1474};
1475
1476ko.validatedObservable = function (initialValue, options) {
1477	if (!options && !kv.utils.isObject(initialValue)) {
1478		return ko.observable(initialValue).extend({ validatable: true });
1479	}
1480
1481	var obsv = ko.observable(initialValue);
1482	obsv.errors = kv.group(kv.utils.isObject(initialValue) ? initialValue : {}, options);
1483	obsv.isValid = ko.observable(obsv.errors().length === 0);
1484
1485	if (ko.isObservable(obsv.errors)) {
1486		obsv.errors.subscribe(function(errors) {
1487			obsv.isValid(errors.length === 0);
1488		});
1489	}
1490	else {
1491		ko.computed(obsv.errors).subscribe(function (errors) {
1492			obsv.isValid(errors.length === 0);
1493		});
1494	}
1495
1496	obsv.subscribe(function(newValue) {
1497		if (!kv.utils.isObject(newValue)) {
1498			/*
1499			 * The validation group works on objects.
1500			 * Since the new value is a primitive (scalar, null or undefined) we need
1501			 * to create an empty object to pass along.
1502			 */
1503			newValue = {};
1504		}
1505		// Force the group to refresh
1506		obsv.errors._updateState(newValue);
1507		obsv.isValid(obsv.errors().length === 0);
1508	});
1509
1510	return obsv;
1511};
1512;}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.