Possible improvements to Sortable Tables Javascript code used by MediaWiki.

SharkD's changes can be found here (note: the diffs may no longer be up-to-date):

Fix: remove alternate rows code

edit

This code marks table rows with classes "odd" and "even":

  • It is apparently not used by anyone
  • It executes on the page load, slowing loading time

The code in question is:

  • var ts_alternate_row_colors = true;
  • function ts_alternate(table) { ...
  • and two calls to this function

Comments 1

edit

it could be used by introducing another class into site CSS, something like table.alternate tr.even {background-color: #F5F5F5}

I would also prefer if it were enabled instead of removed. The fact that people don't know about it is because it isn't advertised. Template:Infobox VG (backlinks edit) for instance could feasably switch from using templates to using JavaScript for alternate row coloring. SharkD (talk) 00:50, 18 August 2008 (UTC)

Simplification: remove THEAD check

edit

As MediaWiki does not support THEAD, nor through wiki-syntax, neither as HTML tag.

function ts_makeSortable(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;

can be replaced with

function ts_makeSortable(table) {
	if (!table.rows || table.rows.length == 0) return
	var firstRow = table.rows[0]

and the line

var rowStart = (table.tHead && table.tHead.rows.length > 0 ? 0 : 1);

in function ts_resortTable(lnk) can be replaced with

var rowStart = 1

Comments 2

edit

The THEAD element may make an appearance in the future, so a link to some record of this code block's former existence would at least be warranted. SharkD (talk) 04:11, 28 August 2008 (UTC)

Also, in theory, a table could be constructed purely from HTML elements, and thus possibly have a THEAD element. SharkD (talk) 04:27, 28 August 2008 (UTC)
Nevermind. The THEAD is not supported at all, even if the table is constructed using HTML. SharkD (talk) 07:08, 28 August 2008 (UTC)

Simplification: month conversion

edit
function ts_dateToSortKey(date) {	
	// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
	if (date.length == 11) {
		switch (date.substr(3,3).toLowerCase()) {
			case "jan": var month = "01"; break;
			case "feb": var month = "02"; break;
			case "mar": var month = "03"; break;
			case "apr": var month = "04"; break;
			case "may": var month = "05"; break;
			case "jun": var month = "06"; break;
			case "jul": var month = "07"; break;
			case "aug": var month = "08"; break;
			case "sep": var month = "09"; break;
			case "oct": var month = "10"; break;
			case "nov": var month = "11"; break;
			case "dec": var month = "12"; break;
			// default: var month = "00";
		}

can be replaced with

function ts_dateToSortKey(date) {      
        // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
        if (date.length == 11) {
		var month = date.substr(3,3).toLowerCase()
		month = "janfebmaraprmayjunjulaugsepoctnovdec".indexOf(month)/3 + 1
		if (month == -1) month = "00"
		else if (month<10) month = "0" + month

Comments 3

edit

In either case, it is not clear to me what format the input dates take. I.e., what do the input dates look like? SharkD (talk) 04:14, 28 August 2008 (UTC)

Nevermind. I'll have to take a closer look at those regexps. SharkD (talk) 04:26, 28 August 2008 (UTC)

It might speed things up if only the ISO 8601 YYYY-MM-DD format were supported, leaving it up to editors to convert dates to this format (either visibly, or invisibly using a template such as {{dts}}). SharkD (talk) 07:43, 28 August 2008 (UTC)

This line:

if (month == -1) month = "00"

can be removed from your proposal, as the condition will never be met. SharkD (talk) 19:02, 29 August 2008 (UTC)

I started a bugzilla report on the locale-specific issues. SharkD (talk) 03:30, 1 September 2008 (UTC)

Proposal: input date format localization

edit

Sortable Tables code determines the sorting mode by analyzing the top non-empty cell. The first RegExp used for possible dates is

if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))

Proposal: accept arbitrary months names, something like

if (itm.match(/^\d\d[\/. -][^\d ]{1,12}[\/. -]\d\d\d\d$/))

and then introduce another parameter ts_months_names, which is set in local Common.js and is equivalent to English "janfebmaraprmayjunjulaugsepoctnovdec"

Then the function ts_dateToSortKey (see above section) is modified to use ts_months_names if month name was not found in the English string of month names.

Comments 4

edit

Is the above supposed to replace the latter two date regexprs as well, or just the first? Also, what if the day value is only a single digit long? SharkD (talk) 04:40, 28 August 2008 (UTC)

Also, is this change supposed to make the script language-agnostic? What if a month name in another language has more than 12 characters or has spaces in it (I have no idea if this will actually ever happen)? SharkD (talk) 05:07, 28 August 2008 (UTC)
The Wiktionary article on "January"[1] lists some translations that use spaces or other weird characters, but there aren't many of them. A few of them are longer than 12 characters, too. SharkD (talk) 06:05, 28 August 2008 (UTC)

The . in [\/. -] matches any character. Is this a good idea? Doesn't that make searching for the other characters redundant? Is it a mistake, and is it supposed to be [\. -] instead? SharkD (talk) 05:55, 28 August 2008 (UTC)

Nevermind. A period in square brackets doesn't match any character as it normally does--it only matches other periods. SharkD (talk) 06:56, 28 August 2008 (UTC)

Proposal: comma/dot localization

edit

The JavaScript hack

//fix for sortable tables: comma as decimal dot
function ts_parseFloat(num){
 if (!num) return 0
 num = parseFloat(num.replace(/\./g, '').replace(/,/, '.'))
 return (isNaN(num) ? 0 : num)
}

which can be found in de:MediaWiki:Common.js or ru:MediaWiki:Common.js, should be replaced with another configuration variable set in local Common.js.


Of course, if MediaWiki can automatically set this parameter depending on wgContentLanguage, this would be even better (this applies to the previous proposal as well).

Comments 5

edit

I'm not sure the above will work. For instance, if the number has decimal values, removing the period will turn them into integers. I suggest the following:

function ts_parseFloat(num) {
	if (!num) return 0;
	if (ts_europeandate == true)
		num = parseFloat(num.replace(/\./g, "").replace(/,/g, "."));
	else
		num = parseFloat(num.replace(/,/g, ""));
	return (isNaN(num) ? 0 : num);
}

Regardless if using ts_europeandate is the wrong way of going about doing it, some fix must be put into place to detect and handle this scenario. SharkD (talk) 13:36, 28 August 2008 (UTC)

Performance optimizations (SharkD (talk))

edit

Change variables in for statements so that the DOM is not traversed each time the block is looped

edit

Change:

for (var i = 0; i < thing.length ; i++)

To:

for (var i = thing.length; i > 0 ; i--)

Or (if the above is not possible):

for (var i = 0, n = thing.length; i < n ; i++)

Use a local variable instead of global, and use conditions to eliminate cases

edit

Change:

	sortfn = ts_sort_caseinsensitive;
	if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
		sortfn = ts_sort_currency;
	if (itm.match(/^[\d.,]+\%?$/))
		sortfn = ts_sort_numeric;

To:

	if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
		var sortfn = ts_sort_date;
	else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
		var sortfn = ts_sort_date;
	else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
		var sortfn = ts_sort_date;
	else if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
		var sortfn = ts_sort_currency;
	else if (itm.match(/^[\d.,]+\%?$/))
		var sortfn = ts_sort_numeric;
	else
		var sortfn = ts_sort_caseinsensitive;

Store DOM nodes in variables so that the DOM is not traversed each time

edit

Change:

			for (var k = 0; k < oldClasses.length; k++) {
				if (oldClasses[k] != "" && oldClasses[k] != "even" && oldClasses[k] != "odd")
					newClassName += oldClasses[k] + " ";
			}

To:

			for (var k = 0, p = oldClasses.length; k < p; k++) {
				var thisClass = oldClasses[k];
				if (thisClass != "" && thisClass != "even" && thisClass != "odd")
					newClassName += thisClass + " ";
			}

Change:

	for (var ti = 0; ti < tables.length ; ti++) {
		if (!tables[ti].id) {
			tables[ti].setAttribute('id','sortable_table_id_'+idnum);
			++idnum;
		}
		ts_makeSortable(tables[ti]);
	}
}

To:

	for (var ti = 0, n = tables.length; ti < n ; ti++) {
		var thisTable = tables[ti];
		if (!thisTable.id) {
			thisTable.setAttribute('id','sortable_table_id_'+idnum);
			++idnum;
		}
		ts_makeSortable(thisTable);
	}
}

Bug: the first (header) row is included in the row count, so only enable sorting when there are three or more rows

edit

Change:

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

To:

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

Arrange conditions/switches in order of greatest to least likelihood (SharkD (talk))

edit

Change:

	sortfn = ts_sort_caseinsensitive;
	if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
		sortfn = ts_sort_currency;
	if (itm.match(/^[\d.,]+\%?$/))
		sortfn = ts_sort_numeric;

To:

	if (itm.match(/^[^\d]+$/))
		var sortfn = ts_sort_caseinsensitive;
	else if (itm.match(/^[\d.,]+\%?$/))
		var sortfn = ts_sort_numeric;
	else if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
		var sortfn = ts_sort_date;
	else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
		var sortfn = ts_sort_date;
	else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
		var sortfn = ts_sort_date;
	else if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
		var sortfn = ts_sort_currency;
	else
		var sortfn = ts_sort_caseinsensitive;

Proposal: "sortreverse" header cell class for default reverse sorting of a column (SharkD (talk))

edit

This proposal is meant to make it possible that a certain column should be sorted in reverse order by default. This works similarly to the "unsortable" class, in that the class is added to the column header, not the table.

Change:

	// 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];
		if ((" "+cell.className+" ").indexOf(" unsortable ") == -1) {
			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>';
		}
	}

To:

	// We have a first row: assume it's the header, and make its contents clickable links
	for (var i = 0, n = firstRow.cells.length; i < n; i++) {
		var cell = firstRow.cells[i];
		var cellClass = " " + cell.className + " ";
		if (cellClass.indexOf(" unsortable ") == -1) {
			if (cellClass.indexOf(" sortreverse ") == -1)
				cell.innerHTML += '&nbsp;&nbsp;<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;"><span class="sortarrow" sortdir="up"><img src="'+ ts_image_path + ts_image_none + '" alt="Sort in ascending order."/></span></a>';
			else
				cell.innerHTML += '&nbsp;&nbsp;<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;"><span class="sortarrow" sortdir="down"><img src="'+ ts_image_path + ts_image_none + '" alt="Sort in descending order."/></span></a>';
		}
	}

Change:

	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');
	}

To:

	if (reverse) {
			span.innerHTML = '<img src="'+ ts_image_path + ts_image_down + '" alt="Sort in ascending order."/>';
			newRows.reverse();
			span.setAttribute('sortdir','up');
	} else {
			span.innerHTML = '<img src="'+ ts_image_path + ts_image_up + '" alt="Sort in descending order."/>';
			span.setAttribute('sortdir','down');
	}

Change:

	// 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;

To:

	// Delete any other arrows there may be showing
	var spans = getElementsByClassName(tr, "span", "sortarrow");
	for (var i = 0, n = spans.length; i < n; i++) {
		var thisSpan = spans[i];
		if (thisSpan != span) {
			var cellClass = " " + thisSpan.parentNode.parentNode.className + " ";
			if (cellClass.indexOf(" sortreverse ") == -1) {
				thisSpan.setAttribute("sortdir","up");
				thisSpan.innerHTML = '<img src="'+ ts_image_path + ts_image_none + '" alt="Sort in ascending order."/>';
			} else {
				thisSpan.setAttribute("sortdir","down");
				thisSpan.innerHTML = '<img src="'+ ts_image_path + ts_image_none + '" alt="Sort in descending order."/>';
			}
		}
	}

Space saving optimizations (SharkD (talk))

edit

Remove all brackets from conditions with only a single line of code

edit

Change:

		if (ts_europeandate == false) {
			return date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
		} else {
			return date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
		}

To:

		if (ts_europeandate == false)
			return date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
		else
			return date.substr(6,4)+date.substr(3,2)+date.substr(0,2);

Use literal definitions for arrays

edit

Change:

	var newRows = new Array();

To:

	var newRows = [];

Change:

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

To:

		newRows[newRows.length] = [row, keyText, oldIndex];

Remove all semicolons

edit

Change:

	var cs = el.childNodes;

To:

	var cs = el.childNodes

Proposal: Put clickable icon in its own cell. (SharkD (talk))

edit

The reason is so that it aligns more nicely and doesn't break onto new lines like inline elements tend to do. This involves wrapping each header cell within its own table. The existing header text is placed in the first cell of the new table; the clickable icon is placed in the second cell. All style/class attributes are copied to the new tables/cells, except for the "sortable" class which should not be copied.

Change the ts_makeSortable() function to this:

function ts_makeSortable(table) {
	var newTableClass = (" " + table.className + " ").replace(" sortable ", " ");
	if (table.rows && table.rows.length > 0) {
		if (table.tHead && table.tHead.rows.length > 0)
			var firstRow = table.tHead.rows[table.tHead.rows.length - 1];
		else
			var 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, n = firstRow.cells.length; i < n; i++) {
		var cell = firstRow.cells[i];
		var cellClass = " " + cell.className + " ";
		var cellType = cell.tagName.toLowerCase();
		var newTable = document.createElement('table');
		var newTbody = document.createElement('tbody');
		var newRow = document.createElement('tr');
		var newCell1 = document.createElement(cellType);
		while (cell.hasChildNodes())
			newCell1.appendChild(cell.removeChild(cell.firstChild));

		cell.style.padding = '0px';
		newTable.className = newTableClass;
		newTable.style.border = '0px';
		newTable.style.margin = '0px';
		newTable.style.width = '100%';
		newCell1.className = cellClass;
		newCell1.style.border = '0px';
		newRow.appendChild(newCell1);

		if (cellClass.indexOf(" unsortable ") == -1) {
			var newCell2 = document.createElement(cellType);

			if (cellClass.indexOf(" sortreverse ") == -1)
				newCell2.innerHTML = '<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;"><span class="sortarrow" sortdir="up"><img src="' + ts_image_path + ts_image_none + '" alt="Sort in ascending order."/></span></a>';
			else
				newCell2.innerHTML = '<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;"><span class="sortarrow" sortdir="down"><img src="' + ts_image_path + ts_image_none + '" alt="Sort in descending order."/></span></a>';

			newCell2.className = cellClass;
			newCell2.style.border = '0px';
			newCell2.style.width = '12px';
			newRow.appendChild(newCell2);
		}
		newTbody.appendChild(newRow);
		newTable.appendChild(newTbody);
		cell.appendChild(newTable);
	}

	if (ts_alternate_row_colors)
		ts_alternate(table);
}

Change:

	var td = lnk.parentNode;

To:

	var td = lnk.parentNode.parentNode.parentNode.parentNode.parentNode;

Fix: include Yen in currency search (SharkD (talk))

edit

Change:

	itm.match(/^[\u00a3$\u20ac]/)

To:

	itm.match(/^[\u00a3$\u20ac\u00a5]/)

Proposal: sort by fraction or ratio (SharkD (talk))

edit

I'm not as enthusiastic about this one. There's no real sense in beginning to evaluate expressions and then not continuing to handle all types of them. Anyway, the change amounts to adding the following:

	else if (itm.match(/^[\d\.,]+\s*[\/\:]\s*[\d\.,]+$/))
		var sortfn = ts_sort_fraction;

And:

function ts_sort_fraction(a, b) {
	a[1] = a[1].replace(/\s+/g, '');
	b[1] = b[1].replace(/\s+/g, '');
	var aa1 = a[1].match(/^[\d\.,]+[\/\:]/)[0].replace(/[\/\:]/, '');
	var aa2 = a[1].match(/[\/\:][\d\.,]+$/)[0].replace(/[\/\:]/, '');
	var bb1 = b[1].match(/^[\d\.,]+[\/\:]/)[0].replace(/[\/\:]/, '');
	var bb2 = b[1].match(/[\/\:][\d\.,]+$/)[0].replace(/[\/\:]/, '');
	if (ts_europeandate == true) {
		aa1 = aa1.replace(/\./g, "");
		aa2 = aa2.replace(/\./g, "");
		bb1 = bb1.replace(/\./g, "");
		bb2 = bb2.replace(/\./g, "");
	} else {
		aa1 = aa1.replace(/,/g, "");
		aa2 = aa2.replace(/,/g, "");
		bb1 = bb1.replace(/,/g, "");
		bb2 = bb2.replace(/,/g, "");
	}
	var aa = parseFloat(aa1) / parseFloat(aa2);
	var bb = parseFloat(bb1) / parseFloat(bb2);
	return (aa != bb ? aa - bb : a[2] - b[2]);
}

Note that the above code interprets the colon in addition to the forward slash as signifying a fraction or ratio. This may present a problem with certain dates or times.

Proposal: Increase the number of detected date formats; split them into different functions. (SharkD (talk))

edit

In its current status, the script uses a lot of code to detect only a few date formats. This change is meant to open the sorting function up to more date formats as well as split them the different formate into separate functions, thereby reducing the number of conditional statements required.

Change:

	sortfn = ts_sort_caseinsensitive;
	if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
		sortfn = ts_sort_currency;
	if (itm.match(/^[\d.,]+\%?$/))
		sortfn = ts_sort_numeric;

To:

	if (itm.match(/^\d{1,2}[\/\.\-]\d{1,2}[\/\.\-](\d{2}|\d{4})$/))		// matches: D(D)-M(M)-YY(YY) or M(M)-D(D)-YY(YY)
		var sortfn = ts_sort_date_1;
	else if (itm.match(/^(\w+[\/\.\- ]\d{1,2}|\d{1,2}[\/\.\- ]\w+),?[\/\.\- ](\d{2}|\d{4})$/))	// matches: D(D)-MONTHABV-YY(YY) or MONTHABV-D(D)-YY(YY) or MONTHABV D(D), YY(YY)
		var sortfn = ts_sort_date_2;
	else if (itm.match(/^[\u00a3$\u20ac\u00a5]/)) 					// pound dollar euro yen
		var sortfn = ts_sort_currency;
	else if (itm.match(/^[\d\.\,]+\%?$/))						// matches: nn.nnn or nn,nnn or nn.nnn% or nn,nnn% or nn.nnn,nnn% or nn,nnn.nnn%
		var sortfn = ts_sort_numeric;
	else
		var sortfn = ts_sort_caseinsensitive;

Change:

function ts_sort_date(a,b) {
	var aa = ts_dateToSortKey(a[1]);
	var bb = ts_dateToSortKey(b[1]);
	return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]);
}

To:

function ts_sort_date_1(a, b) {
	var aa = ts_dateToSortKey_1(a[1]);
	var bb = ts_dateToSortKey_1(b[1]);
	return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]);
}

function ts_sort_date_2(a, b) {
	var aa = ts_dateToSortKey_2(a[1]);
	var bb = ts_dateToSortKey_2(b[1]);
	return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]);
}

Change:

function ts_dateToSortKey(date) {	
	// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
	if (date.length == 11) {
		switch (date.substr(3,3).toLowerCase()) {
			case "jan": var month = "01"; break;
			case "feb": var month = "02"; break;
			case "mar": var month = "03"; break;
			case "apr": var month = "04"; break;
			case "may": var month = "05"; break;
			case "jun": var month = "06"; break;
			case "jul": var month = "07"; break;
			case "aug": var month = "08"; break;
			case "sep": var month = "09"; break;
			case "oct": var month = "10"; break;
			case "nov": var month = "11"; break;
			case "dec": var month = "12"; break;
			// default: var month = "00";
		}
		return date.substr(7,4)+month+date.substr(0,2);
	} else if (date.length == 10) {
		if (ts_europeandate == false) {
			return date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
		} else {
			return date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
		}
	} else if (date.length == 8) {
		yr = date.substr(6,2);
		if (parseInt(yr) < 50) { 
			yr = '20'+yr; 
		} else { 
			yr = '19'+yr; 
		}
		if (ts_europeandate == true) {
			return yr+date.substr(3,2)+date.substr(0,2);
		} else {
			return yr+date.substr(0,2)+date.substr(3,2);
		}
	}
	return "00000000";
}

To:

function ts_dateToSortKey_1(date) {
	// matches: D(D)-M(M)-YY(YY) or M(M)-D(D)-YY(YY)
	// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
	date = date.replace(/[\/\.]/g, '-')
	var dateLen = date.length
	var firstIdx = date.indexOf('-')
	var secndIdx = date.indexOf('-', firstIdx + 1)
	var yearIdx = secndIdx + 1
	var yearLen = dateLen - yearIdx
	var secndLen = dateLen - yearLen - firstIdx - 2
	var year = parseInt(date.substr(yearIdx, yearLen))
	if (yearLen == 2)
		year += year < 50 ? 2000 : 1900
	if (ts_europeandate) {
		var month = date.substr(firstIdx + 1, secndLen)
		var day = date.substr(0, firstIdx)
	} else {
		var month = date.substr(0, firstIdx)
		var day = date.substr(firstIdx + 1, secndLen)
	}
	month = parseInt(month)
	day = parseInt(day)
	if (month < 10) month = "0" + month
	if (day < 10) day = "0" + day
	return "".concat(year, month, day)
}

function ts_dateToSortKey_2(date) {
	// matches: D(D)-MONTHABV-YY(YY) or MONTHABV-D(D)-YY(YY) or MONTHABV D(D), YY(YY)
	// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
	// the following is valid only for the English language, and should ideally be set as a global variable in "common.js"
	var monthStr = "janfebmaraprmayjunjulaugsepoctnovdec"
	var monthLen = 3
	var monthNam = date.match(/[^\d\/\.\- ]+/)[0]
	var monthAbv = monthNam.substr(0, monthLen)
	date = date.replace(/[^\d\/\.\- ]+/, monthAbv)
	date = date.replace(/[\/\. ]/g, '-')
	date = date.replace(/,/g, '')
	var dateLen = date.length
	var firstIdx = date.indexOf('-')
	var secndIdx = date.indexOf('-', firstIdx + 1)
	var yearIdx = secndIdx + 1
	var yearLen = dateLen - yearIdx
	var secndLen = dateLen - yearLen - firstIdx - 2
	var year = parseInt(date.substr(yearIdx, yearLen))
	if (yearLen == 2)
		year += year < 50 ? 2000 : 1900
	if (date.charCodeAt(0) < 58) {
		var month = date.substr(firstIdx + 1, secndLen)
		var day = date.substr(0, firstIdx)
	} else {
		var month = date.substr(0, firstIdx)
		var day = date.substr(firstIdx + 1, secndLen)
	}
	month = monthStr.indexOf(month.toLowerCase()) / monthLen + 1
	day = parseInt(day)
	if (month < 10) month = "0" + month
	if (day < 10) day = "0" + day
	return "".concat(year, month, day)
}

Proposal: change "sortable" from classname to attribute; add column attributes to force sorting function (SharkD (talk))

edit

This proposal is to change the "sortable" table class from a class to an attribute and add a "sortorder" attribute to column headers. The first part would involve the following:

Change:

class="sortable"

To:

sortable="true"

The second part would allow users to force the use of a particular sorting function for a column. The "unsortable" class could also be merged with this item. I.e.:

Change:

	sortfn = ts_sort_caseinsensitive;
	if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
		sortfn = ts_sort_date;
	if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
		sortfn = ts_sort_currency;
	if (itm.match(/^[\d.,]+\%?$/))
		sortfn = ts_sort_numeric;

To something like this (a rough sketch):

	if (cell.getAttribute('sortorder'))
		sortfn = cell.getAttribute('sortorder')
	else {
		sortfn = ts_sort_caseinsensitive;
		if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
			sortfn = ts_sort_date;
		if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
			sortfn = ts_sort_date;
		if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
			sortfn = ts_sort_date;
		if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
			sortfn = ts_sort_currency;
		if (itm.match(/^[\d.,]+\%?$/))
			sortfn = ts_sort_numeric;
	}