// $Id: zptabs-accordion.js 4850 2006-10-22 00:00:53Z vkulov $
/**
 * @fileoverview Accordion style tabs extension. Extends Tabs class (zptabs.js)
 * adding accordion style tabs.
 *
 * <pre>
 * Copyright (c) 2004-2006 by Zapatec, Inc.
 * http://www.zapatec.com
 * 1700 MLK Way, Berkeley, California,
 * 94709, U.S.A.
 * All rights reserved.
 * </pre>
 */

/**
 * Zapatec.AccordionTab constructor. Creates a new accordion tab object with
 * given parameters. Configuration options are the same as in Zapatec.Tab
 *
 * @constructor
 * @extends Zapatec.Widget
 * @param {object} objArgs Tab configuration
 */
Zapatec.AccordionTab = function(objArgs) {
  if(arguments.length == 0){
    objArgs = {};
  }

  // Call constructor of superclass
  Zapatec.AccordionTab.SUPERconstructor.call(this, objArgs);
};

/**
 * Unique static id of the widget class. Gives ability for Zapatec#inherit to
 * determine and store path to this file correctly when it is included using
 * Zapatec#include. When this file is included using Zapatec#include or path
 * to this file is gotten using Zapatec#getPath, this value must be specified
 * as script id.
 * @private
 */
Zapatec.AccordionTab.id = 'Zapatec.AccordionTab';

// Inherit Tab
Zapatec.inherit(Zapatec.AccordionTab, Zapatec.Tab);

/**
 * Creates elements needed for the new tab
 */
Zapatec.AccordionTab.prototype.createTab = function()
{
  // Call parent init
  Zapatec.AccordionTab.SUPERclass.createTab.call(this);

  this.container.getContainer().style.display = 'block';

  if (this.config.content) {
    this.container.getContainer().innerHTML = this.config.content.innerHTML;
  }

  this.container.getContainer().tabId = this.id;

  // Keyboard navigation support
	this.linkNode.tabIndex = Zapatec.AccordionTab.tabIndex;

	if(!this.tab2tab)
	{
		// Next tabIndex is reserved for tab content
		Zapatec.AccordionTab.tabIndex += 2;
		// } else {
	    // No need to increase tabIndex
	}

	// Add title bar to tab
	this.chooser = Zapatec.Utils.createElement('div');
	this.chooser.className = 'tabChooser';
	this.chooser.appendChild(this.linkNode);
	this.container.getContainer().insertBefore(this.chooser,this.container.getContainer().childNodes[0]);

  // Create WCH
  this.wch = Zapatec.Utils.createWCH(this.container.getContainer());
  // Put WCH under container
  if (this.wch) {
    this.wch.style.zIndex = -1;
  }
};

/**
 * Determines child node of the container which gets focus first.
 * Needed for keyboard navigation.
 * @private
 */
Zapatec.AccordionTab.prototype.getFocusOn = function ()
{
	// Remove old value
	this.focusOn = null;

	// Check keyboard navigation type
	if(this.tab2tab)
	{
		return;
	}

	// Put it in separate process to speed up initialization
	var self = this;

	setTimeout(function()
		{
			// Flag to determine lower tabIndex
			var iTabIndex = 0;

			// Gets element with lower tabIndex
			function parse(objNode)
			{
				var objChild = objNode.firstChild;

				while(objChild)
				{
					if(objChild.nodeType == 1)
					{
						// ELEMENT_NODE
						var strTag = objChild.tagName.toLowerCase();

						if(strTag == 'a' || strTag == 'input' || strTag == 'select' ||
							strTag == 'textarea' || strTag == 'button')	// Element may obtain focus
						{
							if(!self.focusOn)
							{
								self.focusOn = objChild;
							}
							else if(objChild.tabIndex && objChild.tabIndex > 0 &&
								(!iTabIndex || iTabIndex > objChild.tabIndex))
							{
								self.focusOn = objChild;
								iTabIndex = objChild.tabIndex;
							}

							if(!objChild.tabIndex)
							{
								objChild.tabIndex = self.linkNode.tabIndex + 1;
							}
						}

						parse(objChild);
					}

				objChild = objChild.nextSibling;
			}
		};

		// Parse tab content
		parse(self.container.getContainer());
	},
	0);
};



/**
 * Zapatec.AccordionTabs constructor. Creates a new accordion tabs object
 * with given parameters.
 *
 * @constructor
 * @extends Zapatec.Widget
 * @param {object} objArgs Tab configuration
 *
 * See Zapatec.Tabs for a list of recognized properties of the config object
 */
Zapatec.AccordionTabs = function(objArgs) {
  Zapatec.AccordionTabs.SUPERconstructor.call(this, objArgs);
};

/**
 * Unique static id of the widget class. Gives ability for Zapatec#inherit to
 * determine and store path to this file correctly when it is included using
 * Zapatec#include. When this file is included using Zapatec#include or path
 * to this file is gotten using Zapatec#getPath, this value must be specified
 * as script id.
 * @private
 */
Zapatec.AccordionTabs.id = 'Zapatec.AccordionTabs';

// Inherit Tabs
Zapatec.inherit(Zapatec.AccordionTabs, Zapatec.Tabs);

/**
 * Initializes object.
 *
 * @param {object} objArgs User configuration
 */
Zapatec.AccordionTabs.prototype.init = function(objArgs,i)
{
	// Reference to this
	var self = this;

	// Patch onInit handler
	var funcOnInit = objArgs.onInit;

	objArgs.onInit = function()
	{
		// Setup tabs
		//var _tabContainer = Zapatec.Widget.getElementById(self.config.tabs);
		var _tabContainer = self.config.tabs;
		var items = _tabContainer.childNodes;
		for(var i=items.length-1; i>=0; i--)
		{
			if(items[i].tagName=='div' || items[i].tagName=='DIV')
			{
				self.config._tabArray.push(items[i]);
				self.config._tabArray[self.config._tabArray.length-1].style.position = 'absolute';
				self.config._tabArray[self.config._tabArray.length-1].style.overflow = 'hidden';
			}
		}
		topPos = self.config._tabArray[self.config._tabArray.length-1].offsetTop;

		// Enable mouse navigation by clicking the title bars
		var items = _tabContainer.getElementsByTagName('div');
		for(var i=0; i<items.length; i++)
		{
			if(items[i].className=='tabChooser')
			{
        var localItem = items[i];
        items[i].onclick = function()
				{
          self.changeTab(this.parentNode.tabId);
					if(this.blur)
					{
						this.blur();
					}

					return true; // To change document.URL
				};
			}
		}

		// Position tabs
		// You need the number of tabs and their order
		// then, you can calculate where they should start and stop.
		////
		// Each tab has a visible area of:
		//	tabContainerHeight - (numOfTabs-1 * heightOfTabTitle)
		//
		// Each tab has a unique viewing position, derived from its place among the tabs as follows:
		//	tabTop = tabContainerTop + (tabIndex-1 * heightOfTabTitle)
		//
		// Each tab has a unique hidden position, derived from its place among the tabs as follows:
		//	tabTop = tabContainerTop + tabContainerHeight - (tabIndex * heightOfTabTitle)
		//
		// The tabs are layerd on top of each other in the order in which they appear in the HTML.
		// The first tab appears as the default.
		// In opening a tab, the chosen tab will carry all tabs below it along with it.
		// In closing a tab, the chosen tab will carry all tabs above it along with it.

		var _tabZIndex = 100;

		for(var i=0; i<self.config._tabArray.length; i++)
		{
			// Make tab height equal to the height of the viewable are
			// in the tab container, minus the heights of the selection bars of the other tabs
			self.config._tabArray[i].style.height = self.config.tabBarHeight + 'px';

			// Subract two to account for border
			self.config._tabArray[i].style.width = parseInt(_tabContainer.style.width) + 'px';

			// Set initial, hidden position of tab
			self.config._tabArray[i].style.top = (topPos + parseInt(_tabContainer.style.height) - ((i+1) * self.config.tabBarHeight)) + 'px';

			// Layer the tabs
			self.config._tabArray[i].style.zIndex = _tabZIndex--;

			// This tab's array position
			self.config._tabArray[i].arrayPosition = i;

			// This tab's viewing position
			self.config._tabArray[i].viewingPosition = topPos + ((self.config._tabArray.length-1-i)*self.config.tabBarHeight);

			// This tab's hidden position
			self.config._tabArray[i].hiddenPosition = topPos + parseInt(_tabContainer.style.height) - ((i+1)*self.config.tabBarHeight);

			// This tab's final viewing height
			self.config._tabArray[i].viewingHeight = parseInt(_tabContainer.style.height) - ((self.config._tabArray.length-1)*self.config.tabBarHeight);

			// This tab's hidden height
			self.config._tabArray[i].hiddenHeight = self.config.tabBarHeight;
		}

		// Set first tab to visible
		self.config._tabArray[self.config._tabArray.length-1].style.top = self.config._tabArray[self.config._tabArray.length-1].viewingPosition + 'px';
		self.config._tabArray[self.config._tabArray.length-1].style.height = self.config._tabArray[self.config._tabArray.length-1].viewingHeight + 'px';


		// Patch onBeforeTabChange handler
		var funcOnBeforeTabChange = self.config.onBeforeTabChange;

		self.config.onBeforeTabChange = function(objArgs)
		{
			// Call original function
			if(typeof funcOnBeforeTabChange == 'function')
			{
				return funcOnBeforeTabChange(objArgs);
			}
			return true;
		};

		// Patch onTabChange handler
		var funcOnTabChange = self.config.onTabChange;

		self.config.onTabChange = function(objArgs)
		{
			// Get new tab id
			var strNewTabId = objArgs.newTabId;

			// Get new tab
			var objNewTab = self.tabs[strNewTabId];

			if(!objNewTab)
			{
				return;
			}

			// Call original function
			if(typeof funcOnTabChange == 'function')
			{
				funcOnTabChange(objArgs);
			}
		};

		// Call original function
		if(typeof funcOnInit == 'function')
		{
			funcOnInit(objArgs);
		}
	};


  // Disable tab bar
  this.noTabBar = true;

  // Define config options
  this.config.windowOnLoad = null;

  // Tab controllers
  this.config._tabArray = new Array();
  this.config.IN_MOTION = false;
  this.config.tabBarHeight = 24;
  this.config.topPos = null;
  this.config.indexOfWidget = i;

  this.tabsThemeSuffix = 'AccordionContent';

  // Call parent init
  Zapatec.AccordionTabs.SUPERclass.init.call(this, objArgs);

};

/**
 * Create a new tab instance
 *
 * @param {object} objArgs tab configuration
 */
Zapatec.AccordionTabs.prototype.newTab = function(objArgs) {
  var objTab = new Zapatec.AccordionTab(objArgs);
  return objTab;
}

/**
 * Display a new accordion tab. If onBeforeTabChange() returns false, the
 * operation is cancelled.
 *
 * @param {string} strNewTabId id of the new tab.
 */
Zapatec.AccordionTabs.prototype.changeTab = function(strNewTabId) {
  var strCurrTabId = null;
	var objTab = null;

	if (this.tabsArray[this.currentIndex])
	{
		strCurrTabId = this.tabsArray[this.currentIndex].id;
		objTab = this.tabsArray[this.currentIndex];
	}

	if (strCurrTabId != strNewTabId && !this.config.IN_MOTION)
	{
		// Change tab
		if(objTab)
		{
			// Instead of hiding the previous tab, move the new tab over it
			objTab.container.getContainer().style.position = 'absolute';
			Zapatec.Utils.removeClass(objTab.linkNode, 'zpTabsActive');
		}

    objTab = this.getTab(strNewTabId);

		// Initiate sliding:
		// If the tab is in the viewing position, move it down
		// If the tab is in the hidden position move it up
		if(objTab.container.getContainer().offsetTop<=objTab.container.getContainer().viewingPosition)
		{
			this.moveDown(objTab.container.getContainer().arrayPosition - 1, 5, 10);
		}
		else
		{
			this.moveUp(objTab.container.getContainer().arrayPosition, 5, 10);
		}

    var oOffset = Zapatec.Utils.getElementOffset(this.config.tabs);
    // Setup WCH
    Zapatec.Utils.setupWCH(objTab.wch, 0, 0, oOffset.width, oOffset.height);

		Zapatec.Utils.addClass(objTab.linkNode, 'zpTabsActive');
		this.currentIndex = objTab.index;
		// Load content from external source if needed
		if(!objTab.isUrlLoaded && objTab.config.url) {
      objTab.isUrlLoaded = true;
			var self = this;
			Zapatec.Transport.fetch({
					url: objTab.config.url,
					onLoad: function(objRequest){
            // Populate tab
						objTab.setInnerHtml(objRequest.responseText);

            // Restore chooser tab
            objTab.container.getContainer().insertBefore(
                    objTab.chooser,objTab.container.getContainer().childNodes[0]);

            // onTabChange
						if (typeof self.config.onTabChange == 'function') {
							self.config.onTabChange({
                oldTabId: strCurrTabId,
                newTabId: strNewTabId
              });
						}
					}
				});
		} else {
			// onTabChange
			if(typeof this.config.onTabChange == 'function') {
				this.config.onTabChange({
          oldTabId: strCurrTabId,
          newTabId: strNewTabId
        });
			}
		}
	}
};

/**
 * Scrolls tabs to the left. Moves tab(s) by pxInc every timeInc
 *
 * @private
 * @param {boolean} setTab Whether the function should set the selected tab
 * on its own.
 */
Zapatec.AccordionTabs.prototype.moveUp = function(index,pxInc,timeInc) {
  if (false == this.config.IN_MOTION) {
    var date = new Date();
    this.moveStartTime = date.getTime();
    this.moveStartTop = this.config._tabArray[index].offsetTop;
  }

	// Test for index validity
	if(isNaN(index) || index<0 || index>=this.config._tabArray.length)
	{
		// Allow switching again
		this.config.IN_MOTION = false;

		return;
	}

  var date = new Date();
  var time = date.getTime();
  var diffTime = time - this.moveStartTime;
  var inc = (diffTime/timeInc)*pxInc;
  // Calculate movement amount
	var tmp = this.moveStartTop - inc;

	// Test for end condition
	if(tmp<=this.config._tabArray[index].viewingPosition)
	{
		// Make sure everyone is in the proper position by manually
		// assigning the final position. This takes care of remainder errors
		// (for example, if pxInc doesn't evenly divide into the tab height)
		for(var i=index; i<this.config._tabArray.length; i++)
		{
			this.config._tabArray[i].style.top = this.config._tabArray[i].viewingPosition + 'px';
			this.config._tabArray[i].style.height = this.config._tabArray[i].viewingHeight + 'px';
		}

		// Allow switching again
		this.config.IN_MOTION = false;

		return;
	}

  // Stop switching while this process is running
	this.config.IN_MOTION = true;

	// Move chosen tab and any tabs that may be above it
	for(var i=index; i<this.config._tabArray.length; i++)
	{
		tmp = this.config._tabArray[i].offsetTop - pxInc;
		if(tmp>=this.config._tabArray[i].viewingPosition)
		{
			// Adjust position of tab(s)
			this.config._tabArray[i].style.top = tmp + 'px';

			// Adjust tab height to compensate for movement.
			// The tab isn't slid to the top, rather, it is grown. This avoids problems with
			// absolute positioning and hiding overflowing content
			//tab.style.height = (tab.offsetHeight+pxInc) + 'px';
			this.config._tabArray[i].style.height = (this.config._tabArray[i].offsetHeight + pxInc) + 'px';
		}
	}

  var self = this;
  // Go again
  setTimeout(function() {
    self.moveUp(index, pxInc, timeInc);
    }, timeInc);
}

/**
 * Moves tab(s) by pxInc every timeInc
 */
Zapatec.AccordionTabs.prototype.moveDown = function(index,pxInc,timeInc)
{
  if (false == this.config.IN_MOTION) {
    var date = new Date();
    this.moveStartTime = date.getTime();
    this.moveStartTop = this.config._tabArray[index].offsetTop;
  }

	// Test for index validity
	if(isNaN(index) || index<0 || index>=this.config._tabArray.length)
	{
		// Allow switching again
		this.config.IN_MOTION = false;
		return;
	}

  // Get time elapseds since move start
  var date = new Date();
  var time = date.getTime();
  var diffTime = time - this.moveStartTime;
  var inc = (diffTime/timeInc)*pxInc;
  // Calculate movement amount
	var tmp = this.moveStartTop + inc;

	// Test for end condition
	if(tmp>=this.config._tabArray[index].hiddenPosition)
	{
		// Make sure everyone is in the proper position by manually
		// assigning the final position. This takes care of remainder errors
		// (for example, if pxInc doesn't evenly divide into the tab height)
		for(var i=0; i<=index; i++)
		{
			this.config._tabArray[i].style.top = this.config._tabArray[i].hiddenPosition + 'px';
			this.config._tabArray[i].style.height = this.config._tabArray[i].hiddenHeight + 'px';
		}

		// Allow switching again
		this.config.IN_MOTION = false;

		return;
	}

	// Stop switching while this process is running
	this.config.IN_MOTION = true;

	// Move chosen tab and any tabs that may be above it
	for(var i=0; i<=index; i++)
	{
		tmp = this.config._tabArray[i].offsetTop + pxInc;
		if(tmp<=this.config._tabArray[i].hiddenPosition)
		{
			// Adjust position of tab(s)
			this.config._tabArray[i].style.top = tmp + 'px';

			// Adjust tab height to compensate for movement.
			// The tab isn't slid to the top, rather, it is grown. This avoids problems with
			// absolute positioning and hiding overflowing content
			//tab.style.height = (tab.offsetHeight+pxInc) + 'px';
			if(this.config._tabArray[i].offsetHeight>this.config.tabBarHeight)
			{
				this.config._tabArray[i].style.height = (this.config._tabArray[i].offsetHeight - pxInc) + 'px';
			}
		}
	}

	var self = this;
  // Go again
	setTimeout(function() {
    self.moveDown(index, pxInc, timeInc);
    }, timeInc);
}
