Reputation: 1601
I've been struggling with this for a while and every time I test something I just end up getting different results.
I have a basic table for test purposes with 3 entries that have 3 different headings to be sorted by: Name, Age, Date Joined.
Here is the table:
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" id="firstName">name</th>
<th scope="col" id="age">age</th>
<th scope="col" id="date">date joined</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td class="firstName">Mark</td>
<td>12</td>
<td>12/02/2006</td>
</tr>
<tr>
<th scope="row">2</th>
<td class="firstName">Jacob</td>
<td>30</td>
<td>03/04/2018</td>
</tr>
<tr>
<th scope="row">3</th>
<td class="firstName">Larry</td>
<td>22</td>
<td>07/01/2009</td>
</tr>
</tbody>
Here is my JS:
function sortTable(column) {
var $tbody = $('.table tbody');
$tbody.find('td.' + column).sort(function(a,b){
var tda = $(a).find('td:eq(1)').text();
var tdb = $(b).find('td:eq(1)').text();
// if a < b return 1
return tda < tdb ? 1
// else if a > b return -1
: tda > tdb ? -1
// else they are equal - return 0
: 0;
}).appendTo($tbody);
}
$('#firstName').click(function() {
sortTable("firstName");
});
$('#age').click(function() {
sortTable("age");
});
$('#date').click(function() {
sortTable("date");
});
I want to be able to click each of the headers to sort the table by its respective content. So for example, when I click name it will sort by name. When I click age it will sort by age and when I click date joined it will sort by date.
I also really want to learn so I will give 100 bonus points for an answer that has good comments that can explain for me what is going on.
Thank you.
function sortTable(column) {
var $tbody = $('.table tbody');
$tbody.find('td.' + column).sort(function(a, b) {
var tda = $(a).find('td:eq(1)').text();
var tdb = $(b).find('td:eq(1)').text();
// if a < b return 1
return tda < tdb ? 1
// else if a > b return -1
:
tda > tdb ? -1
// else they are equal - return 0
:
0;
}).appendTo($tbody);
}
$('#firstName').click(function() {
sortTable("firstName");
});
$('#age').click(function() {
sortTable("age");
});
$('#date').click(function() {
sortTable("date");
});
@import 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css';
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" id="firstName">name</th>
<th scope="col" id="age">age</th>
<th scope="col" id="date">date joined</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td class="firstName">Mark</td>
<td>12</td>
<td>12/02/2006</td>
</tr>
<tr>
<th scope="row">2</th>
<td class="firstName">Jacob</td>
<td>30</td>
<td>03/04/2018</td>
</tr>
<tr>
<th scope="row">3</th>
<td class="firstName">Larry</td>
<td>22</td>
<td>07/01/2009</td>
</tr>
</tbody>
</table>
Upvotes: 0
Views: 13139
Reputation: 26844
Currently, you are sorting <td>
, which actually a cell and not rows(<tr>
).
.sort
's callback function has 2 parameters. You need to compare the 2 parameters and:
You have to compare the data based on its type(number, text, date). Used also the index of the column instead of text because I think it is easier.
function sortTable(column, type) {
//Get and set order
//Use -data to store wheater it will be sorted ascending or descending
var order = $('.table thead tr>th:eq(' + column + ')').data('order');
order = order === 'ASC' ? 'DESC' : 'ASC';
$('.table thead tr>th:eq(' + column + ')').data('order', order);
//Sort the table
$('.table tbody tr').sort(function(a, b) {
// ^ ^
// | |
// The 2 parameters needed to be compared.
// Since you are sorting rows, a and b are <tr>
//Find the <td> using the column number and get the text value.
//Now, the a and b are the text of the <td>
a = $(a).find('td:eq(' + column + ')').text();
b = $(b).find('td:eq(' + column + ')').text();
switch (type) {
case 'text':
//Proper way to compare text in js is using localeCompare
//If order is ascending you can - a.localeCompare(b)
//If order is descending you can - b.localeCompare(a);
return order === 'ASC' ? a.localeCompare(b) : b.localeCompare(a);
break;
case 'number':
//You can use deduct to compare if number.
//If order is ascending you can -> a - b.
//Which means if a is bigger. It will return a positive number. b will be positioned first
//If b is bigger, it will return a negative number. a will be positioned first
return order === 'ASC' ? a - b : b - a;
break;
case 'date':
var dateFormat = function(dt) {
[m, d, y] = dt.split('/');
return [y, m - 1, d];
}
//convert the date string to an object using `new Date`
a = new Date(...dateFormat(a));
b = new Date(...dateFormat(b));
//You can use getTime() to convert the date object into numbers.
//getTime() method returns the number of milliseconds between midnight of January 1, 1970
//So since a and b are numbers now, you can use the same process if the type is number. Just deduct the values.
return order === 'ASC' ? a.getTime() - b.getTime() : b.getTime() - a.getTime();
break;
}
}).appendTo('.table tbody');
}
$('#firstName').click(function() {
sortTable(1, 'text');
});
$('#age').click(function() {
sortTable(2, 'number');
});
$('#date').click(function() {
sortTable(3, 'date');
});
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" id="firstName">name</th>
<th scope="col" id="age">age</th>
<th scope="col" id="date">date joined</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">1</td>
<td class="firstName">Mark</td>
<td>12</td>
<td>12/02/2006</td>
</tr>
<tr>
<td scope="row">2</td>
<td class="firstName">Jacob</td>
<td>30</td>
<td>03/04/2018</td>
</tr>
<tr>
<td scope="row">3</td>
<td class="firstName">Larry</td>
<td>22</td>
<td>07/01/2009</td>
</tr>
</tbody>
Upvotes: 4
Reputation: 10262
You can do sorting on your tr
instead of td
.
DEMO
$('#firstName').click(function() {
sortTable("firstName", this);
});
$('#age').click(function() {
sortTable("age", this);
});
$('#date').click(function() {
sortTable("date", this);
});
function sortTable(column, me) {
var table = $(me).parents('table').eq(0),
rows = table.find('tr:gt(0)').toArray().sort(doComparer($(this).index()))
me.asc = !me.asc
if (!me.asc) {
rows = rows.reverse()
}
for (var i = 0; i < rows.length; i++) {
table.append(rows[i])
}
}
function doComparer(index) {
return function(a, b) {
a = getCellValue(a, index);
b = getCellValue(b, index);
return $.isNumeric(a) && $.isNumeric(b) ? a - b : a.toString().localeCompare(b)
}
}
function getCellValue(row, index) {
return $(row).children('td').eq(index).text()
}
th {
cursor: pointer;
}
<link href="//stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col" id="firstName">name</th>
<th scope="col" id="age">age</th>
<th scope="col" id="date">date joined</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td class="firstName">Mark</td>
<td>12</td>
<td>12/02/2006</td>
</tr>
<tr>
<th scope="row">2</th>
<td class="firstName">Jacob</td>
<td>30</td>
<td>03/04/2018</td>
</tr>
<tr>
<th scope="row">3</th>
<td class="firstName">Larry</td>
<td>22</td>
<td>07/01/2009</td>
</tr>
</tbody>
Upvotes: 2