/* 
	author: Anatoliy Urbanskiy. 
	email:  anatoliy@cheetahmail.com 
	08.16.09
*/
var Chth = Chth || {};
Chth.stIsIE = /*@cc_on!@*/false;
Chth.DATE_RE = /^(\d\d?)[\/\.:-](\d\d?)[\/\.:-]((\d\d)?\d\d)$/;
Chth.ENTER_KEY = '13';
Chth.freshClickDescFlag = false; // use in the scope of the js native sort function.

Chth.sortTable = function() {
	return {
		init: function(conf) {
			if ( ! conf || ! conf.tableId ) return false;
			if (!document.createElement || !document.getElementsByTagName) return false;
			this.DATE_RE         = Chth.DATE_RE;
			this.tableId         = conf.tableId;
			this.table           = document.getElementById(this.tableId);
			if ( ! this.table ) return false;
			if ( typeof this.table.tBodies[0] == 'undefined' )  return false; 
			if ( ! this.table.tBodies[0].rows.length ) return false;
			this.even_row        = conf.even_row  ? conf.even_row  : 'row_b';
			this.evenRE          = new RegExp('\\b'+this.even_row+'\\b');
			this.odd_row         = conf.odd_row   ? conf.odd_row   :  'row_a';
			this.oddRE           = new RegExp('\\b'+this.odd_row+'\\b');
			this.noDisplay       = conf.noDisplay ? conf.noDisplay : 'noDisplay';
			this.noDRE           = new RegExp('\\b'+this.noDisplay+'\\b');
			this.img_up          = conf.img_up   ? conf.img_up   : Chth.stIsIE 
				? '&nbsp;<font face="webdings">5</font>' : '&#x25B4;';
			this.img_down        = conf.img_down ? conf.img_down : Chth.stIsIE 
				? '&nbsp;<font face="webdings">6</font>' : '&#x25BE;';
			if ( this.table.tBodies[0].rows.length == 1 ) {
				this.img_down = ''; 
				this.img_up = '';
			}
			this.fresh_search    = typeof conf.fresh_search !== 'undefined' ? conf.fresh_search : true;
			this.andFlag         = typeof conf.andFlag      !== 'undefined' ? conf.andFlag      : true;
			this.sortString      = conf.sortMap         ? conf.sortMap         : '';
			this.rowOver         = conf.rowOver         ? conf.rowOver         : '';
			this.searchFlag      = false;
			this.presortedTable  = typeof conf.presortedTable !== 'undefined' ? conf.presortedTable : true;
			this.searchInputId   = conf.searchInputId   ? conf.searchInputId   : '';
			this.searchGoButton  = conf.searchGoButton  ? conf.searchGoButton  : '';
			this.noRecordFoundId = conf.noRecordFoundId ? conf.noRecordFoundId : ''; 
			this.noRecordMsg     = conf.noRecordMsg     ? conf.noRecordMsg     : 'No Record Found';
			this.personSortStr   = conf.personSortMap   ? conf.personSortMap   : {}; //{'5':'1:desc,2:desc'}; 
			this.resetSearchButton     = conf.resetSearchButton     ? conf.resetSearchButton     : '';
			this.useDefaultNoRecord    = typeof conf.useDefaultNoRecord !== 'undefined'
			                             ? conf.useDefaultNoRecord : false;
			this.footerOriginId        = conf.footerOriginId        ? conf.footerOriginId        : '';
			this.footerRecordFoundId   = conf.footerRecordFoundId   ? conf.footerRecordFoundId   : '';
			this.footerRecordFoundTdId = conf.footerRecordFoundTdId ? conf.footerRecordFoundTdId : '';
			this.sortMap             = [];
			this.personSortMap       = {};
			this.freshClickDescOrder = {};
			this.freshClickDescStr   = conf.freshClickDescOrder ? conf.freshClickDescOrder : '';
			var rowLength            = this.table.tBodies[0].rows.length;
			//console.log(rowLength);
			this.maxRowsLength     = conf.maxRowsLength     ? conf.maxRowsLength     : 1000;
			this.maxRowsToStopSort = conf.maxRowsToStopSort ? conf.maxRowsToStopSort : 1200;
			this.gotMaxRows = (rowLength > this.maxRowsLength)     ? true : false;
			this.stopSort   = (rowLength > this.maxRowsToStopSort) ? true : false;
			
			if ( this.gotMaxRows === false && this.sortString.length ) this.sortMap = this.convertToMap(this.sortString);
			if ( this.gotMaxRows === false && this.isNotEmpty(this.personSortStr) ) {
				for ( var el in this.personSortStr ) {
					var ind = el-1;
					this.personSortMap[ind] = this.convertToMap(this.personSortStr[el]);
				}
			}
			if ( this.freshClickDescStr.length ) {
				var fresh = this.freshClickDescStr.replace(/\s+/g,'').split(',');
				var _this = this;
				forEach(fresh,function(el) {
					_this.freshClickDescOrder[el-1] = true;
				});
			}
			this.freshClickDescFlag = Chth.freshClickDescFlag;
			this.sortKeyIndex  = this.sortMap.length ? this.sortMap[0]['index'] : false;
			this.sortKeyOrder  = this.sortMap.length ? this.sortMap[0]['order'] : false; // default ascendatn
			this.secondarySort = (this.sortMap.length || this.isNotEmpty(this.personSortMap)) ? true : false;
			this.headrow       = [];
			this.rowOverFlag   = this.rowOver ? true : false;
			this.pointer       = Chth.stIsIE ? 'hand' : 'pointer';
			this.totalRecords  = 0;
			this.totalRowsShow = 0;

			if ( ! this.stopSort ) {
				this.makeSortable(this.table);
				if ( this.sortMap.length && this.presortedTable === false ) {
					this.sortMe(this.table.tBodies[0],this.sortKeyIndex,this.sortKeyOrder);
				}
			}
			else {
				this.stopSortMessage(this.table,rowLength);
			}

			this.adjustClassName(this.table,this.sortKeyIndex);

			this.tableCaption;
			if ( this.searchInputId ) this.makeTableSearchable();
		},
		convertToMap: function(str) {
			var ary = str.replace(/\s+/g,'').split(',');
			var map = [];
			forEach(ary,function(e){
				var el = e.split(':');
				map.push({'index':parseInt(el[0])-1, 'order': el[1] && /^desc/.test(el[1]) ? false : true}); 
			});
			return map;
		},
		makeSortable: function (table) {
			// Safari doesn't support table.tHead, sigh
			if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
			if (table.tHead.rows.length != 1) return; // can't cope with two header rows

			// work through each column and calculate its type
			this.headrow = table.tHead.rows[0].cells;

			for (var i=0; i<this.headrow.length; i++) {
				// explicitly override the type with a sorttable_type attribute
				if ( ! this.headrow[i].className.match(/\bsorttable_nosort\b/) ) { // skip this col
					var mtch = this.headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
					if (mtch) { var override = mtch[1]; }
					if (mtch && typeof this["sort_"+override] == 'function') {
						this.headrow[i].sorttable_sortfunction = this["sort_"+override];
						this.headrow[i].sorttable_functionname = "sort_"+override;
					} else {
						this.headrow[i].sorttable_sortfunction = this.guessType(table,i);
					}
					this.headrow[i].sorttable_columnindex = i;
					this.headrow[i].sorttable_tbody = table.tBodies[0];
					var myImg = document.createElement('span');
					myImg.className = 'sortionImg';
					myImg.innerHTML = '&nbsp';
					this.headrow[i].appendChild(myImg);
					this.headrow[i].myImg = myImg;
					
					// if there is a sortMap - put the sort-indicated image. (Anatoliy)
					if ( this.sortKeyIndex !== false && this.sortKeyIndex == i ) {
						var cN = this.headrow[i].className || '';
						if ( this.sortKeyOrder ) {
							//console.log('sortKeyOrder:',this.sortKeyOrder);
							cN = cN += ' sorttable_sorted';
							this.headrow[i].myImg.innerHTML = this.img_down;
						}
						else {
							cN = cN += ' sorttable_sorted_reverse';
							this.headrow[i].myImg.innerHTML = this.img_up;
						}
						this.headrow[i].className = cN; 
					}

					// make it clickable to sort
					var _this = this;
					dean_addEvent(this.headrow[i],"mouseover", function(e) {
						document.body.style.cursor = _this.pointer;
					});
					dean_addEvent(this.headrow[i],"mouseout", function(e) {
						document.body.style.cursor = 'default';
					});
					dean_addEvent(this.headrow[i],"mousedown", function(e) {
						document.body.style.cursor = 'wait';
						this.style.cursor = 'wait';
					});
					dean_addEvent(this.headrow[i],"click", function(e) {

						var col   = this.sorttable_columnindex;
						var tbody = this.sorttable_tbody;

						// our previous click was on this column
						if (this.className.search(/\bsorttable_sorted\b/) != -1) {
							this.className = this.className.replace('sorttable_sorted',
																'sorttable_sorted_reverse');
							this.myImg.innerHTML = _this.img_up;
							if ( _this.secondarySort ) {
								_this.sortMe(tbody,col,true);
								_this.adjustClassName(table,col);
								_this.togleCursor(this);
								return;
							}
							else {
								// if no sortMap - just revers whole table
								_this.reverse(tbody);
								_this.togleCursor(this);
								return;
							}
						}
						// our previous click was on this column
						if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
							this.className = this.className.replace('sorttable_sorted_reverse',
																'sorttable_sorted');
							this.myImg.innerHTML = _this.img_down;
							if ( _this.secondarySort ) {
								_this.sortMe(tbody,col,false);
								_this.adjustClassName(table,col);
								_this.togleCursor(this);
								return;
							}
							else {
								// if no sortMap - just revers whole table
								_this.reverse(tbody);
								_this.togleCursor(this);
								return;
							}
						}

						// The first click or the fresh click on the column.
						
						// remove sorttable_sorted, sorttable_sorted_reverse classes.
						var theadrow = this.parentNode;
						forEach(theadrow.childNodes, function(cell) {
							if (cell.nodeType == 1) { // an element
								cell.className = cell.className.replace('sorttable_sorted_reverse','');
								cell.className = cell.className.replace('sorttable_sorted','');
							}
						});
						// remove sort-indicated images.
						forEach(_this.headrow, function(item) {
							if (item && item.myImg) item.myImg.innerHTML = '&nbsp;';
						});

						// if our first column in the sortMap has descanding order. 
						// or the this column exists in the _this.freshClickDescOrder (Anatoliy).
						if ( (_this.sortMap.length && this.sorttable_columnindex == _this.sortKeyIndex 
								&& _this.sortKeyOrder === false) 
								|| typeof _this.freshClickDescOrder[this.sorttable_columnindex] !== 'undefined' ) {
							this.className += ' sorttable_sorted_reverse';
							this.myImg.innerHTML = _this.img_up;
						}
						else {
							this.className += ' sorttable_sorted';
							this.myImg.innerHTML = _this.img_down;
						}

						// if multilevel sorting required - use a 'selection sort' algorithm.
						// Wikipedia: "There is no other algorithm with less data movement."
						// a fresh click for the sorting on this column.
						// sort with multilevel sort in ascanding order. 
						// Exception: the first column of the sortMap or
						// freshClickDescOrder for this column is defined. (Anatoliy)
						if ( _this.secondarySort ) {
							var ord = (this.sorttable_columnindex == _this.sortKeyIndex
									&& _this.sortKeyOrder === false ) ? true : false;
							if ( typeof _this.freshClickDescOrder[this.sorttable_columnindex] !== 'undefined' ) {
								ord = true; // fresh click in descendant order.
							}
							//console.log('Sort order is: ',ord);
							_this.sortMe(tbody,col,ord);
							_this.adjustClassName(table,col);
						}
						else { // use native Array.sort javascript function
							var rows = this.sorttable_tbody.rows;
							var row_array = [];

							if ( typeof _this.freshClickDescOrder[this.sorttable_columnindex] !== 'undefined' ) {
								Chth.freshClickDescFlag = true;
							}
							for (var j=0; j<rows.length; j++) {
								row_array[row_array.length] = [_this.getInnerText(rows[j].cells[col]), rows[j]];
							}
							row_array.sort(this.sorttable_sortfunction);

							for (var j=0; j<row_array.length; j++) {
								tbody.appendChild(row_array[j][1]);
							}
							delete row_array;
							_this.adjustClassName(table);
							Chth.freshClickDescFlag = false;
						}
						_this.togleCursor(this);
					});
				}
			}
		},
		sortMe: function (tbody,col,descandant) {
			var rows = tbody.rows;
			if ( this.secondarySort ) {
				if ( typeof rows[col] === 'undefined' ) rows[col] = {};
				if ( ! rows[col].sort_key ) rows[col].sort_key = [];
				if ( ! rows[col].sort_revers_key ) rows[col].sort_revers_key = [];
			}
			var revers = descandant ? typeof(rows[0].sort_revers_key[col]) : typeof(rows[0].sort_key[col]);

			if ( this.presortedTable && revers !== 'undefined' ) {
				// no sorting! just reordering according to the previously created sort_keys
				// or sort_revers_key.
				// the sort_[revers_]keys are created in the this.adjustClassName. (Anatoliy).
				var row_array = [];
				var len = rows.length;
				for ( var i=0;i<len;i++ ) {
					var key = descandant ? rows[i].sort_revers_key[col] : rows[i].sort_key[col];
					row_array[key] = rows[i];
				}
				for (var j=0; j<row_array.length; j++) {
					tbody.appendChild(row_array[j]);
				}
				delete row_array;
				//console.log('just reordering');
			}
			else { // the real multilevel sorting algorithm
				var sortFunction = this.headrow[col].sorttable_functionname;
				var minVal, minInd;
				var ascend = /\bsorttable_sorted\b/.test(this.headrow[col].className);
				// the personSortMap contains the sortMap for specified column.
				var sortAray = typeof(this.personSortMap[col]) !== 'undefined' 
					? this.personSortMap[col] : this.sortMap;

				for ( var i=0;i<rows.length-1;i++ ) {
					minInd = i;
					minVal = this.getInnerText(rows[i].cells[col]);
					var cmp = 0;
					var aryEquals = [i];
					
					for ( var j=i+1;j<rows.length;j++ ) {
						var testVal = this.getInnerText(rows[j].cells[col]);
						// first and second arguments must be one element array for the compatibility 
						// with the rest of sorting functionality (Anatoliy).
						cmp = this.compareValues([minVal],[testVal],sortFunction,ascend);

						if ( cmp > 0 ) {
							minInd = j; minVal = testVal; aryEquals = [j];
							continue;
						}
						if ( cmp == 0 ) { aryEquals.push(j); }
					}
					if ( this.secondarySort && aryEquals.length > 1) {
						
						for ( var k=0;k<sortAray.length;k++ ) {
							var f = sortAray[k];
							if ( f.index == col ) continue;
							
							minInd = aryEquals[0];
							var secondaryAry = [minInd];
							minVal = this.getInnerText(rows[minInd].cells[f.index]);
							var sF = this.headrow[f.index].sorttable_functionname;
							
							for ( var r=1;r<aryEquals.length;r++ ) {
								var rInd = aryEquals[r];
								var testV = this.getInnerText(rows[rInd].cells[f.index]);
								cmp = this.compareValues([minVal],[testV],sF,f.order);
								if ( cmp > 0 ) {
									minInd = rInd; minVal = testV; secondaryAry = [rInd];
									continue;
								}
								if ( cmp == 0 ) { secondaryAry.push(rInd); }
							}
							if ( secondaryAry.length == 1 ) { break; }
							else { aryEquals = secondaryAry; }
						}
					}
					if ( minInd > i ) {
						var tmpEl = tbody.removeChild(tbody.rows[minInd]);
						tbody.insertBefore(tmpEl, tbody.rows[i]);
						//console.error('moved from ', minInd, ' before:',i);
					}
				}
			}
		},
		compareValues: function (v1,v2,sortFunction,ascending) {
			var cmp = this[sortFunction](v1,v2);
			if ( typeof cmp === 'undefined' ) {
				return 0;
			}
			return ascending ? cmp : -cmp;
		},
		guessType: function (table, column) {
			// guess the type of a column based on its first non-blank row
			var sortfn = this.sort_alpha;

			for (var i=0; i<table.tBodies[0].rows.length; i++) {
				var text = this.getInnerText(table.tBodies[0].rows[i].cells[column]);
				if (text != '') {
					// check for a date: dd/mm/yyyy or dd/mm/yy 
					// can have / or . or - as separator
					// can be mm/dd as well
					var possdate = text.match(this.DATE_RE);
					if (text.match(/^-?[£$¤]?[\d,.]+%?$/) && !possdate) {
						this.headrow[column].sorttable_functionname = 'sort_numeric'; 
						return this.sort_numeric;
					}
					if (possdate) {
						// looks like a date
						var first  = parseInt(possdate[1]);
						var second = parseInt(possdate[2]);
						if (first > 12) {
							// definitely dd/mm
							this.headrow[column].sorttable_functionname = 'sort_ddmm'; 
							return this.sort_ddmm;
						} else if (second > 12) {
							this.headrow[column].sorttable_functionname = 'sort_mmdd'; 
							return this.sort_mmdd;
						} else {
							// looks like a date, but we can't tell which, so assume that it's mm/dd
							this.headrow[column].sorttable_functionname = 'sort_mmdd'; 
							return this.sort_mmdd;
						}
					}
					if ( sortfn ) {
						this.headrow[column].sorttable_functionname = 'sort_alpha'; 
						return this.sort_alpha;
					}
				}
			}
		},
		getInnerText: function(node,custom) {

			if ( ! node ) { return ''; }
			// we can dynamically switch off the customkey to use just textContent/innerText
			if (! custom && node.getAttribute("sorttable_customkey") != null) {
				return node.getAttribute("sorttable_customkey");
			}
			else if ( typeof(node.textContent) !== 'undefined' ) {
				return node.textContent.replace(/^\s+|\s+$/g, '');
			}
			else if ( typeof(node.innerText) !== 'undefined' ) {
				return node.innerText.replace(/^\s+|\s+$/g, '');
			}
			else if ( typeof(node.text) !== 'undefined' ) {
				return node.text.replace(/^\s+|\s+$/g, '');
			}
			else { return ''; }
		},
		
		findRows: function(pattern){
			var t = this.table;
			var rows = t.tBodies[0].rows;
			
			var patterns = this.andFlag ? pattern.split(/\s+/) : pattern.split(/\s+/).join('|');
			var noRecordFound = 0;

			for (var j=0; j<rows.length; j++) {
				var myClass = rows[j].className;

				if ( pattern != '' ) { // refresh
					var str = '';
					if ( typeof rows[j].textContent !== 'undefined' ) {
						str = rows[j].textContent;
					}
					else { 
						// IE innerText is a string without any separator for multielements.
						// If the first value is 12 and the second one is 34
						// innerText returns 1234 and pattern 23 will be match! (Anatoliy).
						var ary = [];
						forEach(rows[j].childNodes, function(cell) {
							if (cell.nodeType == 1) { // an element
								var it = cell.innerText;
								if ( it.length ) ary.push(it);
							}
						});
						str = ary.join('\n');
					}
					var iFound = this.andFlag ? this.allPatternMatch(str,patterns) : str.indexOf(patterns.toLowerCase());
					if ( iFound == -1 ) { // no match
						var s = myClass.replace(this.oddRE,'').replace(this.evenRE,'');
						if ( ! this.noDRE.test(s) ) {
							rows[j].className = s+' '+this.noDisplay;
						}
					}
					else {
						if ( this.fresh_search ) {
							rows[j].className = myClass.replace(this.noDRE,'');
							noRecordFound++;
						}
					}
				}
				else {
					rows[j].className = myClass.replace(this.noDRE,'');
					noRecordFound++;
				}
			}
			if ( this.tableCaption ) { 
				this.tableCaption.style.display = noRecordFound ? 'none' 
					: Chth.stIsIE ? 'block' 
					: this.noRecordFoundId ? 'block' : 'table-caption';
			}
			this.adjustClassName(t);
			if ( patterns != '' ) {
				var resB = document.getElementById(this.resetSearchButton);
				if ( resB ) { resB.disabled = false }
			}
			else {
				var resB = document.getElementById(this.resetSearchButton);
				if ( resB ) { resB.disabled = true }
			}
		},
		allPatternMatch: function(str,patterns) {
			for ( var k=0;k<patterns.length;k++ ) {
				var s = str.toLowerCase();
				if ( s.indexOf(patterns[k].toLowerCase()) == -1 ) {
					return -1;
				}
			}
			return k;
		},

		togleCursor: function(cell) {
			var tc = document.body.style.cursor || 'default';
			document.body.style.cursor = 'default';
			cell.style.cursor = this.pointer;
		},
		
		/* sort functions */
		sort_numeric: function(a,b) {
			var aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
			if (isNaN(aa)) aa = 0;
			var bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); 
			if (isNaN(bb)) bb = 0;
			return (Chth.freshClickDescFlag === false) ? aa-bb : bb-aa;
		},
		sort_alpha: function(a,b) {
			if (a[0].toLowerCase() == b[0].toLowerCase()) return 0;
			var cmp = (a[0].toLowerCase() <  b[0].toLowerCase()) ? -1 : 1;
			return (Chth.freshClickDescFlag === false) ? cmp : -cmp;
		},
		// when this function is called from Array.sort a 'this' belongs to the window.
		// if it called from the 'this.sortMe' - the 'this' is a current object.
		// that is why the DATE_RE variable is a global (Anatoliy).
		sort_ddmm: function(a,b) {
			var mtch1 = a[0].match(Chth.DATE_RE);
			if ( mtch1 == null ) { var dt1 = 0; }
			else {
				var y1 = mtch1[3]; m1 = mtch1[2]; d1 = mtch1[1];
				if (m1.length == 1) m = '0'+m1;
				if (d1.length == 1) d = '0'+d1;
				var dt1 = y1+m1+d1;
			}
			var mtch2 = b[0].match(Chth.DATE_RE);
			if ( mtch2 == null ) { dt2 = 0; }
			else {
				var y2 = mtch[3]; var m2 = mtch2[2]; var d2 = mtch2[1];
				if (m2.length == 1) m2 = '0'+m2;
				if (d2.length == 1) d2 = '0'+d2;
				dt2 = y2+m2+d2;
			}
			if (dt1==dt2) return 0;
			var cmp = (dt1<dt2) ? -1 : 1;
			return (Chth.freshClickDescFlag === false) ? cmp : -cmp;
		},
		sort_mmdd: function(a,b) {
			var mtch1 = a[0].match(Chth.DATE_RE);
			if ( mtch1 == null ) { var dt1 = 0; }
			else {
				var m1 = mtch1[1]; var d1 = mtch1[2]; var y1 = mtch1[3];
				if (m1.length == 1) m1 = '0'+m1;
				if (d1.length == 1) d1 = '0'+d1;
				var dt1 = y1+m1+d1;
			}
			var mtch2 = b[0].match(Chth.DATE_RE);
			if ( mtch2 == null ) { var dt2 = 0; }
			else {
				var m2 = mtch2[1]; var d2 = mtch2[2]; var y2 = mtch2[3];
				if (m2.length == 1) m2 = '0'+m2;
				if (d2.length == 1) d2 = '0'+d2;
				var dt2 = y2+m2+d2;
			}
			if (dt1==dt2) return 0;
			var cmp = (dt1<dt2) ? -1 : 1;
			return (Chth.freshClickDescFlag === false) ? cmp : -cmp;
		},

		reverse: function(tbody) {
			// reverse the rows in a tbody
			var newrows = [];
			for (var i=0; i<tbody.rows.length; i++) {
				newrows[newrows.length] = tbody.rows[i];
			}
			for (var i=newrows.length-1; i>=0; i--) {
				tbody.appendChild(newrows[i]);
			}
			delete newrows;
			this.adjustClassName(tbody.parentNode);
		},

		adjustClassName: function(table,col) {
			var counter = 0;
			var rows   = table.tBodies[0].rows;
			this.totalRecords = this.totalRecords ? this.totalRecords : rows.length;
			if ( this.secondarySort && this.headrow.length && typeof col !== 'undefined' ) {
				var cN = this.headrow[col].className || '';
				var descOrd = cN.search(/\bsorttable_sorted_reverse\b/) != -1 ? true : false;
			}

			for (var i=0; i<rows.length; i++) {
				var myClass = rows[i].className;
				if ( ! this.noDRE.test(myClass) ) {
					if ( counter % 2) {
						if ( this.oddRE.test(myClass) ) {
							rows[i].className = myClass.replace(this.oddRE,this.even_row);
						}
						else {
							if ( ! this.evenRE.test(myClass) ) {
								rows[i].className = myClass+' '+this.even_row;
							}
						}
					}
					else {
						if ( this.evenRE.test(myClass) ) {
							rows[i].className = myClass.replace(this.evenRE,this.odd_row);
						}
						else {
							if ( ! this.oddRE.test(myClass) ) {
								rows[i].className = myClass+' '+this.odd_row;
							}
						}
					}
					counter++;
				}
				if ( this.rowOverFlag ) {
					var _this = this;
					var rowRE = new RegExp('\\b'+_this.rowOver+'\\b','g');
					dean_addEvent(rows[i],"mouseover", function(e) {
						this.className = this.className+' '+_this.rowOver;
					});
					dean_addEvent(rows[i],"mouseout", function(e) {
						this.className = this.className.replace(rowRE,'');
					});
				}
				
				if ( typeof col !== 'undefined' ) {
					if ( this.secondarySort && ! rows[i].sort_key ) rows[i].sort_key = [];
					if ( this.secondarySort && ! rows[i].sort_revers_key ) rows[i].sort_revers_key = [];
					if ( descOrd ) {
						if ( this.secondarySort && parseInt(col) >= 0 
							&& (typeof(rows[i].sort_revers_key[col]) === 'undefined') ) { 
							// do it only ones, first time
							rows[i].sort_revers_key[col] = i;
							//console.log(rows[i].sort_revers_key);
						}
					}
					else {
						if ( this.secondarySort && parseInt(col) >= 0 
							&& (typeof(rows[i].sort_key[col]) === 'undefined') ) { 
							// do it only ones, first time
							rows[i].sort_key[col] = i;
							//console.log(rows[i].sort_key);
						}
					}
				}
			}
			this.totalRowsShow = counter;
			if ( this.footerRecordFoundId ) {
				this.displayTotalRecords(counter);
			}
			this.rowOverFlag = false; // do it just ones, first time
		},
		displayTotalRecords: function(counter) {

			var display = Chth.stIsIE ? 'block' : 'table-row';
			var htd = this.footerRecordFoundTdId ? this.footerRecordFoundTdId : this.footerRecordFoundId+'TD';

			var orig   = this.footerOriginId ? document.getElementById(this.footerOriginId) : '';
			var hidd   = document.getElementById(this.footerRecordFoundId);
			var hiddTD = document.getElementById(htd);
			
			//if ( counter < this.totalRecords ) {
			if ( this.searchFlag ) {
				if ( counter == 0 ) {
					hiddTD.innerHTML = this.noRecordMsg;
				}
				else {
					var s = counter > 1 ? 's' : '';
					hiddTD.innerHTML = '<span>'+counter+'</span> Record'+s+' Found';
				}
				if ( orig ) { orig.style.display = 'none'; }
				hidd.style.display = display;
			}
			else {
				if ( orig ) { orig.style.display = display; }
				hidd.style.display = 'none';
			}
		},
		searchPattern: function() {
			var s = document.getElementById(this.searchInputId);
			var pattern = s.value.replace(/^\s+|\s+$/g, '').replace(/\$/g,"\\\$");
			this.searchFlag = true;
			this.findRows(pattern);
		},
		checkEnterKey: function(e) {
			var key = e.which ? e.which : e.keyCode;
			return key == Chth.ENTER_KEY ? true : false;
		},
		makeTableSearchable: function() {
			var searchInput = document.getElementById(this.searchInputId);
			
			if ( searchInput ) {
				var _this = this;
				searchInput.onkeypress = function(event){
					event = event || window.event;
					if ( _this.checkEnterKey(event) === true ) {
						_this.searchPattern();
						// if any submit button exists - do not submit a form
						return false; 
					}
				};
				if ( this.searchGoButton ) {
					var go = document.getElementById(this.searchGoButton);
					if ( go ) go.onclick = function(){ 
						_this.searchPattern(); 
					}
				}
				if ( this.resetSearchButton ) {
					var resB = document.getElementById(this.resetSearchButton);
					if ( resB ) {
						resB.onclick = function(e){ 
							_this.searchFlag = false;
							_this.findRows('');
							document.getElementById(_this.searchInputId).value = '';
							resB.disabled = true;
						}
					}
				}
				if ( this.useDefaultNoRecord ) {
					var cap;
					var cap = this.noRecordFoundId 
						? document.getElementById(this.noRecordFoundId) 
						: document.createElement('caption');
					if ( !cap ) return;
					if ( ! this.noRecordFoundId ) {
						cap.style.captionSide = 'bottom';
						this.table.appendChild(cap);
					}
					cap.className += ' searchTableCaption';
					cap.innerHTML = this.noRecordMsg;
					cap.style.display = 'none';
					this.tableCaption = cap;
				}
			}
		},
		stopSortMessage: function(table,rowLength) {
			var _this = this;
			// Safari doesn't support table.tHead, sigh
			if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
			var thead = table.tHead.rows[0].cells;

			for ( var i=0;i<thead.length;i++ ) {
				if ( ! thead[i].className.match(/\bsorttable_nosort\b/) ) {
					dean_addEvent(thead[i],"mouseover",function(e){
						this.style.cursor = _this.pointer;
					});
					dean_addEvent(thead[i],"mouseout",function(e){
						this.style.cursor = 'default';
					});
					dean_addEvent(thead[i],"click",function(e){
						var str = "The list you are viewing is too large to be sorted.\n";
						str += "Total rows in the list: "+rowLength+".\n";
						str += "The maximum number of rows allowed for sorting: "+_this.maxRowsToStopSort+".\n";
						alert(str);	
					});
				}
			}
		},
		isNotEmpty: function(obj) {
			for ( var tmp in obj ) return true;
			return false;
		},
		escapeString: function(str) {
			return str.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g,"\\$1");
		}
	};
};
/* ******************************************************************
   Supporting functions: bundled here to avoid depending on a library
   ****************************************************************** */

// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini

// http://dean.edwards.name/weblog/2005/10/add-event/

function dean_addEvent(element, type, handler) {
	if (element.addEventListener) {
		element.addEventListener(type, handler, false);
	} else {
		// assign each event handler a unique ID
		if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
		// create a hash table of event types for the element
		if (!element.events) element.events = {};
		// create a hash table of event handlers for each element/event pair
		var handlers = element.events[type];
		if (!handlers) {
			handlers = element.events[type] = {};
			// store the existing event handler (if there is one)
			if (element["on" + type]) {
				handlers[0] = element["on" + type];
			}
		}
		// store the event handler in the hash table
		handlers[handler.$$guid] = handler;
		// assign a global event handler to do all the work
		element["on" + type] = handleEvent;
	}
};
// a counter used to create unique IDs
dean_addEvent.guid = 1;

function removeEvent(element, type, handler) {
	if (element.removeEventListener) {
		element.removeEventListener(type, handler, false);
	} else {
		// delete the event handler from the hash table
		if (element.events && element.events[type]) {
			delete element.events[type][handler.$$guid];
		}
	}
};

function handleEvent(event) {
	var returnValue = true;
	// grab the event object (IE uses a global event object)
	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
	// get a reference to the hash table of event handlers
	var handlers = this.events[event.type];
	// execute each event handler
	for (var i in handlers) {
		this.$$handleEvent = handlers[i];
		if (this.$$handleEvent(event) === false) {
			returnValue = false;
		}
	}
	return returnValue;
};

function fixEvent(event) {
	// add W3C standard event methods
	event.preventDefault = fixEvent.preventDefault;
	event.stopPropagation = fixEvent.stopPropagation;
	return event;
};
fixEvent.preventDefault = function() {
	this.returnValue = false;
};
fixEvent.stopPropagation = function() {
  this.cancelBubble = true;
}

// Dean's forEach: http://dean.edwards.name/base/forEach.js
/*
	forEach, version 1.0
	Copyright 2006, Dean Edwards
	License: http://www.opensource.org/licenses/mit-license.php
*/

// array-like enumeration
if (!Array.forEach) { // mozilla already supports this
	Array.forEach = function(array, block, context) {
		for (var i = 0; i < array.length; i++) {
			block.call(context, array[i], i, array);
		}
	};
}

// generic enumeration
Function.prototype.forEach = function(object, block, context) {
	for (var key in object) {
		if (typeof this.prototype[key] == "undefined") {
			block.call(context, object[key], key, object);
		}
	}
};

// character enumeration
String.forEach = function(string, block, context) {
	Array.forEach(string.split(""), function(chr, index) {
		block.call(context, chr, index, string);
	});
};

// globally resolve forEach enumeration
var forEach = function(object, block, context) {
	if (object) {
		var resolve = Object; // default
		if (object instanceof Function) {
			// functions have a "length" property
			resolve = Function;
		} else if (object.forEach instanceof Function) {
			// the object implements a custom forEach method so use that
			object.forEach(block, context);
			return;
		} else if (typeof object == "string") {
			// the object is a string
			resolve = String;
		} else if (typeof object.length == "number") {
			// the object is array-like
			resolve = Array;
		}
		resolve.forEach(object, block, context);
	}
};

// the save way to use 'onload' window event
function addOnLoadEvent(func) { 
	var oldonload = window.onload; 
	if (typeof window.onload !== 'function') { 
		window.onload = func; 
	} else { 
		window.onload = function() { 
			if (oldonload) { 
				oldonload(); 
			} 
			func(); 
		} 
	} 
} 


