// sorttable.js
// Javascript file for ERM sortable tables

var ERMSortTable =
{
	// Commas are important to seperate JSON member variables/functions! Don't forget them!
	bSorting: 0,						// whether or not we are currently sorting
	iCurrentTable: 0,					// current working table
	iSortColumnIndex: 0,				// column to sort by
	paTables: new Array(),			// array of pointers to tables
	pClickedHeader: null,			// pointer to clicked header
	pClickedDropdown: null,			// pointer to clicked dropdown menu arrow (div within th)
	pPleaseWait: null,				// pointer to "Sorting..." div
	pTimeout: null,					// pointer to Filter keypress setTimeout event, since we may need to cancel
	pEventTarget: null,				// pointer to stored Event (used by evtFilterKeyPress)

	init: function()
	{
		// Declare pointers to all the nodes here for speed effeciency later
		if (!document.getElementsByTagName)
			return;

		// Reset these just in case we're calling init() again
		ERMSortTable.bSorting = false;
		ERMSortTable.iCurrentTable = 0;
		ERMSortTable.iSortColumnIndex = 0;
		ERMSortTable.pPleaseWait = document.getElementById('sorttablepleasewait');
		var aTables = document.getElementsByTagName('table');

		var tablelen = aTables.length;
		for (var i = 0; i < tablelen; i++)
		{
			var table = aTables[i];
			if (table && table.className.indexOf("sortable") != -1)
				ERMSortTable.makeSortable(table);
		}

		// This loops through every table, so call it last
		ERMSortTable.evtResize();

		ERM.attachEvent(document, 'click', ERMSortTable.evtClick);
		ERM.attachEvent(window, 'resize', ERMSortTable.evtResize);
	}, // Comma is important to seperate JSON functions

	makeSortable: function(table)
	{
		// First thing's first - make sure this table hasn't already been added. If so, return
		for (var i = 0; i < ERMSortTable.paTables.length; i++)
			if (ERMSortTable.paTables[i] == table)
				return;

		// Push the pointer to this table into the array of table pointers
		ERMSortTable.paTables[ERMSortTable.paTables.length] = table;

		// Figure out whether the header should be the old simple type, or the new dropdown menu
		var bOldHeader = !(table && table.className.indexOf('headerdropdown') != -1);

		// Get the header row - try <thead> first, and failing that just use the top row
		var headerRow = 0;
		if (table.tHead && table.tHead.rows && table.tHead.rows.length > 0)
			headerRow = table.tHead.rows[0];
		else if (table && table.rows && table.rows.length > 0)
			headerRow = table.rows[0];

		if (!headerRow || !headerRow.cells)
			return;

		headerRow.className = 'sortheader';

		// We have a header, now make its contents clickable links
		for (var i = 0; i < headerRow.cells.length; i++)
		{
			var cell = headerRow.cells[i];

			// Skip non-sortable headers
			if (cell.className == 'nosort')
				continue;

			var txt = ERMSortTable.getInnerText(cell);

			// If we're outputting the old style header (no dropdown), the HTML we spit out is slightly different.
			if (bOldHeader)
			{
				cell.innerHTML = '<a href="#" onclick="return ERMSortTable.clickSort(' + (ERMSortTable.paTables.length - 1)
									+ ', this);" title="Sort By: ' + txt + '">' + txt + '</a>';
			}
			else
			{
				cell.innerHTML = '<div class="heading" onclick="ERMSortTable.clickSort(' + (ERMSortTable.paTables.length - 1)
									+ ', this);" title="Sort By: ' + txt + '">'
									+ '<div class="button" title="Click to show menu" onclick="ERMSortTable.clickMenu('
									+ (ERMSortTable.paTables.length - 1) + ', this, event);"></div>'
									+ txt + '</div>';


				cell.onmouseover = function() { ERMSortTable.hover(this); };
				cell.onmouseout = function() { ERMSortTable.unhover(this); };
			}

			// Make it so they can click the entire TH to fire off the javascript
			cell.onclick = function() { this.firstChild.onclick(); };
			cell.style.cursor = 'pointer';
		}

		if (!bOldHeader)
			ERMSortTable.makeAutofilter(table);
	},

	makeAutofilter: function(table)
	{
		// First, inject the AutoFilter row into the table's HTML
		var blanktr = table.insertRow(1);

		var tr = document.createElement('tr');
		tr.className = 'autofilter';
		tr.style.display = 'none';
		tr.setAttribute('class', 'autofilter');
		tr.setAttribute('style', 'display: none');

		for (var i = 0; i < table.rows[0].cells.length; i++)
		{
			var headercell = table.rows[0].cells[i];
			var td = document.createElement('td');

			// nosort rows can't be autofiltered
			var className = headercell.className;
			if (className != 'nosort')
			{
				td.innerHTML = '<input type="text" autocomplete="off" onkeyup="ERMSortTable.evtFilterKeyPress(this);"'
									+ ' onchange="ERMSortTable.changeFilter(' + (ERMSortTable.paTables.length - 1) + ', this);" />';
			}

			tr.appendChild(td);
		}

		blanktr.parentNode.replaceChild(tr, blanktr);

		// Store a pointer to this autofilter row for later
		table.autofilterrow = tr;

		// Read the cookie and see if this table is in it
		if (!table.id.match(/erm\d+/)) // Don't bother for tables w/ a randomly assigned id
		{
			var a = new Array();
			var x = ERM.readCookie('autofilter');
			if (x && x.length)
				a = x.split('|');

			for(var i = 0; i < a.length; i++)
			{
				if (a[i] == table.id)
				{
					ERMSortTable.showAutoFilter(table, true);
					break;
				}
			}
		}
	},

	// Triggers below delayed click event, to give time for clicking things before clearing out pClickedDropdown
	evtClick: function()
	{
		if (ERMSortTable.pClickedDropdown)
			setTimeout(ERMSortTable.evtClickDelay, 50);
	},

	// Global click event - used for clearing pClickedDropdown if needed
	evtClickDelay: function()
	{
		cell = ERMSortTable.pClickedDropdown.parentNode;
		ERMSortTable.pClickedDropdown = null;
		ERMSortTable.unhover(cell);
	},

	// Resize event
	evtResize: function()
	{
		// For each table we have, resize the button div
		for (var t = 0; t < ERMSortTable.paTables.length; t++)
		{
			var table = ERMSortTable.paTables[t];
			if (!table.rows || !table.rows.length || !table.rows[0])
				continue;

			var tr = table.rows[0];
			var len = tr.cells.length;

			// First get the biggest height of all the cells - Safari Mac needs to iterate each one
/*			IE has major problems with this being very, very slow. Newer versions of Safari work fine, so I'm just going to leave this alone.
			var height = 0;
			for (var i = 0; i < len; i++)
				height = Math.max(height, Math.floor((tr.cells[i].offsetHeight + tr.cells[i].clientHeight) / 2));
*/
			var height = Math.floor((tr.cells[0].offsetHeight + tr.cells[0].clientHeight) / 2);

			// Now apply that height to every button
			for (var i = 0; i < len; i++)
			{
				var cell = tr.cells[i];

				// Check that this is the new dropdown type sortable, and not the old type.
				if (cell && cell.firstChild && cell.firstChild.nodeName == 'DIV' && cell.firstChild.firstChild)
					cell.firstChild.firstChild.style.height = height + 'px';
			}
		}
	},

	hover: function(cell)
	{
		cell.firstChild.className = "heading hover";
	},

	unhover: function(cell)
	{
		if (ERMSortTable.pClickedDropdown && ERMSortTable.pClickedDropdown.parentNode == cell)
			return;

		cell.firstChild.className = "heading";
	},

	clickMenu: function(currentTable, clickedButton, e)
	{
		var table = ERMSortTable.paTables[currentTable];
		if (!table)
			return;

		// If we've already got a menu open remove it's hover, unless we are just clicking the same button again
		if (ERMSortTable.pClickedDropdown && ERMSortTable.pClickedDropdown != clickedButton.parentNode)
		{
			var tmp = ERMSortTable.pClickedDropdown;
			ERMSortTable.pClickedDropdown = null;
			ERMSortTable.unhover(tmp.parentNode);
		}
		ERMSortTable.pClickedDropdown = clickedButton.parentNode;

		// This is a terrible spot to put this, but I can't think of anywhere better right now.
		clickedButton.edmOffsetLeft = -1;
		clickedButton.edmOffsetTop = parseInt(clickedButton.style.height) - 4;

		if (typeof(ERMDropdown) != 'undefined')
		{
			// Before we show the menu, see if we should check the Ascending or Descending menu line
			var wrapper = document.getElementById('edm_' + table.id);
			if (wrapper)
			{
				var dir = ERMSortTable.pClickedDropdown.parentNode.className;
				var items = wrapper.getElementsByTagName('A');
				var itemlen = items.length;

				for (var i = 0; i < itemlen; i++)
				{
					var item = items[i];

					if (dir == 'asc' && item.className == 'icon sortasc')
						item.className = 'icon sortasc checked';
					else if (dir != 'asc' && item.className == 'icon sortasc checked')
						item.className = 'icon sortasc';
					else if (dir == 'desc' && item.className == 'icon sortdesc')
						item.className = 'icon sortdesc checked';
					else if (dir != 'desc' && item.className == 'icon sortdesc checked')
						item.className = 'icon sortdesc';
				}
			}

			ERMDropdown.hide(); // Explicitly hide all menus, even if we're just going to show the same one again
			ERMDropdown.show('edm_' + table.id, clickedButton, e);
		}

		// Prevent bubbling (to stop parentNode onclick)
		// Once again, <3 QuirksMode http://www.quirksmode.org/js/events_order.html
		if (!e) var e = window.event;
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
	},

	// Context-sensitive sort called from dropdown menu. Need a bit of hackery to call the correct function
	contextSort: function(dir)
	{
		if (!ERMSortTable.pClickedDropdown)
			return;

		if (dir != 'desc')
			dir = 'asc';

		var cell = ERMSortTable.pClickedDropdown.parentNode;

		// If we area already sorted the way we want, just return
		if (cell.className && cell.className == dir)
			return;

		// Change header's class to the opposite of what we want
		cell.className = (dir == 'desc' ? 'asc' : 'desc');

		// Call that active menu's click event
		ERMSortTable.pClickedDropdown.onclick();
	},

	clickSort: function(currentTable, clickedHeader)
	{
		// If we're already sorting or filtering, ignore this click
		if (ERMSortTable.bSorting)
			return;

		ERMSortTable.bSorting = true;
		ERMSortTable.iCurrentTable = currentTable;
		ERMSortTable.pClickedHeader = clickedHeader;

		ERMSortTable.showPleaseWait('Sorting...');
		ERM.showWaitCursor();

		// Use a setTimeout so that the screen has a chance to refresh first
		setTimeout(ERMSortTable.resortTable, 50);

		return false;
	},

	resortTable: function()
	{
		var table = ERMSortTable.paTables[ERMSortTable.iCurrentTable];
		var tableRowsLength = table.tBodies[0].rows.length;

		// nothing to sort?
		if (tableRowsLength < 2 || (tableRowsLength == 2 && table.tBodies[0].rows[1].className == 'sortbottom'))
		{
			ERMSortTable.finishSort();
			return;
		}

		var th = ERMSortTable.pClickedHeader.parentNode;
		ERMSortTable.iSortColumnIndex = ERMSortTable.getClickedIndex(th);

		// Move the <table> elements into temp variables. Also get the colorArray while we're at it.
		var pHead = null;
		var pFoot = null;
		var pAutofilter = null;
		var sortedBody = new Array();
		var sortedBodyCount = 0;

		// Build array of colors to use for striping
		var bNeedColors = (table.colorArray ? false : true);
		var colorArrayLen = (bNeedColors ? 0 : table.colorArray.length);
		var iUsedColors = 0;

		for (var i = 0; i < tableRowsLength; i++)
		{
			var row = table.tBodies[0].rows[i];

			if (bNeedColors)
				colorArrayLen = ERMSortTable.getBackgroundColor(table, row);

			// First thing we need to do is check for open subtables. If we find one, close it.
			if (row.cells[0].childNodes.length)
			{
				var spans = row.cells[0].getElementsByTagName('span');
				if (spans.length && spans[0].onclick)
				{
					var img = spans[0].firstChild;
					if (img && img.nodeName.toLowerCase() == 'img' && img.className == 'BlackTri' && img.alt == 'Collapse')
					{
						spans[0].onclick(); // Close the subtable
						tableRowsLength--;
					}
				}
			}

			if (row.className == "sortheader")
				pHead = row;
			else if (row.className == "sortbottom")
				pFoot = row;
			else if (row.className == "autofilter")
				pAutofilter = row;
			else
				sortedBody[sortedBodyCount++] = row;
		}

		// Sort the array
		var sortinfo = ERMSortTable.getSortInfo(th.className);
		ERMSortTable.sort(sortinfo, sortedBody);
		th.className = sortinfo[1];


		// Append the thead, tfoot, and tbody back to the <table> DOM node.
		if (!table.tHead)
			table.appendChild(document.createElement('thead'));
		if (!table.tFoot)
			table.appendChild(document.createElement('tfoot'));

		// Find pointers to the tbody, thead, and tfoot elements
		var tbody = null;
		var thead = null;
		var tfoot = null;
		var tablelen = table.childNodes.length;
		for (var i = 0; i < tablelen; i++)
		{
			var row = table.childNodes[i];
			var tagName = row.tagName;
			if (!tagName)
				continue;

			tagName = tagName.toLowerCase();

			if (tagName == 'thead')
				thead = row;
			else if (tagName == 'tfoot')
				tfoot = row;
			else if (tagName == 'tbody')
				tbody = row;
		}

		// Okay, now we can append to the appropriate areas
		if (thead && pHead)
			thead.appendChild(pHead);
		if (tfoot && pFoot)
			tfoot.appendChild(pFoot);
		if (tbody)
		{
			for (var i = 0; i < sortedBodyCount; i++)
			{
				// Re-stripe the colors in the table
				if (sortedBody[i].style.display != 'none')
				{
					var c = iUsedColors++ % colorArrayLen;
					sortedBody[i].style.backgroundColor = table.colorArray[c];
				}

				// Now append to tbody
				tbody.appendChild(sortedBody[i]);
			}
		}


		// Remove the sort status of other columns besides the current one
		var headers = table.getElementsByTagName('TH');
		var headerslen = headers.length;
		for (var i = 0; i < headerslen; i++)
			if (headers[i] != th)
			headers[i].className = '';

		ERMSortTable.finishSort(th);
	},

	// Sets a timeout event when the user types to manually trigger the onchange event
	evtFilterKeyPress: function(input)
	{
		// If we have an existing timeout event, cancel it
		if (ERMSortTable.pTimeout != null)
		{
			clearTimeout(ERMSortTable.pTimeout);
			ERMSortTable.pTimeout = null;
		}

		// Adjust timeout delay based on the number of rows in the table
		var rowlen = input.parentNode.offsetParent.rows.length;
		var delay = (rowlen > 250 ? 1200 : 250);

		// Save input to pEventTarget because it won't be passed to onchange properly
		ERMSortTable.pEventTarget = input;
		ERMSortTable.pTimeout = setTimeout(input.onchange, delay);
	},

	changeFilter: function(table, input)
	{
		// If we have an existing timeout event, cancel it
		if (ERMSortTable.pTimeout != null)
			clearTimeout(ERMSortTable.pTimeout);

		// clear ERMSortTable.pTimeout now
		ERMSortTable.pTimeout = null;

		// If we got here via evtFilterKeyPress() then input be a pointer to Window, not the correct <input> node
		if (!input || input.nodeType != 1)
			input = ERMSortTable.pEventTarget;

		ERMSortTable.pEventTarget = null;

		if (!input || !input.nodeType)
			return;

		if (ERMSortTable.bSorting) // If we get here, we are currently in our filtering alg. and can't return, so just abort
			return;

		// If we haven't changed search since last filter, just return
		if (!input.oldSearch)
			input.oldSearch = ERMSortTable.getFilterTerm('', true);
		var oSearch = ERMSortTable.getFilterTerm(input.value, true);
		if (oSearch.text == input.oldSearch.text && oSearch.operator == input.oldSearch.operator)
			return;
		else
			input.oldSearch = oSearch;	// Store value of this search for next time

		ERMSortTable.bSorting = true; // Okay we're really filtering, but really we don't want to allow anything until we finish
		ERMSortTable.iCurrentTable = table;
		ERMSortTable.showPleaseWait('Filtering...');
		ERM.showWaitCursor();

		// Use a setTimeout so that the screen has a chance to refresh first
		setTimeout(ERMSortTable.filterTable, 50);

		return false;
	},

	filterTable: function()
	{
		var table = ERMSortTable.paTables[ERMSortTable.iCurrentTable];
		var rowlen = table.rows.length;

		// Count all the filter terms
		var aFilterTerms = new Array();
		var iFilterTerms = 0;
		var collen = table.autofilterrow.cells.length;
		for (var i = 0; i < collen; i++)
		{
			var input = table.autofilterrow.cells[i].firstChild;
			if (input)
				aFilterTerms[i] = ERMSortTable.getFilterTerm(input.value, true);
			else
				aFilterTerms[i] = null;

			if (aFilterTerms[i] && aFilterTerms[i].text)
				iFilterTerms++;
		}

		// Build array of colors to use for striping
		var bNeedColors = (table.colorArray ? false : true);
		var colorArrayLen = (bNeedColors ? 0 : table.colorArray.length);
		var iUsedColors = 0;

		for (var i = 0; i < rowlen; i++)
		{
			var row = table.rows[i];

			switch(row.className)
			{
				case 'sortheader':
				case 'autofilter':
					continue;
				case 'sortbottom':
					// We can't really show/hide the total row due to DOM problems. So instead we change the value of the cells
					var cellslen = row.cells.length;
					for (var k = 0; k < cellslen; k++)
					{
						var cell = row.cells[k];
						if (iFilterTerms == 0)
						{
							cell.innerHTML = cell.oldValue;
							cell.oldValue = null;
						}
						else if (cell.oldValue == null)
						{
							cell.oldValue = cell.innerHTML;
							cell.innerHTML = '&nbsp;';
						}
					}

					continue;
			}

			if (bNeedColors)
				colorArrayLen = ERMSortTable.getBackgroundColor(table, row);

			// Filter all cells
			// We have to traverse through all cells again because removing terms cannot be easily hacked together like adding can
			var iCellShowCount = 0;
			var cellslen = row.cells.length;
			for (var k = 0; k < cellslen; k++)
			{
				var oFilterTerm = aFilterTerms[k];

				// Filtering logic
				if (!oFilterTerm || !oFilterTerm.text)
					iCellShowCount++;
				else
				{
					var celltext = ERMSortTable.getFilterTerm(ERMSortTable.getInnerText(row.cells[k]));
					if (!celltext)
					{
						if (oFilterTerm.operator == '!')
							iCellShowCount++;
						continue;
					}

					switch(oFilterTerm.operator)
					{
						case '>':
							if (parseFloat(celltext) >= parseFloat(oFilterTerm.text))
								iCellShowCount++;
							break;
						case '<':
							if (parseFloat(celltext) <= parseFloat(oFilterTerm.text))
								iCellShowCount++;
							break;
						case '=':	// Exact matches only
							if (celltext == oFilterTerm.text)
								iCellShowCount++;
							break;
						case '!':	// Negation of partial match
							if (celltext.indexOf(oFilterTerm.text) < 0)
								iCellShowCount++;
							break;
						default:		// Partial matches
							if (celltext.indexOf(oFilterTerm.text) >= 0)
								iCellShowCount++;
							break;
					}
				}
			}

			// Only show a row if it matches *all* supplied criteria
			if (iCellShowCount >= aFilterTerms.length)
				row.style.display = '';
			else
				row.style.display = 'none';

			// Re-stripe the colors in the table
			if (row.style.display != 'none')
			{
				var c = iUsedColors++ % colorArrayLen;
				row.style.backgroundColor = table.colorArray[c];
			}
		}

		ERMSortTable.finishSort();
	},

	showAutoFilter: function(table, bShow)
	{
		if (!table.autofilterrow)
			return;

		// Set or unset a cookie for showing the filter again
		if (!table.id.match(/erm\d+/)) // Don't bother for tables w/ a randomly assigned id
		{
			var a = new Array();
			var x = ERM.readCookie('autofilter');
			if (x && x.length)
				a = x.split('|');

			if (bShow)
			{
				// Add the table to the cookie if it isn't already there
				var dupe = false;
				for(var i = 0; !dupe && i < a.length; i++)
					if (a[i] == table.id)
						dupe = true;

				if (!dupe)
					a[a.length] = table.id;
			}
			else
			{
				// Remove the table from the cookie if it is there
				for(var i = 0; i < a.length; i++)
					if (a[i] == table.id)
						a.splice(i--, 1);
			}

			ERM.createCookie('autofilter', a.join('|'), 365);
		}

		// Clear the autofilter if we're hiding
		if (!bShow)
		{
			var pChangedCell = null;
			for(var i = 0; i < table.autofilterrow.cells.length; i++)
			{
				var input = table.autofilterrow.cells[i].firstChild;
				if (input && input.value && ERMSortTable.getFilterTerm(input.value))
				{
					input.value = '';
					pChangedCell = input;
				}
			}

			// Manually trigger the evtFilterKeyPress event
			if (pChangedCell)
				ERMSortTable.evtFilterKeyPress(pChangedCell);
		}


		table.autofilterrow.style.display = (bShow ? '' : 'none');
	},

	// EDM-specific function to toggle autofilter on/off
	toggleAutoFilter: function(id, menu)
	{
		// Try to pull the clicked table, if possible, in case we have multiple tables with the same id
		var table = null;
		if (ERMSortTable.pClickedDropdown)
		{
			var th = ERMSortTable.pClickedDropdown.parentNode;
			if (th)
			{
				var tr = th.parentNode;
				if (tr)
				{
					var tbody = tr.parentNode;
					if (tbody)
					{
						var maybetable = tbody.parentNode;
						if (maybetable && maybetable.id == id)
							table = maybetable;
					}
				}
			}
		}

		// Okay just get the first one w/ this id. Shouldn't be more than one anyway usually.
		if (!table)
			table = document.getElementById(id);

		if (!table)
			return;

		var show = (menu.className == 'icon checked' ? false : true); // Toggle checked status

		ERMSortTable.showAutoFilter(table, show);
		menu.className = (show ? 'icon checked' : 'icon');

		return false;
	},

	// sort() function allows for easy overriding of sort behavior by child classes
	sort: function(sortinfo, sortedBody)
	{
		sortedBody.sort(sortinfo[0]);
	},

	finishSort: function(th)
	{
		if (th)
			ERMSortTable.setAjaxSortOrder(th);

		ERMSortTable.hidePleaseWait();
		ERMSortTable.bSorting = false;
		ERM.hideWaitCursor();
	},

	// Picks the sorting algorithm based off the column data type
	// Note: dir is a tristate variable: '', 'asc', 'desc';	'' means no sort yet
	getSortInfo: function(dir)
	{
		var table = ERMSortTable.paTables[ERMSortTable.iCurrentTable];

		// First, try to pick up the sort type from the header's axis field.
		var axis = table.rows[0].cells[ERMSortTable.iSortColumnIndex].axis;
		if (axis && axis.length)
		{
			switch(axis)
			{
				case 'currency':
				case 'dollar':
					if (dir == 'desc') // DESC is default sort for numbers/currency
						return new Array(ERMSortTable.sort_currency_asc, 'asc');
					else
						return new Array(ERMSortTable.sort_currency_desc, 'desc');
					break;
				case 'filesize':
					if (dir == 'asc')
						return new Array(ERMSortTable.sort_filesize_desc, 'desc');
					else
						return new Array(ERMSortTable.sort_filesize_asc, 'asc');
					break;
				case 'number':
				case 'decimal':
					if (dir == 'desc') // DESC is default sort for numbers/currency
						return new Array(ERMSortTable.sort_numeric_asc, 'asc');
					else
						return new Array(ERMSortTable.sort_numeric_desc, 'desc');
					break;
				case 'date':
					if (dir == 'asc')
						return new Array(ERMSortTable.sort_date_desc, 'desc');
					else
						return new Array(ERMSortTable.sort_date_asc, 'asc');
					break;
			}
		}

		// Okay, that didn't work. Try getting it off the text in the first cell instead...
		var row = table.rows[1];
		if (row.className == 'autofilter')
			row = table.rows[2];

		var celltext = ERMSortTable.getInnerText(row.cells[ERMSortTable.iSortColumnIndex]);
		if (celltext && celltext.length)
			celltext = celltext.replace(/^\xA0+|\xA0+$/g, '');	// Trim out &nbsp; (0xA0)
		if (!celltext || !celltext.length) // Okay um... try the last cell then...
		{
			celltext = ERMSortTable.getInnerText(table.rows[table.rows.length - 1].cells[ERMSortTable.iSortColumnIndex]);
			if (celltext && celltext.length)
				celltext = celltext.replace(/^\xA0+|\xA0+$/g, '');	// Trim out &nbsp; (0xA0)
		}

		if (celltext && celltext.length)
		{
			if (celltext.match(/^[£$]/))
			{
				if (dir == 'desc') // DESC is default sort for numbers/currency
					return new Array(ERMSortTable.sort_currency_asc, 'asc');
				else
					return new Array(ERMSortTable.sort_currency_desc, 'desc');
			}
			if (celltext.match(/^\d\d[\.\/-]\d\d[\.\/-]\d\d\d\d$/) || celltext.match(/^\d\d[\.\/-]\d\d[\.\/-]\d\d$/))
			{
				if (dir == 'asc')
					return new Array(ERMSortTable.sort_date_desc, 'desc');
				else
					return new Array(ERMSortTable.sort_date_asc, 'asc');
			}
			if (celltext.match(/^[\d\.,]+(K|M|G|T)?B$/))
			{
				if (dir == 'asc')
					return new Array(ERMSortTable.sort_filesize_desc, 'desc');
				else
					return new Array(ERMSortTable.sort_filesize_asc, 'asc');
			}
			if (celltext.match(/^[\d\%]/) || celltext.match(/^[\d\.,]+$/))
			{
				if (dir == 'desc') // DESC is default sort for numbers/currency
					return new Array(ERMSortTable.sort_numeric_asc, 'asc');
				else
					return new Array(ERMSortTable.sort_numeric_desc, 'desc');
			}
		}

		// Okay we can't find a sort. spit out the defaults...
		if (dir == 'asc')
			return new Array(ERMSortTable.sort_caseinsensitive_desc, 'desc');
		return new Array(ERMSortTable.sort_caseinsensitive_asc, 'asc');
	},

	// Returns index of clicked <th>
	getClickedIndex: function(th)
	{
		var isSafari = navigator.userAgent.indexOf("Safari") != -1;

		if (!isSafari || th.cellIndex != 0)
			return th.cellIndex;

		var tr = th.parentNode;
		var celllen = tr.cells.length;

		for (var i = 0; i < celllen; i++)
			if (th == tr.cells[i])
				return i;

		return -1;
	},

	getInnerText: function(node)
	{
		if (typeof node == "undefined") return node;
		if (typeof node == "string") return ERM.trim(node);

		if (node.nodeName.toLowerCase() == 'img')
		{
			if (node.alt) return node.alt;
			if (node.title) return node.title;
			if (node.src) return node.src;
		}

		if (ERM.trim(node.innerText)) return ERM.trim(node.innerText);		//Not needed, but it is faster
		if (ERM.trim(node.textContent)) return ERM.trim(node.textContent);	//Not needed, but it is faster

		var str = "";
		var len = node.childNodes.length;
		for (var i = 0; i < len; i++)
		{
			switch (node.childNodes[i].nodeType)
			{
				case 1:	//ELEMENT_NODE
					str += ERMSortTable.getInnerText(node.childNodes[i]);
					break;
				case 3:	//TEXT_NODE
					str += node.childNodes[i].nodeValue;
				break;
			}
		}

		return ERM.trim(str);
	},

	getFilterTerm: function(str, bWantObject)
	{
		var o = { operator: '', text: '' };

		if (!str)
			str = '';

		// If the first character is < > or = then it's an operator
		if (bWantObject)
		{
			switch(str.charAt(0))
			{
				case '=':
				case '>':
				case '<':
				case '!':
					o.operator = str.charAt(0);
					str = str.substring(1);
			}
		}

		// See if str is a date, and if so, pad it from 0/0/00 to 00/00/0000 or 0000-0-0 to 0000-00-00
		var dp = str.match(/^([1-9]|0\d|1[0-2])[\.\/-]([0-2]?\d|3[01])[\.\/-]((19|20)?\d\d)$/);
		if (dp)
		{
			var m = dp[1];
			var d = dp[2];
			var y = dp[3];

			if (m && d && y)
			{
				if (m.length < 2) m = '0' + m;
				if (d.length < 2) d = '0' + d;
				if (y.length == 2) y = (y > 80 ? '19' : '20') + y;

				str = m + d + y;
			}
		}

		// Filter out $ % / and ,   -- also check for numeric
		var tmp = str.replace(/[\$\%\/\,]/g, '');
		if (tmp.length && !isNaN(tmp))
			str = tmp;
		else
			o.operator = (o.operator == '=' || o.operator == '!' ? o.operator : ''); // Only allow '', =, or ! when it's non-numeric

		o.text = ERM.trim(str.toLowerCase());

		if (bWantObject)
			return o;

		return o.text;
	},

	getBackgroundColor: function(table, row)
	{
		var colorlen = (table.colorArray ? table.colorArray.length : 0);

		// Skip header and footer (total) rows
		switch(row.className)
		{
			case 'sortheader':
			case 'sortbottom':
			case 'autofilter':
				return colorlen;
		}

		if (!table.colorArray)
			table.colorArray = new Array();

		var bgColor = row.style.backgroundColor;
		if (!bgColor)
			bgColor = row.bgColor;

		if (bgColor)
		{
			// Make sure there are no duplicates in the array!
			var dupeFound = false;

			for (var c = 0; !dupeFound && c <= colorlen; c++)
				if (table.colorArray[c] && table.colorArray[c] == bgColor)
						dupeFound = true;

			if (!dupeFound)
				table.colorArray[colorlen++] = bgColor;
		}

		return colorlen;
	},


	sort_date_asc: function(a,b)
	{
		// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
		var aa = ERMSortTable.getInnerText(a.cells[ERMSortTable.iSortColumnIndex]);
		var bb = ERMSortTable.getInnerText(b.cells[ERMSortTable.iSortColumnIndex]);

		if (aa.length == 10)
			var dt1 = aa.substr(6,4) + aa.substr(0,2) + aa.substr(3,2);
		else
		{
			var yr = aa.substr(6,2);
			if (parseInt(yr) < 50)
				yr = '20' + yr;
			else
				yr = '19' + yr;
			var dt1 = yr + aa.substr(0,2) + aa.substr(3,2);
		}
		if (bb.length == 10)
			var dt2 = bb.substr(6,4)+bb.substr(0,2)+bb.substr(3,2);
		else
		{
			yr = bb.substr(6,2);
			if (parseInt(yr) < 50)
				yr = '20' + yr;
			else
				yr = '19' + yr;
			var dt2 = yr + bb.substr(0,2) + bb.substr(3,2);
		}

		// Done w/ formatting, now just compare them.
		if (dt1 == dt2) return 0;
		if (dt1 < dt2) return -1;
		return 1;
	},

	sort_filesize_asc: function(a,b)
	{
		var aa = ERMSortTable.getInnerText(a.cells[ERMSortTable.iSortColumnIndex]);
		var bb = ERMSortTable.getInnerText(b.cells[ERMSortTable.iSortColumnIndex]);

		var aunit = aa.replace(/^[\d\.,]+/, ''); // Get B,KB,MB,GB,TG
		var bunit = bb.replace(/^[\d\.,]+/, '');

		if (aunit && aunit.length && bunit && bunit.length)
		{
			var ai = 0;
			if (aunit == 'KB') ai = 1;
			if (aunit == 'MB') ai = 2;
			if (aunit == 'GB') ai = 3;
			if (aunit == 'TB') ai = 4;

			var bi = 0;
			if (bunit == 'KB') bi = 1;
			if (bunit == 'MB') bi = 2;
			if (bunit == 'GB') bi = 3;
			if (bunit == 'TB') bi = 4;

			// If the file units are different, go with the bigger one
			if (ai > bi)
				return 1;
			if (bi > ai)
				return -1;
		}

		return ERMSortTable.sort_numeric_asc(a, b);
	},

	sort_currency_asc: function(a,b) { return ERMSortTable.sort_numeric_asc(a, b); },

	sort_numeric_asc: function(a,b)
	{
		var aa = ERMSortTable.getInnerText(a.cells[ERMSortTable.iSortColumnIndex]);
		var bb = ERMSortTable.getInnerText(b.cells[ERMSortTable.iSortColumnIndex]);

		if (aa && aa.length)
			aa = aa.replace(/[^0-9.]/g, '');
		if (bb && bb.length)
			bb = bb.replace(/[^0-9.]/g, '');

		var aabb = parseFloat(aa) - parseFloat(bb);

		if (isNaN(aabb)) return ERMSortTable.sort_default_asc(a, b);
		else return aabb;
	},

	sort_caseinsensitive_asc: function(a,b)
	{
		var aa = ERMSortTable.getInnerText(a.cells[ERMSortTable.iSortColumnIndex]);
		var bb = ERMSortTable.getInnerText(b.cells[ERMSortTable.iSortColumnIndex]);

		if (!aa)
			aa = '';
		if (!bb)
			bb = '';

		aa = aa.toLowerCase();
		bb = bb.toLowerCase();

		if (aa==bb) return 0;
		if (aa<bb) return -1;
		return 1;
	},

	sort_default_asc: function(a,b)
	{
		var aa = ERMSortTable.getInnerText(a.cells[ERMSortTable.iSortColumnIndex]);
		var bb = ERMSortTable.getInnerText(b.cells[ERMSortTable.iSortColumnIndex]);

		if (!aa)
			aa = '';
		if (!bb)
			bb = '';

		if (aa==bb) return 0;
		if (aa<bb) return -1;
		return 1;
	},

	// Descending sorts are easy - just call the ascending sort w/ the parameters swapped.
	sort_date_desc: function(a,b) { return ERMSortTable.sort_date_asc(b, a); },
	sort_filesize_desc: function(a,b) { return ERMSortTable.sort_filesize_asc(b, a); },
	sort_currency_desc: function(a,b) { return ERMSortTable.sort_currency_asc(b, a); },
	sort_numeric_desc: function(a,b) { return ERMSortTable.sort_numeric_asc(b, a); },
	sort_caseinsensitive_desc: function(a,b) { return ERMSortTable.sort_caseinsensitive_asc(b, a); },
	sort_default_desc: function(a,b) { return ERMSortTable.sort_default_asc(b, a); },


	setAjaxSortOrder: function(th)
	{
		if (!th || !th.offsetParent || !th.offsetParent.summary || !th.offsetParent.id || typeof Ajax == "undefined" || !Ajax.Request)
			return;

		var sPost = 'pageurl=' + th.offsetParent.summary + '&tblid=' + th.offsetParent.id
						+ '&sortfld=' + ERMSortTable.getInnerText(th) + '&sortdir=' + th.className;

		new Ajax.Request('/seaurchin/ftAjaxSort.php', { postBody: sPost } );
//new Ajax.Request('/seaurchin/ftAjaxSort.php',
//{postBody:sPost,onSuccess:function(response){alert('User sort set to:\n'+response.responseText)}});
	},

	showPleaseWait: function(msg)
	{
		if (!ERMSortTable.pPleaseWait)
			return;

		if (msg)
		{
			var div = ERMSortTable.pPleaseWait.firstChild;
			while (div != null && div.nodeType != 1)
				div = div.nextSibling;


			div.innerHTML = msg;
		}

		// only show if there are a 'large' amount of fields to sort.
		if (ERMSortTable.paTables[ERMSortTable.iCurrentTable].rows.length > 100)
			ERMSortTable.pPleaseWait.style.display = "inline";
	},

	hidePleaseWait: function()
	{
		if (ERMSortTable.pPleaseWait)
			ERMSortTable.pPleaseWait.style.display = "none";
	}
}

ERM.attachEvent(window, 'load', ERMSortTable.init);