/*******************************************************
Author:			Tim Miller
Date:			7/16/2009
Filename:		jquery.contentSelector.js

Current version: v.1.04

Change log:
v.1.04	- Modified the midChange callback function timing formula from using just the tDelay to (tDelay+tDuration)/2.
			FROM: setTimeout(function(){ context.doMidChange(); }, context.tDelay*1000);
			TO: setTimeout(function(){ context.doMidChange(); }, (context.tDelay+context.tDuration)/2*1000);
v.1.03	- Added beforeChange, midChange and afterChange callback functions. afterChange replaces callback which is still here for compatibility.
v.1.02 	- Only set the children to position absolute if the transition type is not equal to none.
		- Only set the opacity of the children if the transition type is fade.
v.1.01 	- Fixed next item bug. If you were to go to call nextItem for the same item you were currently on it would turn off the controls then return. Moved the return to above the control deactivation.

***** THIS PLUGIN REQUIRES THE JQUERY LIBRARY *****
***** Some transition effects require the jQuery UI (slideLR) *****

***** 
Required reading:

For some of the transition effects to work properly the children elements must be POSITIONED ABSOLUTELY. When the 
contentSelector is created it sets the position property of the parent element to "RELATIVE" and the position property 
of each of the chilren to "ABSOLUTE." The height of the parent object is set to the height of the tallest child element.
*****
						
"params" is a single object that can have the following properties:

prop. name        | description																	| default
---------------------------------------------------------------------------------------------------------------
*jQSelector       | The jQuery selector for the element containing the children to              | NONE (REQUIRED)
					randomize/flip through.	
childJQSelector   | The jQuery selector for children.											| div
isRand            | If the script should randomly pick the first child to show. boolean			| true
defaultItemIndex  | If isRand is false you can set the first item you would like to show.		| 1
transition        | Type of transition between objects. Options are: none, fade, slide.			| none
	tSlideDir     | Only applicable to the slide transition. Options are: left, right, up, down.| left
tDuration         | Transition duration (seconds).												| 0.5
tDelay            | Transition delay. Default depends on the transition. Enables crossfading...	| null
tMin              | Only applicable to the fade transition. Transition min.						| 0.0
tMax              | Only applicable to the fade transition. Transition max.						| 1.0
slideShow         | Auto change. boolean														| false
ssTimer           | Slideshow timer (seconds).													| 5
beoreChange       | Function runs before item change.                                           | none
midChange         | Function runs mid item change. Uses tDelay time to trigger function.        | none
afterChange       | Function runs after item change is complete.                                | none

Still supported but use options above instead:
callback          | Callback function runs after next item is complete.                         | none

* - Required parameter.
	
This can be called at any time in the body of the document after the parent element has been closed 
or onload of the window.

Examples:
	var inFocusStories = new contentSelector({
		jQSelector :	"div#inFocusContainer"
	});
OR
	var imgRandom = new contentSelector({
		jQSelector			:	"div#inFocusContainer2",
		childJQSelector		:	"img",
		isRand				:	false,
		defaultItemIndex	:	4
	});
*******************************************************/

//Constructor
function contentSelector(params){
	function param_default(pname, def){
		if(typeof params[pname] == "undefined") params[pname] = def;
		return params[pname];
	}
	
	this.jQSelector = param_default("jQSelector", null);
	this.childJQSelector = param_default("childJQSelector", "div");
	this.isRand = param_default("isRand", true);
	this.defaultItemIndex = param_default("defaultItemIndex", 1);
	this.transition = param_default("transition", "none").toLowerCase();
	this.tSlideDir = param_default("tSlideDir", "left").toLowerCase();
	this.tDuration = param_default("tDuration", 0.5);
	this.tDelay = param_default("tDelay", null);
	this.tMin = param_default("tMin", 0.0);
	this.tMax = param_default("tMax", 1.0);
	this.slideShow = param_default("slideShow", false);
	this.ssTimer = param_default("ssTimer", 5);
	this.beforeChange = param_default("beforeChange", function(){});
	this.midChange = param_default("midChange", function(){});
	this.afterChange = param_default("afterChange", function(){});
	this.callback = param_default("callback", function(){});
	
	if(this.jQSelector == null){
		alert("New contentSelector object could not be created. You must specify the jQSelector variable.");
		return;
	}
	if(jQuery(this.jQSelector).length == 0){
		alert("New contentSelector object could not be created, targetParent \"" + this.jQSelector + "\" was not found.");
		return;	
	}
	
	this.firstLoad = true;
	this.numItems = 0;
	this.currentItemIndex = 0;
	this.currentItemObj;
	this.targetParent = jQuery(this.jQSelector);
	this.Rand = contentSelector.Rand;
	this.nextItem = contentSelector.nextItem;
	this.previousItem = contentSelector.previousItem;
	this.setupUpdateInterval = contentSelector.setupUpdateInterval;
	this.clearUpdateInterval = contentSelector.clearUpdateInterval;
	this.startSlideshow = contentSelector.startSlideshow;
	this.stopSlideshow = contentSelector.stopSlideshow;
	this.doBeforeChange = contentSelector.doBeforeChange;
	this.doMidChange = contentSelector.doMidChange;
	this.doAfterChange = contentSelector.doAfterChange;
	this.docallback = contentSelector.docallback;
	this.ssInterval = null;
	this.allowControls = true;
	this.tSlideOpDir;
	this.tSlideDelay;
	
	if(this.transition == "fade"){
		if(this.tDelay == null) this.tDelay = this.tDuration;
	}
	else if(this.transition == "slide"){
		switch(this.tSlideDir){
			case "left":
				this.tSlideOpDir = "right";
				if(this.tDelay == null) this.tDelay = 0.1;
				break;
			case "right":
				this.tSlideOpDir = "left";
				if(this.tDelay == null) this.tDelay = 0.1;
				break;
			case "up":
				this.tSlideOpDir = "down";
				if(this.tDelay == null) this.tDelay = 0.45;
				break;
			case "down":
				this.tSlideOpDir = "up";
				if(this.tDelay == null) this.tDelay = 0.45;
				break;
		}
	}
	
	var context = this;
	var parentHeight = 0;
	this.targetParent.children(this.childJQSelector).each(function(){
		var childObj = jQuery(this);
		context.numItems++;
		if(childObj.attr("id") == ""){
			childObj.attr("id", "CS_"+context.targetParent.attr("id")+"_"+context.numItems);
		}
		if(jQuery(this).outerHeight(true) > parentHeight) parentHeight = jQuery(this).outerHeight(true);
		if(context.transition != "none") childObj.css("position", "absolute");
		if(context.transition == "fade") childObj.css("opacity", context.tMin);
		childObj.css({
			"display": "none"
		});
	});
	
	this.targetParent.css({
		"position": "relative",
		"height": parentHeight+"px"
	});
	
	if(this.isRand){
		this.Rand();
	}else{
		this.nextItem(this.defaultItemIndex);
	}
}

// Function to randomly select a child element inside the containing object to display.
contentSelector.Rand = function(){
	var randNum = Math.ceil(Math.random() * this.numItems);
	this.currentItemIndex = randNum-1;
	
	this.nextItem();
}

// Function to flip through the child elements inside a containing object in order.
contentSelector.nextItem = function(index){
	if(!this.allowControls) return;
	if(typeof index != "undefined" && index == this.currentItemIndex) return;
	this.allowControls = false;
	
	var index = (typeof index == "undefined")?this.currentItemIndex:index-1;
	var nextNum = (index >= this.numItems)?1:index+1;
	var nextItemObj;
	
	if(this.slideShow) this.clearUpdateInterval();
	
	// Set directions for slide effect. Have to do this so the slides move the right direction when jumping to an index 
	// instead of using the next/previous. Need to set these before the currentItemIndex gets set.
	var useDir = (index >= this.currentItemIndex)?this.tSlideDir:this.tSlideOpDir;
	var useOpDir = (index >= this.currentItemIndex)?this.tSlideOpDir:this.tSlideDir;
	
	this.currentItemIndex = nextNum;
	
	this.doBeforeChange();
	
	var context = this;
	var foundItems = 0;
	this.targetParent.children(this.childJQSelector).each(function(){
		var childObj = jQuery(this);
		foundItems++;
		if(foundItems == context.currentItemIndex){
			nextItemObj = childObj;
		}
	});
	
	if(this.firstLoad){
		this.doMidChange();
		if(this.transition == "none" || this.transition == "slide"){
			nextItemObj.css({
				"display": "block"
			});
		}else if(this.transition == "fade"){
			nextItemObj.fadeTo(context.tDuration*1000, context.tMax);
		}
		this.firstLoad = false;
		this.currentItemObj = nextItemObj;
		if(this.slideShow) this.setupUpdateInterval();
		this.allowControls = true;
		this.doAfterChange();
		this.docallback();
	}else{
		if(this.transition == "none"){
			this.currentItemObj.css("display", "none");
			this.doMidChange();
			nextItemObj.css("display", "block");
			this.currentItemObj = nextItemObj;
			if(this.slideShow) this.setupUpdateInterval();
			this.allowControls = true;
			this.doAfterChange();
			this.docallback();
		}
		else if(this.transition == "fade"){
			this.currentItemObj.fadeTo(context.tDuration*1000, context.tMin, function(){
				context.currentItemObj.css("display", "none");
				context.currentItemObj = nextItemObj;
			});
			setTimeout(function(){ context.doMidChange(); }, (context.tDelay+context.tDuration)/2*1000);
			nextItemObj.delay(context.tDelay*1000).css("display", "block").fadeTo(context.tDuration*1000, context.tMax, function(){
				if(context.slideShow) context.setupUpdateInterval();
				context.allowControls = true;
				context.doAfterChange();
				context.docallback();
			});
		}
		else if(this.transition == "slide"){
			this.currentItemObj.delay(12).hide("slide", { direction: useDir }, context.tDuration*1000);
			setTimeout(function(){ context.doMidChange(); }, (context.tDelay+context.tDuration)/2*1000);
			nextItemObj.delay(context.tDelay*1000).show("slide", { direction: useOpDir }, context.tDuration*1000, function(){
				if(context.slideShow) context.setupUpdateInterval();
				context.allowControls = true;
				context.doAfterChange();
				context.docallback();
			});
			context.currentItemObj = nextItemObj;
		}
	}
}

// Function to flip through the child elements inside a containing object in reverse-order.
contentSelector.previousItem = function(index){
	if(!this.allowControls) return;
	this.allowControls = false;

	var index = (typeof index == "undefined")?this.currentItemIndex:index-1;
	var prevNum = (index <= 1)?this.numItems:index-1;
	var previousItemObj;
	
	if(this.slideShow) this.clearUpdateInterval();
	
	this.currentItemIndex = prevNum;
	
	this.doBeforeChange();
	
	var context = this;
	var foundItems = 0;
	this.targetParent.children(this.childJQSelector).each(function(){
		var childObj = jQuery(this);
		foundItems++;
		if(foundItems == context.currentItemIndex){
			previousItemObj = childObj;
		}
	});
	
	if(this.firstLoad){
		this.doMidChange();
		previousItemObj.css({
			"display": "block"
		});
		this.firstLoad = false;
		this.currentItemObj = previousItemObj;
		if(this.slideShow) this.setupUpdateInterval();
		this.allowControls = true;
		this.doAfterChange();
		this.docallback();
	}else{
		if(this.transition == "none"){
			this.currentItemObj.css("display", "none");
			this.doMidChange();
			previousItemObj.css("display", "block");
			this.currentItemObj = previousItemObj;
			if(context.slideShow) context.setupUpdateInterval();
			context.allowControls = true;
			context.doAfterChange();
			context.docallback();
		}
		else if(this.transition == "fade"){
			this.currentItemObj.fadeTo(context.tDuration*1000, context.tMin, function(){
				context.currentItemObj.css("display", "none");
				context.currentItemObj = previousItemObj;
			});
			setTimeout(function(){ context.doMidChange(); }, (context.tDelay+context.tDuration)/2*1000);
			previousItemObj.delay(context.tDelay*1000).css("display", "block").fadeTo(context.tDuration*1000, context.tMax, function(){
				if(context.slideShow) context.setupUpdateInterval();
				context.allowControls = true;
				context.doAfterChange();
				context.docallback();
			});
		}
		else if(this.transition == "slide"){
			this.currentItemObj.delay(12).hide("slide", { direction: context.tSlideOpDir }, context.tDuration*1000);
			setTimeout(function(){ context.doMidChange(); }, (context.tDelay+context.tDuration)/2*1000);
			previousItemObj.delay(context.tDelay*1000).show("slide", { direction: context.tSlideDir }, context.tDuration*1000, function(){
				if(context.slideShow) context.setupUpdateInterval(); 
				context.allowControls = true;
				context.doAfterChange();
				context.docallback();
			});
			context.currentItemObj = previousItemObj;
		}
	}
}

// Slideshow functions
contentSelector.setupUpdateInterval = function(){
	var context = this;
	this.ssInterval = window.setInterval(function(){ context.nextItem() }, this.ssTimer*1000);
}
contentSelector.clearUpdateInterval = function(){
	window.clearInterval(this.ssInterval);
	this.ssInterval = null;
}
contentSelector.startSlideshow = function(){
	this.slideShow = true;
	this.setupUpdateInterval();
}
contentSelector.stopSlideshow = function(){
	this.slideShow = false;
	this.clearUpdateInterval();
}
// Callback functions
contentSelector.doBeforeChange = function(){
	if(this.beforeChange != null){
		this.beforeChange();
	}
}
contentSelector.doMidChange = function(){
	if(this.midChange != null){
		this.midChange();
	}
}
contentSelector.doAfterChange = function(){
	if(this.afterChange != null){
		this.afterChange();
	}
}
contentSelector.docallback = function(){
	if(this.callback != null){
		this.callback();
	}
}
