/*====================================================
	- Javascript for SBD Nicer Tables
	- By Matthew Cooper
	- http://www.simplebydesign.co.uk/joomla/mambots/sbd-nicer-tables.html
=====================================================*/


// Title: tigra tables
// URL: http://www.softcomplex.com/products/tigra_tables/
// Version: 1.1
// Date: 08/15/2005
// Notes: Permission given to use this script in any kind of applications if
//    header lines are left unchanged.

function tigra_tables (
		str_tableid, // table id (req.)
		num_header_offset, // how many rows to skip before applying effects at the begining (opt.)
		num_footer_offset, // how many rows to skip at the bottom of the table (opt.)
		str_odd_color, // background color for odd rows (opt.)
		str_even_color, // background color for even rows (opt.)
		str_mover_color, // background color for rows with mouse over (opt.)
		str_onclick_color, // background color for marked rows (opt.)
		str_odd_style,
		str_even_style,
		str_mover_style,
		str_onclick_style,
		str_use_css
	) {
	//alert( str_tableid );
	// validate required parameters
	if (!str_tableid) return alert ("No table(s) ID specified in parameters");
	var obj_tables = (document.all ? document.all[str_tableid] : document.getElementById(str_tableid));
	if (!obj_tables) return alert ("Can't find table(s) with specified ID (" + str_tableid + ")");

	obj_tables.header_offset = (num_header_offset ? num_header_offset : 0);
	obj_tables.footer_offset = (num_footer_offset ? num_footer_offset : 0);
	// set defaults for optional parameters
	var col_config = [];
	col_config.header_offset = (num_header_offset ? num_header_offset : 0);
	col_config.footer_offset = (num_footer_offset ? num_footer_offset : 0);
	col_config.odd_color = (str_odd_color ? str_odd_color : '#ffffff');
	col_config.even_color = (str_even_color ? str_even_color : '#dbeaf5');
	col_config.mover_color = (str_mover_color ? str_mover_color : '#6699cc');
	col_config.onclick_color = (str_onclick_color ? str_onclick_color : '#4C7DAB');
	col_config.odd_style = (str_odd_style ? str_odd_style : 'sortTableOdd');
	col_config.even_style = (str_even_style ? str_even_style : 'sortTableEven');
	col_config.mover_style = (str_mover_style ? str_mover_style : 'sortTableMover');
	col_config.onclick_style = (str_onclick_style ? str_onclick_style : 'sortTableOnclick');
	col_config.use_css = (str_use_css ? str_use_css : 0 );

	
	
	// init multiple tables with same ID
	if (obj_tables.length)
		for (var i = 0; i < obj_tables.length; i++)
			tt_init_table(obj_tables[i], col_config);
	// init single table
	else
		tt_init_table(obj_tables, col_config);
}

function tt_init_table (obj_table, col_config) {
	var col_lconfig = [],
		col_trs = obj_table.rows;
		
	if (!col_trs) return;
	for (var i = col_config.header_offset; i < col_trs.length - col_config.footer_offset; i++) {
		col_trs[i].config = col_config;
		col_trs[i].lconfig = col_lconfig;
		col_trs[i].set_color = tt_set_color;
		col_trs[i].onmouseover = tt_mover; 
		col_trs[i].onmouseout = tt_mout;
		col_trs[i].onmousedown = tt_onclick;
		col_trs[i].order = (i - col_config.header_offset) % 2;
		col_trs[i].onmouseout();
	}
}
function tt_set_color(str_color, str_style) {
		if( str_style == "" || str_style == "sbdna" )
		{
				this.style.backgroundColor = str_color;
				
				}
		else
		{
				this.className = str_style;
				}
}

// event handlers
function tt_mover () {
	if (this.lconfig.clicked != this)
		this.set_color(this.config.mover_color, this.config.mover_style);

}
function tt_mout () {
	if (this.lconfig.clicked != this)
		this.set_color(this.order ? this.config.odd_color : this.config.even_color, this.order ? this.config.odd_style : this.config.even_style);
}
function tt_onclick () {
	if (this.lconfig.clicked == this) {
		this.lconfig.clicked = null;
		this.onmouseover();
	}
	else {
		var last_clicked = this.lconfig.clicked;
		this.lconfig.clicked = this;
		if (last_clicked) last_clicked.onmouseout();
		this.set_color(this.config.onclick_color, this.config.onclick_style);
	}
}



/*====================================================
	- HTML Table Filter Generator v1.6
	- By Max Guglielmi
	- mguglielmi.free.fr/scripts/TableFilter/?l=en
	- please do not change this comment
	- don't forget to give some credit... it's always
	good for the author
	- Special credit to Cedric Wartel and 
	cnx.claude@free.fr for contribution and 
	inspiration
=====================================================*/

// global vars
var TblId, SearchFlt, SlcArgs;
TblId = new Array();
SlcArgs = new Array();


function setFilterGrid(id)
/*====================================================
	- Checks if id exists and is a table
	- Then looks for additional params 
	- Calls fn that generates the grid
=====================================================*/
{	
	var tbl = grabEBI(id);
	var ref_row, fObj;
	if(tbl != null && tbl.nodeName.toLowerCase() == "table")
	{						
		if(arguments.length>1)
		{
			for(var i=0; i<arguments.length; i++)
			{
				var argtype = typeof arguments[i];
				
				switch(argtype.toLowerCase())
				{
					case "number":
						ref_row = arguments[i];
					break;
					case "object":
						fObj = arguments[i];
					break;
				}//switch
							
			}//for
		}//if
		
		ref_row == undefined ? ref_row=2 : ref_row=(ref_row+2);
		var ncells = getCellsNb(id,ref_row);
		tbl.tf_ncells = ncells;
		if(tbl.tf_ref_row==undefined) tbl.tf_ref_row = ref_row;
		tbl.tf_Obj = fObj;
		if( !hasGrid(id) ) AddGrid(id);		
	}//if tbl!=null
}

function AddGrid(id)
/*====================================================
	- adds a row containing the filtering grid
=====================================================*/
{	
	TblId.push(id);
	var t = grabEBI(id);
	var f = t.tf_Obj, n = t.tf_ncells;	
	var inpclass, fltgrid, displayBtn, btntext, enterkey;
	var modfilter_fn, display_allText, on_slcChange;
	var displaynrows, totrows_text, btnreset, btnreset_text;
	var sort_slc, displayPaging, pagingLength, displayLoader;
	var load_text, exactMatch, alternateBgs, colOperation;
	var rowVisibility, colWidth, bindScript;
	
	f!=undefined && f["grid"]==false ? fltgrid=false : fltgrid=true;//enables/disables filter grid
	f!=undefined && f["btn"]==true ? displayBtn=true : displayBtn=false;//show/hides filter's validation button
	f!=undefined && f["btn_text"]!=undefined ? btntext=f["btn_text"] : btntext="go";//defines button text
	f!=undefined && f["enter_key"]==false ? enterkey=false : enterkey=true;//enables/disables enter key
	f!=undefined && f["mod_filter_fn"] ? modfilter_fn=true : modfilter_fn=false;//defines alternative fn
	f!=undefined && f["display_all_text"]!=undefined ? display_allText=f["display_all_text"] : display_allText="";//defines 1st option text
	f!=undefined && f["on_change"]==false ? on_slcChange=false : on_slcChange=true;//enables/disables onChange event on combo-box 
	f!=undefined && f["rows_counter"]==true ? displaynrows=true : displaynrows=false;//show/hides rows counter
	f!=undefined && f["rows_counter_text"]!=undefined ? totrows_text=f["rows_counter_text"] : totrows_text="Displayed rows: ";//defines rows counter text
	f!=undefined && f["btn_reset"]==true ? btnreset=true : btnreset=false;//show/hides reset link
	f!=undefined && f["btn_reset_text"]!=undefined ? btnreset_text=f["btn_reset_text"] : btnreset_text="Reset";//defines reset text
	f!=undefined && f["sort_select"]==true ? sort_slc=true : sort_slc=false;//enables/disables select options sorting
	f!=undefined && f["paging"]==true ? displayPaging=true : displayPaging=false;//enables/disables table paging
	f!=undefined && f["paging_length"]!=undefined ? pagingLength=f["paging_length"] : pagingLength=10;//defines table paging length
	f!=undefined && f["loader"]==true ? displayLoader=true : displayLoader=false;//enables/disables loader
	f!=undefined && f["loader_text"]!=undefined ? load_text=f["loader_text"] : load_text="Loading...";//defines loader text
	f!=undefined && f["exact_match"]==true ? exactMatch=true : exactMatch=false;//enables/disbles exact match for search
//	f!=undefined && f["alternate_rows"]==true ? alternateBgs=true : alternateBgs=false;//enables/disbles rows alternating bg colors Disabled Simplebydesign
	f!=undefined && f["col_operation"] ? colOperation=true : colOperation=false;//enables/disbles column operation(sum,mean)
	f!=undefined && f["rows_always_visible"] ? rowVisibility=true : rowVisibility=false;//makes a row always visible
	f!=undefined && f["col_width"] ? colWidth=true : colWidth=false;//defines widths of columns
	f!=undefined && f["bind_script"] ? bindScript=true : bindScript=false;

	alternateBgs=true; //forced always to true
	
	// props are added to table in order to be easily accessible from other fns
	t.tf_fltGrid			=	fltgrid;
	t.tf_displayBtn			= 	displayBtn;
	t.tf_btnText			=	btntext;
	t.tf_enterKey			= 	enterkey;
	t.tf_isModfilter_fn		= 	modfilter_fn;
	t.tf_display_allText 	= 	display_allText;
	t.tf_on_slcChange 		= 	on_slcChange;
	t.tf_rowsCounter 		= 	displaynrows;
	t.tf_rowsCounter_text	= 	totrows_text;
	t.tf_btnReset 			= 	btnreset;
	t.tf_btnReset_text 		= 	btnreset_text;
	t.tf_sortSlc 			=	sort_slc;
	t.tf_displayPaging 		= 	displayPaging;
	t.tf_pagingLength 		= 	pagingLength;
	t.tf_displayLoader		= 	displayLoader;
	t.tf_loadText			= 	load_text;
	t.tf_exactMatch 		= 	exactMatch;
	t.tf_alternateBgs		=	alternateBgs;
	t.tf_startPagingRow		= 	0;
	
	if(modfilter_fn) t.tf_modfilter_fn = f["mod_filter_fn"];// used by DetectKey fn

	if(fltgrid)
	{
		var fltrow = t.insertRow(0); //adds filter row
		fltrow.className = "fltrow";
		for(var i=0; i<n; i++)// this loop adds filters
		{
			var fltcell = fltrow.insertCell(i);
			//fltcell.noWrap = true;
			i==n-1 && displayBtn==true ? inpclass = "flt_s" : inpclass = "flt";
			
			if(f==undefined || f["col_"+i]==undefined || f["col_"+i]=="none") 
			{
				var inptype;
				(f==undefined || f["col_"+i]==undefined) ? inptype="text" : inptype="hidden";//show/hide input	
				var inp = createElm( "input",["id","flt"+i+"_"+id],["type",inptype],["class",inpclass] );					
				inp.className = inpclass;// for ie<=6
				fltcell.appendChild(inp);
				if(enterkey) inp.onkeypress = DetectKey;
			}
			else if(f["col_"+i]=="select")
			{
				var slc = createElm( "select",["id","flt"+i+"_"+id],["class",inpclass] );
				slc.className = inpclass;// for ie<=6
				fltcell.appendChild(slc);
				PopulateOptions(id,i);
				if(displayPaging)//stores arguments for GroupByPage() fn
				{
					var args = new Array();
					args.push(id); args.push(i); args.push(n);
					args.push(display_allText); args.push(sort_slc); args.push(displayPaging);
					SlcArgs.push(args);
				}
				if(enterkey) slc.onkeypress = DetectKey;
				if(on_slcChange) 
				{
					(!modfilter_fn) ? slc.onchange = function(){ Filter(id); } : slc.onchange = f["mod_filter_fn"];
				} 
			}
			
			if(i==n-1 && displayBtn==true)// this adds button
			{
				var btn = createElm(
										"input",
										["id","btn"+i+"_"+id],["type","button"],
										["value",btntext],["class","btnflt"] 
									);
				btn.className = "btnflt";
				
				fltcell.appendChild(btn);
				(!modfilter_fn) ? btn.onclick = function(){ Filter(id); } : btn.onclick = f["mod_filter_fn"];					
			}//if		
			
		}// for i		
	}//if fltgrid

	if(displaynrows || btnreset || displayPaging || displayLoader)
	{
		/*** div containing rows # displayer + reset btn ***/
		var infdiv = createElm( "div",["id","inf_"+id],["class","inf"] );
		infdiv.className = "inf";// setAttribute method for class attribute doesn't seem to work on ie<=6
		t.parentNode.insertBefore(infdiv, t);
		
		if(displaynrows)
		{
			/*** left div containing rows # displayer ***/
			var totrows;
			var ldiv = createElm( "div",["id","ldiv_"+id] );
			displaynrows ? ldiv.className = "ldiv" : ldiv.style.display = "none";
			displayPaging ? totrows = pagingLength : totrows = getRowsNb(id);
			
			var totrows_span = createElm( "span",["id","totrows_span_"+id],["class","tot"] ); // tot # of rows displayer
			totrows_span.className = "tot";//for ie<=6
			totrows_span.appendChild( createText(totrows) );
		
			var totrows_txt = createText(totrows_text);
			ldiv.appendChild(totrows_txt);
			ldiv.appendChild(totrows_span);
			infdiv.appendChild(ldiv); 
		}
		
		if(displayLoader)
		{
			/*** div containing loader  ***/
			var loaddiv = createElm( "div",["id","load_"+id],["class","loader"] );
			loaddiv.className = "loader";// for ie<=6
			loaddiv.style.display = "none";
			loaddiv.appendChild( createText(load_text) );	
			infdiv.appendChild(loaddiv);
		}
				
		if(displayPaging)
		{
			/*** mid div containing paging displayer ***/
			var mdiv = createElm( "div",["id","mdiv_"+id] );
			displayPaging ? mdiv.className = "mdiv" : mdiv.style.display = "none";						
			infdiv.appendChild(mdiv);
			
			var start_row = t.tf_ref_row;
			var row = grabTag(t,"tr");
			var nrows = row.length;
			var npages = Math.ceil( (nrows - start_row)/pagingLength );//calculates page nb
			
			var slcPages = createElm( "select",["id","slcPages_"+id] );
			slcPages.onchange = function(){
				if(displayLoader) showLoader(id,"");
				t.tf_startPagingRow = this.value;
				GroupByPage(id);
				if(displayLoader) showLoader(id,"none");
			}
			
			var pgspan = createElm( "span",["id","pgspan_"+id] );
			grabEBI("mdiv_"+id).appendChild( createText(" Page ") );
			grabEBI("mdiv_"+id).appendChild(slcPages);
			grabEBI("mdiv_"+id).appendChild( createText(" of ") );
			pgspan.appendChild( createText(npages+" ") );
			grabEBI("mdiv_"+id).appendChild(pgspan);	
			
			for(var j=start_row; j<nrows; j++)//this sets rows to validRow=true
			{
				row[j].setAttribute("validRow","true");
			}//for j
			
			setPagingInfo(id);
			if(displayLoader) showLoader(id,"none");
		}
		
		if(btnreset && fltgrid)
		{
			/*** right div containing reset button **/	
			var rdiv = createElm( "div",["id","reset_"+id] );
			btnreset ? rdiv.className = "rdiv" : rdiv.style.display = "none";
			
			var fltreset = createElm( 	"a",
										["href","javascript:clearFilters('"+id+"');Filter('"+id+"');"] );
			fltreset.appendChild(createText(btnreset_text));
			rdiv.appendChild(fltreset);
			infdiv.appendChild(rdiv);
		}
		
	}//if displaynrows etc.
	
	if(colWidth)
	{
		t.tf_colWidth = f["col_width"];
		setColWidths(id);
	}

	if(alternateBgs && !displayPaging)
		setAlternateRows(id);
	
	if(colOperation)
	{
		t.tf_colOperation = f["col_operation"];
		setColOperation(id);
	}

	if(rowVisibility)
	{
		t.tf_rowVisibility = f["rows_always_visible"];
		if(displayPaging) setVisibleRows(id);
	}
	
	if(bindScript)
	{
		t.tf_bindScript = f["bind_script"];
		if(	t.tf_bindScript!=undefined &&
			t.tf_bindScript["target_fn"]!=undefined )
		{//calls a fn if defined  
			t.tf_bindScript["target_fn"].call(null,id);
		}
	}//if bindScript
}

function PopulateOptions(id,cellIndex)
/*====================================================
	- populates select
	- adds only 1 occurence of a value
=====================================================*/
{
	var t = grabEBI(id);
	var ncells = t.tf_ncells, opt0txt = t.tf_display_allText;
	var sort_opts = t.tf_sortSlc, paging = t.tf_displayPaging;
	var start_row = t.tf_ref_row;
	var row = grabTag(t,"tr");
	var OptArray = new Array();
	var optIndex = 0; // option index
	var currOpt = new Option(opt0txt,"",false,false); //1st option
	grabEBI("flt"+cellIndex+"_"+id).options[optIndex] = currOpt;
	
	for(var k=start_row; k<row.length; k++)
	{
		var cell = getChildElms(row[k]).childNodes;
		var nchilds = cell.length;
		var isPaged = row[k].getAttribute("paging");
				
		if(nchilds == ncells){// checks if row has exact cell #
			
			for(var j=0; j<nchilds; j++)// this loop retrieves cell data
			{
				if(cellIndex==j)
				{
					var cell_data = getCellText(cell[j]);
					// checks if celldata is already in array
					var isMatched = false;
					for(w in OptArray)
					{

					 	//altered line below to remove white spaces
						if( trim( cell_data ) == trim( OptArray[w] ) ) isMatched = true;

					}
					if(!isMatched ) OptArray.push(cell_data);
				}//if cellIndex==j
			}//for j
		}//if
	}//for k
	
	if(sort_opts) OptArray.sort();
	for(y in OptArray)
	{
		optIndex++;
		var currOpt = new Option(OptArray[y],OptArray[y],false,false);
		if( typeof OptArray[y] != "function" )
				grabEBI("flt"+cellIndex+"_"+id).options[optIndex] = currOpt;		
	}
		
}


function Filter(id)
/*====================================================
	- Filtering fn
	- gets search strings from SearchFlt array
	- retrieves data from each td in every single tr
	and compares to search string for current
	column
	- tr is hidden if all search strings are not 
	found
=====================================================*/
{	
	showLoader(id,"");
	SearchFlt = getFilters(id);
	var t = grabEBI(id);
	t.tf_Obj!=undefined ? fprops = t.tf_Obj : fprops = new Array();
	var SearchArgs = new Array();
	var ncells = getCellsNb(id);
	var totrows = getRowsNb(id), hiddenrows = 0;
	var ematch = t.tf_exactMatch;
	var showPaging = t.tf_displayPaging;
	
	for(var i=0; i<SearchFlt.length; i++)
		SearchArgs.push( (grabEBI(SearchFlt[i]).value).toLowerCase() );
	
	var start_row = t.tf_ref_row;
	var row = grabTag(t,"tr");

	for(var k=start_row; k<row.length; k++)
	{
		/*** if table already filtered some rows are not visible ***/
		if(row[k].style.display == "none") row[k].style.display = "";
		
		var cell = getChildElms(row[k]).childNodes;
		var nchilds = cell.length;

		if(nchilds == ncells)// checks if row has exact cell #
		{
			var cell_value = new Array();
			var occurence = new Array();
			var isRowValid = true;
				
			for(var j=0; j<nchilds; j++)// this loop retrieves cell data
			{
				var cell_data = getCellText(cell[j]).toLowerCase();
				cell_value.push(cell_data);
				
				if(SearchArgs[j]!="")
				{
					var num_cell_data = parseFloat(cell_data);
					
					if(/<=/.test(SearchArgs[j]) && !isNaN(num_cell_data)) // first checks if there is an operator (<,>,<=,>=)
					{
						num_cell_data <= parseFloat(SearchArgs[j].replace(/<=/,"")) ? occurence[j] = true : occurence[j] = false;
					}
					
					else if(/>=/.test(SearchArgs[j]) && !isNaN(num_cell_data))
					{
						num_cell_data >= parseFloat(SearchArgs[j].replace(/>=/,"")) ? occurence[j] = true : occurence[j] = false;
					}
					
					else if(/</.test(SearchArgs[j]) && !isNaN(num_cell_data))
					{
						num_cell_data < parseFloat(SearchArgs[j].replace(/</,"")) ? occurence[j] = true : occurence[j] = false;
					}
										
					else if(/>/.test(SearchArgs[j]) && !isNaN(num_cell_data))
					{
						num_cell_data > parseFloat(SearchArgs[j].replace(/>/,"")) ? occurence[j] = true : occurence[j] = false;
					}					
					
					else 
					{						
						// Improved by Cedric Wartel (cwl)
						// automatic exact match for selects and special characters are now filtered
						// modif cwl : exact match automatique sur les select
						var regexp;
						if(ematch || fprops["col_"+j]=="select") regexp = new RegExp('(^)'+regexpEscape(SearchArgs[j])+'($)',"gi");
						else regexp = new RegExp(regexpEscape(SearchArgs[j]),"gi");
						occurence[j] = regexp.test(cell_data);
					}
				}//if SearchArgs
			}//for j
			
			for(var z=0; z<ncells; z++)
			{
				if(SearchArgs[z]!="" && !occurence[z]) isRowValid = false;
			}//for t
			
		}//if

		if(!isRowValid)
		{ 
			row[k].style.display = "none"; hiddenrows++; 
			if( showPaging ) row[k].setAttribute("validRow","false");
		} else {
			row[k].style.display = ""; 
			if( showPaging ) row[k].setAttribute("validRow","true");
		}
		
	}// for k

	t.tf_nRows = parseInt( getRowsNb(id) )-hiddenrows;
	if( !showPaging ) applyFilterProps(id);//applies filter props after filtering process
	if( showPaging ){ t.tf_startPagingRow=0; setPagingInfo(id); }//starts paging process
		
}

function setPagingInfo(id)
/*====================================================
	- Paging fn
	- calculates page # according to valid rows
	- refreshes paging select according to page #
	- Calls GroupByPage fn
=====================================================*/
{	
	var t = grabEBI(id);
	var start_row = parseInt( t.tf_ref_row );//filter start row
	var pagelength = t.tf_pagingLength;
	var row = grabTag(t,"tr");	
	var mdiv = grabEBI("mdiv_"+id);
	var slcPages = grabEBI("slcPages_"+id);
	var pgspan = grabEBI("pgspan_"+id);
	var nrows = 0;
	
	for(var j=start_row; j<row.length; j++)//counts rows to be grouped 
	{
		if(row[j].getAttribute("validRow") == "true") nrows++;
	}//for j
	
	var npg = Math.ceil( nrows/pagelength );//calculates page nb
	pgspan.innerHTML = npg; //refresh page nb span 
	slcPages.innerHTML = "";//select clearing shortcut
	
	if( npg>0 )
	{
		mdiv.style.visibility = "visible";
		for(var z=0; z<npg; z++)
		{
			var currOpt = new Option((z+1),z*pagelength,false,false);
			slcPages.options[z] = currOpt;
		}
	} else {/*** if no results paging select is hidden ***/
		mdiv.style.visibility = "hidden";
	}
	
	GroupByPage(id);
}

function GroupByPage(id)
/*====================================================
	- Paging fn
	- Displays current page rows
=====================================================*/
{
	showLoader(id,"");
	var t = grabEBI(id);
	var start_row = parseInt( t.tf_ref_row );//filter start row
	var pagelength = parseInt( t.tf_pagingLength );
	var paging_start_row = parseInt( t.tf_startPagingRow );//paging start row
	var paging_end_row = paging_start_row + pagelength;
	var row = grabTag(t,"tr");
	var nrows = 0;
	var validRows = new Array();//stores valid rows index
	
	for(var j=start_row; j<row.length; j++)
	//this loop stores valid rows index in validRows Array
	{
		var isRowValid = row[j].getAttribute("validRow");
		if(isRowValid=="true") validRows.push(j);
	}//for j

	for(h=0; h<validRows.length; h++)
	//this loop shows valid rows of current page
	{
		if( h>=paging_start_row && h<paging_end_row )
		{
			nrows++;
			row[ validRows[h] ].style.display = "";
		} else row[ validRows[h] ].style.display = "none";
	}//for h
	
	t.tf_nRows = parseInt(nrows);
	applyFilterProps(id);//applies filter props after filtering process
}

function applyFilterProps(id)
/*====================================================
	- checks fns that should be called
	after filtering and/or paging process
=====================================================*/
{
	t = grabEBI(id);
	var rowsCounter = t.tf_rowsCounter;
	var nRows = t.tf_nRows;
	var rowVisibility = t.tf_rowVisibility;
	var alternateRows = t.tf_alternateBgs;
	var colOperation = t.tf_colOperation;
	
	if( rowsCounter ) showRowsCounter( id,parseInt(nRows) );//refreshes rows counter
	if( rowVisibility ) setVisibleRows(id);//shows rows always visible
	if( alternateRows ) setAlternateRows(id);//alterning row colors
	if( colOperation  ) setColOperation(id);//makes operation on a col
	showLoader(id,"none");
}

function hasGrid(id)
/*====================================================
	- checks if table has a filter grid
	- returns a boolean
=====================================================*/
{
	var r = false, t = grabEBI(id);
	if(t != null && t.nodeName.toLowerCase() == "table")
	{
		for(i in TblId)
		{
			if(id == TblId[i]) r = true;
		}// for i
	}//if
	return r;
}

function getCellsNb(id,nrow)
/*====================================================
	- returns number of cells in a row
	- if nrow param is passed returns number of cells 
	of that specific row
=====================================================*/
{
  	var t = grabEBI(id);
	var tr;
	if(nrow == undefined) tr = grabTag(t,"tr")[0];
	else  tr = grabTag(t,"tr")[nrow];
	var n = getChildElms(tr);
	return n.childNodes.length;
}

function getRowsNb(id)
/*====================================================
	- returns total nb of filterable rows starting 
	from reference row if defined
=====================================================*/
{
	var t = grabEBI(id);
	var s = t.tf_ref_row;
	var ntrs = grabTag(t,"tr").length;
	return parseInt(ntrs-s);
}

function getFilters(id)
/*====================================================
	- returns an array containing filters ids
	- Note that hidden filters are also returned
=====================================================*/
{
	var SearchFltId = new Array();
	var t = grabEBI(id);
	var tr = grabTag(t,"tr")[0];
	var enfants = tr.childNodes;
	if(t.tf_fltGrid)
	{
		for(var i=0; i<enfants.length; i++) 
			SearchFltId.push(enfants[i].firstChild.getAttribute("id"));		
	}
	return SearchFltId;
}

function clearFilters(id)
/*====================================================
	- clears grid filters
=====================================================*/
{
	SearchFlt = getFilters(id);
	for(i in SearchFlt) grabEBI(SearchFlt[i]).value = "";
}

function showLoader(id,p)
/*====================================================
	- displays/hides loader div
=====================================================*/
{
	var loader = grabEBI("load_"+id);
	if(loader != null && p=="none")
		setTimeout("grabEBI('load_"+id+"').style.display = '"+p+"'",150);
	else if(loader != null && p!="none") loader.style.display = p;
}

function showRowsCounter(id,p)
/*====================================================
	- Shows total number of filtered rows
=====================================================*/
{
	var totrows = grabEBI("totrows_span_"+id);
	if(totrows != null && totrows.nodeName.toLowerCase() == "span" ) 
		totrows.innerHTML = p;
}

function getChildElms(n)
/*====================================================
	- checks passed node is a ELEMENT_NODE nodeType=1
	- removes TEXT_NODE nodeType=3  
=====================================================*/
{
	if(n.nodeType == 1)
	{
		var enfants = n.childNodes;
		for(var i=0; i<enfants.length; i++)
		{
			var child = enfants[i];
			if(child.nodeType == 3) n.removeChild(child);
		}
		return n;	
	}
}

function getCellText(n)
/*====================================================
	- returns text + text of child nodes of a cell
=====================================================*/
{
	var s = "";
	var enfants = n.childNodes;
	for(var i=0; i<enfants.length; i++)
	{
		var child = enfants[i];
		if(child.nodeType == 3) s+= child.data;
		else s+= getCellText(child);
	}
	return s;
}

function getColValues(id,colindex,num)
/*====================================================
	- returns an array containing cell values of
	a column
	- needs following args:
		- filter id (string)
		- column index (number)
		- a boolean set to true if we want only 
		numbers to be returned
=====================================================*/
{
	var t = grabEBI(id);
	var row = grabTag(t,"tr");
	var nrows = row.length;
	var start_row = parseInt( t.tf_ref_row );//filter start row
	var ncells = getCellsNb( id,start_row );
	var colValues = new Array();
	
	for(var i=start_row; i<nrows; i++)//iterates rows
	{
		var cell = getChildElms(row[i]).childNodes;
		var nchilds = cell.length;
	
		if(nchilds == ncells)// checks if row has exact cell #
		{
			for(var j=0; j<nchilds; j++)// this loop retrieves cell data
			{
				if(j==colindex && row[i].style.display=="" )
				{
					var cell_data = getCellText( cell[j] ).toLowerCase();
					(num) ? colValues.push( parseFloat(cell_data) ) : colValues.push( cell_data );
				}//if j==k
			}//for j
		}//if nchilds == ncells
	}//for i
	return colValues;	
}

function setColWidths(id)
/*====================================================
	- sets widths of columns
=====================================================*/
{
	if( hasGrid(id) )
	{
		var t = grabEBI(id);
		t.style.tableLayout = "fixed";
		var colWidth = t.tf_colWidth;
		var start_row = parseInt( t.tf_ref_row );//filter start row
		var row = grabTag(t,"tr")[0];
		var ncells = getCellsNb(id,start_row);
		for(var i=0; i<colWidth.length; i++)
		{
			for(var k=0; k<ncells; k++)
			{
				cell = row.childNodes[k];
				if(k==i) cell.style.width = colWidth[i];
			}//var k
		}//for i
	}//if hasGrid
}

function setVisibleRows(id)
/*====================================================
	- makes a row always visible
=====================================================*/
{
	if( hasGrid(id) )
	{
		var t = grabEBI(id);		
		var row = grabTag(t,"tr");
		var nrows = row.length;
		var showPaging = t.tf_displayPaging;
		var visibleRows = t.tf_rowVisibility;
		for(var i=0; i<visibleRows.length; i++)
		{
			if(visibleRows[i]<=nrows)//row index cannot be > nrows
			{
				if(showPaging)
					row[ visibleRows[i] ].setAttribute("validRow","true");
				row[ visibleRows[i] ].style.display = "";
				
			}//if
		}//for i
	}//if hasGrid
}

function setAlternateRows(id)
/*====================================================
	- alternates row colors for better readability
=====================================================*/
{
	if( hasGrid(id) )
	{
		var t = grabEBI(id);		
		var row = grabTag(t,"tr");
		var nrows = row.length;
		var start_row = parseInt( t.tf_ref_row );//filter start row
		var visiblerows = new Array();
		for(var i=start_row; i<nrows; i++)//visible rows are stored in visiblerows array
			if( row[i].style.display=="" ) visiblerows.push(i);
		
		for(var j=0; j<visiblerows.length; j++)//alternates bg color
		{
				if( j % 2 == 0 ){
					if( row[ visiblerows[j] ].config ){ 				     
						row[ visiblerows[j] ].set_color( row[ visiblerows[j] ].config.even_color, row[ visiblerows[j] ].config.even_style );
						row[ visiblerows[j] ].order = j % 2;
						}
				}else{
					if( row[ visiblerows[j] ].config ){
						row[ visiblerows[j] ].set_color( row[ visiblerows[j] ].config.odd_color, row[ visiblerows[j] ].config.odd_style );
						row[ visiblerows[j] ].order = j % 2;
					}
				}
 
//				(j % 2 == 0) ? row[ visiblerows[j] ].className = row[ visiblerows[j] ].config.even_style : row[ visiblerows[j] ].className = row[ visiblerows[j] ].config.odd_color;// Original line
		}
		
	}//if hasGrid
	
}

function setColOperation(id)
/*====================================================
	- Calculates values of a column
	- params are stored in 'colOperation' table's
	attribute
		- colOperation["id"] contains ids of elements 
		showing result (array)
		- colOperation["col"] contains index of 
		columns (array)
		- colOperation["operation"] contains operation
		type (array, values: sum, mean)
		- colOperation["write_method"] array defines 
		which method to use for displaying the 
		result (innerHTML, setValue, createTextNode).
		Note that innerHTML is the default value.
		
	!!! to be optimised
=====================================================*/
{
	if( hasGrid(id) )
	{
		var t = grabEBI(id);
		var labelId = t.tf_colOperation["id"];
		var colIndex = t.tf_colOperation["col"];
		var operation = t.tf_colOperation["operation"];
		var outputType =  t.tf_colOperation["write_method"];
		var precision = 2;//decimal precision
		
		if( (typeof labelId).toLowerCase()=="object" 
			&& (typeof colIndex).toLowerCase()=="object" 
			&& (typeof operation).toLowerCase()=="object" )
		{
			var row = grabTag(t,"tr");
			var nrows = row.length;
			var start_row = parseInt( t.tf_ref_row );//filter start row
			var ncells = getCellsNb( id,start_row );
			var colvalues = new Array();
						
			for(var k=0; k<colIndex.length; k++)//this retrieves col values
			{
				colvalues.push( getColValues(id,colIndex[k],true) );			
			}//for k
			
			for(var i=0; i<colvalues.length; i++)
			{
				var result=0, nbvalues=0;
				for(var j=0; j<colvalues[i].length; j++ )
				{
					var cvalue = colvalues[i][j];
					if( !isNaN(cvalue) )
					{
						switch( operation[i].toLowerCase() )
						{
							case "sum":
								result += parseFloat( cvalue );
							break;
							case "mean":
								nbvalues++;
								result += parseFloat( cvalue );
							break;
							//add cases for other operations
						}//switch
					}
				}//for j
				
				switch( operation[i].toLowerCase() )
				{
					case "mean":
						result = result/nbvalues;
					break;
				}
				
				if(outputType != undefined && (typeof outputType).toLowerCase()=="object")
				//if outputType is defined
				{
					result = result.toFixed( precision );
					if( grabEBI( labelId[i] )!=undefined )
					{
						switch( outputType[i].toLowerCase() )
						{
							case "innerhtml":
								grabEBI( labelId[i] ).innerHTML = result;
							break;
							case "setvalue":
								grabEBI( labelId[i] ).value = result;
							break;
							case "createtextnode":
								var oldnode = grabEBI( labelId[i] ).firstChild;
								var txtnode = createText( result );
								grabEBI( labelId[i] ).replaceChild( txtnode,oldnode );
							break;
							//other cases could be added
						}//switch
					}
				} else {
					try
					{
						grabEBI( labelId[i] ).innerHTML = result.toFixed( precision );
					} catch(e){ }//catch
				}//else
				
			}//for i

		}//if typeof
	}//if hasGrid
}

function grabEBI(id)
/*====================================================
	- this is just a getElementById shortcut
=====================================================*/
{
	return document.getElementById( id );
}

function grabTag(obj,tagname)
/*====================================================
	- this is just a getElementsByTagName shortcut
=====================================================*/
{
	return obj.getElementsByTagName( tagname );
}

function regexpEscape(s)
/*====================================================
	- escapes special characters [\^$.|?*+() 
	for regexp
	- Many thanks to Cedric Wartel for this fn
=====================================================*/
{
	// traite les caractères spéciaux [\^$.|?*+()
	//remplace le carctère c par \c
	function escape(e)
	{
	  if( typeof e != "function" ){
				a = new RegExp('\\'+e,'g');
				s = s.replace(a,'\\'+e);
		}
	}

	chars = new Array('\\','[','^','$','.','|','?','*','+','(',')');
	//chars.each(escape); // no prototype framework here...
	for(e in chars) escape(chars[e]);
	return s;
}

function createElm(elm)
/*====================================================
	- returns an html element with its attributes
	- accepts the following params:
		- a string defining the html element 
		to create
		- an undetermined # of arrays containing the
		couple "attribute name","value" ["id","myId"]
=====================================================*/
{
	var el = document.createElement( elm );		
	if(arguments.length>1)
	{
		for(var i=0; i<arguments.length; i++)
		{
			var argtype = typeof arguments[i];
			switch( argtype.toLowerCase() )
			{
				case "object":
					if( arguments[i].length==2 )
					{							
						el.setAttribute( arguments[i][0],arguments[i][1] );
					}//if array length==2
				break;
			}//switch
		}//for i
	}//if args
	return el;	
}

function createText(node)
/*====================================================
	- this is just a document.createTextNode shortcut
=====================================================*/
{
	return document.createTextNode( node );
}

function DetectKey(e)
/*====================================================
	- common fn that detects return key for a given
	element (onkeypress attribute on input)
=====================================================*/
{
	var evt=(e)?e:(window.event)?window.event:null;
	if(evt)
	{
		var key=(evt.charCode)?evt.charCode:
			((evt.keyCode)?evt.keyCode:((evt.which)?evt.which:0));
		if(key=="13")
		{
			var cid, leftstr, tblid, CallFn, Match;		
			cid = this.getAttribute("id");
			leftstr = this.getAttribute("id").split("_")[0];
			tblid = cid.substring(leftstr.length+1,cid.length);
			t = grabEBI(tblid);
			(t.tf_isModfilter_fn) ? t.tf_modfilter_fn.call() : Filter(tblid);
		}//if key
	}//if evt	
}

function importScript(scriptName,scriptPath)
{
	var isImported = false; 
	var scripts = grabTag(document,"script");

	for (var i=0; i<scripts.length; i++)
	{
		if(scripts[i].src.match(scriptPath))
		{ 
			isImported = true;	
			break;
		}
	}

	if( !isImported )//imports script if not available
	{
		var head = grabTag(document,"head")[0];
		var extScript = createElm(	"script",
									["id",scriptName],
									["type","text/javascript"],
									["src",scriptPath]	);
		head.appendChild(extScript);
	}
}//fn importScript



/*====================================================
	- Below a collection of public functions 
	for developement purposes
	- all public methods start with prefix 'TF_'
	- These methods can be removed safely if not
	needed
=====================================================*/

function TF_GetFilterIds()
/*====================================================
	- returns an array containing filter grids ids
=====================================================*/
{
	try{ return TblId }
	catch(e){ alert('TF_GetFilterIds() fn: could not retrieve any ids'); }
}

function TF_HasGrid(id)
/*====================================================
	- checks if table has a filter grid
	- returns a boolean
=====================================================*/
{
	return hasGrid(id);
}

function TF_GetFilters(id)
/*====================================================
	- returns an array containing filters ids of a
	specified grid
=====================================================*/
{
	try
	{
		var flts = getFilters(id);
		return flts;
	} catch(e) {
		alert('TF_GetFilters() fn: table id not found');
	}
	
}

function TF_GetStartRow(id)
/*====================================================
	- returns starting row index for filtering
	process
=====================================================*/
{
	try
	{
		var t = grabEBI(id);
		return t.tf_ref_row;
	} catch(e) {
		alert('TF_GetStartRow() fn: table id not found');
	}
}

function TF_GetColValues(id,colindex,num)
/*====================================================
	- returns an array containing cell values of
	a column
	- needs following args:
		- filter id (string)
		- column index (number)
		- a boolean set to true if we want only 
		numbers to be returned
=====================================================*/
{
	if( hasGrid(id) )
	{
		return getColValues(id,colindex,num);
	}//if TF_HasGrid
	else alert('TF_GetColValues() fn: table id not found');
}

function TF_Filter(id)
/*====================================================
	- filters a table
=====================================================*/
{
	var t = grabEBI(id);
	
	if( TF_HasGrid(id) ) Filter(id);
	else alert('TF_Filter() fn: table id not found');

}

function TF_RemoveFilterGrid(id)
/*====================================================
	- removes a filter grid
=====================================================*/
{
	if( TF_HasGrid(id) )
	{
		var t = grabEBI(id);
		clearFilters(id);
				
		if(grabEBI("inf_"+id)!=null)
		{
			t.parentNode.removeChild(t.previousSibling);
		}
		// remove paging here
		var row = grabTag(t,"tr");
		
		for(var j=0; j<row.length; j++)
		//this loop shows all rows and removes validRow attribute
		{			
			row[j].style.display = "";
			try
			{ 
				if( row[j].hasAttribute("validRow") ) 
					row[j].removeAttribute("validRow");
			} //ie<=6 doesn't support hasAttribute method
			catch(e){
				for( var x = 0; x < row[j].attributes.length; x++ ) 
				{
					if( row[j].attributes[x].nodeName.toLowerCase()=="validrow" ) 
						row[j].removeAttribute("validRow");
				}//for x
			}//catch(e)
		}//for j		
		
		if( t.tf_alternateBgs )//removes alterning row colors
		{
			for(var k=0; k<row.length; k++)
			//this loop removes bg className
			{
				row[k].className = "";
			}
		}
		
		if(t.tf_fltGrid) t.deleteRow(0);
		for(i in TblId)//removes grid id value from array
			if(id == TblId[i]) TblId.splice(i,1);
		
	}//if TF_HasGrid
	else alert('TF_RemoveFilterGrid() fn: table id not found');
}

function TF_ClearFilters(id)
/*====================================================
	- clears grid filters only, table is not filtered
=====================================================*/
{
	if( TF_HasGrid(id) ) clearFilters(id);
	else alert('TF_ClearFilters() fn: table id not found');
}

function TF_SetFilterValue(id,index,searcharg)
/*====================================================
	- Inserts value in a specified filter
	- Params:
		- id: table id (string)
		- index: filter column index (numeric value)
		- searcharg: search string
=====================================================*/
{
	if( TF_HasGrid(id) )
	{
		var flts = getFilters(id);
		for(i in flts)
		{
			if( i==index ) grabEBI(flts[i]).value = searcharg;
		}
	} else {
		alert('TF_SetFilterValue() fn: table id not found');
	}
}

/*====================================================
	- bind an external script fns
	- fns below do not belong to filter grid script 
	and are used to interface with external 
	autocomplete script found at the following URL:
	http://www.codeproject.com/jscript/jsactb.asp
	(credit to zichun) 
	- fns used to merge filter grid with external
	scripts
=====================================================*/
var colValues = new Array();

function setAutoComplete(id)
{
	var t = grabEBI(id);
	var bindScript = t.tf_bindScript;
	var scriptName = bindScript["name"];
	var scriptPath = bindScript["path"];
	initAutoComplete();
	
	function initAutoComplete()
	{
		var filters = TF_GetFilters(id);
		for(var i=0; i<filters.length; i++)
		{
			if( grabEBI(filters[i]).nodeName.toLowerCase()=="input")
			{
				colValues.push( getColValues(id,i) );	
			} else colValues.push( '' );
		}//for i

		try{ actb( grabEBI(filters[0]), colValues[0] ); }
		catch(e){ alert(scriptPath + " script may not be loaded"); }

	}//fn
}

// Trimming functions

function ltrim(str) { 
	for(var k = 0; k < str.length && isWhitespace(str.charAt(k)); k++);
	return str.substring(k, str.length);
}
function rtrim(str) {
	for(var j=str.length-1; j>=0 && isWhitespace(str.charAt(j)) ; j--) ;
	return str.substring(0,j+1);
}
function trim(str) {
  if(typeof str == 'function') return str;
	return ltrim(rtrim(str));
}
function isWhitespace(charToCheck) {
	var whitespaceChars = " \t\n\r\f";
	return (whitespaceChars.indexOf(charToCheck) != -1);
}
// String Functions
function InStr(strSearch, charSearchFor)
{
            for (i=0; i < strSearch.length; i++)
            {
                  if (charSearchFor == Mid(strSearch, i, 1))
                  {
                        return i;
                  }
            }
            return -1;
}
function Mid(str, start, len)
{
// Make sure start and len are within proper bounds
    if (start < 0 || len < 0) return "";
    var iEnd, iLen = String(str).length;
    if (start + len > iLen)
          iEnd = iLen;
    else
          iEnd = start + len;
    return String(str).substring(start,iEnd);
}

/*
	Tables Sortting
*/
var sbdTableId = "test";
var sbdHeaderOff = 0;
function enableSort(){
	addEvent(window, "load", sortables_init );
}

var SORT_COLUMN_INDEX;

function sortables_init() {
    // Find all tables with class sortable and make them sortable
    if (!document.getElementsByTagName) return;
    tbls = document.getElementsByTagName("table");
    for (ti=0;ti<tbls.length;ti++) {
        thisTbl = tbls[ti];
        if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {

            ts_makeSortable(thisTbl, thisTbl.id);
        }
    }
}

function ts_makeSortable(table, tableId) {
		if( !table.header_offset ){
			tableHeader = 1;
  	}	else {
			tableHeader = table.header_offset;
		}
    if (table.rows && table.rows.length > 1) {
		var firstRow = table.rows[tableHeader];
    }
    if (!firstRow) return;

    // We have a first row: assume it's the header, and make its contents clickable links
    for (var i=0;i<firstRow.cells.length;i++) {
        var cell = firstRow.cells[i];
        var txt = ts_getInnerText(cell);
        cell.innerHTML = '<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;">'+txt+'<span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a>';
    }
}

function ts_getInnerText(el) {
	if (typeof el == "string") return el;
	if (typeof el == "undefined") { return el };
	if (el.innerText) return el.innerText;	//Not needed but it is faster
	var str = "";

	var cs = el.childNodes;
	var l = cs.length;
	for (var i = 0; i < l; i++) {
		switch (cs[i].nodeType) {
			case 1: //ELEMENT_NODE
				str += ts_getInnerText(cs[i]);
				break;
			case 3:	//TEXT_NODE
				str += cs[i].nodeValue;
				break;
		}
	}
	return str;
}

function ts_resortTable(lnk) {
    // get the span
    var span;
    for (var ci=0;ci<lnk.childNodes.length;ci++) {
        if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
    }
    var spantext = ts_getInnerText(span);
    var td = lnk.parentNode;
    var column = td.cellIndex;
    var table = getParent(td,'TABLE');

		if( !table.header_offset ){
			tableHeader = 1;
  	}	else {
			tableHeader = table.header_offset;
		}		

    // Work out a type for the column
    if (table.rows.length <= tableHeader + 1) return;
    var itm = ts_getInnerText(table.rows[tableHeader + 1].cells[column]);
    sortfn = ts_sort_caseinsensitive;
    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = ts_sort_date;
    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = ts_sort_date;
    //if (itm.match(/^[£$]/)) sortfn = ts_sort_currency;
    if (itm.match(/^[€$]/)) sortfn = ts_sort_currency;
    if (itm.match(/^[\d\.]+$/)) sortfn = ts_sort_numeric;
    SORT_COLUMN_INDEX = column;
    var firstRow = new Array();
    var newRows = new Array();
    for (i=0;i<table.rows[tableHeader].length;i++) { firstRow[i] = table.rows[tableHeader][i]; }
    for (j=tableHeader+1;j<table.rows.length;j++) { newRows[j-(tableHeader+1)] = table.rows[j]; }

    newRows.sort(sortfn);

    if (span.getAttribute("sortdir") == 'down') {
        //ARROW = '&nbsp;&nbsp;&uarr;';
        ARROW = '&nbsp;&nbsp;<img src=mambots/content/sbdtigratable/javascript/images/down.gif border=0>';
        newRows.reverse();
        span.setAttribute('sortdir','up');
    } else {
        //ARROW = '&nbsp;&nbsp;&darr;';
        ARROW = '&nbsp;&nbsp;<img src=mambots/content/sbdtigratable/javascript/images/up.gif border=0>';
        span.setAttribute('sortdir','down');
    }

    // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
    // don't do sortbottom rows
    for (i=0;i<newRows.length;i++) { 
				if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) 
					 table.tBodies[0].appendChild(newRows[i]);
		}
    // do sortbottom rows only
    for (i=0;i<newRows.length;i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) table.tBodies[0].appendChild(newRows[i]);}

    // Delete any other arrows there may be showing
    var allspans = document.getElementsByTagName("span");
    for (var ci=0;ci<allspans.length;ci++) {
        if (allspans[ci].className == 'sortarrow') {
            if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
                allspans[ci].innerHTML = '&nbsp;&nbsp;&nbsp;';
            }
        }
    }
		setAlternateRows(table.id);
    span.innerHTML = ARROW;
}

function getParent(el, pTagName) {
	if (el == null) return null;
	else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
		return el;
	else
		return getParent(el.parentNode, pTagName);
}
function ts_sort_date(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    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;
    if (dt1<dt2) return -1;
    return 1;
}

function ts_sort_currency(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
    return parseFloat(aa) - parseFloat(bb);
}

function ts_sort_numeric(a,b) {
    aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
    if (isNaN(aa)) aa = 0;
    bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
    if (isNaN(bb)) bb = 0;
    return aa-bb;
}

function ts_sort_caseinsensitive(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
    if (aa==bb) return 0;
    if (aa<bb) return -1;
    return 1;
}

function ts_sort_default(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    if (aa==bb) return 0;
    if (aa<bb) return -1;
    return 1;
}


function addEvent(elm, evType, fn, useCapture)
// addEvent and removeEvent
// cross-browser event handling for IE5+,  NS6 and Mozilla
{
  if (elm.addEventListener){
    elm.addEventListener(evType, fn, useCapture);
    return true;
  } else if (elm.attachEvent){
    var r = elm.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be removed");
  }
}
