github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/public/static/js/tablesorter.js (about)

     1  /*
     2   *
     3   * TableSorter 2.0 - Client-side table sorting with ease!
     4   * Version 2.0.5d (update)
     5   * @requires jQuery v1.2.3
     6   *
     7   * Copyright (c) 2007 Christian Bach
     8   * Examples and docs at: http://tablesorter.com
     9   * Dual licensed under the MIT and GPL licenses:
    10   * http://www.opensource.org/licenses/mit-license.php
    11   * http://www.gnu.org/licenses/gpl.html
    12   *
    13   */
    14  /**
    15   *
    16   * @description Create a sortable table with multi-column sorting capabilitys
    17   *
    18   * @example $('table').tablesorter();
    19   * @desc Create a simple tablesorter interface.
    20   *
    21   * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] });
    22   * @desc Create a tablesorter interface and sort on the first and secound column in ascending order.
    23   *
    24   * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } } });
    25   * @desc Create a tablesorter interface and disableing the first and secound column headers.
    26   *
    27   * @example $('table').tablesorter({ headers: { 0: {sorter: "digit"}, 1: {sorter: "currency"} } });
    28   * @desc Create a tablesorter interface and set a column parser for the first and secound column.
    29   *
    30   *
    31   * @param Object settings An object literal containing key/value pairs to provide optional settings.
    32   *
    33   * @option String cssHeader (optional)          A string of the class name to be appended to sortable tr elements in the thead of the table. 
    34   *                                              Default value: "header"
    35   *
    36   * @option String cssAsc (optional)             A string of the class name to be appended to sortable tr elements in the thead on a ascending sort. 
    37   *                                              Default value: "headerSortUp"
    38   *
    39   * @option String cssDesc (optional)            A string of the class name to be appended to sortable tr elements in the thead on a descending sort. 
    40   *                                              Default value: "headerSortDown"
    41   *
    42   * @option String sortInitialOrder (optional)   A string of the inital sorting order can be asc or desc. 
    43   *                                              Default value: "asc"
    44   *
    45   * @option String sortMultisortKey (optional)   A string of the multi-column sort key. 
    46   *                                              Default value: "shiftKey"
    47   *
    48   * @option String textExtraction (optional)     A string of the text-extraction method to use. 
    49   *                                              For complex html structures inside td cell set this option to "complex", 
    50   *                                              on large tables the complex option can be slow. 
    51   *                                              Default value: "simple"
    52   *
    53   * @option Object headers (optional)            An array containing the forces sorting rules. 
    54   *                                              This option let's you specify a default sorting rule. 
    55   *                                              Default value: null
    56   *
    57   * @option Array sortList (optional)            An array containing the forces sorting rules. 
    58   *                                              This option let's you specify a default sorting rule. 
    59   *                                              Default value: null
    60   *
    61   * @option Array sortForce (optional)           An array containing forced sorting rules. 
    62   *                                              This option let's you specify a default sorting rule, which is prepended to user-selected rules.
    63   *                                              Default value: null
    64   *
    65   * @option Array sortAppend (optional)          An array containing forced sorting rules. 
    66   *                                              This option let's you specify a default sorting rule, which is appended to user-selected rules.
    67   *                                              Default value: null
    68   *
    69   * @option Boolean widthFixed (optional)        Boolean flag indicating if tablesorter should apply fixed widths to the table columns.
    70   *                                              This is usefull when using the pager companion plugin.
    71   *                                              This options requires the dimension jquery plugin.
    72   *                                              Default value: false
    73   *
    74   * @option Boolean cancelSelection (optional)   Boolean flag indicating if tablesorter should cancel selection of the table headers text.
    75   *                                              Default value: true
    76   *
    77   * @option Boolean locale (optional)            A locale String indicating which date format and decimal point to use.
    78   *                                              Default value: us
    79   *
    80   * @option Boolean debug (optional)             Boolean flag indicating if tablesorter should display debuging information usefull for development.
    81   *
    82   * @option Boolean useUI (optional)             Boolean flag indicating if tablesorter use the ui theme classes.
    83   *                                              Default value: false
    84   * @type jQuery
    85   *
    86   * @name tablesorter
    87   *
    88   * @cat Plugins/Tablesorter
    89   *
    90   * @author Christian Bach/christian.bach@polyester.se
    91   */
    92  
    93  (function($) {
    94    $.extend({
    95      tablesorter: new function() {
    96  
    97        var parsers = [],
    98          widgets = [];
    99  
   100        this.defaults = {
   101          cssHeader: "header",
   102          cssAsc: "headerSortUp",
   103          cssDesc: "headerSortDown",
   104          cssChildRow: "expand-child",
   105          cssUI: {
   106            widget: "ui-widget ui-widget-content ui-corner-all",
   107            header: "ui-widget-header ui-corner-all",
   108            hover: "ui-state-hover",
   109            icon: "ui-icon",
   110            iconBoth: "ui-icon-arrowthick-2-n-s",
   111            iconDesc: "ui-icon-arrowthick-1-n",
   112            iconAsc: "ui-icon-arrowthick-1-s"
   113          },
   114          sortInitialOrder: "asc",
   115          sortMultiSortKey: "shiftKey",
   116          sortForce: null,
   117          sortAppend: null,
   118          textExtraction: "simple",
   119          parsers: {},
   120          widgets: [],
   121          widgetZebra: {
   122            css: ["even", "odd"]
   123          },
   124          headers: {},
   125          widthFixed: false,
   126          cancelSelection: true,
   127          sortList: [],
   128          headerList: [],
   129          locale: "us",
   130          format: {
   131            us: {
   132              decimal: '.',
   133              date: '/'
   134            },
   135            en: {
   136              decimal: '.',
   137              date: '/'
   138            },
   139            eu: {
   140              decimal: ',',
   141              date: '.'
   142            },
   143            de: {
   144              decimal: ',',
   145              date: '.'
   146            }
   147          },
   148          onRenderHeader: null,
   149          selectorHeaders: 'thead th',
   150          useUI: false,
   151          debug: false
   152        };
   153  
   154        /* debuging utils */
   155        function benchmark(s, d) {
   156          log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
   157        }
   158  
   159        this.benchmark = benchmark;
   160  
   161        function log(s) {
   162          if (typeof console != "undefined" && typeof console.debug != "undefined") {
   163            console.log(s);
   164          } else {
   165            alert(s);
   166          }
   167        }
   168  
   169        /* parsers utils */
   170        function buildParserCache(table, $headers) {
   171  
   172          if (table.config.debug) {
   173            var parsersDebug = "";
   174          }
   175  
   176          var rows = table.tBodies[0].rows;
   177  
   178          if (table.tBodies[0].rows[0]) {
   179  
   180            var list = [],
   181              cells = rows[0].cells,
   182              l = cells.length;
   183  
   184            for (var i = 0; i < l; i++) {
   185              var p = false;
   186  
   187              if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) {
   188  
   189                p = getParserById($($headers[i]).metadata().sorter);
   190  
   191              } else if ((table.config.headers[i] && table.config.headers[i].sorter)) {
   192  
   193                p = getParserById(table.config.headers[i].sorter);
   194              }
   195              if (!p) {
   196                p = detectParserForColumn(table, cells[i]);
   197              }
   198  
   199              if (table.config.debug) {
   200                parsersDebug += "column:" + i + " parser:" + p.id + "\n";
   201              }
   202  
   203              list.push(p);
   204            }
   205          }
   206  
   207          if (table.config.debug) {
   208            log(parsersDebug);
   209          }
   210  
   211          return list;
   212        };
   213  
   214        function detectParserForColumn(table, node) {
   215          var l = parsers.length;
   216          for (var i = 1; i < l; i++) {
   217            if (parsers[i].is($.trim(getElementText(table.config, node)), table, node)) {
   218              return parsers[i];
   219            }
   220          }
   221          // 0 is always the generic parser (text)
   222          return parsers[0];
   223        }
   224  
   225        function getParserById(name) {
   226          var l = parsers.length;
   227          for (var i = 0; i < l; i++) {
   228            if (parsers[i].id.toLowerCase() == name.toLowerCase()) {
   229              return parsers[i];
   230            }
   231          }
   232          return false;
   233        }
   234  
   235        /* utils */
   236        function buildCache(table) {
   237  
   238          if (table.config.debug) {
   239            var cacheTime = new Date();
   240          }
   241  
   242  
   243          var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,
   244            totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,
   245            parsers = table.config.parsers,
   246            cache = {
   247              row: [],
   248              normalized: []
   249            };
   250  
   251          for (var i = 0; i < totalRows; ++i) {
   252  
   253            /** Add the table data to main data array */
   254            var c = $(table.tBodies[0].rows[i]),
   255              cols = [];
   256  
   257            // if this is a child row, add it to the last row's children and continue to the next row
   258            if (c.hasClass(table.config.cssChildRow)) {
   259              cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);
   260              // go to the next for loop
   261              continue;
   262            }
   263  
   264            cache.row.push(c);
   265  
   266            for (var j = 0; j < totalCells; ++j) {
   267              cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j]));
   268            }
   269  
   270            cols.push(cache.normalized.length); // add position for rowCache
   271            cache.normalized.push(cols);
   272            cols = null;
   273          };
   274  
   275          if (table.config.debug) {
   276            benchmark("Building cache for " + totalRows + " rows:", cacheTime);
   277          }
   278  
   279          return cache;
   280        };
   281  
   282        function getElementText(config, node) {
   283  
   284          if (!node) return "";
   285  
   286          var t = "";
   287  
   288          if (config.textExtraction == "simple") {
   289            if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) {
   290              t = node.childNodes[0].innerHTML;
   291            } else {
   292              t = node.innerHTML;
   293            }
   294          } else {
   295            if (typeof(config.textExtraction) == "function") {
   296              t = config.textExtraction(node);
   297            } else {
   298              t = $(node).text();
   299            }
   300          }
   301          return t;
   302        }
   303  
   304        function appendToTable(table, cache) {
   305  
   306          if (table.config.debug) {
   307            var appendTime = new Date();
   308          }
   309  
   310          var c = cache,
   311            r = c.row,
   312            n = c.normalized,
   313            totalRows = n.length,
   314            checkCell = (n[0].length - 1),
   315            tableBody = $(table.tBodies[0]),
   316            rows = [];
   317  
   318          for (var i = 0; i < totalRows; i++) {
   319            var pos = n[i][checkCell];
   320            rows.push(r[pos]);
   321            if (!table.config.appender) {
   322  
   323              var o = r[pos];
   324              var l = o.length;
   325              for (var j = 0; j < l; j++) {
   326                tableBody[0].appendChild(o[j]);
   327              }
   328  
   329              //tableBody.append(r[n[i][checkCell]]);
   330            }
   331          }
   332  
   333          if (table.config.appender) {
   334  
   335            table.config.appender(table, rows);
   336          }
   337  
   338          rows = null;
   339  
   340          if (table.config.debug) {
   341            benchmark("Rebuilt table:", appendTime);
   342          }
   343  
   344          //apply table widgets
   345          applyWidget(table);
   346  
   347          // trigger sortend
   348          setTimeout(function() {
   349            $(table).trigger("sortEnd");
   350          }, 0);
   351  
   352        };
   353  
   354        function buildHeaders(table) {
   355  
   356          if (table.config.debug) {
   357            var time = new Date();
   358          }
   359  
   360          var config = table.config;
   361          var meta = ($.metadata) ? true : false; //, tableHeadersRows = [];
   362  
   363          //for(var i = 0; i < table.tHead.rows.length; i++) { tableHeadersRows[i]=0; };
   364          $tableHeaders = $(config.selectorHeaders, table).each(function(index) {
   365  
   366            this.column = index;
   367            this.order = formatSortingOrder(config.sortInitialOrder);
   368            this.count = this.order;
   369  
   370            if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true;
   371  
   372            if (!this.sortDisabled) {
   373              if (config.useUI) {
   374                // add span element
   375                $(this).prepend('<span></span>').children("span").addClass(config.cssUI.icon);
   376                $(this).hover(function() {
   377                  $(this).addClass(config.cssUI.hover);
   378                }, function() {
   379                  $(this).removeClass(config.cssUI.hover);
   380                });
   381              } else {
   382                if (config.onRenderHeader) config.onRenderHeader.apply(this);
   383              }
   384            }
   385  
   386            // add cell to headerList
   387            config.headerList[index] = this;
   388          });
   389  
   390          if (table.config.debug) {
   391            benchmark("Built headers:", time);
   392            log($tableHeaders);
   393          }
   394  
   395          return $tableHeaders;
   396  
   397        };
   398  
   399        function setupCss(table, $headers) {
   400          var c = table.config;
   401  
   402          if (c.useUI) {
   403            $(table).parent("div:first").addClass(c.cssUI["widget"]);
   404            $headers.addClass(c.cssUI.header);
   405            return [c.cssUI.iconDesc, c.cssUI.iconAsc, c.cssUI.iconBoth];
   406  
   407          } else {
   408            $headers.addClass(c.cssHeader);
   409            return [c.cssDesc, c.cssAsc];
   410          }
   411        }
   412  
   413        function checkCellColSpan(table, rows, row) {
   414          var arr = [],
   415            r = table.tHead.rows,
   416            c = r[row].cells;
   417  
   418          for (var i = 0; i < c.length; i++) {
   419            var cell = c[i];
   420  
   421            if (cell.colSpan > 1) {
   422              arr = arr.concat(checkCellColSpan(table, headerArr, row++));
   423            } else {
   424              if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) {
   425                arr.push(cell);
   426              }
   427              //headerArr[row] = (i+row);
   428            }
   429          }
   430          return arr;
   431        };
   432  
   433        function checkHeaderMetadata(cell) {
   434          if (($.metadata) && ($(cell).metadata().sorter === false)) {
   435            return true;
   436          };
   437          return false;
   438        }
   439  
   440        function checkHeaderOptions(table, i) {
   441          if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) {
   442            return true;
   443          };
   444          return false;
   445        }
   446  
   447        function applyWidget(table) {
   448          var c = table.config.widgets;
   449          var l = c.length;
   450          for (var i = 0; i < l; i++) {
   451  
   452            getWidgetById(c[i]).format(table);
   453          }
   454  
   455        }
   456  
   457        function getWidgetById(name) {
   458          var l = widgets.length;
   459          for (var i = 0; i < l; i++) {
   460            if (widgets[i].id.toLowerCase() == name.toLowerCase()) {
   461              return widgets[i];
   462            }
   463          }
   464        };
   465  
   466        function formatSortingOrder(v) {
   467          if (typeof(v) != "Number") {
   468            return (v.toLowerCase() == "desc") ? 1 : 0;
   469          } else {
   470            return (v == 1) ? 1 : 0;
   471          }
   472        }
   473  
   474        function isValueInArray(v, a) {
   475          var l = a.length;
   476          for (var i = 0; i < l; i++) {
   477            if (a[i][0] == v) {
   478              return true;
   479            }
   480          }
   481          return false;
   482        }
   483  
   484        function setHeadersCss(table, $headers, list, css) {
   485          var c = table.config;
   486          // remove all header information
   487          if (c.useUI) {
   488            $headers.children("span").removeClass(css[0]).removeClass(css[1]).addClass(css[2]);
   489          } else {
   490            $headers.removeClass(css[0]).removeClass(css[1]);
   491          }
   492  
   493          var h = [];
   494          $headers.each(function(offset) {
   495            if (!this.sortDisabled) {
   496              h[this.column] = $(this);
   497            }
   498          });
   499  
   500          var l = list.length;
   501          for (var i = 0; i < l; i++) {
   502            if (c.useUI) {
   503              h[list[i][0]].children("span").removeClass(css[2]).addClass(css[list[i][1]]);
   504            } else {
   505              h[list[i][0]].addClass(css[list[i][1]]);
   506            }
   507          }
   508        }
   509  
   510        function fixColumnWidth(table, $headers) {
   511          var c = table.config;
   512          if (c.widthFixed) {
   513            var colgroup = $('<colgroup>');
   514            $("tr:first td", table.tBodies[0]).each(function() {
   515              colgroup.append($('<col>').css('width', $(this).width()));
   516            });
   517            $(table).prepend(colgroup);
   518          };
   519        }
   520  
   521        function updateHeaderSortCount(table, sortList) {
   522          var c = table.config,
   523            l = sortList.length;
   524          for (var i = 0; i < l; i++) {
   525            var s = sortList[i],
   526              o = c.headerList[s[0]];
   527            o.count = s[1];
   528            o.count++;
   529          }
   530        }
   531  
   532        /* sorting methods */
   533        function multisort(table, sortList, cache) {
   534  
   535          if (table.config.debug) {
   536            var sortTime = new Date();
   537          }
   538  
   539          var dynamicExp = "var sortWrapper = function(a,b) {",
   540            l = sortList.length;
   541  
   542          // TODO: inline functions.  
   543          for (var i = 0; i < l; i++) {
   544  
   545            var c = sortList[i][0];
   546            var order = sortList[i][1];
   547            //var s = (getCachedSortType(table.config.parsers,c) == "text") ? ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ? "sortNumeric" : "sortNumericDesc");
   548            //var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ? makeSortNumeric(c) : makeSortNumericDesc(c));
   549            var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c));
   550            var e = "e" + i;
   551  
   552            dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c + "]); ";
   553            dynamicExp += "if(" + e + ") { return " + e + "; } ";
   554            dynamicExp += "else { ";
   555  
   556          }
   557  
   558          // if value is the same keep orignal order  
   559          var orgOrderCol = cache.normalized[0].length - 1;
   560          dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";
   561  
   562          for (var i = 0; i < l; i++) {
   563            dynamicExp += "}; ";
   564          }
   565  
   566          dynamicExp += "return 0; ";
   567          dynamicExp += "}; ";
   568  
   569          if (table.config.debug) {
   570            benchmark("Evaling expression:" + dynamicExp, new Date());
   571          }
   572  
   573          eval(dynamicExp);
   574  
   575          cache.normalized.sort(sortWrapper);
   576  
   577          if (table.config.debug) {
   578            benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime);
   579          }
   580  
   581          return cache;
   582        };
   583  
   584        function makeSortFunction(type, direction, index) {
   585          var a = "a[" + index + "]",
   586            b = "b[" + index + "]";
   587          if (type == 'text' && direction == 'asc') {
   588            return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));";
   589          } else if (type == 'text' && direction == 'desc') {
   590            return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));";
   591          } else if (type == 'numeric' && direction == 'asc') {
   592            return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));";
   593          } else if (type == 'numeric' && direction == 'desc') {
   594            return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));";
   595          }
   596        };
   597  
   598        function makeSortText(i) {
   599          return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));";
   600        };
   601  
   602        function makeSortTextDesc(i) {
   603          return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));";
   604        };
   605  
   606        function makeSortNumeric(i) {
   607          return "a[" + i + "]-b[" + i + "];";
   608        };
   609  
   610        function makeSortNumericDesc(i) {
   611          return "b[" + i + "]-a[" + i + "];";
   612        };
   613  
   614  
   615        function sortText(a, b) {
   616          return ((a < b) ? -1 : ((a > b) ? 1 : 0));
   617        };
   618  
   619        function sortTextDesc(a, b) {
   620          return ((b < a) ? -1 : ((b > a) ? 1 : 0));
   621        };
   622  
   623        function sortNumeric(a, b) {
   624          return a - b;
   625        };
   626  
   627        function sortNumericDesc(a, b) {
   628          return b - a;
   629        };
   630  
   631        function getCachedSortType(parsers, i) {
   632          return parsers[i].type;
   633        };
   634  
   635        /* public methods */
   636        this.construct = function(settings) {
   637  
   638          return this.each(function() {
   639  
   640            if (!this.tHead || !this.tBodies) return;
   641  
   642            var $this, $document, $headers, cache, config, shiftDown = 0,
   643              sortOrder;
   644  
   645            this.config = {};
   646  
   647            config = $.extend(this.config, $.tablesorter.defaults, settings);
   648  
   649            // store common expression for speed  
   650            $this = $(this);
   651  
   652            // save the settings where they read
   653            $.data(this, "tablesorter", config);
   654  
   655            // build headers
   656            $headers = buildHeaders(this);
   657  
   658            // try to auto detect column type, and store in tables config
   659            this.config.parsers = buildParserCache(this, $headers);
   660  
   661            // build the cache for the tbody cells
   662            cache = buildCache(this);
   663  
   664            // get class names and setup UI if needed
   665            var sortCSS = setupCss(this, $headers);
   666  
   667            // fixate columns if the users supplies the fixedWidth option
   668            fixColumnWidth(this);
   669  
   670            // apply event handling to headers
   671            // this is to big, perhaps break it out?
   672            $headers.click(function(e) {
   673  
   674              var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;
   675  
   676              if (!this.sortDisabled && totalRows > 0) {
   677  
   678                // Only call sortStart if sorting is enabled.
   679                $this.trigger("sortStart");
   680  
   681                // store exp, for speed
   682                var $cell = $(this);
   683  
   684                // get current column index
   685                var i = this.column;
   686  
   687                // get current column sort order
   688                this.order = this.count++ % 2;
   689  
   690                // user only whants to sort on one column
   691                if (!e[config.sortMultiSortKey]) {
   692  
   693                  // flush the sort list
   694                  config.sortList = [];
   695  
   696                  if (config.sortForce != null) {
   697                    var a = config.sortForce;
   698                    for (var j = 0; j < a.length; j++) {
   699                      if (a[j][0] != i) {
   700                        config.sortList.push(a[j]);
   701                      }
   702                    }
   703                  }
   704  
   705                  // add column to sort list
   706                  config.sortList.push([i, this.order]);
   707  
   708                  // multi column sorting
   709                } else {
   710                  // the user has clicked on an all ready sortet column.
   711                  if (isValueInArray(i, config.sortList)) {
   712  
   713                    // revers the sorting direction for all tables.
   714                    for (var j = 0; j < config.sortList.length; j++) {
   715                      var s = config.sortList[j],
   716                        o = config.headerList[s[0]];
   717                      if (s[0] == i) {
   718                        o.count = s[1];
   719                        o.count++;
   720                        s[1] = o.count % 2;
   721                      }
   722                    }
   723                  } else {
   724                    // add column to sort list array
   725                    config.sortList.push([i, this.order]);
   726                  }
   727                };
   728                setTimeout(function() {
   729                  //set css for headers
   730                  setHeadersCss($this[0], $headers, config.sortList, sortCSS);
   731                  appendToTable($this[0], multisort($this[0], config.sortList, cache));
   732                }, 1);
   733                // stop normal event by returning false
   734                return false;
   735              }
   736              // cancel selection 
   737            }).mousedown(function() {
   738              if (config.cancelSelection) {
   739                this.onselectstart = function() {
   740                  return false;
   741                };
   742                return false;
   743              }
   744            });
   745  
   746            // apply easy methods that trigger binded events
   747            $this.bind("update", function() {
   748              var me = this;
   749              setTimeout(function() {
   750                // rebuild parsers.
   751                me.config.parsers = buildParserCache(me, $headers);
   752                // rebuild the cache map
   753                cache = buildCache(me);
   754              }, 1);
   755            }).bind("updateCell", function(e, cell) {
   756              var config = this.config;
   757              // get position from the dom.
   758              var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex];
   759              // update cache
   760              cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(getElementText(config, cell), cell);
   761  
   762            }).bind("sorton", function(e, list) {
   763  
   764              $(this).trigger("sortStart");
   765  
   766              config.sortList = list;
   767  
   768              // update and store the sortlist
   769              var sortList = config.sortList;
   770  
   771              // update header count index
   772              updateHeaderSortCount(this, sortList);
   773  
   774              //set css for headers
   775              setHeadersCss(this, $headers, sortList, sortCSS);
   776  
   777              // sort the table and append it to the dom
   778              appendToTable(this, multisort(this, sortList, cache));
   779  
   780            }).bind("appendCache", function() {
   781  
   782              appendToTable(this, cache);
   783  
   784            }).bind("applyWidgetId", function(e, id) {
   785  
   786              getWidgetById(id).format(this);
   787  
   788            }).bind("applyWidgets", function() {
   789              // apply widgets
   790              applyWidget(this);
   791            });
   792  
   793            if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {
   794              config.sortList = $(this).metadata().sortlist;
   795            }
   796            // if user has supplied a sort list to constructor.
   797            if (config.sortList.length > 0) {
   798              $this.trigger("sorton", [config.sortList]);
   799            } else {
   800              // apply widgets only if there is no sort list in constructor
   801              applyWidget(this);
   802            }
   803          });
   804        };
   805  
   806        this.addParser = function(parser) {
   807          var l = parsers.length,
   808            a = true;
   809          for (var i = 0; i < l; i++) {
   810            if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {
   811              a = false;
   812            }
   813          }
   814          if (a) {
   815            parsers.push(parser);
   816          };
   817        };
   818  
   819        this.addWidget = function(widget) {
   820          widgets.push(widget);
   821        };
   822  
   823        this.formatDate = function(s, config) {
   824          if (config.locale != "us") {
   825            var datePoint = '\\' + config.format[config.locale]["date"];
   826            s = s.replace(new RegExp('[\\-' + datePoint + ']', 'g'), config.format["us"]["date"]);
   827          }
   828          return s;
   829        };
   830        this.formatDecimal = function(s, config) {
   831          if (config.locale != "us") {
   832            s = s.replace(config.format[config.locale]["decimal"], config.format["us"]["decimal"]);
   833          }
   834          return s;
   835        };
   836        this.formatFloat = function(s) {
   837          var i = parseFloat(s);
   838          return (isNaN(i)) ? 0 : i;
   839        };
   840        this.formatInt = function(s) {
   841          var i = parseInt(s);
   842          return (isNaN(i)) ? 0 : i;
   843        };
   844  
   845        this.isDigit = function(s, config) {
   846          var decimalPoint = '\\' + config.format[config.locale]["decimal"];
   847          var exp = '/(^[+]?0(' + decimalPoint + '0+)?$)|(^([-+]?[0-9]*)$)|(^([-+]?((0?[0-9]*)' + decimalPoint + '(0*[0-9]*)))$)|(^[-+]?[0-9]*' + decimalPoint + '0+$)/';
   848          return RegExp(exp).test($.trim(s));
   849        };
   850  
   851        this.clearTableBody = function(table) {
   852          if ($.browser.msie) {
   853            function empty() {
   854              while (this.firstChild) this.removeChild(this.firstChild);
   855            }
   856            empty.apply(table.tBodies[0]);
   857          } else {
   858            table.tBodies[0].innerHTML = "";
   859          }
   860        };
   861      }
   862    });
   863  
   864    // extend plugin scope
   865    $.fn.extend({
   866      tablesorter: $.tablesorter.construct
   867    });
   868  
   869    // make shortcut
   870    var ts = $.tablesorter;
   871  
   872    // add default parsers
   873    ts.addParser({
   874      id: "text",
   875      is: function(s) {
   876        return true;
   877      },
   878      format: function(s) {
   879        return $.trim(s.toLowerCase());
   880      },
   881      type: "text"
   882    });
   883  
   884    ts.addParser({
   885      id: "digit",
   886      is: function(s, table) {
   887        var c = table.config;
   888        return $.tablesorter.isDigit(s, c);
   889      },
   890      format: function(s, table) {
   891        var c = table.config;
   892        s = $.tablesorter.formatDecimal(s, c);
   893        return $.tablesorter.formatFloat(s);
   894      },
   895      type: "numeric"
   896    });
   897  
   898    ts.addParser({
   899      id: "currency",
   900      is: function(s) {
   901        return /^[£$€?.,]/.test(s);
   902      },
   903      format: function(s, table) {
   904        var c = table.config;
   905        s = $.tablesorter.formatDecimal(s, c);
   906        return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), ""));
   907      },
   908      type: "numeric"
   909    });
   910  
   911    ts.addParser({
   912      id: "ipAddress",
   913      is: function(s) {
   914        return /^\d{2,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/.test(s);
   915      },
   916      format: function(s) {
   917        var a = s.split("."),
   918          r = "",
   919          l = a.length;
   920        for (var i = 0; i < l; i++) {
   921          var item = a[i];
   922          if (item.length == 2) {
   923            r += "0" + item;
   924          } else {
   925            r += item;
   926          }
   927        }
   928        return $.tablesorter.formatFloat(r);
   929      },
   930      type: "numeric"
   931    });
   932  
   933    ts.addParser({
   934      id: "url",
   935      is: function(s) {
   936        return /^(https?|ftp|file):\/\/$/.test(s);
   937      },
   938      format: function(s) {
   939        return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), ''));
   940      },
   941      type: "text"
   942    });
   943  
   944    ts.addParser({
   945      id: "isoDate",
   946      is: function(s) {
   947        return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);
   948      },
   949      format: function(s) {
   950        return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(new RegExp(/-/g), "/")).getTime() : "0");
   951      },
   952      type: "numeric"
   953    });
   954  
   955    ts.addParser({
   956      id: "percent",
   957      is: function(s) {
   958        return /\%$/.test($.trim(s));
   959      },
   960      format: function(s, table) {
   961        var c = table.config;
   962        s = $.tablesorter.formatDecimal(s, c);
   963        return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), ""));
   964      },
   965      type: "numeric"
   966    });
   967  
   968    ts.addParser({
   969      id: "usLongDate",
   970      is: function(s) {
   971        return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));
   972      },
   973      format: function(s) {
   974        return $.tablesorter.formatFloat(new Date(s).getTime());
   975      },
   976      type: "numeric"
   977    });
   978  
   979    ts.addParser({
   980      id: "mediumDate",
   981      is: function(s, table) {
   982        var c = table.config;
   983        var datePoint = '\\' + c.format[c.locale]["date"];
   984        var expStr = '\\d{1,2}[\\-' + datePoint + ']\\d{1,2}[\\-' + datePoint + ']\\d{4}';
   985        return RegExp(expStr).test(s);
   986      },
   987      format: function(s, table) {
   988        var c = table.config;
   989        s = $.tablesorter.formatDate(s, c);
   990        if (c.locale == "us") {
   991          // reformat the string in ISO format
   992          s = s.replace(/(\d{1,2})[\/](\d{1,2})[\/](\d{4})/, "$3/$1/$2");
   993        } else if (c.locale == "en" || c.locale == "de" || c.locale == "eu") {
   994          //reformat the string in ISO format
   995          s = s.replace(/(\d{1,2})[\/](\d{1,2})[\/](\d{4})/, "$3/$2/$1");
   996        }
   997        return $.tablesorter.formatFloat((s != "") ? new Date(s).getTime() : 0);
   998      },
   999      type: "numeric"
  1000    });
  1001  
  1002    ts.addParser({
  1003      id: "shortDate",
  1004      is: function(s, table) {
  1005        var c = table.config;
  1006        var datePoint = '\\' + c.format[c.locale]["date"];
  1007        var expStr = '\\d{1,2}[\\-' + datePoint + ']\\d{1,2}[\\-' + datePoint + ']\\d{2}';
  1008        return RegExp(expStr).test(s);
  1009      },
  1010      format: function(s, table) {
  1011        var c = table.config;
  1012        s = $.tablesorter.formatDate(s, c);
  1013        if (c.locale == "us") {
  1014          // reformat the string in non-ISO format
  1015          s = s.replace(/(\d{1,2})[\/](\d{1,2})[\/](\d{2})/, "$1/$2/$3");
  1016        } else if (c.locale == "en" || c.locale == "de" || c.locale == "eu") {
  1017          //reformat the string in non-ISO format
  1018          s = s.replace(/(\d{1,2})[\/](\d{1,2})[\/](\d{2})/, "$2/$1/$3");
  1019        }
  1020        return $.tablesorter.formatFloat((s != "") ? new Date(s).getTime() : 0);
  1021      },
  1022      type: "numeric"
  1023    });
  1024  
  1025  
  1026    ts.addParser({
  1027      id: "time",
  1028      is: function(s) {
  1029        return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);
  1030      },
  1031      format: function(s) {
  1032        return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime());
  1033      },
  1034      type: "numeric"
  1035    });
  1036  
  1037    ts.addParser({
  1038      id: "metadata",
  1039      is: function(s) {
  1040        return false;
  1041      },
  1042      format: function(s, table, cell) {
  1043        var c = table.config,
  1044          p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
  1045        return $(cell).metadata()[p];
  1046      },
  1047      type: "numeric"
  1048    });
  1049  
  1050    // add default widgets
  1051    ts.addWidget({
  1052      id: "zebra",
  1053      format: function(table) {
  1054        if (table.config.debug) {
  1055          var time = new Date();
  1056        }
  1057        var $tr, row = -1,
  1058          odd;
  1059        // loop through the visible rows
  1060        $("tr:visible", table.tBodies[0]).each(function(i) {
  1061          $tr = $(this);
  1062          // style children rows the same way the parent row was styled
  1063          if (!$tr.hasClass(table.config.cssChildRow)) row++;
  1064          odd = (row % 2 == 0);
  1065          $tr.removeClass(table.config.widgetZebra.css[odd ? 0 : 1]).addClass(table.config.widgetZebra.css[odd ? 1 : 0]);
  1066        });
  1067        if (table.config.debug) {
  1068          $.tablesorter.benchmark("Applying Zebra widget", time);
  1069        }
  1070      }
  1071    });
  1072  })(jQuery);