//\//////////////////////////////////////////////////////////////////////////////////
//\  forms 2.0  --  This notice must remain untouched at all times.
//\//////////////////////////////////////////////////////////////////////////////////
//
// Integration
// -----------
//
// just ensure that the page.js is included before this
//
//

function Forms() {

	this._forms = new Array();

	this.add = function (form) {
		this._forms[form.getName()] = form;
	}

	this.get = function(name) {
		return this._forms[name];
	}
	
	return this;
}

var formInputs = new Forms();




/***********************************************************
 *
 * page level functions to provide helper methods to access the formInputs
 *
 ***********************************************************/


var calendar;




/***********************************************************
 *
 * 		WebWorkForm
 *
 * the class for all webwork forms
 *
 ***********************************************************/

function FormInput( name ) /** implements EventListener */
{
	this.onEvent = function(event) {
		if (event.type == "init") {
			this.init();
		}
	}

	/**
	 * the name of the form (incase a field exists that has the name 'name'
	 */
	this.name = name;
	this.getName = function() { return this.name; }

	/**
 	 * the html form that this WebWorkForm 'wrapps'
	 */
	this.form = document.forms[name];
	this.getForm = function() { return this.form; }
	
	/**
	 * a map of name to fieldInput instance
	 */
	this._fieldInputs = new Array();
	this.getField = function(name) {
		return this._fieldInputs[name];
	}
	/**
	 * add a fieldInputs to the fieldInpouts map, indexed on the fieldName
	 */
	this.addField = function(fieldInput)
	{
		this._fieldInputs[fieldInput.getName()] = fieldInput;
	}
	/**
	 * a map of name to array of validators
	 */
	this.fieldValidators = new Array();
	
	/**
	 * indicate if this fieldInput is enabled or disabled.
	 * if it is disabled, it can't be submitted
	 */
	this.disabled = false;
	
	/**
	 * a custom validator function can be specified that gets executed
	 * after any inbuilt validator occurs
	 */
	this.customPostValidator = null;
	this.customPreValidator = null;

	/**
	 * the validators can, and probably will be, added before the fields are added
	 * therefore they can't be bound now
	 *
	 * store them in a map (keyed on name) of lists (of validator)
	 */
	this.addValidator = function(fieldName, validator)
	{
		if (typeof this.fieldValidators[fieldName] == "undefined")
		{
			this.fieldValidators[fieldName] = new Array();
		}
		var length = this.fieldValidators[fieldName].length;
		this.fieldValidators[fieldName][length] = validator;
	}
	
	// allow the form to provide init handler support
	// the form also calls init() on each nested field
	this._inits = new EventSupport();
	this.addInitListener = function(eventListener) {
		this._inits.addEventListener("init", eventListener);
	}
	this.init = function() {
		this._inits.sendEvent(new InitEvent(this));
	}

	this.validate = function()
	{
	
		if (this.customPreValidator != null)
		{
			if (!this.customPreValidator())
				return false;
		}
	
	    for (var fieldName in this.fieldValidators)
	    {
	    	var validatorList = this.fieldValidators[fieldName];
	    	if (!validatorList)
	    	{
	    		return true;
	    	}
	    	
	    	for (var i = 0; i < validatorList.length; i++)
	    	{
				var field = this.getField(fieldName);
				if (field == null) {
					alert("field " + fieldName + " doesn't exist");
					return false;
				}
				
	    		var fieldValidator = validatorList[i];
	
				var result = fieldValidator.isValid(field);
	    		if (""+result != "true")
		   		{
			   		if (""+result == "false") {
		    			alert(fieldValidator.buildMessage());
		    		} else {
			    		alert(result);
			    	}
	    			field.focus();
		    		return false;
	    		}
			}
		}
	
		if (this.customPostValidator != null)
		{
			return this.customPostValidator();
		}
		else
		{
		    return true;
		}
	}
	
	/**
	 * allow a field to be disabled and enabled. 
	 * When disabled it should prevent the from from being submitted at all
	 * Where possible it should indicate the disabled/endabled state visually 
	 */
	this.disable = function()
	{
		this.disabled = true;
		for (var fieldName in this._fieldInputs)
		{
			this.getField(fieldName).disable();
		}
	}

	this.enable = function()
	{
		for (var fieldName in this._fieldInputs)
		{
			this.getField(fieldName).enable();
		}
		this.disabled = false;
	}
	
	this.setDisabled = function(isDisabled) {
		if (isDisabled) {
			this.disable();
		} else {
			this.enable();
		}
	}
	
	this.setDisabledForFields = function(isDisabled, aryFieldNames) {

		for (var index in aryFieldNames)
		{
			var field = this.getField(aryFieldNames[index]);
			if (field) {
				field.setDisabled(isDisabled);
			}
		}
	}
	
	/**
	 * the form.submit event should deligate to this function
	 */
	this.onsubmit = function()
	{
		if (this.disabled) return false;
		
		return this.validate();
	}
	
	
	/**
	 * the form.submit event should deligate to this function
	 */
	this.dosubmit = function()
	{
		if (this.disabled) return false;
		
		if (this.validate() )
		{
			this.getForm().submit();
		}
		return true;
	}
	
	// add the form to the global forms object
	formInputs.add(this);
	
	return this;
}

/***********************************************************
 *
 * 		AbstractFieldInput
 *
 * the base class for all webwork fields
 *
 ***********************************************************/
function AbstractFieldInput(formInput, fieldName)
{
	// the name of this field
	this.name = fieldName;
	this.getName = function() { return this.name; }

	// an associative array of the html inputs that this field uses
	this.inputs = new Array();

	/**
	 * indicate if this fieldInput is enabled or disabled.
	 */
	this.disabled = false;

	this.formInput = formInput;
	this.formInput.addField(this);

	//register this field as an init listener of the formInput
	this.formInput.addInitListener(this);
	
	//provide the event listener implementation
	this.onEvent = function(event) {
		// dispatch init type event tot he init method
		if (event.type == "init") {
			this.init();
		}
	}

	// allow each field to implement an init function
	this._inits = new EventSupport();
	this.addInitListener = function(eventListener) {
		this._inits.addEventListener("init", eventListener);
	}
	this.init = function() {
		// bind this field to its inputs.field
		this._inits.sendEvent(new InitEvent(this));
	}

	// allow each field to implement the focus behavior
	this.focus = function() {}
	
	/**
	 * return the map of list of field validators for this field
	 */
	this.getValidators = function()
	{
		return this.formInput.fieldValidators[this.getName()];
	}
	/**
	 * provide a standard way for field to expose their value
	 *
	 * for multi valued objects, an array, or javascript object value can be used
	 *
	 * the default implementation converts from the field.field's type :
	 *
	 * some of these are still to be implemented
	 *
	 * input type    		return type
	 * ------------------------------------------
	 * input type='text' 	string
	 * textarea      		string
	 * select single		string (of values of the option, or name of option)
	 * radio				string
	 * checkbox				boolean
	 *
	 * unsupported
	 * -----------
	 *
	 * selected				string[]
	 * (there are probably others)
	 */
	this.getValue = function()
	{
		var field = this.inputs.field;
		
		if (typeof field == "undefined")
			return "";
	
		switch (field.type)
		{
					
			case "checkbox":
							
				return field.checked;
				break;
	
			case "text":
			case "textarea":
			case "hidden":
			case "password":
							
				return field.value;
				break;
											
			case "select-one":
				if (field.selectedIndex > -1)
					return field[field.selectedIndex].value;
				else
					return "";
				break;
							
			case "radio":
	
				for (var i = 0; i < field.length; i++)
				{
					if (field[i].selected)
					{
						return field[i].value;
					}
				}
				break;
		}
	
		return "";
	}
	/**
	 * provide a standard way for fields to set their value
	 *
	 * it is a reverse of the getValue method, and should support the same types
	 *
	 */
	this.setValue = function(value)
	{
		var field = this.inputs.field;
		
		if (typeof field == "undefined")
			return;
			
		switch (field.type)
		{
					
			case "checkbox":
							
				field.checked = value;
				break;
	
			case "text":
			case "textarea":
			case "hidden":
			case "password":
							
				if (value == null)
					field.value = "";
				else
					field.value = value;
					
				break;
											
			case "select-one":
				for (var i = 0; i < field.options; i++)
				{
					if (field.options[i].value )
					{
						if (field.options[i].value == value)
						{
							field.options[i].selected = true;
							return;
						}
					}
				}
							
			case "radio":
				for (var i = 0; i < field.length; i++)
				{
					if (field[i].value == value)
					{
						field[i].selected = true;
					}
				}
				break;
		}
	
		return;
	}
	this.enable = function()
	{
		this.setDisabled(false);
	}
	this.disable = function()
	{
		this.setDisabled(true);
	}
	this.setDisabled = function(disabled)
	{
		this.disabled = disabled;
		this.inputs.field.disabled = disabled;
		for (var inputName in this.inputs)
		{
			this.inputs[inputName].disabled = disabled;
		}
	}
	this.getDisabled = function()
	{
		return this.disabled;
	}
	this.disableInput = function(input)
	{
		this.inputs[input].disabled = true;
	}
	this.enableInput = function(input)
	{
		this.inputs[input].disabled = false;
	}
	this.getField = function() {
		return this.inputs.field;
	}
	return this;
}


function AbstractComponentFieldInput(formInput, fieldName)
{
	this.inheritFrom = AbstractFieldInput;
	this.inheritFrom(formInput, fieldName);


	/**
	 * encode the components into the field's value for submission
	 */
	this.serializeComponents = function()
	{
		var encodedValue = "";
		for (var fieldName in this.components)
		{
			if (("" + this.components[fieldName]) != "")
			{
				encodedValue = encodedValue + escape(fieldName) + "=" + escape(this.components[fieldName]) + "&";
			}
		}

		// strip off the extra &
		if (encodedValue.length > 0)
		{
			encodedValue = encodedValue.substring(0, encodedValue.length -1);
		}
		this.inputs.field.value = encodedValue;
		return encodedValue;
	}
}

/*************************************************
 *	datecalendar input
 *************************************************/

function CalendarInput(formInput, fieldName)
{
	this.inheritFrom = AbstractFieldInput;
	this.inheritFrom(formInput, fieldName);
	
	// the button that gets clicked to show the calendar
	this.inputs.button = null;

	// the html text fiels that contains the readonly display of the selected date
	this.inputs.display = null;

	// the date format to use when displaying the selected date in the display field
	this.displayFormat = '';

	this.checkDisabled = null;
	
	// And this gets called when the end-user clicks on the _selected_ date,
	// or clicks on the "Close" button.  It just hides the calendar without
	// destroying it.
	this.calendarCloseHandler = function(cal) {
		cal.hide();
	}

	// This function gets called when the end-user clicks on some date.
	this.calendarSelectionHandler = function (cal, date) {
		cal.sel.value = date; // just update the date in the input field.
		cal.selDisplay.value = cal.date.print("d/m/y");
		cal.callCloseHandler();
		return false;
	}
	
	
	this.showCalendar = function()
	{
		if (calendar != null)
		{
			// we already have some calendar created
			calendar.hide();                 // so we hide it first.

			calendar.setDisabledHandler(this.checkDisabled);
	  	}
	  	else
	  	{
	    	// first-time call, create the calendar.
			var cal = new Calendar(false, null, this.calendarSelectionHandler, this.calendarCloseHandler);
			calendar = cal;                  // remember it in the global var
			cal.setRange(1900, 2070);        // min/max year allowed.
			calendar.weekNumbers = false;

			calendar.setDisabledHandler(this.checkDisabled);

			cal.create();
		}
		calendar.setDateFormat('y-mm-dd');    // set the specified date format
	
		calendar.parseDate(this.inputs.field.value);      // try to parse the text in field
		calendar.sel = this.inputs.field;                 // inform it what input field we use
		calendar.selDisplay = this.inputs.display;                 // inform it what input field we use
		calendar.showAtElement(this.inputs.display);        // show the calendar below it

		return false;
	}
	this.focus = function()
	{
		this.inputs.field.scrollIntoView();
		this.inputs.button.click();
	}
	return this;
}


/*************************************************
 *	doubleselect input
 *************************************************/

var ADD_SELECTED = 1;
var ADD_ALL = 2;
var ADD_NOT_SELECTED = 3;

function DoubleSelectInput(formInput, fieldName)
{
	this.inheritFrom = AbstractFieldInput;
	this.inheritFrom(formInput, fieldName);

	//
	// inputs are : left,right,button1-button6[button7-button9]
	//

	// can the selected list have a user defined order
	// the default is to keep the items in the same order as the sourceOption list
	this.customOrder = false;

	// the list of selectable options .. an array of Option
	this.sourceOptions = new Array();

	this.focus = function()
	{
		this.inputs.left.focus();
	}

	this.getSourceOptions = function()
	{
			return this.sourceOptions;
	}

	this.getSelectedOptions = function()
	{
			return this.inputs.right.options;
	}

	this.getSourceValues = function()
	{
		 var result = new Array();
		 for(var i = 0 ; i < this.getSourceOptions().length ; i++)
		 {
		 	 result[i] = this.getSourceOptions()[i].value;
		 }
			return result;
	}

	this.getSelectedValues = function()
	{
		 var result = new Array();
		 for(var i = 0 ; i < this.getSelectedOptions().length ; i++)
		 {
		 	 result[i] = this.getSelectedOptions()[i].value;
		 }
			return result;
	}
	
	return this;
}



function move(hidden, left, right, command, fromLeft)
{
   var doIt = false;

   with ( ((fromLeft)? left : right) )
   {
      for (var i = 0; i < options.length; i++)
      {
         doIt = false;
         if (command == ADD_SELECTED)
         { 
             if(options[i].selected) doIt = true;
         }
         else if (command == ADD_ALL)
         {
            doIt = true;
         } 
         else 
         {
            if (!options[i].selected) doIt = true;
         }
         
         if (doIt)
         {
            with (options[i])
            {
               if (fromLeft)
                  right.options[right.length] = new Option( text, value );
               else
                  left.options[left.length] = new Option( text, value );
            } 
            options[i] = null;
            i--;
         } 

//			commented out because it causes a really weird response in phoenix browser
//         if(navigator.appName == "Netscape" ) 
//         		history.go(0);

      } // end for loop
      if (options[0] != null)
         options[0].selected = true;

   } // end with fromLeft
	setDoubleSelectValue(hidden, right);
}

function setDoubleSelectValue(hiddenInput, selectedList)
{

	var stringValueList = "";
	with( selectedList )
	{
		for (var i = 0; i < options.length; i++)
		{
      		stringValueList = stringValueList + options[i].value;
      		
      		if (i+1 < options.length)
      		{
	      		stringValueList = stringValueList + ", ";
      		}
		}
	}
	hiddenInput.value = stringValueList;

}

function swapSelected(hidden, input)
{
	// reverse the first two selected items
	var indexA = null;
	for (var i = 0; i < input.options.length; i++)
	{
		if (input.options[i].selected)
		{
			if (indexA == null)
			{
				indexA = i;
			}
			else
			{
				swap(hidden, input, indexA, i);
				input.options[indexA].selected = true;
				input.options[i].selected = true;
				return;
			}
		}	
	}
}
function swap(hidden, input, indexA, indexB)
{
	if (indexA < 0 || indexB < 0 || indexA >= input.options.length || indexB >= input.options.length)
		return;
		
	var options = new Array(input.options.length);
	
	for (var i = 0; i < input.options.length; i++ )
	{
		options[i] = new Option(input.options[i].text, input.options[i].value);
	}

	var tempOption = options[indexA];
	options[indexA] = options[indexB];
	options[indexB] = tempOption;

	for (var i = 0; i < options.length; i++ )
	{
		input.options[i] = options[i];
	}
	input.selectedIndex = indexB

	setDoubleSelectValue(hidden, input);
	
}





/*************************************************
 *	timerange input
 *************************************************/

function TimeRangeInput(formInput, fieldName)
{
	this.inheritFrom = AbstractComponentFieldInput;
	this.inheritFrom(formInput, fieldName);
	
	//
	// inputs are : start,end
	//

	// the components that are serialized into the field value before submission
	this.components = new Array();

	this.focus = function()
	{
		this.inputs.start.focus();
	}
	this.onchange = function()
	{
		this.components.start = this.inputs.start.value;
		this.components.end = this.inputs.end.value;
	
		this.serializeComponents();
	
		return true;
	
	}
	return this;
}

/*************************************************
 *	time input
 *************************************************/

function TimeInput(formInput, fieldName)
{
	this.inheritFrom = AbstractComponentFieldInput;
	this.inheritFrom(formInput, fieldName);
	
	//
	// inputs are : time
	//

	// the components that are serialized into the field value before submission
	this.components = new Array();

	this.focus = function()
	{
		this.inputs.time.focus();
	}
	this.onchange = function()
	{
		this.components.time = this.inputs.time.value;
	
		this.serializeComponents();
	
		return true;
	
	}
	return this;

}

/*************************************************
 *	monthYear input
 *************************************************/

function MonthYearInput(formInput, fieldName)
{
	this.inheritFrom = AbstractComponentFieldInput;
	this.inheritFrom(formInput, fieldName);
	
	//
	// inputs are : month, year
	//

	// the components that are serialized into the field value before submission
	this.components = new Array();

	this.focus = function()
	{
		this.inputs.month.focus();
	}
	this.onchange = function()
	{
		this.components.month = this.inputs.month.value;
		this.components.year = this.inputs.year.value;
	
		this.serializeComponents();
	
		return true;
	
	}
	this.isValid = function()
	{
		return this.inputs.month.value != null
		&&     this.inputs.month.value != ""
		&&	   this.inputs.year.value != null
		&&     this.inputs.year.value != "";
	
	}
	return this;

}

/*************************************************
 *	money input
 *************************************************/
function MoneyInput(formInput, fieldName)
{
	this.inheritFrom = AbstractComponentFieldInput;
	this.inheritFrom(formInput, fieldName);
	
	//
	// inputs are : input
	//

	// the components that are serialized into the field value before submission
	this.components = new Array();
	this.components.cents = "";
	
	this.focus = function()
	{
		this.inputs.input.focus();
	}

	/**
	 * parse the input field, since the value has changed, and serialize it 
	 * into the field value ready for submission
	 */
	this.onchange = function()
	{
		
		// strip non money characters
		this.inputs.input.value = stripCharsNotInBag( this.inputs.input.value, "0123456789.");

		if (this.inputs.input.value.length == 0)
		{
			this.components.cents = "";
		}
		else
		{
			var inputNumber = new Number(this.inputs.input.value);
			this.components.cents = inputNumber * Math.pow(10,this.components.fractionDigits);
		}	
		this.serializeComponents();
	
		return true;
	}
	
	this.setCentsValue = function(newValue)
	{
		newValue = stripCharsNotInBag( ("" + newValue), "0123456789");
		this.components.cents = Number(newValue);
		if(newValue.length == 0)
		{
			newValue = "000";
		}
		else if(newValue.length == 1)
		{
			newValue = "00" + newValue;
		}
		else if(newValue.length == 2)
		{
			newValue = "0" + newValue;
		}
		if(this.inputs.input)
		{
			this.inputs.input.value = newValue.substring(0, newValue.length - 2) + "." + newValue.substring(newValue.length - 2);
		}
		this.serializeComponents();
	}
	
	this.getCentsValue = function()
	{
		return this.components.cents;
	}
	
	this.setValue = function(newValue)
	{
		if(newValue == null)
			this.inputs.input.value = "";
		else
			this.inputs.input.value = newValue;
		this.onchange();
	}
	return this;

}

function stripCharsNotInBag(s, bag)
{
	var i;
	var returnString = "";

	// Search through string's characters one by one.
	// If character is not in bag, append to returnString.

	for (i = 0; i < s.length; i++)
	{ 
		// Check that current character isn't whitespace.
		var c = s.charAt(i);
		if (bag.indexOf(c) != -1) returnString += c;
	}
	return returnString;
}





/*************************************************
 *	default input - lazy wrapping of a simple html field
 *************************************************/

function DefaultFieldInput(formInput, fieldName)
{
	this.inheritFrom = AbstractFieldInput;
	this.inheritFrom(formInput, fieldName);
	this.focus = function() {
		this.inputs.field.focus();
	}
	return this;
}


/*************************************************
 *	radio
 *************************************************/

function RadioInput(formInput, fieldName)
{
	this.inheritFrom = AbstractFieldInput;
	this.inheritFrom(formInput, fieldName);

	//
	// inputs are : radio0-radioN
	//
	this.focus = function()
	{
		// focus on the first radio
		if (this.inputs.radio0)
			this.inputs.radio.focus();
	}
	return this;
}

/*************************************************
 *	checkbox
 *************************************************/

function CheckboxInput(formInput, fieldName)
{
	this.inheritFrom = AbstractFieldInput;
	this.inheritFrom(formInput, fieldName);

    this.customOnChange = null;

	this.getValue = function()
	{
		return this.inputs.display.checked;
	}

	this.setValue = function(newValue)
	{
		var checked = (newValue == null)?false:newValue;
		this.inputs.display.checked = checked;
		this.inputs.field.value = checked;
		this.onchange();
	}
	
	this.onchange = function() 
	{
		if (typeof this.customOnChange == "function")
		{
			this.customOnChange();
		}
		return true;
	}
	
	this.clicked = function()
	{
		this.inputs.field.value = this.inputs.display.checked;
		this.onchange();
	}
		
	return this;

}

/*************************************************
 *	select
 *************************************************/

function SelectInput(formInput, fieldName)
{
	this.inheritFrom = DefaultFieldInput;
	this.inheritFrom(formInput, fieldName);
	
	this.getSelectedName = function()
	{
		if(this.inputs.field.selectedIndex == -1)
		{
			return null;
		}
		else
		{
			return this.inputs.field[this.inputs.field.selectedIndex].text;
		}
	}
	return this;

}

/*************************************************
 *	file
 *************************************************/

function FileInput(formInput, fieldName)
{
	this.inheritFrom = DefaultFieldInput;
	this.inheritFrom(formInput, fieldName);
	
	this.setValue = function(newValue)
	{
	}
	return this;

}