// sorttable.js - JavaScript code to power sortable DHTML tables
//
// Original code by Stuart Langridge, available under a BSD license
// from http://kryogenix.org/code/browser/sorttable/. This version
// is refactored slightly, and modified to sort a set of dimensions
// (for example "1x2x3") by linear inches (sort key = 6).

addEvent(window, 'load', initSortables);

var DEBUG = 0;
var COL_IDX;

// Find all tables with class "sortable" and call makeSortable() on them
function initSortables() {
    if(!document.getElementsByTagName) return;
    var tbls = document.getElementsByTagName('table');
    for(var ti = 0; ti < tbls.length; ti++) {
        thisTbl = tbls[ti];
        if( ((' ' + thisTbl.className + ' ').indexOf('sortable') != -1) &&
            (thisTbl.id) )
        {
            makeSortable(thisTbl);
        }
    }
}

// Turn text in first row of a table into clickable links
function makeSortable(table) {
    if(table.rows && table.rows.length > 0) {
        var firstRow = table.rows[0];
    }
    if(!firstRow) return;

    for(var i = 0; i < firstRow.cells.length; i++) {
        var cell = firstRow.cells[i];
        var txt = getInnerText(cell);
        cell.innerHTML =
            '<a href="#" class="sortheader" ' +
            'onclick="resortTable(this, '+i+'); return false;">' +
            txt + '<span class="sortarrow"></span></a>';
    }
}

function getInnerText(elem) {
    if(typeof elem == 'string')    return elem;
    if(typeof elem == 'undefined') return elem;
    if(elem.innerText)             return elem.innerText;

    var str = '';
    var cs  = elem.childNodes;
    var len = cs.length;
    for(var i = 0; i < len; i++) {
        switch(cs[i].nodeType) {
            case 1:    // ELEMENT_NODE
                str += getInnerText(cs[i]);
                break;
            case 3:    // TEXT_NODE
                str += cs[i].nodeValue;
                break;
        }
    }
    return str;
}

function resortTable(lnk, clid) {
    // Get the span
    var span;
    for(var ci = 0; ci < lnk.childNodes.length; ci++) {
        if( lnk.childNodes[ci].tagName &&
            lnk.childNodes[ci].tagName.toLowerCase() == 'span' )
            span = lnk.childNodes[ci];
    }
    var spantext = getInnerText(span);
    var td = lnk.parentNode;
    var column = clid || td.cellIndex;
    var table = getParent(td, 'TABLE');

    // Work out a type for the column, and pick corresponding sort function
    if(table.rows.length <= 1) return;
    var itm = getInnerText(table.rows[1].cells[column]);
    sortfn = sortCaseInsensitive;
    if(itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = sortDate;
    if(itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/))     sortfn = sortDate;
    if(itm.match(/^[£$]/))                        sortfn = sortCurrency;
    if(itm.match(/\d+\s*x\s*\d+\s*x\s*\d+/))      sortfn = sortDimensions;
    if(itm.match(/^[\d\.]+$/))                    sortfn = sortNumeric;

    // Stash index in global and sort copy of rows
    COL_IDX = column;
    var firstRow = new Array();
    var newRows = new Array();
    for(i = 0; i < table.rows[0].length; i++) firstRow[i]  = table.rows[0][i];
    for(j = 1; j < table.rows.length;    j++) newRows[j-1] = table.rows[j];
    newRows.sort(sortfn);

    // Display appropriate arrow (and mark the span for later finding)
    var arrow;
    if(span.getAttribute('sortdir') == 'down') {
        arrow = '&nbsp;&nbsp;&uarr;';
        newRows.reverse();
        span.setAttribute('sortdir', 'up');
    } else {
        arrow = '&nbsp;&nbsp;&darr;';
        span.setAttribute('sortdir', 'down');
    }

    // We appendChild() rows that already exist to the tbody, so it moves
    // them rather than creating new ones; don't do sortbottom rows
    for(i = 0; i < newRows.length; i++) {
        if( !newRows[i].className ||
            ( newRows[i].className &&
              (newRows[i].className.indexOf('sortbottom') == -1) ))
            table.tBodies[0].appendChild(newRows[i]);
    }

    // Now do sortbottom rows only
    for(i = 0; i < newRows.length; i++) {
        if( newRows[i].className &&
            (newRows[i].className.indexOf('sortbottom') != -1) )
            table.tBodies[0].appendChild(newRows[i]);
    }

    // Delete arrows from previously marked spans
    var allspans = document.getElementsByTagName('span');
    for(var ci = 0; ci < allspans.length; ci++) {
        if(allspans[ci].className == 'sortarrow') {
            // if in the same table as us
            if(getParent(allspans[ci], 'table') == getParent(lnk, 'table')) {
                allspans[ci].innerHTML = '';
            }
        }
    }

    span.innerHTML = arrow;
}

function getParent(elem, pTagName) {
    if(elem == null) return null;
    else if( elem.nodeType == 1 &&
             elem.tagName.toLowerCase() == pTagName.toLowerCase() )
        // Gecko bug, supposed to be uppercase
        return elem;
    else
        return getParent(elem.parentNode, pTagName);
}

function sortDate(a, b) {
    // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
    aa = getInnerText(a.cells[COL_IDX]);
    bb = getInnerText(b.cells[COL_IDX]);
    if(aa.length == 10) {
        dt1 = aa.substr(6, 4) + aa.substr(3, 2) + aa.substr(0, 2);
    } else {
        yr = aa.substr(6, 2);
        if(parseInt(yr) < 50) { yr = '20' + yr; } else { yr = '19' + yr; }
        dt1 = yr + aa.substr(3, 2) + aa.substr(0, 2);
    }
    if(bb.length == 10) {
        dt2 = bb.substr(6, 4) + bb.substr(3, 2) + bb.substr(0, 2);
    } else {
        yr = bb.substr(6, 2);
        if(parseInt(yr) < 50) { yr = '20' + yr; } else { yr = '19' + yr; }
        dt2 = yr + bb.substr(3, 2) + bb.substr(0, 2);
    }
    if(dt1 == dt2) return  0;
    if(dt1 <  dt2) return -1;
    return 1;
}

function sortDimensions(a, b) {
    aM = getInnerText(a.cells[COL_IDX]).match(/(\d+)\s*x\s*(\d+)\s*x\s*(\d+)/);
    bM = getInnerText(b.cells[COL_IDX]).match(/(\d+)\s*x\s*(\d+)\s*x\s*(\d+)/);
    if(aM && bM) {
        aVal = parseFloat(aM[1]) + parseFloat(aM[2]) + parseFloat(aM[3]);
        bVal = parseFloat(bM[1]) + parseFloat(bM[2]) + parseFloat(bM[3]);
        return aVal - bVal;
    } else {
        return 0;
    }
}

function sortCurrency(a, b) {
    aa = getInnerText(a.cells[COL_IDX]).replace(/[^0-9.]/g, '');
    bb = getInnerText(b.cells[COL_IDX]).replace(/[^0-9.]/g, '');
    return parseFloat(aa) - parseFloat(bb);
}

function sortNumeric(a, b) {
    aa = parseFloat(getInnerText(a.cells[COL_IDX]));
    if(isNaN(aa)) aa = 0;
    bb = parseFloat(getInnerText(b.cells[COL_IDX]));
    if(isNaN(bb)) bb = 0;
    return aa - bb;
}

function sortCaseInsensitive(a, b) {
    aa = getInnerText(a.cells[COL_IDX]).toLowerCase();
    bb = getInnerText(b.cells[COL_IDX]).toLowerCase();
    if(aa == bb) return  0;
    if(aa <  bb) return -1;
    return 1;
}

function sortDefault(a, b) {
    aa = getInnerText(a.cells[COL_IDX]);
    bb = getInnerText(b.cells[COL_IDX]);
    if(aa == bb) return  0;
    if(aa <  bb) return -1;
    return 1;
}


// addEvent() cross-browser event handling for IE5+, NS6 and Mozilla.
// Original code by Scott Andrew, available from
// http://www.scottandrew.com/weblog/articles/cbs-events.

function addEvent(elem, evType, fn, useCapture) {
    if(elem.addEventListener) {
        elem.addEventListener(evType, fn, useCapture);
        return true;
    } else if(elem.attachEvent) {
        var r = elem.attachEvent('on' + evType, fn);
        return r;
    } else if(DEBUG) {
        alert('Handler could not be removed');
    }
}
