/**
 * jQuery Sabramedia Plugin Library - Function for Sabramedia Virtual Office System
 * Copyright (C) 2008 - 2010 Sabramedia, LLC
 * http://www.sabramedia.com
 *
 * @author Nick Johnson {@link http://nickjohnson.com}
 */

/* Global Variables */

/*
 * jQuery JSON Plugin
 * version: 1.0 (2008-04-17)
 *
 * This document is licensed as free software under the terms of the
 * MIT License: http://www.opensource.org/licenses/mit-license.php
 *
 * Brantley Harris technically wrote this plugin, but it is based somewhat
 * on the JSON.org website's http://www.json.org/json2.js, which proclaims:
 * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
 * I uphold.  I really just cleaned it up.
 *
 * It is also based heavily on MochiKit's serializeJSON, which is 
 * copywrited 2005 by Bob Ippolito.
 */
 
(function($){function toIntegersAtLease(n){return n<10?'0'+n:n;}Date.prototype.toJSON=function(date){return this.getUTCFullYear()+'-'+toIntegersAtLease(this.getUTCMonth())+'-'+toIntegersAtLease(this.getUTCDate());};var escapeable=/["\\\x00-\x1f\x7f-\x9f]/g;var meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};$.quoteString=function(string){if(escapeable.test(string)){return'"'+string.replace(escapeable,function(a){var c=meta[a];if(typeof c==='string'){return c;}c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}return'"'+string+'"';};$.toJSON=function(o,compact){var type=typeof(o);if(type=="undefined")return"undefined";else if(type=="number"||type=="boolean")return o+"";else if(o===null)return"null";if(type=="string"){return $.quoteString(o);}if(type=="object"&&typeof o.toJSON=="function")return o.toJSON(compact);if(type!="function"&&typeof(o.length)=="number"){var ret=[];for(var i=0;i<o.length;i++){ret.push($.toJSON(o[i],compact));}if(compact)return"["+ret.join(",")+"]";else
return"["+ret.join(", ")+"]";}if(type=="function"){throw new TypeError("Unable to convert object of type 'function' to json.");}var ret=[];for(var k in o){var name;type=typeof(k);if(type=="number")name='"'+k+'"';else if(type=="string")name=$.quoteString(k);else
continue;var val=$.toJSON(o[k],compact);if(typeof(val)!="string"){continue;}if(compact)ret.push(name+":"+val);else
ret.push(name+": "+val);}return"{"+ret.join(", ")+"}";};$.compactJSON=function(o){return $.toJSON(o,true);};$.evalJSON=function(src){return eval("("+src+")");};$.secureEvalJSON=function(src){var filtered=src;filtered=filtered.replace(/\\["\\\/bfnrtu]/g,'@');filtered=filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']');filtered=filtered.replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered))return eval("("+src+")");else
throw new SyntaxError("Error parsing JSON, source is not valid.");};})(jQuery);

/*
 * Metadata - jQuery plugin for parsing metadata from elements
 *
 * Copyright (c) 2006 John Resig, Yehuda Katz, J�örn Zaefferer, Paul McLanahan
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.metadata.js 3620 2007-10-10 20:55:38Z pmclanahan $
 *
 */
(function($){$.extend({metadata:{defaults:{type:'class',name:'metadata',cre:/({.*})/,single:'metadata'},setType:function(type,name){this.defaults.type=type;this.defaults.name=name;},get:function(elem,opts){var settings=$.extend({},this.defaults,opts);if(!settings.single.length)settings.single='metadata';var data=$.data(elem,settings.single);if(data)return data;data="{}";if(settings.type=="class"){var m=settings.cre.exec(elem.className);if(m)data=m[1];}else if(settings.type=="elem"){if(!elem.getElementsByTagName)return;var e=elem.getElementsByTagName(settings.name);if(e.length)data=$.trim(e[0].innerHTML);}else if(elem.getAttribute!=undefined){var attr=elem.getAttribute(settings.name);if(attr)data=attr;}if(data.indexOf('{')<0)data="{"+data+"}";data=eval("("+data+")");$.data(elem,settings.single,data);return data;}}});$.fn.metadata=function(opts){return $.metadata.get(this[0],opts);};})(jQuery);

var sabramediaSpinnerSmall = '<span class="pending-spinner"><img src=\'/admin/theme/default/img/spinner-small.gif\' /></span>';
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g,""); };
String.prototype.ltrim = function() { return this.replace(/^\s+/,""); };
String.prototype.rtrim = function() { return this.replace(/\s+$/,""); };


/* jQuery Begins */
(function ($)
{

/**
 *	@name listCollapser
 *	@desc Simple function that makes a list collapsible. 
 *	 
 *	Note: jQuery UI Accordion widget should be used instead of this in most cases.
 *	
 *	@example <ul id="collapse"><li><h2>Click to Expand</h2><ul><li>Content</li></ul><li><ul>
 *	@example $('#collapse').listCollapser();
 *	@desc
 */	
	$.fn.listCollapser = function()
	{
		$('li:has(ul)',this).children('ul').hide().end()
		.find(':first').click(function(event){
			if(event.target){
				if($(this).parent().children('ul').is(':hidden')){
					$(this)
						.parent().children('ul').slideToggle('normal');
				}else{
					$(this)
						.parent().children('ul').slideToggle('normal');
				}
				return false;
			}
			
		});
	};
/**
 *  @name paginatedSearch
 *  @desc Paginated Search Function
*/

	$.paginatedSearch = function(searchObject){
	window.paginatedSearch = {};
	searchObject.backButton.attr("disabled",true);
	searchObject.nextButton.attr("disabled",true);
	(searchObject.pageLimit) ? window.paginatedSearch.pageLimit = searchObject.pageLimit : window.paginatedSearch.pageLimit = 20 ;
	var search = function(search,page){
	if (window.paginatedSearch.searchRequest != undefined){
		window.paginatedSearch.searchRequest.abort();
		$('.pending-spinner').remove();
	}
	window.paginatedSearch.entryCount = 1;
	window.paginatedSearch.lastSearch = '';
	window.paginatedSearch.currentPage = page;
	var searchLength = window.paginatedSearch.lastSearch.length;
	var cutString = search.substring(0,searchLength);
	if ( ( window.paginatedSearch.entryCount != 0 ) || ( window.paginatedSearch.currentPage != 0 ) || ( cutString.toLowerCase() != window.paginatedSearch.lastSearch.toLowerCase() ) )
	{
		$('#search-field').after(sabramediaSpinnerSmall);
		window.paginatedSearch.lastSearch = search;
		window.paginatedSearch.searchRequest = $.ajax({
				type:"POST",
				url:searchObject.url,
				dataType:"json",
				data:{search:search,limit:window.paginatedSearch.pageLimit,page:page},
				success:function(data){
					$('#transaction-list').empty();
					$('#transaction-table .inserted').remove();
					$('.pending-spinner').remove();
					window.paginatedSearch.entryCount = 0;
					$.each(data,function(key,value){
						window.paginatedSearch.entryCount += 1;
					})
					if (window.paginatedSearch.entryCount < window.paginatedSearch.pageLimit){
						$('#forward-page').attr("disabled",true);
					} else {
						$('#forward-page').attr("disabled",false);
					};
					if (page > 0){
						$('#back-page').attr("disabled",false);
					} else {
						$('#back-page').attr("disabled",true);
					}
					searchObject.callback(data);
					
				}
		});
	}};
	searchObject.backButton.click(function(){
			searchObject.backButton.attr("disabled",true);
			if (searchObject.searchField){
				var searchkey = searchObject.searchField.val();
			} else {var searchkey = ''}
			window.paginatedSearch.currentPage -= 1;
			search(searchkey,window.paginatedSearch.currentPage);
	});
	searchObject.nextButton.click(function(){
		searchObject.nextButton.attr("disabled",true);
		if (searchObject.searchField){
			var searchkey = searchObject.searchField.val();
		} else {var searchkey = ''}
		window.paginatedSearch.currentPage += 1;
		search(searchkey,window.paginatedSearch.currentPage)
	});
	
	if (searchObject.searchField){
		window.paginatedSearch.search_timeout = undefined;
		searchObject.searchField.keyup(function(){
			if (window.paginatedSearch.search_timeout != undefined) {
				clearTimeout(window.paginatedSearch.search_timeout);
			}
			window.paginatedSearch.search_timeout = setTimeout(function(){
				window.paginatedSearch.search_timeout = undefined;
				var searchkey = searchObject.searchField.val();
				search(searchkey,0);
			},750);
			
		});
	};
	search('',0);
	
	};


	
/**
 *	@name navigationMenu
 *	@desc Makes <ul> tags with infinite ul children into hover on/off navigation (requires css styling) 
*/
	$.fn.navigationMenu = function( callerSettings )
	{
		var settings = $.extend({
			eventType : 'hover',
			menuWrap: 'ul',
			menuEffect: 'slide',
			menuSpeed: 100,
			timeout: 300
		}, callerSettings || {});

		var navigation = $(this);

		$('li:has('+settings.menuWrap+')',this).children(settings.menuWrap).hide().end()
			.each(function(i){
				var menu = $(this).children(settings.menuWrap);
				$(this).addClass('ui-has-child');
				switch(settings.eventType){
				case 'hover':
					$(this).mouseover(function(event){
						$(this).addClass('ui-on-child');
						if(event.target){
							if($(menu).is(':hidden')){
								switch( settings.menuEffect ){
								case 'slide':
									$(menu).slideDown( settings.menuSpeed );
								break;
								case 'fade':
									$(menu).fadeIn( settings.menuSpeed );
								break;
								}
								menuHandler(menu);
							}
						}
					});

				break;
				case 'toggle':

					$('a:first', this).toggle(function(event){
						$(this).addClass('ui-on-child');

						//if(event.target){

								switch( settings.menuEffect ){
								case 'slide':
									$(menu).slideDown( settings.menuSpeed );
								break;
								case 'fade':
									$(menu).fadeIn( settings.menuSpeed );
								break;
								}

						//}
					},
					function(event){
							switch( settings.menuEffect ){
							case 'slide':
								$(menu).slideUp( settings.menuSpeed );
							break;
							case 'fade':
								$(menu).fadeOut( settings.menuSpeed );
							break;
							}
					});
				break;
				}
			});

		function menuHandler(menu)
		{
			var timerID = 0;

			switch(settings.eventType){
				case 'hover':
					$(menu).parent().hover(function(event){
						if(timerID){ clearTimeout(timerID); };
						timerID  = 0;
						switch( settings.menuEffect ){
						case 'slide':
							$(this).siblings().children(settings.menuWrap).slideUp( settings.menuSpeed );
						break;
						case 'fade':
							$(this).siblings().children(settings.menuWrap).fadeOut( settings.menuSpeed );
						break;
						}

						$(menu).parent('li').removeClass('ui-on-child');
					},
					function(){
						timerID = setTimeout(function(){
							switch( settings.menuEffect ){
							case 'slide':
								$(menu).slideUp( settings.menuSpeed );
							break;
							case 'fade':
								$(menu).fadeOut( settings.menuSpeed );
							break;
							}

							$(menu).parent('li').removeClass('ui-on-child');
						},settings.timeout);
					});
				break;

				case 'toggle':
					$.log('toggle');
					$(menu).parent().toggle(function(event){
						$.log('1');
						$(this).siblings().children(settings.menuWrap).slideUp( settings.menuSpeed );
						$(menu).parent('li').removeClass('ui-on-child');
					},
					function(){
						$.log('2');
						$(menu).slideUp( settings.menuSpeed );
						$(menu).parent('li').removeClass('ui-on-child');
					});
				break;

			}
		};
	};

	
/* Ajax Handling Functions */

	// Ajax event handler for submiting input fields with in a set of <li> or <tr> tags 
	$.fn.setListFieldEvents = function(callerSettings)
	{
		var settings = $.extend({
			actionDeletePath: false,
			actionSavePath: false,
			rowEvents: false, // For passing custom row-level events to the added row
			showAddButton:false,
			placeAddButton:false,
			removeObject:false,
			index:false
		}, callerSettings || {});
		
		// Hide all parent format containers
		$('.sibling-format').hide();
		var parentContainer = $(this).parent();
		
		if(settings.actionSavePath && settings.index == 0){
			$('.template-add', parentContainer).hide();
			
			if(settings.showAddButton){
				var addButton = $('<input type=\"button\" value=\"Add\" class=\"button-add\" />').click(function(){
					$('.template-add',parentContainer).toggle();
				});
				
				if(settings.placeAddButton){
					settings.placeAddButton( addButton );
				}else{
					$(addButton).insertBefore(parentContainer);
				}
			}
			
			$('.key-enter-add', parentContainer).ajaxEventHandler({
				dataContainer:'.template-add',
				dataObject:{'vo-action':'insert'},
				eventType:'keyup',
				keyCode:13,
				actionController: settings.actionSavePath,
				success:[successAdd]
			});
		}
			$.fn.rowEvents = function()
			{
				var obj = $(this);
				
				if(settings.actionSavePath){
					$('.key-enter-update',this).ajaxEventHandler({
						dataContainer:obj,
						dataObject:{'vo-action':'update'},
						eventType:'keyup',
						keyCode:13,
						actionController: settings.actionSavePath
					});
					
					$('.keyup-update',this).ajaxEventHandler({
						dataContainer:obj,
						dataObject:{'vo-action':'update'},
						eventType:'keyup',
						actionController: settings.actionSavePath
					});
					
					$('.click-update',this).ajaxEventHandler({
						dataContainer:obj,
						dataObject:{'vo-action':'update'},
						eventType:'click',
						actionController: settings.actionSavePath
					});
				}
				
				if(settings.actionDeletePath){
					$('.click-delete',this).ajaxEventHandler({
						dataContainer:obj,
						dataObject:{'vo-action':'delete'},
						eventType:'click',
						actionController: settings.actionDeletePath,
						verifyAction: 'Are you sure you want to delete this?',
						success:[function(data, status){
								// Change class name so that the .click-delete class does not get re-enabled.
								$('.click-delete',obj).attr('class','click-delete-remove');
								var removeObject = obj;
								if(settings.removeObject){
									removeObject = settings.removeObject;
								}
								$(removeObject).fadeOut('normal',function(){
									$(removeObject).remove();
								});
							}]
					}).hover(function(){
						$(this).addClass('ui-state-hover');
					},
					function(){
						$(this).removeClass('ui-state-hover');
					});
				}
			};
		
		$(this).rowEvents();
		
		var rowTemplate = $('.template', parentContainer).hide().clone();
		// Custom success function for adding
		function successAdd( data, status, ajaxOptions )
		{
			var newRow = $(rowTemplate).show().clone();
			
			$.each(data.newRow, function( key, val ){
				if($('.'+key,newRow).is('input')){
					$('.'+key,newRow).val(val);
				}else{
					$('.'+key,newRow).text(val);
				}
			});
			
			$('.template-add', parentContainer).hide(function(){
				$('input',this).val('');
				$(newRow)
					.removeClass('template-add')
					.attr('data-json','{id:' + data.newId + '}')
					.insertAfter('.template-add')
					.rowEvents();
			});
			
			if(settings.rowEvents){
				settings.rowEvents( newRow, data );
			}
		};
		
		function checkULContent(parentContainer)
		{
			if($(parentContainer).children().length == 0){
				$(parentContainer).append('<li>No data here.</li>').hide().slideDown('normal');
			}
		};
	};
	/**
		Default: Assume that the event handler is a parent tag with children input tags.<b>
		If dataObject is set from outside, then only send that and don't run data row functions 
	*/
	
/**
 *	@name ajaxEventHandler
 *	@desc Standard method for connecting dom elements with events that send data to php action controllers.
 *	@dependency livejquery
 * 	@param Object of options
 *	
*/
	$.fn.ajaxEventHandler = function( callerSettings )
	{
		var settings = $.extend({
			eventType: '',
			keyCode: null,
			dataContainer: null, // Container where data-json attributes holds data and any children inputs represent data
			dataObject: null, // Raw JSON data object that can be sent by itself or appended to the dataContainer
			actionController: '/action/public/404-json.php',
			verifyAction: false,
			beforeSend: null,
			waiting: null,
			success: null,
			complete: null,
			global: true, // Allows global ajax events to be triggered
			spinner: true
		}, callerSettings || {});
		
	// If dataObject is passed, send as Ajax data
		var thisUpdateField = $(this);
		
		
	// Set event here
		$(this).bind(settings.eventType,function(event){
			// If a keyCode is set, then we want to check which one before executing the ajaxCall()
			if(settings.keyCode){
				// On keyup from the 'enter' key code which is 13
				if(event.keyCode == settings.keyCode){
					$.voAjax( settings, thisUpdateField );
				}
			}else{
				$.voAjax( settings, thisUpdateField );
			}
				
			return false;
		});	
		
		return this;
	};
	
	$.voAjax = function( settings , thisUpdateField )
	{
		
	// Data Preparation
	
		// if tinyMCE variable set then we need to trigger the new values.
		if(typeof(tinyMCE) != 'undefined'){
			//tinyMCE.triggerSave();
		}
		
		// This doesn't work... I know how to get it to work now, think iframe.
		if(typeof(editAreaLoader) != 'undefined'){
			$('textarea#content-js').append(editAreaLoader.getValue('content-js'));
		}
		
	// Methods
		function getSerializedData()
		{
			var serializedData = '';
			// Set data here. Important to set after this just in case we need to do an preparation.
			if(settings.dataContainer){
				serializedData += $(settings.dataContainer).serializeRowData();
			}
			
			if(settings.dataObject){
				if(settings.dataContainer){ serializedData += '&'; } // Appending character if both are set
				serializedData += serializeJSON(settings.dataObject);
			}
			return serializedData;
		};
		
		function beforeSend( xhr )
		{
			if(settings.spinner){
				$(thisUpdateField).after(sabramediaSpinnerSmall);
			}
			
			var verify = true;
			if(settings.verifyAction){
				verify = confirm(settings.verifyAction);
			}
			
			if( !verify ){
				return false;
			}
			
			// Disable all sibling fields only dataObject is not passed and rowData is used
			// $('button').attr('disabled','disabled');
	
			// Iterate through custom fields
			if(settings.beforeSend){
				$.each(settings.beforeSend,function(i,customFunction){
					// Pass field object of event handler
					customFunction(xhr,thisUpdateField);
				});
			}
		};
		
		function success( data, status )
		{
			$('.pending-spinner').remove();
			if(settings.success){
				$.each(settings.success,function(i,customFunction){
					// Pass field object of event handler
					if($.isFunction( customFunction )){
						customFunction( data, status, thisUpdateField );
					}
				});
			}else{
//				$(thisUpdateField)
//					.focus()
//					.siblings('.temp-message').remove().end()
//					.parent('li').removeAttr('style').end();
			}
			
			if(data.warning){
				$.voMessage.alert( data.warning );
			}
			if(data.message){
				$.voMessage.dock( data.message );
			}
		};
		
		function complete( xhr, status)
		{
			// Re-enable disabled fields upon success
			// $('button').removeAttr('disabled');
			if(settings.complete){
				$.each(settings.complete,function(i,customFunction){
					// Pass field object of event handler
					if($.isFunction( customFunction )){
						customFunction( xhr, status );
					}
				});
			}
		};

	// Ajax intializes here
		
		return $.ajax({
			type:'POST',
			url:settings.actionController,
			dataType:'json',
			data:getSerializedData(),
			beforeSend:beforeSend,
			success:success,
			complete:complete,
			global:settings.global
		});
	};
	
	/**
	* serializeJSON()
	* Turns JSON object into a serialized string in the form of: key=val&key2=val2 
	*
	* @param object literal object
	* @return string serialized for Ajax
	*/
	function serializeJSON(dataObject)
	{
		var dataArray = [];
		var retval;
		
		$.each(dataObject,function(key,data){
			dataArray.push(key + '=' + escape(data));
		});
		
		retval = dataArray.join('&');
		return retval;
	};
	
	function simplifyInputData(dataObject)
	{
		var object = [];
		var retval;
		
		$.each(dataObject,function(key,data){
			
			object[data.name] = data.value;
		});
		
		retval = object;
		return retval;
	};
	
	/** 
	* serializeRowData()
	* Accesses this elements parent element data-json attribute and all sibling element form-type
	* fields, then serializes data for sending via Ajax XHR.<b>
	*
	* @return string serialized data to send via Ajax
	* @dependency metadata
	*/
	$.fn.serializeRowData = function()
	{
		// Gets all json data contained in the parent tag's data-json wrapper 
		var metaData='';
		if($.metadata){
			$.metadata.setType('attr','data-json');
			jsonObject = $('[data-json]',this).andSelf().metadata();
			metaData += serializeJSON(jsonObject)+'&';
		}
		if($(this).data('postdata')){
			metaData += serializeJSON($(this).data('postdata'))+'&';
		}
		// Gets all form type fields based on the current field's containing parent,
		// Then it serializes the data for passing to the ajax controller.
		var inputData = $(this).find(':input');
		
		// Concatentate the jsonData and the inputData
		var serialData = metaData+inputData.serialize();
		//return jsonData;
		// Optional dual-type data object
		
		return serialData;
	};
		
	// Takes JSON returned data and places data in fields or tags with data.key == '.class'
	$.fn.setDataDisplay = function( data )
	{
		if(data){
			object = $(this);
			$.each(data, function( key, val ){
				if(typeof(val) != 'object'){
					var input_type = $('.'+key, object).attr('type');
					
					if($('.'+key, object).is('select')){
						input_type = 'select';
					}
					
					switch (input_type) {
						case 'text':
							$('.'+key, object).val(val);
						break;
						case 'textarea':
							$('.'+key, object).val(val);
						break;
						case 'radio':
							var $thisRadio = $(':radio[name="'+key+'"][value="'+val+'"]', object );
							if( $thisRadio.length ){
								$thisRadio.attr('checked','checked');
							}
						break;
						case 'checkbox':
							if(val == true){ val=1; }
							if(val == false){ val=0; }
							$('.'+ key +'[value="'+ val +'"]', object).attr('checked','checked');
						break;
						case 'select':
							$('.'+key+' option[value="'+val+'"]', object).attr('selected','selected');
						break;
						default:
							$('.'+key, object).text(val);
						break;
					}
					//$('.'+key, object).text(val);
				}
			});
			return this;
		}
	};
		
	$.extend({
	
	/**
	* @name jqPhp
	* @desc Purpose is for php variables to be passed into object to be globally accessed.
	*/
		jqPHP : {
		
			phpUrl : {}, // Url object
			phpVar : {}, // Url object

			// Set the urls from php so custom plugins can access the unique urls created by Sabramedia Virtual Office.
			setUrl : function( name, url )
			{
				this.phpUrl[name] = url;
			},
			// Get the urls in the plugin by name
			getUrl : function( name )
			{
				return this.phpUrl[name];
			},
			
			setVar : function( name, variable )
			{
				this.phpVar[name] = variable;
			},

			getVar : function( name )
			{
				return this.phpVar[name];
			},
			removeVar : function( name )
			{
				delete this.phpVar[name];
			}
		},
	/**
	* @name voMessage
	* @desc Standard method for bringing basic messages to system users.
	*/
		voMessage : {
			// Replaces standard alert with a nicer popup
			alert : function( message )
			{
				var randomId = Math.floor(Math.random()*11);
				var $dialog = $('<div id="vo-dialog'+randomId+'" />');
				$dialog.append('<span class="ui-icon ui-icon-alert" style="float:left; margin:2px 7px 20px 0;"></span>' + message);
				$dialog.appendTo('body'); 
				
				$dialog.dialog({
					title: 'Virtual Office Alert',
					bgiframe: true,
					resizable: false,
					height:200,
					width:300,
					modal: true,
					overlay: {
						backgroundColor: '#000',
						opacity: 0.5
					},
					buttons: {
						'Ok': function() {
							$(this).dialog('close');
							$(this).dialog('destroy');
							$('#vo-dialog'+randomId).remove();
						}
					}
				});
				
			},
			
			// Footer alert
			dock : function( message )
			{
				$("#dock-container").hide();
				$("#dock-container #ajax-message").empty().append( message );
				$("#dock-container").fadeIn('normal');
				setTimeout("$('#dock-container').fadeOut('slow')",4000);
			},
			
			// Safer way to post to Firebug console
			debug : function( message )
			{
				if(window.console){
					console.log(message);
				}else{
					if(typeof message != 'string'){
						message = 'Your browser doesn\'t support advanced debugging. Try Firefox and Firebug!';
					}
					//alert(message);
				}
			}
		},
		
		/**
		* @name log
		* @desc Alias of $.voMessage.debug(); 
		*/
		
		log : function( message )
		{
			$.voMessage.debug( message );
		}
	});
	
		
/* Not Standardized */

	$.fn.addInput = function(name,value,type)
	{
		type = type ? type : 'hidden';
		$('input[type='+type+'][name="'+name+'"]',this).remove();
		$(this).prepend($('<input>').attr({'type':type,'name':name,'value':value}));
	};
	
// Start form setup and validation
//	$.fn.checkWarnings = function( buttonValue )
//	{
//		buttonValue = buttonValue ? buttonValue : 'Next Step >';
//		
//		var numWarnings = $('.warning').length;
//		if(numWarnings > 0){
//			$('.submit',this).attr('disabled','disabled').val('Fill in required fields');
//		}else{
//			$('.submit',this).removeAttr('disabled').val(buttonValue);
//		}
//	};
//
//	$.fn.setFormValidation = function( buttonValue )
//	{
//		var requiredFlag = '<span class="required-flag">*</span>';
//		$('.required-flag',this).remove();
//	
//		$(':input',this).filter('.required')
//		.prev('label').addClass('required')
//		.append(requiredFlag)
//		.parents('li').addClass('warning');
//		
//		$(this).checkWarnings(buttonValue);
//	
//		$(':input:visible',this).each(setRequired).bind('keyup change',setRequired);
//		
//		function setRequired()
//		{
//			if($(this).is('.required')){
//				if($(this).val()){
//					$(this).parents('li:first').removeClass('warning');
//				}else{
//					$(this).parents('li:first').addClass('warning');
//				}
//				$(this).parents('form').checkWarnings(buttonValue);
//			}
//		};
//	};

	/*	Blog Entry:
		jQuery Comments() Plug-in To Access HTML Comments For DOM Templating
		
		Author:
		Ben Nadel / Kinky Solutions
		
		Link:
		http://www.bennadel.com/index.cfm?dax=blog:1563.view
		
	*/
	
	$.fn.comments = function( blnDeep ){
		var blnDeep = (blnDeep || false);
		var jComments = $( [] );
	 
		// Loop over each node to search its children for
		// comment nodes and element nodes (if deep search).
		this.each(
			function( intI, objNode ){
				var objChildNode = objNode.firstChild;
				var strParentID = $( this ).attr( "id" );
				// Keep looping over the top-level children
				// while we have a node to examine.
				while (objChildNode){
					// Check to see if this node is a comment.
					if (objChildNode.nodeType === 8){
						// We found a comment node. Add it to
						// the nodes collection wrapped in a
						// DIV (as we may have HTML).
						/*jComments = jComments.add(
							"<div rel='" + strParentID + "'>" +
							objChildNode.nodeValue +
							"</div>"
							);*/
						
						jComments = jComments.add(objChildNode.nodeValue); // Nick modifed this
					} else if (
						blnDeep &&
						(objChildNode.nodeType === 1)
						) {
	 
						// Traverse this node deeply.
						jComments = jComments.add(
							$( objChildNode ).comments( true )
							);
					}
					// Move to the next sibling.
					objChildNode = objChildNode.nextSibling;
				}
			}
			);
		// Return the jQuery comments collection.
		return( jComments );
	};
	
	// For comparing two arrays.. thanks David @ stackflow
	$.fn.compare = function(t) {
	    if (this.length != t.length) { return false; }
	    var a = this.sort(),
	        b = t.sort();
	    for (var i = 0; t[i]; i++) {
	        if (a[i] !== b[i]) { 
	                return false;
	        }
	    }
	    return true;
	};

	
	
	/*
	 * The backbone for click and discover interaction
	 */
	
	$.voCnd = {
			'settings':
				{
					'controller':null,
					'panel':'#cnd-navigation',
					'pagination':'.cnd-pagination',
					'contentDom': '',
					'contentDomRow': '',
					'filterPanel' : 0,
					'initContent' : 1
				},
			'hierarchyArray' : {}, // Cached arrays for mult-dimensional catgories
			'cache' : {'panel':{}}, // Added to accommodate panel state caching, deprecate the above eventually.
			'filterArray' : {}, // sent via ajax to controller
			'data':{}, // Set externally to be sent via ajax to controller
			'panelGroups' : {},
			'customContentFunction':{},
			'ajaxBeforeSend': function(){},
			'ajaxCompleteFunction': function( data, status ){},
			'resultTotal' : '', // resultTotal set in setInterface() and filterResults()
			'_currentXhr' : null,


			'init' :
				function()
				{
					$.voAjax({
						'actionController': $.voCnd.settings.controller,
						'dataObject':$.extend($.voCnd.data,{'vo-action':'init','filter_conditions':$.toJSON($.voCnd.filterArray)}),
						'beforeSend':[$.voCnd.ajaxBeforeSend,$.voCnd.waitingForResults],
						'success':[$.voCnd.ajaxSuccessFunction,$.voCnd.setInterface],
						'complete':[$.voCnd.ajaxCompleteFunction]
					});
				},

			'setPaginationMessage' :
				function()
				{
					if($.voCnd.resultTotal){
						var totalPage = Math.ceil( ($.voCnd.resultTotal / $.voCnd.filterArray['limit']));
						var currentPage = ($.voCnd.filterArray['page'] + 1);
						$('span.results .current-page',$.voCnd.settings.pagination).text( currentPage );
						$('span.results .total-page',$.voCnd.settings.pagination).text( totalPage );
						$('span.results .total-item',$.voCnd.settings.pagination).text($.voCnd.resultTotal);

						if($.voCnd.filterArray['page'] == 0 ){
							$('button.previous',$.voCnd.settings.pagination).hide();
						}else{
							$('button.previous',$.voCnd.settings.pagination).show();
						}

						if( totalPage == 1 || (totalPage == currentPage) ){
							$('button.next',$.voCnd.settings.pagination).hide();
							if(totalPage == 1){ $($.voCnd.settings.pagination).hide(); }
						}else{
							$('button.next',$.voCnd.settings.pagination).show();
						}
					}
				},

			'setPagination' :
				function()
				{
					if(!$.voCnd.filterArray['page']){ $.voCnd.filterArray['page'] = 0; }



					$.voCnd.setPaginationMessage();
					$('button',$.voCnd.settings.pagination).unbind('click').click(function(e){
						$('html, body').animate({scrollTop:0}, 'slow'); // scroll to to on pagination
						var resultTotal = $.voCnd.resultTotal ? $.voCnd.resultTotal : $($.voCnd.settings.pagination+':first .total-item').text();

						if($(e.currentTarget).is('.previous')){
							var previousPage = $.voCnd.filterArray['page'] - 1;
							if( previousPage + 1 > 0 ){
								$.voCnd.filterArray['page'] = previousPage;
							}else{
								$.voCnd.filterArray['page'] = Math.floor( resultTotal / $.voCnd.filterArray['limit'] ); // go to last page
							}
							$.voCnd.getResults();
						}else{
							// page * limit < results
							var nextPage = $.voCnd.filterArray['page'] + 1;

							if( nextPage * $.voCnd.filterArray['limit'] < resultTotal ){

								$.voCnd.filterArray['page'] = nextPage;
							}else{
								$.voCnd.filterArray['page'] = 0;
							}
							$.voCnd.getResults();
						}

						$.voCnd.setPaginationMessage();
					});
				},

			'setFilterPanel' :
				function( boolean )
				{
					$.voCnd.settings.filterPanel = boolean;
				},

			'setLimit' :
				function( limit )
				{

					// For true customizabliity in SEO friendly versions I will need to get the limit from the DOM
					if( limit ){
						$.voCnd.filterArray.limit = limit;
					}else if( ! isNaN(parseInt($('#pagination-limit',$.voCnd.settings.pagination).text())) ){
						// Default if the limit is not set
						$.voCnd.filterArray.limit = parseInt($('#pagination-limit',$.voCnd.settings.pagination).text());
					}else{
						$.voCnd.filterArray.limit = 12;
					}
				},

			'getResults' :
				function()
				{
					// **Key efficiency logic - abort other ajax requests in favor of current
					// speeds up response
					if($.voCnd._currentXhr){ $.voCnd._currentXhr.abort(); }
					$.voCnd._currentXhr = $.voAjax({
						'actionController': $.voCnd.settings.controller,
						'dataObject':$.extend($.voCnd.data,{'vo-action':null,'filter_conditions':$.toJSON($.voCnd.filterArray)}),
						'beforeSend':[$.voCnd.ajaxBeforeSend,$.voCnd.waitingForResults],
						'success':[$.voCnd.ajaxSuccessFunction,$.voCnd.filterResults],
						'complete':[$.voCnd.ajaxCompleteFunction]
					});
				},

			'setController' :
				function( location )
				{
					$.voCnd.settings.controller = location;
				},

			'setContentDom' :
				function( contentDom, contentDomRow )
				{
					$.voCnd.settings.contentDom = contentDom;
					$.voCnd.settings.contentDomRow = contentDomRow;
				},

			'preload' :
				function()
				{

				},

			'setPanelGroup' :
				function( callerSettings )
				{
					// groupName, groupDisplayName , multiDimension, defaultValue
					// Build the group object, but don't overwrite it proecedurally.
					// The interface conrtoller gets precedence over the Ajax data settings for group.
					$.voCnd.panelGroups[callerSettings.group] = $.extend({
						'displayName' : (callerSettings.displayName ? callerSettings.displayName : callerSettings.group ),
						'ui' : callerSettings.ui, // if true then treat the group like a categoric hierarchy
						'multiDimension' : (callerSettings.multiDimension ? 1 : 0), // if true then treat the group like a categoric hierarchy
						'dataFormatSingle' : callerSettings.dataFormatSingle,
						'filter' : callerSettings.filter,
						'filterEffect' : callerSettings.filterEffect
					},$.voCnd.panelGroups[callerSettings.group] || {});
				},
			'setGroupDefault' :
				function( groupName, id )
				{
					if(typeof($.voCnd.filterArray[groupName]) != 'object'){
						$.voCnd.filterArray[groupName] = [];
					}
					$.voCnd.filterArray[groupName].push(id);
					$.voCnd.panelGroups[groupName] = $.extend({'defaultValue':id},$.voCnd.panelGroups[groupName] || {});
				},

			// Group class
			'group' :
				function( domObject, groupName, groupSettings )
				{
					var thisGroup= this;

					this.menuClose =
						function( e )
						{
							$('h3 span',domObject).removeClass('ui-icon-triangle-1-s');
							$('h3 span',domObject).addClass('ui-icon-triangle-1-e');
							$('.cnd-list',domObject).slideUp('fast');
						};

					this.menuOpen =
						function( e )
						{
							$('h3 span',domObject).removeClass('ui-icon-triangle-1-e');
							$('h3 span',domObject).addClass('ui-icon-triangle-1-s');
							$('.cnd-list',domObject).slideDown('fast');
						};

					this.itemClick =
						function( e )
						{
						var $thisObject = $(this).closest('li'); // Makes sure it is an li that gets the effects
						var hasChild = $thisObject.is('.ui-has-child');
						if($thisObject.is('.ui-state-disabled')){
							e.stopPropagation();
						}else{
							// Click on
							if(!$thisObject.is('.ui-state-active')){
								if(groupSettings.dataFormatSingle){
									$.voCnd.filterArray[groupName] = [$thisObject.attr('rel')];
									$(domObject).find('.cnd-button.cancel').show();
								}else{
								// Format interface so group data is sent as an array
									if((clearItemId =$.inArray($('.crumb-current',domObject).attr('rel'), $.voCnd.filterArray[groupName])) != -1){
										$.voCnd.filterArray[groupName].splice(clearItemId,1);
									}
									$.voCnd.filterArray[groupName].push($thisObject.attr('rel'));

									// Show filter-cancel button
									if($.voCnd.filterArray[groupName].length  > 0){
										$(domObject).find('.cnd-button.cancel').show();
									}
								}
								$.voCnd.filterArray['page'] = 0;

								// Simulate toggle unbind current event and bind another
								/*$(this)
									.unbind('click')
									.bind('click',thisGroup.itemClickOff);*/
								if(groupSettings.dataFormatSingle){
									$('li',domObject).removeClass('ui-state-active');
									$('.handle',domObject).removeClass('ui-state-active');
								}

								if( $thisObject.is('.tier-1') ){
									$('li.ui-has-child ul',domObject).not($thisObject).slideUp('fast');
								}

								if( hasChild ){
									$('ul:first',$thisObject).slideDown('fast');
								}

								$thisObject.addClass('ui-state-active');
								$('.handle:first',$thisObject).addClass('ui-state-active');


								$.voCnd.getResults(); // Remember with new parameters we need to set page to 0
							}else{
							// Click off
								$(domObject).find('.cnd-button.cancel').show();

								// Remove the clicked item id
								var clickedItem = $thisObject.attr('rel');

								/*if(groupSettings.dataFormatSingle){
									$.voCnd.filterArray[groupName] = 0;
								}else{*/
									if((clearCategoryId = $.inArray(clickedItem,$.voCnd.filterArray[groupName])) != -1){
										$.voCnd.filterArray[groupName].splice(clearCategoryId,1);
									}
									if( $thisObject.is('.ui-has-child') ){
										$('ul',$thisObject).slideUp('fast');
									}
								//}
								$.voCnd.filterArray['page'] = 0;

								// Remove filter-cancel button
								if(groupSettings.dataFormatSingle || $.voCnd.filterArray[groupName].length  < 1){
									if($('.crumb-current',domObject).attr('rel')){
										/*if(groupSettings.dataFormatSingle){
											$.voCnd.filterArray[groupName] = $('.crumb-current',domObject).attr('rel');
										}else{*/
											$.voCnd.filterArray[groupName] = [$('.crumb-current',domObject).attr('rel')];
										//}
									}
									$(domObject).find('.cnd-button.cancel').hide();
								}

								// Set class to off
								$thisObject.removeClass('ui-state-active');
								$('.handle:first',$thisObject).removeClass('ui-state-active');
								$.voCnd.getResults(); // Remember with new parameters we need to set page to 0
							}
						}
					};

					this.reset =
						function( e )
						{
							$(this).hide();
							$('li.ui-state-active', domObject)
								.removeClass('ui-state-active');

							if($('.crumb-current',domObject).attr('rel')){
								/*if(groupSettings.dataFormatSingle){
									$.voCnd.filterArray[groupName] = $('.crumb-current',domObject).attr('rel');
								}else{*/
									$.voCnd.filterArray[groupName] = [$('.crumb-current',domObject).attr('rel')];
								//}
							}else{
								$.voCnd.filterArray[groupName] = [];
							}
							$.voCnd.getResults();
						};
					/**
					 * filterResults turns group items on or off based on returned panelFilter data.
					 */
					this.filterResults =
						function( groupFilter )
						{
/**/						// Hack: If results are filtered out, e.g. brand, then we need to remove them from query
							// and go get results again. This should be done on php side eventually

							var rerunResults = false;
							if( typeof(groupFilter) == 'object' && groupFilter.length > 0 ){
								var typeCast = typeof(groupFilter[0]);
								domObject.show();
								$('ul.cnd-list li', domObject).each(function(i, item){
									// For instances where we use string as an ID.
									var itemId = ( typeCast == 'number' ? parseInt( $(item).attr('rel') ) : $(item).attr('rel') );
									if($.inArray(itemId, groupFilter) != -1){
										if(groupSettings.filterEffect == 'hide'){
											$(item).show();

											// This is only for the slide down hierarchy display method
											// Ideally for hierarchy child filter sends back
											// parents, if not, this will make sure the parent structure shows.
											$(item).parents('.'+groupName).show();
										}else{
											$(item).removeClass('ui-state-disabled');
										}
									}else{
										if(groupSettings.filterEffect == 'hide'){
											$(item).hide();
										}else{
											$(item).addClass('ui-state-disabled');
										}

										// Remove filter ids
										if((clearCategoryId = $.inArray(itemId, $.voCnd.filterArray[groupName]) ) != -1){
											$.voCnd.filterArray[groupName].splice(clearCategoryId,1);
											rerunResults = true;
										}
									}
								});
							}else{
								if(groupFilter == 'showall'){
									if(groupSettings.filterEffect == 'hide'){
										$('ul.cnd-list li', domObject).show();
									}else{
										$('ul.cnd-list li', domObject).removeClass('ui-state-disabled');
									}
								}else{
									domObject.hide();
								}
							}
							if(rerunResults){ $.voCnd.getResults(); }
						};

					this.formatHierarchyMenuSlider =
					function( cid, data )
					{
						if(typeof($.voCnd.cache.panel[groupName]) != 'object'){

							$.voCnd.cache.panel[groupName] = (data.panel[groupName]['array'] ? data.panel[groupName]['array'] : data.panel[groupName] ); // cache the hierarchy
						}

						$('h3',domObject).prepend($('<span>'+groupSettings.displayName+'</span>'));

						var panelArray = (data.panel[groupName]['array'] ? data.panel[groupName]['array'] : data.panel[groupName] ); // Backward compat
						var fragment = document.createDocumentFragment();
						//////////////////////

						function printCategoryHierarchy( categoryArray, tier )
						{
							// If cid is set then start looking for the id
							if( typeof(retVal) != 'string' ){ var retVal = ''; };
							if( !tier ){ tier = 1; }

							$.each(categoryArray, function(key, category){
								var hasChildren = $(category.children).length > 0 ? true : false;
								retVal += '<li rel="'+key+'" class="'+groupName+' tier-'+tier+(hasChildren ? ' ui-has-child' : '')+'"><div class="handle">'+category.name+'</div>';

								if( hasChildren ){
									var i = tier + 1;
									retVal += '<ul'+( i > 1 ? ' style="display:none;"' : '' )+'>';
									retVal += printCategoryHierarchy( category.children, i );
									retVal += '</ul>';
								}
								retVal += '</li>';

							});

							return retVal;
						};

						var $categoryStructure = $(printCategoryHierarchy( panelArray ));

						// Activate any li's onload
						$.each($.voCnd.filterArray[groupName], function(key, filterId){
							$('li[rel='+filterId+']', $categoryStructure).addClass('ui-state-active')
								.find('.handle').addClass('ui-state-active')
								.parents('ul').show();
						});

						$('.cnd-list',domObject)
							.append( $categoryStructure )
							.find('div.handle').click( thisGroup.itemClick ).end();
					};

					this.formatHierarchyMenuPortal =
					function( cid, data )
					{
						// Initialize cache and breadcrumb dom here
						if(typeof($.voCnd.hierarchyArray[groupName]) != 'object'){

							$.voCnd.hierarchyArray[groupName] = (data.panel[groupName]['array'] ? data.panel[groupName]['array'] : data.panel[groupName] ); // cache the hierarchy

							$('h3',domObject).append('<span class="crumb-parent" />');
							$('h3',domObject).append('<span class="crumb-current" />');
							// This line sets the default category id if there is one set from the outside voCnd setup.
							if( typeof($.voCnd.filterArray[groupName]) == 'object' ){ cid = $.voCnd.filterArray[groupName][0]; }

							// Events

							// Current Category and Parent
							$('h3 span.crumb-parent',domObject).unbind('click').click(function(){
								/*if(groupSettings.dataFormatSingle){
									if($(this).attr('rel') > 0){
										$.voCnd.filterArray[groupName] = $(this).attr('rel');
									}else{
										$.voCnd.filterArray[groupName] = '';
									}
								}else{*/
									$.voCnd.filterArray[groupName] = [];
									if($(this).attr('rel') > 0){
										$.voCnd.filterArray[groupName].push($(this).attr('rel'));
									}
								//}
								$.voCnd.filterArray['page'] = 0;
								$(domObject).find('.cnd-button.cancel').hide();
								$.voCnd.getResults();
								thisGroup.formatHierarchyMenuPortal( $(this).attr('rel') );
								return false;
							});
						}

						function getCategoryStructure(categoryArray, cid, parentObject)
						{
						// first level if there will be no parent object, then return category array

							if(typeof(cid) == 'undefined' || cid == 0 ){
								return {'parent':null,'categoryArray':{'children':categoryArray}};
							}else{
							// If cid is set then start looking for the id
								if(typeof(retVal) != 'object'){ var retVal = {}; };
								$.each(categoryArray, function(key, category){
									if( category.id == cid ){
										parentObject = typeof(parentObject) == 'object' ? parentObject : null;

										// If this category has no children, then step back up the branch
										if($(category.children).length > 0){
											retVal = {'parent':parentObject,'categoryArray':category};
										}else{
											if(parentObject != null){
												retVal = {'parent':parentObject.parentObject,'categoryArray':parentObject};
											}else{
												retVal = {'parent':null,'categoryArray':{'children':categoryArray}};
											}
										}
										return false;
									}else{

										if( $(category.children).length > 0 && typeof(retVal.categoryArray) != 'object' ){
											var parent = category;
											parent.id = category.id;
											parent.parentObject = parentObject; // (parent's parent) allows for stepping back up the branch
											retVal = getCategoryStructure(category.children, cid, parent);
										}
									}
								});

								return retVal;
							}
						};

						var categoryStructure = getCategoryStructure($.voCnd.hierarchyArray[groupName],cid);

						var category = categoryStructure.categoryArray;
						var parent = categoryStructure.parent ? categoryStructure.parent : null;

							if(cid > 0){
								if(category.name){
									$('h3 span.crumb-current',domObject).html('&gt; '+category.name).attr('rel',category.id).css('display','block');
								}
							}else{
								$('h3 span.crumb-current',domObject).empty().removeAttr('rel').hide();
							}

							if(parent){
								$('h3 span.crumb-parent',domObject).text(parent.name).attr('rel',parent.id);
							}else{

								$('h3 span.crumb-parent',domObject).text(groupSettings.displayName).attr('rel',0);
							}

							$('li',domObject).remove();
							if( category.children ){
								var fragment = document.createDocumentFragment();

								$.each(category.children, function(i,child){

									var newList = $('<li rel=\"'+child.id+'\" class="'+groupName+' ui-state-default">'+child.name+'</li>');

									//if(!groupSettings.dataFormatSingle){
										if($.inArray(child.id,$.voCnd.filterArray[groupName]) != -1){
											newList.addClass('ui-state-active');
											if(!groupSettings.dataFormatSingle){
												$(domObject).find('.cnd-button.cancel').show();
											}
										}
									/*}else{
										if(child.id == $.voCnd.filterArray[groupName]){
											newList.addClass('ui-state-active');
											$(domObject).find('.cnd-button.cancel').show();
										}
									}*/

									if($(child.children).length > 0 ){
										newList.click(function(){
										// If the single data format is set, then set the ajax data as a string rather than an array
											/*if(groupSettings.dataFormatSingle){
												$.voCnd.filterArray[groupName] = $(this).attr('rel');
											}else{*/
												$.voCnd.filterArray[groupName] = [];
												$.voCnd.filterArray[groupName].push($(this).attr('rel'));
											//}
											$.voCnd.filterArray['page'] = 0;
											//$.voMessage.debug($.voCnd.filterArray[groupName]);
											$.voCnd.getResults();
											thisGroup.formatHierarchyMenuPortal($(this).attr('rel'));
											//
											$('.cnd-list li.ui-state-active',domObject).removeClass('ui-state-active');
											$('.cnd-list li[rel='+$(this).attr('rel')+']',domObject).unbind('hover').removeClass('ui-state-hover').addClass('ui-state-active');
											return false;
										});
									}else{
										// Removing the parent only matters on array formatted ajax data
										if(!groupSettings.dataFormatSingle){
											// !! Important, remove the parent id from the array.
											if((clearParent = $.inArray(category.id,$.voCnd.filterArray[groupName])) != -1){
												$.voCnd.filterArray[groupName].splice(clearParent,1);
											}
										}
										newList.click(thisGroup.itemClick);
									}
									fragment.appendChild( newList.get(0) );
								});

								$('.cnd-list', domObject).append( fragment );
							}
					};

					this.formatSingleDimension =
						function( data )
						{
							if(typeof($.voCnd.cache.panel[groupName]) != 'object'){
								$.voCnd.cache.panel[groupName] = (data.panel[groupName]['array'] ? data.panel[groupName]['array'] : data.panel[groupName] ); // cache the hierarchy
							}
							$('h3',domObject).prepend($('<span>'+groupSettings.displayName+'</span>'));

							var panelArray = (data.panel[groupName]['array'] ? data.panel[groupName]['array'] : data.panel[groupName] ); // Backward compat
							var fragment = document.createDocumentFragment();

							$.each(panelArray, function( liKey, liData ){
								var newList = $('<li rel=\"'+liKey+'\" class="'+groupName+' ui-state-default">'+liData.name+'</li>');
								$(newList).attr('rel',liKey);

								// For onload, if the id is in the filterArray, the add an active state
								if($.inArray( liKey, $.voCnd.filterArray[groupName] ) != -1){
									newList.addClass('ui-state-active');
								}

								// NOT SURE why this code was hid conditionally on the data format being single.
								// Keep it around for a while until I remember whay I did this..
								/*if( ! groupSettings.dataFormatSingle ){
									if($.inArray( liKey, $.voCnd.filterArray[groupName] ) != -1){
										newList.addClass('ui-state-active');
										$(domObject).find('.cnd-button.cancel').show();
									}
								}*/
								$(newList).click( thisGroup.itemClick );
								fragment.appendChild( newList.get(0) );
							});

							$('.cnd-list',domObject).append( fragment );
						};

					return this;
				},

			'setInterface' :
				function(data, status)
				{
					$.voCnd.resultTotal = data.resultTotal;
					$.voCnd.setPagination();

				// Set panel dynamically from ajax feed
					$.each(data.panel, function(groupName, groupSettings){
						$.voCnd.setPanelGroup({
							'group':groupName,
							'displayName':groupSettings.display_name,
							'ui':(groupSettings.ui ? groupSettings.ui : 'single_menu'), // single_menu, hierarchy_menu_slider, hierarchy_menu_portal, range_slider
							'multiDimension':(groupSettings.multi_dimension ? groupSettings.multi_dimension : 0),
							'dataFormatSingle':(groupSettings.data_format_single ? 1 : 0),
							'filter':(groupSettings.filter_panel ? 1 : 0),
							'filterEffect':(groupSettings.filter_effect ? groupSettings.filter_effect : 'disable')
						});
					});

					// Set Each Panel Group
					var fragment = document.createDocumentFragment();
					$.each(data.panel, function(groupName){
						var groupSettings = $.voCnd.panelGroups[groupName];

						if( typeof($.voCnd.filterArray[groupName]) != 'object'){
							groupSettings.defaultValue = $.voCnd.filterArray[groupName] ? $.voCnd.filterArray[groupName] : [];
							$.voCnd.filterArray[groupName] = $.voCnd.filterArray[groupName] ? $.voCnd.filterArray[groupName] : [];
						}
						// else{
							//groupSettings.defaultValue = $.voCnd.filterArray[groupName] ? $.voCnd.filterArray[groupName] : "";
							//$.voCnd.filterArray[groupName] = $.voCnd.filterArray[groupName] ? $.voCnd.filterArray[groupName] : "";
						//}*/

						object = $('<div id="cnd-'+ groupName +'" class="cnd-header" />');
						groupObject = new $.voCnd.group( object, groupName, groupSettings );

						$('li',object).remove();
						var collapseButton = $('<div class="ui-state-default ui-corner-all cnd-button collapse"><span class="ui-icon ui-icon-triangle-1-s" /></div>')
												.toggle(groupObject.menuClose, groupObject.menuOpen );
						var cancelButton = $('<div class="ui-state-default ui-corner-all cnd-button cancel"><span class="ui-icon ui-icon-cancel" /></div>')
												.hide()
												.bind('click',groupObject.reset);

						$('<h3 />')
							.addClass('ui-corner-all')
							//.text( groupSettings.displayName ) for now I am doing this in the singleDim and multiDim functions
							.appendTo(object)
							.append(cancelButton)
							.append(collapseButton);

						$('<ul class="cnd-list"></ul>').appendTo(object);

						// Load group's item list
						switch( groupSettings.ui ){
						case 'single_menu':
							groupObject.formatSingleDimension( data );
							break;

						case 'hierarchy_menu_slider':
							groupObject.formatHierarchyMenuSlider((groupSettings.defaultValue ? groupSettings.defaultValue : 0), data);
							break;

						case 'hierarchy_menu_portal':
							groupObject.formatHierarchyMenuPortal((groupSettings.defaultValue ? groupSettings.defaultValue : 0), data);
							break;

						case 'range_slider':
							//
							break;
						}

						if(typeof(data.panelFilter[groupName]) == 'object' || data.panelFilter[groupName] == 'showall'){
							object.show();
							if(groupSettings.filter){
								groupObject.filterResults( data.panelFilter[groupName] );
							}
						}else{
							object.hide();
						}
						fragment.appendChild( object.get(0) );
					});

					$($.voCnd.settings.panel).empty().append(fragment);

					// Set Content
					if( $.voCnd.settings.initContent ){
						$.voCnd.setContent(data.content);
					}
					$('.waiting-for-results').hide();
					$($.voCnd.settings.contentDom).fadeIn('fast');
				},
			'waitingForResults' :
				function()
				{
					$($.voCnd.settings.contentDom).hide();
					$('.no-results').hide();
					$('.waiting-for-results').show();
				},

			'filterResults' :
				function( data )
				{
					$.voCnd.resultTotal = data.resultTotal;
					$.voCnd.setPaginationMessage();
					// Set Each Panel Group
					$.each($.voCnd.panelGroups, function(groupName, groupSettings){
						var groupObject = new $.voCnd.group( $('#cnd-'+ groupName), groupName, groupSettings );
						if(typeof(data.panelFilter[groupName]) == 'object' || data.panelFilter[groupName] == 'showall'){
							$('#cnd-'+ groupName).show();
							if(groupSettings.filter){
								groupObject.filterResults( data.panelFilter[groupName] );
							}
						}else{
							$('#cnd-'+ groupName).hide();
						}
					});

					// Set Content
					$('.waiting-for-results').hide();
					// Slower but more flexible for interface developers
					if(data.content_html){
						$($.voCnd.settings.contentDom).replaceWith($(data.content_html).filter('ul'));
					}else{
					// Faster because unformatted Json data is sent and is formatted by javascript.
						$.voCnd.setContent(data.content);
					}
					//$($.voCnd.settings.contentDom).fadeIn('fast');
				},

			'setContent':
				function( content )
				{
				var thisObject = this;
					var $rowTemplate = $(thisObject.settings.contentDom).comments();
					$(thisObject.settings.contentDomRow,thisObject.settings.contentDom).remove();

					if($(content).length > 0 ){
						$('.no-results').hide();
						var fragment = document.createDocumentFragment();
						$.each(content, function(key, contentItem){
							// Set list display
								var $newRow = $rowTemplate.clone();
								$newRow.setDataDisplay( contentItem );
								$.voCnd.customContentFunction( key, contentItem, $newRow );
								fragment.appendChild($newRow.get(0));
						});

						$(thisObject.settings.contentDom).append( fragment );

						$($.voCnd.settings.contentDom).fadeIn('fast');
					}else{
						$('.no-results').show();
					}
				}
	};
	
	$.voSlugFormat = function( slug )
	{
		var newSlug = slug.replace(/[^a-zA-Z0-9\s\-]/g,'');
		newSlug = newSlug.trim().replace(/\s+/g,'-');
		return newSlug.toLowerCase();
	};
	
/* Non-Standard */
	$.fn.setRSSItemDisplay = function(jsonObject)
	{
		$('.title',this).text(jsonObject.title[0].Text);
		$('.link',this).attr('href',jsonObject.link[0].Text);
		$('.description',this).html(jsonObject.description[0].Text);
		
		//$(this).show();
		return this;
	};
	
	$.fn.setRSSChannelDisplay = function(jsonObject)
	{
		$('.title',this).text(jsonObject.title[0].Text);
		$('.link',this).attr('href',jsonObject.link[0].Text);
		$('.description',this).text(jsonObject.description[0].Text);
		//$('.image',this).attr('src',jsonObject.image[0].url[0].Text);
		
		//$(this).show();
		return this;
	};
	
	// Depends on jquery.XMLUtils.pack.js
	$.fn.XMLReader = function(callerSettings)
	{
		var settings = $.extend({
			feed: false,
			sortName: 'title',
			sortOrder: 'ASC',
			limit: false
		}, callerSettings || {});
		
		var object = this;
		var feed = settings.feed ? settings.feed : $(this).attr('data-feed');
		
		$.ajax({
			type: 'POST',
			url: 'xml',
			data:{feed:feed},
			dataType: 'xml', //Make sure that you specify the type of file you expecting (XML)
			complete: function(data) {
				// Grab the display template
				var feedChannelTemplate = $('.rss-channel',object).remove();
				var feedItemTemplate = $('.rss-items .template',object).remove();
				var json = $.xmlToJSON(data.responseXML); //Please notice that we use responseXML here which is DOMDocument object
				
				$(object).prepend($(feedChannelTemplate).setRSSChannelDisplay(json.channel[0]));
				
				// Now I want to sort all item nodes by their value
				json.channel[0].item.SortByNode(settings.sortName,settings.sortOrder); //Default order is ASC
				for(var i = 0; i < json.channel[0].item.length; i++) {
					var newList = $(feedItemTemplate).clone();
					$('.rss-items', object).append($(newList).setRSSItemDisplay(json.channel[0].item[i]));
				}
				
				if( settings.limit > 0 ){
					$('.rss-items li:gt('+ (settings.limit - 1) +')', object).hide();
				}
			}
		});
	};
	
	// Twitter Interface
	$.voTwitter = {
		'settings':
			{
				'controller':null,
				'panel':'#twitter-feed',
				'contentDom': '',
				'contentDomRow': '',
				'limit': 5
			},
			
		'init': function()
		{
			$.voAjax({
				'actionController': '/action/public/common/twitter',
				'dataObject':{'count':$.voTwitter.settings.limit},
				//'beforeSend':[$.voTwitter.waitingForResults],
				'success':[$.voTwitter.setInterface]
			});
		},

		'waitingForResults' : function()
		{
			
		},

		'setInterface' : function( data )
		{
			var $tweetPanel = $($.voTwitter.settings.panel+' ul');
			var $rowTemplate = $tweetPanel.comments();
				$tweetPanel.empty();
			
			if( !data.message ){
				var fragment = document.createDocumentFragment();
				$.each( data, function( key, tweet){
					$newRow = $rowTemplate.clone();
					$newRow.find('.text').html( tweet.text );
					$newRow.find('.created_at').html( tweet.created_at );
					fragment.appendChild( $newRow.get(0) );
				});
				$tweetPanel.append( fragment );
			}else{
				$tweetPanel.append('<li>'+data.message+'</li>');
			}

			
		},

		'ajaxComplete' : function()
		{

		}
	};
	
/* Deprecated */
	
	// Deprecated because setDataDisplay    
	$.fn.setAddressForm = function(addressObject)
	{
		$(this).attr('data-json','{id:' + addressObject.id + '}');
		$('input[name="full_name"]',this).val(addressObject.full_name);
		$('input[name="address1"]',this).val(addressObject.address1);
		$('input[name="address2"]',this).val(addressObject.address2);
		$('input[name="city"]',this).val(addressObject.city);
		$('input[name="state"]',this).val(addressObject.state);
		$('input[name="zip"]',this).val(addressObject.zip);
		$('input[name="phone"]',this).val(addressObject.phone);
		
		addressObject.default_billing ? $('input[name="type_billing"]').attr('checked','checked') : $('input[name="type_billing"]').attr('checked','');
		addressObject.default_shipping ? $('input[name="type_shipping"]').attr('checked','checked') : $('input[name="type_shipping"]').attr('checked','');
		
		//addressObject.default_billing ? $('input[name="phone"]',this)
		
		return this;
	};
	
	$.fn.loadFileList = function( callerSettings )
	{
		var settings = $.extend({
			actionPath: false,
			data: {},
			sortOrder: 'ASC'
		}, callerSettings || {});
		
		var $object = $(this);
		$object.find('li').empty();
		
		// Grab the display template from the pop-boxes version that way cloned
		// manipulations don't interfere with process
		var $fileTemplate = $object.comments();

		$.getJSON(settings.actionPath, settings.data, function( data ) {
			$.each( data.files, function(key, file ) {
				$newFile = $fileTemplate.clone();
				$newFile.setFileInterface( file );
				$object.append($newFile);
			});
/** BUG ALERT The code below unbinds multiple events on the page. **/
			// Delete button to remove one or more images
			// Depends on jquery.json.js - $.toJSON()

//			$(document).unbind().keyup(function(e){
//				if(e.keyCode == 46){
//					if(confirm('Are you sure you want to delete?')){
//						var idArray = new Object();
//						$('li.ui-selected', $('#vo2-overlays #photo-list')).each(function(i){
//							idArray[i] = $(this).metadata().id;
//							$(this).remove();
//						});
//
//						$.getJSON( $.jqPHP.getUrl('admin-cms-image-remove'),
//									{page_id:$('#page').metadata().id,image_id:$.toJSON(idArray)},
//									function(){});
//					}
//				}
//			});
		});
	};
	
	$.Weather = function( initOptions )
	{
		var options = $.extend({
			'zip':'47546', // Jasper
			'div':{
				'cc':$('div.cc'),
				'dayf':$('div.dayf'),
				'lnks':$('div.weather-lnks')
			},
			'iconCC':115, // 31, 61, 93, 115
			'iconDayf':115, // 31, 61, 93, 115
			'iconDir':'/icons',
			'updateLocation':0,
			'url':'/action/public/common/weather-xml'
		}, initOptions );

		this.loc = {};
		this.lnks = [];
		this.cc = {};
		this.dayf = {};
		this.loaded = 0;
		var obj = this;
		
		this.getCurrentConditions = function()
		{
			$.ajax({
				type: 'POST',
				url: options.url,
				data:{'zip':options.zip,'cc':'*','dayf':0,'update_location':options.updateLocation},
				dataType: 'xml', //Make sure that you specify the type of file you expecting (XML)
				success:function( data ){
					//var json = $.xmlToJSON(data); //Please notice that we use responseXML here which is DOMDocument object
					//$.log(json.cc[0].icon[0].Text);
					
					if($('error',data).length > 0 ){
						switch(parseInt($('error',data).attr('type'))){
						// Invalid zip code
						case 2:
							// Reload with defaults
							$.log('throw error message here: Invalid zip code.');
							options.zip = '47546'; // Use this when error returned from weather.com
							obj.getCurrentConditions();
						break;
						default:
							$.log('throw Default error message here');
							/*options.zip = '47546'; // Use this when error returned from weather.com
							obj.init();*/
						break;
						}
						
					}else{
						var $xloc = $('loc',data);
						var $xcc = $('cc',data);
						obj.loc = {
							'id':$($xloc).attr('id'),
							'dnam':$('dnam',$xloc).text(),
							'tm':$('tm',$xloc).text(),
							'lat':$('lat',$xloc).text(),
							'lon':$('lon',$xloc).text(),
							'sunr':$('sunr',$xloc).text(),
							'suns':$('suns',$xloc).text(),
							'zone':$('zone',$xloc).text()
						};
						
						$('lnks link',data).each(function(i,object){
							var link = {};
							link.l = $('l',this).text();
							link.t = $('t',this).text();
							obj.lnks.push(link);
						});
						
						obj.cc = {
							'lsup':$('lsup',$xcc).text(),
							'obst':$('obst',$xcc).text(),
							'tmp':$('tmp',$xcc).text(),
							'flik':$('tmp',$xcc).text(),
							't':$('t:first',$xcc).text(),
							'icon':$('icon:first',$xcc).text(),
							'bar':{
								'r':$('bar r',$xcc).text(),
								'd':$('bar d',$xcc).text()
							},
							'wind':{
								's':$('wind s',$xcc).text(),
								'gust':$('wind gust',$xcc).text(),
								'd':$('wind d',$xcc).text(),
								't':$('wind t',$xcc).text()
							},
							'hmid':$('hmid',$xcc).text(),
							'vis':$('vis',$xcc).text(),
							'uv':{
								'i':$('uv i',$xcc).text(),
								't':$('uv t',$xcc).text()
							},
							'dewp':$('dewp',$xcc).text(),
							'moon':{
								'icon':$('moon icon',$xcc).text(),
								't':$('moon t',$xcc).text()
							}
						};

						obj.setCurrentConditions();
					}
				},
				'complete': function()
				{
					
				}
			});
		};
		
		this.getForecast = function( numberOfDays )
		{
			$.ajax({
				type: 'POST',
				url: options.url,
				data:{'zip':options.zip,'cc':0,'dayf':numberOfDays,'update_location':options.updateLocation},
				dataType: 'xml', //Make sure that you specify the type of file you expecting (XML)
				success:function( data ){
					//var json = $.xmlToJSON(data); //Please notice that we use responseXML here which is DOMDocument object
					//$.log(json.cc[0].icon[0].Text);
					
					if($('error',data).length > 0 ){
						switch(parseInt($('error',data).attr('type'))){
						// Invalid zip code
						case 2:
							// Reload with defaults
							$.log('throw error message here: Invalid zip code.');
							options.zip = '47546'; // Use this when error returned from weather.com
							obj.getForecast();
						break;
						default:
							$.log('throw Default error message here');
							/*options.zip = '47546'; // Use this when error returned from weather.com
							obj.init();*/
						break;
						}
						
					}else{
						var $xdayf = $('dayf',data);
						obj.dayf.lsup = $('lsup',$xdayf).text();
						obj.dayf.days = [];
						
						$('day',$xdayf).each(function(i,object){
							
							obj.dayf.days.push({
								't':$(this).attr('t'),
								'dt':$(this).attr('dt'),
								'hi':$('hi',this).text(),
								'low':$('low',this).text(),
								'day':{
									'icon':$('part[p=d] icon', this).text(),
									't':$('part[p=d] t:first', this).text(),
									'wind':{
										's': $('part[p=d] wind s', this).text(),
										'gust': $('part[p=d] wind gust', this).text(),
										'd': $('part[p=d] wind d', this).text(),
										't': $('part[p=d] wind t', this).text()
									},
									'bt':$('part[p=d] bt', this).text(),
									'ppcp':$('part[p=d] ppcp', this).text(),
									'hmid':$('part[p=d] hmid', this).text()
							
								},
								'night':{
									'icon':$('part[p=n] icon', this).text(),
									't':$('part[p=n] t:first', this).text(),
									'wind':{
										's': $('part[p=n] wind s', this).text(),
										'gust': $('part[p=n] wind gust', this).text(),
										'd': $('part[p=n] wind d', this).text(),
										't': $('part[p=n] wind t', this).text()
									},
									'bt':$('part[p=n] bt', this).text(),
									'ppcp':$('part[p=n] ppcp', this).text(),
									'hmid':$('part[p=n] hmid', this).text()
							
								}
							});
						});
						
						obj.setDayForecast( numberOfDays );						
						obj.setLinks();
					}
				},
				'complete': function()
				{
					
				}
			});
		};
		
		this.setCurrentConditions = function()
		{
			$('img.icon',options.div.cc).attr({'src':options.iconDir+options.iconCC+'x'+options.iconCC+'/'+obj.cc.icon+'.png','title':obj.cc.t,'alt':obj.cc.t});
			$('.location',options.div.cc).text(obj.loc.dnam);
			$('.tmp',options.div.cc).html(obj.cc.tmp+'&deg;');
			$('.flik',options.div.cc).html(obj.cc.flik+'&deg;');
			$('.description',options.div.cc).html(obj.cc.t);
			$('.uv',options.div.cc).text(obj.cc.uv.i+' '+obj.cc.uv.t);
			$('.wind',options.div.cc).text(obj.cc.wind.t+' '+obj.cc.wind.s+' mph');
			$('.humidity',options.div.cc).text(obj.cc.hmid+'%');
			//$('.pressure',options.div.cc).text(obj.cc.bar.r+' '+obj.cc.bar.d);
			$('.pressure',options.div.cc).text(obj.cc.bar.r);
			$('.dew-point',options.div.cc).html(obj.cc.dewp+'&deg;');
			$('.visibility',options.div.cc).text(obj.cc.vis+' miles');
			$('.sunrise',options.div.cc).text(obj.loc.sunr);
			$('.sunset',options.div.cc).text(obj.loc.suns);
		};
		
		this.setDayForecast = function( numberOfDays )
		{
			var fragment = document.createDocumentFragment();
            var $rowTemplate = $('ul', options.div.dayf).comments();
            $('ul li', options.div.dayf).remove();
           
            $.each(obj.dayf.days, function (i, day){
                // Set list display
                var $newRow = $rowTemplate.clone();
                //var dayHeader = day.t+', '+day.dt;
                var dayHeader = day.t;
                if(i == 0 ){
                	dayHeader = 'Today';
                }
                $('.day-header',$newRow).text(dayHeader);
                
                $('.high',$newRow).html(day.hi+'&deg;');
                $('.low',$newRow).html(day.low+'&deg;');
                // Day
                $('img.icon-day',$newRow).attr('src',options.iconDir+options.iconDayf+'x'+options.iconDayf+'/'+day.day.icon+'.png');
                $('.condition-day',$newRow).text(day.day.t);
                $('.precip-day',$newRow).text(day.day.ppcp+'%');
                $('.humidity-day',$newRow).text(day.day.hmid+'%');
                // Night
                $('img.icon-night',$newRow).attr('src',options.iconDir+options.iconDayf+'x'+options.iconDayf+'/'+day.night.icon+'.png');
                $('.condition-night',$newRow).text(day.night.t);
                $('.precip-night',$newRow).text(day.night.ppcp+'%');
                $('.humidity-night',$newRow).text(day.night.hmid+'%');
                if(numberOfDays == (i+1)){ $newRow.addClass('last'); }
                fragment.appendChild($newRow.get(0));
            });
            $('ul', options.div.dayf).append(fragment);
		};
		
		this.setLinks = function()
		{
			var fragment = document.createDocumentFragment();
            var $rowTemplate = $('ul', options.div.lnks).comments();
            $('ul li', options.div.lnks).remove();
            
            // Custom links
            obj.lnks.splice(0,0,
            	{'t':'Ten Day Forecast','l':'http://www.weather.com/weather/tenday/'+options.zip},
            	{'t':'Hour-by-Hour','l':'http://www.weather.com/weather/hourbyhour/graph/'+options.zip}
            );
            
            $.each(obj.lnks, function (i, link){
                // Set list display
                if( $.inArray(link.t,['Airport Conditions','Rush Hour Traffic']) == -1 ){
	                var $newRow = $rowTemplate.clone();
	                $('a',$newRow).text(link.t);
	                $('a',$newRow).attr('href',link.l);
	                fragment.appendChild($newRow.get(0));
                }
            });
            $('ul', options.div.lnks).append(fragment);
		};
	};
	
	$.fn.formatErrorMessage = function( data_error_message, data_dom_error, customContainer, customScrollContainer )
	{
		// By default an .error-message div container is set in a voForm.
		// Custom container gives the option to set a container for the error
		// message to go to outside of voForm block.
		var $errorContainer = ! customContainer ? $('.error-message',this) : customContainer;
		var $scrollContainer = ! customScrollContainer ? $('html, body') : customScrollContainer;
		// Move to top of page
		$scrollContainer.animate({scrollTop:0}, 'slow');
		
		if(data_error_message){
			var error_message = '';
			if(typeof(data_error_message) == 'object'){
				$.each(data_error_message,function(i,message){
					error_message += '<div>'+message+'</div>';
				});
			}else{
				error_message = data_error_message;
			}
			$errorContainer.html(error_message).fadeIn('fast');
		
			$(':input.error').removeClass('error');
			
			if( data_dom_error ){
				$.each(data_dom_error,function(i,class_name){
					$(':input.'+class_name).addClass('error');
					$(':input.'+class_name).keyup(function(e){
						if( $(this).val() == '' ){
							$(this).addClass('error');
						}else{
							$(this).removeClass('error');
						}
					});
				});
			}
		}else{
			$errorContainer.fadeOut('fast');
		}
	};
	
})(jQuery);
	
// This assists file uploads. We can clean this up later
	var newStageImageTemplate; // For adding dom outside of ready function
	var voFile = function( callerSettings ){
		
		this.settings = $.extend({
			'action':'/action/admin/cms/file-upload',
			'ulDom':'',
			'relateId':'',
			'relateType':''
		}, callerSettings);
		
		this.ulDom = typeof(this.settings.ulDom) == 'object' ?  this.settings.ulDom : $(this.settings.ulDom);
		var obj = this;
		
		this.addFileToDom = function( id, fileName )
		{
			var $newRow = typeof(newStageImageTemplate) == 'object' ? newStageImageTemplate.clone() : this.ulDom.comments();
			$newRow.attr('id','loading'+id);
			$newRow.attr('class','loading');
			var $progressBar = $('<div class="progress-bar" />').progressbar({value:0});
			$newRow.append('<div class="loading-title">Loading <span class="loading-file-name">'+ fileName +'</span></div>');
			$newRow.append($progressBar);
			$('img', $newRow).hide();
			
			$newRow.appendTo(this.ulDom);
		};
		
		this.setProgressBar = function setProgressBar( id, percent )
		{
			$('#loading'+id+' .progress-bar').progressbar('option', 'value', percent);
		};
		
		this.loadThumbnail = function loadThumbnail( id, xhr )
		{
			var $loadingBox = $('#loading'+id);
			$('.progress-bar', $loadingBox).progressbar('destroy');
			
			// Handle HTTP status response
			switch( xhr.status ){
			case 200: // Successful response
				// Load object here
				var data = typeof(xhr.responseText) == 'string' ? $.evalJSON(xhr.responseText) : xhr.responseText;
				
				if( data.error_message ){
					var $removeButton = $('<div><a href="#">Remove</a></div>').click(function(){ $loadingBox.remove(); return false; });
					$('.loading-title, .image-controls',$loadingBox).remove();
					$('.image',$loadingBox).text(data.error_message).append($removeButton);
					$loadingBox.addClass('bad');
				}else{
					$('.progress-bar, .loading-title',$loadingBox).remove();
					this.setInterface( '#loading'+id, data );
					$($loadingBox).hover(function(e){
						$('.image-controls',this).fadeIn('fast');
					},
					function(){
						$('.image-controls',this).fadeOut('fast');
					});
				}
				$loadingBox.attr('id','').removeClass('loading');
			break;
			
			case 500: // Server error, usually a program error
				var $removeButton = $('<div><a href="#">Remove</a></div>').click(function(){ $loadingBox.remove(); return false; });
				var fileName = $('.loading-file-name', $loadingBox).text();
				$('.image-controls',$loadingBox).remove();
				$('.loading-title',$loadingBox).html('Error loading <strong>'+ fileName + '</strong>. If problems persist send file to support.').append($removeButton);
				$loadingBox.addClass('bad');
				$loadingBox.attr('id','').removeClass('loading');
			break;
			
			}
			
		};
		
		this.setInterface = function( loadingObject, data )
		{
			// Set externally 
		};
		
		this.uploadFiles = function ( files ) {
			if( (typeof(FileReader) != 'function') ){
				//alert($(this).val());
				var $inputFile = $(this);
				var $thisParent = $(this).parent();
				var $inputFileClone = $(this).clone(true);
				$inputFile.attr('name','Filedata');
				var max = 10000;
				var id = obj.ulDom.children('li').length;
				obj.setInterface = function( loadingObject, data )
				{
					$(loadingObject).setFileInterface( data.file );
				};
				obj.addFileToDom( id, $(this).val().replace(/.*fakepath\\/,'') );
				
				var $iframe = $( '<iframe name="postframe" id="post-frame" src="about:none" style="visibility:hidden; width:0; height:0;" />' );
				$('body').append( $iframe );
				var $uploadForm = $('<form />').attr({
					'action':obj.settings.action,
					'method':'POST',
					'fileData':$inputFile.val(),
					'MAX_FILE_SIZE':max,
					'enctype':'multipart/form-data',
					'encoding':'multipart/form-data',
					'target':'postframe'
				}).css({'visibility':'hidden'}).append($inputFile).appendTo('body').submit();
				
				//need to get contents of the iframe
				$iframe.load(
					function(){
						iframeContents = $iframe[0].contentDocument.body.innerHTML;
						// This error handling could be spoofed, but it is all I have right now
						// I know all good response should be a proper JSON string, so I check
						// for the first left curly bracket {.
						if( iframeContents[0] == '{' ){
							var data = $.evalJSON(iframeContents);
							// Fake an xhr 200 status for now
							var fakeXhr = {'status':200,'responseText':data};
							obj.loadThumbnail( id, fakeXhr );
						}else{
							obj.loadThumbnail( id, {'status':500} );
						}
						$uploadForm.remove();
						$iframe.remove();
						// For IE Only
						$thisParent.prepend($inputFileClone);
					}
				);
				
			}else{
				var fileList = this.files ? this.files : files;
				var uploadNext = 0;
				
				$.each( fileList, function(i,file){
					obj.addFileToDom( i, file.name );
				});
				
				function upload(id, file){
					
				    var reader = new FileReader();
				    
				  // var imageType = /image.*/;
				  /* if (file.type.match(imageType)) {
				    	var img = new Image();
					    reader.onload = function(e)
					    {
					    	img.src = e.target.result;
					    	img.onload = function()
					    	{
					    		$.log(img.width);
					    	};
					    };
				    }*/
	
				    var fileName = file.name;
				    var fileSize = file.size;
				    var fileType = file.type;
					reader.onloadend = function()
					{
						var source = reader.result;
						var jsonObject = {
							'id':obj.settings.relateId,
							'name':fileName,
							'size':fileSize,
							'type':fileType,
							'source':source,
							'relate':obj.settings.relateType
						};
						var xhr = new XMLHttpRequest();
						
						xhr.upload.addEventListener("progress", function(e) {
					    	var percentage = Math.round((e.loaded * 100) / e.total);
					    	obj.setProgressBar( id, percentage );
					    }, false);
	
						xhr.upload.addEventListener("load", function(e) {
							obj.setProgressBar( id, 100 );
					    }, false);
						
						var jsonString = $.toJSON(jsonObject);
						xhr.open("POST", obj.settings.action, true);
						xhr.onload = function(event)
						{
							obj.loadThumbnail( id, xhr );
							
							if(fileList.length > (uploadNext + 1)){
								uploadNext++;
								upload(uploadNext,fileList[uploadNext]);
							}
						};
						
						xhr.overrideMimeType('text/plain; charset=x-user-defined-binary');
						
						xhr.sendAsBinary(jsonString);
					};
	
					reader.readAsDataURL(file);
				};
				
				upload(uploadNext,fileList[uploadNext]);
			}
		};
	};

// END OF PLUGINS

// Body stuff

/*
Example DOM:
<ul id="n1">
	<li>Level 1
		<ul>
			<li>Level 2</li>
			<li>Level 2</li>
		</ul>
	</li>
	<li>Level 1</li>
</ul>

*/

$(document).ready(function(){

	var nav1 = $('.dropdowns');
	var nav2 = $('li ul',nav1);

	function nav(level)
	{
		var nextLevel = $('li ul',level);
		$(nextLevel).hide()
			.siblings('a').addClass('button').removeAttr('href')
			.mouseover(function(){
				//$(nextLevel).not(':hidden').hide('slow');

				if($(this).siblings('ul').is(':hidden')){
					$(nextLevel).siblings('a').removeClass('buttonOver');
					$(this).addClass('buttonOver');

					$(nextLevel).slideUp('slow');
					$(this).siblings('ul').slideDown('slow');
				}else{
					$(nextLevel).siblings('a').removeClass('buttonOver');
					$(this).siblings('ul').slideUp('slow');
				}
			});
	}
	
	// Click Image Button
	$('#dialog-media-manager').dialog({
		'autoOpen':false,
		'bgiframe':true,
		'height':450,
		'width':960,
		'modal':true,
		'position':'top',
		'title':'Media Manager'
	});
	
// Forms
	$('label.hint').each(function(){
		
		var forField	= $(this).attr("for");
		var input		= $("input[name='"+forField+"']");
		var replaceMethod = $(this).is('.overlay') ? 'overlay' : 'value';
	
			switch( replaceMethod ){
			case 'overlay':
				var $label = $(this);
				var labelText	= $label.text();
				$(input).focus(function(){
					if($(input).val() == labelText){
						$(input).val('');
					}
					$label.css('text-indent','-10000px');
					
				}).blur(function(){
					if($(input).val() == ''){
						$label.css('text-indent','');
					}
				}).blur(function(){
					if($(input).val() == ''){
						$label.css('text-indent','');
					}
				});
				if( input.val() ){
					$label.css('text-indent','-10000px');
				}
				$label.click(function(){ $(input).trigger('focus'); });
			break;
			
			case 'value':
				var labelText	= $(this).remove().text();
				if( $(input).val() == '' ){ $(input).addClass('placeholder').val(labelText); }
				
				$(input).focus(function(){
					if( this.value == labelText ){
						$(this).removeClass('placeholder').val('');
					}
				}).blur(function(){
					if( this.value == '' ){
						$(this).addClass('placeholder').val(labelText);
					}
				});
			break;
			}
		
	});
	
// Button Events (UI)
if($.isFunction($.fn.button)){
	$('button.gui-save').button({text:false,icons:{primary:'ui-icon-check'}});
	$('button.gui-cancel').button({text:false,icons:{primary:'ui-icon-cancel'}});
	$('button.gui-closethick,a.gui-closethick').button({text:false,icons:{primary:'ui-icon-closethick'}});
	$('button.gui-newwin, a.gui-newwin').button({text:false,icons:{primary:'ui-icon-newwin'}});
	$('button.gui-text').button();
	// To remove the focus state... seems to be a bug.
	$('button.gui-text').ajaxSuccess(function(){ $(this).removeClass('ui-state-focus').removeClass('ui-state-hover'); });
}

});
//Handle money formatting
function isThousands( position )
{
	if (Math.floor(position/3)*3==position) return true;
	return false;
};

function formatMoney(theNumber,theCurrency,theThousands,theDecimal)
{
	theNumber = parseFloat(theNumber);
	
	if( !isNaN(theNumber) ){
	theNumber = parseFloat(theNumber);
	var theOutput = theCurrency;
	if( theNumber >= 0 ){
		var theDecimalDigits = Math.round((theNumber*100)-(Math.floor(theNumber)*100));
		theDecimalDigits= ""+ (theDecimalDigits + "0").substring(0,2);
		theNumber = ""+Math.floor(theNumber);
		
	}else{
		var theDecimalDigits = Math.abs(Math.round((theNumber*100)-(Math.ceil(theNumber)*100)));
		theDecimalDigits= ""+ (theDecimalDigits + "0").substring(0,2);
		theNumber = ""+Math.abs(Math.ceil(theNumber));
		theOutput += "-"; 
	}
	
	for (x=0; x<theNumber.length; x++) {
		theOutput += theNumber.substring(x,x+1);
		if (isThousands(theNumber.length-x-1) && (theNumber.length-x-1!=0)) {
			theOutput += theThousands;
		};
	};
	
	theOutput += theDecimal + theDecimalDigits;
	}else{
		theOutput = +'0.00';
	}
	return theOutput;
};

function formatErrorMessage( data_error_message, data_dom_error, errorObject )
{
	// Move to top of page
	$('html, body').animate({scrollTop:0}, 'slow');
	errorObject = errorObject ? errorObject : $('#error-message');
	
	if(data_error_message){
		var error_message = '';
		if(typeof(data_error_message) == 'object'){
			$.each(data_error_message,function(i,message){
				error_message += '<div>'+message+'</div>';
			});
		}else{
			error_message = data_error_message;
		}
		errorObject.html(error_message).fadeIn('fast');
		$(':input.error').removeClass('error');
		$.each(data_dom_error,function(i,class_name){
			$(':input.'+class_name).addClass('error');
			$(':input.'+class_name).keyup(function(e){
				if( $(this).val() == '' ){
					$(this).addClass('error');
				}else{
					$(this).removeClass('error');
				}
			});
		});
	}else{
		errorObject.fadeOut('fast');
	}
};
