JamesG
JamesG

Reputation: 1601

jQuery: Sort table by multiple column values

I've been struggling with this for a while and every time I test something I just end up getting different results.

JSFIDDLE

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

What I am trying to achieve

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

Answers (2)

Eddie
Eddie

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:

  1. Return a negative value if the first parameter should go first.
  2. Return a positive value if the second parameter should go first.
  3. Return 0 if no change in position.

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

Narendra Jadhav
Narendra Jadhav

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

Related Questions