text: /*====================================================
  	- 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
  		
  		var _row;
  		if(ref_row == undefined){
  			ref_row=2;
  		} else {
  			 ref_row=(ref_row+2);
  			 _row = ref_row;
  		}
  		var ncells = getCellsNb(id,_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);
  	if (!n){
  		n = getChildElms(th);
  		return n.childNodes.length;
  	}
  	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
  }