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