I'm trying to add a sortable table to my site but I'm having issues sorting columns with varying digit entries. It works fine when all numbers are the same number of digits in length.
However, when I change the number of digits, the sort function seems to break and sorts them out of order.
The code below is a simple example of this. The table I am working with is much larger and more interesting than people, their jobs and age.
Here is my HTML:
<!DOCTYPE html>
<head>
<title>Sorting Tables w/ JavaScript</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="utf-8" />
</head>
<body>
<table class="table-sortable">
<thead>
<tr>
<th>Rank</th>
<th>Name</th>
<th>Age</th>
<th>Occupation</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Dom</td>
<td>5</td>
<td>Web Developer</td>
</tr>
<tr>
<td>2</td>
<td>Rebecca</td>
<td>29</td>
<td>Teacher</td>
</tr>
<tr>
<td>3</td>
<td>John</td>
<td>100</td>
<td>Civil Engineer</td>
</tr>
<tr>
<td>4</td>
<td>Andre</td>
<td>20</td>
<td>Dentist</td>
</tr>
</tbody>
</table>
<script src="./src/tablesort.js"></script>
</body>
JS:
function sortTableByColumn(table, column, asc = true) {
const dirModifier = asc ? 1 : -1;
const tBody = table.tBodies[0];
const rows = Array.from(tBody.querySelectorAll("tr"));
// Sort each row
const sortedRows = rows.sort((a, b) => {
const aColText = a.querySelector(`td:nth-child(${ column + 1 })`).textContent.trim();
const bColText = b.querySelector(`td:nth-child(${ column + 1 })`).textContent.trim();
return aColText > bColText ? (1 * dirModifier) : (-1 * dirModifier);
});
// Remove all existing TRs from the table
while (tBody.firstChild) {
tBody.removeChild(tBody.firstChild);
}
// Re-add the newly sorted rows
tBody.append(...sortedRows);
// Remember how the column is currently sorted
table.querySelectorAll("th").forEach(th => th.classList.remove("th-sort-asc", "th-sort-desc"));
table.querySelector(`th:nth-child(${ column + 1})`).classList.toggle("th-sort-asc", asc);
table.querySelector(`th:nth-child(${ column + 1})`).classList.toggle("th-sort-desc", !asc);
}
document.querySelectorAll(".table-sortable th").forEach(headerCell => {
headerCell.addEventListener("click", () => {
const tableElement = headerCell.parentElement.parentElement.parentElement;
const headerIndex = Array.prototype.indexOf.call(headerCell.parentElement.children, headerCell);
const currentIsAscending = headerCell.classList.contains("th-sort-asc");
sortTableByColumn(tableElement, headerIndex, !currentIsAscending);
});
});
Any help on this would be much appreciated!! Thank you all so much for your help on this!
To sort by numeric value:
return parseFloat(aColText) > parseFloat(bColText) ? (1 * dirModifier) : (-1 * dirModifier);
Otherwise, sort will be by string value ("2" is bigger than "10").
In general, to sort by numeric value:
array.sort((a,b)=>parseFloat(a)<parseFloat(b)?-1:1)
Related
This question already has answers here:
Sorting an Array of Objects by two Properties
(5 answers)
Closed last year.
How can i sort my table by two columns ( Name, Available) on page load? I can use vanilla js
<table class="table_sort">
<thead>
<tr>
<th class="sorted-asc">Name</th>
<th>Genre</th>
<th>Publish year</th>
<th>Quanity</th>
<th class="available">Available</th>
</tr>
</thead>
<tbody id="tbody">
<tr>
<td>aname1</td>
<td>genre1</td>
<td>year1</td>
<td>quantity1</td>
<td>2</td>
</tr>
<tr>
<td>name1</td>
<td>genre1</td>
<td>year1</td>
<td>quantity1</td>
<td>1</td>
</tr>
<tr>
<td>aname1</td>
<td>genre1</td>
<td>year1</td>
<td>quantity1</td>
<td>10</td>
</tr>
<tr>
<td>aname1</td>
<td>genre1</td>
<td>year1</td>
<td>quantity1</td>
<td>6</td>
</tr>
</tbody>
</table>
I try to sort my table by two columns: Name, Available but it does not work.
document.addEventListener('DOMContentLoaded', () => {
const table = document.querySelector('.table_sort');
const indexToSorting = [...table.tHead.rows[0].cells].findIndex(cell => cell.classList.contains('sorted-asc'));
const availableIndexes = [...table.tHead.rows[0].cells].findIndex(cell => cell.classList.contains('available'));
const sortedRows = [...table.tBodies[0].rows].sort((rowA, rowB) => {
let cellC;
let cellD;
const sortedRowsByAvailable = [...table.tBodies[0].rows].sort((rowC, rowD) => {
cellC = rowC.cells[availableIndexes].innerText;
cellD = rowD.cells[availableIndexes].innerText;
const availableComparison = cellC.localeCompare(cellD);
return availableComparison;
});
const cellA = rowA.cells[indexToSorting].innerText;
const cellB = rowB.cells[indexToSorting].innerText;
const nameComparison = cellA.localeCompare(cellB);
return nameComparison !== 0 ? nameComparison : sortedRowsByAvailable
});
table.tBodies[0].append(...sortedRows);
});
My table is sorted by name, but i need to sort it by columns: name, available. Where is my mistake? I don't understand, please, help me
TBH, I didn't understand why you did cellA - cellB, but you need to add a comparison for Available cells in case cellA and cellB are equal.
const rowAAvailable = rowA.cells[indexToSorting].innerText;
const rowBAbailable = rowB.cells[indexToSorting].innerText;
const nameComparison = cellA.localeCompare(cellB);
const availableComparison = rowAAvailable.localeCompare(rowBAbailable); // sorry for terrible naming, but you get the idea
return nameComparison !== 0 ? nameComparison : availableComparison
This will sort your table by name as the first priority and available the second one.
I want to display a Array of names in a table. ist like a timetable and i have a sort-button because if i want to add a new Student, the Student should be classified at the right place.
My Array contains this values
Benj
Zebra
Yves
Anna
but if i press the sort button the output is like this
Zebra
Yves
Anna
Benj
That dosent make sense ist not ascending and not descending
Here is the code
function sort(){
students.sort();
for(var i = 0;i<students.length+1;i++){
if(i!=0){
alert(students[i-1].innerHTML);
<!table.rows[i].cells[0].innerHTML=students[i1].innerHTML;>
}
}
}
If you're trying to sort the rows of an existing table - here's a better way
document.getElementById('sort1').addEventListener('click', () => {
let tableBody = document.querySelector('#table>tbody');
let rows = [...tableBody.querySelectorAll('#table>tbody>tr')];
rows
.sort((a, b) => a.cells[0].textContent.localeCompare(b.cells[0].textContent))
.forEach(row => tableBody.append(row));
});
document.getElementById('sort2').addEventListener('click', () => {
let tableBody = document.querySelector('#table>tbody');
let rows = [...tableBody.querySelectorAll('#table>tbody>tr')];
rows
.sort((a, b) => a.cells[2].textContent.localeCompare(b.cells[2].textContent))
.forEach(row => tableBody.append(row));
})
<table id="table">
<tbody>
<tr>
<td>Delta</td>
<td>Alpahbetically 4th</td>
<td>Rank 2nd</td>
</tr>
<tr>
<td>Bravo</td>
<td>Alpahbetically 2nd</td>
<td>Rank 1st</td>
</tr>
<tr>
<td>Charlie</td>
<td>Alpahbetically 3rd</td>
<td>Rank 4th</td>
</tr>
<tr>
<td>Alpha</td>
<td>Alpahbetically 1st</td>
<td>Rank 3rd</td>
</tr>
</tbody>
</table>
<button id="sort1">Sort by name</button>
<button id="sort2">Sort by rank</button>
At some point I decided I need a handy jQuery selector to select td:nth-child of rows from x to y. Rather than writing a [:] expression selector, I went for a plugin method - assuming that it should work just as fine as .find() or .prevAll() does.
$.fn.nthTdInRows = function (n, sRow, eRow) {
return this
.filter(function (index, el) {
return el.tagName.toLowerCase() === 'table';
})
.find('tr')
.filter(function (index) {
return index + 1 >= sRow && index + 1 <= eRow;
})
.find('td:nth-child(' + n + ')');
}
Although this code work, it works only for the first table in collection. That's most probably due to lack of .each() within the plugin, but I somehow couldn't wrap my mind around how to use it when a return value is desired. Can this be done along the path I have chosen?
Okay, so here is how you can get it to give you an array of jQuery collections back for each table. Let me know if this is what you were looking for:
Update -- returned a single jQuery collection of DOM elements, rather than a collection of collections so that you can chain jQuery functions off of the .nthTdInRows() call.
$.fn.nthTdInRows = function(n, sRow, eRow) {
var arr = this.map(function() { // <-- this calls the following code for each table
// passed in, and maps each return value into an array element
var tables = $(this).filter(function(index, el) {
return $(this).is("table");
});
var tableRows = tables.find("tr");
var indexedRows = tableRows.filter(function(index) {
return index + 1 >= sRow && index + 1 <= eRow;
});
var tds = indexedRows.find('td:nth-child(' + n + ')');
return tds;
});
//debugger;
var collection = [];
arr.each(function() {
// flatten arr into simple array of DOM elements, rather than nested jQuery collections
collection = collection.concat($.map(this, function(elem, index) {
return elem;
}));
});
// wrap array of DOM elements with jQuery object so we can chain off of nthTdInRows()
return $(collection);
}
$(function() {
var tables = $("#table1, #table2, #table3");
tables.nthTdInRows(2, 1, 4).addClass("highlight");
});
.highlight {
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://code.jquery.com/jquery-2.2.4.js"></script>
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<table id="table1">
<tr>
<th>header1</th>
<th>header2</th>
</tr>
<tr>
<td>data1-1</td>
<td>data2-1</td>
</tr>
<tr>
<td>data1-2</td>
<td>data2-2</td>
</tr>
<tr>
<td>data1-3</td>
<td>data2-3</td>
</tr>
<tr>
<td>data1-4</td>
<td>data2-4</td>
</tr>
</table>
<table id="table2">
<tr>
<th>header3</th>
<th>header4</th>
</tr>
<tr>
<td>data3-1</td>
<td>data4-1</td>
</tr>
<tr>
<td>data3-2</td>
<td>data4-2</td>
</tr>
<tr>
<td>data3-3</td>
<td>data4-3</td>
</tr>
<tr>
<td>data3-4</td>
<td>data4-4</td>
</tr>
</table>
<table id="table3">
<tr>
<th>header5</th>
<th>header6</th>
</tr>
<tr>
<td>data5-1</td>
<td>data6-1</td>
</tr>
<tr>
<td>data5-2</td>
<td>data6-2</td>
</tr>
<tr>
<td>data5-3</td>
<td>data6-3</td>
</tr>
<tr>
<td>data5-4</td>
<td>data6-4</td>
</tr>
</table>
</body>
</html>
If I have a table and each in each row I have an amount, how would I display the sum of groups of rows?
Example:
A 100
A 100
A 100
B 120
I am trying to create a 3rd column that has the 300 (100+100+100) in the middle of these 100/100/100 cells so as to be clear that the number in each cell in the third column is the sum of the previous.
I tried using rowspan but it does not work. How can I achieve this? Example:
<html>
<body>
<table>
<tr>
<td>A</td>
<td>B</td>
<td>100</td>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td>100</td>
<td rowspan="2">$300</td>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td>100</td>
</tr>
<tr>
<td>C</td>
<td>B</td>
<td>100</td>
</tr>
</table>
</body>
</html>
I'm not sure, but this may be an alignment problem. Set border="1" on your table and you'll see why.
Try:
<td rowspan="2" valign="bottom">$300</td>
Not sure exactly what your issue is, but here is a JS OOP solution for getting the summ of cells (assuming that your table is sorted):
(function () {
// Helper
function $(selector) {
return Array.prototype.slice.call(document.querySelectorAll(selector));
}
function Group(name, row) {
this.name = name;
this.summ = 0;
this.length = 0;
this.cell = document.createElement('td');
row.appendChild(this.cell);
}
Group.prototype._update = function () {
this.cell.textContent = this.summ;
this.cell.setAttribute('rowspan', this.length);
};
Group.prototype.add = function (value) {
this.summ += value;
this.length++;
this._update();
};
var group = null;
$('#summup tr').forEach(function (row) {
var name = row.children[0].textContent;
var value = parseFloat(row.children[1].textContent, 10);
if (!group || group.name !== name) {
group = new Group(name, row);
}
group.add(value);
});
})();
Demo: http://jsbin.com/iSATiMus/3/edit?html,js,output
I am trying to sort a table - so when a user clicks on the table heading, it will sort in ascending/descending order. I've got it to the point where I can sort the table based on the column value. However, I have groupings of table rows (two rows per table body), and I want to sort the columns based on the values in the columns of the first row of each table body, but when it reorders the table, it want it to reorder the table bodies, not the table rows.
<table width="100%" id="my-tasks" class="gen-table">
<thead>
<tr>
<th class="sortable"><p>Name</p></th>
<th class="sortable"><p>Project</p></th>
<th class="sortable"><p>Priority</p></th>
<th class="sortable"><p>%</p></th>
</tr>
</thead>
<tbody>
<tr class="sortable-row" id="44">
<td><p>dfgdf</p></td><td><p>Test</p></td>
<td><p>1</p></td><td><p>0</p></td>
</tr>
<tr>
<td></td>
<td colspan="3"><p>asdfds</p></td>
</tr>
</tbody>
<tbody>
<tr class="sortable-row" id="43">
<td><p>a</p></td>
<td><p>Test</p></td>
<td><p>1</p></td>
<td><p>11</p></td>
</tr>
<tr>
<td></td>
<td colspan="3"><p>asdf</p></td>
</tr>
</tbody>
<tbody>
<tr class="sortable-row" id="40">
<td><p>Filter Tasks</p></td>
<td><p>Propel</p></td>
<td><p>10</p></td>
<td><p>10</p></td>
</tr>
<tr>
<td></td>
<td colspan="3"><p>Add a button to filter tasks.</p></td>
</tr>
</tbody>
</table>
With the following javascript:
jQuery(document).ready(function () {
jQuery('thead th').each(function(column) {
jQuery(this).addClass('sortable').click(function() {
var findSortKey = function($cell) {
return $cell.find('.sort-key').text().toUpperCase() + ' ' + $cell.text().toUpperCase();
};
var sortDirection = jQuery(this).is('.sorted-asc') ? -1 : 1;
var $rows = jQuery(this).parent().parent().parent().find('.sortable-row').get();
jQuery.each($rows, function(index, row) {
row.sortKey = findSortKey(jQuery(row).children('td').eq(column));
});
$rows.sort(function(a, b) {
if (a.sortKey < b.sortKey) return -sortDirection;
if (a.sortKey > b.sortKey) return sortDirection;
return 0;
});
jQuery.each($rows, function(index, row) {
jQuery('#propel-my-tasks').append(row);
row.sortKey = null;
});
jQuery('th').removeClass('sorted-asc sorted-desc');
var $sortHead = jQuery('th').filter(':nth-child(' + (column + 1) + ')');
sortDirection == 1 ? $sortHead.addClass('sorted-asc') : $sortHead.addClass('sorted-desc');
jQuery('td').removeClass('sorted').filter(':nth-child(' + (column + 1) + ')').addClass('sorted');
});
});
});
You need to sort the tbody elements, not the row elements. You said that yourself in your description of the problem, but your code actually sorts rows, not tbodies.
A secondary problem is that your sort treats everything as a string, which breaks when sorting 1-digit numeric strings ("2") against two-digit strings ("10").
To fix, replace this:
var $rows = jQuery(this).parent().parent().parent()
.find('.sortable-row').get();
jQuery.each($rows, function(index, row) {
row.sortKey = findSortKey(jQuery(row).children('td').eq(column));
});
with this:
var $tbodies = jQuery(this).parent().parent().parent()
.find('.sortable-row').parent().get();
jQuery.each($tbodies, function(index, tbody) {
var x = findSortKey(jQuery(tbody).find('tr > td').eq(column));
var z = ~~(x); // if integer, z == x
tbody.sortKey = (z == x) ? z : x;
});
And then replace $rows with $tbodies throughout your script, and row with tbody.
Example:
http://jsbin.com/oxuva5
I highly recommend the jQuery plugin http://tablesorter.com/ instead of rolling your own.
It's fully featured and well supported.