Ignore:
Timestamp:
Feb 4, 2014, 10:46:10 PM (10 years ago)
Author:
LucMorizur
Message:

Version 2.3.0, compatibility with Piwigo 2.6

File:
1 edited

Legend:

Unmodified
Added
Removed
  • extensions/LCAS/branch/2.3/admin/template/js/jquery.tablesorter.js

    r27127 r27186  
    1 /*
    2  *
    3  * TableSorter 2.0 - Client-side table sorting with ease!
    4  * Version 2.0.5b
    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 column headers.
    23  *
    24  * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } });
    25  *         
    26  * @desc Create a tablesorter interface and disableing the first and second  column headers.
    27  *     
    28  *
    29  * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } });
    30  *
    31  * @desc Create a tablesorter interface and set a column parser for the first
    32  *       and second column.
    33  *
    34  *
    35  * @param Object
    36  *            settings An object literal containing key/value pairs to provide
    37  *            optional settings.
    38  *
    39  *
    40  * @option String cssHeader (optional) A string of the class name to be appended
    41  *         to sortable tr elements in the thead of the table. Default value:
    42  *         "header"
    43  *
    44  * @option String cssAsc (optional) A string of the class name to be appended to
    45  *         sortable tr elements in the thead on a ascending sort. Default value:
    46  *         "headerSortUp"
    47  *
    48  * @option String cssDesc (optional) A string of the class name to be appended
    49  *         to sortable tr elements in the thead on a descending sort. Default
    50  *         value: "headerSortDown"
    51  *
    52  * @option String sortInitialOrder (optional) A string of the inital sorting
    53  *         order can be asc or desc. Default value: "asc"
    54  *
    55  * @option String sortMultisortKey (optional) A string of the multi-column sort
    56  *         key. Default value: "shiftKey"
    57  *
    58  * @option String textExtraction (optional) A string of the text-extraction
    59  *         method to use. For complex html structures inside td cell set this
    60  *         option to "complex", on large tables the complex option can be slow.
    61  *         Default value: "simple"
    62  *
    63  * @option Object headers (optional) An array containing the forces sorting
    64  *         rules. This option let's you specify a default sorting rule. Default
    65  *         value: null
    66  *
    67  * @option Array sortList (optional) An array containing the forces sorting
    68  *         rules. This option let's you specify a default sorting rule. Default
    69  *         value: null
    70  *
    71  * @option Array sortForce (optional) An array containing forced sorting rules.
    72  *         This option let's you specify a default sorting rule, which is
    73  *         prepended to user-selected rules. Default value: null
    74  *
    75  * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever
    76  *         to use String.localeCampare method or not. Default set to true.
    77  *
    78  *
    79  * @option Array sortAppend (optional) An array containing forced sorting rules.
    80  *         This option let's you specify a default sorting rule, which is
    81  *         appended to user-selected rules. Default value: null
    82  *
    83  * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter
    84  *         should apply fixed widths to the table columns. This is usefull when
    85  *         using the pager companion plugin. This options requires the dimension
    86  *         jquery plugin. Default value: false
    87  *
    88  * @option Boolean cancelSelection (optional) Boolean flag indicating if
    89  *         tablesorter should cancel selection of the table headers text.
    90  *         Default value: true
    91  *
    92  * @option Boolean debug (optional) Boolean flag indicating if tablesorter
    93  *         should display debuging information usefull for development.
    94  *
    95  * @type jQuery
    96  *
    97  * @name tablesorter
    98  *
    99  * @cat Plugins/Tablesorter
    100  *
    101  * @author Christian Bach/christian.bach@polyester.se
    102  */
    103 
    104 (function ($) {
    105     $.extend({
    106         tablesorter: new
    107         function () {
    108 
    109             var parsers = [],
    110                 widgets = [];
    111 
    112             this.defaults = {
    113                 cssHeader: "header",
    114                 cssAsc: "headerSortUp",
    115                 cssDesc: "headerSortDown",
    116                 cssChildRow: "expand-child",
    117                 sortInitialOrder: "asc",
    118                 sortMultiSortKey: "shiftKey",
    119                 sortForce: null,
    120                 sortAppend: null,
    121                 sortLocaleCompare: true,
    122                 textExtraction: "simple",
    123                 parsers: {}, widgets: [],
    124                 widgetZebra: {
    125                     css: ["even", "odd"]
    126                 }, headers: {}, widthFixed: false,
    127                 cancelSelection: true,
    128                 sortList: [],
    129                 headerList: [],
    130                 dateFormat: "us",
    131                 decimal: '/\.|\,/g',
    132                 onRenderHeader: null,
    133                 selectorHeaders: 'thead th',
    134                 debug: false
    135             };
    136 
    137             /* debuging utils */
    138 
    139             function benchmark(s, d) {
    140                 log(s + "," + (new Date().getTime() - d.getTime()) + "ms");
    141             }
    142 
    143             this.benchmark = benchmark;
    144 
    145             function log(s) {
    146                 if (typeof console != "undefined" && typeof console.debug != "undefined") {
    147                     console.log(s);
    148                 } else {
    149                     alert(s);
    150                 }
    151             }
    152 
    153             /* parsers utils */
    154 
    155             function buildParserCache(table, $headers) {
    156 
    157                 if (table.config.debug) {
    158                     var parsersDebug = "";
    159                 }
    160 
    161                 if (table.tBodies.length == 0) return; // In the case of empty tables
    162                 var rows = table.tBodies[0].rows;
    163 
    164                 if (rows[0]) {
    165 
    166                     var list = [],
    167                         cells = rows[0].cells,
    168                         l = cells.length;
    169 
    170                     for (var i = 0; i < l; i++) {
    171 
    172                         var p = false;
    173 
    174                         if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) {
    175 
    176                             p = getParserById($($headers[i]).metadata().sorter);
    177 
    178                         } else if ((table.config.headers[i] && table.config.headers[i].sorter)) {
    179 
    180                             p = getParserById(table.config.headers[i].sorter);
    181                         }
    182                         if (!p) {
    183 
    184                             p = detectParserForColumn(table, rows, -1, i);
    185                         }
    186 
    187                         if (table.config.debug) {
    188                             parsersDebug += "column:" + i + " parser:" + p.id + "\n";
    189                         }
    190 
    191                         list.push(p);
    192                     }
    193                 }
    194 
    195                 if (table.config.debug) {
    196                     log(parsersDebug);
    197                 }
    198 
    199                 return list;
    200             };
    201 
    202             function detectParserForColumn(table, rows, rowIndex, cellIndex) {
    203                 var l = parsers.length,
    204                     node = false,
    205                     nodeValue = false,
    206                     keepLooking = true;
    207                 while (nodeValue == '' && keepLooking) {
    208                     rowIndex++;
    209                     if (rows[rowIndex]) {
    210                         node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex);
    211                         nodeValue = trimAndGetNodeText(table.config, node);
    212                         if (table.config.debug) {
    213                             log('Checking if value was empty on row:' + rowIndex);
    214                         }
    215                     } else {
    216                         keepLooking = false;
    217                     }
    218                 }
    219                 for (var i = 1; i < l; i++) {
    220                     if (parsers[i].is(nodeValue, table, node)) {
    221                         return parsers[i];
    222                     }
    223                 }
    224                 // 0 is always the generic parser (text)
    225                 return parsers[0];
    226             }
    227 
    228             function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) {
    229                 return rows[rowIndex].cells[cellIndex];
    230             }
    231 
    232             function trimAndGetNodeText(config, node) {
    233                 return $.trim(getElementText(config, node));
    234             }
    235 
    236             function getParserById(name) {
    237                 var l = parsers.length;
    238                 for (var i = 0; i < l; i++) {
    239                     if (parsers[i].id.toLowerCase() == name.toLowerCase()) {
    240                         return parsers[i];
    241                     }
    242                 }
    243                 return false;
    244             }
    245 
    246             /* utils */
    247 
    248             function buildCache(table) {
    249 
    250                 if (table.config.debug) {
    251                     var cacheTime = new Date();
    252                 }
    253 
    254                 var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0,
    255                     totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0,
    256                     parsers = table.config.parsers,
    257                     cache = {
    258                         row: [],
    259                         normalized: []
    260                     };
    261 
    262                 for (var i = 0; i < totalRows; ++i) {
    263 
    264                     /** Add the table data to main data array */
    265                     var c = $(table.tBodies[0].rows[i]),
    266                         cols = [];
    267 
    268                     // if this is a child row, add it to the last row's children and
    269                     // continue to the next row
    270                     if (c.hasClass(table.config.cssChildRow)) {
    271                         cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c);
    272                         // go to the next for loop
    273                         continue;
    274                     }
    275 
    276                     cache.row.push(c);
    277 
    278                     for (var j = 0; j < totalCells; ++j) {
    279                         cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j]));
    280                     }
    281 
    282                     cols.push(cache.normalized.length); // add position for rowCache
    283                     cache.normalized.push(cols);
    284                     cols = null;
    285                 };
    286 
    287                 if (table.config.debug) {
    288                     benchmark("Building cache for " + totalRows + " rows:", cacheTime);
    289                 }
    290 
    291                 return cache;
    292             };
    293 
    294             function getElementText(config, node) {
    295 
    296                 var text = "";
    297 
    298                 if (!node) return "";
    299 
    300                 if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false;
    301 
    302                 if (config.textExtraction == "simple") {
    303                     if (config.supportsTextContent) {
    304                         text = node.textContent;
    305                     } else {
    306                         if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) {
    307                             text = node.childNodes[0].innerHTML;
    308                         } else {
    309                             text = node.innerHTML;
    310                         }
    311                     }
    312                 } else {
    313                     if (typeof(config.textExtraction) == "function") {
    314                         text = config.textExtraction(node);
    315                     } else {
    316                         text = $(node).text();
    317                     }
    318                 }
    319                 return text;
    320             }
    321 
    322             function appendToTable(table, cache) {
    323 
    324                 if (table.config.debug) {
    325                     var appendTime = new Date()
    326                 }
    327 
    328                 var c = cache,
    329                     r = c.row,
    330                     n = c.normalized,
    331                     totalRows = n.length,
    332                     checkCell = (n[0].length - 1),
    333                     tableBody = $(table.tBodies[0]),
    334                     rows = [];
    335 
    336 
    337                 for (var i = 0; i < totalRows; i++) {
    338                     var pos = n[i][checkCell];
    339 
    340                     rows.push(r[pos]);
    341 
    342                     if (!table.config.appender) {
    343 
    344                         //var o = ;
    345                         var l = r[pos].length;
    346                         for (var j = 0; j < l; j++) {
    347                             tableBody[0].appendChild(r[pos][j]);
    348                         }
    349 
    350                         //
    351                     }
    352                 }
    353 
    354 
    355 
    356                 if (table.config.appender) {
    357 
    358                     table.config.appender(table, rows);
    359                 }
    360 
    361                 rows = null;
    362 
    363                 if (table.config.debug) {
    364                     benchmark("Rebuilt table:", appendTime);
    365                 }
    366 
    367                 // apply table widgets
    368                 applyWidget(table);
    369 
    370                 // trigger sortend
    371                 setTimeout(function () {
    372                     $(table).trigger("sortEnd");
    373                 }, 0);
    374 
    375             };
    376 
    377             function buildHeaders(table) {
    378 
    379                 if (table.config.debug) {
    380                     var time = new Date();
    381                 }
    382 
    383                 var meta = ($.metadata) ? true : false;
    384                
    385                 var header_index = computeTableHeaderCellIndexes(table);
    386 
    387                 $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) {
    388 
    389                     this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
    390                     // this.column = index;
    391                     this.order = formatSortingOrder(table.config.sortInitialOrder);
    392                    
    393                                        
    394                                         this.count = this.order;
    395 
    396                     if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true;
    397                                         if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index);
    398 
    399                     if (!this.sortDisabled) {
    400                         var $th = $(this).addClass(table.config.cssHeader);
    401                         if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th);
    402                     }
    403 
    404                     // add cell to headerList
    405                     table.config.headerList[index] = this;
    406                 });
    407 
    408                 if (table.config.debug) {
    409                     benchmark("Built headers:", time);
    410                     log($tableHeaders);
    411                 }
    412 
    413                 return $tableHeaders;
    414 
    415             };
    416 
    417             // from:
    418             // http://www.javascripttoolbox.com/lib/table/examples.php
    419             // http://www.javascripttoolbox.com/temp/table_cellindex.html
    420 
    421 
    422             function computeTableHeaderCellIndexes(t) {
    423                 var matrix = [];
    424                 var lookup = {};
    425                 var thead = t.getElementsByTagName('THEAD')[0];
    426                 var trs = thead.getElementsByTagName('TR');
    427 
    428                 for (var i = 0; i < trs.length; i++) {
    429                     var cells = trs[i].cells;
    430                     for (var j = 0; j < cells.length; j++) {
    431                         var c = cells[j];
    432 
    433                         var rowIndex = c.parentNode.rowIndex;
    434                         var cellId = rowIndex + "-" + c.cellIndex;
    435                         var rowSpan = c.rowSpan || 1;
    436                         var colSpan = c.colSpan || 1
    437                         var firstAvailCol;
    438                         if (typeof(matrix[rowIndex]) == "undefined") {
    439                             matrix[rowIndex] = [];
    440                         }
    441                         // Find first available column in the first row
    442                         for (var k = 0; k < matrix[rowIndex].length + 1; k++) {
    443                             if (typeof(matrix[rowIndex][k]) == "undefined") {
    444                                 firstAvailCol = k;
    445                                 break;
    446                             }
    447                         }
    448                         lookup[cellId] = firstAvailCol;
    449                         for (var k = rowIndex; k < rowIndex + rowSpan; k++) {
    450                             if (typeof(matrix[k]) == "undefined") {
    451                                 matrix[k] = [];
    452                             }
    453                             var matrixrow = matrix[k];
    454                             for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
    455                                 matrixrow[l] = "x";
    456                             }
    457                         }
    458                     }
    459                 }
    460                 return lookup;
    461             }
    462 
    463             function checkCellColSpan(table, rows, row) {
    464                 var arr = [],
    465                     r = table.tHead.rows,
    466                     c = r[row].cells;
    467 
    468                 for (var i = 0; i < c.length; i++) {
    469                     var cell = c[i];
    470 
    471                     if (cell.colSpan > 1) {
    472                         arr = arr.concat(checkCellColSpan(table, headerArr, row++));
    473                     } else {
    474                         if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) {
    475                             arr.push(cell);
    476                         }
    477                         // headerArr[row] = (i+row);
    478                     }
    479                 }
    480                 return arr;
    481             };
    482 
    483             function checkHeaderMetadata(cell) {
    484                 if (($.metadata) && ($(cell).metadata().sorter === false)) {
    485                     return true;
    486                 };
    487                 return false;
    488             }
    489 
    490             function checkHeaderOptions(table, i) {
    491                 if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) {
    492                     return true;
    493                 };
    494                 return false;
    495             }
    496                        
    497                          function checkHeaderOptionsSortingLocked(table, i) {
    498                 if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder;
    499                 return false;
    500             }
    501                        
    502             function applyWidget(table) {
    503                 var c = table.config.widgets;
    504                 var l = c.length;
    505                 for (var i = 0; i < l; i++) {
    506 
    507                     getWidgetById(c[i]).format(table);
    508                 }
    509 
    510             }
    511 
    512             function getWidgetById(name) {
    513                 var l = widgets.length;
    514                 for (var i = 0; i < l; i++) {
    515                     if (widgets[i].id.toLowerCase() == name.toLowerCase()) {
    516                         return widgets[i];
    517                     }
    518                 }
    519             };
    520 
    521             function formatSortingOrder(v) {
    522                 if (typeof(v) != "Number") {
    523                     return (v.toLowerCase() == "desc") ? 1 : 0;
    524                 } else {
    525                     return (v == 1) ? 1 : 0;
    526                 }
    527             }
    528 
    529             function isValueInArray(v, a) {
    530                 var l = a.length;
    531                 for (var i = 0; i < l; i++) {
    532                     if (a[i][0] == v) {
    533                         return true;
    534                     }
    535                 }
    536                 return false;
    537             }
    538 
    539             function setHeadersCss(table, $headers, list, css) {
    540                 // remove all header information
    541                 $headers.removeClass(css[0]).removeClass(css[1]);
    542 
    543                 var h = [];
    544                 $headers.each(function (offset) {
    545                     if (!this.sortDisabled) {
    546                         h[this.column] = $(this);
    547                     }
    548                 });
    549 
    550                 var l = list.length;
    551                 for (var i = 0; i < l; i++) {
    552                     h[list[i][0]].addClass(css[list[i][1]]);
    553                 }
    554             }
    555 
    556             function fixColumnWidth(table, $headers) {
    557                 var c = table.config;
    558                 if (c.widthFixed) {
    559                     var colgroup = $('<colgroup>');
    560                     $("tr:first td", table.tBodies[0]).each(function () {
    561                         colgroup.append($('<col>').css('width', $(this).width()));
    562                     });
    563                     $(table).prepend(colgroup);
    564                 };
    565             }
    566 
    567             function updateHeaderSortCount(table, sortList) {
    568                 var c = table.config,
    569                     l = sortList.length;
    570                 for (var i = 0; i < l; i++) {
    571                     var s = sortList[i],
    572                         o = c.headerList[s[0]];
    573                     o.count = s[1];
    574                     o.count++;
    575                 }
    576             }
    577 
    578             /* sorting methods */
    579 
    580             function multisort(table, sortList, cache) {
    581 
    582                 if (table.config.debug) {
    583                     var sortTime = new Date();
    584                 }
    585 
    586                 var dynamicExp = "var sortWrapper = function(a,b) {",
    587                     l = sortList.length;
    588 
    589                 // TODO: inline functions.
    590                 for (var i = 0; i < l; i++) {
    591 
    592                     var c = sortList[i][0];
    593                     var order = sortList[i][1];
    594                     // var s = (getCachedSortType(table.config.parsers,c) == "text") ?
    595                     // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ?
    596                     // "sortNumeric" : "sortNumericDesc");
    597                     // var s = (table.config.parsers[c].type == "text") ? ((order == 0)
    598                     // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ?
    599                     // makeSortNumeric(c) : makeSortNumericDesc(c));
    600                     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));
    601                     var e = "e" + i;
    602 
    603                     dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c
    604                     // + "]); ";
    605                     dynamicExp += "if(" + e + ") { return " + e + "; } ";
    606                     dynamicExp += "else { ";
    607 
    608                 }
    609 
    610                 // if value is the same keep orignal order
    611                 var orgOrderCol = cache.normalized[0].length - 1;
    612                 dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];";
    613 
    614                 for (var i = 0; i < l; i++) {
    615                     dynamicExp += "}; ";
    616                 }
    617 
    618                 dynamicExp += "return 0; ";
    619                 dynamicExp += "}; ";
    620 
    621                 if (table.config.debug) {
    622                     benchmark("Evaling expression:" + dynamicExp, new Date());
    623                 }
    624 
    625                 eval(dynamicExp);
    626 
    627                 cache.normalized.sort(sortWrapper);
    628 
    629                 if (table.config.debug) {
    630                     benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime);
    631                 }
    632 
    633                 return cache;
    634             };
    635 
    636             function makeSortFunction(type, direction, index) {
    637                 var a = "a[" + index + "]",
    638                     b = "b[" + index + "]";
    639                 if (type == 'text' && direction == 'asc') {
    640                     return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));";
    641                 } else if (type == 'text' && direction == 'desc') {
    642                     return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));";
    643                 } else if (type == 'numeric' && direction == 'asc') {
    644                     return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));";
    645                 } else if (type == 'numeric' && direction == 'desc') {
    646                     return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));";
    647                 }
    648             };
    649 
    650             function makeSortText(i) {
    651                 return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));";
    652             };
    653 
    654             function makeSortTextDesc(i) {
    655                 return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));";
    656             };
    657 
    658             function makeSortNumeric(i) {
    659                 return "a[" + i + "]-b[" + i + "];";
    660             };
    661 
    662             function makeSortNumericDesc(i) {
    663                 return "b[" + i + "]-a[" + i + "];";
    664             };
    665 
    666             function sortText(a, b) {
    667                 if (table.config.sortLocaleCompare) return a.localeCompare(b);
    668                 return ((a < b) ? -1 : ((a > b) ? 1 : 0));
    669             };
    670 
    671             function sortTextDesc(a, b) {
    672                 if (table.config.sortLocaleCompare) return b.localeCompare(a);
    673                 return ((b < a) ? -1 : ((b > a) ? 1 : 0));
    674             };
    675 
    676             function sortNumeric(a, b) {
    677                 return a - b;
    678             };
    679 
    680             function sortNumericDesc(a, b) {
    681                 return b - a;
    682             };
    683 
    684             function getCachedSortType(parsers, i) {
    685                 return parsers[i].type;
    686             }; /* public methods */
    687             this.construct = function (settings) {
    688                 return this.each(function () {
    689                     // if no thead or tbody quit.
    690                     if (!this.tHead || !this.tBodies) return;
    691                     // declare
    692                     var $this, $document, $headers, cache, config, shiftDown = 0,
    693                         sortOrder;
    694                     // new blank config object
    695                     this.config = {};
    696                     // merge and extend.
    697                     config = $.extend(this.config, $.tablesorter.defaults, settings);
    698                     // store common expression for speed
    699                     $this = $(this);
    700                     // save the settings where they read
    701                     $.data(this, "tablesorter", config);
    702                     // build headers
    703                     $headers = buildHeaders(this);
    704                     // try to auto detect column type, and store in tables config
    705                     this.config.parsers = buildParserCache(this, $headers);
    706                     // build the cache for the tbody cells
    707                     cache = buildCache(this);
    708                     // get the css class names, could be done else where.
    709                     var sortCSS = [config.cssDesc, config.cssAsc];
    710                     // fixate columns if the users supplies the fixedWidth option
    711                     fixColumnWidth(this);
    712                     // apply event handling to headers
    713                     // this is to big, perhaps break it out?
    714                     $headers.click(
    715 
    716                     function (e) {
    717                         var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0;
    718                         if (!this.sortDisabled && totalRows > 0) {
    719                             // Only call sortStart if sorting is
    720                             // enabled.
    721                             $this.trigger("sortStart");
    722                             // store exp, for speed
    723                             var $cell = $(this);
    724                             // get current column index
    725                             var i = this.column;
    726                             // get current column sort order
    727                             this.order = this.count++ % 2;
    728                                                         // always sort on the locked order.
    729                                                         if(this.lockedOrder) this.order = this.lockedOrder;
    730                                                        
    731                                                         // user only whants to sort on one
    732                             // column
    733                             if (!e[config.sortMultiSortKey]) {
    734                                 // flush the sort list
    735                                 config.sortList = [];
    736                                 if (config.sortForce != null) {
    737                                     var a = config.sortForce;
    738                                     for (var j = 0; j < a.length; j++) {
    739                                         if (a[j][0] != i) {
    740                                             config.sortList.push(a[j]);
    741                                         }
    742                                     }
    743                                 }
    744                                 // add column to sort list
    745                                 config.sortList.push([i, this.order]);
    746                                 // multi column sorting
    747                             } else {
    748                                 // the user has clicked on an all
    749                                 // ready sortet column.
    750                                 if (isValueInArray(i, config.sortList)) {
    751                                     // revers the sorting direction
    752                                     // for all tables.
    753                                     for (var j = 0; j < config.sortList.length; j++) {
    754                                         var s = config.sortList[j],
    755                                             o = config.headerList[s[0]];
    756                                         if (s[0] == i) {
    757                                             o.count = s[1];
    758                                             o.count++;
    759                                             s[1] = o.count % 2;
    760                                         }
    761                                     }
    762                                 } else {
    763                                     // add column to sort list array
    764                                     config.sortList.push([i, this.order]);
    765                                 }
    766                             };
    767                             setTimeout(function () {
    768                                 // set css for headers
    769                                 setHeadersCss($this[0], $headers, config.sortList, sortCSS);
    770                                 appendToTable(
    771                                         $this[0], multisort(
    772                                         $this[0], config.sortList, cache)
    773                                                                 );
    774                             }, 1);
    775                             // stop normal event by returning false
    776                             return false;
    777                         }
    778                         // cancel selection
    779                     }).mousedown(function () {
    780                         if (config.cancelSelection) {
    781                             this.onselectstart = function () {
    782                                 return false
    783                             };
    784                             return false;
    785                         }
    786                     });
    787                     // apply easy methods that trigger binded events
    788                     $this.bind("update", function () {
    789                         var me = this;
    790                         setTimeout(function () {
    791                             // rebuild parsers.
    792                             me.config.parsers = buildParserCache(
    793                             me, $headers);
    794                             // rebuild the cache map
    795                             cache = buildCache(me);
    796                         }, 1);
    797                     }).bind("updateCell", function (e, cell) {
    798                         var config = this.config;
    799                         // get position from the dom.
    800                         var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex];
    801                         // update cache
    802                         cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(
    803                         getElementText(config, cell), cell);
    804                     }).bind("sorton", function (e, list) {
    805                         $(this).trigger("sortStart");
    806                         config.sortList = list;
    807                         // update and store the sortlist
    808                         var sortList = config.sortList;
    809                         // update header count index
    810                         updateHeaderSortCount(this, sortList);
    811                         // set css for headers
    812                         setHeadersCss(this, $headers, sortList, sortCSS);
    813                         // sort the table and append it to the dom
    814                         appendToTable(this, multisort(this, sortList, cache));
    815                     }).bind("appendCache", function () {
    816                         appendToTable(this, cache);
    817                     }).bind("applyWidgetId", function (e, id) {
    818                         getWidgetById(id).format(this);
    819                     }).bind("applyWidgets", function () {
    820                         // apply widgets
    821                         applyWidget(this);
    822                     });
    823                     if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) {
    824                         config.sortList = $(this).metadata().sortlist;
    825                     }
    826                     // if user has supplied a sort list to constructor.
    827                     if (config.sortList.length > 0) {
    828                         $this.trigger("sorton", [config.sortList]);
    829                     }
    830                     // apply widgets
    831                     applyWidget(this);
    832                 });
    833             };
    834             this.addParser = function (parser) {
    835                 var l = parsers.length,
    836                     a = true;
    837                 for (var i = 0; i < l; i++) {
    838                     if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) {
    839                         a = false;
    840                     }
    841                 }
    842                 if (a) {
    843                     parsers.push(parser);
    844                 };
    845             };
    846             this.addWidget = function (widget) {
    847                 widgets.push(widget);
    848             };
    849             this.formatFloat = function (s) {
    850                 var i = parseFloat(s);
    851                 return (isNaN(i)) ? 0 : i;
    852             };
    853             this.formatInt = function (s) {
    854                 var i = parseInt(s);
    855                 return (isNaN(i)) ? 0 : i;
    856             };
    857             this.isDigit = function (s, config) {
    858                 // replace all an wanted chars and match.
    859                 return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, '')));
    860             };
    861             this.clearTableBody = function (table) {
    862                 if ($.browser.msie) {
    863                     function empty() {
    864                         while (this.firstChild)
    865                         this.removeChild(this.firstChild);
    866                     }
    867                     empty.apply(table.tBodies[0]);
    868                 } else {
    869                     table.tBodies[0].innerHTML = "";
    870                 }
    871             };
    872         }
    873     });
    874 
    875     // extend plugin scope
    876     $.fn.extend({
    877         tablesorter: $.tablesorter.construct
    878     });
    879 
    880     // make shortcut
    881     var ts = $.tablesorter;
    882 
    883     // add default parsers
    884     ts.addParser({
    885         id: "text",
    886         is: function (s) {
    887             return true;
    888         }, format: function (s) {
    889             return $.trim(s.toLocaleLowerCase());
    890         }, type: "text"
    891     });
    892 
    893     ts.addParser({
    894         id: "digit",
    895         is: function (s, table) {
    896             var c = table.config;
    897             return $.tablesorter.isDigit(s, c);
    898         }, format: function (s) {
    899             return $.tablesorter.formatFloat(s);
    900         }, type: "numeric"
    901     });
    902 
    903     ts.addParser({
    904         id: "currency",
    905         is: function (s) {
    906             return /^[£$€?.]/.test(s);
    907         }, format: function (s) {
    908             return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), ""));
    909         }, type: "numeric"
    910     });
    911 
    912     ts.addParser({
    913         id: "ipAddress",
    914         is: function (s) {
    915             return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);
    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         }, type: "numeric"
    930     });
    931 
    932     ts.addParser({
    933         id: "url",
    934         is: function (s) {
    935             return /^(https?|ftp|file):\/\/$/.test(s);
    936         }, format: function (s) {
    937             return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), ''));
    938         }, type: "text"
    939     });
    940 
    941     ts.addParser({
    942         id: "isoDate",
    943         is: function (s) {
    944             return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);
    945         }, format: function (s) {
    946             return $.tablesorter.formatFloat((s != "") ? new Date(s.replace(
    947             new RegExp(/-/g), "/")).getTime() : "0");
    948         }, type: "numeric"
    949     });
    950 
    951     ts.addParser({
    952         id: "percent",
    953         is: function (s) {
    954             return /\%$/.test($.trim(s));
    955         }, format: function (s) {
    956             return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), ""));
    957         }, type: "numeric"
    958     });
    959 
    960     ts.addParser({
    961         id: "usLongDate",
    962         is: function (s) {
    963             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)))$/));
    964         }, format: function (s) {
    965             return $.tablesorter.formatFloat(new Date(s).getTime());
    966         }, type: "numeric"
    967     });
    968 
    969     ts.addParser({
    970         id: "shortDate",
    971         is: function (s) {
    972             return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);
    973         }, format: function (s, table) {
    974             var c = table.config;
    975             s = s.replace(/\-/g, "/");
    976             if (c.dateFormat == "us") {
    977                 // reformat the string in ISO format
    978                 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2");
    979             } else if (c.dateFormat == "uk") {
    980                 // reformat the string in ISO format
    981                 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1");
    982             } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") {
    983                 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3");
    984             }
    985             return $.tablesorter.formatFloat(new Date(s).getTime());
    986         }, type: "numeric"
    987     });
    988     ts.addParser({
    989         id: "time",
    990         is: function (s) {
    991             return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);
    992         }, format: function (s) {
    993             return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime());
    994         }, type: "numeric"
    995     });
    996     ts.addParser({
    997         id: "metadata",
    998         is: function (s) {
    999             return false;
    1000         }, format: function (s, table, cell) {
    1001             var c = table.config,
    1002                 p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
    1003             return $(cell).metadata()[p];
    1004         }, type: "numeric"
    1005     });
    1006     // add default widgets
    1007     ts.addWidget({
    1008         id: "zebra",
    1009         format: function (table) {
    1010             if (table.config.debug) {
    1011                 var time = new Date();
    1012             }
    1013             var $tr, row = -1,
    1014                 odd;
    1015             // loop through the visible rows
    1016             $("tr:visible", table.tBodies[0]).each(function (i) {
    1017                 $tr = $(this);
    1018                 // style children rows the same way the parent
    1019                 // row was styled
    1020                 if (!$tr.hasClass(table.config.cssChildRow)) row++;
    1021                 odd = (row % 2 == 0);
    1022                 $tr.removeClass(
    1023                 table.config.widgetZebra.css[odd ? 0 : 1]).addClass(
    1024                 table.config.widgetZebra.css[odd ? 1 : 0])
    1025             });
    1026             if (table.config.debug) {
    1027                 $.tablesorter.benchmark("Applying Zebra widget", time);
    1028             }
    1029         }
    1030     });
     1/**!
     2* TableSorter 2.14.5 - Client-side table sorting with ease!
     3* @requires jQuery v1.2.6+
     4*
     5* Copyright (c) 2007 Christian Bach
     6* Examples and docs at: http://tablesorter.com
     7* Dual licensed under the MIT and GPL licenses:
     8* http://www.opensource.org/licenses/mit-license.php
     9* http://www.gnu.org/licenses/gpl.html
     10*
     11* @type jQuery
     12* @name tablesorter
     13* @cat Plugins/Tablesorter
     14* @author Christian Bach/christian.bach@polyester.se
     15* @contributor Rob Garrison/https://github.com/Mottie/tablesorter
     16*/
     17/*jshint browser:true, jquery:true, unused:false, expr: true */
     18/*global console:false, alert:false */
     19!(function($) {
     20        "use strict";
     21        $.extend({
     22                /*jshint supernew:true */
     23                tablesorter: new function() {
     24
     25                        var ts = this;
     26
     27                        ts.version = "2.14.5";
     28
     29                        ts.parsers = [];
     30                        ts.widgets = [];
     31                        ts.defaults = {
     32
     33                                // *** appearance
     34                                theme            : 'default',  // adds tablesorter-{theme} to the table for styling
     35                                widthFixed       : false,      // adds colgroup to fix widths of columns
     36                                showProcessing   : false,      // show an indeterminate timer icon in the header when the table is sorted or filtered.
     37
     38                                headerTemplate   : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
     39                                onRenderTemplate : null,       // function(index, template){ return template; }, (template is a string)
     40                                onRenderHeader   : null,       // function(index){}, (nothing to return)
     41
     42                                // *** functionality
     43                                cancelSelection  : true,       // prevent text selection in the header
     44                                tabIndex         : true,       // add tabindex to header for keyboard accessibility
     45                                dateFormat       : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd"
     46                                sortMultiSortKey : 'shiftKey', // key used to select additional columns
     47                                sortResetKey     : 'ctrlKey',  // key used to remove sorting on a column
     48                                usNumberFormat   : true,       // false for German "1.234.567,89" or French "1 234 567,89"
     49                                delayInit        : false,      // if false, the parsed table contents will not update until the first sort
     50                                serverSideSorting: false,      // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
     51
     52                                // *** sort options
     53                                headers          : {},         // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
     54                                ignoreCase       : true,       // ignore case while sorting
     55                                sortForce        : null,       // column(s) first sorted; always applied
     56                                sortList         : [],         // Initial sort order; applied initially; updated when manually sorted
     57                                sortAppend       : null,       // column(s) sorted last; always applied
     58                                sortStable       : false,      // when sorting two rows with exactly the same content, the original sort order is maintained
     59
     60                                sortInitialOrder : 'asc',      // sort direction on first click
     61                                sortLocaleCompare: false,      // replace equivalent character (accented characters)
     62                                sortReset        : false,      // third click on the header will reset column to default - unsorted
     63                                sortRestart      : false,      // restart sort to "sortInitialOrder" when clicking on previously unsorted columns
     64
     65                                emptyTo          : 'bottom',   // sort empty cell to bottom, top, none, zero
     66                                stringTo         : 'max',      // sort strings in numerical column as max, min, top, bottom, zero
     67                                textExtraction   : 'simple',   // text extraction method/function - function(node, table, cellIndex){}
     68                                textSorter       : null,       // choose overall or specific column sorter function(a, b, direction, table, columnIndex) [alt: ts.sortText]
     69                                numberSorter     : null,       // choose overall numeric sorter function(a, b, direction, maxColumnValue)
     70
     71                                // *** widget options
     72                                widgets: [],                   // method to add widgets, e.g. widgets: ['zebra']
     73                                widgetOptions    : {
     74                                        zebra : [ 'even', 'odd' ]    // zebra widget alternating row class names
     75                                },
     76                                initWidgets      : true,       // apply widgets on tablesorter initialization
     77
     78                                // *** callbacks
     79                                initialized      : null,       // function(table){},
     80
     81                                // *** extra css class names
     82                                tableClass       : '',
     83                                cssAsc           : '',
     84                                cssDesc          : '',
     85                                cssHeader        : '',
     86                                cssHeaderRow     : '',
     87                                cssProcessing    : '', // processing icon applied to header during sort/filter
     88
     89                                cssChildRow      : 'tablesorter-childRow', // class name indiciating that a row is to be attached to the its parent
     90                                cssIcon          : 'tablesorter-icon',     //  if this class exists, a <i> will be added to the header automatically
     91                                cssInfoBlock     : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!)
     92
     93                                // *** selectors
     94                                selectorHeaders  : '> thead th, > thead td',
     95                                selectorSort     : 'th, td',   // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
     96                                selectorRemove   : '.remove-me',
     97
     98                                // *** advanced
     99                                debug            : false,
     100
     101                                // *** Internal variables
     102                                headerList: [],
     103                                empties: {},
     104                                strings: {},
     105                                parsers: []
     106
     107                                // deprecated; but retained for backwards compatibility
     108                                // widgetZebra: { css: ["even", "odd"] }
     109
     110                        };
     111
     112                        // internal css classes - these will ALWAYS be added to
     113                        // the table and MUST only contain one class name - fixes #381
     114                        ts.css = {
     115                                table      : 'tablesorter',
     116                                childRow   : 'tablesorter-childRow',
     117                                header     : 'tablesorter-header',
     118                                headerRow  : 'tablesorter-headerRow',
     119                                icon       : 'tablesorter-icon',
     120                                info       : 'tablesorter-infoOnly',
     121                                processing : 'tablesorter-processing',
     122                                sortAsc    : 'tablesorter-headerAsc',
     123                                sortDesc   : 'tablesorter-headerDesc'
     124                        };
     125
     126                        /* debuging utils */
     127                        function log() {
     128                                var s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : arguments[0];
     129                                if (typeof console !== "undefined" && typeof console.log !== "undefined") {
     130                                        console.log(s);
     131                                } else {
     132                                        alert(s);
     133                                }
     134                        }
     135
     136                        function benchmark(s, d) {
     137                                log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
     138                        }
     139
     140                        ts.log = log;
     141                        ts.benchmark = benchmark;
     142
     143                        // $.isEmptyObject from jQuery v1.4
     144                        function isEmptyObject(obj) {
     145                                /*jshint forin: false */
     146                                for (var name in obj) {
     147                                        return false;
     148                                }
     149                                return true;
     150                        }
     151
     152                        function getElementText(table, node, cellIndex) {
     153                                if (!node) { return ""; }
     154                                var c = table.config,
     155                                        t = c.textExtraction, text = "";
     156                                if (t === "simple") {
     157                                        if (c.supportsTextContent) {
     158                                                text = node.textContent; // newer browsers support this
     159                                        } else {
     160                                                text = $(node).text();
     161                                        }
     162                                } else {
     163                                        if (typeof t === "function") {
     164                                                text = t(node, table, cellIndex);
     165                                        } else if (typeof t === "object" && t.hasOwnProperty(cellIndex)) {
     166                                                text = t[cellIndex](node, table, cellIndex);
     167                                        } else {
     168                                                text = c.supportsTextContent ? node.textContent : $(node).text();
     169                                        }
     170                                }
     171                                return $.trim(text);
     172                        }
     173
     174                        function detectParserForColumn(table, rows, rowIndex, cellIndex) {
     175                                var cur,
     176                                i = ts.parsers.length,
     177                                node = false,
     178                                nodeValue = '',
     179                                keepLooking = true;
     180                                while (nodeValue === '' && keepLooking) {
     181                                        rowIndex++;
     182                                        if (rows[rowIndex]) {
     183                                                node = rows[rowIndex].cells[cellIndex];
     184                                                nodeValue = getElementText(table, node, cellIndex);
     185                                                if (table.config.debug) {
     186                                                        log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': "' + nodeValue + '"');
     187                                                }
     188                                        } else {
     189                                                keepLooking = false;
     190                                        }
     191                                }
     192                                while (--i >= 0) {
     193                                        cur = ts.parsers[i];
     194                                        // ignore the default text parser because it will always be true
     195                                        if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, table, node)) {
     196                                                return cur;
     197                                        }
     198                                }
     199                                // nothing found, return the generic parser (text)
     200                                return ts.getParserById('text');
     201                        }
     202
     203                        function buildParserCache(table) {
     204                                var c = table.config,
     205                                        // update table bodies in case we start with an empty table
     206                                        tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
     207                                        rows, list, l, i, h, ch, p, time, parsersDebug = "";
     208                                if ( tb.length === 0) {
     209                                        return c.debug ? log('*Empty table!* Not building a parser cache') : '';
     210                                } else if (c.debug) {
     211                                        time = new Date();
     212                                        log('Detecting parsers for each column');
     213                                }
     214                                rows = tb[0].rows;
     215                                if (rows[0]) {
     216                                        list = [];
     217                                        l = rows[0].cells.length;
     218                                        for (i = 0; i < l; i++) {
     219                                                // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
     220                                                // More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174)
     221                                                h = c.$headers.filter(':not([colspan])');
     222                                                h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix
     223                                                        .filter('[data-column="' + i + '"]:last');
     224                                                ch = c.headers[i];
     225                                                // get column parser
     226                                                p = ts.getParserById( ts.getData(h, ch, 'sorter') );
     227                                                // empty cells behaviour - keeping emptyToBottom for backwards compatibility
     228                                                c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
     229                                                // text strings behaviour in numerical sorts
     230                                                c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
     231                                                if (!p) {
     232                                                        p = detectParserForColumn(table, rows, -1, i);
     233                                                }
     234                                                if (c.debug) {
     235                                                        parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
     236                                                }
     237                                                list.push(p);
     238                                        }
     239                                }
     240                                if (c.debug) {
     241                                        log(parsersDebug);
     242                                        benchmark("Completed detecting parsers", time);
     243                                }
     244                                c.parsers = list;
     245                        }
     246
     247                        /* utils */
     248                        function buildCache(table) {
     249                                var b = table.tBodies,
     250                                tc = table.config,
     251                                totalRows,
     252                                totalCells,
     253                                parsers = tc.parsers,
     254                                t, v, i, j, k, c, cols, cacheTime, colMax = [];
     255                                tc.cache = {};
     256                                // if no parsers found, return - it's an empty table.
     257                                if (!parsers) {
     258                                        return tc.debug ? log('*Empty table!* Not building a cache') : '';
     259                                }
     260                                if (tc.debug) {
     261                                        cacheTime = new Date();
     262                                }
     263                                // processing icon
     264                                if (tc.showProcessing) {
     265                                        ts.isProcessing(table, true);
     266                                }
     267                                for (k = 0; k < b.length; k++) {
     268                                        tc.cache[k] = { row: [], normalized: [] };
     269                                        // ignore tbodies with class name from c.cssInfoBlock
     270                                        if (!$(b[k]).hasClass(tc.cssInfoBlock)) {
     271                                                totalRows = (b[k] && b[k].rows.length) || 0;
     272                                                totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0;
     273                                                for (i = 0; i < totalRows; ++i) {
     274                                                        /** Add the table data to main data array */
     275                                                        c = $(b[k].rows[i]);
     276                                                        cols = [];
     277                                                        // if this is a child row, add it to the last row's children and continue to the next row
     278                                                        if (c.hasClass(tc.cssChildRow)) {
     279                                                                tc.cache[k].row[tc.cache[k].row.length - 1] = tc.cache[k].row[tc.cache[k].row.length - 1].add(c);
     280                                                                // go to the next for loop
     281                                                                continue;
     282                                                        }
     283                                                        tc.cache[k].row.push(c);
     284                                                        for (j = 0; j < totalCells; ++j) {
     285                                                                t = getElementText(table, c[0].cells[j], j);
     286                                                                // allow parsing if the string is empty, previously parsing would change it to zero,
     287                                                                // in case the parser needs to extract data from the table cell attributes
     288                                                                v = parsers[j].format(t, table, c[0].cells[j], j);
     289                                                                cols.push(v);
     290                                                                if ((parsers[j].type || '').toLowerCase() === "numeric") {
     291                                                                        colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0); // determine column max value (ignore sign)
     292                                                                }
     293                                                        }
     294                                                        cols.push(tc.cache[k].normalized.length); // add position for rowCache
     295                                                        tc.cache[k].normalized.push(cols);
     296                                                }
     297                                                tc.cache[k].colMax = colMax;
     298                                        }
     299                                }
     300                                if (tc.showProcessing) {
     301                                        ts.isProcessing(table); // remove processing icon
     302                                }
     303                                if (tc.debug) {
     304                                        benchmark("Building cache for " + totalRows + " rows", cacheTime);
     305                                }
     306                        }
     307
     308                        // init flag (true) used by pager plugin to prevent widget application
     309                        function appendToTable(table, init) {
     310                                var c = table.config,
     311                                        wo = c.widgetOptions,
     312                                        b = table.tBodies,
     313                                        rows = [],
     314                                        c2 = c.cache,
     315                                        r, n, totalRows, checkCell, $bk, $tb,
     316                                        i, j, k, l, pos, appendTime;
     317                                if (isEmptyObject(c2)) { return; } // empty table - fixes #206/#346
     318                                if (c.debug) {
     319                                        appendTime = new Date();
     320                                }
     321                                for (k = 0; k < b.length; k++) {
     322                                        $bk = $(b[k]);
     323                                        if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
     324                                                // get tbody
     325                                                $tb = ts.processTbody(table, $bk, true);
     326                                                r = c2[k].row;
     327                                                n = c2[k].normalized;
     328                                                totalRows = n.length;
     329                                                checkCell = totalRows ? (n[0].length - 1) : 0;
     330                                                for (i = 0; i < totalRows; i++) {
     331                                                        pos = n[i][checkCell];
     332                                                        rows.push(r[pos]);
     333                                                        // removeRows used by the pager plugin; don't render if using ajax - fixes #411
     334                                                        if (!c.appender || (c.pager && (!c.pager.removeRows || !wo.pager_removeRows) && !c.pager.ajax)) {
     335                                                                l = r[pos].length;
     336                                                                for (j = 0; j < l; j++) {
     337                                                                        $tb.append(r[pos][j]);
     338                                                                }
     339                                                        }
     340                                                }
     341                                                // restore tbody
     342                                                ts.processTbody(table, $tb, false);
     343                                        }
     344                                }
     345                                if (c.appender) {
     346                                        c.appender(table, rows);
     347                                }
     348                                if (c.debug) {
     349                                        benchmark("Rebuilt table", appendTime);
     350                                }
     351                                // apply table widgets; but not before ajax completes
     352                                if (!init && !c.appender) { ts.applyWidget(table); }
     353                                // trigger sortend
     354                                $(table).trigger("sortEnd", table);
     355                                $(table).trigger("updateComplete", table);
     356                        }
     357
     358                        // computeTableHeaderCellIndexes from:
     359                        // http://www.javascripttoolbox.com/lib/table/examples.php
     360                        // http://www.javascripttoolbox.com/temp/table_cellindex.html
     361                        function computeThIndexes(t) {
     362                                var matrix = [],
     363                                lookup = {},
     364                                cols = 0, // determine the number of columns
     365                                trs = $(t).find('thead:eq(0), tfoot').children('tr'), // children tr in tfoot - see issue #196
     366                                i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
     367                                for (i = 0; i < trs.length; i++) {
     368                                        cells = trs[i].cells;
     369                                        for (j = 0; j < cells.length; j++) {
     370                                                c = cells[j];
     371                                                rowIndex = c.parentNode.rowIndex;
     372                                                cellId = rowIndex + "-" + c.cellIndex;
     373                                                rowSpan = c.rowSpan || 1;
     374                                                colSpan = c.colSpan || 1;
     375                                                if (typeof(matrix[rowIndex]) === "undefined") {
     376                                                        matrix[rowIndex] = [];
     377                                                }
     378                                                // Find first available column in the first row
     379                                                for (k = 0; k < matrix[rowIndex].length + 1; k++) {
     380                                                        if (typeof(matrix[rowIndex][k]) === "undefined") {
     381                                                                firstAvailCol = k;
     382                                                                break;
     383                                                        }
     384                                                }
     385                                                lookup[cellId] = firstAvailCol;
     386                                                cols = Math.max(firstAvailCol, cols);
     387                                                // add data-column
     388                                                $(c).attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
     389                                                for (k = rowIndex; k < rowIndex + rowSpan; k++) {
     390                                                        if (typeof(matrix[k]) === "undefined") {
     391                                                                matrix[k] = [];
     392                                                        }
     393                                                        matrixrow = matrix[k];
     394                                                        for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
     395                                                                matrixrow[l] = "x";
     396                                                        }
     397                                                }
     398                                        }
     399                                }
     400                                // may not be accurate if # header columns !== # tbody columns
     401                                t.config.columns = cols + 1; // add one because it's a zero-based index
     402                                return lookup;
     403                        }
     404
     405                        function formatSortingOrder(v) {
     406                                // look for "d" in "desc" order; return true
     407                                return (/^d/i.test(v) || v === 1);
     408                        }
     409
     410                        function buildHeaders(table) {
     411                                var header_index = computeThIndexes(table), ch, $t,
     412                                        h, i, t, lock, time, c = table.config;
     413                                c.headerList = [];
     414                                c.headerContent = [];
     415                                if (c.debug) {
     416                                        time = new Date();
     417                                }
     418                                // add icon if cssIcon option exists
     419                                i = c.cssIcon ? '<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' : '';
     420                                c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
     421                                        $t = $(this);
     422                                        ch = c.headers[index];
     423                                        c.headerContent[index] = $(this).html(); // save original header content
     424                                        // set up header template
     425                                        t = c.headerTemplate.replace(/\{content\}/g, $(this).html()).replace(/\{icon\}/g, i);
     426                                        if (c.onRenderTemplate) {
     427                                                h = c.onRenderTemplate.apply($t, [index, t]);
     428                                                if (h && typeof h === 'string') { t = h; } // only change t if something is returned
     429                                        }
     430                                        $(this).html('<div class="tablesorter-header-inner">' + t + '</div>'); // faster than wrapInner
     431
     432                                        if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
     433
     434                                        this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
     435                                        this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
     436                                        this.count = -1; // set to -1 because clicking on the header automatically adds one
     437                                        this.lockedOrder = false;
     438                                        lock = ts.getData($t, ch, 'lockedOrder') || false;
     439                                        if (typeof lock !== 'undefined' && lock !== false) {
     440                                                this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
     441                                        }
     442                                        $t.addClass(ts.css.header + ' ' + c.cssHeader);
     443                                        // add cell to headerList
     444                                        c.headerList[index] = this;
     445                                        // add to parent in case there are multiple rows
     446                                        $t.parent().addClass(ts.css.headerRow + ' ' + c.cssHeaderRow);
     447                                        // allow keyboard cursor to focus on element
     448                                        if (c.tabIndex) { $t.attr("tabindex", 0); }
     449                                });
     450                                // enable/disable sorting
     451                                updateHeader(table);
     452                                if (c.debug) {
     453                                        benchmark("Built headers:", time);
     454                                        log(c.$headers);
     455                                }
     456                        }
     457
     458                        function commonUpdate(table, resort, callback) {
     459                                var c = table.config;
     460                                // remove rows/elements before update
     461                                c.$table.find(c.selectorRemove).remove();
     462                                // rebuild parsers
     463                                buildParserCache(table);
     464                                // rebuild the cache map
     465                                buildCache(table);
     466                                checkResort(c.$table, resort, callback);
     467                        }
     468
     469                        function updateHeader(table) {
     470                                var s, c = table.config;
     471                                c.$headers.each(function(index, th){
     472                                        s = ts.getData( th, c.headers[index], 'sorter' ) === 'false';
     473                                        th.sortDisabled = s;
     474                                        $(th)[ s ? 'addClass' : 'removeClass' ]('sorter-false');
     475                                });
     476                        }
     477
     478                        function setHeadersCss(table) {
     479                                var f, i, j, l,
     480                                        c = table.config,
     481                                        list = c.sortList,
     482                                        css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc],
     483                                        // find the footer
     484                                        $t = $(table).find('tfoot tr').children().removeClass(css.join(' '));
     485                                // remove all header information
     486                                c.$headers.removeClass(css.join(' '));
     487                                l = list.length;
     488                                for (i = 0; i < l; i++) {
     489                                        // direction = 2 means reset!
     490                                        if (list[i][1] !== 2) {
     491                                                // multicolumn sorting updating - choose the :last in case there are nested columns
     492                                                f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (l === 1 ? ':last' : '') );
     493                                                if (f.length) {
     494                                                        for (j = 0; j < f.length; j++) {
     495                                                                if (!f[j].sortDisabled) {
     496                                                                        f.eq(j).addClass(css[list[i][1]]);
     497                                                                        // add sorted class to footer, if it exists
     498                                                                        if ($t.length) {
     499                                                                                $t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]);
     500                                                                        }
     501                                                                }
     502                                                        }
     503                                                }
     504                                        }
     505                                }
     506                        }
     507
     508                        // automatically add col group, and column sizes if set
     509                        function fixColumnWidth(table) {
     510                                if (table.config.widthFixed && $(table).find('colgroup').length === 0) {
     511                                        var colgroup = $('<colgroup>'),
     512                                                overallWidth = $(table).width();
     513                                        // only add col for visible columns - fixes #371
     514                                        $(table.tBodies[0]).find("tr:first").children("td:visible").each(function() {
     515                                                colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
     516                                        });
     517                                        $(table).prepend(colgroup);
     518                                }
     519                        }
     520
     521                        function updateHeaderSortCount(table, list) {
     522                                var s, t, o, c = table.config,
     523                                        sl = list || c.sortList;
     524                                c.sortList = [];
     525                                $.each(sl, function(i,v){
     526                                        // ensure all sortList values are numeric - fixes #127
     527                                        s = [ parseInt(v[0], 10), parseInt(v[1], 10) ];
     528                                        // make sure header exists
     529                                        o = c.$headers[s[0]];
     530                                        if (o) { // prevents error if sorton array is wrong
     531                                                c.sortList.push(s);
     532                                                t = $.inArray(s[1], o.order); // fixes issue #167
     533                                                o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2);
     534                                        }
     535                                });
     536                        }
     537
     538                        function getCachedSortType(parsers, i) {
     539                                return (parsers && parsers[i]) ? parsers[i].type || '' : '';
     540                        }
     541
     542                        function initSort(table, cell, e){
     543                                var a, i, j, o, s,
     544                                        c = table.config,
     545                                        k = !e[c.sortMultiSortKey],
     546                                        $this = $(table);
     547                                // Only call sortStart if sorting is enabled
     548                                $this.trigger("sortStart", table);
     549                                // get current column sort order
     550                                cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
     551                                // reset all sorts on non-current column - issue #30
     552                                if (c.sortRestart) {
     553                                        i = cell;
     554                                        c.$headers.each(function() {
     555                                                // only reset counts on columns that weren't just clicked on and if not included in a multisort
     556                                                if (this !== i && (k || !$(this).is('.' + ts.css.sortDesc + ',.' + ts.css.sortAsc))) {
     557                                                        this.count = -1;
     558                                                }
     559                                        });
     560                                }
     561                                // get current column index
     562                                i = cell.column;
     563                                // user only wants to sort on one column
     564                                if (k) {
     565                                        // flush the sort list
     566                                        c.sortList = [];
     567                                        if (c.sortForce !== null) {
     568                                                a = c.sortForce;
     569                                                for (j = 0; j < a.length; j++) {
     570                                                        if (a[j][0] !== i) {
     571                                                                c.sortList.push(a[j]);
     572                                                        }
     573                                                }
     574                                        }
     575                                        // add column to sort list
     576                                        o = cell.order[cell.count];
     577                                        if (o < 2) {
     578                                                c.sortList.push([i, o]);
     579                                                // add other columns if header spans across multiple
     580                                                if (cell.colSpan > 1) {
     581                                                        for (j = 1; j < cell.colSpan; j++) {
     582                                                                c.sortList.push([i + j, o]);
     583                                                        }
     584                                                }
     585                                        }
     586                                        // multi column sorting
     587                                } else {
     588                                        // get rid of the sortAppend before adding more - fixes issue #115
     589                                        if (c.sortAppend && c.sortList.length > 1) {
     590                                                if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) {
     591                                                        c.sortList.pop();
     592                                                }
     593                                        }
     594                                        // the user has clicked on an already sorted column
     595                                        if (ts.isValueInArray(i, c.sortList)) {
     596                                                // reverse the sorting direction
     597                                                for (j = 0; j < c.sortList.length; j++) {
     598                                                        s = c.sortList[j];
     599                                                        o = c.$headers[s[0]];
     600                                                        if (s[0] === i) {
     601                                                                // o.count seems to be incorrect when compared to cell.count
     602                                                                s[1] = o.order[cell.count];
     603                                                                if (s[1] === 2) {
     604                                                                        c.sortList.splice(j,1);
     605                                                                        o.count = -1;
     606                                                                }
     607                                                        }
     608                                                }
     609                                        } else {
     610                                                // add column to sort list array
     611                                                o = cell.order[cell.count];
     612                                                if (o < 2) {
     613                                                        c.sortList.push([i, o]);
     614                                                        // add other columns if header spans across multiple
     615                                                        if (cell.colSpan > 1) {
     616                                                                for (j = 1; j < cell.colSpan; j++) {
     617                                                                        c.sortList.push([i + j, o]);
     618                                                                }
     619                                                        }
     620                                                }
     621                                        }
     622                                }
     623                                if (c.sortAppend !== null) {
     624                                        a = c.sortAppend;
     625                                        for (j = 0; j < a.length; j++) {
     626                                                if (a[j][0] !== i) {
     627                                                        c.sortList.push(a[j]);
     628                                                }
     629                                        }
     630                                }
     631                                // sortBegin event triggered immediately before the sort
     632                                $this.trigger("sortBegin", table);
     633                                // setTimeout needed so the processing icon shows up
     634                                setTimeout(function(){
     635                                        // set css for headers
     636                                        setHeadersCss(table);
     637                                        multisort(table);
     638                                        appendToTable(table);
     639                                }, 1);
     640                        }
     641
     642                        // sort multiple columns
     643                        function multisort(table) { /*jshint loopfunc:true */
     644                                var i, k, num, col, colMax, cache, lc,
     645                                        order, orgOrderCol, sortTime, sort, x, y,
     646                                        dir = 0,
     647                                        c = table.config,
     648                                        cts = c.textSorter || '',
     649                                        sortList = c.sortList,
     650                                        l = sortList.length,
     651                                        bl = table.tBodies.length;
     652                                if (c.serverSideSorting || isEmptyObject(c.cache)) { // empty table - fixes #206/#346
     653                                        return;
     654                                }
     655                                if (c.debug) { sortTime = new Date(); }
     656                                for (k = 0; k < bl; k++) {
     657                                        colMax = c.cache[k].colMax;
     658                                        cache = c.cache[k].normalized;
     659                                        lc = cache.length;
     660                                        orgOrderCol = (cache && cache[0]) ? cache[0].length - 1 : 0;
     661                                        cache.sort(function(a, b) {
     662                                                // cache is undefined here in IE, so don't use it!
     663                                                for (i = 0; i < l; i++) {
     664                                                        col = sortList[i][0];
     665                                                        order = sortList[i][1];
     666                                                        // sort direction, true = asc, false = desc
     667                                                        dir = order === 0;
     668
     669                                                        if (c.sortStable && a[col] === b[col] && l === 1) {
     670                                                                return a[orgOrderCol] - b[orgOrderCol];
     671                                                        }
     672
     673                                                        // fallback to natural sort since it is more robust
     674                                                        num = /n/i.test(getCachedSortType(c.parsers, col));
     675                                                        if (num && c.strings[col]) {
     676                                                                // sort strings in numerical columns
     677                                                                if (typeof (c.string[c.strings[col]]) === 'boolean') {
     678                                                                        num = (dir ? 1 : -1) * (c.string[c.strings[col]] ? -1 : 1);
     679                                                                } else {
     680                                                                        num = (c.strings[col]) ? c.string[c.strings[col]] || 0 : 0;
     681                                                                }
     682                                                                // fall back to built-in numeric sort
     683                                                                // var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir);
     684                                                                sort = c.numberSorter ? c.numberSorter(x[col], y[col], dir, colMax[col], table) :
     685                                                                        ts[ 'sortNumeric' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], num, colMax[col], col, table);
     686                                                        } else {
     687                                                                // set a & b depending on sort direction
     688                                                                x = dir ? a : b;
     689                                                                y = dir ? b : a;
     690                                                                // text sort function
     691                                                                if (typeof(cts) === 'function') {
     692                                                                        // custom OVERALL text sorter
     693                                                                        sort = cts(x[col], y[col], dir, col, table);
     694                                                                } else if (typeof(cts) === 'object' && cts.hasOwnProperty(col)) {
     695                                                                        // custom text sorter for a SPECIFIC COLUMN
     696                                                                        sort = cts[col](x[col], y[col], dir, col, table);
     697                                                                } else {
     698                                                                        // fall back to natural sort
     699                                                                        sort = ts[ 'sortNatural' + (dir ? 'Asc' : 'Desc') ](a[col], b[col], col, table, c);
     700                                                                }
     701                                                        }
     702                                                        if (sort) { return sort; }
     703                                                }
     704                                                return a[orgOrderCol] - b[orgOrderCol];
     705                                        });
     706                                }
     707                                if (c.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
     708                        }
     709
     710                        function resortComplete($table, callback){
     711                                var c = $table[0].config;
     712                                if (c.pager && !c.pager.ajax) {
     713                                        $table.trigger('updateComplete');
     714                                }
     715                                if (typeof callback === "function") {
     716                                        callback($table[0]);
     717                                }
     718                        }
     719
     720                        function checkResort($table, flag, callback) {
     721                                // don't try to resort if the table is still processing
     722                                // this will catch spamming of the updateCell method
     723                                if (flag !== false && !$table[0].isProcessing) {
     724                                        $table.trigger("sorton", [$table[0].config.sortList, function(){
     725                                                resortComplete($table, callback);
     726                                        }]);
     727                                } else {
     728                                        resortComplete($table, callback);
     729                                }
     730                        }
     731
     732                        function bindEvents(table){
     733                                var c = table.config,
     734                                        $this = c.$table,
     735                                        j, downTime;
     736                                // apply event handling to headers
     737                                c.$headers
     738                                // http://stackoverflow.com/questions/5312849/jquery-find-self;
     739                                .find(c.selectorSort).add( c.$headers.filter(c.selectorSort) )
     740                                .unbind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter')
     741                                .bind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter', function(e, external) {
     742                                        // only recognize left clicks or enter
     743                                        if ( ((e.which || e.button) !== 1 && !/sort|keypress/.test(e.type)) || (e.type === 'keypress' && e.which !== 13) ) {
     744                                                return;
     745                                        }
     746                                        // ignore long clicks (prevents resizable widget from initializing a sort)
     747                                        if (e.type === 'mouseup' && external !== true && (new Date().getTime() - downTime > 250)) { return; }
     748                                        // set timer on mousedown
     749                                        if (e.type === 'mousedown') {
     750                                                downTime = new Date().getTime();
     751                                                return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
     752                                        }
     753                                        if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
     754                                        // jQuery v1.2.6 doesn't have closest()
     755                                        var $cell = /TH|TD/.test(this.tagName) ? $(this) : $(this).parents('th, td').filter(':first'), cell = $cell[0];
     756                                        if (!cell.sortDisabled) {
     757                                                initSort(table, cell, e);
     758                                        }
     759                                });
     760                                if (c.cancelSelection) {
     761                                        // cancel selection
     762                                        c.$headers
     763                                                .attr('unselectable', 'on')
     764                                                .bind('selectstart', false)
     765                                                .css({
     766                                                        'user-select': 'none',
     767                                                        'MozUserSelect': 'none' // not needed for jQuery 1.8+
     768                                                });
     769                                }
     770                                // apply easy methods that trigger bound events
     771                                $this
     772                                .unbind('sortReset update updateRows updateCell updateAll addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join('.tablesorter '))
     773                                .bind("sortReset.tablesorter", function(e){
     774                                        e.stopPropagation();
     775                                        c.sortList = [];
     776                                        setHeadersCss(table);
     777                                        multisort(table);
     778                                        appendToTable(table);
     779                                })
     780                                .bind("updateAll.tablesorter", function(e, resort, callback){
     781                                        e.stopPropagation();
     782                                        ts.refreshWidgets(table, true, true);
     783                                        ts.restoreHeaders(table);
     784                                        buildHeaders(table);
     785                                        bindEvents(table);
     786                                        commonUpdate(table, resort, callback);
     787                                })
     788                                .bind("update.tablesorter updateRows.tablesorter", function(e, resort, callback) {
     789                                        e.stopPropagation();
     790                                        // update sorting (if enabled/disabled)
     791                                        updateHeader(table);
     792                                        commonUpdate(table, resort, callback);
     793                                })
     794                                .bind("updateCell.tablesorter", function(e, cell, resort, callback) {
     795                                        e.stopPropagation();
     796                                        $this.find(c.selectorRemove).remove();
     797                                        // get position from the dom
     798                                        var l, row, icell,
     799                                        $tb = $this.find('tbody'),
     800                                        // update cache - format: function(s, table, cell, cellIndex)
     801                                        // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
     802                                        tbdy = $tb.index( $(cell).parents('tbody').filter(':first') ),
     803                                        $row = $(cell).parents('tr').filter(':first');
     804                                        cell = $(cell)[0]; // in case cell is a jQuery object
     805                                        // tbody may not exist if update is initialized while tbody is removed for processing
     806                                        if ($tb.length && tbdy >= 0) {
     807                                                row = $tb.eq(tbdy).find('tr').index( $row );
     808                                                icell = cell.cellIndex;
     809                                                l = c.cache[tbdy].normalized[row].length - 1;
     810                                                c.cache[tbdy].row[table.config.cache[tbdy].normalized[row][l]] = $row;
     811                                                c.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(table, cell, icell), table, cell, icell );
     812                                                checkResort($this, resort, callback);
     813                                        }
     814                                })
     815                                .bind("addRows.tablesorter", function(e, $row, resort, callback) {
     816                                        e.stopPropagation();
     817                                        if (isEmptyObject(c.cache)) {
     818                                                // empty table, do an update instead - fixes #450
     819                                                updateHeader(table);
     820                                                commonUpdate(table, resort, callback);
     821                                        } else {
     822                                                var i, rows = $row.filter('tr').length,
     823                                                dat = [], l = $row[0].cells.length,
     824                                                tbdy = $this.find('tbody').index( $row.parents('tbody').filter(':first') );
     825                                                // fixes adding rows to an empty table - see issue #179
     826                                                if (!c.parsers) {
     827                                                        buildParserCache(table);
     828                                                }
     829                                                // add each row
     830                                                for (i = 0; i < rows; i++) {
     831                                                        // add each cell
     832                                                        for (j = 0; j < l; j++) {
     833                                                                dat[j] = c.parsers[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
     834                                                        }
     835                                                        // add the row index to the end
     836                                                        dat.push(c.cache[tbdy].row.length);
     837                                                        // update cache
     838                                                        c.cache[tbdy].row.push([$row[i]]);
     839                                                        c.cache[tbdy].normalized.push(dat);
     840                                                        dat = [];
     841                                                }
     842                                                // resort using current settings
     843                                                checkResort($this, resort, callback);
     844                                        }
     845                                })
     846                                .bind("sorton.tablesorter", function(e, list, callback, init) {
     847                                        var c = table.config;
     848                                        e.stopPropagation();
     849                                        $this.trigger("sortStart", this);
     850                                        // update header count index
     851                                        updateHeaderSortCount(table, list);
     852                                        // set css for headers
     853                                        setHeadersCss(table);
     854                                        // fixes #346
     855                                        if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
     856                                        $this.trigger("sortBegin", this);
     857                                        // sort the table and append it to the dom
     858                                        multisort(table);
     859                                        appendToTable(table, init);
     860                                        if (typeof callback === "function") {
     861                                                callback(table);
     862                                        }
     863                                })
     864                                .bind("appendCache.tablesorter", function(e, callback, init) {
     865                                        e.stopPropagation();
     866                                        appendToTable(table, init);
     867                                        if (typeof callback === "function") {
     868                                                callback(table);
     869                                        }
     870                                })
     871                                .bind("applyWidgetId.tablesorter", function(e, id) {
     872                                        e.stopPropagation();
     873                                        ts.getWidgetById(id).format(table, c, c.widgetOptions);
     874                                })
     875                                .bind("applyWidgets.tablesorter", function(e, init) {
     876                                        e.stopPropagation();
     877                                        // apply widgets
     878                                        ts.applyWidget(table, init);
     879                                })
     880                                .bind("refreshWidgets.tablesorter", function(e, all, dontapply){
     881                                        e.stopPropagation();
     882                                        ts.refreshWidgets(table, all, dontapply);
     883                                })
     884                                .bind("destroy.tablesorter", function(e, c, cb){
     885                                        e.stopPropagation();
     886                                        ts.destroy(table, c, cb);
     887                                });
     888                        }
     889
     890                        /* public methods */
     891                        ts.construct = function(settings) {
     892                                return this.each(function() {
     893                                        var table = this,
     894                                                // merge & extend config options
     895                                                c = $.extend(true, {}, ts.defaults, settings);
     896                                        // create a table from data (build table widget)
     897                                        if (!table.hasInitialized && ts.buildTable && this.tagName !== 'TABLE') {
     898                                                // return the table (in case the original target is the table's container)
     899                                                ts.buildTable(table, c);
     900                                        }
     901                                        ts.setup(table, c);
     902                                });
     903                        };
     904
     905                        ts.setup = function(table, c) {
     906                                // if no thead or tbody, or tablesorter is already present, quit
     907                                if (!table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true) {
     908                                        return c.debug ? log('stopping initialization! No table, thead, tbody or tablesorter has already been initialized') : '';
     909                                }
     910
     911                                var k = '',
     912                                        $this = $(table),
     913                                        m = $.metadata;
     914                                // initialization flag
     915                                table.hasInitialized = false;
     916                                // table is being processed flag
     917                                table.isProcessing = true;
     918                                // make sure to store the config object
     919                                table.config = c;
     920                                // save the settings where they read
     921                                $.data(table, "tablesorter", c);
     922                                if (c.debug) { $.data( table, 'startoveralltimer', new Date()); }
     923
     924                                // constants
     925                                c.supportsTextContent = $('<span>x</span>')[0].textContent === 'x';
     926                                // removing this in version 3 (only supports jQuery 1.7+)
     927                                c.supportsDataObject = (function(version) {
     928                                        version[0] = parseInt(version[0], 10);
     929                                        return (version[0] > 1) || (version[0] === 1 && parseInt(version[1], 10) >= 4);
     930                                })($.fn.jquery.split("."));
     931                                // digit sort text location; keeping max+/- for backwards compatibility
     932                                c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
     933                                // add table theme class only if there isn't already one there
     934                                if (!/tablesorter\-/.test($this.attr('class'))) {
     935                                        k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
     936                                }
     937                                c.$table = $this.addClass(ts.css.table + ' ' + c.tableClass + k);
     938                                c.$tbodies = $this.children('tbody:not(.' + c.cssInfoBlock + ')');
     939                                c.widgetInit = {}; // keep a list of initialized widgets
     940                                // build headers
     941                                buildHeaders(table);
     942                                // fixate columns if the users supplies the fixedWidth option
     943                                // do this after theme has been applied
     944                                fixColumnWidth(table);
     945                                // try to auto detect column type, and store in tables config
     946                                buildParserCache(table);
     947                                // build the cache for the tbody cells
     948                                // delayInit will delay building the cache until the user starts a sort
     949                                if (!c.delayInit) { buildCache(table); }
     950                                // bind all header events and methods
     951                                bindEvents(table);
     952                                // get sort list from jQuery data or metadata
     953                                // in jQuery < 1.4, an error occurs when calling $this.data()
     954                                if (c.supportsDataObject && typeof $this.data().sortlist !== 'undefined') {
     955                                        c.sortList = $this.data().sortlist;
     956                                } else if (m && ($this.metadata() && $this.metadata().sortlist)) {
     957                                        c.sortList = $this.metadata().sortlist;
     958                                }
     959                                // apply widget init code
     960                                ts.applyWidget(table, true);
     961                                // if user has supplied a sort list to constructor
     962                                if (c.sortList.length > 0) {
     963                                        $this.trigger("sorton", [c.sortList, {}, !c.initWidgets]);
     964                                } else if (c.initWidgets) {
     965                                        // apply widget format
     966                                        ts.applyWidget(table);
     967                                }
     968
     969                                // show processesing icon
     970                                if (c.showProcessing) {
     971                                        $this
     972                                        .unbind('sortBegin.tablesorter sortEnd.tablesorter')
     973                                        .bind('sortBegin.tablesorter sortEnd.tablesorter', function(e) {
     974                                                ts.isProcessing(table, e.type === 'sortBegin');
     975                                        });
     976                                }
     977
     978                                // initialized
     979                                table.hasInitialized = true;
     980                                table.isProcessing = false;
     981                                if (c.debug) {
     982                                        ts.benchmark("Overall initialization time", $.data( table, 'startoveralltimer'));
     983                                }
     984                                $this.trigger('tablesorter-initialized', table);
     985                                if (typeof c.initialized === 'function') { c.initialized(table); }
     986                        };
     987
     988                        // *** Process table ***
     989                        // add processing indicator
     990                        ts.isProcessing = function(table, toggle, $ths) {
     991                                table = $(table);
     992                                var c = table[0].config,
     993                                        // default to all headers
     994                                        $h = $ths || table.find('.' + ts.css.header);
     995                                if (toggle) {
     996                                        if (c.sortList.length > 0) {
     997                                                // get headers from the sortList
     998                                                $h = $h.filter(function(){
     999                                                        // get data-column from attr to keep  compatibility with jQuery 1.2.6
     1000                                                        return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList);
     1001                                                });
     1002                                        }
     1003                                        $h.addClass(ts.css.processing + ' ' + c.cssProcessing);
     1004                                } else {
     1005                                        $h.removeClass(ts.css.processing + ' ' + c.cssProcessing);
     1006                                }
     1007                        };
     1008
     1009                        // detach tbody but save the position
     1010                        // don't use tbody because there are portions that look for a tbody index (updateCell)
     1011                        ts.processTbody = function(table, $tb, getIt){
     1012                                var holdr;
     1013                                if (getIt) {
     1014                                        table.isProcessing = true;
     1015                                        $tb.before('<span class="tablesorter-savemyplace"/>');
     1016                                        holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
     1017                                        return holdr;
     1018                                }
     1019                                holdr = $(table).find('span.tablesorter-savemyplace');
     1020                                $tb.insertAfter( holdr );
     1021                                holdr.remove();
     1022                                table.isProcessing = false;
     1023                        };
     1024
     1025                        ts.clearTableBody = function(table) {
     1026                                $(table)[0].config.$tbodies.empty();
     1027                        };
     1028
     1029                        // restore headers
     1030                        ts.restoreHeaders = function(table){
     1031                                var c = table.config;
     1032                                // don't use c.$headers here in case header cells were swapped
     1033                                c.$table.find(c.selectorHeaders).each(function(i){
     1034                                        // only restore header cells if it is wrapped
     1035                                        // because this is also used by the updateAll method
     1036                                        if ($(this).find('.tablesorter-header-inner').length){
     1037                                                $(this).html( c.headerContent[i] );
     1038                                        }
     1039                                });
     1040                        };
     1041
     1042                        ts.destroy = function(table, removeClasses, callback){
     1043                                table = $(table)[0];
     1044                                if (!table.hasInitialized) { return; }
     1045                                // remove all widgets
     1046                                ts.refreshWidgets(table, true, true);
     1047                                var $t = $(table), c = table.config,
     1048                                $h = $t.find('thead:first'),
     1049                                $r = $h.find('tr.' + ts.css.headerRow).removeClass(ts.css.headerRow + ' ' + c.cssHeaderRow),
     1050                                $f = $t.find('tfoot:first > tr').children('th, td');
     1051                                // remove widget added rows, just in case
     1052                                $h.find('tr').not($r).remove();
     1053                                // disable tablesorter
     1054                                $t
     1055                                        .removeData('tablesorter')
     1056                                        .unbind('sortReset update updateAll updateRows updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd '.split(' ').join('.tablesorter '));
     1057                                c.$headers.add($f)
     1058                                        .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc].join(' ') )
     1059                                        .removeAttr('data-column');
     1060                                $r.find(c.selectorSort).unbind('mousedown.tablesorter mouseup.tablesorter keypress.tablesorter');
     1061                                ts.restoreHeaders(table);
     1062                                if (removeClasses !== false) {
     1063                                        $t.removeClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme);
     1064                                }
     1065                                // clear flag in case the plugin is initialized again
     1066                                table.hasInitialized = false;
     1067                                if (typeof callback === 'function') {
     1068                                        callback(table);
     1069                                }
     1070                        };
     1071
     1072                        // *** sort functions ***
     1073                        // regex used in natural sort
     1074                        ts.regex = {
     1075                                chunk : /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
     1076                                hex: /^0x[0-9a-f]+$/i // hex
     1077                        };
     1078
     1079                        // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
     1080                        ts.sortNatural = function(a, b) {
     1081                                if (a === b) { return 0; }
     1082                                var xN, xD, yN, yD, xF, yF, i, mx,
     1083                                        r = ts.regex;
     1084                                // first try and sort Hex codes
     1085                                if (r.hex.test(b)) {
     1086                                        xD = parseInt(a.match(r.hex), 16);
     1087                                        yD = parseInt(b.match(r.hex), 16);
     1088                                        if ( xD < yD ) { return -1; }
     1089                                        if ( xD > yD ) { return 1; }
     1090                                }
     1091
     1092                                // chunk/tokenize
     1093                                xN = a.replace(r.chunk, '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
     1094                                yN = b.replace(r.chunk, '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
     1095                                mx = Math.max(xN.length, yN.length);
     1096                                // natural sorting through split numeric strings and default strings
     1097                                for (i = 0; i < mx; i++) {
     1098                                        // find floats not starting with '0', string or 0 if not defined
     1099                                        xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0;
     1100                                        yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0;
     1101                                        // handle numeric vs string comparison - number < string - (Kyle Adams)
     1102                                        if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; }
     1103                                        // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
     1104                                        if (typeof xF !== typeof yF) {
     1105                                                xF += '';
     1106                                                yF += '';
     1107                                        }
     1108                                        if (xF < yF) { return -1; }
     1109                                        if (xF > yF) { return 1; }
     1110                                }
     1111                                return 0;
     1112                        };
     1113
     1114                        ts.sortNaturalAsc = function(a, b, col, table, c) {
     1115                                if (a === b) { return 0; }
     1116                                var e = c.string[ (c.empties[col] || c.emptyTo ) ];
     1117                                if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
     1118                                if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
     1119                                return ts.sortNatural(a, b);
     1120                        };
     1121
     1122                        ts.sortNaturalDesc = function(a, b, col, table, c) {
     1123                                if (a === b) { return 0; }
     1124                                var e = c.string[ (c.empties[col] || c.emptyTo ) ];
     1125                                if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
     1126                                if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
     1127                                return ts.sortNatural(b, a);
     1128                        };
     1129
     1130                        // basic alphabetical sort
     1131                        ts.sortText = function(a, b) {
     1132                                return a > b ? 1 : (a < b ? -1 : 0);
     1133                        };
     1134
     1135                        // return text string value by adding up ascii value
     1136                        // so the text is somewhat sorted when using a digital sort
     1137                        // this is NOT an alphanumeric sort
     1138                        ts.getTextValue = function(a, num, mx) {
     1139                                if (mx) {
     1140                                        // make sure the text value is greater than the max numerical value (mx)
     1141                                        var i, l = a ? a.length : 0, n = mx + num;
     1142                                        for (i = 0; i < l; i++) {
     1143                                                n += a.charCodeAt(i);
     1144                                        }
     1145                                        return num * n;
     1146                                }
     1147                                return 0;
     1148                        };
     1149
     1150                        ts.sortNumericAsc = function(a, b, num, mx, col, table) {
     1151                                if (a === b) { return 0; }
     1152                                var c = table.config,
     1153                                        e = c.string[ (c.empties[col] || c.emptyTo ) ];
     1154                                if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
     1155                                if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
     1156                                if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
     1157                                if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
     1158                                return a - b;
     1159                        };
     1160
     1161                        ts.sortNumericDesc = function(a, b, num, mx, col, table) {
     1162                                if (a === b) { return 0; }
     1163                                var c = table.config,
     1164                                        e = c.string[ (c.empties[col] || c.emptyTo ) ];
     1165                                if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
     1166                                if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
     1167                                if (isNaN(a)) { a = ts.getTextValue(a, num, mx); }
     1168                                if (isNaN(b)) { b = ts.getTextValue(b, num, mx); }
     1169                                return b - a;
     1170                        };
     1171
     1172                        ts.sortNumeric = function(a, b) {
     1173                                return a - b;
     1174                        };
     1175
     1176                        // used when replacing accented characters during sorting
     1177                        ts.characterEquivalents = {
     1178                                "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5", // áàâãäąå
     1179                                "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5", // ÁÀÂÃÄĄÅ
     1180                                "c" : "\u00e7\u0107\u010d", // çćč
     1181                                "C" : "\u00c7\u0106\u010c", // ÇĆČ
     1182                                "e" : "\u00e9\u00e8\u00ea\u00eb\u011b\u0119", // éèêëěę
     1183                                "E" : "\u00c9\u00c8\u00ca\u00cb\u011a\u0118", // ÉÈÊËĚĘ
     1184                                "i" : "\u00ed\u00ec\u0130\u00ee\u00ef\u0131", // íìİîïı
     1185                                "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ
     1186                                "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö
     1187                                "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ
     1188                                "ss": "\u00df", // ß (s sharp)
     1189                                "SS": "\u1e9e", // ẞ (Capital sharp s)
     1190                                "u" : "\u00fa\u00f9\u00fb\u00fc\u016f", // úùûüů
     1191                                "U" : "\u00da\u00d9\u00db\u00dc\u016e" // ÚÙÛÜŮ
     1192                        };
     1193                        ts.replaceAccents = function(s) {
     1194                                var a, acc = '[', eq = ts.characterEquivalents;
     1195                                if (!ts.characterRegex) {
     1196                                        ts.characterRegexArray = {};
     1197                                        for (a in eq) {
     1198                                                if (typeof a === 'string') {
     1199                                                        acc += eq[a];
     1200                                                        ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g');
     1201                                                }
     1202                                        }
     1203                                        ts.characterRegex = new RegExp(acc + ']');
     1204                                }
     1205                                if (ts.characterRegex.test(s)) {
     1206                                        for (a in eq) {
     1207                                                if (typeof a === 'string') {
     1208                                                        s = s.replace( ts.characterRegexArray[a], a );
     1209                                                }
     1210                                        }
     1211                                }
     1212                                return s;
     1213                        };
     1214
     1215                        // *** utilities ***
     1216                        ts.isValueInArray = function(v, a) {
     1217                                var i, l = a.length;
     1218                                for (i = 0; i < l; i++) {
     1219                                        if (a[i][0] === v) {
     1220                                                return true;
     1221                                        }
     1222                                }
     1223                                return false;
     1224                        };
     1225
     1226                        ts.addParser = function(parser) {
     1227                                var i, l = ts.parsers.length, a = true;
     1228                                for (i = 0; i < l; i++) {
     1229                                        if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
     1230                                                a = false;
     1231                                        }
     1232                                }
     1233                                if (a) {
     1234                                        ts.parsers.push(parser);
     1235                                }
     1236                        };
     1237
     1238                        ts.getParserById = function(name) {
     1239                                var i, l = ts.parsers.length;
     1240                                for (i = 0; i < l; i++) {
     1241                                        if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) {
     1242                                                return ts.parsers[i];
     1243                                        }
     1244                                }
     1245                                return false;
     1246                        };
     1247
     1248                        ts.addWidget = function(widget) {
     1249                                ts.widgets.push(widget);
     1250                        };
     1251
     1252                        ts.getWidgetById = function(name) {
     1253                                var i, w, l = ts.widgets.length;
     1254                                for (i = 0; i < l; i++) {
     1255                                        w = ts.widgets[i];
     1256                                        if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
     1257                                                return w;
     1258                                        }
     1259                                }
     1260                        };
     1261
     1262                        ts.applyWidget = function(table, init) {
     1263                                table = $(table)[0]; // in case this is called externally
     1264                                var c = table.config,
     1265                                        wo = c.widgetOptions,
     1266                                        widgets = [],
     1267                                        time, w, wd;
     1268                                if (c.debug) { time = new Date(); }
     1269                                if (c.widgets.length) {
     1270                                        // ensure unique widget ids
     1271                                        c.widgets = $.grep(c.widgets, function(v, k){
     1272                                                return $.inArray(v, c.widgets) === k;
     1273                                        });
     1274                                        // build widget array & add priority as needed
     1275                                        $.each(c.widgets || [], function(i,n){
     1276                                                wd = ts.getWidgetById(n);
     1277                                                if (wd && wd.id) {
     1278                                                        // set priority to 10 if not defined
     1279                                                        if (!wd.priority) { wd.priority = 10; }
     1280                                                        widgets[i] = wd;
     1281                                                }
     1282                                        });
     1283                                        // sort widgets by priority
     1284                                        widgets.sort(function(a, b){
     1285                                                return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
     1286                                        });
     1287                                        // add/update selected widgets
     1288                                        $.each(widgets, function(i,w){
     1289                                                if (w) {
     1290                                                        if (init || !(c.widgetInit[w.id])) {
     1291                                                                if (w.hasOwnProperty('options')) {
     1292                                                                        wo = table.config.widgetOptions = $.extend( true, {}, w.options, wo );
     1293                                                                }
     1294                                                                if (w.hasOwnProperty('init')) {
     1295                                                                        w.init(table, w, c, wo);
     1296                                                                }
     1297                                                                c.widgetInit[w.id] = true;
     1298                                                        }
     1299                                                        if (!init && w.hasOwnProperty('format')) {
     1300                                                                w.format(table, c, wo, false);
     1301                                                        }
     1302                                                }
     1303                                        });
     1304                                }
     1305                                if (c.debug) {
     1306                                        w = c.widgets.length;
     1307                                        benchmark("Completed " + (init === true ? "initializing " : "applying ") + w + " widget" + (w !== 1 ? "s" : ""), time);
     1308                                }
     1309                        };
     1310
     1311                        ts.refreshWidgets = function(table, doAll, dontapply) {
     1312                                table = $(table)[0]; // see issue #243
     1313                                var i, c = table.config,
     1314                                        cw = c.widgets,
     1315                                        w = ts.widgets, l = w.length;
     1316                                // remove previous widgets
     1317                                for (i = 0; i < l; i++){
     1318                                        if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
     1319                                                if (c.debug) { log( 'Refeshing widgets: Removing ' + w[i].id  ); }
     1320                                                // only remove widgets that have been initialized - fixes #442
     1321                                                if (w[i].hasOwnProperty('remove') && c.widgetInit[w[i].id]) {
     1322                                                        w[i].remove(table, c, c.widgetOptions);
     1323                                                        c.widgetInit[w[i].id] = false;
     1324                                                }
     1325                                        }
     1326                                }
     1327                                if (dontapply !== true) {
     1328                                        ts.applyWidget(table, doAll);
     1329                                }
     1330                        };
     1331
     1332                        // get sorter, string, empty, etc options for each column from
     1333                        // jQuery data, metadata, header option or header class name ("sorter-false")
     1334                        // priority = jQuery data > meta > headers option > header class name
     1335                        ts.getData = function(h, ch, key) {
     1336                                var val = '', $h = $(h), m, cl;
     1337                                if (!$h.length) { return ''; }
     1338                                m = $.metadata ? $h.metadata() : false;
     1339                                cl = ' ' + ($h.attr('class') || '');
     1340                                if (typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined'){
     1341                                        // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder"
     1342                                        // "data-sort-initial-order" is assigned to "sortInitialOrder"
     1343                                        val += $h.data(key) || $h.data(key.toLowerCase());
     1344                                } else if (m && typeof m[key] !== 'undefined') {
     1345                                        val += m[key];
     1346                                } else if (ch && typeof ch[key] !== 'undefined') {
     1347                                        val += ch[key];
     1348                                } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
     1349                                        // include sorter class name "sorter-text", etc; now works with "sorter-my-custom-parser"
     1350                                        val = cl.match( new RegExp('\\s' + key + '-([\\w-]+)') )[1] || '';
     1351                                }
     1352                                return $.trim(val);
     1353                        };
     1354
     1355                        ts.formatFloat = function(s, table) {
     1356                                if (typeof s !== 'string' || s === '') { return s; }
     1357                                // allow using formatFloat without a table; defaults to US number format
     1358                                var i,
     1359                                        t = table && table.config ? table.config.usNumberFormat !== false :
     1360                                                typeof table !== "undefined" ? table : true;
     1361                                if (t) {
     1362                                        // US Format - 1,234,567.89 -> 1234567.89
     1363                                        s = s.replace(/,/g,'');
     1364                                } else {
     1365                                        // German Format = 1.234.567,89 -> 1234567.89
     1366                                        // French Format = 1 234 567,89 -> 1234567.89
     1367                                        s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.');
     1368                                }
     1369                                if(/^\s*\([.\d]+\)/.test(s)) {
     1370                                        // make (#) into a negative number -> (10) = -10
     1371                                        s = s.replace(/^\s*\(([.\d]+)\)/, '-$1');
     1372                                }
     1373                                i = parseFloat(s);
     1374                                // return the text instead of zero
     1375                                return isNaN(i) ? $.trim(s) : i;
     1376                        };
     1377
     1378                        ts.isDigit = function(s) {
     1379                                // replace all unwanted chars and match
     1380                                return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true;
     1381                        };
     1382
     1383                }()
     1384        });
     1385
     1386        // make shortcut
     1387        var ts = $.tablesorter;
     1388
     1389        // extend plugin scope
     1390        $.fn.extend({
     1391                tablesorter: ts.construct
     1392        });
     1393
     1394        // add default parsers
     1395        ts.addParser({
     1396                id: "text",
     1397                is: function() {
     1398                        return true;
     1399                },
     1400                format: function(s, table) {
     1401                        var c = table.config;
     1402                        if (s) {
     1403                                s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
     1404                                s = c.sortLocaleCompare ? ts.replaceAccents(s) : s;
     1405                        }
     1406                        return s;
     1407                },
     1408                type: "text"
     1409        });
     1410
     1411        ts.addParser({
     1412                id: "digit",
     1413                is: function(s) {
     1414                        return ts.isDigit(s);
     1415                },
     1416                format: function(s, table) {
     1417                        var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
     1418                        return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
     1419                },
     1420                type: "numeric"
     1421        });
     1422
     1423        ts.addParser({
     1424                id: "currency",
     1425                is: function(s) {
     1426                        return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[,. ]/g,'')); // £$€¤¥¢
     1427                },
     1428                format: function(s, table) {
     1429                        var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
     1430                        return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
     1431                },
     1432                type: "numeric"
     1433        });
     1434
     1435        ts.addParser({
     1436                id: "ipAddress",
     1437                is: function(s) {
     1438                        return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s);
     1439                },
     1440                format: function(s, table) {
     1441                        var i, a = s ? s.split(".") : '',
     1442                        r = "",
     1443                        l = a.length;
     1444                        for (i = 0; i < l; i++) {
     1445                                r += ("00" + a[i]).slice(-3);
     1446                        }
     1447                        return s ? ts.formatFloat(r, table) : s;
     1448                },
     1449                type: "numeric"
     1450        });
     1451
     1452        ts.addParser({
     1453                id: "url",
     1454                is: function(s) {
     1455                        return (/^(https?|ftp|file):\/\//).test(s);
     1456                },
     1457                format: function(s) {
     1458                        return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
     1459                },
     1460                type: "text"
     1461        });
     1462
     1463        ts.addParser({
     1464                id: "isoDate",
     1465                is: function(s) {
     1466                        return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
     1467                },
     1468                format: function(s, table) {
     1469                        return s ? ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table) : s;
     1470                },
     1471                type: "numeric"
     1472        });
     1473
     1474        ts.addParser({
     1475                id: "percent",
     1476                is: function(s) {
     1477                        return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
     1478                },
     1479                format: function(s, table) {
     1480                        return s ? ts.formatFloat(s.replace(/%/g, ""), table) : s;
     1481                },
     1482                type: "numeric"
     1483        });
     1484
     1485        ts.addParser({
     1486                id: "usLongDate",
     1487                is: function(s) {
     1488                        // two digit years are not allowed cross-browser
     1489                        // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
     1490                        return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s) || (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
     1491                },
     1492                format: function(s, table) {
     1493                        return s ? ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table) : s;
     1494                },
     1495                type: "numeric"
     1496        });
     1497
     1498        ts.addParser({
     1499                id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
     1500                is: function(s) {
     1501                        // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
     1502                        return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g," ").replace(/[\-.,]/g, "/"));
     1503                },
     1504                format: function(s, table, cell, cellIndex) {
     1505                        if (s) {
     1506                                var c = table.config, ci = c.headerList[cellIndex],
     1507                                format = ci.dateFormat || ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
     1508                                s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
     1509                                if (format === "mmddyyyy") {
     1510                                        s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
     1511                                } else if (format === "ddmmyyyy") {
     1512                                        s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
     1513                                } else if (format === "yyyymmdd") {
     1514                                        s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
     1515                                }
     1516                        }
     1517                        return s ? ts.formatFloat( (new Date(s).getTime() || ''), table) : s;
     1518                },
     1519                type: "numeric"
     1520        });
     1521
     1522        ts.addParser({
     1523                id: "time",
     1524                is: function(s) {
     1525                        return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
     1526                },
     1527                format: function(s, table) {
     1528                        return s ? ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ""), table) : s;
     1529                },
     1530                type: "numeric"
     1531        });
     1532
     1533        ts.addParser({
     1534                id: "metadata",
     1535                is: function() {
     1536                        return false;
     1537                },
     1538                format: function(s, table, cell) {
     1539                        var c = table.config,
     1540                        p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
     1541                        return $(cell).metadata()[p];
     1542                },
     1543                type: "numeric"
     1544        });
     1545
     1546        // add default widgets
     1547        ts.addWidget({
     1548                id: "zebra",
     1549                priority: 90,
     1550                format: function(table, c, wo) {
     1551                        var $tb, $tv, $tr, row, even, time, k, l,
     1552                        child = new RegExp(c.cssChildRow, 'i'),
     1553                        b = c.$tbodies;
     1554                        if (c.debug) {
     1555                                time = new Date();
     1556                        }
     1557                        for (k = 0; k < b.length; k++ ) {
     1558                                // loop through the visible rows
     1559                                $tb = b.eq(k);
     1560                                l = $tb.children('tr').length;
     1561                                if (l > 1) {
     1562                                        row = 0;
     1563                                        $tv = $tb.children('tr:visible').not(c.selectorRemove);
     1564                                        // revered back to using jQuery each - strangely it's the fastest method
     1565                                        /*jshint loopfunc:true */
     1566                                        $tv.each(function(){
     1567                                                $tr = $(this);
     1568                                                // style children rows the same way the parent row was styled
     1569                                                if (!child.test(this.className)) { row++; }
     1570                                                even = (row % 2 === 0);
     1571                                                $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]);
     1572                                        });
     1573                                }
     1574                        }
     1575                        if (c.debug) {
     1576                                ts.benchmark("Applying Zebra widget", time);
     1577                        }
     1578                },
     1579                remove: function(table, c, wo){
     1580                        var k, $tb,
     1581                                b = c.$tbodies,
     1582                                rmv = (wo.zebra || [ "even", "odd" ]).join(' ');
     1583                        for (k = 0; k < b.length; k++ ){
     1584                                $tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
     1585                                $tb.children().removeClass(rmv);
     1586                                $.tablesorter.processTbody(table, $tb, false); // restore tbody
     1587                        }
     1588                }
     1589        });
     1590
    10311591})(jQuery);
Note: See TracChangeset for help on using the changeset viewer.