// Table Sorter Script.

//Taken From Rob's CMX Table Sorter Extension (thanks me!)
//Part of Jump DMS as of Feb 22, 2006
/* Version 1.2 Updated April 27, 2006

Added text-search functionality and original order storage.

*/

//Configuration vars

//Up class
var upClass = "sortableUp";

//Down class
var downClass = "sortableDown";

//Case in-sensitive? This affects the behavior of string searching...
var caresAboutCase = false;

//Does the sorter automatically place blank fields below all others 
//(so that relavent information is sorted and placed above empty/irrelavent data)
var ignoreEmpties = true;
		
//Table preserver array
var tableSortArr = new Array();



function sortStringFunc(a, b) {
	
	//if (a["key"] == "" || a["key"].charCodeAt(0) == 160) {
		//retVal = -1;
	//} else	if (b["key"] == "" || b["key"].charCodeAt(0) == 160) {
		//retVal = 1;
	if (a["key"].toUpperCase() > b["key"].toUpperCase()) {
		retVal = 1;
	} else if (a["key"].toUpperCase() < b["key"].toUpperCase()) {
		retVal = -1;
	} else {
		retVal = 0;
	}

	return retVal;

}

function sortNumericFunc(a, b) {
	a = parseFloat(a["key"]);
	b = parseFloat(b["key"]);
	if (a > b) {
		return 1;
	} else if (a < b) {
		return -1;
	} else {
		return 0;
	}
}

//Function to compare two strings and return the "match points" awarded
function stringMatchPoints(stringA, stringB) {
	//compare characters... the more characters that match, the better
	//String A is the reference, string B is the test
	if (caresAboutCase == false) {
		stringA = String(stringA).toLowerCase();
		stringB = String(stringB).toLowerCase();
		
	}
	
	var testCase = stringB;
	while (testCase.length > 0) {
		if (stringA.indexOf(testCase) == 0) {
			break;
		} else {
			testCase = testCase.substr(0, testCase.length -1);
		}
	}
	
	
	return testCase.length;
}


//Added search key to find closest matches in table and place those at the top of the sort order
function sortByColumn (whichColumn, searchKey) {

	//Only try to sort the table if the browser supports the W3C DOM 
	//(which the behavior relies on to do all the sorting work)
	
	if (document.getElementById) {
		//Get a reference to the table itself.
		workingTable = whichColumn.parentNode;
		while (workingTable.tagName != "TABLE") {
			workingTable = workingTable.parentNode;
		}
		
		var tableEle = workingTable;
		
		//Get the table body
		workingTable = workingTable.lastChild;
		
		
		/* This section allows us to sort based on the original order of elements
		rather than simply what is currently being displayed. This is necessary to that
		after you perform a text search you can still sort normally*/
		if (workingTable.parentNode.origTable == null) {
			
			workingTable.parentNode.origTable = workingTable.cloneNode(true);
		} else {
			var dispTable = workingTable;
			var workingTable = workingTable.parentNode.origTable.cloneNode(true);

		}
		
		//Done special addition
		
		
		var tableRows = workingTable.getElementsByTagName("tr");
		
		//We can determine how many children in the given column is by comparing the given node with each of the child nodes
		//in the header row (row 0)
		if (dispTable != null) {
			var origHead = dispTable.getElementsByTagName("tr");
		} else {
			origHead = tableRows;
		}
		var headerCells = origHead[0].getElementsByTagName("th")
		
		for (var i=0; i < headerCells.length; i++) {
			
			if (headerCells[i] == whichColumn) {
				
				var columnIndex = i;
				
				//get the sort order from the attribute of the header
				var sortOrder = headerCells[i].getAttribute("sortOrder");

				if (sortOrder != 1 && sortOrder != -1) {
					//No sort order defined
					sortOrder = -1;
				}
				//invert the current sort order
				sortOrder = (sortOrder * -1)
				
				//if we're doing a search we always want one direction
				if (searchKey != null) {
					sortOrder = 1;
				}
				//Update the attribute
				headerCells[i].setAttribute("sortOrder", sortOrder);
				
				//Set the class to either up or down
				if (headerCells[i].originalClass == null) {
					headerCells[i].originalClass = headerCells[i].className;
				}
				
				
				if (sortOrder == 1) {
					
					headerCells[i].className = headerCells[i].originalClass + " " + downClass;
				} else if (sortOrder == -1) {
					
					headerCells[i].className = headerCells[i].originalClass + " " + upClass;
				}
				
				
				//break;
			} else {
				if (headerCells[i].getAttribute("sortOrder")) {
					headerCells[i].setAttribute("sortOrder", "");
				}
				if (headerCells[i].originalClass != null) {
					//this column was sorted at some point so restore it's original class (clearing the up/down state)
					headerCells[i].className = headerCells[i].originalClass;
					
					headerCells[i].originalClass = null;
				}
			}
		}
	
		
		//Okay, let's build an array containing A) the key value to sort by, and B) a reference to the entire row
		var rowsArray = new Array();
		
		//Define a default sort function
		var sortFunc = sortStringFunc;
		
		//flag to determine if any alpha-numeric value is present in the column
		var isAlpha = false;
		
		var isNumeric = false;
		
		//check for the search key
		if (searchKey != null) {
			//Default to assuming first row is the best match
			bestMatch = -1;
			//How well do the two strings match?
			bestMatchPoints = 0;
		}
		
		
		for (var i = 1; i < tableRows.length; i++) {
			var thisRowArr = new Array();
			
			var theseCells = tableRows[i].getElementsByTagName("td");
			
			//set the value to sort by to false by default.
			var thisKeyValue = "";
			
			//check to see if a sortOrder attribute has been set on the td first
			//(this overrides any other sorting methods)
			
			if (theseCells[columnIndex] != null) {
				if (theseCells[columnIndex].getAttribute("sortOrder") != null) {
					thisKeyValue = theseCells[columnIndex].getAttribute("sortOrder");
				} else {
					//No sortOrder attribute has been set, so try to determine the contents of the cell in order to sort it.
					
						
					//Make sure that the table cell actually contains information
					if (theseCells[columnIndex].hasChildNodes()) {
						
						if (theseCells[columnIndex].firstChild.nodeType == 3) {
							//The table cell contains text first, so sort by that
							thisKeyValue = theseCells[columnIndex].firstChild.nodeValue;
						
						} else if (theseCells[columnIndex].getElementsByTagName("a").length > 0) {
							//The table cell contains at least one hyperlink, so sort by the link text
							thisKeyValue = theseCells[columnIndex].getElementsByTagName("a").item(0).firstChild.nodeValue;
						}
						
					}
					
				}
				
				/* Moved to after sort to provide more accurate results
				//check for the search key
				if (searchKey != null) {
					//Compare this value to the search key
					thisMatchPoints = stringMatchPoints(thisKeyValue, searchKey);
					
					
					//This key is the best match so far, so flag it as such
					if (thisMatchPoints > bestMatchPoints) {
						
						bestMatchPoints = thisMatchPoints;
						bestMatch = rowsArray.length;
					}
				
				}
				*/
				
				if (theseCells[columnIndex].getAttribute("priority") != null) {
					tableRows[i].priority = true;
				}
				//Only add the row to the list of sortable ones if a value to sort by could be found
				if (thisKeyValue !== false) {
				
					thisRowArr["key"] = thisKeyValue;
					thisRowArr["node"] = tableRows[i];
					
					rowsArray.push(thisRowArr);
					
					
					//use this value to set the sort function
					if (thisKeyValue.match(/^[\d\.]+$/)) {
						
						isNumeric = true;
						//sortFunc = sortNumericFunc;
					} else {
						isAlpha = true;
					}
				}
			}
		}
	
		/* moved to after sort to provide better results 
		//Assign priority to the best matching field
		if (searchKey != null) {
			
			if (bestMatch > -1) {
				rowsArray[bestMatch]["node"].priority = true;
				
			}
		}
		*/
		
		if (isAlpha) {
			sortFunc = sortStringFunc;
		} else {
			if (isNumeric) {
				sortFunc = sortNumericFunc;
			}
		}
		
		
		
		//Now all we have to do is sort the array using a custom sort function
		rowsArray.sort(sortFunc);
		
		
				
		//Prioritize certain keys
		var priorKey = -1;
		for (var p =0; p < rowsArray.length;p++) {
			
			if (searchKey == null || searchKey == "") {
				//Order by the author-defined priority
				if (rowsArray[p]["node"].priority == true) {
					priorKey = p;
					break;
				}
			} else {
				//try to get the bast match
				//Compare this value to the search key
					thisMatchPoints = stringMatchPoints(rowsArray[p]["key"], searchKey);
					
					
					//This key is the best match so far, so flag it as such
					if (thisMatchPoints > bestMatchPoints) {
						bestMatchPoints = thisMatchPoints;
						//bestMatch = rowsArray.length;
						priorKey = p;
					}
				
				
			}
			
		}
		
		var found = true
		if (searchKey != null && searchKey != "") {
			if (bestMatchPoints < searchKey.length) {
				found = false;
			}
		}
		
		var searchInput = document.getElementById("searchTable" + tableEle.id);
		if (searchInput) {
			if (found) {
				searchInput.className = "";
				searchInput.title = "";
			} else {
				searchInput.className = "notFound";
				searchInput.title = "No matches found...";
			}
		}
		
		
		if (priorKey != -1) {
			var newRows = new Array();
			for (var p = priorKey; p < rowsArray.length; p++) {
				newRows.push(rowsArray[p]);
			}
			for (var p = 0; p < priorKey; p++) {
				newRows.push(rowsArray[p]);
			}
			
			rowsArray = newRows;
		}
		
		
		
		//Now output the rows in order
		var empties = new Array();
		if (sortOrder == 1) {
			
			for (var j=0; j < rowsArray.length; j++) {
				if (ignoreEmpties == true) {
					if (rowsArray[j]["key"] == "" || rowsArray[j]["key"].charCodeAt(0) == 160) {
						empties = empties.concat(rowsArray.splice(j, 1));
						j--;
						
						
					} else {
						
						workingTable.appendChild(rowsArray[j]["node"]);
					}
				} else {
					workingTable.appendChild(rowsArray[j]["node"]);
				}
			}
		} else {
			//Output desc
			for (var j= rowsArray.length - 1; j >= 0; j--) {
				if (ignoreEmpties == true) {
					if (rowsArray[j]["key"] == "" || rowsArray[j]["key"].charCodeAt(0) == 160) {
						empties = empties.concat(rowsArray.splice(j, 1));
						
						
					} else {
						
						workingTable.appendChild(rowsArray[j]["node"]);
					}
				} else {
					workingTable.appendChild(rowsArray[j]["node"]);
					
				}
				
			}
		}
		
		
		//Special flag to move empty fields to the bottom of the sort (since they are irrelavent)
		if (ignoreEmpties == true) {
			for (var z=0; z < empties.length; z++) {
				workingTable.appendChild(empties[z]["node"]);
			}
		}
		
		
		//This allows for us to sort based on the original order of the array each time, rather than 
		//based on what's currently displaying.
		if (dispTable != null) {
			var rows = workingTable.getElementsByTagName("tr");
			var oldRows = dispTable.getElementsByTagName("tr");
			
			
			//I'm not really sure why it drops elements from the rows array but it does, so this works...
			for (var q =1; q < oldRows.length; q++) {

				dispTable.replaceChild( rows[1], oldRows[q]);
				

			}
		}
		
		//Set the properties for when the page reloads
		//tableEle.sortCol = whichColumn;
	
		if (tableEle.id) {
			tableSortArr[tableEle.id] = whichColumn;
			
		}
		
		//tableEle.sortOrder = whichColumn.getAttribute("sortOrder") * -1;
		
		
	}
	return false;

}


//Function to draw the search box for all tables
function drawSearches() {
	//Get all tables
	var tables = document.getElementsByTagName("table");
	
	for (var i=0; i < tables.length; i++) {
		var thisTable = tables[i];
		
		if (thisTable.id != null && thisTable.id != "") {
			//Try to find a seach element holder for this table
			var searchHolder = document.getElementById(thisTable.id + "searchBox");
			
			if (searchHolder) {
				//Valid search holder was found, so draw the search box for this table
				
				drawSearchBox(searchHolder, thisTable);
			}
		}
	}
}
	
//Function to output a specific search box
function drawSearchBox(holderEle, tableEle) {
	//Create a form
	
	var theForm = document.createElement("form");
	
	//Create a label and input string element
	var queryLabel = document.createElement("label");
	queryLabel.appendChild(document.createTextNode("Search for:"));
	queryLabel.setAttribute("for", "searchTable" + tableEle.id);
	
	var searchInput = document.createElement("input");
	searchInput.setAttribute("type", "text");
	searchInput.setAttribute("name", "searchTable" + tableEle.id);
	searchInput.id = "searchTable" + tableEle.id;
	
	function performSearch (e) {
		var tableColumns = tableEle.getElementsByTagName("TH");
		
		var searchType = this.form["searchTable" + tableEle.id + "Col"].value;
		var searchKey = this.form["searchTable" + tableEle.id].value;
		
		if (tableColumns[searchType] != null) {
			sortByColumn(tableColumns[searchType], searchKey);
		}
	}
	
	searchInput.onkeyup = performSearch;
	
	theForm.onsubmit = function (e) {
		return false;
	}
	
	theForm.appendChild(queryLabel);
	theForm.appendChild(searchInput);
	
	//Create the label and input for the column selection
	var colLabel = document.createElement("label");
	colLabel.appendChild(document.createTextNode(" in: "));
	colLabel.setAttribute("for", "searchTable" + tableEle.id + "Col");
	
	var colInput = document.createElement("select");
	colInput.setAttribute("name", "searchTable" + tableEle.id + "Col");
	colInput.id = "searchTable" + tableEle.id + "Col";
	
	colInput.onchange = performSearch;
	
	//Generate the options
	var tableColumns = tableEle.getElementsByTagName("TH");
	for (var j=0; j < tableColumns.length; j++) {
		var thisColumn = tableColumns[j];

			var opt = document.createElement("option");

			opt.value = j;
			opt.innerHTML = thisColumn.innerHTML;
			
			colInput.appendChild(opt);
	}
	
	theForm.appendChild(colLabel);
	theForm.appendChild(colInput);
	
	//finally attach the form to the holder element
	holderEle.appendChild(theForm);

}