/* $Header: /.software/regional/wwwdata_math.uwaterloo.ca/data/www/scripts/RCS/sortable.js,v 1.10 2011/08/26 19:56:17 rbutterworth Exp $ */

/*
Simplified and debugged by Ray Butterworth.
Based on a script from http://www.joostdevalk.nl/code/sortable-table,
which was based on the script http://www.kryogenix.org/code/browser/sorttable .
Distributed under the MIT license:
http://www.kryogenix.org/code/browser/licence.html .
Copyright (c) 1997-2010 Ray Butterworth, Stuart Langridge, Joost de Valk.  Version 1.5.7
*/

/*
  Required:
    Add to the document head:
      <script type="text/javascript" src="/scripts/sortable.js"></script>
    Add this attribute to each table to be made sortable:
      class="sortable"
  Optional:
    Add to <style>:
      To cause alternating row colors:
        tr.ts-bg1 { background-color: pink; }
        tr.ts-bg2 { background-color: lightblue; }
        ...
        up to ts-bg9, skipped values will "inherit" the table color.
        Bug: the last numbered color must not be "inherit" or equivalent.
    To prevent a column from being sortable:
      Add class="unsortable" to the <th> tag.
    To keep a row at the bottom of the table:
      Add class="sortbottom" to the <tr> tag.
    Note that clickable headings will have class "sortable".
  To be done:
     Date parsing could be a lot smarter.
*/

var tableSort = {
	// GLOBALS
	SORT_FUNCTION: 0,
	SORT_DIRECTION: 0,
	SORT_COLUMN_INDEX: 0, // Potential bug in first load before sorting!

	initialize: function() { // Process all tables with class "sortable".
		function setup(table) {
			var keyRow;  // Best guess as to the column header to be made clickable.
			if (table.rows && table.rows.length > 0)
				keyRow = (table.tHead && table.tHead.rows.length > 0)
					? table.tHead.rows[table.tHead.rows.length-1]
					: table.rows[0];
			if (!keyRow) return;
			
			for (var index=0; index<keyRow.cells.length; ++index) {
				var cell = keyRow.cells[index];
				if (!tableSort.hasClass(cell, 'unsortable')) {
					//cell.setAttribute('onclick', "tableSort.resort(this); return false;");
					cell.onclick = function anonymous() {tableSort.resort(this); return false;};
					cell.setAttribute('ts_sortDirection', "-1");
					cell.className += " sortable";
				}
			}
			tableSort.color(table);
		}

		if (!document.getElementsByTagName) return;
		var tables = document.getElementsByTagName("table");
		for (var index=0; index<tables.length; ++index)
			if (tableSort.hasClass(tables[index], 'sortable'))
				setup(tables[index]);
	},

	resort: function(link) {
		function getInnerText(element) {
			if ((typeof element == "string") || (typeof element == "undefined"))
				return element;
			if (element.innerText)
				return element.innerText;

			var text = "";
			for (var index=0; index<element.childNodes.length; ++index) {
				var child = element.childNodes[index];
				switch (child.nodeType) {
					case 1: // ELEMENT_NODE
						text += getInnerText(child);
						break;
					case 3:	// TEXT_NODE
						text += child.nodeValue;
						break;
				}
			}
			return text;
		}

		function doTbody(tBody) {
			var newRows = new Array();
			var index;
			var rownum;

			// Why only cells[0] ? ? ?
			for (rownum=0; rownum<tBody.rows.length; ++rownum)
				if (tBody.rows[rownum].cells[0].tagName.toLowerCase() != 'th')
					break;
			for (index=0; rownum<tBody.rows.length; ++rownum,++index) {
				newRows[index] = tBody.rows[rownum];
				newRows[index].setAttribute('ts_position', index);
			}
			newRows.sort(SORT_FUNCTION);

			// Appending rows that already exist effectively moves them to the end.
			for (index=0; index<newRows.length; ++index) // Do sortable rows.
				if (!newRows[index].className
				||    (newRows[index].className
					&& !tableSort.hasClass(newRows[index], "sortbottom")))
						tBody.appendChild(newRows[index]);
			for (index=0; index<newRows.length; ++index) // Do sortbottom rows.
				if (newRows[index].className
				 && tableSort.hasClass(newRows[index], "sortbottom"))
					tBody.appendChild(newRows[index]);
		}

		function getAncestor(element, tag) {
			return (element == null) ? null
				:  ((element.nodeType == 1)
				 && (element.tagName.toLowerCase()==tag.toLowerCase()))
				? element : getAncestor(element.parentNode, tag);
		}

		function sort_default(a,b) {  // Case-insensitive text comparison.
			var aIndex = tableSort.columnIndex(a.cells, SORT_COLUMN_INDEX);
			var bIndex = tableSort.columnIndex(b.cells, SORT_COLUMN_INDEX);

			var s1 = getInnerText(a.cells[aIndex]).toLowerCase();
			var s2 = getInnerText(b.cells[bIndex]).toLowerCase();
			return (s1>s2) ? SORT_DIRECTION
				: (s1<s2) ? -SORT_DIRECTION
				: (parseInt(a.getAttribute("ts_position"))>parseInt(b.getAttribute("ts_position")))
					? 1 : -1;
			//return (s1 == s2) ? 0 : (s1 < s2) ? -SORT_DIRECTION : SORT_DIRECTION;
		}
		function sort_number(a,b) {
			function strip_number(string) { // Handle leading sign and currency
				var sign = (string[0] == '-') ? -1 : 1;
				var num = string
					.replace(new RegExp(/^-?[£$€Û¢´]?/),"")
					.replace(/,/g, "");

				// table.setAttribute("ts_DEBUG", num);

				return sign * parseFloat(num);
			}
			var aIndex = tableSort.columnIndex(a.cells, SORT_COLUMN_INDEX);
			var bIndex = tableSort.columnIndex(b.cells, SORT_COLUMN_INDEX);
			var n1 = strip_number(getInnerText(a.cells[aIndex]));
			var n2 = strip_number(getInnerText(b.cells[bIndex]));
			if (isNaN(n1)) n1 = 0;
			if (isNaN(n2)) n2 = 0;
			return (n1>n2) ? SORT_DIRECTION
				: (n1<n2) ? -SORT_DIRECTION
				: (parseInt(a.getAttribute("ts_position"))>parseInt(b.getAttribute("ts_position")))
					? 1 : -1;
			//return SORT_DIRECTION * ((isNaN(n1) ? 0 : n1) - (isNaN(n2) ? 0 : n2));
		}
		function sort_date(a,b) {
			function set_sort_date(string) {
				// y2k notes: two digit years less than 50 are treated as 20XX,
				// greater than 50 are treated as 19XX
				var date = "00000000";
				if (string.length == 11) {
					switch(string.substr(3,3).toLowerCase()) {
						case "jan": month = "01"; break;
						case "feb": month = "02"; break;
						case "mar": month = "03"; break;
						case "apr": month = "04"; break;
						case "may": month = "05"; break;
						case "jun": month = "06"; break;
						case "jul": month = "07"; break;
						case "aug": month = "08"; break;
						case "sep": month = "09"; break;
						case "oct": month = "10"; break;
						case "nov": month = "11"; break;
						case "dec": month = "12"; break;
					}
					date = string.substr(7,4)+month+string.substr(0,2);
				} else if (string.length == 10) {
					if (string.substr(2,1)=='/') {
						date = string.substr(6,4)+string.substr(3,2)+string.substr(0,2);
					}
					else
						date = string;
				} else if (string.length == 8) {
					var year = string.substr(6,2);
					year = ((parseInt(year) < 50) ? '20' : '19') + year;
					date = year + string.substr(3,2) + string.substr(0,2);
				}
				return date;
			}

			var aIndex = tableSort.columnIndex(a.cells, SORT_COLUMN_INDEX);
			var bIndex = tableSort.columnIndex(b.cells, SORT_COLUMN_INDEX);
			var d1 = set_sort_date(getInnerText(a.cells[aIndex]));
			var d2 = set_sort_date(getInnerText(b.cells[bIndex]));
			return (d1>d2) ? SORT_DIRECTION
				: (d1<d2) ? -SORT_DIRECTION
				: (parseInt(a.getAttribute("ts_position"))>parseInt(b.getAttribute("ts_position")))
					? 1 : -1;
			//return (d1 == d2) ? 0 : (d1 < d2) ? -SORT_DIRECTION : SORT_DIRECTION;
		}

		var column = link.cellIndex;
		var table = getAncestor(link, 'TABLE');
		var parentrow = link.parentNode;
		var realcolumn = tableSort.indexColumn(parentrow.childNodes, column);
		var itm;
		var rownum;

		// Determine type for the column based on the first tbody.
		for (rownum=0; rownum<table.tBodies[0].rows.length; ++rownum) {
			var row = table.tBodies[0].rows[rownum];
			var cellindex = tableSort.columnIndex(row.cells, realcolumn);
			if ((cellindex >= 0)
			 && (cellindex < row.cells.length)
			 && (row.cells[cellindex].tagName.toLowerCase() == "td")) {
				itm = getInnerText(row.cells[cellindex]);
				itm = itm.replace(/^\s+|\s+$/g, "");
				if ((itm.substr(0,4) != "<!--") && (itm.length != 0))
					break;
			}
		}
		if (rownum >= table.tBodies[0].rows.length) return;

		if (itm.match(/^\d\d[\/\.-][a-zA-z][a-zA-Z][a-zA-Z][\/\.-]\d\d\d\d$/)
		 || itm.match(/^\d\d\d\d[\/\.-]\d\d[\/\.-]\d{2}?$/)
		 || itm.match(/^\d\d[\/\.-]\d\d[\/\.-]\d\d\d{2}?$/))
			SORT_FUNCTION = sort_date;
		// else if (itm.match(/^-?[£$€Û¢´]\d/)
		//  || itm.match(/^-?(\d+[,\.]?)+(E[-+][\d]+)?%?$/))
		else if (itm.match(/^-?[£$€Û¢´]?\d/))
			SORT_FUNCTION = sort_number;
		else
			SORT_FUNCTION = sort_default;
		SORT_COLUMN_INDEX = realcolumn;
		SORT_DIRECTION = (link.getAttribute("ts_sortDirection") == '1') ? '-1' : '1';

		// table.setAttribute("ts_DEBUG", SORT_FUNCTION);

		link.setAttribute('ts_sortDirection', SORT_DIRECTION);
		for (var bnum=0; bnum<table.tBodies.length; ++bnum)
			doTbody(table.tBodies[bnum]);
		tableSort.color(table);
	},

	color: function(table) {
		function replace_class(cell, oldname, newname) {
			var oldexp = RegExp('\\b'+oldname+'\\b');
			var newexp = RegExp('\\b'+newname+'\\b');
			if (oldexp.test(cell.className))
				cell.className = cell.className.replace(oldexp, newname);
			else if (!newexp.test(cell.className))
				cell.className += ((cell.className.length ? " " : "")) + newname;
		}

		colors = table.getAttribute("ts_tableColors");
		if (!colors) {
			var tableRows = table.getElementsByTagName("tr");
			for (var rownum=0; rownum<tableRows.length; ++rownum) {
				var row = tableRows[rownum];
				if (!tableSort.hasClass(row, "sortbottom")) {
					var column = tableSort.columnIndex(row.cells, tableSort.SORT_COLUMN_INDEX);
					if (column >= 0) {
						if (row.cells[column].tagName.toLowerCase() == 'td') {
							var oldbg = row.currentStyle
								? row.currentStyle['backgroundColor']
								: getComputedStyle(row, null)['backgroundColor'];
							for (index=9; index>=1; --index) {
								replace_class(row, 'ts-bg[0-9]', 'ts-bg'+index);
								var newbg = row.currentStyle
									? row.currentStyle['backgroundColor']
									: getComputedStyle(row, null)['backgroundColor'];
								if (newbg != oldbg) {
									colors = index;
									break;
								}
							}
							break;
						}
					}
				}
			}
			table.setAttribute("ts_tableColors", colors);
		}

		if (colors) {
			var tableBodies = table.getElementsByTagName("tbody");
			for (var bodynum=0; bodynum<tableBodies.length; ++bodynum) {
				var tableRows = tableBodies[bodynum].getElementsByTagName("tr");
				var colornum = 0;
				for (var rownum=0; rownum<tableRows.length; ++rownum) {
					var row = tableRows[rownum];
					if (!tableSort.hasClass(row, "sortbottom")) {
						var column = tableSort.columnIndex(row.cells, tableSort.SORT_COLUMN_INDEX);
						if (column >= 0) {
							if (row.cells[column].tagName.toLowerCase() == 'td') {
								replace_class(row, 'ts-bg[0-9]',
									'ts-bg' + ((colornum++%colors)+1));
							}
						}
					}
				}
			}
		}
	},

	hasClass: function(item, classname) {
		return RegExp('\\b'+classname+'\\b').test(item.className);
		
	},

	columnIndex: function(cells, target) { /* find index of real column number */
		var index;
		var column;
		
		for (index=column=0; index<cells.length; ++index) {
			var span = cells[index].colSpan ? cells[index].colSpan : 1;
			if (column >= target)
				return index;
			column += span;
		}
		return -1;
	},

	indexColumn: function(nodes, target) { /* find real column number of index */
		var index;
		var column;
		var previous_span;
		
		previous_span = 0;
		for (index=column=0; index<nodes.length; ++index) {
			var tag = nodes[index].tagName;
			if (tag) tag = tag.toLowerCase();
			if ((tag == "td") || (tag == "th")) {
				previous_span = nodes[index].colSpan ? nodes[index].colSpan : 1;
				column += previous_span;
				if (--target <= -1)
					break;
			}
		}
		return column - previous_span;
	}
}

if (window.addEventListener)
	window.addEventListener("load", tableSort.initialize, false);
else if (window.attachEvent)  // Stupid Microsoft
	window.attachEvent("onload", tableSort.initialize);
else
	alert("Handler could not be added");

