/*************************************************** (c)2009 by BookRags, Inc.
A class for a set of panes with tabs on top that control which pane is visible.

TO USE:
	1. Include brutils.1.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-ClosedPane'> for each tab pane.
	   (The initial tab should have class='CTabView-OpenPane', or call selectTab before
	   calling show.)
	   Make sure their widths will fit in the overall CTabView (or use "100%").
	   The control will size itself to accomodate the highest tab that's defined
	   at construction time. Or you can specify an overall height for the tab
	   ctrl in the constructor.
	4. In onload, create a new CTabView, optionally setting the pane height.
	   Call appendTab for each pane, call selectTab to set the initial tab if
	   necessary, then call show.

CONSTRUCTOR
	oTP = new CTabView (idContainer, nPaneH=0, bErrMsgs=false,
						{bkgdbase:tab-imgs-base-url, tabheight:nnn, tabborderradius:nnn, tabbordercoloropen:rgb, tabbordercolorclosed: rgb});
	oTP = new CTabView (idContainer, {paneheight:nnn, errmsgs:0|1});

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.insertTab (idPane, ixTab, {width:nnn, height:nnn, bkgd:url});
	ixTab   = oTP.appendTab (idPane, sTitle, onOpen=null, onClose=null);
	ixTab   = oTP.appendTab (idPane, ixTab, {width:nnn, height:nnn, bkgd:url});
	bOK     = oTP.delTab    (ixTab | oTab | idPane);
	divPane = oTP.selectTab (ixTab | oTab | idPane);
	bOK		= oTP.show      (bShow=true);

EVENT HANDLERS
	oTab.onOpen		(specified in insertTab/appendTab)
	oTab.onClose	(specified in insertTab/appendTab)

CLASS PROPERTIES (= default values)
	CTabView.prototype.??? =

STYLES
	CTabView-ClosedTab	// An unselected tab.
	CTabView-OpenTab	// A selected tab.

   Date	  Who Major changes
--------- --- ----------------------------------------------------------------
12nov2008 jls Cloned from Jennifer Simonds' original tabview library.
17may2009 jls Added extra params to CTabView, appendTab, insertTab methods.
04aug2009 jls Use sprites for image-based tabs.
06oct2009 jls
******************************************************************************/

// REQUIRES: <script type="text/JavaScript" src="jquery.1.3.2.js"></script>
// REQUIRES: <script type="text/JavaScript" src="brutils.1.js"></script>

// Class properties.


/******************************************************************************
Constructor.
******************************************************************************/
function CTabView (p_idContainer, p_nPanesH, p_bErrMsgs, p_oxp)
	{
	var aPanesTemp, nMaxPaneH;

	// Optional params & their default values.
	p_nPanesH	  = (arguments.length > 1) ? arguments[1] : 0;
	this.bErrMsgs = (arguments.length > 2) ? arguments[2] : false;
	this.nTabH	  = 0;
	this.rgbTabBorderOpen = "transparent";
	this.rgbTabBorderClosed = "transparent";
	this.nTabBorderR = 0;
	this.urlBkgd = "";
	if (arguments.length>3 && typeof(p_oxp)=='object')
		{
		this.urlBkgdBase  = (p_oxp['bkgdbase']!==undefined) ? p_oxp['bkgdbase'] : "";
		this.nTabH		  = (p_oxp['tabheight']!==undefined) ? p_oxp['tabheight'] : "";
		this.nTabBorderR  = (p_oxp['tabborderradius']!==undefined) ? p_oxp['tabborderradius'] : "";
		this.rgbTabBorderOpen	= (p_oxp['tabbordercoloropen']!==undefined) ? p_oxp['tabbordercoloropen'] : "";
		this.rgbTabBorderClosed	= (p_oxp['tabbordercolorclosed']!==undefined) ? p_oxp['tabbordercolorclosed'] : "";
		}
	this.bTabsUseBkgd = !!this.urlBkgdBase;


	this.divTop	= document.getElementById (p_idContainer);
	if (!this.divTop)
		{if (this.bErrMsgs)
			{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";
	bru.addClassToElement (this.divTop, "CTabView");

	// 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.nodeType==1) // element
			{
			if (this.divTop.firstChild.offsetHeight > nMaxPaneH)
				{nMaxPaneH = $(this.divTop.firstChild).outerHeight();
				}
			aPanesTemp.push (this.divTop.firstChild);
			}
		this.divTop.removeChild(this.divTop.firstChild);
		}

	// Create the table that'll hold the tabs above the panes.
	this.tblTabs = document.createElement ("table");
	bru.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.
	this.tdLast = this.trTabs.insertCell(0);
	bru.addClassToElement (this.tdLast, "CTabView-EmptyTab");
	this.tdLast.innerHTML = "&nbsp;";
	this.tdLast.width = "100%";

	// Create the container for the panes.
	this.divPanesParent = document.createElement ("div");
	bru.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++)
		{
		aPanesTemp[ix].style.position = "absolute";
//		aPanesTemp[ix].style.width = this.divPanesParent.offsetWidth + "px";
		this.divPanesParent.appendChild (aPanesTemp[ix]);
		}

	// Tab ctrl is sized to accomodate the specified pane height, or else the
	// tallest pane + borders if p_nPaneH wasn't specified.
//	this.divPanesParent.style.height = (p_nPanesH ? p_nPanesH : nMaxPaneH) + "px";
	this.divPanesParent.style.height = (p_nPanesH ? p_nPanesH : nMaxPaneH) + "px";
//	this.divTop.style.height = $(this.divPanesParent).outerHeight() + $(this.tblTabs).outerHeight() + "px";
	this.divTop.style.minHeight = $(this.divPanesParent).outerHeight() + $(this.tblTabs).outerHeight() + "px";

	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		(if insertTab) 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)
	{
	p_sTitle  = arguments.length>1 ? arguments[1] : null;
	p_onOpen  = arguments.length>2 ? arguments[2] : null;
	p_onClose = arguments.length>3 ? arguments[3] : null;
	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));

	p_sTitle  = arguments.length>2 ? arguments[2] : null;
	p_onOpen  = arguments.length>3 ? arguments[3] : null;
	p_onClose = arguments.length>4 ? arguments[4] : null;
	tdTab = this.trTabs.insertCell (p_ixPos);

	oTab = new CTab (this, tdTab, p_idPane, p_sTitle, p_onOpen, p_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 tab's content <div> 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 tab's content <div>.
	bOnEvent=true	True to fire the onOpen or onClose handler, else false.

RETURNS:
	true			if the tab was found.
	false			if the tab was not found.
******************************************************************************/
CTabView.prototype.selectTab = function (p_Tab, p_bOnEvent)
	{
	var bShow, ixTab, oTab;

	p_bOnEvent = arguments.length>1 ? p_bOnEvent : true;

	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 (p_bOnEvent);
		}
	if (oTab)
		{oTab._onOpen (p_bOnEvent);
		}

	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 tab's content <div>.

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)
		{if (this.oTop.bErrMsgs)
			{alert ("Couldn't create tab for element "+p_idPane+": Element not found.");
			}
		}
	this.idPane	= p_idPane;
	this.tdTab	= p_tdTab;
	this.sTitle = p_sTitle;
	if (this.oTop.bTabsUseBkgd)
		{this.urlImg = this.oTop.urlBkgdBase.replace("*",this.sTitle);
		}
	else
		{
		this.urlImg = "";
		this.tdTab.innerHTML = this.sTitle;
		}
	this.tdTab.onclick = bru.bindMethodToEvent (this, "_onClickTD");
	this.onOpen	 = p_onOpen;
	this.onClose = p_onClose;

	if (this.oTop.nTabH)
		{$(this.tdTab).css("height",this.oTop.nTabH);
		}
	else
		{this.oTop.nTabH = $(this.tdTab).outerHeight();
		}

	if (this.oTop.bTabsUseBkgd)
		{
		this.tdTab.style.background = "url("+this.urlImg+") no-repeat left -"+this.oTop.nTabH+"px"; // Initially set to the closed sprite

		// Preload the bkgd image. If OK, use it for the tab, else failsafe w/just the title.
		// We need to do it this way 'cuz we don't know the TD's width until loading its image.
		this.img = new Image();
		$(this.img).bind ("abort error", {oTab: this}, function(e)
							{$(e.data.oTab.tdTab).html ("<span style='color:gray'>"+e.data.oTab.sTitle+"</span>");
							});
		$(this.img).bind ("load", {oTab: this}, function (e)
							{
							$(e.data.oTab.tdTab).html ("<div style='width:"+e.data.oTab.img.width+"px; height:"+e.data.oTab.oTop.nTabH+"px;background-color:transparent'></div>");
//							$(e.data.oTab).css('backgroundPosition', 'left ' + (e.data.oTab.isOpen?'0':('-'+e.data.oTab.oTop.nTabH+'px'))); // the open sprite else the closed one
//$(e.data.oTab.tdTab).html ("<span>img load</span>");
							});
		this.img.src = this.urlImg;

		this.tdTab.style.margin		 = "0";
		this.tdTab.style.borderWidth = "0";
		this.tdTab.style.padding	 = "0";
		this.tdTab.style.height		 = this.oTop.nTabH+"px";
		this.tdTab.style.overflow	 = "hidden";
		}

	// Make sure the PanesParent is still high enough to accomodate all the panes.
//	this.oTop.divPanesParent.style.height = Math.max($(this.oTop.divPanesParent).outerHeight(), $(this.divPane).outerHeight()) + "px";
	this.oTop.divPanesParent.style.minHeight = Math.max($(this.oTop.divPanesParent).outerHeight(), $(this.divPane).outerHeight()) + "px";

	// If the table height or max pane height changed, the enclosing tabview's height should change too.
//	this.oTop.divTop.style.height = $(this.oTop.divPanesParent).outerHeight() + $(this.oTop.tblTabs).outerHeight() + "px";
	this.oTop.divTop.style.minHeight = $(this.oTop.divPanesParent).outerHeight() + $(this.oTop.tblTabs).outerHeight() + "px";

	// Make sure the pane's width fully fits inside the parent.
//	$(this.divPane).width (this.oTop.divPanesParent.offsetWidth - ($(this.divPane).outerWidth() - $(this.divPane).width()));
	$(this.divPane).width ($(this.oTop.divPanesParent).width() - ($(this.divPane).outerWidth() - $(this.divPane).width()));

	bru.removeClassFromElement (this.tdTab,   "CTabView-OpenTab");
	bru.addClassToElement	   (this.tdTab,   "CTabView-ClosedTab");
	bru.removeClassFromElement (this.divPane, "CTabView-OpenPane");
	bru.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 (true);
			}
		this._onOpen (true);
		}
	}


/******************************************************************************
This tab/another tab has been selected.
******************************************************************************/
CTab.prototype._onClose = function (p_bOnEvent)
	{
	var oStyle, aKids, ix;

	// HACK for IE: If a pane has an iframe or table, then hiding its enclosing
	// DIV won't hide those elements. We have to explicitly hide the iframes & tables
	// in script. We can't even merely change their classes to one that has visibility:hidden!
	// So here we find all iframes & tables on the pane, save off their current
	// visibilities, then hide them explicitly.
	if (this.oTop.quirkIE)
		{
		$jElems = $(this.divPane).find("iframe,table");
		if ($jElems.length)
			{
			$jElems.add($(this.divPane)).each(function (i)
							{
							this.prevVisibility = this.style.visibility;
							this.style.visibility = "hidden";
							});
			}
		}

	bru.removeClassFromElement (this.tdTab,   "CTabView-OpenTab");
	bru.removeClassFromElement (this.divPane, "CTabView-OpenPane");
	bru.addClassToElement	   (this.tdTab,   "CTabView-ClosedTab");
	bru.addClassToElement	   (this.divPane, "CTabView-ClosedPane");
	this.isOpen	 = false;
	if (this.oTop.bTabsUseBkgd)
		{
		if (this.img.width)
			{$(this.tdTab).css ("background-position", "left -"+this.oTop.nTabH+"px"); // the closed sprite
//			{$(this.tdTab).html ("<span>_onClose</span>");
			}
		else
			{$(this.tdTab).html ("<span style='color:gray'>"+this.sTitle+"</span>");
			}
		}
	this.oTop.oCurrTab = null;

	if (this.onClose && p_bOnEvent)
		{this.onClose ();
		}
	}

/*****************************************************************************/
CTab.prototype._onOpen = function (p_bOnEvent)
	{
	var aKids, ix;

	if (this.onOpen && p_bOnEvent)
		{this.onOpen ();
		}

	// HACK for IE: If a pane has an iframe or table, then hiding its enclosing
	// DIV won't hide those elements. We have to explicitly hide the iframes & tables
	// in script. We can't even merely change their classes to one that has visibility:hidden!
	// So here we restore all iframes & tables on the pane to their previous visibilities.
	if (this.oTop.quirkIE)
		{
		$jElems = $(this.divPane).find("iframe,table");
		if ($jElems.length)
			{$jElems.add($(this.divPane)).each(function (i)
				{this.style.visibility = this.prevVisibility?this.prevVisibility:"visible";
				});
			}
		}

	bru.removeClassFromElement (this.tdTab,   "CTabView-ClosedTab");
	bru.removeClassFromElement (this.divPane, "CTabView-ClosedPane");
	bru.addClassToElement	   (this.tdTab,   "CTabView-OpenTab");
	bru.addClassToElement	   (this.divPane, "CTabView-OpenPane");
	this.isOpen	 = true;
	if (this.oTop.bTabsUseBkgd)
		{
		if (this.img.width)
			{$(this.tdTab).css ("background-position", "left 0"); // the open sprite
//			{$(this.tdTab).html ("<span>_onOpen</span>");
			}
		else
			{$(this.tdTab).html ("<span style='color:black'>"+this.sTitle+"</span>");
			}
		}
	this.oTop.oCurrTab = this;
	}

/******************************************************************************
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 (false);
		}

	// 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);

	// If the table height changed, the enclosing tabview's height should change too.
//	this.oTop.divTop.style.height = $(this.oTop.divPanesParent).outerHeight() + $(this.oTop.tblTabs).outerHeight() + "px";
	this.oTop.divTop.style.minHeight = $(this.oTop.divPanesParent).outerHeight() + $(this.oTop.tblTabs).outerHeight() + "px";

	return true;
	}
