/* SortTable version 2 7th April 2007 Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/ Instructions: Download this file Add to your HTML Add class="sortable" to any table you'd like to make sortable Click on the headers to sort Thanks to many, many people for contributions and suggestions. Licenced as X11: http://www.kryogenix.org/code/browser/licence.html This basically means: do what you want with it. */ // To allow resetting of the document hash search on hash and uncomment. function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') { window.onload = func; } else { window.onload = function() { if (oldonload) { oldonload(); } func(); } } } var stIsIE = /*@cc_on!@*/false; var TABLE_NUMBER = 0; sorttable = { init: function() { // quit if this function has already been called if (arguments.callee.done) return; // flag this function so we don't do the same thing twice arguments.callee.done = true; // kill the timer if (_timer) clearInterval(_timer); if (!document.createElement || !document.getElementsByTagName) return; sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; forEach( document.getElementsByTagName('table'), function(table) { if (table.className.search(/\bsortable\b/) != -1) { TABLE_NUMBER++; sorttable.makeSortable(table); } } ); if(typeof window.tt_Init == 'function') { // function exists, so we can now call it tt_Init(); } use_doc_hash_value(); }, makeSortable: function(table) { if (table.getElementsByTagName('thead').length == 0) { // table doesn't have a tHead. Since it should have, create one and // put the first table row in it. var the = document.createElement('thead'); the.appendChild(table.rows[0]); table.insertBefore(the,table.firstChild); } // Safari doesn't support table.tHead, sigh if (table.tHead == null) { table.tHead = table.getElementsByTagName('thead')[0]; } // Sorttable v1 put rows with a class of "sortbottom" at the // bottom (as "total" rows, for example). This is B&R, since // what you're supposed to do is put them in a tfoot. So, if // there are sortbottom rows, for backwards compatibility, // move them to tfoot (creating it if needed). var sortbottomrows = []; var tfo; for (var i=0; i5' :' ▴'; } else { sortrevind.innerHTML = stIsIE ? ' 6' : ' ▾'; } this.appendChild(sortrevind); // We need to redo the column ranking if // we have a ranker column if (headrow[0].className.match(/\branker\b/)) { //alert('a:'+table + ':' + null +':'+col+':'+col); recolor(table,null,col); move_tooltips_to_first_row(table); set_direct_link_value(table.id,col); } return; } else if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) { // if we've already sorted by this column in reverse, just // re-reverse the table, which is quicker sorttable.reverse(this.sorttable_tbody); move_tooltips_to_first_row(table); this.className = this.className.replace('sorttable_sorted_reverse', 'sorttable_sorted'); this.removeChild(document.getElementById('sorttable_sortrevind'+ this.table_number)); sortfwdind = document.createElement('span'); sortfwdind.id = "sorttable_sortfwdind"+ this.table_number; // Append a webding to show the sort order. col = this.sorttable_columnindex; if (!headrow[col].className.match(/\bsort_default_asc\b/)) { sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; } else { sortfwdind.innerHTML = stIsIE ? ' 5' : ' ▴'; } this.appendChild(sortfwdind); // We need to redo the column ranking if // we have a ranker column if (headrow[0].className.match(/\branker\b/)) { //alert('b:'+table + ':' + null +':'+col+':'+col); recolor(table,null,col); move_tooltips_to_first_row(table); set_direct_link_value(table.id,col); } return; } // remove sorttable_sorted classes var theadrow = this.parentNode; forEach(theadrow.childNodes, function(cell) { if (cell.nodeType == 1) { // an element cell.className = cell.className.replace('sorttable_sorted_reverse',''); cell.className = cell.className.replace('sorttable_sorted',''); } }); // Search the document for the old span that // has this id and blitz it. sortfwdind = document.getElementById('sorttable_sortfwdind'+ this.table_number); if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); } sortrevind = document.getElementById('sorttable_sortrevind'+ this.table_number); if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); } // alert('sorttable_sortrevind'+ this.table_number); // Create a span that contains the webding. this.className += ' sorttable_sorted'; sortfwdind = document.createElement('span'); sortfwdind.id = "sorttable_sortfwdind"+ this.table_number; // Append a webding to show the sort order. col = this.sorttable_columnindex; // alert("col:"+col); if (!headrow[col].className.match(/\bsort_default_asc\b/)) { sortfwdind.innerHTML = stIsIE ? ' 6' : ' ▾'; } else { sortfwdind.innerHTML = stIsIE ? ' 5' : ' ▴'; } // Append the span to the column we are sorting. this.appendChild(sortfwdind); // build an array to sort. This is a Schwartzian transform thing, // i.e., we "decorate" each row with the actual sort key, // sort based on the sort keys, and then put the rows back in order // which is a lot faster because you only do getInnerText once per row row_array = []; col = this.sorttable_columnindex; rows = this.sorttable_tbody.rows; for (var j=0; j 12) { // definitely dd/mm return sorttable.sort_ddmm; } else if (second > 12) { return sorttable.sort_mmdd; } else { // looks like a date, but we can't tell which, so assume // that it's dd/mm (English imperialism!) and keep looking sortfn = sorttable.sort_ddmm; } } } } return sortfn; }, getInnerText: function(node) { // gets the text we want to use for sorting for a cell. // strips leading and trailing whitespace. // this is *not* a generic getInnerText function; it's special to sorttable. // for example, you can override the cell text with a customkey attribute. // it also gets .value for fields. if (node == null) return ''; var hasInputs = (typeof node.getElementsByTagName == 'function') && node.getElementsByTagName('input').length; if (node.getAttribute("sorttable_customkey") != null) { return node.getAttribute("sorttable_customkey"); } else if (node.getAttribute("csk") != null) { return node.getAttribute("csk"); } else if (typeof node.textContent != 'undefined' && !hasInputs) { return node.textContent.replace(/^\s+|\s+$/g, ''); } else if (typeof node.innerText != 'undefined' && !hasInputs) { return node.innerText.replace(/^\s+|\s+$/g, ''); } else if (typeof node.text != 'undefined' && !hasInputs) { return node.text.replace(/^\s+|\s+$/g, ''); } else { switch (node.nodeType) { case 3: if (node.nodeName.toLowerCase() == 'input') { return node.value.replace(/^\s+|\s+$/g, ''); } case 4: return node.nodeValue.replace(/^\s+|\s+$/g, ''); break; case 1: case 11: var innerText = ''; for (var i = 0; i < node.childNodes.length; i++) { innerText += sorttable.getInnerText(node.childNodes[i]); } return innerText.replace(/^\s+|\s+$/g, ''); break; default: return ''; } } }, reverse: function(tbody) { // reverse the rows in a tbody newrows = []; for (var i=0; i=0; i--) { tbody.appendChild(newrows[i]); } delete newrows; }, /* sort functions each sort function takes two parameters, a and b you are comparing a[0] and b[0] */ sort_numeric: function(a,b) { aa = parseFloat(a[0].replace(/[^0-9.-]/g,'')); if (isNaN(aa)) aa = 0; bb = parseFloat(b[0].replace(/[^0-9.-]/g,'')); if (isNaN(bb)) bb = 0; return aa-bb; }, sort_alpha: function(a,b) { if (a[0]==b[0]) return 0; if (a[0] 0 ) { var q = list[i]; list[i] = list[i+1]; list[i+1] = q; swap = true; } } // for t--; if (!swap) break; for(var i = t; i > b; --i) { if ( comp_func(list[i], list[i-1]) < 0 ) { var q = list[i]; list[i] = list[i-1]; list[i-1] = q; swap = true; } } // for b++; } // while(swap) } } /* ****************************************************************** Convert a table into csv. This is fairly straightforward, except in cases where there are colspans and rowspans. */ function get_csv_output(tableid, do_drop_over_headers) { var tableref = document.getElementById(tableid); var pre_filled_value = -9999; // Delete all of the non-visible rows. var trs = tableref.getElementsBySelector('tr'); // Needs to be in reverse order because we are deleting rows which // causes the idx of subsequent rows to be off by one. for (i = trs.length - 1; i >= 0; i--) { if (!trs[i].visible()) { tableref.deleteRow(i); } } if (!tableref) return 'Converting from PRE-Formatted to CSV does not work, please Reload and then click CSV' ; // Safari doesn't support tableref.tHead, sigh if (tableref.tHead == null) tableref.tHead = tableref.getElementsByTagName('thead')[0]; // work through each column and row and stuff into table_entries // If we have multiple rows in thead, we want the last one. //alert(':' +tableid + ':' + tableref +":" ); //alert(tableref.tHead); var headrow = tableref.tHead.rows[ tableref.tHead.rows.length - 1 ].cells; var maxx = tableref.rows.length; var maxy = headrow.length; var table_entries = new Array(maxx); for (x = 0; x <= maxx; x++) { table_entries [x] = new Array(maxy); for (y = 0; y <= maxy; y++) { table_entries[x][y] = pre_filled_value; } } // Read in all of the values. var initial_row = 0; if (do_drop_over_headers && (do_drop_over_headers == 1)) { initial_row = tableref.tHead.rows.length - 1; } for (var i = 0; i < maxx; i++) { var pre_filled_table_entries = 0; for (var j = 0; j < maxy; j++) { if (table_entries[i][j] == pre_filled_value) { var cell_rowspan = tableref.rows[i].cells[j - pre_filled_table_entries].getAttribute("rowspan"); var cell_colspan = tableref.rows[i].cells[j - pre_filled_table_entries].getAttribute("colspan"); // Read the data within the table cells. var node_value = get_node_inner_text(tableref.rows[i].cells[j - pre_filled_table_entries]); if(i < initial_row) node_value = node_value.substring(0,4); // Remove any commas from the entry. var new_node_value = node_value.replace(/,/g,''); node_value = new_node_value; table_entries[i][j] = node_value; // Handle the rowspans. if (cell_rowspan > 1) { for (k = 0; k < cell_rowspan; k++) { for (l = 0; l < cell_colspan; l++) { // alert('prefill: ' + (i+k) + ',' + (j + l - pre_filled_table_entries) + ',' + pre_filled_table_entries); table_entries[i + k][j + l] = node_value; if (l > 0) { } } } if (cell_colspan > 1) { pre_filled_table_entries += cell_colspan - 1; j = j + cell_colspan - 1; } } else { // Handle the colspans. if (cell_colspan && (cell_colspan > 1)) { for (k = 1; k < cell_colspan; k++) { table_entries[i][j + k ] = node_value; pre_filled_table_entries++; } j += cell_colspan - 1; } } } else { // Note that we've pre-filled something here. //pre_filled_table_entries++; //j = j + 1; } } } // alert(maxx+":"+maxy); // Output all of the values. var csv_output = ''; for (var i = 0; i < maxx; i++) { var row_output = new Array; for (var j = 0; j < maxy; j++) { row_output.push(table_entries[i][j]); } csv_output = csv_output + "\n" + row_output.join(','); } return "" + csv_output; } /* *********************************************************** sort_on_load Creates a fake mouse click to sort the column in question. ********************************************************* */ function sort_on_load(table_name, sorted_column, focus_here) { var table = document.getElementById(table_name); var lastHeadRow = table.tHead.rows.length - 1; var headrow = table.tHead.rows[ lastHeadRow ].cells; if (focus_here) { // First we focus using the hash that actually exists, then the // second one doesn't exist, so nothing bad happens and the url // bar shows what we expect it to. location.hash = table_name; location.hash = table_name + '::' + sorted_column; } if (!document.all) { var fireOnThis = headrow[sorted_column]; var evObj = document.createEvent('MouseEvents'); evObj.initEvent( 'click', true, true ); fireOnThis.dispatchEvent(evObj); } else { var fireOnThis = headrow[sorted_column]; fireOnThis.fireEvent('onclick'); } } /* *********************************************************** use_doc_hash_value Takes the document hash value onload and attempts to sort the tables as needed. The hashes have the form table_name::sorted_column;;table_name2::sorted_column2 ********************************************************* */ function use_doc_hash_value() { // Parse the existing hash and check to see if this table_name is // already set here. var current_hash = window.location.hash; if (current_hash == "") { return 1; } if (current_hash.charAt(0) == '#') { var drop_hash = current_hash.substring(1); current_hash = drop_hash; } var arr_sorted_tables = new Array; var arr_tables_keys = new Array; arr_sorted_tables = current_hash.split(/;;/); for (var i = 0; i < arr_sorted_tables.length; i++) { // Get the table id and the column to sort. arr_tables_keys = arr_sorted_tables[i].split('::'); if (arr_tables_keys.length < 2) { return 1; } if (/^[0-9]+$/.test(arr_tables_keys[1])) { // The (i == 0) is for a focus boolean. We want the // browser window to focus on only the first table we have sorted. sort_on_load(arr_tables_keys[0], arr_tables_keys[1], (i == 0)); } else { show_alt_stat(arr_tables_keys[0], arr_tables_keys[1]); } } } /* *********************************************************** set_doc_hash_value Takes a table and column and adds a sort command to the hash result for use later in providing a bookmarkable link. The hashes have the form table_name::sorted_column;;table_name2::sorted_column2 ********************************************************* */ function set_doc_hash_value(table_name, sorted_column) { // Parse the existing hash and check to see if this table_name is // already set here. //alert("set doc_hash"); var current_hash = window.location.hash; if (current_hash.charAt(0) == '#') { var drop_hash = current_hash.substring(1); current_hash = drop_hash; } var arr_sorted_tables = new Array; var arr_tables_keys = new Array; var arr_new_hash = new Array; var has_found_table = 0; arr_sorted_tables = current_hash.split(/;;/); for (var i = 0; i < arr_sorted_tables.length; i++) { arr_tables_keys = arr_sorted_tables[i].split(/::/); if (arr_tables_keys[0] == table_name) { has_found_table = 1; arr_tables_keys[1] = sorted_column; } if (arr_tables_keys.length == 2) { arr_new_hash.push(arr_tables_keys.join('::')); } } // For a table not in the hash if (!has_found_table) { arr_new_hash.push(table_name + '::' + sorted_column); } // Reset the window's hash. window.location.hash = arr_new_hash.join(';;'); } /* *********************************************************** set_direct_link_value Takes a table and column and replaces a direct link in that table with a table_id::column_number pair that can be used to then send a link to someone else. The hashes have the form table_name::sorted_column ********************************************************* */ function set_direct_link_value(table_name, sorted_column) { // Parse the existing hash and check to see if this table_name is // already set here. //alert("set doc_hash"); var link = document.getElementById('link_' + table_name); if (link) { link.href = '#' + table_name + '::' + sorted_column; var page_url = link.href; var popup_text = "" + page_url + ""; link.onclick = function () { popupTextNoURL(link, popup_text); } } } /* *********************************************************** move_tooltips_to_first_row If we've called up the tooltips and sorted, we always want to move them back to the first entry. ********************************************************* */ function move_row_safe(table, from, to) { // alert(table.id + ":" + from + ":" +to + ":" ); if (from == to) { return; } var tbody = table.tBodies[0]; // Use tbody var row = tbody.rows[from]; // Make sure row stays referenced var insertPos = tbody.rows[to]; var parent = row.parentNode; parent.removeChild(row); parent.insertBefore(row, insertPos); } function move_tooltips_to_first_row(table_dom) { //alert(table_dom.id); // Figure out if there is a tooltip row. var tips_row = table_dom.tBodies[0].getElementsBySelector('tr.delete_this_tip'); // Get the tooltip rows index. and move it to the index of the // first row after thead. if (tips_row.length) { var old_idx = tips_row[0].rowIndex - table_dom.tHead.rows.length; var new_idx = 0; // alert(table_id + ":" + tips_row[0] + ":" + tips_row[0].className + ":" + tips_row[0].rowIndex + ":" + old_idx + ":" +new_idx + ":" ); move_row_safe(table_dom,old_idx,new_idx); } } /* ****************************************************************** Convert a table into csv. This is fairly straightforward, except in cases where there are colspans and rowspans. */ function table2csv(table_id) { var table_div = document.getElementById('div_'+table_id); var csv_output = get_csv_output(table_id); table_div.innerHTML = '

Reload page to return to the table-formatted data.

' + '
' + csv_output + '
'; } /* ****************************************************************** Convert a table into csv and then send it through our csv to pre tool. */ function table2pre(table_id) { var table_div = document.getElementById('div_'+table_id); // get the csv and drop the over_header rows. var csv_output; // Check to see if we've already created the csv output. if (table_div.innerHTML.match(/ALREADYCSV/)) { csv_output = table_div.innerHTML; } else { csv_output = get_csv_output(table_id, 1); } var url = '/friv/csv2pre.cgi'; var pars = 'ajax=1&csv=' + encodeURIComponent(csv_output); var table_div_name = 'div_' + table_id; var myAjax = new Ajax.Updater( table_div_name, url, { method: 'post', parameters: pars } ); } function get_node_inner_text (node) { // gets the text we want to use for sorting for a cell. // strips leading and trailing whitespace. // this is *not* a generic getInnerText function; it's special to sorttable. // for example, you can override the cell text with a customkey attribute. // it also gets .value for fields. if (!node) return ''; hasInputs = (typeof node.getElementsByTagName == 'function') && node.getElementsByTagName('input').length; if (typeof node.textContent != 'undefined' && !hasInputs) { return node.textContent.replace(/^\s+|\s+$/g, ''); } else if (typeof node.innerText != 'undefined' && !hasInputs) { return node.innerText.replace(/^\s+|\s+$/g, ''); } else if (typeof node.text != 'undefined' && !hasInputs) { return node.text.replace(/^\s+|\s+$/g, ''); } else { switch (node.nodeType) { case 3: if (node.nodeName.toLowerCase() == 'input') { return node.value.replace(/^\s+|\s+$/g, ''); } case 4: return node.nodeValue.replace(/^\s+|\s+$/g, ''); break; case 1: case 11: var innerText = ''; for (var i = 0; i < node.childNodes.length; i++) { innerText += sorttable.getInnerText(node.childNodes[i]); } return innerText.replace(/^\s+|\s+$/g, ''); break; default: return ''; } } } /* ****************************************************************** Supporting functions: bundled here to avoid depending on a library ****************************************************************** */ /************************************************************************ */ // Regular expressions for normalizing white space. var whtSpEnds = new RegExp("^\\s*|\\s*$", "g"); var whtSpMult = new RegExp("\\s\\s+", "g"); // Regular expressions for setting class names. function normalizeString(s) { s = s.replace(whtSpMult, " "); // Collapse any multiple whites space. s = s.replace(whtSpEnds, ""); // Remove leading or trailing white space. return s; } var colClsNm = "sort_col"; var colTest = new RegExp(colClsNm, ""); var TURN_ON_HIDE_PARTIAL = 1; var TURN_ON_HIDE_NON_QUALS = 1; var LAST_COLUMN; function recolor(tblEl, strTableName, col) { //alert("tblEl.id:"+tblEl.id+":"); var i, j; var rowEl, cellEl; var do_highlight_column = 0; // Check to see if we actually got a table. //alert('0:'+tblEl + ':' + strTableName +':'+col+':'+LAST_COLUMN); if ((tblEl == null) || (tblEl == '') ) { forEach( document.getElementsByTagName('table'), function(table) { if (table.id == strTableName) { tblEl = table; } } ); //alert('1:'+tblEl + ':' + strTableName +':'+col+':'+LAST_COLUMN); } if (col == null){ col = LAST_COLUMN; //alert('3:'+tblEl + ':' + strTableName +':'+col+':'+LAST_COLUMN); } //alert('4:'+tblEl + ':' + strTableName +':'+col+':'+LAST_COLUMN); // work through each column and calculate its type // Safari doesn't support table.tHead, sigh if (tblEl.tHead == null) tblEl.tHead = tblEl.getElementsByTagName('thead')[0]; if (tblEl.tFoot == null) tblEl.tFoot = tblEl.getElementsByTagName('tfoot')[0]; // Get a handle on the last head row in the thead tag for this // table. var lastHeadRow = tblEl.tHead.rows.length - 1; var cntFootRows = tblEl.tFoot.rows.length; var headrow = tblEl.tHead.rows[ lastHeadRow ].cells; var rankerCount = 0; var hasRankerColumn = 0; // Check the first columns to see if it is a ranker column. if (headrow[0].className.match(/\branker\b/)) { // We have a ranker column. hasRankerColumn = 1; } var do_hide_partial_rows = 1; // This is a column where we want to show all rows even partial ones. if (headrow[col].className.match(/\bshow_partial_when_sorting\b/)) { do_hide_partial_rows = 0; // This is to change the toggle depending on what we have here. var oSpan = document.getElementById(tblEl.id + '_toggle_'); if (oSpan) { oSpan.innerHTML = oSpan.innerHTML.replace('Show','Hide'); oSpan.style.backgroundColor = '#fff'; } } else { var oSpan = document.getElementById(tblEl.id + '_toggle_'); if (oSpan) { oSpan.innerHTML = oSpan.innerHTML.replace('Hide','Show'); oSpan.style.backgroundColor = '#ff9'; } } // For rate stats, we may want to hide non-qualifiers. var do_hide_non_quals = 0; // This is a column where we want to show all rows even partial ones. if (headrow[col].className.match(/\bhide_non_quals\b/)) { do_hide_non_quals = 1; // Check to see if there is a form sortables and a checkbox // hide_non_quals which if un-checked we override this. // This was removed because some pages had multiple // form_sortables on a single page // if(document.form_sortable && // document.form_sortable.hide_non_quals && // !document.form_sortable.hide_non_quals.checked // ) // do_hide_non_quals = 0; if(document.getElementById('fs_' + tblEl.id) && document.getElementById('fs_' + tblEl.id).hide_non_quals && !document.getElementById('fs_'+ tblEl.id).hide_non_quals.checked ) do_hide_non_quals = 0; //alert(tblEl.id + ":do_hide:" + do_hide_non_quals); } // alert(col + ':HidePartials:' + do_hide_partial_rows + ':HideNonQuals:' + do_hide_non_quals); var is_row_hidden; // Set style classes on each row to alternate their appearance. for (i = 0; i < tblEl.rows.length; i++) { is_row_hidden = 0; rowEl = tblEl.rows[i]; if (TURN_ON_HIDE_PARTIAL) { // Do we want to hide or show partial seasons by default if (rowEl && do_hide_partial_rows && rowEl.className.match(/\bpartial_table\b/)) { rowEl.style.display = 'none'; is_row_hidden = 1; } // Turn on a row that may have been hidden before. else if (rowEl && !do_hide_partial_rows && rowEl.className.match(/\bpartial_table\b/)) { rowEl.style.display = ''; } } if (TURN_ON_HIDE_NON_QUALS && !is_row_hidden) { // Do we want to hide or show partial seasons by default if (rowEl && do_hide_non_quals && rowEl.cells[col] && (rowEl.className.match(/\bnon_qual\b/) || rowEl.cells[col].className.match(/\bnon_qual\b/)) ) { //alert(i+':non qual'); rowEl.style.display = 'none'; is_row_hidden = 1; } else if (rowEl && !do_hide_non_quals && rowEl.cells[col] && (rowEl.className.match(/\bnon_qual\b/) || rowEl.cells[col].className.match(/\bnon_qual\b/)) ) { rowEl.style.display = ''; } else { rowEl.style.display = ''; } } // hide all interior_thead rows. if (rowEl.className.match(/\bthead\b/)) { rowEl.style.display = 'none'; is_row_hidden = 1; } // Set the value here. if (hasRankerColumn && (!is_row_hidden) && (i > lastHeadRow) && (i < tblEl.rows.length - cntFootRows) && (!rowEl.className.match(/\bno_ranker\b/)) && (!rowEl.className.match(/\bleague_average_table\b/)) && (!rowEl.className.match(/\bblank_table\b/)) && (!rowEl.className.match(/\bthead\b/)) ){ rankerCount++; rowEl.cells[0].innerHTML = rankerCount; } } } /// End of copied code. /* for Internet Explorer */ /*@cc_on @*/ /*@if (@_win32) document.write("