Advertisement
killrawr

Sorttable.js (original) + bug fixes.

Jun 27th, 2016
96
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.   SortTable
  3.   version 2
  4.   7th April 2007
  5.   Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
  6.  
  7.   Instructions:
  8.   Download this file
  9.   Add <script src="sorttable.js"></script> to your HTML
  10.   Add class="sortable" to any table you'd like to make sortable
  11.   Click on the headers to sort
  12.  
  13.   Thanks to many, many people for contributions and suggestions.
  14.   Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
  15.   This basically means: do what you want with it.
  16. */
  17. var stIsIE = /*@cc_on!@*/ false;
  18.  
  19. sorttable = {
  20.  
  21.     init: function() {
  22.         // quit if this function has already been called
  23.         if (arguments.callee.done) return;
  24.         // flag this function so we don't do the same thing twice
  25.         arguments.callee.done = true;
  26.         // kill the timer
  27.         if (_timer) clearInterval(_timer);
  28.  
  29.         if (!document.createElement || !document.getElementsByTagName) return;
  30.  
  31.         sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
  32.  
  33.         forEach(document.getElementsByTagName('table'), function(table) {
  34.             if (table.className.search(/\bsortable\b/) != -1) {
  35.                 sorttable.makeSortable(table);
  36.             }
  37.         });
  38.  
  39.     },
  40.  
  41.     makeSortable: function(table) {
  42.         if (table.getElementsByTagName('thead').length == 0) {
  43.             // table doesn't have a tHead. Since it should have, create one and
  44.             // put the first table row in it.
  45.             the = document.createElement('thead');
  46.             the.appendChild(table.rows[0]);
  47.             table.insertBefore(the, table.firstChild);
  48.         }
  49.  
  50.         // Safari doesn't support table.tHead, sigh
  51.         if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
  52.  
  53.         if (table.tHead.rows.length != 1) return; // can't cope with two header rows
  54.  
  55.         // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
  56.         // "total" rows, for example). This is B&R, since what you're supposed
  57.         // to do is put them in a tfoot. So, if there are sortbottom rows,
  58.         // for backwards compatibility, move them to tfoot (creating it if needed).
  59.         sortbottomrows = [];
  60.         for (var i = 0; i < table.rows.length; i++) {
  61.             if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
  62.                 sortbottomrows[sortbottomrows.length] = table.rows[i];
  63.             }
  64.         }
  65.         if (sortbottomrows) {
  66.             if (table.tFoot == null) {
  67.                 // table doesn't have a tfoot. Create one.
  68.                 tfo = document.createElement('tfoot');
  69.                 table.appendChild(tfo);
  70.             }
  71.             for (var i = 0; i < sortbottomrows.length; i++) {
  72.                 tfo.appendChild(sortbottomrows[i]);
  73.             }
  74.             delete sortbottomrows;
  75.         }
  76.  
  77.         if (window.jQuery) {
  78.             var jTable = jQuery(table);
  79.         } else {
  80.             var jTable = false;
  81.         }
  82.  
  83.         // work through each column and calculate its type
  84.         headrow = table.tHead.rows[0].cells;
  85.         for (var i = 0; i < headrow.length; i++) {
  86.             // manually override the type with a sorttable_type attribute
  87.             if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
  88.                 mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
  89.                 if (mtch) {
  90.                     override = mtch[1];
  91.                 }
  92.                 if (mtch && typeof sorttable["sort_" + override] == 'function') {
  93.                     headrow[i].sorttable_sortfunction = sorttable["sort_" + override];
  94.                 } else {
  95.                     headrow[i].sorttable_sortfunction = sorttable.guessType(table, i);
  96.                 }
  97.                 // make it clickable to sort
  98.                 headrow[i].sorttable_columnindex = i;
  99.                 headrow[i].sorttable_tbody = table.tBodies[0];
  100.                 dean_addEvent(headrow[i], "click", function(e) {
  101.  
  102.                     // console.log("Click Event");
  103.  
  104.                     if (this.className.search(/\bsorttable_sorted\b/) != -1) {
  105.                         // if we're already sorted by this column, just
  106.                         // reverse the table, which is quicker
  107.                         sorttable.reverse(this.sorttable_tbody);
  108.                         this.className = this.className.replace('sorttable_sorted',
  109.                             'sorttable_sorted_reverse');
  110.                         this.removeChild(document.getElementById('sorttable_sortfwdind'));
  111.                         sortrevind = document.createElement('span');
  112.                         sortrevind.id = "sorttable_sortrevind";
  113.                         sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
  114.                         this.appendChild(sortrevind);
  115.                         // console.log("bsorttable_sorted")
  116.                         return true;
  117.                     }
  118.  
  119.                     if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
  120.                         // if we're already sorted by this column in reverse, just
  121.                         // re-reverse the table, which is quicker
  122.                         sorttable.reverse(this.sorttable_tbody);
  123.                         this.className = this.className.replace('sorttable_sorted_reverse',
  124.                             'sorttable_sorted');
  125.                         this.removeChild(document.getElementById('sorttable_sortrevind'));
  126.                         sortfwdind = document.createElement('span');
  127.                         sortfwdind.id = "sorttable_sortfwdind";
  128.                         sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
  129.                         this.appendChild(sortfwdind);
  130.                         // console.log("bsorttable_sorted_reverse")
  131.                         return true;
  132.                     }
  133.  
  134.                     // remove sorttable_sorted classes
  135.                     theadrow = this.parentNode;
  136.                     forEach(theadrow.childNodes, function(cell) {
  137.                         if (cell.nodeType == 1) { // an element
  138.                             cell.className = cell.className.replace('sorttable_sorted_reverse', '');
  139.                             cell.className = cell.className.replace('sorttable_sorted', '');
  140.                         }
  141.                     });
  142.  
  143.                     sortfwdind = document.getElementById('sorttable_sortfwdind');
  144.                     if (sortfwdind) {
  145.                         sortfwdind.parentNode.removeChild(sortfwdind);
  146.                     }
  147.  
  148.                     sortrevind = document.getElementById('sorttable_sortrevind');
  149.                     if (sortrevind) {
  150.                         sortrevind.parentNode.removeChild(sortrevind);
  151.                     }
  152.  
  153.                     this.className += ' sorttable_sorted';
  154.                     sortfwdind = document.createElement('span');
  155.                     sortfwdind.id = "sorttable_sortfwdind";
  156.                     sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
  157.                     this.appendChild(sortfwdind);
  158.  
  159.                     // build an array to sort. This is a Schwartzian transform thing,
  160.                     // i.e., we "decorate" each row with the actual sort key,
  161.                     // sort based on the sort keys, and then put the rows back in order
  162.                     // which is a lot faster because you only do getInnerText once per row
  163.                     row_array = [];
  164.                     col = this.sorttable_columnindex;
  165.                     rows = this.sorttable_tbody.rows;
  166.                     for (var j = 0; j < rows.length; j++) {
  167.  
  168.                         var jrowCellItem = false;
  169.                         var rowCellItem = rows[j].cells[col];
  170.  
  171.                         if (window.jQuery) {
  172.                             var jrowCellItem = jQuery(rowCellItem);
  173.                         }
  174.  
  175.                         var rowInnerText = sorttable.getInnerText(rowCellItem);
  176.  
  177.                         // Ensure we default this to ""
  178.                         var rowInnerTextType = typeof(rowInnerText);
  179.                         if (typeof(rowInnerText) === 'undefined') {
  180.                             rowInnerText = '';
  181.                         };
  182.  
  183.                         // Image Sortting
  184.                         if (!rowInnerText.length && (jrowCellItem && jrowCellItem.length)) {
  185.  
  186.                             var jrowCellImageItem = jrowCellItem.find('img');
  187.                             jrowCellImageItem = jrowCellImageItem.length > 0 ? jrowCellImageItem.first() : false;
  188.  
  189.                             // Only Get the SRC if, the IMAGE exists within this ROW
  190.                             if (jrowCellImageItem && jrowCellImageItem.length) {
  191.                                 rowInnerText = jrowCellImageItem.attr('src');
  192.                             };
  193.  
  194.                         };
  195.  
  196.                         // Update the ROW ARRAY with row_array_item
  197.                         var row_array_item = [rowInnerText, rows[j]];
  198.                         row_array[row_array.length] = row_array_item;
  199.  
  200.                     }
  201.  
  202.                     /* If you want a stable sort, uncomment the following line */
  203.                     //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
  204.                     /* and comment out this one */
  205.                     row_array.sort(this.sorttable_sortfunction);
  206.                     tb = this.sorttable_tbody;
  207.  
  208.                     // Check if this table contains zebra
  209.                     if (jTable && jTable.length) {
  210.                         var table_zebra = jTable.find('tr.alt').length > 0;
  211.                     } else {
  212.                         var table_zebra = false;
  213.                     }
  214.  
  215.                     var zebra_key = 0;
  216.  
  217.                     for (var j = 0; j < row_array.length; j++) {
  218.  
  219.                         var RowArrayItem = row_array[j][1];
  220.  
  221.                         // Get jQuery revision of RowArrayItem
  222.                         if (jTable && jTable.length) {
  223.                             var jRowArrayItem = jQuery(RowArrayItem);
  224.                         } else {
  225.                             var jRowArrayItem = false;
  226.                         }
  227.  
  228.                         // Re-Zebra the ROW
  229.                         if ((jRowArrayItem && jRowArrayItem.length) && table_zebra) {
  230.                             jRowArrayItem.removeClass('alt'); // Remove "alt"
  231.                             if (zebra_key > 0) {
  232.                                 jRowArrayItem.addClass('alt');
  233.                                 zebra_key = 0;
  234.                             } else {
  235.                                 zebra_key = zebra_key + 1;
  236.                             }
  237.                             RowArrayItem = jRowArrayItem.get(0);
  238.                         };
  239.  
  240.                         tb.appendChild(RowArrayItem);
  241.  
  242.                     };
  243.  
  244.                     delete row_array;
  245.  
  246.                 });
  247.             }
  248.         }
  249.     },
  250.  
  251.     guessType: function(table, column) {
  252.         // guess the type of a column based on its first non-blank row
  253.         sortfn = sorttable.sort_alpha;
  254.         for (var i = 0; i < table.tBodies[0].rows.length; i++) {
  255.             text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
  256.             if (text != '') {
  257.                 if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) {
  258.                     return sorttable.sort_numeric;
  259.                 }
  260.                 // check for a date: dd/mm/yyyy or dd/mm/yy
  261.                 // can have / or . or - as separator
  262.                 // can be mm/dd as well
  263.                 possdate = text.match(sorttable.DATE_RE)
  264.                 if (possdate) {
  265.                     // looks like a date
  266.                     first = parseInt(possdate[1]);
  267.                     second = parseInt(possdate[2]);
  268.                     if (first > 12) {
  269.                         // definitely dd/mm
  270.                         return sorttable.sort_ddmm;
  271.                     } else if (second > 12) {
  272.                         return sorttable.sort_mmdd;
  273.                     } else {
  274.                         // looks like a date, but we can't tell which, so assume
  275.                         // that it's dd/mm (English imperialism!) and keep looking
  276.                         sortfn = sorttable.sort_ddmm;
  277.                     }
  278.                 }
  279.             }
  280.         }
  281.         return sortfn;
  282.     },
  283.  
  284.     getInnerText: function(node) {
  285.  
  286.         // gets the text we want to use for sorting for a cell.
  287.         // strips leading and trailing whitespace.
  288.         // this is *not* a generic getInnerText function; it's special to sorttable.
  289.         // for example, you can override the cell text with a customkey attribute.
  290.         // it also gets .value for <input> fields.
  291.  
  292.         hasInputs = (typeof node.getElementsByTagName == 'function') &&
  293.             node.getElementsByTagName('input').length;
  294.  
  295.         if (node.getAttribute && node.getAttribute("sorttable_customkey") != null) {
  296.             return node.getAttribute("sorttable_customkey");
  297.         } else if (typeof node.textContent != 'undefined' && !hasInputs) {
  298.             return node.textContent.replace(/^\s+|\s+$/g, '');
  299.         } else if (typeof node.innerText != 'undefined' && !hasInputs) {
  300.             return node.innerText.replace(/^\s+|\s+$/g, '');
  301.         } else if (typeof node.text != 'undefined' && !hasInputs) {
  302.             return node.text.replace(/^\s+|\s+$/g, '');
  303.         } else {
  304.             switch (node.nodeType) {
  305.                 case 3:
  306.                     if (node.nodeName.toLowerCase() == 'input') {
  307.                         return node.value.replace(/^\s+|\s+$/g, '');
  308.                     }
  309.                 case 4:
  310.                     return node.nodeValue.replace(/^\s+|\s+$/g, '');
  311.                     break;
  312.                 case 1:
  313.                 case 11:
  314.                     var innerText = '';
  315.                     for (var i = 0; i < node.childNodes.length; i++) {
  316.                         innerText += sorttable.getInnerText(node.childNodes[i]);
  317.                     }
  318.                     return innerText.replace(/^\s+|\s+$/g, '');
  319.                     break;
  320.                 default:
  321.                     return '';
  322.             };
  323.         };
  324.  
  325.     },
  326.  
  327.     reverse: function(tbody) {
  328.         // reverse the rows in a tbody
  329.         newrows = [];
  330.         for (var i = 0; i < tbody.rows.length; i++) {
  331.             newrows[newrows.length] = tbody.rows[i];
  332.         }
  333.         for (var i = newrows.length - 1; i >= 0; i--) {
  334.             tbody.appendChild(newrows[i]);
  335.         }
  336.         delete newrows;
  337.     },
  338.  
  339.     /* sort functions
  340.      each sort function takes two parameters, a and b
  341.      you are comparing a[0] and b[0] */
  342.     sort_numeric: function(a, b) {
  343.         aa = parseFloat(a[0].replace(/[^0-9.-]/g, ''));
  344.         if (isNaN(aa)) aa = 0;
  345.         bb = parseFloat(b[0].replace(/[^0-9.-]/g, ''));
  346.         if (isNaN(bb)) bb = 0;
  347.         return aa - bb;
  348.     },
  349.     sort_alpha: function(a, b) {
  350.         if (a[0] == b[0]) return 0;
  351.         if (a[0] < b[0]) return -1;
  352.         return 1;
  353.     },
  354.     sort_ddmm: function(a, b) {
  355.         mtch = a[0].match(sorttable.DATE_RE);
  356.         y = mtch[3];
  357.         m = mtch[2];
  358.         d = mtch[1];
  359.         if (m.length == 1) m = '0' + m;
  360.         if (d.length == 1) d = '0' + d;
  361.         dt1 = y + m + d;
  362.         mtch = b[0].match(sorttable.DATE_RE);
  363.         y = mtch[3];
  364.         m = mtch[2];
  365.         d = mtch[1];
  366.         if (m.length == 1) m = '0' + m;
  367.         if (d.length == 1) d = '0' + d;
  368.         dt2 = y + m + d;
  369.         if (dt1 == dt2) return 0;
  370.         if (dt1 < dt2) return -1;
  371.         return 1;
  372.     },
  373.     //kryogenix sorttable - how to sort date columns that have empty cells
  374.     //http://stackoverflow.com/questions/9815426/kryogenix-sorttable-how-to-sort-date-columns-that-have-empty-cells
  375.     sort_mmdd: function(a, b) {
  376.         mtch = a[0].match(sorttable.DATE_RE);
  377.         if ((mtch == null) || (mtch == undefined)) {
  378.             y = 0;
  379.             d = 0;
  380.             m = 0;
  381.         } else {
  382.             y = mtch[3];
  383.             d = mtch[2];
  384.             m = mtch[1];
  385.         }
  386.  
  387.         if (m.length == 1) m = '0' + m;
  388.         if (d.length == 1) d = '0' + d;
  389.         dt1 = y + m + d;
  390.         mtch = b[0].match(sorttable.DATE_RE);
  391.         if ((mtch == null) || (mtch == undefined)) {
  392.             y = 0;
  393.             d = 0;
  394.             m = 0;
  395.         } else {
  396.             y = mtch[3];
  397.             d = mtch[2];
  398.             m = mtch[1];
  399.         }
  400.         if (m.length == 1) m = '0' + m;
  401.         if (d.length == 1) d = '0' + d;
  402.         dt2 = y + m + d;
  403.         if (dt1 == dt2) return 0;
  404.         if (dt1 < dt2) return -1;
  405.         return 1;
  406.     },
  407.     shaker_sort: function(list, comp_func) {
  408.         // A stable sort function to allow multi-level sorting of data
  409.         // see: http://en.wikipedia.org/wiki/Cocktail_sort
  410.         // thanks to Joseph Nahmias
  411.         var b = 0;
  412.         var t = list.length - 1;
  413.         var swap = true;
  414.  
  415.         while (swap) {
  416.             swap = false;
  417.             for (var i = b; i < t; ++i) {
  418.                 if (comp_func(list[i], list[i + 1]) > 0) {
  419.                     var q = list[i];
  420.                     list[i] = list[i + 1];
  421.                     list[i + 1] = q;
  422.                     swap = true;
  423.                 }
  424.             } // for
  425.             t--;
  426.  
  427.             if (!swap) break;
  428.  
  429.             for (var i = t; i > b; --i) {
  430.                 if (comp_func(list[i], list[i - 1]) < 0) {
  431.                     var q = list[i];
  432.                     list[i] = list[i - 1];
  433.                     list[i - 1] = q;
  434.                     swap = true;
  435.                 }
  436.             } // for
  437.             b++;
  438.  
  439.         } // while(swap)
  440.     }
  441. }
  442.  
  443. /* ******************************************************************
  444.    Supporting functions: bundled here to avoid depending on a library
  445.    ****************************************************************** */
  446.  
  447. // Dean Edwards/Matthias Miller/John Resig
  448.  
  449. /* for Mozilla/Opera9 */
  450. if (document.addEventListener) {
  451.     document.addEventListener("DOMContentLoaded", sorttable.init, false);
  452. }
  453.  
  454. /* for Internet Explorer */
  455. /*@cc_on @*/
  456. /*@if (@_win32)
  457.     document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
  458.     var script = document.getElementById("__ie_onload");
  459.     script.onreadystatechange = function() {
  460.         if (this.readyState == "complete") {
  461.             sorttable.init(); // call the onload handler
  462.         }
  463.     };
  464. /*@end @*/
  465.  
  466. /* for Safari */
  467. if (/WebKit/i.test(navigator.userAgent)) { // sniff
  468.     var _timer = setInterval(function() {
  469.         if (/loaded|complete/.test(document.readyState)) {
  470.             sorttable.init(); // call the onload handler
  471.         }
  472.     }, 10);
  473. }
  474.  
  475. /* for other browsers */
  476. window.onload = sorttable.init;
  477.  
  478. // written by Dean Edwards, 2005
  479. // with input from Tino Zijdel, Matthias Miller, Diego Perini
  480.  
  481. // http://dean.edwards.name/weblog/2005/10/add-event/
  482.  
  483. function dean_addEvent(element, type, handler) {
  484.     if (element.addEventListener) {
  485.         element.addEventListener(type, handler, false);
  486.     } else {
  487.         // assign each event handler a unique ID
  488.         if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
  489.         // create a hash table of event types for the element
  490.         if (!element.events) element.events = {};
  491.         // create a hash table of event handlers for each element/event pair
  492.         var handlers = element.events[type];
  493.         if (!handlers) {
  494.             handlers = element.events[type] = {};
  495.             // store the existing event handler (if there is one)
  496.             if (element["on" + type]) {
  497.                 handlers[0] = element["on" + type];
  498.             }
  499.         }
  500.         // store the event handler in the hash table
  501.         handlers[handler.$$guid] = handler;
  502.         // assign a global event handler to do all the work
  503.         element["on" + type] = handleEvent;
  504.     }
  505. };
  506. // a counter used to create unique IDs
  507. dean_addEvent.guid = 1;
  508.  
  509. function removeEvent(element, type, handler) {
  510.     if (element.removeEventListener) {
  511.         element.removeEventListener(type, handler, false);
  512.     } else {
  513.         // delete the event handler from the hash table
  514.         if (element.events && element.events[type]) {
  515.             delete element.events[type][handler.$$guid];
  516.         }
  517.     }
  518. };
  519.  
  520. function handleEvent(event) {
  521.     var returnValue = true;
  522.     // grab the event object (IE uses a global event object)
  523.     event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
  524.     // get a reference to the hash table of event handlers
  525.     var handlers = this.events[event.type];
  526.     // execute each event handler
  527.     for (var i in handlers) {
  528.         this.$$handleEvent = handlers[i];
  529.         if (this.$$handleEvent(event) === false) {
  530.             returnValue = false;
  531.         }
  532.     }
  533.     return returnValue;
  534. };
  535.  
  536. function fixEvent(event) {
  537.     // add W3C standard event methods
  538.     event.preventDefault = fixEvent.preventDefault;
  539.     event.stopPropagation = fixEvent.stopPropagation;
  540.     return event;
  541. };
  542. fixEvent.preventDefault = function() {
  543.     this.returnValue = false;
  544. };
  545. fixEvent.stopPropagation = function() {
  546.     this.cancelBubble = true;
  547. }
  548.  
  549. // Dean's forEach: http://dean.edwards.name/base/forEach.js
  550. /*
  551.     forEach, version 1.0
  552.     Copyright 2006, Dean Edwards
  553.     License: http://www.opensource.org/licenses/mit-license.php
  554. */
  555.  
  556. // array-like enumeration
  557. if (!Array.forEach) { // mozilla already supports this
  558.     Array.forEach = function(array, block, context) {
  559.         for (var i = 0; i < array.length; i++) {
  560.             block.call(context, array[i], i, array);
  561.         }
  562.     };
  563. }
  564.  
  565. // generic enumeration
  566. Function.prototype.forEach = function(object, block, context) {
  567.     for (var key in object) {
  568.         if (typeof this.prototype[key] == "undefined") {
  569.             block.call(context, object[key], key, object);
  570.         }
  571.     }
  572. };
  573.  
  574. // character enumeration
  575. String.forEach = function(string, block, context) {
  576.     Array.forEach(string.split(""), function(chr, index) {
  577.         block.call(context, chr, index, string);
  578.     });
  579. };
  580.  
  581. // globally resolve forEach enumeration
  582. var forEach = function(object, block, context) {
  583.     if (object) {
  584.         var resolve = Object; // default
  585.         if (object instanceof Function) {
  586.             // functions have a "length" property
  587.             resolve = Function;
  588.         } else if (object.forEach instanceof Function) {
  589.             // the object implements a custom forEach method so use that
  590.             object.forEach(block, context);
  591.             return;
  592.         } else if (typeof object == "string") {
  593.             // the object is a string
  594.             resolve = String;
  595.         } else if (typeof object.length == "number") {
  596.             // the object is array-like
  597.             resolve = Array;
  598.         }
  599.         resolve.forEach(object, block, context);
  600.     }
  601. };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement