Table sort — detach elements or not?

Benchmark created by Mathias Bynens on


Preparation HTML

<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<table class="sortable">
  <thead>
    <tr>
      <th id="th-1">Text</th>
      <th data-no-sort="nope">Not sortable</th>
      <th data-type="numeric" id="th-2">Numeric</th>
      <th data-type="date" id="th-3">Date</th>
      <th data-type="location" id="th-4">Location (zip)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Mathias Bynens</td>
      <td>LOLWAT</td>
      <td>€42</td>
      <td data-time="20030818">18/08/2003</td>
      <td data-zip="9200">Dendermonde</td>
    </tr>
    <tr>
      <td>Ward Meremans</td>
      <td>LOLWAT</td>
      <td>€191200.00</td>
      <td data-time="19790502">02/05/1979</td>
      <td data-zip="2000">Antwerpen</td>
    </tr>
    <tr>
      <td>Wart Claes</td>
      <td>LOLWAT</td>
      <td>€32010.12</td>
      <td data-time="19980809">09/08/1998</td>
      <td data-zip="9230">Wetteren</td>
    </tr>
    <tr>
      <td>Inge Martens</td>
      <td>LOLWAT</td>
      <td>€122000.00</td>
      <td data-time="19611112">12/11/1961</td>
      <td data-zip="9000">Gent</td>
    </tr>
    <tr>
      <td>Domien Verbeke</td>
      <td>LOLWAT</td>
      <td>€9000</td>
      <td data-time="20020101">01/01/2002</td>
      <td data-zip="9300">Aalst</td>
    </tr>
    <tr>
      <td>Gregg De Winter</td>
      <td>LOLWAT</td>
      <td>€3300</td>
      <td data-time="19950522">22/05/1995</td>
      <td data-zip="1000">Brussel</td>
    </tr>
  </tbody>
</table>
<script>
  var sort = [].sort,
      inverse = false;
  
  var $table = $('table.sortable').first(),
      $tbody = $('tbody', $table),
      index = 0,
      type = 'standard', // default is a reserved keyword
      switchObj;
  
  // Repeat the table’s contents 49 times – just to have some more rows
  $tbody.html(Array(50).join($tbody.html()));
  
  switchObj = {
    'date': function(a, b, inverse) {
      // We could use the child <time> element and its `datetime` attribute here
      // but this is way more efficient:
      a = +a.getAttribute('data-time');
      b = +b.getAttribute('data-time');
      return applyInverse(a > b, inverse);
    },
    'location': function(a, b, inverse) {
      a = +a.getAttribute('data-zip');
      b = +b.getAttribute('data-zip');
      return applyInverse(a > b, inverse);
    },
    'numeric': function(a, b, inverse) {
      a = $(a).text();
      b = $(b).text();
      return applyInverse(numeric(a) > numeric(b), inverse);
    },
    'standard': function(a, b, inverse) {
      a = $(a).text();
      b = $(b).text();
      // text / numeric-only
      return applyInverse(isNaN(a) || isNaN(b) ? a > b : +a > +b, inverse);
    }
  };
  
  
  function applyInverse(bool, inverse) {
    return bool ? inverse ? -1 : 1 : inverse ? 1 : -1;
    // Possible alternative:
    // return bool && !inverse || !bool && inverse ? 1 : -1;
  }
  
  // jQuery#sortElements by @padolsey: http://goo.gl/ruxh6
  $.fn.sortElements = function(comparator, getSortable) {
  
    getSortable || (getSortable = function() {
      return this;
    });
  
    var placements = this.map(function() {
  
      var sortElement = getSortable.call(this),
          parentNode = sortElement.parentNode,
  
          // Since the element itself will change position, we have
          // to have some way of storing its original position in
          // the DOM. The easiest way is to have a 'flag' node:
          nextSibling = parentNode.insertBefore(
        document.createTextNode(''), sortElement.nextSibling);
  
      return function() {
  
        if (parentNode === this) {
          throw new Error('You cannot sort elements if any one is a descendant of another.');
        }
  
        // Insert before flag:
        parentNode.insertBefore(this, nextSibling);
        // Remove flag:
        parentNode.removeChild(nextSibling);
  
      };
  
    });
  
    return sort.call(this, comparator).each(function(i) {
      placements[i].call(getSortable.call(this));
    });
  
  };
  
  function sortTable() {
    $('td', $tbody).filter(function() {
      return $(this).index() == index;
    }).sortElements(
      function(a, b) {
        return (switchObj.hasOwnProperty(type) && switchObj[type] || switchObj.standard)(a, b, inverse);
      }, function() {
        return this.parentNode;
      }
    );
  }
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
Without .detach()
sortTable();
inverse = !inverse;
ready
With .detach()
$tbody.detach();
sortTable();
$tbody.appendTo($table);
inverse = !inverse;
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by Mathias Bynens on