
// -------------------------------------------------------------------------------------------
//  This code was picked up from http://www.kryogenix.org/code/browser/sorttable/
//  and then modified by D. Tolle in October, 2006.

//  The original code was written by Stuart Langridge.  Here are the thanks he gave:



// ---------------------------------  Beginning of Langridge's comments:
// Thanks
// I pinched the getInnerText() function from Erik over at WebFX. 
// Their sortable table implementation does the same sort of thing as mine, 
// but it requires you to add some JavaScript to instantiate the table, 
// and you also have to explicitly specify the types of fields, which is a bit obtrusive.
//
// This was partially developed for a project at work, so thanks to my boss. 
// Thanks also to C. David Eagle for pointing out a bug where I didn't close the script tag in the example.
//
// Updated 3rd April 2006 to work with Safari, as per a Google Answer. Thanks to Brad Kemper and wildeeo-ga!
//
// Stuart Langridge, November 2003


//  Include the Javascript library, by putting a link to it in the HEAD of your page, like so: 
//  <script src="sorttable.js"></script>
//  Mark your table as a sortable one by giving it a class of "sortable": 
//  <table class="sortable">
//  Ensure that your table has an ID: 
//  <table class="sortable" id="unique_id">
//  And that's all you need. 
//  Your table will now have column sorting available by clicking the headers. 
//  For niceness, you might want to add the following styles to your stylesheet, 
//  or make up some of your own based on this:


//  /* Sortable tables */
//  table.sortable a.sortheader {
//    background-color:#eee;
//    color:#666666;
//    font-weight: bold;
//    text-decoration: none;
//    display: block;
//  }
//  table.sortable span.sortarrow {
//    color: black;
//    text-decoration: none;
//  }


//  ---------------------------------  End of Langridge's comments

// We'll trigger sortables_init() from somewhere else, so comment this line out:
// addEvent(window, "load", sortables_init);


var SORT_COLUMN_INDEX;



// -----------------------------------------------------------------------------------
function ts_getInnerText(el) {
	if (typeof el == "string") {return el;}
	if (typeof el == "undefined") { return el;}
	if (el.innerText) {return el.innerText;}	//Not needed but it is faster
	var str = "";
	
	var cs = el.childNodes;
	var l = cs.length;
	for (var i = 0; i < l; i++) {
		switch (cs[i].nodeType) {
			case 1: //ELEMENT_NODE
				str += ts_getInnerText(cs[i]);
				break;
			case 3:	//TEXT_NODE
				str += cs[i].nodeValue;
				break;
		}
	}
	return str;
}     //  End of function ts_getInnerText(el)



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


// -----------------------------------------------------------------------------------
function ts_sort_date(a,b) {
    // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
    var dt1, dt2, yr;
    var aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    var bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    
    if (aa.length === 0) {return -1;}
    if (bb.length === 0) {return  1;}
    
    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, 10) < 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, 10) < 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 ts_sort_currency(a,b) { 
    var aa, bb;
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
    
    if (aa.length === 0) {return -1;}
    if (bb.length === 0) {return  1;}
    
    return parseFloat(aa) - parseFloat(bb);
}

// -----------------------------------------------------------------------------------
function ts_sort_numeric(a,b) {
    var aa, bb; 
    aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
    if (isNaN(aa)) {aa = 0;}
    bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX])); 
    if (isNaN(bb)) {bb = 0;}
    return aa-bb;
}

// -----------------------------------------------------------------------------------
function ts_sort_caseinsensitive(a,b) {
    var aa, bb;
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
    
    if (aa.length === 0) {return -1;}
    if (bb.length === 0) {return  1;}
    
    if (aa==bb) {return 0;}
    if (aa<bb) {return -1;}
    return 1;
}

// -----------------------------------------------------------------------------------
function ts_sort_height(a,b) { 
            var s = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
            var t = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
            
            if (s.length === 0) {return -1;}
            if (t.length === 0) {return  1;}
            
            var sm = s.match(/\d+/g);          // Convert, e.g., 5'10 to 5*12 + 10 = 70 inches
            s = sm[0]*12 + Number(sm[1]);
            var tm = t.match(/\d+/g);
            t = tm[0]*12 + Number(tm[1]);
            return (s-t);
}



// -----------------------------------------------------------------------------------
function addEvent(elm, evType, fn, useCapture) {
// addEvent and removeEvent
// cross-browser event handling for IE5+,  NS6 and Mozilla
// By Scott Andrew

  if (elm.addEventListener){
    elm.addEventListener(evType, fn, useCapture);
    return true;
  } else if (elm.attachEvent){
    var r = elm.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be added");
  }
}      // End of function addEvent(elm, evType, fn, useCapture)




// -----------------------------------------------------------------------------------
// From example 15-4 in Javascript - The Definitive Guide:
//
/**
 * getElements(classname, tagname, root):
 * Return an array of DOM elements that are members of the specified class,
 * have the specified tagname, and are descendants of the specified root.
 * 
 * If no classname is specified, elements are returned regardless of class.
 * If no tagname is specified, elements are returned regardless of tagname.
 * If no root is specified, the document object is used.  If the specified
 * root is a string, it is an element id, and the root
 * element is looked up using getElementsById()
 */
function getElements(classname, tagname, root) {
    // If no root was specified, use the entire document
    // If a string was specified, look it up
    if (!root) root = document;
    else if (typeof root == "string") root = document.getElementById(root);
    
    // if no tagname was specified, use all tags
    if (!tagname) tagname = "*";

    // Find all descendants of the specified root with the specified tagname
    var all = root.getElementsByTagName(tagname);
    // alert("tagname = " + tagname);
    // alert("all.length = " + all.length)
    
    // If no classname was specified, we return all tags
    if (!classname) return all;

    // Otherwise, we filter the element by classname
    var elements = [];  // Start with an empty array
    for(var i = 0; i < all.length; i++) {
        var element = all[i];
        if (isMember(element, classname)) // isMember() is defined below
            elements.push(element);       // Add class members to our array
    }

    // alert("elements.length = " + elements.length);
    
    // Note that we always return an array, even if it is empty
    return elements;

    // Determine whether the specified element is a member of the specified
    // class.  This function is optimized for the common case in which the 
    // className property contains only a single classname.  But it also 
    // handles the case in which it is a list of whitespace-separated classes.
    function isMember(element, classname) {
    

        classes = element.className;  // Get the list of classes   
        
        if (!classes) return false;             // No classes defined
        if (classes == classname) return true;  // Exact match

        // We didn't match exactly, so if there is no whitespace, then 
        // this element is not a member of the class
        var whitespace = /\s+/;
        if (!whitespace.test(classes)) return false;

        // If we get here, the element is a member of more than one class and
        // we've got to check them individually.
        var c = classes.split(whitespace);  // Split with whitespace delimiter
        for(var i = 0; i < c.length; i++) { // Loop through classes
            if (c[i] == classname) return true;  // and check for matches
        }

        return false;  // None of the classes matched
    }
}



// -----------------------------------------------------------------------------------
function ts_makeSortable(table) {

    // Make an individual table sortable, by decorating the header cells with "onclick"
    
    // Is there a <THEAD>?  If so, make the last row in THEAD the clickable row.
    if ((table.tHead === undefined) || (table.tHead === null)) {     // No THEAD: use the first row in TBODY.

        if (table.tBodies[0].rows && table.tBodies[0].rows.length > 0) {
            var clickableRow = table.tBodies[0].rows[0];
        }
         
    }
    else {      // There is a THEAD, so find its last row and use that
        clickableRow = table.tHead.rows[table.tHead.rows.length-1];
    }
    
    if (!clickableRow) {return;}
    
    // Now clickableRow is the row we want to make clickable.
    
    for (var i=0;i<clickableRow.cells.length;i++) {
        var cell = clickableRow.cells[i];
        var txt = ts_getInnerText(cell);

        cell.innerHTML = '<a href="#" class="sortheader" ' + 
                        'onclick="ts_resortTable(this);return false;">' + 
                        txt + '<span class="sortarrow">' + '&nbsp;&nbsp;&nbsp;' + '</span></a>';
    }
    

}     //  End of function ts_makeSortable(table)



// -----------------------------------------------------------------------------------
function sortables_init() {
    // Find all tables with class sortable and make them sortable

    if (!document.getElementsByTagName) {return;}
    var tbls = document.getElementsByTagName("table");
    for (var ti=0;ti<tbls.length;ti++) {
        var thisTbl = tbls[ti];
        if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
            //initTable(thisTbl.id);
            ts_makeSortable(thisTbl);
        }
    }
}     // End of function sortables_init()


// -----------------------------------------------------------------------------------
function show_sort_rank(table, trow, rowlist, clickindex) {
    // Add a sortrank column to the sorted table, with entries 1, 2, ..., n
    // If the column already exists, simply re-set the entries to 1, 2, ..., n
    
    var sranks = getElements("sortrank", "th", table);
    var i, j;
    
    var rank = [];   // Array of sort td items to hold the rank #s
    var text = [];   // Array of text to insert into those rank items                  
    
    var numrows_to_set = rowlist.length - clickindex;
    
    if ( (sranks == null) || (sranks.length == 0) ) {  
        // Set up the initial column, with values 1, 2, ..., n

        for(j=1; j< numrows_to_set; j++) {
            rank[j] = document.createElement("td");             // Create a <td> element  
            rank[j].setAttribute("style", "font-family: arial; font-size: 14px");
            rank[j].setAttribute("className", "row");
            rank[j].setAttribute("align", "center");
            text[j] = document.createTextNode(j.toString());    // Create a text node
            rank[j].appendChild(text[j]);                       // Add text to the <td> element
        }
        
        j = 0;
        rank[j] = document.createElement("th");                 // Create a <th> element  
        rank[j].setAttribute("style", "font-family: arial; font-size: 14px");
        rank[j].setAttribute("className", "row");
        rank[j].className ="sortrank";                          // Give it the class attribute "sortrank"
        rank[j].setAttribute("align", "center");
        text[j] = document.createTextNode("Row");         // Create a text node
        rank[j].appendChild(text[j]);                    // Add text to the <th> element

        for(j=0; j< numrows_to_set; j++) {
            var thisrow = rowlist[clickindex+j];
            thisrow.appendChild(rank[j]);
        }
       
       // Now insert a "" textnode at the front of any header row that's above the one that was clicked:
        rank = [];
        text = [];
        for(j=0; j< clickindex; j++) {
            rank[j] = document.createElement("th");             // Create a <td> element  
            text[j] = document.createTextNode("");              // Create a text node
            rank[j].appendChild(text[j]);                       // Add text to the <td> element
            thisrow = rowlist[j];
            thisrow.appendChild(rank[j]);
            
        } 
        
    }
                                                  
    
    else {
        // The column is already there, so reset its values to 1, 2, ... n:
        for(j=1; j< numrows_to_set; j++) {
            rowlist[clickindex+j].lastChild.lastChild.data = j.toString();
        }
    
    }




}     // End of function show_sort_rank()







// -----------------------------------------------------------------------------------
function ts_resortTable(lnk) {
    //  The user has presumably clicked on the anchor in a header cell.
    //  Sort the table by the column that was clicked on.

    // get the span
    var span;
    var ci;
    for (ci=0;ci<lnk.childNodes.length;ci++) {
        if (lnk.childNodes[ci].tagName && 
            lnk.childNodes[ci].tagName.toLowerCase() == 'span' &&
            lnk.childNodes[ci].className == 'sortarrow') {
            
            span = lnk.childNodes[ci];
            break;   // No use continuing to search for "span" if we've found it!
        }  
    }

    var td = lnk.parentNode;                 // The (header) data cell that was clicked on
    var column = td.cellIndex;                  // The column in the table that we sort on
    var trow = getParent(td, "TR");          // The row of the (header) data cell that was clicked on
    var table = getParent(td,'TABLE');       // The table to be sorted
    
   //  Now we need to know which row is clickable; i.e., its index in the
   //  entire list of rows in the whole table, so that we don't sort the
   //  header rows.
   
    var clickindex = -1;   // Index of the clickable (header) row
    var rowlist = table.getElementsByTagName("tr");

    var i;
    for(i=0; i<rowlist.length; i++) {
        if (rowlist[i] == trow){
            clickindex = i;
            break;
        }
    }
   
    
    // Work out a type for the column
    if (table.rows.length <= clickindex+1) {return;}
    for (i=clickindex+1; i<table.rows.length; i++) {  // Find a representative item in the column
        var itm = ts_getInnerText(table.rows[i].cells[column]);
        if ( (typeof itm == "string") && (itm.search(/\S/) >= 0)) {break;}  // itm is a non-blank string
    }
    
    if (typeof itm != "string") {return;}  // Could not find a representative item, so we don't sort
    
   // Some possibilities are:
   //   44                a number: compare numerically
   //  5'10               5 feet 10 inches: convert to 5*12 + 10 = 70 inches and compare numerically
   //  FB/MLB             A couple of football positions.  Sort by string comparison.
   //  8/4/2006 or 08/04/2006 or 8-4-2006 or 08-04-06 or something similar: do a date comparison
   //  $14.23             Currency comparison
   //  
   
     
    var sortfn;   // The sort function that we'll eventually set
    var sort_type = 0;
    var s0 = itm.match(/\d+/g);
        
    if (s0 === null) {
            sortfn = ts_sort_caseinsensitive;   // No digits---sort it as a string
            sort_type = 1;
    }
    else if (s0.length === 1) {                 // There is only one number; is the whole string a number?
            if (itm.match(/^\d+$/)) {
                 sortfn = ts_sort_numeric;          // Yes: the string is exactly one number---sort it as a number
                 sort_type = 2;
             }
             else if (itm.match(/^[+-]/)) {     
                sortfn = ts_sort_numeric;      // The string has a single number, preceded by + or -.  Sort as a number
                sort_type = 2;
             }            
             else if (itm.match(/^[£$]/)) {     
                sortfn = ts_sort_currency;      // The string has a single number, but also a British pound sign
                sort_type = 6;
             }
            else {                              // No: the string has a single number, but also non-numerics
             sortfn = ts_sort_caseinsensitive;
             sort_type = 7;     //  same as 1
            }
    }
    else if (s0.length === 2) {
             
             if (!isNaN(itm)) {
                sortfn = ts_sort_numeric;       // It's a number, with a decimal point //
                sort_type=2;
             }
             else {
                sortfn = ts_sort_height;    // Exactly two numbers; assume they represent a height: 5'11, for instance
                sort_type = 3;
             }
    }
    else if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/))
             {sortfn = ts_sort_date;
             sort_type = 4;
    }
    else if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) {sortfn = ts_sort_date;
             sort_type = 5;
    }

    else {
             sortfn = ts_sort_caseinsensitive;
             sort_type = 7;     //  same as 1
    }
    
    // alert("sort_type = " + sort_type + " column = " + column + " itm = " + itm);
    
    SORT_COLUMN_INDEX = column;

    // Copy all rows below the clickable row into a temporary array, newRows[]
    var newRows = [];
    for (var j=clickindex+1;j<table.rows.length;j++) { newRows[j-(clickindex+1)] = table.rows[j]; }

    
    // Here is the line that actually does the sorting, using the sortfn function that was determined above:
    newRows.sort(sortfn);
    
    
    var UPARROW = '&nbsp;&nbsp;&uarr;';
    var DOWNARROW = '&nbsp;&nbsp;&darr;';
    var BLANKS = '&nbsp;&nbsp;&nbsp;';
    var ARROW;

    if (span.getAttribute("sortdir") == 'down') {
        ARROW = UPARROW;
        newRows.reverse();
        span.setAttribute('sortdir','up');
    } else {
        ARROW = DOWNARROW;
        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
    // Note that the first clickindex+1 rows were not involved in the sorting and do not have to be appended; they're already there
    
    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]);
       }
    }
    // 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 any other arrows that may be showing
    var allspans = trow.getElementsByTagName("span");
    // alert("allspans.length = " + allspans.length);
    for (ci=0;ci<allspans.length;ci++) {
        if (allspans[ci].className == 'sortarrow') {
            allspans[ci].innerHTML = BLANKS;
        }
    }
        
    span.innerHTML = ARROW;
    
    show_sort_rank(table, trow, rowlist, clickindex);
    
    
}     //  End of function ts_resortTable(lnk)





