
/* formfunctions.js
 * 2000-2002 kenneth dombrowski
 * kenneth@ylayali.net
 * feel free to use for any purpose whatever, but please email improvements to me.
 * 
 *    
 *    --------------------- TO USE ---------------------
 *	1. include this file into your html page, ie:
 *		<script language=javascript src="formfunctions.js"></script>
 *
 *	2. include hidden fields for each test you would like run on the form data
 *		<INPUT TYPE=HIDDEN NAME=valemail VALUE=email>  
 *		comma-delimit multiple fields:
 *		<INPUT TYPE=HIDDEN NAME=required VALUE='first,last,street,city'>
 *    
 *		working tests = required, valemail, valusphone, valuszip, valnumeric, valradiocheckbox, valpassword
 *    
 *		NOTE:
 *      the 'required' field is for elements for which this is the ONLY check..
 *		  other checks call required for you
 * 		('required' WILL work with SELECT elements.. but it seems like you must explicitly give the 
 *		default option a value of "" AND include some kind of text for that option; ie "Select State")
 * 
 *	3. call validate() from yr form on submit event:
 *		 <FORM onsubmit="return validate(this);">
 * 
 *	4. OR, instead of 2 & 3 above, to simply check EVERY ELEMENT is not null:
 *		 <FORM onsubmit="return reqallform(this);"> 
 * 	    !! this function isn't set up to work with radio/checkbox elements..
 *	
 * maxtxalength(formfieldobject, maxlength)
 * ===================================================================================
 *	   will work for any form element i suppose, but only really useful for textareas,
 *	   as the TEXTAREA tag doesn't support the INPUT tag's MAXLENGTH attribute.
 *	   may be called directly from element with onblur="maxtxalength(this,255);"
 *	   ! browser focus gets caught between 2 adjacent fields returning false on onblurs !
 *    
 * formatcurrency(formfieldobject)
 * ===================================================================================
 *    onblur="formatcurrency(this);"
 *    strips nonnumeric 
 *    adds "," every 3 digits from right
 *    prepends "$"
 *    -- no support for coin values @ this time 
 *    
 * select50states(bUseAbbvAsValue,bUseAbbvAsOption,selectedoption)
 * ===================================================================================
 * 	 writes out select box (named 'state') of 50 states/abbvs (+ DC) for you:
 *		 select50states(true,true);		//<OPTION value="AL">AL
 *		 select50states(true,false);		//<OPTION value="AL">Alabama
 *		 select50states(false,false);		//<OPTION>Alabama
 *		 select50states(false,true);		//<OPTION>AL
 *		* if selectedoption is passed it must be passed in the abbreviated format for that state
 * 	
 * valpassword(passfield1,passfield2)
 * ===================================================================================
 * 		does an equality check for confirming password was typed correctly.
 * 		args are your password field & password validation field's NAME attributes
 *    
 *    
 *  _____________________________ TROUBLESHOOTING _____________________________________   
 *  if it's not working, be sure:
 *    1. if your form is not the only one on the page, that it has a NAME attribute
 *    2. that the HIDDEN field NAMEs are valid names of working tests: required, 
 *       valemail, valusphone, valuszip, valnumeric, etc...
 *    2. that the HIDDEN field VALUEs are comma-delimited with no spaces
 *    3. that no elements on your form have a javascript reserved word set for
 *       the NAME attr.; ie. NAME='name' will break validate()
 *    4. for debugging you can call onSubmit="alertAllForm(this)"
 *       -- or uncomment the line which calls alertAllForm from validate() -- 
 *       alertAllForm presents you with name=value alert boxes for each element + formreference
 *    
 *  __________________________OUTSTANDING ISSUES/THINGS TODO___________________________
 *  2000/02/24 
 *    PC Netscape4.5 & IE5 -- element.focus() gets caught between
 * 		 2 adjacent fields returning false on blur event
 *    no noregexp checks not really tested (js1.1 & earlier)
 *    isnumeric(teststr) needs noregexp code
 *    Netscape layerrefs not accounted for. if you're using DIVs or LAYERS nn4 will not work.
 *    
 *  2000/02/25  
 *    reqallform radio/checkboxes not tested..
 *    
 * 	2000/11/30
 *    alertAllForm, reqAllForm & isnotnull checks need code for select-multiple
 *    ?? selectedIndex returns array ??
 *    
 *    
 *  ___TESTING HISTORY___
 *   2000/02/24 kld Netscape4.5\PC IE5\PC
 *   2002/03/21 kld IE6\PC
 *    
 *  ___REVISION HISTORY___
 *   2000/02/28 kld added select50states(bUseAbbvAsValue,bUseAbbvAsOption)
 * 	 2000/11/30 kld finally added valradiocheckbox()
 *   2001/11/01 kld added formatcurrency()
 *   2001/11/27 kld added checklegaldate()
 *   2002/03/21 kld added "alias obscure form names here" as a hack. it should be a separate hidden form field.
 *   2002/03/21 kld added valpassword for retype test
 */ 
 
 	 
var formreference = "0";
// if FORM has no NAME attribute assumes only (or 1st) form on page.  global

function validate(formobject) {
	if (formobject.name) formreference = formobject.name;

	// debug..
	// alertAllForm(formreference);
	// & optionally.. 
	// return false;
	
	if (document.forms[formreference].elements["required"]) {
		afields = document.forms[formreference].elements["required"].value.split(",");
		if (afields.length > 0) {
			for (i = 0; i < afields.length; i++) {
				if(!isnotnull(afields[i])) return false;
			}
		}
	}

	if (document.forms[formreference].elements["valemail"]) {
		afields = document.forms[formreference].elements["valemail"].value.split(",");
		if (afields.length > 0) {
			for (i = 0; i < afields.length; i++) {
				if(!isemail(afields[i])) return false;
			}
		}			
	}

	if (document.forms[formreference].elements["valusphone"]) {
		afields = document.forms[formreference].elements["valusphone"].value.split(",");
		if (afields.length > 0) {
			for (i = 0; i < afields.length; i++) {
				if(!isusphone(afields[i])) return false;
			}
		}			
	}

	if (document.forms[formreference].elements["valuszip"]) {
		afields = document.forms[formreference].elements["valuszip"].value.split(",");
		if (afields.length > 0) {
			for (i = 0; i < afields.length; i++) {
				if(!isuszip(afields[i])) return false;
			}
		}			
	}

	if (document.forms[formreference].elements["valnumeric"]) {
		afields = document.forms[formreference].elements["valnumeric"].value.split(",");
		if (afields.length > 0) {
			for (i = 0; i < afields.length; i++) {
				if(!isnumeric(afields[i])) return false;
			}
		}			
	}

	if (document.forms[formreference].elements["valradiocheckbox"]) {
		afields = document.forms[formreference].elements["valradiocheckbox"].value.split(",");
		if (afields.length > 0) {		
			for (i = 0; i < afields.length; i++) {
				if(!reqradiocheckbox(afields[i])) return false;
			}
		}		
	}	
	
	if (document.forms[formreference].elements["valpassword"]) {
		afields = document.forms[formreference].elements["valpassword"].value.split(",");
		if (afields.length > 0) {
			var passfield1 = document.forms[formreference].elements[afields[0]];
			var passfield2 = document.forms[formreference].elements[afields[1]];
			if(passfield1.value != passfield2.value) {
				negativexit(passfield1,"Password fields do not match");
				return false;
			}
		}		
	}	
	
	//future; valalphabetic
	//future; valalphanumeric
	//future; valdate
	//future; valmath	// (or money) isnumeric & allows < ( [ { / + - . $ % # & * : = ^ | \ } ] ) >  
	
	return true;
}


function reqallform(formobject) {
	// only checks that fields are not null. requires value for every element/element group
	// <FORM onsubmit="return reqallform(this);"> 
	var elemct = document.forms[formreference].elements.length;
	
	for (i = 0; i < elemct; i++) {
		fieldname = document.forms[formreference].elements[i].name;
		fieldtype = document.forms[formreference].elements[i].type;
		if (fieldtype == "select-one") {
			var selop = document.forms[formreference].elements[fieldname].selectedIndex;
			fieldvalue = document.forms[formreference].elements[fieldname].options[selop].value;
		}
		if (fieldtype == "select-multiple") { 
			// SKIPS select-multiple FIELDS...
		}
		else {
			if (fieldtype == "radio" || fieldtype == "checkbox") fieldvalue = reqradiocheckbox(document.forms[formreference].elements[i].name);
			else fieldvalue = stripwhitespace(document.forms[formreference].elements[fieldname].value);
		}
		if (!fieldvalue) {
			negativexit(document.forms[formreference].elements[i],"The " + fieldname + " field requires a value");
			return false;
		}
	}
	return true;
}

function reqradiocheckbox(groupName) {
	var bGroupValueFlag = false;
	var groupType = document.forms[formreference].elements[afields[i]][0].type;								
	for (j = 0; j < document.forms[formreference].elements[afields[i]].length; j++) {				
		if (document.forms[formreference].elements[afields[i]][j].checked == "1") {
			bGroupValueFlag = true;
			break;
		}
	}				
	if (bGroupValueFlag == false) {
		negativexit(document.forms[formreference].elements[afields[i]][0],groupType + " group " + groupName + " requires a value");
		return false;
	}
	return true;
}	

function isnotnull(elementname) {
	elementtype = document.forms[formreference].elements[elementname].type;
	if (elementtype == "select-one") {
		var selop = document.forms[formreference].elements[elementname].selectedIndex;
		elementvalue = document.forms[formreference].elements[elementname].options[selop].value;
	}
	if (elementtype == "select-multiple") { 
			// SKIPS select-multiple FIELDS...
	}
	else elementvalue = stripwhitespace(document.forms[formreference].elements[elementname].value);
	
	if (!elementvalue) {
	
		// alias obscure form names here...
		var elementstr;
		if (elementname == "fname") elementstr = "first name";
		else if (elementname == "lname") elementstr = "last name";
		else if (elementname == "company_nm") elementstr = "company name";
		else if (elementname == "pass") elementstr = "password";
		else if (elementname == "pass_val") elementstr = "confirm password";
		else if (elementname == "contactname") elementstr = "name";
		else elementstr = elementname;
		
		negativexit(document.forms[formreference].elements[elementname],"The " + elementstr + " field requires a value");
		return false;
	}
	return true;
}

function maxtxalength(formfieldobject, maxlength) {	
	var teststr = formfieldobject.value;
	var fieldname = formfieldobject.name;
	
	// uncomment the next line to require a value
	// if (!isnotnull(fieldname)) return false;
	
	var txalength = teststr.length;
	if (txalength > maxlength) {
		negativexit(formfieldobject,"There is a limit of " + maxlength + " characters in the " + fieldname + " field, you have entered " + txalength);
		return false;
	}	
	return true;
}


function formatcurrency(formfieldobject) {	
	var teststr = formfieldobject.value;
	var fieldname = formfieldobject.name;
	var newstr = "";
	teststr = stripnondigits(teststr);
	if (teststr.length > 3) {
		do {
			newstr = "," + teststr.substr(teststr.length - 3) + newstr;
			teststr = teststr.substring(0,teststr.length - 3);
		}
		while (teststr.length > 3);
		newstr = "$" + teststr + newstr;
	}
	else {
		if (teststr.length > 0) newstr = "$" + teststr;
	}
	document.forms[formreference].elements[fieldname].value = newstr;
}

function formatpct(formfieldobject) {
	var teststr = formfieldobject.value;
	var fieldname = formfieldobject.name;
	var newstr = "";
	teststr = stripnondigitsordecimal(teststr);
	newstr = teststr + "%";
	document.forms[formreference].elements[fieldname].value = newstr;
}

function formaturl(formfieldobject) {
	var teststr = formfieldobject.value;
	var fieldname = formfieldobject.name;
	if (teststr == "") return true;	// allow blank
	if (isurl(fieldname)) {
		var newstr = teststr;
		if (teststr.indexOf("http://") == -1) {
			newstr = "http://" + newstr;
		}

		document.forms[formreference].elements[fieldname].value = newstr;
	}
}

function isurl(fieldname) {
	var teststr = document.forms[formreference].elements[fieldname].value;
		
	var illchars = "*|,;<[{()}]>`\^\'\"\\";
	for (i = 0; i < teststr.length; i++) {
		if (illchars.indexOf(teststr.charAt(i)) != -1) {
			negativexit(document.forms[formreference].elements[fieldname],"Please check the " + fieldname + " field.\n" + document.forms[formreference].elements[fieldname].value + " does not appear to be a valid internet address.");
			return false;
		}
	}	
	if (window.RegExp) {
		// regexp check for javascript versions 1.2+
		var regexpemail = /^\S+\.?\S+\.\S+$/i;
		if (!regexpemail.test(teststr)) {
			negativexit(document.forms[formreference].elements[fieldname],"Please check the " + fieldname + " field.\n" + document.forms[formreference].elements[fieldname].value + " does not appear to be a valid internet address.");
			return false;
		}
	}
	else {
		// lame check for earlier versions.. are there (at least) two dots??
		// (you may only want to check one dot to accept "domain.com" as well as "www.domain.com")
		if ((teststr.indexOf(".") == -1) || (! teststr.lastIndexOf(".") > teststr.indexOf(".") )) {
			negativexit(document.forms[formreference].elements[fieldname],"Please check the " + fieldname + " field.\n" + document.forms[formreference].elements[fieldname].value + " does not appear to be a valid internet address.");
			return false;
		}
	}	
	return true;
}

function isemail(fieldname) {
	// uncomment next line to require email address
	// if (!isnotnull(fieldname)) return false;
	var teststr = document.forms[formreference].elements[fieldname].value;
		
	var illchars = "*|,:;<[{()}]>&$#%`\'\"";
	for (i = 0; i < teststr.length; i++) {
		if (illchars.indexOf(teststr.charAt(i)) != -1) {
			negativexit(document.forms[formreference].elements[fieldname],"The " + fieldname + " field requires a valid e-mail address\nillegal character encountered");
			return false;
		}
	}	
	if (window.RegExp) {
		// regexp check for javascript versions 1.2+
		var regexpemail = /^\S+@\S+\.\S+$/i;
		if (!regexpemail.test(teststr)) {
			negativexit(document.forms[formreference].elements[fieldname],"The " + fieldname + " field requires a valid e-mail address");
			return false;
		}
	}
	else {
		// lame check for earlier versions
		if ((teststr.indexOf("@") == -1) || (teststr.indexOf(".") == -1)) {
			negativexit(document.forms[formreference].elements[fieldname],"The " + fieldname + " field requires a valid e-mail address");
			return false;
		}
	}	
	return true;
}

function isnumeric(fieldname) {
	if (!isnotnull(fieldname)) return false;
	var teststr = document.forms[formreference].elements[fieldname].value;
	
	if (window.RegExp) {
		// regexp check for javascript versions 1.2+
		var regexpdigits = /^\d+$/;
		if (!regexpdigits.test(teststr)) {
			negativexit(document.forms[formreference].elements[fieldname],"The " + fieldname + " field requires a numeric value");
			return false;
		}
	}
	else {
		// lame check for earlier versions
		alert("no regexps");
	}
}

function isusphone(fieldname) {
	if (!isnotnull(fieldname)) return false;
	var teststr = document.forms[formreference].elements[fieldname].value;
	teststr = stripnondigits(teststr);
	if (teststr.length == 10) {
		document.forms[formreference].elements[fieldname].value = reformatusphone(teststr);
		return true;
	} 
	else {
		negativexit(document.forms[formreference].elements[fieldname],"Please include US area code & 7-digit phone number.");
		return false;
	}
}

function isuszip(fieldname) {
	if (!isnotnull(fieldname)) return false;
	var teststr = document.forms[formreference].elements[fieldname].value;
	teststr = stripnondigits(teststr);
	if ((teststr.length == 5) || (teststr.length == 9)) {
		document.forms[formreference].elements[fieldname].value = reformatzipcode(teststr);
		return true;
	} 
	else {
		negativexit(document.forms[formreference].elements[fieldname],"Please include 5-digit or 9-digit US zip code.");
		return false;
	}	
}

function stripnondigits(str) {
	// removes all characters except digits. called by isusphone(), isuszip()
	var returnstr = "";
	var digitstr = "0123456789";
	
    for (i = 0; i < str.length; i++) {
		var c = str.charAt(i);
	    if (digitstr.indexOf(c) != -1) returnstr += c;
    }
    return returnstr;
}

function stripnondigitsordecimal(str) {
	// removes all characters except digits. called by isusphone(), isuszip()
	var returnstr = "";
	var digitstr = "0123456789.";
	
    for (i = 0; i < str.length; i++) {
		var c = str.charAt(i);
	    if (digitstr.indexOf(c) != -1) returnstr += c;
    }
    return returnstr;
}

function negativexit(fieldobj,alertstr) {
	// fieldname is required (note: NOT an Object reference!), alertstr is not
	fieldtype = fieldobj.type;
	fieldname = fieldobj.name;
	if (alertstr) alert(alertstr);
	
	if (	// input element supports select().. is if(field.select()) or if(field.prototype.select) legal??
		(fieldtype == "text") || 
		(fieldtype == "textarea") || 
		(fieldtype == "password") || 
		(fieldtype == "file")
		) document.forms[formreference].elements[fieldname].select();
		
	if (	// if group set focus to 1st element...
		(fieldtype == "radio") || 
		(fieldtype == "checkbox")
		) document.forms[formreference].elements[afields[i]][0].focus();
	else document.forms[formreference].elements[fieldname].focus();
	return false;
}


var defaultstate = "NY";
	 
function select50states(bUseAbbvAsValue,bUseAbbvAsOption,selectedoption) {
	// array values from official USPS abbreviation page:
	// http://www.usps.gov/ncsc/lookups/usps_abbreviations.htm
	aStateNames = new Array('Alabama','Alaska','Arizona','Arkansas','California','Colorado','Connecticut','Delaware','District of Columbia','Florida','Georgia','Hawaii','Idaho','Illinois','Indiana','Iowa','Kansas','Kentucky','Louisiana','Maine','Maryland','Massachusetts','Michigan','Minnesota','Mississippi','Missouri','Montana','Nebraska','Nevada','New Hampshire','New Jersey','New Mexico','New York','North Carolina','North Dakota','Ohio','Oklahoma','Oregon','Pennsylvania','Rhode Island','South Carolina','South Dakota','Tennessee','Texas','Utah','Vermont','Virginia','Washington','West Virginia','Wisconsin','Wyoming');
	aStateAbbvs = new Array('AL','AK','AZ','AR','CA','CO','CT','DE','DC','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY');
	// if (selectedoption == "" || selectedoption == null) selectedoption = defaultstate;
	document.writeln('<SELECT NAME="state"><option value="">Select State');
	for (i = 0; i < aStateNames.length; i++) {
		document.write('<option');
		if(selectedoption == aStateAbbvs[i]) document.write(' SELECTED');
		if(bUseAbbvAsValue) document.write(' value="' + aStateAbbvs[i] + '"');
		if(bUseAbbvAsOption) document.write('>' + aStateAbbvs[i] + '\n');
		else document.write('>' + aStateNames[i] + '\n');
	}
	document.writeln('</SELECT>');
}




// debug function
function alertAllForm(formref) {
	alertstr = "formreference = " + formref + "\n";
	
	for (x = 0; x < document.forms[formref].elements.length; x++) {
		elementtype = document.forms[formref].elements[x].type;
		if (elementtype == "radio" || elementtype == "checkbox") {
			for (y = 0; y < document.forms[formref].elements[x].length; y++) {
				alertstr += document.forms[formref].elements[x][y].name + " = ";
				alertstr += document.forms[formref].elements[x][y].value;
				if (document.forms[formref].elements[x][y].checked) alertstr += " CHECKED\n";
				else alertstr += "\n";
			}
		}
		if (elementtype == "select-one") {
			var selop = document.forms[formref].elements[x].selectedIndex;
			alertstr += document.forms[formref].elements[x].name + " = ";
			alertstr += document.forms[formref].elements[x].options[selop].value;
		}
		if (elementtype == "select-multiple") { 
			// SKIPS select-multiple FIELDS...
		}
		else alertstr += document.forms[formref].elements[x].name + " = " + document.forms[formref].elements[x].value + "\n";
	}
}

/*
 * ###########################################################
 *
 * following from Netscape devedge sample code. slightly modified.
 * http://developer.netscape.com/docs/examples/javascript.html
 *
 * ###########################################################
 */

function stripCharsInRE(str, bag) { return str.replace(bag, ""); }
function stripwhitespace(str) { return stripCharsInBag (str," \t\n\r"); }

function stripCharsInBag (s, bag) {
	var i;
    var returnString = "";
    for (i = 0; i < s.length; i++) {   
        var c = s.charAt(i);
        if (bag.indexOf(c) == -1) returnString += c;
    }
    return returnString;
}

function stripCharsNotInBag (s, bag) {
	var i;
    var returnString = "";
    for (i = 0; i < s.length; i++) {   
        var c = s.charAt(i);
        if (bag.indexOf(c) != -1) returnString += c;
    }
    return returnString;
}

function reformat(s) {
	var arg;
	var sPos = 0;
	var resultString = "";
	for (var i = 1; i < reformat.arguments.length; i++) {
		arg = reformat.arguments[i];
		if (i % 2 == 1) resultString += arg;
		else {
			resultString += s.substring(sPos, sPos + arg);
			sPos += arg;
		}
	}
	return resultString;
}

function reformatusphone(usphone) { return ( reformat(usphone, "(", 3, ") ", 3, "-", 4) ) }
function reformatzipcode(ZIPString) {
	if (ZIPString.length == 5) return ZIPString;
	else return (reformat(ZIPString, "", 5, "-", 4));
}


/*
 *  D A T E   V A L I D A T I O N   F U N C T I O N S 
 *  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
 * 
 */
	
	function checkYear(year) { 
		// returns true if leap year, false otherwise
		// copied from http://javascript.internet.com/calculators/leap-year.html
		return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) ? 1 : 0;
	}

	function checklegaldate() {
		/* Expects three form fields for each prefix argument passed, ie:
		 * calling "checkdates('test')" will assume select boxes "test_month", "test_day" & "test_year" exist.
		 * "test_month" values should be 1 - 12.
		 * "test_year" is used for the leap year check.
		 */
		 
		var lastdays = new Array(0,31,28,31,30,31,30,31,31,30,31,30,31);
		var monthnames = new Array(null,"January","February","March","April","May","June","July","August","September","October","November","December");
		
		for (var i = 0; i < arguments.length; i++) {
			var testmonth = document.forms[formreference].elements[arguments[i] + '_month'].options[document.forms['admin'].elements[arguments[i] + '_month'].selectedIndex].value;
			var testday = document.forms[formreference].elements[arguments[i] + '_day'].options[document.forms['admin'].elements[arguments[i] + '_day'].selectedIndex].value;
			var testyear = document.forms[formreference].elements[arguments[i] + '_year'].options[document.forms['admin'].elements[arguments[i] + '_year'].selectedIndex].value;
			var formdate = new Date(testyear,testmonth,testday);

			if ( checkYear(testyear) ) {
				lastdays[2] = 29;
				// alert(arguments[i] + " date is a leap year");
			}
			
			if (lastdays[testmonth] < testday) {
				var errstr 
				errstr = "Error: " + monthnames[testmonth] + " only has " + lastdays[testmonth] + " days";
				if (testmonth == 2) errstr += " in " + testyear;
				alert(errstr);
				return false;
			}
			
		// from unionleagueclub.org
		//	var now = new Date();
		//	if (arguments[i] == 'expire' && formdate < now) return confirm("Set this item to expire in the past?");
		}
	}


