// -------------------------------------------------------------------------------------------------------------
// <SCRIPT SRC="JAVASCRIPT_VALIDATION.JS"> v2.0: Last updated July 15, 2005 for RadioShack
// -------------------------------------------------------------------------------------------------------------
// Latest Changes:
// 2005-07-15 Phone supports 10 or 7 characters (1 field for area code and phone #)

// Coming Improvements:
// 2. If date gets passed 3 parts, it should check indiviual 3 - 1 part = 1 textbox
// 3. Anal: Year : default to 1999 based on where current year is
// 5. Check Catchat & Petclub to ensure I'm using best validation funcitons.
// 6. Add date function, as well as year and perhaps something to check day based on month passed in.
//    Pass date from server so client date is irrelevant.
// 8. Perhaps phone number should allow area code to be typed in.
// 14. How well does this work if the FORM controls aren't named? Does it work. It MIGHT happen
// 24. If you're checking multiple controls, and don't put in user defined error message,
//     the error message you get does NOT necessarily list all the controls because it kicks out of loop at first sign
//     og a bad control.
// 25. What do I do with putting blank into required and trimmed back into numerics.
//     I shouldn't touch if perr is set. Unsure what to do. Please advise.
// 26. Ability to check for letters only (perhaps allow symbols), perhaps allow space.
// 29. For multiple controls (especially radio and checkboxes), change error message to "1 is required."
//     as opposed to every name of the control. Stop required_single from changing .err and have it
//     done by required (based on pall and type of control).
// 30. BUG: Area code needs to use INT check (should use the code in numeric check)
// 32. 2002-09-05 Netscape 7.0 : .Type = "Textbox" with a capitl T. sigh why did they change it?
// 33. 2002-09-05 Netscape 6.0+ : got rid of caller.callee - now I'm just using callee.
// 34. 2002-09-26	Date check severly improved. Now works in Netscape and works better in IE

//	Javascript Class : Usage <SCRIPT SRC = Validation.asp>
// used on client-side for validating form data entry. 

// -------------------------------------------------------------------------------------------------------------
// TRIM : Method added to the string class. Validation class requires it ---------------------------------------
// -------------------------------------------------------------------------------------------------------------
function trim() {
	// removes beginning and trailing spaces from a string
	// i.e. "   jim   " becomes "jim", "  bobby  smith   " becomes "bobby  smith"
	var s=""; b=0; e=0;

	s = this.valueOf().toString();			
	for (b = 0; b < s.length; b++) {
		if (s.charAt(b) != " ") break;
	}
	
	if (b == s.length) {
		s = "";
	} else {
		for (e = (s.length - 1); e >= 0; e--) {
			if (s.charAt(e) != " ") break;			
		}
		s = s.substring(b, e + 1);
	}
	return s;
}
String.prototype.trim = trim;

function capitalize() {
	// Capitalizes the first letter of every word. Changes _ and multiple spaces to space
	// i.e. "  jim_was here  hey___ho  " becomes "Jim Was Here Hey Ho"
	var s=""; i=0;
  var word = false;
  var return_value = "";
    
	s = this.valueOf().toString();
	for (i = 0; i < s.length; i++) {
		if (s.charAt(i).match(/[\ \_]/i)) {
      if (word) {return_value += " ";}
      word=false;
    } else if (!word) {
      // if this is the beginning of a word, captilize it
      return_value += s.charAt(i).toUpperCase();
      word=true;
    } else {
      // in the middle of a word, just add the letter
      return_value += s.charAt(i);
    }      
	}
	// At this point, if there was an underscore or space(s) at the end, they would have been converted to 1 space, remove it.
	if (return_value.charAt(return_value.length-1) == " ") {return_value = return_value.substring(0, return_value.length-1);}
	return return_value;  
}
String.prototype.capitalize = capitalize;

function pad(plength) {
	var s = "";
	var return_value = "";
	
	s = this.valueOf().toString();
	return_value = s;
	if (s.length < plength) {return_value = "000000000".substring(0, plength - s.length) + return_value;}
	return return_value;  	
}
String.prototype.pad = pad;

// -------------------------------------------------------------------------------------------------------------
// CHECK : Objects methods are called by the client to validate user entry--------------------------------------
// -------------------------------------------------------------------------------------------------------------
function Check(planguage) {
	// 1. planguage - integer : 0 or null means english, 1 means french.
	this.err = "";	// expose to outside world.

	var words = new Object();	
	words.is_required = (planguage == 1) ? " obligatoire.\n" : " is required.\n";
	words.is_invalid = (planguage == 1) ? " est incorrecte.\n" : " is invalid.\n";
	words.is_large = (planguage == 1) ? " est plus grande.\n" : " is to large.\n";
	words.numeric = (planguage == 1) ? " doit être numérique.\n" : " must be numeric.\n";
	words.numeric_no_decimal = (planguage == 1) ? " doit être numérique (sans décimale).\n" : " must be numeric (no decimal).\n";
	words.numeric_greater_than = (planguage == 1) ? " doit être autre que" : " must be greater than";
	// Phone Number must not start with 555.
	// Le numéro de téléphone ne doit pas commencer par 555	
 	
	var token = null;

// -------------------------------------------------------------------------------------------------------------
// Required Functions : Used to check whether ANY kind of control is filled in.
// -------------------------------------------------------------------------------------------------------------		
	this.required_single = function required_single(pcontrol) {
		// PRIVATE. called by required. checks a SINGLE control (not an array) to see if it's filled out.
		// 1. pcontrol - object : pointer to a control (or array of radio buttons or checkboxes) on the form.
		// Returns : True if control is filled out, false if it's blank.
		var value = "";
		var dropdown = false;		
		var return_value = false;

		if (pcontrol.type.substring(0,6) == "select") {
			// Is it a dropdown or a listbox? Can't rely on type to tell me (since IE considers both dropdown and list to be select-one)
			if (pcontrol.size) {
				// we're in IE... type is select-one even lists (with multiple selection off)
				if (pcontrol.size < 2) {dropdown = true;} else {dropdown = false;}
			} else {
				// we're in Netscape... size property does not exist. type is select-multiple for lists (even without multiple selection)
				if (pcontrol.type == "select-one") {dropdown = true;} else {dropdown = false;}
			}
			
			if (dropdown) {
				if (pcontrol.selectedIndex > 0) {
					return_value = true;	// 0 means first item is picked... -1 does NOT exist for dropdowns
				} else if (pcontrol.selectedIndex == 0) {	// if first item is picked, is it "" or a value
					value = pcontrol.options[0].value.toString().trim();
					if (value != "") {return_value = true;}	// it's not blank so it's filled out.
				}				
			} else {
				if (pcontrol.selectedIndex > -1) {return_value = true;}	// -1 means nothing is picked (accurate for MULTIPLE as well)
			}

		} else if ((pcontrol.type == "radio") || (pcontrol.type == "checkbox")) {
			if (pcontrol.checked) {return_value = true;}

		} else {
			value = pcontrol.value.toString().trim();
			if (value != "") {return_value = true;}
			if (value != pcontrol.value) {pcontrol.value = value;}	// remove any beginning or trailing spaces in the textbox
		}
		return return_value;		
	}

	
	this.required_array = function required_array(pcontrol, phideerror, pall) {
	// PRIVATE : can only be called by this.required
	// 1. pcontrol - object : pointer to a control or array of controls on the form.
	// 2. phideerror - boolean : do I do anything when the error is discovered? True means I update the error message and focus, False means I just return true/false
	// 3. pall - boolean : if pcontrol is an array of selects or textboxes, specifies whether all (default) selects must be filled out or just some.
	// Returns : True if control is filled out, false if it's blank.
		var arrayz = false;
		var errcontrol = null;
		var return_value = false;		

		if (typeof(pcontrol) != "object") {alert("BUG:Check.required() was NOT passed a valid control."); return false;}
		
		// 1. Have we been passed a single control, or an array of controls? if it's not one of these two possibilities, then it's NOT an array of controls
		if ((pcontrol.type) && (pcontrol.type.substring(0,6) == "select")) {	// pcontrol[0] array check CANNOT detect an array of select lists. since [0] can mean EITHER options[0] OR the second select list
			if (pcontrol[0][0]) {arrayz = true;} // this will only be true if we have an array of select lists
		} else if (pcontrol[0]) {	// if this is true, then we are dealing with an array of controls.
			arrayz = true; // an array of controls (checkboxes, radio buttons, textboxes)
		}
		if (arrayz) {errcontrol = pcontrol[0];} else {errcontrol = pcontrol;}	// set a pointer that we can use the rest of this function
		
		// 2. pall = TRUE does not make sense for radio and checboxes
		if ((errcontrol.type == "radio") || (errcontrol.type == "checkbox")) {pall = false;}	// pall doesn't make sense for radio buttons or checkboxes
		
		// 3. If it's an array of controls, loop through them, otherwise check the individual one
		if (arrayz) {
			return_value = false;	// loop through the array of controls. change return_value to false if any are filled out
			if (pall) {
				// ensure every element of the array is filled out
				for (var i=0; i < pcontrol.length; i++) {if (!this.required_single(pcontrol[i])) {errcontrol = pcontrol[i]; break;}}
				if (i == pcontrol.length) {return_value = true;} // if we looped through every control and they were all filled out, then it's not empty
			} else {
				// ensure just 1 element of the array is filled out
				for (var i=0; i < pcontrol.length; i++) {if (this.required_single(pcontrol[i])) {return_value = true; break;}}
			}
		} else {
			// a single control (textbox, textarea)
			return_value = this.required_single(pcontrol);
		}
		
		// 4. If the control is not filled out, handle it (add to the error message, set focus)
		if ((!phideerror) && (!return_value)) {
			if (this.err == "") {
				errcontrol.focus();
        if (errcontrol.select) {errcontrol.select();} // select lists, and radio buttons in IE4, do not support the select method
				// if (errcontrol.type.substring(0,6) != "select") {errcontrol.select();} // select lists do not support the select method
			}
			this.err += errcontrol.name.capitalize() + words.is_required;			
		}
		return return_value;
	}

	
	// REQUIRED	
	this.required = function required() {
	// SAMPLE CALL : check.required(f.news_toronto, f.news_post, f.news_mail, "new error message", true);
	// 1. pcontrol - object : 1 or more controls on the form
	// 2. perr - string : error message that overrides default, "hide" means don't change error message or focus
	// 3. pall - boolean : if pcontrol is an array of selects or textboxes, specifies whether some or all (default) of the controls must be filled out.
	// Returns : True if the control(s) are filled out, false if they are not (hingent on pall)	
		var controls = new Array(), controlcounter = 0, controlname = "";
		var perr = null, pall = true;
		var hideerror = false;
		var holdmessage = "";
		var allradio = true;
		var return_value = false;
		
		// 1. Dump arguments into proper variables. Remember that you are allowed to send multiple controls to this routine
		// i.e. check.required(f.news_toronto, f.news_post, f.news_mail, "new error message", true); gets dumped into control[i], perr, pall
		controlcounter = 0;
		while (((controlcounter < arguments.length) && (typeof(arguments[controlcounter]))) == "object") {
			controls[controlcounter] = arguments[controlcounter];
			if ((controls[controlcounter].type != "radio") && (controls[controlcounter].type != "checkbox")) {allradio=false;}
			controlcounter++;
		}
		if (controlcounter ==0) {alert("BUG:Check.required() was not passed a control. Check spelling of passed controls. Quotes can NOT be used around the control name."); return false;}
		perr = arguments[controlcounter]; if ((!perr) || (perr == "")) {perr = "";} else {perr = perr.toString().trim();}
		if (perr.toLowerCase() == "hide") {hideerror = true;} else {hideerror = false;}
		pall = arguments[controlcounter+1]; if (typeof(pall) != "boolean") {pall = true;}
		if (allradio) {pall = false;}	// if all the controls passed in are radio buttons, then this MUST be false since all cannot be lit
		
		// 2. Loop through all the controls that have been passed in (which may in turn be arrays of controls although it really wasn't built for that)
		holdmessage = this.err;
		return_value = false;
		if (pall) { // ensure every control passed is filled out
			for (controlcounter=0; controlcounter < controls.length; controlcounter++) {
				if (!this.required_array(controls[controlcounter], hideerror, pall)) {break;}
			}
			if (controlcounter == controls.length) {return_value = true;}
		} else {    // ensure just 1 of the controls passed is filled out			
			for (controlcounter=0; controlcounter < controls.length; controlcounter++) {
				if (this.required_array(controls[controlcounter], hideerror, pall)) {return_value = true; break;}
			}
		}

		// 3. If the set of controls have not been filled in properly, set .err properly
		if (!hideerror) {
			if (!return_value) {
				if (perr != "") {				// Has user passed in their own error message?
					this.err = holdmessage;	// Set message back to what it was (remove errors added).
					this.err += perr + "\n";		
				}
			} else {
				this.err = holdmessage;		// Remove any error messages abouut individual controls because overall objective was fine.
			}
		}
		return return_value;
	}	

// -------------------------------------------------------------------------------------------------------------
// Initialize & Terminate - private functions that contain common code all the other functions use.
// -------------------------------------------------------------------------------------------------------------	
	this.initialize = function initialize(parguments) {	
	// PRIVATE. Called at the beginning of every function.
		// Validate and typecast the arguements
		var caller_name = parguments.callee.toString().match(/function (\w*)/)[1];
		var controlcounter = 0;
		var return_value = 0;
		
		token = null; // Destroy any previous instance
		token = new Object;		

		token.controls = new Array();
		controlcounter = 0;
		while (((controlcounter < parguments.length) && (typeof(parguments[controlcounter]))) == "object") {
			token.controls[controlcounter] = parguments[controlcounter];
			if ((token.controls[controlcounter].type != "Textbox") && (token.controls[controlcounter].type != "text") && (token.controls[controlcounter].type != "textarea")) {alert("BUG:CHECK class:'" + caller_name + "' method: Does not work with '" + token.controls[controlcounter].name + "' controls. It only works with text and textareas.");}
			controlcounter++;
		}				
		if (controlcounter == 0) {alert("BUG:CHECK class:'" + caller_name + "' method: NOT passed a valid control - perhaps a spelling mistake.");}

		token.control = token.controls[0];	// for routines that only use 1 control, make this pointer		
		token.value = token.control.value.toString().trim();	// Strip off any spaces
		if ((!parguments[controlcounter]) || (parguments[controlcounter] == "")) {token.err = "";} else {token.err = parguments[controlcounter].toString().trim();}
		if (typeof(parguments[controlcounter+1]) != "boolean") {token.required = true;} else {token.required = parguments[controlcounter+1];}
    
	 	token.name = token.control.name.capitalize();
		token.holdmessage = "";
		token.return_value = true;	// Assume it's good until it's proven bad
	}

	this.terminate = function terminate() {
	// PRIVATE. Called at the end of every function.		
	// Make the control look like it should (or at least like the functions expect) i.e. postal code adds a dash
   	// Need toString when comparing 9.0 to 9
		if (token.value.toString() != token.control.value.toString()) {token.control.value = token.value;}
				
		// Modify this.err (which client uses to detect if there's been an error)
		if ((token.err.toLowerCase() != "hide") && (!token.return_value)) {
			if (this.err == "") {
				// First error we've had. Set the focus to that control & select it.
				token.control.focus();
				if (token.control.select) {token.control.select();} // select lists, and radio buttons in IE4, do not support the select method
			}				
			if (token.err == "") {this.err += token.holdmessage;} else {this.err += token.err + "\n";}
		}	
	}

// -------------------------------------------------------------------------------------------------------------
// FUNCTIONS EVERYBODY USES
// -------------------------------------------------------------------------------------------------------------
// FIRST 3 ARGUMENTS OF ALL FUNCTIONS :
// 1. pcontrol - object : pointer to a textbox/textarea on the form.
// 2. (optional) perr - string : error message that overrides default, "hide" means don't change error message or focus
// 3. (optional) prequired - true/false : does this control have to be filled in? (defaults to true)
// FUNCTIONS RETURN : True if control meets criteria, False if it doesn't
// SAMPLE CALL : check.numeric(f.age, "new error message", true);
	
	// NUMERIC
	this.numeric = function numeric(pcontrol, perr, prequired, pint, plimit) {
		// SAMPLE CALL : check.numeric(f.age, "new error message", true);
		// 4. (optional) pint - true/false (defaults to true) : does this control only allow integers? 
		// 5. (optional) plimit - integer (defaults to 1) : the textbox cannot be less than this 		        
		this.initialize(arguments);
		if (typeof(arguments[3]) != "boolean") {pint = true;}
		if (typeof(arguments[4]) != "number") {plimit = 0;}

		// Check the value to ensure it's numeric
		if (!token.required || this.required(token.control, token.err)) {
			if (token.value != "") {	// if it's not required, and it's blank, the token.value is OK
				if ((pint) && ((isNaN(token.value)) || (parseInt(token.value) != parseFloat(token.value)))) {
					// 1. It's got letters or it has decimals, so give an error.
					token.holdmessage += token.name + words.numeric_no_decimal;
					token.return_value = false;
				} else if ((!pint) && (isNaN(token.value))) {
					// 2. It's got letters
					token.holdmessage += token.name + words.numeric;
					token.return_value = false;
				} else if (token.value <= plimit) {
					// 3. The token.value is numeric. However, is it less than the limit.
					token.holdmessage += token.name + words.numeric_greater_than + " " + plimit + ".\n";
					token.return_value = false;        
	        } else if (pint) {
	          // We're successful if we get here. If it's an integer, get rid of decimal and trailing zeroes.
	          token.value = parseInt(token.value);
	        }
	      }      
		}

		this.terminate();
		return token.return_value;
	}
	
	// POSTAL CODE (CANADIAN ONLY)
	this.postal_code = function postal_code(pcontrol, perr, prequired) {
		// SAMPLE CALL : check.postal_code(f.postal_code, "new error message", true);		
		this.initialize(arguments);
	
		// Check to see if the value is a valid postal code
		if (!token.required || this.required(token.control, token.err)) {
			if (token.value != "") {	// if it's not required, and it's blank, the value is OK
				token.value = token.value.replace(/[\-\ \.\_]/g, "").toUpperCase();	// remove special characters
				if (!token.value.match(/[a-z][0-9][a-z][0-9][a-z][0-9]/i)) {	// also takes care of missing characters. i.e.if postal code is only 4 digits
					token.holdmessage += token.name + words.is_invalid;
					token.return_value = false;
				} else if (token.value.length > 6) {
					token.holdmessage += token.name + words.is_large;
					token.return_value = false;
				}			
			}
		}

		// if (token.value != "") {token.value = token.value.substring(0, 3) + "-" + token.value.substring(3, token.value.length);}
		this.terminate();
		return token.return_value;		
	}
	

	// AREA CODE (no phone number)
	this.area_code = function area_code(pcontrol, perr, prequired) {
		// SAMPLE CALL : check.area_code(f.area_code, "new error message", true);
		this.initialize(arguments);
	
		// Check to see if the value is a valid area code
		if (!token.required || this.required(token.control, token.err)) {
			if (token.value != "") {	// if it's not required, and it's blank, the value is OK
				token.value = token.value.replace(/[\(\)]/g, "");		// remove special characters
				if (isNaN(token.value) || (token.value.length < 3)) {
					token.holdmessage += token.name + words.is_invalid;
					token.return_value = false;
				} else if (token.value.length > 3) {
					token.holdmessage += token.name + words.is_large;
					token.return_value = false;
				}
			}
		}

		this.terminate();
		return token.return_value;		
	}
	
	// PHONE NUMBER
	// *** ENSURE phone number,area code are using the same stripppers (toUpperCase)
	// *** Add area code ability
	this.phone_number = function phone_number(pcontrol, perr, prequired) {
		// SAMPLE CALL : check.phone_number(f.phone_number, "new error message", true);
		this.initialize(arguments);
	
		// Check to see if the value is a valid phone number
		if (!token.required || this.required(token.control, token.err)) {
			if (token.value != "") {	// if it's not required, and it's blank, the value is OK
				token.value = token.value.replace(/[\(\)\-\.\ ]/g, "");		// remove special characters
				if (((token.value.length != 7) && (token.value.length != 10)) || isNaN(token.value)) {
					token.holdmessage += token.name + words.is_invalid;
					token.return_value = false;
				} else if (token.value.length > 10) {
					token.holdmessage += token.name + words.is_large;
					token.return_value = false;
				}
			}
		}

		this.terminate();
		return token.return_value;		
	}

	
	// E-MAIL (very simple check, should be improved)
	this.email = function email(pcontrol, perr, prequired) {
		// SAMPLE CALL : check.email(f.email, "new error message", true);
		this.initialize(arguments);
	
		// Check to see if the value is a valid e-mail
		if (!token.required || this.required(token.control, token.err)) {
			if (token.value != "") {	// if it's not required, and it's blank, the value is OK
				if (!token.value.match("@")) {
					token.holdmessage += token.name + words.is_invalid;
					token.return_value = false;
				}
			}
		}

		this.terminate();
		return token.return_value;		
	}

	
	// YEAR
	this.year = function year(pcontrol, perr, prequired) {
		// SAMPLE CALL : check.year(f.year, "new error message", true);
		this.initialize(arguments);
	
		// Check to see if the value is a valid year
		if (!token.required || this.required(token.control, token.err)) {
			if (token.value != "") {	// if it's not required, and it's blank, the value is OK
				token.value = parseInt(token.value);
				if (isNaN(token.value)) {
					token.holdmessage += token.name + words.is_invalid;
					token.return_value = false;
				} else if ((token.value < 0) || ((token.value > 99) && (token.value < 1753)) || (token.value > 9999)) {
					token.holdmessage += token.name + words.is_invalid;
					token.return_value = false;
				} else {
					// default anything below 50 to the year 2000, anything above to the year 1999 i.e. 89 becomes 1989, 23 becomes 2023
					if (token.value < 50) {token.value += 2000;} else if (token.value < 100) {token.value += 1900;}
				}
			}
		}

		this.terminate();
		return token.return_value;		
	}

	
	// DATE
	this.date = function date(pcontrol, perr, prequired) {
		// SAMPLE CALL : check.area_code(f.dateto, "new error message", true);
		// longyear = d.toString().substring(d.toString().length-4,d.toString().length);
		var textboxyear, milliseconds, d;
		this.initialize(arguments);
	
		if (!token.required || this.required(token.control, token.err)) {
			if (token.value != "") {	// if it's not required, and it's blank, the value is OK

				// If the user has typed in a short form date, change year 25 into 2025 and year 99 into 1999
				token.value = token.value.replace(/[\-\.]/g, "/");	// for short form dates, change - and . seperators to /
				if (token.value.match(/(\d?\d)\/(\d?\d)\/(\d?\d?\d?\d)/)) {	// is it a short form date?
					textboxyear = parseInt(token.value.match(/\d?\d?\d?\d\/?/g)[2]);	// break up the date into 3 array values, take the third value
					if (textboxyear < 100) {
						if (textboxyear < 50) {textboxyear += 2000;} else {textboxyear += 1900;}
						token.value = token.value.replace(/(\d?\d)\/(\d?\d)\/(\d?\d?\d?\d)/, "$1\/$2\/" + textboxyear);	// replace the short year with the new year
					}
				}
				
				// if date is invalid (after conversion to full year), this line will not work
				milliseconds = Date.parse(token.value);

				if (isNaN(milliseconds)) {
					token.holdmessage += token.name + words.is_invalid;
					token.return_value = false;
				} else {
					// good date. dump it out in new format. note: changes september 14, 2002 into 09/14/2002
					d = new Date(milliseconds);
					token.value = (d.getMonth() + 1).toString().pad(2) + "/" + d.getDate().toString().pad(2) + "/" + d.getFullYear().toString();
				}
			}
		}

		this.terminate();
		return token.return_value;		
	}	
	
}	// END CHECK CLASS

