/**********************************************************************
                           Table sorting script
                      By Mark Wilton-Jones 01/7/2005
   Version 1.1.0 updated 25/03/2010 to support custom sort functions
***********************************************************************

Please see http://www.howtocreate.co.uk/jslibs/ for details and a demo of this script
Please see http://www.howtocreate.co.uk/jslibs/termsOfUse.html for terms of use

Sorts tables (vertically) based on the data they contain. It is able to sort based on either
lexical (text) or numeric content. Sorting can be based on the contents of any chosen column,
and can use either ascending or descending order. It can be told to ignore heading rows, and
rows with specific classes, and can also be told to sort only a specific table body instead
of the whole table. Script runs in DOM browsers.

To use:

Inbetween the <head> tags, put:

	<script src="PATH TO SCRIPT/tablesort.js" type="text/javascript"></script>

The script provides a single function:

	sortTableRows(tableRef,column,sortType,order,rowsToIgnore[,optional: classPatternToIgnore])

	tableRef = node; reference to either a table or tbody element -
	                 if tableRef is a table, the first tbody will be sorted
	                 (or the whole table if there is no tbody in the source)
	column = integer; the column to use to determine sort order (starting from 0)
	sortType = boolean; true = numeric, false = lexical
	           function; custom function to use for Array.sort
	                     like normal array.sort custom functions, it must return <0, 0, or >0
	                     will be passed 4 parameters each time it is called:
	                       string; row A cell text content
	                       string; row B cell text content
	                       object; row A cell DOM node
	                       object; row B cell DOM node
	order = boolean; true = smallest first, false = largest first
	rowsToIgnore = integer; number of initial rows to ignore (for example, heading rows)
	classPatternToIgnore = optional regular expression; pattern of tr tag class attributes
	                                                    to ignore - null or undefined if none

The script will not sort the table if there are less than 2 rows to be sorted.
Any rows that are sorted will be put after any rows that are ignored.

For example:
	if( document.getElementById ) {
		var foo = document.getElementById('mytable');
		sortTableRows(foo,3,true,false,2,/\bheadrow\b|\boddrow\b/);
	}

The script does not take rowspan or colspan into account when selecting columns, and will
fail with errors if invalid parameters are used. It should be able to cope with (X)HTML
within the table cells (sorting will be based on a concatenation of all text nodes).
________________________________________________________________________________*/

function sortTableRows(oTableItem,oColumn,oSortType,oOrder,oIgnoreRows,oIgnoreClass) {

	if( !document.childNodes || !document.appendChild ) { return; }

	//prepare the custom sorting function
	var trimSpace = /^\s+|\s+$/g, oPadding = unescape('%00'), customFunction = ( typeof(oSortType) == 'function' );
	var sortFunc = function (a,b) {
		var c = totalValueOf(a.quickCells[oColumn]).replace(trimSpace,'');
		var d = totalValueOf(b.quickCells[oColumn]).replace(trimSpace,'');
		if( customFunction ) {
			//custom sort function
			return oSortType( c, d, a.quickCells[oColumn], b.quickCells[oColumn] ) * ( oOrder ? -1 : 1 );
		}
		if( oSortType ) {
			//numeric sort
			c = parseFloat(c);
			d = parseFloat(d);
			if( isNaN(c) ) { c = oOrder ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; }
			if( isNaN(d) ) { d = oOrder ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; }
			//'Infinity - Infinity' is NaN, and IE throws an error
			return ( c == d ) ? 0 : ( oOrder ? ( c - d ) : ( d - c ) );
		}
		//alpha sort (default)
		while( c.length > d.length ) { d += oPadding; }
		while( d.length > c.length ) { c += oPadding; }
		return ( c == d ) ? 0 : ( ( oOrder ? ( d > c ) : ( c > d ) ) ? -1 : 1 );
	};

	//prepare the function used to get the text content of the cells - rudimentary, but should hardly be needed
	var totalValueOf = function (oNode) {
		var oStr = '';
		for( var i = 0, j; i < oNode.childNodes.length; i++ ) {
			j = oNode.childNodes[i];
			if( j.nodeType == 3 ) {
				oStr += j.nodeValue;
			} else if( j.nodeType == 1 ) {
				oStr += totalValueOf( j );
			}
		}
		return oStr;
	};

	if( oTableItem.tagName.toLowerCase() == 'table' ) {
		oTableItem = oTableItem.getElementsByTagName('tbody')[0];
		//failed to find a table body - do not sort
		if( !oTableItem ) { return; }
	}

	var usableRows = [], actualRows = 0;

	for( var i = 0, j; i < oTableItem.childNodes.length; i++ ) {
		j = oTableItem.childNodes[i];
		if( j.tagName && j.tagName.toLowerCase() == 'tr' ) {
			actualRows++;
			if( ( actualRows > oIgnoreRows ) && ( !oIgnoreClass || !j.className || !j.className.match(oIgnoreClass) ) ) {
				//we want to sort this row - get accurate refs to the cells (th/td)
				j.quickCells = [];
				for( var n = 0, m; n < j.childNodes.length; n++ ) {
					m = j.childNodes[n];
					if( m.tagName && ( m.tagName.toLowerCase() == 'td' || m.tagName.toLowerCase() == 'th' ) ) {
						j.quickCells[j.quickCells.length] = m;
					}
				}
				usableRows[usableRows.length] = j;
			}
		}
	}

	if( usableRows.length > 1 ) {
		//enough rows to sort
		usableRows.sort(sortFunc);

		if( window.ActiveXObject && navigator.platform.indexOf('Mac') + 1 && !navigator.__ice_version && ( !window.ScriptEngine || ScriptEngine().indexOf('InScript') == -1 ) && !window.opera ) {
			//IE Mac will crash - avoid the bug - slower so I will not use this "fix" in browsers that do not need it
			for( var i = 0; i < usableRows.length; i++ ) {
				if( usableRows[i] != oTableItem.lastChild ) {
					oTableItem.insertBefore(usableRows[i],oTableItem.lastChild);
					oTableItem.insertBefore(oTableItem.lastChild,oTableItem.lastChild.previousSibling);
				}
			}
		} else {
			for( var i = 0; i < usableRows.length; i++ ) {
				oTableItem.appendChild(usableRows[i]);
			}
		}

		//fix Opera rendering bug
		document.body.className = document.body.className;
	}

}