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