//------------------------------------------------------------------------
//
// File: form-validate.js
//
// This file contains functions that are used to validate the required 
// fields in a form. The functions are based on functions found in the 
// book "Javascript: The Definitive Guide" by O'Reilly. Pages 311-314
// contain the functions.
//
// Please note that some modifications have been made, so the functions
// will not be identical to those used in the book.
//
// To make use of these functions, please add the line:
// <SCRIPT language=JavaScript src="/javascript/form-validate.js" type=text/javascript></SCRIPT>
// somewhere in the html (most likely in the HEAD).
//
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// HOW TO USE THESE FUNCTIONS
//
// isblank(s):
// 
//   This function is an internal function and does not need to be used
// in any outside context. It simply sees whether a string contains
// only white space or not.
//
//---
//
// emailOk(s):
//
//   This function is an internal function and does not need to be used
// in any outside context. It simply sees whether a string appears
// to be a valid email address or not.
//
//---
//
// titleOk(s):
//
//   This function is an internal function and does not need to be used
// in any outside context. It simply sees whether a string contains
// a valid artwork title or not.
//
//---
//
// verify(f):
//
//   Verify is used in the onSubmit event handler to check if a form has
// been filled in properly. In order for the function to work, fields 
// need to be set as being required. This is all done in the <FORM> tag
// to make things convenient. An example of this is taken from
// "art-upload-page.cgi":
//
// <FORM action='/cgi-bin/portfolio/art/art_upload.cgi-test'
// 	enctype='multipart/form-data' method='POST'
//	onSubmit = "
//	this.title.required = 'Title';
//	this.medium.required = 'Medium';
//	this.size.required = 'Size';
//	this.size_width.required = 'Width';
//	this.size_height.required = 'Height';
//	this.size_depth.required = 'Depth';
//	this.weight_unit.required = 'Weight Unit';
//	this.size_weight.required = 'Weight';
//	this.price.required = 'Price';
//	this.upfile.required = 'File to Upload';
//	this.type_of_file[0].required = 'Type of File';
//	this.email.email = true;
//	this.title.title = true;
//	this.size_width.number = true;
//	return verify(this);
//	";
// 	>
//
//   Notice how we specify the action, method, enctype, etc. as normal,
// and then add the onSubmit function to the tag. What this says is that
// on submittal of the form, exectue the javascript code that is in the
// double quotes.
//
//   The sytax for the required tags is "form_name.field_name.required".
// The form name is "this" because we are referring the the current form.
// The field name is based on the name of the input as it appears in the
// form. So "title", above, refers to the title of the artwork. "required"
// should always be "required" since we are specifying a new attribute to
// add to the field.
//
//   We set the required values equal to what we want to be shown on error.
// For example, the input name for the file to upload is "upfile", but we
// set the required attribute of upfile to "File to Upload". This is 
// because if no filename is given, then the JavaScript can use the 
// required attribute to print out a nice message of what is missing.
//
//   Notice that "type_of_file" looks different than the others. The "[0]"
// is because radio buttons are read as array objects. If you are
// a radion button (which is almost never), then make sure that you use
// the "[0]" in there so that everything works out ok. If you don't, then
// the radion buttons simply will not be checked.
//
//   Also notice the this.email.email attribute. What we are saying is 
// that we want to set "this" form's "email" input's "email" attribute to
// be "true". This then tells the verify function that the input is 
// supposed to be an email address, and then verify will check to see
// that the email address is valid. There are similar mechanisms for 
// checking for valid titles and numbers as well.
//
//   The last part of the JavaScript is the verify function. Notice that
// the syntax is "return verify(this);". What we are saying is to return
// the return value of the function when it has completed. Returning true
// means that the form will be submitted, and false means that nothing
// will happen. "this" in the verify function referrs to the form that
// the function is being called in (just the same as it is referred to 
// in the required attributes). We are simply saying: return the value
// of this form being verified. Simple, huh?
//
//   Only require fields that are to be required. Leaving the other inputs
// out simply means that they will not be checked. This way we can include
// this function library in all the header files and not have to worry
// about things being checked that do not need to be.
//
//------------------------------------------------------------------------


//------------------------------------------------------------------------
// GLOBAL VARIABLES
//
// JavaScript uses lexical scoping, so we need a few global variables
// that can be accessed in all functions.
//------------------------------------------------------------------------
var errMsg = "";

//------------------------------------------------------------------------
// FUNCTIONS
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// isblank:
//
// A utility function that returns true if a string contains only 
// whitspace characters.
//------------------------------------------------------------------------

function isblank(s)
{
	for (var i = 0; i <= s.length - 1; i++) {
		var c = s.charAt(i);
		if ((c != ' ') && (c != '\n') && (c != '\t')) return false;
	}

	return true;
}

//------------------------------------------------------------------------
// numberOk:
//
// A utility function that returns true if a string is a number.
//------------------------------------------------------------------------

function numberOk(s)
{

	var pattern = /[^\.,0-9]/g;

	var result = s.match(pattern);

	if (result == null) {
		errMsg = "number";
		return true;
	}

	errMsg = "\n\t" + result + " are invalid number character(s)";

	return false;
}

//------------------------------------------------------------------------
// emailOk:
//
// A utility function that returns true if a string appears to be 
// a valid email address. Valid email addresses look like "me@wwar.com".
//------------------------------------------------------------------------

function emailOk(s)
{
	var user = false;
	var at = false;
	var domain = false;
	var dot = false;
	var extension = false;

	for (var i=0; i <= s.length - 1; i++) {
		var c = s.charAt(i);

		if ((c != '@') && (!user)) {
			user = true;
		}

		if ((c == '@') && (user) && (!at)) {
			at = true;
		}

		if ((c != '@') && (user) && (at) && (!domain)) {
			domain = true;
		}

		if ((c != '@') && (user) && (at) && (domain) && (c == '.') && (!dot)) {
			dot = true;
		}

		if ((c != '@') && (user) && (at) && (domain) && (c != '.') && (dot)) {
			return true;
		}
	}

	return false;
}

//------------------------------------------------------------------------
// titleOk:
//
// A utility function that returns true if a string contains all valid
// characters for a title.
//------------------------------------------------------------------------

function titleOk(s)
{
	var pattern = /[^a-zA-Z0-9_ ]/g;

	var result = s.match(pattern);

	if (result == null) {
		return true;
	}
	
	errMsg = "\n\t" + result + " are invalid character(s)";
	
	return false;

}

//------------------------------------------------------------------------
// verify:
//
// This is the function that performs form verification. It will be
// invoked from the onSubmit() event handler. The handler should return
// whatever value this function returns.
//------------------------------------------------------------------------

function verify(f)
{
	var msq;
	var empty_fields = "";
	var errors = "";

	// Loop through the elements of the form, looking for all elements
	// of certain types that have a "required" property defined. Then,
	// check for fields that are empty and make a list of them. Also,
	// if any of these elements have a "min" or a "max" property
	// defined, then verify that they are numbers and that they are in 
	// the right range. Put together error messages for fields that are
	// wrong.
	for (var i = 0; i < f.length; i++) {
		var e = f.elements[i];

		// Check the text, textarea, and password fields.
		if (((e.type == "text") || (e.type == "textarea") || (e.type == "password")) && e.required) {
			// First check if the field is empty.
			if ((e.value == null) || (e.value == "") || isblank(e.value)) {
				empty_fields += "\n\t" + e.required;
				continue;
			}

			// Now check for fields that are supposed to be numeric.
			if ((e.isNumber == true) && (!numberOk(e.value))) {
				errors += "- " + e.required + " must be a number. "
					+ errMsg + "\n";
			}

			// Now check for valid email addresses.
			if ((e.isEmail == true) && (!emailOk(e.value))) {
				errors += "- " + e.required + " contains an invalid email address.\n";
			}

			// Now check for valid titles.
			if ((e.isTitle == true) && (!titleOk(e.value))) {
				errors += "- " + e.required + " contains an invalid title. Use only letters, numbers "
					+ "and spaces." + errMsg + "\n";
			}

		}

		// Check the radio buttons.
		if ((e.type == "radio") && e.required) {
			// Check if the field is empty.
			if ((e.value == null) || (e.value == "") || isblank(e.value)) {
				empty_fields += "\n\t" + e.required;
				continue;
			}
		}

		// Check the check boxes.
		if ((e.type == "checkbox") && e.required) {
			// Check if the field is empty.
			if (e.checked == false) {
				empty_fields += "\n\t" + e.required;
				continue;
			}
		}

		// Check the select inputs.
		if (((e.type == "select-one") || (e.type == "select-multiple")) && e.required) {
			// Check if the field is empty.
			if ((e.selectedIndex == -1) || isblank(e.options[e.selectedIndex].value)) {
				empty_fields += "\n\t" + e.required;
				continue;
			}
		}

		// Check the file inputs.
		if ((e.type == "file") && e.required) {
			// Check if the field is empty.
			if ((e.value == null) || (e.value == "") || isblank(e.value)) {
				empty_fields += "\n\t" + e.required;
				continue;
			}

			// Now check for spaces in the file name.

			// *** This part was skipped. We will do later.

		}
	}

	// Now, if there were any errors, display the messages, and return
	// false to prevent the form from being submitted. Otherwise,
	// return true.
	if (!empty_fields && !errors) return true;

	msg  = "____________________________________________________________\n\n";
	msg += "Your information was not processed because of the following error(s).\n";
	msg += "Please correct these error(s) and re-submit.\n";
	msg += "____________________________________________________________\n\n";

	if (empty_fields) {
		msg += "- The following required field(s) are empty:"
		     + empty_fields + "\n";
		
		if (errors) msg += "\n";
	}
	
	msg += errors;

	alert(msg);
	return false
}
