/**
 * Digital Fruition Javascript Tools
 *
 * A collection of common Digital Fruition functions, including the DFTools
 * library, the com.digitalfruition library, and some assorted common code for
 * dealing with dates, arrays, events, numbers, etc.
 * 
 * All copyright Digital Fruition, LLC except where otherwise noted
 * 
 * A licence must be granted to you by Digital Fruition, LLC in order to make
 * use of this file. USING THIS FILE WITHOUT AGREEMENT TO DIGITAL FRUITION, LLC
 * POLICIES AND TERMS AND CONDITIONS IS EXPRESSLY FORBIDDEN
 * http://www.digitalfruition.com/tos
 *
 * @see http://www.digitalfruition.com/tos
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */


/**
 * Mouse Wheel Event
 *
 * Extend the prototype event class to handle mouse wheel events
 *
 * @author unknown
 * @copyright unknown
 * @license unknown
 */
Object.extend(Event, {
	wheel:function (event){
		var delta = 0;
		if (!event) event = window.event;
		if (event.wheelDelta) {
			delta = event.wheelDelta/120; 
			if (window.opera) delta = -delta;
		} else if (event.detail) { delta = -event.detail/3;	}
		return Math.round(delta); //Safari Round
	}
});

/**
 * Digital Frution Standard Tools Library
 *
 * A collection of standard digital fruition tools, including a console, an
 * error reporting system, math and string functions, and more.
 *
 * @version 1.0
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */
var DFTools =
{
	version: '1.0',
	
	urls: {
		self: '',
		base: '/',
		reportError: 'problemReport.php'
	},
	
	setupUrlBase: function()
	{
		$A(document.getElementsByTagName("script")).findAll( function(s) {
			return (s.src && s.src.match(/DFTools\.js(\?.*)?$/))
		}).each( function(s) {
			DFTools.urls.self = s.src;
			DFTools.urls.base = DFTools.urls.self.replace(/[^\/]+\/DFTools\.js(\?.*)?$/,'');
		});
	},
	
	/**
	 * Create (if necessary) a given namespace
	 *
	 * Taken from http://weblogs.asp.net/mschwarz/archive/2005/08/26/423699.aspx
	 */
	namespace: function(ns)
	{
		var nsParts = ns.split(".");
		var root = window;
		
		for(var i=0; i<nsParts.length; i++)
		{
			if(typeof(root[nsParts[i]]) == "undefined"){
				root[nsParts[i]] = new Object();
			}
			
			root = root[nsParts[i]];
		}
	},
	
	
	
	/**
	 * Digital Fruition console
	 *
	 */
	console: (function()
	{
		var log = $A(), debug = $A();
		
		var consoleLog = function()
		{
			try
			{
				try
				{
					var entry = new Date;
					entry = entry.toString() + ": " + $A(arguments).join(' ');
				}
				catch(e)
				{
					entry = "DFTools.console: Error while building a log entry!"
				}
				log.push(entry);
				
				if(log.length >= DFTools.console.maxLogSize)
					log.shift();
				
				if(console && typeof(console.log) == 'function') { console.log.apply(console,arguments); }
			}
			catch(e) {}
		};

		var consoleError = function()
		{
			try
			{
				try
				{
					var entry = new Date;
					entry = entry.toString() + ": ERROR: " + $A(arguments).join(' ');
				}
				catch(e)
				{
					entry = "DFTools.console: Error while building a log error entry!"
				}
				log.push(entry);

				if(log.length >= DFTools.console.maxLogSize)
					log.shift();

				if(console && typeof(console.error) == 'function') { console.error.apply(console,arguments); }
				else if(console && typeof(console.log) == 'function') { console.log.apply(console,arguments); }
			}
			catch(e) {}
		};

		var consoleException = function(exceptionObject,functionName)
		{
			var msg = '';

			if(exceptionObject instanceof Error)
			{
				if(functionName)
					msg = functionName+': ';

				msg += "Caught a "+exceptionObject.name+" Exception: ";
				msg += exceptionObject.message;
			}
			else if((typeof Exception != "undefined") && (exceptionObject instanceof Exception))
			{
				msg += "Caught an Exception: ";

				if(typeof exceptionObject.message == "string")
					msg += exceptionObject.message;
			}
			else
			{
				msg += "Caught a "+(typeof exceptionObject);
			}

			var trace = DFTools.getStackTrace();
			trace.shift();

			consoleError(msg,exceptionObject,trace.join("  <-  "));
		}
		
		var debugLog = function()
		{
			if(DFTools.console.debugLogEnabled)
			{
				try
				{
					try
					{
						var entry = $A(arguments).join(' ');
					}
					catch(e)
					{
						entry = "DFTools.console: Error while building a debug log entry!"
					}
					debug.push(entry);
					
					if(debug.length >= DFTools.console.maxLogSize)
						debug.shift();
					
					if(console && typeof(console.debug) == 'function') { console.debug.apply(console,arguments); }
				}
				catch(e) {}
			}
		};
		
		var getConsoleLog = function()
		{
			return log.join("\n");
		};
		
		var getDebugLog = function()
		{
			return debug.join("\n");
		};
		
		var getAllLogs = function()
		{
			return {'log':getConsoleLog(), 'debug': getDebugLog()};
		};
				
		return {
			log: consoleLog,
			debug: debugLog,
			error: consoleError,
			logException: consoleException,

			getLog: getConsoleLog,
			getDebugLog: getDebugLog,
			getAll: getAllLogs,
			
			maxLogSize: 1000,
			debugLogEnabled: false
		};
	})(),
	
	cancelEvent: function(e)
	{
		e.stop();
	},
	
	writeEmail: function(u,d,tld)
	{
		document.write('<a href="');
		document.write('mailto:');
		document.write(u);
		document.write('@');
		document.write(d + '.' + tld);
		document.write('">');
		document.write(u);
		document.write('@');
		document.write(d + '.' + tld);
		document.write('</a>');
	},
	
	isValidEmail: function(string)
	{
		return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(string);
	},

	/**
	 * Returns a string that is less than or equal to length.
	 *
	 * If $text is less than or equal to length then text is returned as is. If
	 * text is longer than length, then as many words as will fit in length chars
	 * are returned folled by an elipsis (...). The return value will always be less
	 * than or equal to length.
	 *
	 * @return string
	 * @param text
	 * @param length
	 * @author Joshua Gitlin <josh@digitalfruition.com>
	 * @access public
	*/
	summarize: function(text,length)
	{
		var append = '...', digest = '';

		if (text.length < length)
			digest = text;
		else
		{
			var endp = length - append.length;
			while (endp>0 && text.substr(endp-1,1).trim().length == 0)
			{
				endp--;
			}
			if(endp <= 0)
				endp = length - append.length;
			digest = text.substr(0,endp).trim() + append;
		}

		return digest;
	},
	
	fireEvent: function(element,event)
	{
		var evt;

		if (document.createEventObject)
		{
			// dispatch for IE
			
			evt = document.createEventObject();
			return element.fireEvent('on'+event,evt);
		}
		else
		{
			// dispatch for firefox + others
			
			evt = document.createEvent("HTMLEvents");
			evt.initEvent(event, true, true ); // event type,bubbling,cancelable
			return !element.dispatchEvent(evt);
		}
	},
	
	submitForm: function(form)
	{
		//var submit = document.createEvent('HTMLEvents');
		//submit.initEvent('submit',true,true);
		//myAddToCartForm.dispatchEvent(submit);
		
		return DFTools.fireEvent(form,'submit');
	},
	
	loadFlash: function(options,container)
	{
		options = $H(options);
		
		var flashObj = {embedAttrs: {}, params: {}, objAttrs: {}};
		
		$A(["pluginspage"]).each(function(a){ flashObj.embedAttrs[a] = options.get(a); });
		
		$A(["src","movie"]).each(function(a){ flashObj.embedAttrs["src"] = flashObj.params["movie"] = options.get(a); });
		
		$A(["onafterupdate",
			"onbeforeupdate",
			"onblur",
			"oncellchange",
			"onclick",
			"ondblClick",
			"ondrag",
			"ondragend",
			"ondragenter",
			"ondragleave",
			"ondragover",
			"ondrop",
			"onfinish",
			"onfocus",
			"onhelp",
			"onmousedown",
			"onmouseup",
			"onmouseover",
			"onmousemove",
			"onmouseout",
			"onkeypress",
			"onkeydown",
			"onkeyup",
			"onload",
			"onlosecapture",
			"onpropertychange",
			"onreadystatechange",
			"onrowsdelete",
			"onrowenter",
			"onrowexit",
			"onrowsinserted",
			"onstart",
			"onscroll",
			"onbeforeeditfocus",
			"onactivate",
			"onbeforedeactivate",
			"ondeactivate",
			"type",
			"codebase"]).each(function(a){ if(typeof(options.get(a)) != 'undefined') { flashObj.objAttrs[a] = options.get(a); options.unset(a); } });

		$A(["width",
			"height",
			"align",
			"vspace", 
			"hspace",
			"class",
			"title",
			"accesskey",
			"name",
			"id",
			"tabindex"]).each(function(a){ if(typeof(options.get(a)) != 'undefined') { flashObj.objAttrs[a] = flashObj.embedAttrs[a] = options.get(a); options.unset(a); } });
		
		options.each(function(pair){ flashObj.params[pair.key] = flashObj.embedAttrs[pair.key] = pair.value; });
		
		flashObj.objAttrs["classid"] = "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000";
		flashObj.embedAttrs["type"] = "application/x-shockwave-flash";
		
		
		var tag = '';
		
		tag = $H(flashObj.objAttrs).inject('<object ',function(str,pair){
			return str+' '+pair[0]+'="'+pair[1]+'"';
		});
		
		tag += $H(flashObj.params).inject('>',function(str,pair){
			return str+'<param name="'+pair[0]+'" value="'+pair[1]+'" /> ';
		});
		
		tag += $H(flashObj.embedAttrs).inject('<embed ',function(str,pair){
			return str+' '+pair[0]+'="'+pair[1]+'"';
		});
		
		tag += ' ></embed></object>';
		
		if((container = $(container)))
		{
			container.insert(tag);
		}
		
		return tag;
	},
	
	imagesLoaded: function(container)
	{
		container = $(container);
		
		if(!container)
			return false;
		
		var images = container.getElementsBySelector('img');
		
		return Try.these(
			function() { return images.pluck('complete').all(); },
			function() { alert("Can't be sure if imagews are loaded, assuming yes..."); return true; }
		);
	},
	
	/**
	 * Set the default text for fields, which will appear when the fields are empty and dissapear when
	 * they gain focus.
	 *
	 * This function takes a field or an array of fields and applies default text to them. This means
	 * that when the field is empty, the default text will appear and the field will get a class of
	 * .defaultText, allowing the text to change color or style. When the field gains focus, the default
	 * text and class are removed.
	 *
	 * You may pass either a single element, and element ID, or an array of elements / element IDs. If
	 * the text field is omitted, the text for each field will be pulled from the field's title attribute
	 *
	 * @param Array/Element fields
	 * @param string [text]
	 * @return value
	 */
	defaultFieldText: function()
	{
		var defaultFieldTextHash = $H({});
		
		var defaultText = '';
		
		var defaultFieldTextMethods = $H(
		{
			getDefaultText: function()
			{
				return defaultFieldTextHash.get(this.identify());
			},
			
			setDefaultText: function(text)
			{
				defaultFieldTextHash.set(this.identify(),text);
			},
			
			defaultTextUpdate: function()
			{
				val = $F(this);
				var defaultText = this.getDefaultText();
				
				if(!val.length || val == defaultText)
				{
					this.value = defaultText;
					this.addClassName('defaultText');
				}
				else
				{
					this.removeClassName('defaultText');
				}
			},
			
			defaultTextClear: function()
			{
				val = $F(this);
				var defaultText = this.getDefaultText();
				
				if(val == defaultText)
				{
					this.value = '';
				}
				this.removeClassName('defaultText');
			}
		});
		
		var applyDefaultFieldText = function(field)
		{
			field = $(field);
			
			if(field)
			{
				defaultFieldTextMethods.each(function (pair) { field[pair.key] = pair.value.bind(field); });
				
				field.setDefaultText(defaultText ? defaultText : field.title);
				
				field.observe('blur',field.defaultTextUpdate);
				field.observe('focus',field.defaultTextClear);
				
				field.defaultTextUpdate();
			}
		};
		
		var defaultFieldText = function(fields,text)
		{
			defaultText = text;
			
			if(!(fields instanceof Array)) { fields = [fields]; }
			
			fields = $A(fields);
		
			fields.each(applyDefaultFieldText);
		};
		
		return defaultFieldText;
	}(),
	
	var_dump: function(v,seen)
	{
		var dump = {type:'unknown',value:''};
		
		try
		{
			if(!seen)
				seen = $A();
			
			dump.type = typeof v;
			
			switch (dump.type)
			{
				case "function":
					return false;
				
				case "undefined":
				case "unknown":
					dump.value = 'unknown';
				
				case "boolean":
				case "number":
				case "string":
					dump.value = v;
			}
			
			if (v === null)
			{
				dump.type = 'null';
				dump.value = null;
			}
			else if (Object.isElement(v))
			{
				dump.type = 'Element';
				dump.value = 'N/A';
			}
			else if (dump.type == 'object')
			{
				var index = seen.indexOf(v);
				
				if(index != -1)
				{
					++index;
					dump.value = "(*recusion*)";
				}
				else
				{
					index = seen.push(v);
					
					dump.value = {};
					for (var property in v)
					{
						if (!Object.isUndefined(v[property]))
						{
							dump.value[property] = DFTools.var_dump(v[property],seen);
							
							if(!dump.value[property])
								delete dump.value[property];
						}
					}
				}
				
				dump.type += " (#" + index + ")";
			}
		}
		catch(ex)
		{
			dump = {type:'unknown',value:'Error!'};
		}
		
		return dump;
	},
	
	reportError: function(error,module,data,alert_support)
	{
		var msg;
		
		try
		{
			msg = "Unknown Error";
			
			if(error.message) { msg = error.message; }
			
			DFTools.console.log("DFTools Error Report:",msg,error,module,data);
			
			var post = {
				errorMsg: msg,
				module: module ? module : 'Unknown',
				report: {
					browserName: BrowserDetect.name,
					browserOS: BrowserDetect.OS,
					browserVersion: BrowserDetect.version,
					location: window.location.toString()
				}
			};
			
			
			if(error.name) { post.errorName = error.name; }
			
			if(error.fileName) { post.fileName = error.fileName; }
			if(error.lineNumber) { post.lineNumber = error.lineNumber; }
			if(error.number) { post.errorNumber = error.number; }
			
			if(error.error_function) { post.report.errorFunction = error.error_function; }
			if(error.error_handler) { post.report.errorHandler = error.error_handler; }
			
			if(error.description) { post.report.errorDescription = error.description; }
			if(error.stack) { post.report.errorStack = error.Stack; }
			
			if(window.innerHeight) {
				post.report.windowHeight = window.innerHeight;
				post.report.windowWidth = window.innerWidth;
			}
			
			if(typeof(window.pageXOffset) != 'undefinied') {
				post.report.pageXOffset = window.pageXOffset;
				post.report.pageYOffset = window.pageYOffset;
			}
			
			if(document.referrer) {
				post.report.referrer = document.referrer;
			}
			
			if(data)
				post.report.moduleData = DFTools.var_dump(data);
			
			post.report.logs = DFTools.console.getAll();
			
			post.report.stack = $A(DFTools.getStackTrace()).join("\n");
			
			post.report = Object.toJSON(post.report);

			post.alert_support = alert_support?'1':'0';
			
			new Ajax.Request(DFTools.urls.base+DFTools.urls.reportError,{method:'post', parameters: post});
		}
		catch (error_error)
		{
			// Damn, we just can't win. Error when reporting an error!
			// Just give up and ignore this error.
			
			msg = "Unknown Error";
			
			if(error_error.message) { msg = error_error.message; }
			
			DFTools.console.error("Error while reporting en error!!",msg,error_error);
		}
	},
	
	/**
	 * Set a cookie.
	 *
	 * @url http://www.quirksmode.org/js/cookies.html
	 */
	setCookie: function(name,value,days)
	{
		var expires;

		if (days)
		{
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			expires = "; expires="+date.toGMTString();
		}
		else
			expires = "";
		document.cookie = name+"="+value+expires+"; path=/";
	},
	
	/**
	 * Get the value of a cookie.
	 *
	 * @url http://www.quirksmode.org/js/cookies.html
	 * @return string
	 */
	getCookie: 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;
	},
	
	/**
	 * Remove a cookie.
	 *
	 * @url http://www.quirksmode.org/js/cookies.html
	 */
	deleteCookie: function(name)
	{
		DFTools.setCookie(name,"",-1);
	},
	
	
	format: 
	{
		bytes: function(n)
		{
			var units = $A(['KB','MB','GB','TB']);
			var increment = 1024;
			
			var precision = 2;
			
			var unit = 'b';
			
			for(var i=0; i<units.length && n >= increment; ++i)
			{
				n /= increment;
				unit = units[i];
			}
			
			var mult = Math.pow(10,precision);
			
			n = Math.ceil(n*mult)/mult;
			
			return n+unit;
		}
	}
}

DFTools.cancelEvent = DFTools.cancelEvent.bindAsEventListener(DFTools);

DFTools.namespace("com.digitalfruition");

/* Digital Fruition Standard Exceptions_________________________________________ */

// Ensure we can subclass the Error object:

if(typeof Error.subclasses != "object")
	Error.subclasses = $A();

/**
 * Digital Fruition Base Exception Class
 *
 * @author Joshua Gitlin
 **/
com.digitalfruition.Exception = Class.create(Error,{
	initialize: function(message)
	{
		this.name = "DFException";
		this.message = message;
	}
});

/**
 * Create a new kind of exception
 *
 **/
com.digitalfruition.Exception.Create = function(name,parent,methods)
{
	if(!parent)
		parent = com.digitalfruition.Exception;

	if(!methods)
		methods = { };

	var default_initalize = function(message)
	{
		this.name = "DF"+name+"Exception";
		this.message = message;
	}

	if(methods.initialize)
		methods.initialize = methods.initialize.wrap(function($super,m){default_initalize(m); $super(m);});
	else
		methods.initialize = default_initalize;

	com.digitalfruition.Exception[name] = Class.create(parent,methods);
}



/* Digital Fruition Prototype Enhancements______________________________________ */

com.digitalfruition.Enumerable = {
	eachNotNull: function(itterator,context)
	{
	    return this.each(itterator.wrap(function(go,value,index){
	        return value !== null ? go(value,index) : null;
	    }),context);
	},

	eachNotUndefined: function(itterator,context)
	{
	    return this.each(itterator.wrap(function(go,value,index){
	        return value !== undefined ? go(value,index) : null;
	    }),context);
	},

	eachNotFalse: function(itterator,context)
	{
	    return this.each(itterator.wrap(function(go,value,index){
	        return value ? go(value,index) : null;
	    }),context);
	}
}

Object.extend(Enumerable, com.digitalfruition.Enumerable);
Object.extend(Array.prototype, com.digitalfruition.Enumerable);
Object.extend(Hash.prototype, com.digitalfruition.Enumerable);
Object.extend(ObjectRange.prototype, com.digitalfruition.Enumerable);
Object.extend(Ajax.Responders, Enumerable);




/**
 * Page Unload System
 *
 * The DFTools Page Unload System is a way to detect is the page is being
 * unloaded (due to the user closing the window/tab, clicking back/forward,
 * or taking any other action to navigate away from the page). 
 *
 * DFTools.pageUnload can be queried to find out if the page is being unloaded
 * (I.E. to determine if that is the cause of a failure for an AJAX request to
 * complete) but it can also be used to observe the unload event and cancel it
 * by prompting the user.
 *
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */
DFTools.pageUnload = new function()
{
	var isHappening = false;
	
	var okToUnload = true;
	
	var unloadWarningFunctions = $A();
	
	var unloadWarnings = $A();
	
	var resetHappeningFunction = function()
	{
		isHappening = false;
	}
	
	var resetHappening = new PeriodicalExecuter(resetHappeningFunction,1);
	
	var beforeunloadHandler = function()
	{
		isHappening = true;
		
		if(!this.okToUnload())
		{
			return unloadWarnings.join("\n\n");
		}
	}.bind(this);
	
	//Event.observe(window,'beforeunload',beforeunloadHandler);
	
	window.onbeforeunload = beforeunloadHandler;
	
	this.isHappening = function()
	{
		return isHappening;
	}
	
	this.okToUnload = function()
	{
		unloadWarnings = unloadWarningFunctions.collect(function(f){return f();}).without(false);
		
		okToUnload = !unloadWarnings.any();
		
		return okToUnload;
	}
	
	this.addUnloadWarning = function(f)
	{
		if(typeof(f) == 'function')
		{
			unloadWarningFunctions.push(f);
		}
	}
	
	this.removeUnloadWarning = function(f)
	{
		if(typeof(f) == 'function')
		{
			unloadWarningFunctions = unloadWarningFunctions.without(f);
		}
	}
}


/**
 * Digital Frution Admin UI Library
 *
 * This class needs documentation. Contact the developer for more information.
 *
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */
var DF_UI = 
{
	MoveWindow: function (win,x,y)
	{
		var pos = win.getPosition();
		win.setPosition(pos[0]+x,pos[1]+y);
	}
}


var writeEmail = DFTools.writeEmail;


/**
 * Quirksmode Browser detection Library
 *
 * Provides browser detection capabilities.
 *
 * @author Peter-Paul Koch
 * @copyright 2009 Quirksmode. I don't believe in copyrights for JavaScript or CSS solutions.
 * @license Open: http://www.quirksmode.org/about/copyright.html
 * @see http://www.quirksmode.org/js/detect.html
 */
var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();


/**
 * WebKit Version Detection Library
 *
 * provides version information about browsers using WebKit, including Apple's
 * Safari and Google's Chrome.
 *
 * @author Unknown
 * @copyright unknown
 * @license Open
 * @see http://trac.webkit.org/wiki/DetectingWebKit
 */
var WebKitDetect = {  };

// If the user agent is WebKit, returns true. Otherwise, returns false.
WebKitDetect.isWebKit = function isWebKit()
{
    return RegExp(" AppleWebKit/").test(navigator.userAgent);
}

// If the user agent is WebKit, returns an array of numbers matching the "." separated 
// fields in the WebKit version number, with an "isNightlyBuild" property specifying
// whether the user agent is a WebKit nightly build. Otherwise, returns null.
//
// Example: 418.10.1 => [ 418, 10, 1 ] isNightlyBuild: false
WebKitDetect.version = function version() 
{
    /* Some example strings: 
            Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3
            Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Safari/521.32
     */
     
    // grab (AppleWebKit/)(xxx.x.x)
    var webKitFields = RegExp("( AppleWebKit/)([^ ]+)").exec(navigator.userAgent);
    if (!webKitFields || webKitFields.length < 3)
        return null;
    var versionString = webKitFields[2];

    var isNightlyBuild = versionString.indexOf("+") != -1;

    // Remove '+' or any other stray characters
    var invalidCharacter = RegExp("[^\\.0-9]").exec(versionString);
    if (invalidCharacter)
        versionString = versionString.slice(0, invalidCharacter.index);
    
    var version = versionString.split(".");
    version.isNightlyBuild = isNightlyBuild;
    return version;
}

// If the user agent is a WebKit version greater than or equal to the version specified
// in the string minimumString, returns true. Returns false otherwise. minimumString 
// defaults to "".
//
// Example usage: WebKitDetect.versionIsAtLeast("418.10.1")
WebKitDetect.versionIsAtLeast = function versionIsAtLeast(minimumString)
{
    function toIntOrZero(s) 
    {
        var toInt = parseInt(s);
        return isNaN(toInt) ? 0 : toInt;
    }

    if (minimumString === undefined)
        minimumString = "";
    
    var minimum = minimumString.split(".");
    var version = WebKitDetect.version();

    if (!version)
        return false;
        
    if (version.isNightlyBuild)
        return true;

    for (var i = 0; i < minimum.length; i++) {
        var versionField = toIntOrZero(version[i]);
        var minimumField = toIntOrZero(minimum[i]);
        
        if (versionField > minimumField)
            return true;
        if (versionField < minimumField)
            return false;
    }

    return true;
}

WebKitDetect.isMobile = function isMobile()
{
    return WebKitDetect.isWebKit() && RegExp(" Mobile/").test(navigator.userAgent);
}

WebKitDetect.mobileDevice = function mobileDevice()
{
    if (!WebKitDetect.isMobile())
        return null;
        
    var fields = RegExp("(Mozilla/5.0 \\()([^;]+)").exec(navigator.userAgent);
    if (!fields || fields.length < 3)
        return null;
    return fields[2];
}

// Example: 1C28 => [ 1, C, 28 ]
WebKitDetect._mobileVersion = function _mobileVersion(versionString)
{
    var fields = RegExp("([0-9]+)([A-Z]+)([0-9]+)").exec(versionString);
    if (!fields || fields.length != 4)
        return null;
    return [ fields[1], fields[2], fields[3] ];
}

WebKitDetect.mobileVersion = function mobileVersion()
{
    // grab (Mobile/)(nxnnn)
    var fields = RegExp("( Mobile/)([^ ]+)").exec(navigator.userAgent);
    if (!fields || fields.length < 3)
        return null;
    var versionString = fields[2];
    
    return WebKitDetect._mobileVersion(versionString);
}

WebKitDetect.mobileVersionIsAtLeast = function mobileVersionIsAtLeast(minimumString)
{
    function toIntOrZero(s) 
    {
        var toInt = parseInt(s);
        return isNaN(toInt) ? 0 : toInt;
    }

    if (minimumString === undefined)
        minimumString = "";

    var minimum = WebKitDetect._mobileVersion(minimumString);
    var version = WebKitDetect.mobileVersion();

    if (!version)
        return false;
        
    var majorVersInt = toIntOrZero(version[0]);
    var majorMinInt = toIntOrZero(minimum[0]);
    if (majorVersInt > majorMinInt)
        return true;
    if (majorVersInt < majorMinInt)
        return false;
    
    var majorVersAlpha = version[1];
    var majorMinAlpha = minimum[1];
    if (majorVersAlpha > majorMinAlpha)
        return true;
    if (majorVersAlpha < majorMinAlpha)
        return false;
    
    var minorVersInt = toIntOrZero(version[2]);
    var minorMinInt = toIntOrZero(minimum[2]);
    if (minorVersInt > minorMinInt)
        return true;
    if (minorVersInt < minorMinInt)
        return false;
    
    return true;
}


/**
 * Digital Frution Stack Trace
 *
 * Returns a Javascript function call stack trace. Taken from the web and
 * adapted for our needs. License unknown.
 *
 * @return array
 * @author Unknown
 * @copyright Unknown
 * @license Unknown
 */
DFTools.getStackTrace = (function ()
{
	var mode;
	/*
	try {(0)()} catch (e) {
		mode = e.stack ? 'Firefox' : window.opera ? 'Opera' : 'Other';
	}
	*/
	
	mode = BrowserDetect.browser;
	
	switch (mode)
	{
		case 'Firefox' : return function ()
			{
				try {(0)()} catch (e)
				{
					return e.stack.replace(/^.*?\n/,'').replace(/(?:\n@:0)?\s+$/m,'').replace(/^\(/gm,'{anonymous}(').split("\n");
				}
			};
		
		case 'Opera' : return function () 
			{
				try {(0)()} catch (e)
				{
					var lines = e.message.split("\n"),ANON = '{anonymous}',lineRE = /Line\s+(\d+).*?in\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i,i,j,len;
					
					for (i=4,j=0,len=lines.length; i<len; i+=2)
					{
						if (lineRE.test(lines[i]))
						{
							lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i+1].replace(/^\s+/,'');
						}
					}
					
					lines.splice(j,lines.length-j);
					return lines;
				}
			};
		
		default : return function ()
			{
				var curr = arguments.callee.caller, FUNC = 'function', ANON = "{anonymous}", fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [],j=0,fn,args,i;
				
				while (curr)
				{
					fn    = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
					args  = stack.slice.call(curr.arguments);
					i     = args.length;
					
					while (i--)
					{
						switch (typeof args[i])
						{
							case 'string'  : args[i] = '"'+args[i].replace(new RegExp('"','g'),'\\"')+'"'; break;
							case 'function': args[i] = FUNC; break;
						}
					}
					
					stack[j++] = fn + '(' + args.join() + ')';
					curr = curr.caller;
				}
				
				return stack;
			};
	}
})();









/*
* FastInit: system for observing when the DOM is ready (but images have not yet loaded)
*
* @copyright Copyright (c) 2007 Andrew Tetlaw
*/
var FastInit = {
	onload : function() {
		if (FastInit.done) { return; }
		FastInit.done = true;
		for(var x = 0, al = FastInit.f.length; x < al; x++) {
			FastInit.f[x]();
		}
	},
	addOnLoad : function() {
		var a = arguments;
		for(var x = 0, al = a.length; x < al; x++) {
			if(typeof a[x] === 'function') {
				if (FastInit.done ) {
					a[x]();
				} else {
					FastInit.f.push(a[x]);
				}
			}
		}
	},
	listen : function() {
		if (/WebKit|khtml/i.test(navigator.userAgent)) {
			FastInit.timer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					clearInterval(FastInit.timer);
					delete FastInit.timer;
					FastInit.onload();
				}}, 10);
		} else if (document.addEventListener) {
			document.addEventListener('DOMContentLoaded', FastInit.onload, false);
		} else if(!FastInit.iew32) {
			if(window.addEventListener) {
				window.addEventListener('load', FastInit.onload, false);
			} else if (window.attachEvent) {
				return window.attachEvent('onload', FastInit.onload);
			}
		}
	},
	f:[],done:false,timer:null,iew32:false
};
/*@cc_on @*/
/*@if (@_win32)
FastInit.iew32 = true;
document.write('<script id="__ie_onload" defer src="' + ((location.protocol == 'https:') ? '//0' : 'javascript:void(0)') + '"><\/script>');
document.getElementById('__ie_onload').onreadystatechange = function(){if (this.readyState == 'complete') { FastInit.onload(); }};
/*@end @*/
FastInit.listen();





//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/string/pad [v1.0]

String.prototype.pad = function(l, s, t){
	return s || (s = " "), (l -= this.length) > 0 ? (s = new Array(Math.ceil(l / s.length)
		+ 1).join(s)).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2))
		+ this + s.substr(0, l - t) : this;
};





/* Date/Time Format v0.2; MIT-style license
By Steven Levithan <http://stevenlevithan.com> */

/**
 * Date/Time Format Library
 *
 * Excellent date/time formatting library by Steven Levithan
 *
 * @author Joshua Gitlin
 * @copyright 2009 Steven Levithan <http://stevenlevithan.com>
 * @see http://stevenlevithan.com
 * @version v0.2
 * @license MIT-style license.
 */
Date.prototype.format = function(mask) {
	var d = this; // Needed for the replace() closure
	
	// If preferred, zeroise() can be moved out of the format() method for performance and reuse purposes
	var zeroize = function (value, length) {
		if (!length) length = 2;
		value = String(value);
		for (var i = 0, zeros = ''; i < (length - value.length); i++) {
			zeros += '0';
		}
		return zeros + value;
	};
	
	return mask.replace(/"[^"]*"|'[^']*'|\b(?:d{1,4}|m{1,4}|yy(?:yy)?|([hHMs])\1?|TT|tt|[lL])\b/g, function($0) {
		switch($0) {
			case 'd':	return d.getDate();
			case 'dd':	return zeroize(d.getDate());
			case 'ddd':	return ['Sun','Mon','Tue','Wed','Thr','Fri','Sat'][d.getDay()];
			case 'dddd':	return ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'][d.getDay()];
			case 'm':	return d.getMonth() + 1;
			case 'mm':	return zeroize(d.getMonth() + 1);
			case 'mmm':	return ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][d.getMonth()];
			case 'mmmm':	return ['January','February','March','April','May','June','July','August','September','October','November','December'][d.getMonth()];
			case 'yy':	return String(d.getFullYear()).substr(2);
			case 'yyyy':	return d.getFullYear();
			case 'h':	return d.getHours() % 12 || 12;
			case 'hh':	return zeroize(d.getHours() % 12 || 12);
			case 'H':	return d.getHours();
			case 'HH':	return zeroize(d.getHours());
			case 'M':	return d.getMinutes();
			case 'MM':	return zeroize(d.getMinutes());
			case 's':	return d.getSeconds();
			case 'ss':	return zeroize(d.getSeconds());
			case 'l':	return zeroize(d.getMilliseconds(), 3);
			case 'L':	var m = d.getMilliseconds();
					if (m > 99) m = Math.round(m / 10);
					return zeroize(m);
			case 'tt':	return d.getHours() < 12 ? 'am' : 'pm';
			case 'TT':	return d.getHours() < 12 ? 'AM' : 'PM';
			// Return quoted strings with the surrounding quotes removed
			default:	return $0.substr(1, $0.length - 2);
		}
	});
};





/* Digital Fruition Standard Library____________________________________________ */

DFTools.namespace("com.digitalfruition.Std");


com.digitalfruition.Std.LoadedObserver = Class.create(Abstract.TimedObserver,
{	
	getValue: function()
	{
		var loaded = false;

		if(!this.runs)
		{
			// On the first run we assume the item hasn't loaded yet. This way,
			// if it really has already loaded, on our second run we'll notice
			// a change. Otherwise getValue() would always return true and the
			// load handler would never fire...
			this.runs=1;
		}
		else
		{
			// We've alreacy run at least once. Increment our run counter and
			// return the loaded value.
			++this.runs;

			if(typeof this.element.loaded == 'function')
				loaded = this.element.loaded();
			else if(typeof this.element.loaded != 'undefined')
				loaded = this.element.loaded
			if(typeof this.element.complete != 'undefined')
				loaded = this.element.complete
		}
		
		return loaded;
	}
});


com.digitalfruition.Std.HtmlTag = Class.create(
{
	initialize: function(tag,attrs,children)
	{
		attrs = $H(typeof(attrs) == 'object' ? attrs : {});
		
		if(typeof(children) == 'undefined') {
			children = $A([]);
		} else if(children instanceof Array) {
			children = $(children);
		} else {
			children = $A([children]);
		}
		
		this.tag = tag;
		this.attrs = attrs,
		this.childTags = children.collect(com.digitalfruition.Std.HtmlTag.createFromConfig);
	},
	
	toHTML: function()
	{
		var html = '<'+this.tag;
		
		if(this.attrs && Object.isHash(this.attrs))
		{
			html = this.attrs.inject(html,function(acc,pair){
				return acc + ' ' + pair[0] + '="' + pair[1].escapeHTML().gsub(/"/,'&quot;') + '"';
			});
		}
		
		html += '>';
		
		if(this.childTags.length) {
			html = html + this.childTags.join('') + '</'+this.tag + '>'
		}
		
		return html;
	},
	
	toElement: function()
	{
		var el = new Element(this.tag, this.attrs.toObject());
		
		this.childTags.each(Element.insert.curry(el));
		
		return el;
	}
});

com.digitalfruition.Std.HtmlTag.prototype.toString = com.digitalfruition.Std.HtmlTag.prototype.toHTML;

com.digitalfruition.Std.HtmlTag.createFromConfig = function(config)
{
	if(Object.isHash(config)) {
		config = config.toObject();
	}
	
	if(typeof(config) == 'string') {
		return config;
	} else if(typeof(config) == 'object') {
		return new com.digitalfruition.Std.HtmlTag(config.tag,config.attrs,config.children);
	} else {
		throw new TypeError("config must be a string or an object");
	}

	return null;
};


/**
 * Standard Event Listener Functions
 *
 * This library is meant to be added to a class via Class.addmethods and it
 * provides the class with standard event handling capabilities. Once added to a
 * class the class will then be able to listen for events, fire and respond to
 * events, and stop listening for events.
 */
com.digitalfruition.Std.EventListener = 
{
	/**
	 * Add an event listener
	 * 
	 * Adds handler as an event listener for the event eventName. When eventName is
	 * fired handler will be called in the context of this object with the event
	 * name and any other parameters passed to fire() as it's parameters.
	 * 
	 * This function is aliased as: observe()
	 * 
	 * To remove the event listener, call removeListener() and pass the same event
	 * name and handler.
	 * 
	 * @param {string} eventName
	 * @param {function} handler
	 */
	addListener: function(eventName,handler)
	{
		if(typeof(eventName) != 'string')
		{
			throw new TypeError("eventName must be a string");
		}
		
		if(typeof(handler) != 'function')
		{
			throw new TypeError("handler must be a function");
		}
		
		if(!this._myHandlers)
		{
			this._myHandlers = $H({});
		}
		
		if(!this._myHandlers.get(eventName))
		{
			this._myHandlers.set(eventName,$A([]))
		}
		
		this._myHandlers.get(eventName).push(handler);
		
		return this;
	},
	
	/**
	 * Remove an event listener
	 * 
	 * Removes handler as an event listener for the event eventName. Pass the same
	 * handler and eventName as were passed to addListener to remove that handler.
	 * 
	 * This function is aliased as: stopObserving()
	 * 
	 * @param {string} eventName
	 * @param {function} handler
	 */
	removeListener: function(eventName,handler)
	{
		if(typeof(eventName) != 'string')
		{
			throw new TypeError("eventName must be a string");
		}
		
		if(typeof(handler) != 'function')
		{
			throw new TypeError("handler must be a function");
		}
		
		if(!this._myHandlers)
		{
			this._myHandlers = $H({});
		}
		
		if(!this._myHandlers.get(eventName))
		{
			this._myHandlers.set(eventName,$A([]))
		}
		
		this._myHandlers.set(eventName,this._myHandlers.get(eventName).without(handler));
		
		return this;
	},
	
	/**
	 * Fire an event
	 * 
	 * Fire the event eventName, causing all handlers for that event to be called.
	 * Each handler will be called with this object as it's context and the handler
	 * will receive the same arguments passed to this function, so the first
	 * argument will be the event name, and any subsequent arguments passed to fire
	 * will also be passed to each handler.
	 * 
	 * @param {string} eventName
	 */
	fire: function(eventName)
	{
		if(typeof(eventName) != 'string')
		{
			throw new TypeError("eventName must be a string");
		}
		
		if(this._myHandlers && !this.suspendEvents)
		{
			var events = this._myHandlers.get(eventName);
			
			if(Object.isArray(events))
			{
				events.invoke('apply',this,arguments)
			}
		}
		
		return this;
	}
};
com.digitalfruition.Std.EventListener.observe = com.digitalfruition.Std.EventListener.addListener;
com.digitalfruition.Std.EventListener.stopObserving = com.digitalfruition.Std.EventListener.removeListener;




/* Framework Classes____________________________________________________________ */

DFTools.namespace("com.digitalfruition.Controls");

/**
 * Class for a dropdown list, a replaceent for a select tag
 *
 */
com.digitalfruition.Controls.DropdownList = Class.create(
{
	initialize: function(config)
	{
		this.mySetOptions($H(config));
		
		this.myElements = {};
		
		this.selectedIndex = -1;
		
		this.mySetup();
	},
	
	defaultOptions: function()
	{
		return $H({
			options: [],
			parent: document.body,
			displayInControl: 'label'
		});
	},
	
	addOption: function(value,label,title)
	{
		var option = this.myBuildOptionObjectAndAddToUL(value,label,title);
		
		this.options.push(option);
	},
	
	close: function()
	{
		this.myElements.list.hide();
		this.fire('close');
	},
	
	open: function()
	{
		this.myElements.list.show();
		this.fire('open');
	},
	
	indexOf: function(value,label,title)
	{
		var index = -1;
		
		var selected = this.myBuildOptionObject(value,label,title);
		
		this.options.each(function(o,i)
		{
			var opt = this.myBuildOptionObject(o);
			
			if(opt.value == selected.value && opt.label == selected.label && opt.title == selected.title)
				index = i;	
		},this);
		
		return index;
	},
	
	select: function(index)
	{
		if(typeof index != 'number')
			throw new TypeError("index not numeric");
		
		if(index == -1)
		{
			this.selectedIndex = index;
			
			this.myElements.value.update('');
			
			this.fire('change',null);
		}
		else if (index >=0 && index < this.options.length)
		{
			this.selectedIndex = index;
			
			var html = '';
			
			if((this.displayInControl == 'title' || this.displayInControl == 'value') && !Object.isUndefined(this.options[index][this.displayInControl]))
				html = this.options[index][this.displayInControl];
			else
				html = this.myElements.list.childElements()[index].innerHTML;
			
			this.myElements.value.update(html);
			
			this.fire('change',this.options[index]);
		}
		else
		{
			throw new RangeError("index out of bounds");
		}
	},
	
	rebuildMenu: function()
	{
		this.myElements.list.childElements().invoke('remove');
		
		var addToList = this.myBuildOptionObjectAndAddToUL.wrap(function(f,a){return f(a);}).bind(this);
		
		this.options.each(addToList);
	},
	
	mySetOptions: function(options)
	{
		var defaults = this.defaultOptions();
		
		$H(defaults).each(function(v){
			this[v.key] = (typeof(options.get(v.key)) == 'undefined') ? v.value : options.get(v.key);
		},this);
	},
	
	myBuildOptionObjectAndAddToUL: function(value,label,title)
	{
		var option = this.myBuildOptionObject(value,label,title);
		
		this.myAddOptionObjectToUL(option);
		
		return option;
	},
	
	myAddOptionObjectToUL: function(option)
	{
		var li = new Element('li',{title:option.title+''});
		li.insert(option.label);
		
		this.myElements.list.appendChild(li);
	},
	
	myBuildOptionObject: function(value,label,title)
	{
		var option;
		
		if(!title)
			title = '';
		
		if(!label)
		{
			if(!Object.isUndefined(value.value) && !Object.isUndefined(value.label))
			{
				// We were passed an object
				
				label = value.label;
				if(value.title) title = value.title;
				value = value.value;
			}
			else if(value instanceof Array && value[1])
			{
				label = value[1];
				if(value[1]) title = value[1];
				value = value[0];
			}
			else
			{
				label = value;
			}
		}
		
		option = {
			value: value,
			label: label,
			title: title
		};
		
		return option;
	},
	
	mySetup: function()
	{
		var container = $(this.parent)
		
		if(!container)
			throw new TypeError("com.digitalfruition.Controls.DropdownList: parent not set to a valid element/id");
		
		this.myElements.container = new Element('div').addClassName('dropdownList SitePalette_Text');
		
		this.myElements.container.appendChild(new Element('div').addClassName('arrows'));
		
		this.myElements.hitbox = new Element('div').addClassName('hit');
		this.myElements.value = new Element('div').addClassName('value');
		
		this.myElements.list = new Element('ul').addClassName('list');
		
		this.myElements.container.appendChild(this.myElements.value);
		this.myElements.container.appendChild(this.myElements.hitbox);
		this.myElements.container.appendChild(this.myElements.list);
		
		this.options = $A(this.options);
		
		this.rebuildMenu();
		
		this.myElements.list.hide();
		
		container.appendChild(this.myElements.container);
		
		var hitboxMouseup = function(e)
		{
			DFTools.console.debug("com.digitalfruition.Controls.DropdownList: mouseUp inside hitbox -- setting myIgnoreBodyMouseup to true");
			this.myIgnoreBodyMouseup = true;
		}.bindAsEventListener(this);
		
		var bodyMouseup = function(e)
		{
			if(!this.myIgnoreBodyMouseup)
				this.close();
			this.myIgnoreBodyMouseup = false;
		}.bindAsEventListener(this);

		this.myElements.hitbox.observe('mousedown',this.myHitboxClickHandler.bindAsEventListener(this));
		this.myElements.hitbox.observe('mouseup',hitboxMouseup);

		if(BrowserDetect.browser == "Explorer")
		{
			this.myElements.value.observe('mousedown',this.myHitboxClickHandler.bindAsEventListener(this));
			this.myElements.value.observe('mouseup',hitboxMouseup);
		}

		this.myElements.list.observe('mousedown',this.myListClickHandler.bindAsEventListener(this));
		this.myElements.list.observe('mouseup',this.myListClickHandler.bindAsEventListener(this));
		Event.observe(document.body,'mouseup',bodyMouseup);
	},
	
	myHitboxClickHandler: function(e)
	{
		this.fire('click');
		this.myElements.list.toggle();
		this.fire(this.myElements.list.visible() ? 'open' : 'close');
		e.stop();
	},
	
	myListClickHandler: function(e)
	{
		DFTools.console.debug("com.digitalfruition.Controls.DropdownList#mylistClickHandler called for event",e.type,e);
		
		try
		{
			var el = e.element();
			var tag = el.tagName.toLowerCase();
			
			var item = tag == 'li' ? el : el.up('li');
			
			if(item)
			{
				var index = this.myElements.list.childElements().indexOf(item);
				
				this.fire('choose',index);
				
				this.select(index);

				this.close();
			}
		}
		catch(ex)
		{
			DFTools.console.logException(ex,"com.digitalfruition.Controls.DropdownList#myListClickHandler");
			DFTools.console.log("DropdownList object:",this);
		}
		
		e.stop();
	}
});
com.digitalfruition.Controls.DropdownList.addMethods(com.digitalfruition.Std.EventListener);

/* Framework Classes____________________________________________________________ */

DFTools.namespace("com.digitalfruition.Framework");

/**
 * Base class representing "media": Images, PDFs, Flash, really anything that 
 * can be displayed.
 *
 */
com.digitalfruition.Framework.Media = Class.create(
{
	initialize: function(config)
	{
		$H(config).each(function(pair)
		{
			this[pair.key] = pair.value;
		},this);
		
		this.myHtmlTag = false;
		this.myElement = false;
	},
	
	refresh: function()
	{
		this.myBuildHtmlTag();
		
		if(this.myElement) {
			this.myElement = this.myHtmlTag.toElement();
		}
	},
	
	toElement: function()
	{
		if(!this.myHtmlTag) {
			this.myBuildHtmlTag();
		}
		
		var el = this.myHtmlTag.toElement();
		
		return el;
	},
	
	toHTML: function()
	{
		if(!this.myHtmlTag) {
			this.myBuildHtmlTag();
		}
		
		return this.myHtmlTag.toString();
	},
	
	element: function()
	{
		if(!this.myElement) {
			this.myElement = this.toElement();
		}
		
		return this.myElement;
	},
	
	myBuildHtmlTag: function()
	{
		var attrs = $H({
			href: this.src
		});
		
		if(this.className) {
			attrs.set('class',this.className);
		}
		
		if(this.title) {
			attrs.set('title',this.title);
		}
			
		
		this.myHtmlTag = new com.digitalfruition.Std.HtmlTag('a', attrs, [this.title]);
	}
});

com.digitalfruition.Framework.Media.prototype.toString = com.digitalfruition.Framework.Media.prototype.toHTML;


/**
 * Class representing an image (which means it has a width and height and
 * can be scaled)
 *
 */
com.digitalfruition.Framework.Media.Image = Class.create(com.digitalfruition.Framework.Media,
{
	initialize: function($super,options)
	{
		this.defaultWidth  = this.defaultHeight = 0;
		
		$super(options);
	},
	
	srcForSize: function(width,height)
	{
		var src = this.src;
		
		if(typeof(width) == 'undefined')
		{
			width = this.defaultWidth;
			height = this.defaultHeight;
		}
		
		return src.replace(/([0-9]+)x([0-9]+)/,parseInt(width)+'x'+parseInt(height));
	},
	
	myBuildHtmlTag: function()
	{
		var tagname = 'img', children = $A();
		
		var attrs = $H({
			src: this.srcForSize()
		});
		
		if(this.className) {
			attrs.set('class',this.className);
		}
		
		if(this.title) {
			attrs.set('title',this.title);
		}
		
		if(this.link)
		{
			children.push({
				tag: tagname,
				attrs:attrs.toObject()
			});
			
			attrs = $H({
				href: this.link
			});
			
			tagname = 'a';
		}
		
		this.myHtmlTag = new com.digitalfruition.Std.HtmlTag(tagname, attrs, children);
	}
});



/* Media Classes________________________________________________________________ */


if(com.digitalfruition.Framework && com.digitalfruition.Framework.Media)
{
	// For Framework sites, the media objects are all Framework media objects.
	// For non framework sites com.digitalfruition.Framework would be defined and
	// the media ojects would still work just without their ability to auto scale
	// and other magical features.
	
	com.digitalfruition.Media = com.digitalfruition.Framework.Media;
}
else
{
	/**
	 * Base class representing "media": Images, PDFs, Flash, really anything that 
	 * can be displayed.
	 *
	 */
	com.digitalfruition.Media = Class.create(
	{
		initialize: function(config)
		{
			$H(config).each(function(pair)
			{
				this[pair.key] = pair.value;
			},this);
		}	
	});
	
	
	/**
	 * Class representing an image (which means it has a width and height and
	 * can be scaled)
	 *
	 */
	com.digitalfruition.Media.Image = Class.create(com.digitalfruition.Media,
	{
	
	});
}

/**
 * Class representing a stream of Media objects. Extend this class and provie
 * means to load the stream.
 *
 */
com.digitalfruition.Media.MediaStream = Class.create();

com.digitalfruition.Media.MediaStream.addMethods(Enumerable);
com.digitalfruition.Media.MediaStream.addMethods(com.digitalfruition.Std.EventListener);
com.digitalfruition.Media.MediaStream.addMethods(
{
	initialize: function(config)
	{
		this.media = $A();
		
		this.myPointer = 0;
		
		this.fire('initialize');
	},
	
	getAt: function(index)
	{
		if(index < 0 || index >= this.media.length)
		{
			throw new RangeError("Index out of bounds");
		}
		
		return this.media[index];
	},
	
	prev: function()
	{
		if(this.myPointer <= 0)
		{
			throw new RangeError("End of Stream Reached");
		}
		
		return this.media[this.myPointer--];
	},
	
	next: function()
	{
		if(this.myPointer >= this.media.length)
		{
			this.myPointer = 0;
			throw new RangeError("End of Stream Reached");
		}
		
		return this.media[this.myPointer++];
	},
	
	add: function(media)
	{
		if(media instanceof com.digitalfruition.Media)
		{
			this.media.push(media);
			
			this.fire('add',media);
		}
	},
	
	length: function()
	{
		return this.media.length;
	},
	
	_each: function(iterator)
	{
		return this.media._each(iterator);
	}
});
	

/**
 * Media Stream served by RSS
 *
 */	
com.digitalfruition.Media.MediaStream.RSSMediaStream = Class.create(com.digitalfruition.Media.MediaStream,
{
	initialize: function(config)
	{
		this.url = config.url;
		
		this.method = config.method ? config.method : 'get';
		
		this.parameters = config.parameters ? config.parameters : '';
		
		
		this.myItemTagNames = ['item'];
		
		if(config.itemTags)
		{
			if(config.itemTags instanceof Array)
			{
				this.myItemTagNames = config.itemTags;
			}
			else if(typeof(config.itemTags) == 'string')
			{
				this.myItemTagNames = [config.itemTags];
			}
			else
			{
				this.myItemTagNames = config.itemTags;
			}
		}
		
		this.myItemTagNames = $A(this.myItemTagNames);
		
		this.myNamespacePrefix = config.namespace ? config.namespace+':' : '';
		
		this.myLoaded = false;
		
		this.media = $A();
		
		this.myPointer = 0;
		
		this.fire('initialize',config);
		
		if(config.autoLoad) { this.load(); }
	},
	
	load: function()
	{
		DFTools.console.debug("RSSMediaStream: load() called, requesting AJAX content",this);
		
		this.fire('beforeLoad');
		
		new Ajax.Request(this.url,{
			method: this.method,
			parameters: this.parameters,
			onSuccess: this.myHandleLoadAjaxSuccess.bind(this),
			onFailure: this.myHandleLoadAjaxFailure.bind(this)
		})
	},
	
	loaded: function()
	{
		return this.myLoaded;
	},
	
	myHandleLoadAjaxSuccess: function(transport)
	{
		try
		{
			DFTools.console.debug("RSSMediaStream: AJAX call successful, extracting media from RSS",this);
			
			this.media = this.myExtractMediaFromRSS(transport.responseXML);
			
			this.myLoaded = true;
			
			this.fire('load',this.media);
		}
		catch(e)
		{
			DFTools.console.logException(e,"RSSMediaStream#myHandleLoadAjaxSuccess");
			DFTools.console.log("RSSMediaStream object: ",this);
			
			this.myHandleLoadAjaxFailure(transport,e);
		}
	},
	
	myHandleLoadAjaxFailure: function(transport,err)
	{
		DFTools.console.error("RSSMediaStream: Ajax Loading Failure. Transport: ",transport,", this:",this);
		
		this.myLoaded = false;
		this.myLoadError = "Ajax error";
		
		this.media = $A();
		
		var report = {
			'url': this.url,
			'method': this.method,
			'parameters': this.parameters,
			myItemTagNames: this.myItemTagNames,
			myNamespacePrefix: this.myNamespacePrefix,
			myLoaded: this.myLoaded = false,
			media: this.media,
			myPointer: this.myPointer
		};
		
		if(err.error_function) { report.errorFunction = e.error_function; }
		if(err.error_handler) { report.errorHandler = e.error_handler; }
		
		DFTools.reportError(err,com.digitalfruition.Media.MediaStream.RSSMediaStream,report);
		
		this.fire('loadError');
	},
	
	myExtractMediaFromRSS: function(rss)
	{
		DFTools.console.debug("RSSMediaStream: myExtractMediaFromRSS called on ",this,"with",rss);
		
		var media = $A();
		
		var items = this.myGetRSSItems(rss);
		
		if(items.length)
		{
			DFTools.console.debug("RSSMediaStream: items.length=",items.length);
			
			items.each(function(item)
			{
				var item_images = item.getElementsByTagName(this.myNamespacePrefix+"image");
				if(item_images)
				{
					DFTools.console.debug("RSSMediaStream: found ",item_images.length," image tags! Extracting URLs...");
					media.push($A(item_images).collect(this.myCollectItemImages,this));
				}
			},this);
		}
		
		media = media.flatten();
		
		DFTools.console.debug("RSSMediaStream: myExtractMediaFromRSS found the following media: ",media);
		
		return media;
	},
	
	myGetRSSItems: function(rss)
	{
		DFTools.console.debug("RSSMediaStream: myGetRSSItems called on ",this,"with object",rss," of type",typeof(rss));
		
		items = $A();
		
		if(true || rss && typeof(rss.getElementsByTagName) == 'function')
		{
			this.myItemTagNames.each(function(tagName)
			{
				items.push.apply(items,$A(rss.getElementsByTagName(this.myNamespacePrefix+tagName)));
			},this);
		}
		else
		{
			try
			{
				DFTools.console.debug("RSSMediaStream: myGetRSSItems typeof(rss.getElementsByTagName) != 'function'!");
				DFTools.console.debug("RSSMediaStream: typeof(rss)=",typeof(rss));
				DFTools.console.debug("RSSMediaStream: typeof(rss.getElementsByTagName)=",typeof(rss.getElementsByTagName));
				DFTools.console.debug("RSSMediaStream: Object.inspect(rss)=",Object.inspect(rss));
			}
			catch(e) {}
			
			DFTools.console.error("RSSMediaStream: myGetRSSItems: RSS object doesn't support getElementsByTagName; myGetRSSItems cannot continue.");
		}
		
		
		return items;
	},
	
	myCollectItemImages: function(image)
	{
		var media_object = {src: '', title: '', link: ''};
		
		try { media_object.src = image.getElementsByTagName(this.myNamespacePrefix+"url")[0].firstChild.nodeValue; } catch(e) {}
		try { media_object.link = image.getElementsByTagName(this.myNamespacePrefix+"link")[0].firstChild.nodeValue; } catch(e) {}
		try { media_object.title = image.getElementsByTagName(this.myNamespacePrefix+"title")[0].firstChild.nodeValue; } catch(e) {}
		
		DFTools.console.debug("RSSMediaStream: myCollectItemImages creating a new image from data: ",media_object);
		
		return new com.digitalfruition.Framework.Media.Image(media_object);
	}
});


















//Element.prototype.setWidth = function(w)
//{
//	this.style.width = w;
//}

/* JScrollingSlides: Josh's Scrolling Slideshow Class */

JScrollingSlides = Class.create();
var JScrollingSlide_count = 0;
JScrollingSlides.prototype = 
{
	initialize: function(containerDiv,options)
	{
		this.containerDiv = $(containerDiv);
		
		this.debug = options['debug']?true:false;
		
		this.id = JScrollingSlide_count++;
		this.idPre = "jscrollslides_"+this.id;
		
		var thisObj = this;
		var func = function(e) { thisObj._initializeAfterLoaded(options); };
		Event.observe(window, 'load', func);
	},
	
	_failToLoad: function(message)
	{
		if(typeof(message) == "undefined" || !message || !message.length)
			message = "The slideshow could not be loaded.";
		
		this.containerDiv.update(message);
		this.running = false;
	},
	
	_initializeAfterLoaded: function(options)
	{
		// First check to see if we meet the requirements
		if(options['require_imageComplete'])
		{
			// if we need the complete property of images, WebKit < 523 doesn't have it.
			if(WebKitDetect.isWebKit() && !WebKitDetect.versionIsAtLeast("423"))
			{
				this._failToLoad("This slideshow requires Safari 3 or Firefox.");
				return false;
			}
		}
		
		// We need to delay loading until all of the images in our source list have loaded.
		this.slides = new Array();
		var sourceList = $(options['sourceList']);
		if(sourceList)
		{
			if(!DFTools.imagesLoaded(sourceList))
			{
				// If the images aren't loaded yet, set this function to run 0.5 seconds later, and return false.
				window.setTimeout(function(){this._initializeAfterLoaded(options);}.bind(this),500);
				return false;
			}
			this._loadSlidesFromList(sourceList);
		}
		
		var dimensions = Element.getDimensions(this.containerDiv);
		this.viewerWidth = dimensions.width;
		this.viewerHeight = dimensions.height;
		
		if(this.debug)
		{
			alert("Viewer Window: "+this.containerDiv+"\n"
				 +"   Width: "+dimensions.width+"\n"
				 +"   Height: "+dimensions.height+"\n"
				 +"   CSS Width: "+this.containerDiv.style.width+"\n"
				 +"   CSS Height: "+this.containerDiv.style.height+"\n"
				 +"   offsetWidth: "+this.containerDiv.offsetWidth+"\n"
				 +"   offsetHeight: "+this.containerDiv.offsetHeight+"\n"
				 +"   clientWidth: "+this.containerDiv.clientWidth+"\n"
				 +"   clientHeight: "+this.containerDiv.clientHeight+"\n"
				  );
		}
		
		this.containerDiv.style.position = "relative";
		this.containerDiv.style.overflow = "hidden";
		
		
		this.debug = options['debug']?true:false;
		this.slideSpacing = options['slideSpacing']?options['slideSpacing']:5;
		this.delay = options['delay']?options['delay']:250;
		this.defaultSpeed = this.speed = options['speed']?options['speed']:10;
		this.maxSpeed = options['maxSpeed']?options['maxSpeed']:2*this.speed;
		
		if(typeof(options['allowScroll']) == "undefined" || options['allowScroll'])
		{
			this._observeMouseEvents();
		}
		
		this._setup();
		
		this.start();
	},
	
	_setup: function()
	{
		this.containerDiv.innerHTML='';
		this.sliders = new Array();
		this.sliderWidths = new Array();
		this.sliderPositions = new Array();
		var divName,divCode;
		
		for(var i=0;i<2;i++)
		{
			divName = this.idPre+"_scroll_"+i;
			divCode = "<div id=\""+divName+"\" style=\"position:absolute;visibility:hidden;\"></div>";
			new Insertion.Top(this.containerDiv,divCode);
			$(divName).style.left = "0px";
			$(divName).style.top = "0px";
			if(this.debug)
			{
				$(divName).style.border = "1px solid #333";
				$(divName).style.visibility = "visible";
			}
			this.sliders[i] = $(divName);
			this.sliderWidths[i] = 0;
			this.sliderPositions[i] = 0;
		}
		
		this._fillSliderDivs();
	},
	
	_fillSliderDivs: function()
	{
		var divCode,divStyle,divId;
		var left = 0, totalWidth = 0,slideListCount = 0;
		var finalFirstItem = -1, slideOneWidth = 0;
		var slider = this.sliders[0];
		
		while(totalWidth < 2*this.viewerWidth)
		{
			if(slideListCount > 100)
			{
				// Something is wrong. Bail out!
				var minw = 2*this.viewerWidth;
				var realw = this.masterListDivWidth;
				alert("Slideshow creation failed. We were trying to make the master list div at least "+minw+
						"px wide, it is only "+realw+"px wide, and we're bailing out after 100 tries.");
				return false;
			}
			
			// Add every slide to the master list DIV.
			for(var i=0;i<this.slides.length;i++)
			{
				divId = this.idPre+"_slide_"+i;
				divStyle = "position:absolute;top:0px;left:"+left+"px;white-space:nowrap;";
				divCode = "<div id=\""+divId+"\" style=\""+divStyle+"\">"+this.slides[i]+"</div>";
				new Insertion.Top(slider,divCode);
				var dimensions = Element.getDimensions($(divId));
				left += dimensions.width + this.slideSpacing; 
				totalWidth += dimensions.width + this.slideSpacing;
				
				if(left >= this.viewerWidth && slider == this.sliders[0])	// this item has pushed us past the middle!
				{
					finalFirstItem = i + slideListCount*this.slides.length;
					this.sliderWidths[0] = left;
					left = 0;
					slider = this.sliders[1];
				}
			}
			slideListCount++;
		}
		this.sliderWidths[1] = left;
		
		this.sliderPositions[0] = this.viewerWidth;
		this.sliderPositions[0] = 0;
		this.sliderPositions[1] = this.sliderPositions[0] + this.sliderWidths[0];
		
		if(this.debug)
		{
			alert("Viewer width: " + this.viewerWidth + "\n" +
				  "Slider 1 width: " + this.sliderWidths[0] + "\n" +
				  "Slider 1 position: " + this.sliderPositions[0] + "\n" +
				  "Slider 2 width: " + this.sliderWidths[1] + "\n" +
				  "Slider 2 Position: " + this.sliderPositions[1] + "\n" +
				  "Slide List Count: " + slideListCount + "\n");
		}
		
		this._placeSliders();
		
		for(var i=0;i<2;i++)
		{
			this.sliders[i].style.width=this.sliderWidths[i]+"px";
			this.sliders[i].style.height=(this.viewerHeight-5)+"px";
			this.sliders[i].style.visibility="visible";
		}
	},
	
	_loadSlidesFromList: function(list)
	{
		var listItems = list.getElementsByTagName("LI");
		var item;
		
		for(var i = 0; i < listItems.length; i++)
		{
			item = listItems.item(i);
			if(item.nodeName == "LI")
			{
				this.slides.push(item.innerHTML);
			}
		}
	},
	
	_placeSliders: function()
	{
		this.sliders[0].style.left = this.sliderPositions[0] + 'px';
		this.sliders[1].style.left = this.sliderPositions[1] + 'px';
	},
	
	_normalizeSliders: function()
	{
		if(this.speed >= 0 && this.sliderPositions[0] <= -1*this.sliderWidths[0])
			this.sliderPositions[0] = this.sliderPositions[1] + this.sliderWidths[1];
		if(this.speed >= 0 && this.sliderPositions[1] <= -1*this.sliderWidths[1])
			this.sliderPositions[1] = this.sliderPositions[0] + this.sliderWidths[0];
		if(this.running && this.speed < 0 && this.sliderPositions[0] > this.viewerWidth && this.sliderPositions[1] > this.viewerWidth)
		{
			this.sliderPositions[1] = -1* this.sliderWidths[1] + 1;
			this.sliderPositions[0] = this.sliderPositions[1] - this.sliderWidths[0];
		}
		if(this.running && this.speed < 0 && this.sliderPositions[0] > this.viewerWidth)
			this.sliderPositions[0] = this.sliderPositions[1] - this.sliderWidths[0];
		if(this.running && this.speed < 0 && this.sliderPositions[1] > this.viewerWidth)
			this.sliderPositions[1] = this.sliderPositions[0] - this.sliderWidths[1];
	},
	
	_queueNext: function()
	{
		var thisObj = this;
		var func = function() { thisObj.run(); };
		window.setTimeout(func,this.delay);
	},
	
	_observeMouseEvents: function()
	{
		var thisObj = this;
		var func = function(e) { thisObj.mouseOver(e); };
		Event.observe(this.containerDiv, 'mouseover', func);
		var func = function(e) { thisObj.mouseOut(e); };
		Event.observe(this.containerDiv, 'mouseout', func);
		var func = function(e) { thisObj.mouseMove(e); };
		Event.observe(this.containerDiv, 'mousemove', func);
		this._updateContainerPosition();
	},
	
	_updateContainerPosition: function()
	{
		Position.prepare();
		this.containerPosition = Position.cumulativeOffset(this.containerDiv);
	},
	
	mouseOver: function(e)
	{
		this._updateContainerPosition();
		this.mouseMove(e);
		
		/*var thisObj = this;
		var mouseOver = 
			function(e)
			{
				thisObj.mouseMove(e);
			};
			
		Event.observe(this.containerDiv, 'mousemove', mouseOver);*/
		
		
		//var func = function(e) { Event.stopObserving(this.containerDiv, 'mousemove', mouseOver); Event.stopObserving(this.containerDiv, 'mouseout', func); };
		//Event.observe(this.containerDiv, 'mouseout', func);
	},
	
	mouseOut: function(e)
	{
		this.speed = this.defaultSpeed;
	},
	
	mouseMove: function(e)
	{
		var perc = ((e.clientX-this.containerPosition[0]-this.viewerWidth/2)/(.5*this.viewerWidth))*100;
		//this.speed = (perc/40)^3;
		this.speed = perc*this.maxSpeed/100;
		if(Math.abs(perc) < 10)
			this.speed = 0;
		//window.status = "Mouse Move: clientX="+e.clientX+", perc="+perc+"%";
	},
	
	run: function()
	{
		if(this.running)
		{
			this.slide(this.speed);
			this._queueNext();
		}
		if (false && this.runs < 10)
		{
			alert("run called on "+this+", runs = "+this.runs+", running = "+this.running+"\n"+
				  "Slider 1 width: " + this.sliderWidths[0] + "\n" +
				  "Slider 1 position: " + this.sliderPositions[0] + "\n" +
				  "Slider 2 width: " + this.sliderWidths[1] + "\n" +
				  "Slider 2 Position: " + this.sliderPositions[1] + "\n");
			this.runs++;
		}
		//else
		//{
		//	alert("this.runs = "+this);
		//}
	},
	
	start: function()
	{
		this.running = true;
		this.runs = 0;
		this._queueNext();
	},
	
	slide: function(amt)
	{
		this.sliderPositions[0] -= amt;
		this.sliderPositions[1] -= amt;
		this._normalizeSliders();
		this._placeSliders();
	}
}

JPopinDiv = Class.create();
var JPopinDiv_count = 0;
JPopinDiv.prototype = 
{
	initialize: function(xPos,yPos,initialText,options)
	{
		this.id = JPopinDiv_count++;
		
		this.div_id= 'jpopin_'+this.id+'_div';
		this.topDiv_id= 'jpopin_'+this.id+'_top_div';
		this.midDiv_id= 'jpopin_'+this.id+'_mid_div';
		this.midLeftDiv_id= 'jpopin_'+this.id+'_midLeft_div';
		this.tabDiv_id= 'jpopin_'+this.id+'_tab_div';
		
		this.contentDiv_id= 'jpopin_'+this.id+'_left_div';
		
		this.xPos = xPos;
		this.yPos = yPos;
		
		this.zIndex = options['zIndex'] ? options['zIndex'] : 200;
		
		this.width = options['width'] ? options['width'] : 150;
		this.height = options['height'] ? options['height'] : 150;
		
		this.parentElement = options['parentElement'] ? $(options['parentElement']) : document.body;
		if(!this.parentElement) this.parentElement = document.body;
		
		this.tab = (options['tab']) ? options['tab'] : true;
		this.tabY = parseInt(options['tabY'] ? options['tabY'] : this.height/6);
		
		this.ltab = (options['ltab']) ? options['ltab'] : true;
		this.ltabY = parseInt(options['ltabY'] ? options['ltabY'] : -9999);
		
		var divCode;
		
		new Insertion.Bottom(this.parentElement,'<div id="'+this.div_id+'" style="position:absolute;left:'+this.xPos+'px;top:'+this.yPos+'px;display:none;z-index:'+this.zIndex+';"></div>');
		
		/* Top Divs */
  		new Insertion.Bottom(this.div_id,'<div class="jpopin_tl"></div>');
  		new Insertion.Bottom(this.div_id,'<div id="'+this.topDiv_id+'" class="jpopin_leftFlow" style="top: 0px;"></div>');
  		new Insertion.Bottom(this.topDiv_id,'<div class="jpopin_top" style="width:'+this.width+'px;"></div>');
  		new Insertion.Bottom(this.topDiv_id,'<div class="jpopin_tr" style="left:'+this.width+'px;"></div>');
  		
  		/* Mid Divs */
  		new Insertion.Bottom(this.div_id,'<div class="jpopin_left" style="height:'+this.height+'px;"></div>');
  		new Insertion.Bottom(this.div_id,'<div id="'+this.midDiv_id+'" class="jpopin_midFlow"></div>');
  		new Insertion.Bottom(this.midDiv_id,'<div id="'+this.midLeftDiv_id+'" class="jpopin_leftFlow"></div>');
  		
  		divCode  = '<div id='+this.contentDiv_id+' class="jpopin_content" style="width:'+this.width+'px;height:'+this.height+'px;';
  		if(options['contentDivStyle']) divCode += options['contentDivStyle'];
  		divCode += '"></div>';
  		new Insertion.Bottom(this.midLeftDiv_id,divCode);
  		new Insertion.Bottom(this.contentDiv_id,initialText);
  		
  		new Insertion.Bottom(this.midLeftDiv_id,'<div class="jpopin_right" style="left:'+this.width+'px;height:'+this.height+'px;"></div>');
  		
  		/* Bottom Divs */
  		new Insertion.Bottom(this.midDiv_id,'<div class="jpopin_bl" style="top:'+this.height+'px;"></div>');
  		new Insertion.Bottom(this.midLeftDiv_id,'<div class="jpopin_bottom" style="top:'+this.height+'px;width:'+this.width+'px;"></div>');
  		new Insertion.Bottom(this.midLeftDiv_id,'<div class="jpopin_br" style="top:'+this.height+'px;left:'+this.width+'px;"></div>');
  		
  		if(this.tab)
  		{
	  		new Insertion.Bottom(this.midLeftDiv_id,'<div id="'+this.tabDiv_id+'" class="jpopin_tab" style="left:'+this.width+'px;top:'+this.tabY+'px;"></div>');
  		}
  		if(this.ltab)
  		{
	  		new Insertion.Bottom(this.midLeftDiv_id,'<div id="'+this.tabDiv_id+'" class="jpopin_ltab" style="top:'+this.ltabY+'px;"></div>');
  		}
	},
	
	show: function()
	{
		Element.show(this.div_id);
	},
	
	hide: function()
	{
		Element.hide(this.div_id);
	}
}



/* JRandomImage: Josh's Random Image Class */

JRandomContent = Class.create( 
{
	initialize: function(containerDiv,options)
	{
		this.containerDiv = $(containerDiv);
		
		this.id = JRandomContent.count++;
		this.idPre = "jrandomcontent"+this.id;
		
		var dimensions = Element.getDimensions(this.containerDiv);
		this.viewerWidth = dimensions.width;
		this.viewerHeight = dimensions.height;
		
		var list = $(options['sourceList']);
		
		if(list)
		{
			var listItems = list.select("li");
			
			if(listItems.length)
			{
				var index = Math.min(Math.floor(Math.random()*(listItems.length + 1)),listItems.length-1);
				var item = listItems[index];
				this.containerDiv.appendChild(item.down())
			}
		}
	}
});
JRandomContent.count = 0;


/* JSlideShow: Josh's Slideshow Class */

JSlideShow = Class.create();
var JSlideShow_count = 0;
JSlideShow.prototype = 
{
	initialize: function(containerDiv,options)
	{
		this.containerDiv = $(containerDiv);
		
		this.debug = options['debug']?true:false;
		this.autoplay = (typeof options['autoplay'] != 'undefined')?options['autoplay']:false;
		
		this._setupComplete = false;
		
		this.slideSlots = new Array();
		this.slideCSlots = new Array();
		
		var thisObj = this;
		var func = function(e) { thisObj._initializeAfterLoaded(options); };
		Event.observe(window, 'load', func);
	},
	
	_initializeAfterLoaded: function(options)
	{
		this.id = JScrollingSlide_count++;
		this.idPre = "jslideshow_"+this.id;
		
		this.effectQueueName = this.idPre+"queue";
		
		var dimensions = Element.getDimensions(this.containerDiv);
		this.viewerWidth = dimensions.width;
		this.viewerHeight = dimensions.height;
		
		if(this.debug)
		{
			alert("Viewer Window: "+this.containerDiv+"\n"
				 +"   Width: "+dimensions.width+"\n"
				 +"   Height: "+dimensions.height+"\n"
				 +"   CSS Width: "+this.containerDiv.style.width+"\n"
				 +"   CSS Height: "+this.containerDiv.style.height+"\n"
				 +"   offsetWidth: "+this.containerDiv.offsetWidth+"\n"
				 +"   offsetHeight: "+this.containerDiv.offsetHeight+"\n"
				 +"   clientWidth: "+this.containerDiv.clientWidth+"\n"
				 +"   clientHeight: "+this.containerDiv.clientHeight+"\n"
				  );
		}
		
		this.containerDiv.style.position = "relative";
		this.containerDiv.style.overflow = "hidden";
		
		this.slides = new Array();
		if($(options['sourceList']))
			this._loadSlidesFromList($(options['sourceList']));
		
		//this.debug = options['debug']?true:false;
		this.delay = options['delay']?options['delay']*1000:4000;
		this.fadeDuration = options['fadeDuration']?options['fadeDuration']:3;
		
		this.bottomZ = options['bottomZ']?options['bottomZ']:10;
		
		this.fadeStyle = options['fadeStyle']?options['fadeStyle']:'crossfade'; // Options are 'crossfade' or 'straight'
		
		this.onShow = options['onShow']?options['onShow']:null;
		
		this._setup();
		
		if(options['slideslotStyle'])
			this.setSlideslotStyle(options['slideslotStyle']);
		
		if(this.autoplay)
			this.start();
	},
	
	
	_loadSlidesFromList: function(list)
	{
		var listItems = list.getElementsByTagName("LI");
		var item;
		
		for(var i = 0; i < listItems.length; i++)
		{
			item = listItems.item(i);
			if(item.nodeName == "LI")
			{
				this.slides.push(item);
			}
		}
	},
	
	
	_setup: function()
	{
		this.containerDiv.innerHTML='';
		this.slideSlots = new Array();
		this.slideCSlots = new Array();
		var divName,divCode;
		
		for(var i=0;i<2;i++)
		{
			divName = this.idPre+"_slot_"+i;
			divCName = this.idPre+"_slot_contents_"+i;
			divCode = "<div id=\""+divName+"\" style=\"position:absolute;width:"+
						this.viewerWidth+"px;text-align:center;display:none;\"><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"height:100%;width:100%;\"><tr><td style=\"height:100%;vertical-align:middle;text-align:center;\"><div id=\""+divCName+"\"></div></td></tr></table></div>";
			new Insertion.Top(this.containerDiv,divCode);
			$(divName).style.left = "0px";
			$(divName).style.top = "0px";
			if(this.debug)
			{
				$(divName).style.border = "1px solid "+(i<1?'#ff0000':'#0000ff');
				$(divName).style.display = "block";
			}
			this.slideSlots[i] = $(divName);
			this.slideCSlots[i] = $(divCName);
		}
		
		this.pos = 0;
		this.active = 0;
		
		this.runs = 0;
		this.queues = 0;
		
		this.effects = new Array();
		
		this._fillSlideSlots();
	},
	
	_fillSlideSlots: function()
	{
		var slideHtml = '';
		if(this.slides[this.pos] && this.slides[this.pos].innerHTML)
			slideHtml = this.slides[this.pos].innerHTML;
			
		this.slideCSlots[0].innerHTML = slideHtml;
		
		var nextSlide = this.slides.length ? (this.pos+1)%this.slides.length : 0;
		
		if(this.slides[nextSlide] && this.slides[nextSlide].innerHTML)
			slideHtml = this.slides[nextSlide].innerHTML;
			
		this.slideCSlots[1].innerHTML = slideHtml;
	},
	
	_queueNext: function()
	{
		var func;
		var thisObj = this;
		
		if(this.queues == 0)
		{
			// Nothing in queue, let's queue er up!
			func = function() { thisObj.queues--; thisObj.run(); };
			window.setTimeout(func,this.delay);
			this.queues++;
		}
		else
		{
			// we've been asked to add  to queue but queue is filled
			// so do nothing.
//			func = function() { thisObj.queues--; thisObj.run(); };
		}
	},
	
	setSlideslotStyle: function(propertyList)
	{
		Element.setStyle(this.slideSlots[0], propertyList);
		Element.setStyle(this.slideSlots[1], propertyList);
		return true;
	},
	
	run: function()
	{
		if(this.running)
		{
			this.runs++;
			this.advance(1);
			this._queueNext();
		}
	},
	
	start: function()
	{
		this.running = true;
		
//		new Effect.Appear(this.slideSlots[this.active], {duration: this.fadeDuration});
		new Element.show(this.slideSlots[this.active]);
		
		this._queueNext();
	},
	
	advance: function(n)
	{
		if(this.slides.length < 2)
		{
			if(this.debug)	alert(this.id+': advance called but slides.length==0!');
			return false;
		}
		
		var active = this.active;
		var inactive = (this.active+1)%2;
		var pos = this.pos;
		var next = (this.pos+1)%this.slides.length;
		
		this.slideCSlots[inactive].innerHTML = this.slides[next].innerHTML;
		this.slideSlots[inactive].style.zIndex = this.bottomZ+1;
		this.slideSlots[active].style.zIndex = this.bottomZ;
//		this.slideSlots[inactive].style.display = 'block'
//		this.slideSlots[active].style.display = 'block';
		
		if(this.effects[0])
			this.effects[0].cancel();
		if(this.effects[1])
			this.effects[1].cancel();

		this.effects[0] = new Effect.Fade(this.slideSlots[active], {duration: this.fadeDuration, queue: {scope: this.effectQueueName}});
		this.effects[1] = new Effect.Appear(this.slideSlots[inactive], {duration: this.fadeDuration, queue: {scope: this.effectQueueName}});
		
		if($(this.debug))
		{
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": add content for slide "+next+"<br>");
//			new Insertion.Top(this.debug,"SlideSlot "+active+": hide<br>");
//			new Insertion.Top(this.debug,"SlideSlot "+inactive+": show<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": set Zindex "+(this.bottomZ+1)+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": set Zindex "+(this.bottomZ)+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": Fade<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": Appear<br>");
			new Insertion.Top(this.debug,"<br>");
		}
		
		this.active = inactive;
		this.pos = next;
		
		if(this.onShow)
			this.onShow(next,this);
		
		return true;
	},
	
	_clearEffectQueue: function()
	{
		if(this.effects[0])
			this.effects[0].cancel();
		if(this.effects[1])
			this.effects[1].cancel();
		
		var q = Effect.Queues.get(this.effectQueueName);
		q.effects.each(function(e) { e.cancel() } );
		if($(this.debug))
		{
			alert(q);
			alert(q.length);
		}
	},
	
	show: function(i)
	{
		var active = this.active;
		var inactive = (this.active+1)%2;
		this.lastPos = this.pos;
		var next = i%this.slides.length;
		
		this._clearEffectQueue();
		
		
		this.slideSlots[active].style.opacity = '1.0';
		Element.show(this.slideSlots[active]);
		Element.hide(this.slideSlots[inactive]);
		this.slideSlots[inactive].style.opacity = '1.0';
		
		this.slideCSlots[inactive].innerHTML = this.slides[next].innerHTML;
		this.slideSlots[inactive].style.zIndex = this.bottomZ+1;
		this.slideSlots[active].style.zIndex = this.bottomZ;
		
		this.running = false;
		
		Element.show(this.slideSlots[active]);
		Element.hide(this.slideSlots[inactive]);
		
		if(this.effects[0])
			this.effects[0].cancel();
		if(this.effects[1])
			this.effects[1].cancel();

		this.effects[0] = new Effect.Fade(this.slideSlots[active], {duration: this.fadeDuration, queue: {scope: this.effectQueueName}});
		this.effects[1] = new Effect.Appear(this.slideSlots[inactive], {duration: this.fadeDuration, queue: {scope: this.effectQueueName}});
		
		if($(this.debug))
		{
			new Insertion.Top(this.debug,"--show("+i+")--<br>");
			new Insertion.Top(this.debug,"Cleared Effect Queue<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": show<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": hide<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": add content for slide "+next+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": set Zindex "+(this.bottomZ+1)+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": set Zindex "+(this.bottomZ)+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": Fade<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": Appear<br>");
			new Insertion.Top(this.debug,"<br>");
		}
		
		this.active = inactive;
		this.pos = next;
		
		if(this.onShow)
			this.onShow(next,this);
	}
}

com.digitalfruition.DataGrid = Class.create(
{
	initialize: function(containerDiv,data,options)
	{
		this.containerDiv = $(containerDiv);
		
		options = $H(options);
		
		this.debug = options['debug']?true:false;
		
		this._setupComplete = false;
		
		this.data = new $A(data);
		this.dataView = false;
		
		this._setOptions(options);
		
		var thisObj = this;
		var func = function(e) { window.setTimeout(function(){thisObj._initializeAfterLoaded();},500); };
		Event.observe(window, 'load', func);
		
		this._setContainerStyles();
		
		this._setContainerLoading();
		
		this.headerDiv = false;
		this.headerTable = false;
	},
	
	_setContainerLoading: function()
	{
		if(this.loadingDiv == undefined || !this.loadingDiv)
		{
			var html = "<h2 class=\"dataTable_loadingMessage\">Loading Data...</h2><div class=\"dataTable\" style=\"display:none;\"></div>";
			new Insertion.Top(this.containerDiv,html);
			
			el = $$('#'+this.containerDiv.id+' h2.dataTable_loadingMessage');
			this.loadingDiv = el[0];
		}
		
		this.containerDiv.setStyle(
			{
				cursor: 'wait'
			}
		);
	},
	
	_clearContainerLoading: function()
	{
		this.containerDiv.setStyle(
			{
				cursor: 'default'
			}
		);
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			hidePrefix:			'_',
			resizeDivPrefix:	'dataTable_resize',
			
			resizeWidth:		5,
			defaultColWidth:	200,
			
			colWidths:			[],
			colNames:			{},
			colTypes:			{},
			filterCols:			true,
			filterboxFuncs:		{},
			
			filterIcon:			'/images/filter.png',
			filterboxClass:		'dataTable_filterBox',
			
			sort:				false,
			sortInv:			false,
			
			filter:				false
		});
	},
	
	_setOptions: function(options)
	{
		var defaults = this.defaultOptions();
		
		var thisObj = this;
		
		defaults.each(function(v){ thisObj[v.key] = (typeof(options[v.key]) == 'undefined') ? v.value : options[v.key] });
		
		this.colWidths = $A(this.colWidths);
		this.colNames = $H(this.colNames);
		this.filterFuncs = $H(this.filterFuncs);
		
		if(this.filterCols !== true)
			this.filterCols = $A(this.filterCols);
	},
	
	colName: function(index)
	{
		if(typeof(index) == 'number')
			index = this._cols[index];
		
		if(typeof(index) == 'undefined')
			return false;
		
		if(typeof(this.colNames[index]) == 'undefined' || !this.colNames[index])
			return index;
		
		return this.colNames[index];
	},
	
	_setContainerStyles: function()
	{
		this.containerDiv.setStyle(
			{
				overflow: 'auto',
				backgroundColor: '#fff'
			}
		);
		this.containerDiv.makePositioned();
	},
	
	_initializeAfterLoaded: function()
	{
		this._parseData();
		
		this._doFilterAndSort();
		
		this.refresh();
	},
	
	_parseData: function()
	{
		this._cols = $A();
		
		var thisObj = this;
		
		// Go through each row of data and see what columns we have available.
		this.data.each(function(row)
		{
			var cols = Object.keys(row);
			var prefixLen = thisObj.hidePrefix.length;
			cols = cols.reject(function(e){ return e.substring(0,prefixLen) == thisObj.hidePrefix });
			cols = thisObj._cols.concat(cols).uniq();
			thisObj._cols = cols;
			
		});
		
		// Now, make sure we st up each column properly
		
		this._cols.each(function(col)
		{
			if(typeof(thisObj.colTypes[col]) == 'undefined' || !thisObj.colTypes[col])
			{
				thisObj.colTypes[col] = 'text';
			}
		});
	},
	
	sortBy: function(col,invert)
	{
		if(invert==undefined)
		{
			if(this.sort == col)
			{
				this.sortInv = !this.sortInv;
			}
			else
			{
				this.sort = col;
				this.sortInv = false;
			}
		}
		else
		{
			this.sort = col;
			this.sortInv = invert;
		}
		
		var headers = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_header a');
		
		headers.invoke('removeClassName','sort');
		
		this.filterAndSort();
	},
	
	filterBy: function(col,regexp)
	{
		if(typeof(this.filter)=='undefined' || !this.filter)
			this.filter = $H({});
		
		this.filter[col] = regexp;
		
		this.filterAndSort();
	},
	
	_passesFilter: function(row)
	{
		if(typeof(this.filter)=='undefined' || !this.filter)
			return true;
		
		return this.filter.all(function(test)
			{
				return (row[test.key].toString().indexOf(test.value.toString()) != -1);
			});
	},
	
	_doFilterAndSort: function()
	{
		var thisObj = this;
		
		this._setContainerLoading();
		
		this.dataView = $R(0,this.data.length-1);
		
		if(this.filter)
		{
			this.dataView = this.dataView.select(function(n){ return thisObj._passesFilter(thisObj.data[n]); })
		}
		
		if(this.sort)
		{
			this.dataView = this.dataView.sortBy(function(n)
			{
				return thisObj.data[n][thisObj.sort];
			});
		}
		
		this.dataView = $A(this.dataView);
		
		if(this.sortInv)
			this.dataView.reverse();
	},
	
	filterAndSort: function()
	{
		this._doFilterAndSort();
		
		this.refresh(false);
	},
	
	refresh: function(refreshHeader)
	{
		this._setContainerLoading();
		
		var el;
		
		this._cells = $A();
		
		el = $$('#'+this.containerDiv.id+' div.dataTable');
		this.tableDiv = el[0];
		
		if(refreshHeader != false)
			this._buildHeader();
		
		this._buildGrid();
		this._fillGrid();
		
		this._updateColWidths();
		
		this._addResizeHooks();
		
		this.loadingDiv.hide();
		this.tableDiv.show();
		
		this._updateColWidths();
		
//		Position.clone(this.headerDiv,this.headerHoverDiv,{offsetLeft: 2, offsetTop: 2});
//		this.headerHoverDiv.update(this.headerDiv.innerHTML);
//		this.headerHoverDiv.show();
//		this.headerDiv.setStyle({visibility: 'hidden'});
		
		this._clearContainerLoading();
	},
	
	_observeFilter: function(filterIcon,col)
	{
		var thisObj = this;
		
		if(typeof(thisObj.filterboxFuncs[col]) == 'undefined')
			thisObj.filterboxFuncs[col] = this.defaultFilterboxFunction.bindAsEventListener(this);
		
		filterIcon.observe('click',thisObj.filterboxFuncs[col]);
	},
	
	_observeFilterHide: function(e,box,col)
	{
		if(typeof(box) != 'undefined' && box)
			new Effect.BlindUp(box);
		
		if(typeof(col) != 'undefined' && col)
			this._observeFilter(e.target,col);
		
		return false;
	},
	
	_buildHeader: function()
	{
		var thisObj = this;
		
		var html, header;
		
		// Create the header div and header UL:
		
		var html = "<div class=\"dataTable_header\" style=\"position:relative;top:0px;z-index:5;\"><table class=\"dataTable_header\"><tr></tr></table></div>";
		
		new Insertion.Bottom(this.tableDiv,html);
		
		header = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_header');
		this.headerDiv = header[0];
		
		header = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_header table.dataTable_header');
		this.headerTable = header[0];
		this.headerRow = header[0].down().down();
		
		this.containerDiv.observe('scroll',function(e){thisObj.headerDiv.style.top=(thisObj.containerDiv.scrollTop)+'px';})
		
		var c = 0;
		
		this._cols.each(function(col)
		{
			var e_odd = (c%2)?'odd':'even';
			
			var colHeader = "<a href=\"#\" onclick=\"return false;\">"+thisObj.colName(col)+"</a>";
			
			html = "<td class=\"col"+c+" "+e_odd+"\"><div>"+colHeader+"</div></td>";
			html += "<td class=\"dataTable_resize\"><div class=\""+thisObj.resizeDivPrefix+c+"\">&nbsp;</div></td>";
			new Insertion.Bottom(thisObj.headerRow,html);
			
			var link = thisObj.headerRow.down('td',c*2).down().down();
			link.observe('click', function(e){ thisObj.sortBy(col); $(this).addClassName('sort'); });
			
			
			if(thisObj.filterCols === true || thisObj.filterCols.indexOf(col) != -1)
			{
				colHeader = "<img src=\""+thisObj.filterIcon+"\">";
				new Insertion.After(link,colHeader);
				var filterIcon = link.next();
				
				thisObj._observeFilter(filterIcon,col);
			}
			
			c++;
		});
				
		new Insertion.Top(document.body,'<div class="dataTable_header" style="position: absolute;border: none;z-index: 2;"></div>');
		header = $(document.body).down();
		this.headerHoverDiv = header;
	},
	
	_buildGrid: function()
	{
		var thisObj = this;
		
		var html, el;
		
		// Destroy the existing grid div if it exists:
		
		if(this.gridDiv != undefined)
		{
			if(this.gridDiv)
			{
				el = $(this.gridDiv);
				if(el && el != undefined)
					el.remove();
			}
		}
		
		// Create the grid div:
		
		var html = "<div class=\"dataTable_grid\"><table class=\"dataTable_grid\"><tbody><tr class=\"dataTable_preRow\"></tr><tr class=\"dataTable_postRow\"></tr></tbody></table></div>";
		
		new Insertion.Bottom(this.tableDiv,html);
		
		el = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_grid');
		this.gridDiv = el[0];
		
		el = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_grid table.dataTable_grid');
		this.gridTable = el[0];
		
		el = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_grid table.dataTable_grid tbody tr');
		this.gridPreRow = el[0];
		this.gridPostRow = el[1];
	},
	
	_fillGrid: function()
	{
		var thisObj = this;
		
		var el = this.gridPostRow;			// el is now the tbody.
		
		this.dataView.each(function(n)
		{
			var html = "<tr>";
			
			var row = this.data[n];
			
			var c = 0;
			
			thisObj._cols.each(function(col)
			{
				var e_odd = (c%2)?'odd':'even';
				html += "<td class=\"col"+c+" "+e_odd+"\"><div>"+row[col]+"</div></td>";
				html += "<td class=\"dataTable_resize\"><div class=\""+thisObj.resizeDivPrefix+c+"\">&nbsp;</div></td>";
				c++;
			});
			
			html +'</tr>';
			
			new Insertion.Before(el,html);
		});
	},
	
	_getResizeCells: function()
	{
		//var resizeCells = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_grid table.dataTable_grid tbody tr td.dataTable_resize');
		var resizeCells = $$('#'+this.containerDiv.id+' td.dataTable_resize');
		
		return resizeCells;
	},
	
	_getDataCells: function(col)
	{
		if(!this._cells)
			this._cells = $A();
		
		if(!this._cells[col])
		{
			//this._cells[col] = $$('#'+this.containerDiv.id+' div.dataTable div.dataTable_grid table.dataTable_grid tbody tr td.col'+col);
			this._cells[col] = $$('#'+this.containerDiv.id+' td.col'+col + ' div');
		}
		
		return this._cells[col];
	},
	
	_addResizeHooks: function()
	{
		var thisObj = this;
		
		var resizeCells = this._getResizeCells();
		
		var resizeDragHandler = function(a){thisObj.resizeDragHandler(a)};
		
//		resizeCells.invoke('observe','mouseDown',thisObj.resizeDragHandler);
		resizeCells.each(function(resize){
			var div = resize.down();
//			new Draggable(div); 
			new Draggable(div,{
							constraint:'horizontal',
							change:resizeDragHandler,
							revert: true}); 
		});	
	},
	
	_updateColWidths: function(col)
	{
		var thisObj = this;
		
		var totalWidth = 0;
		
		totalWidth = this.colWidths.inject(0,function(acc,n){return acc+n});
		
		totalWidth += this.resizeWidth * this._cols.length;
		
		this.headerTable.setWidth(totalWidth+'px');
		this.gridTable.setWidth(totalWidth+'px');
		
		var c = 0;
		
		if(col==undefined)
		{
			this._cols.each(function(col)
			{
				var cells;
	
				cells = thisObj._getDataCells(c);
				
				if(typeof(thisObj.colWidths[c]) == 'undefined')
					thisObj.colWidths[c] = thisObj.defaultColWidth;
				
//				cells.invoke('setStyle',{width: thisObj.colWidths[c]+'px'});
				cells.invoke('setWidth',thisObj.colWidths[c]+'px');
				
				c++;
			});
		}
		else
		{
			var cells = this._getDataCells(col);
			cells.invoke('setWidth',this.colWidths[col]+'px');
		}
		
		var resizeCells = this._getResizeCells();
		resizeCells.invoke('setWidth',this.resizeWidth+'px');;
		resizecells = null;
	},
	
	getColNum: function(element)
	{
		var colNum = element.classNames().toArray();
//		var colNum = element.className;
		colNum = colNum[0].substr(this.resizeDivPrefix.length);
		
		return colNum;
	},
	
	resizeDragHandler: function(drag)
	{
		var colNum, colPos, sliderPos, diff;
		
		diff = drag.currentDelta();
		diff = diff[0];
		
		colNum = this.getColNum(drag.element);
		
		this.colWidths[colNum] += diff;
		
		this._updateColWidths(colNum);
	},
	
	resizeFinishHandler: function(e)
	{
		alert("Resize");
	},
	
	defaultFilterboxFunction: function(event)
	{
		var thisObj = this;
		
		if(event.type == 'click')
		{
			// We're responding to the filter icon click.
			// Determine the coumn and column number
			var colNum = this.getColNum(event.target.up().up().next().down());
			var col = this._cols[colNum];
			
			// Tell the filterIcon to stop observing clicks, otherwise we may get called multiple times
			event.target.stopObserving('click',this.filterboxFuncs[col]);
			
			// Create a filter box
			var filterBox = this.newFilterbox(col);
			
			// Display it
			new Effect.BlindDown(filterBox);
			
			event.target.observeFilterHide = this._observeFilterHide.bindAsEventListener(this,filterBox,col);
			
			// Tell the filter icon to observe clicks which will close the box
			event.target.observe('click',event.target.observeFilterHide );
		}
		else if (event.type == 'submit')
		{
			// We're responding to the form submission
			// Find our form and box
			var filterBoxForm = event.target, filterBox = event.target.up();
			
			// Determine col number and reference
			var colNum = $F(filterBoxForm['col']), col = false;
			
			if(typeof(colNum) == 'undefined' || !(col = this._cols[colNum]) || typeof(col) == undefined)
			{
				alert('Column number was not present in form. Please contact a developer.');
				new Effect.Fade(filterBox);
				return false;
			}
			
			// Mark the grid as loading so users know what's going on
			this._setContainerLoading();
			
			// Find the filter icon
			
			var filterIcon = Try.these(
				function() { return thisObj.headerRow.down('td',colNum*2).down().down().next(); },
				function() { DFTools.console.log("Couldn't find the filter icon!!!"); return false; },
				function() { return false; }
			) || false;
			
			// Hide the filter box
			new Effect.BlindUp(filterBox);
			
			// Now, make the filter icon observe the show filter box filter event again
			if(filterIcon)
			{
				filterIcon.stopObserving('click',filterIcon.observeFilterHide);
				this._observeFilter(filterIcon,col);
			}
			
			// Determine the column data type
			var type = this.colTypes[col];
			
			if(typeof(type) == 'undefined' || !type)
				type = 'text';
			
			// Now, filter by that type
			switch(type)
			{
				case 'text':
				default:
					this.filterBy(col,$F(filterBoxForm['filter']));
					break;
			}
			
			// Make sure the "loading" message is cleared
			
			this._clearContainerLoading();
			
			return false;
		}
	},
	
	newFilterbox: function(col)
	{
		var thisObj = this;
		
		var type = this.colTypes[col];
		
		var colNum = this._cols.indexOf(col);
		
		if(typeof(type) == 'undefined' || !type)
			type = 'text';
		
		var html,box,boxForm,headerCell,el,i;
		
		html = "<div class=\""+this.filterboxClass+"\" style=\"position:absolute;display:none;z-index: 10;\"></div>";
		
		new Insertion.Bottom(this.containerDiv,html);
		
		box = this.containerDiv.immediateDescendants();
		box = box[box.length-1];
		
		headerCell = this.headerDiv.down('td.col'+colNum);
		
		Position.clone(headerCell,box,{setWidth:false,setHeight:false,offsetLeft: 0, offsetTop: headerCell.clientHeight});
		
		box.setStyle({backgroundColor: headerCell.getStyle('backgroundColor')});
		
		//new Insertion.Bottom(box,"<p><b>Filter</b>: "+this.colName(col)+"</p>");
		new Insertion.Bottom(box,"<form onsubmit=\"return false;\"></form>");
		
		boxForm = box.down('form');
		
		boxForm.observe('submit',function(e){return thisObj.defaultFilterboxFunction(e);});
		
		var val = '';
		
		if(this.filter && typeof(this.filter[col]) != 'undefined')
			val = this.filter[col];
		
		switch(type)
		{
			case 'number':
				new Insertion.Bottom(boxForm,"<p>Filter: <select name=\"filter\"><option>--Select--</option></select></p>");
				el = $(boxForm['filter']);
				el.down().value='';
				this.uniqVals(col).each(function(v){new Insertion.Bottom(el,"<option>"+v+"</option>");});
				i = $A(el.options).pluck('value').indexOf(val);
				if(i!=-1)
					el.selectedIndex=i;
				new Insertion.Bottom(boxForm,"<input type=\"hidden\" name=\"col\" value=\""+colNum+"\">");
				new Insertion.Bottom(boxForm,"<input type=\"submit\" value=\"Go\">");
				break;
				
			case 'enum':
				new Insertion.Bottom(boxForm,"<p>Filter: <select name=\"filter\"><option>--Select--</option></select></p>");
				el = $(boxForm['filter']);
				el.down().value='';
				this.uniqVals(col).each(function(v){new Insertion.Bottom(el,"<option>"+v+"</option>");});
				i = $A(el.options).pluck('value').indexOf(val);
				if(i!=-1)
					el.selectedIndex=i;
				new Insertion.Bottom(boxForm,"<input type=\"hidden\" name=\"col\" value=\""+colNum+"\">");
				new Insertion.Bottom(boxForm,"<input type=\"submit\" value=\"Go\">");
				break;
			
			case 'text':
			default:
				new Insertion.Bottom(boxForm,"<p>Filter: <input type=\"text\" name=\"filter\" value=\""+val+"\"></p>");
				new Insertion.Bottom(boxForm,"<input type=\"hidden\" name=\"col\" value=\""+colNum+"\">");
				new Insertion.Bottom(boxForm,"<input type=\"submit\" value=\"Go\">");
				break;
		}
		
		return box;
	},
		
	uniqVals: function(col)
	{
		if(typeof(this._uniqVals) == 'undefined')
			this._uniqVals = $H({});
		
		if(typeof(this._uniqVals[col]) == 'undefined')
		{
			this._uniqVals[col] = $A(this.data.pluck(col)).uniq().sort();
		}
		
		return this._uniqVals[col];
	}
});



/** Theme Objects_______________________________________________________________ */


com.digitalfruition.ThemeObject = Class.create(
{
	initialize: function(options)
	{
		com.digitalfruition.ThemeObject.instances.push(this);
		
		this.mySetOptions($H(options));
	},
	
	defaultOptions: function()
	{
		return $H({});
	},
	
	mySetOptions: function(options)
	{
		var defaults = this.defaultOptions();
		
		$H(defaults).each(function(v){
			this[v.key] = (typeof(options.get(v.key)) == 'undefined') ? v.value : options.get(v.key);
		},this);
	}
});
com.digitalfruition.ThemeObject.addMethods(com.digitalfruition.Std.EventListener);

com.digitalfruition.ThemeObject.instances = $A();


/**
 * Create a DFThemeObject from a hash-like object of options.
 *
 * This function will take the options hash (or object which can be converted
 * into a hash) and detremine the appropriate subclass of ThemeObject to create.
 *
 * @return com.digitalfruition.ThemeObject
 */
com.digitalfruition.ThemeObject.create = function(options)
{
	var c = options['class'];
	
	if (c && com.digitalfruition.ThemeObject[c])
	{
		c = com.digitalfruition.ThemeObject[c];
	}
	else
	{
		c = com.digitalfruition.ThemeObject;
	}
	
	return new c(options);
}

/**
 * Create a DFThemeObject from a hash-like object of options.
 *
 * This function will take the options hash (or object which can be converted
 * into a hash) and detremine the appropriate subclass of ThemeObject to create.
 *
 * @return com.digitalfruition.ThemeObject
 */
com.digitalfruition.ThemeObject.createFromForms = function(cssClass)
{
	var config, c, cssClassParts;
	
	cssClass = cssClass ? cssClass : 'DFThemeObject';
	
	return $$('form.'+cssClass).each(function(form)
	{
		config = form.serialize(true);
		
		$H(config).each(function(pair)
		{
			if(typeof(pair.value) == 'string' && pair.value.isJSON())
			{
				config[pair.key] = pair.value.evalJSON();
			}
		});
		
		c = com.digitalfruition.ThemeObject;
		
		cssClassParts = $w(form.className).without(cssClass);
		
		while (cssClassParts.length && c[cssClassParts[0]])
		{
			c = c[cssClassParts.shift()];
		}
		
		if(typeof(c) != 'function')
		{
			c = com.digitalfruition.ThemeObject;
		}
		
		return new c(config);
	}).length
}




/**
 * Breadcrumb: Shows a stack of links, to indicate steps a user has taken.
 *
 * The Breadcrumb theme object can be used to maintain, using JavaScript, an
 * ordered list (technically a stack) of links which a website user has taken.
 * Links can be push()ed onto the stack and they will be appended in the given
 * style.
 */
com.digitalfruition.ThemeObject.Breadcrumb = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		options = options ? options : {};
		
		$super(options);
		
		this.crumbs = $A();
		
		this.container = $(options['container']);
		
		if(!this.container) {
			this.container = new Element('div');
		}
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			separator:	'<span class="sep">&nbsp;&gt;&nbsp;</sep>'
		});
	},
	
	/**
	 * Convert a single crumb (link) of a breadcumb to a DOM Element
	 *
	 * @access private
	 * @return Element
	 */
	crumbToElement: function(crumb)
	{
		var el;
		
		if(typeof(crumb[1]) == 'string')
		{
			el = new Element('a',{href:crumb[1]}).update(crumb[0]);
		}
		else
		{
			el = new Element('a',{href:'#'}).update(crumb[0]);
			
			if(typeof(crumb[1]) == 'function')
			{
				el.observe('click',crumb[1]);
			}
		}
		
		return el;
	},
	
	/**
	 * Append a given crumb element (created via crumbToElement) to
	 * the breadcrumb comtainer.
	 *
	 * @access private
	 * @return void
	 */
	appendCrumbElementToContainer: function(crumbEl)
	{
		if(this.crumbs.length)
		{
			this.container.insert(this.separator);
		}
		this.container.insert(crumbEl);
	},
	
	/**
	 * Append a new crumb (link) onto this breadcrumb. Pass the text as well as the
	 * URL or JavaScript function for the link
	 *
	 * @param string text
	 * @param string/function action
	 * @return void
	 */
	push: function(text,action)
	{
		var crumb = [text,action];
		
		var crumbEl = this.crumbToElement(crumb);
		this.appendCrumbElementToContainer(crumbEl);
		
		this.crumbs.push(crumb);
	},
	
	/**
	 * Refresh the breadcrumb
	 *
	 * @return void
	 */
	refresh: function()
	{
		this.container.update('');
		var crumbEls = this.crumbs.collect(this.crumbToElement,this);
		
		if(crumbEls.length)
		{
			this.container.insert(crumbEls.shift());
			crumbEls.each(this.appendCrumbElementToContainer,this);
		}
	},
	
	/**
	 * Move the Breadcrumb to a new container.
	 *
	 * @param Element element
	 * @return void
	 */
	applyTo: function(element)
	{
		if(element = $(element))
		{
			this.container.update('');
			this.container = element;
			this.refresh();
		}
	}
});



/**
 * Fixed Div Theme Object
 *
 * Theme Object for creating a <div> which has CSS position fixed cross browser.
 */
com.digitalfruition.ThemeObject.FixedDiv = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		$super(options);
		
		this.div = false;
		
		this.pos = $H({});
		
		this._createDiv();
		
		this._mySetupObservers();
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			cssClass:		'DFFixed',
			innerHTML:		'&nbsp;',
			bodyHack:		true
		});
	},
	
	_createDiv: function()
	{
		var html,className, body = $(document.body);
		
		className = this.cssClass;
		html = '<div class="'+this.cssClass+'">'+this.innerHTML+'</div>';
		new Insertion.Top(body,html);
		this.div = body.down();
		this._setDivStyle(this.div);
	},
	
	_setDivStyle: function(div)
	{
		var style = $H({position: 'fixed'});
		
		if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) < 7)
			style.position = 'absolute';
		
		div.setStyle(style);
	},
	
	setPos: function(side,px)
	{
		var style = $H({});
		
		if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) < 7)
		{
			if(this.bodyHack)
			{
				style[side] = px+'px';
				this.div.setStyle(style);
			}
			else
			{
				/*switch(side)
				{
					case 'top': style[side] = 'expression( ('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\' )'; break;
					case 'right': style[side] = px+'px'; break;
					case 'bottom': style[side] = 'expression( ('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\' )'; break;
					case 'left': style[side] = px+'px'; break;
				}*/
				
				switch(side)
				{
					case 'top': this.div.style.setExpression(side, '('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\''); break;
					case 'right': style[side] = px+'px'; break;
					case 'bottom': this.div.style.setExpression('top', "__DFThemeObject__FixedDiv_setBottom(" + px + ',' + this.div.uniqueID + ")"); break;
					case 'left': style[side] = px+'px'; break;
				}
				
				
				if(style.size())
					this.div.setStyle(style);
				
				//alert(side + ' style: ' + this.div.style[side]);
				
				
				//Event.observe(window,'scroll',function(){alert('scroll');this.setPos(side,px);}.bindAsEventListener(this));
			}
		}
		else
		{
			style[side] = px+'px';
			this.div.setStyle(style);
		}
	},
	
	_mySetupObservers: function()
	{
		window.resize()
	}
});
	
function __DFThemeObject__FixedDiv_setBottom(dist,obj)
{
	return dist - obj.offsetHeight + ( document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) 
			+ ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop );
}

/**
 * Point Theme Object: Displays a point which fires mouse events and shows/hides content
 *
 **/
com.digitalfruition.ThemeObject.Point = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		$super(options);

		this._mySetupContainer();

		this._myElements = {
			container: false
		};

		this._myMouseOverHandler = this._myMouseOver.bind(this);
		this._myMouseOutHandler = this._myMouseOut.bind(this);
		this._myClickHandler = this._myClick.bind(this);

		this._mySetupElements();
	},

	defaultOptions: function()
	{
		return $H({
			name:			'',
			title:			'',
			cssClass:		'',
			imageSrc:		'/images/dot.png',
			x:				0,
			y:				0,
			container:		document.body
		});
	},
	
	_myMouseOver: function(e)
	{
		try
		{
			e.stop();

			var el = e.element();

			if(el == this._myElements.container)
			{
				if(this.name)
					$$('DFThemeObject-Point.hover.content.'+this.name).invoke('show');

				this.fire('mouseOver');
			}
		}
		catch(ex)
		{
			DFTools.console.logException(ex,"com.digitalfruition.ThemeObject.Point#_myMouseOver");
		}
	},
	
	_myMouseOut: function(e)
	{
		try
		{
			e.stop();

			var el = e.element();

			if(el == this._myElements.container)
			{
				if(this.name)
					$$('DFThemeObject-Point.hover.content.'+this.name).invoke('hide');

				this.fire('mouseOut');
			}
		}
		catch(ex)
		{
			DFTools.console.logException(ex,"com.digitalfruition.ThemeObject.Point#_myMouseOut");
		}
	},
	
	_myClick: function(e)
	{
		try
		{
			e.stop();

			$$('DFThemeObject-Point.click.content').invoke('hide');

			if(this.name)
				$$('DFThemeObject-Point.click.content.'+this.name).invoke('show');

			this.fire('click');
		}
		catch(ex)
		{
			DFTools.console.logException(ex,"com.digitalfruition.ThemeObject.Point#_myClick");
		}
	},

	_mySetupContainer: function()
	{
		if(this.container && (this.container = $(this.container)))
		{
			this.container.relativize();
		}
	},

	_mySetupElements: function()
	{
		this._myElements.container = new Element('div').addClassName('DFThemeObject-Point '+this.cssClass);

		this._myElements.container.setStyle({
			position: 'absolute',
			display: 'block',
			left: this.x+'px',
			top: this.y+'px'
		});

		var dot = new Element('img',{src:this.imageSrc,alt:this.name,title:this.title});

		this._myElements.container.appendChild(dot);

		dot.observe('mouseover', this._myMouseOverHandler);
		dot.observe('mouseout', this._myMouseOutHandler);
		dot.observe('click', this._myClickHandler);

		if(this.container)
			this.container.appendChild(this._myElements.container);
	}
});

com.digitalfruition.ThemeObject.LoadingIndicator = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		$super(options);
		
		this._myElements = {
			loading: false,
			container: false
		};
		
		this._mySetupElements();
	},
	
	defaultOptions: function()
	{
		return $H({
			cssClass:		'',
			imageSrc:		'/images/loading_01.gif',
			el:				false,
			bgcolor:		false,
			text:			'',
			center: 		true,
			reposInterval:	0.5
		});
	},
	
	show: function()
	{
		this.repos();
		this._myElements.container.show();
		this._myPE = new PeriodicalExecuter(this.repos.bind(this),this.reposInterval);
		this.fire('show');
	},
	
	repos: function()
	{
		try
		{
			this._myElements.container.clonePosition(this.el);
		}
		catch(e) {}
	},
	
	hide: function()
	{
		this._myElements.container.hide();
		if(this._myPE)
			this._myPE.stop();
		this.fire('hide');
	},
	
	setText: function(text)
	{
		this.text = text;
		this._myElements.loading.down('p').update(this.text);
		this.fire('setText',text);
	},
	
	_mySetupElements: function()
	{
		this._myElements.loading = new Element('div');
		this._myElements.loading.addClassName('loading SitePalette_Text');
		
		
		this._myElements.loading.appendChild(new Element('img',{src: this.imageSrc}).wrap('div'));
		
		var style = {};
		
		if(this.bgcolor)
			style.backgroundColor =  this.bgcolor;
		
		this._myElements.loading.down().setStyle(style);
		
		this._myElements.loading.down().appendChild(new Element('p').update(this.text));
		
		if(this.center)
		{
			var d = this._myElements.loading.down();
			d.remove();
			this._myElements.loading.update('<table><tr><td>&nbsp;</td></tr></table>');
			this._myElements.loading.down('td').appendChild(d);
			this._myElements.loading.down('table').setStyle({
				position: 'relative',
				top: '50%',
				margin: 'auto',
				border: 'none',
				borderCollapse: 'collapse'
			});
		}
		
		this._myElements.container = new Element('div');
		this._myElements.container.addClassName('DFThemeObject-LoadingIndicator '+this.cssClass);
		
		this._myElements.container.hide();
		
		document.body.appendChild(this._myElements.container);
		
		this._myElements.container.appendChild(this._myElements.loading);
		
		var background = this._myElements.container.getStyle('background-image');
		background = background.substr(4,background.length-5);
		this._maskPreload = new Image(1,1);
		this._maskPreload.src = background;
	}
});

/**
 * Popin Theme Object
 *
 * Theme Object for creating a <div> which pops in
 */
com.digitalfruition.ThemeObject.Popin = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		$super(options);
		
		this._myElements = {
			popin: false,
			mask: false
		};
		
		this.pos = $H({});
		
		/**
		 * Visibility of the popin.
		 *
		 * Values are:
		 *
		 *  * H - Hidden
		 *  * h - being hidden
		 *  * S - shown
		 *  * s - being shown
		 */
		this._myPopinStatus = 'H';
		
		
		// handler for links/events which close this popin:
		
		var closeHandler = function(e) {
			this.fire('close');
			this.hide();
			e.stop();
		}
		
		this._myCloseHandler = closeHandler.bindAsEventListener(this);
		
		
		this._myCreateDivs();
		
		var afterShow = function() {
			this._myPopinStatus = 'S';
			this._myStartAutoShrink();
			this.fire('show');
		};
		
		var afterHide = function() {
			if(this.hideSelectsInIE && BrowserDetect.browser == 'Explorer' && BrowserDetect.version == 6)
			{
				// #308: In IE 6, <select>s will show through the cart. We hid them in show(), now show them again
				$('container').select('select').invoke('setStyle',{'visibility':'visible'});
			}

			this._myElements.container.hide();
			this._myPopinStatus = 'H';
			this.fire('hide');
		};
		
		this._afterShow = afterShow.bind(this);
		this._afterHide = afterHide.bind(this);
		
		this._mySetupObservers();
		
		if(this.draggable && this._myElements.close)
		{
			new Draggable(this._myElements.popinContainer,{handle:this._myElements.close});
		}
		
		DFTools.console.debug("New com.digitalfruition.ThemeObject.Popin set up completed",this);
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			cssClass:			'',
			innerHTML:			'&nbsp;',
			mask:				false,
			closeLink:			true,
			closeLinkText:		'Close',
			showEffect:			'BlindDown',
			hideEffect:			'BlindUp',
			showDuration:		1,
			hideDuration:		1,
			maskShowEffect:		'Appear',
			maskHideEffect:		'Fade',
			maskShowDuration:	0.5,
			maskHideDuration:	0.5,
			allowMaskFadeInIE:	false,
			hideSelectsInIE:	true,
			draggable:			true
		});
	},
	
	/**
	 * Is the popin visible?
	 *
	 * Returns true if the popin is visible
	 *
	 * @return boolean
	 */
	visible: function()
	{
		return this._myPopinStatus == 's' || this._myPopinStatus == 'S';
	},
	
	/**
	 * Show the popin (using an animation if configured to do so)
	 *
	 * @return void
	 */
	show: function()
	{
		if(this._myPopinStatus == 'h')
		{
			this._myCurrentEffect.cancel();
			this._myPopinStatus = 'H';
		}
		
		if(this._myPopinStatus == 'H')
		{
			this._myElements.container.show();
			
			this.fire('beforeShow');

			var show;

			if(this.showEffect && Effect[this.showEffect])
			{
				show = function()
				{
					new Effect[this.showEffect](this._myElements.popinContainer,{
						duration: this.showDuration,
						afterFinish: this._afterShow
					});
				}
			}
			else
			{
				show = function()
				{
					this._myElements.popinContainer.show();
					this._afterShow.defer();
				}
			}

			show = show.bind(this);

			if(this._myElements.mask)
			{
				// #803: IE doesn't like fading in/out a transparent PNG, so we
				// check to see if we're using IE and if so, we only permit
				// effects if the effect isn't a fade or if we've been
				// configured specifically to allow fades in IE.
				var IEok = (BrowserDetect.browser != 'Explorer' || (this.maskShowEffect != 'Fade' || this.allowMaskFadeInIE))

				if(this.maskShowEffect && Effect[this.maskShowEffect] && IEok)
				{
					new Effect[this.maskShowEffect](this._myElements.mask,{
						duration: this.maskShowDuration,
						afterFinish: show
					});
				}
				else
				{
					this._myElements.mask.show();
					show();
				}
			}
			else
			{
				show();
			}

			if(this.hideSelectsInIE && BrowserDetect.browser == 'Explorer' && BrowserDetect.version == 6)
			{
				// #308: In IE 6, <select>s will show through the cart. We hid them in show(), now show them again
				$('container').select('select').invoke('setStyle',{'visibility':'hidden'});
			}
			
			this._myStartAutoShrink();
			
			this._myPopinStatus = 's';
		}
	},
	
	/**
	 * Hide the popin (using an animation if configured to do so)
	 *
	 * @return void
	 */
	hide: function()
	{
		this.fire('beforeHide');

		var hideMask;

		// #803: IE doesn't like fading in/out a transparent PNG, so we check to
		// see if we're using IE and if so, we only permit effects if the effect
		// isn't a fade or if we've been configured specifically to allow fades
		// in IE.
		var IEok = (BrowserDetect.browser != 'Explorer' || (this.maskShowEffect != 'Fade' || this.allowMaskFadeInIE))

		if(this._myElements.mask && this.maskHideEffect && Effect[this.maskHideEffect] && IEok)
		{
			hideMask = function()
			{
				new Effect[this.maskHideEffect](this._myElements.mask,{
					duration: this.maskHideDuration,
					afterFinish: this._afterHide
				});
			}
		}
		else
		{
			hideMask = function()
			{
				if(this._myElements.mask) this._myElements.mask.hide();
				this._afterHide();
			}
		}

		hideMask = hideMask.bind(this);

		if(this.hideEffect && Effect[this.hideEffect])
		{
			new Effect[this.hideEffect](this._myElements.popinContainer,{
				duration: this.hideDuration,
				afterFinish: hideMask
			});
		}
		else
		{
			this._myElements.popinContainer.hide();
			hideMask();
		}
		
		this._myVisible = false;
		this._myStopAutoShrink();
	},
	
	setContent: function(content)
	{
		this._myElements.popin.update(content);
	},
	
	element: function()
	{
		return this._myElements.popin;
	},
	
	setPos: function(side,px)
	{
		var style = {};

		DFTools.console.debug("com.digitalfruition.ThemeObject.Popin.setPos() called with",side,px,"on",this);
		
		if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) < 7)
		{
			if(this.bodyHack)
			{
				style[side] = px+'px';
				this._myElements.mask.setStyle(style);
			}
			else
			{
				switch(side)
				{
					case 'top':
						this._myElements.mask.style.setExpression(side, 
							'('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\'');
						break;
						
					case 'right':
						style[side] = px+'px';
						break;
					
					case 'bottom':
						this._myElements.mask.style.setExpression('top', "__DFThemeObject__FixedDiv_setBottom(" + px + ',' + this.div.uniqueID + ")");
						break;
					
					case 'left':
						style[side] = px+'px';
						break;
				}
				
				this._myElements.mask.setStyle(style);
			}
		}
		else
		{
			style[side] = px+'px';
			this._myElements.mask.setStyle(style);
		}
	},
	
	autoShrink: function(enabled)
	{
		this._myStopAutoShrink();
		
		this._autoShrink = enabled ? true : false;
		
		this._myStartAutoShrink()
	},
	
	shrinkToContent: function()
	{
		var restoreStyle = {},tempStyle={},sizeStyle = {};
		
		DFTools.console.debug("com.digitalfruition.ThemeObject.Popin.shrinkToContent() called on",this);
		
		switch(this._myPopinStatus)
		{
			case 'H':
				DFTools.console.debug("shrinkToContent temporarily resizing popin content");
				
				tempStyle = $H({
					'display':	BrowserDetect.browser == 'Explorer' ? 'inline' : 'inline-block'
				});
				
				restoreStyle.popin = $H();
				tempStyle.each(function(pair){
					var v = this._myElements.popin.getStyle(pair.key);
					if(v===null) v = 'auto'
					restoreStyle.popin.set(pair.key,v);
				},this);
				
				this._myElements.popin.setStyle(tempStyle.toObject());
				
				
				DFTools.console.debug("shrinkToContent temporarily showing popin container");
				
				tempStyle = $H({
					'left':		'-65534px',
					'top':		'-65534px',
					'display':	'block',
					'position':	'absolute'
				});
				
				restoreStyle.popinContainer = $H();
				tempStyle.each(function(pair){
					var v = this._myElements.popinContainer.getStyle(pair.key);
					if(v===null) v = 'auto'
					restoreStyle.popinContainer.set(pair.key,v);
				},this);
				
				this._myElements.popinContainer.setStyle(tempStyle.toObject());
				
				if(this.mask)
				{
					DFTools.console.debug("shrinkToContent temporarily showing mask container");
					
					tempStyle = $H({
						'left':		'-65534px',
						'top':		'-65534px',
						'bottom':	'auto',
						'right':	'auto',
						'height':	'1px',
						'width':	'1px',
						'display':	'block',
						'position':	'absolute'
					});
					
					restoreStyle.mask = $H();
					tempStyle.each(function(pair){
						var v = this._myElements.mask.getStyle(pair.key);
						if(v===null || (pair.key == 'height' || pair.key == 'width')) v = 'auto'
						restoreStyle.mask.set(pair.key,v);
					},this);
					
					this._myElements.mask.setStyle(tempStyle.toObject());
				}
				
				sizeStyle = this._myElements.popin.getDimensions();
				
				if(this.close)
				{
					sizeStyle.height += this._myElements.close.getHeight();
				}
				
				if(this.mask)
					this._myElements.mask.setStyle(restoreStyle.mask.toObject());
				
				this._myElements.popinContainer.setStyle(restoreStyle.popinContainer.toObject());
				this._myElements.popin.setStyle(restoreStyle.popin.toObject());
				break;
			
			case 'S':
				DFTools.console.debug("shrinkToContent temporarily resizing popin content");
				
				tempStyle = $H({
					'display':	BrowserDetect.browser == 'Explorer' ? 'inline' : 'inline-block'
				});
				
				restoreStyle.popin = $H();
				tempStyle.each(function(pair){
					var v = this._myElements.popin.getStyle(pair.key);
					if(v===null) v = 'auto'
					restoreStyle.popin.set(pair.key,v);
				},this);
				
				this._myElements.popin.setStyle(tempStyle.toObject());
				
				sizeStyle = this._myElements.popin.getDimensions();
				
				DFTools.console.debug("shrinkToContent: found popin dimensions to be ",sizeStyle);
				
				if(this.closeLink)
				{
					var closeHeight = this._myElements.close.getHeight();
					DFTools.console.debug("shrinkToContent: found height of close div to be ",closeHeight);
					sizeStyle.height += closeHeight;
				}
				
				this._myElements.popin.setStyle(restoreStyle.popin.toObject());
				break;
			
			case 's':
			case 'h':
				sizeStyle = this._myElements.popin.getDimensions('width');
				break;
		}
		 
		if(sizeStyle.width)		sizeStyle.width += 'px';
		if(sizeStyle.height)	sizeStyle.height += 'px';
		
		DFTools.console.debug("shrinkToContent setting style to",$H(sizeStyle).inspect());
		
		this._myElements.popinContainer.setStyle(sizeStyle);
	},
	
	_myCreateDivs: function()
	{
		var html,className, body = $(document.body);
		
		// the container for all of our elements:
		
		this._myElements.container = new Element('div').addClassName('DFThemeObject-Popin '+this.cssClass)
		this._myElements.container.hide();
		
		body.appendChild(this._myElements.container);
		
		// Build the popin container, this wraps the actual popin content
		// and contains any other elements we need to control (like the close div)
		
		this._myElements.popinContainer = new Element('div');
		this._myElements.popinContainer.setStyle({display:'block'});
		this._myElements.popinContainer.addClassName('container');
		this._myElements.popinContainer.hide();
		
		
		// Build the close link if requested
		
		if(this.closeLink)
		{
			this._myElements.close = new Element('div').addClassName('close');
			this._myElements.closeLink = new Element('a',{href:'#'}).update(this.closeLinkText);
			this._myElements.closeLink.observe('mousedown',DFTools.cancelEvent);
			this._myElements.closeLink.observe('click',this._myCloseHandler);
			this._myElements.close.appendChild(this._myElements.closeLink);
			
			//this._myElements.close.setStyle({cursor: 'move'});
			
			this._myElements.popinContainer.appendChild(this._myElements.close);
		}
		
		// Build the actual popin
		
		this._myElements.popin = new Element('div');
		this._myElements.popin.addClassName('content');
		this._myElements.popinContainer.appendChild(this._myElements.popin);
		
		this.setContent(this.innerHTML);
		
		this._myElements.container.appendChild(this._myElements.popinContainer);

		// Fix IE 6:

		if(BrowserDetect.browser == 'Explorer' && BrowserDetect.version == 6)
		{
			this._myElements.container.setStyle({
				position: 'absolute'
			})
		}

		// If masking is turned on, build the mask and install onto page
		
		if(this.mask)
		{
			this._myElements.mask = new Element('div');
			this._myElements.mask.addClassName('mask');
			
			this._myElements.mask.setStyle({
				position: 'fixed'
			});
			
			this._myElements.mask.hide();
			
			this._myElements.container.appendChild(this._myElements.mask);
		
			this._myFixMaskInIE6();
		}
	},
	
	_myStartAutoShrink: function()
	{
		if(this._autoShrink)
		{
			this.shrinkToContent();
			if(this.visible())
				this._myAutoshrinker = new PeriodicalExecuter(this.shrinkToContent.bind(this),0.5);
		}
	},
	
	_myStopAutoShrink: function()
	{
		if(this._myAutoshrinker)
			this._myAutoshrinker.stop();
	},
	
	_myFixMaskInIE6: function()
	{
		DFTools.console.debug("com.digitalfruition.ThemeObject.Popin._myFixMaskInIE6() called on",this);

		var bg = this._myElements.mask.getStyle('backgroundImage');
		
		if(bg && BrowserDetect.browser == 'Explorer' && BrowserDetect.version == 6)
		{
			var bgUrl = bg.match(/^url\("?([^")]+)"?\).*$/);

			bgUrl = bgUrl[1] ? bgUrl[1] : '';

			this._myElements.mask.setStyle({
				backgroundImage: 'none',
				filter:'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src=\''+bgUrl+'\')',
				position: 'absolute',
				left: '0px',
				width: '100%',
				bottom: 'auto'
			});

			this._myElements.mask.style.setExpression('height',"(function(){ return document.body.clientHeight+'px'; })()");
		}
	},
	
	_mySetupObservers: function()
	{
		//window.resize();
	}
});




/**
 * Zomable Image Theme Object
 *
 * Theme object allowing a user to hover over an image and see a larger view of
 * the part of the image they are pointing at.
 */
com.digitalfruition.ThemeObject.ZoomImage = Class.create(com.digitalfruition.ThemeObject,
{


});




/* Media Viewer Theme Objects___________________________________________________ */

/**
 * Media Viewer (Theme Object) Base Class
 *
 * The Media Viewer Base Class has all the necessary components required to 
 * support various media viewer theme objects such as Slideshows and 
 * Photo Galleries
 */
com.digitalfruition.ThemeObject.MediaViewerBase = Class.create(com.digitalfruition.ThemeObject,
{
	
	initialize: function($super,config)
	{
		$super(config);
		
		DFTools.console.debug('com.digitalfruition.ThemeObject.MediaViewerBase: initialize called with',arguments)
		
		this.mySetupMediaViewer(config);
	},
	
	start: function()
	{
		DFTools.console.debug('MediaViewerBase: start() called');
		
		if(typeof(this.myRunLoop) != 'function')
		{
			DFTools.console.log("start() called on a media viewer but no run loop defined.",this);
			throw new Error("No run loop available");
		}
		
		if(!this.myPE)
		{
			this.myRunLoop();
			this.myPE = new PeriodicalExecuter(this.myRunLoop,this.delay);
		}
		
		this.myRunning = true;
	},
	
	running: function()
	{
		return this.myRunning && this.myPE; 
	},
	
	stop: function()
	{
		DFTools.console.debug('MediaViewerBase: stop() called');
		
		if(this.myPE) { this.myPE.stop(); }
		
		this.myPE = false;
		
		this.myRunning = false;
	},
	
	myMediaStreamReady: function()
	{
		return this.myMediaStream.loaded();
	},
	
	mySetupMediaViewer: function(options)
	{
		DFTools.console.debug('Setting up media viewer',this);
		
		this.queues = 0;
		
		this.myRunning = this.myPE = false;
		
		if(options.stream && options.stream instanceof com.digitalfruition.Media.MediaStream)
		{
			this.myMediaStream = options.stream;
		}
		else
		{
			var streamClass = com.digitalfruition.Media.MediaStream;
			
			if(options.streamClass && streamClass[options.streamClass] && streamClass[options.streamClass].superclass == streamClass) {
				streamClass = streamClass[options.streamClass];
			}
			
			var streamOptions = typeof(options.streamOptions) == 'object' ? options.streamOptions : {};
			
			if(typeof(options.streamOptions) == 'string') {
				streamOptions = options.streamOptions.toQueryParams();
			}
			
			this.myMediaStream = new streamClass(streamOptions);
		}
		
		if(typeof(this.myMediaStream.load) == 'function') {
			this.myMediaStream.load();
		}
		
		if(!this.myRunLoop)
		{
			this.myRunLoop = Prototype.emptyFunction;
		}
		
		DFTools.console.debug('Base Media Viewer setup complete on',this);
	},
	
	myBuildFadingViewer: function(container,fadeDuration,viewerWidth)
	{
		container = $(container);
		
		DFTools.console.debug('MediaViewerBase: myBuildFadingViewer() called on',this,'with',arguments);
		
		var viewer = {
			kind:'fading',
			fadeDuration: fadeDuration,
			effectQueueName: com.digitalfruition.ThemeObject.MediaViewerBase.prototype.effectQueuePrefix + com.digitalfruition.ThemeObject.MediaViewerBase.prototype.effectQueueCount++,
			containerDiv: container,
			pos: 0,
			bottomZ: 10
		};
		
		viewer.containerDiv.innerHTML='';
		viewer.slideSlots = $A();
		viewer.slideCSlots = $A();
		viewer.effects = $A();
		
		var divName,divCode;
		
		for(var i=0;i<2;i++)
		{
			viewer.slideSlots[i] = new Element('div').setStyle({
				position:'absolute',
				left:'0px',
				top:'0px',
				'text-align':'center',
				display:'none'
			});
			
			if(viewerWidth) {
				viewer.slideSlots[i].setStyle({width: viewerWidth+'px'});
			}
			
			//viewer.slideSlots[i].update("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"height:100%;width:100%;\"><tr><td style=\"height:100%;vertical-align:middle;text-align:center;\"><div></div></td></tr></table>");
			
			viewer.slideSlots[i].update("<div></div>");
			
			viewer.slideCSlots[i] = viewer.slideSlots[i].down('div');
			
			viewer.containerDiv.insert(viewer.slideSlots[i]);
		}
		
		viewer.pos = 0;
		viewer.active = 0;
		
		viewer.runs = 0;
		viewer.queues = 0;
		
		viewer.effects = new Array();
		
		this.myFillFadingSlideSlots(viewer);
		
		DFTools.console.debug('MediaViewerBase: myBuildFadingViewer() created a viewer:',viewer);
		
		return viewer;
	},
	
	myFillFadingSlideSlots: function(viewer)
	{
		DFTools.console.debug('MediaViewerBase: myFillFadingSlideSlots() called on',viewer);
		
		if(this.myMediaStreamReady())
		{
			try
			{
				viewer.slideCSlots[0].update(this.myMediaStream.getAt(viewer.pos++));
				viewer.slideCSlots[1].update(this.myMediaStream.getAt(viewer.pos++));
				
				DFTools.console.debug('MediaViewerBase: myFillFadingSlideSlots() added content to two slide slots',viewer.slideCSlots);
			}
			catch(e)
			{
				if(e instanceof RangeError)
				{
					// Not enough media in the stream!
					DFTools.console.error("com.digitalfruition.ThemeObject.MediaViewerBase.myFillFadingSlideSlots: Not enough media in stream!",this);
					
					//TODO: Some sort of handler should go here
				}
				else
				{
					// Something went wrong...
					DFTools.console.logException(e,"com.digitalfruition.ThemeObject.MediaViewerBase.myFillFadingSlideSlots")
					DFTools.console.log("MediaViewerBase object:",this);
					
					throw e;
				}
			}
		}
		
		DFTools.console.debug('MediaViewerBase: myFillFadingSlideSlots() finished');
	},
	
	mySetViewerSlideSlotStyle: function(viewer,propertyList)
	{
		Element.setStyle(viewer.slideSlots[0], propertyList);
		Element.setStyle(viewer.slideSlots[1], propertyList);
		return true;
	},
	
	myAdvanceFadingViewer: function(viewer,n)
	{	
		DFTools.console.debug('MediaViewerBase: myAdvanceFadingViewer() called with',arguments);
		
		var active = viewer.active;
		var inactive = (viewer.active+1)%2;
		var pos = viewer.pos;
		var next = (viewer.pos+1)%this.myMediaStream.length();
		
		var el;
		
		//viewer.slideCSlots[inactive].update(this.myMediaStream.getAt(next).element());
		//viewer.slideCSlots[inactive].update(this.myMediaStream.getAt(next).toHTML());
		
		if(el = viewer.slideCSlots[inactive].down()) {
			el.remove();
		}
		viewer.slideCSlots[inactive].appendChild(this.myMediaStream.getAt(next).element());
		
		viewer.slideSlots[inactive].style.zIndex = viewer.bottomZ+1;
		viewer.slideSlots[active].style.zIndex = viewer.bottomZ;
		
		if(viewer.effects[0])
			viewer.effects[0].cancel();
		if(viewer.effects[1])
			viewer.effects[1].cancel();

		viewer.effects[0] = new Effect.Fade(viewer.slideSlots[active], {duration: viewer.fadeDuration, queue: {scope: viewer.effectQueueName}});
		viewer.effects[1] = new Effect.Appear(viewer.slideSlots[inactive], {duration: viewer.fadeDuration, queue: {scope: viewer.effectQueueName}});
		
		DFTools.console.debug("SlideSlot ",inactive,": add content for slide ",next);
		DFTools.console.debug("SlideSlot ",inactive,": set Zindex ",viewer.bottomZ+1);
		DFTools.console.debug("SlideSlot ",active,": set Zindex ",viewer.bottomZ);
		DFTools.console.debug("SlideSlot ",active,": Fade");
		DFTools.console.debug("SlideSlot ",inactive,": Appear");
		
		viewer.active = inactive;
		viewer.pos = next;
		
		return true;
	},
	
	myClearEffectQueue: function(viewer)
	{
		DFTools.console.debug('MediaViewerBase: myClearEffectQueue() called with',arguments);
		
		if(viewer.effects[0])
			viewer.effects[0].cancel();
		if(viewer.effects[1])
			viewer.effects[1].cancel();
		
		var q = Effect.Queues.get(viewer.effectQueueName);
		q.effects.each(function(e) { e.cancel() } );
		
		DFTools.console.debug("myClearEffectQueue: ",q);
		DFTools.console.debug("myClearEffectQueue: length now ",q.length);
	},
	
	showFadingSlideshowUNUSED: function(i)
	{
		var active = this.active;
		var inactive = (this.active+1)%2;
		this.lastPos = this.pos;
		var next = i%this.slides.length;
		
		this._clearEffectQueue();
		
		
		this.slideSlots[active].style.opacity = '1.0';
		Element.show(this.slideSlots[active]);
		Element.hide(this.slideSlots[inactive]);
		this.slideSlots[inactive].style.opacity = '1.0';
		
		this.slideCSlots[inactive].innerHTML = this.slides[next].innerHTML;
		this.slideSlots[inactive].style.zIndex = this.bottomZ+1;
		this.slideSlots[active].style.zIndex = this.bottomZ;
		
		this.running = false;
		
		Element.show(this.slideSlots[active]);
		Element.hide(this.slideSlots[inactive]);
		
		if(this.effects[0])
			this.effects[0].cancel();
		if(this.effects[1])
			this.effects[1].cancel();

		this.effects[0] = new Effect.Fade(this.slideSlots[active], {duration: this.fadeDuration, queue: {scope: this.effectQueueName}});
		this.effects[1] = new Effect.Appear(this.slideSlots[inactive], {duration: this.fadeDuration, queue: {scope: this.effectQueueName}});
		
		if($(this.debug))
		{
			new Insertion.Top(this.debug,"--show("+i+")--<br>");
			new Insertion.Top(this.debug,"Cleared Effect Queue<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": show<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": hide<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": add content for slide "+next+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": set Zindex "+(this.bottomZ+1)+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": set Zindex "+(this.bottomZ)+"<br>");
			new Insertion.Top(this.debug,"SlideSlot "+active+": Fade<br>");
			new Insertion.Top(this.debug,"SlideSlot "+inactive+": Appear<br>");
			new Insertion.Top(this.debug,"<br>");
		}
		
		this.active = inactive;
		this.pos = next;
		
		if(this.onShow)
			this.onShow(next,this);
	}
});

com.digitalfruition.ThemeObject.MediaViewerBase.prototype.effectQueuePrefix = 'com.digitalfruition.ThemeObject.MediaViewer.queue.';
com.digitalfruition.ThemeObject.MediaViewerBase.prototype.effectQueueCount = 0;






/**
 * Zoomable Image Object
 *
 * An image which, when clicked, opens a popin with a full-size version of itself.
 */
com.digitalfruition.ThemeObject.ZoomableImage = Class.create(com.digitalfruition.ThemeObject.MediaViewerBase,
{
	initialize: function($super,options)
	{
		DFTools.console.debug('com.digitalfruition.ThemeObject.ZoomableImage: initialize() called with',arguments);
		
		$super(options);
		
		this.image = false;
		
		this.myElements = $A();
		
		if(options.element && (this.myElements['thumbnail'] = $(options.element)))
		{
			this.image = new com.digitalfruition.Media.Image({
				src: this.myElements['thumbnail'].src,
				alt: this.myElements['thumbnail'].alt,
				title: this.myElements['thumbnail'].title
			});
		}
		
		this.mySetupZoomDiv();
		
		if(this.enlargeLink)
			this.mySetupEnlargeLink();
		
		this.mySetupObservers();
	},
	
	defaultOptions: function()
	{
		return {
			autostart: true,
			makeContainerPositioned: true,
			delay: 6,
			fadeDuration: 3,
			container: false,
			mask: true,
			closeLink: false,
			enlargeLink: false,
			enlargeLinkText: 'Click to Enlarge'
		};
	},
	
	show: function()
	{
		DFTools.console.log("DFThemeObject.ZoomableImage.show() called",this);
		
		this.myZoomPopin.autoShrink(true);
		
		this.myZoomPopin.show();
	},
	
	hide: function()
	{
		//this.myElements['zoom'].hide();
		this.myZoomPopin.hide();
	},
	
	start: function($super)
	{
		DFTools.console.debug('com.digitalfruition.ThemeObject.ZoomableImage: start() called on',this);
	},
	
	mySetupMediaViewer: function($super)
	{
		
	},
	
	mySetupZoomDiv: function()
	{
		//this.myElements['zoom'] = new Element('div').addClassName('ZoomableImage').addClassName('ZoomedImage');
		//this.myElements['zoom'].appendChild(this.image.element());
		//this.myElements['zoom'].hide();
		
		
		this.myZoomPopin = new com.digitalfruition.ThemeObject.Popin({
			mask:		this.mask,
			closeLink:	this.closeLink,
			cssClass:	'zoomable'
		});
		
		this.myZoomPopin.element().appendChild(this.image.element());
	},
	
	mySetupEnlargeLink: function()
	{
		var link = new Element('a',{href:'#'}).update(this.enlargeLinkText);
		
		this.myElements.enlargeLink = link.wrap(new Element('div').addClassName('enlarge'))
		
		this._myEnlargeLinkHandler = function(e)
		{
			this.show();
			e.stop();
		}
		
		this._myEnlargeLinkHandler=this._myEnlargeLinkHandler.bindAsEventListener(this);
		
		link.observe('click',this._myEnlargeLinkHandler);
		
		this.myElements['thumbnail'].insert({after:this.myElements.enlargeLink});
	},
	
	mySetupObservers: function()
	{
		this.myElements['thumbnail'].observe('click',this.show.bindAsEventListener(this));
		
		//this.myElements['zoom'].observe('click',this.hide.bindAsEventListener(this));
		this.myZoomPopin.element().observe('click',this.hide.bindAsEventListener(this));
	},
	
	myMediaStreamLoadHandler: function()
	{
		
	}
});



/**
 * Fading Slide Show Theme Object
 *
 * Add description here
 */
com.digitalfruition.ThemeObject.FadingSlideshow = Class.create(com.digitalfruition.ThemeObject.MediaViewerBase,
{
	initialize: function($super,options)
	{
		DFTools.console.debug('com.digitalfruition.ThemeObject.FadingSlideShow: initialize() called with',arguments);
		
		$super(options);
		
		if(this.makeContainerPositioned && $(this.container)) {
			$(this.container).makePositioned();
		}
		
		if(this.myMediaStream.loaded())
		{
			this.myMediaStreamLoadHandler();
		}
		
		this.myMediaStream.observe('load',this.myMediaStreamLoadHandler.bind(this));
	},
	
	defaultOptions: function()
	{
		return {
			autostart: true,
			makeContainerPositioned: true,
			delay: 6,
			fadeDuration: 3,
			container: false
		};
	},
	
	start: function($super)
	{
		DFTools.console.debug('com.digitalfruition.ThemeObject.FadingSlideShow: start() called on',this);
		
		if(!this.myMediaStream.loaded())
		{
			this.autostart = true;
		}
		else
		{
			$super();
		}
	},
	
	myMediaStreamLoadHandler: function()
	{
		this.container = $(this.container);
		
		if(this.container)
		{
			DFTools.console.debug('com.digitalfruition.ThemeObject.FadingSlideShow.myMediaStreamLoadHandler: called on',this,'with',arguments);
			
			var dims = this.container.getDimensions();
			
			this.myMediaStream.each(function(media){
				media.defaultWidth = dims.width;
				media.defaultHeight = dims.height;
				media.refresh();
			})
			
			this.myFadingViewer = this.myBuildFadingViewer(this.container,this.fadeDuration);
			
			this.myRunLoop = this.myAdvanceFadingViewer.bind(this,this.myFadingViewer,dims.width);
			
			if(this.autostart)
			{
				this.start();
			}
		}
	}
});



/**
 * Site Edge Theme Object
 *
 * Theme object for plaing an "edge" or "border" around the entire browser
 * window, used for creating the effect of a frame.
 */
com.digitalfruition.ThemeObject.SiteEdge = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function(options)
	{
		options = $H(options);
		
		this.divs = $H({top:		false,
						 right:		false,
						 bottom:	false,
						 left:		false});
		this.divTOs = $H({top:			false,
						   right:		false,
						   bottom:		false,
						   left:		false});
		
		this._setOptions(options);
		
		//var thisObj = this;
		//this._initialized = false;
		//this._initfunc = function(e) { window.setTimeout(function(){thisObj._initializeAfterLoaded();},500); };
		
		//Event.observe(window, 'load', this._initializeAfterLoaded.bindAsEventListener(this));
		
		
		if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) >= 7)
			this._initializeAfterLoaded();
		else
			FastInit.addOnLoad(this._initializeAfterLoaded.bindAsEventListener(this));
	},
	
	_initializeAfterLoaded: function(e)
	{
		this._createDivs();
		
		if(this.addBodyMargin)
			this._addBodyMargin();
			
		this._setContentSize();
		Event.observe(window,'resize',this._setContentSize.bind(this));
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			cssClassPrefix:		'SiteEdge',
			
			addBodyMargin:		true,
			extraBodyMargin:	{top: 0, right: 0, bottom: 0, left: 0},
			divContents:		{top: '&nbsp;', right: '&nbsp;', bottom: '&nbsp;', left: '&nbsp;'},
			
			contentWidth:		undefined,
			contentHeight:		undefined,
			
			debugDiv:			false,
			backgroundOffset:	false,
			
			callbacks:			{}
		});
	},
	
	_setOptions: function(options)
	{
		var defaults = this.defaultOptions();
		
		var thisObj = this;
		
		defaults.each(function(v){ thisObj[v.key] = (typeof(options[v.key]) == 'undefined') ? v.value : options[v.key] });
		
		this.callbacks = $H(this.callbacks);
		
		var divHashOpts = $A(['extraBodyMargin','divContents'])
		
		divHashOpts.each(function(opt)
		{
			this[opt] = $H(this[opt]);
		
			this.divs.keys().each(function(k)
			{
				if(typeof(this[opt][k]) == 'undefined') this[opt][k]=defaults[opt][k];
			}.bind(this));
		}.bind(this));
	},
	
	_createDivs: function()
	{
		var html,className, body = $(document.body);
		
		var sides = this.divs.keys();
		
		sides.each(function(side)
		{
			className = this.cssClassPrefix+'_'+side;
			html = '<div class="'+this.cssClassPrefix+' '+className+'">'+this.divContents[side]+'</div>';
			//new Insertion.Top(body,html);
			this.divTOs[side] = new FixedDiv({cssClass: this.cssClassPrefix+' '+className, innerHTML: this.divContents[side]});
			this.divs[side] = body.down('.'+className);
			this._setDivStyle(this.divs[side],side);
		}.bind(this));
	},
	
	_setDivStyle: function(div,side)
	{
		switch(side)
		{
			case 'top':
				div.setStyle({
					width: '100%'
				});
				this.divTOs[side].setPos('left',0);
				this.divTOs[side].setPos('top',0);
				break;
				
			case 'right':
				div.setStyle({
					top: '0px',
					right: '0px',
					height: '100%'
				});
				this.divTOs[side].setPos('right',0);
				this.divTOs[side].setPos('top',0);
				break;
			
			case 'bottom':
				div.setStyle({
					bottom: '0px',
					left: '0px',
					width: '100%'
				});
				this.divTOs[side].setPos('left',0);
				this.divTOs[side].setPos('bottom',0);
				break;
			
			case 'left':
				div.setStyle({
					top: '0px',
					left: '0px',
					height: '100%'
				});
				this.divTOs[side].setPos('left',0);
				this.divTOs[side].setPos('top',0);
				break;
		}
		
		if(this.debugDiv)
		{
			new Insertion.Bottom(this.debugDiv,'<p>'+side+' div style: position:'+div.getStyle('position')+', top:'+div.getStyle('top')+'</p>');
		}
	},
	
	_addBodyMargin: function()
	{
		var size, body, style;
		
		//alert('In _addBodyMargin');
		
		body = $(document.body);
		style = $H({});
		
		if(!body) return false;
		
		this.divs.each(function(div)
		{
			if(div.value)
			{
				size = false;
				cssName = 'margin'+div.key.charAt(0).toUpperCase() + div.key.substring(1);
				
				switch(div.key)
				{
					case 'top':
					case 'bottom': size = div.value.getHeight(); break;
						
					case 'top':
					case 'bottom': size = div.value.getWidth(); break;
				}
				
				style[cssName] = (size+this.extraBodyMargin[div.key])+'px';
			}
		}.bind(this));
		
		body.setStyle(style);
	},
	
	_setContentSize: function()
	{
		var size, diff, body, style;
		
		//alert('In _setContentSize');
		
		body = $(document.body);
		
		if(!body) return false;
		
		size = Try.these(
				function() { if(BrowserDetect.browser == 'Safari') throw 'next';
								return [document.documentElement.clientWidth,document.documentElement.clientHeight]; },
				function() { return [window.innerWidth,window.innerHeight]; },
				function() { return [document.body.clientWidth,document.body.clientHeight]; });
		
		if(typeof(this.contentWidth) != 'undefined')
		{
			diff = size[0] - this.contentWidth;
			
			style = {width: (diff/2)+'px'};
			this.divs.left.setStyle(style);
			this.divs.right.setStyle(style);
			
			
			if(this.debugDiv)
			{
				new Insertion.Bottom(this.debugDiv,'<p>Resize: window width:'+size[0]+', diff:'+diff+'</p>');
			}
		}
		
		//alert('set widths');
		
		if(typeof(this.contentHeight) != 'undefined')
		{
			diff = size[1] - this.contentHeight;
			style = {paddingTop: Math.max(Math.floor(diff/2),0)+'px'};
			//alert('set style: '+style.paddingTop);
			this.divs.top.setStyle(style);
			//alert('test');
			style = {marginTop: Math.floor(diff/2)+'px',marginBottom: (diff/2)+'px'};
			this.divs.left.setStyle(style);
			this.divs.right.setStyle(style);
			style = {paddingBottom: Math.max(Math.floor(diff/2),0)+'px'};
			this.divs.bottom.setStyle(style);
			
			
			
			if(this.debugDiv)
			{
				new Insertion.Bottom(this.debugDiv,'<p>Resize: window height:'+size[1]+', diff:'+diff+'</p>');
			}
			
			if(this.backgroundOffset)
			{
				var offset = (size[1]-this.divs.bottom.getHeight()+this.backgroundOffset)*-1;
				this.divs.bottom.setStyle({backgroundPosition: "0px " + (offset)+ "px"}); 
			}
		}
		
		this._addBodyMargin();
		
		if(!this._haveSetContentSize)
		{
			// Setting the size for the first time often adds scroll bars. So we need
			// to recalculate everything again!
			
			this._haveSetContentSize=true;
			this._setContentSize();
		}
		
		if(typeof(this.callbacks.setContentSize) == 'function')
			this.callbacks.setContentSize();
	}
	
});






/**
 * Custom Scrollbar Theme Object
 *
 * Theme Object used for creating custom srollbars for the content of a page.
 */
com.digitalfruition.ThemeObject.CustomScrollbar = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function(containerDiv,options)
	{
		this.containerDiv = $(containerDiv);
		
		options = $H(options);
		
		this._setOptions(options);
		
		//if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) >= 7)
		//	this._initializeAfterLoaded();
		//else
		//	FastInit.addOnLoad(this._initializeAfterLoaded.bindAsEventListener(this));
		
		Event.observe(window,'load',this._initializeAfterLoaded.bindAsEventListener(this));
	},
	
	_initializeAfterLoaded: function(e)
	{
		this._createDivs();
		
		if(this.addBodyMargin)
			this._addBodyMargin();
			
		this._setContentSize();
		Event.observe(window,'resize',this._setContentSize.bind(this));
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			cssClassPrefix:		'customScrollbar',
			minThumbHeight:		10,
			maxThumbHeight:		9999,
			
			scrollbarWidth : 10,
			scrollbarMargin : 5,
			arrowSpeed : 20,
			wheelSpeed : 20,
			showArrows : false,
			arrowSize : 0,
			animateTo : false,
			dragMinHeight : 10,
			dragMaxHeight : 99999,
			animateInterval : 100,
			animateStep: 3,
			maintainPosition: true,
			
			arrowImages: {up: '/images/up.gif', down: '/images/down.gif'}
		});
	},
	
	
	updateThumbHeight: function()
	{	
		this._contentHeight = this._contentDiv.getHeight();
		this._containerHeight = this.containerDiv.getHeight();
		this._trothHeight = this._troth.getHeight();
		
		this._thumbHeight = 0;
		
		if(this._contentHeight < this._containerHeight)
		{
			this._scrollDiv.hide();
			this.enabled = false;
			return true;
		}
		else
		{
			this.enabled = true;
			this._scrollDiv.show();
		}
		
		if(this._contentHeight)
			this._thumbHeight = Math.round((this._trothHeight * this._containerHeight) / this._contentHeight);
		
		if(this._thumbHeight < this.minThumbHeight)
			this._thumbHeight = this.minThumbHeight;
			
		if(this._thumbHeight > this.maxThumbHeight)
			this._thumbHeight = this.maxThumbHeight;
		
		this._thumb.setStyle({
			height: this._thumbHeight +"px"
		});
		
		this.setContainerStyle();
	},
		
	setContainerStyle: function()
	{
		this.containerDiv.setStyle({
			overflow: 'hidden'
		});
	},
	
	_setOptions: function(options)
	{
		var defaults = this.defaultOptions();
		
		var thisObj = this;
		
		defaults.each(function(v){ thisObj[v.key] = (typeof(options.get(v.key)) == 'undefined') ? v.value : options.get(v.key) });
	},
	
	_initializeAfterLoaded: function()
	{
		// Make sure the container is positioned so we can absolutly position within it.
		Element.makePositioned(this.containerDiv);
		
		// The existing container will now contain two DIVs, one for the content and one for the scroll bar
		// We will create those divs now:
		
		this._createContentDiv();
		this._createScrollDiv();
		this._createThumb();
		this.setContainerStyle();
		this._addEventListeners();
		
		window.setTimeout(this.updateThumbHeight.bind(this),500);
	},
	
	_createContentDiv: function()
	{
		// create a new div to hold the content:
		
		this._contentDiv = $(document.createElement("div"));
		this._contentDiv.hide();
		this._contentDiv.addClassName(this.cssClassPrefix + "_content");
		this._setContentBoxStyle(this._contentDiv);
		this._contentDiv.update(this.containerDiv.innerHTML);
		this.containerDiv.update('');
		this.containerDiv.appendChild(this._contentDiv);
		this._contentDiv.show();
	},
	
	_createScrollDiv: function()
	{
		// create a new div to hold the scroll bar:
		
		this._scrollDiv = $(document.createElement("div"));
		this._scrollDiv.hide();
		this._scrollDiv.addClassName(this.cssClassPrefix + "_scrollDiv");
		this._setScrollDivStyle(this._scrollDiv);
		this.containerDiv.appendChild(this._scrollDiv);
		
		// Inside that div we need an up arrow, a down arrow, and a troth for the thumb
		this._arrows = {};
		this._arrows.up = $(document.createElement("div"));
		this._arrows.down = $(document.createElement("div"));
		this._troth = $(document.createElement("div"));
		this._troth.addClassName(this.cssClassPrefix + "_troth");
		this._scrollDiv.appendChild(this._arrows.up);
		this._scrollDiv.appendChild(this._troth);
		this._scrollDiv.appendChild(this._arrows.down);
		this._updateArrows();
		
		// Now show the scrollbar div
		// We have to show it now because otherwise we can't set our troth height
		this._scrollDiv.show();
		
		// We have our arrows so setup our troth
		
		var trothHeight, upHeight, downHeight;
		
		trothHeight = this.containerDiv.getHeight();
		upHeight = this._arrows.up.getHeight();
		downHeight = this._arrows.down.getHeight();
		
		trothHeight -= upHeight + downHeight;
		
		this._troth.setStyle({
			position: 'absolute',
			top: upHeight + 'px',
			left: '0px',
			height: trothHeight + 'px',
			width: this.scrollbarWidth + 'px'
		})
	},
	
	_createThumb: function()
	{
		if(this._thumb)
			this._thumb.remove();
		
		this._thumb = $(document.createElement("div"));
		this._thumb.hide();
		this._thumb.addClassName(this.cssClassPrefix+"_thumb");
		this._troth.appendChild(this._thumb);
		this._thumb.show();
		this.updateThumbHeight();
		
		var change = this._slide.bind(this);
		
		// Now add the behavior to the thumb...
		var sliderOpts = {
			axis:	'vertical',
			range: $R(0,this._contentHeight - this._containerHeight),
			onSlide: change,
			onChange: change
		}
		this._slider = new Control.Slider(this._thumb, this._troth, sliderOpts)
	},
	
	_setContentBoxStyle: function(el)
	{
		var dims;
		
		dims = this.containerDiv.getDimensions();
		
		dims.width -= this.scrollbarWidth;
		
		el.setStyle({
			position: 'absolute',
			left: '0px',
			top: '0px',
			width: dims.width + 'px'
		});
	},
	
	_setScrollDivStyle: function(el)
	{	
		el.setStyle({
			position: 'absolute',
			right: '0px',
			top: '0px',
			height: '100%',
			width: this.scrollbarWidth + 'px'
		});
	},
	
	_updateArrows: function()
	{
		var arrows = $H(this._arrows).keys();
		
		arrows.each(function(a)
		{
			var style = {
				position: 'absolute',
				left: '0px'
			};
			
			switch(a)
			{
				case 'up':		style.top = '0px'; break;
				case 'down':	style.bottom = '0px'; break;
			}
			
			this._arrows[a].setStyle(style);
			this._arrows[a].addClassName(this.cssClassPrefix + "_arrow");
			this._arrows[a].addClassName(this.cssClassPrefix + "_"+a+"Arrow");
			this._arrows[a].update("<a href=\"#\" onclick=\"return false;\"><img src=\""+this.arrowImages[a]+"\" border=\"0\"></a>");
			
		}.bind(this));
	},
	
	scroll: function(n)
	{
		if(!this.enabled)
			return false;
		
		//new Effect.Move(this._thumb, {x: 0, y: -1 * n, mode: 'relative'});
		this._slider.setValue(this._slider.value + n);
	},
	
	_slide: function(n)
	{
		if(!this.enabled)
			return false;
		
		var pos = -1 * n;
		this._contentDiv.setStyle({
			top: pos + 'px'
		});
	},
	
	_addEventListeners: function()
	{
		var func;
		
		func = function(e)
		{
			var dist = this.wheelSpeed * Event.wheel(e) * -1;
			this.scroll(dist);
		}.bind(this);
		
		Event.observe(this.containerDiv, "mousewheel", func, false);
		Event.observe(this.containerDiv, "DOMMouseScroll", func, false); // Firefox
		
		
		func = function(e){this.scroll(this.arrowSpeed * -1);}.bind(this);		
		Event.observe(this._arrows.up, "mousedown", func, false);
		
		func = function(e){this.scroll(this.arrowSpeed);}.bind(this);
		Event.observe(this._arrows.down, "mousedown", func, false);
	}
	
});





/**
 * Dropdown menu theme object
 *
 * Theme object used for creating a dropdown menu, for navigation or UI.
 */
com.digitalfruition.ThemeObject.DropdownMenu = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		/********** Private Members **********/
		var myItems = $A();
		
		/********** Privileged Methods **********/
		
		this.itemCount = function()
		{
			return myItems.length;
		};
		
		this.getItem = function(n)
		{
			return myItems[n];
		};
		
		this.setItem = function(n,newItem)
		{
			return myItems[n] = newItem;
		};
		
		this.addItem = function(menuItem)
		{
			myItems.push(menuItem);
		};
		
		this.removeItem = function(n)
		{
			myItems[n] = null;
		};
		
		this.removeItem = function(n)
		{
			myItems[n] = null;
		};
		
		this._each = myItems._each.bind(myItems);
		
		
		/********** Initialize **********/
		
		$super(options);
		
		DFTools.console.debug('com.digitalfruition.ThemeObject.DropdownMenu: initialize() called on',this,'with',options);
		
		this.rendered = false;
		
		this.visible = false;
		
		this.showing = this.hiding = this.locked = false;
		
		this._els = {};
		
		FastInit.addOnLoad(this._initializeAfterLoaded.bindAsEventListener(this));
		//Event.observe(window,'load',this._initializeAfterLoaded.bindAsEventListener(this));
	}
});

Object.extend(com.digitalfruition.ThemeObject.DropdownMenu.prototype, Enumerable);
Object.extend(com.digitalfruition.ThemeObject.DropdownMenu.prototype,
{	
	MenuItem: function(opts)
	{
		this.name = opts.name ? opts.name : 'menuItem_'+com.digitalfruition.ThemeObject.DropdownMenu.menuItemCount++;
		this.text = opts.text ? opts.text : '';
		this.href = opts.href ? opts.href : '#';
		
		this.handler = typeof(opts.handler) == 'function' ? opts.handler : false;
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			cssPrefix:		'dropdown_',
			source:			false,
			applyTo:		false,
			xOffset:		0,
			yOffset:		0,
			hideDelay:		500,
			hideOthers:		true,
			delayedRepositionings:	[1,5]
		});
	},
	
	
	/********** Public Properties **********/
	
	domIdPrefix: 'DFThemeObject_DropdownMenu_',
	
	/********** Public Methods **********/
	
	buildMenu: function()
	{
		if(!this.rendered)
		{
			this._createMenu();
			this.rendered = true;
		}
	},
	
	show: function()
	{
		if(!this.locked)
		{
			this.showing = true;
			this.buildMenu();
			this._els.container.show();
			this.visible = true;
			
			//this._fixAbsoluteMenuItemDivs();
			
			if(this.hideOthers) {
				com.digitalfruition.ThemeObject.DropdownMenu.menus.invoke('_hide');
			}
			
			this.showing = false;
		}
	},
	
	hide: function()
	{
		this.hiding = true;
		
		window.setTimeout(this._hide.bind(this),this.hideDelay);
	},
	
	apply: function(el)
	{
		this._stopObserving();
		
		if(!el)
			el = this.applyTo;
		
		this.applyTo = $(el)
		
		if(!this.applyTo)
		{
			try{
				this.applyTo = $$(el)[0];
			}catch(e){}
		}
		
		this.reposition();
		
		this._startObserving();
	},
	
	reposition: function()
	{
		var el;
		
		DFTools.console.debug('com.digitalfruition.ThemeObject.DropdownMenu: reposition() called on',this);
		
		if(this.applyTo && (el=$(this.applyTo)))
		{
			var yOffset = parseInt(this.yOffset) || 0;
			var xOffset = parseInt(this.xOffset) || 0;
			
			yOffset += el.getHeight();
			
			try {
				this._els.container.absolutize();
				Element.clonePosition(this._els.container,el,{setLeft:true,setTop:true,setWidth:false,setHeight:false,offsetLeft:xOffset,offsetTop:yOffset});
			} catch(e) { }
			try {
				this._els.container.absolutize();
				Element.clonePosition(this._els.container,el,{setLeft:true,setTop:true,setWidth:false,setHeight:false,offsetLeft:xOffset,offsetTop:yOffset});
			} catch(e) { }
		}
	},
	
	/********** Personal Methods **********/
	
	_initializeAfterLoaded: function(e)
	{
		DFTools.console.debug('com.digitalfruition.ThemeObject.DropdownMenu: _initializeAfterLoaded() called on',this);
		
		this._createDivs();
		
		
		this._loadMenu();
		
		this.buildMenu();
		
		com.digitalfruition.ThemeObject.DropdownMenu.menus.push(this);
		
		this.apply();
		
		var repositionBound = this.reposition.bind(this);
		
		
		if(this.delayedRepositionings)
		{
			if(!(this.delayedRepositionings instanceof Array)) {
				this.delayedRepositionings = [delayedRepositionings];
			}
			
			
			$A(this.delayedRepositionings).each(function(delay)
			{
				delay = parseFloat(delay) || 0.5;
				repositionBound.delay(delay);
			});
		}
		
		Event.observe(window,'resize',function(){ repositionBound.delay(0.5); });
	},
	
	_createDivs: function()
	{
		DFTools.console.debug('com.digitalfruition.ThemeObject.DropdownMenu#_createDivs() called on',this);
		
		// Create our container, outer and inner DIVs:
		this._els.container = new Element('div',{'class':this.cssPrefix+'container'}).setStyle({position:'absolute',overflow:'visible'});
		this._els.outer = new Element('div',{'class':this.cssPrefix+'outer'});
		this._els.inner = new Element('div',{'class':this.cssPrefix+'inner'});
		
		this._els.outer.appendChild(new Element('div'));
		
		this._els.outer.down().appendChild(this._els.inner);
		this._els.container.appendChild(this._els.outer);
		
		this._els.container.hide();
		
		document.body.appendChild(this._els.container);
	},
	
	_loadMenu: function()
	{
		var source = this.source ? this.source : false;
		
		DFTools.console.debug('com.digitalfruition.ThemeObject.DropdownMenu#_loadMenu() called on',this,', loading from:',source);
		
		if(typeof(source) == 'string')
		{
			// We're being asked to load via AJAX.
		}
		else if(typeof(source) == 'object')
		{
			// We're being asked to load via an array.
			source = $A(source);
			source.each(function(item)
			{
				this.addItem(new this.MenuItem(item));
			},this);
		}
	},
	
	_createMenu: function()
	{
		DFTools.console.debug('com.digitalfruition.ThemeObject.DropdownMenu: _createMenu() called on',this);
		
		// Create our menu OL:
		this._els.list = new Element('ol');
		
		//this._els.absolutes = new Element('div').absolutize();
		
		this.each(function(menuItem)
		{
			var link = new Element('a',{href:menuItem.href}).update(menuItem.text);
			//var link2 = new Element('a',{href:menuItem.href}).update(menuItem.text);
			
			//var div = new Element('div',{id: this.domIdPrefix+menuItem.name});
			//div.appendChild(link);
			
			var item = new Element('li');
			item.appendChild(link);
			
			//this._els.absolutes.appendChild(div);
			
			if(menuItem.handler)
			{
				var f = menuItem.handler.bindAsEventHandler(menuItem,this);
				link.observe('click',f);
				item.observe('click',f);
			}
			else
			{
				item.observe('click',function(){window.location=menuItem.href;});
			}
			
			this._els.list.appendChild(item);
		},this);
		
		this._els.inner.appendChild(this._els.list);
		//this._els.inner.appendChild(this._els.absolutes);
	},
	
	_fixAbsoluteMenuItemDivs: function()
	{
		var lis = this._els.list.select('li');
		var divs = this._els.absolutes.select('li');
		
		divs.each(function(li,n)
		{
			div.clonePosition(lis[n]);
			divs[n].show();
		});
	},
	
	_stopObserving: function() {},
	
	coordsWithin: function(coords,el,pad)
	{
		if(!el)
			el = this._els['container'];
		
		if(!pad)
			pad = 0;
		
		var rect = el.cumulativeOffset();
		var d = el.getDimensions();
		
		rect[0]+=pad;
		rect[1]+=pad;
		
		rect.push(rect[0]+d.width-pad,rect[1]+d.height-pad);
		
		return (coords.x >= rect[0] && coords.x <= rect[2] &&
				coords.y >= rect[1] && coords.y <= rect[3])
	},
	
	_checkForUnlockAndHide: function()
	{
		
	},
	
	_startObserving: function()
	{
		var observers = $H();
		
		var btnMouseOver = function()
		{
			this.show();
			this.locked = true;
		}.bindAsEventListener(this);
		
		var btnMouseOut = function()
		{
			this.locked = false;
			this.hide();
		}.bindAsEventListener(this);
		
		
		observers.set('applyTo_mouseover',btnMouseOver);
		observers.set('applyTo_mouseout',btnMouseOut);
		
		
		
		var mouseOver = function(e)
		{
			var within = this.coordsWithin(e.pointer(),this._els.inner);
			DFTools.console.debug('DropdownMenu mouseover on ',e.element(),', within: ',within);
			if(within) { this.locked = true; }
		}.bindAsEventListener(this);
		
		var mouseOut = function(e)
		{
			var within = this.coordsWithin(e.pointer(),this._els.inner,0);
			if(!within)
			{
				this.locked = false;
				this.hide();
			}
		}.bindAsEventListener(this);
		
		observers.set('outer_mouseover',mouseOver);
		observers.set('outer_mouseout',mouseOut);
		
		$(document.body).observe('mousedown',mouseOut);
		
		
		observers.each(function(pair)
		{
			var parts = pair.key.split("_",2);
			
			var el = parts[0] == 'applyTo' ? $(this.applyTo) : this._els[parts[0]];
			
			if(el)
				el.observe(parts[1],pair.value);
			
		},this);
		
		this._stopObserving = function()
		{
			observers.each(function(pair)
			{
				var parts = pair.key.split("_",2);
				
				var el = parts[0] == 'applyTo' ? $(this.applyTo) : this._els[parts[0]];
				
				if(el)
					el.stopObserving(parts[1],pair.value);
				
			},this);
		}.bind(this);
	},
	
	_hide: function()
	{
		if(!this.showing && !this.locked)
		{
			this._els.container.hide();
			this.visible = false;
		}	
	}
});

com.digitalfruition.ThemeObject.DropdownMenu.menus = $A();
com.digitalfruition.ThemeObject.DropdownMenu.menuItemCount = 0;


com.digitalfruition.DFCountdown = Class.create(
{
	initialize: function(date,div,options)
	{
		this.date = new Date(date);
		this.div = $(div);
		
		this.setup();
		
		if(this.div)
		{
			this._p = new PeriodicalExecuter(this.update.bind(this), 1);
		}
	},
	
	setup: function()
	{
		this.units = $H({});
		this.units.second = 1000;
		this.units.minute = this.units.second*60;
		this.units.hour = this.units.minute*60;
		this.units.day = this.units.hour*24;
	},
	
	date:	false,
	div:	false,
	_p:		false,
	sep:	'&nbsp;:&nbsp;',
	
	update: function()
	{
		var now = new Date();
		var diff = this.date.getTime() - now.getTime();
		
		var diff_w = diff;
		
		var units = $H({});
		
		units.day = Math.floor(diff_w/this.units.day);
		diff_w -= units.day * this.units.day;
		units.hour = Math.floor(diff_w/this.units.hour);
		diff_w -= units.hour * this.units.hour;
		units.minute = Math.floor(diff_w/this.units.minute);
		diff_w -= units.minute * this.units.minute;
		units.second = Math.floor(diff_w/this.units.second);
		
		this.countdown = "";
		
		units.each(function(u)
		{
			var num = (u.value<10?'0':'') + u.value;
			
			if(this.countdown.length || u.value)
				this.countdown += num + this.sep;	
		}.bind(this));
		
		if(this.countdown.length)
			this.countdown = this.countdown.substr(0,this.countdown.length - this.sep.length);
		
		this.div.update(this.countdown);
	}
	
});

com.digitalfruition.ColorPicker = Class.create(
{
	initialize: function(options)
	{
		options = $H(options);
		
		this.loaded = false;
		
		this._setOptions(options);
		
		$(document.body).insert(new Element('link',{href:"/_skel/dfstdlib/stylesheets/colorpicker.css",rel:"stylesheet",type:"text/css"}));
	},
	
	_maxValue:{'h':360,'s':100,'v':100},
	HSV:{0:360,1:100,2:100},
	_hSV:165,
	_wSV:162,
	_hH:163,
	_slideHSV:{0:360,1:100,2:100},
	_zINDEX:15,
	_stop:1,
	_tX:0,
	_tY:0,
	_oo:0,
	
	defaultOptions: function()
	{
		return $H(
		{
			cssClassPrefix: 'colorPicker_',
			
			x: 0,
			y: 0,
			z: 999,
			
			containerDiv: false
		});
	},
	
	load: function()
	{
		if(!this.loaded)
		{
			this._createElement();
			this._addEventListeners();
			
			this._loadSV();
	
			this._parentDiv.down('div.'+this.cssClassPrefix+'SVslide').setStyle({top:(80/100*170-7)+'px'});
			this._HSVupdate([0,0,20]);
			
			this.loaded=true;
		}
	},
	
	show: function()
	{
		this.load();
		this.containerDiv.show();
	},
	
	hide: function()
	{
		this.load();
		this.containerDiv.hide();
	},
	
	_setOptions: function(options)
	{
		var defaults = this.defaultOptions();
		
		var thisObj = this;
		
		defaults.each(function(v){ thisObj[v.key] = (typeof(options.get(v.key)) == 'undefined') ? v.value : optionsget(v.key) });
	},
	
	_createElement: function()
	{
		if(!this.containerDiv)
		{
			this.containerDiv = new Element('div', {'class': this.cssClassPrefix+'container', 'style':"position:absolute;left:"+this.x+"px;top:"+this.y+"px;z-index:"+this.z+";display:none;"});
			$(document.body).insert(this.containerDiv);
		}
		
		this._parentDiv = new Element('div', {'class': this.cssClassPrefix+'parent'});
		
		this._parentDiv.appendChild(new Element('div',{'class':this.cssClassPrefix+'plugCUR'}) );
		this._parentDiv.appendChild(new Element('div',{'class':this.cssClassPrefix+'plugHEX'}).update('FFFFFF') );
		this._parentDiv.appendChild(new Element('div',{'class':this.cssClassPrefix+'plugCLOSE'}).update('X') );
		
		this._parentDiv.insert("<br>");
		
		this._parentDiv.appendChild(new Element('div',{'class':this.cssClassPrefix+'SV',title:"Saturation + Value"}).insert(
										new Element('div',{'class':this.cssClassPrefix+'SVslide',style:"top:-4px;left:-4px;"}).update("<br>") 
									));
									
		this._parentDiv.appendChild(new Element('div',{'class':this.cssClassPrefix+'H',title:"Hue"}).insert(
										new Element('div',{'class':this.cssClassPrefix+'Hslide',style:"top:-7px;left:-8px;"}).update("<br>") 
									).insert(
										new Element('div',{'class':this.cssClassPrefix+'Hmodel'})
									));
									
		this.containerDiv.insert(this._parentDiv);
	},

	
	_addEventListeners: function()
	{
		Event.observe(this._parentDiv, "mousedown", this._HSVslide.bindAsEventListener(this,'drag',this._parentDiv), false);
		
		Event.observe(this._parentDiv.down('div.'+this.cssClassPrefix+'plugHEX'), "mousedown", this._plugHEXmousedown.bindAsEventListener(this), false);
		
		Event.observe(this._parentDiv.down('div.'+this.cssClassPrefix+'plugCLOSE'), "click", this.hide.bindAsEventListener(this), false);
		
		Event.observe(this._parentDiv.down('div.'+this.cssClassPrefix+'SV'), "click", this._HSVslide.bindAsEventListener(this,'SVslide',this._parentDiv), false);
		
		Event.observe(this._parentDiv.down('div.'+this.cssClassPrefix+'H'), "click", this._HSVslide.bindAsEventListener(this,'Hslide',this._parentDiv), false);
		
	},
	
	_plugHEXmousedown: function(e)
	{
		this._stop=0;
		setTimeout(function(){this._stop=1}.bind(this),100);
	},
	
	_absPos: function(o) 
	{
		var r={x:o.offsetLeft,y:o.offsetTop}; if(o.offsetParent) { var v=this._absPos(o.offsetParent); r.x+=v.x; r.y+=v.y; } return(r);
	},
	
	_agent: function(v)
	{
		return(Math.max(navigator.userAgent.toLowerCase().indexOf(v),0));
	},
	
	_within: function(v,a,z)
	{
		return((v>=a && v<=z)?true:false);
	},
	
	_XY: function(e,v)
	{
		var z=this._agent('msie')?[event.clientX+document.body.scrollLeft,event.clientY+document.body.scrollTop]:[e.pageX,e.pageY]; return(z[this._zero(v)]);
	},
	
	_XYwin:function(v)
	{
		var z=this._agent('msie')?[document.body.clientHeight,document.body.clientWidth]:[window.innerHeight,window.innerWidth]; return(!isNaN(v)?z[v]:z);
	},
	
	_zero: function(v)
	{
		v=parseInt(v); return(!isNaN(v)?v:0);
	},
	
	_HSVslide: function(e,d,o)
	{
		var tXY = function(e) { this._tY=this._XY(e,1)-ab.y; this._tX=this._XY(e)-ab.x; }.bindAsEventListener(this);
		function mkHSV(a,b,c) { return(Math.min(a,Math.max(0,Math.ceil((parseInt(c)/b)*a)))); }
		
		var ckHSV = function(a,b) { if(this._within(a,0,b)) return(a); else if(a>b) return(b); else if(a<0) return('-'+this._oo); }.bind(this);
		
		this._draghandler = function(e)
		{
			if(!this._stop)
			{
				if(d!='drag') this._XY(e);
				
				if(d=='SVslide')
				{
					var left = ckHSV(this._tX-this._oo,this._wSV);
					DFTools.console.log("left: "+left);
					ds.setStyle({left:+left+'px'});
					ds.setStyle({top:ckHSV(this._tY-this._oo,this._wSV)+'px'});
					
					this._slideHSV[1]=mkHSV(100,this._wSV,ds.style.left);
					this._slideHSV[2]=100-mkHSV(100,this._wSV,ds.style.top);
					
					this._HSVupdate();
				}
				else if(d=='Hslide')
				{
					var ck=ckHSV(this._tY-this._oo,this._hH), j, r='hsv', z={};
					
					ds.setStyle({top:(ck-5)+'px'});
					this._slideHSV[0]=mkHSV(360,this._hH,ck);
					
					for(var i=0; i<=r.length-1; i++)
					{
						j=r.substr(i,1);
						z[i]= (j=='h') ? this._maxValue[j]-mkHSV(this._maxValue[j],this._hH,ck) : HSV[i];
					}
					
					this._HSVupdate(z);
					
					this._parentDiv.down('div.'+this.cssClassPrefix+'SV').setStyle({backgroundColor:'#'+this._hsv2hex([this.HSV[0],100,100])});
					
				}
				else if(d=='drag')
				{
					ds.setStyle({left:this._XY(e)+oX-eX+'px'});
					ds.setStyle({top:this._XY(e,1)+oY-eY+'px'});
				}
			}
		}.bindAsEventListener(this);
		
		this._cancelDragHandler = function()
		{
			this._stop=1;
			$(document).stopObserving('mousemove',this._draghandler);
			$(document).stopObserving('mouseup',this._cancelDragHandler);
		}.bindAsEventListener(this);
	
		if(this._stop)
		{
			this._stop='';
			var ds = $( d!='drag' ? this._parentDiv.down('div.'+this.cssClassPrefix+d) : o);
	
			if(d=='drag')
			{
				var oX=parseInt(ds.style.left), oY=parseInt(ds.style.top), eX=this._XY(e), eY=this._XY(e,1);
				$(o).setStyle({zIndex:this._zINDEX++});
			}
			else
			{
				var ab=this._absPos($(o));
				
				this._oo=(d=='Hslide')?2:4;
				
				ab.x+=10;
				ab.y+=22;
				if(d=='SVslide')
					this._slideHSV[0]=this.HSV[0];
			}
	
			$(document).observe('mousemove',this._draghandler);
			$(document).observe('mouseup',this._cancelDragHandler);
			
			this._draghandler(e);
	
		}
	},
	
	
	
	_HSVupdate: function(v)
	{
		v=this._hsv2hex(this.HSV=v?v:this._slideHSV);
		
		this._parentDiv.down('div.'+this.cssClassPrefix+'plugHEX').update(v);
		this._parentDiv.down('div.'+this.cssClassPrefix+'plugCUR').setStyle({background:'#'+v});
		
		return(v);
	
	},
	
	_loadSV: function()
	{
		var z='';
		for(var i=this._hSV; i>=0; i--)
			z+="<div style=\"BACKGROUND: #"+this._hsv2hex([Math.round((360 / this._hSV)*i),100,100])+";\"><br></div>"
			
		this._parentDiv.down('div.'+this.cssClassPrefix+'Hmodel').update(z);
	},
	
	_toHex: function(v) { v=Math.round(Math.min(Math.max(0,v),255)); return("0123456789ABCDEF".charAt((v-v%16)/16)+"0123456789ABCDEF".charAt(v%16)); },
	
	_rgb2hex: function(r) { return(this._toHex(r[0])+this._toHex(r[1])+this._toHex(r[2])); },
	
	_hsv2hex: function(h) { return(this._rgb2hex(this._hsv2rgb(h))); },

	_hsv2rgb: function(r) { // easyrgb.com/math.php?MATH=M21#text21
	
		var R,B,G,S=r[1]/100,V=r[2]/100,H=r[0]/360;
	
		if(S>0) { if(H>=1) H=0;
	
			H=6*H; F=H-Math.floor(H);
			A=Math.round(255*V*(1.0-S));
			B=Math.round(255*V*(1.0-(S*F)));
			C=Math.round(255*V*(1.0-(S*(1.0-F))));
			V=Math.round(255*V); 
	
			switch(Math.floor(H)) {
	
				case 0: R=V; G=C; B=A; break;
				case 1: R=B; G=V; B=A; break;
				case 2: R=A; G=V; B=C; break;
				case 3: R=A; G=B; B=V; break;
				case 4: R=C; G=A; B=V; break;
				case 5: R=V; G=A; B=B; break;
	
			}
	
			return([R?R:0,G?G:0,B?B:0]);
	
		}
		else return([(V=Math.round(V*255)),V,V]);
	
	}
	
});

/* Legacy Apps */
DFThemeObject = com.digitalfruition.ThemeObject;

FastInit.addOnLoad(DFTools.setupUrlBase);

