/** * The validanguage library was written by DrLongGhost in 2008. See attached MIT_License.js * and readme.txt for licensing and documentation. Visit http://www.drlongghost.com/ for updates. * * * @namespace Global validanguage object * @author DrLongGhost * @version 0.9.6 */ var validanguage = { /** * Valid values are 'none', 'prototype', and 'scriptaculous'. * @public * @default 'none' */ useLibrary: 'none', /** * @private */ version: '0.9.6', /** * @namespace validanguage.settings object */ settings: { /** * Should an alert() be shown when a validation fails? * By default, validanguage.showError() and validanguage.hideError() instead place the * error msg underneath the failed field. * @default false */ showAlert: false, /** * Should the target element of a failed validation receive focus when a validation fails? * IMPORTANT note regarding showAlert and focusOnError. Do NOT set both of these to true if using onblur validations. Pick either one or the other. * When you use both, it is possible to create infinite loops in which a validation failure generates an alert, triggering an onblur, * which triggers another validation failure and subsequent alert. * If you aren't using onblur validations at all, you can safely use both. * @default false */ focusOnerror: false, /** * When a form is submitted, are all form fields validated, or do we stop once one fails? * @default true */ validateAllFieldsOnsubmit: true, /** * Override this to set a global success handlers for all validation results * If you want to use only alert messages via showAlert, set this to {} to turn off inline error msgs * @default 'validanguage.hideError' */ onsuccess: 'validanguage.hideError', /** * Override this to set a global error handler for all validation results * If you want to use only alert messages via showAlert, set this to {} to turn off inline error msgs * @default 'validanguage.showError' */ onerror: 'validanguage.showError', /** * Default generic error message * @default 'You have entered an invalid entry in the form' */ errorMsg: 'You have entered an invalid entry in the form', /** * Default error message for the validateRequired validation * @default 'You have skipped a required field' */ requiredErrorMsg: 'You have skipped a required field', /** * Default error message for the validateMinlength validation * @default 'The indicated field must be at least {!minlength} characters long' */ minlengthErrorMsg: 'The indicated field must be at least {!minlength} characters long', /** * Default error message for the validateMaxlength validation * @default 'The indicated field may not be longer than {!maxlength} characters' */ maxlengthErrorMsg: 'The indicated field may not be longer than {!maxlength} characters', /** * Default error message for the validateCharacters function * @default 'You have entered invalid characters' */ characterValidationErrorMsg: 'You have entered invalid characters', /** * Class name used in showError() to assign to the DIVs * which are created to show the inline error msgs. * @default 'vdError' */ onErrorClassName: 'vdError', /** * Class name used in hideError() to assign to a DIV * which was created to show an inline error msgs which is then removed. * @default 'vdNoError' */ noErrorClassName: 'vdNoError', /** * Class name used in hideError() to assign to a form field which passes validation * @default 'passedField' */ passedFieldClassName: 'passedField', /** * Class name used in showError() to assign to a form field which fails validation * @default 'failedField' */ failedFieldClassName: 'failedField', /** * Used to make the ID used in hideError() to assign to the SPAN element inside the vdError * DIV. The errorMsgSpanSuffix is appended to the end of the form field's ID to make the SPAN ID. * If a SPAN with this ID already exists in the DOM, it will be used. If it doesn't exist, one will * be created dynamically. * @default '_errorMsg' */ errorMsgSpanSuffix: '_errorMsg', /** * To display a combined list of all fields which failed validation in addition to the * inline error msgs, set showFailedFields to true. The fields will be listed using the * "field" attribute (or ID if field is not available). * @default false */ showFailedFields: false, /** * The text specified in errorListText will be placed at the top of the errorDiv generated * by the showFailedFields option in showError(). * @default 'Please correct the following fields:' */ errorListText: 'Please correct the following fields:', /** * Specifies the ID to be assigned to the DIV used for the showFailedFields option in showError(). * If a DIV with this ID exists in the DOM, it will be used. If it doesn't exist, one will * be created dynamically. * @default 'vdErrorDiv' */ errorDivId: 'vdErrorDiv', /** * Specifies the ID to be assigned to the UL used for the showFailedFields option in showError(). * @default 'vdErrorList' */ errorListId: 'vdErrorList', /** * Used to make the ID used for the showFailedFields option in showError(). * The errorListItemSuffix is appended to the end of the form field's ID to make the ID for the LI item. * @default '_vd_li' */ errorListItemSuffix: '_vd_li', /** * Determines the ID of the DIV created in the showSubmitMessage() function used to * replace a form's submit button once the form has been submitted. * @default 'vdSubmitMessage' */ showSubmitMessageId: 'vdSubmitMessage', /** * Determines the text used by the showSubmitMessage() function which is used * replace a form's submit button once the form has been submitted. If desired, you can include HTML * or IMG tags instead of the default text. * @default 'Loading' */ showSubmitMessageMessage: 'Loading', /** * This array is used in the validateRequired function to determine whether a select box * has been left on the default, "empty" option. Add/Remove from this array as needed. * @default [' ','0',' ',''] */ emptyOptionElements: [' ','0',' ',''], /** * If a validation is supplied without any event handlers, how should it be treated in loadElAPI()? * This setting also affects the behavior of the required=true and maxlength/minlength shortcuts. * @default ['submit'] */ defaultValidationHandlers: ['submit'], /** * Should any validanguage.toggle() transformations which are defined for form fields on the * page be automatically called when the page has finished loading. * @default true */ callToggleTransformationsOnload: true, /** * Should the toggle visibility API in validanguage.toggle() default to "hidden" if a given target * does not satisfy any provided "visible" conditions? If you set this to false, you will need to * explicitly provide the desired "hidden" conditions. */ toggleVisibilityDefaultsToHidden: true, /** * Should the HTML document be scanned for validanguage comment tags? * Set this to false if you arent using the comment API for better performance. * @default true */ loadCommentAPI: true, /** * Determines the delimeter used in the loadCommentAPI() function to split up each * comment into multiple validanguage tags. * You probably want to keep this as "\n" to be safe, but if you want to be allowed * to use carriage returns inside validanguage comment tags, you can set this to * "/>" if you are careful to always close your validanguage tags * @default "\n" */ commentDelimiter: "\n", /** * Color for the textbox to flash when invalid input is entered. The default is light red. * Set this to empty to turn flashing off. * @default '#FF6666' */ validationErrorColor : '#FF6666', /** * Normal color of the textbox. The default is empty. Used in conjunction with validationErrorColor * to make the textboxes flash. * @default '' */ normalTextboxColor : '', /** * Amount of time the text box flashes the validationErrorColor. The default is 100ms * @default 100 */ timeDelay : 100, /** * Typing delay for the ontyping event. This is the amount of time between keystrokes * that must elapse before the event fires. The default is just over 1 second. * @default 1100 */ typingDelay: 1100, /** * Should the validateRequiredAlternatives function be assigned onclick to radio buttons * and checkboxes named as "requiredAlternatives"? Setting this to true ensures that * checking/unchecking a radio button or checkbox will correctly call showError/hideError. * @default true */ validateRequiredAlternativesOnclick: true, /** * Defines the default behavior of the validateRegex function. * Is a match against the regex an error or a success? * @default false */ errorOnMatch: false, /** * Override this to setup a function to run after all validanguage form fields have * been intialized inside the populate() function. The default is an empty function. * @default function() { } */ onload: function() { }, //dummy field I put here so the onload above will have a comma after it foo: '' }, //PRIVATE PROGRAM VARIABLES alertCounter: true, //this counter prevents infinite loops from being created between alerts() and onblur handlers el: {}, forms: {}, formLookup: {}, //hash table to map form element IDs to the ID of the parent form. requiredAlternatives: [], //hash table used to store requiredAlternatives associations supportedEvents: ['blur','change','keypress','keyup','keydown','submit','click','typing'], supportedEventHandlers: ['onblur','onchange','onkeypress','onkeyup','onkeydown','onsubmit','onclick','ontyping'], typingDelay: [], //hash table to store ontyping timeouts /** * Generic cross-browser addEvent() function. * * @param {Object} Object to receive the event * @param {Object} Event type * @param {Object} Function to be called */ addEvent: function(obj, event, func){ if (obj.addEventListener) { obj.addEventListener(event, func, false); return true; } else if (obj.attachEvent){ var newEvent = obj.attachEvent("on"+event, func); return newEvent; } }, /** * Reassigns the validanguage.addEvent function, if an external library is being used. */ addEventInit: function() { switch ( this.useLibrary ) { case 'prototype': case 'scriptaculous': //reassign the addEvent function to use Event.observe this.addEvent = function(obj, evtHandler, func){ Event.observe(obj, evtHandler, func); } break; } }, /** * This function wraps multiple validanguage.el.elemId.validations event handlers * and transformations within a single wrapper to call all loaded validations/transformations * and exit as soon as a validation returns false. * * @param {Object} Form element object * @param {string} eventType, such as "blur" or "keydown" * @param {integer} validationsCounter, denotes the array index of this item in * validanguage.el.elemId.validations */ addOrCreateValidationWrapper: function( Obj, eventType, validationsCounter ) { var id = Obj.id; if (eventType == 'submit') { if (this.empty(validationsCounter)) return; // exit early for onsubmit transformations var formId = validanguage.formLookup[id]; if (typeof formId == 'number') { var form = document.forms[formId]; } else { var form = document.getElementById(formId); } if (typeof validanguage.forms[formId].validations == 'undefined') { validanguage.forms[formId].validations = []; this.addEvent(form, eventType, function(e) { var evt = e || window.evt; var result = validanguage.validationWrapper(e); if (result == false) { evt.returnValue = false; //IE if (evt.preventDefault) evt.preventDefault(); //Everyone else return false; } else { return true; } }); } //add the element and validationsCounter to the list of onsubmit validations for the parent form validanguage.forms[formId].validations[validanguage.forms[formId].validations.length] = { element: Obj, validationsCounter: validationsCounter }; } else { if( typeof validanguage.el[id].handlers == 'undefined' ) validanguage.el[id].handlers = {}; if( typeof validanguage.el[id].handlers[eventType] == 'undefined' ) { validanguage.el[id].handlers[eventType] = []; if( eventType == 'typing') { this.addEvent(Obj, 'keyup', function(e){ validanguage.validationWrapper(e, 'typingTimeout'); }); } else { this.addEvent(Obj, eventType, function(e){ validanguage.validationWrapper(e); }); } } //add validationsCounter to the list of validations for this object/eventType combo validanguage.el[id].handlers[eventType][validanguage.el[id].handlers[eventType].length] = validationsCounter; } }, /** * This function is used to either load a new validation for a form field, or to * reactivate a validation previously removed with the removeValidation() method. * * NOTE: When adding a new validation, you will need to have previously inserted * all the relevant details about the validation in the validanguage.el.formField * object. * * @param {String} elemId * @param {String/Array} eventTypes * @param {String/Array/Function} validationNames */ addValidation: function ( elemId, eventTypes, validationNames ) { if( typeof validationNames[0]=='undefined' ) validationNames = [ validationNames ]; if( typeof eventTypes=='string' ) eventTypes = [ eventTypes ]; var vals = this.el[elemId].validations; for (var i = vals.length - 1; i > -1; i--) { if ( validationNames[0] == '*' || this.inArray(vals[i].name, validationNames) ) { for( var j=eventTypes.length-1; j>-1; j--) { this.addOrCreateValidationWrapper(document.getElementById(elemId), eventTypes[j], i); } } } }, /** * Very simple AJAX function * @param {String} url * @param {Function} callback */ ajax: function( url, callback ) { validanguage.ajaxObj.open("POST", url, true); this.ajaxCallback = callback; this.ajaxObj.onreadystatechange = function() { if(validanguage.ajaxObj.readyState==4){ validanguage.ajaxCallback(validanguage.ajaxObj.responseText) } }; this.ajaxObj.send(null); }, /** * Initializes validanguage.ajax as browser-specific */ ajaxInit: function() { if(window.ActiveXObject){ this.ajaxObj = new ActiveXObject("Microsoft.XMLHTTP"); } else if(window.XMLHttpRequest){ this.ajaxObj = new XMLHttpRequest(); } }, /** * Combines 2 node lists into 1 * @param {Object} obj1 * @param {Object} obj2 */ concatCollection: function(obj1,obj2) { var i; var arr = new Array(); var len1 = obj1.length; var len2 = obj2.length; for (i=0; i-1; i-- ) { while (text.indexOf(stripThese.charAt(i)) != -1) { text = text.replace(stripThese.charAt(i),'','g'); } } } if( regexMatch!=null ) { var myreg = (typeof regexMatch=='string') ? new RegExp(regexMatch) : regexMatch; var thisMatch = myreg.exec(text); if (thisMatch == null) return; //exit early for no match } else { //check for required length based on number of x's in the pattern var countMe = pattern.replace(/[^x]/g,''); if( text.length != countMe.length ) return; } var i = pattern.length; var k = -1; //counter for text var newtext = ''; for( var j=0; j': return '62'; case '?': return '63'; case '@': return '64'; case 'A': return '65'; case 'B': return '66'; case 'C': return '67'; case 'D': return '68'; case 'E': return '69'; case 'F': return '70'; case 'G': return '71'; case 'H': return '72'; case 'I': return '73'; case 'J': return '74'; case 'K': return '75'; case 'L': return '76'; case 'M': return '77'; case 'N': return '78'; case 'O': return '79'; case 'P': return '80'; case 'Q': return '81'; case 'R': return '82'; case 'S': return '83'; case 'T': return '84'; case 'U': return '85'; case 'V': return '86'; case 'W': return '87'; case 'X': return '88'; case 'Y': return '89'; case 'Z': return '90'; case '[': return '91'; case '\\': return '92'; case ']': return '93'; case '^': return '94'; case '_': return '95'; case '`': return '96'; case 'a': return '97'; case 'b': return '98'; case 'c': return '99'; case 'd': return '100'; case 'e': return '101'; case 'f': return '102'; case 'g': return '103'; case 'h': return '104'; case 'i': return '105'; case 'j': return '106'; case 'k': return '107'; case 'l': return '108'; case 'm': return '109'; case 'n': return '110'; case 'o': return '111'; case 'p': return '112'; case 'q': return '113'; case 'r': return '114'; case 's': return '115'; case 't': return '116'; case 'u': return '117'; case 'v': return '118'; case 'w': return '119'; case 'x': return '120'; case 'y': return '121'; case 'z': return '122'; case '{': return '123'; case '|': return '124'; case '}': return '125'; case '~': return '126'; } //close switch return ''; }, /** * Fetches all comment nodes in the passed form node and returns them in a node list * Doesnt work in konqueror, since konqueror strips all comments from the DOM * * @param {Containing Node} el */ getComments: function(el) { if (!el) el = document.documentElement; var comments = new Array(); var length = el.childNodes.length; for (var c = 0; c < length; c++) { if (el.childNodes[c].nodeType == 8) { comments[comments.length] = el.childNodes[c]; } else if (el.childNodes[c].nodeType == 1) { comments = comments.concat(this.getComments(el.childNodes[c])); } } return comments; }, /** * Helper function used by validateDate() and validateTimestamp(). * @param {Object} options object provided by the user to validateDate() or validateTimestamp(). * @param {Object} defaults which should be used. Used to allow validateDate() and validateTimestamp() * to have different default dateOrder values. */ getDateTimeDefaultOptions: function ( options, defaults ) { if( options==null ) options = {}; // Date options if( typeof options.dateOrder=='undefined' ) options.dateOrder=defaults.dateOrder; options.dateOrder = options.dateOrder.toLowerCase(); if( typeof options.allowedDelimiters=='undefined' || typeof options.allowedDelimiters!='string' ) options['allowedDelimiters'] = './-'; if( typeof options.twoDigitYearsAllowed=='undefined' ) options.twoDigitYearsAllowed = false; if( typeof options.oneDigitDaysAndMonthsAllowed=='undefined' ) options.oneDigitDaysAndMonthsAllowed = true; if( typeof options.maxYear=='undefined' ) options.maxYear = new Date().getFullYear() + 15; if( typeof options.minYear=='undefined' ) options.minYear = 1900; if( typeof options.rejectDatesInTheFuture=='undefined' ) options.rejectDatesInTheFuture = false; if( typeof options.rejectDatesInThePast=='undefined' ) options.rejectDatesInThePast = false; // Time options if( typeof options.timeIsRequired=='undefined' ) options.timeIsRequired = false; if( typeof options.timeUnits=='undefined' ) options.timeUnits = 'hms'; if( typeof options.microsecondPrecision=='undefined' ) options.microsecondPrecision = 6; return options; }, /** * This function checks for a given setting in increasing specificity * within the validanguage.forms[formId].settings object, and within the passed * validanguage.el objects * * @param {string} Name of the setting to be retrieved * @param {string} ID of the form field object being validated * @param {Object} validanguage.el.objId.validations[index] object */ getElSetting: function( setting, id, validationObj ) { var formSetting = this.getFormSettings(id); var retVal = formSetting[setting]; //global setting if( typeof validationObj!='undefined' && typeof validationObj[setting] != 'undefined' ) { retVal = validationObj[setting]; } else if( typeof this.el[id][setting] != 'undefined' ) { retVal = this.el[id][setting]; } return retVal; }, /** * This function returns the validanguage.form[formId].setting object for the passed element ID * @param {string or Node} id of the input field or input node * @return {Object} settings object */ getFormSettings: function(id) { var formName = ( document.getElementById(id).nodeName.toLowerCase()=='form' ) ? id : this.formLookup[id]; return this.forms[formName].settings; }, /** * This function parses the passed comment to retrieve the indicated setting * * @param {String} Name of the setting to retrieve / needle * @param {String} Full text of the HTML comment / haystack * @return {String} The value of the requested setting */ getSettingFromComment: function( setting, comment ) { var needle = ' '+setting+'='; var startPos = comment.indexOf(needle); if( startPos == -1) return null; var delimiterPos = (startPos*1) + (needle.length*1); var delimeter = '\\' + comment.charAt(delimiterPos); var Regex = needle+delimeter+'(.+?)'+delimeter; var myreg = new RegExp(Regex); var thisMatch = myreg.exec(comment, 'gi'); if (thisMatch == null) { return null; //no match } else if (thisMatch[1]) { //Convert booleans. I hope this doesnt screw anyone later.... if(thisMatch[1]=='true') thisMatch[1]=true; if(thisMatch[1]=='false') thisMatch[1]=false; return thisMatch[1]; } }, /** * This function hides the div containing the validanguage error messages for * failed validations */ hideError: function() { var settings = validanguage.getFormSettings(this.id); var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); if (errorDisplay != null) { errorDisplay.innerHTML = ''; var errorDiv = errorDisplay.parentNode; errorDiv.style.display = 'none'; errorDiv.className = settings.noErrorClassName; } if (! this.className.match(validanguage.settings.passedFieldClassName)) this.className += ' '+validanguage.settings.passedFieldClassName; if (this.className.match(validanguage.settings.failedFieldClassName)) this.className = this.className.replace(validanguage.settings.failedFieldClassName,''); //Do we need to remove any vd_li items? if( !settings.showFailedFields ) return; if( document.getElementById(this.id + settings.errorListItemSuffix) != null ) { var errorList = document.getElementById(settings.errorListId); errorList.removeChild( document.getElementById(this.id + settings.errorListItemSuffix) ); if( errorList.getElementsByTagName('LI').length==0 ) document.getElementById(settings.errorDivId).style.display='none'; } }, /** * Determines whether the passed item is present in the array or object. * * @param {Object} needle * @param {Object} haystack */ inArray: function( needle, haystack ) { for( var i=haystack.length-1; i>-1; i-- ){ if( haystack[i]===needle ) return true; } return false; }, /** * This function searches settingsHaystack for all variables defined in the settingsNeedles * array, and if they are located, they are copied over to the settingsTarget * * @param {Object} settingsHaystack -- Object location to be searched for settings * @param {Array} settingsNeedles -- Array of settings to be checked * @param {Object} settingsTarget -- Object location where any defined settings should be copied to * @param {String} constrainType -- Optional type constraint */ inheritIfDefined: function ( settingsHaystack, settingsNeedles, settingsTarget, constrainType ) { if( typeof settingsNeedles.length == 'undefined' ) return false; for( var i=settingsNeedles.length-1;i>-1;i--) { if ( typeof settingsHaystack[settingsNeedles[i]]!='undefined' && ( this.empty(constrainType) || typeof settingsHaystack[settingsNeedles[i]]==constrainType ) ) { settingsTarget[settingsNeedles[i]] = settingsHaystack[settingsNeedles[i]]; } } }, /** * Initialization function for validanguage. Adds the onload hook * which fires off the populate() method to add all the other event * handlers */ init: function() { this.addEventInit(); this.ajaxInit(); this.addEvent(window, 'load', function() { validanguage.populate.call(validanguage); }); }, /** * Function to insert 1 Node after another in the DOM. If the referenceNode * is a label, this function will use the nextSibling instead * * @param {Node} nodeToAdd * @param {Node} referenceNode */ insertAfter: function (nodeToAdd, referenceNode ) { if (referenceNode.nextSibling) { if (referenceNode.nextSibling.nodeName.toLowerCase() == 'label') { referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling.nextSibling); } else { referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling); } } else { referenceNode.parentNode.appendChild(nodeToAdd); } }, /** * This function parses all comments in the current document, looking for * the comment-based API and converts any validanguage statements it * finds into the element/json-based API for further processing. * * @param {Array} For konqueror, we pass this function an Array with all * the comments (retrieved via AJAX) * For all other browsers, konquerorComments is undefined and * we retrieve the comments normally via the DOM */ loadCommentAPI: function( konquerorComments ) { var supportedSettings = ['mode','expression','suppress','onsubmit','onblur','onchange', 'onkeypress','onkeyup','onkeydown','onclick', 'ontyping', 'errorMsg','onerror','onsuccess','focusOnError', 'showAlert','required','requiredAlternatives', 'maxlength','minlength','regex','field', 'errorOnMatch','modifiers','transformations','validations']; var allComments = (this.empty(konquerorComments)) ? this.getComments() : konquerorComments; var length = allComments.length; for (var j=0; j -1; k--) { var tempSetting = this.getSettingFromComment(supportedSettings[k], commentText); if (!(tempSetting == null || (typeof tempSetting == 'string' && tempSetting == '') )) settings[supportedSettings[k]] = tempSetting; } //iterate thru our targets and assign the settings k = targets.length; for (var l = 0; l < k; l++) { var id = targets[l]; var obj = document.getElementById(id); if (typeof this.el[id] == 'undefined' || obj == null) this.el[id] = {}; /** CHARACTER VALIDATION **/ if (typeof settings.expression != 'undefined') { this.el[id].characters = {}; this.inheritIfDefined(settings, ['expression','errorMsg','mode','suppress','onerror','onsuccess'], this.el[id].characters); this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].characters); } /** REGEX **/ if (typeof settings.regex != 'undefined') { this.el[id].regex = { expression: settings.regex }; this.inheritIfDefined(settings, ['errorOnMatch','modifiers'], this.el[id].regex); this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].regex); } /** MISC SETTINGS **/ // Only inherit event handlers that are non-boolean transformations this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id], 'string'); this.inheritIfDefined(settings, ['minlength','maxlength','requiredAlternatives','required','focusOnError','showAlert', 'onsuccess','onerror','errorMsg'], this.el[id]); if (typeof settings.minlength != 'undefined') { this.el[id].minlengthEvents = {}; this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].minlengthEvents); } if (typeof settings.maxlength != 'undefined') { this.el[id].maxlengthEvents = {}; this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].maxlengthEvents); } if (typeof settings.required != 'undefined') { this.el[id].requiredEvents = {}; this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].requiredEvents); } /** VALIDATIONS AND TRANSFORMATIONS **/ if (typeof this.el[id].validations == 'undefined') this.el[id].validations = []; if (typeof this.el[id].transformations == 'undefined') this.el[id].transformations = []; var functionModifiers = ['focusOnError','showAlert','onsuccess','onerror','errorMsg']; //Load validations if( typeof settings.validations != 'undefined' && !this.empty(settings.validations) ) { this.el[id].validations[this.el[id].validations.length] = {}; this.el[id].validations[this.el[id].validations.length-1].name = settings.validations; this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].validations[this.el[id].validations.length-1]); this.inheritIfDefined(settings, functionModifiers, this.el[id].validations[this.el[id].validations.length-1]); } //Load transformations if( typeof settings.transformations != 'undefined' && !this.empty(settings.transformations) ) { this.el[id].transformations[this.el[id].transformations.length] = {}; this.el[id].transformations[this.el[id].transformations.length-1].name = settings.transformations; this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].transformations[this.el[id].transformations.length-1]); } } // foreach (targets) } // close if(validanguage_comment) } // close tagArray loop } // close allComments loop }, /** * This function parses the validanguage.el object to load all the * form-element-specific validation settings which the end user has defined * via the Object-based API */ loadElAPI: function() { for( var elem in this.el ) { //for each element.... //skip to the next if it's not an element ID try { if( typeof document.getElementById(elem) == undefined || this.empty(document.getElementById(elem)) ) continue; } catch(e) { continue; } var Obj = document.getElementById(elem); var settings = validanguage.getFormSettings(elem); if (typeof this.el[elem].validations == 'undefined') this.el[elem].validations = []; if (typeof this.el[elem].field == 'undefined') this.el[elem].field = elem; /** REQUIRED **/ if (typeof this.el[elem].required != 'undefined' && this.el[elem].required==true) { this.el[elem].validations[this.el[elem].validations.length] = {}; this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateRequired'; this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg; this.inheritIfDefined( this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] ); //If specific requiredEvents are provided, use those instead of the element level event handlers 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] ); //We need to call the validateRequiredAlternatives function when a requiredAlternative is clicked if(settings.validateRequiredAlternativesOnclick==true && typeof this.el[elem].requiredAlternatives != 'undefined' ) { var onsuccessFuncs = (typeof this.el[elem].onsuccess!='undefined') ? this.el[elem].onsuccess : settings.onsuccess; var onerrorFuncs = (typeof this.el[elem].onerror!='undefined') ? this.el[elem].onerror : settings.onerror; var alts = this.resolveArray(this.el[elem].requiredAlternatives,'string'); for( var y=alts.length-1; y>-1; y--) { this.requiredAlternatives[alts[y]] = {}; if( !((typeof document.getElementById(alts[y]).type != 'undefined') && (document.getElementById(alts[y]).type=='checkbox'||document.getElementById(alts[y]).type=='radio')) ) continue; this.requiredAlternatives[alts[y]].onsuccess = onsuccessFuncs; this.requiredAlternatives[alts[y]].onerror = onerrorFuncs; this.requiredAlternatives[alts[y]].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg; this.requiredAlternatives[alts[y]].parentId = elem; this.addEvent( document.getElementById(alts[y]), 'click', function(e) { validanguage.validateRequiredAlternatives(e); } ); } } } /** REGEX **/ if (typeof this.el[elem].regex != 'undefined') { this.el[elem].validations[this.el[elem].validations.length] = {}; this.el[elem].validations[this.el[elem].validations.length - 1].name = 'validanguage.validateRegex'; var errorMsg = (typeof this.el[elem].errorMsg == 'undefined') ? settings.errorMsg : this.el[elem].errorMsg; if(typeof this.el[elem].regex.errorMsg != 'undefined') errorMsg = this.el[elem].regex.errorMsg this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = errorMsg; this.inheritIfDefined(this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]); this.inheritIfDefined(this.el[elem].regex, this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]); if(typeof this.el[elem].regex.errorOnMatch=='undefined') this.el[elem].regex.errorOnMatch=settings.errorOnMatch; } /** MAXLENGTH **/ if (typeof this.el[elem].maxlength != 'undefined') { this.el[elem].validations[this.el[elem].validations.length] = {}; this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMaxlength'; this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.maxlengthErrorMsg.replace('{!maxlength}',this.el[elem].maxlength); //If specific maxlengthEvents are provided, use those instead of the element level event handlers 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] ); } /** MINLENGTH **/ if (typeof this.el[elem].minlength != 'undefined') { this.el[elem].validations[this.el[elem].validations.length] = {}; this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMinlength'; this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.minlengthErrorMsg.replace('{!minlength}',this.el[elem].minlength); //If specific minlengthEvents are provided, use those instead of the element level event handlers 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] ); } /** CHARACTERS **/ if (typeof this.el[elem].characters != 'undefined' && typeof this.el[elem].characters.mode != 'undefined' && typeof this.el[elem].characters.expression != 'undefined' ) { //supported shortcuts var expression = this.el[elem].characters.expression; expression = expression.replace('alphaUpper','ABCDEFGHIJKLMNOPQRSTUVWXYZ'); expression = expression.replace('alphaLower','abcdefghijklmnopqrstuvwxyz'); expression = expression.replace('alpha','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); expression = expression.replace('numeric','0123456789'); this.el[elem].characters.characterExpression = expression; var validanguageExpr = ';'; for (var j=expression.length-1;j>-1;j--){ validanguageExpr += expression.charCodeAt(j) + ';'; } this.el[elem].characters.expression = validanguageExpr; if(typeof this.el[elem].characters.suppress=='undefined' || this.el[elem].characters.suppress==true) this.addEvent(Obj, "keypress", validanguage.validateKeypress ); //Should we assign handlers? -- onblur, onsubmit, etc var assignHandlers = false; for( var z=this.supportedEventHandlers.length-1; z>-1; z--) { if(typeof this.el[elem].characters[this.supportedEventHandlers[z]] != 'undefined' && this.el[elem].characters[this.supportedEventHandlers[z]]==true) assignHandlers = true; } if( assignHandlers ) { this.el[elem].validations[this.el[elem].validations.length] = {}; this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateCharacters'; this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = true; for( var z=this.supportedEventHandlers.length-1; z>-1; z--) { if(typeof this.el[elem].characters[this.supportedEventHandlers[z]] != 'undefined' && this.el[elem].characters[this.supportedEventHandlers[z]]==true) this.el[elem].validations[this.el[elem].validations.length-1][this.supportedEventHandlers[z]] = true; } //assign onerror if(typeof this.el[elem].characters.errorMsg != 'undefined') { this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = this.el[elem].characters.errorMsg; } else { this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.characterValidationErrorMsg; } } } if (typeof this.el[elem].transformations == 'undefined') this.el[elem].transformations = []; /** TRANSFORMATIONS **/ //First, check for transformations listed by event type var j = this.supportedEventHandlers.length; for (var k = 0; k < j; k++) { var handler = this.supportedEventHandlers[k]; if( typeof this.el[elem][handler] != 'undefined' && typeof this.el[elem][handler] != 'boolean' ) { //Add the defined transformation to the transformations array this.el[elem].transformations[this.el[elem].transformations.length] = {}; var n = this.el[elem].transformations.length - 1; this.el[elem].transformations[n].name = this.el[elem][handler]; //store the event handler this.el[elem].transformations[n][handler] = true; } } var h = this.el[elem].transformations.length; for (var i = 0; i < h; i++) { //for each transformation, load the appropriate function in the element's transformations array var eventLoaded = false; var j = this.supportedEvents.length; for (var k = 0; k < j; k++) { if(this.supportedEvents[k]=='submit') continue; if (typeof this.el[elem].transformations[i]['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].transformations[i]['on' + this.supportedEvents[k]] == true) { eventLoaded = true; this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k]); } } //if they didnt supply any events, we default to the defined defaultTransformationsHandlers (usually only "blur") if (eventLoaded == false) { if (Obj.nodeName.toLowerCase() == 'form') { this.addOrCreateValidationWrapper(Obj, 'submit'); } else { for (var l = settings.defaultTransformationsHandlers.length - 1; l > -1; l--) { this.addOrCreateValidationWrapper(Obj, settings.defaultTransformationsHandlers[l]); } } } } /** VALIDATIONS **/ if (typeof this.el[elem].validations != 'undefined') { var h = this.el[elem].validations.length; for (var i = 0; i < h; i++) { //for each validation, load the appropriate function in the element's validations array var eventLoaded = false; var j = this.supportedEvents.length; for (var k = 0; k < j; k++) { if (typeof this.el[elem].validations[i]['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].validations[i]['on' + this.supportedEvents[k]] == true) { eventLoaded = true; this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k], i); } } //if they didnt supply any events, we default to the defined defaultValidationHandlers (usually only "submit") if (eventLoaded == false) { for( var l=settings.defaultValidationHandlers.length-1; l>-1;l--) { this.addOrCreateValidationWrapper(Obj, settings.defaultValidationHandlers[l], i); } } } } } //close elem loop }, /** * This function searches the passed subject and returns an Array of strings * which are delimited by the characters passed to the function in the * first 2 arguments. Used to pull comments from the document source * @param {String} startChar * @param {String} endChar * @param {String} subject * @return {Array} */ parseSubstring: function( startChar, endChar, subject ) { var matches = []; var parts = subject.split(startChar); for( var i=0; i-1; k--) { //iterate thru the array and store the id in our formLookup hash table if (typeof(allObjects[k].id) != 'undefined' && !this.empty(allObjects[k].id)) { this.formLookup[allObjects[k].id] = formName; } } } if (this.browser == 'konqueror' && this.settings.loadCommentAPI == true) { this.ajax(document.location.href, function(docText) { var comments = validanguage.parseSubstring( '', docText ); validanguage.loadCommentAPI( comments ); if (typeof validanguage.overloadFormSettings != 'undefined') validanguage.overloadFormSettings(); if (typeof validanguage.el != undefined && !this.empty(validanguage.el)) validanguage.loadElAPI(); }); } else { //Load comment API if (this.settings.loadCommentAPI == true) this.loadCommentAPI( ); //Load Form-Specific Settings if (typeof this.overloadFormSettings != 'undefined') this.overloadFormSettings(); //Load the validanguage.el API if (typeof this.el != undefined && !this.empty(this.el)) this.loadElAPI(); } // Call any defined validanguage.toggle() functions to true up the UI if (this.settings.callToggleTransformationsOnload) { for (var id in this.el) { if (typeof this.el[id].transformations != 'undefined') { for (var i = this.el[id].transformations.length - 1; i > -1; i--) { if (typeof this.el[id].transformations[i].name == 'undefined') continue; var funcString = this.el[id].transformations[i].name; if (typeof funcString == 'string' && funcString.indexOf('validanguage.toggle') > -1) { var transformations = this.resolveArray(funcString, 'function'); var j = transformations.length; for (var k = 0; k < j; k++) { transformations[k].call(document.getElementById(id)); } } } } } } //Garbage collection this.addEvent(window, 'unload', function() { delete validanguage; }); //Call any onload handler defined by the user this.settings.onload.call(this); }, //close populate /** * This function is used to deactivate a previously loaded validation. * Provide the element ID of the field and a list of event types and validation * names to deactivate. You can use the * character as the eventType and/or * the validationName arguments to include ALL eventTypes/validationNames. * * NOTE: if you are tempted to use removeValidation('id','*','*'), you may be * better off using validanguage.el.id.disabled=true, as this is much easier to * undo later. * * NOTE: It is up to you to make sure no error msgs are displaying before disabling * a validation. If one is showing and all validations are disabled, the onsuccess * handlers used to clear the error msgs will never be called. * * @param {String} elemId * @param {String/Array} eventTypes * @param {String/Array/Function} validationNames */ removeValidation: function ( elemId, eventTypes, validationNames ) { //prep our arguments if( eventTypes == '*' ) { eventTypes = this.supportedEvents; } else if( typeof eventTypes[0]=='undefined') { eventTypes = [ eventTypes ]; } if( typeof validationNames=='string' ) validationNames = [ validationNames ]; for (var j = eventTypes.length - 1; j > -1; j--) { if (eventTypes[j] == 'submit') { //Remove form.onsubmit validations var vals = this.forms[this.formLookup[elemId]].validations; formValLoop: for (var i = vals.length - 1; i > -1; i--) { if( vals[i]==undefined || vals[i].element.id != elemId ) continue formValLoop; if ( validationNames[0] == '*' || this.inArray( this.el[elemId].validations[vals[i].validationsCounter].name, validationNames ) ) { try { delete vals[i]; } catch(e) {} } } } else { //Remove field-specific validations var vals = this.el[elemId].validations; for (var i = vals.length - 1; i > -1; i--) { if ( validationNames[0] == '*' || this.inArray(vals[i].name, validationNames) ) { try { delete this.el[elemId].handlers[eventTypes[j]][i]; } catch(e) {} } } } } }, /** * This function accepts as input a function, a string, an array of * functions, an array of strings, or a comma-delimited list of functon * names as its argument and returns an Array of functions or strings * comprising the passed arguments. Example: transforms 'foo, bar' * to [foo, bar] * * @param {Function or String or Array} args * @param {String} returnType should be either 'string' or 'function' * @return {Array of Functions} */ resolveArray: function (args, returnType, ignoreCommas) { var returnArray = []; if( typeof args == 'object' ) { var i=args.length; for (var j=0; j '; errorDiv.innerHTML = innerHTML; errorDiv.className = settings.onErrorClassName; var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix); } else { var errorDiv = errorDisplay.parentNode; errorDiv.style.display = 'block'; errorDiv.className = settings.onErrorClassName; } if(validanguage.useLibrary=='scriptaculous') new Effect.Highlight(errorDiv, { startcolor: '#A85F5F', endcolor: '#C0A6A6', restorecolor: '#ddd' }); errorDisplay.innerHTML = errorMsg; if (! this.className.match(validanguage.settings.failedFieldClassName)) this.className += ' '+validanguage.settings.failedFieldClassName; if (this.className.match(validanguage.settings.passedFieldClassName)) this.className = this.className.replace(validanguage.settings.passedFieldClassName,''); //if(this.nodeName.toLowerCase()=='input' && this.type.toLowerCase()=='text') this.style.backgroundColor = '#FF6666'; //Do we need to add any vd_li items? if( !settings.showFailedFields ) return; if( document.getElementById(settings.errorDivId) == null ) { var errorDiv = document.createElement('DIV'); errorDiv.id = settings.errorDivId; document.body.appendChild(errorDiv); } else { var errorDiv = document.getElementById(settings.errorDivId); } if (document.getElementById(settings.errorListId) == null) { errorDiv.innerHTML = settings.errorListText + '
    '; } var errorDivInner = errorDiv.innerHTML.toLowerCase(); errorDivInner = errorDivInner.replace(/"/g,''); //remove quotes for IE weirdness var errorList = document.getElementById(settings.errorListId); var listItem = '
  • '+validanguage.el[this.id].field+'
  • '; var listItemExists = listItem.toLowerCase(); listItemExists = listItemExists.replace(/"/g,''); //remove quotes for IE weirdness if(errorDivInner.indexOf(listItemExists)==-1) errorList.innerHTML += listItem; document.getElementById(settings.errorDivId).style.display='block'; }, /** * This function is intended for us as an onsubmit transformation. It replaces the form's * submit button with the text specified in settings.showSubmitMessageMessage. * * @param {Object} validationResult * @param {Object} failedValidations */ showSubmitMessage: function( validationResult, failedValidations ) { if( validationResult==false ) return; var settings = validanguage.forms[this.id].settings; //first, we need to find the submit button and hide it var inputs = this.getElementsByTagName('INPUT'); for( var i=inputs.length-1; i>-1; i-- ) { if( typeof inputs[i].type!='undefined' && inputs[i].type=='submit' ) { validanguage.forms.submitButton = submitButton = inputs[i]; break; } } submitButton.style.display='none'; var loadingDiv = document.createElement('DIV'); loadingDiv.id = settings.showSubmitMessageId; loadingDiv.innerHTML = settings.showSubmitMessageMessage; validanguage.insertAfter( loadingDiv, inputs[i] ); //set a timeout to unhide the submit button after 60 seconds //in case the request times out and the user needs to resubmit the form setTimeout( function( ){ validanguage.forms.submitButton.style.display='inline'; }, 60000); }, /** * This function splits a string into an array using commas as the delimiter, * while being smart enough to ignore commas appearing inside parenthesis and * braces. * * @param {string} Comma-delimited string * @return {Array} */ smartCommaSplit: function ( str ) { var openParens = 0; var openBraces = 0; var lastSplit = 0; var returnArray = []; var len = str.length; for( var i=0; i -1; k--) if (radios[k].checked) radioButtonChecked = true; if (!radioButtonChecked) radioExceptionApplies=true; } // toggle visibility if (typeof obj.toggle != 'undefined') { var visibleMet = (typeof obj.toggle.visible != 'undefined') ? validanguage.toggleCriteriaMet(this, obj.toggle.visible, settings) : false; var hiddenMet = (typeof obj.toggle.hidden != 'undefined') ? validanguage.toggleCriteriaMet(this, obj.toggle.hidden, settings) : false; for (var k = targets.length - 1; k > -1; k--) { if (visibleMet && !radioExceptionApplies) validanguage.toggleDisplay(targets[k], ''); if (hiddenMet || (settings.toggleVisibilityDefaultsToHidden && !visibleMet)) validanguage.toggleDisplay(targets[k], 'none'); } } // close toggle visibility // value control if (typeof obj.values != 'undefined') { for (var k = targets.length - 1; k > -1; k--) { if (typeof obj.values.checked != 'undefined' && this.checked==true) document.getElementById(targets[k]).value=obj.values.checked; else if (typeof obj.values.unchecked != 'undefined' && this.checked==false) document.getElementById(targets[k]).value=obj.values.unchecked; else if (typeof obj.values[this.value] != 'undefined') document.getElementById(targets[k]).value=obj.values[this.value]; } } // close toggle value // dynamic selects if (typeof obj.dynamicSelect != 'undefined') { var sel2 = document.getElementById(targets[0]); // dynamicSelect only supports 1 target for (var sel1Key in obj.dynamicSelect) { if (typeof obj.dynamicSelect[sel1Key] == 'object' ) { var sel1Val = obj.dynamicSelect[sel1Key]; if (sel1Key == this.value) { // remove the existing options while (sel2.options.length > 0) { sel2.remove(0); } for (var sel2Key in sel1Val) { if (sel2Key == '_default') continue; var opt = document.createElement('option'); opt.value = sel2Key; opt.text = sel1Val[sel2Key]; sel2.options.add(opt); } if (typeof sel1Val['_default'] != 'undefined') sel2.value = sel1Val['_default']; } } } } // close dynamicSelect } }, /** * Determines whether the criteria used by the toggle function * has been met * @param {Object} field * @param {string} criteria * @param {Object} settings object */ toggleCriteriaMet: function( field, criteria, settings ) { if (criteria == 'checked') { return !!(field.checked); } else if (criteria == 'unchecked') { return !(field.checked); } else if (criteria == 'empty') { return !!(validanguage.inArray(field.value, settings.emptyOptionElements)); } else if (criteria == 'notEmpty') { return !(validanguage.inArray(field.value, settings.emptyOptionElements)); } else { return !!(field.value == criteria); } }, /** * Function used to hide or show a node. This function will also automatically disable or enable * any form fields contained within the passed node. * @param {Object} nodeId ID of the node to hide/show * @param {Object} visibility (optional) Pass either 'none' or '' to hide/show. Or leave blank to toggle */ toggleDisplay: function( nodeId, visibility ) { var node = document.getElementById(nodeId); var nodeName = node.nodeName.toLowerCase(); if (visibility==null||visibility==undefined) visibility = (node.style.display=='none') ? '' : 'none'; disabledBool = (visibility=='none') ? true : false; // show/hide the passed node node.style.display = visibility; // disable/enable any form fields contained within the node if (nodeName == 'input' || nodeName == 'textarea' || nodeName == 'select') { node.disabled = disabledBool; return; } var allInputs = node.getElementsByTagName('input'); var allTextareas = node.getElementsByTagName('textarea'); var allSelect = node.getElementsByTagName('select'); var allObjects = this.concatCollection(allInputs, allTextareas); var allObjects = this.concatCollection(allObjects, allSelect); for (var i = allObjects.length - 1; i > -1; i--) { allObjects[i].disabled = disabledBool; } }, /** * Validates that a field does not contain any of the invalid characters * listed in the characters validation rules. * * @param {string} text */ validateCharacters: function( text ) { var id = this.id; var mode = validanguage.el[id].characters.mode; var expression = validanguage.el[id].characters.characterExpression; switch(mode) { case 'allow': outerLoop: for( var i=text.length-1;i>-1;i--) { innerLoop: for (var j=expression.length-1; j>-1; j--) { if(expression.charAt(j)==text.charAt(i)) continue outerLoop; } //if we got here, they entered a disallowed character return false; } break; case 'deny': outerLoop: for( var i=text.length-1;i>-1;i--) { innerLoop: for (var j=expression.length-1; j>-1; j--) { if(expression.charAt(j)==text.charAt(i)) return false; } } break; } return true; }, /** * Validates that a valid credit card number has been supplied * @param {string} text * @param {array} cardTypes * @param {boolean} testChecksum Pass false to skip the luhn checksum test */ validateCreditCard: function (text, cardTypes, testChecksum) { if (validanguage.empty(cardTypes)) cardTypes = ['amex','disc','mc','visa']; // Strip any non-digits var text=text.replace(/\D/g,''); var cards = { 'amex' : '^3[4|7]\\d{13}$', 'bankcard' : '^56(10\\d\\d|022[1-5])\\d{10}$', 'diners' : '^(?:3(0[0-5]|[68]\\d)\\d{11})|(?:5[1-5]\\d{14})$', 'disc' : '^(?:6011|650\\d)\\d{12}$', 'electron' : '^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$', 'enroute' : '^2(?:014|149)\\d{11}$', 'jcb' : '^(3\\d{4}|2100|1800)\\d{11}$', 'maestro' : '^(?:5020|6\\d{3})\\d{12}$', 'mc' : '^5[1-5]\\d{14}$', 'solo' : '^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$', 'switch' : '^(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)$', 'visa' : '^4\\d{12}(\\d{3})?$', 'voyager' : '^8699[0-9]{11}$' }; var validCard = false; for (var i = cardTypes.length; i--; i > -1) { validCard = validanguage.validateRegex(text, { expression: cards[cardTypes[i]] }); if (validCard) break; } if (!validCard) return false; if (testChecksum===false) return true; /** Run the luhn checksum test * Luhn algorithm number checker - (c) 2005-2008 shaman - www.planzero.org */ // Set the string length and parity var number_length=text.length; var parity=number_length % 2; // Loop through each digit and do the maths var total=0; for (var i=0; i < number_length; i++) { var digit=text.charAt(i); // Multiply alternate digits by two if (i % 2 == parity) { digit=digit * 2; // If the sum is two digits, add them together (in effect) if (digit > 9) { digit=digit - 9; } } // Total up the digits total = total + parseInt(digit); } // If the total mod 10 equals 0, the number is valid if (total % 10 == 0) { return true; } else { return false; } }, /** * Validates that a valid date is supplied and is entered in the correct format. * The format is specified by the following arguments
    * @param {String} text to be validated
    * @param {Object} Options object containing any of the following options
    * dateOrder: {String} This must be either 'ymd','mdy','dmy','myd','ydm', or 'dym
    * allowedDelimiters: {String}. A string containing the list of delimiters which are allowed. * Example: './-'
    * twoDigitYearsAllowed: {Boolean}. Is a 2-digit year valid
    * oneDigitDaysAndMonthsAllowed: {Boolean}. Is a 1-digit month or a 1-digit day valid
    * maxYear: {Integer}. Years greater than maxYear will be treated as invalid. Defaults to 15 years from today
    * minYear: {Integer}. Years less than minYear will be treated as invalid. Defaults to 1900
    * rejectDatesInTheFuture: {Boolean}. Are dates in the future valid? rejectDatesInTheFuture defaults to false
    * rejectDatesInThePast: {Boolean}. Are dates in the past valid? rejectDatesInThePast defaults to false
    */ validateDate: function( text, options ) { //Set default values options = validanguage.getDateTimeDefaultOptions(options, {dateOrder: 'mdy'} ); //Loop thru the allowedDelimiters to start building our regex and figure out which one was actually used var delimiterUsed; var delimiterRegex = '('; for( var i=options.allowedDelimiters.length-1;i>-1;i--) { delimiterRegex += '\\' + options.allowedDelimiters.charAt(i); if (i>0) delimiterRegex += '|'; if (text.indexOf(options.allowedDelimiters.charAt(i)) > -1) { delimiterUsed = options.allowedDelimiters.charAt(i); } } delimiterRegex += ')'; if( delimiterUsed==null ) return false; //no delimiter was used var parts = text.split(delimiterUsed); if( parts.length!=3 ) return false; //they used more than one delimiter or didnt give us a valid date //Next we need to build the regex to validate the date comprises only integers and delimiters var regex = '^'; for(var j=0; j<3; j++) { switch( options.dateOrder.charAt(j) ) { case 'y': var num = (options.twoDigitYearsAllowed) ? '{2,4}' : '{4}'; regex += '\\d' + num; break; case 'm': case 'd': var num = (options.oneDigitDaysAndMonthsAllowed) ? '{1,2}' : '{2}'; regex += '\\d' + num; break; } if(j<2) regex += delimiterRegex; } regex += '$'; //Run the regex var reg = new RegExp( regex ); var thisMatch = reg.exec(text); if (thisMatch == null) return false; //grab our dates var year = parts[options.dateOrder.indexOf('y')]; var month = parts[options.dateOrder.indexOf('m')]; var day = parts[options.dateOrder.indexOf('d')]; // Verify the year isnt 3-digits long to account for me being lazy in the regex check above if( year.length==3 ) return false; //Make sure the year is in bounds if( (year < options.minYear && year.length==4) || (year > options.maxYear) ) return false; //Next we check that the date actually exists, to rule out stuff like "12/32/1976" if( !validanguage.validateDateExists(year,month,day) ) return false; if( options.rejectDatesInTheFuture || options.rejectDatesInThePast ){ var now = new Date(); var then = new Date(); then.setDate(day); then.setMonth(--month); // January = 0 then.setFullYear(year); if( (options.rejectDatesInTheFuture && then > now) || (options.rejectDatesInThePast && then < now) ) return false; } return true; }, /** * Helper function to verify a date actually exists. Used to reject values * such as "12/35/2009" * @param {integer} year, preferably 4-digit * @param {integer} month * @param {integer} day * @return {Boolean} */ validateDateExists: function(year, month, day) { if(year.length==2) { var prefix = (year > 20 ) ? '19' : '20'; year = prefix+year.toString(); } if( month.charAt(0)=='0' ) month = month.substr(1,1); if( day.charAt(0)=='0' ) day = day.substr(1,1); if( month < 0 || month > 12 ) return false; switch( month.toString() ) { case '4': case '6': case '9': case '11': var maxDay = 30; break; case '2': var maxDay = ((year % 4 == 0) && ( (!(year % 100 == 0)) || (year % 400 == 0)) ) ? 29 : 28; break; default: var maxDay = 31; } if( day < 0 || day > maxDay ) return false; return true; }, /** * Validates an email address */ validateEmail: function( text ) { if(! text.match(/^([a-zA-Z0-9]+[a-zA-Z0-9._%-]*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4})$/) ) return false; else return true; }, /** * Calls the validationWrapper function on the passed form ID * @param {String or Form Node} Id of the form * @return {Object} Object containing 2 elements: * Object.result is a boolean to indicate whether any fields in the form failed validation * Object.failedValidations will contain info detailing any form fields with failed validations */ validateForm: function( formId ) { var form; if( formId==undefined) form = document.forms[0]; else if (typeof formId=='string') form = document.getElementById(formId); else form = formId; return this.validationWrapper( form, 'validateForm' ); }, /** * Validates that a field contains a valid IPv4 address */ validateIP: function( text ) { var bytes = text.split('.'); if (bytes.length == 4) { for (var i=bytes.length-1; i> -1; i--) { if (!(validanguage.validateNumeric(bytes[i]) && bytes[i] >= 0 && bytes[i] <= 255)) { return false; } } return true; } return false; }, /** * Function to suppress the keys specified in validanguage.el.characters * from being entered into a textarea or text box. * * This function must be forked to fetch the keyCode * and differentiate between control and non-control characters. * For example: in Mozilla both the delete key and . equate to 46. * We dont fork based on supported functions because IE and Moz event models * for keyCodes/charCodes are different and confusing, so we're * better off sniffing the browser. * * @param {Event Object} e */ validateKeypress: function(e) { var evt = e || window.evt; var $this = evt.currentTarget || evt.srcElement; var id = $this.id; var formName = validanguage.formLookup[id]; var settings = validanguage.getFormSettings(id); if (validanguage.browser=='ie' || validanguage.browser=='opera' ) { //branch for IE and opera //we dont have to worry about noncontrol keys since they dont fire keypress events in IE charCode = evt.keyCode; if ( ( (charCode == 16) && (evt.shiftKey) ) || (evt.ctrlKey) ) return true; //prevents firing on ctrl key in opera } else { //branch for Mozilla if ( (evt.charCode == 0) || (evt.ctrlKey) ) return true; //return true for all control keys and if control is held down charCode = evt.which; //capture charCode of non-control keys } charCode += ';'; searchString = new String(validanguage.el[id].characters.expression); var mode = validanguage.el[id].characters.mode; if ( ( (searchString.search(charCode) != -1 ) && (mode == 'allow') ) || ( (searchString.search(charCode) == -1 ) && (mode == 'deny') ) ){ return true; } else { $this.style.backgroundColor = settings.validationErrorColor; setTimeout("document.getElementById('" + id + "').style.backgroundColor = validanguage.forms['"+formName+"'].settings.normalTextboxColor",validanguage.forms[formName].settings.timeDelay); evt.returnValue = false; //IE if(evt.preventDefault) evt.preventDefault(); //Everyone else return false; } }, /** * Validates that a field is less than maxlength characters long * @param {string} text */ validateMaxlength: function( text, max ) { var id = this.id; var maxlength = (validanguage.empty(max)) ? validanguage.el[id].maxlength : max; if(text.length > maxlength) return false; else return true; }, /** * Validates that a field is greater than minlength characters long * @param {string} text */ validateMinlength: function( text, min ) { var id = this.id; var minlength = (validanguage.empty(min)) ? validanguage.el[id].minlength : min; if(text.length < minlength) return false; else return true; }, /** * Validates that a field is numeric * @param {string} text */ validateNumeric: function( text ) { if(! text.match(/^\d+$/) ) return false; else return true; }, /** * Validates the element against a user-defined regex stored in * validanguage.el[id].regex * * @param {string} text * @param {object} optional object containing regex settings */ validateRegex: function( text, obj ) { var id = this.id; var regexObj = (validanguage.empty(obj)) ? validanguage.el[id].regex : obj; if(typeof regexObj.modifiers=='undefined') regexObj.modifiers=''; if(typeof regexObj.errorOnMatch=='undefined') regexObj.errorOnMatch=false; var myreg = (typeof regexObj.expression=='string') ? new RegExp(regexObj.expression) : regexObj.expression; var thisMatch = myreg.exec(text, regexObj.modifiers); if (thisMatch == null) { //no match var returnStatus = (regexObj.errorOnMatch==false||regexObj.errorOnMatch=='false') ? false : true; } else { //match var returnStatus = (regexObj.errorOnMatch==false||regexObj.errorOnMatch=='false') ? true : false; } return returnStatus; }, /*** * Validates whether or not an element has been filled out, * selected or checked. This function is a wrapper which * calls validateRequiredChild() * * @param {string} text */ validateRequired: function( unused ) { var id = this.id; if(typeof validanguage.el[id].requiredAlternatives == 'undefined') { var alternatives = [ id ]; } else { var alternatives = validanguage.resolveArray(validanguage.el[id].requiredAlternatives,'string'); alternatives[alternatives.length] = id; } for( var i=alternatives.length-1; i>-1; i--) { id = alternatives[i]; var elem = document.getElementById(id); var text = elem.value; var notEmpty = validanguage.validateRequiredChild.call(elem, text); if(notEmpty==true) return true; //if this element or one of its alternatives is not empty, it validates } return false; }, /** * This function calls the validateRequired method on the "master/required" * form field when the "alternative" form field is clicked and then calls * the appropriate onerorr/onsuccess function. */ validateRequiredAlternatives: function(e) { var evt = e || window.evt; var $this = evt.currentTarget || evt.srcElement; var id = $this.id; var parentId = validanguage.requiredAlternatives[id].parentId; var onsuccess = validanguage.requiredAlternatives[id].onsuccess; var onerror = validanguage.requiredAlternatives[id].onerror; var parent = document.getElementById(parentId); if (validanguage.validateRequired.call(parent) == true) { successHandlers = validanguage.resolveArray(onsuccess, 'function'); for (var m = successHandlers.length - 1; m > -1; m--) { successHandlers[m].call(parent); } } else { errorHandlers = validanguage.resolveArray(onerror, 'function'); for (var m = errorHandlers.length - 1; m > -1; m--) { errorHandlers[m].call(parent, validanguage.requiredAlternatives[id].errorMsg); } } }, /*** * Child function called by validateRequired to validates whether or not an element has been filled out, * selected or checked. validateRequiredChild is required to add support for the requiredAlternatives * array. * * @param {string} text */ validateRequiredChild: function( text ) { var type = ( typeof this.type != 'undefined' ) ? this.type : null; if( this.nodeName.toLowerCase() == 'textarea' ) type = 'text'; if( this.nodeName.toLowerCase() == 'select' ) type = 'select'; switch( type ) { case 'checkbox': if( this.checked == false ){ return false; } break; case 'radio': var formId = validanguage.formLookup[this.id]; var radios = (typeof formId == 'number') ? document.forms[formId][this.name] : document.getElementById(formId)[this.name]; for( var i=radios.length-1; i>-1;i--) { if (radios[i].checked == true) return true; } return false; break; case 'text': case 'password': case 'file': if(validanguage.empty(text)) { return false; } break; case 'select': if (validanguage.empty(text)) { return false; } settings = validanguage.getFormSettings(this.id); for( var i=settings.emptyOptionElements.length-1; i>-1; i-- ) { //see if they have selected any of the "empty" option elements if( text==settings.emptyOptionElements[i]) return false; } break; } //close switch return true; }, /** * Validates that the entered text is a valid timestamp. The options object supports all the options listed * under the validateDate function as well as the following additional onces
    * @param {String} text to be validated
    * @param {Object} Options object containing any of the following options:
    * timeIsRequired: {Boolean} Is a date which is provided without an accompanying time considered a valid timestamp? * timeIsRequired defaults to false.
    * timeUnits: {String} A string containing a list of all the time units which are allowed to be entered in the timestamp. * These may include any of the following: h for hours, m for minutes, s for seconds, u for microseconds, * and t for timezone. Example: "hms" for hours, minutes and seconds or "hmsut" for all 5 units. * Defaults to "hms"
    * microsecondPrecision {Integer} Indicates the supported number of decimal places for the microseconds. Defaults to 6. * */ validateTimestamp: function( text, options ) { // Set default options options = validanguage.getDateTimeDefaultOptions(options, {dateOrder: 'ymd'} ); // Check the date portion of the timestamp var pos = text.indexOf(' '); var date = ( pos == -1 ) ? text : text.substr(0,pos); if( !validanguage.validateDate(date, options) ) return false; // Check whether they provided a time if( pos != -1 ) { var time = text.substring(++pos); } else { if( !options.timeIsRequired ) return true; if( options.timeIsRequired ) return false; } // Build the regex to validate the time var regex = '^\\d{1,2}:\\d{1,2}'; if( options.timeUnits.indexOf('s')!=-1 ) regex += '(:\\d{1,2}'; if( options.timeUnits.indexOf('u')!=-1 ) regex += '(\\.\\d{1,'+options.microsecondPrecision+'})?'; if( options.timeUnits.indexOf('s')!=-1 ) regex += ')?'; if( options.timeUnits.indexOf('t')!=-1 ) { regex += '( ?[\\+|\\-]{1,1}(\\d|0\\d|10|11|12|13)(\\:(00|30))?)?'; } regex += '$'; //Run the regex var reg = new RegExp( regex ); var thisMatch = reg.exec(time); if (thisMatch == null) return false; //Finally we need to make sure that the hours, minutes and seconds which were entered are valid var timeparts = time.split(':'); if( timeparts[0] > 23) return false; if( timeparts[1] > 59) return false; if( timeparts.length > 2){ var seconds = timeparts[2].substr(0,2); if(seconds > 59) return false; } return true; }, /** * Validates a URL * * @param {string} text */ validateURL: function( text ) { if(! text.match(/^((([hH][tT][tT][pP][sS]?|[fF][tT][pP])\:\/\/)?([\w\.\-]+(\:[\w\.\&%\$\-]+)*@)?((([^\s\(\)\<\>\\\"\.\[\]\,@;:]+)(\.[^\s\(\)\<\>\\\"\.\[\]\,@;:]+)*(\.[a-zA-Z]{2,4}))|((([01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}([01]?\d{1,2}|2[0-4]\d|25[0-5])))(\b\:(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)\b)?((\/[^\/][\w\.\,\?\'\\\/\+&%\$#\=~_\-@]*)*[^\.\,\?\"\'\(\)\[\]!;<>{}\s\x7F-\xFF])?)$/) ) return false; else return true; }, /** * DEPRECATED - Please use validateDate() instead as validateUSDate * will be removed in the future. validateDate() defaults to MM/DD/YYYY * when called without any additional arguments. * @param {string} text */ validateUSDate: function( text ) { return validanguage.validateDate(text); }, /** * Validates that a US Phone number is entered * * @param {string} text */ validateUSPhoneNumber: function( text ) { if(! text.match(/^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$/) ) return false; else return true; }, /** * Validates that a US Social Security Number is entered * * @param {string} text */ validateUSSSN: function( text ) { if(! text.match(/^\d{3}( |-|.){0,1}\d{2}( |-|.){0,1}\d{4}$/) ) return false; else return true; }, /** * Validates that a US zip code was entered * * @param {string} text */ validateUSZipCode: function( text ) { if(! text.match(/^\d{5}( |-|.){0,1}(\d{4})?$/) ) return false; else return true; }, /** * This is a wrapper for all the validation event handlers assigned to both * form field element and to forms themselves. This function also calls any * transformations before running the validations. * * We use a wrapper for a number of reasons, including exiting validation * early as soon as a single validation function fails. * * @param {Event Object} e * @param {String} Custom Event * ' */ validationWrapper: function(e, customEvent) { if( customEvent=='typing') { var $this = document.getElementById(e); var type = 'typing'; var id = e; } else if (customEvent=='validateForm') { var $this = e; var form = validanguage.whichFormAmI($this); var type = 'submit'; } else { var evt = e || window.evt; var $this = evt.currentTarget || evt.srcElement; var type = evt.type; if (type == 'submit') { var form = validanguage.whichFormAmI($this); } else { var id = $this.id; } if( customEvent=='typingTimeout') { if( validanguage.typingDelay[id] ) window.clearTimeout(validanguage.typingDelay[id]); eval("validanguage.typingDelay[id] = window.setTimeout(\"validanguage.validationWrapper('"+id+"', 'typing')\", validanguage.settings.typingDelay );"); return true; } } // Bail out early if the parent form is marked as disabled if( typeof validanguage.el[form] != 'undefined' && typeof validanguage.el[form].disabled != 'undefined' && validanguage.el[form].disabled == true ) return true; var validations = (type=='submit') ? validanguage.forms[form].validations : validanguage.el[id].handlers[type]; var i=validations.length; var failedValidations = {}; //key value pair of id => validation if(type=='submit') { outerLoop: for(var j=0; j-1; m--) { if( typeof failedValidations[id] != 'undefined' ) continue innerLoop; //this field already flunked var result = funcs[m].call($this, $this.value); if( result == false ) { failedValidations[id] = validation; //defer onerror handlers till later failedValidations[id].field = validanguage.el[id].field; if(! validanguage.forms[form].settings.validateAllFieldsOnsubmit) break outerLoop; } else { //run the onsuccess handlers var onsuccess = validanguage.getElSetting('onsuccess',id,validation); successHandlers = validanguage.resolveArray(onsuccess, 'function'); for (var n=successHandlers.length-1; n>-1; n--) { successHandlers[n].call($this); } } } } //close outerLoop if( validanguage.empty(failedValidations) ) { var submitStatus = true; } else { //call all appropriate onerror handlers for (var o in failedValidations) { if( typeof failedValidations[o] == 'function' ) continue; var id = o; $this = document.getElementById(o); validation = failedValidations[o]; var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation); var errorMsg = validanguage.getElSetting('errorMsg',id,validation); var onerror = validanguage.getElSetting('onerror',id,validation); errorHandlers = validanguage.resolveArray(onerror,'function'); for (var m=errorHandlers.length-1; m>-1; m--) { errorHandlers[m].call($this, errorMsg); } var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation); if( focusOnerror==true ) $this.focus(); var showAlert = validanguage.getElSetting('showAlert',id,validation); if( showAlert ) validanguage.safeAlert(errorMsg); } var submitStatus = false; } //Call onsubmit transformations var transformation = ( typeof validanguage.el[form] != 'undefined' ) ? validanguage.el[form].transformations : []; for (var n=transformation.length-1; n>-1; n--) { transformations = validanguage.resolveArray(transformation[n].name,'function'); for (var o=transformations.length-1; o>-1; o--) { var returnStatus = transformations[o].call(document.getElementById(form), submitStatus, failedValidations); if(typeof returnStatus == 'boolean') submitStatus = returnStatus; } } if( customEvent=='validateForm' ) { return { result: submitStatus, failedValidations: failedValidations }; } return submitStatus; } else { //Skip disabled fields if( (typeof validanguage.el[id].disabled=='boolean' && validanguage.el[id].disabled==true) || (typeof $this.disabled!='undefined' && $this.disabled==true) ) { return; } var transformations = validanguage.el[id].transformations; var p = transformations.length; for( var q=0; q-1; m--) { transformation[m].call($this); } } if( typeof validanguage.el[id].failed=='boolean' && validanguage.el[id].failed==true ) { //Allow a user to manually flunk a field result = false; } else { //see if the field is valid var validationCounter; outerLoop: for(var j=0; j-1; m--) { var result = funcs[m].call($this, $this.value); if( result == false ) break outerLoop; } } if(validationCounter == undefined) return true; //exit early if all validations have been removed } //handle the result if( result == true ) { var onsuccess = validanguage.getElSetting('onsuccess',id,validation); successHandlers = validanguage.resolveArray(onsuccess,'function'); for (var m=successHandlers.length-1; m>-1; m--) { successHandlers[m].call($this); } return true; } else { var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation); var errorMsg = validanguage.getElSetting('errorMsg',id,validation); var onerror = validanguage.getElSetting('onerror',id,validation); errorHandlers = validanguage.resolveArray(onerror,'function'); for (var m=errorHandlers.length-1; m>-1; m--) { errorHandlers[m].call($this, errorMsg); } var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation); if( focusOnerror==true ) $this.focus(); var showAlert = validanguage.getElSetting('showAlert',id,validation); if( showAlert ) validanguage.safeAlert(errorMsg); return false; } } }, /** * This function returns the ID of a form, if it exists. Otherwise, it * returns the form's index position in the document.forms array. * By using this function, we need not require all forms to have * distinct IDs. * * @param {DOM Node} Form Node * @return {String} Form ID or Index position */ whichFormAmI: function ( obj ) { if (typeof obj.id != 'undefined' && (!validanguage.empty(obj.id)) ) return obj.id; var forms = document.forms; for (var i=0, j=forms.length; i