//Description:    Client-side code library for Children's .asp templates
//Author:         Steve Eidemiller
//Created:        03/30/2009 (migrated from Library.js)

/**************************************************************************
Revision History:
03/30/2009 - SteveE - MooTools 1.2 and 10gR3 Consumer Site updates
10/16/2009 - SteveE - Added Google Analytics function
**************************************************************************/



///////////////////////////////////////////////////////////////////////////
// Global defaults
///////////////////////////////////////////////////////////////////////////



if (!chcWebResourceServer) var chcWebResourceServer = 'http://webresources.childrensmn.org/';
if (!chcStyleIndex)        var chcStyleIndex = 1;
if (!chcStyleFolder)       var chcStyleFolder = 'Styles/Childrens-1/';

if (!chcGradientColorTop)              var chcGradientColorTop = 'BED600';
if (!chcGradientColorBottom)           var chcGradientColorBottom = 'F1F6CD';
if (!chcGradientColorAlertTop)         var chcGradientColorAlertTop = 'ECAEBD';
if (!chcGradientColorAlertBottom)      var chcGradientColorAlertBottom = 'FBE9ED';
if (!chcGradientColorBubbleTop)        var chcGradientColorBubbleTop = 'C2D82C';
if (!chcGradientColorBubbleBottom)     var chcGradientColorBubbleBottom = 'DFEE9A';
if (!chcGradientColorBubbleBackground) var chcGradientColorBubbleBackground = 'F1F6CD';



///////////////////////////////////////////////////////////////////////////
// Style and CSS support
///////////////////////////////////////////////////////////////////////////



//Standard colors (NOTE: this is copied from ChildrensWebStyle.wsc)
var chcColors = new Array(
	//Dark versions
	new Array(10, '303030', 'Olson Black'),
	new Array(11, '00A9E0', 'Olson Blue'),
	new Array(12, 'BD8A5E', 'Olson Brown'),
	new Array(13, '80379B', 'Olson Dark Red'),
	new Array(14, '9A9B9C', 'Olson Gray'),
	new Array(15, 'BED600', 'Olson Green'),
	new Array(16, 'FFA02F', 'Olson Orange'),
	new Array(17, '4C5CC5', 'Olson Purple'),
	new Array(18, 'CA005D', 'Olson Red (Rubine)'),
	new Array(19, '009AA6', 'Olson Teal'),
	new Array(20, 'FCD900', 'Olson Yellow'),

	//Light versions
	new Array(60, '808080', 'Olson Black (light)'), //Not defined in the style guide
	new Array(61, '72B5CC', 'Olson Blue (light)'),
	new Array(62, 'D3BF96', 'Olson Brown (light)'),
	new Array(63, 'B382C7', 'Olson Dark Red (light)'),
	new Array(64, '9FA0A4', 'Olson Gray (light)'),  //Not defined in the style guide
	new Array(65, 'BAC696', 'Olson Green (light)'),
	new Array(66, 'FBAF73', 'Olson Orange (light)'),
	new Array(67, '8193DB', 'Olson Purple (light)'),
	new Array(68, 'E28A9E', 'Olson Red (light)'),
	new Array(69, '5BBBB7', 'Olson Teal (light)'),
	new Array(70, 'E8CE79', 'Olson Yellow (light)')
);

//Convert a chcColors[] index to its hex value
function chcColorHexFromIndex(index)
{
	var hex = '000000';
	for (var i = 0; i < chcColors.length; i++)
	{
		if (chcColors[i][0] == index)
		{
			hex = chcColors[i][1];
			break;
		}
	}
	return hex;
}

//Convert a hex value to its corresponding chcColors[] index (or -1 if the hex value is not a standard color)
function chcColorIndexFromHex(hex)
{
	var index = -1;
	for (var i = 0; i < chcColors.length; i++)
	{
		if (chcColors[i][1].toLowerCase() == hex.toLowerCase())
		{
			index = chcColors[i][0];
			break;
		}
	}
	return index;
}

//Determine the absolute x,y position of a CSS element
function chcElementAbsolutePosition(cssElement)
{
	var x = 0;
	var y = 0;
	while (cssElement.offsetParent)
	{
		x += cssElement.offsetLeft;
		y += cssElement.offsetTop;
		if (typeof(cssElement.clientLeft) != 'undefined') x += cssElement.clientLeft; //IE padding
		if (typeof(cssElement.clientTop)  != 'undefined') y += cssElement.clientTop;  //IE padding
		cssElement = cssElement.offsetParent;
	}
	return { 'x' : x, 'y' : y }; //Return a "point" as an object with .x and .y properties
}



///////////////////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////////////////



//
window.addEvent('domready', function()
{
	//Initialize the menu system
	chcMainMenuHandler.initialize();

	//
	if ($('chcPrinterFriendlyMode') == null)
	{
		//Implement background gradients for blocks
		var diURL = chcWebResourceServer + 'DynamicImages/';
		$$('td.olsonBgGradientBlock').each(function(td, i)
		{
			//Plain vertical gradients
			var bgURL = diURL + 'VerticalGradient.ashx?height=' + td.clientHeight + '&rgb1=' + chcGradientColorTop + '&rgb2=' + chcGradientColorBottom;
			td.setStyle('background-image', 'url(' + bgURL + ')');
		});
		$$('td.olsonBgGradientBubble').each(function(td, i)
		{
			if (td.hasClass('olsonBgGradientAlert'))
			{
				//"Alert" style bubble gradients
				var bgURL = diURL + 'AlertGradient.ashx?height=' + td.clientHeight + '&width=' + td.clientWidth + '&rgb1=' + chcGradientColorAlertTop + '&rgb2=' + chcGradientColorAlertBottom + '&bgcolor=FFFFFF';
			}
			else
			{
				//"Bubble" style gradients
				var bgURL = diURL + 'VerticalGradient2.ashx?height=' + td.clientHeight + '&width=' + td.clientWidth + '&rgb1=' + chcGradientColorBubbleTop + '&rgb2=' + chcGradientColorBubbleBottom + "&bgcolor=" + chcGradientColorBubbleBackground;
			}
			td.setStyle('background-image', 'url(' + bgURL + ')');
		});
		$$('div.olsonBgGradientBubble').each(function(div, i)
		{
			//"Bubble" style gradient for the "Quick Links" (which doesn't have a gradient, so the top and bottom colors are the same)
			var divWidth  = (div.clientWidth  == 0 ? 240 : div.clientWidth);  //Konqueror hack, for when .clientWidth  isn't known
			var divHeight = (div.clientHeight == 0 ? 188 : div.clientHeight); //Konqueror hack, for when .clientHeight isn't known
			var bgURL = diURL + 'VerticalGradient2.ashx?margin=15&height=' + divHeight + '&width=' + divWidth + '&rgb1=' + chcGradientColorBubbleTop + '&rgb2=' + chcGradientColorBubbleTop + "&bgcolor=" + chcGradientColorBubbleBackground;
			div.setStyle('background-image', 'url(' + bgURL + ')');
		});
	}

	//Set all block column heights within each block column container to equal heights, to propagate background treatments all the way down to the bottom of the container for "shorter" block columns
	$$('table.ssBlockColumnContainer').each(function(columnContainer, i)
	{
		//Drill down through any intervening <tbody> tags, or any other tags, to this table's first <tr>
		var child = columnContainer.getFirst();
		while (child != null && child.tagName != 'TR') //NOTE: .tagName is *always* upper case
		{
			child = child.getFirst();
		}

		//For (each <tr> sibling of the container <table>)...
		for (var childTR = child; childTR != null; childTR = childTR.getNext('tr'))
		{
			//If (there is more than one contained block column)...
			var blockColumns = childTR.getElements('td.ssBlockColumn');
			if (blockColumns.length > 1)
			{
				//Determine the height of the tallest contained block column
				var maxHeight = -1;
				blockColumns.each(function(blockColumn, i)
				{
					var tdHeight = blockColumn.getStyle('height');
					if (String(tdHeight).indexOf('px') > -1) //Make sure the height is in pixels, otherwise we can't use it
					{
						maxHeight = Math.max(maxHeight, Number(tdHeight.replace(/px/gi, '')).valueOf());
					}
				});

				//If (a max height could be found/calculated)...
				if (!isNaN(maxHeight) && maxHeight > -1)
				{
					//Set all contained block column heights to the maxHeight
					blockColumns.each(function(blockColumn, i)
					{
						blockColumn.setStyle('height', maxHeight + 'px');
					});
				}
			}
		}
	});

	//Add focus/blur events to the search box in the tag line at the top of the page
	var searchBox = $('chcSiteSearchBox');
	if (searchBox)
	{
		searchBox.store('defaultText', 'Search...');
		searchBox.value = searchBox.retrieve('defaultText');
		searchBox.addEvent('focus', function()
		{
			if (this.hasClass('tagLineSearchBoxBlur'))
			{
				this.value = '';
			}
			this.addClass('tagLineSearchBoxFocus');
			this.removeClass('tagLineSearchBoxBlur');
		});
		searchBox.addEvent('blur', function()
		{
			if (this.value.trim() == '')
			{
				this.addClass('tagLineSearchBoxBlur');
				this.removeClass('tagLineSearchBoxFocus');
				this.value = searchBox.retrieve('defaultText');
			}
		});
	}

	//Add the click function to the search button, because SSPU messes it up so we can't do it inline
	var searchButton = $('TagLineSearchButtonLink');
	if (searchButton)
	{
		searchButton.addEvent('click', function()
		{
			$('frmTagLineSearch').submit();
		});
	}

	//This is a patch to address a bug in IE6 SP1 that causes certain images to not be loaded.
	if (navigator.appName == 'Microsoft Internet Explorer' && navigator.userAgent.indexOf('MSIE 6.0') > -1 && navigator.appMinorVersion.indexOf('SP1') > -1) //Verify that we're running IE6 SP1 (browser detection is intentionally sparse here and designed to detect only IE6 SP1)
	{
		//For (each image on the page)...
		for (var i = 0; i < document.images.length; i++)
		{
			//If (the image has not loaded) Then try and force a re-load by re-assigning the URL to .src
			if (!document.images[i].complete) document.images[i].src = document.images[i].src;
		}
	}
});

//Create and show a popup window containing a printable version of the web page
function chcPrinterFriendlyPopup()
{
	//Start some HTML for the popup window
	var html = '';
	html += '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n';
	html += '<html xmlns="http://www.w3.org/1999/xhtml">';
	html += '<head>';
	var docTitle = document.title.trim();
	html += '<title>' + document.title + (docTitle.length == 0 ? '' : ' - ') + 'Printer Friendly Version</title>';

	//Copy over the <link> style sheets for default element styles
	var links = $(document.head).getElements('link'); //Get all <link> tags from the <head> of this page
	for (var i = 0; i < links.length; i++) //For (each <link> tag)...
	{
		//If (the <link> is for a style sheet AND it's not for AddThis.com)...
		if (links[i].rel.toLowerCase() == 'stylesheet' && links[i].href.toLowerCase().indexOf('.addthis.com') == -1)
		{
			//Generate a <link> tag for the popup window
			html += '<link rel="stylesheet" type="text/css" href="' + links[i].href + '"/>';
		}
	}

	//Copy over embedded <style> tags for all cart screens (and anything else) on this page
	html += '\n<style>\n';
	var styles = $(document.body).getElements('style'); //Get all <style> tags from the <body> of this page
	for (var i = 0; i < styles.length; i++) //For (each <style> tag)...
	{
		//Copy over the raw <style> information
		html += styles[i].get('html') + '\n';
	}

	//Convert all gradient styles to "white". Code below will remove their background images.
	html += '.olsonBgShadedLight    { background-color: white; }' + '\n';
	html += '.olsonBgShadedDark     { background-color: white; }' + '\n';
	html += '.olsonBgGradientBlock  { background-color: white; }' + '\n';
	html += '.olsonBgGradientBubble { background-color: white; }' + '\n';
	html += '.olsonShadedBlock a.olsonLink { font-weight: 900; color: #4C5CC5; text-decoration: none; }' + '\n';
	html += '.olsonShadedBlock a           { font-weight: 900; color: #4C5CC5; text-decoration: underline; }' + '\n';

	html += '\n</style>\n';

	//Copy over the <script> links
	var scripts =  $(document.head).getElements('script');  //Get all <script> tags from the <head> of this page
	scripts.extend($(document.body).getElements('script')); //Get all <script> tags from the <body> of this page
	for (var i = 0; i < scripts.length; i++) //For (each <script> tag)...
	{
		//Generate a <script> tag for the popup window
		var scriptType = '';
		if (scripts[i].type != '') scriptType = ' type="' + scripts[i].type + '"';
		var scriptSource = '';
		if (scripts[i].src != '') scriptSource = ' src="' + scripts[i].src + '"';
		if (scriptSource.toLowerCase().indexOf('.addthis.com') == -1)
		{
			html += '<script' + scriptType + scriptSource + '>' + scripts[i].get('html') + '</script>\n';
		}
	}

	//Done with the popup's <head> section
	html += '</head>';

	//Get the body HTML from the currently displayed page
	var htmlBody = $(document.body).get('html');

	//Remove all background images, which will be gradients and bubbles, etc.
	htmlBody = htmlBody.replace(/background-image\:\s*url\(.+?\);?/gi, '');

	//Remove other sections from the page that don't have much bearing on a printout
	htmlBody = htmlBody.replace(/<\!-- BEGIN AUX NAVIGATION -->[\w\W]*<\!-- END AUX NAVIGATION -->/, '&nbsp;');
	htmlBody = htmlBody.replace(/<\!-- BEGIN TAG LINE SEARCH -->[\w\W]*<\!-- END TAG LINE SEARCH -->/, '<div style="float: right; margin-top: 2px; margin-right: 14px;"><a href="javascript:window.print();">Print This Page</a></div>');
	htmlBody = htmlBody.replace(/<\!-- BEGIN TABS -->[\w\W]*<\!-- END TABS -->/, '');
	htmlBody = htmlBody.replace(/<\!-- BEGIN BANNER -->[\w\W]*<\!-- END BANNER -->/, '');

	//Remove the "Add This" HTML
	var addThisDiv = $('at20mc');
	if (addThisDiv)
	{
		var addThisHTML = addThisDiv.get('html');
		htmlBody = htmlBody.replace(addThisHTML, '');
	}

	//Add the edited body content
	html += htmlBody;

	//Add a "marker" for the domready event so that it won't add dynamic background images back in
	html += '<div id="chcPrinterFriendlyMode" style="display: none;"></div>';

	//Done with all HTML for the popup
	html += '</html>';

	//Create the popup window
	var popup = window.open('', '_blank', 'height=650,width=1000,resizable=yes,scrollbars=yes');

	//Write the HTML to the popup window
	popup.document.open('text/html');
	popup.document.write(html);
	popup.document.close();
}



///////////////////////////////////////////////////////////////////////////
// Tab menu handler
///////////////////////////////////////////////////////////////////////////



//All menu handler functions will be in one object/class
var chcMainMenuHandler =
{
	'menuContainer' : '#chcMainMenu', //CSS selector (the "#ID") for the HTML element containing the menu system of UL's and LI's
	'menuDelay' : 1000, //Milliseconds to delay before hiding a menu
	'menuSelected' : new Array(), //Current hierarchy of selected menu items (the array index 1-3 is the nesting level of the menu item)
	'menuSelectedTimer' : new Array(), //Timers for onmouseout() to hide menu items after a short delay (the array index 1-3 is the nesting level of the menu item)

	//Crawl the menu HTML and setup event handlers and background images for the <li> tags
	'initialize' : function()
	{
		//If (there is a menu system)...
		var menuItems = $$(this.menuContainer + ' li'); //All menu items, including the tabs, are constructed with <li> tags
		if (menuItems)
		{
			//For (each <li> menu item, which could be a tab or a sub-menu or a sub-sub-menu)...
			menuItems.each(function(li, i)
			{
				//Determine the "level" of the menu, with "1" being the tab row itself
				var menuLevel = 0; //Will be: 1, 2 or 3
				var ul = li.getParent('ul'); //Determine the container "menu" for this <li> menu item
				while (ul != null && ul.getParent(this.menuContainer) != null) //Don't go up past the #chcMainMenu container element, for the improbable case that this menu system could be completely contained within an outer <ul>
				{
					menuLevel++; //Track how many levels deep this <li> menu item is
					ul = ul.getParent('ul'); //Find this <li> container's parent container, essentially going up one level
				}
				li.store('menuLevel', menuLevel); //The "level" will be needed by the JavaScript event functions, so hang on to it

				//If (this is not a tab)...
				if (menuLevel > 1)
				{
					//Set the background image based on whether this sub-menu has sub-sub-menu's or not
					var hasSubmenus = li.getChildren('ul').length > 0;
					li.addClass(hasSubmenus ? 'bgWithSubmenu' : 'bgNoSubmenu');
				}

				//Setup all mouse events for this menu item
				li.addEvent('mouseover', function()
				{
					chcMainMenuHandler.mouseOver($(this));
				});
				li.addEvent('mouseout', function()
				{
					chcMainMenuHandler.mouseOut($(this));
				});
			});
		}

		//If (there is a menu system)...
		var sfMenus = $$(this.menuContainer + ' ul');
		if (sfMenus)
		{
			//For (each menu container)...
			sfMenus.each(function(ul, i)
			{
				//Find the last menu item in the list
				var li = ul.getLast('li');
				if (li != null && li.retrieve('menuLevel') > 1)
				{
					//Fix the bottom border, because the background images only have left/top/right borders so that they can dynamically expand vertically for multiline menu items
					li.addClass('bottomMenuItem');
				}
			});
		}
	},

	//Display a menu when the mouse hovers over an <li> menu item, and hide any other menus at the same level or deeper
	'showMenu' : function(level, li)
	{
		//If (this is a duplicate event for the same menu item that is currently shown)...   NOTE: IE does this, and if we don't compensate, then the menus flicker
		if (li == this.menuSelected[level])
		{
			//Cancel the "hide menu" timer
			this.clearTimer(level);
			return;
		}

		//Show the new menu
		this.hideMenu(level);          //Hide any other menus at the same level or deeper
		li.addClass('menuHover');      //Show this menu
		this.menuSelected[level] = li; //Track which menu is currently active at each level
	},

	//When the mouse hovers away from an <li> menu item, start a timer to hide that menu after a short delay, so that the menus don't disappear instantly
	'fadeMenu' : function(level)
	{
		this.menuSelectedTimer[level] = this.hideMenu.delay(this.menuDelay, chcMainMenuHandler, level);
	},

	//Hide a menu, normally called on a timer event or when the mouse hovers over a different menu and an old menu has to be forcibly hidden
	'hideMenu' : function(level)
	{
		//If (there are any deeper menus that may be displayed)...
		if (level < this.menuSelected.length - 1)
		{
			//Hide those first
			this.hideMenu(level + 1);
		}

		//Cancel the "hide menu" timer for this menu item, since we're hiding it right now
		this.clearTimer(level);

		//If (this menu is currently displayed)...
		if (this.menuSelected[level])
		{
			//Hide the current menu
			this.menuSelected[level].removeClass('menuHover');
			this.menuSelected[level] = null;
		}
	},

	//Cancel the "hide menu" timer for a given menu item
	'clearTimer' : function(level)
	{
		//If (there is a "hide menu" timer for the specified menu level)...
		if (this.menuSelectedTimer[level])
		{
			//Cancel the timer
			window.clearTimeout(this.menuSelectedTimer[level]);
			this.menuSelectedTimer[level] = null;
		}
	},

	//Mouse hover event handler for <li> menu items
	'mouseOver' : function(li)
	{
		//Determine which level this <li> menu item is at
		var level = li.retrieve('menuLevel');

		//Display this menu, since the mouse is hovering over it
		this.showMenu(level, li);

		//Adjust the vertical position of submenus when the current menu item is multiline, such that submenus line up with the top edge of all parent menu items
		var childUL = li.getFirst('ul');   //See if this menu item has a submenu
		var liHeight = li.getSize().y;     //Height, in pixels, of this menu item
		if (li.hasClass('bottomMenuItem')) //If (this menu item is the last one in its list)...
		{
			liHeight--; //Account for the 1px bottom border
		}
		var marginTop = '-' + liHeight + 'px'; //Construct the CSS
		if (level > 1 && childUL != null && childUL.getStyle('margin-top') != marginTop) //If (this is not a tab AND this menu item has a submenu AND its vertical position is not correct)...
		{
			//Correct the submenu's vertical position
			childUL.setStyle('margin-top', marginTop);
		}
	},

	//Mouse out event handler for <li> menu items
	'mouseOut' : function(li)
	{
		//Just start the "hide menu" timer
		this.fadeMenu(li.retrieve('menuLevel'));
	}
};



///////////////////////////////////////////////////////////////////////////
// Rotating image banner (rotates <div>'s of full HTML, not just images)
///////////////////////////////////////////////////////////////////////////



var chcSlideShowHandler_InstanceCounter = 0;
var chcSlideShowHandler = new Class(
{
	'Implements' : [Options],
	'options' :
	{
		'transitionDelay' : 10000, //Milliseconds
		'transitionTime'  : 1000, //Milliseconds
		'restartDelay'    : 5000, //Milliseconds
		'container' : null, //<div> element that will contain the slideshow
		'slidePrefix' : 'chcSlideShowSlide_',
		'zIndexBase'  : 200,
		'width'  : 680, //Pixels
		'height' : 190, //Pixels
		'slides' : [],
		'startingSlide' : 0 //First slide
	},
	'slides' : [],
	'currentSlide' : 0,
	'isMouseHovering' : false,
	'tweenElement' : null, //The slide <div> currently doing a tween()
	'timer' : null,
	'restartTimer' : null,
	'initialize' : function(options)
	{
		this.setOptions(options);
		if (this.options.slides != null)
		{
			this.slides.extend(this.options.slides);
		}
		this.options.slidePrefix += chcSlideShowHandler_InstanceCounter + '_';
		chcSlideShowHandler_InstanceCounter++;

		if (this.slides.length > 1)
		{
			var html = '';
			var buttonHTML = '';
			var idPrefix = this.options.slidePrefix;
			var w = this.options.width;
			var h = this.options.height;
			var z = this.options.zIndexBase;
			this.currentSlide = this.options.startingSlide;

			this.slides.each(function(slide, i)
			{
				//NOTE: This *must* have a background color (or image) to prevent the first/next slide <div>'s from showing overlapped content at the same time, because the default background for HTML elements is transparent
				html += '<div id="' + (idPrefix + i) + '" class="slideShowSlide" style="z-index: ' + (z + i) + '; width: ' + w + 'px; height: ' + h + 'px;">' + slide + '</div>';

				//Create buttons for each slide
				var cssClass = 'PNGFix slideShowButton';
				if (i == this.options.startingSlide) cssClass += ' slideShowSlideButtonSelected'; //Select the starting slide
				buttonHTML += '<td id="' + idPrefix + 'Button_' + i + '" class="' + cssClass + '">' + (i + 1) + '</td>';
			}, this);

			var buttonSpacing = 3;
			var buttonBarMargin = 8;
			var verticalOffset = h - 21 - buttonSpacing - buttonBarMargin; //Slideshow height - background image height - <table> cellspacing - whatever bottom margin is desired (i.e. 5px)
			html += '<div id="' + idPrefix + 'Controls" class="slideShowControls" style="z-index: ' + (z + this.slides.length) + '; width: ' + w + 'px; height: ' + h + 'px; padding-top: ' + verticalOffset + 'px;">' +
			            '<table cellspacing="' + buttonSpacing + '" cellpadding="0" border="0" style="float: right; margin-right: ' + (buttonBarMargin - buttonSpacing) + 'px;">' +
			                '<tr>' +
			                    buttonHTML +
			                    '<td><a id="' + idPrefix + 'PlayPause_Link" style="cursor: pointer; text-decoration: none;"><img id="' + idPrefix + 'PlayPause" class="PNGFix" src="" width="21" height="21"/></a></td>'
			                '</tr>' +
			            '</table>' +
			        '</div>';
			$(this.options.container).set('html', html);

			this.slides.each(function(slide, i)
			{
				var div = $(idPrefix + i);
				div.set('opacity', (i == this.options.startingSlide) ? 1 : 0);
				div.setStyle('display', 'block');
				div.set('tween', { 'duration' : this.options.transitionTime });
				var td = $(idPrefix + 'Button_' + i);
				td.store('chcSlideShowHandler', this);
				td.store('slideIndex', i);
				td.addEvent('mouseover', this.buttonMouseOver);
				td.addEvent('mouseout', this.buttonMouseOut);
				td.addEvent('click', this.buttonClick);
			}, this);

			var aPlayPause = $(idPrefix + 'PlayPause_Link');
			aPlayPause.store('chcSlideShowHandler', this);
			aPlayPause.addEvent('mouseover', this.playPauseOver);
			aPlayPause.addEvent('mouseout',  this.playPauseOut);
			aPlayPause.addEvent('click',  this.playPauseClick);

			$(this.options.container).addEvent('mouseover', this.slideShowMouseOver);
			$(this.options.container).addEvent('mouseout',  this.slideShowMouseOut);

			this.start();
		}
	},
	'nextSlide' : function()
	{
		//If (the mouse is not hovering over the slideshow)...
		var isMouseHovering = $(this.options.container).retrieve('isMouseHovering', false);
		if (!isMouseHovering)
		{
			var idPrefix = this.options.slidePrefix;

			//Advance the current slide by one, and wrap around to the beginning if necessary
			var lastSlide = this.currentSlide;
			this.currentSlide++;
			if (this.currentSlide >= this.slides.length) this.currentSlide = 0; //Wrap around

			//Update the buttons to show which slide will be current
			var tdLast = $(idPrefix + 'Button_' + lastSlide);
			var tdNext = $(idPrefix + 'Button_' + this.currentSlide);
			tdLast.removeClass('slideShowSlideButtonSelected');
			tdNext.addClass('slideShowSlideButtonSelected');

			//Transition to the next slide with a tween()
			var divLast = $(idPrefix + lastSlide);
			var divNext = $(idPrefix + this.currentSlide);
			if (lastSlide < this.currentSlide)
			{
				//Fade the next slide in, then make the last slide invisible after the fade is complete
				this.tweenElement = divNext;
				divNext.tween('opacity', 1);
				(function(div) { div.set('opacity', 0); } ).delay(this.options.transitionTime, this, divLast);
			}
			else
			{
				//Make the first slide visible, then just fade out the last slide
				this.tweenElement = divLast;
				divNext.set('opacity', 1);
				divLast.tween('opacity', 0);
			}
		}
	},
	'slideShowMouseOver' : function()
	{
		$(this).store('isMouseHovering', true);
	},
	'slideShowMouseOut' : function()
	{
		$(this).store('isMouseHovering', false);
	},
	'buttonMouseOver' : function()
	{
		$(this).addClass('slideShowButtonHover');
	},
	'buttonMouseOut' : function()
	{
		$(this).removeClass('slideShowButtonHover');
	},
	'buttonClick' : function()
	{
		//Stop the slideshow
		var handler = $(this).retrieve('chcSlideShowHandler');
		handler.stop();

		//Stop whatever restart timer that may have been running
		if (handler.restartTimer)
		{
			$clear(handler.restartTimer);
			handler.restartTimer = null;
		}

		//Show the slide that was clicked on, if it's not already current
		var lastSlide = handler.currentSlide;
		var nextSlide = $(this).retrieve('slideIndex');
		if (lastSlide != nextSlide)
		{
			var idPrefix = handler.options.slidePrefix;

			//Fix the button highlights
			var tdLast = $(idPrefix + 'Button_' + lastSlide);
			var tdNext = $(idPrefix + 'Button_' + nextSlide);
			tdLast.removeClass('slideShowSlideButtonSelected');
			tdNext.addClass('slideShowSlideButtonSelected');

			//Show the new slide and hide the old one
			var divLast = $(idPrefix + lastSlide);
			var divNext = $(idPrefix + nextSlide);
			divNext.set('opacity', 1);
			divLast.set('opacity', 0);

			//Update the current slide
			handler.currentSlide = nextSlide;
		}

		//Create a new restart timer to auto-restart the slideshow
		handler.restartTimer = (function()
		{
			this.start();     //Restart the slideshow
			this.nextSlide(); //Force the next slide right away
		}).delay(handler.options.restartDelay, handler);
	},
	'playPauseFixImage' : function()
	{
		var isRunning  = (this.timer != null);
		var isHovering = $(this.options.slidePrefix + 'PlayPause_Link').retrieve('isMouseHovering');
		$(this.options.slidePrefix + 'PlayPause').src = chcWebResourceServer + chcStyleFolder + 'SlideShow-' + (isRunning ? 'Pause' : 'Play') + (isHovering ? '_Hover' : '') + '.png';
	},
	'playPauseOver' : function()
	{
		$(this).store('isMouseHovering', true);
		var handler = $(this).retrieve('chcSlideShowHandler');
		handler.playPauseFixImage.call(handler);
	},
	'playPauseOut' : function()
	{
		$(this).store('isMouseHovering', false);
		var handler = $(this).retrieve('chcSlideShowHandler');
		handler.playPauseFixImage.call(handler);
	},
	'playPauseClick' : function()
	{
		var handler = $(this).retrieve('chcSlideShowHandler');
		if (handler.timer)
		{
			handler.stop.call(handler);
		}
		else
		{
			handler.start.call(handler);
		}
	},
	'start' : function()
	{
		if (this.restartTimer != null)
		{
			$clear(this.restartTimer);
			this.restartTimer = null;
		}
		if (this.timer == null)
		{
			this.timer = this.nextSlide.periodical(this.options.transitionDelay, this);
			this.playPauseFixImage();
		}
	},
	'stop' : function()
	{
		if (this.timer)
		{
			if (this.tweenElement)
			{
				this.tweenElement.get('tween').cancel();
				this.tweenElement = null;
			}
			$clear(this.timer);
		}
		this.timer = null;
		this.playPauseFixImage();
	}
});

//Dynamically determine a height for a slide show, then start it
var chcBannerHeightCheckerTimerID = null;
function chcStartSlideShow(containerElement, startFunction)
{
	if (containerElement != null && startFunction != null)
	{
		//Dynamically determine the slide show's height by looking at the starting slide's height
		var slideHeight = containerElement.getSize().y;

		//Some browsers may not report a height, or may report a bad height. So, examine the starting slide to
		//extract the tallest child element's height and use that for the slide show height. This assumes that
		//the starting slide will have at least one large full-height image/object/element inside of it.
		if (slideHeight <= 0) //NOTE: Konqueror reports a zero height on the first pass through the page
		{
			//Get a list of all child elements for the starting slide
			var elements = containerElement.getChildren();
			for (var i = 0; i < elements.length; i++)
			{
				//For (each child element)...
				var children = elements[i].getChildren();
				while (children.length)
				{
					//Push all of its child elements on to the stack
					elements.push(children.pop());
				}
			}

			//Function to look for all child element ".height" properties to get populated, meaning all <img> and
			//<object> tags should be fully loaded. If this never completes, then the page will just keep showing
			//the starting slide, and the slide navigation buttons won't appear either, which is OK. If browsers
			//can't handle the slide show, then just being stuck on the starting slide is acceptable.
			var chcBannerHeightChecker = function()
			{
				var startFunction = arguments[0];
				var slideHeight = 0;

				//For (each child element of the starting slide)...
				for (var i = 0; i < this.length; i++) //NOTE: "this" is the array of child elements bound to this function
				{
					//If (this element has a ".height" property)...
					if (this[i].height)
					{
						if (this[i].height == 0) return; //Not all tags are fully populated yet, so just exit
						slideHeight = Math.max(slideHeight, this[i].height); //Keep the tallest .height
					}
				}

				//If (a usable height was found)...
				if (slideHeight)
				{
					$clear(chcBannerHeightCheckerTimerID); //Stop the timer
					chcBannerHeightCheckerTimerID = null;
					startFunction(slideHeight); //Start the slide show
				}
			};

			//Setup a timer to repeatedly call the above function, waiting for all child elements to load so the
			//tallest element can be found (probably an <img> or <object>) and use that for the slide show height.
			//NOTE: The array of child elements will be bound to the function, making it "this" inside the function
			chcBannerHeightCheckerTimerID = chcBannerHeightChecker.periodical(50, elements, startFunction);
		}
		else
		{
			//Start the slide show
			startFunction(slideHeight);
		}
	}
}



///////////////////////////////////////////////////////////////////////////
// Video support
///////////////////////////////////////////////////////////////////////////



//Show a popup window centered on the screen and showing the specified URL
function showVideo(w, h, url)
{
	var locationLeft = (screen.width  - w) / 2;
	var locationTop  = (screen.height - h) / 2;
	window.open(url, 'video', 'width=' + w + ', height=' + h + ', left=' + locationLeft + ', top=' + locationTop + ', scrollbars=no');
	return false;
}

//Generic callback function for swfobject.embedSWF() and the <swfobject> Site Studio macro
function chcSwfObjectCallback(e)
{
	//If (this browser instance is IE on Windows AND "e" is populated properly by swfobject)...
	if (swfobject && swfobject.ua.ie && swfobject.ua.win && e.id)
	{
		//Get some element ID's
		var baseID = e.id.replace(/Replace/, '');
		var swfContainerID = baseID + 'Container';
		var swfPlayerID = baseID + 'Player';

		//If (we have a reference to a SWF <object>)...
		var swfPlayer = swfobject.getObjectById(swfPlayerID);
		if (swfPlayer)
		{
			//Function to wait for the <object> tag to become "ready", which indicates that the SWF loaded or failed to load
			//NOTE: "this" will refer to the player object inside this stateMonitor() function
			var stateMonitor = function()
			{
				//If (the <object> tag is "ready")...
				if (this.readyState == 4)
				{
					//If (SWF failed to load)...
					var timerID = this.swfStateMonitorTimerID;
					if (this.PercentLoaded() == 0)
					{
						var div = $(this.swfContainerID);
						div.setStyle('border', 'solid 1px #808080');
						div.set('html', '<div align="center">Video unavailable internally</div>');
					}

					//Cancel the timer
					$clear(timerID);
				}
			};

			//Setup a timer to call the stateMonitor() function repeatedly until the SWF either loads or times out
			var timerID = stateMonitor.periodical(200, swfPlayer);
			swfPlayer.swfContainerID = swfContainerID;  //Pass in the container element ID
			swfPlayer.swfStateMonitorTimerID = timerID; //Keep the timer ID so the stateMonitor() can properly cancel the timer when no longer needed
		}
		else
		{
			//No <object> reference, so assume that Flash isn't installed
			var div = $(swfContainerID);
			if (div)
			{
				div.setStyle('border', 'solid 1px #808080');
				div.set('html', '<div align="center"><a href="http://www.adobe.com/products/flashplayer">Adobe Flash Player not installed</div>');
			}
		}
	}
}



///////////////////////////////////////////////////////////////////////////
// Common support functions
///////////////////////////////////////////////////////////////////////////



//Remove leading/trailing space from a string (legacy support since MooTools has .trim() prototyped now)
function strtrim(str)
{
	return str.replace(/^\s+/, '').replace(/\s+$/, '');
}



///////////////////////////////////////////////////////////////////////////
// Form validation for specific <form>'s on the site
///////////////////////////////////////////////////////////////////////////



//Validate generic "Search" forms
function validateSearchForm(txt)
{
	if (strtrim(txt.value).length == 0) txt.focus();
	return strtrim(txt.value).length != 0;
}

//Validate the Provider Directory search form (which = the <form> instance)
function validateProviderSearchForm(which)
{
	//If (no fields have values)...
	if (which.drname.value == '' && which.city.value == '' && which.clinic.value == '' && which.specialty.selectedIndex == 0)
	{
		alert('You must enter a name, city, clinic or select a specialty field.  Please complete them, then submit again.');
		which.drname.focus();
		return false;
	}
	else
	{
		return true;
	}
}

//See if ANY radio button is selected in a given radio group
function validateRadioGroup(frm, groupName)
{
	var checked = false;
	for (var i = 0; i < frm.elements.length; i++)
	{
		if (frm.elements[i].type == 'radio')
		{
			if (frm.elements[i].name == groupName) checked |= frm.elements[i].checked;
		}
	}
	return checked; //TRUE or FALSE
}

//Ensure that something was selected for each radio button group on a <form>
function validatePoll(frm)
{
	//For (each form element)...
	for (var i = 0; i < frm.elements.length; i++)
	{
		//If (this element is a radio button)...
		if (frm.elements[i].type == 'radio')
		{
			//If (a radio button is NOT selected in this group)...
			if (!validateRadioGroup(frm, frm.elements[i].name))
			{
				//Tell the user why the vote can't be accepted yet
				alert('Please select an option for each question before voting.');
				return false; //Vote denied until the form is properly filled out
			}
		}
	}

	//All fields are filled out properly, so allow the form to be submitted
	return true;
}



///////////////////////////////////////////////////////////////////////////
// Legacy form validation functions
///////////////////////////////////////////////////////////////////////////



//Check if string contains NO useful information (is null or is empty or is full of whitespace)
function isEmpty(s)
{
	if (s == null) return true;
	return (strtrim(s).length == 0);
}

//Function validateEmail implemented using regular expressions, checking for a valid format:
function validateEmail(sField)
{
	var addressPattern = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;

	if (isEmpty(sField.value)) return true;
	if (!addressPattern.test(sField.value)) return false;

	return true;
}

//Validate SSN
function SSNValidation(ssn)
{
	var matchArr = ssn.value.match(/^(\d{3})-?\d{2}-?\d{4}$/);
	var numDashes = ssn.value.split('-').length - 1;

	if (ssn.value.length == 0) return true;
	if (matchArr == null || numDashes == 1) return false;

	return true;
}

//Validate a zip code by checking for five digits or five+four digits
function validateZipCode(sField)
{
	var zipCodePattern = /(^\d{5}$)|(^\d{5}\-\d{4}$)/;

	if (isEmpty(sField.value)) return true;
	if (!zipCodePattern.test(sField.value)) return false;

	return true;
}

//Validates that a phone number is 10 digits and contains only numbers
function validatePhone(fld)
{
	var stripped = fld.value.replace(/[\(\)\.\-\ ]/g, '');

	if (stripped.length == 0) return 0;
	if (isNaN(parseInt(stripped))) return 1; //Illegal Characters
	if (!(stripped.length == 10)) return 2;  //Not enough digits

	return 0;
}

//This function will check the character length on the passed in a <textarea> field and, if it exceeds the max passed in length,
//will send back how many extra characters were entered.
function maxlength(field, maxvalue)
{
	//If (the field has no text in it) Then, that's within the maxvalue limit, so that's OK
	if (field.value == null) return 0;

	//Determine how many characters have been entered, and how many need to be trimmed off for the user
	var q = field.value.length;
	var r = q - maxvalue;

	//If (the field has too much text in it)...
	if (q > maxvalue) return r;

	//Field is OK
	return 0;
}

//Function validateStateCode, checking for a valid format:
function validateStateCode(sField)
{
	//Valid U.S. Postal Codes for states, territories, armed forces, etc.
	//See http://www.usps.gov/ncsc/lookups/abbr_state.txt.

	var USStateCodeDelimiter = '|';
	var USStateCodes = 'AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|MP|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY|AE|AA|AE|AE|AP';

	if (isEmpty(sField.value)) return true;
	return ( (USStateCodes.indexOf(sField.value.toUpperCase()) != -1) && (sField.value.indexOf(USStateCodeDelimiter) == -1) )
}



///////////////////////////////////////////////////////////////////////////
// Steve Eidemiller's Form/AJAX Support Functions
///////////////////////////////////////////////////////////////////////////



//Ensure that a text field has something typed in it, ignoring any leading/trailing whitespace
function chcForms_ValidateTextField(fieldName)
{
	return ($(fieldName).value.trim().length != 0); //TRUE if the field has meaningful data
}

//Validate a 10-digit phone number field
function chcForms_ValidatePhoneNumber10(fieldName)
{
	//Extract all digits such that "(651) 555-1212" becomes "6515551212", effectively stripping off any markup or formatting
	var phone = $(fieldName).value.replace(/[^0-9]/g, ''); //Replace all non-digit characters with an empty string, including any whitespace

	//Ignore leading 1's on 11-digit numbers
	if (phone.length == 11 && phone.charAt(0) == '1') return true; //These phone numbers are OK

	//Validate the correct number of digits
	return (phone.length == 10); //TRUE if the phone number has 10 digits
}

//Validate a Social Security Number field as being 9 digits, regardless of dashes
function chcForms_ValidateSSN(fieldName)
{
	return new RegExp(/^(\d{3})-?\d{2}-?\d{4}$/).test($(fieldName).value.trim()); //TRUE if the field contains a valid SSN
}

//Validate a 5-digit zip code field
function chcForms_ValidateZipCode5(fieldName)
{
	return new RegExp(/^\d{5}$/).test($(fieldName).value.trim()); //TRUE if the field contains a 5-digit zip code
}

//Validate a 5-digit/9-digit zip code field
function chcForms_ValidateZipCode59(fieldName)
{
	return new RegExp(/(^\d{5}$)|(^\d{5}-\d{4}$)/).test($(fieldName).value.trim()); //TRUE if the field contains a 5-digit or 9-digit zip+4 code
}

//Validate an email field
function chcForms_ValidateEmail(fieldName)
{
	return new RegExp(/[\w-_\.]+@[\w-\.]*\.[a-zA-Z]{2,}/).test($(fieldName).value.trim()); //TRUE if the field contains a valid email address
}

//Validate a "mm/dd/yyyy" date field, not including a time part
function chcForms_ValidateDate(fieldName, minDate, maxDate)
{
	//Defaults
	var now = new Date();
	minDate = minDate || new Date(now.getFullYear() - 10, now.getMonth(), now.getDate()); //10 years ago
	maxDate = maxDate || new Date(now.getFullYear() + 10, now.getMonth(), now.getDate()); //10 years from now

	//Split into component mm, dd, yyyy fields in dateParts[], which will be NULL if a pattern match is not found
	var dateParts = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec($(fieldName).value.trim());

	//Enforce the "mm/dd/yyyy" format, allowing for 1-digit months or dates, but forcing 4-digit years
	if (dateParts == null) return false; //No pattern match found

	//Verify the month
	var month = dateParts[1];
	if (month < 1 || month > 12) return false; //Month out of bounds

	//Check for leap years
	var year = dateParts[3];
	var isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);

	//Verify the day
	var day   = dateParts[2];
	if (day == 0) return false; //Zero date
	if (month == 2 && day > (isLeapYear ? 29 : 28)) return false; //Too many days in February
	if ((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) return false; //Too many days in a 30-day month
	if (day > 31) return false; //Too many days in a 31-day month

	//Attempt an actual Date() conversion. If we did this without the above checks for day and month, Date() would just convert March 32nd (3/32) to April 1st (4/1), which is not what we want.
	var d = new Date(fieldValue);
	if (isNaN(d)) return false; //Not a valid date

	//Enforce min/max constraints
	return (d.valueOf() >= minDate.valueOf() && d.valueOf() <= maxDate.valueOf());
}

//Remove all formatting (non-digit characters) for numeric fields, such as zip codes, SSN's and phone numbers
function chcForms_ExtractDigits(fieldName)
{
	return $(fieldName).value.replace(/[^\d]/g, '');
}

//Markup strings such that they don't break any HTML
function chcForms_EscapeHTML(html)
{
	return (new String(html)).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');
}

//Accepts a Date() object and returns a date string formatted like mm/dd/yyyy
function chcForms_FormatDate(dateObject)
{
	var month = new String('00' + (dateObject.getMonth() + 1));
	var day = new String('00' + dateObject.getDate());
	return new String(month.slice(month.length - 2) + '/' + day.slice(day.length - 2) + '/' + dateObject.getFullYear());
}

//Accepts a Date() object and returns a time string formatted like hh:mm (24 hour clock)
function chcForms_FormatTime(dateObject)
{
	var hours = new String('00' + dateObject.getHours());
	var minutes = new String('00' + dateObject.getMinutes());
	return new String(hours.slice(hours.length - 2) + ':' + minutes.slice(minutes.length - 2));
}

//Accepts a Date() object and returns a date/time string formatted like mm/dd/yyyy hh:mm (24 hour clock)
function chcForms_FormatDateTime(dateObject)
{
	return chcForms_FormatDate(dateObject) + ' ' + chcForms_FormatTime(dateObject);
}

//Format a phone number like: "(800) 555-1212"
function chcForms_FormatPhoneNumber(phoneNumber)
{
	var digits = new String(phoneNumber.replace(/[^\d]/g, '')); //Remove any non-digit characters
	var formatted = '';
	if (digits.length == 11)
	{
		formatted = digits.substr(0, 1) + ' ';
		digits = digits.substring(1);
	}
	return formatted + '(' + digits.substr(0, 3) + ') ' + digits.substr(3, 3) + '-' + digits.substr(6, 4);
}



///////////////////////////////////////////////////////////////////////////
// Google Analytics
///////////////////////////////////////////////////////////////////////////



function chcGoogleAnalytics_TrackThisPage()
{
	var gaJsHost = (('https:' == document.location.protocol) ? 'https://ssl.' : 'http://www.');
	document.write(unescape('%3Cscript src="' + gaJsHost + 'google-analytics.com/ga.js" type="text/javascript"%3E%3C/script%3E'));
	try
	{
		var pageTracker = _gat._getTracker('UA-11218847-1'); //Unique Google Analytics profile ID for "www.chidrensmn.org"
		pageTracker._setDomainName('.childrensmn.org');  //Force our domain name
		pageTracker._trackPageview();
	} catch(e) {}
}
