//form_validation.js

// $Id: form_validation.js,v 1.31 2009-01-15 15:08:57 xim Exp $

//This file does site wide generic validation for W3 Compliant DOMs

//v1.0 Author - Spacepleb@psand.net 
//     Copyright - Psand Ltd. 2003
//Free Software released under GPL - http://www.gnu.org

//To use this file the things are required:
//  1: A script tag in the head, dragging in this file of the type
//    <script type="text/javascript" src="form_validation.js"></script>

//  2: A script tag below this dragging aUsed array from server if needed

//  3: EITHER an attribute adding to the form element like this:
//  <form action="whatever" onsubmit = "return validate();">

//     OR this line adding to the scripts in the page:
//  document.getElementById("form").onsubmit = function(){return validate();}
//     assuming the form has been given the attribute id="form"

//  4: The relevant fields being given id's

//  5: A javascript at the bottom of the page with a function called validate()
//     this function needs to call the relevant validation functions below.

//The functions are to be called with a multidimensional array
//  passing at least field id and field label to each function, with the id
//  being the field id attribute and the label being the name to appear
//  as the field label in the javascript alert box upon failure.

//The functions accept arrays of the format:
// compulsory ([['field_id' , 'field_label'],
//              ['field_id' , 'field_label']]);

//and
// max_length ([['field_id' , 'field_label' , 20],
//              ['field_id' , 'field_label' , 30]]); 

//Variable naming conventions
//---------------------------

//Due to the awful type declaration in JS the variables all have a prefix
//  to avoid confusion:

// sVariable  - String
// aVariable  - Array
// iVariable  - Integer
// fVariable  - Float
// isVariable - Boolean
// oVariable  - Object
// rVariable  - Regular expression

//A note on function calling order
//--------------------------------

//It is wise to trim the fields first.
//  Then check compulsory fields for not blank
//  Then check for maxlength, as characters outside of this are irrelevant anyway.
//  Then go into character testing.

// This order requires least irrelevant processing AND user irritation.

var d = document; 
var isValid = true; //Declare the validity flag as global.
var sCurrent = "";  //This holds the current reference if there is one.
var sId = '';       //The id of the field currently being processed,
                    //    this needs to be global because the field focussing
                    //    timeouts run outside the scope of the function,
                    //    10 milliseconds later. 
 
//  Subserviant functions - these are called from within this file
//  ---------------------

function setFocus(){	
    window.setTimeout("d.getElementById(sId).focus();window.scrollBy(0,-50)",10);
    return true;
}


function ltrim(sTxt){ // Trim left whitespace

    rExpression = /^[\s\n\f\r]+/;
    sTxt = sTxt.replace(rExpression,'');
    return sTxt;
}

function rtrim(sTxt){	//Trim right whitespace
    rExpression = /[\s\n\f\r]+$/;
    sTxt = sTxt.replace(rExpression,'');
    return sTxt;
}


function lrtrim(sTxt){  //Trim both left and right whitespace

    sTxt = ltrim(sTxt);
    sTxt = rtrim(sTxt);
    return sTxt;
}

//  Main Functions called from the form page.
//  -----------------------------------------

function trim(aFields){

    //This function removes whitespace from the beginning 
    //  and end of the text fields, then replaces them on the form.

    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];

	var oField;	

	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		oField = d.getElementById(sId);
		oField.value = lrtrim (oField.value);   
	    }
	}
    }
    return true;
}

function compulsory(aFields){

    //This function checks each required field for blankness
    //  alerting a warning and returning false if blank.

    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];

	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		if (oField.value == ""){
		    alert (aFields[i][1] +" may not be left blank.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function money(aFields){

    //This function checks each required field
    // for a valid money format
 

    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];

	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		if (!isMoney(oField.value)){
		    alert (aFields[i][1] +" does not have a valid money value.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}


function nobackslash(aFields){

    //This function checks each required field for backslashes
    //  alerting a warning and returning false if found.

    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];
	
	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		var iFound = oField.value.search(/\\/);	
		if (iFound !=-1){
		    alert(aFields[i][1] + " may not include the backslash character ( \\ )");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function valid_alt_text(aFields){

    //This function checks each required field for validity as alt_text
    //  alerting a warning and returning false if it finds invalid characters.

    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];

	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		rExpression = /"/gi; //"
		    var iFound = oField.value.search(rExpression);	
		if (iFound !=-1){
		    alert(aFields[i][1] + " may not include speech marks ( \" )");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function isMoney (sText) {
    var rFormat = /^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(\.[0-9]{2})?$/;
    return rFormat.test(sText);
} 

function alphanumeric(aFields){

    //This function checks each required field for the presence
    //  of Alphanumeric, ', ", ! , ? , &, full stops, commas and whitespace
    //  anything else alerts a warning and returns false.

    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];
	
	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		
		var iFound = oField.value.search(/^[\s\w\'\"\?!&\.]+$/);
		if (iFound!=0){
		    iFound = oField.value.search(/[^\s\w\'\"\?!&\.]/);
		    alert(aFields[i][1] + " may only include letters, numbers, underscores(_), ! , ? , & and spaces\nThe character \""+oField.value.substring(iFound,(iFound+1))+"\" is not allowed.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function used(aField){

    //This function checks if a field value has already been used
    //  dependent upon the aUsed array, which must be parsed in to the document
    //  below this .js file.

  
    sId = aField[0][0];
    if (d.getElementById(sId)){
	var oField = d.getElementById(sId);
	
	// We need to replace &quot; with \" to do our testing, as the 
	// sCurrent uses HTML character entities not javascript escaping.

	rExpression = /&quot;/gi;
	sCurrent = sCurrent.replace(rExpression,'\"');

	//  Now test to see if the reference has been used.
	
	for (i = 0; i<aUsed.length; i++){
	    
	    if (aUsed[i][1].toLowerCase() == oField.value.toLowerCase()){
		if (sCurrent.toLowerCase() != aUsed[i][1].toLowerCase()){ 
		    alert(aField[0][1] + " \"" + oField.value + "\" has already been\nused, please use a different one.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function max_length(aFields){

    //This function checks each required field for the maximum
    //  character length, alerting a warning and returning false if blank.
    
    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];
	
	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		if (oField.value.length > aFields[i][2]){
		    alert (aFields[i][1] + ' may not be longer than ' + aFields[i][2] + ' characters.');
		    oField.value = oField.value.substr(0 , aFields[i][2]);
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function validate_doln_tag(aFields) {

    // dolnml tags may consist only of alphanumber characters 
    // and underscores (no whitespace) anything else 
    // alerts a warning and returns false.
    
    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];
	
	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		var iFound = oField.value.search(/\W/);
		if (iFound>=0){
		    alert(aFields[i][1] + " may only include letters, numbers or underscores(_) and may not include spaces.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}


function signed_integer(aFields) {

    // Checks a field only contains positive or negative integer.
    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];

	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		sReg = /^\s*(\+|\-|)\s*\d+$/;
		var isPass = sReg.test(oField.value);
		if (!isPass){
		    alert(aFields[i][1] + " may only be a positive or negative integer");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}


function numeric(aFields) {

    // Checks a field only contains numbers.
    
    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];

	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);

		var iFound = oField.value.search(/\D/);
		if (iFound>=0){
		    alert(aFields[i][1] + " may only include numbers.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function hexRGB(aFields) {

    // Checks a field is off the format #rrggbb where rr is from 00 to FF.
  
    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];
  
	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		sReg = /^#[0-9a-fA-F]{6}?$/;
		valid = sReg.test(oField.value);
	  
		if (!valid){
		    alert(aFields[i][1] + " must be a valid hexadecimal RGB value (for example #00FF00).");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function positiveFloatNumeric(aFields) {

    // Checks a field is positive, and a valid float value.
    
    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];

	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		sReg = /^\d*(\.|)\d*$/;
		valid = sReg.test(oField.value);
		if (!valid){
		    alert(aFields[i][1] + " must be a valid positive decimal number.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function floatNumeric(aFields) {

    // Checks a field is a valid float value, positive or negative.
 
    for (a=0; a< arguments.length; a++){
	aFields = arguments[a];
   
	for (i = 0; i<aFields.length; i++){
	    sId = aFields[i][0];
	    if (d.getElementById(sId)){
		var oField = d.getElementById(sId);
		sReg = /^\d*(\.|)\d*$/;
		valid = sReg.test(oField.value);
		if (!valid){
		    alert(aFields[i][1] + " must be a valid decimal number.");
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function bounded(aFields, min, max) {

    // Checks a field is within certain numeric values.
    
    for (i = 0; i<aFields.length; i++){
	sId = aFields[i][0];
	if (d.getElementById(sId)){
	    var oField = d.getElementById(sId);

	    var fField = oField.value;
	    if (fField<min){
		alert(aFields[i][1] + " must be greater than or equal to "+min);
		setFocus();
		isValid = false;
		return false;
	    }
 
	    if (fField>max){
		alert(aFields[i][1] + " must be less than or equal to "+max);
		setFocus();
		isValid = false;
		return false;
	    }

	}
    }
}


function unique(sIdentity, sMessage, aUsed, sCurrent){

    //This function checks if a field value has already been used
    //  dependent upon the aUsed array, which must be parsed in to the document
    //  below this .js file.

    if (d.getElementById(sIdentity)){
	var oField = d.getElementById(sIdentity);
	
	// We need to replace &quot; with \" to do our testing, as the 
	// sCurrent uses HTML character entities not javascript escaping.

	rExpression = /&quot;/gi;
	sCurrent = sCurrent.replace(rExpression,'\"');

	//  Now test to see if the reference has been used.
	
	for (i = 0; i<aUsed.length; i++){
	    
	    if (aUsed[i][1].toLowerCase() == oField.value.toLowerCase()){
		if (sCurrent.toLowerCase() != aUsed[i][1].toLowerCase()){ 
		    alert(sMessage + " \"" + oField.value + "\" has already been used,\n please use a different one.");
		    sId = sIdentity;
		    setFocus();
		    isValid = false;
		    return false;
		}
	    }
	}
    }
    return true;
}

function checkUnique(sIdentity, sMessage, aUsed, sCurrent){
    
    //This function checks if a field value has already been used
    //  dependent upon the aUsed JSON object, which must be parsed in to the document
    //  below this .js file.
    
    if (d.getElementById(sIdentity)){
        var oField = d.getElementById(sIdentity);
	
        // We need to replace &quot; with \" to do our testing, as the
        // sCurrent uses HTML character entities not javascript escaping.
	
        rExpression = /&quot;/gi;
        sCurrent = sCurrent.replace(rExpression,'\"');
	
        //  Now test to see if the reference has been used.
	
        for (i = 0; i<aUsed.length; i++){
	    
            if (aUsed[i].textNode.toLowerCase() == oField.value.toLowerCase()){
                if (sCurrent.toLowerCase() != aUsed[i].textNode.toLowerCase()){
                    alert(sMessage + " \"" + oField.value + "\" has already been used,\n please use a different one.");
		    sId = sIdentity;
                    setFocus();
                    isValid = false;
                    return false;
                }
            }
        }
    }
    return true;
}




function dateCheck(d, m, y){ 

    // Checks whether a day,month,year combination is a valid date

    var aMonths = new Array();
    aMonths[4] = "April";
    aMonths[6] = "June";
    aMonths[9] = "September";
    aMonths[11] = "November";
    
    if(m == "4" || m == "6" || m == "9" || m == "11"){ 
	if(d == "31"){ 
	    alert("There are only 30 days in " + aMonths[m]);
	    isValid = false;
	    return false; 
	} 
    } 
    
    if(m == "2"){ // February 
	// If the year is divisible by 4 then it is a leap year 
	//   we will ignore the fact that 2400 is not ;)
	if(parseInt(y)%4 != 0 && d == "29"){ 
	    alert("There are only 28 days in February unless it is a leap year.") 
		isValid = false;
	    return false; 
	} 
	else if(d == 30 || d == 31){ // There are never more than 29 days in February 
	    alert("There are no more than 29 days in February");
	    isValid = false;
	    return false; 
	} 
    } 
    return true; 
} 

function check_sane_date(sStem){

    iDays   = parseInt(d.getElementById(sStem + '-days').value);
    iMonths = parseInt(d.getElementById(sStem + '-months').value);
    iYears  = parseInt(d.getElementById(sStem + '-years').value);

    if (!dateCheck( iDays,iMonths,iYears)){
	sId = sStem + '-days';
	setFocus();
	isValid = false;
	return false;
    } 
    return true;
}

function check_sane_dates(sFirst, sSecond, sMessage) {

    // Takes STEM id of two dates (ie STEM-days, STEM-months & STEM-years)
    // Checks that the first is before the second or displays the message and
    // returns false 

    if(!sFirst || !sSecond) {
	return true;
    }
    iFirst_days   = parseInt(d.getElementById(sFirst + '-days').value);
    iFirst_months = parseInt(d.getElementById(sFirst + '-months').value);
    iFirst_years  = parseInt(d.getElementById(sFirst + '-years').value);

    iSecond_days   = parseInt(d.getElementById(sSecond + '-days').value);
    iSecond_months = parseInt(d.getElementById(sSecond + '-months').value);
    iSecond_years  = parseInt(d.getElementById(sSecond + '-years').value);

    if (!dateCheck( iFirst_days,iFirst_months,iFirst_years)){
	sId = sFirst + '-days';
	setFocus();
	isValid = false;
	return false;
    }

    if (!dateCheck( iSecond_days,iSecond_months,iSecond_years)){
	sId = sSecond + '-days';
	setFocus();
	isValid = false;
	return false;
    }

    if (iFirst_years < iSecond_years) {
	return true;
    }

    if (iFirst_years > iSecond_years) {
	sId = sFirst + '-days';
	alert(sMessage);
	setFocus();
	isValid = false;
	return false;
    }

    if (iFirst_months < iSecond_months) {
	return true;
    }

    if (iFirst_months > iSecond_months) {
	sId = sFirst + '-days';
	alert(sMessage);
	setFocus();
	isValid = false;
	return false;
    }

    if (iFirst_days < iSecond_days) {
	return true;
    } else {
	sId = sFirst + '-days';	
	alert(sMessage);
	setFocus();
	isValid = false;
	return false;
    }
}




