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.2 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.2', 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.addEventListener) { 354 obj.addEventListener(event, func, false); 355 return true; 356 } else if (obj.attachEvent){ 357 var newEvent = obj.attachEvent("on"+event, func); 358 return newEvent; 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 this.addEvent = function(obj, evtHandler, func) { 371 Event.observe(obj, evtHandler, func); 372 } 373 break; 374 case 'dojo': 375 this.addEvent = function(obj, evtHandler, func) { 376 dojo.connect(obj, 'on'+evtHandler, func); 377 } 378 break; 379 case 'jquery': 380 this.addEvent = function(obj, evtHandler, func) { 381 if (obj == window) { 382 jQuery(document).ready(func); 383 } else { 384 var selector = '#' + obj.id; 385 jQuery(selector).bind(evtHandler, func); 386 } 387 } 388 break; 389 } 390 }, //close addEventInit 391 392 /** 393 * This function wraps multiple validanguage.el.elemId.validations event handlers 394 * and transformations within a single wrapper to call all loaded validations/transformations 395 * and exit as soon as a validation returns false. 396 * 397 * @param {Object} Form element object 398 * @param {string} eventType, such as "blur" or "keydown" 399 * @param {integer} validationsCounter, denotes the array index of this item in 400 * validanguage.el.elemId.validations 401 */ 402 addOrCreateValidationWrapper: function( Obj, eventType, validationsCounter ) { 403 var id = Obj.id; 404 if (eventType == 'submit') { 405 if (this.empty(validationsCounter)) return; // exit early for onsubmit transformations 406 var formId = validanguage.getFormId(id); 407 if (typeof formId == 'number') { 408 var form = document.forms[formId]; 409 } else { 410 var form = this.$(formId); 411 } 412 if (typeof validanguage.forms[formId].validations == 'undefined') { 413 validanguage.forms[formId].validations = []; 414 this.addEvent(form, eventType, function(e) { 415 var evt = e || window.evt; 416 var result = validanguage.validationWrapper(e); 417 if (result == false) { 418 evt.returnValue = false; //IE 419 if (evt.preventDefault) evt.preventDefault(); //Everyone else 420 return false; 421 } else { 422 return true; 423 } 424 }); 425 } 426 //add the element and validationsCounter to the list of onsubmit validations for the parent form 427 validanguage.forms[formId].validations[validanguage.forms[formId].validations.length] = { element: Obj, validationsCounter: validationsCounter }; 428 } else { 429 430 if( typeof validanguage.el[id].handlers == 'undefined' ) validanguage.el[id].handlers = {}; 431 if( typeof validanguage.el[id].handlers[eventType] == 'undefined' ) { 432 validanguage.el[id].handlers[eventType] = []; 433 if( eventType == 'typing') { 434 this.addEvent(Obj, 'keyup', function(e){ validanguage.validationWrapper(e, 'typingTimeout'); }); 435 } else { 436 this.addEvent(Obj, eventType, function(e){ validanguage.validationWrapper(e); }); 437 } 438 } 439 //add validationsCounter to the list of validations for this object/eventType combo 440 validanguage.el[id].handlers[eventType][validanguage.el[id].handlers[eventType].length] = validationsCounter; 441 } 442 }, //close addOrCreateValidationWrapper 443 444 /** 445 * This function is used to either load a new validation for a form field, or to 446 * reactivate a validation previously removed with the removeValidation() method. 447 * 448 * NOTE: When adding a new validation, you will need to have previously inserted 449 * all the relevant details about the validation in the validanguage.el.formField 450 * object. 451 * 452 * @param {String} elemId 453 * @param {String/Array} eventTypes 454 * @param {String/Array/Function} validationNames 455 */ 456 addValidation: function ( elemId, eventTypes, validationNames ) { 457 if( typeof validationNames[0]=='undefined' ) validationNames = [ validationNames ]; 458 if( typeof eventTypes=='string' ) eventTypes = [ eventTypes ]; 459 460 var vals = this.el[elemId].validations; 461 for (var i = vals.length - 1; i > -1; i--) { 462 if ( validationNames[0] == '*' || this.inArray(vals[i].name, validationNames) ) { 463 for( var j=eventTypes.length-1; j>-1; j--) { 464 this.addOrCreateValidationWrapper(this.$(elemId), eventTypes[j]); 465 } 466 } 467 } 468 }, 469 470 /** 471 * Very simple AJAX function 472 * @param {String} url 473 * @param {Function} callback 474 */ 475 ajax: function( url, callback ) { 476 if(window.ActiveXObject){ 477 var ajaxObj = new ActiveXObject("Microsoft.XMLHTTP"); 478 } else if(window.XMLHttpRequest){ 479 var ajaxObj = new XMLHttpRequest(); 480 } 481 482 ajaxObj.open("POST", url, true); 483 ajaxObj.onreadystatechange = function() { 484 if(ajaxObj.readyState==4) { 485 callback(ajaxObj.responseText); 486 } 487 }; 488 ajaxObj.send(null); 489 }, //close ajax 490 491 /** 492 * Initializes validanguage.ajax as browser-specific 493 */ 494 ajaxInit: function() { 495 switch ( this.useLibrary ) { 496 //reassign validanguage.ajax 497 case 'prototype': 498 case 'scriptaculous': 499 this.ajax = function(url, callback, options) { 500 if (validanguage.empty(options)) options = {}; 501 options.onSuccess = callback; 502 new Ajax.Request(url, options); 503 } 504 break; 505 case 'dojo': 506 this.ajax = function(url, callback, options) { 507 if (validanguage.empty(options)) options = {}; 508 options.url = url; 509 options.handle = callback; 510 dojo.xhrGet(options); 511 } 512 break; 513 case 'jquery': 514 this.ajax = function(url, callback, options) { 515 if (validanguage.empty(options)) options = {}; 516 options.url = url; 517 options.success = callback; 518 jQuery.ajax(options); 519 } 520 break; 521 } 522 }, 523 524 /** 525 * This function is called by setInterval and is used to check 526 * whether or not all ajax callbacks for a form have returned. 527 * @param {String} ID or index of the form 528 * @param {String} Event Type 529 */ 530 ajaxValidationWrapper: function( form, eventType ) { 531 var nodeType = (this.getFormId(form)==null) ? 'forms' : 'fields'; 532 if (this.empty(validanguage[nodeType][form][eventType].dispatchedAjax)) { 533 window.clearInterval(validanguage[nodeType][form][eventType].ajaxInterval); 534 } else { 535 for (var id in validanguage[nodeType][form][eventType].dispatchedAjax) { 536 if (typeof time == 'function') continue; 537 if (validanguage[nodeType][form][eventType].dispatchedAjax[id] + (validanguage.settings.ajaxTimeout*1000) < new Date().getTime()) { 538 //abort requests older than X seconds old 539 if (this.debug) console.log('Aborting request...'); 540 delete this[nodeType][form][eventType].failedValidations[id]; 541 if (this.empty(this[nodeType][form][eventType].failedValidations)) this[nodeType][form][eventType].failedValidations = 'callManually'; 542 delete this[nodeType][form][eventType].dispatchedAjax; 543 if (nodeType=='forms' && this.validateForm(form).result === true) { 544 if (this.debug) console.log('Request Aborted.'); 545 if (this.settings.submitFormOnExpiredAjax) { 546 this.forcedSubmission = true; 547 this.$(form).submit(); 548 } 549 } 550 // If a non-"Form Submit" ajax request is aborted, this is currently handled by doing nothing... 551 return; 552 } 553 } 554 } 555 }, //close ajaxValidationWrapper 556 557 /** 558 * This function loads all the validanguage.toggle() rules which 559 * are defined for a form following document.onload() 560 */ 561 callToggleTransformationsOnload: function() { 562 if (this.settings.callToggleTransformationsOnload) { 563 for (var id in this.el) { 564 if (typeof this.el[id].transformations != 'undefined') { 565 for (var i = this.el[id].transformations.length - 1; i > -1; i--) { 566 if (typeof this.el[id].transformations[i].name == 'undefined') 567 continue; 568 var funcString = this.el[id].transformations[i].name; 569 if (typeof funcString == 'string' && funcString.indexOf('validanguage.toggle') > -1) { 570 var transformations = this.resolveArray(funcString, 'function'); 571 var j = transformations.length; 572 for (var k = 0; k < j; k++) { 573 transformations[k].call(this.$(id)); 574 } 575 } 576 } 577 } 578 } 579 } 580 }, 581 582 /** 583 * Combines 2 node lists into 1 584 * @param {Object} obj1 585 * @param {Object} obj2 586 */ 587 concatCollection: function(obj1,obj2) { 588 var i; 589 var arr = new Array(); 590 var len1 = obj1.length; 591 var len2 = obj2.length; 592 for (i=0; i<len1; i++) { 593 arr.push(obj1[i]); 594 } 595 for (i=0; i<len2; i++) { 596 arr.push(obj2[i]); 597 } 598 return arr; 599 }, 600 601 /** 602 * Determines whether the passed domNode is contained 603 * within the passed parentNode. Useful for telling if 604 * a form field belongs to a given form or DIV. * 605 * @param {Object|String} node or ID 606 * @param {Object|String} node or ID 607 */ 608 contains: function (needle, _parentNode) { 609 needle = this.$(needle); 610 _parentNode = this.$(_parentNode); 611 while (needle && _parentNode != needle) { 612 needle = needle.parentNode; 613 } 614 return needle == _parentNode; 615 }, //close contains 616 617 /** 618 * Emulates PHP's empty() function. For convenience, you can specify whether 619 * boolean false is considered empty. Defaults to false is NOT empty. 620 * Ignores functions. 621 * 622 * @param {Object} testVar 623 * @param {bool} falseIsEmpty 624 */ 625 empty: function ( testVar, falseIsEmpty ) { 626 if (testVar == null || testVar == undefined || testVar == NaN || testVar === 'null' || (testVar =='' && typeof testVar == 'string') ) return true; 627 if (falseIsEmpty==true && testVar==false) { 628 return true; 629 } 630 if (typeof testVar == 'object') { 631 for (var i in testVar) { 632 if( typeof testVar[i] == 'function' ) continue; 633 634 // Prevent infinite recursion in Safari 4 635 var recurse = true; 636 for (var j in testVar[i]) { 637 if (testVar[i][j] === testVar) recurse = false; 638 } 639 640 if(recurse && validanguage.empty(testVar[i], falseIsEmpty)==false ) { 641 return false; 642 } 643 } 644 return true; 645 } else { 646 return false; 647 } 648 }, 649 650 /** 651 * This is a preset transformation which is used to reformat text input 652 * to match a desired pattern 653 * @param {String} Pattern using x to represent alphanumeric characters. 654 * For example: "(xxx) xxx-xxxx" 655 * @param {String} String listing any characters to be removed from the 656 * form field's value prior to potential reformatting. 657 * INCLUDE ALL the delimiters used in "pattern" 658 * For example: "()- " 659 * @param {String/Regex} Regular expression which, if provided, will be used 660 * to determine whether or not to proceed with reformatting. 661 * If not provided, the function will only reformat if the number 662 * of characters in the form field (after stripThese is applied) 663 * matches the number of x's in the provided pattern 664 */ 665 format: function( pattern, stripThese, regexMatch ) { 666 var text = this.value; 667 var origText = text; 668 669 if(stripThese!=null && typeof stripThese=='string') { 670 var i = stripThese.length; 671 for( var i=stripThese.length-1; i>-1; i-- ) { 672 while (text.indexOf(stripThese.charAt(i)) != -1) { 673 text = text.replace(stripThese.charAt(i),'','g'); 674 } 675 } 676 } 677 678 if (this.priorStrippedValue && (this.priorStrippedValue == text)) { 679 // exit early if they hit backspace key to delete a delimeter 680 return; 681 } 682 683 // Save the value in DOM for later 684 this.priorStrippedValue = text; 685 if (regexMatch!=null) { 686 var myreg = (typeof regexMatch=='string') ? new RegExp(regexMatch) : regexMatch; 687 var thisMatch = myreg.exec(text); 688 if (thisMatch == null) return; //exit early for no match 689 } else { 690 //check for required length based on number of x's in the pattern 691 var countMe = pattern.replace(/[^x]/g,''); 692 if( text.length != countMe.length ) return; 693 } 694 695 // Store the caret position 696 var pos = validanguage.getCaretPos(this); 697 698 var i = pattern.length; 699 var textLength = text.length; 700 var k = -1; //counter for text 701 var newtext = ''; 702 var numEaten = 0; 703 // We iterate thru the length of the pattern, 704 // but we exit early once the X's have been exhausted. 705 for (var j = 0; j < i; j++) { 706 if (pattern.charAt(j) == 'x') { 707 numEaten++; 708 if (numEaten > textLength) break; 709 } 710 newtext += (pattern.charAt(j) == 'x') ? text.charAt(++k) : pattern.charAt(j); 711 } 712 713 // Don't change anything unless we need to 714 if (newtext != origText) { 715 this.value = newtext; 716 717 // Adjust caret pos if the text and text length changed 718 if (pos == origText.length) 719 pos = newtext.length; 720 721 // Restore the caret position 722 validanguage.setCaretPos(this, pos); 723 } 724 }, //close format 725 726 /** 727 * This function iterates thru the ajaxLookup array and returns the 728 * index number corresponding to the passed counter value 729 * @param {String} element ID 730 * @param {Object} counter 731 */ 732 getAjaxLookupIndex: function(id, counter) { 733 for( var i=this.ajaxLookup[id].length-1; i>-1; i--) { 734 if (this.ajaxLookup[id][i].counter==counter) return i; 735 } 736 return 0; 737 }, //close getAjaxLookupIndex 738 739 /** 740 * Gets the current caret position on an object 741 * @param {Object} obj 742 */ 743 getCaretPos: function(obj) { 744 if (obj.createTextRange && this.browser!='opera') { 745 // IE 746 if (obj.nodeName.toLowerCase() == 'input') { 747 var range = document.selection.createRange().duplicate(); 748 range.moveEnd('character', obj.value.length); 749 if (range.text == '') 750 return obj.value.length; 751 return obj.value.lastIndexOf(range.text); 752 } else { 753 // Code below from http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html 754 // Unfortnately, it doesn't seem to work consistently for multi-line textareas, 755 // so I commented it out for the moment. Maybe I'll try and fix it one day. 756 /* 757 var selection_range = document.selection.createRange().duplicate(); 758 759 var before_range = document.body.createTextRange(); 760 before_range.moveToElementText(obj); // Selects all the text 761 before_range.setEndPoint("EndToStart", selection_range); // Moves the end where we need it 762 var before_finished = false; 763 var before_text, untrimmed_before_text; 764 765 // Load the text values we need to compare 766 before_text = untrimmed_before_text = before_range.text; 767 768 // Check each range for trimmed newlines by shrinking the range by 1 character and seeing 769 // if the text property has changed. If it has not changed then we know that IE has trimmed 770 // a \r\n from the end. 771 do { 772 if (!before_finished) { 773 if (before_range.compareEndPoints("StartToEnd", before_range) == 0) { 774 before_finished = true; 775 } 776 else { 777 before_range.moveEnd("character", -1) 778 if (before_range.text == before_text) { 779 untrimmed_before_text += "\r\n"; 780 } 781 else { 782 before_finished = true; 783 } 784 } 785 } 786 787 } 788 while (!before_finished); 789 return untrimmed_before_text.length; 790 */ 791 return 0; 792 } 793 } else { 794 // Moz 795 return obj.selectionStart; 796 } 797 }, //close getCaretPos 798 799 /** 800 * Fetches all comment nodes in the passed form node and returns them in a node list 801 * Doesnt work in konqueror, since konqueror strips all comments from the DOM 802 * 803 * @param {Containing Node} el 804 */ 805 getComments: function(el) { 806 if (!el) el = document.documentElement; 807 var comments = new Array(); 808 var length = (el.childNodes) ? el.childNodes.length : 0; 809 for (var c = 0; c < length; c++) { 810 if (el.childNodes[c].nodeType == 8) { 811 comments[comments.length] = el.childNodes[c]; 812 } else if (el.childNodes[c].nodeType == 1) { 813 comments = comments.concat(this.getComments(el.childNodes[c])); 814 } 815 } 816 return comments; 817 }, //close getComments 818 819 /** 820 * Helper function used by validateDate() and validateTimestamp(). 821 * @param {Object} options object provided by the user to validateDate() or validateTimestamp(). 822 * @param {Object} defaults which should be used. Used to allow validateDate() and validateTimestamp() 823 * to have different default dateOrder values. 824 */ 825 getDateTimeDefaultOptions: function ( options, defaults ) { 826 if( options==null ) options = {}; 827 828 // Date options 829 if( typeof options.dateOrder=='undefined' ) options.dateOrder=defaults.dateOrder; 830 options.dateOrder = options.dateOrder.toLowerCase(); 831 if( typeof options.allowedDelimiters=='undefined' || typeof options.allowedDelimiters!='string' ) options['allowedDelimiters'] = './-'; 832 if( typeof options.twoDigitYearsAllowed=='undefined' ) options.twoDigitYearsAllowed = false; 833 if( typeof options.oneDigitDaysAndMonthsAllowed=='undefined' ) options.oneDigitDaysAndMonthsAllowed = true; 834 if( typeof options.maxYear=='undefined' ) options.maxYear = new Date().getFullYear() + 15; 835 if( typeof options.minYear=='undefined' ) options.minYear = 1900; 836 if( typeof options.rejectDatesInTheFuture=='undefined' ) options.rejectDatesInTheFuture = false; 837 if( typeof options.rejectDatesInThePast=='undefined' ) options.rejectDatesInThePast = false; 838 839 // Time options 840 if( typeof options.timeIsRequired=='undefined' ) options.timeIsRequired = false; 841 if( typeof options.timeUnits=='undefined' ) options.timeUnits = 'hms'; 842 if( typeof options.microsecondPrecision=='undefined' ) options.microsecondPrecision = 6; 843 return options; 844 }, //close getDateTimeDefaultOptions 845 846 /** 847 * This function checks for a given setting in increasing specificity 848 * within the validanguage.forms[formId].settings object, and within the passed 849 * validanguage.el objects 850 * 851 * @param {string} Name of the setting to be retrieved 852 * @param {string} ID of the form field object being validated 853 * @param {Object} validanguage.el.objId.validations[index] object 854 */ 855 getElSetting: function( setting, id, validationObj ) { 856 var formSetting = this.getFormSettings(id); 857 var retVal = formSetting[setting]; //global setting 858 if( typeof validationObj!='undefined' && typeof validationObj[setting] != 'undefined' ) { 859 retVal = validationObj[setting]; 860 } else if( typeof this.el[id][setting] != 'undefined' ) { 861 retVal = this.el[id][setting]; 862 } 863 return retVal; 864 }, 865 866 /** 867 * This function returns the validanguage.form[formId].setting object for the passed element ID 868 * @param {string or Node} id of the input field or input node 869 * @return {Object} settings object 870 */ 871 getFormSettings: function(id) { 872 var formName = ( this.$(id).nodeName.toLowerCase()=='form' ) ? 873 id : this.getFormId(id); 874 return this.forms[formName].settings; 875 }, 876 877 /** 878 * This function returns the Id of the parent Form for an element 879 * @param {String|DomNode} Form node or its ID 880 */ 881 getFormId: function(formField) { 882 if (this.$(formField).form) { 883 return this.$(formField).form.getAttribute("id"); 884 } else { 885 return null; 886 } 887 }, 888 889 /** 890 * This function parses the passed comment to retrieve the indicated setting 891 * 892 * @param {String} Name of the setting to retrieve / needle 893 * @param {String} Full text of the HTML comment / haystack 894 * @return {String} The value of the requested setting 895 */ 896 getSettingFromComment: function( setting, comment ) { 897 var needle = ' '+setting+'='; 898 var startPos = comment.indexOf(needle); 899 if( startPos == -1) return null; 900 var delimiterPos = (startPos*1) + (needle.length*1); 901 var delimeter = '\\' + comment.charAt(delimiterPos); 902 var Regex = needle+delimeter+'(.+?)'+delimeter; 903 var myreg = new RegExp(Regex); 904 var thisMatch = myreg.exec(comment, 'gi'); 905 if (thisMatch == null) { 906 return null; //no match 907 } else if (thisMatch[1]) { 908 //Convert booleans. I hope this doesnt screw anyone later.... 909 if(thisMatch[1]=='true') thisMatch[1]=true; 910 if(thisMatch[1]=='false') thisMatch[1]=false; 911 return thisMatch[1]; 912 } 913 }, //close getSettingFromComment 914 915 /** 916 * This function hides the div containing the validanguage error messages for 917 * failed validations 918 */ 919 hideError: function() { 920 var settings = validanguage.getFormSettings(this.id); 921 var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); 922 if (errorDisplay != null) { 923 errorDisplay.innerHTML = ''; 924 var errorDiv = errorDisplay.parentNode; 925 926 errorDiv.style.display = 'none'; 927 errorDiv.className = settings.noErrorClassName; 928 } 929 if (! this.className.match(validanguage.settings.passedFieldClassName)) this.className += ' '+validanguage.settings.passedFieldClassName; 930 if (this.className.match(validanguage.settings.failedFieldClassName)) this.className = this.className.replace(validanguage.settings.failedFieldClassName,''); 931 932 //Do we need to remove any vd_li items? 933 if( !settings.showFailedFields ) return; 934 if( document.getElementById(this.id + settings.errorListItemSuffix) != null ) { 935 var errorList = document.getElementById(settings.errorListId); 936 errorList.removeChild( document.getElementById(this.id + settings.errorListItemSuffix) ); 937 if( errorList.getElementsByTagName('LI').length==0 ) 938 document.getElementById(settings.errorDivId).style.display='none'; 939 } 940 }, //close hideError 941 942 /** 943 * Determines whether the passed item is present in the array or object. 944 * 945 * @param {Object} needle 946 * @param {Object} haystack 947 */ 948 inArray: function( needle, haystack ) { 949 for( var i=haystack.length-1; i>-1; i-- ){ 950 if( haystack[i]===needle ) return true; 951 } 952 return false; 953 }, 954 955 /** 956 * This function searches settingsHaystack for all variables defined in the settingsNeedles 957 * array, and if they are located, they are copied over to the settingsTarget 958 * 959 * @param {Object} settingsHaystack -- Object location to be searched for settings 960 * @param {Array} settingsNeedles -- Array of settings to be checked 961 * @param {Object} settingsTarget -- Object location where any defined settings should be copied to 962 * @param {String} constrainType -- Optional type constraint 963 */ 964 inheritIfDefined: function ( settingsHaystack, settingsNeedles, settingsTarget, constrainType ) { 965 if( typeof settingsNeedles.length == 'undefined' ) return false; 966 for( var i=settingsNeedles.length-1;i>-1;i--) { 967 if ( typeof settingsHaystack[settingsNeedles[i]]!='undefined' && 968 ( this.empty(constrainType) || typeof settingsHaystack[settingsNeedles[i]]==constrainType ) 969 ) { 970 settingsTarget[settingsNeedles[i]] = settingsHaystack[settingsNeedles[i]]; 971 } 972 } 973 }, 974 975 /** 976 * Initialization function for validanguage. Adds the onload hook 977 * which fires off the populate() method to add all the other event 978 * handlers 979 */ 980 init: function() { 981 if (typeof validanguageLibrary!='undefined') this.useLibrary = validanguageLibrary; 982 this.addEventInit(); 983 this.ajaxInit(); 984 this.addEvent(window, 'load', function() { 985 validanguage.populate.call(validanguage); 986 }); 987 }, 988 989 /** 990 * Function to insert 1 Node after another in the DOM. If the referenceNode 991 * is a label, this function will use the nextSibling instead 992 * 993 * @param {Node} nodeToAdd 994 * @param {Node} referenceNode 995 */ 996 insertAfter: function (nodeToAdd, referenceNode ) { 997 if (referenceNode.nextSibling) { 998 if (referenceNode.nextSibling.nodeName.toLowerCase() == 'label') { 999 referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling.nextSibling); 1000 } else { 1001 referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling); 1002 } 1003 } else { 1004 referenceNode.parentNode.appendChild(nodeToAdd); 1005 } 1006 }, //close insertAfter 1007 1008 /** 1009 * This function examines the ajaxLookup array to determine whether or not the 1010 * specified ajaxCounter pertains to the most recent ajax call for that form field. 1011 * @param {String} formFieldId 1012 * @param {Integer} ajaxCounter 1013 */ 1014 isExpiredAjax: function (formFieldId, ajaxCounter) { 1015 if (this.empty(formFieldId) || this.empty(ajaxCounter)) return false; 1016 var h = this.getAjaxLookupIndex(formFieldId, ajaxCounter); 1017 var arr = this.ajaxLookup[formFieldId]; 1018 var event = arr[h].eventType; 1019 1020 for (var i = arr.length-1; i > 0; i--) { 1021 if (event == arr[i].eventType) { 1022 if (arr[i].counter == ajaxCounter) { 1023 return false; 1024 } else { 1025 return true; 1026 } 1027 } 1028 } 1029 return false; 1030 }, //close isExpiredAjax 1031 1032 /** 1033 * This function parses all comments in the current document, looking for 1034 * the comment-based API and converts any validanguage statements it 1035 * finds into the element/json-based API for further processing. 1036 * 1037 * @param {Object} Dom Node containing comments to be loaded 1038 * @param {Array} For konqueror, we pass this function an Array with all 1039 * the comments (retrieved via AJAX) 1040 * For all other browsers, konquerorComments is undefined and 1041 * we retrieve the comments normally via the DOM 1042 */ 1043 loadCommentAPI: function( domNode, konquerorComments ) { 1044 domNode = this.$(domNode); 1045 1046 var supportedSettings = ['mode','expression','suppress','onsubmit','onblur','onchange', 1047 'onkeypress','onkeyup','onkeydown','onclick', 'ontyping','onfocus', 1048 'errorMsg','onerror','onsuccess','focusOnError', 1049 'showAlert','required','requiredAlternatives', 1050 'maxlength','minlength','regex','field', 1051 'errorOnMatch','modifiers','transformations','validations']; 1052 1053 var allComments = (this.empty(konquerorComments)) ? this.getComments(domNode) : konquerorComments; 1054 var length = allComments.length; 1055 for (var j=0; j<length; j++) { 1056 1057 var singleComment = (this.empty(konquerorComments)) ? allComments[j].nodeValue : allComments[j]; 1058 var tagArray = singleComment.split(validanguage.settings.commentDelimiter); 1059 var tagArrayLength = tagArray.length; 1060 1061 for (var a=0; a<tagArrayLength; a++) { 1062 var commentText = tagArray[a]; 1063 commentText = commentText.replace(/\n/g,' '); 1064 commentText = commentText.replace(/\r/g,' '); 1065 var isValidanguageRegEx = /<validanguage/i; 1066 if (isValidanguageRegEx.test(commentText)) { 1067 //get the targets 1068 var targets = this.getSettingFromComment('target', commentText); 1069 var settings = []; //reset settings 1070 if (this.empty(targets, true)) 1071 continue; 1072 targets = this.resolveArray(targets, 'string'); 1073 for (var k = supportedSettings.length - 1; k > -1; k--) { 1074 var tempSetting = this.getSettingFromComment(supportedSettings[k], commentText); 1075 if (!(tempSetting == null || (typeof tempSetting == 'string' && tempSetting == '') )) 1076 settings[supportedSettings[k]] = tempSetting; 1077 } 1078 1079 //iterate thru our targets and assign the settings 1080 k = targets.length; 1081 for (var l = 0; l < k; l++) { 1082 var id = targets[l]; 1083 var obj = this.$(id); 1084 if (typeof this.el[id] == 'undefined' || obj == null) 1085 this.el[id] = {}; 1086 1087 /** CHARACTER VALIDATION **/ 1088 //start keypressValidation 1089 if (typeof settings.expression != 'undefined') { 1090 this.el[id].characters = {}; 1091 this.inheritIfDefined(settings, ['expression','errorMsg','mode','suppress','validateCharacters','onerror','onsuccess'], this.el[id].characters); 1092 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].characters); 1093 } 1094 //close keypressValidation 1095 1096 /** REGEX **/ 1097 if (typeof settings.regex != 'undefined') { 1098 this.el[id].regex = { expression: settings.regex }; 1099 this.inheritIfDefined(settings, ['errorOnMatch','modifiers'], this.el[id].regex); 1100 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].regex); 1101 } 1102 1103 /** MISC SETTINGS **/ 1104 // Only inherit event handlers that are non-boolean transformations 1105 this.inheritIfDefined(settings, ['field'], this.el[id], 'string'); 1106 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id], 'string'); 1107 this.inheritIfDefined(settings, ['minlength','maxlength','requiredAlternatives','required','focusOnError','showAlert', 1108 'onsuccess','onerror','errorMsg'], this.el[id]); 1109 if (typeof settings.minlength != 'undefined') { 1110 this.el[id].minlengthEvents = {}; 1111 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].minlengthEvents); 1112 } 1113 if (typeof settings.maxlength != 'undefined') { 1114 this.el[id].maxlengthEvents = {}; 1115 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].maxlengthEvents); 1116 } 1117 if (typeof settings.required != 'undefined') { 1118 this.el[id].requiredEvents = {}; 1119 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].requiredEvents); 1120 } 1121 1122 /** VALIDATIONS AND TRANSFORMATIONS **/ 1123 if (typeof this.el[id].validations == 'undefined') this.el[id].validations = []; 1124 if (typeof this.el[id].transformations == 'undefined') this.el[id].transformations = []; 1125 var functionModifiers = ['focusOnError','showAlert','onsuccess','onerror','errorMsg','isAjax']; 1126 1127 //Load validations 1128 if( typeof settings.validations != 'undefined' && !this.empty(settings.validations) ) { 1129 this.el[id].validations[this.el[id].validations.length] = {}; 1130 this.el[id].validations[this.el[id].validations.length-1].name = settings.validations; 1131 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].validations[this.el[id].validations.length-1]); 1132 this.inheritIfDefined(settings, functionModifiers, this.el[id].validations[this.el[id].validations.length-1]); 1133 } 1134 //Load transformations 1135 if( typeof settings.transformations != 'undefined' && !this.empty(settings.transformations) ) { 1136 this.el[id].transformations[this.el[id].transformations.length] = {}; 1137 this.el[id].transformations[this.el[id].transformations.length-1].name = settings.transformations; 1138 this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].transformations[this.el[id].transformations.length-1]); 1139 } 1140 1141 } // foreach (targets) 1142 } // close if(validanguage_comment) 1143 } // close tagArray loop 1144 } // close allComments loop 1145 }, //close loadCommentAPI 1146 1147 /** 1148 * This function parses the validanguage.el object to load all the 1149 * form-element-specific validation settings which the end user has defined 1150 * via the Object-based API 1151 * 1152 * @param {String|Object} (Optional) If provided, this is either a DOM node 1153 * or a node's ID. Providing a DOM node will limit the function 1154 * to loading validations for only elements contained within that node, 1155 * or if the node is a form field itself, limit to only that field. 1156 */ 1157 loadElAPI: function( _parentNode ) { 1158 if (_parentNode != null) _parentNode = this.$(_parentNode); 1159 1160 for( var elem in this.el ) { //for each element.... 1161 1162 // Skip to the next if it's not an element ID 1163 try { if( typeof this.$(elem) == undefined || this.empty(this.$(elem)) ) continue; } catch(e) { continue; } 1164 1165 if (_parentNode != null && _parentNode.getAttribute("id") != elem) { 1166 // Skip this item if its not a descendant of _parentNode 1167 if (!this.contains(elem, _parentNode)) continue; 1168 } 1169 var Obj = this.$(elem); 1170 var settings = validanguage.getFormSettings(elem); 1171 if (typeof this.el[elem].validations == 'undefined') this.el[elem].validations = []; 1172 if (typeof this.el[elem].field == 'undefined') this.el[elem].field = elem; 1173 1174 /** REQUIRED **/ 1175 if (typeof this.el[elem].required != 'undefined' && this.el[elem].required==true) { 1176 this.el[elem].validations[this.el[elem].validations.length] = {}; 1177 this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateRequired'; 1178 this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg; 1179 this.inheritIfDefined( this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] ); 1180 1181 //If specific requiredEvents are provided, use those instead of the element level event handlers 1182 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] ); 1183 1184 //We need to call the validateRequiredAlternatives function when a requiredAlternative is clicked 1185 if(settings.validateRequiredAlternativesOnclick==true && typeof this.el[elem].requiredAlternatives != 'undefined' ) { 1186 var onsuccessFuncs = (typeof this.el[elem].onsuccess!='undefined') ? this.el[elem].onsuccess : settings.onsuccess; 1187 var onerrorFuncs = (typeof this.el[elem].onerror!='undefined') ? this.el[elem].onerror : settings.onerror; 1188 var alts = this.resolveArray(this.el[elem].requiredAlternatives,'string'); 1189 for( var y=alts.length-1; y>-1; y--) { 1190 this.requiredAlternatives[alts[y]] = {}; 1191 if( !((typeof this.$(alts[y]).type != 'undefined') && (this.$(alts[y]).type=='checkbox'||this.$(alts[y]).type=='radio')) ) continue; 1192 this.requiredAlternatives[alts[y]].onsuccess = onsuccessFuncs; 1193 this.requiredAlternatives[alts[y]].onerror = onerrorFuncs; 1194 this.requiredAlternatives[alts[y]].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg; 1195 this.requiredAlternatives[alts[y]].parentId = elem; 1196 this.addEvent( this.$(alts[y]), 'click', function(e) { validanguage.validateRequiredAlternatives(e); } ); 1197 } 1198 } 1199 } 1200 1201 /** REGEX **/ 1202 if (typeof this.el[elem].regex != 'undefined') { 1203 this.el[elem].validations[this.el[elem].validations.length] = {}; 1204 this.el[elem].validations[this.el[elem].validations.length - 1].name = 'validanguage.validateRegex'; 1205 var errorMsg = (typeof this.el[elem].errorMsg == 'undefined') ? settings.errorMsg : this.el[elem].errorMsg; 1206 if(typeof this.el[elem].regex.errorMsg != 'undefined') errorMsg = this.el[elem].regex.errorMsg 1207 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = errorMsg; 1208 this.inheritIfDefined(this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]); 1209 this.inheritIfDefined(this.el[elem].regex, this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]); 1210 if(typeof this.el[elem].regex.errorOnMatch=='undefined') this.el[elem].regex.errorOnMatch=settings.errorOnMatch; 1211 } 1212 1213 /** MAXLENGTH **/ 1214 if (typeof this.el[elem].maxlength != 'undefined') { 1215 this.el[elem].validations[this.el[elem].validations.length] = {}; 1216 this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMaxlength'; 1217 this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.maxlengthErrorMsg.replace('{!maxlength}',this.el[elem].maxlength); 1218 //If specific maxlengthEvents are provided, use those instead of the element level event handlers 1219 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] ); 1220 } 1221 1222 /** MINLENGTH **/ 1223 if (typeof this.el[elem].minlength != 'undefined') { 1224 this.el[elem].validations[this.el[elem].validations.length] = {}; 1225 this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMinlength'; 1226 this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.minlengthErrorMsg.replace('{!minlength}',this.el[elem].minlength); 1227 //If specific minlengthEvents are provided, use those instead of the element level event handlers 1228 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] ); 1229 } 1230 1231 //start keypressValidation 1232 /** CHARACTERS **/ 1233 if (typeof this.el[elem].characters != 'undefined' && 1234 typeof this.el[elem].characters.mode != 'undefined' && 1235 typeof this.el[elem].characters.expression != 'undefined' 1236 ) { 1237 1238 //supported shortcuts 1239 var expression = this.el[elem].characters.expression; 1240 expression = expression.replace('alphaUpper','ABCDEFGHIJKLMNOPQRSTUVWXYZ'); 1241 expression = expression.replace('alphaLower','abcdefghijklmnopqrstuvwxyz'); 1242 expression = expression.replace('alpha','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); 1243 expression = expression.replace('numeric','0123456789'); 1244 expression = expression.replace('special','`~!@#$%^&*()-_=+[{]}\\|;:\'",<.>/?'); 1245 this.el[elem].characters.characterExpression = expression; 1246 1247 var validanguageExpr = ';'; 1248 for (var j=expression.length-1;j>-1;j--){ 1249 validanguageExpr += expression.charCodeAt(j) + ';'; 1250 } 1251 this.el[elem].characters.expression = validanguageExpr; 1252 if(typeof this.el[elem].characters.suppress=='undefined' || this.el[elem].characters.suppress==true) this.addEvent(Obj, "keypress", validanguage.validateKeypress ); 1253 1254 // Load validanguage.validateCharacters 1255 if (typeof this.el[elem].characters.validateCharacters == 'undefined' || this.el[elem].characters.validateCharacters) { 1256 this.el[elem].validations[this.el[elem].validations.length] = {}; 1257 this.el[elem].validations[this.el[elem].validations.length - 1].name = 'validanguage.validateCharacters'; 1258 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = true; 1259 1260 for (var z = this.supportedEventHandlers.length - 1; z > -1; z--) { 1261 if (typeof this.el[elem].characters[this.supportedEventHandlers[z]] != 'undefined' && this.el[elem].characters[this.supportedEventHandlers[z]] == true) 1262 this.el[elem].validations[this.el[elem].validations.length - 1][this.supportedEventHandlers[z]] = true; 1263 } 1264 1265 //assign onerror 1266 if (typeof this.el[elem].characters.errorMsg != 'undefined') { 1267 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = this.el[elem].characters.errorMsg; 1268 } else { 1269 this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = settings.characterValidationErrorMsg; 1270 } 1271 } 1272 } 1273 //close keypressValidation 1274 if (typeof this.el[elem].transformations == 'undefined') this.el[elem].transformations = []; 1275 1276 /** TRANSFORMATIONS **/ 1277 //First, check for transformations listed by event type, such as onblur="foo" 1278 var j = this.supportedEventHandlers.length; 1279 for (var k = 0; k < j; k++) { 1280 var handler = this.supportedEventHandlers[k]; 1281 if( typeof this.el[elem][handler] != 'undefined' && typeof this.el[elem][handler] != 'boolean' ) { 1282 //Add the defined transformation to the transformations array 1283 this.el[elem].transformations[this.el[elem].transformations.length] = {}; 1284 var n = this.el[elem].transformations.length - 1; 1285 this.el[elem].transformations[n].name = this.el[elem][handler]; 1286 //store the event handler 1287 this.el[elem].transformations[n][handler] = true; 1288 } 1289 } 1290 1291 var h = this.el[elem].transformations.length; 1292 for (var i = 0; i < h; i++) { 1293 //for each transformation, load the appropriate function in the element's transformations array 1294 var eventLoaded = false; 1295 var j = this.supportedEvents.length; 1296 for (var k = 0; k < j; k++) { 1297 if(this.supportedEvents[k]=='submit') continue; 1298 if (typeof this.el[elem].transformations[i]['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].transformations[i]['on' + this.supportedEvents[k]] == true) { 1299 eventLoaded = true; 1300 this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k]); 1301 } 1302 } 1303 1304 //if they didnt supply any events, we default to the defined defaultTransformationHandlers (usually only "blur") 1305 if (eventLoaded == false) { 1306 if (Obj.nodeName.toLowerCase() == 'form') { 1307 this.addOrCreateValidationWrapper(Obj, 'submit'); 1308 } else { 1309 for (var l = settings.defaultTransformationHandlers.length - 1; l > -1; l--) { 1310 this.addOrCreateValidationWrapper(Obj, settings.defaultTransformationHandlers[l], 999); 1311 } 1312 } 1313 } 1314 } 1315 1316 /** VALIDATIONS **/ 1317 if (typeof this.el[elem].validations != 'undefined') { 1318 1319 // Sort the validations array to ensure that any isAjax function appears last 1320 var h = this.el[elem].validations.length-1; 1321 ajaxLoop: 1322 for (var i = 0; i < h; i++) { 1323 if (this.el[elem].validations[i].isAjax) { 1324 var copy = this.el[elem].validations[i]; 1325 this.el[elem].validations.splice(i,1); 1326 this.el[elem].validations.push(copy); 1327 break ajaxLoop; 1328 } 1329 } 1330 1331 var h = this.el[elem].validations.length; 1332 for (var i = 0; i < h; i++) { 1333 //for each validation, load the appropriate function in the element's validations array 1334 var eventLoaded = false; 1335 var j = this.supportedEvents.length; 1336 for (var k = 0; k < j; k++) { 1337 if (typeof this.el[elem].validations[i]['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].validations[i]['on' + this.supportedEvents[k]] === true) { 1338 eventLoaded = true; 1339 this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k], i); 1340 } 1341 } 1342 1343 //if they didnt supply any events, we default to the defined defaultValidationHandlers (usually only "submit") 1344 if (eventLoaded == false) { 1345 for( var l=settings.defaultValidationHandlers.length-1; l>-1;l--) { 1346 this.addOrCreateValidationWrapper(Obj, settings.defaultValidationHandlers[l], i); 1347 } 1348 } 1349 } 1350 } 1351 } //close elem loop 1352 }, 1353 1354 /** 1355 * This function loads a form and its settings into the 1356 * global validanguage object so it can receive validation 1357 * criteria. If the form does not have an ID, one is assigned. 1358 * @param {String|Object} form Node or ID of the form 1359 */ 1360 loadForm: function(form) { 1361 var formName; 1362 form = this.$(form); 1363 if (this.empty(form.getAttribute("id"))) { 1364 formName = 'validanguageForm' + this.formCounter; 1365 form.id = formName; 1366 } else { 1367 formName = form.getAttribute("id"); 1368 } 1369 this.forms[formName] = { settings: this.settings }; 1370 }, //close loadForm 1371 1372 /** 1373 * This function loads all the validation rules specified within the 1374 * passed element. For example, if it is passed a DIV, all the validation 1375 * rules in that DIV will be loaded. This supports both the comment API 1376 * and the Object API. However, if you are using the object API with an 1377 * AJAX request, you may need to find the script tags within the returned 1378 * HTML and eval() them prior to calling loadNewFields(). Also, be aware 1379 * that you can mess up your form's validation by loading rules which have 1380 * already been loaded once. Be sure that the DIV or form field which you 1381 * give to the loadNewFields() function doesn't contain validations which 1382 * have previously been loaded, or unexpected issues may arise. 1383 * @param {String|DomNode} containingElement or its ID 1384 */ 1385 loadNewFields: function(containingElement) { 1386 this.loadCommentAPI(containingElement); 1387 this.loadElAPI(containingElement); 1388 }, 1389 1390 /** 1391 * This function searches the passed subject and returns an Array of strings 1392 * which are delimited by the characters passed to the function in the 1393 * first 2 arguments. Used to pull comments from the document source 1394 * @param {String} startChar 1395 * @param {String} endChar 1396 * @param {String} subject 1397 * @return {Array} 1398 */ 1399 parseSubstring: function( startChar, endChar, subject ) { 1400 var matches = []; 1401 var parts = subject.split(startChar); 1402 for( var i=0; i<parts.length; i++) { 1403 var endPos = parts[i].indexOf(endChar); 1404 if( endPos != -1) matches.push( parts[i].substring(0, endPos) ); 1405 } 1406 return matches; 1407 }, //close parseSubstring 1408 1409 /** 1410 * Main function to be called onload to load all the validations 1411 */ 1412 populate: function(){ 1413 this.sniffBrowser(); 1414 if( this.browser=='ie5' ) return; //There's no way I'm supporting IE5, so it's safest to just not run validanguage at all 1415 1416 if (typeof console == 'undefined') this.debug=false; 1417 1418 /** 1419 * Iterate thru all the form elements on the page to populate 1420 * the formLookup hash table and load the default settings 1421 **/ 1422 var forms = document.getElementsByTagName('form'); 1423 for (var i=0, j=forms.length; i<j; i++) { 1424 this.formCounter = i; // this supports forms with no Ids 1425 this.loadForm(forms[i]); 1426 } 1427 1428 if (this.browser == 'konqueror' && this.settings.loadCommentAPI == true) { 1429 this.ajax(document.location.href, function(docText) { 1430 //prototype 1431 if (docText.responseText) docText = docText.responseText; 1432 var comments = validanguage.parseSubstring( '<!--', '-->', docText ); 1433 validanguage.loadCommentAPI( window.document, comments ); 1434 if (validanguage.overloadFormSettings) validanguage.overloadFormSettings(); 1435 if (validanguage.el && !validanguage.empty(validanguage.el)) { 1436 validanguage.loadElAPI(); 1437 if (validanguage.callToggleTransformationsOnload) validanguage.callToggleTransformationsOnload(); 1438 //Call any onload handler defined by the user 1439 validanguage.settings.onload.call(validanguage); 1440 validanguage.vdLoaded = true; 1441 } 1442 }); 1443 } else { 1444 //Load comment API 1445 if (this.settings.loadCommentAPI == true) this.loadCommentAPI( ); 1446 1447 //Load Form-Specific Settings 1448 if (this.overloadFormSettings) this.overloadFormSettings(); 1449 1450 //Load the validanguage.el API 1451 if (this.el && !this.empty(this.el)) this.loadElAPI(); 1452 1453 // Call any defined validanguage.toggle() functions to true up the UI 1454 if (this.callToggleTransformationsOnload) this.callToggleTransformationsOnload(); 1455 1456 //Call any onload handler defined by the user 1457 this.settings.onload.call(this); 1458 1459 this.vdLoaded = true; 1460 } 1461 //Garbage collection 1462 this.addEvent(window, 'unload', function() { delete validanguage; }); 1463 1464 }, //close populate 1465 1466 /** 1467 * This transformation function updates a div or span 1468 * with the total number of characters remaining, 1469 * based on a comparison between the number of characters 1470 * the user has typed and the defined minLength and maxLength 1471 * values for the field. See the demo page for an example. 1472 */ 1473 remainingChars: function() { 1474 var div = validanguage.$(this.id+'_remaining'); 1475 var minLength = validanguage.el[this.id].minlength || 0; 1476 var maxLength = validanguage.el[this.id].maxlength; 1477 var length = this.value.length; 1478 var remainingClass = ((length <= maxLength) && (length >= minLength)) ? 'vdLengthPassed' : 'vdLengthFailed'; 1479 div.innerHTML = '<span class="'+remainingClass+'">' + length + '</span> / ' + maxLength; 1480 }, //close remainingChars 1481 1482 /** 1483 * This function removes all references to a form and its elements from 1484 * the global validanguage object 1485 * @param {String|DomNode} Which form to remove 1486 */ 1487 removeForm: function (formId) { 1488 if (typeof formId != 'string') formId = formId.getAttribute("id"); 1489 1490 // Remove any related form fields from validanguage.el 1491 for (var elem in this.el) { 1492 if (this.contains(elem, formId)) delete this.el[elem]; 1493 } 1494 delete this.forms[formId]; 1495 }, //close removeForm 1496 1497 /** 1498 * This function will remove all the validations for any 1499 * form fields contained within the passed containing element. 1500 * For example, if containingElem is a DIV that has 3 textareas 1501 * inside it, all the validations for those 3 textareas will be 1502 * removed. You can also call the function with no argument 1503 * to remove all the validations on the current page. 1504 * @param {String|DomNode} Containing element or its ID 1505 */ 1506 removeAllValidations: function (containingElem) { 1507 if (containingElem==undefined) containingElem = window.document; 1508 for (var elem in this.el) { 1509 if (this.contains(elem, containingElem)) this.removeValidation(elem, '*', '*'); 1510 } 1511 }, //close removeAllValidations 1512 1513 /** 1514 * This function is used to deactivate a previously loaded validation. 1515 * Provide the element ID of the field and a list of event types and validation 1516 * names to deactivate. You can use the * character as the eventType and/or 1517 * the validationName arguments to include ALL eventTypes/validationNames. 1518 * 1519 * NOTE: if you are tempted to use removeValidation('id','*','*'), you may be 1520 * better off using validanguage.el.id.disabled=true, as this is much easier to 1521 * undo later. 1522 * 1523 * NOTE: It is up to you to make sure no error msgs are displaying before disabling 1524 * a validation. If one is showing and all validations are disabled, the onsuccess 1525 * handlers used to clear the error msgs will never be called. 1526 * 1527 * @param {String} elemId 1528 * @param {String/Array} eventTypes 1529 * @param {String/Array/Function} validationNames 1530 */ 1531 removeValidation: function ( elemId, eventTypes, validationNames ) { 1532 //prep our arguments 1533 if( eventTypes == '*' ) { 1534 eventTypes = this.supportedEvents; 1535 } else if( typeof eventTypes[0]=='undefined') { 1536 eventTypes = [ eventTypes ]; 1537 } 1538 if( typeof validationNames=='string' ) validationNames = [ validationNames ]; 1539 for (var j = eventTypes.length - 1; j > -1; j--) { 1540 if (eventTypes[j] == 'submit') { 1541 // Remove form.onsubmit validations 1542 var vals = this.forms[this.getFormId(elemId)].validations; 1543 formValLoop: 1544 for (var i = vals.length - 1; i > -1; i--) { 1545 if( vals[i]==undefined || vals[i].element.getAttribute("id") != elemId ) continue formValLoop; 1546 if ( validationNames[0] == '*' || this.inArray( this.el[elemId].validations[vals[i].validationsCounter].name, validationNames ) ) { 1547 try { delete vals[i]; } catch(e) {} 1548 } 1549 } 1550 } else { 1551 // Remove field-specific validations 1552 var vals = this.el[elemId].validations; 1553 for (var i = vals.length - 1; i > -1; i--) { 1554 if ( validationNames[0] == '*' || this.inArray(vals[i].name, validationNames) ) { 1555 try { delete this.el[elemId].handlers[eventTypes[j]][i]; } catch(e) {} 1556 } 1557 } 1558 } 1559 } 1560 }, //close removeValidation 1561 1562 /** 1563 * This function accepts as input a function, a string, an array of 1564 * functions, an array of strings, or a comma-delimited list of functon 1565 * names as its argument and returns an Array of functions or strings 1566 * comprising the passed arguments. Example: transforms 'foo, bar' 1567 * to [foo, bar] 1568 * 1569 * @param {Function or String or Array} args 1570 * @param {String} returnType should be either 'string' or 'function' 1571 * @return {Array of Functions} 1572 */ 1573 resolveArray: function (args, returnType, ignoreCommas) { 1574 var returnArray = []; 1575 if( typeof args == 'object' ) { 1576 var i=args.length; 1577 for (var j=0; j<i; j++) { 1578 returnArray[returnArray.length] = this.resolveArray(args[j],returnType)[0]; 1579 } 1580 return returnArray; 1581 } 1582 if( typeof args == 'function' ) { 1583 returnArray[0] = args; 1584 return returnArray; 1585 } 1586 if ( typeof args == 'string' ) { 1587 if(returnType=='string') args = args.replace(' ',''); //dont remove spaces when returning a function 1588 1589 if( args.indexOf(',') == -1 || ignoreCommas==true ) { 1590 //function name as a string 1591 if( returnType=='function' ) { 1592 if( args.indexOf('(') != -1 && args.indexOf('function')==-1 ) { 1593 //In order to preserve scope for functions with parameters attached, 1594 //we must transform "func1(text,foo)" into "function(text) { return func1.call(this,text,foo) }" 1595 var splitAt = args.indexOf('('); 1596 var funcName = args.substring(0,splitAt); 1597 var params = args.substring(++splitAt,args.length); 1598 var args = 'function(text) { return '+funcName+'.call(this,'+params+'}'; 1599 } 1600 eval("var argsHandle="+args); //easiest way to handle dot notation and framesets 1601 returnArray[returnArray.length] = argsHandle; 1602 } else { 1603 returnArray[returnArray.length] = args; 1604 } 1605 } else { 1606 //comma-delimited list of function names 1607 var tempArray = this.smartCommaSplit(args); 1608 var i=tempArray.length; 1609 if(i==1) { 1610 //The only commas in the string appear within braces or parens 1611 returnArray = this.resolveArray(tempArray[0], returnType, true); 1612 } else { 1613 for (var j=0; j<i; j++) { 1614 returnArray[returnArray.length] = this.resolveArray(tempArray[j],returnType)[0]; 1615 } 1616 } 1617 } 1618 return returnArray; 1619 } 1620 return false; 1621 }, //close resolveArray 1622 1623 /** 1624 * Sets the caret at a specified position on an object 1625 * @param {Object} Dom Node 1626 * @param {Object} Position 1627 */ 1628 setCaretPos: function(obj, pos) { 1629 if(obj.createTextRange && this.browser!='opera') { 1630 // IE 1631 var range = obj.createTextRange(); 1632 range.move('character', pos); 1633 range.select(); 1634 } else if(obj.selectionStart) { 1635 // Moz 1636 obj.focus(); 1637 obj.setSelectionRange(pos, pos); 1638 } 1639 }, //close setCaretPos 1640 1641 /** 1642 * This function is called from an ajax callback to report back 1643 * to validanguage the status of the ajax validation. 1644 * @param {Object} id Id of the form field associated with this validation 1645 * @param {Boolean} returnStatus Whether or not the field is valid 1646 * @param {String|Integer} type Event Type or (alternately) the ajaxCounter that can be used 1647 * to check validanguage.ajaxLookup to determine the eventType 1648 * @param {String} errorMsg Error message to show for the failed field 1649 */ 1650 setValidationStatus: function( id, returnStatus, type, errorMsg ) { 1651 if (type == undefined) { 1652 type = 'submit'; 1653 } else if (!this.inArray(type, this.supportedEvents)) { 1654 var i = this.getAjaxLookupIndex(id, type); 1655 type = (this.ajaxLookup[id][i].eventType) ? this.ajaxLookup[id][i].eventType : 'submit'; 1656 } 1657 var nodeType = (type=='submit') ? 'forms' : 'fields'; 1658 var form = (type=='submit') ? validanguage.getFormId(id) : id; 1659 1660 if (this.debug) { 1661 console.log('setValidationStatus for '+id+'. Pending requests before deleting:'); 1662 console.dir(this[nodeType][form][type].dispatchedAjax); 1663 console.dir(this[nodeType][form][type].failedValidations); 1664 } 1665 1666 //exit early if the request has been aborted. TO DO: Better way to detect aborted request 1667 if (typeof this[nodeType][form][type].failedValidations[id] == 'undefined') { 1668 if (this.debug) console.log('Exiting setValidationStatus for aborted request'); 1669 //return; 1670 } 1671 1672 if (returnStatus === false) { 1673 if (!this.empty(errorMsg)) this[nodeType][form][type].failedValidations[id].errorMsg = errorMsg; 1674 } else { 1675 delete this[nodeType][form][type].failedValidations[id]; 1676 if (this.empty(this[nodeType][form][type].failedValidations)) this[nodeType][form][type].failedValidations = 'callManually'; 1677 } 1678 delete this[nodeType][form][type].dispatchedAjax[id]; 1679 1680 // Store the details on this request in ajaxLookup if the user supplied an ajaxCounter 1681 if (typeof i != 'undefined') { 1682 this.ajaxLookup[id][i].result = returnStatus; 1683 if (!this.empty(errorMsg)) this.ajaxLookup[id][i].errorMsg = errorMsg; 1684 } 1685 1686 if (this.debug) { 1687 console.log('Pending requests after deleting:'); 1688 console.dir(this[nodeType][form][type].dispatchedAjax); 1689 console.log('Failed validations:'); 1690 console.dir(this[nodeType][form][type].failedValidations); 1691 } 1692 1693 // We only validate the form if there are no other pending ajax requests we're waiting on to come back 1694 if (nodeType=='forms' && this.empty(this[nodeType][form][type].dispatchedAjax) && this.validateForm(form).result === true) { 1695 if (this.debug) console.log('Form Submitted'); 1696 validanguage.forcedSubmission = true; 1697 this.$(form).submit(); 1698 } else if (nodeType=='fields' && this.empty(this[nodeType][form][type].dispatchedAjax)) { 1699 // Trigger another blur/typing/etc event 1700 if (this.debug) console.log('Throwing Event'); 1701 this.validationWrapper(id, type); 1702 } 1703 }, //close setValidationStatus 1704 1705 /** 1706 * This function shows the error messages for failed validations by dynamically 1707 * creating a div 1708 * @param {string} Text of the error message to be displayed 1709 */ 1710 showError: function( errorMsg ) { 1711 var settings = validanguage.getFormSettings(this.id); 1712 var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); 1713 if( errorDisplay == null ) { 1714 var formField = document.getElementById(this.id); 1715 var errorDiv = document.createElement('DIV'); 1716 validanguage.insertAfter( errorDiv, formField ); 1717 var innerHTML = '<span id="'+ this.id + settings.errorMsgSpanSuffix+'"> </span>'; 1718 errorDiv.innerHTML = innerHTML; 1719 errorDiv.className = settings.onErrorClassName; 1720 var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); 1721 } else { 1722 var errorDiv = errorDisplay.parentNode; 1723 errorDiv.style.display = 'block'; 1724 errorDiv.className = settings.onErrorClassName; 1725 } 1726 if(validanguage.useLibrary=='scriptaculous') new Effect.Highlight(errorDiv, { startcolor: '#A85F5F', endcolor: '#C0A6A6', restorecolor: '#ddd' }); 1727 errorDisplay.innerHTML = errorMsg; 1728 if (!this.type || (this.type != 'radio' && this.type != 'checkbox')) { 1729 if (!this.className.match(validanguage.settings.failedFieldClassName)) 1730 this.className += ' ' + validanguage.settings.failedFieldClassName; 1731 if (this.className.match(validanguage.settings.passedFieldClassName)) 1732 this.className = this.className.replace(validanguage.settings.passedFieldClassName, ''); 1733 } 1734 1735 //Do we need to add any vd_li items? 1736 if( !settings.showFailedFields ) return; 1737 if( document.getElementById(settings.errorDivId) == null ) { 1738 var errorDiv = document.createElement('DIV'); 1739 errorDiv.id = settings.errorDivId; 1740 document.body.appendChild(errorDiv); 1741 } else { 1742 var errorDiv = document.getElementById(settings.errorDivId); 1743 } 1744 if (document.getElementById(settings.errorListId) == null) { 1745 errorDiv.innerHTML = settings.errorListText + '<br/><ul id="'+settings.errorListId+'"></ul>'; 1746 } 1747 var errorDivInner = errorDiv.innerHTML.toLowerCase(); 1748 errorDivInner = errorDivInner.replace(/"/g,''); //remove quotes for IE weirdness 1749 1750 var errorList = document.getElementById(settings.errorListId); 1751 var listItem = '<li id="'+this.id+settings.errorListItemSuffix+'">'+validanguage.el[this.id].field+'</li>'; 1752 var listItemExists = listItem.toLowerCase(); 1753 listItemExists = listItemExists.replace(/"/g,''); //remove quotes for IE weirdness 1754 1755 if(errorDivInner.indexOf(listItemExists)==-1) errorList.innerHTML += listItem; 1756 document.getElementById(settings.errorDivId).style.display='block'; 1757 }, //close showError 1758 1759 /** 1760 * This function is intended for us as an onsubmit transformation. It replaces the form's 1761 * submit button with the text specified in settings.showSubmitMessageMessage. 1762 * 1763 * @param {Object} validationResult 1764 * @param {Object} failedValidations 1765 */ 1766 showSubmitMessage: function( validationResult, failedValidations ) { 1767 if( validationResult==false ) return; 1768 var settings = validanguage.forms[this.getAttribute("id")].settings; 1769 1770 //first, we need to find the submit button and hide it 1771 var inputs = this.getElementsByTagName('INPUT'); 1772 for( var i=inputs.length-1; i>-1; i-- ) { 1773 if( typeof inputs[i].type!='undefined' && inputs[i].type=='submit' ) { 1774 validanguage.forms.submitButton = submitButton = inputs[i]; 1775 break; 1776 } 1777 } 1778 submitButton.style.display='none'; 1779 var loadingDiv = document.createElement('DIV'); 1780 loadingDiv.id = settings.showSubmitMessageId; 1781 loadingDiv.innerHTML = settings.showSubmitMessageMessage; 1782 validanguage.insertAfter( loadingDiv, inputs[i] ); 1783 1784 //set a timeout to unhide the submit button after 60 seconds 1785 //in case the request times out and the user needs to resubmit the form 1786 setTimeout( function( ){ validanguage.forms.submitButton.style.display='inline'; }, 60000); 1787 }, //close showSubmitMessage 1788 1789 /** 1790 * This function splits a string into an array using commas as the delimiter, 1791 * while being smart enough to ignore commas appearing inside parenthesis and 1792 * braces. 1793 * 1794 * @param {string} Comma-delimited string 1795 * @return {Array} 1796 */ 1797 smartCommaSplit: function ( str ) { 1798 var openParens = 0; 1799 var openBraces = 0; 1800 var lastSplit = 0; 1801 var returnArray = []; 1802 var len = str.length; 1803 for( var i=0; i<len; i++ ) { 1804 switch (str.charAt(i)) { 1805 case '(': openParens++; break; 1806 case ')': openParens--; break; 1807 case '{': openBraces++; break; 1808 case '}': openBraces--; break; 1809 case ',': 1810 if( openParens==0 && openBraces==0 ){ 1811 returnArray[returnArray.length] = str.substring(lastSplit,i); 1812 lastSplit = ++i; 1813 } 1814 break; 1815 } 1816 } 1817 returnArray[returnArray.length] = str.substring(lastSplit,i); 1818 return returnArray; 1819 }, 1820 1821 /** 1822 * Determines roughly which browser they're using. Defaults to FF for anything 1823 * that isnt IE, Opera, Konqueror or Safari, which assuming the browser is standards-compliant, 1824 * should be good enough for what I'm using it for. Yea, yea, I know.... 1825 */ 1826 sniffBrowser: function() { 1827 //yo... dont hate. 1828 var isIE/*@cc_on=1@*/; 1829 if (isIE) { 1830 this.browser = 'ie'; 1831 var version = parseFloat(navigator.appVersion.split('MSIE')[1]); 1832 if( version < 6 ) this.browser = 'ie5'; 1833 } else if(navigator.appName.indexOf('Opera')!=-1) { 1834 this.browser = 'opera'; 1835 } else if(navigator.vendor.indexOf('Apple')!=-1) { 1836 this.browser = 'safari'; 1837 } else if (navigator.vendor.indexOf('KDE')!=-1) { 1838 this.browser = 'konqueror'; 1839 } else { 1840 this.browser = 'ff'; 1841 } 1842 }, 1843 1844 /** 1845 * This function will strip all the leading and trailing whitespace 1846 * from a form field prior to its being validated. 1847 */ 1848 stripWhitespace: function() { 1849 this.value = this.value.replace(/^\s+|\s+$/g,''); 1850 }, 1851 1852 /** 1853 * This function replaces one subset of text with a different subset 1854 * of text, such as making all uppercase letters, lowercase. 1855 * @param {Array|String} Array of characters to replace 1856 * or either "upper" or "lower" 1857 * @param {Array|String} Array of characters used for replacements 1858 * or either "upper" or "lower" 1859 */ 1860 substituteText: function( find, replaceWith ) { 1861 var lower = ['a','b','c','d','e','f','g','h','i','j','k','l','m', 1862 'n','o','p','q','r','s','t','u','v','w','x','y','z']; 1863 var upper = ['A','B','C','D','E','F','G','H','I','J','K','L','M', 1864 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z']; 1865 1866 if (find === 'lower' || validanguage.empty(find)) { 1867 find = lower; 1868 } else if (find === 'upper') { 1869 find = upper; 1870 } 1871 if (replaceWith === 'upper' || validanguage.empty(replaceWith)) { 1872 replaceWith = upper; 1873 } else if (replaceWith === 'lower') { 1874 replaceWith = lower; 1875 } 1876 var subject = this.value; 1877 1878 // Store the caret position 1879 var pos = validanguage.getCaretPos(this); 1880 1881 // Do our regex replace 1882 for (var i = find.length - 1; i > -1; i--) { 1883 var myreg = new RegExp(find[i], 'g'); 1884 subject = subject.replace(myreg, replaceWith[i]); 1885 } 1886 this.value = subject; 1887 1888 // Restore the caret position 1889 validanguage.setCaretPos(this, pos); 1890 }, //close substituteText 1891 1892 /** 1893 * Transformation supporting 3 major features: toggling visibility, 1894 * changing the values of form fields and adding options to select 1895 * elements. 1896 * @param {Array} toggleArgs Array of objects 1897 */ 1898 toggle: function( toggleArgs ) { 1899 var j = toggleArgs.length; 1900 var settings = validanguage.getFormSettings(this.id); 1901 var formName = validanguage.getFormId(this.id); 1902 for (var i=0; i < j; i++) { 1903 var obj = toggleArgs[i]; 1904 var targets = validanguage.resolveArray(obj.target, 'string'); 1905 var radioExceptionApplies = false; 1906 // If we are running toggle() for a radio button find out if all the other radio buttons in the same group are unchecked, 1907 if (this.nodeName.toLowerCase()=='input' && this.type.toLowerCase() == 'radio') { 1908 var radioButtonChecked = false; 1909 var radios = false; 1910 for (var k = document.forms.length - 1; k > -1; k--) { 1911 if (document.forms[k].id == formName) { 1912 radios = document.forms[k][this.name]; 1913 break; 1914 } 1915 } 1916 for (var k = radios.length - 1; k > -1; k--) if (radios[k].checked) radioButtonChecked = true; 1917 if (!radioButtonChecked) radioExceptionApplies=true; 1918 } 1919 1920 // toggle visibility 1921 if (obj.toggle) { 1922 var visibleMet = (obj.toggle.visible) ? validanguage.toggleCriteriaMet(this, obj.toggle.visible, settings) : false; 1923 var hiddenMet = (obj.toggle.hidden) ? validanguage.toggleCriteriaMet(this, obj.toggle.hidden, settings) : false; 1924 for (var k = targets.length - 1; k > -1; k--) { 1925 if (visibleMet && !radioExceptionApplies) validanguage.toggleDisplay(targets[k], ''); 1926 if (hiddenMet || (settings.toggleVisibilityDefaultsToHidden && !visibleMet)) validanguage.toggleDisplay(targets[k], 'none'); 1927 } 1928 } // close toggle visibility 1929 1930 // toggleAttribute 1931 if (typeof obj.toggleAttribute != 'undefined') { 1932 for (var k = targets.length - 1; k > -1; k--) { 1933 var attribute = obj.toggleAttribute.attribute; 1934 var value = obj.toggleAttribute.value; 1935 if (obj.toggleAttribute.condition == 'checked' && this.checked == true) 1936 validanguage.$(targets[k])[attribute] = value; 1937 else if (obj.toggleAttribute.condition == 'unchecked' && this.checked == false) 1938 validanguage.$(targets[k])[attribute] = value; 1939 else if ((this.value) && (obj.toggleAttribute.condition == this.value)) 1940 validanguage.$(targets[k])[attribute] = value ; 1941 } 1942 } // close toggleAttribute 1943 1944 // value control 1945 if (typeof obj.values != 'undefined') { 1946 for (var k = targets.length - 1; k > -1; k--) { 1947 if (typeof obj.values.checked != 'undefined' && this.checked==true) validanguage.$(targets[k]).value=obj.values.checked; 1948 else if (typeof obj.values.unchecked != 'undefined' && this.checked==false) validanguage.$(targets[k]).value=obj.values.unchecked; 1949 else if (typeof obj.values[this.value] != 'undefined') validanguage.$(targets[k]).value=obj.values[this.value]; 1950 } 1951 } // close toggle value 1952 1953 // dynamic selects 1954 if (typeof obj.dynamicSelect != 'undefined') { 1955 // store the value 1956 var newValue = this.value; 1957 if (typeof validanguage.el[this.id]['value']!= 'undefined' && validanguage.el[this.id]['value']==newValue) return; 1958 var sel2 = validanguage.$(targets[0]); // dynamicSelect only supports 1 target 1959 for (var sel1Key in obj.dynamicSelect) { 1960 if (typeof obj.dynamicSelect[sel1Key] == 'object' ) { 1961 var sel1Val = obj.dynamicSelect[sel1Key]; 1962 if (sel1Key == this.value) { 1963 // remove the existing options 1964 while (sel2.options.length > 0) { sel2.remove(0); } 1965 for (var sel2Key in sel1Val) { 1966 if (sel2Key == '_default') continue; 1967 var opt = document.createElement('option'); 1968 opt.value = sel2Key; 1969 opt.text = sel1Val[sel2Key]; 1970 sel2.options.add(opt); 1971 } 1972 if (typeof sel1Val['_default'] != 'undefined') sel2.value = sel1Val['_default']; 1973 } 1974 } 1975 } 1976 validanguage.el[this.id]['value'] = newValue; 1977 } // close dynamicSelect 1978 } 1979 }, //close toggle 1980 1981 /** 1982 * Determines whether the criteria used by the toggle function 1983 * has been met 1984 * @param {Object} field 1985 * @param {string} criteria 1986 * @param {Object} settings object 1987 */ 1988 toggleCriteriaMet: function( field, criteria, settings ) { 1989 if (criteria == 'checked') { 1990 return !!(field.checked); 1991 } else if (criteria == 'unchecked') { 1992 return !(field.checked); 1993 } else if (criteria == 'empty') { 1994 return !!(validanguage.inArray(field.value, settings.emptyOptionElements)); 1995 } else if (criteria == 'notEmpty') { 1996 return !(validanguage.inArray(field.value, settings.emptyOptionElements)); 1997 } else { 1998 return !!(field.value == criteria); 1999 } 2000 }, //close toggleCriteriaMet 2001 2002 /** 2003 * Function used to hide or show a node. This function will also automatically disable or enable 2004 * any form fields contained within the passed node. 2005 * @param {Object} nodeId ID of the node to hide/show 2006 * @param {Object} visibility (optional) Pass either 'none' or '' to hide/show. Or leave blank to toggle 2007 */ 2008 toggleDisplay: function( nodeId, visibility ) { 2009 var node = validanguage.$(nodeId); 2010 var nodeName = node.nodeName.toLowerCase(); 2011 if (visibility==null||visibility==undefined) visibility = (node.style.display=='none') ? '' : 'none'; 2012 disabledBool =