tablesorter

A javascript sorter for html tables using the document object model (DOM).

demo

Just click on the column heads to sort the table. Try subsequent sorts of different columns.

Kd-Nr Name Vorname controlNo
9 Laurel Stan 0
9 Laurel Oliver 1
9 Hardy Stan 2
9 Hardy Oliver 3
10 Laurel Stan 4
10 Laurel Oliver 5
10 Hardy Stan 6
10 Hardy Oliver 7

step by step

  1. Include tableSorter.js into your html page containing the table
  2. Give the table an id
  3. Define the sort function doSort(col) as onclick-event in the thead section of the table
  4. Create the TableSorter class with new

Look at the source code of this page as an example.
Important: The table needs a thead and tbody section!

source

/** Javascript table sorter and debug utilities
    CVS:       $Id: tableSorter.js,v 1.12 2006/02/09 09:50:28 becki Exp $
    Author:    Stefan Beckert  ( http://becki.skiclub-mitwitz.de )
    License:   Public Domain
    Encoding:  UTF-8

    Sorting is tested and works with Mozilla Firefox 1.5, Konqueror 3.2.3
    and IE6.
    The work is done on the client side, no AJAX is necessary.
    */


/** Set to mozilladump & start Gecko based Browsers with -console parameter for
    debug output (Doesn't work with Internet Explorer)
    Set to newwindow for any browser
    */
//var debug = new Logger('TabSort', 'suppress');
//var debug = new Logger('TabSort', 'mozilla');
//var debug = new Logger('TabSort', 'extwindow');

/** Reference to the TableSorter object as global variable.
    Necessary because within the comparator function is no "this" available
    */
var tsObj;

/** Callback for array sorting algorithm.
    Global Function, does not belong to class TableSorter.
    The sorting algorithm of javascript isn't stable, ie.
    it doesn't respect the order of equal elements coming from previous sorts.
    This funcion takes this (bad) behaviour into account and emulates a stable
    sort by holding a sort history and doing subsorts of equal elements.
    For 'stable' explanation see:
    http://java.sun.com/docs/books/tutorial/collections/algorithms/
    */
function compare(a, b) {
    for (var ix=0; ix<tsObj.sortCols.length; ix++) {
        var col= tsObj.sortCols[ix];
        // We need new local varibles, because overwriting of function args
        // a & b doesn't work with IE 6.0:
        var c= a.txts[col];
        var d= b.txts[col];
        if (c > d) return  tsObj.sdir[col];
        if (c < d) return -tsObj.sdir[col];
    }
    return 0;
}


/** Maintains sort history.
    Public method of class TableSorter, can be used to modify sort history.
    Sort history works like a stack which can't contain duplicate elements.
    The index of newest sorted col is insterted at the beginning
    */
function addToSortHist(col) {
    if (this.sortCols.length==0 || this.sortCols[0]!=col) {
        // user clicked on a new column:
        for (var i=0; i<this.sortCols.length; i++) {
            // remove this col in the sort history:
            if (this.sortCols[i] == col) this.sortCols.splice(i,1);
        }
        this.sortCols.unshift(col);   // insert new col as first element
    }
}

/** Changes mouse cursor to wait symbol and status bar to 'sorting...'.
    Public method of class TableSorter
    Can be called with onmousedown before doSort() is called with onclick
    */
function initSort(obj) {
    obj.style.cursor= 'wait';
    window.status= 'sorting...';
}

/** Sort the table after column number 'scol'.
    Public and most important method of class TableSorter
    To be called with with 'onclick'
    */
function doSort(scol) {
    //debug.lf();
    tsObj= this; // compare() method needs a ref to TableSorter Object

    // get text content of col when sorted first time after this col:
    if (this.trNodes[0].txts[scol] == null) {
        var numeric= true;
        for (var line=0; line<this.trCnt; line++) {
            var bf = this.trNodes[line].node.getElementsByTagName("td");
            bf= bf[scol];
            while ((bf.nodeType != 3) && bf.firstChild) bf= bf.firstChild;
            bf= bf.nodeValue;
            if (!bf) bf=''; // for empty table cells
            if (isNaN(bf)) numeric= false;
            this.trNodes[line].txts[scol] = bf;
        }
        for (var line=0; line<this.trCnt; line++) {
            var bf= this.trNodes[line].txts[scol];
            if (numeric) bf= parseFloat(bf);
            else if (this.ignoreCase) bf= bf.toLowerCase();
            this.trNodes[line].txts[scol]= bf;
        }
        //debug.log('col '+ scol+ ' numeric: '+ numeric);
    }

    // set sort direction and maintain sort history:
    this.sdir[scol]= -this.sdir[scol]; // toggle sort direction
    this.addToSortHist(scol);

    // purge the table:
    for (var line=0; line<this.trCnt; line++)
        this.tbody.removeChild(this.trNodes[line].node);

    // do the sorting:
    //var start = new Date().getTime(); // measure sorting time
    this.trNodes.sort(compare); // sort
    //debug.log('col '+ scol+ ': '+ ((new Date().getTime()) - start)+ 'ms' );
    //debug.log('hist: '+ this.sortCols);

    // refill the table:
    for (var line=0; line<this.trCnt; line++)
        this.tbody.appendChild(this.trNodes[line].node);

    // set the sorting indicator at column header.
    if (this.sortCols.length > 1) { // remove old indicator if present
        var bf= this.sortCols[1];   // get the old column number
        bf= this.thIdx[bf];         // get the corresponding th col number
        this.thAdrs[bf].firstChild.nodeValue= this.thText[bf];
    }
    var bf= this.sortCols[0]; // get the new column number
    var ind= (this.sdir[bf] > 0) ? '↓' :  '↑';
    bf= this.thIdx[bf];       // get the corresponding th col number
    this.thAdrs[bf].firstChild.nodeValue= ind+ this.thText[bf]+ ind;

    // restore cursor and status bar:
    this.thAdrs[bf].style.cursor= 'pointer';
    window.status= '';
}

/** Constructor of class TableSorter. */
function TableSorter(
        tableId,           // <table id="tableId">
        sortCaseSensitive  // defaults to false if omitted i.e. 'ignore case'
        ) {
    this.ignoreCase = !sortCaseSensitive;
    // function "pointers" to class methods:
    this.doSort        = doSort;
    this.initSort      = initSort;      // class method
    this.addToSortHist = addToSortHist; // class method
    // instance arrays:
    this.thIdx    = new Array(); // td-index to th-index mapping (th-colspan)
    this.thAdrs   = new Array(); // refs to the table header nodes
    this.thText   = new Array(); // table header strings
    this.sortCols = new Array(); // sort column history queue

    // get table headers:
    var buf= document.getElementById(tableId).getElementsByTagName("thead");
    buf= buf[0].getElementsByTagName("tr");
    var ths= buf[0].getElementsByTagName("th"); // all thead th elements

    // get real number of colums (take colspans of th's into account)
    for (var col=0; col<ths.length; col++) {
        if (! (buf= ths[col].getAttribute('colspan'))) buf= 1;
        for (var i=0; i<buf; i++) this.thIdx.push(col);

        buf= ths[col];
        this.thAdrs[col]= buf;
        this.thText[col]= buf.firstChild.nodeValue;
        if (buf.getAttribute('onclick')) buf.style.cursor= 'pointer';
        /*  Firefox supports setting event handling function like this:
            buf.setAttribute('onmousedown', tableId+'.initSort(this)');
            buf.setAttribute('onclick', tableId+'.doSort('+col+')');
            But IE6 doesn't :-( ,therefore setting the sorting functions as
            event handler has to be done manually in html table
            http://kryogenix.org/code/browser/sorttable/ does it with innerHTML
            maybe this is a solution?
            */
    }
    //debug.log('thIdx: '+ this.thIdx);

    // store the real number of columns:
    this.colCnt = this.thIdx.length; // number of columns of the table
    //debug.log('colCnt: '+ this.colCnt);

    // setup sort direction (asc/desc) history for each col:
    this.sdir     = new Array(this.colCnt); // can only have values of -1 or +1
    for (var col=0; col<this.colCnt; col++)  this.sdir[col] = -1;

    // get table content:
    buf= document.getElementById(tableId).getElementsByTagName("tbody");
    this.tbody= buf[0]; // later necessary for doSort()
    buf= buf[0].getElementsByTagName("tr"); // all tbody tr elements as object
    this.trCnt= buf.length;                 // number of table rows
    this.trNodes = new Array(buf.length);   // all tbody tr elements as array
    for (var line=0; line<this.trCnt; line++)
        this.trNodes[line]= new TableRow(buf[line], this.colCnt);
}


/** Abstract data type (struct)
    Contains a reference to a table row node and the text content of all
    cells of the table row
    used by class TableSorter
    */
function TableRow(trNode, colCnt) {
    this.node = trNode;
    this.txts = new Array(colCnt);
    for (var col=0; col<colCnt; col++) this.txts[col] = null;
}