0

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>

2 Answers 2

4

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>

Sign up to request clarification or add additional context in comments.

4 Comments

Can you explain what the code does? maybe put some more comments in. For example, I have no idea why you have "column + 1" in there?
@JamesG I added some comments. Regarding column + 1 I updated the code so that it would be less confusing.
Please let me know if you have questions.
you can remove all switch breaks as you are using return in all cases
2

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.