function TableSort(id) {
    this.tbl = document.getElementById(id);
    this.lastSortedTh = null;
    if (this.tbl && this.tbl.nodeName == "TABLE") {
        var headings = this.tbl.tHead.rows[0].cells;
        for (var i=0; headings[i]; i++) {
            if (headings[i].className.match(/asc|dsc/)) {
                this.lastSortedTh = headings[i];
            }
        }
        this.makeSortable();
    }
}

TableSort.prototype.makeSortable = function () {
    var headings = this.tbl.tHead.rows[0].cells;
    for (var i=0; headings[i]; i++) {
	    headings[i].cIdx = i;
        var a = document.createElement("a");
            a.href = "#";
            a.innerHTML = headings[i].innerHTML;
            a.onclick = function (that) {
                return function () {
                    that.sortCol(this);
                    return false;
                }
            }(this);
        headings[i].innerHTML = "";
        headings[i].appendChild(a);
    }
}

TableSort.prototype.sortCol = function (el) {
    /*
     * Get cell data for column that is to be sorted from HTML table
     */
    var rows = this.tbl.rows;
    var alpha = [], numeric = [];
    var aIdx = 0, nIdx = 0;
    var th = el.parentNode;
    var cellIndex = th.cIdx;
    for (var i=1; rows[i]; i++) {
        var cell = rows[i].cells[cellIndex];
        var content = cell.textContent ? cell.textContent : cell.innerText;
        /*
         * Split data into two separate arrays, one for numeric content and 
         * one for everything else (alphabetic). Store both the actual data
         * that will be used for comparison by the sort algorithm (thus the need
         * to parseFloat() the numeric data) as well as a reference to the 
         * element's parent row. The row reference will be used after the new
         * order of content is determined in order to actually reorder the HTML
         * table's rows.
         */
        var num = content.replace(/(\$|\,|\s)/g, "");
          if (parseFloat(num) == num) { 
            numeric[nIdx++] = {
                value: Number(num),
                row: rows[i]
            }
        } else {
            alpha[aIdx++] = {
                value: content,
                row: rows[i]
            }
        }
    }
    
    /*
     * Sort according to direction (ascending or descending)
     */
    var col = [], top, bottom;
    if (th.className.match("asc")) {
        top = bubbleSort(alpha, -1);
        bottom = bubbleSort(numeric, -1);
        th.className = th.className.replace(/asc/, "dsc");
    } else {
        top = bubbleSort(numeric, 1);
        bottom = bubbleSort(alpha, 1);
        if (th.className.match("dsc")) {
            th.className = th.className.replace(/dsc/, "asc");
        } else {
            th.className += "asc";
        }
    }
    
    /*
     * Clear asc/dsc class names from the last sorted column's th if it isnt the
     * same as the one that was just clicked
     */
    if (this.lastSortedTh && th != this.lastSortedTh) {
        this.lastSortedTh.className = this.lastSortedTh.className.replace(/dsc|asc/g, "");
    }
    this.lastSortedTh = th;
    
    /*
     *  Reorder HTML table based on new order of data found in the col array
     */
    col = top.concat(bottom);
    var tBody = this.tbl.tBodies[0];
    for (var i=0; col[i]; i++) {
        tBody.appendChild(col[i].row);
    }
}

function bubbleSort(arr, dir) {
    // Pre-calculate directional information
    var start, end;
    if (dir === 1) {
        start = 0;
        end = arr.length;
    } else if (dir === -1) {
        start = arr.length-1;
        end = -1;
    }
    
    // Bubble sort: http://en.wikipedia.org/wiki/Bubble_sort
    var unsorted = true;
    while (unsorted) {
        unsorted = false;
        for (var i=start; i!=end; i=i+dir) {
            if (arr[i+dir] && arr[i].value > arr[i+dir].value) {
                var a = arr[i];
                var b = arr[i+dir];
                var c = a;
                arr[i] = b;
                arr[i+dir] = c;
                unsorted = true;
            }
        }
    }
    return arr;
}
