13     function Tablesort(el, options) {
 
   14         if (!(this instanceof Tablesort)) return new Tablesort(el, options);
 
   16         if (!el || el.tagName !== 'TABLE') {
 
   17             throw new Error('Element must be a table');
 
   19         this.init(el, options || {});
 
   24     var createEvent = function(name) {
 
   27         if (!window.CustomEvent || typeof window.CustomEvent !== 'function') {
 
   28             evt = document.createEvent('CustomEvent');
 
   29             evt.initCustomEvent(name, false, false, undefined);
 
   31             evt = new CustomEvent(name);
 
   37     var getInnerText = function(el) {
 
   38         return el.getAttribute('data-sort') || el.textContent || el.innerText || '';
 
   41     // Default sort method if no better sort method is found
 
   42     var caseInsensitiveSort = function(a, b) {
 
   46         if (a === b) return 0;
 
   52     // Stable sort function
 
   53     // If two elements are equal under the original sort function,
 
   54     // then there relative order is reversed
 
   55     var stabilize = function(sort, antiStabilize) {
 
   56         return function(a, b) {
 
   57             var unstableResult = sort(a.td, b.td);
 
   59             if (unstableResult === 0) {
 
   60                 if (antiStabilize) return b.index - a.index;
 
   61                 return a.index - b.index;
 
   64             return unstableResult;
 
   68     Tablesort.extend = function(name, pattern, sort) {
 
   69         if (typeof pattern !== 'function' || typeof sort !== 'function') {
 
   70             throw new Error('Pattern and sort must be a function');
 
   80     Tablesort.prototype = {
 
   82         init: function(el, options) {
 
   91             that.options = options;
 
   93             if (el.rows && el.rows.length > 0) {
 
   94                 if (el.tHead && el.tHead.rows.length > 0) {
 
   95                     for (i = 0; i < el.tHead.rows.length; i++) {
 
   96                         if (el.tHead.rows[i].getAttribute('data-sort-method') === 'thead') {
 
   97                             firstRow = el.tHead.rows[i];
 
  102                         firstRow = el.tHead.rows[el.tHead.rows.length - 1];
 
  106                     firstRow = el.rows[0];
 
  110             if (!firstRow) return;
 
  112             var onClick = function() {
 
  113                 if (that.current && that.current !== this) {
 
  114                     that.current.removeAttribute('aria-sort');
 
  118                 that.sortTable(this);
 
  121             // Assume first row is the header and attach a click handler to each.
 
  122             for (i = 0; i < firstRow.cells.length; i++) {
 
  123                 cell = firstRow.cells[i];
 
  124                 cell.setAttribute('role','columnheader');
 
  125                 if (cell.getAttribute('data-sort-method') !== 'none') {
 
  127                     cell.addEventListener('click', onClick, false);
 
  129                     if (cell.getAttribute('data-sort-default') !== null) {
 
  136                 that.current = defaultSort;
 
  137                 that.sortTable(defaultSort);
 
  141         sortTable: function(header, update) {
 
  143                 column = header.cellIndex,
 
  144                 sortFunction = caseInsensitiveSort,
 
  147                 i = that.thead ? 0 : 1,
 
  148                 sortMethod = header.getAttribute('data-sort-method'),
 
  149                 sortOrder = header.getAttribute('aria-sort');
 
  151             that.table.dispatchEvent(createEvent('beforeSort'));
 
  153             // If updating an existing sort, direction should remain unchanged.
 
  155                 if (sortOrder === 'ascending') {
 
  156                     sortOrder = 'descending';
 
  157                 } else if (sortOrder === 'descending') {
 
  158                     sortOrder = 'ascending';
 
  160                     sortOrder = that.options.descending ? 'ascending' : 'descending';
 
  163                 header.setAttribute('aria-sort', sortOrder);
 
  166             if (that.table.rows.length < 2) return;
 
  168             // If we force a sort method, it is not necessary to check rows
 
  170                 while (items.length < 3 && i < that.table.tBodies[0].rows.length) {
 
  171                     item = getInnerText(that.table.tBodies[0].rows[i].cells[column]);
 
  174                     if (item.length > 0) {
 
  184             for (i = 0; i < sortOptions.length; i++) {
 
  185                 item = sortOptions[i];
 
  188                     if (item.name === sortMethod) {
 
  189                         sortFunction = item.sort;
 
  192                 } else if (items.every(item.pattern)) {
 
  193                     sortFunction = item.sort;
 
  200             for (i = 0; i < that.table.tBodies.length; i++) {
 
  207                 if (that.table.tBodies[i].rows.length < 2) continue;
 
  209                 for (j = 0; j < that.table.tBodies[i].rows.length; j++) {
 
  210                     item = that.table.tBodies[i].rows[j];
 
  211                     if (item.getAttribute('data-sort-method') === 'none') {
 
  212                         // keep no-sorts in separate list to be able to insert
 
  213                         // them back at their original position later
 
  214                         noSorts[totalRows] = item;
 
  216                         // Save the index for stable sorting
 
  219                             td: getInnerText(item.cells[that.col]),
 
  225                 // Before we append should we reverse the new array or not?
 
  226                 // If we reverse, the sort needs to be `anti-stable` so that
 
  227                 // the double negatives cancel out
 
  228                 if (sortOrder === 'descending') {
 
  229                     newRows.sort(stabilize(sortFunction, true));
 
  232                     newRows.sort(stabilize(sortFunction, false));
 
  235                 // append rows that already exist rather than creating new ones
 
  236                 for (j = 0; j < totalRows; j++) {
 
  238                         // We have a no-sort row for this position, insert it here.
 
  242                         item = newRows[j - noSortsSoFar].tr;
 
  245                     // appendChild(x) moves x if already present somewhere else in the DOM
 
  246                     that.table.tBodies[i].appendChild(item);
 
  250             that.table.dispatchEvent(createEvent('afterSort'));
 
  253         refresh: function() {
 
  254             if (this.current !== undefined) {
 
  255                 this.sortTable(this.current, true);
 
  260     if (typeof module !== 'undefined' && module.exports) {
 
  261         module.exports = Tablesort;
 
  263         window.Tablesort = Tablesort;