Subversion Repositories wimsdev

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. ;(function() {
  2.   function Tablesort(el, options) {
  3.     if (!(this instanceof Tablesort)) return new Tablesort(el, options);
  4.  
  5.     if (!el || el.tagName !== 'TABLE') {
  6.       throw new Error('Element must be a table');
  7.     }
  8.     this.init(el, options || {});
  9.   }
  10.  
  11.   var sortOptions = [];
  12.  
  13.   var createEvent = function(name) {
  14.     var evt;
  15.  
  16.     if (!window.CustomEvent || typeof window.CustomEvent !== 'function') {
  17.       evt = document.createEvent('CustomEvent');
  18.       evt.initCustomEvent(name, false, false, undefined);
  19.     } else {
  20.       evt = new CustomEvent(name);
  21.     }
  22.  
  23.     return evt;
  24.   };
  25.  
  26.   var getInnerText = function(el) {
  27.     return el.getAttribute('data-sort') || el.textContent || el.innerText || '';
  28.   };
  29.  
  30.   // Default sort method if no better sort method is found
  31.   var caseInsensitiveSort = function(a, b) {
  32.     a = a.trim().toLowerCase();
  33.     b = b.trim().toLowerCase();
  34.  
  35.     if (a === b) return 0;
  36.     if (a < b) return 1;
  37.  
  38.     return -1;
  39.   };
  40.  
  41.   // Stable sort function
  42.   // If two elements are equal under the original sort function,
  43.   // then there relative order is reversed
  44.   var stabilize = function(sort, antiStabilize) {
  45.     return function(a, b) {
  46.       var unstableResult = sort(a.td, b.td);
  47.  
  48.       if (unstableResult === 0) {
  49.         if (antiStabilize) return b.index - a.index;
  50.         return a.index - b.index;
  51.       }
  52.  
  53.       return unstableResult;
  54.     };
  55.   };
  56.  
  57.   Tablesort.extend = function(name, pattern, sort) {
  58.     if (typeof pattern !== 'function' || typeof sort !== 'function') {
  59.       throw new Error('Pattern and sort must be a function');
  60.     }
  61.  
  62.     sortOptions.push({
  63.       name: name,
  64.       pattern: pattern,
  65.       sort: sort
  66.     });
  67.   };
  68.  
  69.   Tablesort.prototype = {
  70.  
  71.     init: function(el, options) {
  72.       var that = this,
  73.           firstRow,
  74.           defaultSort,
  75.           i,
  76.           cell;
  77.  
  78.       that.table = el;
  79.       that.thead = false;
  80.       that.options = options;
  81.  
  82.       if (el.rows && el.rows.length > 0) {
  83.         if (el.tHead && el.tHead.rows.length > 0) {
  84.           for (i = 0; i < el.tHead.rows.length; i++) {
  85.             if (el.tHead.rows[i].getAttribute('data-sort-method') === 'thead') {
  86.               firstRow = el.tHead.rows[i];
  87.               break;
  88.             }
  89.           }
  90.           if (!firstRow) {
  91.             firstRow = el.tHead.rows[el.tHead.rows.length - 1];
  92.           }
  93.           that.thead = true;
  94.         } else {
  95.           firstRow = el.rows[0];
  96.         }
  97.       }
  98.  
  99.       if (!firstRow) return;
  100.  
  101.       var onClick = function() {
  102.         if (that.current && that.current !== this) {
  103.           that.current.removeAttribute('aria-sort');
  104.         }
  105.  
  106.         that.current = this;
  107.         that.sortTable(this);
  108.       };
  109.  
  110.       // Assume first row is the header and attach a click handler to each.
  111.       for (i = 0; i < firstRow.cells.length; i++) {
  112.         cell = firstRow.cells[i];
  113.         cell.setAttribute('role','columnheader');
  114.         if (cell.getAttribute('data-sort-method') !== 'none') {
  115.           cell.tabindex = 0;
  116.           cell.addEventListener('click', onClick, false);
  117.  
  118.           if (cell.getAttribute('data-sort-default') !== null) {
  119.             defaultSort = cell;
  120.           }
  121.         }
  122.       }
  123.  
  124.       if (defaultSort) {
  125.         that.current = defaultSort;
  126.         that.sortTable(defaultSort);
  127.       }
  128.     },
  129.  
  130.     sortTable: function(header, update) {
  131.       var that = this,
  132.           column = header.cellIndex,
  133.           sortFunction = caseInsensitiveSort,
  134.           item = '',
  135.           items = [],
  136.           i = that.thead ? 0 : 1,
  137.           sortMethod = header.getAttribute('data-sort-method'),
  138.           sortOrder = header.getAttribute('aria-sort');
  139.  
  140.       that.table.dispatchEvent(createEvent('beforeSort'));
  141.  
  142.       // If updating an existing sort, direction should remain unchanged.
  143.       if (!update) {
  144.         if (sortOrder === 'ascending') {
  145.           sortOrder = 'descending';
  146.         } else if (sortOrder === 'descending') {
  147.           sortOrder = 'ascending';
  148.         } else {
  149.           sortOrder = that.options.descending ? 'descending' : 'ascending';
  150.         }
  151.  
  152.         header.setAttribute('aria-sort', sortOrder);
  153.       }
  154.  
  155.       if (that.table.rows.length < 2) return;
  156.  
  157.       // If we force a sort method, it is not necessary to check rows
  158.       if (!sortMethod) {
  159.         while (items.length < 3 && i < that.table.tBodies[0].rows.length) {
  160.           item = getInnerText(that.table.tBodies[0].rows[i].cells[column]);
  161.           item = item.trim();
  162.  
  163.           if (item.length > 0) {
  164.             items.push(item);
  165.           }
  166.  
  167.           i++;
  168.         }
  169.  
  170.         if (!items) return;
  171.       }
  172.  
  173.       for (i = 0; i < sortOptions.length; i++) {
  174.         item = sortOptions[i];
  175.  
  176.         if (sortMethod) {
  177.           if (item.name === sortMethod) {
  178.             sortFunction = item.sort;
  179.             break;
  180.           }
  181.         } else if (items.every(item.pattern)) {
  182.           sortFunction = item.sort;
  183.           break;
  184.         }
  185.       }
  186.  
  187.       that.col = column;
  188.  
  189.       for (i = 0; i < that.table.tBodies.length; i++) {
  190.         var newRows = [],
  191.             noSorts = {},
  192.             j,
  193.             totalRows = 0,
  194.             noSortsSoFar = 0;
  195.  
  196.         if (that.table.tBodies[i].rows.length < 2) continue;
  197.  
  198.         for (j = 0; j < that.table.tBodies[i].rows.length; j++) {
  199.           item = that.table.tBodies[i].rows[j];
  200.           if (item.getAttribute('data-sort-method') === 'none') {
  201.             // keep no-sorts in separate list to be able to insert
  202.             // them back at their original position later
  203.             noSorts[totalRows] = item;
  204.           } else {
  205.             // Save the index for stable sorting
  206.             newRows.push({
  207.               tr: item,
  208.               td: getInnerText(item.cells[that.col]),
  209.               index: totalRows
  210.             });
  211.           }
  212.           totalRows++;
  213.         }
  214.         // Before we append should we reverse the new array or not?
  215.         // If we reverse, the sort needs to be `anti-stable` so that
  216.         // the double negatives cancel out
  217.         if (sortOrder === 'descending') {
  218.           newRows.sort(stabilize(sortFunction, true));
  219.         } else {
  220.           newRows.sort(stabilize(sortFunction, false));
  221.           newRows.reverse();
  222.         }
  223.  
  224.         // append rows that already exist rather than creating new ones
  225.         for (j = 0; j < totalRows; j++) {
  226.           if (noSorts[j]) {
  227.             // We have a no-sort row for this position, insert it here.
  228.             item = noSorts[j];
  229.             noSortsSoFar++;
  230.           } else {
  231.             item = newRows[j - noSortsSoFar].tr;
  232.           }
  233.  
  234.           // appendChild(x) moves x if already present somewhere else in the DOM
  235.           that.table.tBodies[i].appendChild(item);
  236.         }
  237.       }
  238.  
  239.       that.table.dispatchEvent(createEvent('afterSort'));
  240.     },
  241.  
  242.     refresh: function() {
  243.       if (this.current !== undefined) {
  244.         this.sortTable(this.current, true);
  245.       }
  246.     }
  247.   };
  248.  
  249.   if (typeof module !== 'undefined' && module.exports) {
  250.     module.exports = Tablesort;
  251.   } else {
  252.     window.Tablesort = Tablesort;
  253.   }
  254. })();
  255.