
//	Instant Column Filtering by Steve Cochrane
//	http://stevecochrane.com/instant-column-filtering

//	Code is free for use anywhere as long as you keep the above comment.

/*	HELPER FUNCTIONS ------------------------------------------------------------------------------- */
//	Originally by Simon Willison, found in "DOM Scripting" by Jeremy Keith
function addLoadEvent(func) {
	var oldonload = window.onload;
	if (typeof window.onload != 'function') {
		window.onload = func;
	} else {
		window.onload = function() {
			oldonload();
			func();
		}
	}
}

//	by Peter-Paul Koch & Alex Tingle
function findPosX(obj) {
	var curleft = 0;
	if (obj.offsetParent)
		while(1) 
		{
		  curleft += obj.offsetLeft;
		  if(!obj.offsetParent)
			break;
		  obj = obj.offsetParent;
		}
	else if (obj.x)
		curleft += obj.x;
	return curleft;
}
function findPosY(obj) {
	var curtop = 0;
	if (obj.offsetParent)
		while(1)
		{
		  curtop += obj.offsetTop;
		  if (!obj.offsetParent)
			break;
		  obj = obj.offsetParent;
		}
	else if (obj.y)
		curtop += obj.y;
	return curtop;
}

//	Written by Jonathan Snook, http://www.snook.ca/jonathan
//	Add-ons by Robert Nyman, http://www.robertnyman.com
function getElementsByClassName(oElm, strTagName, strClassName){
	var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
	var arrReturnElements = new Array();
	strClassName = strClassName.replace(/-/g, "\-");
	var oRegExp = new RegExp("(^|\s)" + strClassName + "(\s|$)");
	var oElement;
	for(var i=0; i<arrElements.length; i++){
		oElement = arrElements[i];
		if(oRegExp.test(oElement.className)){
			arrReturnElements.push(oElement);
		}
	}
	return (arrReturnElements)
}

//*** This code is copyright 2002-2003 by Gavin Kistner and Refinery; www.refinery.com
//*** It is covered under the license viewable at http://phrogz.net/JS/_ReuseLicense.txt

//***Adds a new class to an object, preserving existing classes
function AddClass(obj,cName){ KillClass(obj,cName); return obj && (obj.className+=(obj.className.length>0?' ':'')+cName); }

//***Removes a particular class from an object, preserving other existing classes.
function KillClass(obj,cName){ return obj && (obj.className=obj.className.replace(new RegExp("^"+cName+"\\b\\s*|\\s*\\b"+cName+"\\b",'g'),'')); }

//***Returns true if the object has the class assigned, false otherwise.
function HasClass(obj,cName){ return (!obj || !obj.className)?false:(new RegExp("\\b"+cName+"\\b")).test(obj.className) }

/*	(END HELPER FUNCTIONS) */

/*	THE NEW STUFF -------------------------------------------------------------------------------------- */

//	Finds any table header cells with the "filtered" class and creates links to the filter menus
//	I'd prefer not to have to use innerHTML here but I couldn't figure out how to insert a DOM node at the end 
//	of the TH's contents without removing what is already there.
function buildColFilterLinks() {
	var tableHeaders = document.getElementsByTagName("TH");
	var tableHeadersLength = tableHeaders.length;
	var filteredCells = new Array();
	var filteredCellsLength, currentCellContent, wrappedCellContent;
	
	//	Goes through all TH cells in the document and puts any with the class of "filtered" into the filteredCells array
	for (var j = 0; j < tableHeadersLength; j++) {
		if (HasClass(tableHeaders[j], "filtered")) {
			filteredCells.push(tableHeaders[j]);
		}
	}

	filteredCellsLength = filteredCells.length;
	for (var i = 0; i < filteredCellsLength; i++) {
		currentCellContent = filteredCells[i].innerHTML;
		
		//	wraps the current cell content in a span, so we can apply float: left to both spans
		//	(so the filter can be a block element and be positioned to the right of the current content)
		wrappedCellContent = "<span>" + currentCellContent + "</span>";
		linkCodeToAdd = "<span class=\"col-filter\" id=\"col-filter" + i + "\">&nbsp;</span>";
		filteredCells[i].innerHTML = wrappedCellContent + linkCodeToAdd;
	}
}

//	This master function should be run whenever the filter menus need to be built/rebuilt
//	Specifically, onload (handled at the end of this file) and whenever an option is selected from a filter menu
function buildColFilterMenus() {
	//	Stop here if there aren't any column filters in the page
	var colFilterClassed = getElementsByClassName(document, "SPAN", "col-filter");
	if (colFilterClassed.length) {

		var filterCell, filterTable, THCells, colNumber, showAllOption, showAllOptionTextNode, tableRows,
		 	currentRowCells, theContent, currentOptions, currentOptionCount, newSelectOption,
			newSelectOptionText, filterPos, filterBottom, filterLeft, tempOptionArray, tempOptionArrayCount;
		var i = 0;
		var filters = new Array();
		var currentFilterId = "col-filter" + i;
		var tempAIPs = new Array();
		var	formNodes = new Array();
		var filterSelects = new Array();
		var flag0 = true;
		var showAllOptionText = "(all)";

		//	Finds each col-filter in the page and files them into an array
		while (document.getElementById(currentFilterId)) {
			filters[i] = document.getElementById(currentFilterId);
			i++;
			currentFilterId = "col-filter" + i;
		}

		//	For each filter in the page
		for (var j = 0; j < filters.length; j++) {
			//	Reset our temporary variables
			tempOptionArray = new Array();
			tempOptionArrayCount = 0;
			
			//	Attach toggle behavior
			filters[j].setAttribute("onmouseup", "toggleColFilterMenu(this, event);");
			
			//	Get info about the filter's location
			filterInfo = returnFilterInfo(filters[j]);
			filterTable = filterInfo[0];
			colNumber = filterInfo[1];

			//	Create the div where the <select> will live
			tempAIPs[j] = document.createElement("div");
			tempAIPs[j].setAttribute("id", "col-filter" + j + "-select");
			tempAIPs[j].className = "temp-aip";
			//	Create the form element that will wrap the <select>
			formNodes[j] = document.createElement("form");
			//	Create the <select> where the possible options will go
			filterSelects[j] = document.createElement("select");
			
			filterSelects[j].setAttribute("onmouseup", "filterEventHandler(this, event);");
			filterSelects[j].setAttribute("onkeyup", "filterEventHandler(this, event);");
			
			//	Allows the user to select multiple options in the <select>
			filterSelects[j].setAttribute("multiple", "multiple");
			//	Always add the "(all)" option as the first option for each <select>
			showAllOption = document.createElement("option");
			showAllOption.setAttribute("value", showAllOptionText);
			showAllOptionTextNode = document.createTextNode(showAllOptionText);
			showAllOption.appendChild(showAllOptionTextNode);
			filterSelects[j].appendChild(showAllOption);


			//	Make a list of all rows in the table
			tableRows = filterTable.getElementsByTagName("tr");
			//	For each row in the table
			for (var l = 0; l < tableRows.length; l++) {
				//	If the row is currently hidden by an existing filter, skip the row
				//	to prevent having its value added
				if (HasClass(tableRows[l], "hidden") == false) {
					//	Reset the flag for each iteration
					flag0 = true;
					currentRowCells = tableRows[l].getElementsByTagName("td");
					//	Check if the row has TD cells, to skip over any header rows
					if (currentRowCells.length) {
						
//						if (l < 5) alert(currentRowCells[colNumber].firstChild.innerHTML);

						theContent = stripHTMLFromNode(currentRowCells[colNumber]);

						//	Now check if the current value is already in the menu, if so, ignore this cell
						for (var m = 0; m < tempOptionArrayCount; m++) {
							if (tempOptionArray[m] == theContent || theContent == "") {
								flag0 = false;
								break;
							}
						}

						//	If the flag was never set to false, then this text is not a duplicate
						//	and we can make a new option
						if (flag0 == true) {
							tempOptionArray[tempOptionArrayCount] = theContent;
							tempOptionArrayCount++;
						}
					}
				}
			}
			
			//	Now that we have the values to be inserted into the menu, we can alphabetize them first
			tempOptionArray.sort();					
			
			//	Now we can finally insert the option values into the menu itself
			for (var n = 0; n < tempOptionArrayCount; n++) {
				newSelectOption = document.createElement("option");
				newSelectOption.setAttribute("value", tempOptionArray[n]);
				newSelectOptionText = document.createTextNode(tempOptionArray[n]);
				newSelectOption.appendChild(newSelectOptionText);
				filterSelects[j].appendChild(newSelectOption);
			}
			
			//	Append the new <select> to its form, append the form to the wrapper div
			formNodes[j].appendChild(filterSelects[j]);
			tempAIPs[j].appendChild(formNodes[j]);

			//	Now we find where the filter button is located, and position the AIP under it
			filterLeft = findPosX(filters[j]);
			filterBottom = findPosY(filters[j]) + filters[j].offsetHeight;

			tempAIPs[j].style.display = "none"; // to be toggled on later
			tempAIPs[j].style.position = "absolute";
			tempAIPs[j].style.top = filterBottom + "px";
			tempAIPs[j].style.left = filterLeft + "px";		

			document.body.appendChild(tempAIPs[j]);
		}
	}
}

//	Removes any HTML elements from the contents of a node, and combines all the remaining text node values
function stripHTMLFromNode(node, text) {
//	alert("starting stripHTMLFromNode for: " + node);
	var justTheText = text ? text : new String;
//	alert("justTheText = " + justTheText);
	var childNodes = node.childNodes;
	var childNodesLength = childNodes.length;
	for (var i = 0; i < childNodesLength; i++) {
//		alert("running childNodes loop: " + i);
		if (childNodes[i].nodeType == 3) {
			//	We want to ignore any non-breaking spaces, because they transform into something weird after 
			//	going through as a nodeValue
			if (childNodes[i].parentNode.innerHTML != "&nbsp;") {
				justTheText = justTheText + childNodes[i].nodeValue;
//				alert("text node found, justTheText = " + justTheText);
			}
		} else if (childNodes[i].nodeType == 1) {
//			alert("non-text node found, running recursive loop");
			justTheText = stripHTMLFromNode(childNodes[i], justTheText);
		}
	}
	return justTheText;
}

function filterEventHandler(object, e) {
	//	If the user has clicked on something without holding down shift or ctrl, take action
	if (e.type == "mouseup" && e.shiftKey == false && e.ctrlKey == false) {
		runFilter(object, e);
		toggleColFilterMenu(object, e);
	//	If the user has let up on the shift key or the ctrl key, take action
	//	Shift = 16, Ctrl = 17, Cmd (Mac) = 224
	} else if (e.type == "keyup" && (e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 224)) {
		runFilter(object, e);
		toggleColFilterMenu(object, e);
	//	Otherwise do nothing for now
	} else {
		return false;
	}
}

function toggleColFilterMenu(object, e) {
	//	Designed to work if passed the AIP's span or the select menu
	//	For either one it must first find the div wrapper around the select
	if (object.tagName == "SPAN") {
		theMenu = document.getElementById(object.id + "-select");
	} else if (object.tagName == "SELECT") {
		theMenu = object.parentNode.parentNode;
	} else {
		return false;
	}

	//	Now that we have the div, hide it if it's visible or vice versa
	if (theMenu.style.display == "none") {
		//	Show menu
		theMenu.style.display = "block";
	} else if (theMenu.style.display == "block" && e.shiftKey == false) {
		//	Hide menu
		theMenu.style.display = "none";
		//	Clear any selected values from the menu (firstChild is the <form> in this case)
		theMenu.firstChild.reset();
	} else {
		return false;
	}
}

function runFilter(menuSelect, e) {
	var filterBy = getAllSelectedValues(menuSelect);
	var filterByLength = filterBy.length;
	
	var menuId = menuSelect.parentNode.parentNode.id;
	var colFilterId = menuId.substring(0, menuId.indexOf("-select"));
	var colFilter = document.getElementById(colFilterId);
	var filterInfo = returnFilterInfo(colFilter);
	var filterTable = filterInfo[0];
	var colNumber = filterInfo[1];
	var filterTableRows = filterTable.getElementsByTagName("TR");
	var filterTableRowsLength = filterTableRows.length;
	var allSelected = false;
	var flag;

	//	If any of the options selected are "(all)", defer to the reset function
	for (var i = 0; i < filterByLength; i++) {
		if (filterBy[i] == "(all)") {
			resetFilter(filterTable);
			allSelected = true;
		}
	}

	if (allSelected == false) {
		for (var j = 0; j < filterTableRowsLength; j++) {
			flag = false;
			currentRowCells = filterTableRows[j].getElementsByTagName("TD");
		
			//	If the current row has no TDs (i.e. a header row) then ignore the row
			if (currentRowCells.length) {
			
				//	Cycle through each of the filter's selected values
				for (var k = 0; k < filterByLength; k++) {
					if (stripHTMLFromNode(currentRowCells[colNumber]) == filterBy[k]) {
						flag = true;
					}
				}
			
				if (flag == false) {
					KillClass(filterTableRows[j], "shown");
					AddClass(filterTableRows[j], "hidden");
				}
			}
		}
	}

	// Rebuild column filter menus based on the new configuration
	buildColFilterMenus();
}

//	Takes in a <select> and returns an array of all its currently selected options
function getAllSelectedValues(menuSelect) {
	var selectedValues = new Array();
	var count = 0;
	var menuSelectLength = menuSelect.length;
	for (var i = 0; i < menuSelectLength; i++) {
		if (menuSelect.options[i].selected) {
			selectedValues[count] = menuSelect.options[i].value;
			count++;
		}
	}
	return selectedValues;
}

//	Currently this simply shows all rows on the page
function resetFilter(filterTable) {
	var tableRows = filterTable.getElementsByTagName("TR");
	for (var i = 0; i < tableRows.length; i++) {
		KillClass(tableRows[i], "hidden");
		AddClass(tableRows[i], "shown");
	}			
}

//	The following function takes a filter element and returns its parent table (vitals[0])
//	and its column number (vitals[1])
function returnFilterInfo(filter) {
	var filterCell, THCells;
	var vitals = new Array();
	
	//	Move up the tree until the table header cell that the filter resides in is found
	filterCell = filter.parentNode;
	while (filterCell.nodeName != "TH") {
		if (filterCell.parentNode) {
			filterCell = filterCell.parentNode;
		} else {
			return false;
		}
	}
	
	//  Move up the tree further until the table itself is found
	//	Can't just assume it's up two levels, because there may or may not be a thead
	vitals[0] = filterCell.parentNode;
	while (vitals[0].nodeName != "TABLE") {
		if (vitals[0].parentNode) {
			vitals[0] = vitals[0].parentNode;
		} else {
			return false;
		}
	}
	
	//	Find the number of the column that the filter is in
	THCells = vitals[0].getElementsByTagName("th");
	for (var i = 0; i < THCells.length; i++) {
		if (THCells[i] == filterCell) {
			vitals[1] = i;
		}
	}
	return vitals;
}

addLoadEvent(buildColFilterLinks);
addLoadEvent(buildColFilterMenus);

