/* -------------------------------------------------------------------------- */
/*  (CC) 2006 Ross Harmes & Tristan Roy. Some Rights Reserved.                */
/*         http://creativecommons.org/licenses/by/2.0                         */
/* This Javscript file is licensed under a Creative Commons License.          */		
/* -------------------------------------------------------------------------- */

                   /* ----------------------------------- *\
                       Why namespace your code?             
                       Conflicting var / functions names    
                       can ruin your day.                   
                   \* ----------------------------------- */

/* -------------------------------------------------------------------------- */
/*  Set up the TF namespace.                                                  */
/* -------------------------------------------------------------------------- */

var TF = window.TF || {};


/* -------------------------------------------------------------------------- */
/*  Functions to set, read and delete cookies.                                */
/* -------------------------------------------------------------------------- */

TF.Cookie = function () {

	return {	// Public methods below:
	
		create: function(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=/";	
		},
		
		read: function(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;
		},

		erase: function(name) {

			TF.Cookie.create(name, "", -1);
		}
	}

} ();	// Invoke the function to return the public methods and vars.	



/* -------------------------------------------------------------------------- */
/*  Functions to initialize and perform the scrolling anchor links.           */
/* -------------------------------------------------------------------------- */

TF.Scroller = function () {

	var stepIncrement = 50;	// The number of pixels that each step moves the window.
	var stepDelay = 10;	// The number of milliseconds between steps.
	var limit = 6 * 1000;	// After 6 seconds the scroll is killed.
	
	var running = false;
	
	/* Recursive scrolling method. Steps through the complete scroll. */
		
	function scrollStep(to, dest, down) {
	
		if(!running || (down && to >= dest) || (!down && to <= dest)) {
			TF.Scroller.killScroll();
			return;
		}

		if((down && to >= (dest - (2 * stepIncrement))) ||
		   (!down && to <= (dest - (2 * stepIncrement)))) {
			stepIncrement = stepIncrement * .55;
		}

		window.scrollTo(0, to);
		
		// Assign the returned function to a public method.
		
		TF.Scroller.nextStep = callNext(+to + stepIncrement, dest, down);

		window.setTimeout(TF.Scroller.nextStep, stepDelay);
	}
	
	/* Create a closure so that scrollStep can be accessed by window.setTimeout(). */
	
	function callNext(to, dest, down) {
	
		return function() { scrollStep(to, dest, down); };
	}

	return {
	
		nextStep: null,
		killTimeout: null,
	
		/* Sets up and calls scrollStep. */
	
		anchorScroll: function(e, obj) {

			var clickedLink = YAHOO.util.Event.getTarget(e);
			var anchorId = clickedLink.href.replace(/^.*#/, '');
			var target = YAHOO.util.Dom.get(anchorId);
			
			if(target) {
			
				YAHOO.util.Event.stopEvent(e);
				running = true;
				
				var yCoord = ((YAHOO.util.Dom.getY(target) - 6) < 0) ? 0 : YAHOO.util.Dom.getY(target) - 6;
				var currentYPosition = (document.all) ? document.body.scrollTop : window.pageYOffset;
				var down = true;
	
				if(currentYPosition > yCoord) {
					stepIncrement *= -1;
					down = false;
				}
	
				// Stop the scroll once the time limit is reached.
	
				TF.Scroller.killTimeout = window.setTimeout(TF.Scroller.killScroll, limit);
	
				scrollStep(currentYPosition + stepIncrement, yCoord, down);	
			}
		},
		
		/* Kill the scroll after a timeout, to prevent an endless loop. */
		
		killScroll: function() {
			window.clearTimeout(TF.Scroller.killTimeout);
			running = false;
			stepIncrement = 50;			
		},
	
		/* Attach the scrolling method to the links with the class 'scrolling-link'. */

		init: function() {
		
			var links = YAHOO.util.Dom.getElementsByClassName('scrolling-link', 'a');
			YAHOO.util.Event.addListener(links, 'click', TF.Scroller.anchorScroll, TF.Scroller, true);
		}
	}

} ();

YAHOO.util.Event.onAvailable('doc', TF.Scroller.init, TF.Scroller, true);	



/* -------------------------------------------------------------------------- */
/*  Functions to collapse and show the header. Cookies are used to save the   */
/*  setting.                                                                  */
/* -------------------------------------------------------------------------- */

TF.Header = function () {

	var headerShowing;
	
	function show() {
		
		var oAnimExpand = new YAHOO.util.Anim('hd');
		oAnimExpand.attributes.height = { to: 280 };
		oAnimExpand.duration = 0.5;
		
		if(YAHOO.util.Dom.get('navigation')) {
		
			var oAnimFadeOut = new YAHOO.util.Anim(['nav-home', 'nav-archives', 'nav-examples', 'nav-about'], { opacity: { to: 0 } }, 0.5);
			oAnimFadeOut.onComplete.subscribe(startExpand);

			var oAnimFadeIn = new YAHOO.util.Anim(['intro-p', 'nav-header', 'nav-search', 'recent-posts', 'authors', 'nav-home', 'nav-archives', 'nav-examples', 'nav-about'], { opacity: { from: 0, to: 100 } }, 0.5);
			oAnimExpand.onComplete.subscribe(fadeIn);
		}	
		
		(oAnimFadeOut) ? oAnimFadeOut.animate() : startExpand();

		
		YAHOO.util.Dom.get('toggle-header').innerHTML = 'Hide Header &uarr;';
		TF.Cookie.create('showHeader', 1, 365);
		headerShowing = 1;	

		
		function fadeIn() {
			oAnimFadeIn.animate();
		}
		
		function startExpand() {
			YAHOO.util.Dom.get('hd').className = '';
			oAnimExpand.animate();
		}
	}
	
	function hide() {
	
		var oAnimCollapse = new YAHOO.util.Anim('hd');
		oAnimCollapse.attributes.height = { to: 50 };
		oAnimCollapse.duration = 0.5;	
	
		if(YAHOO.util.Dom.get('navigation')) {

			var oAnimFadeOut = new YAHOO.util.Anim(['intro-p', 'nav-header', 'nav-search', 'recent-posts', 'authors', 'nav-home', 'nav-archives', 'nav-examples', 'nav-about'], { opacity: { to: 0 } }, 0.5);
			oAnimFadeOut.onComplete.subscribe(startCollapse);
			
			var oAnimFadeIn = new YAHOO.util.Anim(['nav-home', 'nav-archives', 'nav-examples', 'nav-about'], { opacity: { from: 0, to: 100 } }, 0.5);
			oAnimCollapse.onComplete.subscribe(fadeIn);
		}	
		
		(oAnimFadeOut) ? oAnimFadeOut.animate() : startCollapse();

		YAHOO.util.Dom.get('toggle-header').innerHTML = 'Show Header &darr;';
		TF.Cookie.create('showHeader', 0, 365);
		headerShowing = 0;	
		
		
		function fadeIn() {
			YAHOO.util.Dom.get('hd').className = 'collapsed';
			oAnimFadeIn.animate();
		}
		
		function startCollapse() {
			oAnimCollapse.animate();
		}
	}
	
	return {

		toggle: function(e, obj) {

			YAHOO.util.Event.stopEvent(e);

			(headerShowing == 1) ? hide() : show();
		},
		
		init: function() {

			headerShowing = TF.Cookie.read('showHeader') || 1;

			YAHOO.util.Event.addListener('toggle-header', 'click', TF.Header.toggle, TF.Header, true); 
		}

	}

} ();

YAHOO.util.Event.onAvailable('toggle-header', TF.Header.init, TF.Header, true); 



/* -------------------------------------------------------------------------- */
/*  Functions to toggle the display of code as plain text.                    */
/* -------------------------------------------------------------------------- */

TF.CodeViewer = function () {

	var swapDuration = 0.3;
	var easeMethod = YAHOO.util.Easing.easeOut;
	var status = {};

	return {
	
		showPlain: function(e, obj) {

			YAHOO.util.Event.stopEvent(e);
	
			var clickedLink = YAHOO.util.Event.getTarget(e);
			
			var fragmentId = clickedLink.id.replace(/link-/, '');

			var codeContainer = YAHOO.util.Dom.get('container-' + fragmentId);
			var codeList = YAHOO.util.Dom.get('code-' + fragmentId);
			var codePlain = YAHOO.util.Dom.get('plain-' + fragmentId);
			
			var oAnimRollUp = new YAHOO.util.Anim(codeContainer, { height : { to: 0 } }, swapDuration, easeMethod);				
			oAnimRollUp.onComplete.subscribe( function() { TF.CodeViewer.toggle(codeContainer, codeList, codePlain, fragmentId); } );
			
			oAnimRollUp.animate();
		},
		
		/* Hides/shows the formatted/plain code. */
		
		toggle: function(codeContainer, codeList, codePlain, fragId) {
		
			var state = status[fragId] || 0;
			var codeLink = YAHOO.util.Dom.get('link-' + fragId)
			var fullHeight;
		
			if(state === 0) {
			
				codeList.style.display = 'none';
				codePlain.style.display = 'block';
				
				codeLink.innerHTML = 'View formatted text';
				
				fullHeight = parseInt(YAHOO.util.Dom.getStyle(codePlain, 'height'), 10) + 42; // Add a bit for the bottom scroller.
			}
			else {
			
				codePlain.style.display = 'none';
				codeList.style.display = 'block';	
				
				codeLink.innerHTML = 'View plain text';
				
				fullHeight = parseInt(YAHOO.util.Dom.getStyle(codeList, 'height'), 10) + 9;
			}
		
			status[fragId] = 1 - state;

			var oAnimRollDown = new YAHOO.util.Anim(codeContainer, { height : { to: fullHeight, from : 0 } }, swapDuration, easeMethod);
			
			oAnimRollDown.animate();
		},

		/* Attach the showPlain method to the links with the class 'code-plain'. */

		init: function() {
		
			var links = YAHOO.util.Dom.getElementsByClassName('code-plain-link', 'a');
			YAHOO.util.Event.addListener(links, 'click', TF.CodeViewer.showPlain, TF.CodeViewer, true);
			if(links.length > 0) {
				YAHOO.util.Dom.setStyle(links, 'visibility', 'visible');	// These links will remain hidden if JS is not enabled.
			}
		}
	}

} ();

YAHOO.util.Event.onAvailable('doc', TF.CodeViewer.init, TF.CodeViewer, true);



/* -------------------------------------------------------------------------- */
/*  Functions to for live comment preview. Adapted from similar functions     */
/*  created by Jon Hicks: http://www.hicksdesign.co.uk/js/global.js           */
/* -------------------------------------------------------------------------- */

TF.Comments = function() {

	var commentForm,
		formText, formTitle, formAuthor,
		previewText, previewTitle, previewAuthor;

	return {
	
		/* Loads the text from the comment form fields into the correct preview divs. */
	
		reloadPreviewText: function() { 
			previewText.innerHTML = formText.value.replace(/(\n|\r)/g,'<br />').replace(/(<br \/>){2,}/gi,'<'+'/p><p>');
		},
	
		reloadPreviewAuthor: function() {
			previewAuthor.innerHTML = formAuthor.value + ':';
		},
	
		/* Comment form checker. Backed up by server-side validation. */

		checkCommentForm: function(e) {
			if(formAuthor.value == '' || formText.value == '') {	 
	   			alert("Please fill out both author and comment fields.");
	   			YAHOO.util.Event.stopEvent(e);
			}
		},
		
		init: function() {
		
			commentForm   = YAHOO.util.Dom.get('comment-form');
		
			formText      = YAHOO.util.Dom.get('comment-text'); 
			formAuthor    = YAHOO.util.Dom.get('comment-author');

			previewText   = YAHOO.util.Dom.get('preview-text');
			previewAuthor = YAHOO.util.Dom.get('preview-author');
			
			YAHOO.util.Event.on(commentForm, 'submit', TF.Comments.checkCommentForm, TF.Comments, true);
			
			YAHOO.util.Event.on(formAuthor, 'keyup', TF.Comments.reloadPreviewAuthor, TF.Comments, true);
			YAHOO.util.Event.on(formText, 'keyup', TF.Comments.reloadPreviewText, TF.Comments, true);
		}
	}	
	
} ();

YAHOO.util.Event.onAvailable('doc', TF.Comments.init, TF.Comments, true);



/* -------------------------------------------------------------------------- */
/*  Functions to for live search.                                             */
/* -------------------------------------------------------------------------- */

TF.LiveSearch = function() {

	var duration = 0.3;
	var easeMethod = YAHOO.util.Easing.easeOut;

	var inputField,
		resultsDiv,
		defaultValue = 'live search -- type and pause',
		defaultNoDiv = 'site search',
		searchRunning = false;

	return {
	
		/* Clear or set the value when focused on. */
	
		toggleValue: function(e) {
		
			if(inputField.value === defaultValue) {
				inputField.value = '';
				inputField.className = '';
			}
			else if(inputField.value === '') {
				inputField.className = 'grey';
				inputField.value = defaultValue;
			}
		},
	
		/* Sends the query to the search script. */
	
		sendQuery: function(e) { 
		
			if(inputField.value !== '') {
				
				if(!searchRunning) {	// Open the results div.
				
					searchRunning = true;
					
					YAHOO.util.Dom.setStyle(resultsDiv, 'display', 'block');
					var openAnim = new YAHOO.util.Anim(resultsDiv, { height: { to: 360 } }, duration, easeMethod);
					openAnim.animate();
				}
				
				// Send the async request.
				
				var ajaxRequest = YAHOO.util.Connect.asyncRequest('POST', '/live_search/', { success: TF.LiveSearch.processResults, scope: TF.LiveSearch }, 'q=' + escape(inputField.value));
				
				YAHOO.log('sending request');
			}
			if(inputField.value === '' && searchRunning) {	// Close the results div.
			
				var closeAnim = new YAHOO.util.Anim(resultsDiv, { height: { to: 0 } }, duration, easeMethod);
				closeAnim.onComplete.subscribe(function() {
					YAHOO.util.Dom.setStyle(resultsDiv, 'display', 'none');
				});
				closeAnim.animate();								
			
				searchRunning = false;
			}
		},
		
		/* Parse the results of the search and format the output. */ 
		
		processResults: function(o) {
		
			resultsDiv.innerHTML = o.responseText;
		
		},
		
		init: function() {
		
			inputField = YAHOO.util.Dom.get('search-field');
			resultsDiv = YAHOO.util.Dom.get('live-results'); 
			
			if(!inputField) return;
			if(!resultsDiv) defaultValue = defaultNoDiv;

			TF.LiveSearch.toggleValue();
			inputField.setAttribute('autocomplete', 'off');
			
			YAHOO.util.Event.on(inputField, 'focus', TF.LiveSearch.toggleValue, TF.LiveSearch, true);
			YAHOO.util.Event.on(inputField, 'blur', TF.LiveSearch.toggleValue, TF.LiveSearch, true);
			YAHOO.util.Event.on(inputField, 'keyup', TF.LiveSearch.sendQuery, TF.LiveSearch, true);
		}
	}	
	
} ();

YAHOO.util.Event.onAvailable('doc', TF.LiveSearch.init, TF.LiveSearch, true);



/* -------------------------------------------------------------------------- */
/*  Functions to getting and logging the total elapsed page load time.        */
/*  Requires TF.Cookie, YUI Event & YUI Connection.                           */
/* -------------------------------------------------------------------------- */

TF.Elapsed = function() {

	var logger = '/elapsed/logger.php';

	return {
	
		/* Retreive the time cookie (if it exists) and send the total elapsed time to the logger. */
	
		getTime: function() {
			var currentTime = new Date();
			var elapsedEnd = currentTime.getTime();
			var elapsedStart = TF.Cookie.read('elapsedStart');
			
			if(!elapsedStart) return;
			
			TF.Cookie.erase('elapsedStart');
			var elapsed = elapsedEnd - elapsedStart;
			var res = document.location;
			YAHOO.util.Connect.asyncRequest('POST', logger, { }, "elapsed=" + elapsed + "&res=" + res); 
		},
		
		/* Set the cookie with the current time. */
		
		setTime: function() {
			var currentTime = new Date();
			TF.Cookie.create('elapsedStart', currentTime.getTime(), 1);			
		}
	}	
	
} ();

YAHOO.util.Event.on(window, 'load', TF.Elapsed.getTime, TF.Elapsed, true);
YAHOO.util.Event.on(window, 'beforeunload', TF.Elapsed.setTime, TF.Elapsed, true);



/* -------------------------------------------------------------------------- */
/*  Functions for showing and hiding the site's system messages.              */
/* -------------------------------------------------------------------------- */

TF.SystemMessage = function() {

	var duration = 0.3;
	var easeMethod = YAHOO.util.Easing.easeOut;

	var msgCreated = false,
	    msgShown = false;
	var messageElement,
	    messageContainer;

	return {
		
		create: function() {
			
			if(msgShown) TF.SystemMessage.hideAndDestroy();
			else if(msgCreated) TF.SystemMessage.destroy();
			
			msgCreated = true;
			
			messageElement = document.createElement('DIV');
			messageElement.id = 'system-message';
			YAHOO.util.Dom.get('doc').insertBefore(messageElement, YAHOO.util.Dom.get('bd'));
			
			messageContainer = document.createElement('DIV');
			messageContainer.id = 'system-message-container';
			messageElement.appendChild(messageContainer);
		},
		
		destroy: function() {
			msgCreated = false;
			
			messageElement.parentNode.removeChild(messageElement);
			messageElement = null;	
		},
		
		insertHTML: function(string) {
			if(!messageContainer) return;
			messageContainer.innerHTML = string;
		},

		show: function() {
			msgShown = true;
			
			var totalHeight = parseInt(YAHOO.util.Dom.getStyle(messageContainer, 'height'), 10) +
			                  parseInt(YAHOO.util.Dom.getStyle(messageContainer, 'padding-top'), 10) +
			                  parseInt(YAHOO.util.Dom.getStyle(messageContainer, 'padding-bottom'), 10);
			if(isNaN(totalHeight)) totalHeight = 168;
			
			var showAnim = new YAHOO.util.Anim(messageElement, { height: { to: totalHeight } }, duration, easeMethod);
			showAnim.animate();
		},

		hide: function(fn) {
			msgShown = false;
			
			var hideAnim = new YAHOO.util.Anim(messageElement, { height: { to: 0 } }, duration, easeMethod);
			hideAnim.onComplete.subscribe(fn);
			hideAnim.animate();
		},
		
		hideAndDestroy: function() {
			TF.SystemMessage.hide(TF.SystemMessage.destroy);
		}
	}	
} ();



/* -------------------------------------------------------------------------- */
/*  Functions for changing the site's stylesheet.                             */
/*  Requires TF.Cookie, TF.SystemMessage, YUI Event & YUI Connection.         */
/* -------------------------------------------------------------------------- */

TF.StyleSwitcher = function() {

	var switcherShown = false;
	
	/* Set the style preference. */

	function setActiveStyleSheet(title) {
		for(var n = 0, a; a = document.getElementsByTagName("link")[n]; n++) {
			if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
				a.disabled = !(a.getAttribute("title") == title);
			}
		}
	}

	/* Get the choosen stylesheet. */

	function getActiveStyleSheet() {
		for(var n = 0, a; a = document.getElementsByTagName("link")[n]; n++) {
			if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled) {
				return a.getAttribute("title");
			}
		}
		return null;
	}

	/* Get the default active stylesheet. */

	function getPreferredStyleSheet() {
		for(var n = 0, a; a = document.getElementsByTagName("link")[n]; n++) {
			if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("rel").indexOf("alt") == -1 && a.getAttribute("title")) {
				return a.getAttribute("title");
			}
		}
		return null;
	}	

	return {
		
		setStyle: function(e) {		
			var style = YAHOO.util.Event.getTarget(e).id.replace(/style-/, '');
			
			switcherShown = false;
			YAHOO.util.Event.stopEvent(e);
			TF.SystemMessage.hideAndDestroy();

			TF.Cookie.create('style', style, 365);
			setActiveStyleSheet(style);
		},
		
		/* Toggle the system message and show/hide the switcher tags. */
		
		toggleSwitcher: function(e) {
			YAHOO.util.Event.stopEvent(e);
			
			if(!switcherShown) {
				switcherShown = true;
				
				TF.SystemMessage.create();
				TF.SystemMessage.insertHTML('<h2>Style Switcher</h2><p id="switcher-p"><a href="#" id="style-light" class="style-thumb" title="dark text on a white background">light</a><a href="#" id="style-dark" class="style-thumb" title="light text on a black background">dark</a></p>');
				YAHOO.util.Event.on(['style-light', 'style-dark'], 'click', TF.StyleSwitcher.setStyle, TF.StyleSwitcher, true);				
				TF.SystemMessage.show();
			}
			else {
				switcherShown = false;
				TF.SystemMessage.hideAndDestroy();
			}			
		},
		
		/* On tf.js load, set the preferred style. */
		
		loadStyle: function() {
			var cookie = TF.Cookie.read("style");
			var title = (cookie) ? cookie : getPreferredStyleSheet();
			setActiveStyleSheet(title);
		},
		
		/* Attach the onclick event to the link. */
		
		init: function() {
			YAHOO.util.Event.on('toggle-switcher', 'click', TF.StyleSwitcher.toggleSwitcher, TF.StyleSwitcher, true);
		}
	}	
	
} ();

TF.StyleSwitcher.loadStyle();
YAHOO.util.Event.onAvailable('doc', TF.StyleSwitcher.init, TF.StyleSwitcher, true);