


/** copia.jsp **/



Copia = {
	vars: {},
	init: function(){
		var body = $('body');
		Copia.vars.page = $('body').attr('id');
		Copia.vars.pageType = $('body').attr('class') || 'userMod';
		var personas = ['copia', 'user', 'friend', 'group'];
		for(i in personas){
			if(body.hasClass(personas[i] + 'Identity')){
				Copia.vars.persona = personas[i];
			}
		}
		$.ajaxSettings.traditional = true;
		$.ajaxSettings.cache = false;
		
		if($.flash){ $.flash.expressInstaller = '/flash/swf/expressInstall.swf'; }
	},
	url: function(url, personaType, personaId){
		if(arguments.length < 2) {
			var personaType = _C.personaType,
				personaId = _C.personaId;
		}
		
		if(!personaType || personaType == "ALL"){ return url; }
		
		var extraParams = {
			p : personaType.substring(0,1).toLowerCase() + personaId
		};
		
		return $.param.querystring(url, extraParams , 1)
	},
	shorten: function(str, max){
		return (str.length > max) ? str.substr(0, max - 1) + '&hellip;' : str;
	},
	truncate : function(string, max){
		var $elems = $(string),
			len = $elems.text().length,
			$elem, diff, text, newText;

		var arr = $.makeArray($elems);
		if(len > max){
			for(var i = arr.length - 1; i > -1; i--){
				$elem = $(arr[i])
				text = $elem.text();
				len -= text.length
				if(max < len){
					arr.pop();
				} else {
					newText = text.substring(0, max - len);
					if($elem[0].nodeType == 3){ $elem[0].textContent = newText; }
					else { $elem.text(newText); }
					break;
				}
			}
		}
		return $([]).pushStack(arr);
	}
}
C = Copia;
_C = Copia.vars;

/**
 * Adds Copia.init to document.ready.
 * It will be run first of the Copia document.readies which makes the initialized variables accessible to subsequent files.
 */
$(document).ready(Copia.init);
/** copia2.jsp **/








/*
	_C.stats.action("add-to-library", "Hello World");
	_C.stats.action("edit-meta-data", "Tags");
 */
(function(){
	Copia.Stats = {};
	var self = Copia.Stats;
	var tracker = null;
	$.extend(Copia.Stats, {
		init : function(page, pageGroup, user){
			if(window.YMA){
				tracker = YWA.getTracker("1000651242210");
				tracker.setDomains("www.copia.com, beta.copia.com")
				tracker.setDocumentGroup(pageGroup);
				tracker.setDocumentName(page);
				tracker.setMemberId(user);
				tracker.submit();
			}
		},
		action : function(action, value, submit){
			if(tracker){
				var aCode = self.ActionCodes[action];
				tracker.setAction(aCode)
				
				if(arguments.length > 1){
					var fCode = self.FieldCodes[action];
					tracker.setCF(fCode, value);
				}
				if(submit != true){ self.submit(); }
			}
		},
		search : function(term, results){
			if(tracker){
				tracker.setAction("INTERNAL_SEARCH")
				tracker.setISK(term);
				//tracker.setISR(results);
				self.submit();
			}
		},
		submit: function(){
			tracker.submit_action();
		},
		ActionCodes : {
			'registration' 	 	  : '02',
			'log-in' 		 	  : '03',
			'ebook-download' 	  : '05',
			'add-to-library' 	  : '06', // Implemented
			'edit-meta-data' 	  : '07',
			'create-group' 	 	  : '08', // Implemented
			'join-group' 	 	  : '09', // Implemented
			'join-disscussion' 	  : '10', // Implemented
			'add-friend' 		  : '11', // Implemented
			'book-privacy-change' : '12'
		},
		FieldCodes : {
			'log-in' 		 	  :  1,
			'ebook-download' 	  : 24,
			'add-to-library' 	  : 18,
			'edit-meta-data' 	  : 19,
			'join-discussion'	  : 21,
			'add-friend' 		  : 23,
			'book-privacy-change' : 22
		}
	});
	
	$(document).ready(function(){
		C.Stats.init(null, _C.page, _C.userId);
	});
})()



$.fn.autoComplete = function(url, options){
	$(this).each(function(){
		new Copia.AutoComplete(this, url, options);
	});
};

Copia.AutoComplete = function(elem, url, options){
	this.init(elem, url, options);
};

$.extend(Copia.AutoComplete, {
	specialKeys: {
		27: 'esc', 9: 'tab', 32:'space', 13: 'return', 8:'backspace', 145: 'scroll', 
		20: 'capslock', 144: 'numlock', 19:'pause', 45:'insert', 36:'home', 46:'del',
		35:'end', 33: 'pageup', 34:'pagedown', 37:'left', 38:'up', 39:'right',40:'down', 
		112:'f1',113:'f2', 114:'f3', 115:'f4', 116:'f5', 117:'f6', 118:'f7', 119:'f8', 
		120:'f9', 121:'f10', 122:'f11', 123:'f12' },
	defaultOptions: {
		// Delay in milliseconds between when the user stops typing and the autocomplete activates
		delay: 250,
		startLength: 4,
		parser: function(data){ return data; },
		wrapPrefix : '',
		wrapSuffix : ''
	},
	prototype:{
		// Houses the constructor
		init:function(elem, url, options){
			this.options = $.extend({}, Copia.AutoComplete.defaultOptions, options);
		
			this.$elem = $(elem)
				.attr('autocomplete', 'off')
				.bind({
					'keyup.Copia.AutoComplete' : $.proxy(this, "keypress"),
					'blur.Copia.AutoComplete'  : $.proxy(this, "hide"),
					'click.Copia.AutoComplete' : $.proxy(this, "clear") 
				});
			
			this.url = url;
			this.current = null; // current Suggestion

			this.initList();
		},
		// creates and initializes the suggestion list
		initList: function(){
			var offset = this.$elem.offset();
			this.$list = $('<ul>', {
				'class': 'autoCompleteList',
				'css'  : {
					'top': offset.top + this.$elem.outerHeight(),
					'left': offset.left,
					'width': this.$elem.innerWidth()
				}
			}).appendTo('body');
			$("ul.autoCompleteList li").live("mouseover mouseout", function(e) {
				$(e.currentTarget).toggleClass("active");
			});
		},
		// Handles user pressing a key while the search bar in in focus
		keypress:function(e){
			e.stopPropagation();
			e.preventDefault();
			
			var isVisible = this.$list.is(':visible');
			
			if($.trim(this.$elem.val()).length > this.options.startLength - 1 && !(e.keyCode in Copia.AutoComplete.specialKeys)){
				$.timeout('Copia.AutoComplete', $.proxy(this, "load"), 500)
			}else if( isVisible && e.keyCode == 40){
				this.next();
			}else if(isVisible && e.keyCode == 38){
				this.prev();
			}else if(e.keyCode == 13){
				//this.setValue();
				this.submit();
				this.hide();
			}else if(this.$elem.val().length < this.options.startLength - 1 ){
				this.hide();
			}
		},
		// Fills text field with previous suggestion
		prev:function(){
			var prev = (this.current == null || this.current.prev().length == 0 ) ? this.$list.children().last() : this.current.prev();
			this.changeActive(prev);
		},
		// Fills text field with next suggestion
		next:function(){
			var next = ( this.current == null || this.current.next().length == 0) ? this.$list.children().first() : this.current.next();
			this.changeActive(next);
		},
		changeActive: function(newActive){
			if(newActive.length > 0){
				if(this.current != null){ this.current.removeClass('active'); }
				this.current = newActive.addClass('active');
				this.setValue(true);
			}
		},
		// Sets the value of the input field equal to the text of the current suggestion
		setValue: function(noWrap){
			var txt = this.$list.find('.active').text();
			if(txt != ''){
				if(noWrap == true){
					this.$elem.val(txt)
				} else {
					this.$elem.val(this.options.wrapPrefix + txt + this.options.wrapSuffix);
				}
			}
		},
		// Submits the search
		submit:function(){
			if(this.$list.find('.active').length > 0) {
				this.setValue();
				this.hide();
				this.$elem.parents('form').submit();
			} else this.hide();
		},
		show: function(data){
			data = this.options.parser(data);
			this.$list.empty();
			if(!this.$elem.val() || data.length == 0){
				this.$list.fadeOut('fast');
			} else {
				var html = [];
				$.each(data, function(i, item){
					html.push('<li>' + item + '</li>');
				});
				this.$list.html(html.join('')).fadeIn("fast");
			}
			$('li', this.$list).click($.proxy(this, 'suggestionClick'));
		},
		// hides $list
		hide:function(e){
			if(e && this.$list.find(".active").length > 0) {
				//console.log(["nothing to hide", e, this.$list.find(".active").length]);
				return;
			}
			this.$list.find(".active").removeClass("active");
			
			$.timeout('Copia.AutoComplete', $.noop);
			var me = this;
			setTimeout(function(){ me.$list.hide(); }, 20);
			/*
			if(!e || $(e.currentTarget).parents(".autoCompleteList").length == 0) {
				var me = this;
				$.timeout('Copia.AutoComplete', function(){ me.$list.hide(); }, 20);
			}*/
		},
		load:function(){
			$.ajax({
				url: this.url + this.$elem.val(), 
				dataType: 'json',
				success: $.proxy(this, "show")
			});
		},
		suggestionClick:function(e) {
			$(e.currentTarget).addClass('active');
			this.submit();
		},
		clear: function(){
			if(this.current){ this.current.removeClass('active'); }
		}
	}
});



/**
 *	Sortable selectable table
 *
 *	==== HTML ====
	
	<!-- Check boxes are optiona -->
	<div class="datatable">
		<!-- This table acts as a thead -->
		<table cellpadding="0" cellspacing="0">
			<tr>
				<caption>
					<label>Filter Sort Column: <input type="text" class="datatableSearch" /></label>
				</caption>
				<th class="sorted up"><div class="sortArrow"></div><input type="checkbox" />Title</th>
				<th><div class="sortArrow"></div>Privacy</th>
			</tr>
		</table>
		<div class="data">
			<!-- Stores content rows -->
			<table cellpadding="0" cellspacing="0">
					<tr>
						<!-- If there is a checkbox it will toggled when the row gets clicked on -->
						<td><input type="checkbox" name="colId" value="38"/>hello</td>
						<td><em>Private</em></td>
					</tr>
			</table>
		</div>
	</div>

 *	==== Javascript Stuff ====
 *
 *	 Initialization
 *		$(selector).dataTable([optionsObject])
 *
 *	Options:
 *		sortColumn (int) the 0 based index of the column you wanted sorted intially
 *		sortDirection (asc|desc) the direction to initially sort the column
 *	
 *	Sorting:
 *		If you dont want a column sorted by its text content you can specify an attribute called "data-sort-value" on the <td>
 */
$.fn.dataTable = function(options){
	$(this).each(function(){
		new Copia.DataTable(this, options);
	});
}

Copia.DataTable = function(elem, options){
	this.init(elem, options);
}

Copia.DataTable.defaultOptions = {
	sortColumn : 0,
	sortDirection : 'asc',
	selectCallback : null
}

Copia.DataTable.prototype = {
	init: function(elem, options){
		this.$elem = $(elem).bind('reInit', $.proxy(this, 'reInit'));
		this.options = $.extend({}, Copia.DataTable.defaultOptions, options);
		
		this.$headers = $('th', this.$elem).click($.proxy(this, 'headerClick'));
		this.$selectAllCheckBox = $(':checkbox, :radio', this.$headers).click($.proxy(this, 'selectAllRows'));
		
		this.$datatable = $('table:eq(1)', this.$elem);
		
		this.reInit();
		
		this.$searchBox = $(':text', this.$elem).eq(0).keyup($.proxy(this, 'filter'));
		
		this.sortColumn = -1;
		this.$headers.eq(this.options.sortColumn).trigger('click', this.options.sortDirection);
	},
	filter: function(e){
		var regexFilter = new RegExp(this.$searchBox.val() + ".*", 'i')
		$rows = this.$rows;
		for(var i = 0; i < $rows.length; i++){
			$row = $rows.eq(i);
			$row.toggle(regexFilter.test($row.find('td').eq(this.sortColumn).text()));
		}
		this.colorize();
	},
	selectAllRows : function(e){
		e.stopPropagation();
		this.$rows.not(":hidden").find(':checkbox')
			.attr('checked', !$(e.currentTarget).is(':checked'))
			.end()
			.click();
	},
	selectRow : function(e){
		var $target = $(e.currentTarget),
			$checkbox = $target.find(':checkbox, :radio'),
			checked = !$checkbox.is(':checked');
		$checkbox.attr('checked', checked);
		var $row = ($target.is('tr')) ? $target : $target.parents('tr').eq(0);
		$row.toggleClass('selected', checked);
		$('caption .count', this.$elem).text(this.$rows.filter('.selected').length);
		if(this.options.selectCallback) {
			this.options.selectCallback.apply(window, [$row]);
		}
	},
	headerClick : function(e, sortType){
		var $target = $(e.currentTarget);
		
		$target.toggleClass('asc', sortType == 'asc' || !$target.hasClass('asc'));
	
		this.$headers.eq(this.sortColumn).removeClass('sorted');
		this.sortColumn = this.$headers.index($target);
		
		$target.addClass('sorted');

		this.sort($target.hasClass('asc'));
	},
	sort : function(asc){
		var sortColumn = this.sortColumn,
			arr = $.makeArray(this.$rows).sort($.proxy(this, (asc ? 'asc' : 'desc') + 'Sort'));
		this.$rows.detach();
		this.$datatable.html(arr);
		this.colorize();
	},
	ascSort : function(a, b){ 
		return this.compare(a, b);
	},
	descSort : function(a, b){
		return this.compare(b, a); 
	},
	compare: function(a, b, sortColumn){
		var $columnA = $(a).find('td').eq(this.sortColumn),
			a = $columnA.attr('data-sort-value') || $columnA.text(),
			$columnB = $(b).find('td').eq(this.sortColumn),
			b = $columnB.attr('data-sort-value') || $columnB.text();
		
		// Fixes number comparison
		if(/^([0-9]*)$/.test(a)){ a = parseInt(a); }
		if(/^([0-9]*)$/.test(b)){ b = parseInt(b); }
		
		return (a > b) ? 1 : (a < b) ? -1 : 0;
	},
	colorize: function(){
		this.$datatable.find('tr').filter(':visible:odd').addClass('odd');
		this.$datatable.find('tr').filter(':visible:even').removeClass('odd');
	},
	reInit:function(){
		this.$rows = $('tr', this.$datatable)
			.click($.proxy(this, 'selectRow'))
			.each(function(){
				$(this).toggleClass('selected', $(this).find(':checked').length > 0);
			});
			
		this.$rows.find(':checkbox, :radio').each(function(){
			$(this).unbind('click').click(function(e){ this.checked = !this.checked; });
		});
	}
}



Copia.GlobalSearch = function(){
	var $form = $('#headerSearch');
	$form.bind({
		mouseout: function(){
			$.timeout('SearchHide', function(){ $form.removeClass('active'); }, 500);
		},
		submit : Copia.GlobalSearch.submit
	});
	$form.children().add($form).mouseover(function(e){ $.timeout('SearchHide'); });
	$('input[type=text]', $form).bind("click focus", function(){ $(this).parent().addClass('active'); });
}

$.extend(Copia.GlobalSearch, {
	locations :	{
		'titles' : '/catalog/index.html?key=title:',
		'people' : '/people/index.html?key=',
		'groups' : '/groups/index.html?key=',
		'authors' : '/catalog/index.html?key=author:',
		'author' : '/author/search.html?key='
	},
	submit: function(e) { 
		e.preventDefault();

		var searchType = $('#headerSearch :checked').val(),
			searchValue = $("#headerSearch input[name=search]").val();
		
		if(searchValue){
			window.location = Copia.GlobalSearch.locations[searchType] + searchValue;
		}
	}
});



/**
	UserMessage
*/
(function(){
	// Internal shortcut for Copia.UserMessage
	// Think 'n' for notify
	var n;
	Copia.UserMessage = n = {};
	
	$.extend(n, {
		options: {
			showDuration : 5000,
			hideDuration : 2000
		},
		init: function(){
			n.$elem = $('.userMsg.ajaxUserMsg');
			n.$wrap = n.$elem.find('.userMsgWrap');
			n.$text = n.$elem.find('.userMsgText');
			n.$elem.find('.closeBtn').bind('click', function(){  n.hide(); });
			n.$sharePrompt = n.$elem.find('.sharePrompt');
			n.$networks = n.$sharePrompt.find('.networks');
			n.$shareLink = n.$sharePrompt.find('a:eq(0)').click(n.sendPromptResults);
					
			$(document).bind({
				'ajaxStart'	   : n.onStart,
				'ajaxStop'	   : n.onStop,
				'ajaxSuccess'  : n.onSuccess
			});
			$.ajaxSetup({
				error 	 : n.error,
				useMsgHandler : true
			});
			
			if(_C.socialMediaPrompt){
				n.process(_C.socialMediaPrompt);
			}
		},
		error : function(xhr, textStatus, exception){
			if(n.errors[textStatus]){
				n.errors[textStatus](this, xhr);	
			} else if(n.errors[xhr.status]) {
				n.errors[xhr.status](this, xhr);
			} else {
				n.set(exception.name + ': ' + exception.message);
			}

			$(n.$elem).addClass('error');
		},
		set : function(text, type, subType){
			n.$text.html(text);
			n.$elem.attr('class', 'userMsg ajaxUserMsg userMsg' + type + ((subType) ? ' userMsg' + subType : ''));
		},
		setError : function(text, subType){
			n.set(text, 'Error', subType);
		},
		setSuccess : function(text, subType){
			n.set(text, 'Success', subType);
		},
		setSharePrompt : function(shareData){
			if(shareData){
				var iconToNetwork = { facebook : 'smallFBIcon.png', twitter : 'smallTwitterIcon.png' },
					noNetworks = true,
					networks = shareData.networks,
					networksHtml = ['<input type="hidden" name="id" value="', shareData.id, '" />',
									'<input type="hidden" name="type" value="', shareData.type, '" />'];
				
				for(var networkId in networks){
					if(networks[networkId] == true){
						noNetworks = false;
						networksHtml.push('<input type="checkbox" name="networks" value="', networkId, '" checked="checked"/>',
										  '<img src="/images/', iconToNetwork[networkId], '" alt="facebook" />');
					}
				}
			
				if(!noNetworks){
					n.$networks.html(networksHtml.join(''));
					n.$sharePrompt.show();
					n.$sharePrompt.find('.action:eq(0)').show();
				} else {
					n.$sharePrompt.find('.action:eq(0)').hide();
				}
				
			} else {
				n.$sharePrompt.hide();
			}
		},
		sendPromptResults : function(e){
			e.preventDefault();
			var shareData = n.$networks.find(':hidden, :checkbox:checked').serializeArray();
			$.ajax({
				url : n.$shareLink.attr('href'),
				global : false,
				data : shareData
			});
			n.hide(n.options.hideDuration);
		},
		show : function(){
			n.$wrap.stop(true, true).fadeIn(n.options.showDuration);
			$.timeout('Copia.UserMessage');
		},
		hide : function(fade){
			n.$wrap.stop(true, true).fadeOut(fade, function(){
				n.$elem.attr('class', 'userMsg ajaxUserMsg userMsgLoading');
			});
		},
		onStart : function(){
			if(n.$elem.is('.userMsgLoading')){
				n.show();
				n.set('Loading', 'Loading');
				/* This can be used to indicate there is a long delay
				 * - modify onStop to clear this out
				$.timeout("Copia.UserMessage.Delay", function() {
					n.set('Still Loading', 'Loading');
				}, 5000);
				*/
			}
		},
		onSuccess : function(e, xhr, settings){
			if(settings.dataType === 'json'){
				n.process($.httpData(xhr, settings.dataType, settings));
			}
		},
		process: function(data){
			var message = data.message;
			
			if(message && message.value && message.handlerGlobal == true){
				// sets the messages
				if(data.success == true){
					n.setSuccess(message.value, message.type);
					n.setSharePrompt(data.sharePrompt || null);
				} else {
					n.setError(message.value, message.type);
				}
				// shows message
				n.show();
				
				// intializes fade away functionality
				if(message.delay != false && message.sticky == false) {
					$.timeout('Copia.UserMessage', n.hide, message.delay, [message.fade]);
				}
			}
		},
		onStop : function(){
			if(n.$elem.is('.userMsgLoading')){ n.hide(n.options.hideDuration); }
		},
		errors : {
			/* HTTP Status Codes */
			'302' : function(){
				//console.log('redirect me');
			},
			'403' : function(){
				n.set('You are no longer logged in.'); 
				location.search += "&_=" + new Date().getTime();
			},
			'404' : function(){ n.set('Page missing', 404); },
			'408' : function(){ n.errors['timeout'](); },
			'500' :	function(){ n.set('Sorry, some of the information on this page is temporarily unavailable. Please click refresh or go back and try again.', 'UnknownError'); },
			/* jQuery Status Codes */
			'parsererror': function(settings, xhr){
				$.ajax({
					url:'/home/error.json',
					data : { error_url : settings.url },
					global : false
				});
				//n.set('Our bad. This page has experienced a data problem <a href="#" class="reportError">Report this error</a>');
				n.set('Sorry, some of the information on this page is temporarily unavailable. Please click refresh or go back and try again.');
			},
			'timeout' : function(){ n.set('', 'Timeout'); }
		}
	});
	
	$(document).ready(function(){
		n.init();
		// placebo for when a user reports an error because it will automatically report the error
		/* Removed since the text has been removed.
		$('.reportError').live('click', function(e){
			n.set("Thank you for reporting an issue.", 'Success');
		});
		*/
	})
	
})()



/** 
 *	Copia.Search - search and load more class
 *
 *	Options:
 *		loadBar (selector) the selector for the load bar element
 *		results (selector) the selector for the container of the results
 *		defaultURL (url) the url that gets used most in the search object
 *		validStartParams (array) an array valid parameter keys for a search that gets kicked off when a page loads with those parameters
 *		searchOnPageLoad (bool) whether or not to perform a search when the page loads based on the pages query parameters
 *		userSearchBar (bool) whether or not to bind events to the search bar on the page
 *		useSortField (bool) whether or not to bind events to the pages sortField
 *
 *	Example Implementation: see examples folder
 */
Copia.Search = Class.extend({
	defaultOptions : {
		loadBar : '.loadBar',
		results : '.catalogList',
		defaultURL : '',
		validStartParams : ['key'],
		searchOnPageLoad : true,
		useSearchBar : true,
		useSortField : true,
		rowsPerPage : 10
	},
	/**
	 *	Contstructor
	 *	options (OptionsObject) and object containing optional settings
	 */
	init: function(options){
		this.allowMoreResults = true ;
		this.options = $.extend({}, this.getDefaultOptions(), options);
		this.initElements();
		
		this.reset();
		// Handles initial searching
		if(this.options.searchOnPageLoad){
			this.onPageLoadSearch();
		}
	},
	sortSearch : function(e){
		// Reset the page (potentially call reset)
		this.page = 1;
		this.loadResults(true);
		e.preventDefault();
	},
	initElements : function(){
		this.$loadBar = $(this.options.loadBar);
		this.$loadBar.find('.loadMore').click($.proxy(this, 'loadMore'));
		
		this.$results = $(this.options.results);
		
		// Initializes big search form
		if(this.options.useSearchBar){
			this.$searchBar = 	$('#search').submit($.proxy(this, 'search'));
		}
		
		// initializes sort select field
		if(this.options.useSortField){
			$('#sortField').change($.proxy(this, 'sortSearch'));
		}
	},
	addParam: function(key, value){
		this.params[key] = value;
	},
	removeParam: function(key, index, reload){
		delete this.params[key];
		if(reload){ this.loadResults(true); }
	},
	reset:function(url, params){
		this.url = url || this.options.defaultURL;
		
		this.page = 1;
		this.count = 0;
		this.shownCount = 0;
		this.params = params || {};
		$(".noResultsBin").hide();
	},
	// via submit button
	search : function(e){
		if(e){ e.preventDefault() }
		var key = $('#key').val();
		if(key){
			key = key.replace(/título:/gi, 'title:').replace(/autor:/gi, 'author:');
			this.reset();
			this.params['key'] = key;
			C.Stats.search(key);
			this.loadResults(true);
		}
	},
	/**
	 *	Checks to see if a search needs to be perforemed based on the url's query parameters during page load.
	 */
	onPageLoadSearch : function(){
		var params = $.deparam(location.search.substr(1));
		for(var key in params){
			if($.inArray(key, this.options.validStartParams) > -1){
				this.params[key] = params[key]
			} else if(key == "rowsPerPage") {
				this.options.rowsPerPage = params[key];
			} else if(key == "page") {
				this.options.page = params[key];
			}
		}
		
		if(this.hasParams()) {
			this.loadResults(true);
			this.locked = true;
		}
	},
	loadResults : function(isNew){
		var params = $.extend({}, {
			rowsPerPage : this.options.rowsPerPage,
			page 		: this.page,
			sortField	: $('#sortField').val()
		}, this.params);
		
		var me = this;
		$.getJSON(C.url($.param.querystring(this.url, params)), function(data){
			if(isNew){ me.$results.empty(); }
			me.loadCallback(data);
		});
	},
	loadCallback : function(data){
		if(this.options.useSearchBar){
			$('#key').val('').blur();
		}
		this.count = (data) ? parseInt(data.resultsCount) : 0;
		this.setTotalResults();
		this.shownCount = Math.min(this.count, this.page * this.options.rowsPerPage);
		this.$loadBar.toggle(this.page * this.options.rowsPerPage < this.count).find('.endResult').text(this.shownCount);
		if(this.count == 0) this.showNoResultsBlock();
		else this.hideNoResultsBlock();
		// TBD Does this need to call callback if there are no results?
		this.allowMoreResults = true;
		this.callback(data);
	},
	showNoResultsBlock: function() {
		$("#noResultsBlock").show();
	},
	hideNoResultsBlock: function() {
		$("#noResultsBlock").hide();
	},
	/**
	 *	Handles adding items to the dom and whatever else needs to be done after results are returned.
	 *	data (JSON object) The json object returned from the loadResults request
	*/
	callback: function(data){},
	loadMore : function(e){
		if(e){ e.preventDefault(); }
		if (this.allowMoreResults)
		{
			this.allowMoreResults = false;
			this.page++
			this.loadResults();
		}
		
	},
	setTotalResults : function(){
		if(!this.count) this.count = 0;
		var countStr = this.count + "";
		var num = countStr.substring(0, countStr.length%3);
		var tail = countStr.substring(num.length);
		for(var i=0; i<tail.length; i+=3) {
		    if(num.length > 0) num += ",";
		    num += tail.substring(i, i+3);
		}
		$('.totalResults').html(num + ' item' + ((this.count == 1) ? '' : 's'));
		if (this.count == 0)
			$('.totalResults').hide();
		else
			$('.totalResults').show();		
	},
	/* Options Utility Functions */
	getDefaultOptions: function(){
		if(this._parent.getDefaultOptions){ return $.extend(true, {}, this._parent.getDefaultOptions(), this.defaultOptions); }
		else { return this.defaultOptions; }
	},
	hasParams: function() {
		// FIXME This is a hack
		var params = $.extend({}, this.params);
		delete params.downloadable;
		
		return !$.isEmptyObject(params);
	}
});



/*
	JSON markup
	Response:
		{
			id : <int>,
			name : <string>,
			count : <int>,
			start : <int>,
			end : <int>,
			
			items: <array of ItemObject's>,
		}
	
		ItemObject = {
			id : <string>,
			title : <string>,
			image : <path>,
			formats: [{ authors: [ { name: <string> } ] }]
		}
	
	Request:
		id : <int> // The id of the collection or saved search
		index : <int>
		count : <int> // how many ItemObjects to return
		
*/
Copia.vars.sliders = [];

$.fn.slider = function(url, options){
	if($(this).data('Copia.Slider') != null){
		return $(this).data('Copia.Slider');
	}
	$(this).each(function(){
		_C.sliders.push(new Copia.Slider(this, url, options));
	});
};

Copia.Slider = function(elem, url, options){
	this.init(elem, url, options);
};

$.extend(Copia.Slider, {
	defaultOptions: {
		interval : 4,
		duration : 1000,
		scrollDistance : null,
		extraParams : {},
		onSuccess : $.noop,
		createItem : null
	},
	
	prototype: {
		init: function(elem, url, options){
			this.$elem = $(elem);
			this.$elem.data('Copia.Slider', this);
			this.url = C.url(url);
			
			this.options = $.extend({}, Copia.Slider.defaultOptions, options);
			
			//Elements
			this.$window = $('.sliderItems .sliderWindow', this.$elem);

			this.$prevBtn = $('.prevBtn', this.$elem).click($.proxy(this, 'prev'));
			this.$nextBtn = $('.nextBtn', this.$elem).click($.proxy(this, 'next'));

			this.cache = new $.Hash();
			
			this.scrollDistance = (this.options.scrollDistance) ? this.options.scrollDistance : parseInt($('.sliderItems', this.$elem).css('width'))
			
			this.index = 0;
			this.total = 0;
			
			this.scrolling = false;
			this.calling = false;
			
			this.initialLoad();
		},
		reset : function(url, options){
			this.url = C.url(url);
			
			this.options = $.extend(this.options, options);
			this.initialLoad();
		},
		initialLoad: function(){
			this.doJSON('initialCallback');
		},
		initialCallback: function(data){
			this.total = data.count;
			interval = this.options.interval
			
			this.$current = $('.current', this.$window);
			this.$prev = this.$current.prev();
			this.$next = this.$current.next();
			
			if(data.count > interval){
				current = data.items.slice(interval, interval * 2);
				this.callback(data);
			}else {
				current = data.items.slice(0, interval);
				this.options.onSuccess.call(this, data);
				this.$prevBtn.hide();
				this.$nextBtn.hide();
			}

			this.fill(this.$current, current, this.index);
		},
		callback : function(data, reset){
			this.data = data;

			interval = this.options.interval;
			
			this.end = this.calcNext(this.index, interval - 1);
			
			prev = data.items.slice(0, interval);
			next = data.items.slice(interval * 2, interval * 3);
			
			me = this;
			fillFn = function(){
				if(reset) { me.resetPositions(); }
				me.fill(me.$prev, prev, me.calcPrev(me.index));
				me.fill(me.$next, next, me.calcNext(me.index));
			};
			
			if(this.scrolling == true){
				this.heyCallThis = fillFn;
			}else {
				fillFn();
			}
			
			this.options.onSuccess.call(this, data);
			this.calling = false;
		},
		fill: function($elem, items, start){
			$elem.empty();
			createFn = this.options.createItem || this.createItem;
			for( i in items){
				$elem.append(createFn.call(this, items[i]));
				this.cache.add(start, items[i]);
				start = this.calcNext(start, 1);
			}
		},
		resetPositions : function(){
			var children = this.$window.find('ul');
			var elemIndex = children.index(this.$current);
			
			children.filter(':lt(' + elemIndex + '), :gt(' + elemIndex + ')').remove();
			
			this.$prev = $('<ul>').css('left', parseInt(this.$current.css('left')) - this.scrollDistance).insertBefore(this.$current);
			this.$next = $('<ul>').css('left',  parseInt(this.$current.css('left')) + this.scrollDistance).insertAfter(this.$current)
		},
		calcPrev : function(num, count){
			count = count || this.options.interval;
			newStart = num - count;
			return (newStart < 0) ? this.total + newStart : newStart;
		},
		calcNext : function(num, count){
			count = count || this.options.interval;
			newStart = num + count;
			return (newStart > this.total - 1) ? newStart - this.total : newStart;
		},
		prev : function(e){
			e.preventDefault();
			if(this.scrolling == false && this.calling == false){
				this.$current = this.$current.prev();
				//$('.next', this.$window).prependTo(this.$window);
				this.index = this.calcPrev(this.index);
				this.scroll('+');
			}
		},
		next : function(e){
			if(e){ e.preventDefault(); }
			if(this.scrolling == false && this.calling == false){
				this.$current = this.$current.next();
				//$('.prev', this.$window).appendTo(this.$window);
				this.index = this.calcNext(this.index);
				this.scroll('-');
			}
		},
		doJSON:function(callback){
			this.calling = true;
			var data = $.extend(this.options.extraParams, {
				'index' : this.index, 
				'count' : this.options.interval
			});
			$.getJSON(this.url, data, $.proxy(this, callback));
		},
		scroll: function (direction){
			this.scrolling = true;
			
			this.$window.stop(true, true).animate({ left: direction + '=' + this.scrollDistance + 'px' }, this.options.duration, $.proxy(this, 'scrollEnd'));
			this.doJSON('callback');
		},
		scrollEnd: function(){
			this.scrolling = false;
			
			if(this.calling == false){
				this.heyCallThis();
			}
		},
		createItem: function(data){
			return $('<li><a href="/catalog/details.html?catId=' + data.id + '"><img class="cover large generic' + ((parseInt(data.id) % 4) + 1) + '" src="' + data.image + '" title="' + data.title + '" onerror="bookImg(this);"/ alt="' + data.title + '"></a></li>');
		}
	}
});




/**
	JSON markup
	Response:
		{
			resultsCount : <int>,
			items: <array of ItemObject's>,
		}
		
		BookItemObject = {
			id : <string>,
			title : <string>,
			image : <path>,
			formats: [{ authors: [ { name: <string> } ] }]
		}
	
	Request:
		id : <int> // The id of the collection or saved search
		index : <int>
		count : <int> // how many ItemObjects to return
	
	
	
	Elements:
		this.$elem (jquery Object) the primary element for the slider.  A <div> with a class of slider
		this.$window (jquery Object) the element that serves as the view port for the slider
		this.$prevBtn (jQuery Object) the previous arrow element
		this.$nextBtn (jQuery Object) the next arrow element
		this.$prev (jQuery Object) previous <ul>
		this.$next (jQuery Object) next <ul>
	
	Variables:
		this.index (integer) the index of the first item in the current <ul>
		this.prevIndex (integer) the index of the first item in the previous set
		this.nextIndex (integer) the index of the first item in the next set
		this.cacheIndex (integer) the index of the first item of the set that was last added to the cache
		
		this.scrollDistance (integer) the number of pixels that need to be scrolled
		
		this.total (integer) the total number of items possible in the slider
		
		this.scrolling (bool) flag saying the slider is currenly scrolling
		this.calling (bool) flag saying the slider is in the middle of a json request

*/

$.fn.slider2 = function(url, options){
	if($(this).data('Copia.Slider') != null){
		return $(this).data('Copia.Slider');
	}
	$(this).each(function(){
		_C.sliders.push(new Copia.Slider2(this, url, options));
	});
};

Copia.Slider2 = Class.extend({
	init: function(elem, url, options){
		this.$elem = $(elem);
		this.$elem.data('Copia.Slider', this);
		this.url = C.url(url);
		
		this.options = $.extend({}, Copia.Slider2.defaultOptions, options);
		
		//Elements
		this.$window = $('.sliderItems .sliderWindow', this.$elem);

		this.$prevBtn = $('.prevBtn', this.$elem).click($.proxy(this, 'prev'));
		this.$nextBtn = $('.nextBtn', this.$elem).click($.proxy(this, 'next'));

		this.cache = new $.Hash();
		
		this.scrollDistance = (this.options.scrollDistance) ? this.options.scrollDistance : parseInt($('.sliderItems', this.$elem).css('width'))
		
		this.index = 0;
		this.cacheIndex = 0;
		this.total = 0;
		
		this.scrolling = false;
		this.calling = false;
		
		this.initialLoad();
	},
	reset: function(url, options){
		this.url = url;
		
		this.options = $.extend(this.options, options);
		this.initialLoad();
	},
	initialLoad: function(){
		this.doJSON('initialCallback');
	},
	/**
	 *	Callback for first ajax request
	 *	Includes some additional processing not present in 'callback' which it eventually calls
	 */
	initialCallback: function(data){
		this.total = parseInt( data.resultsCount );
		if(isNaN(this.total)){ this.total = 0; }
		this.$current = $('.current', this.$window);
		var currentSet = data[this.options.itemsProperty].slice(0, this.options.interval)
		this.cache.add(this.index, currentSet);
		this.fillInSet(this.$current, this.index, currentSet);
		
		this.callback(data);
		this.options.onIndexChange.call(this);
	},
	/**
	 * Callback for ajax request
	 */
	callback: function(data, reset){
		interval = this.options.interval;
		
		//this.cache.add(this.jsonIndex, data[this.options.itemsProperty].slice(0, interval));
		//this.cache.add(this.jsonIndex + interval, data[this.options.itemsProperty].slice(interval, interval * 2));

		var len = data[this.options.itemsProperty].length;
		for(var k=0; k< Math.ceil(len*1.0 / interval); k++){
			this.cache.add(this.jsonIndex + interval * k, data[this.options.itemsProperty].slice(interval * k, Math.min(len, interval * (k+1))));
		}
		
		
		if(this.scrolling == false){ this.fill(); }
		
		this.options.onSuccess.call(this, data); // on sucess callback
		this.calling = false; // Flag saying json call is finished.
	},
	/**
	 *	Fills in a designated <ul>
	 */
	fillInSet: function($elem, start, items){
		$elem.empty();
		if(this.options.createItem != undefined)
			createFn = this.options.createItem;
		else if(this.options.type != undefined && this.options.type.toLowerCase() == "user")
			createFn = this.deafultCreateGroupItem;
		else if(this.options.exInfo == "true")
			createFn = this.defaultCreateItemExInfo;
		else
			createFn = this.defaultCreateItem;
			
					
		for(i in items){
			$elem.append(createFn.call(this, items[i]));
		}
	},
	/**
	 *	Fills in the next a previous <ul>s
	 */
	fill: function(){
		this.resetPositions();

		this.fillInSet(this.$prev, this.prevIndex, this.cache.get(this.prevIndex));
		this.fillInSet(this.$next, this.nextIndex, this.cache.get(this.nextIndex));
		this.$prev.find('a').attr('tabindex', -1);
		this.$next.find('a').attr('tabindex', -1);
	},
	/**
	 * Handles disabling next and previous arrows as well as inserting appropriate uls and  calculating next and previous indexes
	 */
	resetPositions: function(){
		this.$prevBtn.toggleClass('disabled', this.index == 0);
		this.$nextBtn.toggleClass('disabled', this.index >= this.total - this.options.interval);
		
		var children = this.$window.find('ul');
		var elemIndex = children.index(this.$current);
		
		children.filter(':lt(' + elemIndex + '), :gt(' + elemIndex + ')').remove();
		
		this.$prev = $('<ul>').css('left', parseInt(this.$current.css('left')) - this.scrollDistance).insertBefore(this.$current);
		this.$next = $('<ul>').css('left',  parseInt(this.$current.css('left')) + this.scrollDistance).insertAfter(this.$current);

		this.prevIndex = this.index - this.options.interval;
		this.nextIndex = this.index + this.options.interval;
	},
	/**
	 *	Click event handler for previous arrow
	 */
	prev: function(e){
		if(e){ e.preventDefault(); }
		if(this.scrolling == false && this.calling == false){
			this.$current = this.$current.prev();
			this.index -= this.options.interval
			this.scroll('+');
		}
	},
	/**
	 *	Click event handler for next arrow
	 */
	next: function(e){
		if(e){ e.preventDefault(); }
		if(this.scrolling == false && this.calling == false){
			this.$current = this.$current.next();
			this.index += this.options.interval;
			this.scroll('-');
		}
	},
	doJSON:function(callback){
		// Checks to see if a json call is actually required			
		if(this.needsJSON(this.index) || this.needsJSON(this.cacheIndex)){
			this.calling = true;
			this.jsonIndex =  (this.needsJSON(this.index)) ? this.index : this.cacheIndex;
			data = $.extend(this.options.extraParams, { 
				'index' : this.jsonIndex,
				'count' : this.options.interval * 2,
				'rows' : this.options.interval * 2,
				'page' : Math.max(1, parseInt(this.jsonIndex / (this.options.interval * 2)) + 1),
				'rowsPerPage' : this.options.interval * 2
			});
		
			$.getJSON(C.url(this.url), data, $.proxy(this, callback));
			this.cacheIndex += this.options.interval * 2;
		}
		// if JSON call is not required it populates slider from the cache.
		else {
			if(this.scrolling == false){ this.fill(); }
		}
	},
	/**
	 *	Determines whether or not an ajax request needs to be performed
	 *	returns (bool)
	 */
	needsJSON: function(index){
		return !(this.cache.hasKey(index) || this.cache.hasKey(index + this.options.interval)) && !(index > this.total);
	},
	scroll: function (direction){
		this.scrolling = true;
		
		this.$window.stop(true, true).animate({ left: direction + '=' + this.scrollDistance + 'px' }, this.options.duration, $.proxy(this, 'scrollEnd'));
		
		if(direction == '-'){
			this.doJSON('callback');
		}
		this.options.onIndexChange.call(this);
	},
	/**
	 *	Callback for when scrolling animation finishes
	 */
	scrollEnd: function(){
		this.scrolling = false;
		
		if(this.calling == false){ this.fill(); }
	},
	
	defaultCreateItem: function(data){
		return $('<li><a href="'+ C.url('/catalog/details.html?catId=' + data.id) + '"><img class="cover large generic' + ((parseInt(data.id) % 4) + 1) + '" src="' + data.image + '" title="' + data.title + '" onerror="bookImg(this);"/ alt="' + data.title + '"></a></li>');
	},
	
	defaultCreateItemExInfo: function(data){
		var lowPrice = (typeof data.lowPrice != "number") ? parseFloat(data.lowPrice.replace(/,/g, "")) : data.lowPrice;
		var listPrice = (typeof data.listPrice != "number") ? parseFloat(data.listPrice.replace(/,/g, "")) : data.listPrice;
		var msrpPrice = (typeof data.formats[0].msrp != "number") ? parseFloat(data.formats[0].msrp.replace(/,/g, "")) : data.formats[0].msrp;
		var price = "", currency = "$";
		if (data.downloadableFlag == "true" || data.buyableFlag == "true" ) {
			if (listPrice > lowPrice) { price = "<strike>" + currency + data.listPrice + "</strike> &nbsp;"; }
			else if (msrpPrice > lowPrice) { price = "<strike>" + currency + data.formats[0].msrp + "</strike> &nbsp;"; }
			price += "<b>" + (lowPrice > 0.0 ? currency + data.lowPrice : "Free") + "</b>";
		}
		var len = Math.max(35, data.title.indexOf(';', 30));
		var titleText = data.title.substring(0, len) + (data.title.length > len ? "..." : "");
		var clazz = "cover large generic"+((parseInt(data.id) % 3) + 1);
		var img = '<img class="'+clazz+'" src="' + data.largeCover + '" title="' + data.title + '" onerror="bookImg(this);" alt="' + data.title + '" />';
		var div = '<div class="' +  clazz+ '"><div class="gcBanner"><div><p>' + C.shorten(data.title, 40) + '</p></div></div></div>';
		var actualImg = data.largeCover ? img : div;
		return $('<li><div><a href="/catalog/details.html?catId=' + data.id + '">'+actualImg+'</a><div class="titleName"><a href="/catalog/details.html?catId=' + data.id + '">' + titleText + '</a></div><div class="titlePrice">' + price + '</div></div></li>');
	},
	
	deafultCreateGroupItem: function(data){
		defaultImage = '/images/defaultUser_Small.png';
		return $('<li><div class="groupMembers"><a href="/home/index.html?p=g' + data.id + '"><img src="' + data.image + '"/><span class="groupName">' + data.name + '</span></a></div></li>');
	}
});

Copia.Slider2.defaultOptions = {
	interval : 4,
	duration : 1000,
	scrollDistance : null,
	extraParams : {},
	onSuccess : $.noop,
	onIndexChange : $.noop,
	createItem : null,
	itemsProperty : 'items'
};



//Modified version of http://www.leigeber.com/2008/06/javascript-tooltip/
$.fn.tooltip = function(options){
	$(this).each(function(){
		new Copia.ToolTip(this, options);
	});
};

Copia.ToolTip = function(elem, options){
	this.init(elem, options);
	this.id = ++$.guid;
};

$.extend(Copia.ToolTip, {
	defaultOptions: {
		id : 'tt', // used to specify the id of the tooltip element used for the elements
		fadeSpeed : 100,
		delay: 500,
		extraClass: '',
		cursorOffset: 3,
		fill : $.noop
	},
	prototype:{
		init: function(elem, options){
			this.opts = $.extend({}, Copia.ToolTip.defaultOptions, options);
			
			this.$elem = $(elem).hover($.proxy(this, 'show'), $.proxy(this, 'hide'));
			this.text = this.$elem.attr('data-tooltip');
		
			this.tt = $('#' + this.opts.id);
			
			if(this.tt.length == 0){
				this.tt = $('<div>', { id : this.opts.id, 'class': 'tooltip ' +this.opts.extraClass, 'css' : {display: 'none'}}).appendTo('body');
			}
			this.$elem.mousemove($.proxy(this, 'move'));
		},
		show: function(e){
			(this.opts.fill == $.noop) ? this.tt.text(this.text) : this.opts.fill.apply(this);
			this.pos(e);
			$.timeout('Copia.ToolTip' + this.id, $.proxy(this, 'delayedShow'), this.opts.delay);
		},
		delayedShow: function(){
			this.tt.stop(true, true).fadeIn(this.opts.fadeSpeed);
		},
		move: function(e){
			if(!this.tt.is(':visible')){
				this.pos(e);
			}
		},
		pos : function(e){
			this.tt.css({
				'top'  : e.pageY - (this.tt.outerHeight() + this.opts.cursorOffset),
				'left' : e.pageX + this.opts.cursorOffset
			});
		},
		hide:function(){
			$.timeout('Copia.ToolTip' + this.id); 
			this.tt.stop(true, true).hide();
		}
	}
});

Copia.ToolTip.Fillers = {
	CommunityRating: function(){
		var star = $('<div>', {'class': this.$elem.attr('class')});
		var span = $('<span>', {'text' : this.$elem.attr('data-count') + ' | '});
		this.tt.empty().append(span).append(star);
	}
}




/** Menu
 * 	Provides dropdown menu functionality
 */
 
 /**  
	=======  HTML Markup ======= 
	
	<menu>
		<a href="#" role="button">
			<div class="text">
					<!-- Heading used for large menus's -->
					<span>Added To Library:</span>
					<!-- Current Value -->
					Reading
			</div>
			<div class="arrow"><span></span></div>
		</a>
		<ul>
				<li><a href="#" class="action">Unread</a></li>
				<li><a href="#" class="action">Wanted</a></li>
				<li><a href="#" class="action">Reading</a></li>
				<li><a href="#" class="action">Finished</a></li>
				<li><a href="#" class="action">Reference</a></li>
		</ul>
	</menu>
 
 */
 
/**
 *	======= Javascrip Usage =======
 *	
 *	Get the jquery object for a menu markup and call .menu([optionsObject])
 *	
 *	Initialize:
 *		ex. $('menu.theMenuIWant').menu({showSelected: true});
 *	
 *	Change value text:	
 *		ex. $('menu.theMenuIWant')..trigger('setValue', 'My New Value');
 *
 *	 Change header text:	
 *		ex. $('menu.theMenuIWant')..trigger('setHeader', 'My New Value');
 * 	
 *	Options:
 *		showSelected (bool) whether or not to show the selected menu item as the text when the menu is closed; default = false
 *		onSelect: (callback) function(text, $target){}
 *			text (string) the text value for the menu item selected
 *			$target (jQuery) the jQuery object for the menu item was selected
 *	
 *	'this' in all callbacks is set to the current Menu object
 *
 *	Variables:
 *		this.$menu (jQuery) the menu's <menu> element
 *		this.$dropdown (jQuery) the <ul> inside the <menu> which contains the menu items
 *		this.$btn (jQuery) the <a> inside the <menu> that when clicks show the $dropdown 
 *		this.$text (jQuery) the <div class="text"> inside of $btn that holds the current value as well as possibly the text header like "Added to Library" which would be in a <span> 
 */

$.fn.menu = function(options){
	$(this).each(function(){
		new Copia.Menu(this, options);
	});
}

Copia.Menu = function(menu, options){
	this.init(menu, options);
}
$.extend(Copia.Menu, {
	prototype:{
		/**
		 *	Initializes Copia.Menu
		 */
		init:function(menu, options){
			this.guid = $.guid;
			$.guid++;
			
			this.options = $.extend({}, Copia.Menu.defaultOptions, options);
			
			// Element initialization
			this.$menu = $(menu).bind({
				'setValue' 	: $.proxy(this, 'setValue'),
				'setHeader' : $.proxy(this, 'setHeader')
			});
			
			this.$dropdown = this.$menu.find('ul:first').bind({
				'mouseover' : $.proxy(this, "mouseOver"),
				'mouseout'	 : $.proxy(this, "mouseOut")
			});
			
			this.$items = this.$dropdown.find('a')
				.click( $.proxy(this, 'select'))
			
			this.$btn = this.$menu.find('a:first').bind({
				click 		   : $.proxy(this, "click"),
				mouseover : $.proxy(this, "mouseOver"),
				mouseout   : $.proxy(this, "mouseOut")
			});
			
			this.$text = this.$btn.find('.text');
		},
		/**
		 *	Toggles $dropdown
		 */
		toggle: function(){
			(this.$dropdown.is(':visible')) ? this.hide() : this.show();
		},
		/**
		 *	Shows $dropdown
		 */
		show:function(){
			this.$menu.addClass('active')
			this.$dropdown.stop(true, true).slideDown();
		},
		/**
		 *	Hides $dropdown
		 */
		hide: function(){
			this.$dropdown.stop(true, true).slideUp(function(){
				$(this).parent().removeClass('active');
			});
		},
		/**
		 * Click event handler for $btn
		 * slide toggles $dropdown
		 */
		click:function(e){
			e.preventDefault();
			if(!this.$menu.is('.disabled')){
				this.toggle();
			}
		},
		/**
		 * Mouseout event handler for $menu
		 * sets a timeout for hiding $dropdown
		 */
		mouseOut:function(e){
			$.timeout("Copia.Menu" + this.guid, $.proxy(this, "hide"), 500);
		},
		/**
		 * Mouseover event handler for $menu
		 * Removes the timeout set when mouseout event occurs
		 */
		mouseOver:function(e){
			$.timeout('Copia.Menu' + this.guid);
		},
		/**
		 *	Click event handler for clicking one of the $items in $dropdown
		 */
		select: function(e){
			e.preventDefault();
			$target = $(e.currentTarget);
			if(this.options.onSelect.call(this, $target.text(), $target) != true){
				if(this.options.showSelected){
					this.$menu.trigger('setValue', $target.text());
					//this.showSelected.call(this, target);
				}
				this.hide();
			}
		},
		/**
		 *	Sets header text inside $text
		 * 	Get a jquery object for an intialized <menu> and call trigger('setHeader', newValue')
		 *	ex. $('menu.iWantToChangeYour').trigger('setHeader', 'My New Value');
		 */
		setHeader : function(e, newValue){
			this.$text.find('span').text(newValue);
		},
		/**
		 * 	Sets the value present in $text 
		 * 	Get a jquery object for an intialized <menu> and call trigger('setValue', newValue')
		 *	ex. $('menu.iWantToChangeYour').trigger('setValue', 'My New Value');
		 *	ex. this.$menu.trigger('setHeader', 'NewValue');
		 */
		setValue:function(e, newValue){
			// Grabs all the text nodes in $text
			textNodes = this.$text.contents().filter(function(){
				if(this.nodeType == 3){ return $.trim(this.nodeValue) != ''; }
			});
			// If there are any text nodes set the value of the first one to the new value
			if(textNodes[0] != undefined){textNodes[0].nodeValue = newValue;}
			// otherwise append it to .text; the case when this would occur is when a large menu's header text contains two lines of content like on detail page "Add To Your Library"
			else{ this.$btn.find('.text').append(newValue) }
		}
	},
	/**
	 *	Default Options
	 */
	defaultOptions:{
		onSelect: $.noop,
		showSelected: false
	}
});




// Houses Functions for menu implementations

Copia.Menus = {};

/**
 * Handles library Status dropdown menus.
 */
 // TODO: This is spagetti
Copia.Menus.LibraryStatus = function(item, $target){

	var	catalogId =  this.$menu.attr('data-catalog-id'),
		publicationId = this.$menu.attr('data-publication-id'),
		origText = item,
		item = $target.attr('data-status'),
		initialStatus = this.$menu.attr('data-initial-status'),
		menu = this;
	
	// Adds rating and review sections if user sets to Finished or Reference
	if($.inArray(item, ["finished", "reference"]) > -1){
		var $ratingElem = $('<ul class="starRating"><li class="userRate userRate0star" data-catalog-id="' + catalogId + '"><span></span></li></ul>');
		this.$menu.parents('.listRateIt').find(".ratingType").html($ratingElem);
		$("#ratingreview").show();
	}else{
		$("#ratingreview").hide();
	}
	
	if(catalogId){
		$('[data-catalog-id=' + catalogId + ']:not(.addLibraryToGroup,.recommendToFriend)').trigger('setHeader', 'Added to Library'); //.trigger('setValue', origText);
	} else {
		$('[data-publication-id=' + publicationId + ']:not(.addLibraryToGroup,.recommendToFriend)').trigger('setHeader', 'Added to Library'); //.trigger('setValue', origText);
	}
	if(item == 'remove' && !confirm('Are you sure you want to delete this book from your library?\nIf you do everything associated with this book will be gone forever')){ return true; }
	
	$.getJSON('/library/updatestatus.json', {'bookStatusCode' : item, 'catalogId' :  catalogId, 'pubId' : publicationId }, function(data){
			// only happens on adding book to library
		if(initialStatus == ''){
			C.Stats.action('add-to-library', publicationId || catalogId);	 // Stats Stuff
			if(_C.editPubDetails == undefined || _C.editPubDetails == true){
				var lboxData = {
					'catalogId' : catalogId,
					'publicationId' : publicationId,
					'notificationId' : data.sharePrompt.id,
					'promptFacebook' : data.sharePrompt.networks.facebook,
					'promptTwitter' : data.sharePrompt.networks.twitter
				};
				
				$.lbox($.param.querystring( '/library/firstBookAdded.html', lboxData), { type : 'ajax', useGlobalMsgHandler : false } );
				
			}
			menu.$dropdown.find('[data-status=remove]').show();
		} else {
			menu.$dropdown.find('[data-status=remove]').hide();
			if(data.success){
				if(item == 'remove') setTimeout(function(){ menu.$menu.trigger('setHeader', 'Add to Your Library'); });
			} else {
				//setTimeout(function(){ menu.$menu.trigger('setValue', initialStatus); });
				Copia.UserMessage.setError(data.error.message);
				Copia.UserMessage.show();
			}
		}
		
	});
	
	menu.$menu.attr('data-initial-status', (item == 'remove') ? '' : item);
	

};
/**
 * Handles adding to group library dropdown
 */
Copia.Menus.GroupLibraryStatus = function(item, target){
	$.getJSON('/library/addbooktogroup.json', { 'groupId' : $(target).attr("data-group"), 'catalogId' :  this.$menu.attr('data-catalog-id') }, function(data){
		// @todo: Need to add error handling
	});
};

/**
 * Handles adding to group library dropdown
 */
Copia.Menus.Recommendation = function(item, $target){
	alert("In Copia.Menus.Rec");
	var	catalogId =  this.$menu.attr('data-catalog-id'),
		publicationId = this.$menu.attr('data-publication-id'),
		item = $target.attr('data-status'),
		initialStatus = this.$menu.attr('data-initial-status'),
		menu = this;
	$.getJSON('/library/updatestatus.json', {'bookStatusCode' : item, 'catalogId' :  catalogId, 'pubId' : publicationId }, function(data){
		var lboxData = {
			'catalogId' : catalogId,
			'publicationId' : publicationId//,
			//'notificationId' : data.sharePrompt.id
		};
		$.lbox($.param.querystring( '/catalog/recommendThisBook.html', lboxData), { type : 'ajax', useGlobalMsgHandler : false } );
		
	});
	
	menu.$menu.attr('data-initial-status', (item == 'remove') ? '' : item);
	/*$.getJSON('/library/recomendation.json', { 'groupId' : $(target).attr("data-group"), 'catalogId' :  this.$menu.attr('data-catalog-id') }, function(data){
		// @todo: Need to add error handling
	});*/

};


Copia.Menus.GroupMembershipStatus = function(item, target) {
	var me = this;
	me.$menu.find(".text").text('Leaving Group...');
	$.getJSON($(target).attr("href"), null, function(data){
		// @todo: Need to add error handling
		me.$menu.find(".text").text('Membership Removed');
		me.$menu.find("ul").empty();
		me.$menu.addClass('disabled');
		var id = me.$menu.attr('data-group-id');
		$.timeout("leaveGroup-"+id, function(){
			me.$menu.fadeOut();
		}, 10000);
	});
};

/**
 * People Connection Dropdown callback
 */
Copia.Menus.SocialConnectionStatus = function(item,target) {
	var me = this, id = target.attr('id');
	var autoAccepts = me.$menu.hasClass('autoAccepts');
	if (id == 'menufollow'){
		$.getJSON($(target).attr("href"), null, function(data){
			var fadeElem = me.$menu;
			if (autoAccepts){
				fadeElem.find(".text").text(item) //'Accepted'
			}else{
				fadeElem.find(".text").text('Request sent');
			}
			fadeElem.find("ul").empty();
			fadeElem.addClass('disabled');
			var id = me.$menu.attr('data-group-id');
			$.timeout("Following-"+id,function(){
				fadeElem.fadeOut();
			}, 10000);
		});
	}
	if (id == 'menuBlock'){
		$.getJSON($(target).attr("href"), null, function(data){
			
			var fadeElem = me.$menu;
			fadeElem.find(".text").text(item); //'Blocked'
			fadeElem.find("ul").empty();
			fadeElem.addClass('disabled');
			var id = me.$menu.attr('data-group-id');
			$.timeout("Blocked-"+id,function(){
				fadeElem.fadeOut();
			},10000);
		});
	}
	if (id == 'menuUnfollow'){
		$.getJSON($(target).attr("href"), null, function(data){
			
			var fadeElem = me.$menu;
			fadeElem.find(".text").text(item); //'Unfollow'
			fadeElem.find("ul").empty();
			fadeElem.addClass('disabled');
			var id = me.$menu.attr('data-group-id');
			$.timeout("Unfollow-"+id,function(){
				fadeElem.fadeOut();
			},10000);
		});
	}
};

/* My Copia and Pubkic Copia  DROPDOWN MENUS*/

$(document).ready(function(){
  $('header nav.topNav li ul').css({
	display: "none",
	left: "auto"
  });
  $('header nav.topNav li').hover(function() {
	$(this)
	  .find('ul')
	  .stop(true, true)
	  .slideDown('fast');
  }, function() {
	$(this)
	  .find('ul')
	  .stop(true,true)
	  .fadeOut('fast');
  });
});	





/**
 * 	FormChecker - a Form validator
 *	Form checker is a little different than other form validation scripts
 *	This is because any validation and error messages it displays must also be displayable by server on submit if a person does not have javascript enabled
 *	Formchecker had to be created because of this unfortuante problem.
 *	And so Formchecker is a bit messed up
 
 *	--- How it displays error messages ---
 *	Error messages are 
 */
$.fn.formChecker = function(options){
	return new Copia.FormChecker(this, options);
}

Copia.FormChecker = function(formElem, options) {
	this.init(formElem, options);
}

$.extend(Copia.FormChecker, {
	defaultOptions: {
		msgClass: 'errorMsg'
	},
	ResultObject: function(){
		return {
			ignore : false,
			failed : [],
			passed : [],
			failedCSS: [],
			passedCSS: [],
			failedCount : function(){ return this.failedCSS.length },
			passedCount : function(){ return this.passedCSS.length },
			add: function(type, index, checker){
				this[type][index] = checker;
				this[type + 'CSS'].push('errorMsg-' + index);
			}
		};
	},
	prototype:{
		rules: {},
		init:function(formElem, options){
			this.guid = $.guid;
			$.guid++;
			
			this.$form = $(formElem).submit($.proxy(this, 'checkForm'));
			
			// FIXME This should work for all of them, but just in case
			var btn = $("button.primary", this.$form);
			if(btn.length > 0) this.$submitBtn = btn;
			else this.$submitBtn = $('button[type=submit]', this.$form);
			
			this.$submitBtn.click($.proxy(this, 'checkForm'));
			
			//enables $submitBtn - firefox remembers if a button was previously disabled
			this.$submitBtn.removeAttr('disabled');
			
			this.options = $.extend({}, Copia.FormChecker.defaultOptions, options);
			this.rules = options.rules || {};
			
			for(selector in this.rules){
				$(selector).bind('blur', selector, $.proxy(this, 'blur'));
				$(selector).bind('click', selector, $.proxy(this, 'blur'));
			}
		},
		blur: function(e){
			$.timeout('FormChecker' + this.guid, $.proxy(this, 'checkField'), 20, [e.data, true]);
		},
		checkForm: function(e){
			var results = {},
				  passed = true;
			
			for (selector in this.rules){
				results[selector] = this.checkField(selector);
				if(results[selector].failedCount() > 0){ passed = false; }
			}
			this.processErrors(results, passed);
			
			return passed;
		},
		checkField: function(selector, process, ignoreFocus){
			var $elem = $(selector),
				result = new Copia.FormChecker.ResultObject(),
				i = 1,
				checker, fn, param, results;
			if ( $elem.is(":hidden") || $elem.attr('ignore-checker') == 'all') {
				result.ignore = true;
				return result;
			}
				
			if(ignoreFocus || $elem.filter('[name=' + document.activeElement.name + ']').length < 1){
				for(checker in this.rules[selector]){
					if($elem.attr('ignore-checker') != checker){
						fn = Copia.FormChecker.checkers[checker];
						param = this.rules[selector][checker];
						checkResult = fn.call(this, $elem.val(), $elem, param, i);
						if(checkResult == null){ 
							result.ignore = true;
						} else {
							result.add((!checkResult) ? 'failed' : 'passed', i, checker);
						}
					}
					i++
				}
				if( process === true){
					results = {};
					results[selector] = result;
					this.processErrors(results, result.failedCount() < 1);
				}
			}
			
			return result;
		},
		processErrors:function(results, passed){
			var	totalFail = false,
				$elem, $parent, result;

			for(selector in results){
				result = results[selector];
				if(!result.ignore){
					$elem = $(selector);
					$parent = $elem.parents('li').eq(0);
					
					$parent.addClass(result.failedCSS.join(" "))
						   .removeClass(result.passedCSS.join(" "))
						   .attr('data-validation', (result.failedCount() > 0) ? 'failed' : 'passed')
						   .find('.validationIcon').fadeTo(1, 1); // fixes issue with webkit where it fails to repaint background-image for validationIcon
				}
			}
			this.refreshSubmitButton(passed);
			/*if(passed && this.$form.find('[data-validation=failed]').length < 1){ this.$submitBtn.removeAttr('disabled'); }
			else{ this.$submitBtn.attr('disabled', 'disabled'); }
			*/
		},
		refreshSubmitButton : function(passed){
			if(passed && this.$form.find('[data-validation=failed]').length < 1){ 
				this.$submitBtn.removeAttr('disabled'); 
			} else { 
				this.$submitBtn.attr('disabled', 'disabled'); 
			}
		},
		/* 
			Is used by validation functions to determine whether it is neccessary to perform their validation if the form is optional and is blank
		 */
		isOptional:function(element){
			return $.trim(element.val()) == '';
		}
	}
});

Copia.formatTimeZone = function(offset) {
	var str = "GMT";
	str += (offset < 0) ? "-" : "+";
	
	var absOffset = Math.abs(offset);
	var floor = Math.floor(absOffset);
	var ceil = Math.ceil(absOffset);
	
	if(floor < 10) str += "0";
	str += floor + ":";
	
	if(ceil != floor) {
		var m = (absOffset - floor) * 60;
		if(m < 10) str += "0";
		str += m;
	} else str += "00";
	return str;
};
Copia.setTimeZoneAndDst = function(timeZoneElem, adjustDstElem) {
	var now = new Date();
	var feb = new Date(now.getFullYear(), 2, 1, 0, 0, 0, 0);
	var june = new Date(now.getFullYear(), 6, 1, 0, 0, 0, 0);
	var oct = new Date(now.getFullYear(), 10, 1, 0, 0, 0, 0);
	var offset = -1 * now.getTimezoneOffset() / 60;
	var monthOffsets = [];
	monthOffsets[0] = -1 * feb.getTimezoneOffset() / 60;
	monthOffsets[1] = -1 * june.getTimezoneOffset() / 60;
	monthOffsets[2] = -1 * oct.getTimezoneOffset() / 60;

	var dst = 0;
	var isDstObserved = false;
	for(var i=0; i<monthOffsets.length; ++i) {
		if(offset != monthOffsets[i]) {
			isDstObserved = true;
			if(offset > monthOffsets[i]) dst = monthOffsets[i] - offset;
			break;
		}
	}

	$(timeZoneElem).attr("value", Copia.formatTimeZone(offset + dst));
	if(isDstObserved) {
		var $dst = $(adjustDstElem);
		var attr = ($dst.attr("type") == "checkbox") ? "checked" : "value";
		$dst.attr(attr, true);
	}
};




// taken from jquery.validate unless otherwise stated
Copia.FormChecker.checkers = {
	minLength: function(value, element, min) {
		return this.isOptional(element) || value != null && value.length >= min;
	},
	maxLength: function(value, element, max) {
		return value != null && value.length <= max;
	},
	rangeLength: function(value, element, range){
		return this.isOptional(element) || value.length >= range.min && value.length <= range.max;
	},
	number: function(value, element) {
		return this.isOptional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value);
	},
	// taken from ketchup and modded
	required: function(value, element) {
		if(element.length > 1){
			var result = false;
			element.each(function (){
				result = result || Copia.FormChecker.checkers.required(value, $(this));
			});
			return result;
		}else{
			if(element.attr('type') == 'checkbox' 
			|| element.attr('type') == 'radio') {
				return element.attr('checked') == true;
			} else{
				return value.length > 0;
			}
		}
	},
	usPhone: function(phone_number, element) {
		var phone_number = phone_number.replace(/\s+/g, ""); 
		return this.isOptional(element) || phone_number.length > 9 && phone_number.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
	},
	date: function(value, element) {
		return !/Invalid|NaN/.test(new Date(value));
	},
	expDate: function(value) {
		var monthIn = ($("#m").val());
		var yearIn = ($("#y").val());
		if ((!monthIn)&&(!yearIn))
			return true;
		if ((!monthIn)||(!yearIn))
			return false;
		var today = new Date();
		var thisMonth = today.getMonth()+1;
		var thisYear = today.getYear();
		// IE and Firefox differ in how year is returned. 
		if (thisYear < 1900)
			thisYear += 1900;
		var thisDate = (thisYear*12)+thisMonth;
		var dateIn = (parseInt(yearIn)*12)+parseInt(monthIn);
		return (dateIn > thisDate);
	},
	remote: function(value, element, url, index){
		var data = {};
		data[element.attr("name")] = value;

		var me = this;

		if( this.isOptional(element)){
			return  this.isOptional(element);
		}
		$.ajax({
			'url' 		  : url, 
			'data' 		  : data,
			'contentType' : "application/x-www-form-urlencoded;charset=UTF-8",
			'dataType:'   : "json",
			'success'     : function(data){
				if(data){
					var results = {};
					var result = new Copia.FormChecker.ResultObject();
					result.add(data.available ? 'passed' : 'failed', index, 'remote');
					
					results[element.selector] = result;
					if(result.failedCount() > 0){ me.processErrors(results, result.failedCount() < 1); }
				}
			}
		});
			
		return true;
	},
	// http://docs.jquery.com/Plugins/Validation/Methods/email
	email: function(value, element) {
		// contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
		return this.isOptional(element) || /^((([a-z]|\d|[!#$%&'\*\+\-\/=\?\^_`{\|}~]|[A-Z])+(\.([a-z]|\d|[!#$%&'\*\+\-\/=\?\^_`{\|}~]|[A-Z])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[A-Z])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[A-Z]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[A-Z])|(([a-z]|\d|[A-Z])([a-z]|\d|-|\.|_|~|[A-Z])*([a-z]|\d|[A-Z])))\.)+(([a-z]|[A-Z])|(([a-z]|[A-Z])([a-z]|\d|-|\.|_|~|[A-Z])*([a-z]|[A-Z])))\.?$/i.test(value);
	},
	match: function(value, element, param){
		return this.isOptional(element) || $(param).val() == value; 
	},
	// fails if the specified element also fails
	depend: function(value, element, param){
		var result = this.checkField(param, false, true);
		if(result.failedCount() > 0){
			return null;
		}
		return true;
	},
	regex: function(value, element, pattern) {
		return this.isOptional(element) || pattern.test(value)
	},

	selectDate: function(){
		return true;
	},
	
	alphaNumeric: function(value, element) {
		var pattern = /^[a-zA-Z\d\x20\x2e\x2d]*$/;
		return this.isOptional(element) || pattern.test(value);
	} 
};

// registration form specific
$.extend(Copia.FormChecker.checkers, {
	//required to be over 18;
	dateOfBirth: function(value, element){
		var month = ((element.eq(0).val() < 10) ? '0' : '') + element.eq(0).val();
		var day = ((element.eq(1).val() < 10) ? '0' : '') + element.eq(1).val();
		var year = element.eq(2).val();
		//str = year + '-' + month + '-' + day;
		var str = month + "/" + day + "/" + year;
		if (month == "0" || day == "0" || !year || !month || !day)
			return false;
		//return Copia.FormChecker.checkers.date.call(this, str, element);
		if(!Copia.FormChecker.checkers.date.call(this, str, element))
			return false;
		var checkDate = new Date(), bd = new Date(str);
		checkDate.setFullYear(checkDate.getFullYear() - 18);
		return bd.getTime() <= checkDate.getTime();
	},
	username: function(value, element){
		var success = (value.match(/[A-Za-z0-9\u00BF-\u00FF\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+[._]{0,1}/g) || []).join("") == value;
		var dn = $("#displayname").val();
		if(success && dn == "")
			$("#displayname").val(value);
		return success;
	},
	password: function(value){
		strength = passwordStrength(value, $("#username").val());
		return strength != 'Weak';
	},
	usPhone: function(phone_number, element) {
		var phone_number = phone_number.replace(/[^\d+]/g, "");
		element.val(phone_number);
		
		return this.isOptional(element) || phone_number.length > 9 && phone_number.match(/^(1?\+?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
	},
	cvv:  function(cvvNumber, element) {
		var re = /^$|^\d{3,4}$/;
		return re.test(cvvNumber);
	},
	creditCard: function(cardNumber, element) {

		if ( this.isOptional(element) )
			return "dependency-mismatch";
		
		var isValid = false;
		var type;
		var ccCheckRegExp = /^[\d- ]*$/;
		// Check for numbers, hyphens, and spaces only
		isValid = ccCheckRegExp.test(cardNumber);

		if (isValid) {
			// Remove all the spaces and hyphens. Identify the card type and verify the number's length 
			// based on the credit type's identifying prefix.
			var cardNumbersOnly = cardNumber.replace(/ /g, "");
			cardNumbersOnly = cardNumbersOnly.replace(/-/g, "");
			// replace the card number value with the stripped content.
			element.val(cardNumbersOnly);
			var cardNumberLength = cardNumbersOnly.length;
			var lengthIsValid = false;
			var prefixIsValid = false;
			var prefixRegExp;

			switch (cardNumbersOnly.charAt(0)) {
			case '6':  // Discover
				lengthIsValid = (cardNumberLength == 16);
				prefixRegExp = /^(6011|622|64|65)/;
				type="DSCV";
				break;			
			case '5':  // MasterCard
				lengthIsValid = (cardNumberLength == 16);
				prefixRegExp = /^5[1-5]/;
				type = "MC";
				break;
			case '4':  // Visa
				lengthIsValid = (cardNumberLength == 16 || cardNumberLength == 13);
				prefixRegExp = /^4/;
				type = "VISA";
				break;
			case '3':  // American Express
				lengthIsValid = (cardNumberLength == 15);
				prefixRegExp = /^3(4|7)/;
				type = "AMEX";
				break;
			default:
				prefixRegExp = /^$/;
			}
			prefixIsValid = prefixRegExp.test(cardNumbersOnly);
			isValid = prefixIsValid && lengthIsValid;
		}

		if (isValid) {
			// Verify that the number is structured correctly using the LUHN Mod10 algorithm.
			var numberProduct;
			var numberProductDigitIndex;
			var checkSumTotal = 0;

			for (digitCounter = cardNumberLength - 1; digitCounter >= 0; digitCounter--) {
				checkSumTotal += parseInt(cardNumbersOnly.charAt(digitCounter));
				digitCounter--;
				numberProduct = String((cardNumbersOnly.charAt(digitCounter) * 2));
				for ( var productDigitCounter = 0; productDigitCounter < numberProduct.length; productDigitCounter++) {
					checkSumTotal += parseInt(numberProduct.charAt(productDigitCounter));
				}
			}
			isValid = (checkSumTotal % 10 == 0);
		}

		return isValid;
	}
});



/** LBox
 *
 * 	Yet another implementation of the lightbox design pattern
 *
 *	It uses a global lightbox that gets injected by little instances
 *	
 *	====== HTML Method ======
 *		if rel attribute starts with "lbox;" it will automatically be initialzed on page load;
 *		options can be placed inline in the rel attribute after 'lbox;' semi-colon separated with = signs
 *		
 *		ex. rel="lbox;height=200"
 *		
 *		HTML:
 *			<a href="#targetElementWithThisId" rel="lbox;height=200;width=300;">Click Me Im somewhere on the page already</a>
 *			<a href="somePageLoadedWithAjax.html" rel="lbox;height=200;width=300;type=ajax">Click Me I am loaded with ajax</a>
 * 			<a href="somePageToLoadInIframe.html" rel="lbox;height=200;width=300;type=iframe">Click Me I am an iframe</a>
 *	
 *	Initialization:
 *		$(selector).lbox(optionsObject);
 *		ex. $('a.iWantYou').lbox({height:200});
 *	
 *	Creating and opening a new Lbox from within javascript:
 *		$.lbox(target, optionsObject);
 *		
 *		Examples:
 *			$.lbox('#targetElementWithThisId', { height : 200, width : 200; });
 *			$.lbox('somePageLoadedWithAjax.html', { height : 200, width : 200, type : 'ajax' });
 *			$.lbox('somePageLoadedInIframe.html', { height : 200, width : 200, type : 'iframe' });
 *	
 *	Options
 *		type: (string) ajax|; default = 'html',
 *		height (int) height of the lightbox
 *		width (int) width of the lightbox
 *		resizeBox (bool) whether or make the lightbox smaller with scroll bars if the window is too small
 *		hideOnOverlayClick (bool) whether on not when a user clicks on the background overlay it should hide the lightbox; default = true
 *		callback (callback) function()
 *		useGlobalMsgHandler (callback) Whether or not to use the global ajax handler; default = true
 *	
 *	Useful Methods:
 *		Copia.LBox.close() - closes currently opened lightbox
 */

(function($){
	Copia.LBox = function(){ Copia.LBox.init(); }
	
	var self = Copia.LBox;
	// Current LBoxInstance
	var currentLBox;
	
	$.extend(Copia.LBox, {
		init: function(){
			if(Copia.LBox.overlay == null){
				self.$overlay = $("<div class='lboxOverlay'>").appendTo('body').click(self.overlayClick);
				self.$content = $('<div class="lboxContent">').prependTo('body');
				self.$shell = $('<div class="">').appendTo(self.$content);
				self.$closeBtn = $('<div class="lboxCloseBtn">').appendTo(self.$content).click(self.close);
			
				$(window).bind('hashchange', function(){
					if(self.$content.is(':visible') && $.bbq.getState('lbox') != currentLBox.timestamp){
						self.close();
					}
				});
				
				// Sets up event handler on any lbox html elements created even after page load
				$("a[rel^=lbox]").live('click', function(e){
					e.preventDefault();
					Copia.LBox.open(new Copia.LBox.Instance($(e.currentTarget)));
				});
			}
		},
		isOpen: function() {
			return self.$content.is(':visible');
		},
		open: function(instance){			

			if(self.$content.is(':visible')){
				self.close(true);
			}
			currentLBox = $.extend(currentLBox , instance);
			self.$overlay.fadeIn(100, function(){
				var type = currentLBox.options.type;

				if(self.types[type].open){
					self.types[type].open.call(currentLBox);
				}
			});
		
			$(window).resize(self.resize);
		},
		forceClose: function() {
			var fail = false;
			try {
				if(currentLBox) this.close();
				else if(parent.currentLBox) parent.Copia.LBox.close();
				else fail = true;
			} catch(e) { fail = true; }
			if(fail) {
				// Must be in different protocols
				var url = new RegExp(/^(https?):\/\/([\w\.]+)\/.*/g).exec(location.href);
				var protocol = (url[1] == "http") ? "https" : "http";
				location.href = protocol + "://" + url[2] + "/home/close.html";
			}
		},
		close: function(onlyContent){
			if(!currentLBox) return;
			var type = currentLBox.options.type;
			if(!type) return;
			if(onlyContent != true){
				self.$content.hide();
				self.$overlay.hide();
			
				self.$content.height(0).width(0);
			}
			
			if(self.types[type].close){
				self.types[type].close.call(currentLBox);
			}
			
			self.$shell.empty();
			$(window).unbind('resize', self.resize);
		},
		// makes compatable with the old lbox
		closeAll: function(){
			self.close();
		},
		center : function(resize){
			// For some reason, the desktop app needs to scroll to the top
			try {
				if(isDesktop()) {
					scroll(0,0);
				}
			} catch(e) {
				//console.log(e);
			}

			var $win = $(window),
				$content = self.$content;
			
			if(!$content.is(':visible') && resize != true){
				$content.css({ 
					'left'    : ($win.width() - 200) / 2,
					'display' : 'block',
					'top'	  : $win.scrollTop() + 50,
					'height' : 200,
					'width' : 200
				});
			}
	
			// Selects first input
			$content.find(':input:eq(0)').focus();
		},
		autoResize : function(width, height, left){
			
			var $win = $(window),
				$content = self.$content,
				winWidth = $win.width(),
				scrollTop = $win.scrollTop(),
				scrollLeft = $win.scrollLeft();
			
				height = currentLBox.options.height || self.$shell.height(),
				width = currentLBox.options.width || self.$shell.width(),
				left = (winWidth - width - 20) / 2 + scrollLeft;
			
			$content.stop(true, true).animate({
					'height': height,
					'width': width,
					'left': left
				}, {
					duration : 500,
					'easing' : "easeOutCirc", 
					'complete' : function(){
						self.$shell.show().children().show();
						currentLBox.timestamp = new Date().getTime();
						
						$.bbq.pushState({'lbox' :  currentLBox.timestamp});
					},
					'overflow': null
				}
			);
			// Selects first input
			$content.find(':input:eq(0)').focus();
		},
		resize: function(){
			self.center(true);
		},
	
		defaultOptions : {
			type: 'html',
			height: null,
			width: null,
			resizeBox : true,
			hideOnOverlayClick: true,
			callback : null,
			useGlobalMsgHandler : true,
			ignoreExisting: false
		},
		overlayClick: function(e){
			if(currentLBox.options.hideOnOverlayClick != 'false' && currentLBox.options.hideOnOverlayClick != false ){
				self.close();
			}
		}
	});

	Copia.LBox.types = {
		html : {
			open: function(){
				self.$shell.html($(currentLBox.target).detach());
				self.center();
				self.autoResize();
			},
			close: function(){
				$(currentLBox.target).detach().hide().appendTo('body');
			}
		},
		ajax : {
			open: function(){
				var _this = this;
				self.$shell.hide();
				$.ajax({
					url : currentLBox.target,
					type : 'get',
					dataType : 'html',
					global : currentLBox.useGlobalMsgHandler,
					data : { time: new Date().getTime() },
					success : function(data){
						self.$shell.html(data);
						if(self.$content.is(':visible')){ 
							setTimeout(function() {
								//$("a[rel^=lbox]", self.$content).lbox();
								if(_this.options.callback) _this.options.callback.apply(_this, []);
								self.autoResize();
							}, 200);
							return true;
						}
					}
				});
				self.center();
			}
		},
		iframe : {
			open: function(){
				iframe = $('<iframe scrolling="no" frameborder="0" height="' + currentLBox.options.height + 'px" width="' + currentLBox.options.width + 'px">')
						.attr("src", currentLBox.target)
				self.$shell.html(iframe);
				self.center();
				self.autoResize();
			}
		}
	};
	/**
	 *LightBox Instance Storage Class 
	 */
	Copia.LBox.Instance = function(elem, options){
		var $elem = $(elem),
			opts = {}, 
			pair, iOpts,
			potentialLBox = $elem.data('Copia.LBox');

		if(potentialLBox){ return potentialLBox; }

		this.target = $elem.attr("href");
		
		// inline options	
		if(iOpts = $elem.attr('rel') != null)
		{
			iOpts = $($elem).attr('rel').split(';');
			for(var x = 1; x < iOpts.length; x++){
				pair = iOpts[x].split('=');
				opts[pair[0]] = pair[1];
			}
			if(opts.callback) {
				try { opts.callback = eval(opts.callback); }
				catch(e) { opts.callback = null; }
			}
		}
		this.options = $.extend(this.options, self.defaultOptions, options, opts);

		if (!this.options.ignoreExisting) {
			$elem.data("Copia.LBox", this);
		}
	}
	
	$.fn.lbox = function(options){
		if($(this).length == 1){ return new Copia.LBox.Instance(this, options); }
		$(this).each(function(){
			return new Copia.LBox.Instance(this, options);
		});
	}
	
	$.lbox = function(target, options){
		var instance = {}
		instance.options = $.extend(this.options, self.defaultOptions, options);
		instance.target = target;
		self.open(instance);
	}
	
	// Adds fancy easing function
	$.extend($.easing, {
		easeOutCirc: function (x, t, b, c, d) {
			return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
		}
	});
	
})(jQuery)

$(document).ready(function(){
	Copia.LBox();
	// makes cancel buttons in lightboxes close lightbox
	$('.lboxContent .secondary, .closeMe').live('click', function(e){
		e.preventDefault();
		Copia.LBox.close();
	});
});



/** Tabs
 *	Provides Tabbing Design Pattern functionality
 *
 *	===== HTML =====
	<ul role="tablist">
		<li role="tab">First</li>
		<li role="tab">Second</li>
	</ul>
	<div role="tabcontent" class="FirstTab">
	</div>
 	<div role="tabcontent" class="SecondTab">
	</div>
	
 *	====== i18n Safe HTML =====
	<ul role="tablist">
		<li role="tab" data-tab-content=".FirstTab">First</li>
		<li role="tab" data-tab-content=".SecondTab">Second</li>
	</ul>
	<div role="tabcontent" class="FirstTab">
	</div>
 	<div role="tabcontent" class="SecondTab">
	</div>
 
 *	Options:
 *		activeClass (cssClassName) class added to an active tab; default = active
 *		tabSelector (selector) used to grab tabs from tablist
 *		contentSelector (selector) used to grab tabcontent for a particular tab; relative to tablist
 *		onChange - (callback) function(e, isFirstTime){}
 *			e - (event) event for when a tab is clicked on
 *			isFirstTime - *bool* whether or not the onchange was the first time the tab was selected.
 *		onFirstTime (callback) function(e){}
 *			e - (event) event for when a tab is clicked on
 *
 *		'this' in all callbacks is set to the current Tabs object
 *
 *	Variables:
 *		this.$tablist (jQuery) the tablist
 *		this.tablistId (string) the id for the tablist if it has one at all
 *		this.$tab (jQuery) currently selected tab
 *		this.$tabContent (jQuery) currently sekected tab's content
 *		this.tabIndex - (int) the index for the currently selected tab
 *		this.clickedArr - (array) contains the indexies of all tabs that have been selected so far
 *
 *	Events:
 *		tabReset : $('.subTabList').trigger('tabReset');
 *
 */
(function(){

$.fn.tabs = function(options){
	$(this).each(function(){
		new $.tabs(this, options);
	});
}
$.tabs = function(tablist, options){
	this.init(tablist, options);	
}

$.extend($.tabs, {
	defaultOptions: {
		activeClass : 'active',
		tabSelector : 'li[role=tab]',
		contentSelector : '~ div[role=tabcontent]',
		onChange : $.noop,
		onFirstTime: $.noop,
		autoScroll: true
	},
	deepLinked : {},
	prototype: {
		init: function(tablist, options){
			this.opts = $.extend({}, $.tabs.defaultOptions, options);
			
			this.$tablist = $(tablist).bind('tabReset', $.proxy(this, 'tabReset'));
			this.$tabs = $(this.opts.tabSelector, this.$tablist).click($.proxy(this, 'select'));
			this.$tab = this.$tabContent = this.tabIndex = null;
			
			this.clickedArr = [];
			
			this.tablistId = this.$tablist.attr('id');
			if(this.tablistId){ $.tabs.deepLinked[this.tablistId] = this; }
			//When $.tabs is initialized it automattically selects the first tab or tab indicated in hash
			var temp = this.$tabs.eq($.bbq.getState(this.tablistId) || 0);
			(temp.is('.disabled')) ? this.$tabs.filter(':not(.disabled)').eq(0).trigger('click', false) : temp.trigger('click', false);
		},
		select: function(e, bbqIt, useCallbacks, reload){
			var target = $(e.target),
				isFirstTime = false;
			if(target.hasClass("tab_follow")) return;
			
			e.preventDefault();
			
			if(!target.is('.disabled') && (this.$tab == null || (this.$tab.text() != target.text() || reload==true))){
				if(this.$tab != null){
					this.$tabContent.hide(); // hides old tabcontent
					this.$tab.removeClass(this.opts.activeClass); // marks old tab as inactive;
				}
				this.$tab = target.addClass(this.opts.activeClass); // marks tab as active;
				this.tabIndex =  this.$tabs.index(this.$tab);

				this.$tabContent = this.findTabContent(this.$tab).show();
				
				if(this.tablistId && bbqIt != false){
					var state = {}
					state[this.tablistId] = this.$tabs.index(this.$tab);
					$.bbq.pushState(state);
				}
				
				if($.inArray(this.tabIndex, this.clickedArr) < 0){
					if(useCallbacks != false) { this.opts.onFirstTime.call(this, e); }
					isFirstTime = true;
					this.clickedArr.push(this.tabIndex);
				}
				// calls callback onChange function
				if(useCallbacks != false){ this.opts.onChange.call(this, e, isFirstTime);	}		  
				
				// Scroll window to the top of the where the tablist is if the page is scrolled down further than the tablist
				var tablistTop = this.$tablist.offset().top;
				if(this.autoScroll && tablistTop < $(window).scrollTop()){
					$(window).scrollTop(tablistTop);
				}
			}
		},
		findTabContent: function(tab){
			return $(this.opts.contentSelector + (tab.attr('data-tab-content') || '.' +  tab.text().split(' ').join('') + 'Tab'), this.$tablist);
		},
		/**
		 * Resets isFirst flag for all tabs when there is an onChange delegate
		 */
		tabReset : function(e){
			this.clickedArr = [];
		}
	}
});

$(window).bind('hashchange', function(){
	for(key in $.tabs.deepLinked){
		var state = $.bbq.getState(key) || 0;
		$('#' + key + ' > li').eq(state).trigger('click', false);
	}
});

})();



/***/


(function(){

	$.fn.rotator = function(options){
		$(this).each(function(){
			new $.rotator(this, options);
		});
	};

	$.rotator = function(rotlist, options){
		this.init(rotlist, options);	
	};

	$.extend($.rotator, {
		/*defaultOptions: {
			activeClass : 'active',
			tabSelector : 'li[role=tab]',
			contentSelector : '~ div[role=tabcontent]',
			onChange : $.noop
		},
		deepLinked : {},*/
		prototype: {
			init: function(rotlist, options){
				var rotator = this;
				//this.opts = $.extend({}, $.rotator.defaultOptions, options);
				this.$rotlist = $self = $(rotlist);
				this.id = $self.attr('id');
				this.rotatorIndex = 0;
				this.ival = null;
				this.tabs = $("li[role=tab]", $self);
				this.tabctrl = $("ul[role=tablist]", $self);
				this.tabctrl.tabs({ onChange: function() { rotator.changeBanner(this); }, autoScroll: false });
				$self.mouseenter($.proxy(this, "stop")).mouseleave($.proxy(this, "start"));
				this.start();
			},
			stop: function() {
				if (this.ival)
					window.clearTimeout(this.ival);
				this.ival = null;
			},
			start: function() {
				this.stop();
				this.ival = window.setTimeout($.proxy(this, "autoRetab"), 5000);
			},
			autoRetab: function() {
				this.rotatorIndex = (this.rotatorIndex + 1) % this.tabs.length;
				$(this.tabs[this.rotatorIndex]).click();
				this.start();
			},
			changeBanner: function(tabs) {
				var self = this, currTab = null;
				$("div[role=tabcontent]", self.$rotlist).each(function(index) {
					if (index == tabs.tabIndex)
						self.rotatorIndex = index, currTab = $(this);//$(this).fadeIn();
					else
						$(this).hide();
				});
				if (currTab)
					currTab.fadeIn();
				this.start();
			}
		}
	});
})();




/*
 *	Uses <input>'s HTML5 placeholder attribute to display hint text.
 * 	Example: 
 *  if(!$.support.placeholderText){ $('input').placeholderText(); }
*/
(function(){
	var placeholderCSS = 'placeholder'
	$.fn.placeholderText = function(cssClass){
		var cssClass = cssClass || placeholderCSS;
		$(this).each(function(){
			var $elem = $(this),
				pText = $elem.attr('placeholder');
				
			$elem.bind({
				'focus.placeholderText' : function(){
					if(pText == this.value){ $elem.removeClass(cssClass).val(''); }
				},
				'blur.placeholderText' : function(){
					if($elem.val() == ''){ $elem.addClass(cssClass).val(pText) }
				}
			});
			$elem.trigger('blur.placeholderText');
		});
	}

	var oldValFunction = $.fn.val;
	
	// overrides jquery's built in .val() function so that it will return empty string if the value of the element is equal to the elements placeholder attribute.
	$.fn.val = function(value){
		if(arguments.length == 0){
			var currentValue = oldValFunction.call(this);
			return ($(this).attr('placeholder') != currentValue) ? currentValue : '';
		} else {
			oldValFunction.call(this, value);
			return this;
		}
	}
})()



$.fn.taxonomyEdit = function(){
	$(this).each(function(){new Copia.TaxonomyEdit(this);})
}
Copia.TaxonomyEdit=function(elem){
	this.init(elem);
}
$.extend(Copia.TaxonomyEdit,{
	prototype:{
		init:function(elem){
			this.$elem = $(elem);
			this.$textarea = $("textarea",this.$elem);
			this.$links = $("a",this.$elem);
			this.$links.click($.proxy(this,"autoAdd"));
			this.terms = new $.Hash();
		},
		autoAdd:function(e){
			e.preventDefault();
			var text = $(e.currentTarget).text();
			var val = this.$textarea.val();
			var arr = val.split (",");
			if (val.length) {for (var i = 0; i < arr.length; i++){
				this.terms.add( $.trim(arr[i]),$.trim(arr[i]));
			}}
			if(this.terms.hasKey(text)){
				this.terms.remove(text);
				$(e.currentTarget).removeClass('userEntriesSelected');
			}
			else{
				this.terms.add(text, text);
				$(e.currentTarget).addClass('userEntriesSelected');
			}
			this.$textarea.val(this.terms.join(", "));
		}
	}
})




/* Extends slider and adds delete and move up move down  functionality. */
$.fn.userManagedSlider = function(url, options){
	if($(this).data('Copia.Slider') != null){
		return $(this).data('Copia.Slider');
	}
	$(this).each(function(){
		_C.sliders.push(new Copia.UserManagedSlider(this, url, options));
	});
};

Copia.UserManagedSlider = Copia.Slider2.extend({
	init: function(elem, url, options){
		var $elem = $(elem);
		this.id = $elem.attr('data-collection-id');

		$.extend(true, options, {
			extraParams : {
				id: this.id
			},
			onIndexChange : this.onScrollSuccess
		});
		this._super(elem, url, options);
	
		$('.moveUpBtn', this.$elem).click($.proxy(this, 'moveUp'));
		$('.moveDownBtn', this.$elem).click($.proxy(this, 'moveDown'));
		
		$('.delete', this.$elem).click($.proxy(this, 'remove'));
	},
	onScrollSuccess : function(){
		$('.count', this.$elem).text(this.total);
		$('.start', this.$elem).text(Math.min(this.total,this.index + 1));
		$('.end', this.$elem).text(Math.min(this.index + this.options.interval, this.total));
	},
	/* Movement Methods */
	move : function(direction){
		$.getJSON(this.options.moveUrl + this.id, {'direction' : direction });
		var $mover = this.$elem,
			$container = $mover.parents('div').eq(0),
			index = $container.children().index($mover);

		if(direction > 0 && index > 0){
			$mover.detach();
			$mover.insertBefore($container.children().eq(index - 1));
		} else if(direction < 0 && index < $container.children().length - 1 ){
			$mover.detach();
			$mover.insertAfter($container.children().eq(index));
		}
	},
	moveUp : function(e){
		e.preventDefault();
		this.move(1);
	},
	moveDown : function(e){
		e.preventDefault();
		this.move(-1);
	},
	remove : function(e){
		e.preventDefault();
		if(confirm(this.options.removeMsg + ' ' + this.$elem.find('.titleBar h3').text())){
			$.getJSON(this.options.removeUrl + this.id);
			p = this.$elem.parent();
			this.$elem.remove();
			if (!p.children().length)
				location.reload();
		}
	}
})





Copia.BookSearch = function(){}

Copia.BookSearch = Copia.Search.extend({
	defaultOptions : {
		defaultURL : '/catalog/list.json'
	},
	init:function(options){
		this._super(options)
	
		$(this.options.results + ' .categories a').live('click', $.proxy(this, 'listSubjectSearch')); // Subject Link Search
		// TBD Disable this click listener to go to FiledBy's author page instead of doing a search
		//$(this.options.results + ' li h4 a').live('click', $.proxy(this, 'listAuthorSearch')); 	   // Author Link Search
		$(this.options.results + ' a.tagSearch').live('click', $.proxy(this, 'listTagSearch'));				   // Tag Link Search
		
		$('.filterBar a.remove').live('click', $.proxy(this, 'removeParamClick'));	   // Remove Filter

		if(this.options.useSearchBar) {
			$("#downloadable").live('click', $.proxy(this, 'downloadableClick')); // Show only downloadable
			$("#languageOnly").live('click', $.proxy(this, 'languageOnlyClick')); // Show only downloadable
		}
	},
	search:function(e){
		if(e){ e.preventDefault() }
		if($(".autoCompleteList li.active").length > 0) {
			//console.log([e, "no go"]);
			return;
		}
		var key = $('#key').val();
		//var value = $('#searchType').val();
		var value = "key";
		
		if(key){
			key = key.replace(/título:/gi, 'title:').replace(/autor:/gi, 'author:');
			this.reset();
			this.params[value] = key;

			if($("#downloadable").attr("checked")) this.params["downloadable"] = true;
			else delete this.params["downloadable"];
			
			C.Stats.search(key);
			this.loadResults(true);
		}
	},
	// Special Search Types
	listSubjectSearch : function(e){
		if(e){ e.preventDefault(); }
		
		this.reset();
		var catId = $(e.currentTarget).attr('data-cat-id');
		var subcatId = $(e.currentTarget).attr('data-subcat-id');
		if(subcatId) this.addParam('subcatid', subcatId);
		else if(catId) this.addParam('catid', catId);
		this.loadResults(true);
	},
	listAuthorSearch : function(e){
		if(e){ e.preventDefault(); }
		
		this.reset();
		this.addParam('author', $(e.currentTarget).attr("data-author"));
		this.loadResults(true);
	},
	listTagSearch : function(e){
		if(e){ e.preventDefault(); }
		
		this.reset();
		this.addParam('tag', $(e.currentTarget).attr('data-tag') || $(e.currentTarget).text());
		this.addParam('scopeFilter', 'TAGS');
		this.loadResults(true);
	},
	callback: function(data){
		var me = this;
		$.each(data.publications, function(i, item){
			item.resultIndex = me.shownCount - data.count + i + 1;
			item.defaultCover = "/images/book_covers/generic_book_"+(1+Math.floor(Math.random()*4))+"_lrg.jpg"
			
			isCollapsed = true; // me.$results.parents('[role=tabcontent]').eq(1).find('ul[role=tablist] > li:eq(0)').is('.active');
			$item = $("#catalogItemTemplate").jqote(item).toggleClass('collapsedList',  isCollapsed);
			$item.find('.cover').toggleClass('small', isCollapsed).toggleClass('large', !isCollapsed)
			$item.appendTo(me.$results);
		});
		
		// Initializes Libraries
		$("menu.libraryStatus").menu({onSelect: Copia.Menus.LibraryStatus, showSelected : true });
		
		if(data.parameters && data.parameters.filters && data.parameters.filters.length > 0){ this.createFilterBar(data.parameters.filters); }
	},
	// This needs to be somewhat refactored to support things like category ID
	// - If the parameter name name value were stored to the object, it would be better than deriving it from the text
	getFilterBarParams: function() {
		var types = [];
		var params = {};
		
		$('.filterBar a[data-filter-type]').each(function(){
			var jthis = $(this);
			var type = jthis.attr("data-filter-type");
			if(!params[type]) {
				types.push(type);
				params[type] = [];
			}
			//var regex = new RegExp("^(?:" + type + ":)?(.+)\\sx$");
			var value = jthis.attr("data-filter-text"); //jthis.parent().text().replace(regex, "$1");
			params[type].push(value);
		});
		
		var parameters;
		if(types.length > 0) {
			parameters = {};
			for(var i=0; i<types.length; ++i) {
				var type = types[i];
				parameters[type] = params[type].join(" ");
			}
			if($("#downloadable").attr("checked")) parameters.downloadable = "true";
		} else parameters = null;
		return parameters;
	},
	removeParamClick: function(e){
		e.preventDefault();
		var type = $(e.currentTarget).attr('data-filter-type');
		var index = $(e.currentTarget).attr('data-filter-index');
		$(e.currentTarget).parent().remove();
		
		this.params = this.getFilterBarParams();
		if(this.params != null) {
			this.loadResults(true);
		} else {
			this.reset();
			if($("body#catalog").length > 0) location.href = "#mainTabs=0";
			else this.loadResults(true);
		}
	},
	createFilterBar : function(filters){
		var filterTrans = {
	
		
		

		
	
		};
		var getTranslatedFilter = function(txt, ok) {
			if (ok && !$.isEmptyObject(filterTrans)) {
				for (var i in filterTrans) {
					txt = txt.replace(i, filterTrans[i]);
				}
			}
			return txt;
		};

		$("#searchBar").toggleClass("withFilterBar", filters.length > 0);
		
		//$(".filterBar").empty();
		$(".filterBar li").remove(":not(.downloadFilter)");
		
		if(filters.length > 0) {
			$.each(filters, function(index, item){
				var newFilter = "";
				if (item.sign == "-")
					newFilter = item.sign;
				var strippedFilter = item.filter; //.replace(/[^\w]/g, "");
				if( strippedFilter.length > 0){ 
					if ( item.type != "key"){ newFilter += getTranslatedFilter(item.type, true) + ":"; }

					newFilter += getTranslatedFilter(strippedFilter, item.type == 'category')  + " ";
					if (item.type == "language")
					{
						additional_html = "<a style='color:#55C4B3' href='/account/index.html'></a>";
						$(".filterBar").append("<li class='filter'>"+getTranslatedFilter(newFilter, true)+"<a class='remove' href=#' data-filter-type='" + item.type.toLowerCase() + "' data-filter-index='" + item.index + "' data-filter-text='" + getTranslatedFilter(item.filter,true) + "'>"+ additional_html + "</a></li>");
					}	
					else
					{
						$(".filterBar").append("<li class='filter'>"+newFilter+"<a class='remove' href=#' data-filter-type='" + item.type.toLowerCase() + "' data-filter-index='" + item.index + "' data-filter-text='" + item.filter + "'>x</a></li>");
					}
						
				}
			});
			$(".filterBar").show();
		}
	},
	clearFilterBar : function(disableResults) {
		$("#searchBar").removeClass("withFilterBar");
		$("span.totalResults").html("");
		if(disableResults) {
			$("#listTab").addClass("disabled");
			$("#gridTab").addClass("disabled");
			$("#exploreTab").addClass("disabled");
		}
	},
	reset:function(url,params){
		if(!params) params = {};
		if($("input[name=downloadable]").attr("checked")) params.downloadable = true;
		this._super(url,params);
		var isLibrary = ($("body#library").length > 0);
		this.clearFilterBar(!isLibrary);
		//if(isLibrary) this.loadResults(true);
	},
	updateGrid : function(){
		var url = C.url(this.url + '?' + $.param($.extend({'key': ''}, this.params)) + '&sortField=' + $('#sortField').val() + '&rowsPerPage=64&page=1');
		var type = $.trim($('.GridTab ul[role=tablist] > li.active').attr('data-grid-type'));

		if(document.flashGrid){
			document.flashGrid.updateGridData(url, type);
		}
	},
	downloadableClick: function(e) {
		var downloadable = e.currentTarget.checked;
		if(downloadable) this.addParam("downloadable", downloadable);
		else this.removeParam("downloadable");
		//var value = $("#key").attr("value");
		//if(!value || $("#key").attr("placeholder") == value) {
			this.loadResults(true);
		//}
	},
	languageOnlyClick: function(e) {
		//var languageOnly = e.currentTarget.checked;
		//this.addParam("languageOnly", languageOnly);
		//var value = $("#key").attr("value");
		//if(!value || $("#key").attr("placeholder") == value) {
			this.loadResults(true);
		//}
	},
	
	onPageLoadSearch : function(){
		this._super();
		if(this.hasParams()) {
			var _this = this;
			setTimeout(function() { _this.selectListTab(); }, 100);
		}
	},
	selectListTab: function(){
		$("#mainTabs > li:eq(1)").trigger('click', [true, false]);
	}
});


$.fn.setCursorPosition = function(pos) {
	var $elem = $(this);
	if($elem.get(0).setSelectionRange) {
    	$elem.get(0).setSelectionRange(pos, pos);
	} else if ($elem.get(0).createTextRange) {
    	var range = $elem.get(0).createTextRange();
    	range.collapse(true);
    	range.moveEnd('character', pos);
    	range.moveStart('character', pos);
    	range.select();
    }
	return $elem;
};

$.fn.searchPrefix = function(typeSelector){
	$(this).each(function(){
		new Copia.SearchPrefix(this, typeSelector);
	});
};

Copia.SearchPrefix = function(elem, typeSelector){
	this.init(elem, typeSelector);
};

$.extend(Copia.SearchPrefix, {
	prototype: {
		init: function(elem, typeSelector) {
			var typeRegex = "(";
			this.$type = $(typeSelector).bind({
				"change.Copia.SearchPrefix" : $.proxy(this, "changeType")
			}).each(function() {
				if(this.value) {
					if(typeRegex.length > 1) typeRegex += "|";
					typeRegex += this.value;
				}
			});
			this.typeRegex = typeRegex + "):";

			this.$elem = $(elem).bind({
				"focus.Copia.SearchPrefix" : $.proxy(this, "show"),
				"blur.Copia.SearchPrefix"  : $.proxy(this, "hide"),
				"keyup.Copia.SearchPrefix" : $.proxy(this, "keypress")
			});
		},
		changeType: function(e) {
			var type = $(e.currentTarget).val();
			var val = this.$elem.val();
			
			var cursorPos = -1;
			if(val != "" && !this.isOnlyAnyPrefix()) {
				var typeRegex = new RegExp(this.typeRegex);
				
				// Ensure there isn't a dangling search type at the end
				var terms = val.split(" ");
				var lastTerm = "";
				for(var i=terms.length-1; i>-1; --i) {
					if(terms[i] != "") {
						lastTerm = terms[i];
						break;
					}
				}
				if(new RegExp(this.typeRegex + "$").test(lastTerm)) {
					val = val.substring(0, val.lastIndexOf(lastTerm)-1);
				}
				
				if(type != "") {
					this.$elem.val(val += " " + type + ":").addClass("addedPrefix");
				} else {
					if(this.$elem.hasClass("addedPrefix")) {
						terms = val.split(" ");
						var str = "";
						for(var i=0; i < terms.length; ++i) {
							if(typeRegex.test(terms[i])) break;
							
							if(str != "") str += " ";
							str += terms[i];
						}
						
						if(str.length == 0) {
							this.$elem.val(" " + val);
							cursorPos = 0;
						} else if(str.length == val.length) {
							if(val.substring(val.lenght-1) != " ") {
								this.$elem.val(val + " ");
							}
						} else {
							this.$elem.val(val.substring(0, str.length) + " " + val.substring(str.length));
							cursorPos = str.length+1;
						}
					} else if(val.substring(val.lenght-1) != " ") {
						this.$elem.val(val + " ");
					}
				}
			}
			var valLength = this.$elem.val().length;
			if(cursorPos < 0) cursorPos = valLength;
			this.$elem.focus();
			if(valLength > 0) this.$elem.setCursorPosition(cursorPos);
		},
		show: function() {
			if(this.$elem.val() == "") {
				var type = this.getType();
				if(type != "") {
					this.$elem.val(type + ":").addClass("addedPrefix").setCursorPosition(type.length+1);
				}
			}
		},
		hide: function() {
			if(this.isOnlyPrefix()) {
				this.clear();
				this.$elem.trigger("blur.placeholderText");
			}
		},
		keypress: function(e) {
			if(e.keyCode == 8) {
				var type = this.getType();
				if(this.isOnlyPrefix()) {
					this.$type.filter("[value='']").attr("checked", true);
					this.clear();
				}
			}
		},
		isOnlyPrefix: function(type) {
			if(this.$elem.hasClass("addedPrefix")) {
				if(arguments.length < 1) type = this.getType();
				type += ":";
				var val = this.$elem.val();
				return (val.length <= type.length && type.indexOf(val) > -1);
			}
			return false;
		},
		isOnlyAnyPrefix: function() {
			return new RegExp("^\s*" + this.typeRegex + "\s*$").test(this.$elem.val());
		},
		getType: function() {
			return this.$type.filter(":checked").val();
		},
		clear: function() {
			this.$elem.val("").removeClass("addedPrefix");
		}
	}
});



/**
 * Google Search
 */
 Copia.CatalogSearch = Copia.BookSearch.extend({
	defaultOptions : {
		results	 	: '.ListTab .catalogList',
		loadBar		: '.ListTab .loadBar',
		defaultURL 	: '/catalog/list.json',
		//useSearchBar: false,
		validStartParams: ['author', 'key', 'subject', 'title', 'exact_title', 'tag', 'downloadable','description', 'isbn', 'idlist', 'scopeFilter', 'textbook']
	},
	initElements:function(){
		this._super();
		$('#listSearch1, #listSearch2').click($.proxy(this, 'handleListSearch'));
		$('.CategoriesTab .categories a[data-cat-id]').live('click', $.proxy(this, 'listSubjectSearch'));
		//if(/\bdownloadable\=true\b/.test(location.search)) $("input[name=downloadable]").attr("checked", true);
	},
	callback: function(data){
		this._super(data);
		$("#mainTabs[role=tablist] li:gt(0)").removeClass('disabled'); // enable other tabs
		this.selectListTab(); // select list tab
		
		// hides create save search button when its a tag search
		var allowSavedSearch = true; // whether or not to show add saved search button;
		for(var i = 0; i < data.parameters.filters.length; i++){
			if(data.parameters.filters[0].type == 'Tag'){
				allowSavedSearch = false;
				break;
			}
		}
		$('a[href=#savedSearch]').toggle(allowSavedSearch) 
	},
	/*sortSearch : function(e){
		// Reset the page (potentially call reset)
		this.page = 1;
		this.loadResults(true);
		e.preventDefault();
	},*/
	listSavedSearch : function(id){
		this.reset();
		this.addParam('searchId', id)
		this.loadResults(true);
	},
	listFeatured : function(id){
		this.reset();
		this.addParam('key', id);
		this.url = '/catalog/showFeaturedContentAsList.json';
		this.loadResults(true);
	},
	selectListTab: function(){
		$('#listTab').removeClass('disabled').addClass('active').click();
	},
	/* Saved Searches Stuff */
	saveSearch: function(){
		var searchName = $("#savedSearchName").val();
		if (searchName== null || searchName == ''){
			$("#errorMessage").fadeIn("normal");
			return false;
		}else{
			$("#errorMessage").fadeOut("normal");
		}
		
		var data = {
			'dashboardFlag' : $("#dashboardFlag").val(),
			'searchName' : searchName
		}
		$.getJSON("saveSearch.json", data, $.proxy(this, 'saveSearchCallback'));
	},
	saveSearchCallback : function(json) {
		if (json.success !== false) {
			var $elem = $('#sliderTemplate').jqote(json);
			$elem.find('.toggle').click();
			$elem.appendTo('.savedSearchContainer');
			Copia.LBox.close();
		}
	},
	handleListSearch : function() {
		var $savedSearchDropDowns =  $('#searchDropDown1, #searchDropDown2')
		$.getJSON("listSearch.json", function(json){
			var dropdownList = '';
			$.each(json.searches, function(index,sp) { 
				dropdownList +='<li><a href="#" onclick=\'_C.catalogSearch.listSavedSearch('+sp.id+'); return false;\' class="action">'+sp.name+'</a></li>';
			});
			$savedSearchDropDowns.html(dropdownList);
		});
	},
	loadResults: function(isNew){
		$("input[name=downloadable]").attr("checked", this.params.downloadable);
		if ($("input[name=languageOnly]") != null)
		{ 
			this.params.languageOnly = $("input[name=languageOnly]").attr("checked");
			//$("input[name=languageOnly]").attr("checked", this.params.languageOnly);
		}
		this.selectListTab(isNew);
		this._super(isNew);
	}
});
/**
 * Google Search
 */
Copia.GoogleSearch = Copia.BookSearch.extend({
	defaultOptions: {
		defaultURL : '/catalog/googleSearch.json',
		results : '.GoogleTab .catalogList',
		loadBar : '.GoogleTab .loadBar',
		searchOnPageLoad : false,
		useSortField : false,
		useSearchBar : false,
		y : 42 //unnecessary but somehow forces the searchbar to initialize
	},
	reset: function(){
		this._super();
		this.params = { sortField : 'score' };
	},
	googleSearch:function(){
		this.selectListTab();
		this.params = _C.catalogSearch.params;
		this.loadResults(true);
	},
	selectListTab: function(){
		$('#mainTabs > li:eq(4)').removeClass('disabled').click();
	}
});

/**
 * TODO: When we have more time, have this handle the promo name.
 */

/**
 * Promo Search
 */
Copia.PromoSearch = Copia.BookSearch.extend({
	defaultOptions : {
		results:'#promo_catalogList',
		loadBar:'.FreeTab .loadBar',
		useSearchBar:false,
		useSortField:false,
		searchOnPageLoad:false
	},
	listFree : function(){
		this.reset('/catalog/promotionlist.json?promotionName=Beta2010A');
		this.loadResults(true);
	},
	createFilterBar : $.noop,
	removeParamClick : $.noop,
	selectListTab : $.noop
});

/**
 * Promo Harlequin Search
 */
Copia.PromoHarlequinSearch = Copia.BookSearch.extend({
	defaultOptions : {
		results:'#promoHarlequin_catalogList',
		loadBar:'.FreeHarlequinTab .loadBar',
		useSearchBar:false,
		useSortField:false,
		searchOnPageLoad:false,
		rowsPerPage:22
	},
	listFree : function(){
		this.reset('/catalog/promotionlist.json?promotionName=Harlequin');
		this.loadResults(true);
	},
	createFilterBar : $.noop,
	removeParamClick : $.noop,
	selectListTab : $.noop
});




Pages = Class.extend({
	init : function(url, totalResults, options){
		this.url = url;
		this.totalResults = totalResults;
		this.options = $.extend({}, this.defaultOptions, options);
		this.totalPages = Math.ceil(totalResults / this.options.perPage);
	},
	generate: function(page){
		var options = this.options;
			url = [this.url, '&', options.perPageParam, '=', options.perPage, '&', options.pageParam, '='].join('');
			maxPages = options.maxPages,
			totalPages = this.totalPages,
			startPage = Math.max(1, (totalPages > maxPages) ? page - maxPages / 2 : 1),
			endPage = Math.min(totalPages, startPage + 1 + maxPages);
			html = [];
		
		for(var i = startPage; i <= endPage; i++){
			html.push('<a href="', url, i, '" class="pageNumber">', i, '</a>');
		}
		
		// adds first and last page links
		html.unshift('<a href="', url, 1, '" class="pageNumber firstPage', ((page == 1) ? ' disabled' : ''), '">First Page</a>');
		html.push('<a href="', url, totalPages, '" class="pageNumber lastPage', ((page == totalPages) ? ' disabled' : ''), '">Last Page</a>');
		
		return html.join('');
	},
	getFirstPageLink : function(){
		
	},
	defaultOptions : {
		maxPages : 4, // additional pages
		pagesElem : '.loadMore',
		perPage : 10,
		pageParam : 'page',
		perPageParam : 'rowsPerPage'
	}
});

<!-- Discussion loader -->



Copia.DiscussionLoader = Copia.Search.extend({
	defaultOptions : {
		results : '.discussionsList',
		defaultURL : '/conversations/list.json',
		useSortField : true,
		rowsPerPage : 10
	},
	loadAll:function(gFilter){
		this.reset();
		this.addParam('gFilter', gFilter);
		this.loadResults(true);
	},
	loadStarted:function(gFilter){
		this.reset();
		this.addParam('filter', 'started');
		this.addParam('gFilter', gFilter);
		this.loadResults(true);
	},
	loadCommented:function(gFilter){
		this.reset();
		this.addParam('filter', 'commented');
		this.addParam('gFilter', gFilter);
		this.loadResults(true);
	},
	loadForum:function(forumId){
		this.reset();
		this.addParam('forumId', forumId);
		this.loadResults(true);
	},
	callback:function(data){
		targetElem = this.$results;
		tpl = $('#discussionItemTemplate');
		$.each(data.discussions,function(i,topic){
			$(targetElem).append(tpl.jqote(topic));
			/*	
			Doesn't work with IE
			item = tpl.jqote(topic);
			$(targetElem).append(item);
			*/
		});
		/* $(targetElem).last().remove('.divider'); */
	}
});


