
	
	
	/* 
		processResponse
		This function is the standard handler for all AJAX requests.  It allows the application to ultimately send the requests to different handling functions, while at the same time centralizing logic to see if login is required, to identify what node to update with the results, etc.
	*/
	
	function processResponse(markup, node, successFunction, loginFunction, options) {
		// this function checks an AJAX response text to see if the user has to log in
		// if not, it sends the markup to the responseFunction to process normally
		// (for instance, when adding a tag, we get back a simple array)
		// if it does detect a login, it slices off the identifier, 
		// then either executes a passed function or hangs the whole thing on the 
		// ajaxDiv element on every page.  (the server then redirects appropriately).
	
		debug("Begun!");
		//debug("markup = " + markup);
	
		var loginMarker = "^LOGIN^";
	
		if (node == null || node == "")
			node = "ajaxDiv";	
		
		if (markup.indexOf(loginMarker) == 0){
			if (typeof loginFunction == "function"){
				debug("executing loginFunction")
				loginFunction(markup.slice(loginMarker.length), node);
			}
			else {
				debug("executing standard")
				hangAjaxResponse(markup.slice(loginMarker.length));
			}
			//debug("done -- " + markup.indexOf(loginMarker));
			return
		}
	
		debug("executing normally");
		
		// if this is normal, go forward
		if (typeof successFunction == "function"){
			debug("using successFunction");
			successFunction(markup, node, options);
		}
		else {
			debug("using standard.");
			hangAjaxResponse(markup, node);
		}		
	}
	
	/*
		hangAjaxResponse
		This is the standard function to update a target node with an Ajax response.  If there are scripts in the Ajax response, it uses the evalScripts function specified in the Prototype library to execute them.
	*/
	function hangAjaxResponse(text, node){
		debug("hanging ajax at " + node);
		debug ("text: " + text);
		
		$(node).innerHTML = text;
		
		debug("ajax hung");
		try {
			text.evalScripts();
			debug("scripts executed");
		}
		catch (err) {
			debug(err);
		}
	}

	function googleTracker(){
		if (typeof(_gat) == "object"){
			var pageTracker = _gat._getTracker("UA-4418673-1");
			pageTracker._initData();
			pageTracker._trackPageview();
		}
	}
	
	function showHiddenResults(element, hideThis) {
		// we should be passed the <li> element
		if (hideThis && element)
			element.style.display = "none";

		if (!(element && element.tagName == "UL")){
			if (!element || element.parentNode.tagName == "HTML")
				return;
			else
				showHiddenResults(element.parentNode);
		}
			
		// now we're at the parent ul element
		for (var i = 0; i < element.childNodes.length; i++){
			if (element.childNodes[i].className == "resultWhereCandidateDoesNOTSpeak")
				appear(element.childNodes[i], { noQueuing: true });
				//element.childNodes[i].className = "resultWhereCandidateDoesNOTSpeak";
		}			
	}

	function sendToLogin(){
		document.location = "/home/login?return=" + escape(document.location);
	}
	
	// thwart email harvesting bots...hopefully
	var emailStart = "2alexdf";
	var emailEnd = "creadcongress.orgawes";
	function contactUs(){
		document.location = "mailto:" + emailStart.slice(1,5) + "@" + emailEnd.slice(1,17);
	}

		function smoothlyInsert(item, parent, options) {
		// this function smoothly inserts a node into another node
		if (!options)
			options = {};		
		
		smoothlyManipulate(item, parent, options);	
	}

	function smoothlyRemove(item, parent, options){
		if (!options)
			options = {};
		
		options["isRemove"] = true;
		
		debug("Smoothly removing: scale.");
		if (!isIE)
			scale(item, -100, options);
		debug("Smoothly remove - manipulate called.");
		smoothlyManipulate(item, parent, options);
	}

	function smoothlyHide(item, parent, options){
		if (!options)
			options = {};
		
		options["isHide"] = true;

		if (!isIE)
			scale(item, -100, options);
		debug("Smoothly hiding - manipulate called.");
		smoothlyManipulate(item, parent, options);
	}

	function smoothlyManipulate(item, container, options) {
		if (typeof item == "string")
			item = $(item);
		if (typeof container == "string")
			container = $(container);

		//debug("Smoothly manipulating " + item.id + " with container " + container.id);
			
		if (item == null || container == null){
			debug("SmoothlyManipulate passed null argument.");
			return;
		}
		
		var containerHeight = container.clientHeight;
		
		// calculate the scaling
		if (!(options["isRemove"] || options["isHide"])){
			item.style.position = "absolute";
			item.style.left = -10000;
			item.style.opacity = 0; // make it invisible to start
			if (item.tagName == "DIV")
				item.style.display = "block"; // reset the display style
			else if (item.tagName == "SPAN")
				item.style.display = "inline";
			else
				item.style.display = "block"; // reset the display style
				
			if (container.style.display == "none" || container.className == "hidden")
				if (container.tagName == "DIV")
					container.style.display = "block"; // reset the display style
				else if (container.tagName == "SPAN")
					container.style.display = "inline";
				else
					container.style.display = "block"; // reset the display style	
					
			item.style.width = container.clientWidth; // to account for wrapping
			
			if (item.parentNode != container) // if this is making something appear
				container.appendChild(item);	
		}
		// get the height; if it's remove, make it negative
		var h = item.clientHeight;
		if (!h)
			h = 1;
			
		if (options["isRemove"] || options["isHide"])
			h *= -1; 

		if (!(options["isRemove"] || options["isHide"])){
			item.style.left = "";
			item.style.position = "";
		}
		//the scaling is calculated to increase the text box by just the percentage needed for the element being added
		var scaling;
		if (!options["recheckHeight"])
			scaling = calculateScaling(h, containerHeight);
		else {
			scaling = h;
			options["beforeStart"] = function(effect) { effect.options.scaleTo = calculateScaling(effect.options.scaleTo, effect.element.clientHeight); debug("Scaling to: " + effect.options.scaleTo);  };
		}
		
		if (!(options["isRemove"] || options["isHide"]))
			item.style.width = ""; // reset the width

		debug("Scaling " + container.id + " by " + scaling + " based on height " + h + " with client height " + containerHeight);
		
		// now make it appear
		scale(container, scaling, options);
		if ((options["isRemove"] || options["isHide"]) && $(item.id)) {
			var statement = "new Effect.Fade(item, { ";
			if (options["isRemove"])
				statement += "afterFinish: function() { removeNode(\"" + item.id + "\", " + (options["queue"] ? ("\"" + options["queue"]["scope"] + "\"") : "\"\"") + "); }, ";
			statement += "queue: { position: \"end\", scope: item.id + \"Fading\" } })";
			debug("Fade statement: " + statement);
			eval(statement);
		}
		else
			appear(item, options);
	}
	
	function appear(item, options){
	
		if ($(item)) {
			if (options && !options["noQueuing"])
				queue = { queue: { position: "end", scope: item.id + "Fading" } };
			else
				queue = null;
				
			new Effect.Appear(item, queue);
		}
	}
	
	function removeNode(id, scope){
		var node = $(id);
		
		if (node){
			var effectCheck = function(effect) {
				if (effect.element == node)
					effect.cancel();
			}			

			// make sure there are no effects currently running on the node before removing it
			if (scope) {
				var queue = Effect.Queues.get(node.id);
				if (queue) queue.each(effectCheck);
			}

			if (node.id) {
				var queue = Effect.Queues.get(node.id);
				if (queue) queue.each(effectCheck);
			}
			
			if (node.parentNode.id){
				var queue = Effect.Queues.get(node.parentNode.id);
				if (queue) queue.each(effectCheck);
			}
			
			Effect.Queue.each(effectCheck);


			// now that we've canceled all actions on the node, remove it
			node.parentNode.removeChild(node);
		}
	
	}
	
	function calculateScaling(height, containerHeight){
		var adjust = 0, temp = 1 + height / containerHeight;
		if (temp > 1.5)
			adjust = -1;
			
		return Math.round((1 + height / containerHeight) * 100) - adjust;	
	}
	
	function scale(item, percentage, options){
		
		if (!options)
			options = {};
		
		var scaleOptions = {};
		
		scaleOptions["scaleX"] = false;
		scaleOptions["scaleContent"] = false;
		scaleOptions["queue"] = { position: "end", scope: options["scope"] || (item.id + "Scaling") };	
		if (options["originalHeight"]) scaleOptions["originalHeight"] = options["originalHeight"]; 
		if (options["beforeStart"]) scaleOptions["beforeStart"] = options["beforeStart"];
		if (options["afterFinish"]) scaleOptions["afterFinish"] = options["afterFinish"];

		debug("Scaling! " + item.clientHeight);	
		if ($(item) && percentage)
			new Effect.Scale(item, percentage, scaleOptions);
	}
	function makeOverlay() {
	//create the confirmation box
	var body = $("body");

	var overlayTop = null, overlayLeft = null;

	var confBox = $("overlay_box");
		
	if (overlayTop == null) // if we don't have an explicit location for the box
		overlayTop = 0.35 * body.clientHeight;
	if (overlayLeft == null)
		overlayLeft = 0.3 * body.clientWidth;
		
	confBox.style.top = overlayTop;
	confBox.style.left = overlayLeft;
	
	var overlay = document.getElementById("overlay");
	
	// if this is IE,we have to do extraordinary things to get the right sizing, unfortunately
	if (isIE){
		overlay.style.width = body.clientWidth;
		overlay.style.height = body.clientHeight;
		body.style.overflow = "hidden";
		
		debug("Overlay height/width: " + body.clientWidth + ", " + body.clientHeight);
	}	

	overlay.style.left = 0;
	
	var overlay_box = document.getElementById("overlay_box");
	overlay_box.className = "saved_confirm";

}

function removeOverlay(){
	$('overlay_header').parentNode.removeChild($('overlay_header'));
	
}
	
	

	var textField = document.getElementById("text");
	var pages, headers;
	
	var neighbors = new Array();
	var targets = new Array();
	var otherPages = new Array();

	var currentPosition = 0;
	var originalOffset = 0;
	var padding = "2%";
	moving = false;
	
	function initializeScroll() {
		content = $("fixedContent");
		if (content){
			window.onscroll = scrollDown;
			originalOffset = content.offsetTop;
			currentPosition = originalOffset;

			content.style.top = 0;
			$("fixedContentPadding").style.height = originalOffset;

			debug("ClientHeight: " + content.clientHeight);
		}
	}
	
	
	function scrollDown() {
		padder = $("fixedContentPadding");		
		goal = 10;

		if (padder && moving == false){
			moving = true;
			options = {afterFinish: function() { moving = false; } };
			
			if (window.pageYOffset > originalOffset && padder.clientHeight == originalOffset){
				// if we've scrolled beyond the original offset and the padder is expanded
				// expand the padder to the full size
				factor = 100 * goal / originalOffset;
				scale(padder, factor, options);
				debug("Shrinking padder by " + factor + ", new height = " + padder.clientHeight);	
			}
			else if (window.pageYOffset <= originalOffset && padder.clientHeight == goal){
				// otherwise, if we've gone back up to the top
				// shrink the header back to where it was
				factor = 100 * originalOffset / goal;
				scale(padder, factor, options);
				debug("Expanding padder by " + factor + ", new height = " + padder.clientHeight);	
			}
			else
				moving = false; // we're not actually going to move
		}
	}

	function changeDisplay(array, display){
		for (var i = 0; i < array.length; i++){
			try {
				// all of our pages should be built with the page number as the first child, which can be clicked on to flip the display
				// to change the display, we just click on it programmatically
				array[i].firstChild.onclick(display);
			}
			catch (err) {}
		}
	}

	
	function process_json() {
		/*	this processes JSON data that's been previously loaded into the application via a script tag
			it assumes that there have been a set of variables defined:
				docTitle = title of the document
				docID = the document's ID as defined by the GPO
				pageRange = the range of pages this covers
				pages[] = an array of page objects consisting of
					pageText
					pageNumber
					nameMatch = whether the name contains the target
		*/
		debug("Processing JSON");
		
		if (speeches == null) {
			var error = document.createElement("span");
			error.className = "error";
			error.innerHTML = "There has been an error getting the text.  Please try again later.";
			debug("process_json: no pages!");
			return;
		}
		
		// take care of the title screen
		var title = document.getElementById("documentTitle");
		//var subtitle = document.getElementById("documentInfo");
		
		// remove the loading image
		var loadImage = document.getElementById("loadImage");
		loadImage.parentNode.removeChild(loadImage);

		if (!docTitle || docTitle.length == 0)
			docTitle == "Untitled Document";
		else 
			docTitle = unescape(docTitle);
		
		if (docTitle.length > 0){
			title.innerHTML = docTitle.replace(/\ /, "&nbsp;");
			document.title = docTitle + " - ReadCongress.org";
		}
		
		//if (docSource.length > 0)
		//	title.innerHTML = "From the " + unescape(docSource);
		
		if (docDate.length > 0)
			title.innerHTML += " (" + unescape(docDate) + ")";
			
		//if (pageRange.length > 0)
		//	subtitle.innerHTML += " (pages " + unescape(pageRange) + ")";
		
		// set up the document ID -- in later iterations, this should possibly be stored server-side
		var docIDField = document.getElementById("review[document_id]");
		if (docIDField)
			docIDField.value = docID;
		
		var appearanceIDField = $("review[appearance_id]");
		if (appearanceIDField)
			appearanceIDField.value = appearances[0].id;
		
		var textParent = document.getElementById("text");
		
		var textHeader = document.createElement("div");
		textHeader.className = "textHeader";
		textHeader.innerHTML = "You are reviewing the " + docTitle.replace(":", " from").replace("(<strong>Senate</strong>)", "");
		if (lastRecordPage > 1)
			textHeader.innerHTML += " (page " + currentRecordPage + " of " + lastRecordPage + ")";
		
		if (subTitle)
			textHeader.innerHTML += subTitle;
		
		
		textParent.appendChild(textHeader);
		
		if (currentRecordPage > 1){
			// if we need a prev page link
			debug("Creating previous link.");
			
			prevLink = document.location + "";
			if (prevLink.match(/page=[0-9]*/)) // if we're specified a page number
				prevLink = prevLink.replace(/page=[0-9]*/, "page=" + (currentRecordPage - 1));
			else {
				if (prevLink.match(/\?/)) // if we have parameters already
					prevLink += "&"
				else
					prevLink += "?"
				
				prevLink += "page=" + (currentRecordPage - 1);
			}
			
			debug("Previous link: " + prevLink);

			holderDiv = document.createElement("div");
			holderDiv.className = "speechBlock p";
			floaterDiv = document.createElement("div");
			floaterDiv.className = "floatLeft";

			floaterDiv.innerHTML = "&nbsp;";
			holderDiv.appendChild(floaterDiv);
			
			prevDiv = document.createElement("div");
			prevDiv.className = "loadingDiv";
			prevA = document.createElement("a");
			prevA.href = prevLink;
			
			
			prevImageDiv = document.createElement("div");
			prevImageDiv.innerHTML = '<img id="loadImage" src="/images/24-em-up.png" />';
			
			prevTextDiv = document.createElement("div");
			prevTextDiv.className = "center";
			prevTextDiv.innerHTML = "Back to page " + (currentRecordPage - 1);
			
			prevA.appendChild(prevImageDiv);
			prevA.appendChild(prevTextDiv);
			prevDiv.appendChild(prevA);
			holderDiv.appendChild(prevDiv);

			floaterDiv = document.createElement("div");
			floaterDiv.className = "floatRight";
			floaterDiv.innerHTML = "&nbsp;";
			holderDiv.appendChild(floaterDiv);
			
			textParent.appendChild(holderDiv);
			debug("Done creating link.");
		}

		displaySpeechesInChunks(speeches, textParent.id);
		
		if (currentRecordPage < lastRecordPage){
			debug("Creating next link.");
			
			// if we need a next page link
			nextLink = document.location + "";
			if (nextLink.match(/page=[0-9]*/)) // if we're specified a page number
				nextLink = nextLink.replace(/page=[0-9]*/, "page=" + (currentRecordPage + 1));
			else {
				if (nextLink.match(/\?/)) // if we have parameters already
					nextLink += "&"
				else
					nextLink += "?"
				
				nextLink += "page=" + (currentRecordPage + 1);
			}
			
			debug("Next link: " + nextLink);

			holderDiv = document.createElement("div");
			holderDiv.className = "speechBlock p";
			floaterDiv = document.createElement("div");
			floaterDiv.className = "floatLeft";
			floaterDiv.innerHTML = "&nbsp;";
			holderDiv.appendChild(floaterDiv);
			
			nextDiv = document.createElement("div");
			nextDiv.className = "loadingDiv";
			nextA = document.createElement("a");
			nextA.href = nextLink;
			
			
			nextImageDiv = document.createElement("div");
			nextImageDiv.innerHTML = '<img id="loadImage" src="/images/24-em-down.png" />';
			
			nextTextDiv = document.createElement("div");
			nextTextDiv.className = "center";
			nextTextDiv.innerHTML = "Onward to page " + (currentRecordPage + 1);
			
			nextA.appendChild(nextTextDiv);
			nextA.appendChild(nextImageDiv);
			nextDiv.appendChild(nextA);
			holderDiv.appendChild(nextDiv);

			floaterDiv = document.createElement("div");
			floaterDiv.className = "floatRight";
			floaterDiv.innerHTML = "&nbsp;";
			holderDiv.appendChild(floaterDiv);

			
			textParent.appendChild(holderDiv);
			debug("Done creating link.");
		}

		clearFloatsDiv = document.createElement("div");
		clearFloatsDiv.className = "no_floats";
		clearFloatsDiv.innerHTML = "&nbsp;";
		textParent.appendChild(clearFloatsDiv);

		if (hasQuotes)
			registerQuotes();
			
		fixTextHeight(); // reset the height to match the containing div perfectly
	}

	function displaySpeechesInChunks(speeches, textParentID, startIndex){
		increment = speeches.length;
		
		if (!startIndex)
			startIndex = 0;

		// if we're still in the speech range, display the next chunk
		// then if we're not at the end, wait half a second and start again
		if (startIndex < speeches.length){
			result = displaySpeeches(speeches, textParentID, startIndex, increment);
			if (result < speeches.length){
				setTimeout("displaySpeechesInChunks(speeches, '" + textParentID + "', " + (result + 1) + ", " + increment + ")", 500);
				return;
			}
		}
		
		// if we're here, we're finished, so register quotes and fix the height
		if (hasQuotes)
			registerQuotes();
			
		fixTextHeight(); // reset the height to match the containing div perfectly		
	}
	
	function displaySpeeches(speeches, textParent, startIndex, numberOfSpeeches){
		var i;
		textParent = $(textParent);
		
		// now handle the speech
		for (i = startIndex; i < speeches.length && i < startIndex + numberOfSpeeches; i++){
			var speech = speeches[i];
			speakerBoxOuter = null;
			var name = "";
			
			debug("Doing speech " + i + " of " + speeches.length);
			
			// create the overall div that will hold all elements of this speech
			var speechBlock = document.createElement("div");
			speechBlock.id = "speech" + speech.id;
			speechBlock.className = "speechBlock";
			
			// create the node that will contain the text
			speechText = document.createElement("div");
			speechText.id = speechBlock.id + "Text";
			// if this isn't a speech but is, rather, descriptive text, give the speech block the appropriate class
			if (!speech.legislatorID || speech.legislatorID < 0) // note these conditions are replicated below
				speechText.className = "speechText speechByRecord";
			else {
				// otherwise, this is speech by a speaker				
				speechText.className = "speechText speechByPerson";
					
				// create our speaker info area
				speakerBoxOuter = document.createElement("div");
				speakerBoxOuter.id = speechBlock.id + "Speaker";
				speakerBoxOuter.className = "speakerBoxOuter";

				speakerBoxInner = document.createElement("div");
				speakerBoxInner.id = speechBlock.id + "Speaker";
				speakerBoxInner.className = "speakerBoxInner";

				speakerBoxOuter.appendChild(speakerBoxInner);
				
				// now create the elements of that area
				// the speaker's name
				speakerName = document.createElement("div");
				speakerName.innerHTML = unescape((legislators[speech.legislatorID].firstName ? legislators[speech.legislatorID].firstName+ " " : "") + legislators[speech.legislatorID].lastName)
				speakerName.className = "speakerName";
				
				// get the name for later
				name = speakerName.innerHTML;
				
				// the speaker's state and party
				speakerStateParty = document.createElement("div");
				speakerStateParty.className = "speakerStateParty";
			
				// for certain speakers like the presiding officer, etc., there may be no state or party
				if (legislators[speech.legislatorID].party)
					speakerStateParty.innerHTML = legislators[speech.legislatorID].party;
				if (legislators[speech.legislatorID].state){
					if (speakerStateParty.innerHTML)
						speakerStateParty.innerHTML += " - ";
					speakerStateParty.innerHTML += legislators[speech.legislatorID].state;
				}
				speakerStateParty.innerHTML = unescape(speakerStateParty.innerHTML);

				// now put them together
				speakerBoxInner.appendChild(speakerName);
				if (speakerStateParty.innerHTML)
					speakerBoxInner.appendChild(speakerStateParty);
					
				
				speechBlock.appendChild(speakerBoxOuter);
			}
			
			var newText = unescape(speech.text);
			if (i == 0) // don't recreate the document title if it appears
				newText = newText.replace(new RegExp(docTitle + "\n", "i"), "");

			newText = newText.replace(/\n/g, "<br>\n").replace(/^\s\s/, "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;").replace(/\n\s\s/g, "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");

			speechText.innerHTML = newText;
			speechText.legislatorName = name; // this is stored and used for quotes
				
			//if (!speechText.innerHTML.match(/<br \/>/) && legislators[speech.legislatorID].party)
			//	speechText.innerHTML += "<br />&nbsp;";

			speechBlock.appendChild(speechText);
			
			textParent.appendChild(speechBlock);
			
			if (!speech.legislatorID || speech.legislatorID < 0){
				// if we're displaying just record text, make sure to add a floated div so containing elements size properly
				floaterDiv = document.createElement("div");
				floaterDiv.className = "floatLeft";
				floaterDiv.innerHTML = "&nbsp;";
				textParent.appendChild(floaterDiv);
			}
			
			// if this is a one line speech by someone who's a legislator, we need to add a space so that the lines match up
			// render these and see if this is true
			if (speakerBoxOuter && speechText.clientHeight < speakerBoxOuter.clientHeight)
				speechText.innerHTML += "<br />&nbsp;";
			
			clearDiv = document.createElement("div");
			clearDiv.className = "no_floats";
			textParent.appendChild(clearDiv);
			
			debug("Speech " + i  + " done.");			 
		}
		
		fixTextHeight(); // reset the height to match the containing div perfectly	
		
		return i;
	}
	
	function registerQuotes(){
		for (var i = 0; i < quotes.length; i++){
			pageOfQuote = $(quotes[i]["page"] + "Text");
			speechOfQuote = $("speech" + quotes[i]["speech"] + "Text");
			element = null;
			
			if (pageOfQuote)
				element = pageOfQuote;
			else if (speechOfQuote)
				element = speechOfQuote;
				
			if (element){
				element.innerHTML = element.innerHTML.replace(unescape(quotes[i]["quote"]), "<span id=\"quote" + i + "\" class=\"highlightedQuote\">" + unescape(quotes[i]["quote"]) + "</span>");
				
				var tooltip = document.createElement("div");
				tooltip.id = "quote" + i + "_text";
				tooltip.innerHTML = "Quote found by <a href='/users/profile/" + quotes[i]["userID"] + "' target='_blank'>" + quotes[i]["user"] + "</a>";
				tooltip.className = "hidden";
				$("tooltip_texts").appendChild(tooltip);
			}

			

		}
		process_tooltips();
	}	
		
	/*************************************
	 *		page filter buttons			 *
	 *************************************/
	
	function setupViewControls() {
		/*
		document.getElementById("checkboxNeighbors").onclick = function() {
			
			// if this is selected
			if (this.checked == true){
			
				// we know at least one other option is selected, so hide these
				changeDisplay(neighbors, "none");
				return;	
			}
			
			// if this is deselected
			changeDisplay(neighbors, "block");			
		
		}
		*/
		
		document.getElementById("checkboxOtherPages").onclick = function() {
			
			// if this is selected
			if (this.checked == true){
				
				// we know at least one other option is selected, so hide these
				changeDisplay(otherPages, "none");
				return;	
			}
			
			// if this is deselected
			changeDisplay(otherPages, "block");
		}
	}
		
		
	/**************************
	 *			tooltips	  *
	 **************************/

	var tooltip_elements = new Array();
	var tooltip_timers = new Array();
	 
	function makeTooltip(id) {
		debug("activating tooltip, called by id = " + id);
		
		if (id == null)
			return;
		else if (id.match("_text")) // if the tooltip is generating this, switch to the parent
			id = id.replace("_text", "");
	
		if (tooltip_elements[id] != null){
			// catch if the tooltip is already up; if so, we don't need to do anything but clear the timeout until you leave the element
			clearTimeout(tooltip_timers[id]);
			debug("returning!");
			return;
		}
		else {
			var tooltip = document.createElement("div");
			tooltip_elements[id] = tooltip;
		}

		var parent = document.getElementById(id);
		var tooltip;
		
		// make sure tooltip text is defined, if not, we don't make the tooltip
		var text_node = document.getElementById(id + "_text"); // this should exist since that's how tooltips are defined, but let's be sure
		if (!text_node)
			return;
			
		var text = text_node.innerHTML;

		tooltip.className = "tooltip";
		tooltip.innerHTML = text;
		tooltip.id = parent.id + "_tooltip";
		
		tooltip.style.left = parent.offsetLeft + 5;
		tooltip.style.top = parent.offsetTop + parent.offsetHeight + 1;
		
		parent.appendChild(tooltip);
		
		
		// if you go over to the tooltip, stop the timeout to clear the tooltip until you leave the tooltip
		tooltip.onmouseover = function() { 
			// stop the timeout that's triggered when you leave the parent
			clearTimeout(tooltip_timers[id]); 
		}

		// reset the onmouseout and onmouseover events to cover the tooltip too
		// to force Firefox to use the appropriate current values of our variables, we have to use eval, unfortunately 
		var mouseover = "parent.onmouseover = function() { makeTooltip('" + parent.id + "'); }";
		var mouseout = "parent.onmouseout = function() { tooltip_timers[id] = setTimeout(\"removeTooltip('" + parent.id + "');\", 250); }";
		eval(mouseover);
		eval(mouseout); 
	}
	
	function removeTooltip(id) {
	
		// first, find out if this is triggered by the tooltip or the parent
		if (!id)
			return;
		else if (!id.match("_tooltip")) // if this is the parent
			id += "_tooltip";
		
		tooltip = document.getElementById(id);

		if (!tooltip){
			// if there is no such element
			clearTimeout(tooltip_timers[id]);
			tooltip_timers[id] = null;
			tooltip_elements[id.replace("_tooltip", "")] == null;
		}
		else if (tooltip.parentNode) {
			var parent = tooltip.parentNode;
			parent.removeChild(tooltip);
			tooltip_elements[parent.id] = null;
		}
		else {
			// if the tooltip has no parents, clear it just to be sure
			clearTimeout(tooltip_timers[id]);
			tooltip_timers[id] = null;
			tooltip_elements[id.replace("_tooltip", "")] == null;				
		}
	}
	
	/* process_tooltip is at the top since it's called before the page is fully loaded */		
	
	/**************************
	 *			categories	  *
	 **************************/

	function chooseCategory(id){
		var categoryNode = document.getElementById("cat" + id + "_id");
		
		// make sure our category exists
		if (!categoryNode)
			return;
		
		if (categoryNode.className == "selectedCategory"){
			// this category is already selected
			categoryNode.className = "linkLike";
			
			// get the data node if it exists
			dataNodes = document.getElementsByName("taggings[" + id + "]");
			for (var i = 0; i < dataNodes.length; i++){
				// there should only be one, but no harm in being sure
				if (dataNodes[i].parentNode)
					dataNodes[i].parentNode.removeChild(dataNodes[i]);
			}
		}
		else {
			// if it's not selected, make it selected
			categoryNode.className = "selectedCategory";
			
			var dataNode = document.createElement("input");
			dataNode.type = "hidden";
			dataNode.name = "taggings[" + id + "]";
			dataNode.value = "true";
			
			document.getElementById("hiddenInputs").appendChild(dataNode);
		}
	}


	function getSelectedText() {	
		if (window.getSelection) { // Firefox and Opera
			return getRangeObject(window.getSelection()).toString();
		}
		else if (document.selection) { // IE behavior last
			return document.selection.createRange();
		}
	}
	
	
	function getRangeObject(selectionObject) {
		// thanks, http://www.quirksmode.org/dom/range_intro.html!
		if (selectionObject.getRangeAt)
			return selectionObject.getRangeAt(0);
		else { // Safari!
			var range = document.createRange();
			range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
			range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);
			return range;
		}
	}

/*
	function determineSpeechProperties(selection){
		/* this function looks at the selection made for a speech and does some analysis on it
		   we want to know: 
		   		- is this a speech within the same element (e.g. a snippet of one person's speech)
		   		- if not, is this part of the same speech, that is, from the speech node and the label node with the speaker's name
		   		- if not, we're clearly in an exchange, so we want to just grab the whole thing and go with it
		   	this function returns a hash with the following properties:
		   		- isSpeech:	if it's a clip of the same speech (cases 1 or 2 above); if false, it's an exchange
		   		- textToStore: the text we want to put into the database; for speeches, it will be the exact text (stripped of any portion from the speaker's name), for exchanges, it will be the direct text plus the name of the speaker where the quote starts
		
		
		// we use both the selection and the range, because the selection preserves carriage returns and the range has location info
		range = selection.getRangeAt(0); 
		startNode = range.startContainer;
		if (startNode.nodeType == 3) // if we have the text node, get the parent, which should be the div
			startNode = startNode.parentNode;
		endNode = range.endContainer;
		if (endNode.nodeType == 3)
			endNode = endNode.parentNode;
			
		results = {}
		
		if (range.startContainer == range.endContainer) {
			// we're in the same block, so the speech is okay
			results["isSpeech"] = true;
			results["textToStore"] = selection.toString();
		}
		else {
			// there are a few situations -- we could be inside a highlighted quote in the speech and hence inside the same speech
			if (range.startContainer.parentNode == range.endContainer || range.startContainer == range.endContainer.parentNode){
				results["isSpeech"] = true;
				results["textToStore"] = selection.toString(); // this strips out any HTML, and works fine since we're inside the same speech

			}
			else {
				// or we could be in a situation where the user has copied the text as well as the legislator's name
				// in which case, as long as we stay within the same speech, we can cut out that bit
				if 
		
		}
	}

*/
	function activateQuoteButton(event) {
		// thanks, http://www.quirksmode.org/dom/range_intro.html
		var selection = getSelectedText();

		if (!selection)
			return;

		if (!event) var event = window.event;

		var speechNum;		
		// for IE, we need to do an extra step to get the text
		if (selection.text){
			debug("Got source from " + event.srcElement.id);
			speechNum = event.srcElement.id.replace(/Text/, "").replace(/speech/, "");
			selection = selection.text;
		}
		else {
			if (!event.target)
				return;

			debug("Got source from " + event.target.id);			
			var id = event.target.id;
			if (!id) id = "no_id_error";
			speechNum = id.replace(/Text/, "").replace(/speech/, "");
		}

		if (selection.length > 6) {
			debug("You've selected: " + selection); 
			$("quoteButton").disabled = false;
			eval('$("quoteButton").onclick = function() { copyQuote(' + speechNum + ', "' + escape(selection) + '"); return false; };');
			$("quoteButton").value = "Click here to copy this quote.";
		}
		else
			debug("Nothing selected!");
	}
	
	function clearQuoteSelection(event){
		// clear the selection
		if (window.getSelection) { 
			// get rid of any existing selection
			try { window.getSelection().collapseToStart(); }
			catch (err) {} 
		}
		else if (document.selection) {
			$("text").blur();
			//document.selection.empty();
		}
		
		// disable quote button
		$("quoteButton").disabled = true;
		$("quoteButton").onclick = function() { return false; };
		$("quoteButton").value = "Select some text to make a quote."; 
	}
	
	function initializeQuotes(){
		// when the mouse goes down, clear any selected text to prevent misfiring
		$("text").onmousedown = function(event) { clearQuoteSelection(event) };

		// when the mouse goes up, if there's text selected, get it
		$("text").onmouseup = function(event) { activateQuoteButton(event) };
	}


	function toggleValue(element){
		var name = element.id;
		var value;
		if (element.type == "radio"){
			name = element.name;
			value = element.value;
		}
		else if (element.checked)
			value = 1;
		else
			value = 0;
			
		var hiddenInput = $("review[" + name + "]");
		if (!hiddenInput){
			hiddenInput = document.createElement("input");
			hiddenInput.type = "hidden";
			hiddenInput.id = "review[" + name + "]";
			hiddenInput.name = hiddenInput.id;
			$("hiddenInputs").appendChild(hiddenInput);
		}
		
		hiddenInput.value = value;
	}
	
	/**********************
	 *		quotes		  *
	 **********************/

	var quoteCount = 0;

	function copyQuote(speechNumber, selection) {
		if (selection && selection.length > 6){
			// we have a quote
			// add it to the data fields
			var dataNode = document.createElement("input");
			dataNode.type = "hidden";
			dataNode.name = "quotes[" + quoteCount + "][text]";
			dataNode.id = "quotes[" + quoteCount + "][text]";	
			dataNode.value = selection;
			$("hiddenInputs").appendChild(dataNode);	

			dataNode = document.createElement("input");
			dataNode.type = "hidden";
			dataNode.name = "quotes[" + quoteCount + "][id]";
			dataNode.id = "quotes[" + quoteCount + "][id]";	
			dataNode.value = speechNumber;
			$("hiddenInputs").appendChild(dataNode);	
	
			// and display it
			var textNode = document.createElement("div");
			textNode.innerHTML = "<span class=\"taggedCategory\">" + unescape(selection).truncate(40) + "</span> [<span class=\"removeTagLink\" onclick=\"removeQuote(" + quoteCount + ")\"> - </span>]";
			textNode.id = "quoteText[" + quoteCount + "]";
			smoothlyInsert(textNode, $("quotesChosen"));
	
			quoteCount++;
		}

		// clear the quote selection, whether or not it's valid
		clearQuoteSelection();

	}
	
	function removeQuote(quoteNumber){
		// removes the text of a tag
		var quoteData = $("quotes[text][" + quoteNumber + "]")
		if (quoteData)
			quoteData.parentNode.removeChild(quoteData);		
		
		var quoteText = $("quoteText[" + quoteNumber + "]");
		if (quoteText) {
			smoothlyRemove(quoteText, quoteText.parentNode);
		}
	}	

	function submittingTag() {
		// this function clears the error area and puts up a little loading thing
		// we should have this cancel if there's no tag data submitted
		$("tagButton").disabled = true;
		$("tagButton").value = "Sending...";
	}

	function addTag(idAndTitle){
		// the script to process the new tag will create a hidden input called "newTagID"
		// we'll use that to get the id of the new tag

		//clear the input box and reenable the button
		$("tagButton").disabled = false;
		$("tagButton").value = "Add tag";
		$("new_tag").value = "Enter another tag here.";
		$("new_tag").className = "defaultText";	
		
		debug("loading using data: " + idAndTitle);
		
		// clear the error field if there are any errors
		var errorZone = $("errorZone"); 
		errorZone.innerHTML = "";
		
		debug("Past errorZone");
		
		// originally, we get the tag info back as an "[type, id, title]" array  -- so let's eval it
		var data;
		if (typeof idAndTitle == "string")
			eval("data = " + idAndTitle + ";");
		else // if this is recursive
			data = idAndTitle;

		debug("Past data eval, length: " + data.length);
		
		// see if we have one result or multiples
		if (data == ""){
			debug("addTag: Empty tag data!");
			return;
		}
		
	 	if (data[0][0] == "error"){
			// we have an error -- not that it's blank, but they entered only white space and stuff
			// should this just be ignored?
			var errorHolder = document.createElement("div");
			for (var i = 1; i < data.length; i++){
				var error = document.createElement("div");
				error.innerHTML = unescape(data[i]);
				errorHolder.appendChild(error);
			}
			
			smoothlyInsert(errorHolder, errorZone);
			new Effect.Highlight("errorZone");
			return;
		}
		
		var output = document.createElement("div");
		
		for (var i = 0; i < data.length; i++){
			if (data[i][0] == "tag"){
				// if we received valid data, add the input
				debug("processing tag");
				
				// make sure the tag wasn't previously submitted
				if (!$(data[i][2])){
					var dataNode = document.createElement("input");
					dataNode.type = "hidden";
					dataNode.name = "taggings[" + data[i][1] + "]";
					dataNode.id = "taggings[" + data[i][1] + "]";			
					dataNode.value = "true";
					$("hiddenInputs").appendChild(dataNode);
					
					// set up the category item
					var textNode = document.createElement("div");
					textNode.className = "insertedItem";
					textNode.innerHTML = "<span class=\"taggedCategory\" id=\"" + data[i][2] + "\">" + unescape(data[i][2]) + "</span> [<span class=\"removeTagLink\" onclick=\"removeTag(" + data[i][1] + ")\"> - </span>]";
					textNode.id = "text[" + data[i][1] + "]";
		
					// insert it 
					output.appendChild(textNode);
				}
			}
		}

		smoothlyInsert(output, $("tagsChosen"));
	}

	function rescueOrphanedTags(){
		tagField = $("new_tag");
		
		// if there are any tag values that the user forgot to submit, bring them along
		if (tagField && tagField.value && tagField.value != ""){
			var unprocessedTags = document.createElement("input");
			unprocessedTags.type = "hidden";
			unprocessedTags.name = "unprocessedTags";
			unprocessedTags.id = "unprocessedTags";
			unprocessedTags.value = tagField.value;
			$("hiddenInputs").appendChild(unprocessedTags);			
		}
	}
	
	function removeTag(tagNumber){
		// removes the text of a tag
		var tagData = $("taggings[" + tagNumber + "]")
		if (tagData)
			tagData.parentNode.removeChild(tagData);		
		
		var tagText = $("text[" + tagNumber + "]");
		if (tagText) {
			smoothlyRemove(tagText, $("tagsChosen"));
		}
	}	
	
	function checkTagField() { 
		// to be defined 
	}
	
	function focusOnTagField(element) {
		if (element.value == "Enter a new tag here." || element.value == "Enter another tag here."){
			element.value = "";
			element.className = "";
		}
	}
	
	function blurTagField(element) {
		if (element.value == ""){
			element.value = "Enter a new tag here.";
			element.className = "defaultText";
		}
	}

	
	function flipSenateBusiness() {
		// toggle whether the review elements are shown
		Element.toggle($('reviewcategories')); 
		Element.toggle($('tagFields'));
		Element.toggle($('significant_content')); 
		Element.toggle($('goodstuff'));
	
		// flip the senate business flag
		if ($("is_senate_business").value == "false")
			$("is_senate_business").value = "true";
		else
			$("is_senate_business").value = "false";
	}
	
	function enableForms(){
		$("reviewButton").disabled = false;		
		$("review[new_tags]").disabled = false;
		$("post").disabled = false;
	}
		
	/**********************
	 *		reading		  *
	 **********************/
	 
	function initializeReader(){
		if (hasPosts == false && hasQuotes == false && hasTags == false){
			if ($("reviewLoading"))
				$("reviewLoading").innerHTML = "No one's reviewed this record yet -- <a href='/record/review/" + docID + "'>be the first</a>!";
			else if ($("tagCloud"))
				$("tagCloud").innerHTML = "No one's reviewed this record yet -- <a href='/record/review/" + docID + "'>be the first</a>!";
		}
		else {
			//smoothlyRemove("reviewLoading", "readerBlock");
			//smoothlyInsert("reviewOutput", "readerBlock");
			if (hasTags)
				tagCloud(tags);
			else
				if ($("tagCloud"))
					$("tagCloud").innerHTML = "No one's tagged this record yet -- write a review and <a href='/record/review/" + docID + "'>give it one</a>!";

		}
	}
	
	function tagCloud(tags, site){
		if (!site)
			site = "tagCloud";
		
		site = $(site);
				
		if (!tags || !site)
			return;
			
		site.innerHTML = null;
		
		if (tags.length == 0)
			site.innerHTML = "No one's tagged this record yet -- write a review and <a href='/record/review/" + docID + "'>give it one</a>!";
		else {
			for (var i = 0; i < tags.length; i++){
				var tagNode = document.createElement("span");
				tagNode.innerHTML = tags[i][0];
				tagNode.className = "tag";
				tagNode.style.fontSize = (1 + 0.15 * tags[i][1]) + "em";
				
				site.appendChild(tagNode);
			}
		}
	}
	

		function queueReferralValidation(input) {
		// this function is triggered by user typing into the referral field, and is
		// used to referralenticate
		debug("triggered");
		
		if (formTimer)
			clearTimeout(formTimer);
	
		formTimer = setTimeout("checkReferral();", 500);
		$("referral_status").innerHTML = "Checking...";
	}

	function checkReferral() {
		// this function takes the referral code entered and referralenticates it
 		debug("checking referral...");
 
		var referralInput = $("referral_code").value;

		// make sure it's not null and not white space -- can we check length?
		if (referralInput == null || referralInput == ""/* || referralInput.match(/\w/) != null*/) {
			return false;
		}
		
		debug("submitting referral...");

		// we're good to go, so submit it
		$("referralForm").onsubmit();
	}

	function analyzeValidation(response){
	
		debug("evaluating response...")
		
		eval("var data = " + response);
		
		// try the results, and if it fails, we got malformed data
		try {	
			// we've received back the URL, so display the valid method and then go forward
			if (data[0] == "incorrect"){
				debug("incorrect response");
				$("referral_status").innerHTML = "Uh oh!  That code's not valid.";
 				$("referral_status").className = "incorrect_response";
 			}
			else {
				debug("correct response");
				$("referral_status").innerHTML = "Great -- let's begin!"	
 				$("referral_status").className = "correct_response";
 				setTimeout("window.location = '" + data[1] + "';", 2000);	
 			}
		}
		catch (err) {
			$("referral_status").innerHTML = "Error!"			
			debug("error!");
		}		
		
	}
	

	function ieWarning(){
		ieDiv = $("ieWarning");
		if (isIE && ieDiv){
			ieDiv.className = ieDiv.className.replace(/hidden/, "");
		}	
	}

	/*****************
	 *  tag clouds   *
     *****************/
 
     function switchTagCloud(to){
     	if (to == "all") {
     		debug("Switching to all.");
     		for (var i = 0; i < $("legislatorCloud").childNodes.length; i++) { 
     			if ($("legislatorCloud").childNodes[i].tagName == "DIV")
     				$("legislatorCloud").childNodes[i].className = $("legislatorCloud").childNodes[i].className.replace(/hidden/, "tag"); 
     		}
     		$("tagHeader").innerHTML = 'Our Congress, c. 2008 <span class="switchListLink">(<a href="#" onclick="switchTagCloud(\'gop\');">see just GOP incumbents</a>)</span>';
		}
		else { 
     		debug("Switching to GOP.");
     		for (var i = 0; i < $("legislatorCloud").childNodes.length; i++) { 
     			if ($("legislatorCloud").childNodes[i].tagName == "DIV")
     				$("legislatorCloud").childNodes[i].className = $("legislatorCloud").childNodes[i].className.replace(/tag/, "hidden"); 
     		}
     		$("tagHeader").innerHTML = 'Republican Senators running this year <span class="switchListLink">(<a href="#" onclick="switchTagCloud(\'all\');">see the whole Senate</a>)</span>';
     	}
     }

	/*******************
	 *  intro screen   * I suspect most or all of this could be cut but don't have the time to analyze it now
	 *******************/

	var text; // global placeholder for the setTimeout statements

	/*
		startLogin
		This function is executed when the Ajax call for the login page returns; it puts the resulting text in a global storage variable, then fades the login page in.
	*/

	function startLogin(responseText) {
		text = responseText ;
		setTimeout("fadeInBlock(0);", 1300);
		setTimeout("focusOnLogin(0);", 1300);
	}

	/*
		startBlock
		I'm not entirely sure what this function does.
	*/

	function startBlock(responseText) {
		text = responseText ;
		setTimeout("fadeInBlock(0);", 1300);
	}
	
	/*
		fadeInBlock
		This function fades in the main block of text for the home page after a specified interal; for instance, when logging in, it makes the login page appear after the home page has faded out.
	*/

	function fadeInBlock(interval){
	
		if ($("homeBody").style.display == "none" && interval < 4000)
			setTimeout("fadeInBlock(" + (interval + 500) + ");", 500);			
					
		$("homeBody").innerHTML = text;
		text.evalScripts();
		
		new Effect.Appear('homeBody');
	}
	
	/*
		focusOnLogin
		This function puts the focus on the username field on the login page.  Currently it's executed using setTimeout, but should actually be rewritten to use the Effect.Fade's afterFinish option.
	*/ 

	function focusOnLogin(interval){
	
		if ($("homeBody").style.display == "none" && interval < 4000)
			setTimeout("focusOnLogin(" + (interval + 500) + ");", 500);			
		
		setTimeout("$('login_username').focus();", 1300);
	}	
	
	/*******************
	 *  record search  *
	 *******************/

	var lastSearch = ""; // global variable for the last submitted search term

	/*
		queueRecordSearch
		When the user starts typing into the record search field, the application starts a timer; if the user leaves the field alone for the length of the timer, the search is initiated.  (If the user changes the field, it resets the timer.)  It takes the input field that's being typed onto to make sure the value has changed since the last search.
		This could be rewritten to be generic and hence reusable.
	*/		

	function queueRecordSearch(input) {		
		// this function is triggered by user typing into the record search field
		if (formTimer)
			clearTimeout(formTimer);
		
		// make sure there's  are no previous identical searches
		if (lastSearch == input.value)
			return; // make sure this isn't the same search previously submitted (for instance, if both onchange and onkeyup are triggered
	
		formTimer = setTimeout("checkRecordSearchInput('recordSearchForm', '" + input.id + "', true);", 500);
	}
	
	function checkRecordSearchInput(form, input, allowWhiteSpace) {
		// this function takes the referral code entered and referralenticates it 
		var formInput = $(input).value;

		// make sure there's  are no previous identical searches
		if (lastSearch == formInput)
			return; // make sure this isn't the same search previously submitted (for instance, if both onchange and onkeyup are triggered

		if ($("recordSearchOutput")) {
			debug("New search -- removing.");
			if (!isIE)
				scale("recordResults", -100);
			smoothlyRemove("recordSearchOutput", "recordResults", { recheckHeight: true });
		}
		else if ($("recordResults").clientHeight > 5)
			// the results area should never have a larger size when there's no record output
			scale("recordResults", -100);
			
		
		// if the field is cleared, remove the output
		if (formInput.length == 0){
			lastSearch = "";
			return;
		}
			
 		debug("Searching for records based on " + formInput);

		// make sure it's not null and not white space -- can we check length?
		if (!allowWhiteSpace && (formInput == null || formInput == ""/* || formInput.match(/\w/) != null*/)) {
			return false;
		}
		
		// we're good to go, so submit it
		$(form).onsubmit();
		lastSearch = formInput;
	}
	
	function listRecords(response, node, sentQuery){
	
		if (lastSearch != sentQuery) { // if another query has been sent since this one went out
			debug("Not current query!");
			return;
		}
				
		eval("var data = " + response + ";");
	
		var scope = $(node).id + "Scope";
	
		var output = $("recordSearchOutput");
		if (output) {
			smoothlyRemove("recordSearchOutput", "recordResults", { scope: scope, recheckHeight: true }); // resize 
			//temp.parentNode.removeChild(temp); // clear the element
		}
		else {
			output = document.createElement("div");
			output.id = "recordSearchOutput";
			output.className = "ajaxResultList";
		}
		
		var buffer = document.createElement("div");
		buffer.className = "ajaxResultBuffer";
		buffer.innerHTML = "&nbsp;";
		output.appendChild(buffer);
		
		if (!data || data.length == 0) {
			textNode = document.createElement("div");
			textNode.className = "noResults";
			textNode.innerHTML = "No results found!";
			textNode.id = "noResults";
			output.appendChild(textNode);			
		}
		else {
			for (var i = 0; i < data.length; i++){
				var textNode = document.createElement("div");
				textNode.innerHTML = "<a href=\"/record/review/" + data[i]["id"] + "\">" + unescape(data[i]["text"]) + "</a> (" + data[i]["year"] + ")";
				textNode.className = "indentLeft";
				output.appendChild(textNode);
			}
		}
		
		debug("processResults: smoothly inserting with scope " + scope);
		smoothlyInsert(output, node, { scope: scope, recheckHeight: true });
	}
	
	function showMore(data, target, link){
		data = $(data);
		target = $(target);
		
		if (!(data && target))
			return;
			
		data.parentNode.removeChild(data);
		smoothlyInsert(data, target);
		
		if (link)
			smoothlyRemove(link, $(link).parentNode.id);
	}
	
	function initializeHomepage() {
		if (isIE){
			var ieDiv = document.createElement("div");
			ieDiv.className = "error";
			ieDiv.innerHTML = "Warning: There are some known display issues with Internet Explorer, and some pages of the ReadCongress site may be awkwardly laid out.  The site should still function properly. <div>&nbsp;</div> We are actively working on the problem; in the meanwhile, please consider using <a href='www.mozilla.org'>Firefox</a> or Safari.  We apologize for the problem!";
			$("ieWarning").appendChild(ieDiv);
		}
	
		if ($("learnMoreAnchor"))
			$("learnMoreAnchor").onclick = function() { showMore('learnMoreData', 'learnMoreTarget', 'learnMoreLink'); }
	}
	/******************
 *  quote ratings *
 ******************/

	function quoteRatingMouseover(quoteId, rating) {
		rate = $("quote_rating_" + quoteId + "_" + rating);
		if (!rate)
			return;
		
		for (var i = 1; i < rating + 1; i++)
			$("quote_rating_" + quoteId + "_" + i).style.backgroundPosition = (-12 * (rating - 1)) + "px 0";
	
		for (var i = rating + 1; i < 6; i++)
			$("quote_rating_" + quoteId + "_" + i).style.backgroundPosition = "-60px 0";
	
	}
	
	function quoteRatingMouseout(quoteId, rating) {
		rate = $("quote_rating_" + quoteId + "_" + rating);
		if (!rate)
			return;
	
		for (var i = 1; i < rating + 1; i++)
			$("quote_rating_" + quoteId + "_" + i).style.backgroundPosition = "-60px 0";
	
	}
	
	function rateQuote(quoteId, rating){
		rateField = $("rating" + quoteId + "[rating]");
		if (rateField)
			rateField.value = rating;

		quoteRatingMouseover(quoteId, rating);

		// color everything right, and make it so that the current rating is sticky
		for (var i = 1; i < 6; i++)
			eval('$("quote_rating_' + quoteId + '_' + i + '").onmouseout = function() { quoteRatingMouseover(' + quoteId + ', ' + rating + '); };');

		
		rateForm = $("rate_quote_" + quoteId);
		if (rateForm)
			rateForm.onsubmit();
	}
	
	function quoteRatingReturned(responseText){	
		var data = "";
		eval("data = " + responseText);
		if (data == null || data == "")
			return false;
			
		quoteId = data[0];
		rating = data[1];
	}
	
	function initQuoteRating(quoteId, rating){
		ratedValue = parseInt(rating);
		if (!ratedValue)
			ratedValue = null;
			
		for (var i = 1; i < 6; i++){
			button = $("quote_rating_" + quoteId + "_" + i);
			if (button){
				var text = "button.onmouseover = function() { quoteRatingMouseover(" + quoteId + ", " + i + ") };";
	
				if (!ratedValue)
					text += "button.onmouseout = function() { quoteRatingMouseout(" + quoteId + ", " + i + ") };";
				else 
					text += "button.onmouseout = function() { quoteRatingMouseover(" + quoteId + ", " + ratedValue + ") };";
	
				text += "button.onclick = function() { rateQuote(" + quoteId + ", " + i + "); };";
				
				eval(text);
			}
		}
	}

/********************
 * document ratings *
 ********************/
 
 	documentRatingMax = 3;

	function initDocumentRating(rating){
		ratedValue = parseInt(rating);
		if (!ratedValue)
			ratedValue = null;
			
		for (var i = 1; i < (documentRatingMax + 1); i++){
			button = $("document_rating_" + i);
			if (button){
				var text = "button.onmouseover = function() { documentRatingMouseover(" + i + ") };";
	
				if (!ratedValue)
					text += "button.onmouseout = function() { documentRatingMouseout(" + i + ") };";
				else 
					text += "button.onmouseout = function() { documentRatingMouseover(" + ratedValue + ") };";
	
				text += "button.onclick = function() { rateDocument(" + i + "); };";
				
				eval(text);
			}
		}
	}
	
	function documentRatingMouseover(rating) {
		rate = $("document_rating_" + rating);
		
		if (!rate)
			return;
		
		for (var i = 1; i < rating + 1; i++)
			$("document_rating_" + i).style.backgroundPosition = (-12 * (rating - 1)) + "px 0";
	
		for (var i = rating + 1; i < (documentRatingMax + 1); i++)
			$("document_rating_" + i).style.backgroundPosition = "-60px 0";
	
	}
	
	function documentRatingMouseout(rating) {
		rate = $("document_rating_" + rating);
		if (!rate)
			return;
	
		for (var i = 1; i < rating + 1; i++)
			$("document_rating_" + i).style.backgroundPosition = "-60px 0";
	
	}
	
	function rateDocument(rating){
		rateField = $("review[rating]");
		if (rateField)
			rateField.value = rating;
	
		// color everything right, and make it so that the current rating is sticky
		documentRatingMouseover(rating);						
		for (var i = 1; i < (documentRatingMax + 1); i++)
			eval('$("document_rating_' + i + '").onmouseout = function() { documentRatingMouseover(' + rating + '); };');
	}


	/* Facebook Connect-related functions */

/* set things up -- this gets run on every page time */
//FB_RequireFeatures(["XFBML"], function(){ FB.Facebook.init("9114a38516be35601d5e2803730edd74", "xd_receiver.htm"); }); 


/* respond to the button being clicked */
function facebook_button_onclick() { 
	FB_RequireFeatures(["XFBML", "CanvasUtil"], function() { 
		// initialize Facebook 
		FB.Facebook.init("9114a38516be35601d5e2803730edd74", "/xd_receiver.htm"); 
		// set up the callback 
		FB.Facebook.get_sessionState().waitUntilReady(function() { 
			// when we're authenticated, send the browser to a  page to process the facebook authentication
			// and then send it back to the current page
			window.location = "/home/fb_signon?return=" + escape(window.location);
		}); // trigger the whole process FB.Connect.requireSession(); } 
	})
}
	/************************************************************************************************************
	_project.js.erb		   
	These Javascript functions are used by the project page (e.g. /projects/stevens or /projects/mccain) 
	to display, sort, and filter lists of records that are supplied via Ajax from the DataController.
 ************************************************************************************************************/

var appearanceArray = []; // this contains each group of records (e.g. a year's worth of records, search results, etc.)
var currentSort = "date"; // by default, sort by date
var currentPage = 0; // by default, start at the first page
var lastPage = 0; // global variable for the last page, calculated when the data are loaded
var numberToShow =  25; // default number of records to show per page
var searchTerm = ""; // declare the global variable to hold the search term
var yearDuringSearch = currentlyShowing; // variable to track the year when a search is happening
var searchQuerySent = false; // variable to see if a search is in process
var legislatorLastName = "Inouye"; // variable that's used for display purposes if this is legislator-specific
var currentSource = "all"; // variable to say which source (congressional record, committee hearings, etc.) is being viewed now
var sources = []; // container for sources
var foundCount = 0; // how many records were found during a filter

if (!currentlyShowing) {
	// when loading, if nothing's set as currently showing, set it to this year
	// this might be preset by the containing page, for instance, when searching all records
	var currentlyShowing = 2010;
}



/* RENDERING THE LIST OF RECORDS */
	
function showLegislatorAppearances(appearances, node, options){
	// this function actually does the work of parsing a set of appearances (records) into a bulleted list.
	// it takes three arguments: appearances (the set of appearances as an array), node (where to put the output), 
	// and options (a hash of options to control the output).

	node = $(node); // make sure we're looking at the node object, not the ID string
	
	node.innerHTML = ""; // clear what's there now
	
	// find out what page we're on
	if (!options["offset"] || options["offset"] < 0)
		offset = 0;
	else
		offset = options["offset"];
	
	if (appearances) {
		// find out if there's a limit  
		if (options["limit"] == null || options["limit"] == "all")
			limit = appearances.length;
		else
			limit = options["limit"];
		
		filterCount = 0;
		
		if (appearances.length > 1){	
			// get the sources
			debug("Assigning sources.");
			sources = appearances[appearances.length - 1].sources;
			debug("Found " + sources.length + " sources.");
			
			// add the appearances we have received
			list = document.createElement("ul");
			list.className = "appearanceList";
			foundCount = 0;
			
			for (i = offset; (i < appearances.length - 1 && foundCount < limit); i++){
				// see if we're hiding records that have been reviewed and if so, if the record has reviews
				if (options["filter"] != "hide" || appearances[i].numberOfReviews == 0){
					// see if we're filtering on a specific source, and if so, make sure it matches
					if (currentSource == "all" || appearances[i].sourceID == sources[currentSource].id){						
						foundCount += 1;
						
						// jump the user right to the page where the match occurs
						pageString = "";
						if (appearances[i].startPage && appearances[i].startPage > 1)
							pageString = "?page=" + appearances[i].startPage;
							
						// create the HTML element and attach it
						// this could be replaced by Scriptaculous' Sortable object -- I should consider rebuilding it if that has better functionality
						bullet = document.createElement("li");
						bullet.innerHTML = "<a href=\"/record/read/" + appearances[i].id + pageString + "\" target=\"_blank\">" + unescape(appearances[i].title) + "</a>";
						if (options["show"] == "speechLength")
							bullet.innerHTML += " (" + appearances[i].speechLength + ")";
						if (options["show"] == "date")
							bullet.innerHTML += " (" + (appearances[i].date.getMonth() + 1) + "/" + appearances[i].date.getDate() + "/" + (appearances[i].date.getYear() > 99 ? "0" + (appearances[i].date.getYear() - 100) : appearances[i].date.getYear()) + ")";
	
						list.appendChild(bullet);
					}
					else {
						filterCount++;
					}
				}
				else {
					filterCount++;					
				}
			}
			
			// if we're filtering, update the filter count
			if (options["filter"] == "hide" && options["filterDiv"])
				$(options["filterDiv"]).innerHTML += " (" + filterCount + " hidden)";
			
			// display the result
			if (options["useSmoothFunctions"] == true)
				smoothlyInsert(list, node);
			else
				node.appendChild(list);
		}
	}
	else
		node.innerHTML = "Uh oh!  An error occured.";

	updateNavLinks();
}

/* GETTING DATA */

function getAppearanceData(legislatorID, year, term){
	// this function sends an Ajax request for a specific legislator's information for a specific year
	// usually this would be embedded inside a script tag at the bottom of a page, one for each year he or she has been in Congress
	// thus the data start loading immediately
	new Ajax.Request("/data/get_appearances", 
					{ 	parameters: { lid: legislatorID, year: year, term: term, limit: "none" },
						onSuccess: function(response) {
							appearanceArray[year] = response.responseJSON;
							activateYearSelector(year);
						}
					}
				);
}

function activateYearSelector(year){
	// this function is called when the data are retrieved
	// it appropriately stores the results (sent as JSON), activates the links to access the data (usually a specific year), and does some other housekeeping
	
	if (year != "all")
		$("seeRecordsOf" + year).innerHTML = "<a href='#" + year + "' onclick='showRecords(\"" + year + "\"); return false;'>" + year + "<\/a>";
	else // if there's no specific year, we're looking at all records
		$("seeRecordsOfall").innerHTML = "<a href='#all' onclick='showRecords(\"all\"); return false;'>All Years<\/a>";
	
	if (year == currentlyShowing){
		// for the currently showing year, calculate the number of pages and activate links as appropriate
		lastPage = (appearanceArray[year].length - (appearanceArray[year].length % numberToShow)) / numberToShow;

		if (lastPage > 0){ // if we have more than one page
			$("nextLinkBottom").className = "";
			$("nextLinkTop").className = "";
			$("nextLinkBottomInactive").className = "hidden";
			$("nextLinkTopInactive").className = "hidden";				
		}
		
		$("searchTermBox").disabled = false;
		$("searchTermBox").value = "";
		$("textSearchThisYearSendButton").disabled = false;
		$("textSearchAllYearsSendButton").disabled = false;

		updateShowingText();
	}
}



/* SEARCH FUNCTIONS */

function sendSearchFromForm(){
	// this sends the form if the user hits enter while in the field (making it easier to use than requiring the button click)
	if (!searchQuerySent)
		startTextSearch('searchTermBox', 462, 'all'); 	
}

function startTextSearch(field, legislatorID, searchOnYear) {
	// this function is called when a full text search is begun
	// it gets the appropriate data, then uses Prototype's Ajax.Request to send the request and set up processing on its return
	
	field = $(field);
	
	searchTerm = field.value;
	debug("Searching for " + searchTerm);

	if (searchTerm && searchTerm.length > 0){
		if (searchOnYear){
			// if we want to run the search for a specific year (most often 'all'), switch to that year
			if (currentlyShowing != "results")
				switchYearTo(searchOnYear, currentlyShowing);
			else
				switchYearTo(searchOnYear, yearDuringSearch);
				
		}	
		
		if (currentlyShowing != "results")
			// if we're searching for the first time, or switching years with a search filter active, update the yearDuringSearch field
			yearDuringSearch = currentlyShowing;
		
		startSearch(); // update the display

		debug("Sending request.");
		
		new Ajax.Request("/data/full_text_search", 
						{ 	parameters: { lid: legislatorID, year: (yearDuringSearch == "all" ? null : yearDuringSearch), term: escape(searchTerm), limit: "none" },
							onSuccess: function(response) {
								debug("Response received, searchQuerySent = " + searchQuerySent);
								
								// if we've switched years since then or done something else to cancel the search, don't do anything when it comes
								if (searchQuerySent){
									debug("Processing.");
									stopSearch(); // update the display
									debug("Assigning value.");
									appearanceArray["results"] = response.responseJSON;
									debug("Found " + appearanceArray["results"].length + " results.");
									offset = 0;
									showRecords("results");	// render the results
								}
							}
						}
					);			
	}
}

function startSearch() {
	// this function adjusts the various buttons and does other mostly visual changes for when a search starts
	searchQuerySent = true;

	// adjust the buttons appropriately
	$("textSearchThisYearSendButton").disabled = true;
	$("textSearchThisYearSendButton").value = "Loading...";
	$("textSearchAllYearsSendButton").disabled = true;
	$("textSearchAllYearsSendButton").value = "Loading...";

	$("textSearchClearButton").disabled = false;

	// now hide the list and show the loading area
	$("appearanceListContainer").className += " hidden";
	$("loadingContainer").className = $("loadingContainer").className.replace(/hidden/, "");
	
	// set up the delayed load text
	setTimeout("showLongerLoadingText()", 10000);
	setTimeout("showLongerLoadingText(2)", 20000);
}

function showLongerLoadingText(number){
	// this function shows the longer loading text unless the search has completed
	if (searchQuerySent){
		$("longerLoadingText").className = "";
		if (number == 2)
			$("longerLoadingText2").className = "";
	}
}

function clearSearch(){
	// this resets the buttons if they're not already, and shows the basic set of values for whatever one was looking at
	// this disables the clear box
	stopSearch();
	$("searchTermBox").value = "";
	$("textSearchClearButton").disabled = true;
	searchTerm == null;
	currentSource = "all";
	
	appearanceArray["results"] = null;
	showRecords(yearDuringSearch);
}

function stopSearch() {
	// this function is called when a search is finished (e.g. completes) or is canceled (the clear button is pressed or a year is changed)
	// this does not disable the clear button
	$("textSearchThisYearSendButton").disabled = false;
	$("textSearchThisYearSendButton").value = "Search this year";
	$("textSearchAllYearsSendButton").disabled = false;
	$("textSearchAllYearsSendButton").value = "Search all years";
	
	searchQuerySent = false;

	// now show the results area
	// now hide the list and the longer loading text and show the loading area
	$("loadingContainer").className += " hidden";
	$("appearanceListContainer").className = "content";
	$("longerLoadingText").className = "hidden";
	$("longerLoadingText2").className = "hidden";
}


function searchTitles(field) {
	// legacy function that just searches on document titles within an already loaded array
	// not used now that we have full text search, but could be reimplemented
	
	if (!appearanceArray[currentlyShowing]){
		// need to write code here to make it wait until it's loaded
		return;
	}
	
	searchTerm = field.value;
	debug("Searching for " + searchTerm);

	if (searchTerm && searchTerm.length > 0){
		if (currentlyShowing != "results")
			// if we're searching for the first time, or switching years with a search filter active, update the yearDuringSearch field
			yearDuringSearch = currentlyShowing;
			
		appearanceArray["results"] = []
		appearanceArray[yearDuringSearch].collect(titleMatch);
		appearanceArray["results"].push(appearanceArray["results"].length); // match how JSON is received
		debug("Found " + appearanceArray["results"].length + " results.");
		offset = 0;
		
		showRecords("results");		
	}
	else {
		// we've cleared a search term
		searchTerm == null;
		appearanceArray["results"] = null;

		showRecords(yearDuringSearch);
	}
}

function titleMatch(item){
	// legacy function, unused now
	// function that determines if the title matches a search term
	s = searchTerm.toUpperCase();
	if (item && item.title){
		if (item.title.toUpperCase().include(s))
			appearanceArray["results"].push(item);
	}
}

/* NAVIGATION FUNCTIONS */

function toggleReviewed(action){
	// hide or show reviewed records
	if (!appearanceArray[currentlyShowing]){
		// need to write code here to make it wait until it's loaded
		return;
	}
	
	if (action == "hide"){
		$("filterReviewed").innerHTML = "<span class='recordListOptionTitle'>Show<\/span> records that have already been reviewed";
		$("filterReviewed").onclick = function() { toggleReviewed('show'); return false; };
	}
	else {
		$("filterReviewed").innerHTML = "<span class='recordListOptionTitle'>Hide<\/span> records that have already been reviewed";
		$("filterReviewed").onclick = function() { toggleReviewed('hide'); return false; };
	}		
	showLegislatorAppearances(appearanceArray[currentlyShowing], "appearanceList", { offset: currentPage * numberToShow, limit: numberToShow, show: currentSort, filter: action, filterDiv: "filterReviewed" });					
}

function switchSourceTo(sourceID){
	// we're switching from one source to another (congressional records to committee hearings, etc.)
	// so update the navigation links, then rerender the records
	
	currentPage = 0; // go back to the first page
	
	// update the label for the current year, switch years, then update the new set of labels
	$("seeRecordsFrom" + currentSource).className = "";
	$("seeRecordsFrom" + currentSource + "Inactive").className = "hidden";			
	currentSource = sourceID;
	$("seeRecordsFrom" + currentSource).className = "hidden";
	$("seeRecordsFrom" + currentSource + "Inactive").className = "selectedMode";			
	
	showLegislatorAppearances(appearanceArray[currentlyShowing], "appearanceList", { offset: currentPage * numberToShow, limit: numberToShow, show: currentSort });						
	updateShowingText();
}

function updateSourceLabels(){
	// this function updates the set of links that allow the user to switch between document sources
	// they are passed as part of a hash that's in the last position of the appearance array
	
	$("sourcesContainer").innerHTML = "";
	total = 0;
	
	for (i = 0; i < sources.length; i++){
		$("sourcesContainer").innerHTML += " | ";

		sourceActive = document.createElement("span");
		sourceInactive = document.createElement("span");
		sourceActive.id = "seeRecordsFrom" + i;
		sourceInactive.id = "seeRecordsFrom" + i + "Inactive";

		sourceInactive.innerHTML = sources[i].name + " (" + sources[i].count + ")";
		sourceActive.innerHTML = "<a href=\"#recordSource\" onclick=\"switchSourceTo(" + i + ")\">" + sources[i].name + " (" + sources[i].count + ")</a>";

		total += sources[i].count;
		
		sourceActive.href = "#recordSource"
		
		sourceInactive.className = "hidden";
		
		$("sourcesContainer").appendChild(sourceActive);
		$("sourcesContainer").appendChild(sourceInactive);
	}
	
	$("seeRecordsFromallInactive").innerHTML = "All Sources (" + total + ")";
	$("seeRecordsFromall").innerHTML = "All Sources (" + total + ")";
}


function previousAppearances(){
	// this function goes backward one page of record data, until the current page is the first one
	// it simply changes the current page and does some housekeeping

	if (!appearanceArray[currentlyShowing]){
		// need to write better code here to make it wait until it's loaded
		// perhaps deferring the changes until the data has come back
		// but more likely, i'll continue with the current build of simply not enabling the link until everything's ready
		return;
	}
	
	currentPage--;
	activateNavLinks("next"); // make sure the links to the next page are active in case they weren't		

	if (currentPage <= 0){
		// if we've hit the first page, disable the previous link
		inactivateNavLinks("previous");	

		if (currentPage < 0)
			currentPage = 0;
	}
	
	// update the description of what's being displayed
	updateShowingText();

	// rerender
	showLegislatorAppearances(appearanceArray[currentlyShowing], "appearanceList", { offset: currentPage * numberToShow, limit: numberToShow, show: currentSort });		
}

function nextAppearances(){
	// the equivalent function for going forward
	
	if (!appearanceArray[currentlyShowing]){
		// need to write code here to make it wait until it's loaded
		return;
	}
	
	currentPage++;
	activateNavLinks("previous"); // make sure the links backward are active		
	
	if (currentPage >= Math.round(appearanceArray[currentlyShowing].length / numberToShow)){
		// if we've hit the last page, disable the previous link
		activateNavLinks("next");
					
		if (currentPage > lastPage)
			currentPage = lastPage;
	}

	updateShowingText();

	showLegislatorAppearances(appearanceArray[currentlyShowing], "appearanceList", { offset: currentPage * numberToShow, limit: numberToShow, show: currentSort });		
}


function updateShowingText(){
	// this function is called to update the line of text describing which records are currently showing
	// e.g. "You are now viewing record 0-25 of 1023."
	
	searchText = "";
	debug("Updating text, can view = " + canViewAllSpeeches());
	
	if (searchTerm)
		searchText = " from your search for '" + searchTerm + "'";
	
	if (appearanceArray[currentlyShowing].length > 1){
		// if we have records (rather than just the metadata that's appended to the end of the array
		// assemble the text
		
		numberOfRecords = (currentSource == "all" ? appearanceArray[currentlyShowing].length - 1 : sources[currentSource].count);
		
		if (numberToShow == "all")
			$("currentlyShown").innerHTML = "You're looking at all " + numberOfRecords + " records" + searchText;
		else {
			startNumber = currentPage * numberToShow + 1;
			endNumber = ((numberToShow * (currentPage + 1) < numberOfRecords) ? (numberToShow * (currentPage + 1)) : numberOfRecords);
			if (numberOfRecords < numberToShow) // if we only have a few results
				endNumber = numberOfRecords;
			
			$("currentlyShown").innerHTML = "You're looking at records " + startNumber + "-" + endNumber + " of " + numberOfRecords + searchText;
		}
		
		$("currentlyShown").innerHTML += ".";

		if (canViewAllSpeeches())
			if (legislatorSpecific)
				$("currentlyShown").innerHTML += "<br /><a href='/record/read/collected_speeches/'>See all the sections where " + legislatorLastName + " mentions '" +  searchTerm + "' together<\/a> (out of context).";
			else
				$("currentlyShown").innerHTML += "<br /><a href='/record/read/collected_speeches/'>See all the sections where legislators have mentioned '" +  searchTerm + "' together<\/a> (out of context).";
	
	}
	else 
		$("currentlyShown").innerHTML = "No records were found -- perhaps try a different set of search parameters?";
}

function canViewAllSpeeches(){
	// this checks the metadata in the last position of the array of records and determines whether you can view all the matches out of context
	// e.g. just read every speech dealing with deregulation, without having to comb through everything else
	
	if (appearanceArray[currentlyShowing][appearanceArray[currentlyShowing].length - 1] && appearanceArray[currentlyShowing][appearanceArray[currentlyShowing].length - 1].saved)
		return true;
	else
		return false;
}

function inactivateNavLinks(type){
	// inactive either the forward or backward links when the first or last page is hit
	$(type + "LinkBottom").className = "hidden";
	$(type + "LinkTop").className = "hidden";
	$(type + "LinkBottomInactive").className = "";
	$(type + "LinkTopInactive").className = "";	
}

function activateNavLinks(type){
	// active either the forward or backward links if the first or last page is left

	$(type + "LinkBottom").className = "";
	$(type + "LinkTop").className = "";
	$(type + "LinkBottomInactive").className = "hidden";
	$(type + "LinkTopInactive").className = "hidden";	
}

function updateNavLinks() {
	// this function evaluates what page we're on, and changes the links appropriately
	
	if  (currentPage == 0){
		// if we're on the first page, make sure to inactivate the previous link
		inactivateNavLinks("previous");
	}
	else
		activateNavLinks("previous");
		
	if (currentPage >= lastPage) {
		// if we're on the last page, inactivate the next link
		inactivateNavLinks("next");		
	}
	else
		activateNavLinks("next");
}


function switchSortTo(mode) {
	// change the sort of the list of records
	
	// make sure we've loaded everything
	if (!appearanceArray[currentlyShowing]){
		// need to write code here to make it wait until it's loaded
		return;
	}
	
	if (mode == currentSort){
		// we don't need to do anything
		return;
	}
	
	// sort the array based on the mode requested
	appearanceArray[currentlyShowing] = appearanceArray[currentlyShowing].sortBy(function(s){ return s[mode]; });

	// highlight the appropriate mode
	$(currentSort + "SortActive").className = "hidden";
	$(currentSort + "Sort").className = "";
	currentSort = mode;
	$(currentSort + "Sort").className = "hidden";
	$(currentSort + "SortActive").className = "selectedMode";
	
	updateShowingText();

	// we want longest first, but it'll sort shortest first, so we reverse
	if (currentSort == "speechLength")
		appearanceArray[currentlyShowing] = appearanceArray[currentlyShowing].reverse();
	
	// now redisplay the data
	showLegislatorAppearances(appearanceArray[currentlyShowing], "appearanceList", { offset: currentPage * numberToShow, limit: numberToShow, show: currentSort }); 
}

function switchLengthTo(length) {
	// change how many records are displayed at once
	
	// make sure we've loaded everything
	if (!appearanceArray[currentlyShowing]){
		// need to write code here to make it wait until it's loaded
		return;
	}
	
	if (length == numberToShow){
		// we don't need to do anything
		return;
	}

	if (length != "all"){
		//recalculate the current page and round downward so we keep showing the same set of records
		currentPage = (currentPage * numberToShow) / length;
		currentPage -= currentPage % 1;		
	}
	
	// highlight the appropriate length
	$(numberToShow + "LengthActive").className = "hidden";
	$(numberToShow + "Length").className = "";
	numberToShow = length;
	$(numberToShow + "Length").className = "hidden";
	$(numberToShow + "LengthActive").className = "selectedMode";
	
	
	// update the navigation links appropriately
	if (length == "all"){
		$("recordListNavLinksTop").className = "hidden";
		$("recordListNavLinksBottom").className = "hidden";	
	}
	else {
		$("recordListNavLinksTop").className = "";
		$("recordListNavLinksBottom").className = "";

		$("previousLinkBottom").innerHTML = "<< Previous " + numberToShow;
		$("previousLinkTop").innerHTML = "<< Previous " + numberToShow;
		$("nextLinkBottom").innerHTML = "Next " + numberToShow + " >>";
		$("nextLinkTop").innerHTML = "Next " + numberToShow + " >>";
		$("previousLinkBottomInactive").innerHTML = "<< Previous " + numberToShow;
		$("previousLinkTopInactive").innerHTML = "<< Previous " + numberToShow;
		$("nextLinkBottomInactive").innerHTML = "Next " + numberToShow + " >>";
		$("nextLinkTopInactive").innerHTML = "Next " + numberToShow + " >>";
	}
	updateShowingText();

	// now redisplay the data
	showLegislatorAppearances(appearanceArray[currentlyShowing], "appearanceList", { offset: currentPage * numberToShow, limit: numberToShow, show: currentSort }); 
}

function switchYearTo(year, from){
	// cosmetic changes when you switch from one year's worth of records to another
	// this is called from the showRecords function
	
	debug("Switching year from " + from + " to " + year);

	// update the links (make the one for the chosen year inactive and for the previous year active)	
	$("seeRecordsOf" + from).className = "";
	$("seeRecordsOf" + from + "Inactive").className = "hidden";
	currentlyShowing = year;
	$("seeRecordsOf" + currentlyShowing + "Inactive").className = "selectedMode";
	$("seeRecordsOf" + currentlyShowing).className = "hidden";	

	// if we're showing all years, hide the specific year search button
	// if not, show it
	if (currentlyShowing == "all")
		$("textSearchThisYearSendButton").className = "hidden";
	else
		$("textSearchThisYearSendButton").className = "";

	debug("Year is now " + currentlyShowing);			
}

function showRecords(year){
	// this is the function that actually switches from one year to another
	// it's also used when searches are returned
	// it does all the visual housekeeping and then rerenders the record
	
	if (year == currentlyShowing && !searchTerm){
		// we don't need to do anything
		return;
	}
	
	if (searchQuerySent) 
		// if we're switching years after a search is sent, abort the search
		clearSearch();

	if (year != "results"){
		// if we're changing years (rather than displaying results), update the display fields
		currentlyHighlighted = currentlyShowing; // store the current year in a temp variable
		
		if (currentlyShowing == "results")
			// if we're changing years during a search is a search and we're switching years, make change the look appropriately
			currentlyHighlighted = yearDuringSearch;
		
		switchYearTo(year, currentlyHighlighted);

		if (yearDuringSearch != currentlyShowing && searchTerm){
			// if we have a search term active and switch years, redo the search
			yearDuringSearch = currentlyShowing;
			runSearch(true);
			return;
		}
	}
	else {
		// we're starting a search
		currentlyShowing = year;
	}

	lastPage = (appearanceArray[year].length - (appearanceArray[year].length % numberToShow)) / numberToShow;
	currentPage = 0;
		
	// sort to the appropriate mode
	// we want longest first, but it'll sort shortest first, so we reverse
	appearanceArray[currentlyShowing] = appearanceArray[currentlyShowing].sortBy(function(s){ return s[currentSort]; });
	if (currentSort == "speechLength")
		appearanceArray[currentlyShowing] = appearanceArray[currentlyShowing].reverse();

	// now update 
	switchSourceTo("all"); // this resets the source and renders the records per the new settings
	updateShowingText();
	updateSourceLabels();
}

function runSearch(yearSpecific){
	// this is used when the page specifies a default search (e.g. searches as soon as it's loaded) 
	// so for instance, saved searches for a particular term
	// all it does is switch to 'all years' then send the search
	if (yearSpecific)
		$("textSearchThisYearSendButton").onclick();
	else {
		switchYearTo('all', currentlyShowing);
		$("textSearchAllYearsSendButton").onclick();
	}
}

	
	
	// this should be the last line on the page before the </html> tag
	initialize();
