PageSourceSearch

https://fortwilliamgolfclub.com/shared/js/knockout.validation.js

js fortwilliamgolfclub.com collected 2026-10-06 18:06:34 UTC 48,340 bytes, 1,511 lines download raw bytes

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

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.