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