// $Header: /home/cvs/cvsroot/site_data/001/00000001/static_data/js/FormComponentsBehavior.js,v 1.22 2007/08/09 16:07:27 cchu Exp $

/**
 * @fileoverview This file contains functions for the behavior of data form components.
 */

// ---------------------------------------------------
// These HTML classes must be used by the form components in order for the behavior
// in this file to be correctly activated:
// ---------------------------------------------------

/**
 * An element that contains the entire row. 'FormRow'
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_ROW_CLASS = 'FormRow';

/**
 * An element that contains the input element (s).
 * @private
 * @final
 * @type String
 * @value 'FormInput'
 * @member FormComponent
 */
var FC_INPUT_CLASS = 'FormInput';

/**
 * A BUTTON or INPUT that is the 'edit' button shown in preview mode.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_EDIT_BUTTON_CLASS = 'FormPreviewEditButton';

/**
 * An element that contains the messages.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_MSG_ROW_CLASS = 'holdsErrorMsg';

/**
 * An element that contains the error image and marks the location and size
 * for dynamically rendered info. and warning images.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_MESSAGE_ICON_CLASS = 'FormMsgIcon';

/**
 * An element that contains the error message.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_ERROR_TEXT_CLASS = 'FormMsgText';

/**
 * An element that contains the informational message.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_INFO_TEXT_CLASS = 'FormElementHiddenInfoLabel';

/**
 * An element that contains the warning message.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_WARN_TEXT_CLASS = 'FormElementHiddenWarnLabel';

/**
 * An element that contains the informational message.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_REQUIRED_CLASS = 'FormElementHiddenRequired';

/**
 * An element that contains the field label.
 * @private
 * @final
 * @type String
 * @member FormComponent
 */
var FC_LABEL_TEXT_CLASS = 'FormLabelText';

/**
 * Not thoroughgoing support for the undefined primitive;
 * this fixes the problem by asserting it without assigning a value:
 * @member FormComponent
 */
var undefined;

/**
 * @private
 * @type FCGlobalMessages
 * @member FormComponent
 */
var fc_globalMessages;

/**
 * Set the function to be called when the user selects the "edit" button shown in
 * the preview mode.  The function must accept the 'edit' button as the first
 * parameter.
 * @type function
 * @member FormComponent
 */
var fc_edit_component_fn;

/**
 * @class FormComponent Global methods in the FormComponentsBehavior.js file.
 * This is not really a class, but it serves to better organize the javadoc
 * comments generated from this file.
 * Without this, all methods get documented in a 'GLOBALS' page with methods
 * from all other .js files.
 */
function FormComponent()
{
}

/**
 * Sets the function to be called to edit form components from the preview mode.
 * @param fn the function to be called.  It should take one parameter that is
 *           the HTML element that triggered the callback.
 * @member FormComponent
 */
function fc_setEditComponentFn (fn)
{
	fc_edit_component_fn = fn;
}

/**
 * Creates a FCGlobalMessages that contains the global default text strings for
 * the alternate text information of the images used for information and warning
 * and the default warning message.
 *
 * @class a container for global messages so that they can be passed in from a
 *        message catalog via template tags.
 *
 * @param info_alt the alternative text for information icons.
 * @param warn_alt the alternative text for warning icons.
 * @param warn_text the warning text.
 *
 * @private
 */
function FCGlobalMessages (info_alt, warn_alt, warn_text)
{
	this.info_img_alt = info_alt;
	this.warn_img_alt = warn_alt;
	this.warn_text = warn_text;
}

/**
 * Sets the global default text strings for the alternate text information of
 * the images used for information and warning and the default warning message.
 *
 * @param info_alt the alternative text for information icons.
 * @param warn_alt the alternative text for warning icons.
 * @param warn_text the warning text.
 * @member FormComponent
 */
function fc_setGlobalMessages (info_alt, warn_alt, warn_text)
{
	fc_globalMessages = new FCGlobalMessages (info_alt, warn_alt, warn_text);
}

/**
 * Registers a component for displaying informational and warning messages.
 * @private
 * @param _element an HTML element within a component
 * @param _isRequired boolean specification of whether the component required
 *                    a response from the user.
 * @member FormComponent
 */
function fc_registerComponent (_element, _isRequired)
{
	var el = ((typeof _element) == 'string')
					 ? document.getElementById (_element)
					 : _element;
	Utils.addEvent (el, 'focus', fc_showInformational);
	var blurFn = _isRequired ? fc_showInfosRequired : fc_showInfosNotRequired;
	Utils.addEvent (el, 'blur', blurFn);

	if ((el.tagName == 'INPUT')
			&& ((el.type == 'text') || (el.type == 'radio') || (el.type == 'checkbox')))
	{
		Utils.addEvent (el, 'keypress', fc_handle_enter_key);
	}

	var infoText = fc_getPeerByClass (el, FC_INFO_TEXT_CLASS);
	var warnText = fc_getPeerByClass (el, FC_WARN_TEXT_CLASS);

	fc_initMsgContainers (el, infoText, warnText);

	if (infoText && infoText.hasChildNodes()) {

		// Text
		var info = fc_getInfoMsgContainer (el);

		if (info && !info.hasChildNodes()) {
			fc_copyChildren (infoText, info);
			set_visible (info, false);
		}

		// Icon
		var infoCont = fc_getInfoImgContainer (el, true);

		if (infoCont && !infoCont.hasChildNodes()) {
			infoCont.appendChild (fc_makeInfoMsgImg());
			set_visible (infoCont, false);
		}
	}

	if (warnText && warnText.hasChildNodes()) {
		var error = fc_getErrorMsgContainer (el);

		if (getElementText (error) != getElementText (warnText)) {

			// Text
			var warning = fc_getWarnMsgContainer (el);

			if (warning.hasChildNodes() == false) {
				fc_copyChildren (warnText, warning);
				set_visible (warning, false);
			}

			// Icon
			var warnCont = fc_getWarnImgContainer (el);

			if (warnCont.hasChildNodes() == false) {
				warnCont.appendChild (fc_makeWarningMsgImg());
				set_visible (warnCont, false);
			}
		}
	}

	var hidErr = fc_getPeerByClass (el, 'HiddenErrorImage');
	hide_element (hidErr);

	fc_setMessageDisplay (el, false, false);
}

/**
 * Hides 'DIV' children of a given container if unless they are of the
 * {@link #FC_ERROR_TEXT_CLASS} CSS class or have a 'nohide' attribute set.
 * @private
 * @param _container an HTML element that contains child elements.
 * @member FormComponent
 */
function fc_hideChildren (_container)
{
	if (_container) {
		var ch = _container.childNodes;

		for (var i = 0; (i < ch.length); i++)
			if (!isOfClass (ch[i], FC_ERROR_TEXT_CLASS) && (ch[i].tagName == 'DIV') && !ch[i].nohide)
				hide_element (ch[i]);
	}
}

/**
 * Shows and hides the elements used to display information and warning text and
 * images.  May also adjust other attributes, such as their dimensions.
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @param _showInfo boolean that specifies whether to show the information message.
 * @param _showWarn boolean that specifies whether to show the warning message.
 * @member FormComponent
 */
function fc_setMessageDisplay (_obj, _showInfo, _showWarn)
{
	var warning = fc_getWarnMsgContainer (_obj);
	var warningImg = fc_getWarnImgContainer (_obj);
	var info = fc_getInfoMsgContainer (_obj);
	var infoImg = fc_getInfoImgContainer (_obj);

	fc_hideChildren (fc_getPeerByClass (_obj, 'FormInfoMsg'));

	if (_showInfo && info) {
		hide_element (warningImg);
		show_element (info);
		show_element (infoImg);
	}
	else if (_showWarn && warning) {
		hide_element (infoImg);
		show_element (warning);
		show_element (warningImg);
	}
	else if (!warning || (info && (info.offsetHeight >= warning.offsetHeight))) {
		hide_element (warningImg);
		show_element (info);
		show_element (infoImg);
	}
	else {
		hide_element (infoImg);
		show_element (warning);
		show_element (warningImg);
	}

	if (info && warning) {
		if (info.offsetHeight > warning.offsetHeight) {
			warning.style.height = info.offsetHeight + 'px';
		}
		else if (info.offsetHeight < warning.offsetHeight) {
			info.style.height = warning.offsetHeight + 'px';
		}
	}
}

/**
 * Event handler that executes the function to edit a component.
 * The component is the one that contains the HTML element that was the source
 * of the event.
 * @private
 * @param _evt the event that triggered the call.  In some browsers (e.g., IE),
 *             this parameter is undefined and the event is in a global variable
 *             named 'event'.
 * @see #fc_setEditComponentFn
 * @member FormComponent
 */
function fc_editComponent (_evt)
{
	if (fc_edit_component_fn) {
		// IE vs. Mozilla event model
		_evt = _evt ? _evt : event;
		var el = _evt.target ? _evt.target : _evt.srcElement;

		fc_edit_component_fn (el);
	}
}

/**
 * Event handler that displays the information message and image for a component.
 * The component is the one that contains the HTML element that was the source
 * of the event.
 * @private
 * @param _evt the event that triggered the call.  In some browsers (e.g., IE),
 *             this parameter is undefined and the event is in a global variable
 *             named 'event'.
 * @member FormComponent
 */
function fc_showInformational (_evt)
{
	// IE vs. Mozilla event model
	_evt = _evt ? _evt : event;
	var obj = _evt.target ? _evt.target : _evt.srcElement;

	fc_setMessageDisplay (obj, true, false);
	fc_hideErrorMsg (obj);
	fc_hideWarnMsg (obj);

	var message = fc_getInfoMsgContainer (obj);

	if (message && message.hasChildNodes()) {
		show_element (message);
		set_visible (message, true);
		fc_showIcon (fc_getInfoImgContainer (obj, false), message);
	}
}

/**
 * Event handler that responds to the 'Enter' key by looking for a 'Finish' or
 * 'Next' button and activating it.  This overrides the default behavior, which
 * is to activate the first 'submit' button.  Our forms usually have the 'Cancel'
 * button first and we do not want it activated by the 'Enter' key.
 * @private
 * @param _evt the event that triggered the call.  In some browsers (e.g., IE),
 *             this parameter is undefined and the event is in a global variable
 *             named 'event'.
 * @return <code>true</code> if the browser should continue processing
 * @type boolean
 * @member FormComponent
 */
function fc_handle_enter_key (_evt)
{
	// IE vs. Mozilla event model
	_evt = _evt ? _evt : event;
	var obj = _evt.target ? _evt.target : _evt.srcElement;
	var rc = true;

	if (_evt.type && (_evt.type == 'keypress') && (_evt.keyCode == 13)) {
		var inputs = document.getElementsByTagName ('INPUT');

		if (inputs.length < 500) { // IE is VERY slow with large arrays
			rc = false;

			for (var i = 0; (!rc && (i < inputs.length)); i++) {
				if (inputs[i].type == 'submit') {
					if (fc_button_purpose(inputs[i], 'next') || fc_button_purpose(inputs[i], 'finish')) {
						inputs[i].focus(); // for IE
						inputs[i].click(); // for FF
						rc = true;
					}
				}
			}
		}
	}

	return rc;
}

/**
 * Tries to determine the purpose of a button by examining its value, name, and
 * class.
 * @private
 * @param _btn the button to examine.
 * @param _purpose the purpose (e.g., 'finish') that we're looking for.
 * @return <code>true</code> if the specified purpose is found in any of those attributes.
 * @type boolean
 * @see #fc_handle_enter_key
 * @member FormComponent
 */
function fc_button_purpose(_btn, _purpose)
{
	var rc = false;
	var purpose = _purpose.toLowerCase();

	if ((_btn.value.toLowerCase().indexOf (purpose) >= 0)
	    || (_btn.id.toLowerCase().indexOf (purpose) >= 0)
	    || (_btn.className.toLowerCase().indexOf (purpose) >= 0))
	{
		rc = true;
	}

	return rc;
}

/**
 * Event handler that hides or shows a required component's warning message.
 * It is shown if the input field is empty.
 * The source of the event must be an HTML element within the component's row.
 * @param _evt the event that triggered the call.  In some browsers (e.g., IE),
 *             this parameter is undefined and the event is in a global variable
 *             named 'event'.
 * @private
 * @member FormComponent
 */
function fc_showInfosRequired (_evt)
{
	// IE vs. Mozilla event model
	_evt = _evt ? _evt : event;
	var obj = _evt.target ? _evt.target : _evt.srcElement;
	fc_updateWarningDisplay (obj, true);
}

/**
 * Event handler that hides or shows a required checkbox component's warning message.
 * It is shown if the input field is empty.
 * The source of the event must be an HTML element within the component's row.
 * @param _evt the event that triggered the call.  In some browsers (e.g., IE),
 *             this parameter is undefined and the event is in a global variable
 *             named 'event'.
 * @private
 * @member FormComponent
 */
function showCheckboxInfosRequired (_evt)
{
	// IE vs. Mozilla event model
	_evt = _evt ? _evt : event;
	var obj = _evt.target ? _evt.target : _evt.srcElement;

	if ((obj.tagName == 'INPUT') && (obj.type == 'checkbox'))
		fc_updateWarningDisplay (obj, true);
}

/**
 * Event handler that hides a non-required component's warning message.
 * The source of the event must be an HTML element within the component's row.
 * @param _evt the event that triggered the call.  In some browsers (e.g., IE),
 *             this parameter is undefined and the event is in a global variable
 *             named 'event'.
 * @private
 * @member FormComponent
 */
function fc_showInfosNotRequired (_evt)
{
	// IE vs. Mozilla event model
	_evt = _evt ? _evt : event;
	var obj = _evt.target ? _evt.target : _evt.srcElement;
	fc_updateWarningDisplay (obj, false);
}

/**
 * Finds the top-level HTML element that contains a form component.
 * The container is the first ancestor having a CSS class name containing
 * {@link #FC_ROW_CLASS}.
 * @member FormComponent
 * @param _obj an HTML element within a component.
 * @return the component's containing HTML element, or undefined
 * @type HTMLElement
 */
function fc_getFormRow (_obj)
{
	var row;

	if (_obj) {
		if (_obj.fc_FormRow)
			row = _obj.fc_FormRow;
		else {
			var el = _obj;

			while (el && !row) {
				if (el.className && (el.className.indexOf (FC_ROW_CLASS) >= 0))
					row = el;
				else
					el = el.parentNode;
			}

			_obj.fc_FormRow = row;
		}
	}

	return row;
}

/**
 * Finds the element of a particular class within the same component container.
 * @member FormComponent
 * @param _obj an HTML element from which to find a form component's container.
 * @param _className the CSS class of the desired peer element.
 * @return the peer element within the same component, or undefined.
 * @type HTMLElement
 */
function fc_getPeerByClass (_obj, _className)
{
	var peer;
	var row = fc_getFormRow (_obj);

	if (row) {
		peer = eval ('row.' + _className);

		if (!peer) {
			peer = fc_getChildByClass (row, _className);

			if (peer)
				eval ('row.' + _className + '=peer');
		}
	}

	return peer;
}

/**
 * Finds the first descendant element of a particular class.
 * Note that despite the function name, it recursively descends the hierarchy to
 * find any descendant (child, grandchild, etc.).
 * @member FormComponent
 * @param _obj an HTML element from which to locate a descendant.
 * @param _className the CSS class of the desired descendant element.
 * @return the descendant element, or undefined.
 * @type HTMLElement
 */
function fc_getChildByClass (_obj, _className)
{
	var found;
	var elements = _obj.childNodes;

	if (!elements)
		return found;

	var len = elements.length;
	var i = 0;

	while (i < len && !found) {
		var child = elements[i++];

		if (isOfClass (child, _className)) {
			found = child;
		}
		else if (child.tagName != undefined ) {
			var grandChild = fc_getChildByClass (child, _className);

			if (grandChild)
				found = grandChild;
		}
	}

	return found;
}

/**
 * Gets the plain (non-HTML) text in an element.
 * @member FormComponent
 * @param _el the HTML element
 * @see Utils#getElementText
 * @return the element's text.
 * @type String
 */
function fc_getElementText (_el)
{
	return getElementText (_el);
}

/**
 * Returns the absolute x,y position of an object to the upper left of the window.
 * @private
 * @member FormComponent
 * @param posObj an HTML element for which to compute the position
 * @return an object containing 'x' and 'y' fields.
 * @type Object
 */
function fc_getAbsolutePosition (posObj)
{
	// TODO: define a class for this object.
	var retVal = new Object();
	retVal.x = 0;
	retVal.y = 0;

	while (posObj) {
		retVal.x += posObj.offsetLeft;
		retVal.y += posObj.offsetTop;
		posObj    = posObj.offsetParent;
	}

	return retVal;
}

/**
 * Returns the plain text label for the field containing a given element.
 * HTML is stripped out.
 * @private
 * @member FormComponent
 * @param _el an HTML element for which to return the label
 * @see #fc_getElementText
 * @return the element's component's field label.
 * @type String
 */
function fc_getFieldLabel (_el)
{
	return fc_getElementText (fc_getPeerByClass (_el, FC_LABEL_TEXT_CLASS));
}

/**
 * Returns the plain text information for the field containing a given element.
 * HTML is stripped out.
 * @private
 * @member FormComponent
 * @param _el an HTML element for which to return the label
 * @see #fc_getElementText
 * @return the element's component's information text.
 * @type String
 */
function fc_getFieldInfoText (_el)
{
	return fc_getElementText (fc_getPeerByClass (_el, FC_INFO_TEXT_CLASS));
}

/**
 * Displays an icon associated with a warning or information message.
 * @private
 * @member FormComponent
 * @param _img the icon image element.
 * @param _msg the message container element.
 */
function fc_showIcon (_img, _msg)
{
	set_visible (_img, true);

	var imgContainer = fc_getErrorImgContainer (_img, false);
	show_element (imgContainer);
	_img.style.position = 'static';
	var top = _msg.offsetTop - _img.offsetTop;
	_img.style.position = 'relative';
	_img.style.top = ((top > 0) ? top : 0) + 'px';
}

/**
 * Makes an image element for the informational message.
 * @private
 * @member FormComponent
 * @return the image element
 * @type HTMLImageElement
 */
function fc_makeInfoMsgImg()
{
	var img = document.createElement ('IMG');
	img.src = '../images/info.gif';
	img.alt = img.title = fc_globalMessages.info_img_alt;
	img.width = 16;
	img.height = 16;
	return img;
}

/**
 * Makes an image element for the warning message.
 * @private
 * @member FormComponent
 * @return the image element
 * @type HTMLImageElement
 */
function fc_makeWarningMsgImg ()
{
	var img = document.createElement ('IMG');
	img.src = '../images/warning.gif';
	img.alt = img.title = fc_globalMessages.warn_img_alt;
	img.width = 16;
	img.height = 16;
	return img;
}

/**
 * Makes an image element for use as a spacer.
 * @member FormComponent
 * @param _width the width, in pixels, of the image.
 * @return the image element
 * @type HTMLImageElement
 */
function fc_makeSpacerImg (_width)
{
	var img = document.createElement ('IMG');
	img.className = 'spacer';
	img.src = '../images/Spacer.gif';
	img.width = _width;
	img.height = 1;
	img.alt = ' ';
	return img;
}

/**
 * Initializes the containers (e.g., DIVs) for messages and icons for errors,
 * information, and warnings.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 * @param _infoText the text of the informational message (can include HTML)
 * @param _warnText the text of the warning message (can include HTML)
 */
function fc_initMsgContainers (_obj, _infoText, _warnText)
{
	var msgCont = fc_getPeerByClass (_obj, FC_MSG_ROW_CLASS);

	if (msgCont) {
		var row = fc_getFormRow (_obj);
		var txtCont = fc_getChildByClass (row, FC_ERROR_TEXT_CLASS);

		if (txtCont) {
			var p = txtCont.parentNode;

			if (_infoText && _infoText.hasChildNodes())
				row.fc_infoMsg = fc_getOrMakeChildDiv (p, 'InfoMessage2');

			if (_warnText && _warnText.hasChildNodes())
				row.fc_warnMsg = fc_getOrMakeChildDiv (p, 'WarnMessage2');
		}

		var imgCont = fc_getChildByClass (row, FC_MESSAGE_ICON_CLASS);

		if (imgCont) {
			if (_infoText && _infoText.hasChildNodes())
				row.fc_infoImg = fc_getOrMakeChildDiv (imgCont, 'InfoImage2');

			if (_warnText && _warnText.hasChildNodes())
				row.fc_warnImg = fc_getOrMakeChildDiv (imgCont, 'WarnImage2');
		}
	}
}

/**
 * Returns a component's 'DIV' element of the specified CSS class.
 * It makes a new one if one does not already exist.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @param _class the CSS class name
 * @return the container element
 * @type HTMLElement
 */
function fc_getOrMakeChildDiv (_obj, _class)
{
	var container = fc_getChildByClass (_obj, _class);

	if (!container && _obj) {
		container = document.createElement ('DIV');
		container.className = _class;
		_obj.appendChild (container);
	}

	return container;
}

/**
 * Returns a component's container element of the information message.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @return the container element
 * @type HTMLElement
 */
function fc_getInfoMsgContainer (_obj)
{
	var row = fc_getFormRow (_obj);
	return row ? row.fc_infoMsg : null;
}

/**
 * Returns a component's container element of the information message icon.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @return the container element
 * @type HTMLElement
 */
function fc_getInfoImgContainer(_obj)
{
	var row = fc_getFormRow (_obj);
	return row ? row.fc_infoImg : null;
}

/**
 * Returns a component's container element of the warning message.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @return the container element
 * @type HTMLElement
 */
function fc_getWarnMsgContainer (_obj)
{
	var row = fc_getFormRow (_obj);
	return row ? row.fc_warnMsg : null;
}

/**
 * Returns a component's container element of the warning message icon.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @return the container element
 * @type HTMLElement
 */
function fc_getWarnImgContainer(_obj)
{
	var row = fc_getFormRow (_obj);
	return row ? row.fc_warnImg : null;
}

/**
 * Returns a component's container element of the error message.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @return the container element
 * @type HTMLElement
 */
function fc_getErrorMsgContainer (_obj)
{
	return fc_getPeerByClass (_obj, FC_ERROR_TEXT_CLASS);
}

/**
 * Returns a component's container element of the error message icon.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @return the container element
 * @type HTMLElement
 */
function fc_getErrorImgContainer (_obj)
{
	return fc_getPeerByClass (_obj, FC_MESSAGE_ICON_CLASS);
}

/**
 * Returns a component's container element of the specified CSS class.
 * Creates a new one if the <code>_create</code> parameter is <code>true</code>
 * and one does not already exist.
 * @member FormComponent
 * @private
 * @param _obj an HTML element within a component, used to find the component's container.
 * @param _class the CSS class
 * @param _create boolean indicating whether to create if nonexistent.
 * @return the container element
 * @type HTMLElement
 */
function fc_getContainer (_obj, _class, _create)
{
	var container = fc_getPeerByClass (_obj, _class);

	if (!container && _create) {
		container = document.createElement ('DIV');
		container.className = _class;
	}

	return container;
}

/**
 * Show the elements that display the warning message and image.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 */
function fc_showWarningMessage (_obj)
{
	fc_setMessageDisplay (_obj, false, true);
	var warning = fc_getWarnMsgContainer (_obj, false);

	if (warning && warning.hasChildNodes()) {
		show_element (warning);
		set_visible (warning, true);
		fc_showIcon (fc_getWarnImgContainer (_obj, false), warning);
	}
}

/**
 * Hide the information message in a component.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 */
function fc_hideInitialMessage (_obj)
{
	fc_hideInfoMsg (_obj);
	fc_setMessageDisplay (_obj, false, false);
}

/**
 * Hide the warning message in a component.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 */
function fc_hideWarningMessage (_obj)
{
	fc_hideInfoMsg (_obj);
	fc_setMessageDisplay (_obj, false, false);
}

/**
 * Hide or show the elements that display the warning message and image
 * depending on whether the input is empty.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 * @param _required boolean specification of whether the component requires
 *                  input from the user.
 */
function fc_updateWarningDisplay (_obj, _required)
{
	fc_hideErrorMsg (_obj);
	fc_hideInfoMsg (_obj);

	if (_obj.tagName != 'TR') {
		if (!_required || !fc_isEmptyField (_obj, _required))
			fc_hideWarningMessage (_obj);
		else
			fc_showWarningMessage (_obj);
	}
}

/**
 * Examines an element and indicates whether it is empty.
 * For elements that are not a checkbox or radio button, this checks whether
 * the 'value' is non-empty.
 * For checkboxes and radio buttons, they are only empty if the 'testSelection'
 * parameter is true and they are unchecked (not selected).
 * @private
 * @member FormComponent
 */
function fc_isEmptyField (element, testSelection)
{
	var empty;

	if (element.tagName == 'INPUT') {
		if (element.type == 'radio')
			empty = !get_which_radio (element);
		else if (element.type == 'checkbox')
			empty = testSelection && !element.checked;
		else
			empty = (element.value.length == 0);
	}
	else
		empty = (element.value.length == 0);

	return empty;
}

/**
 * Hide the information message in a component.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 */
function fc_hideInfoMsg (_obj)
{
	set_visible (fc_getInfoMsgContainer (_obj), false);
	set_visible (fc_getInfoImgContainer (_obj), false);
}

/**
 * Hide the warning message in a component.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 */
function fc_hideWarnMsg (_obj)
{
	set_visible (fc_getWarnMsgContainer (_obj), false);
	set_visible (fc_getWarnImgContainer (_obj), false);
}

/**
 * Hide the error message in a component.
 * @private
 * @member FormComponent
 * @param _obj an HTML element within a component, used to find the component's container.
 */
function fc_hideErrorMsg (_obj)
{
	return;
}

/**
 * Recursively copies all descendants of an HTML element to another HTML element.
 * @private
 * @member FormComponent
 * @param srcParent the HTML element from which to copy.
 * @param destParent the HTML element to which to copy.
 */
function fc_copyChildren (srcParent, destParent)
{
	// Copy all label contents.
	var ch = srcParent.firstChild;

	while (ch) {
		destParent.appendChild (ch.cloneNode (true));
		ch = ch.nextSibling;
	}
}

/**
 * Adds padding to the bottom of the element named 'overTable'.
 * @private
 * @member FormComponent
 */
function fc_addTablePadding()
{
	var table = document.getElementById ('overTable');

	if (table)
		table.style.paddingBottom = '45px';
}

/**
 * Indicates whether the current browser is IE on a Mac.
 * @private
 * @member FormComponent
 * @return <code>true</code> if the browser is IE on a Mac.
 * @type boolean
 */
function fc_isMacIE()
{
	return ((navigator.appName == 'Microsoft Internet Explorer')
				  && (navigator.userAgent.toLowerCase().indexOf ('mac') != -1));
}

/**
 * Constructs an FCDynamicMessageInfo object that contains a DOM element which
 * contains the content of a message.
 * @class Associates an HTML message container with an HTML input element.
 * @param _inputEl the HTML input element within the form component row.
 * @param _msgHolder the element that contains the message content.
 */
function FCDynamicMessageInfo (_inputEl, _msgHolder)
{
	this.inputElement = _inputEl;
	this.messageHolder = _msgHolder;
}

/**
 * Given an array of possible messages to display in an element, this method
 * initializes the size of the element's row to be large enough to fit each
 * message.
 * @param _msgInfoArray an array of FCDynamicMessageInfo objects.
 * @member FormComponent
 */
function fc_setDimensions(_msgInfoArray, _holderClassName)
{
	if (!_msgInfoArray || !_msgInfoArray.length)
		return;

	var input = _msgInfoArray[0].inputElement;
	var msgCell = fc_getPeerByClass (input, 'FormInfoMsg');
	var imgCell = fc_getPeerByClass (input, 'FormMsgIcon');
	var table = getAncestor ('TABLE', msgCell);

	if (msgCell && imgCell) {

		// Temporary element added after the table in order to measure the
		// height of the table.
		var elHide = document.createElement ('DIV');
		table.parentNode.insertBefore (elHide, table.nextSibling);

		var maxHeight = msgCell.style.height;
		var maxWidth = msgCell.style.width;
		var left = msgCell.offsetLeft;
		var msgClone = msgCell.cloneNode (true);

		// Get the desired size of each level 'ask' message.
		for (var m = 0; (m < _msgInfoArray.length); m++) {

			// Put the message in the appropriate table cell.
			var input = _msgInfoArray[m].inputElement;
			var holder = _msgInfoArray[m].messageHolder;
			fc_showHTMLBlock (msgCell, holder, msgCell);

			// Maintain max. width and height.

			if (elHide.offsetTop > maxHeight)
				maxHeight = elHide.offsetTop;

			if (msgCell.offsetWidth > maxWidth)
				maxWidth = msgCell.offsetWidth;
		}

		table.parentNode.removeChild (elHide);
		removeChildren (msgCell);

		// Update the table cell with the max. size.
		maxWidth += 5; // not sure why, but IE needs this
		msgCell.height = msgCell.style.height = maxHeight;
		msgCell.width = maxWidth;

		// When the message text is placed in the msgCell in response to the user
		// actions, the imgCell will be emptied and the text will have the maxWidth
		// available.  But until then, set the msgCell width to the maxWidth less
		// the width of the initial content of the imgCell.
		var minWidth = maxWidth - left;

		if (minWidth && minWidth > 0) {
			// msgCell.style.width = minWidth + 'px';
			msgCell.style.width = maxWidth + 'px';
			msgCell.style.minWidth = minWidth + 'px';
			msgCell.appendChild (fc_makeSpacerImg (minWidth));
		}

		// Restore original content (e.g., error icon and message);
		fc_copyChildren (msgClone, msgCell);
		show_element (imgCell);

		var container = fc_getContainer (input, _holderClassName, true);
		container.nohide = true;
		msgCell.appendChild (container);
		set_visible (msgCell, true);
	}
}

/**
 * Displays a block of HTML in the column reserved for messages.
 * @private
 * @param obj an HTML element within the component, used to find the component container.
 * @param message the HTML container in which to display the HTML block.
 * @param contentHolder an HTML container of the content to put into the display area.
 * @member FormComponent
 */
function fc_showHTMLBlock (obj, contentHolder, message)
{
	var icon = fc_getPeerByClass (obj, 'FormMsgIcon');

	if (message) {
		var hide = true;
		var ch = icon.childNodes;

		for (var i = 0; (hide && (i < ch.length)); i++)
			if (ch[i].tagName == 'IMG' && ch[i].src.indexOf ('error') >= 0 && !isOfClass (ch[i], 'HiddenErrorImage'))
				hide = false;

		if (hide)
			hide_element (icon);

		removeChildren (message);

		if (message.style.width) {
			var spacer = fc_makeSpacerImg (parseInt (message.width));
			message.appendChild (spacer);
		}

		if (contentHolder && contentHolder.hasChildNodes()) {
			fc_copyChildren (contentHolder, message);
		}

		set_visible (message, true);
		show_element (message);
	}
}

/**
 * Shows some message HTML in the column reserved for messages.
 * @member FormComponent
 * @param _el an HTML element within a component, used to find the component's container.
 * @param _msgHolder an HTML element containing the HTML message to display.
 * @param _destContainerClass the CSS class of the HTML element in which to
 *        display the message.
 */
function fc_showOtherMessage (_el, _msgHolder, _destContainerClass)
{
	var infoImgCell = fc_getPeerByClass (_el, 'FormMsgIcon');

	if (infoImgCell) {
		fc_hideInitialMessage (_el);

		// Section 508 Compliance is important, too.
		// This bit gets the value from the alt of the spacer gif above,
		// concatenates the value that is to display in the message area on the
		// right, and reinserts the combined value into the alt attribute, thus
		// giving us 508 compliance.
		var holder = fc_getPeerByClass (_el, 'ExpandedMessageHolder');

		if (holder)
			holder.alt = holder.alt + ' ' + fc_getElementText (_msgHolder);

		var cont = fc_getContainer (_el, _destContainerClass, true);
		fc_hideChildren (cont.parentNode);

		if (_msgHolder)
			fc_showHTMLBlock (infoImgCell, _msgHolder, cont);
		else
			set_visible (cont, false);
	}
}

/**
 * Finds data form elements' preview mode 'edit' buttons and attaches event
 * handlers to them.
 * @private
 * @member FormComponent
 */
function fc_activateEditButtons()
{
	var btnTypes = ['INPUT', 'BUTTON'];

	for (var t = 0; (t < btnTypes.length); t++) {
		var btns = document.getElementsByTagName (btnTypes[t]);

		if (btns.length < 300) { // IE is VERY slow with large arrays
			for (var b = 0; (b < btns.length); b++) {
				if (isOfClass (btns[b], FC_EDIT_BUTTON_CLASS))
					Utils.addEvent (btns[b], 'click', fc_editComponent);
			}
		}
	}
}

/**
 * Finds data form elements' input elements and attaches event
 * handlers to them.
 * @private
 * @member FormComponent
 */
function fc_activateFormInputs()
{
	var containerTypes = ['TD', 'DIV'];

	for (var t = 0; (t < containerTypes.length); t++) {
		var containers = document.getElementsByTagName (containerTypes[t]);

		if (containers.length < 1000) { // IE is VERY slow with large arrays
			for (var c = 0; (c < containers.length); c++) {
				if (isOfClass (containers[c], FC_INPUT_CLASS))
					fc_activateInputs (containers[c]);
			}
		}
	}
}

/**
 * Finds data form elements' input elements and attaches event
 * handlers to them.
 * @private
 * @member FormComponent
 * @param _container an HTML element within a component, used to find the component's container.
 */
function fc_activateInputs (_container)
{
	var required = fc_getPeerByClass (_container, FC_REQUIRED_CLASS) ? true : false;
	var inputTypes = ['INPUT', 'BUTTON', 'SELECT', 'TEXTAREA'];

	for (var t = 0; (t < inputTypes.length); t++) {
		var inputs = _container.getElementsByTagName (inputTypes[t]);

		if (inputs.length < 300) { // IE is VERY slow with large arrays
			for (var i = 0; (i < inputs.length); i++) {
				if ((inputs[i].tagName != 'INPUT') || (inputs[i].type != 'hidden'))
					fc_registerComponent (inputs[i], required);
			}
		}
	}
}

/**
 * Initialize form components.
 * @private
 * @member FormComponent
 * @param _evt the event that triggered the call.  In some browsers (e.g., IE),
 *             this parameter is undefined and the event is in a global variable
 *             named 'event'.
 */
function fc_init (_evt)
{

	// This initializes some default, English text messages.
	// They should be overridden in a template somewhere by calling fc_setTextMessages()
	// and passing text obtained from a message catalog.
	if (!fc_globalMessages) {
		fc_globalMessages = new FCGlobalMessages(
			'Information Cue',
			'Warning Cue',
			'This is Required.'
		);
	}

	fc_activateFormInputs();
	fc_activateEditButtons();

	// On MSIE on Mac, the table overflows onto the buttons at the bottom.
	if (fc_isMacIE()) {
		fc_addTablePadding();
	}
}

// -----------------------------------------------------------------
// In-line code:
// -----------------------------------------------------------------

addOnLoadHandler (fc_init);

