/* Smart Form Validation 1.6 (John_Smart@SmartBCi.com) http://SmartBCi.com Includes publicly avaialble code from Jake Howlett (jake@codestore.org), http://codestore.net Cyanide_7 (leo7278@hotmail.com), http://www7.ewebcity.com/cyanide7 You may use and distribute provided that the above lines are not removed. Version History 1.0 Written/Assembled by John Smart on 9/26/2001 1.1 Created clearValidationMsgs, updated some comments, fixed for dates (date min/max still broken). 5/29/2002 1.2 Added email validation 1.3 Added support for textareas 7/18/2002 1.4 Added integer validation 7/25/2002 1.41 Fixed min/max for text length msg, fixed date min/max validations 7/29/2002 1.42 Fixed regressed bug where registerValidationElement wasn't setting .valiRequired properly 8/8/2002 1.43 Corrected "field must be between 5 and 5 characters long" to "field must be 5 characters long." 1.5 Added min/max for Checkbox. 9/3/2002 1.6 Fixed bug that didn't allow dash-delimited dates. Code now changes valid date values to a slash-delimited format. (e.g. converts "12-4-02" to "12/4/2002") 12/4/2002 1.7 La till personnummer hanteringen //Per 1.8 Lade till hantering för telefonnummer, mobiltelefon och postnummer //Johan 1.9 Lade till funktionen unregisterValidationElement(o) som tar bort registrerade kontroller. Körs ex. från onSubmit före validering //Johan 2.0 Lade till en kontroll för dialoglistor i Notes. Funkar dock inte om man väljer ifrån en vy. //Johan 2004-10-05 2.01 Översätter min/max kontrollen till svenska /Erik 2005-11-25 2.2 Lade till hantering för alfanumeriska tecken samt bättre felmeddelanden för personnummer som redan fanns i subformuläret subJavascriptValidering som används för validering i notes. //Johan 2006-05-10 Three steps necessary to use this script's validation: Step 1: Change the form's onSubmit code to "return standardValidation()" Step 2: For all single-value keyword fields on the form, make sure the first option is a dummy value like "" or "(Select One)" Step 3: In the form's JS Header, create a JavaScript function named registerValidationElements() that calls this subform's registerValidationElement function for each object to be validated. (Your registerValidationElements function will be called only the first time that standardValidation() is called.) The registerValidationElement function uses up to six arguments: o Object field to be validated. e.g. document.forms[0].ZipCode sName String title to use when prompting any invalid values to the user. e.g. "Zip Code", or "Name" bRequired boolean boolean value indicating whether or not this field is required. e.g. true, or false vMax Number If sType is "number" or "date", minimum value allowed. If sType is "text" then maximum length allowed. This value is ignored for radio butons and checkboxes. e.g. 10 or "10/5/2090" or a date value vMin Number If sType is "number" or "date", minimum value allowed. If sType is "text" then minimum length allowed. This value is ignored for radio butons and checkboxes. e.g. 5 or "5/10/1990" or a date value sType String This argument allows you to override the default type of the object. If null or not supplied, the type will be o.type. This is great for most fields, but in the case of an INPUT field, o.type returns "text". In some cases, the o in question holds date or numerical values. You can augment the validation behavior by using "number", "date", "percent", "integter", or "email" for this argument. Note: We've seen issues with fields that have been solved by explicitly passing the type in this argument. If you're validating checkbox or radio objects, you shouldn't-but-might have to include "checkbox" or "radio" here. e.g. "number", "date", "percent", "integer", "email", "checkbox", "radio", "textarea", "select-one", "select-multiple" So an example registerValidationElements would be: function registerValidationElements() { var F = document.forms[0]; registerValidationElement(F.LastName, "Last Name", true) // input text that can't be empty registerValidationElement(F.Age, "Age", false, 150, 1, "integer") // non-required input that must be a valid integer between 1 and 150 inclusive registerValidationElement(F.State, "State", true) // keyword field of States that must have a value registerValidationElement(F.Interests, "Interests", true) // checkboxes where at least one must be checked registerValidaitonElement(F.Phone1, "Home Phone", false, null, 10) // input field that, if filled, must have at least ten characters registerValidationElement(F.Discount, "% Discount", false, 100, 0, "percent") // non-required percent value between 0 and 100 inclusive. registerValidationElement(F.BirthDate, "Birth Date", false, Now, "1/1/1900", "date") // optional date between 1900 and today registerValidationElement(F.Email, "Email Address", true, null, null, "email") //required email address field. registerValidationElement(F.Personnummer, "Personnummer", true, null, null, "personnummer") //required personnummer field. } and as long as you call standardValidation() in the onSubmit, you're done! Note: This code also works within Notes clients. ADDITIONAL JAVASCRIPT FUNCTIONS WITHIN THIS SUBFORM: *********************************************************************** validateElement is the central code in this JS library. It returns true if a field is valid, otherwise it adds the field name to the global validation message strings and returns false. *********************************************************************** valiDate returns true if a date is valid *********************************************************************** valiEmail returns true if a email is valid *********************************************************************** validationMsg takes global string variables of invalid field names and concatenates them into a single string to alert to the user. (These global string variables are cleared by clearValidationMsgs and appended to by validateElement) *********************************************************************** clearValidationMsgs clears the global string variables that are used in validationMsg (above). *********************************************************************** setValidationMsgs first clears (using clearValidationMsgs) then populates (using validateElement) the global string variables of invalid fields that are used in validationMsg *********************************************************************** standardValidation calls setValidationMsgs and, if there was a validation issue, calls alert(validationMsg()) If you want something more customized for your form, then instead of calling this function in the onSubmit, write your own function. *********************************************************************** isNumOutOfRange returns true if the first argument is not between the second and third *********************************************************************** inputToNumber returns the numeric value of argumentobject.value, or 0 if not a valid number. *********************************************************************** For a currency field, put this.value = formatCurrency(inputToNumber(this)) in the onBlur to automatically format the fields to currency. This function isn't called by any other functions in this script library. *********************************************************************** For a percentage field, put this.value = formatPercent(inputToNumber(this)) in the onBlur to automatically format the fields to a percentage. This is also done when validating fields registered with an sType of "percent". ***********************************************************************/ var gsValiPrefix = "\n - "; // constant var gsValiEmpty; // list of field names that are required but empty, each prefixed by gsValiPrefix var gsValiNaN; // list of numeric field names that don't have valid numbers. var gsValiNotDate; var gsValiOther; // list of field names and why they are invalid var gaoValiElements = new Array(); // array of elements that need to be validated. var aoInvalidObjName = new Array(); function isNumOutOfRange(num, min, max) { if (min != null && num < min) return -1; if (max != null && num > max) return 1; return 0; } function inputToNumber(o) { // given an input field, returns the numeric value, or zero if isNaN. var r = /[^\-0-9.]/g; var n = o.value.replace(r, ""); return (isNaN(n) ? 0 : Number(n)); } function formatPercent(n) { return (n < 1 ? (n * 100) : n ) + "%"; } function formatCurrency(n) { /* Author: Cyanide_7 (leo7278@hotmail.com), http://www7.ewebcity.com/cyanide7 Found on http://javascript.internet.com/forms/currency-format.html */ n = n.toString().replace(/\$|\,/g,''); if (isNaN(n)) n = "0"; sign = (n == (n = Math.abs(n))); n = Math.floor(n*100+0.50000000001); cents = n%100; n = Math.floor(n/100).toString(); if (cents<10) cents = "0" + cents; for (var i = 0; i < Math.floor((n.length-(1+i))/3); i++) n = n.substring(0, n.length - (4 * i + 3)) + ',' + n.substring(n.length - (4 * i + 3)); return (((sign)?'':'-') + '$' + n + '.' + cents); } function dateComponents(dateStr, format) { var results = new Array(); /* Added functionality for if dateStr is actually a date, not a string */ if (dateStr.getDate) { // This is a date instead of a string results[0] = dateStr.getDate(); results[1] = dateStr.getMonth() + 1; results[2] = dateStr.getFullYear(); return results; } /* The following splits a date into day, month and year components Used with persmission from Jake Holwett, http://www.codestore.org */ // var datePat = /^(\d{1,2})([\-\/\.])(\d{1,2})\2(\d{2}|\d{4})$/; var datePat = /^(\d{4})-(\d{2})-(\d{2})$/; var matchArray = dateStr.match(datePat); if (matchArray == null) { return null; } //check for two digit years and prepend 20. matchArray[1] = (matchArray[1].length == 2) ? '20' + matchArray[1] : matchArray[1]; // parse date into variables if (format.charAt(0)=="d"){ //what format does the server use for dates? results[0] = Number(matchArray[1]); results[1] = Number(matchArray[3]); } else { results[1] = Number(matchArray[2]); results[0] = Number(matchArray[3]); } results[2] = Number(matchArray[1]); return results; } function valiDate(dateBits) { // example usage: if (valiDate(dateComponents(obj.value, "mm/dd/yyyy"))) alert("Felaktigt datum!") if (dateBits == null) return false; day = dateBits[0]; month = dateBits[1]; year = dateBits[2]; if ((month < 1 || month > 12) || (day < 1 || day > 31)) { // check month range return false; } if ((month==4 || month==6 || month==9 || month==11) && day==31) { return false; } if (month == 2) { // check for february 29th var isleap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); if (day>29 || (day==29 && !isleap)) { return false; } } return true; } function valiEmail(e) { // Email validation from http://www.perlscriptsjavascripts.com/js/check_email.html ok = "1234567890qwertyuiop[]asdfghjklzxcvbnm.@-_QWERTYUIOPASDFGHJKLZXCVBNM"; for(i=0; i < e.length ;i++) if(ok.indexOf(e.charAt(i))<0)return (false) re = /(@.*@)|(\.\.)|(^\.)|(^@)|(@$)|(\.$)|(@\.)/; re_two = /^.+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/; if (!e.match(re) && e.match(re_two)) return (true); return (false) } function personnummer(nr){ /* this.valid=false; if(!nr.match(/^(\d{2})(\d{2})(\d{2})\-(\d{4})$/)){ return false; } this.now=new Date(); this.nowFullYear=this.now.getFullYear()+""; this.nowCentury=this.nowFullYear.substring(0,2); this.nowShortYear=this.nowFullYear.substring(2,4); this.year=RegExp.$1; this.month=RegExp.$2; this.day=RegExp.$3; this.controldigits=RegExp.$4; this.fullYear=(this.year*1<=this.nowShortYear*1)?(this.nowCentury+this.year)*1:((this.nowCentury*1-1)+this.year)*1; var months = new Array(31,28,31,30,31,30,31,31,30,31,30,31); if(this.fullYear%400==0||this.fullYear%4==0&&this.fullYear%100!=0){ months[1]=29; } if(this.month*1>12||this.day*1>months[this.month*1-1]){ return false; } this.alldigits=this.year+this.month+this.day+this.controldigits; var nn=""; for(var n=0;n12||this.day*1>months[this.month*1-1]){ this.error='date'; return false; } this.alldigits=this.year+this.month+this.day+this.controldigits; var nn=""; for(var n=0;n 9) ? iM : "0" + iM; var sD = (iD > 9) ? iD : "0" + iD; var sY = d.getFullYear().toString(); return sM + "/" + sD + "/" + sY; } function registerValidationElement(o, sName, bRequired, nMax, nMin, sType) { o.valiName = (sName ? sName : o.name); o.valiType = (sType ? sType : o.type); o.valiMin = nMin; o.valiMax = nMax; if (bRequired) o.valiRequired = true; gaoValiElements[gaoValiElements.length] = o; } //Tar bort registerade kontroller function unregisterValidationElement(o) { for (var i=0; i < gaoValiElements.length; i++) { if(gaoValiElements[i] == o) { gaoValiElements.splice(i, 1) } } } function standardValidation() { clearValidationMsgs(); if (setValidationMsgs()) return true else { alert(validationMsg()) return false } } //Validering som även färgkodar en divtag runt fältet function standardValidationColorMark() { clearValidationMsgs(); clearInvalidObjects(); if (setValidationMsgs()) return true else { var sId='fraga'; var elem; var firstElem; //Loopar igenom alla icka valida id:n for(var i=0; i0) { sId=o[0].id; for (i = 0; i < o.length; i++) { if (o[i].checked) { val = i; bEmpty = false; break; } } } break; default : alert("Error: validateElement doesn't know how to deal with valiType = " + sType + "\n(" + sName + ")"); return false; } // if bEmpty is true, immediately return false or true, depending on if it's required or not. if (bEmpty) { if (bRequired) { gsValiEmpty += gsValiPrefix + sName; aoInvalidObjName.push(sId); return false; } else return true } // now for the real validation switch (sType) { case "text" : case "textarea" : iLen = val.length; if (isNumOutOfRange(iLen, vMin, vMax)) { if (vMin == null) gsValiOther += gsValiPrefix + sName + " får inte vara mer än " + vMax + " tecken."; else if (vMax == null) gsValiOther += gsValiPrefix + sName + " måste vara mer än " + vMin + " tecken."; else if (vMax == vMin) gsValiOther += gsValiPrefix + sName + " måste vara " + vMin + " tecken."; else gsValiOther += gsValiPrefix + sName + " måste vara mellan " + vMin + " och " + vMax + " tecken."; aoInvalidObjName.push(o.id); return false; } return true; case "email" : if (valiEmail(o.value)) return true else { gsValiOther += gsValiPrefix + sName + " kräver en valid e-post adress."; aoInvalidObjName.push(o.id); return false } case "multiEmail" : var asMail=o.value.split('\n'); var bOk=true; for(i=0; i < asMail.length;i++) { asMail[i]=fTrim(asMail[i]); if (!valiEmail(asMail[i])) { bOk=false; } } if(bOk) { return true; } else { gsValiOther += gsValiPrefix + sName + " kräver valida e-post adresser."; aoInvalidObjName.push(o.id); return false; } case "personnummer" : var pn=new personnummer(o.value); if(pn.valid) { return true; } else if(!pn.valid && pn.error=='format') { gsValiOther += gsValiPrefix + sName + " kräver ett personnummer enligt ex. 111111-2222."; aoInvalidObjName.push(o.id); return false; } else if (!pn.valid && pn.error=='date') { gsValiOther += gsValiPrefix + sName + " innehåller inte ett korrekt datum för personnummret."; aoInvalidObjName.push(o.id); return false; } else if (!pn.valid && pn.error=='checksum') { gsValiOther += gsValiPrefix + sName + " innehåller inte ett giltligt personnummer."; aoInvalidObjName.push(o.id); return false; /* var pn=new personnummer(o.value); if(pn.valid) { return true; } else { gsValiOther += gsValiPrefix + sName; aoInvalidObjName.push(o.id); return false; */ } case "telefonnummer" : if(fValidTelefonnumber(o.value)) { return true; } else { gsValiOther += gsValiPrefix + sName + " kräver ett telefonnummer enligt ex. 031-111111"; aoInvalidObjName.push(o.id); return false; } case "mobilnummer" : if(fValidMobilnumber(o.value)) { return true; } else { gsValiOther += gsValiPrefix + sName + " kräver ett mobilnummer enligt ex. 0711-111111"; aoInvalidObjName.push(o.id); return false; } case "postnummer" : if(fValidPostnumber(o.value)) { return true; } else { gsValiOther += gsValiPrefix + sName; aoInvalidObjName.push(o.id); return false; } case "alfanumeriskt" : if(fValidLetterOrNumber(o.value)) { return true; } else { gsValiOther += gsValiPrefix + sName + " tillåter enbart alfanumeriska tecken dvs. A-Z, a-z, 0-9"; aoInvalidObjName.push(o.id); return false; } case "percent" : case "number" : case "integer" : if (sType == "integer") r = /[, ]/g; // regexp set to look for all commas and spaces. else r = /[$,% ]/g; // regexp set to look for all dollar signs, commas, percent signs, and spaces. val = val.replace(r,"") // strip them out if (isNaN( val )) { gsValiNaN += "\n - " + sName; aoInvalidObjName.push(o.id); return false } else if (isNumOutOfRange(val, vMin, vMax)) { if (vMin == null) gsValiOther += gsValiPrefix + sName + " måste vara mindre än eller lika med " + vMax else if (vMax == null) gsValiOther += gsValiPrefix + sName + " måste vara minst " + vMin else gsValiOther += gsValiPrefix + sName + " måste vara mellan " + vMin + " och " + vMax; aoInvalidObjName.push(o.id); return false } else if (sType == "integer" && val != parseInt(val)) { gsValiOther += gsValiPrefix + sName + " must be an integer."; aoInvalidObjName.push(o.id); return false; } if (sType == "percent") o.value = formatPercent(val); // make sure a % is within the field. Domino will treat '5' as 500% return true; case "date" : var aiDateBits = dateComponents(val, sDateFormat); if (aiDateBits == null) { gsValiNotDate += gsValiPrefix + sName; aoInvalidObjName.push(o.id); return false; } //Check it is a valid date first if (valiDate(aiDateBits) == false) { gsValiNotDate += gsValiPrefix + sName; aoInvalidObjName.push(o.id); return false; } //Now check whether a range is specified and if in bounds var theDate = new Date(aiDateBits[2], parseInt(aiDateBits[1]) - 1, aiDateBits[0]); if ( vMin ) { minBits = dateComponents (vMin, sDateFormat); for (i = 2; i > -1; i--) { if (minBits[i] < aiDateBits[i]) break // if year is before, stop. Don't compare month numbers. else if (minBits[i] > aiDateBits[i]) { if ( vMax ) gsValiOther += gsValiPrefix + sName + " must be between " + formatMMDDYYYY(vMin) + " and " + formatMMDDYYYY(vMax) else gsValiOther += gsValiPrefix + sName + " must be on or after " + formatMMDDYYYY(vMin); aoInvalidObjName.push(o.id); return false; } } } if ( vMax) { maxBits = dateComponents (vMax, sDateFormat); for (i = 2; i > -1; i--) { if (maxBits[i] > aiDateBits[i]) break else if (maxBits[i] < aiDateBits[i]) { if ( vMin ) gsValiOther += gsValiPrefix + sName + " must be between " + formatMMDDYYYY(vMin) + " and " + formatMMDDYYYY(vMax) else gsValiOther += gsValiPrefix + sName + " must be on or before " + formatMMDDYYYY(vMax); aoInvalidObjName.push(o.id); return false; } } } // Detta ändrar formtatet på datumet trots att det är godkänt. // if (sDateFormat.charAt(0)=="d") //what format does the server use for dates? // o.value = aiDateBits[0] + "-" + aiDateBits[1] + "-" + aiDateBits[2] // else // o.value = aiDateBits[2] + "-" + aiDateBits[1] + "-" + aiDateBits[2]; return true; case "checkbox" : if (vMin || vMax) { // don't bother checking if no need. var iChecked = 1; for (i = val + 1; i < o.length; i++) if (o[i].checked) iChecked++ if (isNumOutOfRange(iChecked, vMin, vMax)) { if (vMin == null) gsValiOther += gsValiPrefix + sName + " must have less than " + (vMax + 1) + " choices selected." else if (vMax == null) gsValiOther += gsValiPrefix + sName + " must have at least " + vMin + " choices selected." else if (vMax == vMin) gsValiOther += gsValiPrefix + sName + " must have " + vMin + " choices selected." else gsValiOther += gsValiPrefix + sName + " must have between " + vMin + " and " + vMax + " choices selected."; aoInvalidObjName.push(o.id); return false; } } return true; default : // select, radio, etc return true } } function fValidPostnumber(sPostnr) { var objRegExp = /(^\d{5}$)|(^\d{3}\s\d{2}$)/; return objRegExp.test(sPostnr); } function fValidTelefonnumber(sTfnnr) { var objRegExp = /^0\d{1,3}-\d{5,8}$/; return objRegExp.test(sTfnnr); } function fValidMobilnumber(sMobnr) { var objRegExp = /^0\d{3}-\d{6}$/; return objRegExp.test(sMobnr); } function fValidLetterOrNumber(sString) { var objRegExp = /^[A-Za-z0-9]+$/; return objRegExp.test(sString); } //Trim funktion function fTrim(sStringToTrim) { return sStringToTrim.replace(/^\s{1,}/, "").replace(/\s{1,}$/, ""); }