

function TableSorter(table, events)
{
	this.table = (typeof(table) == "string") ? $(table) : table;

	if (events)
	{
		for (var key in this.events)
			if (typeof(events[key]) == "function")
				this.events[key] = events[key];
	}
}

TableSorter.prototype.sortedBy = null;
TableSorter.prototype.sortedDesc = false;
TableSorter.prototype.events = {
	onBeforeRowAppend	: null,
	onAfterSort	: null,
	onBeforeSort	: null
};

TableSorter.prototype.sort = function(columnIndex, comparer)
{
	if (typeof(this.events["onBeforeSort"]) == "function")
		this.events["onBeforeSort"](columnIndex);

	var sortable = new Array();
	var tbody = this.table.tBodies[0];
	for (var i=0; i<tbody.rows.length; i++)
		sortable.push(tbody.rows[i]);

	if (!comparer)
		comparer = new TableSorter.InnerTextComparer(false, false);

	sortable = sortable.sort(
		function(a, b)
		{
			if (typeof(comparer) == "function")
				comparer(a.cells.item(columnIndex), b.cells.item(columnIndex));
			else if (typeof(comparer) == "object" && typeof(comparer.compare) == "function")
				return comparer.compare(a.cells.item(columnIndex), b.cells.item(columnIndex));

			throw new Error("TableSorter.sort() -> comparer object expected.");
		}
	);



	if (this.sortedBy == columnIndex)
	{
		this.sortedDesc = !this.sortedDesc;
		if (this.sortedDesc)
			sortable = sortable.reverse();
	}
	else
	{
		this.sortedBy = columnIndex;
		this.sortedDesc = false;
	}

	var events = this.events;
	var applySort = function()
	{
		for (var i=0; i<sortable.length; i++)
		{
			var row = sortable[i];
			tbody.removeChild(row);

			if (typeof(events["onBeforeRowAppend"]) == "function")
				events["onBeforeRowAppend"](row, i);

			tbody.appendChild(row);
		}

		if (typeof(events["onAfterSort"]) == "function")
			events["onAfterSort"](columnIndex);
	}

	window.setTimeout(applySort, 100);
}







TableSorter.InnerTextComparer = function(isNumeric, caseSensitive)
{
	this.isNumeric = isNumeric ? true : false;
	this.caseSensitive = caseSensitive ? true : false;
}

TableSorter.InnerTextComparer.prototype.compare = function(a, b)
{
	var atext = this.getInnerText(a).replace(/^\s+|\s+$/g);
	var btext = this.getInnerText(b).replace(/^\s+|\s+$/g);

	if (this.isNumeric)
	{
		atext = Number(atext);
		btext = Number(btext);
		if (atext > btext)
			return 1;
		else if (atext < btext)
			return -1;
		else
			return 0;
	}
	else
	{
		if (!this.caseSensitive)
		{
			atext = atext.toLowerCase();
			btext = btext.toLowerCase();
		}
		return atext.localeCompare(btext);
	}
}

TableSorter.InnerTextComparer.prototype.getInnerText = function(node)
{
	if (node.nodeType == 3)
	{
		return node.nodeValue;
	}
	else if (node.nodeType == 1)
	{
		if (node._innerText)
			return node._innerText;

		var s = new Array();
		for (var i=0; i<node.childNodes.length; i++)
		{
			var t = this.getInnerText(node.childNodes.item(i));
			if (t != null)
				s.push(t);
		}
		return node._innerText = s.join("");
	}
	else 
		return null;
}





TableSorter.AttributeComparer = function(attributeName, isNumeric, caseSensitive)
{
	this.attributeName = attributeName;
	this.isNumeric = isNumeric ? true : false;
	this.caseSensitive = caseSensitive ? true : false;
}

TableSorter.AttributeComparer.prototype.compare = function(a, b)
{
	var atext = a.getAttribute(this.attributeName);
	var btext = b.getAttribute(this.attributeName);

	if (this.isNumeric)
	{
		atext = Number(atext);
		btext = Number(btext);
		if (atext > btext)
			return 1;
		else if (atext < btext)
			return -1;
		else
			return 0;
	}
	else
	{
		if (!this.caseSensitive)
		{
			atext = atext.toLowerCase();
			btext = btext.toLowerCase();
		}
		return atext.localeCompare(btext);
	}
}
