/**
 * This module provides functionality to help with form validation, help, navigation, etc.
 */
var submitInProgress = false;
var selectMenuTimer = null;
var selectObserver = null;

var formRules = {
		'input' : function(element) {
			var f = function() {
				element.select();
			};
			var oldFunc = element.onfocus;
			if (typeof oldFunc != 'function') {
				element.onfocus = f;
			} else {
				element.onfocus = function() {
					oldFunc();
					f();
				};
			}
		},
		'button.form_submit' : function(element) {
			var form = findParent(element, 'form');
			form.onsubmit = function() {
				element.disabled = true;	// prevent double submits
				return true;
			};
		},
		'#transfer_box_move_from' : function(element) {
			innerToTitle(element);
		},
		'#transfer_box_move_to' : function(element) {
			innerToTitle(element);
		},
		/**
		 * This function handles form validation in lots of interesting ways.
		 * There are a few ground rules (convention over configuration):
		 *   1.  The submit button name must match the form name
		 *   2.  The JavaScript function name is 'validate' followed by the form name.
		 *   3.  The Ajax call is the same as the form action but with ?validate=true
		 *   4.  Various styles are used. Those will be documented in a CSS file or somewhere.
		 */
		'button.validate_form_submit' : function(element) {
			var form = findParent(element, 'form');
			form.onsubmit = function() {
				var val = validateFormSubmit(form);
				return val;
			};
		},
		'button.survey_defer' : function(element) {
			element.onclick = function() {
				go(buildViewCurrentPageLink());
			};
		},
		'button.survey_decline' : function(element) {
			element.onclick = function() {
				go(buildDeclineSurveyLink());
			};
		},
		'input.survey_accept' : function(element) {
			element.onclick = function() {
				go(buildConductSurveyLink());
				go(element.name,'true');
				
			};
		},
		'span.label_with_help' : function(element) {
			element.onmouseover = 
				function (e) {
					// Not sure why I get JavaScript errors on this all the time
					// I even tried adding if (showHelp) and I got an error on that!
					try {
						showHelp(element);
					} catch (ex) {}
				};
			element.onmouseout = 
				function (e) {
					// See comment in showHelp
					try {
						hideHelp(element);
					} catch (ex) {}
				};
		},
		'#maintenance_delete_action' : function(element) {
			element.onclick = function() {
				var data = document.getElementsByClassAndTag('data', 'span', element);
				var msg = data[0].innerHTML;
				var answer = confirm (msg);
				if (answer) {
					// they want to delete
					var xml = syncAjaxCall('ajax.maintenance.remove', element.href, null, null, ERROR_HANDLING_SHOW);
					if (xml) {
						go(data[1].innerHTML);
					}
				}
				return false;
			};
		},
		/**
		 * Display mouseover help.  This works in tandem with the formElement tag, although if you
		 * get the CSS classes right, it will work for hand-crafted HTML as well.
		 */
		'span.form_element_label' : function(element) {
			element.onmouseover = 
				function (e) {
					// Not sure why I get JavaScript errors on this all the time
					// I even tried adding if (showHelp) and I got an error on that!
					try {
						showHelp(element);
					} catch (ex) {}
				};
			element.onmouseout = 
				function (e) {
					// See comment in showHelp
					try {
						hideHelp(element);
					} catch (ex) {}
				};
		},
		'span.left_form_element_label' : function(element) {
			element.onmouseover = 
				function (e) {
					// Not sure why I get JavaScript errors on this all the time
					// I even tried adding if (showHelp) and I got an error on that!
					try {
						showHelp(element);
					} catch (ex) {}
				};
			element.onmouseout = 
				function (e) {
					// See comment in showHelp
					try {
						hideHelp(element);
					} catch (ex) {}
				};
		},
		
		'span.form_element_label_single' : function(element) {
			element.onmouseover = 
				function (e) {
					if (showHelp) {
						showHelp(element);
					}
				};
			element.onmouseout = 
				function (e) {
					if (hideHelp) {
						hideHelp(element);
					}
				};
		},
		
		/**
		 * The following behaviours deal with creating a menu-like dropdown to replace the normal html select.
		 * These behaviours go with the selectList tag.
		 */
		 
		/**
		 * Add the behaviours to show and hide the select menu.
		 */
		'a.select_menu_link' : function(element) {
			element.onclick =
				function() {
					showSelect(element);
					var menu = findParent(element, 'div');
					registerKeyPress(function() { hideSelect(menu); });
					
					selectObserver = function(e) { 
						var target = Event.element(e);
						if (target.tagName.toLowerCase() == 'a' && findParent(target,'div').id == findParent(element, 'div').id) {
							return false;
						}
						var menu = findParent(element, 'div');
						hideSelect(menu); 
					}
					Event.observe(document, "mousedown", selectObserver, true);
					return false;
				};
		},
		/**
		 * This is only needed because IE doesn't support :hover pseudo thingeys on anything
		 * but <a>.  We want it on <li> so we can display a different background across the
		 * whole menu.
		 */
		'li.dropdown_option' : function(element) {
			element.onmouseover =
				function() {
					if (element.id != "") {
						Element.addClassName(element, 'dropdown_option_hover');
					}
				};
				
			element.onmouseout =
				function() {
					Element.removeClassName(element, 'dropdown_option_hover');
				};
		},
		/**
		 * Handle the onclick event for the select menu.
		 */
		'li.dropdown_option a' : function(element) {
			element.onclick = 
				function() {
					return chooseSelect(element);
				};
		},
		'select.select' : function(element) {
			element.onchange =
				function() {
					return chooseSelect(element);
				};
		},
		/**
		 * Adjustable text area
		 */
		'input.adjustable_textarea' : function(element) {
			initializeTextArea(element);
			element.onkeyup =
				function() {
					adjustRows(element); 
				};
				
			element.onfocus = 
				function() {
					adjustRows(element); 
				};
		},
		'select.datePicker' : function(element) {
			element.onchange = function() {
				var datePickerSnippets = document.getElementsByClassAndTag('date_picker_custom_html', 'div', currentPopup);
				if (datePickerSnippets) {
					for (var i=0; i < datePickerSnippets.length; ++i) {
						Element.addClassName(datePickerSnippets[i], 'hidden');
					}
					var choice = $F(element);
					if (choice) {
						var customHtmlId = choice + '_custom_html';
						if ($(customHtmlId)) {
							Element.removeClassName($(customHtmlId), 'hidden');
						}
						var customFunction = choice.replace(/\./g,"_") + '_onShow';
						if (functionExists(customFunction)) {
							//alert(customFunction);
							eval(customFunction + '()');
						}
					}
				}
			};
		},
		'a.transfer_box_move_to' : function(element) {
			sortListBox(element);

			element.onclick = function() {
				setTimeout(function() {showCursorBusy();}, 100);
				var left, right, leftSelected, rightSelected;
				if (element.id.indexOf("move_to") >= 0) {
					var base = element.id.replace("_move_to","");
					left = $(base + '_from');
					right = $(base + '_to');
					rightSelected = base + '_selected';
				} else {
					var idx = element.id.indexOf("_to_");
					left = $(element.id.substring(0, idx));
					right = $(element.id.substring(idx+4));
					leftSelected = left.id + '_selected';
					rightSelected = right.id + '_selected';
				}
			    for (i=0; i < left.options.length; i++) {
			        var current = left.options[i];
			        if (current.selected) {
			        	var len = right.options.length;
			            right.options[len] = new Option(current.text, current.value);
			            if (current.title && current.title != '') {
			            	right.options[len].title= current.title;
			            } else {
			            	right.options[len].title= current.text;
			            }
			            left.options[i] = null;
			            i--;
			        }
			    }
			    if ($(rightSelected)) {
				    var selected = '';
				    for (i=0; i < right.options.length; ++i) {
				    	selected += right.options[i].value;
				    	if (i < right.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(rightSelected).value = selected;
			    }
			    if ($(leftSelected)) {
			    	var selected = '';
				    for (i=0; i < left.options.length; ++i) {
				    	selected += left.options[i].value;
				    	if (i < left.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(leftSelected).value = selected;
			    }
		    	sortListBox(right);
		    	setTimeout(function() {showCursorNormal();}, 100);
			    return false;
			};
		},
		'a.transfer_box_move_from' : function(element) {
			element.onclick = function() {
				
				var left, right, leftSelected, rightSelected;
				if (element.id.indexOf("move_from") >= 0) {
					var base = element.id.replace("_move_from","");
					left = $(base + '_from');
					right = $(base + '_to');
					rightSelected = base + '_selected';
				} else {
					var idx = element.id.indexOf("_from_");
					left = $(element.id.substring(0, idx));
					right = $(element.id.substring(idx+6));
					leftSelected = left.id + '_selected';
					rightSelected = right.id + '_selected';
				}
			    for (i=0; i < right.options.length; i++) {
			        var current = right.options[i];
			        if (current.selected) {
			        	var len = left.options.length;
			            left.options[len] = new Option(current.text, current.value);
			            if (current.title && current.title != '') {
			            	left.options[len].title = current.title;
			            } else {
			            	left.options[len].title = current.text;
			            }
			            right.options[i] = null;
			            i--;
			        }
			    }
			    if ($(rightSelected)) {
				    var selected = '';
				    for (i=0; i < right.options.length; ++i) {
				    	selected += right.options[i].value;
				    	if (i < right.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(rightSelected).value = selected;
			    }
			    if ($(leftSelected)) {
			    	var selected = '';
				    for (i=0; i < left.options.length; ++i) {
				    	selected += left.options[i].value;
				    	if (i < left.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(leftSelected).value = selected;
			    }
			    sortListBox(left);
			    return false;
			};
		},
		'a.transfer_box_move_down' : function(element) {
			sortListBox(element);

			element.onclick = function() {
				setTimeout(function() {showCursorBusy();}, 100);
				
				// authors_move_down = from -> to 
				// authors_move_up = to -> from
				
				var left, right, leftSelected, rightSelected;
				if (element.id.indexOf("move_to") >= 0) {
					base = element.id.replace("_move_to","");
					left = $(base + '_from');
					right = $(base + '_to');
					rightSelected = base + '_selected';
				} else {
					var idx = element.id.indexOf("_to_");
					left = $(element.id.substring(0, idx));
					right = $(element.id.substring(idx+4));
					leftSelected = left.id + '_selected';
					rightSelected = right.id + '_selected';					
				}
			    for (i=0; i < left.options.length; i++) {
			        var current = left.options[i];
			        if (current.selected) {
			        	var len = right.options.length;
			            right.options[len] = new Option(current.text, current.value);
			            if (current.title && current.title != '') {
			            	right.options[len].title = current.title;
			            } else {
			            	right.options[len].title = current.text;
			            }
			            left.options[i] = null;
			            i--;
			        }
			    }
			    if ($(rightSelected)) {
				    var selected = '';
				    for (i=0; i < right.options.length; ++i) {
				    	selected += right.options[i].value;
				    	if (i < right.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(rightSelected).value = selected;
			    }
			    if ($(leftSelected)) {
			    	var selected = '';
				    for (i=0; i < left.options.length; ++i) {
				    	selected += left.options[i].value;
				    	if (i < left.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(leftSelected).value = selected;
			    }
		    	sortListBox(right);
		    	setTimeout(function() {showCursorNormal();}, 100);
			    return false;
			};
		},
		'a.transfer_box_move_up' : function(element) {
			element.onclick = function() {
				var left, right, leftSelected, rightSelected;
				if (element.id.indexOf("move_from") >= 0) {
					var base = element.id.replace("_move_from","");
					left = $(base + '_from');
					right = $(base + '_to');
					rightSelected = base + '_selected';
				} else {
					var idx = element.id.indexOf("_from_");
					left = $(element.id.substring(0, idx));
					right = $(element.id.substring(idx+6));
					leftSelected = left.id + '_selected';
					rightSelected = right.id + '_selected';					
				}
			    for (i=0; i < right.options.length; i++) {
			        var current = right.options[i];
			        if (current.selected) {
			        	var len = left.options.length;
			            left.options[len] = new Option(current.text, current.value);
			            if (current.title && current.title != '') {
			            	left.options[len].title = current.title;
			            } else {
			            	left.options[len].title = current.text;
			            }
			            right.options[i] = null;
			            i--;
			        }
			    }
			    if ($(rightSelected)) {
				    var selected = '';
				    for (i=0; i < right.options.length; ++i) {
				    	selected += right.options[i].value;
				    	if (i < right.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(rightSelected).value = selected;
			    }
			    if ($(leftSelected)) {
			    	var selected = '';
				    for (i=0; i < left.options.length; ++i) {
				    	selected += left.options[i].value;
				    	if (i < left.options.length-1) {
				    		selected += ',';
				    	}
				    }
				    $(leftSelected).value = selected;
			    }
			    sortListBox(left);
			    return false;
			};
		}		
	};
	
Behaviour.register(formRules, 'FormRules');
Behaviour.addLoadEvent(function() { 	
	if ($('survey') && $F('survey') == "conduct_survey") {
		go(buildConductSurvey());
	}
	
	return false; 
});
function validateFormSubmit(form, path) {
	// Get rid of any errors from the last time they submitted
	clearErrors(form);
	log.debug('.validate_form_submit: form = ' + form.id);
	
	// Perform built-in validation for the easy stuff; currently only required fields
	validationErrors = standardValidate(form);
	// Process custom client-side validation specific to this form
	var jsValidate = 'validate' + form.id;
	if (functionExists(jsValidate)) {
		validationErrors = validationErrors.concat(eval(jsValidate+'(form)'));
	}
	
	// Process custom server-side validation specific to this form
	validationErrors = validationErrors.concat(ajaxValidate(form, path));
	log.debug('after ajaxValidate: errors = ' + validationErrors.length);
	if (validationErrors != null && validationErrors.length > 0) {
		try {
			showErrors(form, validationErrors);
		} catch (ex) {
			logException(ex, ERROR_HANDLING_SHOW, 'Error showing errors');
		}
		return false;
	}
	return true;
};
			
/**
 * Hide any validation errors from a previous validation attempt.
 * There are certain standard CSS classes that are handled.
 */
function clearErrors(form) {
	var errors = document.getElementsByClassAndTag('form_element_inline_error','div',form);
	for (var i=0; i < errors.length; ++i) {
		Element.hide(errors[i]);
	}
	if ($('errors')) {
		Element.hide('errors');
		Element.setStyle($('errors'), {'display':'none'});
	}
	if ($('bottom_errors')) {
		Element.hide('bottom_errors');
		Element.setStyle($('bottom_errors'), {'display':'none'});
	}
	if ($('popup_errors')) {
		Element.hide('popup_errors');
		Element.setStyle($('popup_errors'), {'display':'none'});
	}
	if ($('popup_bottom_errors')) {
		Element.hide('popup_bottom_errors');
		Element.setStyle($('popup_bottom_errors'), {'display':'none'});
	}
	var error_list = $('error_list');
	if (error_list) {
		error_list.innerHTML = '';
	}
	
	var popup_error_list = $('popup_error_list');
	if (popup_error_list) {
		popup_error_list.innerHTML = '';
	}
	
	var requiredMarkers = document.getElementsByClassAndTag('required_error','span',form);
	for (var i=0; i < requiredMarkers.length; ++i) {
		requiredMarkers[i].parentNode.removeChild(requiredMarkers[i]);
	}
	if ($('required_fields_missing')) {
		Element.setStyle('required_fields_missing', {'display':'none'});
	}	
	if ($('popup_required_fields_missing')) {
		Element.setStyle('popup_required_fields_missing', {'display':'none'});
	}	
	
	var in_errors = document.getElementsByClassName('in_error',form);
	for (var i=0; i < in_errors.length; ++i) {
		Element.removeClassName(in_errors[i], 'in_error');
	}
}
	
/**
 * This function will perform basic validation on elements that have a certain CSS
 * class.  Currently, it just handles required fields.  Any element that has
 * a CSS class of "required" will be validated to see if the use entered value. It
 * only works for text boxes at the moment.
 */ 
function standardValidate(form) {
	var requiredFields = document.getElementsByClassName('required',form);
	var errors = new Array();
	
	for (var i=0; i < requiredFields.length; ++i) {
		var field = requiredFields[i];
		if (findParent(field, 'form') != form) {
			continue;
		}
		if (field.value == null || trim(field.value) == '') {
			errors.push(new ValidationError(field.id, null, 'required', null));
		}
	}
	return errors;
}

/**
 * This function makes an Ajax call to allow server-side validation of the form, but without
 * causing a page refresh.  It uses the same action as the form but with ?validate=true.  Any
 * submittable action can handle that parameter and return the appropriate response.
 
	/*  This is the response format that ajaxValidate is expecting.  This maps to the
	    JS ValidationError class defined below.
	    
		<ajax-response>
			<errors>
				<error>
					<id>element id for pre-loaded error divs/spans</id>
					<element>optional id of form element for setFocus</element>
					<message>optional message if id does not exist; will be inserted below form</message>
					<parameters>optional substitution parameters for message or element text ({1},{2},etc)
						<parameter>param1</parameter>
						<parameter>param2</parameter>
					</parameters>
				</error>
			</errors>
		</ajax-response>
	*/
function ajaxValidate(form, path) {
	var validationErrors = null;
	if (! path) {
		path = form.action.replace("/act/", "/validate/").replace("/view/","/validate/");
	}
	var parameters = 'validate=true&' + Form.serialize(form);
	// For popups, the parent form may have some useful info in it
	var parentForm = findParent(form, 'form');
	if (parentForm) {
		if (parameters) {
			parameters += '&';
		}
		parameters += Form.serialize(parentForm);
	}
	
	var xml = syncAjaxCall('ajax.form.validate', path, parameters, 'post', ERROR_HANDLING_IGNORE);
	
	if (! xml) {
		var e = new ValidationError(null, null, 'Error validating', null);
		validationErrors = new Array();
		validationErrors.push(e);	
		return validationErrors;
	}
	try {
		var errors;
		try {
			// Get the errors from the response
			errors = xml.getElementsByTagName('errors')[0].childNodes;
		} catch (ex) {
			log.WARN('Error parsing xml response: ' + ex.description);
		}
		// Create ValidationError objects from the errors
		validationErrors = buildErrors(errors, form);
	} catch (ex) {
		log.warn('ajaxValidate: error ' + ex.description + ' during ajaxValidate. ');
		// TODO: translate this
		var e = new ValidationError(null, null, 'Error validating', null);
		validationErrors = new Array();
		validationErrors.push(e);
	}
	return validationErrors;
}

/**
 * This function builds ValidationError objects out of the xml returned via ajaxValidate.
 */ 
function buildErrors(errorNodes, form) {
	var errors = new Array();
	if (! errorNodes || errorNodes.length == 0) {
		return errors;
	}
	for ( var i = 0 ; i < errorNodes.length ; i++ ) {
         var errorNode = errorNodes[i];
         // only process nodes of type element
		if ( errorNode.nodeType != 1 ) {
			continue;
		}
		var e = createValidationError(errorNode);
		errors.push(e);
	}
	return errors;
}

var currentErrors = {};

/**
 * This function initiates the process of inserting and unhiding the error messages.
 */
function showErrors(form, errors) {
	 currentErrors = {};
	for (var i=0; i < errors.length; ++i) {
		var e = errors[i];
		e.show(form);
	}
}

/**
 * Create a ValidationError object given an <error>...</error> xml string.  See ajaxValidate.
 */
function createValidationError(error) {
	var element, id, message;
	var parameters = new Array();
	for (var i=0; i < error.childNodes.length; ++i) {
		var childNode = error.childNodes[i];
		if (childNode.nodeType != 1) {
			continue;
		}
		var text = getNodeTextValue(childNode);
		if (childNode.nodeName == 'element') {
			element = text;
		} else if (childNode.nodeName == 'id') {
			id = text;
		} else if (childNode.nodeName == 'message') {
			message = text;
		} else if (childNode.nodeName == 'parameters') {
			for (var j=0; j < childNode.childNodes.length; ++j) {
				var parameterNode = childNode.childNodes[j];
				if (parameterNode.nodeType != 1) {
					continue;
				}
				var parameter = getNodeTextValue(parameterNode);
				parameters.push(parameter);
			}
		}
	}
	return new ValidationError(element, id, message, parameters);
}
	
/**
 * This class represents a single validation error. See the initialize method for a
 * description of the members.
 */ 
var ValidationError = Class.create();
ValidationError.prototype = {
	initialize : function(element, error, message, parameters) {
		this.element = $(element);	// The DOM ID of the element that caused the error, if any
		if (error && error != null && error != '') {
			this.error = $(error);	// The DOM ID of the container where the error will display
		} else {
			this.error = null;
		}
		this.message = message; // The message to display (overrides error.innerHTML);
		this.parameters = parameters; // Substitution parameters for the error using {1},{2} syntax
	},
	/**
	 * Perform parameter substitution on the error message using standard Java MessageFormat
	 * syntax ({1},{2},...).
	 */
	substitute : function() {
		if (this.message && this.message == 'required') {
			return;
		}
		var m = this.message;
		log.debug('ValidationError.substitute: message = ' + m);
		if ((! m || m == '') && this.error) {
			m = this.error.innerHTML;
		}
		if (this.parameters) {
			for (var i=0; i < this.parameters.length; ++i) {
				var param = this.parameters[i];
				m.replace('{' + i+1 + '}', param);
			}
		}
		if (this.error) {
			this.error.innerHTML = m;
		} else {
			this.message = m;
		}
	},
	/**
	 * Create or unhide the DOM elements needed to display the errors.
	 */
	show : function(form) {
		this.substitute();
		var popup = isOverlayShowing() ? 'popup_' : '';
		var el = null;
		if (this.element) {
			el = $(this.element);
		}
		if (el && this.message && this.message == 'required') {
			// Add the required marker after the form element
			if (this.element.type == 'select-one' || this.element.type == 'select-multiple') {
				if (! $(this.element.id + '_error_icon')) {
					Element.addClassName(this.element, 'in_error');
					new Insertion.After(this.element, '<span id="' + this.element.id + '_error_icon" class="required_error"><span>*</span></span>');
				}
			} 
			if ($(popup + 'required_fields_missing')) {
				// There needs to be a div or span with an id of 'required_field_missing' that
				// indicates to the user what the *'s mean; we can't auto insert this because
				// we don't have hooks to do translation easily
				Element.setStyle(popup + 'required_fields_missing', {'display':'block'});
			} else {
				log.warn('standardValidate: no required fields error container');
			}
		} else if (this.error) {
			// this shows a pre-defined error element
			Element.setStyle(this.error, {'display':'block'});
		} else if (this.message) {
			if (! currentErrors[this.message]) {
				currentErrors[this.message] = '1';
				// this builds a new error element and puts it at the top of the form
				var id = this.element ? this.element.id : 'generic';
				if ($(id + '_error')) {
					// in case this one was already built, just change the message
					$(id + '_error').innerHTML = this.message;
				} else {
					/* This mess is just to get the right HTML in place without duplicating.  
					   Basically it's supposed to look like:
						 <div id="errors">
						 	<div class="start"/>
						 	<ul id="error_list" class="error_list">
						 		<li id="[error_id]_error" class="error_list_item">Error text</li>
						 		...
						 	</ul>
						 	<div class="end"/>
						  </div>
					*/
					var errorListItem = '<li class="error_list_item" id="' + id + '_error">' + this.message + '</li>';
					var errorList = $(popup + 'error_list');
					
					if (! errorList) {
						log.debug('ValidationError.show: creating error list');
						var errors = $(popup + 'errors');
						var errorHTML = '<ul id="' + popup + 'error_list" class="error_list">' + errorListItem + '</ul>';
						if (! errors) {
							errorHTML = '<div id="' + popup + 'errors" class="errors"><div class="start"/>' + errorHTML + '<div class="end"/></div>';
							if ($(popup + 'form_elements')) {
								new Insertion.Top($('form_elements'), errorHTML);
							} else {
								new Insertion.Top(form, errorHTML);
							}
						} else {
							new Insertion.Bottom($(popup + 'errors'), errorHTML);
							Element.setStyle($(popup + 'errors'), {'display':'block'});
						}
					} else {
						new Insertion.Bottom($(popup + 'error_list'), errorListItem);
					}
				}
				if ($(popup + 'errors')) {
					Element.setStyle(popup + 'errors', {'display':'block'});
				}
			}
		}
		if (this.element) {
			if (! $(this.element.id + '_error_icon')) {
				new Insertion.After(this.element, '<span id="' + this.element.id + '_error_icon" class="required_error"><span>*</span></span>');
				Element.addClassName(this.element, 'in_error');
			}
		}
		if ($(popup + 'bottom_errors') && $('errors')) {
			$(popup + 'bottom_errors').innerHTML = $(popup + 'errors').innerHTML;
			Element.setStyle(popup + 'bottom_errors', {'display':'block'});
		}
	}
};

var helpTimer = null;
var hideTimer = null;
var cursorTimer = null;
/**
 * Display the popup help for a form element.  This works in conjunction with the formElement tag,
 * although, the same classes could be created manually and it would work.<b> 
 */
function showHelp(element, helpId) {
	element = $(element);
	
	log.debug('showHelp: element = ' + element.id);
	if (helpTimer) {
		clearTimeout(helpTimer);
		helpTimer = null;
	}
	// Whatever the label is that will display the hover help, the help is that with s/label/help/
	if (! helpId) {
		helpId = element.id.replace(/label$/,"help");
	}
	if (! $(helpId) || $(helpId).innerHTML == '') {
		return;
	}
	// Set the cursor to indicate that they can hover for help; delay a little so as not
	// to be to annoying.
	cursorTimer = setTimeout( function() { document.body.style.cursor = 'help'; }, 200 );
	
	// Delay a little more for the help itself
	helpTimer = setTimeout( function() { showHelpPopup(element, helpId); }, 500 );
}

/**
 * This is the function that displays the actual help div. The goal is to get it
 * to the right of the label slightly offset down, without it being hidden by the
 * walls of the browser.
 */
function showHelpPopup(label, helpId) {
	label = $(label);
	log.debug('showHelpPopup: showing help popup for ' + label.id);
	
	// Set the help div to display, but invisible so we can get the height
	if (! helpId) {
		helpId = label.id.replace(/label$/,"help");
	}
	var help = $(helpId);
	
	positionAndDisplay(label, help, 5);
}

function positionAndDisplay(label, help, offset, borderClass, overlay) {
	label = $(label);
	help = $(help);
	
	Element.setStyle(help, {'visibility':'hidden','position':'absolute','display':'block'});
	
	// Get the position of the label
	var labelPos = Position.cumulativeOffset(label);
	var labelX = labelPos[0];
	var labelY = labelPos[1];
	var labelWidth = Element.getDimensions(label).width;
	
	// Figure out some boundaries based on the window area
	var scrollTop = document.body.scrollTop;
	var scrollLeft = document.body.scrollLeft;
	var windowHeight = getWindowHeight();
	
	// This is fairly random, but narrow boxes look stupid with long help and wide boxes
	// look stupid with short help, so allow for a style for both normal and small.
	var helpText = help.innerHTML;
	if (Element.hasClassName(element, 'form_element_help') && helpText.length < 200) {
		Element.removeClassName(help, 'form_element_help');
		Element.addClassName(help, 'form_element_help_small');
	}
	if (Element.hasClassName(element, 'left_form_element_help') && helpText.length < 200) {
		Element.removeClassName(help, 'left_form_element_help');
		Element.addClassName(help, 'left_form_element_help_small');
	}
	
	// Grab the width and height of the help box
	var helpHeight = Element.getDimensions(help).height;
	var helpWidth = Element.getDimensions(help).width;
	
	/*
		This positions it to the left of the label; we prefer the right,
		but then we run into the select list z-index problem, which we can
		fix with an iframe, but if it doesn't hold up in all browsers, this
		is a decent compromise.
	var helpX = labelX - helpWidth - 5;
	if (helpX < (scrollLeft+5)) {
		helpX = (scrollLeft +5);
	}
	*/
	
	/*
		This positions it to the right, which is preferred, but then we
		have to do the iframe shimmy because it may end up over select lists
		which don't obey z-index.
	*/
	var helpX = labelX + (offset > 0 ? labelWidth + offset : offset);
	
	// Position it vertically, at about the middle of the label
	//var helpY = labelY - helpHeight*.6;
	var helpY = labelY + 20;
	
	// Make sure it's not hidden by the bottom
	if ((helpY + helpHeight) > (scrollTop+windowHeight)) {
		helpY = (scrollTop+windowHeight) - helpHeight - 5;
	}
	
	// And now make sure it's not hidden by the top.  If there's a conflict, let it be
	// hidden by the bottom, but that would be some bigass help or a smallass browser window.
	if (helpY < scrollTop) {
		helpY = scrollTop + 0;
	}
	
	Element.setStyle(help, {'top': helpY, 'left': helpX});
	
	if (overlay) {
		showPopup(help);
	} else {
		showPopupNoOverlay(help);
	}
	if (helpTimer) {
		clearTimeout(helpTimer);
	}
	helpTimer = null;
}
	
/**
 * Hide the help popup.
 */
function hideHelp(element, helpId) {
	element = $(element);
	if (helpTimer) {
		clearTimeout(helpTimer);
		helpTimer = null;
	}
	if (cursorTimer) {
		clearTimeout(cursorTimer);
		cursorTimer = null;
	}
	document.body.style.cursor = 'auto';
	if (! helpId) {
		helpId = element.id.replace(/label$/,"help");
	}
	if (! $(helpId) || $(helpId).innerHTML == '') {
		return;
	}
	hideTimer = setTimeout( function() { hideHelpPopup(element, helpId); }, 200 );
}

/**
 * Really hide the help popup.  That other one was just to set a timer.
 */
function hideHelpPopup(element, helpId) {
	element = $(element);
	if (! helpId) {
		helpId = element.id.replace(/label$/,"help");
	}
	hidePopupNoOverlay(helpId);
}


function showTooltip(target, text, textId, overlay) {
	if (helpTimer || hideTimer) {
		return;
	}
	// Set the cursor to indicate that they can hover for help; delay a little so as not
	// to be to annoying.
	cursorTimer = setTimeout( function() { document.body.style.cursor = 'help'; }, 200 );
	
	// Delay a little more for the help itself
	helpTimer = setTimeout( function() { showTooltipPopup(target, text, textId, overlay); }, 800 );
}

function showTooltipPopup(target, text, textId, overlay) {
	var div = $(textId);
	if (! div) {
		div = document.createElement('div');
		div.id = textId;
		div.className = 'form_element_help';
		div.innerHTML = '<div class="drop-shadow-container">'
			+ '<div class="drop-shadow1">'
			+ '<div class="drop-shadow2">'
			+ '<div class="drop-shadow3">'
			+ '<div class="popup_help_container" style="width: 300px;">'
			+ text
			+ '</div></div></div></div></div>';
		document.body.appendChild(div);
	}
	positionAndDisplay(target, div, -1, '', overlay);
}

function hideTooltip(textId, overlay) {
	var text = $(textId);
	if (helpTimer) {
		clearTimeout(helpTimer);
		helpTimer = null;
	}
	if (cursorTimer) {
		clearTimeout(cursorTimer);
		cursorTimer = null;
	}
	document.body.style.cursor = 'auto';
	if (! text) {
		hideTimer = null;
		return;
	}
	if (overlay) {
		hideTimer = setTimeout( function() { hidePopup(text); text.parentNode.removeChild(text); setTimeout(function() { hideTimer = null; }, 1000); }, 200 );
	} else {
		hideTimer = setTimeout( function() { hidePopupNoOverlay(text); text.parentNode.removeChild(text); hideTimer = null;}, 200 );
	}
		
}
/**
 * This function displays the select menu created by the bsl:selectList tag
 * when menuStyle="true".
 */
 
function showSelect(element) {
	
	var menu = findParent(element, 'div');
	
	// Reset the z-index, iframe, and class of any other select menus.  
	// If they're all the same we might get some overlap with the dropdown 
	// part of one being superceded by the click part of a lower one. And
	// with very fast mouse movements, we could overwrite our hide timer so
	// this will clean up any extra menus that might be showing.
	/*
	var allMenus = document.getElementsBySelector('.select_menu');
	log.debug('showSelect:  found ' + allMenus.length + ' select menus');
	for (var i=0; i < allMenus.length; ++i) {
		hideSelect(allMenus[i]);
	}
	*/
	// IE doesn't support hover on non-anchors so we need to apply a custom style
	// that un-hides the select menu
	Element.addClassName(menu, 'select_menu_open');
	Element.removeClassName(menu, 'select_menu');
	Element.scrollTo(menu);
	
	
	// Neither browser is very good at expanding the anchor so that you can click
	// off to the right of it and have it register.  This will set the width of
	// each anchor to the width of the list to get around that.
	var ulWidth = Element.getDimensions(menu).width;
	var anchors = menu.getElementsByTagName('a');
	if (anchors) {
		for (var i=0; i < anchors.length; ++i) {
			var liWidth = Element.getDimensions(anchors[i].parentNode).width;
			Element.setStyle(anchors[i], {'width':liWidth+'px'});
		}
	}
	
	// Once again we have to add the iframe shimmy so that other select boxes
	// won't show through our menu
	var dropdown = menu.getElementsByTagName('ul')[0]; //$(element.id.replace(/menu/,"list"));
	showIframeBlocker(dropdown);
}

/**
 * Hide the select menu and get rid of the blocking iframe.  We have to
 * set a timer to do this because we get a lot of back to back mouseout/mousein
 * events due to event bubbling and capture.  The timer will be killed if
 * a subsequent mouseover event fires for the same id.
 */
function hideSelect(menu) {
	Element.addClassName(menu, 'select_menu');
	Element.removeClassName(menu, 'select_menu_open');
	var dropdown = menu.getElementsByTagName('ul')[0];
	hideIframeBlocker(dropdown);
	if (selectObserver != null) {
		Event.stopObserving(document, "mousedown", selectObserver, true); 
	}
}

/**
 * Choose a select menu option.  Update the click element and
 * store the key in a hidden so it gets posted.
 */
function chooseSelect(element) {
	var menuStyleData = document.getElementsByClassAndTag('select_data','span',element.parentNode);
	var menuStyle = menuStyleData && menuStyleData.length > 0;
	if (menuStyle) {
		var data = menuStyleData[0].innerHTML.parseJSON();
		var id = data['id'];
		var key = data['key'];
		var newDisplay = unescape(data['display']);
	} else {
		var data = {'key':$F(element.id)};
		var id = element.id;
	}
	// This was false and it was preventing the default from being chosen
	// when I cleared the display on a add table.  But I don't know why
	// we would set success to false when they choose to not choose a
	// value.
	var success = true;
	
	// Check for any post-processing functions; all of them must return true
	// for the new value to take
	if (key != '') {
		// First look for one based on the id
		var onSelect = id + '_onselect';
		if (functionExists(onSelect)) {
			success = eval(onSelect+'(element, data)');
		}
		
		// Next look for one based on the class names
		Element.classNames(id).each(function(className) {
			onSelect = className + '_onselect';
			if (functionExists(onSelect)){
				success = success && eval(onSelect+'(element, data)');
			}
		});
	}
	
	if (success && menuStyle) {
		$(id).value = key;
		$(id+'_display').innerHTML = newDisplay;
	}
	
	if (menuStyle) {
		var menu = findParent(element, 'div');
		hideSelect(menu);
	}
	
	return false;
}

function adjustRows (textarea) {
	bUserTyped = true;
	if (document.all) { // IE only
		var bGrew = false;
		while ((textarea.scrollHeight > textarea.clientHeight) && (textarea.rows < 30)) {
			textarea.rows += 3;
			bGrew = true;
		}
		if ( bGrew ) {
			textarea.scrollTop = 0;
		}
	} else {
		var rgLines = textarea.value.split('\n');
		var nRows = 1;
		for ( var i=0; i<rgLines.length; i++ ) {
			if ( rgLines[i].length > textarea.cols ) {
				nRows += Math.ceil(rgLines[i].length/textarea.cols);
			}
		}
		nRows += rgLines.length;
		if ( nRows > textarea.rows ) {
			textarea.rows = Math.min(nRows, 30);
		}
	}
}

function initializeTextArea(textarea) {
	var value = $(textarea).innerHTML;
	var split = value.split("\n");
	var count = split.length;
	if (count < $(textarea).rows) {
		count = $(textarea).rows;
	}
	$(textarea).rows = count;
	adjustRows($(textarea));
}

function hideCustomDatePicker() {
	Element.addClassName('datePicker_table', 'hidden');
}

function showCustomDatePicker() {
	
	Element.removeClassName('datePicker_table', 'hidden');
	
	var from = $('datePicker_from');
	var to = $('datePicker_to');
	
    Calendar.setup({
        inputField		:	'datePicker_from',     		    // id of the input field
        ifFormat		:	"%m/%d/%Y %H:%M",      			// format of the input field
        button			:	'datePicker_from_cal',  		// trigger for the calendar (button ID)
        singleClick		:	true,
        weekNumbers		:	false,
        align			:	'Br',
        cache			:	true,
        showsTime		: 	true,
        onUpdate		: 	function(cal) { updateInputFromCalendar(cal, from); }
    });
    
    Calendar.setup({
        inputField		:	'datePicker_to',     		    // id of the input field
        ifFormat		:	"%m/%d/%Y %H:%M",      			// format of the input field
        button			:	'datePicker_to_cal',  		// trigger for the calendar (button ID)
        singleClick		:	true,
        weekNumbers		:	false,
        align			:	'Br',
        cache			:	true,
        showsTime		: 	true,
        onUpdate		: 	function(cal) { updateInputFromCalendar(cal, to); }
    });    
}

function showDatePicker(text_box_id, button_id, show_time, updateFunc) {
	if (! updateFunc) {
		updateFunc = Prototype.emptyFunction;
	}
	var text = $(text_box_id);
	var ifFormat = "%m/%d/%Y %H:%M";
	if (! show_time) {
		ifFormat = "%m/%d/%Y";
	} 
	var update = Prototype.EMPTY_FUNCTION;
	if (show_time) {
		update = function(cal) { updateInputFromCalendar(cal, text, show_time); updateFunc(); $(text_box_id).focus(); };
	} else {
		update = function(cal) { updateFunc(); $(text_box_id).focus(); }
	}
    Calendar.setup({
        inputField		:	text_box_id,     		    // id of the input field
        ifFormat		:	ifFormat,      				// format of the input field
        button			:	button_id,			  		// trigger for the calendar (button ID)
        singleClick		:	true,
        weekNumbers		:	false,
        align			:	'Br',
        cache			:	true,
        showsTime		: 	show_time,
        onUpdate		: 	update
    });
}

function updateInputFromCalendar(cal, element, show_time) {
	var date = cal.date;
	if (date.getHours() == 0 && date.getMinutes() == 0) {
		date.setHours(null);
		date.setMinutes(null);
		element.value = element.value.substring(0, element.value.indexOf(" "));
	}
}

function selectSearch(text, container, destination) {
	var path = buildGetDynamicSelectData();
	var parms = 's=' + $(text+'_search_text').value + '&ds=' + $(text+'_ds').value
		+ '&c=' + $(text+'_class').value + '&da=' + $(text+'_dependent_attributes').value;
		 
	var jsonResponse = syncAjaxCallText('ajax.dynamic.select', path, parms, 'post', ERROR_HANDLING_SHOW);
	var map = jsonResponse.parseJSON();
	var empty = true;
	for (var entry in map) {
		if (entry == 'extend' || entry == 'toJSONString') {
			continue;
		}
		empty = false;
		break;
	}
	
	if (empty) {
		alert('No records found that met the criteria.');
	} else {
		destination = $(destination);
		var current = null;
		var existing = {};
		if (destination) {
			for (var i=0; i < destination.options.length; ++i) {
				current = destination.options[i];
				existing[current.value] = current.text;
			}
		}
			
		container = $(container);
		var current = null;
		var toRemove = new Array();
	    for (var i=0; i < container.options.length; i++) {
	    	if (! destination) {
	    		if (container.options[i].selected) {
	    			current = container.options[i].value;
	    			continue;
	    		}
		    	if (trim(container.options[i].value) == '') {
		    		continue;
		    	}
	    	}
			toRemove.push(i);
		}
		toRemove.reverse();
		for (var i=0; i < toRemove.length; ++i) {
			var idx = toRemove[i];
			container.remove(idx);
		}
		var j = container.options.length;
		var found = false;
		for (var entry in map) {
			if (entry == 'extend' || entry == 'toJSONString') {
				continue;
			}
			if (existing[entry]) {
				continue;
			}
			found = true;
			container.options[j] = new Option(map[entry], entry);
			container.options[j].title = map[entry];
			j++;
		}
		if (! found) {
			alert('All records found are already configured.');
		}
		sortListBox(container);
		if (current) {
			for (var i=0; i < container.options.length; ++i) {
				if (container.options[i].value == current) {
					container.options[i].selected = true;
				} else {
					container.options[i].selected = false;
				}
			}
		}
	}
}
