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