/*	***********  Menu Classes  ***********
 *	Generic implementation code only.  Specific content should go in a separate file.
 *	
 *	08-MAY-2006 - JMB - Created
 */


MENU_SHOW_DELAY_MS = 400;
MENU_HIDE_DELAY_MS = 700;  // hide should always be longer than show (logic problems if not)
MENU_HIDE_IMMEDIATE_DELAY_MS = 250;  // hide should always be longer than show (logic problems if not)



/*	***********  FL_MenuGroup  ***********
 *	A group of menu items in a single "pane".  There can be many MenuGroups within one menu system.
 *	A MenuGroup always travels together, toggling its display as one unit.
 */
function FL_MenuGroup(elementId, parentGroupObj)
{
	if (typeof(this.containerType) == 'undefined')  // careful not to re-set this if a derived class set it prior to running this constructor
		this.containerType = 'FL_MenuGroup';
	this.bSuppressElementWarnings = true;  // set before constructor to ignore set-element warnings

	this.baseConstructor = ViewContainer;
    this.baseConstructor(elementId);
    delete this.baseConstructor;
    this.childConstructor = FL_MenuItem;

	this.IsTopLevel = FL_MenuGroup_IsTopLevel;
	this.Render = FL_MenuGroup_Render;
	this.IsRendered = function() {return this.bIsRendered;};
	this.LoadData = function() {};  // Menus don't participate in the data-reload model (too expensive)
	this.Localize_local = FL_MenuGroup_Localize;
	this.RepositionNextActivate = FL_MenuGroup_RepositionNextActivate;
	this.SelectItem = FL_MenuGroup_SelectItem;
	this.ActivateRequest = FL_MenuGroup_ActivateRequest;
	this.DeactivateRequest = FL_MenuGroup_DeactivateRequest;
	this.Activate = FL_MenuGroup_Activate;
	this.Deactivate = FL_MenuGroup_Deactivate;
	this.ActionMouseOver = FL_MenuGroup_ActionMouseOver;
	this.ActionMouseOut = FL_MenuGroup_ActionMouseOut;
	this.UpdateBranchFocusCount = FL_MenuGroup_UpdateBranchFocusCount;
	this.SetCssClassForMenuGroups = FL_MenuGroup_SetCssClassForMenuGroups;
	this.Highlight = FL_MenuGroup_Highlight;

	this.GetMetrics = FL_MenuGroup_GetMetrics;
	this.Position = FL_MenuGroup_Position;

	if (typeof(parentGroupObj) != 'undefined')
		parentGroupObj.AssignChild(this);

	this.bMenuChild = false;
	this.bMenuBarChild = false;
	this.parentMenuItem = null;
	this.parentMenuGroup = null;
	this.topLevelMenuGroup = null;
	this.bRequested = false;
	this.bMouseFocus = false;
	this.bActive = false;
	this.bChildMouseFocus = false;
	this.branchFocusCount = 0;
	this.showTimer = null;
	this.hideTimer = null;
	this.bflowingLeft = false;
	this.bMustPositionOnNextActivate = true;
	this.bLocalizedFirstTime = false;
	this.iframeElement = null;
	this.strCssClassForMenuGroups = null;
	this.bIsRendered = false;

	this.metrics = [];
	this.metrics.bMetricsCalculated = false;
	this.metrics.windowMarginLeft = 0;
	this.metrics.windowMarginRight = 0;
	this.metrics.windowMarginTop = 0;
	this.metrics.windowMarginBottom = 0;
	this.metrics.menuBarChildOffsetLeft = 0;
	this.metrics.menuBarChildOffsetTop = 0;
	this.metrics.menuGroupChildOffsetLeft = 0;
	this.metrics.menuGroupChildOffsetTop = 0;
	this.metrics.menuGroupMaxWidth = 0;
}

function FL_MenuGroup_IsTopLevel()
{
	return(this.containerType == 'FL_MenuBar');
}

function FL_MenuGroup_Render()
{
	if (null !== this.Parent  &&  null !== this.Parent.Parent  &&  typeof(this.Parent.Parent.containerType) != 'undefined')
	{
		var conType = this.Parent.Parent.containerType;
		this.bMenuChild = (conType == 'FL_MenuGroup'  ||  conType == 'FL_MenuBar');
		this.bMenuBarChild = (conType == 'FL_MenuBar');

		if (this.bMenuChild)
		{
			this.parentMenuItem = this.Parent;
			this.parentMenuGroup = this.Parent.Parent;
			if (this.bMenuBarChild)
				this.topLevelMenuGroup = this.parentMenuGroup;
			else
				this.topLevelMenuGroup = this.parentMenuGroup.topLevelMenuGroup;
		}
	}

	if ('FL_MenuBar' != this.containerType  &&  null === this.parentMenuItem)
		FL_ASSERT(false, 'Cannot get reference to parent menu item. (elementID='+this.GetElementId()+')');
	if ('FL_MenuBar' != this.containerType  &&  null === this.parentMenuGroup)
		FL_ASSERT(false, 'Cannot get reference to parent menu group. (elementID='+this.GetElementId()+')');
	if ('FL_MenuBar' != this.containerType  &&  null === this.topLevelMenuGroup)
		FL_ASSERT(false, 'Cannot get reference to top-level menu group. (elementID='+this.GetElementId()+')');

	//	Try to inherit an assigned css class from the topmost menu group.
	var ancestorGroup = this.parentMenuGroup;
	var bFoundCssClassForGroups = false;
	while(!bFoundCssClassForGroups  &&  typeof(ancestorGroup) != 'undefined'  &&  null !== ancestorGroup)
	{
		if (null !== ancestorGroup.strCssClassForMenuGroups)
		{
			bFoundCssClassForGroups = true;
			this.strCssClassForMenuGroups = ancestorGroup.strCssClassForMenuGroups;
		}
		else
		{
			ancestorGroup = ancestorGroup.parentMenuGroup;
		}
	}
	this.GetMetrics();

	this.elementObj = document.createElement('DIV');
	this.elementObj.id = this.GetElementId();
	this.elementObj.className = 'Menu_All Menu_Group ' + this.strCssClassForMenuGroups;
	this.elementObj.style.display = 'none';
	this.elementObj.style.zIndex = 99;  // highest possible (on top of all other elements)

	//	append before doing anything with children, so they don't end up higher in the doc.
	window.document.body.appendChild(this.elementObj);

	this.iframeElement = document.createElement('IFRAME');
	this.iframeElement.style.display = 'none';
	this.iframeElement.style.zIndex = 98;  // underneath group tiles
	this.iframeElement.style.position = 'absolute';
	this.iframeElement.className = 'Menu_Iframe_Mask';
	this.iframeElement.id = this.GetElementId() + '_iframe_mask';
	window.document.body.appendChild(this.iframeElement);
	
	for (var i=0; i<this.ChildList.length; i++)
	{
		var childElem = this.ChildList[i].Render();
		if (this.elementObj)
			this.elementObj.appendChild(childElem);
	}

	eval('this.elementObj.onmouseover = function() { window["'+this.windowObjectName+'"].ActionMouseOver(); };');
	eval('this.elementObj.onmouseout = function() { window["'+this.windowObjectName+'"].ActionMouseOut(); };');

	this.bIsRendered = true;
}


function FL_MenuGroup_Localize()
{
	//	Set a flag so we know we should reposition the next time we display this group.
	this.bMustPositionOnNextActivate = true;
	this.bLocalizedFirstTime = true;
}

function FL_MenuGroup_RepositionNextActivate()
{
	this.bMustPositionOnNextActivate = true;
}


function FL_MenuGroup_SelectItem(menuItemObj)
{
	for (var i=0; i<this.ChildList.length; i++)
	{
		if ('FL_MenuSubgroup' == this.ChildList[i].containerType)
		{
			var subgroup = this.ChildList[i];
			for (var sgItem=0; sgItem<subgroup.ChildList.length; sgItem++)
			{
				if (subgroup.ChildList[sgItem] === menuItemObj)
					subgroup.ChildList[sgItem].Select();
				else
					subgroup.ChildList[sgItem].Deselect(true);
			}
		}
		else
		{
			if (this.ChildList[i] === menuItemObj)
				this.ChildList[i].Select();
			else
				this.ChildList[i].Deselect(true);
		}
	}
}


//	ActivateRequest/DeactivateRequest is triggered by mouse activity.
function FL_MenuGroup_ActivateRequest(bImmediate)
{
	if (typeof(bImmediate) == 'undefined')   bImmediate = false;
	this.bRequested = true;
	clearTimeout(this.showHideTimer);
	if (bImmediate)
		this.Activate(bImmediate);
	else
//		this.showHideTimer = safe_setTimeout("window['"+this.windowObjectName+"'].Activate();", MENU_SHOW_DELAY_MS, 'FL_MenuGroup_ActivateRequest');
		this.showHideTimer = setTimeout("window['"+this.windowObjectName+"'].Activate();", MENU_SHOW_DELAY_MS);
}

function FL_MenuGroup_DeactivateRequest(bImmediate)
{
	if (typeof(bImmediate) == 'undefined')   bImmediate = false;
	this.bRequested = false;
	clearTimeout(this.showHideTimer);
	if (bImmediate)
		// even "immediate" must have a small delay, so that mouse movement quickly crossing from the menu bar to the panel doesn't result in an immediate deactivation
		this.showHideTimer = setTimeout("window['"+this.windowObjectName+"'].Deactivate();", MENU_HIDE_IMMEDIATE_DELAY_MS);
		//this.Deactivate(bImmediate);
	else
		this.showHideTimer = setTimeout("window['"+this.windowObjectName+"'].Deactivate();", MENU_HIDE_DELAY_MS);
}

//	Activate/Deactivate should only execute when we really mean it... shouldn't be tied directly to mouse activity.
//	We have to always "let the last one win".  When we execute these after the delay, always check to 
//	make sure the opposite wasn't requested in the interim.
function FL_MenuGroup_Activate()
{
	//  Make sure the condition that got us here is still true... if not, abort to prevent flicker & logic problems.
	if (!this.bRequested)  // current realtime state is Deactivate
		return;

	if (this.bActive)  // skip if this already happened
		return;
	this.bActive = true;

	//	All clear.  If we got this far, then there are no other outstanding requests.
	clearTimeout(this.showHideTimer);
	this.showHideTimer = null;

	this.elementObj.style.display = '';
	this.iframeElement.style.display = '';

	//	If we localized since the last activation, reposition the group.
	if (this.bMustPositionOnNextActivate)
		this.Position();

	//	notify all menu items in this group that they are about to be shown
	for (var i=0; i<this.ChildList.length; i++)
	{
		this.ChildList[i].OnBeforeDisplay();
	}

	this.elementObj.style.visibility = 'visible';  // last, after positioning is done
	this.iframeElement.style.visibility = 'visible';
}

function FL_MenuGroup_Deactivate(bForce)
{
	//	bForce=true means disregard all safety checks, except the bActive performance check.
	if (typeof(bForce) == 'undefined')   bForce = false;

	if (!this.bActive)  // skip if this already happened
		return;

	//  Make sure the condition that got us here is still true.
	if (!bForce  &&  this.bRequested)  // current realtime state is Activate
		return;

	if (!bForce  &&  this.branchFocusCount > 0)
		return;

	//  Suppress hide if either this or a *child* menu group has focus.
	if (!bForce  &&  (this.bMouseFocus  ||  this.bChildMouseFocus))
		return;

	//	All clear.  If we got this far, then there are no other outstanding requests.
	clearTimeout(this.showHideTimer);
	this.showHideTimer = null;

	//	top-level menu bar is never "inactive" and is never hidden by deactivate events
	if (!this.IsTopLevel())
	{
		this.bActive = false;
		this.iframeElement.style.visibility = 'hidden';
		this.iframeElement.style.display = 'none';
		this.elementObj.style.visibility = 'hidden';
		this.elementObj.style.display = 'none';
	}

	//  Immediately de-select all child menu items.  This will have the additional effect of 
	//	cascading a Deactivate request to any Menu Groups below each item.
	for (var i=0; i<this.ChildList.length; i++)
	{
		this.ChildList[i].Deselect(true);  // true = immediate
	}
}

function FL_MenuGroup_ActionMouseOver()
{
	this.UpdateBranchFocusCount(1);
	//  Set the Focus flag to protect us from being hidden by parents losing focus
	this.bMouseFocus = true;
	if (this.bMenuChild)
	{
		this.parentMenuGroup.bChildMouseFocus = true;
	}
}

function FL_MenuGroup_ActionMouseOut()
{
	this.UpdateBranchFocusCount(-1);
	this.bMouseFocus = false;
	if (this.bMenuChild)
	{
		this.parentMenuGroup.bChildMouseFocus = false;
	}

	// make the whole system double-check its focus
	if (this.bMenuChild)
		this.topLevelMenuGroup.DeactivateRequest();
	else
		this.DeactivateRequest();
}

function FL_MenuGroup_UpdateBranchFocusCount(incr)
{
	this.branchFocusCount += incr;

	// cascade up
	if (null !== this.parentMenuGroup)
		this.parentMenuGroup.UpdateBranchFocusCount(incr);
}

function FL_MenuGroup_GetMetrics()
{
	if (null !== this.parentMenuGroup  &&  this.parentMenuGroup.metrics.bMetricsCalculated)
	{
		this.metrics = this.parentMenuGroup.metrics;
		// no need to set bMetricsCalculates, b/c this is a reference, not a copy
	}
	else
	{
		var tempMetricsDiv = document.createElement('DIV');
		document.body.appendChild(tempMetricsDiv);  // must be in doc tree to interpret styles

		tempMetricsDiv.className = 'MENU_WINDOW_LAYOUT';
		this.metrics.windowMarginLeft = (fl_getIntFromCssPx(tempMetricsDiv, 'marginLeft', 'margin-left')  ||  0);
		this.metrics.windowMarginRight = (fl_getIntFromCssPx(tempMetricsDiv, 'marginRight', 'margin-right')  ||  0);
		this.metrics.windowMarginTop = (fl_getIntFromCssPx(tempMetricsDiv, 'marginTop', 'margin-top')  ||  0);
		this.metrics.windowMarginBottom = (fl_getIntFromCssPx(tempMetricsDiv, 'marginBottom', 'margin-bottom')  ||  0);

		tempMetricsDiv.className = 'MENU_BAR_CHILD_OFFSET';
		this.metrics.menuBarChildOffsetLeft = (fl_getIntFromCssPx(tempMetricsDiv, 'left', 'left')  ||  0);
		this.metrics.menuBarChildOffsetTop = (fl_getIntFromCssPx(tempMetricsDiv, 'top', 'top')  ||  0);

		tempMetricsDiv.className = 'MENU_GROUP_CHILD_OFFSET';
		this.metrics.menuGroupChildOffsetLeft = (fl_getIntFromCssPx(tempMetricsDiv, 'left', 'left')  ||  0);
		this.metrics.menuGroupChildOffsetTop = (fl_getIntFromCssPx(tempMetricsDiv, 'top', 'top')  ||  0);

		tempMetricsDiv.className = 'MENU_GROUP_MAX_DIMENSIONS';
		this.metrics.menuGroupMaxWidth = (fl_getIntFromCssPx(tempMetricsDiv, 'width', 'width')  ||  null);

		document.body.removeChild(tempMetricsDiv);
		tempMetricsDiv = null;
		this.metrics.bMetricsCalculated = true;
	}
}

function FL_MenuGroup_Position()
{
	//	If this is the child of another menu item, reposition it based on the metrics of the parent.
	//	If not (parent is some other container, or no parent) then don't change anything.
	if (null !== this.Parent  &&  this.bMenuChild)
	{
		var desiredLeft = 0;
		var desiredTop = 0;
		var maxLeft = this.metrics.windowMarginLeft;
		var maxRight = (window.document.documentElement.offsetWidth - this.metrics.windowMarginRight);

		//	Get the real width by temporarily putting the layout at 0 (left edge), so the window boundaries don't constrain it.
		this.elementObj.style.position = 'absolute';
		this.elementObj.style.left = '0px';
		this.elementObj.style.width = 'auto';  // in case we set a width last time

		//	set a width if we exceed the max (will make text wrap)
		if (null !== this.metrics.menuGroupMaxWidth  &&  this.metrics.menuGroupMaxWidth > 0  &&
			(this.elementObj.offsetWidth > this.metrics.menuGroupMaxWidth))
			this.elementObj.style.width = this.metrics.menuGroupMaxWidth + 'px';

		var posOnScreen = this.Parent.GetPositionOnScreen();

		var realWidth = this.elementObj.offsetWidth;
		this.bflowingLeft = false;
		if (this.bMenuBarChild)  // 1st group under menu bar item, show directly underneath
		{
			var parentLeft = posOnScreen.left;
			var parentBottom = posOnScreen.bottom;
			desiredLeft = parentLeft + this.metrics.menuBarChildOffsetLeft;
			desiredTop = parentBottom + this.metrics.menuBarChildOffsetTop;

			//	Flow left if we're at or over the right edge (or if our parent group flowed left.)
			if (this.parentMenuGroup.bflowingLeft  ||  (desiredLeft+realWidth) >= maxRight)
			{
				this.bflowingLeft = true;
				desiredLeft = maxRight - this.elementObj.offsetWidth;  // as far right as possible
			}
		}

		else  // child of a deep item, show to the right of item
		{
			var parentRight = posOnScreen.right;
			var parentTop = posOnScreen.top;
			desiredLeft = parentRight + this.metrics.menuGroupChildOffsetLeft;
			desiredTop = parentTop + this.metrics.menuGroupChildOffsetTop;

			//	Flow left if we're at or over the right edge
			if (  (desiredLeft+realWidth) >= maxRight  )
			{
				this.bflowingLeft = true;
				var parentLeft = posOnScreen.left;
				desiredLeft = ((parentLeft - this.elementObj.offsetWidth) - this.metrics.menuGroupChildOffsetLeft) + 1;  // reverse offset
			}
		}

		//  make sure it's inside left/top margins
		desiredLeft = Math.max(desiredLeft, this.metrics.windowMarginLeft);
		desiredTop = Math.max(desiredTop, this.metrics.windowMarginTop);
		this.elementObj.style.left = desiredLeft+'px';
		this.elementObj.style.top = desiredTop+'px';

		this.iframeElement.style.left = desiredLeft+'px';
		this.iframeElement.style.top = desiredTop+'px';
		this.iframeElement.style.width = this.elementObj.offsetWidth;
		this.iframeElement.style.height = this.elementObj.offsetHeight;

		//	toggle display off/on to wake up IE
		var prevElemDisplayVal = this.elementObj.style.display;
		var prevFrameDisplayVal = this.iframeElement.style.display;
		this.elementObj.style.display = 'none';
		this.iframeElement.style.display = 'none';
		this.elementObj.style.display = prevElemDisplayVal;
		this.iframeElement.style.display = prevFrameDisplayVal;

		this.bMustPositionOnNextActivate = false;  // suppress positioning until the next language change (performance)
	}
}

//function FL_MenuGroup_GetLeftPx()
//{
//	if (null === this.elementObj)   return 0;
//	return this.elementObj.offsetLeft;
//}
//function FL_MenuGroup_GetRightPx()
//{
//	if (null === this.elementObj)   return 0;
//	return (this.elementObj.offsetLeft + this.elementObj.offsetWidth);
//}
//function FL_MenuGroup_GetTopPx()
//{
//	if (null === this.elementObj)   return 0;
//	return this.elementObj.offsetTop;
//}
//function FL_MenuGroup_GetBottomPx()
//{
//	if (null === this.elementObj)   return 0;
//	return (this.elementObj.offsetTop + this.elementObj.offsetHeight);
//}

function FL_MenuGroup_SetCssClassForMenuGroups(strClassName)
{
	if (this.IsRendered())
		FL_ASSERT(false, 'FL_MenuGroup_SetCssClassForMenuGroups() must be configured before rendering.');
	this.strCssClassForMenuGroups = strClassName;
}

function FL_MenuGroup_Highlight(bHighlight)
{
	if (null !== this.Parent  &&  typeof(this.Parent.Highlight) != 'undefined')
	{
		this.Parent.Highlight(bHighlight);
	}
}







/*	***********  FL_MenuBar  ***********
 *	FL_MenuBar is a special MenuGroup that is meant to be the top-level grouping of menus,
 *	usually visible as a horizontal area in the app.  Differs from MenuGroup:
 *	  -	rendered in a horizontal layout
 *	  -	is not initially hidden
 *	  -	takes an EXISTING element as "elementId" instead of generating it.  **The
 *		document is responsible for laying out this element**, which becomes the 
 *		visual boundaries of the menu bar.
 *
 *	  -	If element passed in is not valid, this will try to attach to a ViewContainer parent.
 *	  -	If there is no parent, this will attach directly to the document body.
 */
function FL_MenuBar(elementId, parentGroupObj)
{
	if (typeof(this.containerType) == 'undefined')  // careful not to re-set this if a derived class set it prior to running this constructor
		this.containerType = 'FL_MenuBar';

	this.baseConstructor = FL_MenuGroup;
    this.baseConstructor(elementId, parentGroupObj);
    delete this.baseConstructor;
    this.childConstructor = FL_MenuItem;
    
	this.Render = FL_MenuBar_Render;
	this.Position = FL_MenuBar_Position;
	this.baseShow = this.Show;  // we need more control over when the base Show behavior happens
	this.Show = FL_MenuBar_Show;
	this.Hide = FL_MenuBar_Hide;
	this.Localize_local = FL_MenuBar_Localize;

	this.bDeferPosition = false;
	this.bPositioned = false;
	this.bActive = true;  // the top-level menu bar is always "active"
	this.bChildElementsOriginalHeights = null;
}

function FL_MenuBar_Render()
{
	this.GetMetrics();

	fl_addClassToElement(this.elementObj, ['Menu_All', 'Menu_Group', 'Menu_Bar'], ' ');

	var childElemType = ('TABLE' == this.elementObj.tagName)  ?  'TD'  :  'DIV';
	var attachToElem = null;
	if ('TABLE' == this.elementObj.tagName)
	{
		var trnl = this.elementObj.getElementsByTagName('TR');
		if (trnl.length > 0)
		{
			attachToElem = trnl[0];
		}
		else
		{
			var tb = document.createElement('TBODY');
			this.elementObj.appendChild(tb);
			attachToElem = document.createElement('TR');
			tb.appendChild(attachToElem);
		}
	}
	else
	{
		attachToElem = this.elementObj;
	}

	for (var i=0; i<this.ChildList.length; i++)
	{
		var childElem = this.ChildList[i].Render(childElemType);
		attachToElem.appendChild(childElem);
	}

	eval('this.elementObj.onmouseover = function() { window["'+this.windowObjectName+'"].ActionMouseOver(); };');
	eval('this.elementObj.onmouseout = function() { window["'+this.windowObjectName+'"].ActionMouseOut(); };');

	this.IsRendered = true;
	this.Position();
	this.elementObj.style.display = 'none';  // let the user of this class decide when to show

	return this.elementObj;
}

function FL_MenuBar_Position()
{
	if (!this.IsShowing())
	{
		this.bDeferPosition = true;
		return;
	}

	//	menu bars only need to be positioned the first time.
//	if (this.bPositioned)
//		return;
	this.bPositioned = true;

//	fl_spaceChildElements(this.elementObj, 'width', true);  // true=use proportions

	//	We want to re-space the heights of child menu items, but only if they were originally 
	//	specified (in the style sheets) as "auto".  Problem is, once we respace, they're not
	//	auto anymore.  So we have to remember whether they were auto or not the first time.
	if (null === this.bChildElementsOriginalHeights)
	{
		this.bChildElementsOriginalHeights = [];
		for (var i=0; i<this.ChildList.length; i++)
		{
			var childElem = this.ChildList[i].GetElementObj();
			if (null !== childElem  &&  '' !== childElem.id)
				this.bChildElementsOriginalHeights[childElem.id] = fl_getComputedStyle(childElem, 'height', 'height');
		}
	}
	for (var i=0; i<this.ChildList.length; i++)
	{
		var childElem = this.ChildList[i].GetElementObj();
		if (null !== childElem  &&  '' !== childElem.id  &&  this.bChildElementsOriginalHeights[childElem.id] == 'auto')
			fl_fitElementToParent(childElem, this.elementObj, false, true);  // height but not width
	}
}

function FL_MenuBar_Show()
{
	//  We really don't want any cascading to children.  Show/Hide means something different to menus.
	//	This overrides (not extends) the base VC Show method, so we are careful to mimic all the things
	//	vc.Show normally does.

	if (!this.IsValid())
		return;

	if (this.bSuppressed)
		return;

	if (!this.PolicyVisible)
		return;

	this.bIsShowing = true; // base VC property

	//	if we tried to position before but were still hidden, do it now.
	if (this.bDeferPosition)
	{
		//  make invisible before activating layout (display="") or positioning
		this.elementObj.style.display = '';  // in case it was none (we're not going to change display)
		this.Position();
		this.elementObj.style.visibility = 'inherit';
	}
	else
	{
		this.elementObj.style.display = '';
	}
	this.bDeferPosition = false;
}

function FL_MenuBar_Hide()
{
	if (this.IsValid())
	{
		this.elementObj.style.visibility = 'hidden';
		this.bIsShowing = false;
	}
}

function FL_MenuBar_Localize()
{
	this.Position();
}











/*	***********  FL_MenuItem  ***********
 *	A single menu item that the user can select.  Cannot live by itself in the document,
 *	must be a child of a MenuGroup or MenuBar.  Menu items cannot be individually positioned,
 *	although they can be enabled/disabled or have their display toggled (for an add/remove
 *	effect.)
 */
function FL_MenuItem(stringId, parentGroupObj)
{
	if (typeof(this.containerType) == 'undefined')  // careful not to re-set this if a derived class set it prior to running this constructor
		this.containerType = 'FL_MenuItem';
	this.bSuppressElementWarnings = true;  // set before constructor to ignore set-element warnings

	//	Validate params and create IDs before running base constructor.
	this.internalId = fl_createIdFromText(stringId);
	if ('' === this.internalId)
	{
		FL_ASSERT(false, 'FL_MenuItem(stringId, ...) -- Invalid String ID.');
	}

    this.baseConstructor = TextContainer;
    this.baseConstructor((this.internalId + '_FL_MenuItem'), stringId);
    delete this.baseConstructor;

	this.DetermineIdentity = FL_MenuItem_DetermineIdentity;
	this.HasChildren = function() { return(this.ChildList.length > 0); };
	this.Render = FL_MenuItem_Render;
	this.LoadData = function() {};  // Menus don't participate in the data-reload model (too expensive)
	this.UpdateString = FL_MenuItem_UpdateString;
	this.Localize_local = FL_MenuItem_Localize;
	this.OnBeforeDisplay = FL_MenuItem_OnBeforeDisplay;
	this.Show = FL_MenuItem_Show;
	this.Hide = FL_MenuItem_Hide;
	this.Enable = FL_MenuItem_Enable;
	this.ActionMouseOver = FL_MenuItem_ActionMouseOver;
	this.ActionMouseOut = FL_MenuItem_ActionMouseOut;
	this.Select = FL_MenuItem_Select;
	this.Deselect = FL_MenuItem_Deselect;
	this.ActionClick = FL_MenuItem_ActionClick;
	this.SetAction = FL_MenuItem_SetAction;
	this.SetActionNavigate = FL_MenuItem_SetActionNavigate;
	this.HasAction = FL_MenuItem_HasAction;
	this.SetDialogStyle = FL_MenuItem_SetDialogStyle;
	this.SetBeginGroup = FL_MenuItem_SetBeginGroup;
	this.AddCssClassName = FL_MenuItem_AddCssClassName;
	this.RemoveCssClassName = FL_MenuItem_RemoveCssClassName;
	this.InitializeCssClassNames = FL_MenuItem_InitializeCssClassNames;
	this.__AddOrRemoveCssClassName = __FL_MenuItem_AddOrRemoveCssClassName;
	this.IsMenuBarItem = FL_MenuItem_IsMenuBarItem;
	this.Highlight = FL_MenuItem_Highlight;

//	this.GetLeftPx = FL_MenuItem_GetLeftPx;
//	this.GetRightPx = FL_MenuItem_GetRightPx;
//	this.GetTopPx = FL_MenuItem_GetTopPx;
//	this.GetBottomPx = FL_MenuItem_GetBottomPx;

	this.classArray = [];
	this.classArraySelected = [];
	this.classArray_additional = [];
	this.classArraySelected_additional = [];
	this.classString = null;
	this.classStringSelected = null;

	this.bMenuBarItem = false;
	this.bIdentityDetermined = false;
	this.bAboveSeparator = false;
	this.bBelowSeparator = false;
	this.bDialogStyle = false;
	this.scriptAction = null;
	this.navigateUrl = null;
	this.bDeferHide = false;
	this.bDeferEnableDisable = false;
	this.bDeferEnableDisableValue = false;
	this.bSelected = false;
	this.parentMenuGroupObj = null;

	//	The "IsShowing()" base property has too much baggage.  We need to track if the code *wants* this visible, not just whether it is visible right now.
	this.IsVisible = function() { return this.bVisible; };
	this.bVisible = true;

	if (typeof(parentGroupObj) != 'undefined')
		parentGroupObj.AssignChild(this);
}

function FL_MenuItem_DetermineIdentity()
{
	this.parentMenuGroupObj = ('FL_MenuSubgroup' == this.Parent.containerType)  ?  this.Parent.Parent  :  this.Parent;
	this.bMenuBarItem = (this.parentMenuGroupObj.containerType == 'FL_MenuBar');
	this.bIdentityDetermined = true;
}

function FL_MenuItem_IsMenuBarItem()
{
	if (!this.bIdentityDetermined)
	{
		this.DetermineIdentity();
	}
	return this.bMenuBarItem;
}

function FL_MenuItem_Render(childElemType)
{
	FL_ASSERT((null !== this.Parent), 'Does not have a parent container. (id='+this.internalId+')');

	this.DetermineIdentity();

	//	Create CSS class-name strings here ahead of time, for better performance (simple assignment faster than add-remove classes.)

	if (typeof(childElemType) == 'undefined')   childElemType = 'DIV';

	this.classArray = [];
	this.classArraySelected = [];
	this.classArray.push('Menu_Item');
	this.classArraySelected.push('Menu_Item_Selected');

	if (this.HasChildren())
	{
		this.classArray.push('Menu_Item_HasChildren');
		this.classArraySelected.push('Menu_Item_HasChildren_Selected');
	}

	if (this.IsMenuBarItem())
	{
		this.classArray.push('Menu_Bar_Item');
		this.classArraySelected.push('Menu_Bar_Item_Selected');
		if (this.HasChildren())
		{
			this.classArray.push('Menu_Bar_Item_HasChildren');
			this.classArraySelected.push('Menu_Bar_Item_HasChildren_Selected');
		}
	}

	if (0 == this.indexInParent)  // first item
	{
		this.classArray.push('Menu_Item_First');
		this.classArraySelected.push('Menu_Item_First');
	}
	if ((this.parentMenuGroupObj.ChildList.length-1) == this.indexInParent)  // last item
	{
		this.classArray.push('Menu_Item_Last');
		this.classArraySelected.push('Menu_Item_Last');
	}

	if (this.bAboveSeparator)
		this.classArray.push('Menu_Item_Above_Separator');
	if (this.bBelowSeparator)
		this.classArray.push('Menu_Item_Below_Separator');

	//	For items that are linked to a specific URL, check to see if we are the current URL.
	if (null !== this.navigateUrl  &&  '' !== this.navigateUrl)
	{
		var currentUrl = window.location.href;
		var thisItemUrl = this.navigateUrl;
		var qpos = currentUrl.indexOf('?');
		if (qpos >= 0)
			currentUrl = currentUrl.substr(0, qpos);
		qpos = thisItemUrl.indexOf('?');
		if (qpos >= 0)
			thisItemUrl = thisItemUrl.substr(0, qpos);
		if (currentUrl == thisItemUrl)
			this.Highlight(true);
	}

	this.elementObj = document.createElement(childElemType);
	this.elementObj.id = this.GetElementId();
	if (!this.IsVisible())  // a menu item might have "Hide" executed before the first "Render"
		this.elementObj.style.display = 'none';

	this.InitializeCssClassNames();
	this.elementObj.className = this.classString;

	eval('this.elementObj.onmouseover = function() { window["'+this.windowObjectName+'"].ActionMouseOver(); };');
	eval('this.elementObj.onmouseout = function() { window["'+this.windowObjectName+'"].ActionMouseOut(); };');

	var textElem = document.createElement('SPAN');
	textElem.className = 'Menu_Item_Text';
	this.elementObj.appendChild(textElem);

	if (this.HasAction())
	{
		var aElem = document.createElement('A');
		aElem.className = (this.IsMenuBarItem())  ?  'Menu_Bar_Item_Link'  :  'Menu_Item_Link';
		if (null !== this.navigateUrl  &&  '' !== this.navigateUrl)
		{
			aElem.href = this.navigateUrl;
		}
		else
		{
			eval('aElem.onclick = function() { window["'+this.windowObjectName+'"].ActionClick(); return false; };');
		}
		textElem.appendChild(aElem);
		this.SetTextElement(aElem);
	}
	else
	{
		this.SetTextElement(textElem);
	}

	//	were any hides or enable/disables executed before rendering
	if (this.bDeferEnableDisable)
		this.Enable(this.bDeferEnableDisableValue);
	if (this.bDeferHide)
		this.Hide();

	if (this.HasChildren())
	{
		this.ChildList[0].Render();  // can assume only one child
	}

	return this.elementObj;
}

function FL_MenuItem_AddCssClassName(strClassName, bSelected)
{
	this.__AddOrRemoveCssClassName(true, strClassName, bSelected);
}

function FL_MenuItem_RemoveCssClassName(strClassName, bSelected)
{
	this.__AddOrRemoveCssClassName(false, strClassName, bSelected);
}

function __FL_MenuItem_AddOrRemoveCssClassName(bAdd, strClassName, bSelected)
{
	// wrapped in one method b/c the logic to determine which array is 90% of the work.
	var arrayToManipulate = null;
	if (null !== this.classString)
	{
		//	class strings are already calculated, so we append to the real list.
		if (bSelected)
			arrayToManipulate = this.classArraySelected;
		else
			arrayToManipulate = this.classArray;
	}
	else
	{
		//	strings not yet calculated, so we append to the "additional" list so they don't end up 
		//	in front of the basic ones when initializing the strings for the first time.
		if (bSelected)
			arrayToManipulate = this.classArraySelected_additional;
		else
			arrayToManipulate = this.classArray_additional;
	}

	if (bAdd)
	{
		arrayToManipulate.push(strClassName);
	}
	else
	{
		var pos = fl_posInArray(arrayToManipulate, strClassName);
		if (pos >= 0)
		{
			arrayToManipulate.splice(pos, 1);
		}
	}

	if (null !== this.classString) // strings already calculated, so we have to do them again.
	{
		this.InitializeCssClassNames();
	}
}

function FL_MenuItem_InitializeCssClassNames()
{
	this.classArray = this.classArray.concat(this.classArray_additional);
	this.classArray_additional = null;

	this.classArraySelected = this.classArraySelected.concat(this.classArraySelected_additional);
	this.classArraySelected_additional = null;

	this.classString = this.classArray.join(' ');
	//	All "special" classes are written as _overrides_, not as total replacements.  Therefore the resulting Selected class strings 
	//	must contain duplicates of the regular class names as a baseline.
	this.classStringSelected = this.classString + ' ' + this.classArraySelected.join(' ');

	//	immediately reassign the class so we get realtime screen change
	if (null !== this.elementObj)
		this.elementObj.className = (this.bSelected) ? this.classStringSelected : this.classString;
}

function FL_MenuItem_UpdateString(stringId)
{
	//	When we want to change strings at runtime, it's important for the parent menu group to recalculate 
	//	its position, because the new string might take up a lot more/less space than the old.
	this.SetStringId(stringId);
	this.Localize();
	if (null !== this.parentMenuGroupObj)
	{
		this.parentMenuGroupObj.RepositionNextActivate();
	}
}

function FL_MenuItem_Localize()
{
	if (('' != this.textOverride) && ('undefined' != typeof(this.textOverride)))
	{
		this.SetValue(this.textOverride);
	}
	else if ('' != this.GetStringId())
	{
		//	this can be used simply w/o the localization framework, if the string ID passed in was the desired text.
		var display = (typeof(LocalizeString) != 'undefined')  ?  LocalizeString(this.GetStringId())  :  this.GetStringId();
		if (this.bDialogStyle)
			display += '...';
		this.SetValue(display);

	}
}

function FL_MenuItem_OnBeforeDisplay()
{
	//	can be used in subclasses to run extra logic when menu item appears
}

function FL_MenuItem_Show()
{
	if (!this.IsValid())
		return;

	this.bVisible = true;
	this.elementObj.style.display = '';
}

function FL_MenuItem_Hide()
{
	if (!this.IsValid())  // not rendered yet, set a flag so it happens later
	{
		this.bDeferHide = true;
		return;
	}

	this.bVisible = false;
	this.elementObj.style.display = 'none';
}

function FL_MenuItem_Enable(bEnable)
{
	if (typeof(bEnable) == 'undefined')
		bEnable = true;
	else
		bEnable = safeBool(bEnable);  // convert from string if needed ("true|false" or "1|0", etc.)

	if (!this.IsValid())
	{
		this.bDeferEnableDisable = true;
		this.bDeferEnableDisableValue = bEnable;
		return;
	}

	if (bEnable == this.bEnabled)  // no change, don't bother
		return;

	this.bEnabled = bEnable;
	if (bEnable)  // remove the disabled class
		this.AddCssClassName('Menu_Item_Disabled', false);
	else
		this.RemoveCssClassName('Menu_Item_Disabled', false);
	this.InitializeCssClassNames();

	this.elementObj.setAttribute('menuEnabled', (bEnable ? 'true' : 'false'));
}


//	Keep the Mouse movement and the resulting Selection changes as separate concepts.
//	Keyboard behaviors will run the Selection methods directly.

//	A simple mouseover=select / mouseout=deselect has lots of unintended side effects, because
//	moving from an item to its child group is technically a mouseout -- but we wouldn't want 
//	a de-selection in that case.
//	Best way to do this is for the mouseover to run a "select this" method on the *parent*, which 
//	would be responsible for de-selecting others.  This way there really is no mouseout event.

function FL_MenuItem_ActionMouseOver()
{
	this.parentMenuGroupObj.SelectItem(this);
}

function FL_MenuItem_ActionMouseOut()
{
}

function FL_MenuItem_Select(bImmediate)
{
	if (this.bEnabled)
	{
		this.bSelected = true;
		if (this.HasAction()  ||  this.HasChildren())
		{
			this.elementObj.className = this.classStringSelected;  // don't make dead items appear selected (no action or children)
		}
		if (this.HasChildren())
			this.ChildList[0].ActivateRequest(bImmediate);  // can assume only one child
	}
}

function FL_MenuItem_Deselect(bImmediate)
{
	this.bSelected = false;
	this.elementObj.className = this.classString;
	if (this.HasChildren())
		this.ChildList[0].DeactivateRequest(bImmediate);  // can assume only one child
}

function FL_MenuItem_SetAction(action)
{
	this.scriptAction = action;
	this.navigateUrl = null;
}

function FL_MenuItem_SetActionNavigate(url)
{
	this.navigateUrl = url;
	this.scriptAction = null;
	if (this.IsMenuBarItem())
	{
		this.AddCssClassName('Menu_Item_HasLink Menu_Bar_Item_HasLink', false);
		this.AddCssClassName('Menu_Item_HasLink_Selected Menu_Bar_Item_HasLink_Selected', true);
	}
	else
	{
		this.AddCssClassName('Menu_Item_HasLink', false);
		this.AddCssClassName('Menu_Item_HasLink_Selected', true);
	}
}

function FL_MenuItem_HasAction()
{
	return (
				(null !== this.scriptAction  &&  '' !== this.scriptAction)  ||
				(null !== this.navigateUrl  &&  '' !== this.navigateUrl)
			);
}

function FL_MenuItem_ActionClick()
{
	if (this.bEnabled  &&  this.bVisible)
	{
		if (!this.HasAction()  &&  this.HasChildren())  // grouping item with no action ... expand children on click
		{
			this.Select(true);  // true = expand children immedately.
		}
		else
		{
			if (this.parentMenuGroupObj.bMenuChild)
				this.parentMenuGroupObj.topLevelMenuGroup.DeactivateRequest(true); // immediately clear all menus

			if (null !== this.scriptAction  &&  '' !== this.scriptAction)
			{
//				safe_setTimeout(this.scriptAction, 100, 'FL_MenuItem-ActionClick-'+this.GetElementId());   // tiny delay, so the deactivate completes first & screen can paint
				setTimeout(this.scriptAction, 100);   // tiny delay, so the deactivate completes first & screen can paint
			}
			else if (null !== this.navigateUrl  &&  '' !== this.navigateUrl)
			{
				this.navigateUrl = this.navigateUrl.replace(/\'/g, '\\\'');
				safe_setTimeout('window.location = \''+this.navigateUrl+'\'', 100, 'FL_MenuItem-ActionClick-'+this.GetElementId());   // tiny delay, so the deactivate completes first & screen can paint
//				setTimeout('window.location = \''+this.navigateUrl+'\'', 100);   // tiny delay, so the deactivate completes first & screen can paint
			}
		}
	}
}

function FL_MenuItem_SetDialogStyle(b)  // Must be set before rendering.
{
	if (typeof(b) == 'undefined')   b = true;
	this.bDialogStyle = b;
}
function FL_MenuItem_SetBeginGroup()  // Must be set before rendering.
{
	if (this.indexInParent < 1)	return;  // don't bother on the first one
	if (typeof(b) == 'undefined')   b = true;

	this.bBelowSeparator = b;

	//	if there is a previous sibling, set its "above separator" flag
	if (typeof(this.parentMenuGroupObj.ChildList[this.indexInParent-1]) != 'undefined')
		this.parentMenuGroupObj.ChildList[this.indexInParent-1].bAboveSeparator = b;
}

function FL_MenuItem_Highlight(bHighlight)
{
	var classSelected = '';
	var classDeselected = '';
	if (this.IsMenuBarItem())
	{
		classSelected = 'Menu_Bar_Item_Highlight';
		classDeselected = 'Menu_Bar_Item_Highlight_Selected';
	}
	else
	{
		classSelected = 'Menu_Item_Highlight';
		classDeselected = 'Menu_Item_Highlight_Selected';
	}

	if (bHighlight)
	{
		this.AddCssClassName(classSelected, false);
		this.AddCssClassName(classDeselected, true);
	}
	else
	{
		this.RemoveCssClassName(classSelected, false);
		this.RemoveCssClassName(classDeselected, true);
	}

	if (null !== this.Parent  &&  typeof(this.Parent.Highlight) != 'undefined')
	{
		this.Parent.Highlight(bHighlight);
	}
}




//	Px getters are not so simple for menu items.  For absolute measurements, we need the containing DIV
//	as a baseline along with the individual item DIV.  (ex. menu group is at 100x150, menu item is at 0x20
//	inside of that, so the overall px measurement is 100x170.
//function FL_MenuItem_GetLeftPx()
//{
//	if (null === this.elementObj)   return 0;

//	var groupDiv = this.elementObj.parentNode;
//	return groupDiv.offsetLeft + this.elementObj.offsetLeft;
//}
//function FL_MenuItem_GetRightPx()
//{
//	if (null === this.elementObj)   return 0;

//	var groupDiv = this.elementObj.parentNode;
//	return groupDiv.offsetLeft + (this.elementObj.offsetLeft + this.elementObj.offsetWidth);
//}
//function FL_MenuItem_GetTopPx()
//{
//	if (null === this.elementObj)   return 0;

//	var groupDiv = this.elementObj.parentNode;
//	return groupDiv.offsetTop + this.elementObj.offsetTop;
//}
//function FL_MenuItem_GetBottomPx()
//{
//	if (null === this.elementObj)   return 0;

//	var groupDiv = this.elementObj.parentNode;
//	return groupDiv.offsetTop + (this.elementObj.offsetTop + this.elementObj.offsetHeight);
//}




function FL_MenuHeaderItem(stringId, parentGroupObj)
{
	if (typeof(this.containerType) == 'undefined')  // careful not to re-set this if a derived class set it prior to running this constructor
		this.containerType = 'FL_MenuItem';
	this.bSuppressElementWarnings = true;  // set before constructor to ignore set-element warnings

    this.baseConstructor = FL_MenuItem;
    this.baseConstructor(stringId, parentGroupObj);
    delete this.baseConstructor;

	this._base_Render = this.Render;
	this.Render = FL_MenuHeaderItem_Render;
}

function FL_MenuHeaderItem_Render()
{
	var retVal = this._base_Render();
	this.AddCssClassName('Menu_Header_Item', false);
	this.AddCssClassName('Menu_Header_Item_Selected', true);
	return retVal;
}





//	FL_MenuSubgroup is an odd hybrid -- structurally it is a grouping of menu items, but it behaves like a menu item itself with no attached behaviors.
function FL_MenuSubgroup(parentGroupObj)
{
	if (typeof(this.containerType) == 'undefined')  // careful not to re-set this if a derived class set it prior to running this constructor
		this.containerType = 'FL_MenuSubgroup';
	this.bSuppressElementWarnings = true;  // set before constructor to ignore set-element warnings

    this.baseConstructor = ViewContainer;
    this.baseConstructor();
    delete this.baseConstructor;

	this.Localize_local = FL_MenuSubgroup_Localize;
	this.Render = FL_MenuSubgroup_Render;
	this.Select = FL_MenuSubgroup_Select;
	this.Deselect = FL_MenuSubgroup_Deselect;
	this.OnBeforeDisplay = FL_MenuSubgroup_OnBeforeDisplay;
	this.Highlight = FL_MenuSubgroup_Highlight;
//	this.ActionMouseOver = FL_MenuItem_ActionMouseOver;  // inherit the standard menu ITEM behavior (not menu group)
//	this.ActionMouseOut = FL_MenuItem_ActionMouseOut;

	parentGroupObj.AssignChild(this);
	this.parentMenuGroupObj = parentGroupObj;
}

function FL_MenuSubgroup_Localize()
{
}

function FL_MenuSubgroup_Render()
{
	this.elementObj = document.createElement('DIV');
	this.elementObj.className = 'Menu_All Menu_Subgroup';
	this.elementObj.id = this.GetElementId();

	for (var i=0; i<this.ChildList.length; i++)
	{
		// skip the internally created text container
		if (this.textContainerObj === this.ChildList[i])  continue;

		var childElem = this.ChildList[i].Render();
		if (this.elementObj)
			this.elementObj.appendChild(childElem);
	}

//	eval('this.elementObj.onmouseover = function() { window["'+this.windowObjectName+'"].ActionMouseOver(); };');
//	eval('this.elementObj.onmouseout = function() { window["'+this.windowObjectName+'"].ActionMouseOut(); };');
	this.bIsRendered = true;
	return this.elementObj;
}

// these don't do anything, but parent objects will try to call them in larger sequences of children
function FL_MenuSubgroup_Select() { }
function FL_MenuSubgroup_Deselect() { }
function FL_MenuSubgroup_OnBeforeDisplay() { }

function FL_MenuSubgroup_Highlight(bHighlight)
{
	if (null !== this.Parent  &&  typeof(this.Parent.Highlight) != 'undefined')
	{
		this.Parent.Highlight(bHighlight);
	}
}
