/**

 * Written by Neil Crosby. 

 * http://www.workingwith.me.uk/articles/scripting/standardista_table_sorting

 *

 * This module is based on Stuart Langridge's "sorttable" code.  Specifically, 

 * the determineSortFunction, sortCaseInsensitive, sortDate, sortNumeric, and

 * sortCurrency functions are heavily based on his code.  This module would not

 * have been possible without Stuart's earlier outstanding work.

 *

 * Use this wherever you want, but please keep this comment at the top of this file.

 *

 * Copyright (c) 2006 Neil Crosby

 *

 * Permission is hereby granted, free of charge, to any person obtaining a copy 

 * of this software and associated documentation files (the "Software"), to deal 

 * in the Software without restriction, including without limitation the rights

 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 

 * copies of the Software, and to permit persons to whom the Software is 

 * furnished to do so, subject to the following conditions:

 *

 * The above copyright notice and this permission notice shall be included in 

 * all copies or substantial portions of the Software.

 *

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

 * SOFTWARE.

 **/

var standardistaTableSorting = {



	that: false,

	isOdd: false,



	sortColumnIndex : -1,

	lastAssignedId : 0,

	newRows: -1,

	lastSortedTable: -1,



	/**

	 * Initialises the Standardista Table Sorting module

	 **/

	init : function() {

		// first, check whether this web browser is capable of running this script

		if (!document.getElementsByTagName) {

			return;

		}

		

		this.that = this;

		

		this.run();

		

	},

	

	/**

	 * Runs over each table in the document, making it sortable if it has a class

	 * assigned named "sortable" and an id assigned.

	 **/

	run : function() {

		var tables = document.getElementsByTagName("table");

		

		for (var i=0; i < tables.length; i++) {

			var thisTable = tables[i];

			

			if (css.elementHasClass(thisTable, 'sortable')) {

				this.makeSortable(thisTable);

			}

		}

	},

	

	/**

	 * Makes the given table sortable.

	 **/

	makeSortable : function(table) {

	

		// first, check if the table has an id.  if it doesn't, give it one

		if (!table.id) {

			table.id = 'sortableTable'+this.lastAssignedId++;

		}

		

		// if this table does not have a thead, we don't want to know about it

		if (!table.tHead || !table.tHead.rows || 0 == table.tHead.rows.length) {

			return;

		}

		

		// we'll assume that the last row of headings in the thead is the row that 

		// wants to become clickable

		var row = table.tHead.rows[table.tHead.rows.length - 1];

		

		for (var i=0; i < row.cells.length; i++) {

		

			// create a link with an onClick event which will 

			// control the sorting of the table

			var linkEl = createElement('a');

			linkEl.href = '#';

			linkEl.onclick = this.headingClicked;

			linkEl.setAttribute('columnId', i);

			

			// move the current contents of the cell that we're 

			// hyperlinking into the hyperlink

			var innerEls = row.cells[i].childNodes;

			for (var j = 0; j < innerEls.length; j++) {

				linkEl.appendChild(innerEls[j]);

			}

			

			// and finally add the new link back into the cell

			row.cells[i].appendChild(linkEl);



			var spanEl = createElement('span');

			spanEl.className = 'tableSortArrow';

			spanEl.appendChild(document.createTextNode(''));

			row.cells[i].appendChild(spanEl);



		}

	

		if (css.elementHasClass(table, 'autostripe')) {

			this.isOdd = false;

			var rows = table.tBodies[0].rows;

		

			// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones

			for (var i=0;i<rows.length;i++) { 

				this.doStripe(rows[i]);

			}

		}

	},

	

	headingClicked: function(e) {

		

		var that = standardistaTableSorting.that;

		

		// linkEl is the hyperlink that was clicked on which caused

		// this method to be called

		var linkEl = getEventTarget(e);

		

		// directly outside it is a td, tr, thead and table

		var td     = linkEl.parentNode;

		var tr     = td.parentNode;

		var thead  = tr.parentNode;

		var table  = thead.parentNode;

		

		// if the table we're looking at doesn't have any rows

		// (or only has one) then there's no point trying to sort it

		if (!table.tBodies || table.tBodies[0].rows.length <= 1) {

			return false;

		}



		// the column we want is indicated by td.cellIndex

		var column = linkEl.getAttribute('columnId') || td.cellIndex;

		//var column = td.cellIndex;

		

		// find out what the current sort order of this column is

		var arrows = css.getElementsByClass(td, 'tableSortArrow', 'span');

		var previousSortOrder = '';

		if (arrows.length < 0) {

			previousSortOrder = arrows[0].getAttribute('sortOrder');

		}

		

		// work out how we want to sort this column using the data in the first cell

		// but just getting the first cell is no good if it contains no data

		// so if the first cell just contains white space then we need to track

		// down until we find a cell which does contain some actual data

		var itm = ''

		var rowNum = 0;

		while ('' == itm && rowNum < table.tBodies[0].rows.length) {

			itm = that.getInnerText(table.tBodies[0].rows[rowNum].cells[column]);

			rowNum++;

		}

		var sortfn = that.determineSortFunction(itm);



		// if the last column that was sorted was this one, then all we need to 

		// do is reverse the sorting on this column

		if (table.id == that.lastSortedTable && column == that.sortColumnIndex) {

			newRows = that.newRows;

			newRows.reverse();

		// otherwise, we have to do the full sort

		} else {

			that.sortColumnIndex = column;

			var newRows = new Array();



			for (var j = 0; j < table.tBodies[0].rows.length; j++) { 

				newRows[j] = table.tBodies[0].rows[j]; 

			}



			newRows.sort(sortfn);
			newRows.reverse(sortfn);

		}



		that.moveRows(table, newRows);

		that.newRows = newRows;

		that.lastSortedTable = table.id;

		

		// now, give the user some feedback about which way the column is sorted

		

		// first, get rid of any arrows in any heading cells

		var arrows = css.getElementsByClass(tr, 'tableSortArrow', 'span');

		for (var j = 0; j < arrows.length; j++) {

			var arrowParent = arrows[j].parentNode;

			arrowParent.removeChild(arrows[j]);



			if (arrowParent != td) {

				spanEl = createElement('span');

				spanEl.className = 'tableSortArrow';

				spanEl.appendChild(document.createTextNode(''));

				arrowParent.appendChild(spanEl);

			}

		}

		

			

		td.appendChild(spanEl);

		

		return false;

	},



	getInnerText : function(el) {

		

		if ('string' == typeof el || 'undefined' == typeof el) {

			return el;

		}

		

		if (el.innerText) {

			return el.innerText;  // Not needed but it is faster

		}



		var str = el.getAttribute('standardistaTableSortingInnerText');

		if (null != str && '' != str) {

			return str;

		}

		str = '';



		var cs = el.childNodes;

		var l = cs.length;

		for (var i = 0; i < l; i++) {

			// 'if' is considerably quicker than a 'switch' statement, 

			// in Internet Explorer which translates up to a good time 

			// reduction since this is a very often called recursive function

			if (1 == cs[i].nodeType) { // ELEMENT NODE

				str += this.getInnerText(cs[i]);

				break;

			} else if (3 == cs[i].nodeType) { //TEXT_NODE

				str += cs[i].nodeValue;

				break;

			}

		}

		

		// set the innertext for this element directly on the element

		// so that it can be retrieved early next time the innertext

		// is requested

		el.setAttribute('standardistaTableSortingInnerText', str);

		

		return str;

	},



	determineSortFunction : function(itm) {

		

		var sortfn = this.sortCaseInsensitive;

		

		if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) {

			sortfn = this.sortDate;

		}

		if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) {

			sortfn = this.sortDate;

		}

		if (itm.match(/^[£$]/)) {

			sortfn = this.sortCurrency;

		}

		if (itm.match(/^\d?\.?\d+$/)) {

			sortfn = this.sortNumeric;

		}

		if (itm.match(/^[+-]?\d*\.?\d+([eE]-?\d+)?$/)) {

			sortfn = this.sortNumeric;

		}

    		if (itm.match(/^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$/)) {

        		sortfn = this.sortIP;

   		}



		return sortfn;

	},

	

	sortCaseInsensitive : function(a, b) {

		var that = standardistaTableSorting.that;

		

		var aa = that.getInnerText(a.cells[that.sortColumnIndex]).toLowerCase();

		var bb = that.getInnerText(b.cells[that.sortColumnIndex]).toLowerCase();

		if (aa==bb) {

			return 0;

		} else if (aa<bb) {

			return -1;

		} else {

			return 1;

		}

	},

	

	sortDate : function(a,b) {

		var that = standardistaTableSorting.that;



		// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX

		var aa = that.getInnerText(a.cells[that.sortColumnIndex]);

		var bb = that.getInnerText(b.cells[that.sortColumnIndex]);

		

		var dt1, dt2, yr = -1;

		

		if (aa.length == 10) {

			dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);

		} else {

			yr = aa.substr(6,2);

			if (parseInt(yr) < 50) { 

				yr = '20'+yr; 

			} else { 

				yr = '19'+yr; 

			}

			dt1 = yr+aa.substr(3,2)+aa.substr(0,2);

		}

		

		if (bb.length == 10) {

			dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);

		} else {

			yr = bb.substr(6,2);

			if (parseInt(yr) < 50) { 

				yr = '20'+yr; 

			} else { 

				yr = '19'+yr; 

			}

			dt2 = yr+bb.substr(3,2)+bb.substr(0,2);

		}

		

		if (dt1==dt2) {

			return 0;

		} else if (dt1<dt2) {

			return -1;

		}

		return 1;

	},



	sortCurrency : function(a,b) { 

		var that = standardistaTableSorting.that;



		var aa = that.getInnerText(a.cells[that.sortColumnIndex]).replace(/[^0-9.]/g,'');

		var bb = that.getInnerText(b.cells[that.sortColumnIndex]).replace(/[^0-9.]/g,'');

		return parseFloat(aa) - parseFloat(bb);

	},



	sortNumeric : function(a,b) { 

		var that = standardistaTableSorting.that;



		var aa = parseFloat(that.getInnerText(a.cells[that.sortColumnIndex]));

		if (isNaN(aa)) { 

			aa = 0;

		}

		var bb = parseFloat(that.getInnerText(b.cells[that.sortColumnIndex])); 

		if (isNaN(bb)) { 

			bb = 0;

		}

		return aa-bb;

	},



	makeStandardIPAddress : function(val) {

		var vals = val.split('.');



		for (x in vals) {

			val = vals[x];



			while (3 > val.length) {

				val = '0'+val;

			}

			vals[x] = val;

		}



		val = vals.join('.');



		return val;

	},



	sortIP : function(a,b) { 

		var that = standardistaTableSorting.that;



		var aa = that.makeStandardIPAddress(that.getInnerText(a.cells[that.sortColumnIndex]).toLowerCase());

		var bb = that.makeStandardIPAddress(that.getInnerText(b.cells[that.sortColumnIndex]).toLowerCase());

		if (aa==bb) {

			return 0;

		} else if (aa<bb) {

			return -1;

		} else {

			return 1;

		}

	},



	moveRows : function(table, newRows) {

		this.isOdd = false;



		// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones

		for (var i=0;i<newRows.length;i++) { 

			var rowItem = newRows[i];



			this.doStripe(rowItem);



			table.tBodies[0].appendChild(rowItem); 

		}

	},

	

	doStripe : function(rowItem) {

		if (this.isOdd) {

			css.addClassToElement(rowItem, 'odd');

		} else {

			css.removeClassFromElement(rowItem, 'odd');

		}

		

		this.isOdd = !this.isOdd;

	}



}



function standardistaTableSortingInit() {

	standardistaTableSorting.init();

}



addEvent(window, 'load', standardistaTableSortingInit)