﻿/*
JTILITIES.JS
------------------
Utilities and functions for @Ford which rely on JQuery.

Last Modified:
	2009-05-01 sshepar3:
		Consolidated FordUserProfile.js into jtilities.js.
		Commented-out text resize functions.


/* allow window to behave like an extendable object */
$.extend(window,{});

/* detect browser */
if (window.ActiveXObject) window.ie = window[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
else window.ie = false;

/* fix ie6 image caching bug */
if (window.ie6)  document.execCommand("BackgroundImageCache", false, true);


/* The VPN adds extra HTML docs, so we always need to filter for the correct one. */
var thisHtml = $('#ctl00_Html1');

/* use JS class to display or hide elements using CSS when javascript is loaded. */
$(thisHtml).addClass('JS');


/*
FUNCTION: (void) fedsearch
---------------------------------
Sets the fedsearch action based on the type of search.
*/
function fedsearch(suppressErrors){
	switch ($('#fedsearch_s').val()){
		case 'All Sites': $('#fedsearch').get(0).action = "https://search.sp.ford.com/Pages/Results.aspx"; break;
		case 'This Site': 
			$('#fedsearch').get(0).action = "http://www.at.ford.com/Search/Results.aspx"; 
			$('#fedsearch_s').val('All Sites');
			break;
		case 'People': $('#fedsearch').get(0).action = "https://search.sp.ford.com/Pages/peopleresults.aspx"; break;
	}
	if (!suppressErrors && trimMore($('#fedsearch_k').val()).length == 0) {
		alert('Please enter one or more search words.');
		return false;
	}
	return true;
}




/*
FUNCTION: (void) textSizeEvents
---------------------------------
Creates events that change text size.

function textSizeEvents(){
	$('#text_sm a', thisHtml).click(function(){
		createCookie('FordECTextSize', 'small');
		setCSSFromCookie()
	});
	
	$('#text_md a', thisHtml).click(function(){
		createCookie('FordECTextSize', 'medium');
		setCSSFromCookie()
	});

	$('#text_lg a', thisHtml).click(function(){
		createCookie('FordECTextSize', 'large');
		setCSSFromCookie()
	});
	
}*/

/*
FUNCTION: (void) setCSSFromCookie
------------------------------
Inserts the correct CSS file depending on the user's selection.
setTimeout*() fixes IE6 DHTML update bug.

function setCSSFromCookie(){
	var textSize = readCookie('FordECTextSize') || 'medium';
	switch (textSize) {
		case 'small': 
			setTimeout(function(){
				$('link[id$=text_size_stylesheet]', thisHtml).attr('href', '/Style%20Library/en-us/Core%20Styles/text_sm.css');
			}, 100);
			createCookie('FordECTextSize', 'small');
			break;

		case 'large':
			setTimeout(function(){
				$('link[id$=text_size_stylesheet]', thisHtml).attr('href', '/Style%20Library/en-us/Core%20Styles/text_lg.css');
			}, 100);
			createCookie('FordECTextSize', 'large');
			break;

		case 'medium':
		default:
			setTimeout(function(){
				$('link[id$=text_size_stylesheet]', thisHtml).attr('href', 'none');
			}, 100);
			createCookie('FordECTextSize', 'medium');
			break;
	}
}
*/



/*
FUNCTION: (void) navMain
----------------------------
The behavior logic for the primary navigation menus.
*/
var navMain = function(open) {
	return function() {
		var self = this;
		var ul = $("ul", this);
		var hasSubNav = (ul.length == 0) ? false : true;
		
		if (open) {
			
			if (hasSubNav) ul.slideUp("fast", function(){ $(self).removeClass('open'); });
			else $(self).removeClass('open');
			
		} else {
			
			if (!$(self).hasClass("open")) {
				
				if (hasSubNav) {
					ul.slideDown("fast", function(){ $(self).addClass('open'); });
					$(self).addClass("open");
				} else {
					$(self).addClass("open");
				}
				
			}
		}
	};
}


/*
FUNCTION: (void) hideEmptyAreas
----------------------------
Certain areas of the site should have all formatting removed if there is no content within.
*/
function hideEmptyAreas(){
	/*
	Detect if there is an article photo.  If not, hide the photo area.
	*/
	$('div.article_photo', thisHtml).each(function(i, photo){
		if ($(photo).find('img').length == 0) $(photo).remove();
	});
	
	/* 
	Remove photo caption styling if no caption. 
	*/
	$('div.article_photo', thisHtml).each(function(i, photo){
		var caption = $($(photo).find('h5>div'));
		if (trimMore(caption.text()).length == 0) $(photo).find('h5').remove();
	});

	
	
	/*
	Detect if there are any related pages.  If not, hide the "Related Materials" area. 
	Otherwise, make sure Related Materials line items without content are hidden.
	Set the link target="_blank".
	*/
	var relatedMaterial = $('div.related_material', thisHtml);
	var relatedMaterials = relatedMaterial.find('li');
	
	if (relatedMaterials.length > 0) {
		relatedMaterials.each(function(i, li){
		//work around test link can't be clicked issue. Defect 495
			if (trimMore($(li).text()).length == 0) {
			    $(li).remove();
			} else {
				$(li).find('a').attr('target', '_blank');
			}
		});
	}
	
	relatedMaterials = relatedMaterial.find('li');
	if (relatedMaterials.length == 0) {
		relatedMaterial.remove();
	}
	
	
	/* if article photo and related material are removed, then collapse the right column */
	if ($('div.article_photo', thisHtml).length + relatedMaterial.find('li').length == 0){
		$('.pageContent', thisHtml).css('margin-right', 0);
	}
	
	/*
	Detect byline content (.author) and remove byline_prefix if it is empty.
	*/
	$('.author', thisHtml).each(function(i){
		var byline = $($('.byline_prefix + .author')[i], thisHtml);
		if (trimMore(byline.text()).length == 0 ) {
			$(byline).parent().find('.byline_prefix').css('visibility','hidden');
		}
	});
	
	$('#feature span.author', thisHtml).each(function(i, byline){
		var text = trimMore($(byline).text()).toLowerCase(); 
		if (text.length == 0 || text == 'by') {
			$(byline).css('visibility','hidden');
		}
	});
	
	$('#feature div.rounded_box_white_inner', thisHtml).each(function(i, div) {
		if (trimMore($(div).html()).length == 0) $('#feature').hide();
	});
	
	
}


/*
FUNCTION: (String) trimMore
--------------------------------
Removes beginning and ending whitespace as well as any "&nbsp;".  
JQuery .trim() does not strip &nbsp;, but Sharepoint seems to add &nbsp; when a field is empty.

PARAMETERS:
============
	str (String) - The string to trim.
*/
function trimMore(str){
	return (str || "").replace(/[^a-zA-Z0-9]/g,"");
}


/*
FUNCTION (void) createCookie
--------------------------------
Creates a cookie.  Adopted from www.quirksmode.org.

PARAMETERS:
============
	name (String): The name of the cookie.
	value (String): The contents of the cookie.
	days (int): Number of days cookie is valid.

UASAGE:
	createCookie('FordStockQuote', '1.33, 10:06AM ET, -0.000, etc...', 30);
*/
function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

/*
FUNCTION (void) readCookie
--------------------------------
Returns the value of a cookie.  Adopted from www.quirksmode.org.

PARAMETERS:
============
	name (String): The name of the cookie.

UASAGE:
	var stockTickerValues = readCookie('FordStockQuote');
*/
function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

/*
FUNCTION (void) eraseCookie
--------------------------------
Removes a cookie.  Adopted from www.quirksmode.org.

PARAMETERS:
============
	name (String): The name of the cookie.

UASAGE:
	eraseCookie('FordStockQuote');
*/
function eraseCookie(name) {
	createCookie(name,"",-1);
}


/*
FUNCTION: (Array) $keys
	Returns an array containing all the keys in the object passed.  Taken from Mootools 1.11

Returns:
	An array containing all the values of the hash
*/
var $keys = function(obj){
	var keys = [];
	for (var property in obj) keys.push(property);
	return keys;
}

/*
FUNCTION: (Array) $values
	Returns an array containing all the values, in the same order as the keys returned by <Hash.keys>.  Taken from Mootools 1.11

Returns:
	An array containing all the values of the hash
*/
var $values = function(obj){
	var values = [];
	for (var property in obj) values.push(obj[property]);
	return values;
}

/* 
FUNCTION (Object) $defined
-------------------------
Determines if a variable has been defined and is not null.
*/
function $defined(obj) {
	return (obj != undefined && obj != null) ? true : false;
}

/* 
FUNCTION (Object) $choose
-------------------------
Returns the first object with a value.
*/
function $choose(){
	for (var i=0,len=arguments.length;i<=len; i++){
		var arg = arguments[i];
		if ($defined(arg)) return arg;
	}
	return null;
};


/*
FUNCTION $type
-----------------------
Returns the type of the object passed.  Borrowed from Mootools 1.11.  
*/
function $type(obj){
	if (obj == undefined) return false;
	if (obj.htmlElement) return 'element';
	var type = typeof obj;
	if (type == 'object' && obj.nodeName){
		switch(obj.nodeType){
			case 1: return 'element';
			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
		}
	}
	if (type == 'object' || type == 'function'){
		switch(obj.constructor){
			case Array: return 'array';
			case RegExp: return 'regexp';
		}
		if (typeof obj.length == 'number'){
			if (obj.item) return 'collection';
			if (obj.callee) return 'arguments';
		}
	}
	return type;
};


/*
Class: Class 
	The base class object of the <http://mootools.net> framework.
	Creates a new class, its initialize method will fire upon class instantiation.
	Initialize wont fire on instantiation when you pass *null*.

Arguments:
	properties - the collection of properties that apply to the class.

Example:
	(start code)
	var Cat = new Class({
		initialize: function(name){
			this.name = name;
		}
	});
	var myCat = new Cat('Micia');
	alert(myCat.name); //alerts 'Micia'
	(end)
*/

var Class = function(properties){
	var klass = function(){
		return (arguments[0] !== null && this.initialize && $type(this.initialize) == 'function') ? this.initialize.apply(this, arguments) : this;
	};
	$extend(klass, this);
	klass.prototype = properties;
	klass.constructor = Class;
	return klass;
};
var $extend = function(){
	var args = arguments;
	if (!args[1]) args = [this, args[0]];
	for (var property in args[1]) args[0][property] = args[1][property];
	return args[0];
};
Function.prototype.bind = function(bind, args){
	return this.create({'bind': bind, 'arguments': args});
}
Function.prototype.create = function(options){
	var fn = this;
	options = $.extend({
		'bind': fn,
		'event': false,
		'arguments': null,
		'delay': false,
		'periodical': false,
		'attempt': false
	}, options);
	if ($defined(options.arguments) && $type(options.arguments) != 'array') options.arguments = [options.arguments];
	return function(event){
		var args;
		if (options.event){
			event = event || window.event;
			args = [(options.event === true) ? event : new options.event(event)];
			if (options.arguments) $.extend(args, options.arguments);
		}
		else args = options.arguments || arguments;
		var returns = function(){
			return fn.apply($choose(options.bind, fn), args);
		};
		if (options.delay) return setTimeout(returns, options.delay);
		if (options.periodical) return setInterval(returns, options.periodical);
		if (options.attempt) try {return returns();} catch(err){return false;};
		return returns();
	};
}

/*
CLASS NAME: CharacterCounter
----------------------------------------------------------------------
Finds character counter components in the HTML and initializes them.  For text fields, use the maxlength attribute.  
For textarea fields, use a class name like limitis255, where "limitis" is the class name prefix and the number is the max length.

USAGE:
============
<script>
$(function({
	var charlimitEls = $('.charlimit');
	var charlimitReadouts = $('.charlimit_readout');
	charlimitEls.each(function(i, charEl){
		try {
			new CharacterCounter(charEl, charlimitReadouts[i]);
		} catch (e) {}
	});
});
</script>

<label for="charlimit_demo">Character Limit Demo (text):</label>
<input type="text" id="charlimit_demo" class="charlimit" maxlength="50" />
<div class="charlimit_readout">max: 50 char | 50 char remain</div>

<!-- or -->

<label for="charlimit_demo2">Character Limit Demo (textarea):</label>
<textarea id="charlimit_demo2" class="charlimit limitis255" />
<div class="charlimit_readout">max: 255 char | 255 char remain</div>

*/
var CharacterCounter = new Class({
	
	note_class: 'ms-formdescription',
	limit_class_prefix: 'limitis',
	
	initialize: function(field, readoutEl, maxlength, activeLimit){
		field = $(field);
		if (field.length == 0) return;
		
		if (maxlength) this.maxlength = maxlength;
		else {
			if (field.attr('maxlength')) this.maxlength = field.attr('maxlength');
			else this.maxlength = getClassSuffix(field, this.limit_class_prefix);
			if (!this.maxlength) return;
		}
		
		this.field = field;
		this.readoutEl = this.createReadout(readoutEl);
		this.activeLimit = activeLimit;
		this.updateReadout();
		this.events();
		return this;
	},
	
	createReadout: function(readoutEl){
		if (!readoutEl || readoutEl.length == 0){
			var readoutEl = document.createElement('p');
			readoutEl.className = this.note_class;
			$(this.field.parent()).append(readoutEl);
		}
		return $(readoutEl);
	},
		
	updateReadout: function(){
		var currentLength = this.field.val().length;
		var remaining = this.maxlength-currentLength;
		if (this.activeLimit && remaining < 0) {
			this.field.val(this.field.val().substring(0,this.maxlength ));
			currentLength = this.maxlength ;
			remaining = 0;
		}

		this.readoutEl.html('(max: ' + this.maxlength + ' char | ' + remaining + ' char remaining)');
		if (remaining < 0) this.readoutEl.addClass('error');
		else this.readoutEl.removeClass('error');
	},
	
	events: function(){
		/* keydown might be too agressive 
		this.field.keydown(function(){ this.updateReadout() }.bind(this) );*/
		this.field.keyup(function(){ this.updateReadout() }.bind(this) );
	}
	
});


/*
FUNCTION: (String) getClassSuffix
--------------------------------------
Returns the end of a class name of a given element and class name prefix.

PARAMETERS:
=============
	el (Element, jQuery wrapper): The element tag to look at.
	prefix (String): The beginning of the class whose suffix we are interested in.
	
USAGE:
=======
<textarea class="limival255">text goes here</textarea>
var maxchars = getClassSuffix($('textarea').get(0), 'limitval');
*/
function getClassSuffix(el, prefix){
	el = ($type(el) == 'object') ? ($type(el.get(0)) == 'element') ? el.get(0) : el : el;
	var classes = el.className.split(" ");
	var suffix = ""; 
	
	for (var i=0, klass; klass = classes[i]; i++) {
	
		if (klass.indexOf(prefix) == 0) {
			try {
				suffix = klass.slice(prefix.length);
				break;
			} catch (e) { }
		}
	}
	return suffix;
}



/* FUNCTION: (XmlHttpRequest) ajaxContent
----------------------------------------------------
Gets content from a page by using ajax. 

PARAMETERS
=============
filePath (String): The path to the file we are calling with ajax.
fileName (String): The name of the file we are calling with ajax.
*/
function ajaxContent(filePath, fileName){
	return xhr = $.ajax({
		type: "GET", 
		url: vpnUrl(filePath, fileName),
		cache: true,
		dataType: 'html',
		data: { 'ajax':'true' },
		success: function(txt){
		

			secondaryContentHtml = txt;
		}
	});
}


function processAjaxContent(txt){
	if (txt!= null) {
		var sandbox = $('#sandbox');
		sandbox.get(0).innerHTML = txt;
		var ajaxContentAreas = $('.extractAjaxContent', sandbox);
		var lastContent = '';
		
		for (var i=0,len=ajaxContentAreas.length; i<len; i++){
			var ajaxEl = $(ajaxContentAreas[i]);
			var content = "" + ajaxEl.html();
			var id = ajaxContentAreas[i].id;
			var dynamicEl = $('#'+id).html(content);
			lastContent = content;
		}
		
		sandbox.get(0).innerHTML = '';
		return lastContent;
	}
}



/*
FUNCTION: (String (url)) vpnUrl
------------------------------------
Augments the passed URL to allow it to work in the VPN.
*/
function vpnUrl(filePath, fileName){
	var sampleUrlArr = $('head link').get(0).href.split(',');
	var len = sampleUrlArr.length;
	if (len < 2) return filePath + fileName;
	for (var i=0; i<len; i++){
		if (/^DanaInfo=/.test(sampleUrlArr[i])) {
			return filePath + ',' + sampleUrlArr[i] + '+' + fileName;
		}
	}
	return filePath + fileName;
}


/*
CLASS: (secondaryContentToggle) secondaryContentToggle
-------------------------------------------
Collapse/expand toggle - Remove menu indicator first, then add collapse indicator.	
*/
var secondaryContentToggle = new Class({ 
	stateCookie: 'FordSecondaryContentState',
	panelsCSS: '#content_secondary div.webPart_secondary_inner',
	
	initialize: function(){
		this.minimized = false;
		this.createPanelToggles();
		
		var cookieState = readCookie(this.stateCookie);
		if (cookieState == 'minimized') this.min(this);
		else this.max(this);
	},
	
	/* min - collapses panel */
	min: function(obj){

		/* loop the toggles and prepare them for maximizing */
		$(obj.panelsCSS + ' a.secondaryToggle').each(function(i,a){
			a = $(a);
			a.removeClass('minimize');
			a.addClass('maximize');
			a.attr('alt', "Maximize");
			a.text('+');
			a.unbind();
			a.click(function(){obj.max(obj)}.bind(obj));
		}.bind(obj));
		
		/* collapse the panels */
		$(obj.panelsCSS).animate({'height':'24px'}, 10).css('overflow-y', 'hidden');
		
		/* record the state */
		obj.mimimized = true;
		createCookie(this.stateCookie, 'minimized');

	},

	
	/* max - opens panel back to previous height */
	max: function(obj){

		/* loop the toggles and prepare them for minimizing */
		$(obj.panelsCSS + ' a.secondaryToggle').each(function(i,a){
			a = $(a);
			a.removeClass('maximize');
			a.addClass('minimize');
			a.attr('alt', "Minimize");
			a.text('–');
			a.unbind();
			a.click(function(){obj.min(obj)}.bind(obj));

		}.bind(obj));
		
		/* restore the panels */
		var height = {'height': obj.height + "px"};
		$extend(height, {'min-height':"'" + obj.height + "px"});
		$(obj.panelsCSS).animate(height , 10);
		
		/* record the state */
		obj.mimimized = false;
		createCookie(obj.stateCookie, 'maximized');

	},
		
	createPanelToggles: function(){
		var panels = $(this.panelsCSS);
		/* panel needs to be a viable element */
		if (panels.length == 0) return;
				
		/* get max height and create toggle icons */
		var height = 330;
		panels.each(function(i, panel){
			var panel = $(panel);
			h = (parseInt(panel.css('min-height')) > parseInt(panel.css('height'))) ?
				parseInt(panel.css('min-height')) : parseInt(panel.css('height'));
			if (h > height) height = h;
			
			/* add toggles */
			var a = $('<a class="maximize secondaryToggle" href="javascript:;"></a>').appendTo(panel);
			
			/* create click event */
			a.click(function(){
				if (this.minimized) this.max(this);
				else this.min(this); 
			}.bind(this));
			
		}.bind(this));
		this.height = height;
		
		
		/* make all panels the same height */
		panels.css('height', height);
	}
	
});



/*
FUNCTION (void) soapCall
----------------------------
Calls the User Profile Service and extracts values to place on the page.
Last Updated: 2008-11-13 sshepar3 - creation.

INPUTS:
===============
	fieldTitles (Array[String]): The titles of the fields to be updated.  
		Normally we'd use ID, but this is sharepoint...
	propertyNames (Array[String]): The name of the properties from the XML Response 
		to look for the values in. 
*/
function soapCall(fieldTitles, propertyNames, callback){
	return $.ajax({
		type: "POST", 
		url: vpnUrl("/_vti_bin/", "userprofileservice.asmx"),
		data: '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><GetUserProfileByName xmlns="http://microsoft.com/webservices/SharePointPortalServer/UserProfileService"><AccountName></AccountName></GetUserProfileByName></soap:Body></soap:Envelope>',
		contentType: "text/xml; charset=utf-8",
		dataType: "xml", 
		cache: false,
		success: function(xml){ 
			try {
				var propertyNodes = $("PropertyData", xml);
				if (!propertyNodes || propertyNodes.length == 0) return;
				
				for (var i=0, field; field = fieldTitles[i];i++){
					field = $('input[title*="'+field+'"]');
					/* skip this field if it does not exist or it already has a value */
					if (!field || field.length == 0 || field.val().length>0) continue;
					
					/* Iterate each PropertyData node for the Name of the property we want.
					Once found, the value of the property is in /Values/ValueData/Value */
					for (var j=0, property;property=propertyNodes[j];j++){
						if ($('Name', property).text() == propertyNames[i]) {
							field.val($('Values>ValueData>Value', property).text());
						}
					}
				}
				
				/* run callback */
				if (callback) callback(xml);
			} catch (e) {  }
		}
	});
}

/*
FUNCTION: (String (url)) vpnUrl
------------------------------------
Augments the passed URL to allow it to work in the VPN.
*/
function vpnUrl(filePath, fileName){
	var sampleUrlArr = $('head link').get(0).href.split(',');
	var len = sampleUrlArr.length;
	if (len < 2) return filePath + fileName;
	for (var i=0; i<len; i++){
		if (/^DanaInfo=/.test(sampleUrlArr[i])) {
			return filePath + ',' + sampleUrlArr[i] + '+' + fileName;
		}
	}
	return filePath + fileName;
}


/* 
FUNCTION: CduRotator
----------------
Rotator provides a fade between a group of absolutely positioned elements that hide behind each other.
Rotator first hides all but the first one, then rotates through them using a fade effect.

PARAMETERS (Optional):
=============
	root (Element): The element to look for all rotator parts.  Defaults to document.
	containerCSS (String [css selector]): The element that encapsulates all rotating elements.
	panelsCSS (String [css selector]): The CSS selector used to find each rotating element within 
		the container.
	controlsCSS (String [css selector]): The CSS selector used to define where controls 
		should be located.  Indicating "false" will disable user controls.
	controlPrev (String): HTML string indicator to move previous one index.
	controlNext (String): HTML string indicator to move to the next index.
	controlPause (String): HTML string indicator to pause the rotator.
	controlResume (String): HTML string indicator to resume the rotator.
	controlPRSeparator (String): HTML string indicator used to separate Pause and Resume.

	transDuration (integer): The number of miliseconds the animated transition will take 
		to fade from one rotating element to the the next.
	waitDuration (integer): The number of miliseconds to wait between transitions.
	displayIndex (integer): The index number of the panel to start with.
	useControls (boolean [true]): Display controls.
	collapseOnEmpty (boolean [true]): Collapse containerCSS if there are no panels.

	
USAGE:
==========
	new CduRotator({
		containerCSS: '#cycling_media',
		panelsCSS: '.content_wrapper',
		useControls: true,
		collapseOnEmpty: true,
		controlsCSS: '.cycling_controls',
		controlPause: 'Pause',
		controlResume: 'Resume',
		controlPRSeparator: ' / ',
		transDuration: 2000,
		waitDuration: 10000,
		displayIndex: 0
	});
	
	CSS for controls: 
		(CSS selectors are assuming the parameters in the example Rotator above.)
	#cycling_media .cycling_controls ul.items { } unordered list that allows the user to select a panel to view.
	#cycling_media .cycling_controls ul.items li.active { } the css used to indicate which panel index is active.
	#cycling_media .cycling_controls div.controls { } Pause and Resume buttons/text.
	#cycling_media .cycling_controls div.controls .resume.active { } Resume button/text is activated.
	#cycling_media .cycling_controls div.controls .pause.active { } Pause button/text is activated.
	
	Example HTML: Structure of each panel. (comments are just for your information).
	<div id="cycling_media">
		<div class="content_wrapper">
			<!-- content of this rotating panel -->
			<div class="cycling_controls">
				<!-- any content you put in here will be preserved -->
				<!-- this is where the controls are inserted by "Rotator" -->
			</div>
		</div>
	</div>

*/
var CduRotator = function(options){
	var defaults = {
		containerCSS: '#cycling_media',
		panelsCSS: '.content_wrapper',
		useControls: true,
		collapseOnEmpty: true,
		controlsCSS: '.cycling_controls',
		controlPrev: '&lt;',
		controlNext: '&gt;',
		controlPause: 'Pause',
		controlPRSeparator: ' / ',
		controlResume: 'Play',
		transDuration: 2000,
		waitDuration: 20000,
		displayIndex: 0
	}
	
	/* $this is a locally-scoped pointer to 'this'.  'this' is this instance of this rotator. */
	var $this = this;

	
	/* PRIVATE METHOD: (void) panelIndexControls
	------------------------------
	Create the control panel
	*/
	panelIndexControls = function($this){
		
		if ($this.panEls.length == 0) {
			if (window.editMode != true) $($this.containerCSS).hide();
			return false;
		}
		
		var controlsElCSS = $this.controlsCSS;
		var panelLength = $this.panEls.length;
		var style = {};	
		
		$this.panEls.each(function(i, panel){
			var controlsEl = $(controlsElCSS, panel);
			
			if (controlsEl.length == 1 && $this.useControls) {
				var ul = document.createElement('ul');
				ul.className = 'items';
				var li = document.createElement('li');
				li.innerHTML = $this.controlPrev;
				$(li).click(function(){ $this.previous() });
				$(ul).append(li);

				for (var j=0; j<panelLength; j++){
					var li = document.createElement('li');
					
					li.innerHTML = (" " + (j+1) + " <div></div>")
					if (i == j) li.className = ('active');
					$(li).click(function(){ $this.rotate($(this).text()-1); })
					$(ul).append(li);
				}
				
				li = document.createElement('li');
				li.innerHTML = $this.controlNext;
				$(li).click(function(){ $this.next() });
				$(ul).append(li);
				
				controlsEl.append(ul);
				
				var controls = document.createElement('div');
				controls.className = 'controls';
				
				var resume = document.createElement('div');
				resume.innerHTML = $this.controlResume;
				$(resume).addClass('active resume').click(function(){ $this.resume() });
				$(controls).append(resume);
				
				var pr_divider = document.createElement('div');
				pr_divider.className = 'pr_divider';
				pr_divider.innerHTML = $this.controlPRSeparator;
				$(controls).append(pr_divider);

				var pause = document.createElement('div');
				pause.className = 'pause';
				pause.innerHTML = $this.controlPause;
				$(controls).append(pause);
				$(pause).click($this.pause);
				
				controlsEl.append(controls);
				
			}
			
			/* only the starting panel should be displayed on creation */
			if (i == $this.displayIndex) {
				$(panel).css('visibility','visible').show();
			} else {
				$(panel).css('visibility','visible').hide();
			}
			
		});
	}


	/* METHOD: (void) rotate
	------------------------------
	The mechanics of switching to the next panel
	
	PARAMETERS:
	===============
		nextPanel (Integer): A user-selected panel. 
	*/
	this.rotate = function(nextPanel){
		clearTimeout($this.timer);
		$this.timer = setTimeout(function(){ $this.rotate() }, this.waitDuration);
		$($this.containerCSS + " " + $this.controlsCSS + ' .pause').removeClass('active');
		$($this.containerCSS + " " + $this.controlsCSS + ' .resume').addClass('active');
		/* determine which panel should be displayed next. Start at 0, or loop back to the last panel */
		nextPanel = (nextPanel == undefined) ? $this.displayIndex+1 : nextPanel;
		
		if (nextPanel > $this.panEls.length-1) nextPanel = 0;
		if (nextPanel < 0) nextPanel = $this.panEls.length-1;
		if (nextPanel != $this.displayIndex) {
			var style = {};
			/* Fade out the current panel */
			$($this.panEls[$this.displayIndex]).fadeOut($this.transDuration);
	
			/* Iterate to the next panel, or switch to the user-selected panel */
			$($this.panEls[nextPanel]).fadeIn($this.transDuration);
		}
		$this.displayIndex = nextPanel;
	}
	
	
	/* METHOD: (void) resume
	------------------------------
	Start rotating again.
	*/
	this.resume = function(){
		$this.rotate($this.displayIndex+1);
	}
	
	
	/* METHOD: (void) pause
	------------------------------
	Stop the rotation timer.
	*/
	this.pause = function(){
		clearTimeout($this.timer);
		$($this.containerCSS + " " + $this.controlsCSS + ' .pause').addClass('active');
		$($this.containerCSS + " " + $this.controlsCSS + ' .resume').removeClass('active');
	}

	/* METHOD: (void) next
	------------------------------
	Go to the next panel immediately.
	*/
	this.next = function(){
		$this.rotate($this.displayIndex+1);
	}
	
	/* METHOD: (void) previous
	------------------------------
	go to the previous panel immediately.
	*/
	this.previous = function(){
		$this.rotate($this.displayIndex-1);
	}
	
	/*
	--------------------
	PRIMARY LOGIC
	--------------------
	*/
	$.extend(this, defaults, options);
	
	this.panEls = $(this.containerCSS + " " + this.panelsCSS);
	//if (this.panEls.length == 0) return {};
	
	panelIndexControls(this);
	this.rotate(this.displayIndex);

	return this;
}

