Skip to content
This repository has been archived by the owner on Nov 15, 2021. It is now read-only.

Commit

Permalink
Adding proper JS version of table-sort script
Browse files Browse the repository at this point in the history
  • Loading branch information
LJWatson committed Oct 31, 2016
1 parent 7bfba72 commit 7fe56b9
Showing 1 changed file with 113 additions and 71 deletions.
184 changes: 113 additions & 71 deletions js/table-sort.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,88 @@
var sortOrder;

$('.sortable').each(function() {
var $table = $(this);
// Initialization of all sortable tables in the page

$table.find('th').each(function() {
this.setAttribute('aria-sort', 'none');
/**
* Initialization
* Configure all sortable tables on the page
*/
var sortableTables = document.getElementsByClassName('sortable');
[].forEach.call(sortableTables, function(table) {

// Add a default ARIA unsorted state, and attach the sort
// handler to any sortable columns in this table.
var cellIndex = 0; // track numeric cell index to simplify sort logic
var headerCells = table.getElementsByClassName('sortableColumn');
[].forEach.call(headerCells, function(th) {
th.setAttribute('aria-sort', 'none');
th.dataset.index = cellIndex++;
th.addEventListener('click', sortCol);
});

$table.find('span.th-body').each(function() {
this.setAttribute('role', 'button');
this.setAttribute('tabindex', '0');
// Give the span 'buttons' within the table headers focus and keyboard handling
var buttonSpans = table.getElementsByClassName('th-body');
[].forEach.call(buttonSpans, function(span) {
span.setAttribute('role', 'button');
span.setAttribute('tabindex', '0');
span.addEventListener('keydown', function(e) {
if (e.which === 13 || e.which === 32) {
this.click();
}
});
});
});

$table.on('click', '.sortableColumn', sortCol);
$table.on('keydown', '.th-body', function (e) {
if (e.which === 13 || e.which === 32) {
this.click();

/**
* getParentTable - helper to find the parent table element from any child node
*
* @param {HTMLElement} node any child node in a table
* @return {HTMLElement} parent table or undef
*/
function getParentTable(node) {
while (node) {
node = node.parentNode;
if (node.tagName.toLowerCase() === 'table') {
return node;
}
});
});
}
return undefined;
}

function sortCol() {
var $table = $(this).closest('.sortable');
var $tbody = $table.find('tbody');
var $rows = $table.find('tbody tr');
var $sortableColumns = $table.find('.sortableColumn');

// updates the sort icon and returns the new sort state
sortOrder = updateIcon(this);
var items = [];
var sortType = this.getAttribute('data-sort');
var thisIndex = $.inArray(this, $sortableColumns);
/**
* sortCol - Sort event handler. Attached to all sortable column headers
*
* @param {Event} e The event triggering the sort action
*/
function sortCol(e) {
// sortCol event gets triggered from the th or the nested span,
// identify the TH col, and assign some element lookups
var thisCol = e.target.tagName === 'TH' ? e.target : e.target.parentNode;
var table = getParentTable(thisCol);
var tbody = table.getElementsByTagName('tbody').item(0);
var rows = tbody.getElementsByTagName('tr');
var cols = table.getElementsByClassName('sortableColumn');

var sortType = thisCol.getAttribute('data-sort');
var thisIndex = thisCol.getAttribute('data-index');

// update the sort icon and return the new sort state
sortOrder = updateIcon(thisCol);

// loop through each row and build our `items` array
// which will become an array of objects:
// {
// tr: (the HTMLElement reference to the given row),
// val: (the String value of the corresponding td)
// }
$rows.each(function () {
var item = {};
item.tr = this;
$tds = $(this).find('td');
var td = $tds[thisIndex];
item.val = $(td).text();
items.push(item);
var items = [];
[].forEach.call(rows, function (row) {
var content = row.getElementsByTagName('td').item(thisIndex);
items.push({ tr: row, val: content.innerText });
});

// sort the array of values
// sort the array of values, using an appropriate sorter
if (!sortType || sortType === 'standard') {
items.sort(standardSort);
} else if (sortType === 'date') {
Expand All @@ -58,69 +93,76 @@ function sortCol() {
items.sort(moneySort);
}

// clear the tbody's contents
$tbody.html('');

// append each row in the new, sorted order
// currently not working in IE
$.each(items, function (i, item) {
$tbody.append(item.tr);
// Create a new table body, appending each row in the new, sorted order
var newTbody = document.createElement('tbody');
[].forEach.call(items, function (item) {
newTbody.appendChild(item.tr);
});

// update the live region:
var updatedMessage = ' (Sorted by ' + $(this).text() + ': ' + sortOrder + ')';
// Swap out the existing table body with our reconstructed sorted body
table.replaceChild(newTbody, tbody);

$('#liveForScreenReaders').text(updatedMessage);
setTimeout(function() {
$('#liveForScreenReaders').html('');
}, 2000);
// Update the live region for a couple of seconds
var updatedMessage = ' (Sorted by ' + thisCol.innerText + ': ' + sortOrder + ')';
var liveRegion = document.getElementById('live');
liveRegion.innerText = updatedMessage;
setTimeout(function() { liveRegion.innerText = ''; }, 2000);
}


/**
* Updates the arrow icon based on new sort status
* updateIcon - Updates the arrow icon based on new sort status
* @param {HTMLElement} th The table heading element reference
* @return {String} state The new sort state ("ascending" or "descending")
*/
function updateIcon(th) {
var state = 'ascending';
var $icon = $(th).find('i');
if ($icon.hasClass('arrow')) { // No sort -> Ascending
$icon
.removeClass('arrow')
.addClass('arrow-up');
} else if ($icon.hasClass('arrow-down')) { // Descending -> Ascending
$icon
.removeClass('arrow-down')
.addClass('arrow-up');
var state = 'ascending';
var icon = th.getElementsByTagName('i').item(0);
var ourIndex = th.getAttribute('data-index');

// classList is supported in pretty much everything after IE8,
// use that rather than a regex to modify the arrow classes
if (icon.classList.contains('arrow')) { // No sort -> Ascending
icon.classList.remove('arrow');
icon.classList.add('arrow-up');
} else if (icon.classList.contains('arrow-down')) { // Descending -> Ascending
icon.classList.remove('arrow-down');
icon.classList.add('arrow-up');
state = 'ascending';
} else { // Ascending -> Descending
$icon
.removeClass('arrow-up')
.addClass('arrow-down');
icon.classList.remove('arrow-up');
icon.classList.add('arrow-down');
state = 'descending';
}

$(th).attr('aria-sort', state);
th.setAttribute('aria-sort', state);

$(th).siblings().each(function () {
// update all other rows with the neutral sort icon
$(this)
.attr('aria-sort', 'none')
.find('i')
.removeClass('arrow-up')
.removeClass('arrow-down')
.addClass('arrow');
// update all other rows with the neutral sort icon
var allTh = th.parentNode.getElementsByTagName('th');
[].forEach.call(allTh, function (thisTh, thisIndex) {
// skip our sorted column
if (thisIndex == ourIndex) {
return;
}

// reset the state for an unsorted column
thisTh.setAttribute('aria-sort', 'none');
var thisIcon = thisTh.getElementsByTagName('i').item(0);
thisIcon.classList.remove('arrow-up');
thisIcon.classList.remove('arrow-down');
thisIcon.classList.add('arrow');
});
return state;
}


/**
* Executes a standard sort (direct comparisons)
*/
function standardSort(a, b) {
return (sortOrder === 'ascending')
? a.val - b.val
: b.val - a.val;
? a.val - b.val
: b.val - a.val;
}

/**
Expand All @@ -129,8 +171,8 @@ function standardSort(a, b) {
*/
function dateSort(a, b) {
return (sortOrder === 'ascending')
? formatDate(a.val) - formatDate(b.val)
: formatDate(b.val) - formatDate(a.val);
? formatDate(a.val) - formatDate(b.val)
: formatDate(b.val) - formatDate(a.val);
}

function textSort(a, b) {
Expand Down

0 comments on commit 7fe56b9

Please sign in to comment.