/*====================================================
	- 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
	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;
	
	// 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)
					{
						if( cell_data == 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);
		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
			(j % 2 == 0) ? row[ visiblerows[j] ].className = "even" : row[ visiblerows[j] ].className = "odd";
		
	}//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)
	{
		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
}