/* 

InfoTab Class:
	Version: 0.018
	Date: 20070509
	Author: Marty Stake

Dependencies:
	fixed getBase by exending the element returned if parentHasOnClass is set.

*/

var InfoTabManager = Class.create();

InfoTabManager.prototype = {
		
	initialize: function(baseId, options) {
		
		this.slides = [];
		this.triggers = [];
		
		this.options = { 

			baseName: 			'tabs',
			
			// trigger options
			triggerWrapper: 	'tabs-triggers',
			triggerSelector:	'',
			triggerBaseNode:	 '',
			triggerActivator: 	'a',
			parentHasOnClass: 	false,
			
			// slide options
			slides: 			'',
			useSlideClass: 		false,
			slideBaseWrapper: 	'div',
			
			// main options
			firstIndex: 		1,
			action: 			'click',
			onState: 			'on',
			offState: 			'off',
			hoverState: 		'hover',
			noFollow: 			false,

			// indicator
			onBackground: 		'',
			mode: 				'contained',
			horizontalConstrain: false,
			horizontalRightOffset: 0,
			horizontalLeftOffset: 0,
			verticalOffset: 0
		}
		
		// check to see if the base tab wrapper is the first property passed in the constructor.  If not, pass the options property to the element property, and get the baseId from the options //
		if (typeof baseId == 'string') 
			options.baseName = baseId;
		else {
			options = baseId;
		}
	
		Object.extend( this.options, options || {});
		
		if (!this.options.slides) this.options.slides = this.options.baseName
		
		// set up the trigger items which change the slides  //
		if (this.options.triggerSelector) { // this is here if we dont want to use a separated trigger wrapper div //
			var triggers = $$(this.options.triggerSelector);
		}
		else {
			var triggers = $(this.options.triggerWrapper).getElementsByTagName(this.options.triggerActivator);
		}
	
		this.triggers = $A(triggers);
		this.setTriggers(this.triggers);
		
	
		
		// set up the data that switches when the trigger is clicked //
		this.totalSlides = this.setslides(this.options.slides);
		
		// set up global event handlers //
		Event.observe(this.options.baseName, 'mouseout', this.detectExit.bindAsEventListener(this, this.options.baseName));
		
		this.currentSlide = this.options.firstIndex ? this.options.firstIndex - 1 : 0;
		
		if (this.options.mode == 'contained' || this.options.mode == 'tooltip') this.currentSlide = -1;
		
		
	},
	
	isChildOf: function(el, base) {
		
		var toElement = el;
		
		// see if the element is a child of the base, the base itself (you will get this because of padding), or the trigger //
		if(toElement) {
			if ( ( $(toElement).childOf(base) ) || (base == toElement.id ) || (toElement.id == this.options.triggerWrapper)  ) {
				return true;
			}
		
		return false;
		
		}
		
		
	},
	
	
	detectExit : function(evt, base) {
	
		// get the element that you left //
		var toElement = evt.relatedTarget || evt.toElement;
		
		// check to see if a mouseover element is a child.  if so, return false so we dont run the mouse event //	
		if(this.isChildOf(toElement, base)) {
				return false;
			}
		
		// otherwise, you are leaving the box.  Reset the tabs. //
		this.reset(evt);

	},
	
	setTriggers: function(els) {  // takes array of elements
		for(var x=0; x<els.length; x++) {
			
			els[x].id = els[x].id || this.options.baseName + "-tr-" + x;
		
			// hide the initial state that is on for javascriptless browsers
			this.getBase(els[x].id).removeClassName(this.options.onState);				
			
			if (this.options.mode == 'contained') {
				Event.observe(els[x], 'mouseover', this.show.bind(this) );
			}
			
			if (this.options.mode == 'tooltip') {
				Event.observe(els[x], 'mouseover', this.show.bind(this) );
				Event.observe(els[x], 'mouseout', this.reset.bind(this) );
			}
			
			else {
				Event.observe(els[x], this.options.action, this.show.bind(this) );
			}
			
			if ( (this.options.action != 'click') && (this.options.noFollow) )
					Event.observe(els[x], 'click', function(evt) { Event.stop(evt) } )
			
			els[x].style.outline = 'none';
			els[x].style.cursor = 'pointer';
		
		}

		return els.length;
	},
	
	
	setslides: function(id) { // takes an id

			// if we want to use a class to define the slides //
			if (this.options.useSlideClass) 
				this.slides = $A( $(id).select('.' + this.options.useSlideClass) );
			else 
				this.slides = $A( $(id).getElementsByTagName(this.options.slideBaseWrapper) );
			
			// set an off button if there are off buttons //
			for(var x=0; x<this.slides.length; x++) {
					this.slides[x].id = this.options.baseName + "-sl-" + x;
					
					var offButtons = $(this.slides[x]).select('.close');
					
					if (offButtons != '') Event.observe(offButtons[0], 'click', this.reset.bindAsEventListener(this) )
					
					// set the onmouseout to shut off the slide if it is on //
					Event.observe($(this.slides[x]), 'mouseout', this.detectExit.bindAsEventListener(this, this.slides[x].id))
					
					this.slides[x].style.display = 'none'; // hide em all and set up the initial "on" so it wont goof up the page if JS is off //
					
			}
			
		return this.slides.length;

	},
	
	
	getIndex: function(node, el) { // takes an element //
				
		if (el == null) {
			el = node;
			node = this.triggers;
		}
			
		// do this in case we need to use spans or ems or whatever and we are not using a whole triggerActivator to show the slide //	
		while (node.indexOf(el) < 0) {
			el = el.parentNode;
		}
		
		return node.indexOf(el);
	},
	
	getBase: function(id) { // takes an id, returns element //
	
		// getBase returns the node which carries the on class for the trigger //
		
		// if parentHasOnClass is true, the immediate parent is returned //
		if (this.options.parentHasOnClass) {
			return $( $(id).parentNode );
		}
		
		else {
			
			// otherwise, get the node that will hold the on class //
			if (this.options.triggerBaseNode) {
				var t = $(id).tagName
				var parent = $(id);
				
				while( t != this.options.triggerBaseNode.toUpperCase()) {
					parent = parent.parentNode
					t = parent.tagName
				}
				return $(parent);
			}
			
			// otherwise, the trigger carries the on class //
			else {
				return $(id);
			}
		}
		
	},

	show : function(evt, index) { // takes an index //
		
		// change the behavior of index depending on the mode you are in and set the index //
		if (typeof evt != 'number') {
			index = this.getIndex(this.triggers, Event.element(evt));
		}
		else {
			index = evt; // this would be an index if smartSwap is calling - or a call outside the object //
		}
	
		// hide everything - reset all only if the button is not on already //
		if (this.currentSlide != index) {
			
			this.reset();
			
			this.currentSlide = index;
	
			// run a callback to add any functionality you want after we show //
			if (typeof this.options.beforeShow == 'function') 
				this.options.beforeShow.apply(this, arguments);
	
			// turn on the trigger you rolled over //
			parentNode = this.getBase(this.triggers[index].id)
			parentNode.addClassName(this.options.onState);
			
			var slideId = this.options.baseName + "-sl-" + index;
			
			if (this.options.horizontalConstrain)
				this.horizontalConstrain( $(slideId), $(parentNode));
				
			if (this.options.verticalConstrain) 
				this.verticalConstrain(index);	
			
			this.slideOn( $(slideId) );
		
			// run a callback to add any functionality you want after we show //
			if (typeof this.options.afterShow == 'function') 
				this.options.afterShow.apply(this, arguments);
		}
		
		else {
			return false;
		}
		
	},
	
	slideOn: function(slide) {
		
		if (slide.style.display == 'none') {
			
			if (this.options.effect) {
				if (this.slideEffectOn) this.slideEffectOn.cancel();
				this.slideEffectOn = new Effect.Appear(slide, 
					{ duration: .2, 
					  to: 1 } )
			}
			else {
				slide.style.display = 'block';
			}
			
		}
		
	},
	
	reset : function(evt) {
	
		if (this.options.onBackground) {
			$(this.options.triggerWrapper).style.backgroundImage = ''
			$(this.options.triggerWrapper).style.backgroundPosition = ''
			$(this.options.triggerWrapper).style.backgroundRepeat = ''
		}
		
		this.triggers.each(function(trigger) { 
			var parentNode = this.getBase(trigger.id)
			parentNode.removeClassName(this.options.onState);
			}.bind(this))
			
		this.slides.each(function(slide) {
			$(slide).style.display = 'none';
		}.bind(this))
			
			
		// run a callback to add any functionality you want after we show //
			if (typeof this.options.afterReset == 'function') 
				this.options.afterReset.apply(this, arguments);
				
		if(evt) {
			Event.stop(evt);
		}
		
		// reset currentSlide so that the first //
		this.currentSlide = -1;

	}

}

InfoTabManager.Placement = {

		horizontalConstrain : function(slide, trigger) {
			
			var slideBase = $(this.options.slides);
		
			var triggerOffsetLeft = trigger.offsetLeft; 
			var triggerOffsetRight = trigger.offsetLeft + trigger.getWidth(); 
			
			var useOffset = false;
			var rightShift = this.options.horizontalRightOffset;  // use this to cancel out any shadows or such //
			var leftShift = this.options.horizontalLeftOffset;  // use this to cancel out any shadows or such //
	
			var horizontalOrientation = ($(slideBase).getWidth() - (slide.getWidth() + triggerOffsetLeft + leftShift)) > 0 ? 'left' : 'right';
			
			switch(horizontalOrientation) {
				case 'left' :
					if ( (slide.getWidth() < triggerOffsetRight) || 
						 (slide.getWidth() + triggerOffsetLeft < $(slideBase).getWidth()) )
						useOffset = true
				break
					
				case 'right': 
					if ( (slide.getWidth() + ($(slideBase).getWidth() - triggerOffsetRight) < $(this.options.slides).getWidth()) ) 
					useOffset = false //set this true to use an offset on the right ones //
				break
			}
			
			if (useOffset) {
				var horizontalPosition = horizontalOrientation == 'left' ? triggerOffsetLeft + leftShift : $(this.options.slides).getWidth() - (triggerOffsetRight - rightShift);
			}
			else {
				var horizontalPosition = horizontalOrientation == 'left' ? leftShift : rightShift;
			}
				
			// set the position //
			if (horizontalOrientation == 'left') {
				slide.style.right = '';
				slide.style.left = horizontalPosition + 'px';
			}
			else {
				slide.style.right = horizontalPosition + 'px';
				slide.style.left = '';
			}
			
		
			var triggerBase = $(this.options.triggerWrapper);
			if (this.options.onBackground) {
				// set the background images for the on state //
				triggerBase.style.backgroundImage = 'url(' + this.options.onBackground + ')'
				triggerBase.style.backgroundPosition = triggerOffsetLeft + 70 + 'px 0'
				triggerBase.style.backgroundRepeat = 'no-repeat';
			}
			
		},
	

		verticalConstrain: function(index) {
			// hidden slides need to be positioned absolutely, and the wrapper relatively (or absolutely) -- main height is based off of the base wrapper //
		
			var slideOffset = this.options.verticalOffset;
			
			var baseHeight = $(this.options.baseName).getHeight();
			var totalBasePadding = parseInt($(this.options.baseName).getStyle('padding-top')) + parseInt($(this.options.baseName).getStyle('padding-bottom'));
		
			baseHeight = baseHeight - totalBasePadding;
			
			var parentNode = this.getBase(this.triggers[index].id)
			var triggerTop = parentNode.offsetTop;
			
			var slideId = this.options.baseName + "-sl-" + index;
			
			var slideHeight = $(slideId).getHeight();
			var slideBasePadding = parseInt($(slideId).getStyle('padding-top')) + parseInt($(slideId).getStyle('padding-bottom'));
			var slideBorders = parseInt($(slideId).getStyle('border-top-width')) + parseInt($(slideId).getStyle('border-bottom-width'));
			
			var useScroll = true;
			if (useScroll) {
				if (slideHeight > baseHeight) {
					$(slideId).style.height = baseHeight - slideBasePadding - slideBorders + 'px';
					$(slideId).style.overflow = 'auto';
					
				}
			}
			
			var useOffset = false;
			
			// if the height of the slide is less than the total height of the container, orient to the top. Else, pop to the bottom of the container //
			var verticalOrientation = ((triggerTop + slideHeight) < (baseHeight + slideOffset) ) ? 'top' : 'bottom';
			
			switch(verticalOrientation) {
				case 'top' :
					useOffset = true
				break
							
				case 'bottom': 
					useOffset = false //set this true to use an offset on the bottom ones //
				break
			}
			
			
			if (useOffset) {
				if (verticalOrientation == 'top')
					var verticalPosition = ((triggerTop - slideOffset) > 0) ? triggerTop - slideOffset : -slideOffset;
				else {
					// this is wrong //
					var verticalPosition = 0 + slideOffset;
				}
			}
			else {
				var verticalPosition = 0 - slideOffset;
			}
			
			if (verticalOrientation == 'top') {
				$(slideId).style.top = verticalPosition + 'px';
				$(slideId).style.bottom = 'auto';
			}
			else {
				$(slideId).style.top = 'auto';
				$(slideId).style.bottom = verticalPosition + 'px';
			}
					
		},
			
	
		sendToBack : function(el) {
			
			// see if the animation is still running - if so, stop the effect so we only have one runnning //
			if (this.backEffect) this.backEffect.cancel();
			this.backEffect = new Effect.Fade(el, 
				{ duration: .5, 
				  to: .2 } )
		},
		
		sendToFront : function(el) {
			
			// see if the animation is still running - if so, stop the effect so we only have one runnning //
			if (this.backEffect) this.backEffect.cancel();
			this.backEffect = new Effect.Appear(el, 
				{ duration: .5, 
				  to: .99999 })
				
		}
		
	}
	
// add the add-on methods to the main tab prototype //
Object.extend(InfoTabManager.prototype, InfoTabManager.Placement)

// overwrite prototype's show method to specifically add block -- necessary if we want to use Effect.Appear and hide the background with CSS display: none; //

var newShow = { show : function(element) {
	$(element).style.display = 'block';
	return element;
  	}
  }
// 
Element.addMethods(newShow);
