JavascriptToolbox.com Now Available!

Almost all of my javascript code has been moved over to its new home at The Javascript Toolbox. Please go there to find the latest scripts, information, etc. These pages will remain here for a while for historical purposes in case anyone needs a production copy of old code.
Sortable Table
[Toolbox]  [Example]  [Source]  [Documentation]
Click the tabs to see other source options.
  Original     Compact  
The original source file (12k)
Right-click and "Save As" to download this file: sorttable.js or copy and paste the text from below.

// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

/*
sorttable.js
  Matt Kruse
  Last Modified: 1/22/03

(NOTE: SCRIPT tags are broken into two pieces in these comments so the browser 
 doesn't interpret them)
  
This javascript creates a table with client-side data sorting. It works in 4.x or
higher browsers. In IE, it uses CSS and in Netscape it uses Layers.

Instructions for use in your HTML:

Include the js file inside your HTML source:
   <SCR IPT LANGUAGE="JAVASCRIPT" SRC="sorttable.js"></SCR IPT>
Due to a Netscape browser bug, if the page you are using this on is inside a frameset,
you may need to include an empty SCRIPT tag before the included file. Like this:
	<SCR IPT></SCR IPT>
   <SCR IPT LANGUAGE="JAVASCRIPT" SRC="sorttable.js"></SCR IPT>

Insert the style sheet types in your HTML source, inside <HEAD></HEAD>:
   <STYLE type=text/css>
   .rel {POSITION: relative; width:100%;}
   .abs {POSITION: absolute; width:100%;}
   .right { text-align:right; }
   .left { text-align:left; }
   .center { text-align:center; }
   </STYLE>

Define the table somewhere in your HTML file. Preferrably this will go in the 
<HEAD> of your document, but it may also be included in-line:
     var t = new SortTable("t");
This creates a new SortTable object. You must pass the function an argument of
the name of the object you are creating. For example,
     var mytable = new SortTable("mytable");

Now define the columns of your table:
     t.AddColumn("Name","","left","");
     t.AddColumn("ID","","center","numeric");
     t.AddColumn("Bonus","BGCOLOR=ffffcc","right","money");
The first argument is the name of the column, which you can use when sorting.
The second argument is the <TD> properties. Anything in this argument will be
  added to the <TD> for each element in this column.
The third argument is the alignment of this column. left, center, right. (default left)
The last argument is the data type of the column. This is used for sorting. More data
  types may be defined manually. The default data type for sorting is alphanumeric.
  Accepted types are:
     - numeric : float data
	  - form : If this columns contains form elements, you must use this type!
	           (this will cause the table to be non-sortable in Netscape)
     - date : Date data must be in a format understood by Date.parse()!
	  				  
Now populate the table:

     t.AddLine("John Doe",12345,"$500.00");

This adds a row of data to the table.
Sometimes, the table cell will contain text other than the actual data. For example, if
your cells contain a "Work Order Number" like '032097' that are inside of a link, your
table cell would contain:
     <a href="whatever">032097</a>
Sorting based on this text data does not give the correct results, however. Instead, you
want to sort based on just the number itself - 032097. For this, you need to define the
sort data separately from the cell contents. To continue with the above example, let's 
say that "John Doe" is actually a link to his home page. Your AddLine would look like this:

     t.AddLine("<a href='http://www.johndoe.com/'>Jone Doe</a>,12345,"$500.00");

To correctly sort the first column by only his name, you would then add this command
immediately after the AddLine() command:

     t.AddLineSortData("John Doe",'','');

When blank entries are left for the sort data, the text data entered with AddLine() is
used for the sorting.

You have one more option for the line of data you just entered. You may include a string
that will be included in the <TR> for this line of the table. You can then include a
background color for the row, for example. NOTE: This applies to the row number, and does
NOT move when the rows are sorted. For example, if you put a red background in a row,
and then re-sort the table, the red background will not move. The syntax for adding an
argument to the row's <TR> is:
     
	  t.AddLineProperties(text);
For example:
     t.AddLineProperties("align=center bgcolor=#F6F6F6");
This will center the whole row and give it a light gray background color. This option is
mostly useful for giving every other row a different background color to make reading
easier.
	  
You are now done defining the data of your table. To put the table into your HTML, you
must manually define the table itself. In order for Netscape to properly show cells of
different lengths, a width of the table MUST be specified!!
      
     <table border=1 width=600>
     <tr>
     	<th><a href="javascript:SortRows(t,0)">Name</a></th>
     	<th><a href="javascript:SortRows(t,1)">ID</a></th>
     	<th><a href="javascript:SortRows(t,2)">Bonus</a></th>

The links are to the SortRows() function. This will sort and re-populate the table. It
will be most common to call this function when a user clicks a table header, but you 
could actually call this function to sort from anywhere in the page. The sort function
requires the table name and column number (starting with index 0) as arguments.

Note: If SortRows() is called on the same row as the last sort, it will inverse the 
order of the last sort.

Now, as the body of your table, simply call the WriteRows() method of the table to
output the data in its original order. Do not wrap the WriteRows() call around <TR>
or <TD> tags - it creates them:
     </tr>
     <SCR IPT>t.WriteRows()</SCR IPT>
     </table>

That's it!
*/
var use_css=false;
var use_layers=false;   
var use_dom=false;

if (document.all)    { use_css    = true; }
if (document.layers) { use_layers = true; }
if (document.getElementById) { use_dom=true; }

var sort_object;
var sort_column;
var reverse=0;

// Constructor for SortTable object
function SortTable(name) {
	// Properties
	this.name = name;
	this.sortcolumn="";
	this.dosort=true;
	this.tablecontainsforms=false;
	// Methods
	this.AddLine = AddLine;
	this.AddColumn = AddColumn;
	this.WriteRows = WriteRows;
	this.SortRows = SortRows;
	this.AddLineProperties = AddLineProperties;
	this.AddLineSortData = AddLineSortData;
	// Structure
	this.Columns = new Array();
	this.Lines = new Array();
	this.LineProperties = new Array();
	}
// Add a line to the grid
function AddLine() {
	var index = this.Lines.length;
	this.Lines[index] = new Array();
	for (var i=0; i<arguments.length; i++) {
		this.Lines[index][i] = new Object();
		this.Lines[index][i].text = arguments[i];
		this.Lines[index][i].data = arguments[i];
		}
	}
// Define properties for the <TR> of the last line added
function AddLineProperties(prop) {
	var index = this.Lines.length-1;
	this.LineProperties[index] = prop;
	}
// Define sorting data for the last line added
function AddLineSortData() {
	var index = this.Lines.length-1;
	for (var i=0; i<arguments.length; i++) {
		if (arguments[i] != '') {
			this.Lines[index][i].data = arguments[i];
			}
		}
	}

// Add a column definition to the table
// Arguments:
//   name = name of the column
//   td   = any arguments to go into the <TD> tag for this column (ex: BGCOLOR="red")
//   align= Alignment of data in cells
//   type = type of data in this column (numeric, money, etc) - default alphanumeric
function AddColumn(name,td,align,type) {
	var index = this.Columns.length;
	this.Columns[index] = new Object;
	this.Columns[index].name = name;
	this.Columns[index].td   = td;
	this.Columns[index].align=align;
	this.Columns[index].type = type;
	if (type == "form") {
		 this.tablecontainsforms=true; 
		 if (use_layers) { 
		 	this.dosort=false;
			}
		}
	}
// Print out the original set of rows in the grid
function WriteRows() {
	var open_div = "";
	var close_div = "";
	for (var i=0; i<this.Lines.length; i++) {
	document.write("<TR "+this.LineProperties[i]+">");
		for (var j=0; j<this.Columns.length; j++) {
			var div_name = "d"+this.name+"-"+i+"-"+j;
			if (use_css || use_dom) {
				if (this.Columns[j].align != '') {
					var align = " ALIGN="+this.Columns[j].align;
					}
				else {
					var align = "";
					}
				open_div = "<DIV ID=\""+div_name+"\" "+align+">";
				close_div= "</DIV>";
				}
			else if (use_layers) {
				// If the table contains form elements, don't use <LAYER> tags or the
				// form will be forced closed.
				if (!this.dosort) {
					if (this.Columns[j].align != '') {
						open_div="<SPAN CLASS=\""+this.Columns[j].align+"\">";
						}
					}
				else {
					open_div = "<ILAYER NAME=\""+div_name+"\" WIDTH=100%>";
					open_div+= "<LAYER NAME=\""+div_name+"x\" WIDTH=100%>";
					if (this.Columns[j].align != '') {
						open_div+= "<SPAN CLASS=\""+this.Columns[j].align+"\">";
						}
					}
				if (this.Columns[j].align != '') {
	 				close_div = "</SPAN>";
					}
				if (this.dosort) {
					close_div += "</LAYER></ILAYER>";
					}
				}
			document.write("<TD "+this.Columns[j].td+">"+open_div+this.Lines[i][j].text+close_div+"</TD>");
			}
		document.write("</TR>");
		}
	}
// Sort the table and re-write the results to the existing table
function SortRows(table,column) {
	sort_object = table;
	if (!sort_object.dosort) { return; }
	if (sort_column == column) { reverse=1-reverse; }
	else { reverse=0; }
	sort_column = column;

	// Save all form column contents into a temporary object
	// This is a nasty hack to keep the current values of form elements intact
	if (table.tablecontainsforms) {
		var iname="1";
		var tempcolumns = new Object();
		for (var i=0; i<table.Lines.length; i++) {
			for (var j=0; j<table.Columns.length; j++) {
				if(table.Columns[j].type == "form") {
					var cell_name = "d"+table.name+"-"+i+"-"+j;
					if (use_css) {
						tempcolumns[iname] = document.all[cell_name].innerHTML;
					}
					else {
						tempcolumns[iname] = document.getElementById(cell_name).innerHTML;
					}
					table.Lines[i][j].text = iname;
					iname++;
					}
				}
			}
		}
	
	if (table.Columns[column].type == "numeric") {
		// Sort by Float
		table.Lines.sort(	function by_name(a,b) {
									if (parseFloat(a[column].data) < parseFloat(b[column].data) ) { return -1; }
									if (parseFloat(a[column].data) > parseFloat(b[column].data) ) { return 1; }
									return 0;
									}
								);
		}
	else if (table.Columns[column].type == "money") {
		// Sort by Money
		table.Lines.sort(	function by_name(a,b) {
									if (parseFloat(a[column].data.substring(1)) < parseFloat(b[column].data.substring(1)) ) { return -1; }
									if (parseFloat(a[column].data.substring(1)) > parseFloat(b[column].data.substring(1)) ) { return 1; }
									return 0;
									}
								);
		}
	else if (table.Columns[column].type == "date") {
		// Sort by Date
		table.Lines.sort(	function by_name(a,b) {
									if (Date.parse(a[column].data) < Date.parse(b[column].data) ) { return -1; }
									if (Date.parse(a[column].data) > Date.parse(b[column].data) ) { return 1; }
									return 0;
									}
								);
		}

	else {
		// Sort by alphanumeric
		table.Lines.sort(	function by_name(a,b) {
									if (a[column].data+"" < b[column].data+"") { return -1; }
									if (a[column].data+"" > b[column].data+"") { return 1; }
									return 0;
									}
								);
		}

	if (reverse) { table.Lines.reverse(); }
	for (var i=0; i<table.Lines.length; i++) {
		for (var j=0; j<table.Columns.length; j++) {
			var cell_name = "d"+table.name+"-"+i+"-"+j;
			if (use_dom) {
				if(table.Columns[j].type == "form") {
					var iname = table.Lines[i][j].text;
					document.getElementById(cell_name).innerHTML = tempcolumns[iname];
					}
				else {
					document.getElementById(cell_name).innerHTML = table.Lines[i][j].text;
					}
				}
			else if (use_css) {
				if(table.Columns[j].type == "form") {
					var iname = table.Lines[i][j].text;
					document.all[cell_name].innerHTML = tempcolumns[iname];
					}
				else {
					document.all[cell_name].innerHTML = table.Lines[i][j].text;
					}
				}
			else if (use_layers) {
				var cell_namex= "d"+table.name+"-"+i+"-"+j+"x";
				if (table.Columns[j].align != '') {
					document.layers[cell_name].document.layers[cell_namex].document.write("<SPAN CLASS=\""+table.Columns[j].align+"\">");
					}
				document.layers[cell_name].document.layers[cell_namex].document.write(table.Lines[i][j].text);
				if (table.Columns[j].align != '') {
					document.layers[cell_name].document.layers[cell_namex].document.write("</SPAN>");
					}
				document.layers[cell_name].document.layers[cell_namex].document.close();
				}
			}
		}
	}