function init() {
    $$("table.sortable thead td").each( function(cell) { 
	cell.innerHTML="<span onclick='sort(this)'>"+cell.innerHTML+"</span>";
    } );
}

function sort(span) {
    var table = getAncestor(span, "table");
    var rows = $A(table.rows);
    var bodyRows = rows.without(rows.first());
    var position = getCellIndex(getAncestor(span, "td"));
    var getCellText = function(row) { return getText($A(row.getElementsByTagName("td"))[position]); }
    var columnValues = bodyRows.map(getCellText);

    var simpleCompare = function(a,b) { return a < b ? -1 : a == b ? 0 : 1; };
    var compareComposer = function(normalizeFn) { return function(a,b) { return simpleCompare(normalizeFn(a), normalizeFn(b) ) }; }
    var compareFunctions = {
	"caseSensitive" : simpleCompare ,
	"text" : compareComposer(function(a) { return a.toLowerCase(); }) ,
	// Extracts the first numeric part of a string
	"numeric" : compareComposer(function(a) { return parseInt(a.replace(/^.*?(\d+).*$/,"$1")) }) ,
	// Extracts the first numeric part of a string
	"money" : compareComposer(function(a) { return parseInt(a.replace(/,/gi,"").replace(/$/gi,"").replace(/^.*?(\d+).*$/,"$1")) }) ,
	// Expects an ISO date format "13 MAR 2006 10:17:02 GMT"
	"date" : compareComposer(Date.parse) ,
	// Converts a date "13 MAR 10:17" to "13 MAR 2000 10:17", which is an ISO date format usable by Date.parse
	"shortDate" : compareComposer(function(a) { return Date.parse(a.replace(/^(\d+)\s*(\w+)\s*(\d+:\d+)$/,"$1 $2 2000 $3")); })

    }
    var className = getAncestor(span, "td").className;
    var sortfn = (compareFunctions[className]!=null) ? compareFunctions[className] : compareFunctions["text"] ;

    var order = (span.className=="ascending") ? 1 : -1;
    span.className= (order==-1) ? "ascending" : "descending";
    bodyRows.sort(function(rowA, rowB) { return order * sortfn( getCellText(rowA), getCellText(rowB) ); });
    bodyRows.each(function(row) { table.tBodies[0].appendChild(row); })
}

function getText(element) { return (element.textContent) ? element.textContent : element.innerText; }

// The td.cellIndex member doesn't work in Safari, so use a function to do the same task
function getCellIndex(td) { return $A(td.parentNode.cells).indexOf(td); }

function getAncestor(element, type) { 
    var ancestor = element; 
    while(ancestor && ancestor.tagName.toUpperCase()!=type.toUpperCase()) { ancestor=ancestor.parentNode; } 
    return ancestor; 
}

Event.observe(window, 'load', init, false);
