/***********************
 *    Sortable Grid    *
 *    -------------    *
 *                     *
 * Version: 2.0.1      *
 * Author: Dan Sutton  *
 * Release: 8/19/2010  *
 ***********************/

// Speed thing for Internet Explorer
try { window.offscreenBuffering=true; }
catch(e) { }

// Alignment Constants
var gaNone=0;
var gaLeft=1;
var gaRight=2;
var gaCenter=3;
var gaTop=4;
var gaMiddle=5;
var gaBottom=6;

// Sort Constants
var gsNone=0;
var gsAsc=1;
var gsDesc=2;

// Control Variable
var gridControl=
{
	items: [],								// Grids
	images: "/images/grid/",	// Images directory
	scriptRoot: "/scripts/",	// Root of Grid Script directory
	registry: [],							// Registry for created items
	highlight: "#EEEEEE",			// Background color for highlighted cell
	normal:    "#DDDDDD",			// Background color for normal cell
	firefox:	(navigator.userAgent.toLowerCase().indexOf("firefox")>=0),
	ie:				(navigator.userAgent.toLowerCase().indexOf("msie")>=0),
	opera:		(navigator.userAgent.toLowerCase().indexOf("opera")>=0),
	netscape:	(navigator.userAgent.toLowerCase().indexOf("netscape")>=0),
	safari:		(navigator.userAgent.toLowerCase().indexOf("safari")>=0),
	
	// Add a Grid
	add: function(id)
	{
		var x=new grid(id);
		this.items[this.items.length]=x;
		return x;
	},
	
	register: function(id, obj)
	{
		this.registry[id]=obj;
	},
	
	find: function(id)
	{
		return this.registry[id];
	},
	

	// Find the grid containing the given Entity
	findGrid: function(id)
	{
		return this.find(id.split("_")[0]);
	},

	// Happens when the Header is clicked
	headerClick: function(id)
	{
		var x=gridControl.findGrid(id);
		if (x)
			x.headerClick(id);
	},
	
	// onClick event for the given Entity
	onclick: function(id)
	{
		var f=gridControl.find(id);
		if (f)
			f.onclick();
	},
	
	// onMouseOver event for the given Entity
	onmouseover: function(id)
	{
		var f=gridControl.find(id);
		if (f)
			f.onmouseover();
	},
	
	// onMouseOut event for the given Entity
	onmouseout: function(id)
	{
		var f=gridControl.find(id);
		if (f)
			f.onmouseout();
	},
	
	// onMouseDown event for the given Entity
	onmousedown: function(id)
	{
		var f=gridControl.find(id);
		if (f)
			f.onmousedown();
	},
	
	// Search String change event
	search: function(id, value)
	{
		for (i in gridControl.items)
			gridControl.items[i].search(id,value);
	}
};

// Grid Entity superclass
function gridEntity(){}

// Returns the HTML for "align"
gridEntity.prototype.alignHTML=function()
{
	if (this.align>0)
		return "align='"+(this.align==gaRight? "right" : this.align==gaCenter? "center" : "left")+"'";
	else
		return "";
};

// Returns the HTML for "valign"
gridEntity.prototype.valignHTML=function()
{
	if (this.valign>0)
		return "valign='"+(this.valign==gaTop? "top" : this.valign==gaBottom? "bottom" : "middle")+"'";
	else
		return "";
};

// Returns the HTML for "height"
gridEntity.prototype.heightHTML=function()
{
	return (this.height? " height='"+this.height+"'" : "");
};

// Returns the HTML for "width"
gridEntity.prototype.widthHTML=function()
{
	return (this.width? " width='"+this.width+"'" : "");
};

// Returns the HTML for "events"
gridEntity.prototype.eventsHTML=function()
{
	return "onclick='javascript:gridControl.onclick(\""+this.id+"\");'"
	 +" onmouseover='javascript:gridControl.onmouseover(\""+this.id+"\");'"
	 +" onmouseout='javascript:gridControl.onmouseout(\""+this.id+"\");'"
	 +" onmousedown='javascript:gridControl.onmousedown(\""+this.id+"\");'";
};

// Returns all Control HTML
gridEntity.prototype.controlHTML=function()
{
	return this.alignHTML()+" "+this.valignHTML()+" "
	 +this.heightHTML()+" "+this.widthHTML()+" "+this.eventsHTML();
};

// Events
gridEntity.prototype.onclick=function(){};
gridEntity.prototype.onmouseover=function(){};
gridEntity.prototype.onmousedown=function(){};
gridEntity.prototype.onmouseout=function(){};

// Class for gridCell. Use <!> for value in innerHTML
function gridCell(id,innerHTML,value,nowrap,classOverride)
{
	this.id=id;
	this.innerHTML=innerHTML;
	this.value=value;
	this.nowrap=nowrap;
	this.align=gaLeft;
	this.valign=gaMiddle;
	this.width="";
	this.height="";
	this.style="";
	this.classOverride=classOverride;
	gridControl.register(id,this);
}
gridCell.prototype=new gridEntity;

// toString overload
gridCell.prototype.toString=function(classOverride)
{
	if (!classOverride)
		classOverride="gridCell";
	return "<td class='"+classOverride+"' id='"+this.id+"' "
	 +(this.nowrap? "nowrap " : "")
	 +(this.style? "style=\""+this.style+"\" " : "")
	 +this.controlHTML()
	 +">"+this.innerHTML+"</td>";
};

gridCell.prototype.highlight=function(highlighted)
{
	var x=document.getElementById(this.id);
	if (x)
		if (this.classOverride)
			x.className=highlighted? "gridCellHighlight" : this.classOverride;
		else
			x.style.backgroundColor=highlighted? gridControl.highlight : gridControl.normal;
};

// Class for gridRow
function gridRow(id,cellCount,classOverride)
{
	this.id=id;
	this.classOverride=classOverride;
	this.items=[];
	this.setCellCount(cellCount);
	this.align=gaLeft;
	this.valign=gaMiddle;
	this.width="";
	this.height="";
	this.cellCount=0;
	this.hidden=false;
	this.style="";
	gridControl.register(id,this);
}
gridRow.prototype=new gridEntity;

gridRow.prototype.filter=function(col,value)
{
	this.hidden=((this.items[col].value+"").toLowerCase().indexOf(value.toLowerCase())<0);
	this.showHide();
};
		
gridRow.prototype.showHide=function()
{
	var x=document.getElementById(this.id);
	var d=this.hidden? "none" : "";
	if (x)
		if (x.style.display!=d)
			x.style.display=d;
};

// toString overload
gridRow.prototype.toString=function()
{
	var HTML="<tr class='gridRow' id='"+this.id+"' "+this.controlHTML()
	 +(this.style || this.hidden?
	  " style=\""+(this.hidden? " display:none; ":"")+this.style+"\"" : "")+">";
	for (i in this.items)
		HTML+=this.items[i].toString(this.classOverride);
	return HTML+"</tr>";
};

// Set number of columns
gridRow.prototype.setCellCount=function(cellCount)
{
	this.cellCount=cellCount;
	if (cellCount>this.items.length)
		for (var i=this.items.length; i<cellCount; i++)
			this.setCell(i);
	else if (cellCount<this.items.length)
		for (var i=cellCount; i<this.items.length; i++)
			this.items[i]=null;
};

gridRow.prototype.setCell=function(x,innerHTML,value,nowrap)
{
	var c=null;
	try
	{
		c=this.items[x];
		c.innerHTML=innerHTML;
		c.value=value;
		c.nowrap=nowrap;
	}
	catch(e)
	{
		var id=this.id+"_c_"+x;
		c=new gridCell(id,innerHTML,value,nowrap,this.classOverride);
		this.items[x]=c;
	}
	return c;
};

gridRow.prototype.onmouseover=function()
{
	for (i in this.items)
		this.items[i].highlight(true);
};

gridRow.prototype.onmouseout=function()
{
	for (i in this.items)
		this.items[i].highlight(false);
};

// Class for Grid Sort Information
function gridSort(){}
gridSort.prototype.column=0;
gridSort.prototype.order=gsNone;

// Class for Grid Header
function gridHeader(id,text,sortable,searchable)
{
	this.id=id;
	this.sid=this.id+"_src";
	this.text=text;
	this.sortable=sortable;
	this.searchable=searchable;
	this.total="";
	this.wrap=false;
	gridControl.register(id,this);
}
gridHeader.prototype.search="";

gridHeader.prototype.clearSearch=function()
{
	this.search="";
	var x=document.getElementById(this.sid);
	if (x)
		x.value="";
};

gridHeader.prototype.searchString=function()
{
	var HTML="<td nowrap class='gridSearchTD'>";
	if (this.searchable)
		HTML+="<table cellspacing=0 cellpadding=0 border=0 width='100%'><tr><td class='gridSearchImgTd'>"
		 +"<img class='gridSearchImg' src='"+gridControl.images+"search.png'></td><td width='100%'>"
		 +"<input class='gridSearchInput' id='"+this.sid+"' name='"+this.sid+"'"
		 +" onkeyup='javascript:gridControl.search(\""+this.id+"\",this.value);'"
		 +" value='"+this.search.replace("'","\'")+"'>"
		 +"</td></tr></table>";
	else
		HTML+="&nbsp;";
	return HTML+"</td>";
};

gridHeader.prototype.toString=function()
{
	return "<td id='"+this.id+"' class='gridHeaderTD"
	 +(this.sortable? "_s" : "")+"'"
	 +(this.sortable? 
	  " onclick='javascript:gridControl.headerClick(\""+this.id+"\");'" : "")
	 +(this.width? " width='"+this.width+"'" : "")
	 +" height='1%'"
	 +"><table cellspacing=0 cellpadding=0 border=0 class='gridHeader"+(this.wrap? "W" : "")+"' width='100%'"
	 +(this.wrap? " height='100%'" : "")
	 +">"
	 +"<tr"+(this.wrap? " valign='bottom'" : "")+">"
	 +"<td width='100%' align='center'"+(this.wrap? "" : " nowrap")+">"+this.text+"</td>"
	 +(this.sortable? 
	  "<td width='1%'><img id='"+this.id+"_i' class='gridSortIcon'"
		+" src='"+gridControl.images+"sortoff.gif' height=10 width=11></td>" :"")
	 +"</tr></table></td>";
};

gridHeader.prototype.totalToString=function()
{
	if (this.total)
		return "<td class='gridCell' nowrap align='right'><b>"+this.total+"</b></td>";
	else
		return "<td class='gridHeaderW'>&nbsp;</td>";
};

gridHeader.prototype.setSortIcon=function(order)
{
	var x=document.getElementById(this.id+"_i");
	var s="off";
	if (x)
	{
		if (order==gsAsc)
			s="up";
		else if (order==gsDesc)
			s="down";
		x.src=gridControl.images+"sort"+s+".gif";
	}
};

// Class for Grid
function grid(id)
{
	this.id=id;
	this.headers=[];									// Row Headers
	this.rows=[];											// Rows
	this.currentSort=new gridSort();	// Sort information
	this.headerCount=0;
	this.rowCount=0;
	gridControl.register(id,this);
}

grid.prototype.hasTotals=function()
{
	var r=false;
	for (i in this.headers)
		if (this.headers[i].total)
		{
			r = true;
			break;
		}
	return r;
};

grid.prototype.clear=function()
{
	this.headers=[];
	this.rows=[];
	this.headercount=0;
	this.rowcount=0;
};

grid.prototype.searchable=function()
{
	var r=false;
	for (i in this.headers)
		if (r=this.headers[i].searchable)
		  break;
	return r;
};

grid.prototype.addHeader=function(text,sortable,searchable)
{
	var x=new gridHeader(this.id+"_h_"+(this.headerCount++),text,sortable,searchable);
	this.headers[this.headers.length]=x;
	for (i in this.rows)
		this.rows[i].setCellCount(this.headerCount);
	return x;
};

grid.prototype.addRow=function(classOverride)
{
	var x=new gridRow(this.id+"_r_"+(this.rowCount++),this.headers.length,classOverride);
	this.rows[this.rows.length]=x;
	return x;
};

grid.prototype.setCell=function(x,y,innerHTML,value,nowrap)
{
	try
	{
		var r=this.rows[y];
		return r.setCell(x,innerHTML,value,nowrap);
	}
	catch(e)
	{
		return null;
	}
};

grid.prototype.getCell=function(x,y)
{
	try
	{
		return this.rows[y].items[x];
	}
	catch(e)
	{
		return null;
	}
};

// toString function for Grid
grid.prototype.toString=function()
{
	return "<div class='gridFrame'><iframe id='"+this.id+"_f' name='"+this.id+"_f'></iframe></div>"
	 +"<div id='"+this.id+"'>"
	 +this.innerString()+"</div>";
};

// Execute URL in hidden iFrame
grid.prototype.execute=function(url)
{
	var f=null;
	var name=this.id+"_f";
	if (gridControl.firefox || gridControl.opera)
		f=eval("window.frames."+name);
	else
		f=document.getElementById(name);
	if (f)
	{
		url+=(url.indexOf("?")>=0? "&" : "?")
		 +"grid="+this.id
		 +"&if_rnd="+Math.random();
		f.src=url;
	}
};

grid.prototype.innerString=function(sortTree)
{
	var HTML="<table class='gridTable'"
	 +" cellspacing=0 cellpadding=0 border=0>";
	if (this.searchable())
	{
		HTML+="<tr class='gridSearchRow'>";
		for (i in this.headers)
			HTML+=this.headers[i].searchString();
		HTML+="</tr>";
	}
	HTML+="<tr class='gridHeaderRow'>"
	for (i in this.headers)
		HTML+=this.headers[i].toString();
	HTML+="</tr>";
	if (sortTree)
		HTML+=sortTree.getResults(this);
	else
		for (y in this.rows)
			HTML+=this.rows[y].toString();
	if (this.hasTotals())
	{
		HTML+="<tr>";
		for (i in this.headers)
			HTML+=this.headers[i].totalToString();
		HTML+="</tr>";
	}
	return HTML+"</table>";
};

grid.prototype.find=function(id)
{
	return gridControl.find(id);
};

grid.prototype.headerClick=function(id)
{
	var n=-1;
	var h=null;
	for (var i=0; i<this.headers.length; i++)
		if (this.headers[i].id==id)
		{
			n=i;
			h=this.headers[i];
			break;
		}
	if (n>=0)
	{
		with (this.currentSort)
		{
			if ((order==gsNone) || (column!=n))
				order=gsAsc;
			else
				order=3-order;
			column=n;
//			this.execute(gridControl.scriptRoot+"gridFunc.htm?action=sort&order="+order+"&column="+column);
			this.sortGrid();
			this.headers[column].setSortIcon(order);
		}
	}
};

// Search based on header contents
grid.prototype.search=function(id, value)
{
	var n=-1;
	var h=null;
	for (i in this.headers)
		if (this.headers[i].id==id)
		{
			this.execute(gridControl.scriptRoot+"gridFunc.htm"
			 +"?action=search&hdr="+id+"&value="+value);
			break;
		}
}

// Actual search; called back by iFrame
grid.prototype.doSearch=function(id, value)
{
	var n=-1;
	var h=null;
	for (i in this.headers)
		if (this.headers[i].id==id)
		{
			n=i;
			h=this.headers[i];
			break;
		}
		else
			this.headers[i].clearSearch();
	if (n>=0)
	{
		h.search=value;
		for (i in this.rows)
			this.rows[i].filter(n,value);
	}
};

// Sort function -- uses a Tree Sort
grid.prototype.sortGrid=function()
{
	var t=new gridSortTree();
	for (i in this.rows)
		with (this.currentSort)
			t.add(i, this.rows[i].items[column], order);
	var x=document.getElementById(this.id);
	x.innerHTML=this.innerString(t);
};

// This is a Sort Tree for the Grid
function gridSortTree()
{
	this.nodeList=[];
}

// Add cell to the tree: index=row index; col=actual column
gridSortTree.prototype.add=function(index,col,sortorder)
{
	var n=new gridSortNode(index,col);
	var c=0;
	try
	{
		c=col.value? col.value.charCodeAt(0):32;
	}
	catch(e){}
	try
	{
		if (sortorder==gsDesc)
		{
			c=255-c;
			this.nodeList[c].addDesc(n);
		}
		else
			this.nodeList[c].add(n);
	}
	catch(e)
	{
		this.nodeList[c]=n;
	}
};

// Return HTML for the sorted Grid rows
gridSortTree.prototype.getResults=function(grid)
{
	var HTML="";
	for (var i=0; i<256; i++)
		try
		{
			HTML+=this.nodeList[i].output(grid);
		}
		catch(e){}
	return HTML;
};


// Sortable Node for the gridSortTree. index=grid row index; col=column object
function gridSortNode(index,col)
{
	this.index=index;
	this.col=col;
}
gridSortNode.prototype.left=null;  // Left-branch in Tree
gridSortNode.prototype.right=null; // Right-branch in Tree

// Add a sub-node to a Node
gridSortNode.prototype.add=function(node)
{
	if ((node.col.value? node.col.value : " ")<(this.col.value? this.col.value : " "))
	{
		if (this.left)
			this.left.add(node);
		else
			this.left=node;
	}
	else if (this.right)
		this.right.add(node);
	else
		this.right=node;
};

// Add a sub-node to a Node (descending order)
gridSortNode.prototype.addDesc=function(node)
{
	if ((node.col.value? node.col.value : " ")>(this.col.value? this.col.value : " "))
	{
		if (this.left)
			this.left.addDesc(node);
		else
			this.left=node;
	}
	else if (this.right)
		this.right.addDesc(node);
	else
		this.right=node;
};

// Return Output for a Node
gridSortNode.prototype.output=function(grid)
{
	var HTML="";
	if (this.left)
		HTML+=this.left.output(grid);
	HTML+=grid.rows[this.index].toString();
	if (this.right)
		HTML+=this.right.output(grid);
	return HTML;
};