First NameMiddle NameLast NameAge
RobertJamesAnderson29
HenryJonathanMurray40
EdwardRichardJones32
PatrickElroyEmerson53
LisaAnneSmith49
Click the table heading columns to sort the data.

Table Sorter
The main focus of this experiment was to see what could be done to a raw HTML table that has not been written with the functionality you see in mind, I.E -- no ID's to reference table rows and columns, no hard coded onclick events, etc. As you can see from the source, none of these things exist for the above table -- it is nothing more than bare bones HTML.

Onclick events for the TDs and TRs are provided programatically as the code loops over the length of those elements. Determining what row and column were clicked on is done with the offset properties of the objects and the clientX/pageX, clientY/pageY of the mouse captured from the documents onmousedown event. Once the column or row has been determined, its source index is passed to the appropriate functions which perform the manipulations on the objects.

The rows are sorted when one of the heading columns is clicked. First, the column is determined, which gives us our source index for the TD objects within their parent TR objects. From there, we add their innerHTML and their original source index to a multi-dimensional array, sort it using the sort() method, then using that data we clone them using cloneNode, adding that new object to an array. The code then uses insertBefore to insert the cloned objects into the DOM, in order, and the original objects are removed using removeChild.

Using this method of manipulation leaves the process completely open ended. Any table of data will be sortable with this method, regardless of the number of columns or rows.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>slayeroffice | table sorter</title>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-15" />
<style type="text/css">
body {
	margin:10px;
}
table {
	font-family:verdana;
	font-size:9pt;
	border:1px solid #000000;
	cursor:default;
	width:300px;
	color:#FFFFFF;
}

td {
	padding:5px;
}
</style>
<script language="javascript">
if(!document.all)document.captureEvents(Event.MOUSEDOWN);

var rowStart = 1; // excludes the heading row from manipulations. change to 0 if there is no heading.
var moz_firstSort = true;

var col_evenColor = "#006699";
var col_oddColor = "#00968C";
var col_highlightColor = "#008299";

function init() {
	initRows();
	if(rowStart)initHeading();
}

function initHeading() {
	for(i=0;i<document.getElementsByTagName("tr")[0].childNodes.length;i++) {
		if(document.getElementsByTagName("tr")[0].childNodes[i].tagName == "TD") {
			document.getElementsByTagName("tr")[0].childNodes[i].onclick = initSort;
			document.getElementsByTagName("tr")[0].childNodes[i].style.backgroundColor = "#FFFFFF";
			document.getElementsByTagName("tr")[0].childNodes[i].style.color = "#000000";
			document.getElementsByTagName("tr")[0].childNodes[i].style.cursor = "pointer";
		}
	}
}

function initSort(e) {
	mouseX = document.all?window.event.clientX-11:e.pageX - 11;
	column = returnColumnClicked(mouseX);
	handleSort(column);
}

function initRows() {
	colorizeTableRows();
	for(i=rowStart;i<document.getElementsByTagName("tr").length;i++) {
		document.getElementsByTagName("tr")[i].id = "row" + i;
		document.getElementsByTagName("tr")[i].onclick = handleRowClick;
	}
}

function colorizeTableRows() {
	for(i=rowStart;i<document.getElementsByTagName("tr").length;i++) {
		i%2?document.getElementsByTagName("tr")[i].style.backgroundColor = col_evenColor:document.getElementsByTagName("tr")[i].style.backgroundColor = col_oddColor;
	}
}

function colorizeTableColumn(column) {
	for(i=rowStart;i<document.getElementsByTagName("tr").length;i++) {
		for(z=0;z<document.getElementsByTagName("tr")[i].childNodes.length;z++) {
			if(document.getElementsByTagName("tr")[i].childNodes[z].tagName == "TD")document.getElementsByTagName("tr")[i].childNodes[z].style.backgroundColor = "";
		}
		document.getElementsByTagName("tr")[i].childNodes[column].style.backgroundColor = col_highlightColor;
	}
}

function handleRowClick(e) {
	mouseY = document.all?window.event.clientY-11:e.pageY-11;
	row = returnRowClicked(mouseY);
	highlightTableRow(row);
}

function highlightTableRow(row) {
	colorizeTableRows();
	document.getElementsByTagName("tr")[row].style.backgroundColor = col_highlightColor;
}

function handleSort(column) {
	sortBy = new Array();
	dataID = document.all?column[1]:column[0];
	for(i=rowStart;i<document.getElementsByTagName("tr").length;i++) {
		sortBy[sortBy.length] = document.getElementsByTagName("tr")[i].childNodes[dataID].innerHTML;
	}
	if(rowStart) {
		resetHeaderColors();
		document.getElementsByTagName("tr")[0].childNodes[dataID].style.backgroundColor = col_highlightColor;
	}
	sortRowData(dataID);
}

function resetHeaderColors() {
	for(i=0;i<document.getElementsByTagName("tr")[0].childNodes.length;i++) {
		if(document.getElementsByTagName("tr")[0].childNodes[i].tagName == "TD")document.getElementsByTagName("tr")[0].childNodes[i].style.backgroundColor = "#FFFFFF";
	}
}

function sortRowData(dataID) {
	order = new Array();
	colorizeTableColumn(dataID);
	for(i=rowStart;i<document.getElementsByTagName("tr").length;i++) order[order.length] = new Array(document.getElementsByTagName("tr")[i].childNodes[dataID].innerHTML,i);
	order=order.sort();
	reorderTable(order);
}

function reorderTable(order) {
	mDiv = new Array();
	// create sorted object references
	for(i=0;i<order.length;i++) mDiv[mDiv.length] = document.getElementsByTagName("tr")[order[i][1]].cloneNode(true);
	// insert sorted TR objects
	z=0;
	for(i=rowStart;i<document.getElementsByTagName("tbody")[0].childNodes.length;i++) {
		if(document.getElementsByTagName("tbody")[0].childNodes[i].tagName == "TR") {
			try { 
				if(document.all) {
					document.getElementsByTagName("tbody")[0].insertBefore(mDiv[z],document.getElementsByTagName("tr")[i]); 
				} else {
					document.getElementsByTagName("tbody")[0].insertBefore(mDiv[z],document.getElementsByTagName("tr")[document.getElementsByTagName("tr").length]); 
				}
			} catch(err) { }
			z++;
		}
	}
	removeChildren(mDiv.length+1);
	initRows();
}

function removeChildren(startFrom) {
	err ="";
	if(document.all) {
		do { try { document.getElementsByTagName("tbody")[0].removeChild(document.getElementsByTagName("tr")[startFrom]) } catch (err) { } } while (err == "");
	} else {
		z=0;
		for(i=1;z<startFrom-1;i++) {
			if(document.getElementsByTagName("tbody")[0].childNodes[i].tagName == "TR") {
				if(moz_firstSort) {
					document.getElementsByTagName("tbody")[0].removeChild(document.getElementsByTagName("tbody")[0].childNodes[i]);
				} else {
					document.getElementsByTagName("tbody")[0].removeChild(document.getElementsByTagName("tbody")[0].childNodes[startFrom+1]);
				}
				z++;
			}
		}
	}
	if(moz_firstSort)moz_firstSort = false; // mozilla sure does suck sometimes...
}

function returnRowClicked(y) {
	for(i=rowStart;i<document.getElementsByTagName("tr").length;i++) {
		height = document.getElementsByTagName("tr")[i].offsetHeight;
		if(y>=document.getElementsByTagName("tr")[i].offsetTop && y<=document.getElementsByTagName("tr")[i].offsetTop + height)return i;
	}
}

function returnColumnClicked(x) {
	tdIncr = 0; // mozilla counts text nodes as child nodes, ie tabbed code for legibility, so returning "i" will result in incorrect TD indexes
	for(i=0;i<document.getElementsByTagName("tr")[0].childNodes.length;i++) {
		if(document.getElementsByTagName("tr")[0].childNodes[i].tagName == "TD") {
			width = document.getElementsByTagName("td")[i].offsetWidth;
			if(x>=document.getElementsByTagName("tr")[0].childNodes[i].offsetLeft && x<= document.getElementsByTagName("tr")[0].childNodes[i].offsetLeft + width) {
				columnInfo = new Array(i,tdIncr);
				return columnInfo;			
			}
			tdIncr++;
		}
	}
}
</script>

<body onload="init();">
<table cellpadding="0" cellspacing="0">
<tr><td>First Name</td><td>Middle Name</td><td>Last Name</td><td>Age</td></tr>
<tr><td>Robert</td><td>James</td><td>Anderson</td><td>29</td></tr>
<tr><td>Henry</td><td>Jonathan</td><td>Murray</td><td>40</td></tr>
<tr><td>Edward</td><td>Richard</td><td>Jones</td><td>32</td></tr>
<tr><td>Patrick</td><td>Elroy</td><td>Emerson</td><td>53</td></tr>
<tr><td>Lisa</td><td>Anne</td><td>Smith</td><td>49</td></tr>
</table>
</body>
</html>

Table Sorter v1.0
last revision: 09.04.2003
steve@slayeroffice.com
http://www.slayeroffice.com