/* SpryAccordion.js - Revision: Spry Preview Release 1.4 */

// Copyright (c) 2006. Adobe Systems Incorporated.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of Adobe Systems Incorporated nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.


/* Edited by Tom Mullier, Finding Sanctuary (September 2009) to allow nested accordion functionality.
   Updates are within the openPanel(panel) object, animation overlays panels if enabled and slows down
   Internet Explorer. Only allows one master accordion, change the variable topLevelAccordion within
   the openPanel object to reference the master accordion name (e.g. Accordion1). Could be extended to 
   allow arbitrary nested accordions, but need a method of accessing the name of the top level object.  
   Internet Explorer throws an error in function getElementChildren - doesn't seem to affect overall
   functionality though.										*/


var Spry;
if (!Spry) Spry = {};
if (!Spry.Widget) Spry.Widget = {};

Spry.Widget.Accordion = function(element, opts)
{
	this.element = this.getElement(element);
	this.defaultPanel = 0;
	this.hoverClass = "AccordionPanelTabHover";
	this.openClass = "AccordionPanelOpen";
	this.closedClass = "AccordionPanelClosed";
	this.focusedClass = "AccordionFocused";
	this.enableAnimation = false;
	this.enableKeyboardNavigation = true;
	this.currentPanel = null;
	this.animator = null;
	this.hasFocus = null;
	this.duration = 500;

	this.previousPanelKeyCode = Spry.Widget.Accordion.KEY_UP;
	this.nextPanelKeyCode = Spry.Widget.Accordion.KEY_DOWN;

	this.useFixedPanelHeights = true;
	this.fixedPanelHeight = 0;

	Spry.Widget.Accordion.setOptions(this, opts, true);

	// Unfortunately in some browsers like Safari, the Stylesheets our
	// page depends on may not have been loaded at the time we are called.
	// This means we have to defer attaching our behaviors until after the
	// onload event fires, since some of our behaviors rely on dimensions
	// specified in the CSS.

	if (Spry.Widget.Accordion.onloadDidFire)
		this.attachBehaviors();
	else
		Spry.Widget.Accordion.loadQueue.push(this);
};

Spry.Widget.Accordion.onloadDidFire = false;
Spry.Widget.Accordion.loadQueue = [];

Spry.Widget.Accordion.addLoadListener = function(handler)
{
	if (typeof window.addEventListener != 'undefined')
		window.addEventListener('load', handler, false);
	else if (typeof document.addEventListener != 'undefined')
		document.addEventListener('load', handler, false);
	else if (typeof window.attachEvent != 'undefined')
		window.attachEvent('onload', handler);
};

Spry.Widget.Accordion.processLoadQueue = function(handler)
{
	Spry.Widget.Accordion.onloadDidFire = true;
	var q = Spry.Widget.Accordion.loadQueue;
	var qlen = q.length;
	for (var i = 0; i < qlen; i++)
		q[i].attachBehaviors();
};

Spry.Widget.Accordion.addLoadListener(Spry.Widget.Accordion.processLoadQueue);

Spry.Widget.Accordion.prototype.getElement = function(ele)
{
	if (ele && typeof ele == "string")
		return document.getElementById(ele);
	return ele;
};

Spry.Widget.Accordion.prototype.addClassName = function(ele, className)
{
	if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) != -1))
		return;
	ele.className += (ele.className ? " " : "") + className;
};

Spry.Widget.Accordion.prototype.removeClassName = function(ele, className)
{
	if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) == -1))
		return;
	ele.className = ele.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), "");
};

Spry.Widget.Accordion.setOptions = function(obj, optionsObj, ignoreUndefinedProps)
{
	if (!optionsObj)
		return;
	for (var optionName in optionsObj)
	{
		if (ignoreUndefinedProps && optionsObj[optionName] == undefined)
			continue;
		obj[optionName] = optionsObj[optionName];
	}
};

Spry.Widget.Accordion.prototype.onPanelTabMouseOver = function(panel)
{
	if (panel)
		this.addClassName(this.getPanelTab(panel), this.hoverClass);
};

Spry.Widget.Accordion.prototype.onPanelTabMouseOut = function(panel)
{
	if (panel)
		this.removeClassName(this.getPanelTab(panel), this.hoverClass);
};

Spry.Widget.Accordion.prototype.openPanel = function(panel)
{
	var panelA = this.currentPanel;
	var panelB = panel;
	
	if (!panelB || panelA == panelB)	
		return;

	var contentA; 
	if( panelA )
		contentA = this.getPanelContent(panelA);
	var contentB = this.getPanelContent(panelB);

	if (! contentB)
		return;

	if (this.useFixedPanelHeights && !this.fixedPanelHeight)
	{
		this.fixedPanelHeight = (contentA.offsetHeight) ? contentA.offsetHeight : contentA.scrollHeight;
	}

	if (this.enableAnimation)
	{
		if (this.animator)
			this.animator.stop();
		this.animator = new Spry.Widget.Accordion.PanelAnimator(this, panelB, { duration: this.duration });
		this.animator.start();
	}
	else
	{
		if(contentA)
			contentA.style.height = "0px";
		contentB.style.height = (this.useFixedPanelHeights ? this.fixedPanelHeight : contentB.scrollHeight) + "px";
	}

	if(panelA)
	{
		this.removeClassName(panelA, this.openClass);
		this.addClassName(panelA, this.closedClass);
	}

	this.removeClassName(panelB, this.closedClass);
	this.addClassName(panelB, this.openClass);

	this.currentPanel = panelB;

	/* =================================================================
		Tom's edits - allow nested accordion objects (09/2009).
		A little slow in IE, maybe disable animation?
		'Accordion1' is the name of the master accordion
	================================================================= */
	
	/* ==== Defines the Top Level accordion.  Change as required ==== */
	topLevelAccordion = Accordion1;
	/* ============================================================== */
	
	var panelC = topLevelAccordion.currentPanel;
	var contentC = topLevelAccordion.getPanelContent(panelC);
	var panels = this.getPanels();
	var numberPanels = panels.length+1;
	var newPanelHeight = contentB.scrollHeight;
	var panels2 = topLevelAccordion.getPanels();
	
	//Iterate through and adjust the size of the main accordions panels
	for(var i=0; i < panels2.length; i++ )
	{
		if (panels2[i] == panelC) {
			panelC.style.height = newPanelHeight + (numberPanels*23) + "px";
		} else {
			panels2[i].style.height = '23px';
		}
	}	

	//Adjust the height of the main accordions (topLevelAccordion) open panel content
	if (this != Accordion1) {
		var offset2 = newPanelHeight + (panels.length*23);
		
		if (contentC.style.height != '0px') {
			contentC.style.height = offset2 + "px";
		}
	}
	
	//Iterate through and adjust the size of the open internal accordions panels.
	for(var b=0; b < panels.length; b++ )
	{
		if (panels[b] == this.currentPanel) {
			panels[b].style.height = newPanelHeight + 23 + "px";
	
		} else {
			panels[b].style.height = '23px';
		}
	}
	
	//End of Tom's edits
};

Spry.Widget.Accordion.prototype.openNextPanel = function()
{
	var panels = this.getPanels();
	var curPanelIndex = this.getCurrentPanelIndex();
	
	if( panels && curPanelIndex >= 0 && (curPanelIndex+1) < panels.length )
		this.openPanel(panels[curPanelIndex+1]);
};

Spry.Widget.Accordion.prototype.openPreviousPanel = function()
{
	var panels = this.getPanels();
	var curPanelIndex = this.getCurrentPanelIndex();
	
	if( panels && curPanelIndex > 0 && curPanelIndex < panels.length )
		this.openPanel(panels[curPanelIndex-1]);
};

Spry.Widget.Accordion.prototype.openFirstPanel = function()
{
	var panels = this.getPanels();
	if( panels )
		this.openPanel(panels[0]);
};

Spry.Widget.Accordion.prototype.openLastPanel = function()
{
	var panels = this.getPanels();
	if( panels )
		this.openPanel(panels[panels.length-1]);
};

Spry.Widget.Accordion.prototype.onPanelClick = function(panel)
{
	// if (this.enableKeyboardNavigation)
	// 	this.element.focus();
	if (panel != this.currentPanel)
		this.openPanel(panel);
	this.focus();
};

Spry.Widget.Accordion.prototype.onFocus = function(e)
{
	// this.element.focus();
	this.hasFocus = true;
	this.addClassName(this.element, this.focusedClass);
};

Spry.Widget.Accordion.prototype.onBlur = function(e)
{
	// this.element.blur();
	this.hasFocus = false;
	this.removeClassName(this.element, this.focusedClass);
};

Spry.Widget.Accordion.KEY_UP = 38;
Spry.Widget.Accordion.KEY_DOWN = 40;

Spry.Widget.Accordion.prototype.onKeyDown = function(e)
{
	var key = e.keyCode;
	if (!this.hasFocus || (key != this.previousPanelKeyCode && key != this.nextPanelKeyCode))
		return true;
	
	var panels = this.getPanels();
	if (!panels || panels.length < 1)
		return false;
	var currentPanel = this.currentPanel ? this.currentPanel : panels[0];
	var nextPanel = (key == this.nextPanelKeyCode) ? currentPanel.nextSibling : currentPanel.previousSibling;
	
	while (nextPanel)
	{
		if (nextPanel.nodeType == 1 /* Node.ELEMENT_NODE */)
			break;
		nextPanel = (key == this.nextPanelKeyCode) ? nextPanel.nextSibling : nextPanel.previousSibling;
	}
	
	if (nextPanel && currentPanel != nextPanel)
		this.openPanel(nextPanel);

	if (e.stopPropagation)
		e.stopPropagation();
	if (e.preventDefault)
		e.preventDefault();

	return false;
};

Spry.Widget.Accordion.prototype.attachPanelHandlers = function(panel)
{
	if (!panel)
		return;

	var tab = this.getPanelTab(panel);

	if (tab)
	{
		var self = this;
		Spry.Widget.Accordion.addEventListener(tab, "click", function(e) { return self.onPanelClick(panel); }, false);
		Spry.Widget.Accordion.addEventListener(tab, "mouseover", function(e) { return self.onPanelTabMouseOver(panel); }, false);
		Spry.Widget.Accordion.addEventListener(tab, "mouseout", function(e) { return self.onPanelTabMouseOut(panel); }, false);
	}
};

Spry.Widget.Accordion.addEventListener = function(element, eventType, handler, capture)
{
	try
	{
		if (element.addEventListener)
			element.addEventListener(eventType, handler, capture);
		else if (element.attachEvent)
			element.attachEvent("on" + eventType, handler);
	}
	catch (e) {}
};

Spry.Widget.Accordion.prototype.initPanel = function(panel, isDefault)
{
	var content = this.getPanelContent(panel);
	if (isDefault)
	{
		this.currentPanel = panel;
		this.removeClassName(panel, this.closedClass);
		this.addClassName(panel, this.openClass);
	}
	else
	{
		this.removeClassName(panel, this.openClass);
		this.addClassName(panel, this.closedClass);
		content.style.height = "0px";
	}
	
	this.attachPanelHandlers(panel);
};

Spry.Widget.Accordion.prototype.attachBehaviors = function()
{
	var panels = this.getPanels();
	for (var i = 0; i < panels.length; i++)
	{
		this.initPanel(panels[i], i == this.defaultPanel);
	}

	if (this.enableKeyboardNavigation)
	{
		// XXX: IE doesn't allow the setting of tabindex dynamically. This means we can't
		// rely on adding the tabindex attribute if it is missing to enable keyboard navigation
		// by default.

		var tabIndexAttr = this.element.attributes.getNamedItem("tabindex");
		// if (!tabIndexAttr) this.element.tabindex = 0;
		if (tabIndexAttr)
		{
			var self = this;
			Spry.Widget.Accordion.addEventListener(this.element, "focus", function(e) { return self.onFocus(e); }, false);
			Spry.Widget.Accordion.addEventListener(this.element, "blur", function(e) { return self.onBlur(e); }, false);
			Spry.Widget.Accordion.addEventListener(this.element, "keydown", function(e) { return self.onKeyDown(e); }, false);
		}
	}
};

Spry.Widget.Accordion.prototype.getPanels = function()
{
	return this.getElementChildren(this.element);
};

Spry.Widget.Accordion.prototype.getCurrentPanel = function()
{
	return this.currentPanel;
};

Spry.Widget.Accordion.prototype.getCurrentPanelIndex = function()
{
	var panels = this.getPanels();
	for( var i = 0 ; i < panels.length; i++ )
	{
		if( this.currentPanel == panels[i] )
			return i;
	}
	return 0;
};

Spry.Widget.Accordion.prototype.getPanelTab = function(panel)
{
	if (!panel)
		return null;
	return this.getElementChildren(panel)[0];
};

Spry.Widget.Accordion.prototype.getPanelContent = function(panel)
{
	if (!panel)
		return null;
	return this.getElementChildren(panel)[1];
};

Spry.Widget.Accordion.prototype.getElementChildren = function(element)
{
	var children = [];
	var child = element.firstChild;

	//IE throws (apparantly non-critical) error here
	//if (element.firstChild != null) { child = element.firstChild; }

	while (child)
	{
		if (child.nodeType == 1 /* Node.ELEMENT_NODE */)
			children.push(child);
		child = child.nextSibling;
	}
	return children;
};




Spry.Widget.Accordion.prototype.focus = function()
{
	if (this.element && this.element.focus)
		this.element.focus();
};

/////////////////////////////////////////////////////

Spry.Widget.Accordion.PanelAnimator = function(accordion, panel, opts)
{
	this.timer = null;
	this.interval = 0;
	this.stepCount = 0;

	this.fps = 0;
	this.steps = 10;
	this.duration = 500;
	this.onComplete = null;

	this.panel = panel;
	this.panelToOpen = accordion.getElement(panel);
	this.panelData = [];

	Spry.Widget.Accordion.setOptions(this, opts, true);


	// If caller specified speed in terms of frames per second,
	// convert them into steps.

	if (this.fps > 0)
	{
		this.interval = Math.floor(1000 / this.fps);
		this.steps = parseInt((this.duration + (this.interval - 1)) / this.interval);
	}
	else if (this.steps > 0)
		this.interval = this.duration / this.steps;

	// Set up the array of panels we want to animate.

	var panels = accordion.getPanels();
	for (var i = 0; i < panels.length; i++)
	{
		var p = panels[i];
		var c = accordion.getPanelContent(p);
		if (c)
		{
			var h = c.offsetHeight;
			if (h == undefined)
				h = 0;
			if (p == panel || h > 0)
			{
				var obj = new Object;
				obj.panel = p;
				obj.content = c;
				obj.fromHeight = h;
				obj.toHeight = (p == panel) ? (accordion.useFixedPanelHeights ? accordion.fixedPanelHeight : c.scrollHeight) : 0;
				obj.increment = (obj.toHeight - obj.fromHeight) / this.steps;
				obj.overflow = c.style.overflow;
				this.panelData.push(obj);

				c.style.overflow = "hidden";
				c.style.height = h + "px";
			}
		}
	}
};

Spry.Widget.Accordion.PanelAnimator.prototype.start = function()
{
	var self = this;
	this.timer = setTimeout(function() { self.stepAnimation(); }, this.interval);
};

Spry.Widget.Accordion.PanelAnimator.prototype.stop = function()
{
	if (this.timer)
	{
		clearTimeout(this.timer);

		// If we're killing the timer, restore the overflow
		// properties on the panels we were animating!

		if (this.stepCount < this.steps)
		{
			for (i = 0; i < this.panelData.length; i++)
			{
				obj = this.panelData[i];
				obj.content.style.overflow = obj.overflow;
			}
		}
	}

	this.timer = null;
};

Spry.Widget.Accordion.PanelAnimator.prototype.stepAnimation = function()
{
	++this.stepCount;

	this.animate();

	if (this.stepCount < this.steps)
		this.start();
	else if (this.onComplete)
		this.onComplete();
};

Spry.Widget.Accordion.PanelAnimator.prototype.animate = function()
{
	var i, obj;

	if (this.stepCount >= this.steps)
	{
		for (i = 0; i < this.panelData.length; i++)
		{
			obj = this.panelData[i];
			if (obj.panel != this.panel)
				obj.content.style.height = "0px";
			obj.content.style.overflow = obj.overflow;
			obj.content.style.height = obj.toHeight + "px";
		}
	}
	else
	{
		for (i = 0; i < this.panelData.length; i++)
		{
			obj = this.panelData[i];
			obj.fromHeight += obj.increment;
			obj.content.style.height = obj.fromHeight + "px";
		}
	}
};

