hjg
2023-11-17 3780c5e65b05bf23020810798babc6d20311fa79
提交 | 用户 | 时间
58d006 1 /*=============================================================================
A 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.1                                            
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
40 var 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
66 var configuration = extend({}, defaults);
67
68 configuration.html5Attributes = ['required', 'pattern', 'min', 'max', 'step'];
69 configuration.html5InputTypes = ['email', 'number', 'date'];
70
71 configuration.reset = function () {
72     extend(configuration, defaults);
73 };
74
75 kv.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 (!utils.isArray(replacements)) {
447                 replacements = [replacements];
448             }
449             return message.replace(/{(\d+)}/gi, function(match, index) {
450                 if (typeof replacements[index] !== 'undefined') {
451                     return replacements[index];
452                 }
453                 return match;
454             });
455         },
456
457         // addRule:
458         // This takes in a ko.observable and a Rule Context - which is just a rule name and params to supply to the validator
459         // ie: kv.addRule(myObservable, {
460         //          rule: 'required',
461         //          params: true
462         //      });
463         //
464         addRule: function (observable, rule) {
465             observable.extend({ validatable: true });
466
467             var hasRule = !!koUtils.arrayFirst(observable.rules(), function(item) {
468                 return item.rule && item.rule === rule.rule;
469             });
470
471             if (!hasRule) {
472                 //push a Rule Context to the observables local array of Rule Contexts
473                 observable.rules.push(rule);
474             }
475             return observable;
476         },
477
478         // addAnonymousRule:
479         // Anonymous Rules essentially have all the properties of a Rule, but are only specific for a certain property
480         // and developers typically are wanting to add them on the fly or not register a rule with the 'kv.rules' object
481         //
482         // Example:
483         // var test = ko.observable('something').extend{(
484         //      validation: {
485         //          validator: function(val, someOtherVal){
486         //              return true;
487         //          },
488         //          message: "Something must be really wrong!',
489         //          params: true
490         //      }
491         //  )};
492         addAnonymousRule: function (observable, ruleObj) {
493             if (ruleObj['message'] === undefined) {
494                 ruleObj['message'] = 'Error';
495             }
496
497             //make sure onlyIf is honoured
498             if (ruleObj.onlyIf) {
499                 ruleObj.condition = ruleObj.onlyIf;
500             }
501
502             //add the anonymous rule to the observable
503             kv.addRule(observable, ruleObj);
504         },
505
506         addExtender: function (ruleName) {
507             ko.extenders[ruleName] = function (observable, params) {
508                 //params can come in a few flavors
509                 // 1. Just the params to be passed to the validator
510                 // 2. An object containing the Message to be used and the Params to pass to the validator
511                 // 3. A condition when the validation rule to be applied
512                 //
513                 // Example:
514                 // var test = ko.observable(3).extend({
515                 //      max: {
516                 //          message: 'This special field has a Max of {0}',
517                 //          params: 2,
518                 //          onlyIf: function() {
519                 //                      return specialField.IsVisible();
520                 //                  }
521                 //      }
522                 //  )};
523                 //
524                 if (params && (params.message || params.onlyIf)) { //if it has a message or condition object, then its an object literal to use
525                     return kv.addRule(observable, {
526                         rule: ruleName,
527                         message: params.message,
528                         params: utils.isEmptyVal(params.params) ? true : params.params,
529                         condition: params.onlyIf
530                     });
531                 } else {
532                     return kv.addRule(observable, {
533                         rule: ruleName,
534                         params: params
535                     });
536                 }
537             };
538         },
539
540         // loops through all kv.rules and adds them as extenders to
541         // ko.extenders
542         registerExtenders: function () { // root extenders optional, use 'validation' extender if would cause conflicts
543             if (configuration.registerExtenders) {
544                 for (var ruleName in kv.rules) {
545                     if (kv.rules.hasOwnProperty(ruleName)) {
546                         if (!ko.extenders[ruleName]) {
547                             kv.addExtender(ruleName);
548                         }
549                     }
550                 }
551             }
552         },
553
554         //creates a span next to the @element with the specified error class
555         insertValidationMessage: function (element) {
556             var span = document.createElement('SPAN');
557             span.className = utils.getConfigOptions(element).errorMessageClass;
558             utils.insertAfter(element, span);
559             return span;
560         },
561
562         // if html-5 validation attributes have been specified, this parses
563         // the attributes on @element
564         parseInputValidationAttributes: function (element, valueAccessor) {
565             forEach(kv.configuration.html5Attributes, function (attr) {
566                 if (utils.hasAttribute(element, attr)) {
567
568                     var params = element.getAttribute(attr) || true;
569
570                     if (attr === 'min' || attr === 'max')
571                     {
572                         // If we're validating based on the min and max attributes, we'll
573                         // need to know what the 'type' attribute is set to
574                         var typeAttr = element.getAttribute('type');
575                         if (typeof typeAttr === "undefined" || !typeAttr)
576                         {
577                             // From http://www.w3.org/TR/html-markup/input:
578                             //   An input element with no type attribute specified represents the
579                             //   same thing as an input element with its type attribute set to "text".
580                             typeAttr = "text";
581                         }
582                         params = {typeAttr: typeAttr, value: params};
583                     }
584
585                     kv.addRule(valueAccessor(), {
586                         rule: attr,
587                         params: params
588                     });
589                 }
590             });
591
592             var currentType = element.getAttribute('type');
593             forEach(kv.configuration.html5InputTypes, function (type) {
594                 if (type === currentType) {
595                     kv.addRule(valueAccessor(), {
596                         rule: (type === 'date') ? 'dateISO' : type,
597                         params: true
598                     });
599                 }
600             });
601         },
602
603         // writes html5 validation attributes on the element passed in
604         writeInputValidationAttributes: function (element, valueAccessor) {
605             var observable = valueAccessor();
606
607             if (!observable || !observable.rules) {
608                 return;
609             }
610
611             var contexts = observable.rules(); // observable array
612
613             // loop through the attributes and add the information needed
614             forEach(kv.configuration.html5Attributes, function (attr) {
615                 var ctx = koUtils.arrayFirst(contexts, function (ctx) {
616                     return ctx.rule && ctx.rule.toLowerCase() === attr.toLowerCase();
617                 });
618
619                 if (!ctx) {
620                     return;
621                 }
622
623                 // we have a rule matching a validation attribute at this point
624                 // so lets add it to the element along with the params
625                 ko.computed({
626                     read: function() {
627                         var params = ko.unwrap(ctx.params);
628
629                         // we have to do some special things for the pattern validation
630                         if (ctx.rule === "pattern" && params instanceof RegExp) {
631                             // we need the pure string representation of the RegExpr without the //gi stuff
632                             params = params.source;
633                         }
634
635                         element.setAttribute(attr, params);
636                     },
637                     disposeWhenNodeIsRemoved: element
638                 });
639             });
640
641             contexts = null;
642         },
643
644         //take an existing binding handler and make it cause automatic validations
645         makeBindingHandlerValidatable: function (handlerName) {
646             var init = ko.bindingHandlers[handlerName].init;
647
648             ko.bindingHandlers[handlerName].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
649
650                 init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
651
652                 return ko.bindingHandlers['validationCore'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
653             };
654         },
655
656         // visit an objects properties and apply validation rules from a definition
657         setRules: function (target, definition) {
658             var setRules = function (target, definition) {
659                 if (!target || !definition) { return; }
660
661                 for (var prop in definition) {
662                     if (!definition.hasOwnProperty(prop)) { continue; }
663                     var ruleDefinitions = definition[prop];
664
665                     //check the target property exists and has a value
666                     if (!target[prop]) { continue; }
667                     var targetValue = target[prop],
668                         unwrappedTargetValue = unwrap(targetValue),
669                         rules = {},
670                         nonRules = {};
671
672                     for (var rule in ruleDefinitions) {
673                         if (!ruleDefinitions.hasOwnProperty(rule)) { continue; }
674                         if (kv.rules[rule]) {
675                             rules[rule] = ruleDefinitions[rule];
676                         } else {
677                             nonRules[rule] = ruleDefinitions[rule];
678                         }
679                     }
680
681                     //apply rules
682                     if (ko.isObservable(targetValue)) {
683                         targetValue.extend(rules);
684                     }
685
686                     //then apply child rules
687                     //if it's an array, apply rules to all children
688                     if (unwrappedTargetValue && utils.isArray(unwrappedTargetValue)) {
689                         for (var i = 0; i < unwrappedTargetValue.length; i++) {
690                             setRules(unwrappedTargetValue[i], nonRules);
691                         }
692                         //otherwise, just apply to this property
693                     } else {
694                         setRules(unwrappedTargetValue, nonRules);
695                     }
696                 }
697             };
698             setRules(target, definition);
699         }
700     };
701
702 }());
703
704 // expose api publicly
705 extend(ko.validation, api);
706 ;//Validation Rules:
707 // You can view and override messages or rules via:
708 // kv.rules[ruleName]
709 //
710 // To implement a custom Rule, simply use this template:
711 // kv.rules['<custom rule name>'] = {
712 //      validator: function (val, param) {
713 //          <custom logic>
714 //          return <true or false>;
715 //      },
716 //      message: '<custom validation message>' //optionally you can also use a '{0}' to denote a placeholder that will be replaced with your 'param'
717 // };
718 //
719 // Example:
720 // kv.rules['mustEqual'] = {
721 //      validator: function( val, mustEqualVal ){
722 //          return val === mustEqualVal;
723 //      },
724 //      message: 'This field must equal {0}'
725 // };
726 //
727 kv.rules = {};
728 kv.rules['required'] = {
729     validator: function (val, required) {
730         var testVal;
731
732         if (val === undefined || val === null) {
733             return !required;
734         }
735
736         testVal = val;
737         if (typeof (val) === 'string') {
738             if (String.prototype.trim) {
739                 testVal = val.trim();
740             }
741             else {
742                 testVal = val.replace(/^\s+|\s+$/g, '');
743             }
744         }
745
746         if (!required) {// if they passed: { required: false }, then don't require this
747             return true;
748         }
749
750         return ((testVal + '').length > 0);
751     },
752     message: 'This field is required.'
753 };
754
755 function minMaxValidatorFactory(validatorName) {
756     var isMaxValidation = validatorName === "max";
757
758     return function (val, options) {
759         if (kv.utils.isEmptyVal(val)) {
760             return true;
761         }
762
763         var comparisonValue, type;
764         if (options.typeAttr === undefined) {
765             // This validator is being called from javascript rather than
766             // being bound from markup
767             type = "text";
768             comparisonValue = options;
769         } else {
770             type = options.typeAttr;
771             comparisonValue = options.value;
772         }
773
774         // From http://www.w3.org/TR/2012/WD-html5-20121025/common-input-element-attributes.html#attr-input-min,
775         // if the value is parseable to a number, then the minimum should be numeric
776         if (!isNaN(comparisonValue) && !(comparisonValue instanceof Date)) {
777             type = "number";
778         }
779
780         var regex, valMatches, comparisonValueMatches;
781         switch (type.toLowerCase()) {
782             case "week":
783                 regex = /^(\d{4})-W(\d{2})$/;
784                 valMatches = val.match(regex);
785                 if (valMatches === null) {
786                     throw new Error("Invalid value for " + validatorName + " attribute for week input.  Should look like " +
787                         "'2000-W33' http://www.w3.org/TR/html-markup/input.week.html#input.week.attrs.min");
788                 }
789                 comparisonValueMatches = comparisonValue.match(regex);
790                 // If no regex matches were found, validation fails
791                 if (!comparisonValueMatches) {
792                     return false;
793                 }
794
795                 if (isMaxValidation) {
796                     return (valMatches[1] < comparisonValueMatches[1]) || // older year
797                         // same year, older week
798                         ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2]));
799                 } else {
800                     return (valMatches[1] > comparisonValueMatches[1]) || // newer year
801                         // same year, newer week
802                         ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
803                 }
804                 break;
805
806             case "month":
807                 regex = /^(\d{4})-(\d{2})$/;
808                 valMatches = val.match(regex);
809                 if (valMatches === null) {
810                     throw new Error("Invalid value for " + validatorName + " attribute for month input.  Should look like " +
811                         "'2000-03' http://www.w3.org/TR/html-markup/input.month.html#input.month.attrs.min");
812                 }
813                 comparisonValueMatches = comparisonValue.match(regex);
814                 // If no regex matches were found, validation fails
815                 if (!comparisonValueMatches) {
816                     return false;
817                 }
818
819                 if (isMaxValidation) {
820                     return ((valMatches[1] < comparisonValueMatches[1]) || // older year
821                         // same year, older month
822                         ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2])));
823                 } else {
824                     return (valMatches[1] > comparisonValueMatches[1]) || // newer year
825                         // same year, newer month
826                         ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
827                 }
828                 break;
829
830             case "number":
831             case "range":
832                 if (isMaxValidation) {
833                     return (!isNaN(val) && parseFloat(val) <= parseFloat(comparisonValue));
834                 } else {
835                     return (!isNaN(val) && parseFloat(val) >= parseFloat(comparisonValue));
836                 }
837                 break;
838
839             default:
840                 if (isMaxValidation) {
841                     return val <= comparisonValue;
842                 } else {
843                     return val >= comparisonValue;
844                 }
845         }
846     };
847 }
848
849 kv.rules['min'] = {
850     validator: minMaxValidatorFactory("min"),
851     message: 'Please enter a value greater than or equal to {0}.'
852 };
853
854 kv.rules['max'] = {
855     validator: minMaxValidatorFactory("max"),
856     message: 'Please enter a value less than or equal to {0}.'
857 };
858
859 kv.rules['minLength'] = {
860     validator: function (val, minLength) {
861         if(kv.utils.isEmptyVal(val)) { return true; }
862         var normalizedVal = kv.utils.isNumber(val) ? ('' + val) : val;
863         return normalizedVal.length >= minLength;
864     },
865     message: 'Please enter at least {0} characters.'
866 };
867
868 kv.rules['maxLength'] = {
869     validator: function (val, maxLength) {
870         if(kv.utils.isEmptyVal(val)) { return true; }
871         var normalizedVal = kv.utils.isNumber(val) ? ('' + val) : val;
872         return normalizedVal.length <= maxLength;
873     },
874     message: 'Please enter no more than {0} characters.'
875 };
876
877 kv.rules['pattern'] = {
878     validator: function (val, regex) {
879         return kv.utils.isEmptyVal(val) || val.toString().match(regex) !== null;
880     },
881     message: 'Please check this value.'
882 };
883
884 kv.rules['step'] = {
885     validator: function (val, step) {
886
887         // in order to handle steps of .1 & .01 etc.. Modulus won't work
888         // if the value is a decimal, so we have to correct for that
889         if (kv.utils.isEmptyVal(val) || step === 'any') { return true; }
890         var dif = (val * 100) % (step * 100);
891         return Math.abs(dif) < 0.00001 || Math.abs(1 - dif) < 0.00001;
892     },
893     message: 'The value must increment by {0}.'
894 };
895
896 kv.rules['email'] = {
897     validator: function (val, validate) {
898         if (!validate) { return true; }
899
900         //I think an empty email address is also a valid entry
901         //if one want's to enforce entry it should be done with 'required: true'
902         return kv.utils.isEmptyVal(val) || (
903             // jquery validate regex - thanks Scott Gonzalez
904             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)
905         );
906     },
907     message: 'Please enter a proper email address.'
908 };
909
910 kv.rules['date'] = {
911     validator: function (value, validate) {
912         if (!validate) { return true; }
913         return kv.utils.isEmptyVal(value) || (validate && !/Invalid|NaN/.test(new Date(value)));
914     },
915     message: 'Please enter a proper date.'
916 };
917
918 kv.rules['dateISO'] = {
919     validator: function (value, validate) {
920         if (!validate) { return true; }
921         return kv.utils.isEmptyVal(value) || (validate && /^\d{4}[-/](?:0?[1-9]|1[012])[-/](?:0?[1-9]|[12][0-9]|3[01])$/.test(value));
922     },
923     message: 'Please enter a proper date.'
924 };
925
926 kv.rules['number'] = {
927     validator: function (value, validate) {
928         if (!validate) { return true; }
929         return kv.utils.isEmptyVal(value) || (validate && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value));
930     },
931     message: 'Please enter a number.'
932 };
933
934 kv.rules['digit'] = {
935     validator: function (value, validate) {
936         if (!validate) { return true; }
937         return kv.utils.isEmptyVal(value) || (validate && /^\d+$/.test(value));
938     },
939     message: 'Please enter a digit.'
940 };
941
942 kv.rules['phoneUS'] = {
943     validator: function (phoneNumber, validate) {
944         if (!validate) { return true; }
945         if (kv.utils.isEmptyVal(phoneNumber)) { return true; } // makes it optional, use 'required' rule if it should be required
946         if (typeof (phoneNumber) !== 'string') { return false; }
947         phoneNumber = phoneNumber.replace(/\s+/g, "");
948         return validate && phoneNumber.length > 9 && phoneNumber.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
949     },
950     message: 'Please specify a valid phone number.'
951 };
952
953 kv.rules['equal'] = {
954     validator: function (val, params) {
955         var otherValue = params;
956         return val === kv.utils.getValue(otherValue);
957     },
958     message: 'Values must equal.'
959 };
960
961 kv.rules['notEqual'] = {
962     validator: function (val, params) {
963         var otherValue = params;
964         return val !== kv.utils.getValue(otherValue);
965     },
966     message: 'Please choose another value.'
967 };
968
969 //unique in collection
970 // options are:
971 //    collection: array or function returning (observable) array
972 //              in which the value has to be unique
973 //    valueAccessor: function that returns value from an object stored in collection
974 //              if it is null the value is compared directly
975 //    external: set to true when object you are validating is automatically updating collection
976 kv.rules['unique'] = {
977     validator: function (val, options) {
978         var c = kv.utils.getValue(options.collection),
979             external = kv.utils.getValue(options.externalValue),
980             counter = 0;
981
982         if (!val || !c) { return true; }
983
984         koUtils.arrayFilter(c, function (item) {
985             if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
986         });
987         // if value is external even 1 same value in collection means the value is not unique
988         return counter < (!!external ? 1 : 2);
989     },
990     message: 'Please make sure the value is unique.'
991 };
992
993
994 //now register all of these!
995 (function () {
996     kv.registerExtenders();
997 }());
998 ;// The core binding handler
999 // this allows us to setup any value binding that internally always
1000 // performs the same functionality
1001 ko.bindingHandlers['validationCore'] = (function () {
1002
1003     return {
1004         init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
1005             var config = kv.utils.getConfigOptions(element);
1006             var observable = valueAccessor();
1007
1008             // parse html5 input validation attributes, optional feature
1009             if (config.parseInputAttributes) {
1010                 kv.utils.async(function () { kv.parseInputValidationAttributes(element, valueAccessor); });
1011             }
1012
1013             // if requested insert message element and apply bindings
1014             if (config.insertMessages && kv.utils.isValidatable(observable)) {
1015
1016                 // insert the <span></span>
1017                 var validationMessageElement = kv.insertValidationMessage(element);
1018
1019                 // if we're told to use a template, make sure that gets rendered
1020                 if (config.messageTemplate) {
1021                     ko.renderTemplate(config.messageTemplate, { field: observable }, null, validationMessageElement, 'replaceNode');
1022                 } else {
1023                     ko.applyBindingsToNode(validationMessageElement, { validationMessage: observable });
1024                 }
1025             }
1026
1027             // write the html5 attributes if indicated by the config
1028             if (config.writeInputAttributes && kv.utils.isValidatable(observable)) {
1029
1030                 kv.writeInputValidationAttributes(element, valueAccessor);
1031             }
1032
1033             // if requested, add binding to decorate element
1034             if (config.decorateInputElement && kv.utils.isValidatable(observable)) {
1035                 ko.applyBindingsToNode(element, { validationElement: observable });
1036             }
1037         }
1038     };
1039
1040 }());
1041
1042 // override for KO's default 'value', 'checked', 'textInput' and selectedOptions bindings
1043 kv.makeBindingHandlerValidatable("value");
1044 kv.makeBindingHandlerValidatable("checked");
1045 if (ko.bindingHandlers.textInput) {
1046     kv.makeBindingHandlerValidatable("textInput");
1047 }
1048 kv.makeBindingHandlerValidatable("selectedOptions");
1049
1050
1051 ko.bindingHandlers['validationMessage'] = { // individual error message, if modified or post binding
1052     update: function (element, valueAccessor) {
1053         var obsv = valueAccessor(),
1054             config = kv.utils.getConfigOptions(element),
1055             val = unwrap(obsv),
1056             msg = null,
1057             isModified = false,
1058             isValid = false;
1059
1060         if (obsv === null || typeof obsv === 'undefined') {
1061             throw new Error('Cannot bind validationMessage to undefined value. data-bind expression: ' +
1062                 element.getAttribute('data-bind'));
1063         }
1064
1065         if (!obsv.isValid || !obsv.isModified) {
1066             throw new Error("Observable is not validatable");
1067         }
1068
1069         isModified = obsv.isModified();
1070         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
1094 ko.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         if (!obsv.isValid || !obsv.isModified) {
1109             throw new Error("Observable is not validatable");
1110         }
1111
1112         isModified = obsv.isModified();
1113         isValid = obsv.isValid();
1114
1115         // create an evaluator function that will return something like:
1116         // css: { validationElement: true }
1117         var cssSettingsAccessor = function () {
1118             var css = {};
1119
1120             var shouldShow = ((!config.decorateElementOnModified || isModified) ? !isValid : false);
1121
1122             // css: { validationElement: false }
1123             css[config.errorElementClass] = shouldShow;
1124
1125             return css;
1126         };
1127
1128         //add or remove class on the element;
1129         ko.bindingHandlers.css.update(element, cssSettingsAccessor, allBindingsAccessor);
1130         if (!config.errorsAsTitle) { return; }
1131
1132         ko.bindingHandlers.attr.update(element, function () {
1133             var
1134                 hasModification = !config.errorsAsTitleOnModified || isModified,
1135                 title = kv.utils.getOriginalElementTitle(element);
1136
1137             if (hasModification && !isValid) {
1138                 return { title: obsv.error, 'data-orig-title': title };
1139             } else if (!hasModification || isValid) {
1140                 return { title: title, 'data-orig-title': null };
1141             }
1142         });
1143     }
1144 };
1145
1146 // ValidationOptions:
1147 // This binding handler allows you to override the initial config by setting any of the options for a specific element or context of elements
1148 //
1149 // Example:
1150 // <div data-bind="validationOptions: { insertMessages: true, messageTemplate: 'customTemplate', errorMessageClass: 'mySpecialClass'}">
1151 //      <input type="text" data-bind="value: someValue"/>
1152 //      <input type="text" data-bind="value: someValue2"/>
1153 // </div>
1154 ko.bindingHandlers['validationOptions'] = (function () {
1155     return {
1156         init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
1157             var options = unwrap(valueAccessor());
1158             if (options) {
1159                 var newConfig = extend({}, kv.configuration);
1160                 extend(newConfig, options);
1161
1162                 //store the validation options on the node so we can retrieve it later
1163                 kv.utils.setDomData(element, newConfig);
1164             }
1165         }
1166     };
1167 }());
1168 ;// Validation Extender:
1169 // This is for creating custom validation logic on the fly
1170 // Example:
1171 // var test = ko.observable('something').extend{(
1172 //      validation: {
1173 //          validator: function(val, someOtherVal){
1174 //              return true;
1175 //          },
1176 //          message: "Something must be really wrong!',
1177 //          params: true
1178 //      }
1179 //  )};
1180 ko.extenders['validation'] = function (observable, rules) { // allow single rule or array
1181     forEach(kv.utils.isArray(rules) ? rules : [rules], function (rule) {
1182         // the 'rule' being passed in here has no name to identify a core Rule,
1183         // so we add it as an anonymous rule
1184         // If the developer is wanting to use a core Rule, but use a different message see the 'addExtender' logic for examples
1185         kv.addAnonymousRule(observable, rule);
1186     });
1187     return observable;
1188 };
1189
1190 //This is the extender that makes a Knockout Observable also 'Validatable'
1191 //examples include:
1192 // 1. var test = ko.observable('something').extend({validatable: true});
1193 // this will ensure that the Observable object is setup properly to respond to rules
1194 //
1195 // 2. test.extend({validatable: false});
1196 // this will remove the validation properties from the Observable object should you need to do that.
1197 ko.extenders['validatable'] = function (observable, options) {
1198     if (!kv.utils.isObject(options)) {
1199         options = { enable: options };
1200     }
1201
1202     if (!('enable' in options)) {
1203         options.enable = true;
1204     }
1205
1206     if (options.enable && !kv.utils.isValidatable(observable)) {
1207         var config = kv.configuration.validate || {};
1208         var validationOptions = {
1209             throttleEvaluation : options.throttle || config.throttle
1210         };
1211
1212         observable.error = ko.observable(null); // holds the error message, we only need one since we stop processing validators when one is invalid
1213
1214         // observable.rules:
1215         // ObservableArray of Rule Contexts, where a Rule Context is simply the name of a rule and the params to supply to it
1216         //
1217         // Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>' }
1218         observable.rules = ko.observableArray(); //holds the rule Contexts to use as part of validation
1219
1220         //in case async validation is occurring
1221         observable.isValidating = ko.observable(false);
1222
1223         //the true holder of whether the observable is valid or not
1224         observable.__valid__ = ko.observable(true);
1225
1226         observable.isModified = ko.observable(false);
1227
1228         // a semi-protected observable
1229         observable.isValid = ko.computed(observable.__valid__);
1230
1231         //manually set error state
1232         observable.setError = function (error) {
1233             var previousError = observable.error.peek();
1234             var previousIsValid = observable.__valid__.peek();
1235
1236             observable.error(error);
1237             observable.__valid__(false);
1238
1239             if (previousError !== error && !previousIsValid) {
1240                 // if the observable was not valid before then isValid will not mutate,
1241                 // hence causing any grouping to not display the latest error.
1242                 observable.isValid.notifySubscribers();
1243             }
1244         };
1245
1246         //manually clear error state
1247         observable.clearError = function () {
1248             observable.error(null);
1249             observable.__valid__(true);
1250             return observable;
1251         };
1252
1253         //subscribe to changes in the observable
1254         var h_change = observable.subscribe(function () {
1255             observable.isModified(true);
1256         });
1257
1258         // we use a computed here to ensure that anytime a dependency changes, the
1259         // validation logic evaluates
1260         var h_obsValidationTrigger = ko.computed(extend({
1261             read: function () {
1262                 var obs = observable(),
1263                     ruleContexts = observable.rules();
1264
1265                 kv.validateObservable(observable);
1266
1267                 return true;
1268             }
1269         }, validationOptions));
1270
1271         extend(h_obsValidationTrigger, validationOptions);
1272
1273         observable._disposeValidation = function () {
1274             //first dispose of the subscriptions
1275             observable.isValid.dispose();
1276             observable.rules.removeAll();
1277             if (observable.isModified.getSubscriptionsCount() > 0) {
1278                 observable.isModified._subscriptions['change'] = [];
1279             }
1280             if (observable.isValidating.getSubscriptionsCount() > 0) {
1281                 observable.isValidating._subscriptions['change'] = [];
1282             }
1283             if (observable.__valid__.getSubscriptionsCount() > 0) {
1284                 observable.__valid__._subscriptions['change'] = [];
1285             }
1286             h_change.dispose();
1287             h_obsValidationTrigger.dispose();
1288
1289             delete observable['rules'];
1290             delete observable['error'];
1291             delete observable['isValid'];
1292             delete observable['isValidating'];
1293             delete observable['__valid__'];
1294             delete observable['isModified'];
1295         };
1296     } else if (options.enable === false && observable._disposeValidation) {
1297         observable._disposeValidation();
1298     }
1299     return observable;
1300 };
1301
1302 function validateSync(observable, rule, ctx) {
1303     //Execute the validator and see if its valid
1304     if (!rule.validator(observable(), (ctx.params === undefined ? true : unwrap(ctx.params)))) { // default param is true, eg. required = true
1305
1306         //not valid, so format the error message and stick it in the 'error' variable
1307         observable.setError(kv.formatMessage(
1308                     ctx.message || rule.message,
1309                     unwrap(ctx.params),
1310                     observable));
1311         return false;
1312     } else {
1313         return true;
1314     }
1315 }
1316
1317 function validateAsync(observable, rule, ctx) {
1318     observable.isValidating(true);
1319
1320     var callBack = function (valObj) {
1321         var isValid = false,
1322             msg = '';
1323
1324         if (!observable.__valid__()) {
1325
1326             // since we're returning early, make sure we turn this off
1327             observable.isValidating(false);
1328
1329             return; //if its already NOT valid, don't add to that
1330         }
1331
1332         //we were handed back a complex object
1333         if (valObj['message']) {
1334             isValid = valObj.isValid;
1335             msg = valObj.message;
1336         } else {
1337             isValid = valObj;
1338         }
1339
1340         if (!isValid) {
1341             //not valid, so format the error message and stick it in the 'error' variable
1342             observable.error(kv.formatMessage(
1343                 msg || ctx.message || rule.message,
1344                 unwrap(ctx.params),
1345                 observable));
1346             observable.__valid__(isValid);
1347         }
1348
1349         // tell it that we're done
1350         observable.isValidating(false);
1351     };
1352
1353     //fire the validator and hand it the callback
1354     rule.validator(observable(), unwrap(ctx.params || true), callBack);
1355 }
1356
1357 kv.validateObservable = function (observable) {
1358     var i = 0,
1359         rule, // the rule validator to execute
1360         ctx, // the current Rule Context for the loop
1361         ruleContexts = observable.rules(), //cache for iterator
1362         len = ruleContexts.length; //cache for iterator
1363
1364     for (; i < len; i++) {
1365
1366         //get the Rule Context info to give to the core Rule
1367         ctx = ruleContexts[i];
1368
1369         // checks an 'onlyIf' condition
1370         if (ctx.condition && !ctx.condition()) {
1371             continue;
1372         }
1373
1374         //get the core Rule to use for validation
1375         rule = ctx.rule ? kv.rules[ctx.rule] : ctx;
1376
1377         if (rule['async'] || ctx['async']) {
1378             //run async validation
1379             validateAsync(observable, rule, ctx);
1380
1381         } else {
1382             //run normal sync validation
1383             if (!validateSync(observable, rule, ctx)) {
1384                 return false; //break out of the loop
1385             }
1386         }
1387     }
1388     //finally if we got this far, make the observable valid again!
1389     observable.clearError();
1390     return true;
1391 };
1392 ;
1393 var _locales = {};
1394 var _currentLocale;
1395
1396 kv.defineLocale = function(name, values) {
1397     if (name && values) {
1398         _locales[name.toLowerCase()] = values;
1399         return values;
1400     }
1401     return null;
1402 };
1403
1404 kv.locale = function(name) {
1405     if (name) {
1406         name = name.toLowerCase();
1407
1408         if (_locales.hasOwnProperty(name)) {
1409             kv.localize(_locales[name]);
1410             _currentLocale = name;
1411         }
1412         else {
1413             throw new Error('Localization ' + name + ' has not been loaded.');
1414         }
1415     }
1416     return _currentLocale;
1417 };
1418
1419 //quick function to override rule messages
1420 kv.localize = function (msgTranslations) {
1421     var rules = kv.rules;
1422
1423     //loop the properties in the object and assign the msg to the rule
1424     for (var ruleName in msgTranslations) {
1425         if (rules.hasOwnProperty(ruleName)) {
1426             rules[ruleName].message = msgTranslations[ruleName];
1427         }
1428     }
1429 };
1430
1431 // Populate default locale (this will make en-US.js somewhat redundant)
1432 (function() {
1433     var localeData = {};
1434     var rules = kv.rules;
1435
1436     for (var ruleName in rules) {
1437         if (rules.hasOwnProperty(ruleName)) {
1438             localeData[ruleName] = rules[ruleName].message;
1439         }
1440     }
1441     kv.defineLocale('en-us', localeData);
1442 })();
1443
1444 // No need to invoke locale because the messages are already defined along with the rules for en-US
1445 _currentLocale = 'en-us';
1446 ;/**
1447  * Possible invocations:
1448  *         applyBindingsWithValidation(viewModel)
1449  *         applyBindingsWithValidation(viewModel, options)
1450  *         applyBindingsWithValidation(viewModel, rootNode)
1451  *        applyBindingsWithValidation(viewModel, rootNode, options)
1452  */
1453 ko.applyBindingsWithValidation = function (viewModel, rootNode, options) {
1454     var node = document.body,
1455         config;
1456
1457     if (rootNode && rootNode.nodeType) {
1458         node = rootNode;
1459         config = options;
1460     }
1461     else {
1462         config = rootNode;
1463     }
1464
1465     kv.init();
1466
1467     if (config) {
1468         config = extend(extend({}, kv.configuration), config);
1469         kv.utils.setDomData(node, config);
1470     }
1471
1472     ko.applyBindings(viewModel, node);
1473 };
1474
1475 //override the original applyBindings so that we can ensure all new rules and what not are correctly registered
1476 var origApplyBindings = ko.applyBindings;
1477 ko.applyBindings = function (viewModel, rootNode) {
1478
1479     kv.init();
1480
1481     origApplyBindings(viewModel, rootNode);
1482 };
1483
1484 ko.validatedObservable = function (initialValue, options) {
1485     if (!options && !kv.utils.isObject(initialValue)) {
1486         return ko.observable(initialValue).extend({ validatable: true });
1487     }
1488
1489     var obsv = ko.observable(initialValue);
1490     obsv.errors = kv.group(kv.utils.isObject(initialValue) ? initialValue : {}, options);
1491     obsv.isValid = ko.observable(obsv.errors().length === 0);
1492
1493     if (ko.isObservable(obsv.errors)) {
1494         obsv.errors.subscribe(function(errors) {
1495             obsv.isValid(errors.length === 0);
1496         });
1497     }
1498     else {
1499         ko.computed(obsv.errors).subscribe(function (errors) {
1500             obsv.isValid(errors.length === 0);
1501         });
1502     }
1503
1504     obsv.subscribe(function(newValue) {
1505         if (!kv.utils.isObject(newValue)) {
1506             /*
1507              * The validation group works on objects.
1508              * Since the new value is a primitive (scalar, null or undefined) we need
1509              * to create an empty object to pass along.
1510              */
1511             newValue = {};
1512         }
1513         // Force the group to refresh
1514         obsv.errors._updateState(newValue);
1515         obsv.isValid(obsv.errors().length === 0);
1516     });
1517
1518     return obsv;
1519 };
1520 ;}));