
// Begin EditMask.js
// Created by Steve Seaquist
//
// Limitations:  onBlur is too constricting to the user with this.focus(), so we use onChange in that case.  
// (onChange allows getting out of the field with bad values after an notify, but at least it's tolerable.) 
// EditMask cannot be used with TYPE=PASSWORD, because the browser never let's us see the field value, 
// for security reasons.  EditMask is pretty pointless with TYPE=CHECKBOX, TYPE=RADIO or TYPE=SELECT, 
// where the values are constrained by the HTML.  But not all HTML constraints are reliable.  For example, 
// a TYPE=TEXT can contain a longer length than its MAXLENGTH if the VALUE clause is stuffed in from an 
// erroneous database value.  By calling EditMask (..., 'X', ..., (max)), you can force the user to correct 
// erroneous database values.  
//
// Currently supported mask characters and their meanings: 
//		mask  mnemonic		  criteria
//		----  --------		  --------
//		#	- number sign	- numeric or decimal point
//		$	- dollars		- numeric, dollar sign, comma or decimal point
//		%	- percentage	- numeric, decimal point or percent sign
//		9	- number		- numeric only
//		A	- alphabetic	- alphabetic only
//		B	- blank			= alphabetic or space
//		E	- e-mail		- alphabetic, numeric, '&', '-', '.', '@' or '_'
//		L	- LDAP			- alphabetic, numeric, '-' or '_' (note: Active Directory is LDAP, so Windows logins too)
//		N	- numeric		= alphabetic or numeric
//		P	- person's name	- alphabetic, numeric, '-', ' ' or '_' (Note: I disagree with numeric and '_' - Steve S.)
//		S	- sign			- numeric, dollar sign, comma, decimal point, plus or minus
//		U	- URL			- alphabetic, numeric, '$', '%', '&', '+', '-', '.', '/', ':', '=', '?', '@', '_' or '~'
//							  and beginning with 'ftp://', 'gopher://', 'http://', 'https://' or 'telnet://' (There 
//							  used to be validation of the top level domain name as well, but so many foreign countries 
//							  licensed their TLD for use in the US that it eventually became unmanageable.) 
//		W	- Web page URL	- same as URL, but beginning with 'http' or 'https'
//		X	- X = unknown	- any character
//		Y	- Yes/No		- 'Y' or 'N'
//		Any other character	- must match exactly that character (often used with hyphens in phone numbers, for example)
//
// For security reasons, browsers don't let JavaScript retrieve <input type="Password"> values. That's why "P" was 
// available for person's name. In all mask characters that allow decimal point, EditMask makes sure that there isn't 
// more than one decimal point. 
//
// The following variables are global, so that other functions can communicate with EditMask, 
// and so that EditMask can communicate with other functions.  The initial 'g' means "global":  

var	gAtSignWasEncountered					= 0; // output, whether or not an "at sign" ('@') was encountered
var	gAccumErrMsg							= "";
var	gAccumErrMsgBase						= "COULD NOT SUBMIT FORM DUE TO THE FOLLOWING ERROR(S): \n";
var	gDataValidation							= 1; // input,  whether to do data validation
var	gDigitsAfterPeriod						= 0; // output, number of digits after the decimal point
var	gPeriodWasEncountered					= 0; // output, whether or not a decimal point was encountered
var	gPreviousFNam							= '';// in/out, workaround for onBlur with Communicator 4.0x
var	gSilentEditMask							= 0; // in/out, allows using EditMask without notifying user of errors
var	gSilentEditMaskAccumErrMsg				= false;//Special kind of gSilentEditMask that accumulates messages. 
var	gValueAfterPeriod						= 0; // output, numeric value of digits after  the decimal point
var	gValueBeforePeriod						= 0; // output, numeric value of digits before the decimal point
var	gValueInCents							= 0; // output, numeric value of field in hundredths (= cents if money)

function notify								(pMsg)	// "notify" = "maybe alert"
{
if	(gSilentEditMaskAccumErrMsg)
	{
	if	(gAccumErrMsg.length < gAccumErrMsgBase.length)
		gAccumErrMsg						= gAccumErrMsgBase;
	gAccumErrMsg							+= pMsg + "\n";
	gSilentEditMask							= 1;
	gSilentEditMaskAccumErrMsg				= false;// Always revert to notifying the user
	}
if	(gSilentEditMask != 1)
	alert (pMsg);
gSilentEditMask								= 0;	// Always revert to notifying the user
}

function EditMask							(pFNam, pFVal, pMask, pReqd, pMinLen, pMaxLen) // Parameters begin with 'p'.
{
// Older incarnations of JavaScript compared all string length properties equal!  That is, if we said 
// "if	(pFVal.length == pMask.length)", it would always be true on an older browser!  Assignment to 
// sFLen and sMLen always works, so we do that.  "Lowest common denominator" coding style:  
// The initial 's' means "stack variable" (= "local variable" = "automatic variable"). 

var	sFLen									= pFVal.length; 
var	sMLen									= pMask.length;
var	sLastPeriodIdx							= -1;
var sValueIsNegative						= 0;

if	(gDataValidation == 0)
	{
	gSilentEditMask							= 0; // Always revert to notifying the user
	return true;
	}

gAtSignWasEncountered						= 0;
gDigitsAfterPeriod							= 0;
gPeriodWasEncountered						= 0;
gValueAfterPeriod							= 0;
gValueBeforePeriod							= 0;
gValueInCents								= 0;

if	(sFLen < 1)
	if	(pReqd > 0)
		{
		if	(gPreviousFNam != pFNam)
			notify ("Mandatory field.  You must enter something into the " + pFNam + " field.");
		gPreviousFNam						= pFNam;
		gSilentEditMask						= 0; // Always revert to notifying the user
		return false;
		}
	else
		{
		gPreviousFNam						= pFNam;
		gSilentEditMask						= 0; // Always revert to notifying the user
		return true;
		}

gPreviousFNam = pFNam;

if	(sMLen < 1)
	{
	notify ("INTERNAL ERROR.  The " + pFNam + " field has incomplete data validation criteria.");
	return false;
	}

if	((sFLen >= pMinLen) && (sFLen <= pMaxLen) && (pMask != "X"))
	{
	var	sAllDollars							= 1;
	var	sAllEMail							= 1;
	var	sAllNumbers							= 1;
	var	sAllPercents						= 1;
	var	sAllURL								= 1;
	var	sAllWs								= 1;
	for	(var i = 0; i < sFLen; i++)
		{
		var e;
		var j								= i + 1;
		var f								= pFVal.substring(i,j).toUpperCase();
		var m;

		if	(f == ".")
			sLastPeriodIdx					= i;

		if	(i < sMLen)
			m								= pMask.substring(i,j).toUpperCase();
		else
			{
			var sLastChar					= sMLen - 1;											// If we run out, the 
			m								= pMask.substring(sLastChar,sMLen).toUpperCase();		// last mask char repeats.
			}

		if	 (m != "#")						sAllNumbers		= 0;
		if	((m != "$") && (m != "S"))		sAllDollars		= 0;
		if	 (m != "%")						sAllPercents	= 0;
		if	 (m != "E")						sAllEMail		= 0;
		if	((m != "U") && (m != "W"))		sAllURL			= 0;
		if	 (m != "W")						sAllWs			= 0;

		e = "ERROR.  " + pFNam;
		if		(pMask == "#")				e = e + " is not a valid number.\n";
		else if	(pMask == "$")				e = e + " is not a valid dollar amount.\n";
		else if	(pMask == "S$")				e = e + " is not a valid signed dollar amount.\n";
		else if	(pMask == "%")				e = e + " is not a valid percentage.\n";
		else if	(pMask == "9")				e = e + " is not a valid number.\n";
		else if	(pMask == "999-999-9999")	e = e + " is not a valid phone number (999-999-9999 format).\n";
		else if	(pMask == "999-999-9999X")	e = e + " is not a valid phone number (999-999-9999 format, followed by extension).\n";
		else if	(pMask == "A")				e = e + " is not alphabetic.\n";
		else if	(pMask == "B")				e = e + " is not alphabetic or space.\n";
		else if	(pMask == "E")				e = e + " is not a valid e-mail address.\n";
		else if	(pMask == "L")				e = e + " is not a valid LDAP/Windows login name.\n";
		else if	(pMask == "P")				e = e + " is not a valid person's name.\n";
		else if	(pMask == "U")				e = e + " is not a valid URL.\n";
		else if	(pMask == "W")				e = e + " is not a valid Web page URL.\n";
		else								e = e + " must adhere to the format '" + pMask + "'.\n";

		e									  = e + "Failure was at character " + j + " ('" + f + "'),\n";

		if	(f == '\"')
			{
			e								= e + "which cannot be double-quote, "
												+ "because double-quote interferes with Web communications.";
			return false;
			}

		if	((m == "9") || (m == "#") || (m == "$") || (m == "%") || (m == "S"))
			{
			if		((m == "9") && ((f < "0") || (f > "9")))
				{
				notify (e + "which should've been numeric.");
				return false;
				}
			else if	((m == "#") && ((f < "0") || (f > "9")) && (f != "."))
				{
				notify (e + "which should've been numeric or decimal point.");
				return false;
				}
			else if	((m == "$") && ((f < "0") || (f > "9")) && (f != "$") && (f != ",") && (f != "."))
				{
				notify (e + "which should've been numeric, dollar sign, comma or decimal point.");
				return false;
				}
			else if	((m == "%") && ((f < "0") || (f > "9")) && (f != ".") && (f != "%"))
				{
				notify (e + "which should've been numeric, decimal point or percent sign.");
				return false;
				}
			else if	((m == "S") && ((f < "0") || (f > "9")) && (f != "$") && (f != ",") && (f != ".") && (f != "+") && (f != "-"))
				{
				notify (e + "which should've been numeric, dollar sign, comma, decimal point, plus or minus.");
				return false;
				}
			if	(f == ".")
				{
				if	(gPeriodWasEncountered > 0)
					{
					notify (e + "which cannot have 2 decimal points.");
					return false;
					}
				else
					gPeriodWasEncountered	= 1;
				}
			else if	((m == "S") && (f == "-"))
				sValueIsNegative			= 1;
			if	((f >= "0") && (f <= "9"))
				{
				if	(gPeriodWasEncountered > 0)
					{
					gDigitsAfterPeriod		= gDigitsAfterPeriod  + 1;
					gValueAfterPeriod		= (gValueAfterPeriod  * 10) + (f - "0");
					}
				else
					gValueBeforePeriod		= (gValueBeforePeriod * 10) + (f - "0");
				}
			}
		else if	(m == "A")
			{
			if	((f < "A") || (f > "Z"))
				{
				notify (e + "which should've been alphabetic.");
				return false;
				}
			}
		else if	(m == "B")
			{
			if	(((f < "A") || (f > "Z")) && (f != " "))
				{
				notify (e + "which should've been alphabetic or space.");
				return false;
				}
			}
		else if	(m == "E")
			{
			if	(!(  ((f >= "0") && (f <= "9")) 
				  || ((f >= "A") && (f <= "Z")) // (locase not needed:  f has been upcased)
				  ||  (f == "&") ||  (f == "-") || (f == ".") || (f == "@") || (f == "_")))
				{
				notify (e + "which should've been alphabetic, numeric, '&', '-', '.', '@' or '_'.");
				return false;
				}
			if	(f == "@")
				{
				if	(gAtSignWasEncountered > 0)
					{
					notify (e + "which cannot contain more than 1 '@'.");
					return false;
					}
				else
					gAtSignWasEncountered = 1;
				}
			}
		else if	(m == "L")
			{
			if	(!(  ((f >= "0") && (f <= "9")) 
				  || ((f >= "A") && (f <= "Z"))
				  || (f == "-")
				  || (f == "_")))
				{
				notify (e + "which should've been alphabetic, numeric, hyphen or underscore.");
				return false;
				}
			}
		else if	(m == "N")
			{
			if	(!(  ((f >= "0") && (f <= "9")) 
				  || ((f >= "A") && (f <= "Z"))))
				{
				notify (e + "which should've been alphabetic or numeric.");
				return false;
				}
			}
		else if	(m == "P")
			{
			if	(!(  ((f >= "0") && (f <= "9")) 
				  || ((f >= "A") && (f <= "Z"))
				  || (f == "-")
				  || (f == "_")
				  || (f == " ")))
				{
				notify (e + "which should've been alphabetic, numeric, hyphen, underscore or space.");
				return false;
				}
			}
		else if	((m == "U") || (m == "W"))
			{
			if	(!(  ((f >= "0") && (f <= "9")) 
				  || ((f >= "A") && (f <= "Z")) 
//				  || ((f >= "a") && (f <= "z")) // (not needed:  f has been upcased)
				  ||  (f == "$") || (f == "%") || (f == "&") || (f == "+")
				  ||  (f == "-") || (f == ".") || (f == "/") || (f == ":")
				  ||  (f == "=") || (f == "?") || (f == "@") || (f == "_")
				  ||  (f == "~")))
				{
				notify (e + "which should've been alphabetic, numeric, '$', '%', '&', '+', "
						  + "'-', '.', '/', ':', '=', '?', '@', '_' or '~'.");
				return false;
				}
			}
		else if	(m == "Y")
			{
			if	((f != "Y") && (f != "N"))
				{
				notify (e + "which should've been 'Y' or 'N'.");
				return false;
				}
			}
		else if	((m != "X") && (f != m))
			{
			notify (e + "which should've been '" + m + "'.");
			return false;
			}
		}

	if	((sAllDollars > 0) || (sAllNumbers > 0) || (sAllPercents > 0))
		{
		if		(gDigitsAfterPeriod == 0)
			gValueInCents					=  gValueBeforePeriod * 100;
		else if	(gDigitsAfterPeriod == 1)
			gValueInCents					= (gValueBeforePeriod * 100) + (gValueAfterPeriod * 10);
		else if	(gDigitsAfterPeriod == 2)
			gValueInCents					= (gValueBeforePeriod * 100) + gValueAfterPeriod;
		else
			{
			e								= "ERROR.  " + pFNam + " cannot have more than 2 digits after the decimal point";
			if	((sAllPercents > 0) && (gValueBeforePeriod == 0))
				notify (e + "\n(express as a percentage, not as a fraction of 1.0000).");
			else
				notify (e + ".");
			return false;
			}
		if	(sValueIsNegative)
			gValueInCents					= 0 - gValueInCents;
		}
	if	(sAllEMail > 0)
		{
		if	(gAtSignWasEncountered == 0)
			{
			notify ("ERROR.  "				+ pFNam + " must contain '@'.");
			return false;
			}
	// We now no longer restrict TLDs to the United States, but save the code in case needed later: 
	//
	//	var	sTLDError						= false;
	//	if  (sLastPeriodIdx < 0)
	//		sTLDError						= true;
	//	else
	//		{
	//		var	sTLD						= pFVal.substring(sLastPeriodIdx,sFLen).toLowerCase();
	//		if ((sTLD						!= ".aero")
	//		&&	(sTLD						!= ".biz")
	//		&&	(sTLD						!= ".com")
	//		&&	(sTLD						!= ".coop")
	//		&&	(sTLD						!= ".edu")
	//		&&	(sTLD						!= ".gov")
	//		&&	(sTLD						!= ".info")
	//		&&	(sTLD						!= ".int")
	//		&&	(sTLD						!= ".museum")
	//		&&	(sTLD						!= ".mil")
	//		&&	(sTLD						!= ".name")
	//		&&	(sTLD						!= ".net")
	//		&&	(sTLD						!= ".org")
	//		&&	(sTLD						!= ".pro")
	//		&&	(sTLD						!= ".us")
	//		&&	(sTLD						!= ".ws")
	//		//	foreign, but allowed:
	//		&&	(sTLD						!= ".cc")
	//		&&	(sTLD						!= ".nu")
	//		&&	(sTLD						!= ".pr")
	//		&&	(sTLD						!= ".to"))
	//			sTLDError					= true;
	//		}
	//	if	(sTLDError)
	//		{
	//		notify ("ERROR.  " + pFNam		+ " must end in a United States Top Level Domain name "
	//										+ "(aero, biz, com, coop, edu, gov, info, int, mil, museum, "
	//										+ "name, net, org, pro, us or ws), or a foreign country "
	//										+ "code being used by ISPs in the United States (cc, nu or to).");
	//		return false;
	//		}
		}
	if	(sAllURL > 0)
		{
		var sLocasedURL						= pFVal.toLowerCase();
		var sProtocolOk						= false;
		if	((sFLen >= 7) && (sLocasedURL.substring(0,7) == "http://"))					sProtocolOk = true;
		if	((sFLen >= 8) && (sLocasedURL.substring(0,8) == "https://"))				sProtocolOk = true;
		if	(sAllWs == 0)
			{
			if	((sFLen >= 6) && (sLocasedURL.substring(0,6) == "ftp://"))				sProtocolOk = true;
			if	((sFLen >= 9) && (sLocasedURL.substring(0,9) == "gopher://"))			sProtocolOk = true;
			if	((sFLen >= 9) && (sLocasedURL.substring(0,9) == "telnet://"))			sProtocolOk = true;
			}
		if	(!sProtocolOk)
			{
			if	(sAllWs > 0)
				notify ("ERROR.  "			+ pFNam + " must begin with 'http://' or 'https://'.");
			else
				notify ("ERROR.  "			+ pFNam + " must begin with 'ftp://', 'gopher://', "
											+ "'http://', 'https://' or 'telnet://'.");
			return false;
			}
		}
	}
else if	(sFLen < pMinLen)
	{
	if	(pMinLen == pMaxLen)
		if	(pMinLen == 1)
			{
			notify ("ERROR.  "				+ pFNam + " must contain exactly 1 character.");
			return false;
			}
		else
			{
			notify ("ERROR.  "				+ pFNam + " must contain exactly " + pMinLen + " characters.");
			return false;
			}
	else if	(pMinLen == 1)
		{
		notify ("ERROR.  "					+ pFNam + " must contain at least 1 character.");
		return false;
		}
	else
		{
		notify ("ERROR.  "					+ pFNam + " must contain at least " + pMinLen + " characters.");
		return false;
		}
	}
else if	(sFLen > pMaxLen)
	{
	if	(pMinLen == pMaxLen)
		if	(pMaxLen == 1)
			{
			notify ("ERROR.  "				+ pFNam + " must contain exactly 1 character.");
			return false;
			}
		else
			{
			notify ("ERROR.  "				+ pFNam + " must contain exactly " + pMaxLen + " characters.");
			return false;
			}
	else if	(pMaxLen == 1)
		{
		notify ("ERROR.  "					+ pFNam + " cannot contain more than 1 character.");
		return false;
		}
	else if	(pMaxLen > 10) // give user a count to help chop down long entry (textareas and such):
		{
		notify ("ERROR.  "					+ pFNam + " cannot contain more than " + pMaxLen + " characters.  "
											+ "(You entered " + sFLen + ".)");
		return false;
		}
	else // special case (long text and textarea fields): user needs to know how many chars entered:
		{
		notify ("ERROR.  "					+ pFNam + " cannot contain more than " + pMaxLen + " characters.");
		return false;
		}
	}

gSilentEditMask								= 0; // Always revert to notifying the user
return true;
}
// End EditMask.js

