1 /** 2 * The validanguage library was written by DrLongGhost in 2008. See attached MIT_License.js 3 * and readme.txt for licensing and documentation. 4 * Visit http://www.drlongghost.com/validanguage.php for updates. 5 * 6 * 7 * @namespace Global validanguage object 8 * @author DrLongGhost 9 * @version 1.0.3 10 */ 11 var validanguage = { 12 /** 13 * Valid values are 'none', 'dojo', 'jquery', 'prototype', and 'scriptaculous'. 14 * @public 15 * @default 'none' 16 */ 17 useLibrary: 'none', 18 19 /** 20 * @private 21 */ 22 version: '1.0.3', 23 24 /** 25 * @namespace validanguage.settings object 26 */ 27 settings: { 28 /** 29 * Should an alert() be shown when a validation fails? 30 * By default, validanguage.showError() and validanguage.hideError() instead place the 31 * error msg underneath the failed field. 32 * @default false 33 */ 34 showAlert: false, 35 36 /** 37 * Should the target element of a failed validation receive focus when a validation fails? 38 * IMPORTANT note regarding showAlert and focusOnError. Do NOT set both of these to true if using onblur validations. Pick either one or the other. 39 * When you use both, it is possible to create infinite loops in which a validation failure generates an alert, triggering an onblur, 40 * which triggers another validation failure and subsequent alert. 41 * If you aren't using onblur validations at all, you can safely use both. 42 * @default false 43 */ 44 focusOnerror: false, 45 46 /** 47 * When a form is submitted, are all form fields validated, or do we stop once one fails? 48 * @default true 49 */ 50 validateAllFieldsOnsubmit: true, 51 52 /** 53 * Override this to set a global success handlers for all validation results 54 * If you want to use only alert messages via showAlert, set this to {} to turn off inline error msgs 55 * @default 'validanguage.hideError' 56 */ 57 onsuccess: 'validanguage.hideError', 58 59 /** 60 * Override this to set a global error handler for all validation results 61 * If you want to use only alert messages via showAlert, set this to {} to turn off inline error msgs 62 * @default 'validanguage.showError' 63 */ 64 onerror: 'validanguage.showError', 65 66 /** 67 * Default generic error message 68 * @default 'You have entered an invalid entry in the form' 69 */ 70 errorMsg: 'You have entered an invalid entry in the form', 71 72 /** 73 * Default error message for the validateRequired validation 74 * @default 'You have skipped a required field' 75 */ 76 requiredErrorMsg: 'You have skipped a required field', 77 78 /** 79 * Default error message for the validateMinlength validation 80 * @default 'The indicated field must be at least {!minlength} characters long' 81 */ 82 minlengthErrorMsg: 'The indicated field must be at least {!minlength} characters long', 83 84 /** 85 * Default error message for the validateMaxlength validation 86 * @default 'The indicated field may not be longer than {!maxlength} characters' 87 */ 88 maxlengthErrorMsg: 'The indicated field may not be longer than {!maxlength} characters', 89 90 /** 91 * Default error message for the validateCharacters function 92 * @default 'You have entered invalid characters' 93 */ 94 characterValidationErrorMsg: 'You have entered invalid characters', 95 96 /** 97 * Class name used in showError() to assign to the DIVs 98 * which are created to show the inline error msgs. 99 * @default 'vdError' 100 */ 101 onErrorClassName: 'vdError', 102 103 /** 104 * Class name used in hideError() to assign to a DIV 105 * which was created to show an inline error msgs which is then removed. 106 * @default 'vdNoError' 107 */ 108 noErrorClassName: 'vdNoError', 109 110 /** 111 * Class name used in hideError() to assign to a form field which passes validation 112 * @default 'passedField' 113 */ 114 passedFieldClassName: 'passedField', 115 116 /** 117 * Class name used in showError() to assign to a form field which fails validation 118 * @default 'failedField' 119 */ 120 failedFieldClassName: 'failedField', 121 122 /** 123 * Used to make the ID used in hideError() to assign to the SPAN element inside the vdError 124 * DIV. The errorMsgSpanSuffix is appended to the end of the form field's ID to make the SPAN ID. 125 * If a SPAN with this ID already exists in the DOM, it will be used. If it doesn't exist, one will 126 * be created dynamically. 127 * @default '_errorMsg' 128 */ 129 errorMsgSpanSuffix: '_errorMsg', 130 131 /** 132 * To display a combined list of all fields which failed validation in addition to the 133 * inline error msgs, set showFailedFields to true. The fields will be listed using the 134 * "field" attribute (or ID if field is not available). 135 * @default false 136 */ 137 showFailedFields: false, 138 139 /** 140 * The text specified in errorListText will be placed at the top of the errorDiv generated 141 * by the showFailedFields option in showError(). 142 * @default '<strong>Please correct the following fields:</strong>' 143 */ 144 errorListText: '<strong>Please correct the following fields:</strong>', 145 146 /** 147 * Specifies the ID to be assigned to the DIV used for the showFailedFields option in showError(). 148 * If a DIV with this ID exists in the DOM, it will be used. If it doesn't exist, one will 149 * be created dynamically. 150 * @default 'vdErrorDiv' 151 */ 152 errorDivId: 'vdErrorDiv', 153 154 /** 155 * Specifies the ID to be assigned to the UL used for the showFailedFields option in showError(). 156 * @default 'vdErrorList' 157 */ 158 errorListId: 'vdErrorList', 159 160 /** 161 * Used to make the ID used for the showFailedFields option in showError(). 162 * The errorListItemSuffix is appended to the end of the form field's ID to make the ID for the LI item. 163 * @default '_vd_li' 164 */ 165 errorListItemSuffix: '_vd_li', 166 167 /** 168 * Determines the ID of the DIV created in the showSubmitMessage() function used to 169 * replace a form's submit button once the form has been submitted. 170 * @default 'vdSubmitMessage' 171 */ 172 showSubmitMessageId: 'vdSubmitMessage', 173 174 /** 175 * Determines the text used by the showSubmitMessage() function which is used 176 * replace a form's submit button once the form has been submitted. If desired, you can include HTML 177 * or IMG tags instead of the default text. 178 * @default 'Loading' 179 */ 180 showSubmitMessageMessage: 'Loading', 181 182 /** 183 * This array is used in the validateRequired function to determine whether a select box 184 * has been left on the default, "empty" option. Add/Remove from this array as needed. 185 * @default [' ','0',' ',''] 186 */ 187 emptyOptionElements: [' ','0',' ',''], 188 189 /** 190 * If a validation is supplied without any event handlers, how should it be treated in loadElAPI()? 191 * This setting also affects the behavior of the required=true and maxlength/minlength shortcuts. 192 * @default ['submit'] 193 */ 194 defaultValidationHandlers: ['submit'], 195 196 /** 197 * If a transformation is supplied without any event handlers, how should it be treated in loadElAPI()? 198 * @default ['blur'] 199 */ 200 defaultTransformationHandlers: ['blur'], 201 202 /** 203 * Should any validanguage.toggle() transformations which are defined for form fields on the 204 * page be automatically called when the page has finished loading. 205 * @default true 206 */ 207 callToggleTransformationsOnload: true, 208 209 /** 210 * Should the toggle visibility API in validanguage.toggle() default to "hidden" if a given target 211 * does not satisfy any provided "visible" conditions? If you set this to false, you will need to 212 * explicitly provide the desired "hidden" conditions. 213 * @default true 214 */ 215 toggleVisibilityDefaultsToHidden: true, 216 217 /** 218 * How long are ajax requests allowed to run before they are timed out? 219 * Especially useful for ajax requests used as part of a form submission. 220 * @default 30 221 */ 222 ajaxTimeout: 30, 223 224 /** 225 * When an ajax request is sent as part of a form submit validation routine 226 * and it times out, should the form submit by default? 227 * This defaults to true to help guard against programming errors blocking 228 * users from being able to submit a form due to a broken ajax validation. 229 * @default true 230 */ 231 submitFormOnExpiredAjax: true, 232 233 /** 234 * Should ajax lookups be cached? If set to true, any ajax calls for the same value 235 * on the same form field will use the cached results from the earlier lookup. 236 * @default true 237 */ 238 cacheAjaxLookups: true, 239 240 /** 241 * Should the HTML document be scanned for validanguage comment tags? 242 * Set this to false if you arent using the comment API for better performance. 243 * @default true 244 */ 245 loadCommentAPI: true, 246 247 /** 248 * Determines the delimeter used in the loadCommentAPI() function to split up each 249 * comment into multiple validanguage tags. 250 * You probably want to keep this as "\n" to be safe, but if you want to be allowed 251 * to use carriage returns inside validanguage comment tags, you can set this to 252 * "/>" if you are careful to always close your validanguage tags 253 * @default "\n" 254 */ 255 commentDelimiter: "\n", 256 257 /** 258 * Color for the textbox to flash when invalid input is entered. The default is light red. 259 * Set this to empty to turn flashing off. 260 * @default '#FF6666' 261 */ 262 validationErrorColor: '#FF6666', 263 264 /** 265 * If a field has already failed validation and it fails again, should the new 266 * failure trigger the onerror handlers? Set this to false to prevent a given 267 * field's onkeydown validation from retriggering with every keystroke. If 268 * retriggerErrors is set to false and a different validation function fails, 269 * ie, the error message should change from one error to another, the error WILL 270 * be triggered. 271 * @default true 272 */ 273 retriggerErrors: true, 274 275 /** 276 * Normal color of the textbox. The default is empty. Used in conjunction with validationErrorColor 277 * to make the textboxes flash. 278 * @default '' 279 */ 280 normalTextboxColor: '', 281 282 /** 283 * Amount of time the text box flashes the validationErrorColor. The default is 100ms 284 * @default 100 285 */ 286 timeDelay: 100, 287 288 /** 289 * Typing delay for the ontyping event. This is the amount of time between keystrokes 290 * that must elapse before the event fires. The default is just over 1 second. 291 * @default 1100 292 */ 293 typingDelay: 1100, 294 295 /** 296 * Should the validateRequiredAlternatives function be assigned onclick to radio buttons 297 * and checkboxes named as "requiredAlternatives"? Setting this to true ensures that 298 * checking/unchecking a radio button or checkbox will correctly call showError/hideError. 299 * @default true 300 */ 301 validateRequiredAlternativesOnclick: true, 302 303 /** 304 * Defines the default behavior of the validateRegex function. 305 * Is a match against the regex an error or a success? 306 * @default false 307 */ 308 errorOnMatch: false, 309 310 /** 311 * Override this to setup a function to run after all validanguage form fields have 312 * been intialized inside the populate() function. The default is an empty function. 313 * @default function() { } 314 */ 315 onload: function() { }, 316 317 //dummy field I put here so the onload above will have a comma after it 318 foo: '' 319 }, 320 321 //PRIVATE PROGRAM VARIABLES 322 ajaxLookup: {}, //hash table to store details on dispatched ajax requests 323 alertCounter: true, //this counter prevents infinite loops from being created between alerts() and onblur handlers 324 debug: false, //enable debugging msgs in Ajax code? 325 el: {}, 326 fields: {}, 327 forcedSubmission: false, //enables a form submission to bypass all validation 328 forms: {}, 329 ignoreTheseKeyCodes: [8,37,38,39,40,46], //keycodes that are always permitted during keypress suppression 330 requiredAlternatives: [], //hash table used to store requiredAlternatives associations 331 supportedEvents: ['blur','change','keypress','keyup','keydown','submit','click','typing','focus'], 332 supportedEventHandlers: ['onblur','onchange','onkeypress','onkeyup','onkeydown','onsubmit','onclick','ontyping','onfocus'], 333 typingDelay: [], //hash table to store ontyping timeouts 334 vdLoaded: false, //changed to true after populate() has completed 335 336 /** 337 * Omnipresent dollar function. Converts an ID to a domNode 338 * @param {String|DomNode} DomNode or its ID 339 */ 340 $: function(input) { 341 if (typeof input=='string') input = document.getElementById(input); 342 return input; 343 }, //close $ 344 345 /** 346 * Generic cross-browser addEvent() function. 347 * 348 * @param {Object} Object to receive the event 349 * @param {Object} Event type 350 * @param {Object} Function to be called 351 */ 352 addEvent: function(obj, event, func){ 353 if (obj.attachEvent) { 354 var newEvent = obj.attachEvent("on"+event, func); 355 return newEvent; 356 } else if (obj.addEventListener) { 357 obj.addEventListener(event, func, false); 358 return true; 359 } 360 }, //close addEvent 361 362 /** 363 * Reassigns the validanguage.addEvent function, if an external library is being used. 364 */ 365 addEventInit: function() { 366 // overwrite validanguage.addEvent() 367 switch ( this.useLibrary ) { 368 case 'prototype': 369 case 'scriptaculous': 370 case 'dojo': 371 this.addEvent = function(obj, evtHandler, func) { 372 Event.observe(obj, evtHandler, func); 373 } 374 break; 375 case 'dojo': 376 this.addEvent = function(obj, evtHandler, func) { 377 dojo.connect(obj, 'on'+evtHandler, func); 378 } 379 break; 380 case 'jquery': 381 this.addEvent = function(obj, evtHandler, func) { 382 if (obj == window) { 383 jQuery(document).ready(func); 384 } else { 385 var selector = '#' + obj.id; 386 jQuery(selector).bind(evtHandler, func); 387 } 388 } 389 break; 390 } 391 }, //close addEventInit 392 393 /** 394 * This function wraps multiple validanguage.el.elemId.validations event handlers 395 * and transformations within a single wrapper to call all loaded validations/transformations 396 * and exit as soon as a validation returns false. 397 * 398 * @param {Object} Form element object 399 * @param {string} eventType, such as "blur" or "keydown" 400 * @param {integer} validationsCounter, denotes the array index of this item in 401 * validanguage.el.elemId.validations 402 */ 403 addOrCreateValidationWrapper: function( Obj, eventType, validationsCounter ) { 404 var id = Obj.id; 405 if (eventType == 'submit') { 406 if (this.empty(validationsCounter)) return; // exit early for onsubmit transformations 407 var formId = validanguage.getFormId(id); 408 if (typeof formId == 'number') { 409 var form = document.forms[formId]; 410 } else { 411 var form = this.$(formId); 412 } 413 if (typeof validanguage.forms[formId].validations == 'undefined') { 414 validanguage.forms[formId].validations = []; 415 this.addEvent(form, eventType, function(e) { 416 var evt = e || window.evt; 417 var result = validanguage.validationWrapper(e); 418 if (result == false) { 419 evt.returnValue = false; //IE 420 if (evt.preventDefault) evt.preventDefault(); //Everyone else 421 return false; 422 } else { 423 return true; 424 } 425 }); 426 } 427 //add the element and validationsCounter to the list of onsubmit validations for the parent form 428 validanguage.forms[formId].validations[validanguage.forms[formId].validations.length] = { element: Obj, validationsCounter: validationsCounter }; 429 } else { 430 431 if( typeof validanguage.el[id].handlers == 'undefined' ) validanguage.el[id].handlers = {}; 432 if( typeof validanguage.el[id].handlers[eventType] == 'undefined' ) { 433 validanguage.el[id].handlers[eventType] = []; 434 if( eventType == 'typing') { 435 this.addEvent(Obj, 'keyup', function(e){ validanguage.validationWrapper(e, 'typingTimeout'); }); 436 } else { 437 this.addEvent(Obj, eventType, function(e){ validanguage.validationWrapper(e); }); 438 } 439 } 440 //add validationsCounter to the list of validations for this object/eventType combo 441 validanguage.el[id].handlers[eventType][validanguage.el[id].handlers[eventType].length] = validationsCounter; 442 } 443 }, //close addOrCreateValidationWrapper 444 445 /** 446 * This function is used to either load a new validation for a form field, or to 447 * reactivate a validation previously removed with the removeValidation() method. 448 * 449 * NOTE: When adding a new validation, you will need to have previously inserted 450 * all the relevant details about the validation in the validanguage.el.formField 451 * object. 452 * 453 * @param {String} elemId 454 * @param {String/Array} eventTypes 455 * @param {String/Array/Function} validationNames 456 */ 457 addValidation: function ( elemId, eventTypes, validationNames ) { 458 if( typeof validationNames[0]=='undefined' ) validationNames = [ validationNames ]; 459 if( typeof eventTypes=='string' ) eventTypes = [ eventTypes ]; 460 461 var vals = this.el[elemId].validations; 462 for (var i = vals.length - 1; i > -1; i--) { 463 if ( validationNames[0] == '*' || this.inArray(vals[i].name, validationNames) ) { 464 for( var j=eventTypes.length-1; j>-1; j--) { 465 this.addOrCreateValidationWrapper(this.$(elemId), eventTypes[j]); 466 } 467 } 468 } 469 }, //close addValidation 470 471 /** 472 * Very simple AJAX function 473 * @param {String} url 474 * @param {Function} callback 475 */ 476 ajax: function( url, callback ) { 477 if(window.ActiveXObject){ 478 var ajaxObj = new ActiveXObject("Microsoft.XMLHTTP"); 479 } else if(window.XMLHttpRequest){ 480 var ajaxObj = new XMLHttpRequest(); 481 } 482 483 ajaxObj.open("POST", url, true); 484 ajaxObj.onreadystatechange = function() { 485 if(ajaxObj.readyState==4) { 486 callback(ajaxObj.responseText); 487 } 488 }; 489 ajaxObj.send(null); 490 }, //close ajax 491 492 /** 493 * Initializes validanguage.ajax as browser-specific 494 */ 495 ajaxInit: function() { 496 switch ( this.useLibrary ) { 497 //reassign validanguage.ajax 498 case 'prototype': 499 case 'scriptaculous': 500 this.ajax = function(url, callback, options) { 501 if (validanguage.empty(options)) options = {}; 502 options.onSuccess = callback; 503 new Ajax.Request(url, options); 504 } 505 break; 506 case 'dojo': 507 this.ajax = function(url, callback, options) { 508 if (validanguage.empty(options)) options = {}; 509 options.url = url; 510 options.handle = callback; 511 dojo.xhrGet(options); 512 } 513 break; 514 case 'jquery': 515 this.ajax = function(url, callback, options) { 516 if (validanguage.empty(options)) options = {}; 517 options.url = url; 518 options.success = callback; 519 jQuery.ajax(options); 520 } 521 break; 522 } 523 }, 524 525 /** 526 * This function is called by setInterval and is used to check 527 * whether or not all ajax callbacks for a form have returned. 528 * @param {String} ID or index of the form 529 * @param {String} Event Type 530 */ 531 ajaxValidationWrapper: function( form, eventType ) { 532 var nodeType = (this.getFormId(form)==null) ? 'forms' : 'fields'; 533 if (this.empty(validanguage[nodeType][form][eventType].dispatchedAjax)) { 534 window.clearInterval(validanguage[nodeType][form][eventType].ajaxInterval); 535 } else { 536 for (var id in validanguage[nodeType][form][eventType].dispatchedAjax) { 537 if (typeof time == 'function') continue; 538 if (validanguage[nodeType][form][eventType].dispatchedAjax[id] + (validanguage.settings.ajaxTimeout*1000) < new Date().getTime()) { 539 //abort requests older than X seconds old 540 if (this.debug) console.log('Aborting request...'); 541 delete this[nodeType][form][eventType].failedValidations[id]; 542 if (this.empty(this[nodeType][form][eventType].failedValidations)) this[nodeType][form][eventType].failedValidations = 'callManually'; 543 delete this[nodeType][form][eventType].dispatchedAjax; 544 if (nodeType=='forms' && this.validateForm(form).result === true) { 545 if (this.debug) console.log('Request Aborted.'); 546 if (this.settings.submitFormOnExpiredAjax) { 547 this.forcedSubmission = true; 548 this.$(form).submit(); 549 } 550 } 551 // If a non-"Form Submit" ajax request is aborted, this is currently handled by doing nothing... 552 return; 553 } 554 } 555 } 556 }, //close ajaxValidationWrapper 557 558 /** 559 * This function loads all the validanguage.toggle() rules which 560 * are defined for a form following document.onload() 561 */ 562 callToggleTransformationsOnload: function() { 563 if (this.settings.callToggleTransformationsOnload) { 564 for (var id in this.el) { 565 if (typeof this.el[id].transformations != 'undefined') { 566 for (var i = this.el[id].transformations.length - 1; i > -1; i--) { 567 if (typeof this.el[id].transformations[i].name == 'undefined') 568 continue; 569 var funcString = this.el[id].transformations[i].name; 570 if (typeof funcString == 'string' && funcString.indexOf('validanguage.toggle') > -1) { 571 var transformations = this.resolveArray(funcString, 'function'); 572 var j = transformations.length; 573 for (var k = 0; k < j; k++) { 574 transformations[k].call(this.$(id)); 575 } 576 } 577 } 578 } 579 } 580 } 581 }, 582 583 /** 584 * Combines 2 node lists into 1 585 * @param {Object} obj1 586 * @param {Object} obj2 587 */ 588 concatCollection: function(obj1,obj2) { 589 var i; 590 var arr = new Array(); 591 var len1 = obj1.length; 592 var len2 = obj2.length; 593 for (i=0; i<len1; i++) { 594 arr.push(obj1[i]); 595 } 596 for (i=0; i<len2; i++) { 597 arr.push(obj2[i]); 598 } 599 return arr; 600 }, 601 602 /** 603 * Determines whether the passed domNode is contained 604 * within the passed parentNode. Useful for telling if 605 * a form field belongs to a given form or DIV. * 606 * @param {Object|String} node or ID 607 * @param {Object|String} node or ID 608 */ 609 contains: function (needle, _parentNode) { 610 needle = this.$(needle); 611 _parentNode = this.$(_parentNode); 612 while (needle && _parentNode != needle) { 613 needle = needle.parentNode; 614 } 615 return needle == _parentNode; 616 }, //close contains 617 618 /** 619 * Emulates PHP's empty() function. For convenience, you can specify whether 620 * boolean false is considered empty. Defaults to false is NOT empty. 621 * Ignores functions. 622 * 623 * @param {Object} testVar 624 * @param {bool} falseIsEmpty 625 */ 626 empty: function ( testVar, falseIsEmpty ) { 627 if (testVar == null || testVar == undefined || testVar == NaN || testVar === 'null' || (testVar =='' && typeof testVar == 'string') ) return true; 628 if (falseIsEmpty==true && testVar==false) { 629 return true; 630 } 631 if (typeof testVar == 'object') { 632 for (var i in testVar) { 633 if( typeof testVar[i] == 'function' ) continue; 634 635 // Prevent infinite recursion 636 var recurse = true; 637 638 // Dont recurse into DOM elements 639 if (testVar.nodeName) return false; 640 641 for (var j in testVar[i]) { 642 if (testVar[i][j] === testVar) recurse = false; 643 } 644 645 if(recurse && validanguage.empty(testVar[i], falseIsEmpty)==false ) { 646 return false; 647 } 648 } 649 return true; 650 } else { 651 return false; 652 } 653 }, 654 655 /** 656 * This is a preset transformation which is used to reformat text input 657 * to match a desired pattern 658 * @param {String} Pattern using x to represent alphanumeric characters. 659 * For example: "(xxx) xxx-xxxx" 660 * @param {String} String listing any characters to be removed from the 661 * form field's value prior to potential reformatting. 662 * INCLUDE ALL the delimiters used in "pattern" 663 * For example: "()- " 664 * @param {String/Regex} Regular expression which, if provided, will be used 665 * to determine whether or not to proceed with reformatting. 666 * If not provided, the function will only reformat if the number 667 * of characters in the form field (after stripThese is applied) 668 * matches the number of x's in the provided pattern 669 */ 670 format: function( pattern, stripThese, regexMatch ) { 671 var text = this.value; 672 var origText = text; 673 674 if(stripThese!=null && typeof stripThese=='string') { 675 var i = stripThese.length; 676 for( var i=stripThese.length-1; i>-1; i-- ) { 677 while (text.indexOf(stripThese.charAt(i)) != -1) { 678 text = text.replace(stripThese.charAt(i),'','g'); 679 } 680 } 681 } 682 683 if (this.priorStrippedValue && (this.priorStrippedValue == text)) { 684 // exit early if they hit backspace key to delete a delimeter 685 return; 686 } 687 688 // Save the value in DOM for later 689 this.priorStrippedValue = text; 690 if (regexMatch!=null) { 691 var myreg = (typeof regexMatch=='string') ? new RegExp(regexMatch) : regexMatch; 692 var thisMatch = myreg.exec(text); 693 if (thisMatch == null) return; //exit early for no match 694 } else { 695 //check for required length based on number of x's in the pattern 696 var countMe = pattern.replace(/[^x]/g,''); 697 if( text.length != countMe.length ) return; 698 } 699 700 // Store the caret position 701 var pos = validanguage.getCaretPos(this); 702 703 var i = pattern.length; 704 var textLength = text.length; 705 var k = -1; //counter for text 706 var newtext = ''; 707 var numEaten = 0; 708 // We iterate thru the length of the pattern, 709 // but we exit early once the X's have been exhausted. 710 for (var j = 0; j < i; j++) { 711 if (pattern.charAt(j) == 'x') { 712 numEaten++; 713 if (numEaten > textLength) break; 714 } 715 newtext += (pattern.charAt(j) == 'x') ? text.charAt(++k) : pattern.charAt(j); 716 } 717 718 // Don't change anything unless we need to 719 if (newtext != origText) { 720 this.value = newtext; 721 722 // Adjust caret pos if the text and text length changed 723 if (pos == origText.length) 724 pos = newtext.length; 725 726 // Restore the caret position 727 validanguage.setCaretPos(this, pos); 728 } 729 }, //close format 730 731 /** 732 * This function iterates thru the ajaxLookup array and returns the 733 * index number corresponding to the passed counter value 734 * @param {String} element ID 735 * @param {Object} counter 736 */ 737 getAjaxLookupIndex: function(id, counter) { 738 for( var i=this.ajaxLookup[id].length-1; i>-1; i--) { 739 if (this.ajaxLookup[id][i].counter==counter) return i; 740 } 741 return 0; 742 }, //close getAjaxLookupIndex 743 744 /** 745 * Gets the current caret position on an object 746 * @param {Object} obj 747 */ 748 getCaretPos: function(obj) { 749 if (obj.createTextRange && this.browser!='opera') { 750 // IE 751 if (obj.nodeName.toLowerCase() == 'input') { 752 var range = document.selection.createRange().duplicate(); 753 range.moveEnd('character', obj.value.length); 754 if (range.text == '') 755 return obj.value.length; 756 return obj.value.lastIndexOf(range.text); 757 } else { 758 // Code below from http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html 759 // Unfortnately, it doesn't seem to work consistently for multi-line textareas, 760 // so I commented it out for the moment. Maybe I'll try and fix it one day. 761 /* 762 var selection_range = document.selection.createRange().duplicate(); 763 764 var before_range = document.body.createTextRange(); 765 before_range.moveToElementText(obj); // Selects all the text 766 before_range.setEndPoint("EndToStart", selection_range); // Moves the end where we need it 767 var before_finished = false; 768 var before_text, untrimmed_before_text; 769 770 // Load the text values we need to compare 771 before_text = untrimmed_before_text = before_range.text; 772 773 // Check each range for trimmed newlines by shrinking the range by 1 character and seeing 774 // if the text property has changed. If it has not changed then we know that IE has trimmed 775 // a \r\n from the end. 776 do { 777 if (!before_finished) { 778 if (before_range.compareEndPoints("StartToEnd", before_range) == 0) { 779 before_finished = true; 780 } 781 else { 782 before_range.moveEnd("character", -1) 783 if (before_range.text == before_text) { 784 untrimmed_before_text += "\r\n"; 785 } 786 else { 787 before_finished = true; 788 } 789 } 790 } 791 792 } 793 while (!before_finished); 794 return untrimmed_before_text.length; 795 */ 796 return 0; 797 } 798 } else { 799 // Moz 800 return obj.selectionStart; 801 } 802 }, //close getCaretPos 803 804 /** 805 * Fetches all comment nodes in the passed form node and returns them in a node list 806 * Doesnt work in konqueror, since konqueror strips all comments from the DOM 807 * 808 * @param {Containing Node} el 809 */ 810 getComments: function(el) { 811 if (!el) el = document.documentElement; 812 var comments = new Array(); 813 var length = (el.childNodes) ? el.childNodes.length : 0; 814 for (var c = 0; c < length; c++) { 815 if (el.childNodes[c].nodeType == 8) { 816 comments[comments.length] = el.childNodes[c]; 817 } else if (el.childNodes[c].nodeType == 1) { 818 comments = comments.concat(this.getComments(el.childNodes[c])); 819 } 820 } 821 return comments; 822 }, //close getComments 823 824 /** 825 * Helper function used by validateDate() and validateTimestamp(). 826 * @param {Object} options object provided by the user to validateDate() or validateTimestamp(). 827 * @param {Object} defaults which should be used. Used to allow validateDate() and validateTimestamp() 828 * to have different default dateOrder values. 829 */ 830 getDateTimeDefaultOptions: function ( options, defaults ) { 831 if( options==null ) options = {}; 832 833 // Date options 834 if( typeof options.dateOrder=='undefined' ) options.dateOrder=defaults.dateOrder; 835 options.dateOrder = options.dateOrder.toLowerCase(); 836 if( typeof options.allowedDelimiters=='undefined' || typeof options.allowedDelimiters!='string' ) options['allowedDelimiters'] = './-'; 837 if( typeof options.twoDigitYearsAllowed=='undefined' ) options.twoDigitYearsAllowed = false; 838 if( typeof options.oneDigitDaysAndMonthsAllowed=='undefined' ) options.oneDigitDaysAndMonthsAllowed = true; 839 if( typeof options.maxYear=='undefined' ) options.maxYear = new Date().getFullYear() + 15; 840 if( typeof options.minYear=='undefined' ) options.minYear = 1900; 841 if( typeof options.rejectDatesInTheFuture=='undefined' ) options.rejectDatesInTheFuture = false; 842 if( typeof options.rejectDatesInThePast=='undefined' ) options.rejectDatesInThePast = false; 843 844 // Time options 845 if( typeof options.timeIsRequired=='undefined' ) options.timeIsRequired = false; 846 if( typeof options.timeUnits=='undefined' ) options.timeUnits = 'hms'; 847 if( typeof options.microsecondPrecision=='undefined' ) options.microsecondPrecision = 6; 848 return options; 849 }, //close getDateTimeDefaultOptions 850 851 /** 852 * This function checks for a given setting in increasing specificity 853 * within the validanguage.forms[formId].settings object, and within the passed 854 * validanguage.el objects 855 * 856 * @param {string} Name of the setting to be retrieved 857 * @param {string} ID of the form field object being validated 858 * @param {Object} validanguage.el.objId.validations[index] object 859 */ 860 getElSetting: function( setting, id, validationObj ) { 861 var formSetting = this.getFormSettings(id); 862 var retVal = formSetting[setting]; //global setting 863 if( typeof validationObj!='undefined' && typeof validationObj[setting] != 'undefined' ) { 864 retVal = validationObj[setting]; 865 } else if( typeof this.el[id][setting] != 'undefined' ) { 866 retVal = this.el[id][setting]; 867 } 868 return retVal; 869 }, 870 871 /** 872 * This function returns the validanguage.form[formId].setting object for the passed element ID 873 * @param {string or Node} id of the input field or input node 874 * @return {Object} settings object 875 */ 876 getFormSettings: function(id) { 877 var formName = ( this.$(id).nodeName.toLowerCase()=='form' ) ? 878 id : this.getFormId(id); 879 return this.forms[formName].settings; 880 }, 881 882 /** 883 * This function returns the Id of the parent Form for an element 884 * @param {String|DomNode} Form node or its ID 885 */ 886 getFormId: function(formField) { 887 if (this.$(formField).form) { 888 return this.$(formField).form.getAttribute("id"); 889 } else { 890 return null; 891 } 892 }, 893 894 /** 895 * This function parses the passed comment to retrieve the indicated setting 896 * 897 * @param {String} Name of the setting to retrieve / needle 898 * @param {String} Full text of the HTML comment / haystack 899 * @return {String} The value of the requested setting 900 */ 901 getSettingFromComment: function( setting, comment ) { 902 var needle = ' '+setting+'='; 903 var startPos = comment.indexOf(needle); 904 if( startPos == -1) return null; 905 var delimiterPos = (startPos*1) + (needle.length*1); 906 var delimeter = '\\' + comment.charAt(delimiterPos); 907 var Regex = needle+delimeter+'(.+?)'+delimeter; 908 var myreg = new RegExp(Regex); 909 var thisMatch = myreg.exec(comment, 'gi'); 910 if (thisMatch == null) { 911 return null; //no match 912 } else if (thisMatch[1]) { 913 //Convert booleans. I hope this doesnt screw anyone later.... 914 if(thisMatch[1]=='true') thisMatch[1]=true; 915 if(thisMatch[1]=='false') thisMatch[1]=false; 916 return thisMatch[1]; 917 } 918 }, //close getSettingFromComment 919 920 /** 921 * This function hides the div containing the validanguage error messages for 922 * failed validations 923 */ 924 hideError: function() { 925 var settings = validanguage.getFormSettings(this.id); 926 var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); 927 if (errorDisplay != null) { 928 errorDisplay.innerHTML = ''; 929 var errorDiv = errorDisplay.parentNode; 930 931 errorDiv.style.display = 'none'; 932 errorDiv.className = settings.noErrorClassName; 933 } 934 if (! this.className.match(validanguage.settings.passedFieldClassName)) this.className += ' '+validanguage.settings.passedFieldClassName; 935 if (this.className.match(validanguage.settings.failedFieldClassName)) this.className = this.className.replace(validanguage.settings.failedFieldClassName,''); 936 937 //Do we need to remove any vd_li items? 938 if( !settings.showFailedFields ) return; 939 if( document.getElementById(this.id + settings.errorListItemSuffix) != null ) { 940 var errorList = document.getElementById(settings.errorListId); 941 errorList.removeChild( document.getElementById(this.id + settings.errorListItemSuffix) ); 942 if( errorList.getElementsByTagName('LI').length==0 ) 943 document.getElementById(settings.errorDivId).style.display='none'; 944 } 945 }, //close hideError 946 947 /** 948 * Determines whether the passed item is present in the array or object. 949 * 950 * @param {Object} needle 951 * @param {Object} haystack 952 */ 953 inArray: function( needle, haystack ) { 954 for( var i=haystack.length-1; i>-1; i-- ){ 955 if( haystack[i]===needle ) return true; 956 } 957 return false; 958 }, 959 960 /** 961 * This function searches settingsHaystack for all variables defined in the settingsNeedles 962 * array, and if they are located, they are copied over to the settingsTarget 963 * 964 * @param {Object} settingsHaystack -- Object location to be searched for settings 965 * @param {Array} settingsNeedles -- Array of settings to be checked 966 * @param {Object} settingsTarget -- Object location where any defined settings should be copied to 967 * @param {String} constrainType -- Optional type constraint 968 */ 969 inheritIfDefined: function ( settingsHaystack, settingsNeedles, settingsTarget, constrainType ) { 970 if( typeof settingsNeedles.length == 'undefined' ) return false; 971 for( var i=settingsNeedles.length-1;i>-1;i--) { 972 if ( typeof settingsHaystack[settingsNeedles[i]]!='undefined' && 973 ( this.empty(constrainType) || typeof settingsHaystack[settingsNeedles[i]]==constrainType ) 974 ) { 975 settingsTarget[settingsNeedles[i]] = settingsHaystack[settingsNeedles[i]]; 976 } 977 } 978 }, 979 980 /** 981 * Initialization function for validanguage. Adds the onload hook 982 * which fires off the populate() method to add all the other event 983 * handlers 984 */ 985 init: function() { 986 if (typeof validanguageLibrary!='undefined') this.useLibrary = validanguageLibrary; 987 this.addEventInit(); 988 this.ajaxInit(); 989 this.addEvent(window, 'load', function() { 990 validanguage.populate.call(validanguage); 991 }); 992 }, 993 994 /** 995 * Function to insert 1 Node after another in the DOM. If the referenceNode 996 * is a label, this function will use the nextSibling instead 997 * 998 * @param {Node} nodeToAdd 999 * @param {Node} referenceNode 1000 */ 1001 insertAfter: function (nodeToAdd, referenceNode ) { 1002 if (referenceNode.nextSibling) { 1003 if (referenceNode.nextSibling.nodeName.toLowerCase() == 'label') { 1004 referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling.nextSibling); 1005 } else { 1006 referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling); 1007 } 1008 } else { 1009 referenceNode.parentNode.appendChild(nodeToAdd); 1010 } 1011 }, //close insertAfter 1012 1013 /** 1014 * This function examines the ajaxLookup array to determine whether or not the 1015 * specified ajaxCounter pertains to the most recent ajax call for that form field. 1016 * @param {String} formFieldId 1017 * @param {Integer} ajaxCounter 1018 */ 1019 isExpiredAjax: function (formFieldId, ajaxCounter) { 1020 if (this.empty(formFieldId) || this.empty(ajaxCounter)) return false; 1021 var h = this.getAjaxLookupIndex(formFieldId, ajaxCounter); 1022 var arr = this.ajaxLookup[formFieldId]; 1023 var event = arr[h].eventType; 1024 1025 for (var i = arr.length-1; i > 0; i--) { 1026 if (event == arr[i].eventType) { 1027 if (arr[i].counter == ajaxCounter) { 1028 return false; 1029 } else { 1030 return true; 1031 } 1032 } 1033 } 1034 return false; 1035 }, //close isExpiredAjax 1036 1037 /** 1038 * This function parses all comments in the current document, looking for 1039 * the comment-based API and converts any validanguage statements it 1040 * finds into the element/json-based API for further processing. 1041 * 1042 * @param {Object} Dom Node containing comments to be loaded 1043 * @param {Array} For konqueror, we pass this function an Array with all 1044 * the comments (retrieved via AJAX) 1045 * For all other browsers, konquerorComments is undefined and 1046 * we retrieve the comments normally via the DOM 1047 */ 1048 loadCommentAPI: function( domNode, konquerorComments ) { 1049 domNode = this.$(domNode); 1050 1051 var supportedSettings = ['mode','expression','suppress','onsubmit','onblur','onchange', 1052 'onkeypress','onkeyup','onkeydown','onclick', 'ontyping','onfocus', 1053 'errorMsg','onerror','onsuccess','focusOnError', 1054 'showAlert','required','requiredAlternatives', 1055 'maxlength','minlength','regex','field', 1056 'errorOnMatch','modifiers','transformations','validations']; 1057 1058 var allComments = (this.empty(konquerorComments)) ? this.getComments(domNode) : konquerorComments; 1059 var length = allComments.length; 1060 for (var j=0; j<length; j++) { 1061 1062 var singleComment = (this.empty(konquerorComments)) ? allComments[j].nodeValue : allComments[j]; 1063 var tagArray = singleComment.split(validanguage.settings.commentDelimiter); 1064 var tagArrayLength = tagArray.length; 1065 1066 for (var a=0; a<tagArrayLength; a++) { 1067 var commentText = tagArray[a]; 1068 commentText = commentText.replace(/\n/g,' '); 1069 commentText = commentText.replace(/\r/g,' '); 1070 var isValidanguageRegEx = /<validanguage/i; 1071 if (isValidanguageRegEx.test(commentText)) { 1072 //get the targets 1073 var targets = this.getSettingFromComment('target', commentText); 1074 var settings = []; //reset settings 1075 if (this.empty(targets, true)) 1076 continue; 1077 targets = this.resolveArray(targets, 'string'); 1078 for (var k = supportedSettings.length - 1; k > -1; k--) { 1079 var tempSetting = this.getSettingFromComment(supportedSettings[k], commentText); 1080 if (!(tempSetting == null || (typeof tempSetting == 'string' && tempSetting == '') )) 1081 settings[supportedSettings[k]] = tempSetting; 1082 } 1083 1084 //iterate thru our targets and assign the settings 1085 k = targets.length; 1086 for (var l = 0; l < k; l++) { 1087 var id = targets[l]; 1088 var obj = this.$(id); 1089 if (typeof this.el[id] == 'undefined' || obj == null) 1090 this.el[id] = {}; 1091 1092 /** CHARACTER VALIDATION **/ 1093 //start keypressValidation 1094 if (typeof settings.expression != 'undefined') { 1095 this.el[id].characters = {}; 1096 this.inheritIfDefined(settings, ['expression','errorMsg','mode','suppress','validateCharacters','onerror','onsuccess'], this.el[id].characters); 1097 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].characters); 1098 } 1099 //close keypressValidation 1100 1101 /** REGEX **/ 1102 if (typeof settings.regex != 'undefined') { 1103 this.el[id].regex = { expression: settings.regex }; 1104 this.inheritIfDefined(settings, ['errorOnMatch','modifiers'], this.el[id].regex); 1105 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].regex); 1106 } 1107 1108 /** MISC SETTINGS **/ 1109 // Only inherit event handlers that are non-boolean transformations 1110 this.inheritIfDefined(settings, ['field'], this.el[id], 'string'); 1111 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id], 'string'); 1112 this.inheritIfDefined(settings, ['minlength','maxlength','requiredAlternatives','required','focusOnError','showAlert', 1113 'onsuccess','onerror','errorMsg'], this.el[id]); 1114 if (typeof settings.minlength != 'undefined') { 1115 this.el[id].minlengthEvents = {}; 1116 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].minlengthEvents); 1117 } 1118 if (typeof settings.maxlength != 'undefined') { 1119 this.el[id].maxlengthEvents = {}; 1120 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].maxlengthEvents); 1121 } 1122 if (typeof settings.required != 'undefined') { 1123 this.el[id].requiredEvents = {}; 1124 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].requiredEvents); 1125 } 1126 1127 /** VALIDATIONS AND TRANSFORMATIONS **/ 1128 if (typeof this.el[id].validations == 'undefined') this.el[id].validations = []; 1129 if (typeof this.el[id].transformations == 'undefined') this.el[id].transformations = []; 1130 var functionModifiers = ['focusOnError','showAlert','onsuccess','onerror','errorMsg','isAjax']; 1131 1132 //Load validations 1133 if( typeof settings.validations != 'undefined' && !this.empty(settings.validations) ) { 1134 this.el[id].validations[this.el[id].validations.length] = {}; 1135 this.el[id].validations[this.el[id].validations.length-1].name = settings.validations; 1136 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].validations[this.el[id].validations.length-1]); 1137 this.inheritIfDefined(settings, functionModifiers, this.el[id].validations[this.el[id].validations.length-1]); 1138 } 1139 //Load transformations 1140 if( typeof settings.transformations != 'undefined' && !this.empty(settings.transformations) ) { 1141 this.el[id].transformations[this.el[id].transformations.length] = {}; 1142 this.el[id].transformations[this.el[id].transformations.length-1].name = settings.transformations; 1143 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].transformations[this.el[id].transformations.length-1]); 1144 } 1145 1146 } // foreach (targets) 1147 } // close if(validanguage_comment) 1148 } // close tagArray loop 1149 } // close allComments loop 1150 }, //close loadCommentAPI 1151 1152 /** 1153 * This function parses the validanguage.el object to load all the 1154 * form-element-specific validation settings which the end user has defined 1155 * via the Object-based API 1156 * 1157 * @param {String|Object} (Optional) If provided, this is either a DOM node 1158 * or a node's ID. Providing a DOM node will limit the function 1159 * to loading validations for only elements contained within that node, 1160 * or if the node is a form field itself, limit to only that field. 1161 */ 1162 loadElAPI: function( _parentNode ) { 1163 if (_parentNode != null) _parentNode = this.$(_parentNode); 1164 1165 for( var elem in this.el ) { //for each element.... 1166 1167 // Skip to the next if it's not an element ID 1168 try { if( typeof this.$(elem) == undefined || this.empty(this.$(elem)) ) continue; } catch(e) { continue; } 1169 1170 if (_parentNode != null && _parentNode.getAttribute("id") != elem) { 1171 // Skip this item if its not a descendant of _parentNode 1172 if (!this.contains(elem, _parentNode)) continue; 1173 } 1174 var Obj = this.$(elem); 1175 var settings = validanguage.getFormSettings(elem); 1176 if (typeof this.el[elem].validations == 'undefined') this.el[elem].validations = []; 1177 if (typeof this.el[elem].field == 'undefined') this.el[elem].field = elem; 1178 1179 /** REQUIRED **/ 1180 if (typeof this.el[elem].required != 'undefined' && this.el[elem].required==true) { 1181 this.el[elem].validations[this.el[elem].validations.length] = {}; 1182 this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateRequired'; 1183 this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg; 1184 this.inheritIfDefined( this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] ); 1185 1186 //If specific requiredEvents are provided, use those instead of the element level event handlers 1187 if( typeof this.el[elem]['requiredEvents']!='undefined') this.inheritIfDefined( this.el[elem]['requiredEvents'], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] ); 1188 1189 //We need to call the validateRequiredAlternatives function when a requiredAlternative is clicked 1190 if(settings.validateRequiredAlternativesOnclick==true && typeof this.el[elem].requiredAlternatives != 'undefined' ) { 1191 var onsuccessFuncs = (typeof this.el[elem].onsuccess!='undefined') ? this.el[elem].onsuccess : settings.onsuccess; 1192 var onerrorFuncs = (typeof this.el[elem].onerror!='undefined') ? this.el[elem].onerror : settings.onerror; 1193 var alts = this.resolveArray(this.el[elem].requiredAlternatives,'string'); 1194 for( var y=alts.length-1; y>-1; y--) { 1195 this.requiredAlternatives[alts[y]] = {}; 1196 if( !((typeof this.$(alts[y]).type != 'undefined') && (this.$(alts[y]).type=='checkbox'||this.$(alts[y]).type=='radio')) ) continue; 1197 this.requiredAlternatives[alts[y]].onsuccess = onsuccessFuncs; 1198 this.requiredAlternatives[alts[y]].onerror = onerrorFuncs; 1199 this.requiredAlternatives[alts[y]].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg; 1200 this.requiredAlternatives[alts[y]].parentId = elem; 1201 this.addEvent( this.$(alts[y]), 'click', function(e) { validanguage.validateRequiredAlternatives(e); } ); 1202 } 1203 } 1204 } 1205 1206 /** REGEX **/ 1207 if (typeof this.el[elem].regex != 'undefined') { 1208 this.el[elem].validations[this.el[elem].validations.length] = {}; 1209 this.el[elem].validations[this.el[elem].validations.length - 1].name = 'validanguage.validateRegex'; 1210 var errorMsg = (typeof this.el[elem].errorMsg == 'undefined') ? settings.errorMsg : this.el[elem].errorMsg; 1211 if(typeof this.el[elem].regex.errorMsg != 'undefined') errorMsg = this.el[elem].regex.errorMsg 1212 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = errorMsg; 1213 this.inheritIfDefined(this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]); 1214 this.inheritIfDefined(this.el[elem].regex, this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]); 1215 if(typeof this.el[elem].regex.errorOnMatch=='undefined') this.el[elem].regex.errorOnMatch=settings.errorOnMatch; 1216 } 1217 1218 /** MAXLENGTH **/ 1219 if (typeof this.el[elem].maxlength != 'undefined') { 1220 this.el[elem].validations[this.el[elem].validations.length] = {}; 1221 this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMaxlength'; 1222 this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.maxlengthErrorMsg.replace('{!maxlength}',this.el[elem].maxlength); 1223 //If specific maxlengthEvents are provided, use those instead of the element level event handlers 1224 if( typeof this.el[elem]['maxlengthEvents']!='undefined') this.inheritIfDefined( this.el[elem]['maxlengthEvents'], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] ); 1225 } 1226 1227 /** MINLENGTH **/ 1228 if (typeof this.el[elem].minlength != 'undefined') { 1229 this.el[elem].validations[this.el[elem].validations.length] = {}; 1230 this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMinlength'; 1231 this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.minlengthErrorMsg.replace('{!minlength}',this.el[elem].minlength); 1232 //If specific minlengthEvents are provided, use those instead of the element level event handlers 1233 if( typeof this.el[elem]['minlengthEvents']!='undefined') this.inheritIfDefined( this.el[elem]['minlengthEvents'], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] ); 1234 } 1235 1236 //start keypressValidation 1237 /** CHARACTERS **/ 1238 if (typeof this.el[elem].characters != 'undefined' && 1239 typeof this.el[elem].characters.mode != 'undefined' && 1240 typeof this.el[elem].characters.expression != 'undefined' 1241 ) { 1242 1243 //supported shortcuts 1244 var expression = this.el[elem].characters.expression; 1245 expression = expression.replace('alphaUpper','ABCDEFGHIJKLMNOPQRSTUVWXYZ'); 1246 expression = expression.replace('alphaLower','abcdefghijklmnopqrstuvwxyz'); 1247 expression = expression.replace('alpha','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); 1248 expression = expression.replace('numeric','0123456789'); 1249 expression = expression.replace('special','`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?'); 1250 this.el[elem].characters.characterExpression = expression; 1251 1252 var validanguageExpr = ';'; 1253 for (var j=expression.length-1;j>-1;j--){ 1254 validanguageExpr += expression.charCodeAt(j) + ';'; 1255 } 1256 this.el[elem].characters.expression = validanguageExpr; 1257 if(typeof this.el[elem].characters.suppress=='undefined' || this.el[elem].characters.suppress==true) this.addEvent(Obj, "keypress", validanguage.validateKeypress ); 1258 1259 // Load validanguage.validateCharacters 1260 if (typeof this.el[elem].characters.validateCharacters == 'undefined' || this.el[elem].characters.validateCharacters) { 1261 this.el[elem].validations[this.el[elem].validations.length] = {}; 1262 this.el[elem].validations[this.el[elem].validations.length - 1].name = 'validanguage.validateCharacters'; 1263 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = true; 1264 1265 for (var z = this.supportedEventHandlers.length - 1; z > -1; z--) { 1266 if (typeof this.el[elem].characters[this.supportedEventHandlers[z]] != 'undefined' && this.el[elem].characters[this.supportedEventHandlers[z]] == true) 1267 this.el[elem].validations[this.el[elem].validations.length - 1][this.supportedEventHandlers[z]] = true; 1268 } 1269 1270 //assign onerror 1271 if (typeof this.el[elem].characters.errorMsg != 'undefined') { 1272 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = this.el[elem].characters.errorMsg; 1273 } else { 1274 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = settings.characterValidationErrorMsg; 1275 } 1276 } 1277 } 1278 //close keypressValidation 1279 if (typeof this.el[elem].transformations == 'undefined') this.el[elem].transformations = []; 1280 1281 /** TRANSFORMATIONS **/ 1282 //First, check for transformations listed by event type, such as onblur="foo" 1283 var j = this.supportedEventHandlers.length; 1284 for (var k = 0; k < j; k++) { 1285 var handler = this.supportedEventHandlers[k]; 1286 if( typeof this.el[elem][handler] != 'undefined' && typeof this.el[elem][handler] != 'boolean' ) { 1287 //Add the defined transformation to the transformations array 1288 this.el[elem].transformations[this.el[elem].transformations.length] = {}; 1289 var n = this.el[elem].transformations.length - 1; 1290 this.el[elem].transformations[n].name = this.el[elem][handler]; 1291 //store the event handler 1292 this.el[elem].transformations[n][handler] = true; 1293 } 1294 } 1295 1296 var h = this.el[elem].transformations.length; 1297 for (var i = 0; i < h; i++) { 1298 //for each transformation, load the appropriate function in the element's transformations array 1299 var eventLoaded = false; 1300 var j = this.supportedEvents.length; 1301 for (var k = 0; k < j; k++) { 1302 if(this.supportedEvents[k]=='submit') continue; 1303 if (typeof this.el[elem].transformations[i]['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].transformations[i]['on' + this.supportedEvents[k]] == true) { 1304 eventLoaded = true; 1305 this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k]); 1306 } 1307 } 1308 1309 //if they didnt supply any events, we default to the defined defaultTransformationHandlers (usually only "blur") 1310 if (eventLoaded == false) { 1311 if (Obj.nodeName.toLowerCase() == 'form') { 1312 this.addOrCreateValidationWrapper(Obj, 'submit'); 1313 } else { 1314 for (var l = settings.defaultTransformationHandlers.length - 1; l > -1; l--) { 1315 this.addOrCreateValidationWrapper(Obj, settings.defaultTransformationHandlers[l], 999); 1316 } 1317 } 1318 } 1319 } 1320 1321 /** VALIDATIONS **/ 1322 if (typeof this.el[elem].validations != 'undefined') { 1323 1324 // Sort the validations array to ensure that any isAjax function appears last 1325 var h = this.el[elem].validations.length-1; 1326 ajaxLoop: 1327 for (var i = 0; i < h; i++) { 1328 if (this.el[elem].validations[i].isAjax) { 1329 var copy = this.el[elem].validations[i]; 1330 this.el[elem].validations.splice(i,1); 1331 this.el[elem].validations.push(copy); 1332 break ajaxLoop; 1333 } 1334 } 1335 1336 var h = this.el[elem].validations.length; 1337 for (var i = 0; i < h; i++) { 1338 //for each validation, load the appropriate function in the element's validations array 1339 var eventLoaded = false; 1340 var j = this.supportedEvents.length; 1341 for (var k = 0; k < j; k++) { 1342 if (typeof this.el[elem].validations[i]['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].validations[i]['on' + this.supportedEvents[k]] === true) { 1343 eventLoaded = true; 1344 this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k], i); 1345 } 1346 } 1347 1348 //if they didnt supply any events, we default to the defined defaultValidationHandlers (usually only "submit") 1349 if (eventLoaded == false) { 1350 for( var l=settings.defaultValidationHandlers.length-1; l>-1;l--) { 1351 this.addOrCreateValidationWrapper(Obj, settings.defaultValidationHandlers[l], i); 1352 } 1353 } 1354 } 1355 } 1356 } //close elem loop 1357 }, 1358 1359 /** 1360 * This function loads a form and its settings into the 1361 * global validanguage object so it can receive validation 1362 * criteria. If the form does not have an ID, one is assigned. 1363 * @param {String|Object} form Node or ID of the form 1364 */ 1365 loadForm: function(form) { 1366 var formName; 1367 form = this.$(form); 1368 if (this.empty(form.getAttribute("id"))) { 1369 formName = 'validanguageForm' + this.formCounter; 1370 form.id = formName; 1371 } else { 1372 formName = form.getAttribute("id"); 1373 } 1374 this.forms[formName] = { settings: this.settings }; 1375 }, //close loadForm 1376 1377 /** 1378 * This function loads all the validation rules specified within the 1379 * passed element. For example, if it is passed a DIV, all the validation 1380 * rules in that DIV will be loaded. This supports both the comment API 1381 * and the Object API. However, if you are using the object API with an 1382 * AJAX request, you may need to find the script tags within the returned 1383 * HTML and eval() them prior to calling loadNewFields(). Also, be aware 1384 * that you can mess up your form's validation by loading rules which have 1385 * already been loaded once. Be sure that the DIV or form field which you 1386 * give to the loadNewFields() function doesn't contain validations which 1387 * have previously been loaded, or unexpected issues may arise. 1388 * @param {String|DomNode} containingElement or its ID 1389 */ 1390 loadNewFields: function(containingElement) { 1391 this.loadCommentAPI(containingElement); 1392 this.loadElAPI(containingElement); 1393 }, 1394 1395 /** 1396 * This function searches the passed subject and returns an Array of strings 1397 * which are delimited by the characters passed to the function in the 1398 * first 2 arguments. Used to pull comments from the document source 1399 * @param {String} startChar 1400 * @param {String} endChar 1401 * @param {String} subject 1402 * @return {Array} 1403 */ 1404 parseSubstring: function( startChar, endChar, subject ) { 1405 var matches = []; 1406 var parts = subject.split(startChar); 1407 for( var i=0; i<parts.length; i++) { 1408 var endPos = parts[i].indexOf(endChar); 1409 if( endPos != -1) matches.push( parts[i].substring(0, endPos) ); 1410 } 1411 return matches; 1412 }, //close parseSubstring 1413 1414 /** 1415 * Main function to be called onload to load all the validations 1416 */ 1417 populate: function(){ 1418 this.sniffBrowser(); 1419 if( this.browser=='ie5' ) return; //There's no way I'm supporting IE5, so it's safest to just not run validanguage at all 1420 1421 if (typeof console == 'undefined') this.debug=false; 1422 1423 /** 1424 * Iterate thru all the form elements on the page to populate 1425 * the formLookup hash table and load the default settings 1426 **/ 1427 var forms = document.getElementsByTagName('form'); 1428 for (var i=0, j=forms.length; i<j; i++) { 1429 this.formCounter = i; // this supports forms with no Ids 1430 this.loadForm(forms[i]); 1431 } 1432 1433 if (this.browser == 'konqueror' && this.settings.loadCommentAPI == true) { 1434 this.ajax(document.location.href, function(docText) { 1435 //prototype 1436 if (docText.responseText) docText = docText.responseText; 1437 var comments = validanguage.parseSubstring( '<!--', '-->', docText ); 1438 validanguage.loadCommentAPI( window.document, comments ); 1439 if (validanguage.overloadFormSettings) validanguage.overloadFormSettings(); 1440 if (validanguage.el && !validanguage.empty(validanguage.el)) { 1441 validanguage.loadElAPI(); 1442 if (validanguage.callToggleTransformationsOnload) validanguage.callToggleTransformationsOnload(); 1443 //Call any onload handler defined by the user 1444 validanguage.settings.onload.call(validanguage); 1445 validanguage.vdLoaded = true; 1446 } 1447 }); 1448 } else { 1449 //Load comment API 1450 if (this.settings.loadCommentAPI == true) this.loadCommentAPI( ); 1451 1452 //Load Form-Specific Settings 1453 if (this.overloadFormSettings) this.overloadFormSettings(); 1454 1455 //Load the validanguage.el API 1456 if (this.el && !this.empty(this.el)) this.loadElAPI(); 1457 1458 // Call any defined validanguage.toggle() functions to true up the UI 1459 if (this.callToggleTransformationsOnload) this.callToggleTransformationsOnload(); 1460 1461 //Call any onload handler defined by the user 1462 this.settings.onload.call(this); 1463 1464 this.vdLoaded = true; 1465 } 1466 //Garbage collection 1467 this.addEvent(window, 'unload', function() { delete validanguage; }); 1468 1469 }, //close populate 1470 1471 /** 1472 * This transformation function updates a div or span 1473 * with the total number of characters remaining, 1474 * based on a comparison between the number of characters 1475 * the user has typed and the defined minLength and maxLength 1476 * values for the field. See the demo page for an example. 1477 */ 1478 remainingChars: function() { 1479 var div = validanguage.$(this.id+'_remaining'); 1480 var minLength = validanguage.el[this.id].minlength || 0; 1481 var maxLength = validanguage.el[this.id].maxlength; 1482 var length = this.value.length; 1483 var remainingClass = ((length <= maxLength) && (length >= minLength)) ? 'vdLengthPassed' : 'vdLengthFailed'; 1484 div.innerHTML = '<span class="'+remainingClass+'">' + length + '</span> / ' + maxLength; 1485 }, //close remainingChars 1486 1487 /** 1488 * This function removes all references to a form and its elements from 1489 * the global validanguage object 1490 * @param {String|DomNode} Which form to remove 1491 */ 1492 removeForm: function (formId) { 1493 if (typeof formId != 'string') formId = formId.getAttribute("id"); 1494 1495 // Remove any related form fields from validanguage.el 1496 for (var elem in this.el) { 1497 if (this.contains(elem, formId)) delete this.el[elem]; 1498 } 1499 delete this.forms[formId]; 1500 }, //close removeForm 1501 1502 /** 1503 * This function will remove all the validations for any 1504 * form fields contained within the passed containing element. 1505 * For example, if containingElem is a DIV that has 3 textareas 1506 * inside it, all the validations for those 3 textareas will be 1507 * removed. You can also call the function with no argument 1508 * to remove all the validations on the current page. 1509 * @param {String|DomNode} Containing element or its ID 1510 */ 1511 removeAllValidations: function (containingElem) { 1512 if (containingElem==undefined) containingElem = window.document; 1513 for (var elem in this.el) { 1514 if (this.contains(elem, containingElem)) this.removeValidation(elem, '*', '*'); 1515 } 1516 }, //close removeAllValidations 1517 1518 /** 1519 * This function is used to deactivate a previously loaded validation. 1520 * Provide the element ID of the field and a list of event types and validation 1521 * names to deactivate. You can use the * character as the eventType and/or 1522 * the validationName arguments to include ALL eventTypes/validationNames. 1523 * 1524 * NOTE: if you are tempted to use removeValidation('id','*','*'), you may be 1525 * better off using validanguage.el.id.disabled=true, as this is much easier to 1526 * undo later. 1527 * 1528 * NOTE: It is up to you to make sure no error msgs are displaying before disabling 1529 * a validation. If one is showing and all validations are disabled, the onsuccess 1530 * handlers used to clear the error msgs will never be called. 1531 * 1532 * @param {String} elemId 1533 * @param {String/Array} eventTypes 1534 * @param {String/Array/Function} validationNames 1535 */ 1536 removeValidation: function ( elemId, eventTypes, validationNames ) { 1537 //prep our arguments 1538 if( eventTypes == '*' ) { 1539 eventTypes = this.supportedEvents; 1540 } else if( typeof eventTypes[0]=='undefined') { 1541 eventTypes = [ eventTypes ]; 1542 } 1543 if( typeof validationNames=='string' ) validationNames = [ validationNames ]; 1544 for (var j = eventTypes.length - 1; j > -1; j--) { 1545 if (eventTypes[j] == 'submit') { 1546 // Remove form.onsubmit validations 1547 var vals = this.forms[this.getFormId(elemId)].validations; 1548 formValLoop: 1549 for (var i = vals.length - 1; i > -1; i--) { 1550 if( vals[i]==undefined || vals[i].element.getAttribute("id") != elemId ) continue formValLoop; 1551 if ( validationNames[0] == '*' || this.inArray( this.el[elemId].validations[vals[i].validationsCounter].name, validationNames ) ) { 1552 try { delete vals[i]; } catch(e) {} 1553 } 1554 } 1555 } else { 1556 // Remove field-specific validations 1557 var vals = this.el[elemId].validations; 1558 for (var i = vals.length - 1; i > -1; i--) { 1559 if ( validationNames[0] == '*' || this.inArray(vals[i].name, validationNames) ) { 1560 try { delete this.el[elemId].handlers[eventTypes[j]][i]; } catch(e) {} 1561 } 1562 } 1563 } 1564 } 1565 }, //close removeValidation 1566 1567 /** 1568 * This function accepts as input a function, a string, an array of 1569 * functions, an array of strings, or a comma-delimited list of functon 1570 * names as its argument and returns an Array of functions or strings 1571 * comprising the passed arguments. Example: transforms 'foo, bar' 1572 * to [foo, bar] 1573 * 1574 * @param {Function or String or Array} args 1575 * @param {String} returnType should be either 'string' or 'function' 1576 * @return {Array of Functions} 1577 */ 1578 resolveArray: function (args, returnType, ignoreCommas) { 1579 var returnArray = []; 1580 if( typeof args == 'object' ) { 1581 var i=args.length; 1582 for (var j=0; j<i; j++) { 1583 returnArray[returnArray.length] = this.resolveArray(args[j],returnType)[0]; 1584 } 1585 return returnArray; 1586 } 1587 if( typeof args == 'function' ) { 1588 returnArray[0] = args; 1589 return returnArray; 1590 } 1591 if ( typeof args == 'string' ) { 1592 if(returnType=='string') args = args.replace(' ',''); //dont remove spaces when returning a function 1593 1594 if( args.indexOf(',') == -1 || ignoreCommas==true ) { 1595 //function name as a string 1596 if( returnType=='function' ) { 1597 if( args.indexOf('(') != -1 && args.indexOf('function')==-1 ) { 1598 //In order to preserve scope for functions with parameters attached, 1599 //we must transform "func1(text,foo)" into "function(text) { return func1.call(this,text,foo) }" 1600 var splitAt = args.indexOf('('); 1601 var funcName = args.substring(0,splitAt); 1602 var params = args.substring(++splitAt,args.length); 1603 var args = 'function(text) { return '+funcName+'.call(this,'+params+'}'; 1604 } 1605 eval("var argsHandle="+args); //easiest way to handle dot notation and framesets 1606 returnArray[returnArray.length] = argsHandle; 1607 } else { 1608 returnArray[returnArray.length] = args; 1609 } 1610 } else { 1611 //comma-delimited list of function names 1612 var tempArray = this.smartCommaSplit(args); 1613 var i=tempArray.length; 1614 if(i==1) { 1615 //The only commas in the string appear within braces or parens 1616 returnArray = this.resolveArray(tempArray[0], returnType, true); 1617 } else { 1618 for (var j=0; j<i; j++) { 1619 returnArray[returnArray.length] = this.resolveArray(tempArray[j],returnType)[0]; 1620 } 1621 } 1622 } 1623 return returnArray; 1624 } 1625 return false; 1626 }, //close resolveArray 1627 1628 /** 1629 * Sets the caret at a specified position on an object 1630 * @param {Object} Dom Node 1631 * @param {Object} Position 1632 */ 1633 setCaretPos: function(obj, pos) { 1634 if(obj.createTextRange && this.browser!='opera') { 1635 // IE 1636 var range = obj.createTextRange(); 1637 range.move('character', pos); 1638 range.select(); 1639 } else if(obj.selectionStart) { 1640 // Moz 1641 obj.focus(); 1642 obj.setSelectionRange(pos, pos); 1643 } 1644 }, //close setCaretPos 1645 1646 /** 1647 * This function is called from an ajax callback to report back 1648 * to validanguage the status of the ajax validation. 1649 * @param {Object} id Id of the form field associated with this validation 1650 * @param {Boolean} returnStatus Whether or not the field is valid 1651 * @param {String|Integer} type Event Type or (alternately) the ajaxCounter that can be used 1652 * to check validanguage.ajaxLookup to determine the eventType 1653 * @param {String} errorMsg Error message to show for the failed field 1654 */ 1655 setValidationStatus: function( id, returnStatus, type, errorMsg ) { 1656 if (type == undefined) { 1657 type = 'submit'; 1658 } else if (!this.inArray(type, this.supportedEvents)) { 1659 var i = this.getAjaxLookupIndex(id, type); 1660 type = (this.ajaxLookup[id][i].eventType) ? this.ajaxLookup[id][i].eventType : 'submit'; 1661 } 1662 var nodeType = (type=='submit') ? 'forms' : 'fields'; 1663 var form = (type=='submit') ? validanguage.getFormId(id) : id; 1664 1665 if (this.debug) { 1666 console.log('setValidationStatus for '+id+'. Pending requests before deleting:'); 1667 console.dir(this[nodeType][form][type].dispatchedAjax); 1668 console.dir(this[nodeType][form][type].failedValidations); 1669 } 1670 1671 //exit early if the request has been aborted. TO DO: Better way to detect aborted request 1672 if (typeof this[nodeType][form][type].failedValidations[id] == 'undefined') { 1673 if (this.debug) console.log('Exiting setValidationStatus for aborted request'); 1674 //return; 1675 } 1676 1677 if (returnStatus === false) { 1678 if (!this.empty(errorMsg)) this[nodeType][form][type].failedValidations[id].errorMsg = errorMsg; 1679 } else { 1680 delete this[nodeType][form][type].failedValidations[id]; 1681 if (this.empty(this[nodeType][form][type].failedValidations)) this[nodeType][form][type].failedValidations = 'callManually'; 1682 } 1683 delete this[nodeType][form][type].dispatchedAjax[id]; 1684 1685 // Store the details on this request in ajaxLookup if the user supplied an ajaxCounter 1686 if (typeof i != 'undefined') { 1687 this.ajaxLookup[id][i].result = returnStatus; 1688 if (!this.empty(errorMsg)) this.ajaxLookup[id][i].errorMsg = errorMsg; 1689 } 1690 1691 if (this.debug) { 1692 console.log('Pending requests after deleting:'); 1693 console.dir(this[nodeType][form][type].dispatchedAjax); 1694 console.log('Failed validations:'); 1695 console.dir(this[nodeType][form][type].failedValidations); 1696 } 1697 1698 // We only validate the form if there are no other pending ajax requests we're waiting on to come back 1699 if (nodeType=='forms' && this.empty(this[nodeType][form][type].dispatchedAjax) && this.validateForm(form).result === true) { 1700 if (this.debug) console.log('Form Submitted'); 1701 validanguage.forcedSubmission = true; 1702 this.$(form).submit(); 1703 } else if (nodeType=='fields' && this.empty(this[nodeType][form][type].dispatchedAjax)) { 1704 // Trigger another blur/typing/etc event 1705 if (this.debug) console.log('Throwing Event'); 1706 this.validationWrapper(id, type); 1707 } 1708 }, //close setValidationStatus 1709 1710 /** 1711 * This function shows the error messages for failed validations by dynamically 1712 * creating a div 1713 * @param {string} Text of the error message to be displayed 1714 */ 1715 showError: function( errorMsg ) { 1716 var settings = validanguage.getFormSettings(this.id); 1717 var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); 1718 if( errorDisplay == null ) { 1719 var formField = document.getElementById(this.id); 1720 var errorDiv = document.createElement('DIV'); 1721 validanguage.insertAfter( errorDiv, formField ); 1722 var innerHTML = '<span id="'+ this.id + settings.errorMsgSpanSuffix+'"> </span>'; 1723 errorDiv.innerHTML = innerHTML; 1724 errorDiv.className = settings.onErrorClassName; 1725 var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); 1726 } else { 1727 var errorDiv = errorDisplay.parentNode; 1728 errorDiv.style.display = 'block'; 1729 errorDiv.className = settings.onErrorClassName; 1730 } 1731 if(validanguage.useLibrary=='scriptaculous') new Effect.Highlight(errorDiv, { startcolor: '#A85F5F', endcolor: '#C0A6A6', restorecolor: '#ddd' }); 1732 errorDisplay.innerHTML = errorMsg; 1733 if (!this.type || (this.type != 'radio' && this.type != 'checkbox')) { 1734 if (!this.className.match(validanguage.settings.failedFieldClassName)) 1735 this.className += ' ' + validanguage.settings.failedFieldClassName; 1736 if (this.className.match(validanguage.settings.passedFieldClassName)) 1737 this.className = this.className.replace(validanguage.settings.passedFieldClassName, ''); 1738 } 1739 1740 //Do we need to add any vd_li items? 1741 if( !settings.showFailedFields ) return; 1742 if( document.getElementById(settings.errorDivId) == null ) { 1743 var errorDiv = document.createElement('DIV'); 1744 errorDiv.id = settings.errorDivId; 1745 document.body.appendChild(errorDiv); 1746 } else { 1747 var errorDiv = document.getElementById(settings.errorDivId); 1748 } 1749 if (document.getElementById(settings.errorListId) == null) { 1750 errorDiv.innerHTML = settings.errorListText + '<br/><ul id="'+settings.errorListId+'"></ul>'; 1751 } 1752 var errorDivInner = errorDiv.innerHTML.toLowerCase(); 1753 errorDivInner = errorDivInner.replace(/"/g,''); //remove quotes for IE weirdness 1754 1755 var errorList = document.getElementById(settings.errorListId); 1756 var listItem = '<li id="'+this.id+settings.errorListItemSuffix+'">'+validanguage.el[this.id].field+'</li>'; 1757 var listItemExists = listItem.toLowerCase(); 1758 listItemExists = listItemExists.replace(/"/g,''); //remove quotes for IE weirdness 1759 1760 if(errorDivInner.indexOf(listItemExists)==-1) errorList.innerHTML += listItem; 1761 document.getElementById(settings.errorDivId).style.display='block'; 1762 }, //close showError 1763 1764 /** 1765 * This function is intended for us as an onsubmit transformation. It replaces the form's 1766 * submit button with the text specified in settings.showSubmitMessageMessage. 1767 * 1768 * @param {Object} validationResult 1769 * @param {Object} failedValidations 1770 */ 1771 showSubmitMessage: function( validationResult, failedValidations ) { 1772 if( validationResult==false ) return; 1773 var settings = validanguage.forms[this.getAttribute("id")].settings; 1774 1775 //first, we need to find the submit button and hide it 1776 var inputs = this.getElementsByTagName('INPUT'); 1777 for( var i=inputs.length-1; i>-1; i-- ) { 1778 if( typeof inputs[i].type!='undefined' && (inputs[i].type=='submit' || inputs[i].type=='image') ) { 1779 validanguage.forms.submitButton = submitButton = inputs[i]; 1780 break; 1781 } 1782 } 1783 submitButton.style.display='none'; 1784 var loadingDiv = document.createElement('DIV'); 1785 loadingDiv.id = settings.showSubmitMessageId; 1786 loadingDiv.innerHTML = settings.showSubmitMessageMessage; 1787 validanguage.insertAfter( loadingDiv, inputs[i] ); 1788 1789 //set a timeout to unhide the submit button after 60 seconds 1790 //in case the request times out and the user needs to resubmit the form 1791 setTimeout( function( ){ validanguage.forms.submitButton.style.display='inline'; }, 60000); 1792 }, //close showSubmitMessage 1793 1794 /** 1795 * This function splits a string into an array using commas as the delimiter, 1796 * while being smart enough to ignore commas appearing inside parenthesis and 1797 * braces. 1798 * 1799 * @param {string} Comma-delimited string 1800 * @return {Array} 1801 */ 1802 smartCommaSplit: function ( str ) { 1803 var openParens = 0; 1804 var openBraces = 0; 1805 var lastSplit = 0; 1806 var returnArray = []; 1807 var len = str.length; 1808 for( var i=0; i<len; i++ ) { 1809 switch (str.charAt(i)) { 1810 case '(': openParens++; break; 1811 case ')': openParens--; break; 1812 case '{': openBraces++; break; 1813 case '}': openBraces--; break; 1814 case ',': 1815 if( openParens==0 && openBraces==0 ){ 1816 returnArray[returnArray.length] = str.substring(lastSplit,i); 1817 lastSplit = ++i; 1818 } 1819 break; 1820 } 1821 } 1822 returnArray[returnArray.length] = str.substring(lastSplit,i); 1823 return returnArray; 1824 }, 1825 1826 /** 1827 * Determines roughly which browser they're using. Defaults to FF for anything 1828 * that isnt IE, Opera, Konqueror or Safari, which assuming the browser is standards-compliant, 1829 * should be good enough for what I'm using it for. Yea, yea, I know.... 1830 */ 1831 sniffBrowser: function() { 1832 //yo... dont hate. 1833 var isIE/*@cc_on=1@*/; 1834 if (isIE) { 1835 this.browser = 'ie'; 1836 var version = parseFloat(navigator.appVersion.split('MSIE')[1]); 1837 if( version < 6 ) this.browser = 'ie5'; 1838 } else if(navigator.appName.indexOf('Opera')!=-1) { 1839 this.browser = 'opera'; 1840 } else if(navigator.vendor.indexOf('Apple')!=-1) { 1841 this.browser = 'safari'; 1842 } else if (navigator.vendor.indexOf('KDE')!=-1) { 1843 this.browser = 'konqueror'; 1844 } else { 1845 this.browser = 'ff'; 1846 } 1847 }, 1848 1849 /** 1850 * This function will strip all the leading and trailing whitespace 1851 * from a form field prior to its being validated. 1852 */ 1853 stripWhitespace: function() { 1854 this.value = this.value.replace(/^\s+|\s+$/g,''); 1855 }, 1856 1857 /** 1858 * This function replaces one subset of text with a different subset 1859 * of text, such as making all uppercase letters, lowercase. 1860 * @param {Array|String} Array of characters to replace 1861 * or either "upper" or "lower" 1862 * @param {Array|String} Array of characters used for replacements 1863 * or either "upper" or "lower" 1864 */ 1865 substituteText: function( find, replaceWith ) { 1866 var lower = ['a','b','c','d','e','f','g','h','i','j','k','l','m', 1867 'n','o','p','q','r','s','t','u','v','w','x','y','z']; 1868 var upper = ['A','B','C','D','E','F','G','H','I','J','K','L','M', 1869 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z']; 1870 1871 if (find === 'lower' || validanguage.empty(find)) { 1872 find = lower; 1873 } else if (find === 'upper') { 1874 find = upper; 1875 } 1876 if (replaceWith === 'upper' || validanguage.empty(replaceWith)) { 1877 replaceWith = upper; 1878 } else if (replaceWith === 'lower') { 1879 replaceWith = lower; 1880 } 1881 var subject = this.value; 1882 1883 // Store the caret position 1884 var pos = validanguage.getCaretPos(this); 1885 1886 // Do our regex replace 1887 for (var i = find.length - 1; i > -1; i--) { 1888 var myreg = new RegExp(find[i], 'g'); 1889 subject = subject.replace(myreg, replaceWith[i]); 1890 } 1891 this.value = subject; 1892 1893 // Restore the caret position 1894 validanguage.setCaretPos(this, pos); 1895 }, //close substituteText 1896 1897 /** 1898 * Transformation supporting 3 major features: toggling visibility, 1899 * changing the values of form fields and adding options to select 1900 * elements. 1901 * @param {Array} toggleArgs Array of objects 1902 */ 1903 toggle: function( toggleArgs ) { 1904 var j = toggleArgs.length; 1905 var settings = validanguage.getFormSettings(this.id); 1906 var formName = validanguage.getFormId(this.id); 1907 for (var i=0; i < j; i++) { 1908 var obj = toggleArgs[i]; 1909 var targets = validanguage.resolveArray(obj.target, 'string'); 1910 var radioExceptionApplies = false; 1911 // If we are running toggle() for a radio button find out if all the other radio buttons in the same group are unchecked, 1912 if (this.nodeName.toLowerCase()=='input' && this.type.toLowerCase() == 'radio') { 1913 var radioButtonChecked = false; 1914 var radios = false; 1915 for (var k = document.forms.length - 1; k > -1; k--) { 1916 if (document.forms[k].id == formName) { 1917 radios = document.forms[k][this.name]; 1918 break; 1919 } 1920 } 1921 for (var k = radios.length - 1; k > -1; k--) if (radios[k].checked) radioButtonChecked = true; 1922 if (!radioButtonChecked) radioExceptionApplies=true; 1923 } 1924 1925 // toggle visibility 1926 if (obj.toggle) { 1927 var visibleMet = (obj.toggle.visible) ? validanguage.toggleCriteriaMet(this, obj.toggle.visible, settings) : false; 1928 var hiddenMet = (obj.toggle.hidden) ? validanguage.toggleCriteriaMet(this, obj.toggle.hidden, settings) : false; 1929 for (var k = targets.length - 1; k > -1; k--) { 1930 if (visibleMet && !radioExceptionApplies) validanguage.toggleDisplay(targets[k], ''); 1931 if (hiddenMet || (settings.toggleVisibilityDefaultsToHidden && !visibleMet)) validanguage.toggleDisplay(targets[k], 'none'); 1932 } 1933 } // close toggle visibility 1934 1935 // toggleAttribute 1936 if (typeof obj.toggleAttribute != 'undefined') { 1937 for (var k = targets.length - 1; k > -1; k--) { 1938 var attribute = obj.toggleAttribute.attribute; 1939 var value = obj.toggleAttribute.value; 1940 if (obj.toggleAttribute.condition == 'checked' && this.checked == true) 1941 validanguage.$(targets[k])[attribute] = value; 1942 else if (obj.toggleAttribute.condition == 'unchecked' && this.checked == false) 1943 validanguage.$(targets[k])[attribute] = value; 1944 else if ((this.value) && (obj.toggleAttribute.condition == this.value)) 1945 validanguage.$(targets[k])[attribute] = value ; 1946 } 1947 } // close toggleAttribute 1948 1949 // value control 1950 if (typeof obj.values != 'undefined') { 1951 for (var k = targets.length - 1; k > -1; k--) { 1952 if (typeof obj.values.checked != 'undefined' && this.checked==true) validanguage.$(targets[k]).value=obj.values.checked; 1953 else if (typeof obj.values.unchecked != 'undefined' && this.checked==false) validanguage.$(targets[k]).value=obj.values.unchecked; 1954 else if (typeof obj.values[this.value] != 'undefined') validanguage.$(targets[k]).value=obj.values[this.value]; 1955 } 1956 } // close toggle value 1957 1958 // dynamic selects 1959 if (typeof obj.dynamicSelect != 'undefined') { 1960 // store the value 1961 var newValue = this.value; 1962 if (typeof validanguage.el[this.id]['value']!= 'undefined' && validanguage.el[this.id]['value']==newValue) return; 1963 var sel2 = validanguage.$(targets[0]); // dynamicSelect only supports 1 target 1964 for (var sel1Key in obj.dynamicSelect) { 1965 if (typeof obj.dynamicSelect[sel1Key] == 'object' ) { 1966 var sel1Val = obj.dynamicSelect[sel1Key]; 1967 if (sel1Key == this.value) { 1968 // remove the existing options 1969 while (sel2.options.length > 0) { sel2.remove(0); } 1970 for (var sel2Key in sel1Val) { 1971 if (sel2Key == '_default') continue; 1972 var opt = document.createElement('option'); 1973 opt.value = sel2Key; 1974 opt.text = sel1Val[sel2Key]; 1975 sel2.options.add(opt); 1976 } 1977 if (typeof sel1Val['_default'] != 'undefined') sel2.value = sel1Val['_default']; 1978 } 1979 } 1980 } 1981 validanguage.el[this.id]['value'] = newValue; 1982 } // close dynamicSelect 1983 } 1984 }, //close toggle 1985 1986 /** 1987 * Determines whether the criteria used by the toggle function 1988 * has been met 1989 * @param {Object} field 1990 * @param {string} criteria 1991 * @param {Object} settings object 1992 */ 1993 toggleCriteriaMet: function( field, criteria, settings ) { 1994 if (criteria == 'checked') { 1995 return !!(field.checked); 1996 } else if (criteria == 'unchecked') { 1997 return !(field.checked); 1998 } else if (criteria == 'empty') { 1999 return !!(validanguage.inArray(field.value, settings.emptyOptionElements)); 2000 } else if (criteria == 'notEmpty') { 2001 return !(validanguage.inArray(field.value, settings.emptyOptionElements)); 2002 } else { 2003 return !!(field.value == criteria); 2004 } 2005 }, //close toggleCriteriaMet 2006 2007 /** 2008 * Function used to hide or show a node. This function will also automatically disable or enable 2009 * any form fields contained within the passed node. 2010 * @param {Object} nodeId ID of the node to hide/show 2011 * @param {Object} visibility (optional) Pass either 'none' or '' to hide/show. Or leave blank to toggle 2012 */ 2013 toggleDisplay: function( nodeId, visibility ) { 2014 var node = validanguage.$(nodeId); 2015 var nodeName = node.nodeName.toLowerCase(); 2016 if (visibility==null||visibility==undefined) visibility = (node.style.display=='none') ? '' : 'none'; 2017 disabledBool = (visibility=='none') ? true : false; 2018 2019 // show/hide the passed node 2020 node.style.display = visibility; 2021 2022 // disable/enable any form fields contained within the node 2023 if (nodeName == 'input' || nodeName == 'textarea' || nodeName == 'select') { 2024 node.disabled = disabledBool; 2025 return; 2026 } 2027 var allInputs = node.getElementsByTagName('input'); 2028 var allTextareas = node.getElementsByTagName('textarea'); 2029 var allSelect = node.getElementsByTagName('select'); 2030 var allObjects = this.concatCollection(allInputs, allTextareas); 2031 var allObjects = this.concatCollection(allObjects, allSelect); 2032 for (var i = allObjects.length - 1; i > -1; i--) { 2033 allObjects[i].disabled = disabledBool; 2034 } 2035 }, //close toggleDisplay 2036 2037 /** 2038 * Transformation function which strips all leading and trailing 2039 * whitespace from a form field prior to validation. 2040 */ 2041 trim: function() { 2042 this.value = this.value.replace(/^\s+|\s+$/g,''); 2043 }, 2044 2045 /** 2046 * Validates that a field does not contain any of the invalid characters 2047 * listed in the characters validation rules. 2048 * 2049 * @param {string} text 2050 */ 2051 validateCharacters: function( text ) { 2052 var id = this.id; 2053 var mode = validanguage.el[id].characters.mode; 2054 var expression = validanguage.el[id].characters.characterExpression; 2055 switch(mode) { 2056 case 'allow': 2057 outerLoop: 2058 for( var i=text.length-1;i>-1;i--) { 2059 innerLoop: 2060 for (var j=expression.length-1; j>-1; j--) { 2061 if(expression.charAt(j)==text.charAt(i)) continue outerLoop; 2062 } 2063 //if we got here, they entered a disallowed character 2064 return false; 2065 } 2066 break; 2067 case 'deny': 2068 outerLoop: 2069 for( var i=text.length-1;i>-1;i--) { 2070 innerLoop: 2071 for (var j=expression.length-1; j>-1; j--) { 2072 if(expression.charAt(j)==text.charAt(i)) return false; 2073 } 2074 } 2075 break; 2076 } 2077 return true; 2078 }, //close validateCharacters 2079 2080 /** 2081 * Validates that a valid credit card number has been supplied 2082 * @param {string} text 2083 * @param {array} cardTypes 2084 * @param {boolean} testChecksum Pass false to skip the luhn checksum test 2085 */ 2086 validateCreditCard: function (text, cardTypes, testChecksum) { 2087 if (validanguage.empty(cardTypes)) cardTypes = ['amex','disc','mc','visa']; 2088 // Strip any non-digits 2089 var text=text.replace(/\D/g,''); 2090 2091 var cards = { 2092 'amex' : '^3[4|7]\\d{13}$', 2093 'bankcard' : '^56(10\\d\\d|022[1-5])\\d{10}$', 2094 'diners' : '^(?:3(0[0-5]|[68]\\d)\\d{11})|(?:5[1-5]\\d{14})$', 2095 'disc' : '^(?:6011|650\\d)\\d{12}$', 2096 'electron' : '^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$', 2097 'enroute' : '^2(?:014|149)\\d{11}$', 2098 'jcb' : '^(3\\d{4}|2100|1800)\\d{11}$', 2099 'maestro' : '^(?:5020|6\\d{3})\\d{12}$', 2100 'mc' : '^5[1-5]\\d{14}$', 2101 'solo' : '^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$', 2102 'switch' : '^(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)$', 2103 'visa' : '^4\\d{12}(\\d{3})?$', 2104 'voyager' : '^8699[0-9]{11}$' 2105 }; 2106 var validCard = false; 2107 for (var i = cardTypes.length; i--; i > -1) { 2108 validCard = validanguage.validateRegex(text, { expression: cards[cardTypes[i]] }); 2109 if (validCard) break; 2110 } 2111 if (!validCard) return false; 2112 if (testChecksum===false) return true; 2113 2114 /** Run the luhn checksum test 2115 * Luhn algorithm number checker - (c) 2005-2008 shaman - www.planzero.org 2116 */ 2117 2118 // Set the string length and parity 2119 var number_length=text.length; 2120 var parity=number_length % 2; 2121 2122 // Loop through each digit and do the maths 2123 var total=0; 2124 for (var i=0; i < number_length; i++) { 2125 var digit=text.charAt(i); 2126 // Multiply alternate digits by two 2127 if (i % 2 == parity) { 2128 digit=digit * 2; 2129 // If the sum is two digits, add them together (in effect) 2130 if (digit > 9) { 2131 digit=digit - 9; 2132 } 2133 } 2134 // Total up the digits 2135 total = total + parseInt(digit); 2136 } 2137 2138 // If the total mod 10 equals 0, the number is valid 2139 if (total % 10 == 0) { 2140 return true; 2141 } else { 2142 return false; 2143 } 2144 }, //close validateCreditCard 2145 2146 /** 2147 * Validates that a valid date is supplied and is entered in the correct format. 2148 * The format is specified by the following arguments<br/> 2149 * @param {String} text to be validated<br/> 2150 * @param {Object} Options object containing any of the following options<br/> 2151 * dateOrder: {String} This must be either 'ymd','mdy','dmy','myd','ydm', or 'dym<br/> 2152 * allowedDelimiters: {String}. A string containing the list of delimiters which are allowed. 2153 * Example: './-'<br/> 2154 * twoDigitYearsAllowed: {Boolean}. Is a 2-digit year valid<br/> 2155 * oneDigitDaysAndMonthsAllowed: {Boolean}. Is a 1-digit month or a 1-digit day valid<br/> 2156 * maxYear: {Integer}. Years greater than maxYear will be treated as invalid. Defaults to 15 years from today<br/> 2157 * minYear: {Integer}. Years less than minYear will be treated as invalid. Defaults to 1900<br/> 2158 * rejectDatesInTheFuture: {Boolean}. Are dates in the future valid? rejectDatesInTheFuture defaults to false<br/> 2159 * rejectDatesInThePast: {Boolean}. Are dates in the past valid? rejectDatesInThePast defaults to false<br/> 2160 */ 2161 validateDate: function( text, options ) { 2162 //Set default values 2163 options = validanguage.getDateTimeDefaultOptions(options, {dateOrder: 'mdy'} ); 2164 2165 //Loop thru the allowedDelimiters to start building our regex and figure out which one was actually used 2166 var delimiterUsed; 2167 var delimiterRegex = '('; 2168 for( var i=options.allowedDelimiters.length-1;i>-1;i--) { 2169 delimiterRegex += '\\' + options.allowedDelimiters.charAt(i); 2170 if (i>0) delimiterRegex += '|'; 2171 if (text.indexOf(options.allowedDelimiters.charAt(i)) > -1) { 2172 delimiterUsed = options.allowedDelimiters.charAt(i); 2173 } 2174 } 2175 delimiterRegex += ')'; 2176 if( delimiterUsed==null ) return false; //no delimiter was used 2177 var parts = text.split(delimiterUsed); 2178 if( parts.length!=3 ) return false; //they used more than one delimiter or didnt give us a valid date 2179 2180 //Next we need to build the regex to validate the date comprises only integers and delimiters 2181 var regex = '^'; 2182 for(var j=0; j<3; j++) { 2183 switch( options.dateOrder.charAt(j) ) { 2184 case 'y': 2185 var num = (options.twoDigitYearsAllowed) ? '{2,4}' : '{4}'; 2186 regex += '\\d' + num; 2187 break; 2188 case 'm': 2189 case 'd': 2190 var num = (options.oneDigitDaysAndMonthsAllowed) ? '{1,2}' : '{2}'; 2191 regex += '\\d' + num; 2192 break; 2193 } 2194 if(j<2) regex += delimiterRegex; 2195 } 2196 regex += '$'; 2197 //Run the regex 2198 var reg = new RegExp( regex ); 2199 var thisMatch = reg.exec(text); 2200 if (thisMatch == null) return false; 2201 2202 //grab our dates 2203 var year = parts[options.dateOrder.indexOf('y')]; 2204 var month = parts[options.dateOrder.indexOf('m')]; 2205 var day = parts[options.dateOrder.indexOf('d')]; 2206 2207 // Verify the year isnt 3-digits long to account for me being lazy in the regex check above 2208 if( year.length==3 ) return false; 2209 2210 //Make sure the year is in bounds 2211 if( (year < options.minYear && year.length==4) || (year > options.maxYear) ) return false; 2212 2213 //Next we check that the date actually exists, to rule out stuff like "12/32/1976" 2214 if( !validanguage.validateDateExists(year,month,day) ) return false; 2215 2216 if( options.rejectDatesInTheFuture || options.rejectDatesInThePast ){ 2217 var now = new Date(); 2218 var then = new Date(); 2219 then.setDate(day); 2220 then.setMonth(--month); // January = 0 2221 then.setFullYear(year); 2222 if( (options.rejectDatesInTheFuture && then > now) || (options.rejectDatesInThePast && then < now) ) return false; 2223 } 2224 return true; 2225 }, //close validateDate 2226 2227 /** 2228 * Helper function to verify a date actually exists. Used to reject values 2229 * such as "12/35/2009" 2230 * @param {integer} year, preferably 4-digit 2231 * @param {integer} month 2232 * @param {integer} day 2233 * @return {Boolean} 2234 */ 2235 validateDateExists: function(year, month, day) { 2236 if(year.length==2) { 2237 var prefix = (year > 20 ) ? '19' : '20'; 2238 year = prefix+year.toString(); 2239 } 2240 if( month.charAt(0)=='0' ) month = month.substr(1,1); 2241 if( day.charAt(0)=='0' ) day = day.substr(1,1); 2242 if( month < 0 || month > 12 ) return false; 2243 switch( month.toString() ) { 2244 case '4': 2245 case '6': 2246 case '9': 2247 case '11': 2248 var maxDay = 30; 2249 break; 2250 case '2': 2251 var maxDay = ((year % 4 == 0) && ( (!(year % 100 == 0)) || (year % 400 == 0)) ) ? 29 : 28; 2252 break; 2253 default: 2254 var maxDay = 31; 2255 } 2256 if( day < 0 || day > maxDay ) return false; 2257 return true; 2258 }, //close validateDateExists 2259 2260 /** 2261 * Validates an email address 2262 */ 2263 validateEmail: function( text ) { 2264 if(! text.match(/^([a-zA-Z0-9]+[a-zA-Z0-9._%-\+]*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4})$/) ) 2265 return false; 2266 else return true; 2267 }, //close validateEmail 2268 2269 /** 2270 * Calls the validationWrapper function on the passed form ID 2271 * @param {String|Form Node} Form node or its ID 2272 * @return {Object} Object containing 2 elements: 2273 * Object.result is a boolean to indicate whether any fields in the form failed validation 2274 * Object.failedValidations will contain info detailing any form fields with failed validations 2275 */ 2276 validateForm: function(form) { 2277 form = this.$(form); 2278 if (form==undefined) form = document.forms[0]; 2279 return this.validationWrapper( form, 'validateForm' ); 2280 }, //close validateForm 2281 2282 /** 2283 * Validates that a field contains a valid IPv4 address 2284 */ 2285 validateIP: function( text ) { 2286 var bytes = text.split('.'); 2287 if (bytes.length == 4) { 2288 for (var i=bytes.length-1; i> -1; i--) { 2289 if (!(validanguage.validateNumeric(bytes[i]) && bytes[i] >= 0 && bytes[i] <= 255)) { 2290 return false; 2291 } 2292 } 2293 return true; 2294 } 2295 return false; 2296 }, //close validateIP 2297 2298 /** 2299 * Function to suppress the keys specified in validanguage.el.characters 2300 * from being entered into a textarea or text box. 2301 * 2302 * This function must be forked to fetch the keyCode 2303 * and differentiate between control and non-control characters. 2304 * For example: in Mozilla both the delete key and . equate to 46. 2305 * We dont fork based on supported functions because IE and Moz event models 2306 * for keyCodes/charCodes are different and confusing, so we're 2307 * better off sniffing the browser. 2308 * 2309 * @param {Event Object} e 2310 */ 2311 validateKeypress: function(e) { 2312 var evt = e || window.evt; 2313 var $this = evt.currentTarget || evt.srcElement; 2314 var id = $this.id; 2315 var formName = validanguage.getFormId(id); 2316 var settings = validanguage.getFormSettings(id); 2317 2318 if (validanguage.browser == 'ie' || validanguage.browser == 'opera') { 2319 //branch for IE and opera 2320 //we dont have to worry about noncontrol keys since they dont fire keypress events in IE 2321 var charCode = evt.keyCode; 2322 if (((charCode == 16) && (evt.shiftKey)) || (evt.ctrlKey)) 2323 return true; //prevents firing on ctrl key in opera 2324 2325 if (evt.which == 0) return true; 2326 2327 } else { 2328 //branch for Mozilla 2329 if ((evt.charCode == 0) || (evt.ctrlKey)) 2330 return true; 2331 //return true for all control keys and if control is held down 2332 2333 charCode = evt.which; //capture charCode of non-control keys 2334 } 2335 2336 charCode += ';'; 2337 searchString = new String(validanguage.el[id].characters.expression); 2338 var mode = validanguage.el[id].characters.mode; 2339 2340 if ( ( (searchString.search(charCode) != -1 ) && (mode == 'allow') ) || 2341 ( (searchString.search(charCode) == -1 ) && (mode == 'deny') ) ){ 2342 return true; 2343 } else { 2344 $this.style.backgroundColor = settings.validationErrorColor; 2345 setTimeout("document.getElementById('" + id + "').style.backgroundColor = validanguage.forms['"+formName+"'].settings.normalTextboxColor",validanguage.forms[formName].settings.timeDelay); 2346 evt.returnValue = false; //IE 2347 if(evt.preventDefault) evt.preventDefault(); //Everyone else 2348 return false; 2349 } 2350 }, //close validateKeypress 2351 2352 /** 2353 * Validates that a field is less than maxlength characters long 2354 * @param {string} text 2355 */ 2356 validateMaxlength: function( text, max ) { 2357 var id = this.id; 2358 var maxlength = (validanguage.empty(max)) ? validanguage.el[id].maxlength : max; 2359 if(text.length > maxlength) 2360 return false; 2361 else return true; 2362 }, //close validateMaxlength 2363 2364 /** 2365 * Validates that a field is greater than minlength characters long 2366 * @param {string} text 2367 */ 2368 validateMinlength: function( text, min ) { 2369 var id = this.id; 2370 var minlength = (validanguage.empty(min)) ? validanguage.el[id].minlength : min; 2371 if(text.length < minlength) 2372 return false; 2373 else return true; 2374 }, //close validateMinlength 2375 2376 /** 2377 * Validates that a field is numeric 2378 * @param {string} text 2379 */ 2380 validateNumeric: function( text ) { 2381 if(! text.match(/^\d+$/) ) 2382 return false; 2383 else return true; 2384 }, //close validateNumeric 2385 2386 /** 2387 * Validates whether a password is adequately secure. 2388 * Additionally, this function can display a password 2389 * strength meter, based on the same, or different, 2390 * criteria than whether or not it validates. 2391 * With default arguments, a password must contain at 2392 * least one letter and one number and be 6 characters 2393 * long, but you can easily make the requirements more 2394 * stringent.<br/> 2395 * @param {String} text 2396 * @param {Object} Argument object. The following options 2397 * are supported:<br/> 2398 * args.minLength: Minlength for the password<br/> 2399 * args.minStrength: Minimum strength for the password 2400 * to validate. This is a number from 1 to 4 to indicate 2401 * how many character types it must have.<br/> 2402 * args.mustMatch: Array of character types which password 2403 * must have to validate. For example, to require all 2404 * 4 you would use args.mustMatch = ['hasUpper','hasLower', 2405 * 'hasDigit','hasSpecial']<br/> 2406 * args.strong: Used for password meter. Array of numbers 2407 * determining what qualifies as a "strong" password. 2408 * For example: args.strong = [4] // All 4 char types<br/> 2409 * args.medium: Used for password meter. Array of numbers 2410 * determining what qualifies as a "medium" password. 2411 * For example: args.medium = [2,3] // 2 or 3 char types 2412 * If a password doesn't qualify as strong or medium, then 2413 * it defaults to weak.<br/> 2414 */ 2415 validatePasswordStrength: function(text, args) { 2416 if (!args) args = {}; 2417 var minLength = args.minLength || 6; 2418 var minStrength = args.minStrength || 2; 2419 var strong = args.strong || [4]; 2420 var medium = args.medium || [2,3]; 2421 var mustMatch = args.mustMatch || ['hasDigit']; 2422 2423 var hasDigit = text.match(/\d/); 2424 var hasUpper = text.match(/[A-Z]/); 2425 var hasLower = text.match(/[a-z]/); 2426 var hasSpecial = text.match(/[\`|\~|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\}|\]|\\|\|]|\;|\:|\'|\"|\,|\<|\.|\>|\/|\?/); 2427 2428 var strength = 0; 2429 if (hasDigit) strength++; 2430 if (hasUpper) strength++; 2431 if (hasLower) strength++; 2432 if (hasSpecial) strength++; 2433 if (text.length < minLength) strength = 0; 2434 2435 var ps = document.getElementById('passwordStrength'); 2436 if (ps) { 2437 if (validanguage.inArray(strength, strong)) { 2438 var strengthLevel = 'Strong'; 2439 } else if (validanguage.inArray(strength, medium)) { 2440 var strengthLevel = 'Medium'; 2441 } else { 2442 var strengthLevel = 'Weak'; 2443 } 2444 var msg = '<span class="passwordStrengthMsg">Password Strength: ' + strengthLevel + '</span>'; 2445 msg += '<br/><div class="passwordClass"><div class="passwordClass' + strengthLevel + '"> </div></div><br/>'; 2446 ps.innerHTML = msg; 2447 } 2448 2449 for (var i=mustMatch.length-1; i>-1; i--) { 2450 eval('if (!'+mustMatch[i]+') strength=0;'); 2451 } 2452 2453 if (strength >= minStrength) { 2454 return true; 2455 } else { 2456 return false; 2457 } 2458 }, //close validatePasswordStrength 2459 2460 /** 2461 * Validates the element against a user-defined regex stored in 2462 * validanguage.el[id].regex. Modifiers are supported by supplying 2463 * them in obj within the regex, or, *if obj is a string*, you can 2464 * pass the modifiers in obj.modifiers. 2465 * 2466 * @param {string} text 2467 * @param {object} optional object containing regex settings. 2468 * Supports "errorOnMatch" and "modifiers" 2469 */ 2470 validateRegex: function( text, obj ) { 2471 var id = this.id; 2472 var regexObj = (obj && obj.expression) ? obj : validanguage.el[id].regex; 2473 if(typeof regexObj.modifiers=='undefined') regexObj.modifiers=''; 2474 if(typeof regexObj.errorOnMatch=='undefined') regexObj.errorOnMatch=false; 2475 var myreg = (typeof regexObj.expression=='string') ? new RegExp(regexObj.expression, regexObj.modifiers) : regexObj.expression; 2476 var thisMatch = myreg.exec(text); 2477 if (thisMatch == null) { //no match 2478 var returnStatus = (regexObj.errorOnMatch==false||regexObj.errorOnMatch=='false') ? false : true; 2479 } else { //match 2480 var returnStatus = (regexObj.errorOnMatch==false||regexObj.errorOnMatch=='false') ? true : false; 2481 } 2482 return returnStatus; 2483 }, //close validateRegex 2484 2485 /*** 2486 * Validates whether or not an element has been filled out, 2487 * selected or checked. This function is a wrapper which 2488 * calls validateRequiredChild() 2489 * 2490 * @param {string} text 2491 */ 2492 validateRequired: function( unused ) { 2493 var id = this.id; 2494 if(typeof validanguage.el[id].requiredAlternatives == 'undefined') { 2495 var alternatives = [ id ]; 2496 } else { 2497 var alternatives = validanguage.resolveArray(validanguage.el[id].requiredAlternatives,'string'); 2498 alternatives[alternatives.length] = id; 2499 } 2500 for( var i=alternatives.length-1; i>-1; i--) { 2501 id = alternatives[i]; 2502 var elem = validanguage.$(id); 2503 var text = elem.value; 2504 var notEmpty = validanguage.validateRequiredChild.call(elem, text); 2505 if(notEmpty==true) return true; //if this element or one of its alternatives is not empty, it validates 2506 } 2507 return false; 2508 }, 2509 2510 /** 2511 * This function calls the validateRequired method on the "master/required" 2512 * form field when the "alternative" form field is clicked and then calls 2513 * the appropriate onerorr/onsuccess function. 2514 */ 2515 validateRequiredAlternatives: function(e) { 2516 var evt = e || window.evt; 2517 var $this = evt.currentTarget || evt.srcElement; 2518 var id = $this.id; 2519 var parentId = validanguage.requiredAlternatives[id].parentId; 2520 var onsuccess = validanguage.requiredAlternatives[id].onsuccess; 2521 var onerror = validanguage.requiredAlternatives[id].onerror; 2522 var parent = validanguage.$(parentId); 2523 if (validanguage.validateRequired.call(parent) == true) { 2524 successHandlers = validanguage.resolveArray(onsuccess, 'function'); 2525 for (var m = successHandlers.length - 1; m > -1; m--) { 2526 successHandlers[m].call(parent); 2527 } 2528 } else { 2529 errorHandlers = validanguage.resolveArray(onerror, 'function'); 2530 for (var m = errorHandlers.length - 1; m > -1; m--) { 2531 errorHandlers[m].call(parent, validanguage.requiredAlternatives[id].errorMsg); 2532 } 2533 } 2534 }, 2535 2536 /*** 2537 * Child function called by validateRequired to validates whether or not an element has been filled out, 2538 * selected or checked. validateRequiredChild is required to add support for the requiredAlternatives 2539 * array. 2540 * 2541 * @param {string} text 2542 */ 2543 validateRequiredChild: function( text ) { 2544 var type = ( typeof this.type != 'undefined' ) ? this.type : null; 2545 if( this.nodeName.toLowerCase() == 'textarea' ) type = 'text'; 2546 if( this.nodeName.toLowerCase() == 'select' ) type = 'select'; 2547 2548 switch( type ) { 2549 case 'checkbox': 2550 if( this.checked == false ){ 2551 return false; 2552 } 2553 break; 2554 2555 case 'radio': 2556 var formId = validanguage.getFormId(this.id); 2557 var radios = (typeof formId == 'number') ? document.forms[formId][this.name] : validanguage.$(formId)[this.name]; 2558 for( var i=radios.length-1; i>-1;i--) { 2559 if (radios[i].checked == true) return true; 2560 } 2561 return false; 2562 break; 2563 2564 case 'text': 2565 case 'password': 2566 case 'file': 2567 if(validanguage.empty(text)) { 2568 return false; 2569 } 2570 break; 2571 2572 case 'select': 2573 if (validanguage.empty(text)) { 2574 return false; 2575 } 2576 settings = validanguage.getFormSettings(this.id); 2577 for( var i=settings.emptyOptionElements.length-1; i>-1; i-- ) { 2578 //see if they have selected any of the "empty" option elements 2579 if( text==settings.emptyOptionElements[i]) return false; 2580 } 2581 break; 2582 } //close switch 2583 return true; 2584 }, 2585 2586 /** 2587 * Validates that the entered text is a valid timestamp. The options object supports all the options listed 2588 * under the validateDate function as well as the following additional onces<br/> 2589 * @param {String} text to be validated<br/> 2590 * @param {Object} Options object containing any of the following options:<br/> 2591 * timeIsRequired: {Boolean} Is a date which is provided without an accompanying time considered a valid timestamp? 2592 * timeIsRequired defaults to false.<br/> 2593 * timeUnits: {String} A string containing a list of all the time units which are allowed to be entered in the timestamp. 2594 * These may include any of the following: h for hours, m for minutes, s for seconds, u for microseconds, 2595 * and t for timezone. Example: "hms" for hours, minutes and seconds or "hmsut" for all 5 units. 2596 * Defaults to "hms"<br/> 2597 * microsecondPrecision {Integer} Indicates the supported number of decimal places for the microseconds. Defaults to 6. 2598 * 2599 */ 2600 validateTimestamp: function( text, options ) { 2601 // Set default options 2602 options = validanguage.getDateTimeDefaultOptions(options, {dateOrder: 'ymd'} ); 2603 2604 // Check the date portion of the timestamp 2605 var pos = text.indexOf(' '); 2606 var date = ( pos == -1 ) ? text : text.substr(0,pos); 2607 if( !validanguage.validateDate(date, options) ) return false; 2608 2609 // Check whether they provided a time 2610 if( pos != -1 ) { 2611 var time = text.substring(++pos); 2612 } else { 2613 if( !options.timeIsRequired ) return true; 2614 if( options.timeIsRequired ) return false; 2615 } 2616 2617 // Build the regex to validate the time 2618 var regex = '^\\d{1,2}:\\d{1,2}'; 2619 if( options.timeUnits.indexOf('s')!=-1 ) regex += '(:\\d{1,2}'; 2620 if( options.timeUnits.indexOf('u')!=-1 ) regex += '(\\.\\d{1,'+options.microsecondPrecision+'})?'; 2621 if( options.timeUnits.indexOf('s')!=-1 ) regex += ')?'; 2622 if( options.timeUnits.indexOf('t')!=-1 ) { 2623 regex += '( ?[\\+|\\-]{1,1}(\\d|0\\d|10|11|12|13)(\\:(00|30))?)?'; 2624 } 2625 regex += '$'; 2626 2627 //Run the regex 2628 var reg = new RegExp( regex ); 2629 var thisMatch = reg.exec(time); 2630 if (thisMatch == null) return false; 2631 2632 //Finally we need to make sure that the hours, minutes and seconds which were entered are valid 2633 var timeparts = time.split(':'); 2634 if( timeparts[0] > 23) return false; 2635 if( timeparts[1] > 59) return false; 2636 if( timeparts.length > 2){ 2637 var seconds = timeparts[2].substr(0,2); 2638 if(seconds > 59) return false; 2639 } 2640 return true; 2641 }, //close validateTimestamp 2642 2643 /** 2644 * Validates a URL 2645 * 2646 * @param {string} text 2647 */ 2648 validateURL: function( text ) { 2649 if(! text.match(/^((([hH][tT][tT][pP][sS]?|[fF][tT][pP])\:\/\/)?([\w\.\-]+(\:[\w\.\&%\$\-]+)*@)?((([^\s\(\)\<\>\\\"\.\[\]\,@;:]+)(\.[^\s\(\)\<\>\\\"\.\[\]\,@;:]+)*(\.[a-zA-Z]{2,4}))|((([01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}([01]?\d{1,2}|2[0-4]\d|25[0-5])))(\b\:(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)\b)?((\/[^\/][\w\.\,\?\'\\\/\+&%\$#\=~_\-@]*)*[^\.\,\?\"\'\(\)\[\]!;<>{}\s\x7F-\xFF])?)$/) ) 2650 return false; 2651 else return true; 2652 }, 2653 2654 /** 2655 * Validates that a US Phone number is entered 2656 * 2657 * @param {string} text 2658 */ 2659 validateUSPhoneNumber: function( text ) { 2660 if(! text.match(/^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$/) ) 2661 return false; 2662 else return true; 2663 }, 2664 2665 /** 2666 * Validates that a US Social Security Number is entered 2667 * 2668 * @param {string} text 2669 */ 2670 validateUSSSN: function( text ) { 2671 if(! text.match(/^\d{3}( |-|.){0,1}\d{2}( |-|.){0,1}\d{4}$/) ) 2672 return false; 2673 else return true; 2674 }, 2675 2676 /** 2677 * Validates that a US zip code was entered 2678 * 2679 * @param {string} text 2680 */ 2681 validateUSZipCode: function( text ) { 2682 if(! text.match(/^\d{5}( |-|.){0,1}(\d{4})?$/) ) 2683 return false; 2684 else return true; 2685 }, 2686 2687 /** 2688 * This is a wrapper for all the validation event handlers assigned to both 2689 * form field element and to forms themselves. This function also calls any 2690 * transformations before running the validations. 2691 * 2692 * We use a wrapper for a number of reasons, including exiting validation 2693 * early as soon as a single validation function fails. 2694 * 2695 * @param {Event|String} Event object or Node ID 2696 * @param {String} Custom Event 2697 * @param {Number} ajaxLookupIndex 2698 * 2699 */ 2700 validationWrapper: function(e, customEvent) { 2701 var calledManually = false; 2702 if (validanguage.inArray(customEvent, validanguage.supportedEvents)) { 2703 // check for manually thrown events 2704 var $this = validanguage.$(e); 2705 var type = customEvent; 2706 var id = e; 2707 var calledManually = true; 2708 } else if (customEvent=='validateForm') { 2709 // validate form event 2710 var $this = e; 2711 var form = $this.getAttribute('id'); 2712 var id = form; 2713 var type = 'submit'; 2714 } else { 2715 // standard event handlers 2716 var evt = e || window.evt; 2717 var $this = evt.currentTarget || evt.srcElement; 2718 var type = evt.type; 2719 if (type == 'submit') { 2720 var form = $this.getAttribute('id'); 2721 var id = form; 2722 } else { 2723 var id = $this.id; 2724 var form = validanguage.getFormId(id); 2725 } 2726 if (customEvent=='typingTimeout') { 2727 if( validanguage.typingDelay[id] ) window.clearTimeout(validanguage.typingDelay[id]); 2728 eval("validanguage.typingDelay[id] = window.setTimeout(\"validanguage.validationWrapper('"+id+"', 'typing')\", validanguage.settings.typingDelay );"); 2729 return true; 2730 } 2731 } 2732 2733 // Bail out early if the parent form is marked as disabled 2734 if( typeof validanguage.el[form] != 'undefined' && typeof validanguage.el[form].disabled != 'undefined' && validanguage.el[form].disabled == true ) return true; 2735 if (validanguage.forcedSubmission) return true; 2736 var validations = (type=='submit') ? validanguage.forms[form].validations : validanguage.el[id].handlers[type]; 2737 var i=validations.length; 2738 var failedValidations = {}; //key value pair of id => validation 2739 2740 // Prep the dispatchedAjax and failedValidations vars 2741 if (!validanguage.ajaxLookup[id]) validanguage.ajaxLookup[id] = [ 1 ]; 2742 var ajaxLocation = (type=='submit') ? 'forms' : 'fields'; 2743 2744 if (!validanguage[ajaxLocation][id]) validanguage[ajaxLocation][id] = {}; 2745 if (!validanguage[ajaxLocation][id][type]) validanguage[ajaxLocation][id][type] = {}; 2746 validanguage[ajaxLocation][id][type].dispatchedAjax = {}; 2747 if (typeof validanguage[ajaxLocation][id][type].failedValidations == 'undefined') 2748 validanguage[ajaxLocation][id][type].failedValidations = {}; 2749 2750 // Handle re-validating a field when a prior ajax validation still has not returned 2751 // by resetting failedValidations and aborting the pending ajax requests 2752 if (!calledManually && !validanguage.empty(validanguage[ajaxLocation][id][type].failedValidations)) { 2753 validanguage[ajaxLocation][id][type].failedValidations = {}; 2754 } 2755 2756 if(type=='submit') { 2757 2758 if (validanguage.empty(validanguage[ajaxLocation][form][type].failedValidations)) { 2759 2760 outerLoop: 2761 for(var j=0; j<i; j++) { 2762 if( typeof validations[j]=='undefined' || validations[j]==999) continue outerLoop; //skip deativated validations and transformations 2763 id = validations[j].element.id; //reassign $this and id to the form field in question 2764 var $this = validations[j].element; 2765 if( (typeof failedValidations[id] != 'undefined') || 2766 (typeof $this.disabled != 'undefined' && $this.disabled==true) || 2767 (typeof validanguage.el[id].disabled != 'undefined' && validanguage.el[id].disabled==true) 2768 ) { 2769 continue outerLoop; //skip disabled fields or fields that already flunked 2770 } 2771 if( typeof validanguage.el[id].failed != 'undefined' && validanguage.el[id].failed==true) { 2772 failedValidations[id] = { failed: true, field: validanguage.el[id].field }; 2773 continue outerLoop; //handle fields manually marked as invalid 2774 } 2775 var validOptionalField = !!(typeof validanguage.el[id].required != 'undefined' && (validanguage.el[id].required==false||validanguage.el[id].required=='false') && 2776 !$this.value.match(/[^\s]/)); 2777 2778 var validationsCounter = validations[j].validationsCounter; 2779 var validation = validanguage.el[id].validations[validationsCounter]; 2780 var funcs = validanguage.resolveArray(validation.name, 'function'); 2781 innerLoop: 2782 for (var m=funcs.length-1; m>-1; m--) { 2783 if (typeof failedValidations[id] != 'undefined') continue innerLoop; //this field already flunked 2784 2785 //handle ajax validations 2786 if (validation.isAjax && !validOptionalField) { 2787 2788 if (!validanguage.ajaxLookup[id]) validanguage.ajaxLookup[id] = [ 1 ]; 2789 var dispatchAjax = true; 2790 2791 // handle cached ajax lookups 2792 if (validanguage.settings.cacheAjaxLookups && validanguage.ajaxLookup[id].length > 1) { 2793 ajaxLookupLoop: 2794 for (var ajaxLookupIndex = validanguage.ajaxLookup[id].length - 1; ajaxLookupIndex > -1; ajaxLookupIndex--) { 2795 var lookupToCheck = validanguage.ajaxLookup[id][ajaxLookupIndex]; 2796 if (lookupToCheck.value == $this.value && typeof lookupToCheck.result=='boolean') { 2797 dispatchAjax = false; 2798 var result = lookupToCheck.result; 2799 if (result == false) { 2800 failedValidations[id] = validation; //store this function in failedValidations. We remove it later if it's not applicable. 2801 failedValidations[id].field = validanguage.el[id].field; 2802 failedValidations[id].errorMsg = lookupToCheck.errorMsg; 2803 } 2804 break ajaxLookupLoop; 2805 } 2806 } 2807 } 2808 2809 if (dispatchAjax) { 2810 validanguage[ajaxLocation][form][type].dispatchedAjax[$this.id] = new Date().getTime(); 2811 failedValidations[id] = validation; //store this function in failedValidations. We remove it later if it's not applicable. 2812 failedValidations[id].field = validanguage.el[id].field; 2813 funcs[m].call($this, $this.value, validanguage.ajaxLookup[id][0]); //fire off the ajax call 2814 2815 // Store the details in ajaxLookup 2816 var ajaxCounter = validanguage.ajaxLookup[id][0]++; 2817 validanguage.ajaxLookup[id].push({ 2818 counter: ajaxCounter, 2819 eventType: type, 2820 value: $this.value, 2821 result: 'pending' 2822 }); 2823 var result = 'pending'; 2824 } 2825 } else { 2826 var result = (validOptionalField || funcs[m].call($this, $this.value)); 2827 } 2828 2829 // Run the handlers on this item 2830 if (result == false) { 2831 failedValidations[id] = validation; //defer onerror handlers till later 2832 failedValidations[id].field = validanguage.el[id].field; 2833 if(! validanguage.forms[form].settings.validateAllFieldsOnsubmit) break outerLoop; 2834 } else { 2835 var onsuccess = validanguage.getElSetting('onsuccess',id,validation); 2836 successHandlers = validanguage.resolveArray(onsuccess, 'function'); 2837 for (var n=successHandlers.length-1; n>-1; n--) { 2838 successHandlers[n].call($this); 2839 } 2840 } 2841 } //close innerLoop 2842 } //close outerLoop 2843 validanguage.forms[form][type].failedValidations = failedValidations; 2844 } 2845 2846 if (!validanguage.empty(validanguage.forms[form][type].dispatchedAjax)) { 2847 //If one or more ajax calls are pending, cancel the form submit until 2848 //the ajax call comes back 2849 validanguage.forms[form][type].ajaxInterval = window.setInterval( function() { 2850 validanguage.ajaxValidationWrapper(form, type); 2851 }, 500); 2852 return false; 2853 } 2854 2855 //swap failedValidations into local variable 2856 failedValidations = (validanguage.forms[form][type].failedValidations==='callManually') ? {} : validanguage.forms[form][type].failedValidations; 2857 validanguage.forms[form][type].failedValidations = {}; 2858 2859 if( validanguage.empty(failedValidations) ) { 2860 var submitStatus = true; 2861 } else { //call all appropriate onerror handlers 2862 for (var o in failedValidations) { 2863 if( typeof failedValidations[o] == 'function' ) continue; 2864 var id = o; 2865 $this = validanguage.$(o); 2866 validation = failedValidations[o]; 2867 var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation); 2868 var errorMsg = validanguage.getElSetting('errorMsg',id,validation); 2869 var onerror = validanguage.getElSetting('onerror',id,validation); 2870 errorHandlers = validanguage.resolveArray(onerror,'function'); 2871 for (var m=errorHandlers.length-1; m>-1; m--) { 2872 errorHandlers[m].call($this, errorMsg); 2873 } 2874 2875 var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation); 2876 if( focusOnerror==true ) $this.focus(); 2877 var showAlert = validanguage.getElSetting('showAlert',id,validation); 2878 if( showAlert ) alert(errorMsg); 2879 } 2880 var submitStatus = false; 2881 } 2882 2883 //Call onsubmit transformations 2884 var transformation = (validanguage.el[form] && validanguage.el[form].onsubmit) ? validanguage.el[form].onsubmit : []; 2885 if (typeof transformation == 'string' || typeof transformation == 'function') transformation = [ transformation ]; 2886 for (var n=transformation.length-1; n>-1; n--) { 2887 var transformations = validanguage.resolveArray(transformation[n],'function'); 2888 for (var o=transformations.length-1; o>-1; o--) { 2889 var returnStatus = transformations[o].call(validanguage.$(form), submitStatus, failedValidations); 2890 if(typeof returnStatus == 'boolean') submitStatus = returnStatus; 2891 } 2892 } 2893 if( customEvent=='validateForm' ) { 2894 return { result: submitStatus, failedValidations: failedValidations }; 2895 } 2896 return submitStatus; 2897 2898 } else { 2899 2900 var validation; 2901 2902 //Skip disabled fields 2903 if( (typeof validanguage.el[id].disabled=='boolean' && validanguage.el[id].disabled==true) 2904 || (typeof $this.disabled!='undefined' && $this.disabled==true) ) { 2905 return; 2906 } 2907 var transformations = validanguage.el[id].transformations; 2908 var p = transformations.length; 2909 for( var q=0; q<p; q++) { 2910 if( typeof transformations[q]['on'+type]=='undefined' || transformations[q]['on'+type]!=true ) continue; 2911 var transformation = validanguage.resolveArray(transformations[q].name, 'function'); 2912 var trLength = transformation.length; 2913 for (var m=0; m<trLength; m++) { 2914 transformation[m].call($this); 2915 } 2916 } 2917 2918 var validOptionalField = !!(typeof validanguage.el[id].required != 'undefined' && (validanguage.el[id].required==false||validanguage.el[id].required=='false') && 2919 !$this.value.match(/[^\s]/)); 2920 2921 if( typeof validanguage.el[id].failed=='boolean' && validanguage.el[id].failed==true ) { 2922 //Allow a user to manually flunk a field 2923 result = false; 2924 } else { 2925 2926 if (validanguage.empty(validanguage.fields[id][type].failedValidations)) { 2927 //see if the field is valid 2928 var validationCounter; 2929 outerLoop: for (var j = 0; j < i; j++) { 2930 if (typeof validations[j] == 'undefined' || validations[j] == 999) { 2931 continue outerLoop; //skip deactivated validations and transformations 2932 } 2933 else { 2934 validationCounter = validations[j]; 2935 } 2936 //we run thru all the validations until one fails 2937 validation = validanguage.el[id].validations[validationCounter]; 2938 var funcs = validanguage.resolveArray(validation.name, 'function'); 2939 2940 for (var m = funcs.length - 1; m > -1; m--) { 2941 //handle ajax validations 2942 if (typeof validation.isAjax != 'undefined') { 2943 2944 var dispatchAjax = true; 2945 2946 // handle cached ajax lookups 2947 if (validanguage.settings.cacheAjaxLookups && validanguage.ajaxLookup[id].length > 1) { 2948 ajaxLookupLoop: 2949 for (var ajaxLookupIndex = validanguage.ajaxLookup[id].length - 1; ajaxLookupIndex > -1; ajaxLookupIndex--) { 2950 var lookupToCheck = validanguage.ajaxLookup[id][ajaxLookupIndex]; 2951 if (lookupToCheck.value == $this.value && typeof lookupToCheck.result=='boolean') { 2952 dispatchAjax = false; 2953 var result = lookupToCheck.result; 2954 if (result == false) { 2955 failedValidations[id] = validation; //store this function in failedValidations. We remove it later if it's not applicable. 2956 failedValidations[id].field = validanguage.el[id].field; 2957 failedValidations[id].errorMsg = lookupToCheck.errorMsg; 2958 } 2959 break ajaxLookupLoop; 2960 } 2961 } 2962 } 2963 2964 if (dispatchAjax && !validOptionalField) { 2965 validanguage.fields[id][type].dispatchedAjax[id] = new Date().getTime(); 2966 failedValidations[id] = validation; //store this function in failedValidations. We remove it later if it's not applicable. 2967 failedValidations[id].field = validanguage.el[id].field; 2968 funcs[m].call($this, $this.value, validanguage.ajaxLookup[id][0]); //fire off the ajax call 2969 2970 // Store the details in ajaxLookup 2971 var ajaxCounter = validanguage.ajaxLookup[id][0]++; 2972 validanguage.ajaxLookup[id].push({ 2973 counter: ajaxCounter, 2974 eventType: type, 2975 value: $this.value, 2976 result: 'pending' 2977 }); 2978 } 2979 } else { 2980 var result = (validOptionalField || funcs[m].call($this, $this.value)); 2981 } 2982 2983 if (result == false) { 2984 // Record lastFailed1 and 2 2985 if (validanguage.el[id].lastFailed1) validanguage.el[id].lastFailed2 = validanguage.el[id].lastFailed1; 2986 validanguage.el[id].lastFailed1 = funcs[m].toString(); 2987 break outerLoop; 2988 } 2989 } 2990 } 2991 if (validationCounter == undefined) return true; //exit early if all validations have been removed 2992 validanguage.fields[id][type].failedValidations = failedValidations; 2993 } 2994 } 2995 2996 if (!validanguage.empty(validanguage.fields[id][type].dispatchedAjax)) { 2997 //If one or more ajax calls are pending, exit early until the ajax call comes back 2998 validanguage.fields[id][type].ajaxInterval = window.setInterval( function() { 2999 validanguage.ajaxValidationWrapper(id, type); 3000 }, 500); 3001 return false; 3002 } 3003 3004 //swap failedValidations into local variable 3005 if (typeof result == 'undefined') { 3006 failedValidations = (validanguage.fields[id][type].failedValidations === 'callManually') ? {} : validanguage.fields[id][type].failedValidations; 3007 if (failedValidations[id] && failedValidations[id].name) validation = failedValidations[id].name; 3008 var result = validanguage.empty(failedValidations) ? true : false; 3009 } 3010 validanguage.fields[id][type].failedValidations = {}; 3011 3012 //handle the result 3013 if( result == true ) { 3014 validanguage.el[id].lastFailed1 = {}; 3015 var onsuccess = validanguage.getElSetting('onsuccess',id,validation); 3016 successHandlers = validanguage.resolveArray(onsuccess,'function'); 3017 for (var m=successHandlers.length-1; m>-1; m--) { 3018 successHandlers[m].call($this); 3019 } 3020 return true; 3021 } else { 3022 var retriggerErrors = validanguage.getElSetting('retriggerErrors',id,validation); 3023 var failedFieldClassName = validanguage.getElSetting('failedFieldClassName',id,validation); 3024 3025 // Trigger errors if retriggerErrors is on, or if the last 2 failures were different 3026 if (retriggerErrors || (validanguage.el[id].lastFailed1!=validanguage.el[id].lastFailed2)) { 3027 3028 var focusOnerror = validanguage.getElSetting('focusOnerror', id, validation); 3029 var errorMsg = (failedValidations[id] && failedValidations[id].errorMsg) ? failedValidations[id].errorMsg : validanguage.getElSetting('errorMsg', id, validation); 3030 var onerror = (failedValidations[id] && failedValidations[id].onerror) ? failedValidations[id].onerror : validanguage.getElSetting('onerror', id, validation); 3031 errorHandlers = validanguage.resolveArray(onerror, 'function'); 3032 for (var m = errorHandlers.length - 1; m > -1; m--) { 3033 errorHandlers[m].call($this, errorMsg); 3034 } 3035 3036 var focusOnerror = validanguage.getElSetting('focusOnerror', id, validation); 3037 if (focusOnerror == true) $this.focus(); 3038 3039 var showAlert = validanguage.getElSetting('showAlert', id, validation); 3040 if (showAlert) alert(errorMsg); 3041 } 3042 return false; 3043 } 3044 } 3045 } 3046 3047 } //close validanguage 3048 3049 validanguage.init(); 3050