/**
	The forms library allows for an efficient handling in HTML forms.
	
	mandatory
	By adding a [mandatory='true'] attribute to any input field, this field
	becomes mandatory. On load, the CSS class <FORMS_CSS_STYLE_MANDATORY_FIELD>
	is added to the input field's class list. Additionally, a star (*) is added
	to the label associated with this field. (To change this, see forms_renderSingleForm)
	Note that for the latter to work, the label for the input field must be
	tagged as such (LABEL) and contain a correct [for] attribute.
	
	On form submit, the field is checked for a value. If empty, the CSS class 
	<FORMS_CSS_STYLE_MANDATORY_ERROR> is added to it's class names to indicate 
	an error.
	
	format
	Formats are added by defining a 'format' attribute. Predefined formats are:
	money
	date
	percent
	expdate
	
	Also, by adding a [regexp='<a valid regular expression>'] attribute to any input field, 
	the value of this field will be validated on submit. If validation fails, the CSS
	class <FORMS_CSS_STYLE_FORMAT_> is added to the input field's class list.
	
	error messages
	The error messages used are defined in
	* MSG_ERROR_MANDATORY_MISSING
	* MSG_ERROR_WRONG_FORMAT
	Overwrite these by loading a subsequent JS script, e.g. clientmessages.js.jsp.
	
	@copyright Copyright &copy; 2004, 2005, 2006, Umbrella Software AG 
	@author sniederb
	@version $Id: forms.js 4758 2010-06-26 10:49:04 +0200 (Sat, 26 Jun 2010) simon $	
	@since 26.5.2006
*/

var FORMS_DONT_HANDLE = "forms_leave_me_alone";
var FORMS_FOCUS_ME = "forms_focushere";
var FORMS_ONLY_ENABLED_IF_VALID_DATA = "forms_only_enabled_if_valid_data";

var FORMS_CSS_STYLE_MANDATORY_FIELD = "forms_mandatory_field";
var FORMS_CSS_STYLE_MANDATORY_ERROR = "forms_mandatory_error";
var FORMS_CSS_STYLE_FORMAT_ERROR = "forms_format_error";

var FORMS_TABID_COOKIEKEY = "forms_tabid";

try {
	MSG_ALREADY_DEFINED = MSG_ALREADY_DEFINED;
}
catch (e) {
	var MSG_ERROR_MANDATORY_MISSING = "Missing data";
	var MSG_ERROR_WRONG_FORMAT = "Wrong data";
}

var forms_fp_error_output;
var forms_fp_error_clear;
var forms_is_initialized = false;
var forms_databound_elements_cache = new Array();

// onLoad event handler for 'forms_init' is defined in scriptsAndStyles.jsp
// in order to guarantee correct load order

/* ------------------------------------------------------------------------
	Function:  forms_init

	Synopsis:  This method runs on loading the page and initializes
			   all forms on the page.

	Arguments: none

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_init() {
		if (forms_is_initialized) {
			return;
		}
		forms_is_initialized = true;
	
		if (!forms_fp_error_output) {		
			forms_setErrorOutputFunction(forms_defaultErrorOutputFunction);
		}
		
		if (!forms_fp_error_clear) {		
			forms_setErrorClearFunction(forms_defaultErrorClearFunction);
		}
		
	
		var docForms = document.getElementsByTagName("FORM");
		
		for (var i = 0; i < docForms.length; i++) {
		    var currentForm = docForms[i];
			currentForm.autoComplete = "off";
		    
		    if (!currentForm[FORMS_DONT_HANDLE]) {
				forms_setToInitialState(currentForm);
				forms_renderSingleForm(currentForm);	

				// using attributes('id').nodeValue seems awkward. but if there is an INPUT field
				// within the form with the name 'id', all of the following methods return
				// a reference to that input field:
				// currentForm.id
				// currentForm["id"]
				// currentForm.getAttribute('id')
				
				// the attachEvent method will be called AFTER the event handler. If the form as an onsubmit
				// event handler, that method is therefore called first!
				var onsbmtfn = currentForm.onsubmit;
				currentForm.onsubmit = forms_createOnSubmitFunction(currentForm.attributes('id').nodeValue, true);
				if (onsbmtfn) {
					if (typeof(onsbmtfn) == 'string') {
						currentForm.onvalidSubmit = function() {
								eval(onsbmtfn);
							};
					}
					else {
						currentForm.onvalidSubmit = onsbmtfn;
					}
				}
				
				var controls = currentForm.getElementsByTagName("INPUT");
				for (var c = 0; c < controls.length; c++) {
					if (controls[c]["format"] != "") {
						controls[c].attachEvent("ondeactivate", forms_reformat);
					}
				}
			}
		}
	}
	
	function forms_focusFirstControl() {
		// Try to focus the first input element
		try {
			var firstControl = document.getElementById(FORMS_FOCUS_ME);
			
			// we have an input field that is designated as FOCUS_ME
			if (firstControl) {
				firstControl.focus();
				return;
			}
			
			var firstForm = null;
			// The first form that hasn't set the attribute 'FORMS_DONT_HANDLE'.
			// In most cases this is the search form which doesn't need client side checks.
			var firstNonSearchForm = null;
			
			var params = document.location.search;
			var docForms = document.getElementsByTagName("FORM");
			
			for (var i = 0; i < docForms.length; i++) {
				if (docForms[i][FORMS_DONT_HANDLE] != 'true') {
					
					if (firstForm == null) {
						firstForm = docForms[i];
					}
					
					// use window. prefix here as SEARCHTABLE_SEARCHFORM_ID might be undefined
					if (docForms[i].id != window.SEARCHTABLE_SEARCHFORM_ID /* from searchtable.js */) {
						if (firstNonSearchForm == null) {
							firstNonSearchForm = docForms[i];
						}
					}
				}
			}
			
			// If an "id" parameter was set in the request, the form displays
			// the data of a specific object, therefore the first input element 
			// in the 'data' form will be focused. Otherwise the search field 
			// will be focused.
			if (document.location.search.match("id=[0-9]*") &&
				(firstNonSearchForm != null)) {
				forms_focusFirstFormElement(firstNonSearchForm);
			}
			else if (firstForm != null) {
				// focus on first form on the page
				forms_focusFirstFormElement(firstForm);		
			}
			// do not set focus as FORMS_DONT_HANDLE is set
		} catch (e) {
			// something went wrong, don't bother the user with it
		}
	}
	
	function forms_createOnSubmitFunction(id, talk) {
		var temp1 = id;
		var temp2 = talk;
		
		return function(){
			return forms_finalCheck(temp1, temp2);
		}
	}
	
/* ------------------------------------------------------------------------
	Function:  forms_setValuesToElementId

	Synopsis:  Populates the text input fields of a form with their ID
			   instead of the provided value.

	Arguments: A form object.

	Returns:   void

	Notes:     Use this function to gather technical information about a page.
	------------------------------------------------------------------------*/
	function forms_setValuesToElementId(frm) {
		for (var i = 0; i < frm.elements.length; i++) {
			var element = frm.elements[i];
			if (element.type == 'text') {
				element.value = element.id;
			}
		}
		frm.attachEvent("onsubmit", function() { return false; });
	
	}	
	
/* ------------------------------------------------------------------------
	Function:  forms_setErrorOutputFunction / forms_defaultErrorOutputFunction

	Synopsis:  Set a default output function for error messages. Allow to
			   redefine the output function with a different function pointer.

	Arguments: Function pointer to a function which takes an error message
			   as argument.

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/	
	function forms_setErrorOutputFunction(fp) {
		forms_fp_error_output = fp;
	}
	
	function forms_defaultErrorOutputFunction(msg) {
		window.status = msg;
	}
	
	function forms_setErrorClearFunction(fp) {
		forms_fp_error_clear = fp;
	}
	
	function forms_defaultErrorClearFunction() {
		window.status = "";
	}	

/* ------------------------------------------------------------------------
	Function:  forms_focusFirstFormElement

	Synopsis:  This method will set the focus to the first form element
			   in the given form.

	Arguments: The form in which the first element should be selected.

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/		
	function forms_focusFirstFormElement(onForm) {
		var firstVisibleElement = false;
		for (var i = 0; i < onForm.elements.length; i++) {
			if (onForm.elements[i].type != 'hidden') {
				firstVisibleElement = onForm.elements[i];
				break;
			}
		}
		
		if (firstVisibleElement) {
			firstVisibleElement.focus();
			
			if (firstVisibleElement.id && firstVisibleElement.id.indexOf('search.needle') >= 0) {
				var txtrange = firstVisibleElement.createTextRange();
				txtrange.select();
			}
		}
	}

/* ------------------------------------------------------------------------
	Function:  forms_renderSingleForm

	Synopsis:  This method will search all INPUT tags for the current form and
			   read the "mandatory" flag. If present and set to true, it will
			   mark the INPUT field as mandatory.

	Arguments: none

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_renderSingleForm(theFormToRender) {
		
		if (theFormToRender == null) {
			forms_alert("forms_renderSingleForm: Form object is null");
			return;
		}
		
		var inputFields = theFormToRender.getElementsByTagName("INPUT");
		var selectFields = theFormToRender.getElementsByTagName("SELECT");
		var textareaFields = theFormToRender.getElementsByTagName("TEXTAREA");

		for (var i = 0; i < inputFields.length; i++) {
			forms_renderMandatoryField(theFormToRender, inputFields[i]);
			forms_registerDataboundElement(theFormToRender, inputFields[i]);
		}
		for (var i = 0; i < selectFields.length; i++) {
			forms_renderMandatoryField(theFormToRender, selectFields[i]);
			forms_registerDataboundElement(theFormToRender, selectFields[i]);
		}
		for (var i = 0; i < textareaFields.length; i++) {
			forms_renderMandatoryField(theFormToRender, textareaFields[i]);
			forms_registerDataboundElement(theFormToRender, textareaFields[i]);
		}
	}
	
/* ------------------------------------------------------------------------
	Function:  forms_renderMandatoryField

	Synopsis:  Renders a single field as mandatory if the attribute 
			   'mandatory' is set to 'true'.

	Arguments: The form and the field in it to be rendered.

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/	
	function forms_renderMandatoryField(form, field) {
		if (field["mandatory"] && (field["mandatory"] != "false")) {
			field.className += " " + FORMS_CSS_STYLE_MANDATORY_FIELD + " ";
			
			var lbl = forms_getLabelFor(form, field.id);
			if (lbl != null) {
				if (lbl.innerText.indexOf("*") == -1) {
					lbl.insertAdjacentHTML("beforeEnd", "&nbsp;*");
				}
			}
		}
	}
	
	function forms_registerDataboundElement(form, field) {
		if ((field["forms_databound"] == "true") && !field['dirty_marking_handled']) {
			
			forms_databound_elements_cache.push( field );
			field['dirty_marking_handled'] = true;
			
			// some controls have immediate 'mark dirty' actions ...
			if ((field.type.toLowerCase() == 'checkbox') || (field.type.toLowerCase() == 'radio')) {
				field.attachEvent("onclick", forms_markDirtyTrue);
			}
			else if (field.tagName.toLowerCase() == 'select') {
				field.attachEvent("onchange", forms_markDirtyTrue);
			}
			else {
				field.attachEvent("onkeyup", forms_onKeyUp);
			}
		}
		
	}
	
/* ------------------------------------------------------------------------
	Function:  forms_getLabelFor

	Synopsis:  This method will search all INPUT tags for the current form and
			   read the "mandatory" flag. If present and set to true, it will
			   mark the INPUT field as mandatory.

	Arguments: theform - The FORM to search for the label
			   imputid - The id attribute of the INPUT field

	Returns:   A label object or NULL if none found

	Notes:     The "for" attribute is represented by "htmlFor" in JS.
	------------------------------------------------------------------------*/	
	function forms_getLabelFor(theform, inputid) {
		var labels = theform.getElementsByTagName("LABEL");
		
		for (var i = 0; i < labels.length; i++) {
			if (labels[i]["htmlFor"] == inputid) {
				return labels[i];
			}
		}		
		
		return null;
	}
	
/* ------------------------------------------------------------------------
	Function:  forms_setToInitialState

	Synopsis:  Check button accessibility and assign event handler to update
			   buttons on change.

	Arguments: none

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_setToInitialState(theFormToInitialize) {

		if (theFormToInitialize == null) {
			forms_alert("forms_setToInitialState: Form object is null");
			return;
		}
	}

/* ------------------------------------------------------------------------
	Function:  forms_checkButtonAccessibility

	Synopsis:  This method updates buttons disabled property depending
			   on mandatory fields being populated.

	Arguments: none

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/	
	function forms_checkButtonAccessibility(theIdOfTheFormToCheck) {
		
		var theFormToCheck = document.getElementById(theIdOfTheFormToCheck);
		
		if (theFormToCheck == null) {
			forms_alert("forms_checkButtonAccessibility: Form object is null");
			return;
		}		
		
		var formIsValid = forms_finalCheck(theIdOfTheFormToCheck, false);
		
		var formButtons = theFormToCheck.getElementsByTagName("BUTTON");
		for (var i = 0; i < formButtons.length; i++) {
			if (formButtons[i]["formEnable"] == FORMS_ONLY_ENABLED_IF_VALID_DATA) {
				formButtons[i].disabled = !formIsValid;
			}
		}
	}	
	
/* ------------------------------------------------------------------------
	Function:  forms_reformat

	Synopsis:  This method reformats the controls content based on a format
			   attribute.

	Arguments: none

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/		
	function forms_reformat() {
		var elem = window.event.srcElement;
		try {
			if (elem["format"] == "date") {
				elem.value = autocomplete_Date(elem.value);
			}
			else if (elem["format"] == "expdate") {
				elem.value = autocomplete_ExpDate(elem.value);
			}
			else if (elem["format"] == "dayandmonth") {
				elem.value = autocomplete_DayAndMonth(elem.value);
			}
			else if (elem["format"] == "money") {
				elem.value = autocomplete_Money(elem.value);
			}
			else if (elem["format"] == "double") {
				elem.value = autocomplete_TextToFloat(elem.value);
			}
			else if (elem["format"] == "percent") {
				elem.value = autocomplete_Percent(elem.value);
			}
			else if (elem["format"] == "datetime") {
				elem.value = autocomplete_Datetime(elem.value);
			}
		}
		catch(e) {
			forms_alert("Error on reformatting input: " + e.description);
		}
	}

/* ------------------------------------------------------------------------
	Function:  forms_finalCheck

	Synopsis:  This method is fired on submitting a form. All input
			   fields are validated and  message(s) are displayed.

	Arguments: none

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_finalCheck(theIdOfTheFormToCheck, talkative) {
		var erroneousField = null;

		var mandatoryMissing = false;
		var wrongFormat = false;
		
		if (!theIdOfTheFormToCheck) {
			forms_alert("Handled form has no ID, either set ID attribute or set '" + FORMS_DONT_HANDLE + "'");
			return true;
		}
		
		var theFormToCheck = document.getElementById(theIdOfTheFormToCheck);
		if (!theFormToCheck) {
			forms_alert("No form found with ID " + theIdOfTheFormToCheck);
			return true;
		}
		
		var inputFields = theFormToCheck.getElementsByTagName("INPUT");
		for (var i = 0; i < inputFields.length; i++) {
			if (forms_checkField_mandatoryMissing(inputFields[i])) {
				mandatoryMissing = true;
				erroneousField = (erroneousField == null) ? inputFields[i] : erroneousField;
			}
			if (forms_checkField_wrongFormat(inputFields[i])) {
				wrongFormat = true;
				erroneousField = (erroneousField == null) ? inputFields[i] : erroneousField;
			}
		}
		
		var txtareaFields = theFormToCheck.getElementsByTagName("TEXTAREA");
		for (var i = 0; i < txtareaFields.length; i++) {
			if (forms_checkField_mandatoryMissing(txtareaFields[i])) {
				mandatoryMissing = true;
				erroneousField = (erroneousField == null) ? txtareaFields[i] : erroneousField;
			}
		}

		var selectFields = theFormToCheck.getElementsByTagName("SELECT");
		for (var i = 0; i < selectFields.length; i++) {
			if (forms_checkField_mandatoryMissing(selectFields[i])) {
				mandatoryMissing = true;
				erroneousField = (erroneousField == null) ? selectFields[i] : erroneousField;
			}
		}

		var errorMessages = new Array();
		if (mandatoryMissing) {
			errorMessages.push(MSG_ERROR_MANDATORY_MISSING);
		}
		
		if (wrongFormat) {
			errorMessages.push(MSG_ERROR_WRONG_FORMAT);
		}

		if (errorMessages.length != 0) {
			try {
				erroneousField.focus();
			}
			catch (exp) {
				errorMessages.push("Erroneous field is " + erroneousField.id + ", but it is not currently visible");
			}
			if (talkative) {
				forms_alert(errorMessages.join("<br />"));
			}
			return false;
		}
		
		if (theFormToCheck.onvalidSubmit) {
			return theFormToCheck.onvalidSubmit();
		}
		return true;
	}

/* ------------------------------------------------------------------------
	Function:  forms_checkField_mandatoryMissing

	Synopsis:  This method checks the value in a single field. If the 
			   attribute 'mandatory' is set, its value can't be empty.

	Arguments: The field to be checked.

	Returns:   True if the check failed, false otherwise.

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_checkField_mandatoryMissing(field) {
		if ((field["mandatory"]) &&	(""+field["mandatory"] == "true")) {
			if (!field.value || dojo.trim(field.value).length == 0) {
				field.className += " " + FORMS_CSS_STYLE_MANDATORY_ERROR + " ";
				return true;
			}
			else {
				field.className = field.className.replace(FORMS_CSS_STYLE_MANDATORY_ERROR, "");
			}
		}
		return false;
	}

/* ------------------------------------------------------------------------
	Function:  forms_checkField_wrongFormat

	Synopsis:  This method checks if the value entered has the correct 
			   format. The format is checked upon the regular expression 
			   provided by the attribute 'regexp'.

	Arguments: The field to be checked.

	Returns:   True if the check failed, false otherwise.

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_checkField_wrongFormat(field) {
		if ((field["regexp"]) && (field.value != "")) {
			var reg = new RegExp(field["regexp"]);
			if (!reg.test(field.value)) {
				field.className += " " + FORMS_CSS_STYLE_FORMAT_ERROR + " ";
				return true;
			}
			else {
				field.className = field.className.replace(FORMS_CSS_STYLE_FORMAT_ERROR, "");
			}
		}
		return false;
	}

/* ------------------------------------------------------------------------
	Function:  forms_reset

	Synopsis:  Reset a form by reloading the URL with GET. If the URL
			   references a NEW entity, load the provided default URL.

	Arguments: newEntityString - A string pattern which indicates that
				the current URL references a NEW entity
			   defaultURL      -  The URL to load if cancelling on a 
			   	new entity

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_reset(newEntityString, defaultURL) {
		var targetURL = window.location.href;
		if ((defaultURL) &&
			((!newEntityString) || (targetURL.indexOf(newEntityString) >= 0))) {
			targetURL = defaultURL;
		}
		window.location.href = targetURL;
	}

/* ------------------------------------------------------------------------
	Function:  forms_alert

	Synopsis:  This method will show an alert of the message provided if
			   FORMS_IS_DEBUG is set to true. The message will always be
			   written to the status bar.

	Arguments: The message to be displayed

	Returns:   void

	Notes:     none
	------------------------------------------------------------------------*/
	function forms_alert(msg) {
		if (forms_fp_error_output) {
			forms_fp_error_output(msg);
		}
		else {
			window.alert(msg);
		}
	}
	
	function forms_saveSelectedTab(prefix, tabid) {
		var cookieKey = FORMS_TABID_COOKIEKEY + '_' + prefix;
		var date = new Date();
		date.setTime(date.getTime()+(5*60*1000)); // only keep cookie for 5min
		var expires = "; expires="+date.toGMTString();
		document.cookie = cookieKey + '=' + tabid + expires + '; path=/';
	}
	
	function forms_getSavedTabId(prefix) {
		var cookieKey = FORMS_TABID_COOKIEKEY + '_' + prefix;
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++) {
			var c = ca[i];
			c = c.replace(/^\s*/, "");
			if (c.indexOf(cookieKey) == 0) {
				return c.substring(cookieKey.length + 1, c.length);
			}
		}
		
		return false;
	}
	
	/* ------------------------------------------------------------------------
	Function:  forms_markDirty
	
	Synopsis:  This method is called when the user changes data. The 
			   implementation is free to change the display in any way
			   to indicate a dirty document.

	Arguments: isDirty - If true, document is marked as dirty, otherwise 
			   reverted to clean

	Returns:   none

	Notes:     none
	------------------------------------------------------------------------*/	
	function forms_markDirtyTrue() {
		forms_markDirty(true);
	}
	
	function forms_markDirty(isDirty) {
		for (var i = 0; i < forms_databound_elements_cache.length; i++) {
			var currElem = forms_databound_elements_cache[i];
			if (!currElem["forms_databound"] || currElem['dirty']) {
				continue;
			}
			if (isDirty) {
				currElem.className += " dirty ";
				currElem['dirty'] = true;
				// reduce the load by detaching keyup event handler
				currElem.detachEvent("onkeyup", forms_onKeyUp);
			}
			else {
				currElem.className = currElem.className.replace("dirty", "");
				currElem['dirty'] = false;
			}
		}
	}
	
	/*
	 * keyCode returns the "virtual-key code" from the WM_KEY* messages in the Windows API when 
	 * checked in the keydown and keyup events, and the ASCII/Unicode character codes in the keypress 
	 * event.
	 * 
	 */
	function forms_onKeyUp() {
		// Handle keys which create a dirty field
		/* if key is one which changes data, markDirty
			NOTE: keypress event will catch
			- Letters: A - Z (uppercase and lowercase) 
			- Numerals: 0 - 9 
			- Symbols: ! @ # $ % ^ &amp; * ( ) _ - + = &lt; [ ] { } , . / ? \ | ' ` " ~ 
			- System: ESC, SPACEBAR, ENTER 
			
			Additionally, the following key events need to be captured:
			- BACKSPACE, DEL
			- CTRL-X
			- CTRL-V
		*/
		if (window.event.srcElement &&
			window.event.srcElement.isTextEdit &&
			(window.event.srcElement.defaultValue != window.event.srcElement.value)) { 

			forms_markDirty(true);
		}
		else if (window.event.keyCode == 27) {
			// Handle ESC key to reset data forms
			forms_markDirty(false);
			// ESC is mapped to the reset button per default
		}
		
	}
