Skip to content
Rob Garrison edited this page Feb 22, 2019 · 15 revisions

Wiki: Home | FAQ | Customize | Snippets | Search | Language | Changes | Older-changes-2.25.0 | Older-changes-2.13.0 | Change summary

List of FAQ

* NOTE * If your problem isn't listed in these FAQ, be sure to enable the debug option to see if that gives you a hint as to why tablesorter is misbehaving.

Why does the filter widget not work when my content contains a space-hyphen-space?

The filter widget includes a range filter that looks for a " - " pattern (hyphen, dash or minus symbol surrounded by spaces). For example, if you want to find a range of numbers between 1 and 10, enter 1 - 10; but if your data contains a " - " pattern, the range filter will miss that content because it modified the user search to find that range of numbers.

To fix this, exclude the range filter for that column (demo):

$('table').tablesorter({
  theme: 'blue',
  widgets: ['zebra', 'filter'],
  widgetOptions: {
    filter_excludeFilter: {
      // zero-based column index, or use a jQuery selector
      0: 'range'
    }
  }
});

Now, if your data contains ranges (e.g. 5 - 10) and you want to find if a value is within that range, or search for a range 4 - 7 that includes a value from the range, then try the "insideRange" custom filter.

Why is my new content disappearing?

The most common reason why new content disappears is because the internal cache wasn't updated.

Make sure to trigger an "update" event on the table to tell tablesorter that content was either added or removed so it can update its own internal cache.

$("#myTable tbody")
  .append("<tr>...</tr>")
  // the "update" event bubbles up to the table element
  .trigger("update");

Why isn't my table sorting the content?

If you add new content to a table, depending on the browser, sometimes that new content is added into a separate tbody instead of the same tbody that all the other rows are inside.

// not all browsers add the new row to the main tbody;
$("table")
  .append("<tr>...</tr>")
  .trigger("update");

There have also been multiple issues where loops that build content unintentionally add each row into a separate tbody:

// assuming data is an array of arrays containing rows & cell content
var columnIndex, columns, rowIndex,
  rows = data.length,
  html = "<table><thead><tr>...</tr></thead>";
for (rowIndex = 0; rowIndex < rows; rowIndex++) {
  html += "<tbody>"; // NO! Do this outside the loop!
  columns = data[rowIndex].length;
  html += "<tr>";
  for (columnIndex = 0; columnIndex < columns; columnIndex++) {
    html += "<td>" + data[rowIndex][columnIndex] + "</td>";
  }
  html += "</tr>";
}
$("#tableWrapper")
  .html(html)
  .find("table")
  .tablesorter();

Why does my column only sort once?

There are various reasons why this could happen, but the most often it is due to an incorrect parser being set.

When a table has tablesorter applied to it, it must determine what type of data each column contains. The code that does this isn't really that smart, because all it does is looks at the first cell in that column - it is smart enough to skip empty cells and go to the next row - and then runs through each parser until something matches.

So say you have text included after a date, something like "Aug 21, 2009 12:21 PM blah", the date parser won't think this is a date because of the extra text. So it will set the parser as just plain text. See this demo. To fix this, wrap the date text in a span

<td><span>Aug 21, 2009 12:21 PM</span> blah</td>

then target the date using the textExtraction option, like this (working demo)

textExtraction: {
  1: function (node) {
    return $(node).find('span').text();
  }
}

Alternatively you can wrap the extra text in a span

<td>Aug 21, 2009 12:21 PM <span>blah</span></td>

Then modify the text extraction function like this (demo):

textExtraction: {
  1: function (node) {
    return node.childNodes[0].nodeType === 3 ? node.childNodes[0].nodeValue : $(node).text();
  }
}

Or, write your own custom parser (demo):

$.tablesorter.addParser({
  id: 'dates',
  is: function (s) { return false; }, // don't auto detect this parser
  format: function (s, table) {
    // match dates in this format: 'Mmm dd, yyyy hh:mm:ss AM'
    var date,
      isDate = s ? s.match(/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(?:\d{4})(?:\s+\d{1,2}:\d{2}(?::\d{2})?(?:\s+[AP]M)?)?/i) : s;
    if ( isDate ) {
      date = new Date( isDate[0] );
      return date instanceof Date && isFinite( date ) ? date.getTime() : s;
    }
    return s;
  },
  type: 'numeric'
});

$('table').tablesorter({
  headers : {
    1 : { sorter : 'dates' }
  }
});

If your issue doesn't seem related to this, then please check the development console (Press F12 in the browser, while on the problematic page), then

Why doesn't the zebra widget work in tabs, popups or dialog boxes?

The zebra widget only alternates class names on every visible row. So when tablesorter is inside of a tab, popup or dialog that isn't visible when the zebra widget is applied (tablesorter initialization), the table and all of its rows are hidden. So that means the zebra widget find any visible rows and ends up not adding any class names.

The easiest solution would be to use the tab, popup or dialog callback which is executed when it becomes visible. Since there are so many, I'm only going to show an example using jQuery UI widgets. For tabs, do the following:

$('#tabs').on('tabsactivate', function(event, ui){
  // make sure zebra widget is applied
  // after the tab becomes active
  ui.newPanel.find('table').trigger('applyWidgets');
});

and for the jQuery UI Dialog, do this:

$('#dialog')
  // initialize dialog widget
  .dialog()
  // use open callback to update the zebra widget
  .on('dialogopen', function(event, ui){
    // make sure zebra widget is applied after
    // the dialog is visible
    $(this).find('table').trigger('applyWidgets');
  });

Why is this fork slower than the original on very large tables?

Well, to be fair, this fork does do a lot more stuff during initialization.

Example 1:

Say you have a page with 90 tables each 19 columns by around 20 rows; that ends up being around 32,000 table cells to process!

  • When tested in IE11, the original tablesorter (v2.0.5) takes about 400ms. Not too shabby.
  • When tested in IE11, this fork takes up to 6800ms! Chrome takes about 1100ms.

Yeah, huge difference!

So, to break it down. Some of the little things that add to the initialization time of this fork include:

  • Aria support (the original does nothing)

  • Text Extraction:

    • The original checks for a node's textContent (if supported) then falls back to innerHTML - quick & simple.

    • This fork first checks for a data-attribute (to allow alternate text to be stored in "data-text"), then textContent, then innerText, then jQuery .text()

    • The data-attribute check adds some pretty significant time to the overall initialization time, especially in IE.

    • Adding the following two optimizations cuts the overall time to about 3600ms:

      // when true, the table cache is not built initially. The cache is built
      // if the table has an initial sort, initial filter settings, or once
      // the user clicks on a header cell to sort the table
      delayInit: true,
      // Simplified text extraction method
      textExtraction: function(node){
        return node.textContent || $(node).text() || '';
      },
  • Header cell processing:

    • The original TS does nothing to the table header cell.
    • This fork wraps the header cell content within a div:
      • The main reason for this is due to Firefox because "position:relative" can not be set on a table cell in Firefox, but all other browsers allow it.

      • With this inner div, icon elements (<i> used by Bootstrap and jQuery UI themes) can be absolutely positioned within the header cell. This also includes the resizable widget handle.

      • If you only want to use very basic widgets, then turning off this option cuts the example's total initialization time to around 2500ms - 3200ms.

        headerTemplate: '',

So the resulting optimized code woud look like this:

$('table').tablesorter({
  headerTemplate: '',
  delayInit: true,
  textExtraction: function(node){
    return node.textContent || $(node).text() || '';
  }
});

I tried to get the time better than this, but the rest of the code is all stuff that isn't easy to trim down, such as the ability to set options using data-attributes, jQuery .data() and header class names - they all need to be checked.

With more optimization and using native javascript instead of jQuery in critical places might speed up the code even more... I'll see what I can do when I have the time.

But really the bottom line is huge tables, or many many tables on one page is just too much. That tested HTML file with around 32,000 table cells was over 400kb in size, which is too much for a mobile device/network to handle.

Example 2:

The following section only applies to large tables. As eluded to above, this definition does not depend on the number or rows or columns, but the actual number of table cells.

A table with 2 columns and 1000 rows (2000 cells) does not have significant performace issues, even in Internet Explorer.

But a table with 20 columns and 1000 rows (20,000 cells) will have a more noticable lag during initialization and sorting.

Sometimes the performance issue can be overcome by fixing some or all of the following factors (the relative impact of each block is shown with plus signs, one plus + being minimal impact on performace up to three pluses +++ showing a more significant impact):

Setup

  • Options
    • + Defining plugin header options is faster than adding a class name, data-attribute or jQuery data to define an attribute for a column.
    • + When using the headers option, using the zero-based column index (0 : { sorter : false }) is more efficent than using a class name ('.disabled' : { sorter : false }).
    • + Defining a parser in the headers option would be faster then using a class name, data-attribute or predefined jQuery data (0 : { sorter : 'text' }) - see the "Parsers" block below.
    • ++ Setting the headerTemplate option to an empty string ('') skips the code that adds extra elements to each header cell - see the "Markup" block below.
    • + Leave the widthFixed option as its default of false. When true, it measures the width of every header cell and calculates a percentage of total table width. It then creates a <colgroup> with <col> elements set to the percentage width.
    • +++ Defining an initial sort in the sortList or data-sortlist attribute will execute the table sort code, adding to the initialization time.
    • +++ Setting the delayInit option to true is essential in speeding up initialization time when the page contains a large or numerous tables. When true, the internal parser cache, which contains the parsed data from every table cell, is not built unless the table has an initial sort, or the user sorts or filters the table.
    • ++ The more widgets that are defined in the widgets option will add to the initialization time. Some widgets may even force the table to build the cache thus skipping the benefit of setting the delayInit option to false. For example, if the filter widget were included with an initial query, the cache would be built before the search was performed.
  • Parsers
    • + When the plugin auto-detects the parser, it needs to gather data from the table, so if your table has something like 50 columns, initialization time can be improved by simply pre-defining a parser for each column. This can be as simple as adding a headers option (e.g. 0 : { sorter : 'text' }) for each header cell.
    • + The more empty cells there are near the top of the table (within a column), the further down in rows the parser detection code needs to search to find usable content; so an empty column - a column would "appear" empty if it was filled with checkboxes, or inputs - would have the parser detection code search every row!
  • Markup - setting up your headers in the markup can also speed up initialization time:
    • ++ If you are just using a basic theme, one that doesn't require any extra elements to be added to the th, then set the headerTemplate option to an empty string ('').

    • + If you are using Bootstrap, jQuery UI or some other theme that does require extra markup. Add it yourself! Do something like this:

      <th>
        <div class="tablesorter-wrapper">
          <div class="tablesorter-header-inner">
            {header name}
            <i class="tablesorter-icon"></i>
          </div>
        </div>
      </th>

      Please note:

      • There is an issue with Firefox (before version 30) not allowing a position:relative style to be assigned to a table cell (ref).
      • So that's why there must be a div wrapping the content inside the table header cell.
      • The "tablesorter-wrapper" is added by the uitheme widget & resizable widget.

Customization

  • +++ Inefficient custom parsers. Feel free to ask for help to optimize the code.
  • +++ Inefficient custom textExtration functions - node.textContent is much more efficient than other methods, for pretty much all versions of IE (ref). I ended up removing node.innerText from the internal code because it was doubling the initialization time in IE!
  • ++ Widgets that need to apply styles to one or more cells in every row (i.e. the columns widget) - don't use it on large tables!
  • ++ Widgets that need to apply styles to every row (i.e. zebra widget)
  • ++ Widgets that need to search the table DOM for text/HTML (i.e. filter widget; but you can make it search through the parsed data, which is much faster)
  • +++ Age of the browser (IE7 & 8 will be slow, period); but even the newest versions of IE are not nearly as fast as the other modern browsers.

The final word on large tables

Don't do it!