/**
 * ******************** UNCOMPRESSED VERSION ********************
 *
 * THIS IS AN UNCOMPRESSED VERSION OF THE tableSortObj.js FILE
 * THIS FILE SHOULD BE ONLY USED FOR AMMENDMENTS & SHOULD NOT
 * BE USED AS THE VERSION SERVED TO USER AGENTS
 *
 * ONCE ALL CHANGES ARE COMPLETED THIS FILE MUST BE COMPRESSED 
 * - I.E. REMOVE ALL COMMENTS - AND SAVED AS THE SERVERED VERSION
 * - tableSortObj.js
 *
 * ******************** UNCOMPRESSED VERSION ********************
 */


/**
 * tableSortObj.js
 *
 * A script which can be used to sort HTML tables
 *
 * @SPEC  DOM 1
 ***** BROWSER SUPPORT *****
 * :: PC :: IE5.0, IE5.5, IE 6.0 , Opera 7.1, Opera 7.50 (BETA), Mozilla 1.6, FireFox 0.8, NS 6.2, NS 7.1
 */
 
/**
 * Object Constructor
 * @PARAM tableId - string - ID of table
 * @PARAM sortTypes - array - array of sort types for columns
 * @PARAM zebra - int - whether to re-stripe the table (using odd & even class names on the table rows) when the table is re-ordered
 */
function tableSortObj(tableId,sortTypes,zebra) {
	if(document.getElementById) {
		this.table = document.getElementById(tableId);
		/* make sure it has being attached to the table */
		if(this.table) {
			this.sortTypes = sortTypes;
			this.zebraTable = zebra;
			
			this.instructions = 'To re-order the results, simply click on the column header to order by that column';
			this.cellTitleBegin = 'Click here to order by ';
			
			this.tHead = this.table.tHead;
			this.tBody = this.table.tBodies[0];
			this.document =  this.table.ownerDocument ||  this.table.document;
			
			this.sortColumn = null;
			this.cellIndex = null;
			this.desc = false; // default sort order. true => descending, false => ascending
			this.sortType = null;
			
			/**
				capture the event here so that the proper headClick method can 
				execute methods
			**/
			var fakeThis = this;
			this._headClick = function (e) {
				fakeThis.headClick(e);
			};
					
			// assign a fake function to run once the sort is complete (stop NS tying itself in knots)
			this.onsort = function () {};
	
			this.gecko = navigator.product == "Gecko";
			this.msie = /msie/i.test(navigator.userAgent);
			
			this.onsort = function () {};
	
			
			// setup the method handlers
			this.initHead = initHead;
			this.headClick = headClick;			
			this.sortCol = sortCol;
			this.getCellIndex = getCellIndex;
			this.basicCompare = basicCompare;
			this.textCompare = textCompare;
			this.getCache = getCache;
			this.destroyCache = destroyCache;
			this.getRowValue = getRowValue;
			this.getInnerText = getInnerText;
			this.getSortTypeFunc = getSortTypeFunc;
			this.displayInstructions = displayInstructions;
			this.updateHeaderArrows = updateHeaderArrows;
			this.zebraUpdate = zebraUpdate;
			this.isUndefined = isUndefined;
			
			this.setInstructions = setInstructions;
			
			this.displayInstructions();
			// initiate the header
			this.initHead();
		} else return false;
	}	else return false;
}

/**
 * isUndefined(v)
 *
 * IE 5.0 does not support the undefined keyword, so we cannot do a direct
 * comparison such as v===undefined so we have to test against an undefined variable.
 *
 * @PARAM v - var - variable to test whether defined or not
 * @RETURN boolean
 */
function isUndefined(v) {
	var undef;
	return v===undef;
}

/**
 * setInstructions(str)
 *
 * Sets a different string (from the default) for the instructions)
 *
 * @PARAM str - string 
 */
function setInstructions(str) {
	this.instructions = str;
	this.displayInstructions();
}

/**
 * displayInstructions()
 *
 * outputs the instructions string to an html element which has the ID 'sortInstructions' applied
 */
function displayInstructions() {
	if(document.getElementById('sortInstructions')) {
		document.getElementById('sortInstructions').innerHTML = this.instructions;
	}
}

/**
 * initHead()
 *
 * finds all the table header cells & assigns the correct sort type & filter to the header cell
 * then attaches an event handler (for onclick) to the headers 
 */
function initHead() {
	// whack all the header cells into a var then loop round
	var cells = this.tHead.rows[0].cells;
	var l = cells.length;
	for(i=0;i<l;i++) {
		thisCell = cells[i];
		colFilter = null;
		// check if a colspan sort filter has been passed 
		if(temp = this.sortTypes[i].split(/\|/)) {
			this.sortTypes[i] = temp[0];
			colFilter = temp[1];
		}
		
		if(this.sortTypes[i] != null && this.sortTypes[i] != "none") {
			// for each header cell assign the correct sort type & an event listener
			thisCell._sortType = this.sortTypes[i];
			if(colFilter != null) thisCell._colFilter = colFilter;
			thisCell._desc = this.desc;
			if(typeof thisCell.addEventListener != "undefined") thisCell.addEventListener("click", this._headClick, false);
			else if (typeof thisCell.attachEvent != "undefined") thisCell.attachEvent("onclick", this._headClick);
			else thisCell.onclick = this._headClick;
			
			// update the title
			if(!this.isUndefined(thisCell.title)) thisCell.title = this.cellTitleBegin + thisCell.title;
			
		} else {
			thisCell.setAttribute( "_sortType", this.sortTypes[i] );
			thisCell._sortType = "None";
		}
	}
}

/**
 * updateHeaderArrows()
 *
 * updates the classname of the header cell to 'sort-arrow-up', 'sort-arrow-dn' or 'sort-arrow-none'
 * depending on how the column is sorted (or not)
 */
function updateHeaderArrows() {
	var cells = this.tHead.rows[0].cells;
	for (var i = 0; i < cells.length; i++) {
		if (cells[i]._sortType != null && cells[i]._sortType != "None") {
			if(i == this.cellIndex) newClass = "sort-arrow-" + (this.desc ? "dn" : "up"); 
			else newClass = "sort-arrow-none";
			
			cells[i].className = newClass;
		}
	}
}


/**
 * headClick(e)
 *
 * function to capture the onclick events on the header cells
 * @PARAM e - event fired from onclick event listner
 */
function headClick(e) {
	var el = e.target || e.srcElement;
	
	/* 
		as netscape 6.2 passes the inner text as a text object when clicked on
		(instead of a table head object) do this check
	 */
	if(!this.isUndefined(el._sortType))	this.sortType = el._sortType;  // get sort type
	else {
		/* 
			if the node didn't have a sort type defined assume its NS6.2 being stupid
		 */
		el = el.parentNode;
		if(!this.isUndefined(el._sortType))	this.sortType = el._sortType;
		else return;  // if it's still not defined quit now
	}
	// get col span filter
	if(!this.isUndefined(el._colFilter)) colFilter = el._colFilter; 
	else colFilter = 0;
	this.desc = el._desc;  // get order
	el._desc = !el._desc;  // reverse order

	this.cellIndex = this.msie ? this.getCellIndex(el) : el.cellIndex;
	this.sortColumn = this.cellIndex + parseInt(colFilter);
	this.sortCol();
	this.updateHeaderArrows();
	
	if(this.zebraTable) this.zebraUpdate();
}

/**
 * zebreaUpdate()
 *
 * restripes the table using the class names 'odd' & 'even' onto the table rows
 */
function zebraUpdate() {
	var trs = this.tBody.getElementsByTagName("tr");
	// ... and iterate through them
	for (var i = 0; i < trs.length; i++) {
		if(i % 2 == 0) trs[i].className = 'odd';
		else trs[i].className = 'even';
	}
}
     
/*
 * getCache()
 *
 * caches a copy of the current table contents for sorting
 * @RETURN array
 */
function getCache() {
	var rows = this.tBody.rows;
	var a = new Array(rows.length);
	var r;
	for(var i=0; i < rows.length; i++) {
		r = rows[i];
		a[i] = {
			value:this.getRowValue(r),
			element:r
		}
	}
	return a;
}

/**
 * destroyCache(a)
 *
 * emptys the cached table contents post-sorting to save system resources
 * @PARAM a - array - the table cache
 */
function destroyCache(a) {
	for (var i = 0; i < a.length; i++) {
		a[i].value = null;
		a[i].element = null;
		a[i] = null;
	}
}

/**
 * getRowValue(row)
 *
 * returns the innerText of the column
 * @PARAM row - int - the current row
 * @RETURN text - string - innerText of the cell
 */
function getRowValue(row) {
	var dbg = document.getElementById('debugWindow');
	var text;
	var thisCell = row.cells[this.sortColumn];
	if(typeof thisCell.innerText != "undefined") text = thisCell.innerText;
	else text = this.getInnerText(thisCell);	
	
	if(this.sortType == 'price') text = text.substr(1, text.length);
	return text;
}



/**
 * getInnerText(thisCell)
 *
 * extended function to get the innerText of a cell due to browser inconsistances
 * @PARAM thisCell - table cell index
 * @RETURN s - string - innerText of the cell 
 */
function getInnerText(thisCell) {
	var s;
	var children = thisCell.childNodes;
	for(i = 0; i < children.length; i++) {
		switch(children[i].nodeType) {
			case 1: //ELEMENT_NODE
				s += this.getInnerText(children[i]);
				break;
			case 3://TEXT_NODE
				s += children[i].nodeValue;
				break;
		}
	}
	return s.replace('undefined', ''); // fix for Mozilla
}

/**
 * getSortTypeFunc()
 *
 * returns the function to be used for sorting a column depending on it's sortType
 * @RETURN function
 */
function getSortTypeFunc() {
	switch(this.sortType) {
		case 'string':
			return this.textCompare;
		case 'stars':
			return this.basicCompare;
		case 'price':
			return this.basicCompare;
		case 'int':
			return this.basicCompare;
		default:
			return this.basicCompare;
	}
		
}

/**
 * textCompare(n1, n2)
 *
 * function to do basic text comparison
 * @PARAM - n1 - string - text to compare
 * @PARAM - n2 - string - text to compare
 * @RETURN - comparison value - int - the comparison match ( -1 for n1 < n2, 1 for n2 < n1, 0 for exact match)
 */
function textCompare(n1, n2) {
	if(n1.value.toUpperCase() < n2.value.toUpperCase()) return -1;
	else if(n2.value.toUpperCase() < n1.value.toUpperCase()) return 1;
	return 0;
}

/**
 * basicCompare(n1, n2)
 *
 * function to do basic numeric comparison
 * @PARAM - n1 - int - value to compare
 * @PARAM - n2 - int - value to compare
 * @RETURN - comparison value - int - the comparison match ( -1 for n1 < n2, 1 for n2 < n1, 0 for exact match)
 */
function basicCompare(n1, n2) {
	var a = parseInt(n1.value);
	var b = parseInt(n2.value);
	return (b<a)-(a<b); 
}

/**
 * sortCol()
 *
 * performs the column sorting
 */
function sortCol() {
	// return if sortType is none
	if(this.sortType == "none") return;

	// get the function to do the sorting with
	var f = this.getSortTypeFunc();
	
	var a = this.getCache();
	a.sort(eval(f));

	if(this.desc) a.reverse();

	// insert in the new order
	for (i=0; i < a.length; i++) {
		this.tBody.appendChild(a[i].element);
	}

	this.destroyCache(a);	
	
	// run the fake function to stop NS tying itself up
	if(typeof this.onsort == "function") this.onsort();
}


/**
 * getCellIndex(el)
 *
 * gets the table cell index, needed because IE returns wrong cell index if columns are hidden
 * @PARAM el - HTML element - the table cell
 * @RETURN i - the cell index
 */
function getCellIndex(el) {
	var cells = el.parentNode.childNodes;
	for(i=0; cells[i] != el && i < cells.length; i++) {
		// snip
	}
	return i;
}
