/*************************************************** (c)2008 by Jennifer Simonds
A class for a set of panes with tabs on top that control which pane is visible.

TO USE: 
	1. Include domutils.js, tabview.js, and tabview.css in your page.
	2. Create a <div class='CTabView'> for the container.
	3. Inside this, create a <div class='CTabView-ClosedTab'> for each 
	   tab pane. The initial tab should have class='CTabView-OpenTab'.
	   Make sure their widths will fit in the CTabView (or use "100%").
	   Also make sure to specify their heights if their content will be too
	   long for your desired tab height! The control will size itself to
	   accomodate the highest tab that's defined at construction time.
	4. In onload, create a new CTabView, optionally setting the pane height.
	   Call appendTab for each pane, call selectTab to set the initial tab, then 
	   call show.

CONSTRUCTOR
	oTP = new CTabView (idContainer, nPaneH=0);

PROPERTIES
	oTP.divContainer // The div that contains the whole control.
	oTP.divOpenTab   // The tab div that's open
	oTP.nOpenTab     // The index to the tab that's open

METHODS
	ixTab   = oTP.insertTab (idPane, ixTab, sTitle, onOpen=null, onClose=null);
	ixTab   = oTP.appendTab (idPane, sTitle, onOpen=null, onClose=null);
	bOK     = oTP.delTab    (ixTab);
	bOK     = oTP.delTab    (idPane | oTab);
	divPane = oTP.selectTab (ixTab, bShow=false);
	ixTab   = oTP.selectTab (idPane | oTab, bShow=false);
	bOK		= oTP.show      (bShow=true);
	
EVENT HANDLERS

CLASS PROPERTIES (= default values)
	CTabView.prototype.??? = 

STYLES
	CTabView-ClosedTab	// An unselected tab.
	CTabView-OpenTab	// A selected tab.
	
   Date	  Who Changes
--------- --- ----------------------------------------------------------------
10may2008 jls Split off & refactored from chartable project.
28sep2008 jls Using gdutils.1.0.js instead of domutils.js.
28sep2008 jls 
******************************************************************************/

// REQUIRES: <script type="text/JavaScript" src="gdutils.1.0.js"></script>

// Class properties.


/******************************************************************************
Constructor.
******************************************************************************/
function CTabView (p_idContainer, p_nPanesH)
	{
	var aPanesTemp, nMaxPaneH;
	
	// Optional params & their default values.
	p_nPanesH = (arguments.length > 1) ? arguments[1] : 0;
	
	this.divTop	= document.getElementById (p_idContainer);
	if (!this.divTop)
		{
//		alert ("Couldn't create tabview: Element '"+p_idContainer+"' not found.");
		return;
		}
	
	// Browser type detection for handling quirks.
	// (Very primitive. I'm sure there's a better way.)
	this.quirkIE  = (window.createPopup!=null);
	this.quirkMoz = (!window.createPopup);
	
	this.divTop.style.visibility = "hidden";
	
	// Save off the panes so we can add them back in later.
	aPanesTemp = new Array();
	nMaxPaneH = 0;
	while (this.divTop.firstChild)
		{
		if (this.divTop.firstChild.offsetHeight > nMaxPaneH)
			{nMaxPaneH = this.divTop.firstChild.offsetHeight;
			}
		aPanesTemp.push (this.divTop.removeChild(this.divTop.firstChild));
		}
	
	// Create the table that'll hold the tabs above the panes.
	this.tblTabs = document.createElement ("table");
	gdu.addClassToElement (this.tblTabs, "CTabView-TabTable");
	this.tblTabs.cellSpacing = 0;
	this.divTop.appendChild (this.tblTabs);
	this.trTabs = this.tblTabs.insertRow (0);
	
	// Tables attempt to fit all cells in a row to the whole width. We don't want
	// that - we want each tab to be sized to its text. So we make a dummy last
	// column to take up the rest of the row's width.
	tdLast = this.trTabs.insertCell(0);
	gdu.addClassToElement (tdLast, "CTabView-EmptyTab");
	tdLast.innerHTML = "&nbsp;";
	tdLast.width = "100%";
	
	// Create the container for the panes.
	this.divPanesParent = document.createElement ("div");
	gdu.addClassToElement (this.divPanesParent, "CTabView-PanesParent");
	this.divTop.appendChild (this.divPanesParent);
	
	// Move the panes' nodes to be under the new panes container.
	for (ix=0; ix<aPanesTemp.length; ix++)
		{
		if (aPanesTemp[ix].nodeType==1)
			{aPanesTemp[ix].style.position = "absolute";
			}
		this.divPanesParent.appendChild (aPanesTemp[ix]);
		}
	
	// Tab ctrl is sized to accomodate the specified pane size, or else the
	// tallest pane + borders if p_nPaneH wasn't specified.
	this.divPanesParent.style.height = (p_nPanesH ? p_nPanesH : nMaxPaneH) + "px";
	this.divTop.style.height = this.divPanesParent.offsetHeight + this.tblTabs.offsetHeight;
	
	this.aTabs	  = new Array();
	this.oCurrTab = null;
	}


/******************************************************************************
Displays or hides the control. The CTabView starts out hidden, so it won't get
updated until after you add all your initial tabs & call show.

PARAMS:
	bShow		(Def=true) True to show the control, else false.
******************************************************************************/
CTabView.prototype.show = function (p_bShow)
	{
	bShow = arguments.length>0 ? p_bShow  : true;
	
	if (bShow)
		{this.divTop.style.visibility = "visible";
		}
	else
		{this.divTop.style.visibility = "hidden";
		}
	}


/******************************************************************************
Adds a tab to the control.

PARAMS:
	idPane		The id of the <div> to display when the tab is clicked.
	ixPos		The position in the array of tabs to insert this, or -1 to append it.
	sTitle		The markup to put inside the tab.
	onOpen		(Def=null) An event handler for when the user opens this pane.
	onClose		(Def=null) An event handler for when the user opens a different pane.

RETURNS:
	The new tab's object.
******************************************************************************/
CTabView.prototype.appendTab = function (p_idPane, p_sTitle, p_onOpen, p_onClose)
	{
	return this.insertTab (p_idPane, -1, p_sTitle, p_onOpen, p_onClose);
	}

/*****************************************************************************/
CTabView.prototype.insertTab = function (p_idPane, p_ixPos, p_sTitle, p_onOpen, p_onClose)
	{
	var ixPos, onOpen, onClose, oTab;
	
	// Params & their default values.
	if (p_ixPos==-1)
		{// We can't directly append a tab, since the last one is a dummy spacer.
		p_ixPos = this.trTabs.cells.length-1;
		}
	ixPos = Math.max(-1, Math.min(p_ixPos, this.trTabs.cells.length-1));
	onOpen	= arguments.length>3 ? p_onOpen  : null;
	onClose = arguments.length>3 ? p_onClose : null;
	
	tdTab = this.trTabs.insertCell (p_ixPos);
	
	oTab = new CTab (this, tdTab, p_idPane, p_sTitle, onOpen, onClose);
	
	this.aTabs.push (oTab);
	
	return oTab;
	}


/******************************************************************************
Removes a tab from the control.

PARAMS:
	oTab | ixTab | idPane
				The tab object we got when we called addTab,
				or the index in the array of tabs,
				or the id of the <div> to display when the tab is chosen.
	nPos		The position in the array of tabs to insert this, or -1 to append it.
	onOpen		(Def=null) An event handler for when the user opens this pane.
	onClose		(Def=null) An event handler for when the user opens a different pane.

RETURNS:
	true		if the tab was found.
	false		if the tab was not found.
******************************************************************************/
CTabView.prototype.delTab = function (p_Tab)
	{
	var ixTab, oTab;
	
	ixTab = this._getTabIndex (p_Tab);
	oTab = this.aTabs.slice (ixTab, ixTab+1);
	
	if (ixTab == this.ixCurrTab)
		{this.selectTab (ixTab-1);
		}
	
	// Remove this tab from the table on the page.
	this.trTabs.deleteCell (ixTab);
	
	return !(oTab!=null && oTab!=undefined);
	}


/******************************************************************************
Selects a tab & displays its pane.

PARAMS:
	oTab | ixTab | idPane
					The tab object we got when we called addTab,
					or the index in the array of tabs,
					or the id of the <div> to display.
	bShow=false		True to bring the tab into view, else false to keep the 
					display wherever it is.
	
RETURNS:
	true			if the tab was found.
	false			if the tab was not found.
******************************************************************************/
CTabView.prototype.selectTab = function (p_Tab, p_bShow)
	{
	var bShow, ixTab, oTab;
	
	bShow = arguments.length>1 ? p_bShow  : false;
	
	ixTab = this._getTabIndex (p_Tab);
	oTab = this.aTabs[ixTab];
	
	// Change the styles for prev/this tabs, and prev/this panes.
	if (this.oCurrTab && oTab!=this.oCurrTab)
		{this.oCurrTab._onClose ();
		}
	if (oTab)
		{oTab._onOpen (bShow);
		}
	
	return !(oTab!=null && oTab!=undefined);
	}

	
/******************************************************************************
Finds a tab object, given an object reference, id for its pane's <div>, or index
into the array of tabs.

PARAMS:
	oTab | ixTab | idPane
				The tab object we got when we called addTab,
				or the index in the array of tabs,
				or the id of the <div> to display.

RETURNS:
	integer		The index into the array of tabs.
	null		If the tab was not found.
******************************************************************************/
CTabView.prototype._getTabIndex = function (p_Tab)
	{
	if (typeof(p_Tab) == "object")
		{// Assume it's the tab object we got when we called appendTab.
		for (ix=0; ix<this.aTabs.length; ix++)
			{if (this.aTabs[ix] == p_Tab)
				{return ix;
				}
			}
		}
	else if (typeof(p_Tab) == "number")
		{// It's the index itself.
		return p_Tab;
		}
	else if (typeof(p_Tab) == "string")
		{// Assume it's the id of the pane.
		divPane = document.getElementById(p_Tab);
		for (ix=0; ix<this.aTabs.length; ix++)
			{if (this.aTabs[ix].divPane == divPane)
				{return ix;
				}
			}
		}
	
	return -1;
	}


/******************************************************************************
Constructor.
******************************************************************************/
function CTab (p_oTabView, p_tdTab, p_idPane, p_sTitle, p_onOpen, p_onClose)
	{
	var oCompStyle;
	
	this.oTop	 = p_oTabView;
	this.divPane = document.getElementById (p_idPane);
	if (!this.divPane)
		{alert ("Couldn't create tab for element "+p_idPane+": Element not found.");
		}
	this.idPane  = p_idPane;
		
	if (this.oTop.quirkMoz)
		{// Padding increases an element's width/height. Make reported size 
		//  conform to what's inside the border.
		this.divPane.style.MozBoxSizing = "border-box";
		}
	
	this.tdTab			 = p_tdTab;
	this.tdTab.innerHTML = p_sTitle;
	this.tdTab.onclick	 = gdu.bindMethodToEvent (this, "_onClickTD");
	this.onOpen			 = p_onOpen;
	this.onClose		 = p_onClose;
	
	gdu.removeClassFromElement (this.tdTab,   "CTabView-OpenTab");
	gdu.addClassToElement	   (this.tdTab,   "CTabView-ClosedTab");
	gdu.removeClassFromElement (this.divPane, "CTabView-OpenPane");
	gdu.addClassToElement	   (this.divPane, "CTabView-ClosedPane");
	this.isOpen	 = false;
	}


/******************************************************************************
The user clicked on this tab.
******************************************************************************/
CTab.prototype._onClickTD = function ()
	{
	if (this != this.oTop.oCurrTab)
		{
		if (this.oTop.oCurrTab)
			{this.oTop.oCurrTab._onClose ();
			}
		this._onOpen ();
		}
	}

	
/******************************************************************************
This tab/another tab has been selected.
******************************************************************************/
CTab.prototype._onClose = function ()
	{
	var oStyle, aKids, ix;
	
	// QUIRK: In IE, if a pane has an iframe, which itself has loaded in a page
	// that has an iframe on it, then hiding its enclosing DIV won't hide the
	// inner iframe. We have to explicitly hide the outer iframe in script.
	// We can't even merely change a class to one that has visibility:hidden!
	// So here we find all iframes on the pane, save off their current visibility,
	// then hide them explicitly.
	if (this.oTop.quirkIE)
		{
		oStyle = this.divPane.currentStyle;
		if (this.divPane.tagName.toUpperCase()=="IFRAME")
			{
//aKids = this.divPane.document.getElementsByTagName("iframe");
//alert ("IFRAME has "+aKids.length+" kid frames");
			this.divPane.prevVisibility = oStyle.visibility;
			this.divPane.style.visibility = "hidden";
			}
		aKids = this.divPane.document.getElementsByTagName("iframe");
		for (ix=0; ix<aKids.length; ix++)
			{
//alert ("kid is a "+aKids[ix].id);
			if (aKids[ix].nodeType==1 && aKids[ix].tagName.toUpperCase()=="iframe")
				{
				aKids[ix].prevVisibility = oStyle.visibility;
				aKids[ix].style.visibility = "hidden";
				}
			}
		}
		
	gdu.removeClassFromElement (this.tdTab,   "CTabView-OpenTab");
	gdu.removeClassFromElement (this.divPane, "CTabView-OpenPane");
	gdu.addClassToElement	   (this.tdTab,   "CTabView-ClosedTab");
	gdu.addClassToElement	   (this.divPane, "CTabView-ClosedPane");
	this.isOpen	 = false;
	this.oTop.oCurrTab = null;
	
	if (this.onClose)
		{this.onClose ();
		}
	}

/*****************************************************************************/
CTab.prototype._onOpen = function (p_bShow)
	{
	var aKids, ix;
	
	// HACK for IE: If a pane has an iframe, which itself has loaded in a page
	// that has an iframe on it, then hiding its enclosing DIV won't hide the
	// inner iframe. We have to explicitly hide the outer iframe in script.
	// We can't even merely change its class to one that has visibility:hidden!
	// So here we restore all iframes on the pane to their previous visibility.
	if (this.oTop.quirkIE)
		{
		if (this.divPane.tagName.toUpperCase()=="IFRAME")
			{if (this.divPane.prevVisibility)
				{this.divPane.style.visibility = this.divPane.prevVisibility;
				}
			}
		aKids = this.divPane.document.getElementsByTagName("iframe");
		for (ix=0; ix<aKids.length; ix++)
			{if (aKids[ix].nodeType==1 && aKids[ix].tagName.toUpperCase()=="iframe")
				{if (aKids[ix].prevVisibility)
					{aKids[ix].style.visibility = aKids[ix].prevVisibility;
					}
				}
			}
		}
	
	if (this.onOpen)
		{this.onOpen ();
		}
	
	gdu.removeClassFromElement (this.tdTab,   "CTabView-ClosedTab");
	gdu.removeClassFromElement (this.divPane, "CTabView-ClosedPane");
	gdu.addClassToElement	   (this.tdTab,   "CTabView-OpenTab");
	gdu.addClassToElement	   (this.divPane, "CTabView-OpenPane");
	this.isOpen	 = true;
	this.oTop.oCurrTab = this;
	if (p_bShow)
		{this.oTop.divTop.scrollIntoView();
		}
	}

/******************************************************************************
Removes this tab from the CTabView.

RETURNS:
	true if it was found, else false.
******************************************************************************/
CTab.prototype.remove = function ()
	{
	var ixTab;
	
	ixTab = this._getTabIndex(this);
	if (ixTab < 0)
		{return false;
		}
	
	// Select the next tab.
	if (this == this.oTop.oCurrTab)
		{this._onClose ();
		}
	
	// Remove this tab's TD from the table.
	this.oTop.trTabs.deleteCell (ixTab);
	
	// Remove the object from our array.
	this.oTop.aTabs.slice (ixTab, ixTab+1);
	
	return true;
	}
