Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// Code adapted by Franamax from wikibits.js table sort functions
// The wikibits.js code is based on code (c) 1997-2006 Stuart Langridge and Joost de Valk:
// http://www.joostdevalk.nl/code/sortable-table/
// http://www.kryogenix.org/code/browser/sorttable/

function sortables_init() {
	var idnum = 0;
	// Find all tables with class sortable and make them sortable
// sortable2 begin
   var srch = "sortable";
   for (x=0; x<2; ++x) {
      if (x==1) srch = "sortable2";
	var tables = getElementsByClassName(document, "table", srch);
// sortable2 end
	for (var ti = 0; ti < tables.length ; ti++) {
		if (!tables[ti].id) {
			tables[ti].setAttribute('id','sortable_table_id_'+idnum);
			++idnum;
		}
// sortable2 begin
		(x==0 ? ts_makeSortable(tables[ti]) : ts_makeSortable2(tables[ti]));
	}
// sortable2 end
   }
}
function ts_makeSortable2(table) {
	var firstRow;
/*	if (table.rows && table.rows.length > 0) {
		if (table.tHead && table.tHead.rows.length > 0) {
			firstRow = table.tHead.rows[table.tHead.rows.length-1];
		} else {
			firstRow = table.rows[0];
		}
	}
	if (!firstRow) return;

	// We have a first row: assume it's the header, and make its contents clickable links
	for (var i = 0; i < firstRow.cells.length; i++) {
		var cell = firstRow.cells[i];
*/
// sortable2 begin
for (var x=0;x<table.rows.length;++x) {
   if ( (" "+table.rows[x].className+" ").indexOf(" sorthdg ") <0) break;
   for (var i=0; i<table.rows[x].cells.length; ++i) {
      var cell = table.rows[x].cells[i];
		if ((" "+cell.className+" ").indexOf(" sortkey ") >= 0) {
// sortable2 end
			cell.innerHTML += '&nbsp;&nbsp;<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;"><span class="sortarrow"><img src="'+ ts_image_path + ts_image_none + '" alt="&darr;"/></span></a>';
		}
	}
// sortable2 begin
}
//sortable2 end
	if (ts_alternate_row_colors) {
		ts_alternate(table);
	}
}

function ts_resortTable(lnk) {
	// get the span
	var span = lnk.getElementsByTagName('span')[0];

	var td = lnk.parentNode;
	var tr = td.parentNode;
	var column = td.cellIndex;

	var table = tr.parentNode;
	while (table && !(table.tagName && table.tagName.toLowerCase() == 'table'))
		table = table.parentNode;
	if (!table) return;

	// Work out a type for the column
	if (table.rows.length <= 1) return;

	// Skip the first row if that's where the headings are
	var rowStart = (table.tHead && table.tHead.rows.length > 0 ? 0 : 1);
// sortable2 begin
for (var i=0; i<table.rows.length; ++i) {
   if ((" "+table.rows[i].className+" ").indexOf(" sorthdg ") == -1) break;
   rowStart = i+1;
   }
// find the column index in the actual table data
// uncol is the number of columns rowspanned from higher up in the table
// tgtrow is the rowspan adjusted row index of the clock target
// tgtcol is the colspan adjusted column index of the click target
// stop is a counter for when to stop examining columns in the previous rows
// thisrow is an indexed row for span-counting, thiscell is an indexed cell
// tgtspan is the rowspan of the cell in thisrow above the target
// minspan is the smallest rowspan in thisrow, for the rare occasion when all the rowspans are >1
// x, y and rspan are work variables
var uncol = 0; var stop = 0; var x = 0; var y = 0; var rspan = 0; var tgtspan = 0; var minspan = 0;
var tgtrow = tr.rowIndex; var tgtcol = column;
// find the "true" column index of the target within its own row
for (x=0; x<column; ++x) {
   tgtcol += tr.cells[x].colSpan - 1;
   }
// first pass - find the "real" target row by counting the rowspans of the cells above the target
for (x=0; x<tgtrow; ++x) {
   stop = 0;  // count the non-interfering columns before the target
   minspan = 999; tgtspan = 0;
   thisrow = table.rows[x];
   for (y=0; y<thisrow.cells.length; ++y) {
      thiscell = thisrow.cells[y];
      rspan = thiscell.rowSpan; if (rspan < minspan) minspan = rspan;
// if the rowspan interferes with the current tgtrow, ignore it - we're just trying to find the cell above the target on thisrow in pass 1.
      if ( (x+rspan) >= (tgtrow+1) ) continue;
      stop += thiscell.colSpan;
      if (stop >= tgtcol && tgtspan <= 0) tgtspn = rspan;
      }
// increment the target row by the rowspan in the cell above it (minus the smallest span in the row)
   if (tgtspan > 0 && minspan != 999) {  // sanity check, should always be true
      tgtrow += tgtspan - minspan;
      if (tgtrow < 1) tgtrow = 1;  // this would be serious bad news if true
      if (tgtrow > 100) {
         tgtrow = 1; break;  // this would be horrible news of breakage
         }
      }
   }
// 2nd pass - now run down the rows above the target, find and count the cells with rowspans that will intrude onto the target row
for (x=0;x<tgtrow;++x) {
   stop = 0;  // count the non-interfering columns before the target
   thisrow = table.rows[x];
   for (y=0; y<thisrow.cells.length; ++y) {
      thiscell = thisrow.cells[y];
      rspan = thiscell.rowSpan;
// if the current column interferes with our target, it's an uncol!
      if ( (x+rspan) >= (tgtrow+1) ) {
         uncol += thiscell.colSpan; continue;
         }
      stop += thiscell.colSspan;
      if (stop >= tgtcol) break;  
      }
   }
// now set the "real" column in the actual table
column = tgtcol + uncol;
// sortable2 end
	var itm = "";
	for (var i = rowStart; i < table.rows.length; i++) {
		if (table.rows[i].cells.length > column) {
			itm = ts_getInnerText(table.rows[i].cells[column]);
			itm = itm.replace(/^[\s\xa0]+/, "").replace(/[\s\xa0]+$/, "");
			if (itm != "") break;
		}
	}
	var sortfn = ts_sort_caseinsensitive;
	if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
		sortfn = ts_sort_date;
	else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
		sortfn = ts_sort_date;
	else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
		sortfn = ts_sort_date;
	// pound dollar euro yen currency cents
	else if (itm.match(/(^[\u00a3$\u20ac\u00a4\u00a5]|\u00a2$)/))
		sortfn = ts_sort_currency;
	// We allow a trailing percent sign, which we just strip.  This works fine
	// if percents and regular numbers aren't being mixed.
	else if (itm.match(/^[+-]?\d[\d,]*(\.[\d,]*)?([eE][+-]?\d[\d,]*)?\%?$/) ||
	itm.match(/^[+-]?\.\d[\d,]*([eE][+-]?\d[\d,]*)?\%?$/) ||
	itm.match(/^0x[\da-f]+$/i))
		sortfn = ts_sort_numeric;

	var reverse = (span.getAttribute("sortdir") == 'down');

	var newRows = new Array();
	for (var j = rowStart; j < table.rows.length; j++) {
		var row = table.rows[j];
		var keyText = ts_getInnerText(row.cells[column]);
		var oldIndex = (reverse ? -j : j);

		newRows[newRows.length] = new Array(row, keyText, oldIndex);
	}

	newRows.sort(sortfn);

	var arrowHTML;
	if (reverse) {
		arrowHTML = '<img src="'+ ts_image_path + ts_image_down + '" alt="&darr;"/>';
		newRows.reverse();
		span.setAttribute('sortdir','up');
	} else {
		arrowHTML = '<img src="'+ ts_image_path + ts_image_up + '" alt="&uarr;"/>';
		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 (var i = 0; i < newRows.length; i++) {
		if ((" "+newRows[i][0].className+" ").indexOf(" sortbottom ") == -1)
			table.tBodies[0].appendChild(newRows[i][0]);
	}
	// do sortbottom rows only
	for (var i = 0; i < newRows.length; i++) {
		if ((" "+newRows[i][0].className+" ").indexOf(" sortbottom ") != -1)
			table.tBodies[0].appendChild(newRows[i][0]);
	}

	// Delete any other arrows there may be showing
	var spans = getElementsByClassName(tr, "span", "sortarrow");
	for (var i = 0; i < spans.length; i++) {
		spans[i].innerHTML = '<img src="'+ ts_image_path + ts_image_none + '" alt="&darr;"/>';
	}
	span.innerHTML = arrowHTML;

	if (ts_alternate_row_colors) {
		ts_alternate(table);
	}
}