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

Version 2.3.0, compatibility with Piwigo 2.6

Location:
extensions/LCAS/trunk/admin
Files:
7 added
6 edited

Legend:

Unmodified
Added
Removed
  • extensions/LCAS/trunk/admin/LCAS_admin.php

    r27129 r27186  
    4545    $_POST['LCAS_Mail'],
    4646    $_POST['LCAS_MailText']);
    47 
    48   conf_update_param('LoginCaseAccentsSensitivity', pwg_db_real_escape_string(serialize($newconf_LCAS)));
     47 
     48  $conf['LoginCaseAccentsSensitivity'] = serialize($newconf_LCAS);
     49
     50  conf_update_param('LoginCaseAccentsSensitivity',
     51          pwg_db_real_escape_string($conf['LoginCaseAccentsSensitivity']));
    4952
    5053  array_push($page['infos'], l10n('LCAS_save_config'));
  • extensions/LCAS/trunk/admin/template/global.tpl

    r27129 r27186  
    11{html_head}
    2   {include file= $LCAS_PATH_ABS|@cat:'template/header22.tpl'}
     2  {combine_script id='jquery' path='themes/default/js/jquery.min.js'}
     3  {combine_script id='jquery.cluetip' require='jquery' path='themes/default/js/plugins/jquery.cluetip.js'}
     4  {combine_script id='jquery.tablesorter' require='jquery' path=$LCAS_PATH|@cat:'admin/template/js/jquery.tablesorter.min.js'}
     5  {combine_script id='jquery.tablesorter.pager' require='jquery' path=$LCAS_PATH|@cat:'admin/template/js/jquery.tablesorter.pager.js'}
     6  {combine_css path= $LCAS_PATH|@cat:'admin/template/lcas.css'}
    37{/html_head}
    48
     
    96100            </label>
    97101            <br><br>
    98               <input type="radio" value="false" {$LCAS_MAIL_FALSE} name="LCAS_Mail">
     102            <label for = "LCAS_mail_0" id ="LCAS_mail_0_">
     103              <input type="radio" value="false" {$LCAS_MAIL_FALSE} name="LCAS_Mail" id = "LCAS_mail_0">
    99104                {'LCAS_DisableOption'|@translate}<br>
    100               <input type="radio" value="true" {$LCAS_MAIL_TRUE} name="LCAS_Mail">
     105            </label>
     106            <label for = "LCAS_mail_1" id ="LCAS_mail_1_">
     107              <input type="radio" value="true" {$LCAS_MAIL_TRUE} name="LCAS_Mail" id = "LCAS_mail_1">
    101108                {'LCAS_EnableOption'|@translate}<br>
     109            </label>
     110            <br><br>
     111
     112            <ul>
     113              <li>
     114                <label class="cluetip" title="{'LCAS_Email_Customization'|translate}|{'LCAS_Email_Customization_d'|translate}">
     115                  {'LCAS_MailText'|@translate}
     116                </label>
    102117              <br><br>
    103 
    104               <ul>
    105                 <li>
    106                   <label class="cluetip" title="{'LCAS_Email_Customization'|translate}|{'LCAS_Email_Customization_d'|translate}">
    107                     {'LCAS_MailText'|@translate}
    108                   </label>
    109                 <br><br>
    110                   <textarea class="LCAS_Mailtextfields" name="LCAS_MailText" id="LCAS_MailText" rows="10" {$TAG_INPUT_ENABLED}>{$LCAS_MAILTEXT}</textarea>
    111                 <br><br>
    112                 </li>
    113               </ul>
     118              <textarea class="LCAS_Mailtextfields" name="LCAS_MailText" id="LCAS_MailText" rows="10" {$TAG_INPUT_ENABLED}>{$LCAS_MAILTEXT}</textarea>
     119              <br><br>
     120              </li>
     121            </ul>
    114122          </li>
    115123        </ul>
  • extensions/LCAS/trunk/admin/template/js/jquery.metadata.js

    r9229 r27186  
    22 * Metadata - jQuery plugin for parsing metadata from elements
    33 *
    4  * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
     4 * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
    55 *
    66 * Dual licensed under the MIT and GPL licenses:
    77 *   http://www.opensource.org/licenses/mit-license.php
    88 *   http://www.gnu.org/licenses/gpl.html
    9  *
    10  * Revision: $Id$
    119 *
    1210 */
     
    1917 *
    2018 *   attr:  Inside an attribute. The name parameter indicates *which* attribute.
    21  *         
     19 *
    2220 *   class: Inside the class attribute, wrapped in curly braces: { }
    23  *   
     21 *
    2422 *   elem:  Inside a child element (e.g. a script tag). The
    2523 *          name parameter indicates *which* element.
    26  *         
     24 *
    2725 * The metadata for an element is loaded the first time the element is accessed via jQuery.
    2826 *
    2927 * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
    3028 * matched by expr, then redefine the metadata type and run another $(expr) for other elements.
    31  * 
     29 *
    3230 * @name $.metadata.setType
    3331 *
     
    3634 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
    3735 * @desc Reads metadata from the class attribute
    38  * 
     36 *
    3937 * @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
    4038 * @before $.metadata.setType("attr", "data")
    4139 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
    4240 * @desc Reads metadata from a "data" attribute
    43  * 
     41 *
    4442 * @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
    4543 * @before $.metadata.setType("elem", "script")
    4644 * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
    4745 * @desc Reads metadata from a nested script element
    48  * 
     46 *
    4947 * @param String type The encoding type
    5048 * @param String name The name of the attribute to be used to get metadata (optional)
     
    6260                        type: 'class',
    6361                        name: 'metadata',
    64                         cre: /({.*})/,
     62                        cre: /(\{.*\})/,
    6563                        single: 'metadata'
    6664                },
     
    7068                },
    7169                get: function( elem, opts ){
    72                         var settings = $.extend({},this.defaults,opts);
     70                        var data, m, e, attr,
     71                                settings = $.extend({},this.defaults,opts);
    7372                        // check for empty string in single property
    74                         if ( !settings.single.length ) settings.single = 'metadata';
    75                        
    76                         var data = $.data(elem, settings.single);
     73                        if ( !settings.single.length ) { settings.single = 'metadata'; }
     74
     75                        data = $.data(elem, settings.single);
    7776                        // returned cached data if it already exists
    78                         if ( data ) return data;
    79                        
     77                        if ( data ) { return data; }
     78
    8079                        data = "{}";
    81                        
    82                         if ( settings.type == "class" ) {
    83                                 var m = settings.cre.exec( elem.className );
    84                                 if ( m )
    85                                         data = m[1];
    86                         } else if ( settings.type == "elem" ) {
    87                                 if( !elem.getElementsByTagName )
    88                                         return undefined;
    89                                 var e = elem.getElementsByTagName(settings.name);
    90                                 if ( e.length )
    91                                         data = $.trim(e[0].innerHTML);
    92                         } else if ( elem.getAttribute != undefined ) {
    93                                 var attr = elem.getAttribute( settings.name );
    94                                 if ( attr )
    95                                         data = attr;
     80
     81                        if ( settings.type === "class" ) {
     82                                m = settings.cre.exec( elem.className );
     83                                if ( m ) { data = m[1]; }
     84                        } else if ( settings.type === "elem" ) {
     85                                if( !elem.getElementsByTagName ) { return undefined; }
     86                                e = elem.getElementsByTagName(settings.name);
     87                                if ( e.length ) { data = $.trim(e[0].innerHTML); }
     88                        } else if ( elem.getAttribute !== undefined ) {
     89                                attr = elem.getAttribute( settings.name );
     90                                if ( attr ) { data = attr; }
    9691                        }
    97                        
    98                         if ( data.indexOf( '{' ) <0 )
    99                         data = "{" + data + "}";
    100                        
     92
     93                        if ( data.indexOf( '{' ) <0 ) { data = "{" + data + "}"; }
     94
    10195                        data = eval("(" + data + ")");
    102                        
     96
    10397                        $.data( elem, settings.single, data );
    10498                        return data;
  • extensions/LCAS/trunk/admin/template/js/jquery.tablesorter.js

    r9326 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);
  • extensions/LCAS/trunk/admin/template/js/jquery.tablesorter.min.js

    r9326 r27186  
    1 
    2 (function($){$.extend({tablesorter:new
    3 function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
    4 var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];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));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling expression:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){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)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
     1/*!
     2* TableSorter 2.14.4 min - Client-side table sorting with ease!
     3* Copyright (c) 2007 Christian Bach
     4*/
     5!function(g){g.extend({tablesorter:new function(){function c(){var a=1<arguments.length?Array.prototype.slice.call(arguments):arguments[0];"undefined"!==typeof console&&"undefined"!==typeof console.log?console.log(a):alert(a)}function r(a,b){c(a+" ("+((new Date).getTime()-b.getTime())+"ms)")}function k(a){for(var b in a)return!1;return!0}function p(a,b,d){if(!b)return"";var h=a.config,e=h.textExtraction,n="",n="simple"===e?h.supportsTextContent?b.textContent:g(b).text():"function"===typeof e?e(b, a,d):"object"===typeof e&&e.hasOwnProperty(d)?e[d](b,a,d):h.supportsTextContent?b.textContent:g(b).text();return g.trim(n)}function s(a){var b=a.config,d=b.$tbodies=b.$table.children("tbody:not(."+b.cssInfoBlock+")"),h,e,n,l,x,g,m,v="";if(0===d.length)return b.debug?c("*Empty table!* Not building a parser cache"):"";b.debug&&(m=new Date,c("Detecting parsers for each column"));d=d[0].rows;if(d[0])for(h=[],e=d[0].cells.length,n=0;n<e;n++){l=b.$headers.filter(":not([colspan])");l=l.add(b.$headers.filter('[colspan="1"]')).filter('[data-column="'+ n+'"]:last');x=b.headers[n];g=f.getParserById(f.getData(l,x,"sorter"));b.empties[n]=f.getData(l,x,"empty")||b.emptyTo||(b.emptyToBottom?"bottom":"top");b.strings[n]=f.getData(l,x,"string")||b.stringTo||"max";if(!g)a:{l=a;x=d;g=-1;for(var k=n,u=void 0,A=f.parsers.length,q=!1,s="",u=!0;""===s&&u;)g++,x[g]?(q=x[g].cells[k],s=p(l,q,k),l.config.debug&&c("Checking if value was empty on row "+g+", column: "+k+': "'+s+'"')):u=!1;for(;0<=--A;)if((u=f.parsers[A])&&"text"!==u.id&&u.is&&u.is(s,l,q)){g=u;break a}g= f.getParserById("text")}b.debug&&(v+="column:"+n+"; parser:"+g.id+"; string:"+b.strings[n]+"; empty: "+b.empties[n]+"\n");h.push(g)}b.debug&&(c(v),r("Completed detecting parsers",m));b.parsers=h}function w(a){var b=a.tBodies,d=a.config,h,e,n=d.parsers,l,x,y,m,v,k,u,A=[];d.cache={};if(!n)return d.debug?c("*Empty table!* Not building a cache"):"";d.debug&&(u=new Date);d.showProcessing&&f.isProcessing(a,!0);for(m=0;m<b.length;m++)if(d.cache[m]={row:[],normalized:[]},!g(b[m]).hasClass(d.cssInfoBlock)){h= b[m]&&b[m].rows.length||0;e=b[m].rows[0]&&b[m].rows[0].cells.length||0;for(x=0;x<h;++x)if(v=g(b[m].rows[x]),k=[],v.hasClass(d.cssChildRow))d.cache[m].row[d.cache[m].row.length-1]=d.cache[m].row[d.cache[m].row.length-1].add(v);else{d.cache[m].row.push(v);for(y=0;y<e;++y)l=p(a,v[0].cells[y],y),l=n[y].format(l,a,v[0].cells[y],y),k.push(l),"numeric"===(n[y].type||"").toLowerCase()&&(A[y]=Math.max(Math.abs(l)||0,A[y]||0));k.push(d.cache[m].normalized.length);d.cache[m].normalized.push(k)}d.cache[m].colMax= A}d.showProcessing&&f.isProcessing(a);d.debug&&r("Building cache for "+h+" rows",u)}function z(a,b){var d=a.config,h=d.widgetOptions,e=a.tBodies,n=[],l=d.cache,c,y,m,v,p,u,A,q,s,t,w;if(!k(l)){d.debug&&(w=new Date);for(q=0;q<e.length;q++)if(c=g(e[q]),c.length&&!c.hasClass(d.cssInfoBlock)){p=f.processTbody(a,c,!0);c=l[q].row;y=l[q].normalized;v=(m=y.length)?y[0].length-1:0;for(u=0;u<m;u++)if(t=y[u][v],n.push(c[t]),!d.appender||d.pager&&!(d.pager.removeRows&&h.pager_removeRows||d.pager.ajax))for(s=c[t].length, A=0;A<s;A++)p.append(c[t][A]);f.processTbody(a,p,!1)}d.appender&&d.appender(a,n);d.debug&&r("Rebuilt table",w);b||d.appender||f.applyWidget(a);g(a).trigger("sortEnd",a);g(a).trigger("updateComplete",a)}}function D(a){var b=[],d={},h=0,e=g(a).find("thead:eq(0), tfoot").children("tr"),n,l,c,f,m,k,r,p,t,q;for(n=0;n<e.length;n++)for(m=e[n].cells,l=0;l<m.length;l++){f=m[l];k=f.parentNode.rowIndex;r=k+"-"+f.cellIndex;p=f.rowSpan||1;t=f.colSpan||1;"undefined"===typeof b[k]&&(b[k]=[]);for(c=0;c<b[k].length+ 1;c++)if("undefined"===typeof b[k][c]){q=c;break}d[r]=q;h=Math.max(q,h);g(f).attr({"data-column":q});for(c=k;c<k+p;c++)for("undefined"===typeof b[c]&&(b[c]=[]),r=b[c],f=q;f<q+t;f++)r[f]="x"}a.config.columns=h+1;return d}function C(a){return/^d/i.test(a)||1===a}function E(a){var b=D(a),d,h,e,n,l,x,k,m=a.config;m.headerList=[];m.headerContent=[];m.debug&&(k=new Date);n=m.cssIcon?'<i class="'+(m.cssIcon===f.css.icon?f.css.icon:m.cssIcon+" "+f.css.icon)+'"></i>':"";m.$headers=g(a).find(m.selectorHeaders).each(function(a){h= g(this);d=m.headers[a];m.headerContent[a]=g(this).html();l=m.headerTemplate.replace(/\{content\}/g,g(this).html()).replace(/\{icon\}/g,n);m.onRenderTemplate&&(e=m.onRenderTemplate.apply(h,[a,l]))&&"string"===typeof e&&(l=e);g(this).html('<div class="tablesorter-header-inner">'+l+"</div>");m.onRenderHeader&&m.onRenderHeader.apply(h,[a]);this.column=b[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=C(f.getData(h,d,"sortInitialOrder")||m.sortInitialOrder)?[1,0,2]:[0,1,2];this.count=-1;this.lockedOrder= !1;x=f.getData(h,d,"lockedOrder")||!1;"undefined"!==typeof x&&!1!==x&&(this.order=this.lockedOrder=C(x)?[1,1,1]:[0,0,0]);h.addClass(f.css.header+" "+m.cssHeader);m.headerList[a]=this;h.parent().addClass(f.css.headerRow+" "+m.cssHeaderRow);m.tabIndex&&h.attr("tabindex",0)});F(a);m.debug&&(r("Built headers:",k),c(m.$headers))}function B(a,b,d){var h=a.config;h.$table.find(h.selectorRemove).remove();s(a);w(a);G(h.$table,b,d)}function F(a){var b,d=a.config;d.$headers.each(function(a,e){b="false"===f.getData(e, d.headers[a],"sorter");e.sortDisabled=b;g(e)[b?"addClass":"removeClass"]("sorter-false")})}function H(a){var b,d,h,e=a.config,n=e.sortList,l=[f.css.sortAsc+" "+e.cssAsc,f.css.sortDesc+" "+e.cssDesc],c=g(a).find("tfoot tr").children().removeClass(l.join(" "));e.$headers.removeClass(l.join(" "));h=n.length;for(b=0;b<h;b++)if(2!==n[b][1]&&(a=e.$headers.not(".sorter-false").filter('[data-column="'+n[b][0]+'"]'+(1===h?":last":"")),a.length))for(d=0;d<a.length;d++)a[d].sortDisabled||(a.eq(d).addClass(l[n[b][1]]), c.length&&c.filter('[data-column="'+n[b][0]+'"]').eq(d).addClass(l[n[b][1]]))}function L(a){if(a.config.widthFixed&&0===g(a).find("colgroup").length){var b=g("<colgroup>"),d=g(a).width();g(a.tBodies[0]).find("tr:first").children("td:visible").each(function(){b.append(g("<col>").css("width",parseInt(g(this).width()/d*1E3,10)/10+"%"))});g(a).prepend(b)}}function M(a,b){var d,h,e,n=a.config,c=b||n.sortList;n.sortList=[];g.each(c,function(a,b){d=[parseInt(b[0],10),parseInt(b[1],10)];if(e=n.$headers[d[0]])n.sortList.push(d), h=g.inArray(d[1],e.order),e.count=0<=h?h:d[1]%(n.sortReset?3:2)})}function N(a,b){return a&&a[b]?a[b].type||"":""}function O(a,b,d){var h,e,n,c=a.config,x=!d[c.sortMultiSortKey],k=g(a);k.trigger("sortStart",a);b.count=d[c.sortResetKey]?2:(b.count+1)%(c.sortReset?3:2);c.sortRestart&&(e=b,c.$headers.each(function(){this===e||!x&&g(this).is("."+f.css.sortDesc+",."+f.css.sortAsc)||(this.count=-1)}));e=b.column;if(x){c.sortList=[];if(null!==c.sortForce)for(h=c.sortForce,d=0;d<h.length;d++)h[d][0]!==e&& c.sortList.push(h[d]);h=b.order[b.count];if(2>h&&(c.sortList.push([e,h]),1<b.colSpan))for(d=1;d<b.colSpan;d++)c.sortList.push([e+d,h])}else if(c.sortAppend&&1<c.sortList.length&&f.isValueInArray(c.sortAppend[0][0],c.sortList)&&c.sortList.pop(),f.isValueInArray(e,c.sortList))for(d=0;d<c.sortList.length;d++)n=c.sortList[d],h=c.$headers[n[0]],n[0]===e&&(n[1]=h.order[b.count],2===n[1]&&(c.sortList.splice(d,1),h.count=-1));else if(h=b.order[b.count],2>h&&(c.sortList.push([e,h]),1<b.colSpan))for(d=1;d< b.colSpan;d++)c.sortList.push([e+d,h]);if(null!==c.sortAppend)for(h=c.sortAppend,d=0;d<h.length;d++)h[d][0]!==e&&c.sortList.push(h[d]);k.trigger("sortBegin",a);setTimeout(function(){H(a);I(a);z(a)},1)}function I(a){var b,d,c,e,n,l,g,p,m,v,t,u,s=0,q=a.config,w=q.textSorter||"",z=q.sortList,B=z.length,C=a.tBodies.length;if(!q.serverSideSorting&&!k(q.cache)){q.debug&&(m=new Date);for(d=0;d<C;d++)n=q.cache[d].colMax,p=(l=q.cache[d].normalized)&&l[0]?l[0].length-1:0,l.sort(function(d,l){for(b=0;b<B;b++){e= z[b][0];g=z[b][1];s=0===g;if(q.sortStable&&d[e]===l[e]&&1===B)break;(c=/n/i.test(N(q.parsers,e)))&&q.strings[e]?(c="boolean"===typeof q.string[q.strings[e]]?(s?1:-1)*(q.string[q.strings[e]]?-1:1):q.strings[e]?q.string[q.strings[e]]||0:0,v=q.numberSorter?q.numberSorter(t[e],u[e],s,n[e],a):f["sortNumeric"+(s?"Asc":"Desc")](d[e],l[e],c,n[e],e,a)):(t=s?d:l,u=s?l:d,v="function"===typeof w?w(t[e],u[e],s,e,a):"object"===typeof w&&w.hasOwnProperty(e)?w[e](t[e],u[e],s,e,a):f["sortNatural"+(s?"Asc":"Desc")](d[e], l[e],e,a,q));if(v)return v}return d[p]-l[p]});q.debug&&r("Sorting on "+z.toString()+" and dir "+g+" time",m)}}function J(a,b){var d=a[0].config;d.pager&&!d.pager.ajax&&a.trigger("updateComplete");"function"===typeof b&&b(a[0])}function G(a,b,d){!1===b||a[0].isProcessing?J(a,d):a.trigger("sorton",[a[0].config.sortList,function(){J(a,d)}])}function K(a){var b=a.config,d=b.$table,c,e;b.$headers.find(b.selectorSort).add(b.$headers.filter(b.selectorSort)).unbind("mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter").bind("mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter", function(d,c){if(!(1!==(d.which||d.button)&&!/sort|keypress/.test(d.type)||"keypress"===d.type&&13!==d.which||"mouseup"===d.type&&!0!==c&&250<(new Date).getTime()-e)){if("mousedown"===d.type)return e=(new Date).getTime(),"INPUT"===d.target.tagName?"":!b.cancelSelection;b.delayInit&&k(b.cache)&&w(a);var h=(/TH|TD/.test(this.tagName)?g(this):g(this).parents("th, td").filter(":first"))[0];h.sortDisabled||O(a,h,d)}});b.cancelSelection&&b.$headers.attr("unselectable","on").bind("selectstart",!1).css({"user-select":"none", MozUserSelect:"none"});d.unbind("sortReset update updateRows updateCell updateAll addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave ".split(" ").join(".tablesorter ")).bind("sortReset.tablesorter",function(d){d.stopPropagation();b.sortList=[];H(a);I(a);z(a)}).bind("updateAll.tablesorter",function(b,d,c){b.stopPropagation();f.refreshWidgets(a,!0,!0);f.restoreHeaders(a);E(a);K(a);B(a,d,c)}).bind("update.tablesorter updateRows.tablesorter",function(b,d,c){b.stopPropagation(); F(a);B(a,d,c)}).bind("updateCell.tablesorter",function(c,e,h,f){c.stopPropagation();d.find(b.selectorRemove).remove();var m,k,r;m=d.find("tbody");c=m.index(g(e).parents("tbody").filter(":first"));var s=g(e).parents("tr").filter(":first");e=g(e)[0];m.length&&0<=c&&(k=m.eq(c).find("tr").index(s),r=e.cellIndex,m=b.cache[c].normalized[k].length-1,b.cache[c].row[a.config.cache[c].normalized[k][m]]=s,b.cache[c].normalized[k][r]=b.parsers[r].format(p(a,e,r),a,e,r),G(d,h,f))}).bind("addRows.tablesorter", function(e,f,g,r){e.stopPropagation();if(k(b.cache))F(a),B(a,g,r);else{var m=f.filter("tr").length,v=[],w=f[0].cells.length,u=d.find("tbody").index(f.parents("tbody").filter(":first"));b.parsers||s(a);for(e=0;e<m;e++){for(c=0;c<w;c++)v[c]=b.parsers[c].format(p(a,f[e].cells[c],c),a,f[e].cells[c],c);v.push(b.cache[u].row.length);b.cache[u].row.push([f[e]]);b.cache[u].normalized.push(v);v=[]}G(d,g,r)}}).bind("sorton.tablesorter",function(b,c,e,h){var f=a.config;b.stopPropagation();d.trigger("sortStart", this);M(a,c);H(a);f.delayInit&&k(f.cache)&&w(a);d.trigger("sortBegin",this);I(a);z(a,h);"function"===typeof e&&e(a)}).bind("appendCache.tablesorter",function(b,d,c){b.stopPropagation();z(a,c);"function"===typeof d&&d(a)}).bind("applyWidgetId.tablesorter",function(d,c){d.stopPropagation();f.getWidgetById(c).format(a,b,b.widgetOptions)}).bind("applyWidgets.tablesorter",function(b,d){b.stopPropagation();f.applyWidget(a,d)}).bind("refreshWidgets.tablesorter",function(b,d,c){b.stopPropagation();f.refreshWidgets(a, d,c)}).bind("destroy.tablesorter",function(b,d,c){b.stopPropagation();f.destroy(a,d,c)})}var f=this;f.version="2.14.4";f.parsers=[];f.widgets=[];f.defaults={theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,tabIndex:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,headers:{},ignoreCase:!0,sortForce:null,sortList:[],sortAppend:null,sortStable:!1, sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",textExtraction:"simple",textSorter:null,numberSorter:null,widgets:[],widgetOptions:{zebra:["even","odd"]},initWidgets:!0,initialized:null,tableClass:"",cssAsc:"",cssDesc:"",cssHeader:"",cssHeaderRow:"",cssProcessing:"",cssChildRow:"tablesorter-childRow",cssIcon:"tablesorter-icon",cssInfoBlock:"tablesorter-infoOnly",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me", debug:!1,headerList:[],empties:{},strings:{},parsers:[]};f.css={table:"tablesorter",childRow:"tablesorter-childRow",header:"tablesorter-header",headerRow:"tablesorter-headerRow",icon:"tablesorter-icon",info:"tablesorter-infoOnly",processing:"tablesorter-processing",sortAsc:"tablesorter-headerAsc",sortDesc:"tablesorter-headerDesc"};f.log=c;f.benchmark=r;f.construct=function(a){return this.each(function(){var b=g.extend(!0,{},f.defaults,a);!this.hasInitialized&&f.buildTable&&"TABLE"!==this.tagName&& f.buildTable(this,b);f.setup(this,b)})};f.setup=function(a,b){if(!a||!a.tHead||0===a.tBodies.length||!0===a.hasInitialized)return b.debug?c("stopping initialization! No table, thead, tbody or tablesorter has already been initialized"):"";var d="",h=g(a),e=g.metadata;a.hasInitialized=!1;a.isProcessing=!0;a.config=b;g.data(a,"tablesorter",b);b.debug&&g.data(a,"startoveralltimer",new Date);b.supportsTextContent="x"===g("<span>x</span>")[0].textContent;b.supportsDataObject=function(a){a[0]=parseInt(a[0], 10);return 1<a[0]||1===a[0]&&4<=parseInt(a[1],10)}(g.fn.jquery.split("."));b.string={max:1,min:-1,"max+":1,"max-":-1,zero:0,none:0,"null":0,top:!0,bottom:!1};/tablesorter\-/.test(h.attr("class"))||(d=""!==b.theme?" tablesorter-"+b.theme:"");b.$table=h.addClass(f.css.table+" "+b.tableClass+d);b.$tbodies=h.children("tbody:not(."+b.cssInfoBlock+")");b.widgetInit={};E(a);L(a);s(a);b.delayInit||w(a);K(a);b.supportsDataObject&&"undefined"!==typeof h.data().sortlist?b.sortList=h.data().sortlist:e&&h.metadata()&& h.metadata().sortlist&&(b.sortList=h.metadata().sortlist);f.applyWidget(a,!0);0<b.sortList.length?h.trigger("sorton",[b.sortList,{},!b.initWidgets]):b.initWidgets&&f.applyWidget(a);b.showProcessing&&h.unbind("sortBegin.tablesorter sortEnd.tablesorter").bind("sortBegin.tablesorter sortEnd.tablesorter",function(b){f.isProcessing(a,"sortBegin"===b.type)});a.hasInitialized=!0;a.isProcessing=!1;b.debug&&f.benchmark("Overall initialization time",g.data(a,"startoveralltimer"));h.trigger("tablesorter-initialized", a);"function"===typeof b.initialized&&b.initialized(a)};f.isProcessing=function(a,b,d){a=g(a);var c=a[0].config;a=d||a.find("."+f.css.header);b?(0<c.sortList.length&&(a=a.filter(function(){return this.sortDisabled?!1:f.isValueInArray(parseFloat(g(this).attr("data-column")),c.sortList)})),a.addClass(f.css.processing+" "+c.cssProcessing)):a.removeClass(f.css.processing+" "+c.cssProcessing)};f.processTbody=function(a,b,d){if(d)return a.isProcessing=!0,b.before('<span class="tablesorter-savemyplace"/>'), d=g.fn.detach?b.detach():b.remove();d=g(a).find("span.tablesorter-savemyplace");b.insertAfter(d);d.remove();a.isProcessing=!1};f.clearTableBody=function(a){g(a)[0].config.$tbodies.empty()};f.restoreHeaders=function(a){var b=a.config;b.$table.find(b.selectorHeaders).each(function(a){g(this).find(".tablesorter-header-inner").length&&g(this).html(b.headerContent[a])})};f.destroy=function(a,b,d){a=g(a)[0];if(a.hasInitialized){f.refreshWidgets(a,!0,!0);var c=g(a),e=a.config,n=c.find("thead:first"),l=n.find("tr."+ f.css.headerRow).removeClass(f.css.headerRow+" "+e.cssHeaderRow),k=c.find("tfoot:first > tr").children("th, td");n.find("tr").not(l).remove();c.removeData("tablesorter").unbind("sortReset update updateAll updateRows updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd ".split(" ").join(".tablesorter "));e.$headers.add(k).removeClass([f.css.header,e.cssHeader,e.cssAsc,e.cssDesc,f.css.sortAsc,f.css.sortDesc].join(" ")).removeAttr("data-column"); l.find(e.selectorSort).unbind("mousedown.tablesorter mouseup.tablesorter keypress.tablesorter");f.restoreHeaders(a);!1!==b&&c.removeClass(f.css.table+" "+e.tableClass+" tablesorter-"+e.theme);a.hasInitialized=!1;"function"===typeof d&&d(a)}};f.regex={chunk:/(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,hex:/^0x[0-9a-f]+$/i};f.sortNatural=function(a,b){if(a===b)return 0;var d,c,e,g,l,k;c=f.regex;if(c.hex.test(b)){d=parseInt(a.match(c.hex),16);e=parseInt(b.match(c.hex), 16);if(d<e)return-1;if(d>e)return 1}d=a.replace(c.chunk,"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");c=b.replace(c.chunk,"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");k=Math.max(d.length,c.length);for(l=0;l<k;l++){e=isNaN(d[l])?d[l]||0:parseFloat(d[l])||0;g=isNaN(c[l])?c[l]||0:parseFloat(c[l])||0;if(isNaN(e)!==isNaN(g))return isNaN(e)?1:-1;typeof e!==typeof g&&(e+="",g+="");if(e<g)return-1;if(e>g)return 1}return 0};f.sortNaturalAsc=function(a,b,d,c,e){if(a===b)return 0; d=e.string[e.empties[d]||e.emptyTo];return""===a&&0!==d?"boolean"===typeof d?d?-1:1:-d||-1:""===b&&0!==d?"boolean"===typeof d?d?1:-1:d||1:f.sortNatural(a,b)};f.sortNaturalDesc=function(a,b,d,c,e){if(a===b)return 0;d=e.string[e.empties[d]||e.emptyTo];return""===a&&0!==d?"boolean"===typeof d?d?-1:1:d||1:""===b&&0!==d?"boolean"===typeof d?d?1:-1:-d||-1:f.sortNatural(b,a)};f.sortText=function(a,b){return a>b?1:a<b?-1:0};f.getTextValue=function(a,b,d){if(d){var c=a?a.length:0,e=d+b;for(d=0;d<c;d++)e+= a.charCodeAt(d);return b*e}return 0};f.sortNumericAsc=function(a,b,d,c,e,g){if(a===b)return 0;g=g.config;e=g.string[g.empties[e]||g.emptyTo];if(""===a&&0!==e)return"boolean"===typeof e?e?-1:1:-e||-1;if(""===b&&0!==e)return"boolean"===typeof e?e?1:-1:e||1;isNaN(a)&&(a=f.getTextValue(a,d,c));isNaN(b)&&(b=f.getTextValue(b,d,c));return a-b};f.sortNumericDesc=function(a,b,d,c,e,g){if(a===b)return 0;g=g.config;e=g.string[g.empties[e]||g.emptyTo];if(""===a&&0!==e)return"boolean"===typeof e?e?-1:1:e||1;if(""=== b&&0!==e)return"boolean"===typeof e?e?1:-1:-e||-1;isNaN(a)&&(a=f.getTextValue(a,d,c));isNaN(b)&&(b=f.getTextValue(b,d,c));return b-a};f.sortNumeric=function(a,b){return a-b};f.characterEquivalents={a:"\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5",A:"\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5",c:"\u00e7\u0107\u010d",C:"\u00c7\u0106\u010c",e:"\u00e9\u00e8\u00ea\u00eb\u011b\u0119",E:"\u00c9\u00c8\u00ca\u00cb\u011a\u0118",i:"\u00ed\u00ec\u0130\u00ee\u00ef\u0131",I:"\u00cd\u00cc\u0130\u00ce\u00cf",o:"\u00f3\u00f2\u00f4\u00f5\u00f6", O:"\u00d3\u00d2\u00d4\u00d5\u00d6",ss:"\u00df",SS:"\u1e9e",u:"\u00fa\u00f9\u00fb\u00fc\u016f",U:"\u00da\u00d9\u00db\u00dc\u016e"};f.replaceAccents=function(a){var b,d="[",c=f.characterEquivalents;if(!f.characterRegex){f.characterRegexArray={};for(b in c)"string"===typeof b&&(d+=c[b],f.characterRegexArray[b]=RegExp("["+c[b]+"]","g"));f.characterRegex=RegExp(d+"]")}if(f.characterRegex.test(a))for(b in c)"string"===typeof b&&(a=a.replace(f.characterRegexArray[b],b));return a};f.isValueInArray=function(a, b){var d,c=b.length;for(d=0;d<c;d++)if(b[d][0]===a)return!0;return!1};f.addParser=function(a){var b,d=f.parsers.length,c=!0;for(b=0;b<d;b++)f.parsers[b].id.toLowerCase()===a.id.toLowerCase()&&(c=!1);c&&f.parsers.push(a)};f.getParserById=function(a){var b,d=f.parsers.length;for(b=0;b<d;b++)if(f.parsers[b].id.toLowerCase()===a.toString().toLowerCase())return f.parsers[b];return!1};f.addWidget=function(a){f.widgets.push(a)};f.getWidgetById=function(a){var b,d,c=f.widgets.length;for(b=0;b<c;b++)if((d= f.widgets[b])&&d.hasOwnProperty("id")&&d.id.toLowerCase()===a.toLowerCase())return d};f.applyWidget=function(a,b){a=g(a)[0];var d=a.config,c=d.widgetOptions,e=[],k,l,p;d.debug&&(k=new Date);d.widgets.length&&(d.widgets=g.grep(d.widgets,function(a,b){return g.inArray(a,d.widgets)===b}),g.each(d.widgets||[],function(a,b){(p=f.getWidgetById(b))&&p.id&&(p.priority||(p.priority=10),e[a]=p)}),e.sort(function(a,b){return a.priority<b.priority?-1:a.priority===b.priority?0:1}),g.each(e,function(e,f){if(f){if(b|| !d.widgetInit[f.id])f.hasOwnProperty("options")&&(c=a.config.widgetOptions=g.extend(!0,{},f.options,c)),f.hasOwnProperty("init")&&f.init(a,f,d,c),d.widgetInit[f.id]=!0;!b&&f.hasOwnProperty("format")&&f.format(a,d,c,!1)}}));d.debug&&(l=d.widgets.length,r("Completed "+(!0===b?"initializing ":"applying ")+l+" widget"+(1!==l?"s":""),k))};f.refreshWidgets=function(a,b,d){a=g(a)[0];var h,e=a.config,k=e.widgets,l=f.widgets,r=l.length;for(h=0;h<r;h++)l[h]&&l[h].id&&(b||0>g.inArray(l[h].id,k))&&(e.debug&& c("Refeshing widgets: Removing "+l[h].id),l[h].hasOwnProperty("remove")&&e.widgetInit[l[h].id]&&(l[h].remove(a,e,e.widgetOptions),e.widgetInit[l[h].id]=!1));!0!==d&&f.applyWidget(a,b)};f.getData=function(a,b,d){var c="";a=g(a);var e,f;if(!a.length)return"";e=g.metadata?a.metadata():!1;f=" "+(a.attr("class")||"");"undefined"!==typeof a.data(d)||"undefined"!==typeof a.data(d.toLowerCase())?c+=a.data(d)||a.data(d.toLowerCase()):e&&"undefined"!==typeof e[d]?c+=e[d]:b&&"undefined"!==typeof b[d]?c+=b[d]: " "!==f&&f.match(" "+d+"-")&&(c=f.match(RegExp("\\s"+d+"-([\\w-]+)"))[1]||"");return g.trim(c)};f.formatFloat=function(a,b){if("string"!==typeof a||""===a)return a;var c;a=(b&&b.config?!1!==b.config.usNumberFormat:"undefined"!==typeof b?b:1)?a.replace(/,/g,""):a.replace(/[\s|\.]/g,"").replace(/,/g,".");/^\s*\([.\d]+\)/.test(a)&&(a=a.replace(/^\s*\(([.\d]+)\)/,"-$1"));c=parseFloat(a);return isNaN(c)?g.trim(a):c};f.isDigit=function(a){return isNaN(a)?/^[\-+(]?\d+[)]?$/.test(a.toString().replace(/[,.'"\s]/g, "")):!0}}});var p=g.tablesorter;g.fn.extend({tablesorter:p.construct});p.addParser({id:"text",is:function(){return!0},format:function(c,r){var k=r.config;c&&(c=g.trim(k.ignoreCase?c.toLocaleLowerCase():c),c=k.sortLocaleCompare?p.replaceAccents(c):c);return c},type:"text"});p.addParser({id:"digit",is:function(c){return p.isDigit(c)},format:function(c,r){var k=p.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),r);return c&&"number"===typeof k?k:c?g.trim(c&&r.config.ignoreCase?c.toLocaleLowerCase():c): c},type:"numeric"});p.addParser({id:"currency",is:function(c){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test((c||"").replace(/[,. ]/g,""))},format:function(c,r){var k=p.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),r);return c&&"number"===typeof k?k:c?g.trim(c&&r.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"});p.addParser({id:"ipAddress",is:function(c){return/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/.test(c)},format:function(c, g){var k,t=c?c.split("."):"",s="",w=t.length;for(k=0;k<w;k++)s+=("00"+t[k]).slice(-3);return c?p.formatFloat(s,g):c},type:"numeric"});p.addParser({id:"url",is:function(c){return/^(https?|ftp|file):\/\//.test(c)},format:function(c){return c?g.trim(c.replace(/(https?|ftp|file):\/\//,"")):c},type:"text"});p.addParser({id:"isoDate",is:function(c){return/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(c)},format:function(c,g){return c?p.formatFloat(""!==c?(new Date(c.replace(/-/g,"/"))).getTime()||"":"",g):c}, type:"numeric"});p.addParser({id:"percent",is:function(c){return/(\d\s*?%|%\s*?\d)/.test(c)&&15>c.length},format:function(c,g){return c?p.formatFloat(c.replace(/%/g,""),g):c},type:"numeric"});p.addParser({id:"usLongDate",is:function(c){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(c)||/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(c)},format:function(c,g){return c?p.formatFloat((new Date(c.replace(/(\S)([AP]M)$/i,"$1 $2"))).getTime()||"",g):c},type:"numeric"}); p.addParser({id:"shortDate",is:function(c){return/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/.test((c||"").replace(/\s+/g," ").replace(/[\-.,]/g,"/"))},format:function(c,g,k,t){if(c){k=g.config;var s=k.headerList[t];t=s.dateFormat||p.getData(s,k.headers[t],"dateFormat")||k.dateFormat;c=c.replace(/\s+/g," ").replace(/[\-.,]/g,"/");"mmddyyyy"===t?c=c.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$1/$2"):"ddmmyyyy"===t?c=c.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1"):"yyyymmdd"===t&&(c=c.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,"$1/$2/$3"))}return c?p.formatFloat((new Date(c)).getTime()||"",g):c},type:"numeric"});p.addParser({id:"time",is:function(c){return/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(c)},format:function(c,g){return c?p.formatFloat((new Date("2000/01/01 "+c.replace(/(\S)([AP]M)$/i,"$1 $2"))).getTime()||"",g):c},type:"numeric"});p.addParser({id:"metadata",is:function(){return!1},format:function(c,p,k){c=p.config; c=c.parserMetadataName?c.parserMetadataName:"sortValue";return g(k).metadata()[c]},type:"numeric"});p.addWidget({id:"zebra",priority:90,format:function(c,r,k){var t,s,w,z,D,C,E=RegExp(r.cssChildRow,"i"),B=r.$tbodies;r.debug&&(D=new Date);for(c=0;c<B.length;c++)t=B.eq(c),C=t.children("tr").length,1<C&&(w=0,t=t.children("tr:visible").not(r.selectorRemove),t.each(function(){s=g(this);E.test(this.className)||w++;z=0===w%2;s.removeClass(k.zebra[z?1:0]).addClass(k.zebra[z?0:1])}));r.debug&&p.benchmark("Applying Zebra widget", D)},remove:function(c,p,k){var t;p=p.$tbodies;var s=(k.zebra||["even","odd"]).join(" ");for(k=0;k<p.length;k++)t=g.tablesorter.processTbody(c,p.eq(k),!0),t.children().removeClass(s),g.tablesorter.processTbody(c,t,!1)}})}(jQuery);
  • extensions/LCAS/trunk/admin/template/js/jquery.tablesorter.pager.js

    r9229 r27186  
    1 (function($) {
    2         $.extend({
    3                 tablesorterPager: new function() {
    4                        
    5                         function updatePageDisplay(c) {
    6                                 var s = $(c.cssPageDisplay,c.container).val((c.page+1) + c.seperator + c.totalPages);   
    7                         }
    8                        
    9                         function setPageSize(table,size) {
    10                                 var c = table.config;
    11                                 c.size = size;
    12                                 c.totalPages = Math.ceil(c.totalRows / c.size);
    13                                 c.pagerPositionSet = false;
    14                                 moveToPage(table);
    15                                 fixPosition(table);
    16                         }
    17                        
    18                         function fixPosition(table) {
    19                                 var c = table.config;
    20                                 if(!c.pagerPositionSet && c.positionFixed) {
    21                                         var c = table.config, o = $(table);
    22                                         if(o.offset) {
    23                                                 c.container.css({
    24                                                         top: o.offset().top + o.height() + 'px',
    25                                                         position: 'absolute'
     1/*!
     2 * tablesorter pager plugin
     3 * updated 12/16/2013 (v2.14.5)
     4 */
     5/*jshint browser:true, jquery:true, unused:false */
     6;(function($) {
     7        "use strict";
     8        /*jshint supernew:true */
     9        var ts = $.tablesorter;
     10
     11        $.extend({ tablesorterPager: new function() {
     12
     13                this.defaults = {
     14                        // target the pager markup
     15                        container: null,
     16
     17                        // use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}"
     18                        // where {page} is replaced by the page number, {size} is replaced by the number of records to show,
     19                        // {sortList:col} adds the sortList to the url into a "col" array, and {filterList:fcol} adds
     20                        // the filterList to the url into an "fcol" array.
     21                        // So a sortList = [[2,0],[3,0]] becomes "&col[2]=0&col[3]=0" in the url
     22                        // and a filterList = [[2,Blue],[3,13]] becomes "&fcol[2]=Blue&fcol[3]=13" in the url
     23                        ajaxUrl: null,
     24
     25                        // modify the url after all processing has been applied
     26                        customAjaxUrl: function(table, url) { return url; },
     27
     28                        // modify the $.ajax object to allow complete control over your ajax requests
     29                        ajaxObject: {
     30                                dataType: 'json'
     31                        },
     32
     33                        // set this to false if you want to block ajax loading on init
     34                        processAjaxOnInit: true,
     35
     36                        // process ajax so that the following information is returned:
     37                        // [ total_rows (number), rows (array of arrays), headers (array; optional) ]
     38                        // example:
     39                        // [
     40                        //   100,  // total rows
     41                        //   [
     42                        //     [ "row1cell1", "row1cell2", ... "row1cellN" ],
     43                        //     [ "row2cell1", "row2cell2", ... "row2cellN" ],
     44                        //     ...
     45                        //     [ "rowNcell1", "rowNcell2", ... "rowNcellN" ]
     46                        //   ],
     47                        //   [ "header1", "header2", ... "headerN" ] // optional
     48                        // ]
     49                        ajaxProcessing: function(ajax){ return [ 0, [], null ]; },
     50
     51                        // output default: '{page}/{totalPages}'
     52                        // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows}
     53                        output: '{startRow} to {endRow} of {totalRows} rows', // '{page}/{totalPages}'
     54
     55                        // apply disabled classname to the pager arrows when the rows at either extreme is visible
     56                        updateArrows: true,
     57
     58                        // starting page of the pager (zero based index)
     59                        page: 0,
     60
     61                        // Number of visible rows
     62                        size: 10,
     63
     64                        // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js)
     65                        savePages: true,
     66
     67                        // if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty
     68                        // table row set to a height to compensate; default is false
     69                        fixedHeight: false,
     70
     71                        // count child rows towards the set page size? (set true if it is a visible table row within the pager)
     72                        // if true, child row(s) may not appear to be attached to its parent row, may be split across pages or
     73                        // may distort the table if rowspan or cellspans are included.
     74                        countChildRows: false,
     75
     76                        // remove rows from the table to speed up the sort of large tables.
     77                        // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled.
     78                        removeRows: false, // removing rows in larger tables speeds up the sort
     79
     80                        // css class names of pager arrows
     81                        cssFirst: '.first', // go to first page arrow
     82                        cssPrev: '.prev', // previous page arrow
     83                        cssNext: '.next', // next page arrow
     84                        cssLast: '.last', // go to last page arrow
     85                        cssGoto: '.gotoPage', // go to page selector - select dropdown that sets the current page
     86                        cssPageDisplay: '.pagedisplay', // location of where the "output" is displayed
     87                        cssPageSize: '.pagesize', // page size selector - select dropdown that sets the "size" option
     88                        cssErrorRow: 'tablesorter-errorRow', // error information row
     89
     90                        // class added to arrows when at the extremes (i.e. prev/first arrows are "disabled" when on the first page)
     91                        cssDisabled: 'disabled', // Note there is no period "." in front of this class name
     92
     93                        // stuff not set by the user
     94                        totalRows: 0,
     95                        totalPages: 0,
     96                        filteredRows: 0,
     97                        filteredPages: 0,
     98                        ajaxCounter: 0,
     99                        currentFilters: [],
     100                        startRow: 0,
     101                        endRow: 0,
     102                        $size: null,
     103                        last: {}
     104
     105                };
     106
     107                var $this = this,
     108
     109                // hide arrows at extremes
     110                pagerArrows = function(p, disable) {
     111                        var a = 'addClass',
     112                        r = 'removeClass',
     113                        d = p.cssDisabled,
     114                        dis = !!disable,
     115                        tp = Math.min( p.totalPages, p.filteredPages );
     116                        if ( p.updateArrows ) {
     117                                p.$container.find(p.cssFirst + ',' + p.cssPrev)[ ( dis || p.page === 0 ) ? a : r ](d);
     118                                p.$container.find(p.cssNext + ',' + p.cssLast)[ ( dis || p.page === tp - 1 || p.totalPages === 0 ) ? a : r ](d);
     119                        }
     120                },
     121
     122                updatePageDisplay = function(table, p, flag) {
     123                        var i, pg, s, out,
     124                                c = table.config,
     125                                f = c.$table.hasClass('hasFilters') && !p.ajaxUrl,
     126                                t = (c.widgetOptions && c.widgetOptions.filter_filteredRow || 'filtered') + ',' + c.selectorRemove +
     127                                        (p.countChildRows ? '' : ',.' + c.cssChildRow),
     128                                sz = p.size || 10; // don't allow dividing by zero
     129                        p.totalPages = Math.ceil( p.totalRows / sz ); // needed for "pageSize" method
     130                        p.filteredRows = (f) ? c.$tbodies.eq(0).children('tr').not('.' + t ).length : p.totalRows;
     131                        p.filteredPages = (f) ? Math.ceil( p.filteredRows / sz ) || 1 : p.totalPages;
     132                        if ( Math.min( p.totalPages, p.filteredPages ) >= 0 ) {
     133                                t = (p.size * p.page > p.filteredRows);
     134                                p.startRow = (t) ? 1 : (p.filteredRows === 0 ? 0 : p.size * p.page + 1);
     135                                p.page = (t) ? 0 : p.page;
     136                                p.endRow = Math.min( p.filteredRows, p.totalRows, p.size * ( p.page + 1 ) );
     137                                out = p.$container.find(p.cssPageDisplay);
     138                                // form the output string (can now get a new output string from the server)
     139                                s = ( p.ajaxData && p.ajaxData.output ? p.ajaxData.output || p.output : p.output )
     140                                        // {page} = one-based index; {page+#} = zero based index +/- value
     141                                        .replace(/\{page([\-+]\d+)?\}/gi, function(m,n){
     142                                                return p.totalPages ? p.page + (n ? parseInt(n, 10) : 1) : 0;
     143                                        })
     144                                        // {totalPages}, {extra}, {extra:0} (array) or {extra : key} (object)
     145                                        .replace(/\{\w+(\s*:\s*\w+)?\}/gi, function(m){
     146                                                var str = m.replace(/[{}\s]/g,''),
     147                                                        extra = str.split(':'),
     148                                                        data = p.ajaxData,
     149                                                        // return zero for default page/row numbers
     150                                                        deflt = /(rows?|pages?)$/i.test(str) ? 0 : '';
     151                                                return extra.length > 1 && data && data[extra[0]] ? data[extra[0]][extra[1]] : p[str] || (data ? data[str] : deflt) || deflt;
     152                                        });
     153                                if (out.length) {
     154                                        out[ (out[0].tagName === 'INPUT') ? 'val' : 'html' ](s);
     155                                        if ( p.$goto.length ) {
     156                                                t = '';
     157                                                pg = Math.min( p.totalPages, p.filteredPages );
     158                                                for ( i = 1; i <= pg; i++ ) {
     159                                                        t += '<option>' + i + '</option>';
     160                                                }
     161                                                p.$goto.html(t).val( p.page + 1 );
     162                                        }
     163                                }
     164                        }
     165                        pagerArrows(p);
     166                        if (p.initialized && flag !== false) {
     167                                c.$table.trigger('pagerComplete', p);
     168                                // save pager info to storage
     169                                if (p.savePages && ts.storage) {
     170                                        ts.storage(table, 'tablesorter-pager', {
     171                                                page : p.page,
     172                                                size : p.size
     173                                        });
     174                                }
     175                        }
     176                },
     177
     178                fixHeight = function(table, p) {
     179                        var d, h,
     180                                c = table.config,
     181                                $b = c.$tbodies.eq(0);
     182                        if (p.fixedHeight) {
     183                                $b.find('tr.pagerSavedHeightSpacer').remove();
     184                                h = $.data(table, 'pagerSavedHeight');
     185                                if (h) {
     186                                        d = h - $b.height();
     187                                        if ( d > 5 && $.data(table, 'pagerLastSize') === p.size && $b.children('tr:visible').length < p.size ) {
     188                                                $b.append('<tr class="pagerSavedHeightSpacer ' + c.selectorRemove.replace(/(tr)?\./g,'') + '" style="height:' + d + 'px;"></tr>');
     189                                        }
     190                                }
     191                        }
     192                },
     193
     194                changeHeight = function(table, p) {
     195                        var $b = table.config.$tbodies.eq(0);
     196                        $b.find('tr.pagerSavedHeightSpacer').remove();
     197                        $.data(table, 'pagerSavedHeight', $b.height());
     198                        fixHeight(table, p);
     199                        $.data(table, 'pagerLastSize', p.size);
     200                },
     201
     202                hideRows = function(table, p){
     203                        if (!p.ajaxUrl) {
     204                                var i,
     205                                c = table.config,
     206                                rows = c.$tbodies.eq(0).children(),
     207                                l = rows.length,
     208                                s = ( p.page * p.size ),
     209                                e =  s + p.size,
     210                                f = c.widgetOptions && c.widgetOptions.filter_filteredRow || 'filtered',
     211                                j = 0; // size counter
     212                                for ( i = 0; i < l; i++ ){
     213                                        if ( !rows[i].className.match(f) ) {
     214                                                rows[i].style.display = ( j >= s && j < e ) ? '' : 'none';
     215                                                // don't count child rows
     216                                                j += rows[i].className.match(c.cssChildRow + '|' + c.selectorRemove.slice(1)) && !p.countChildRows ? 0 : 1;
     217                                        }
     218                                }
     219                        }
     220                },
     221
     222                hideRowsSetup = function(table, p){
     223                        p.size = parseInt( p.$size.val(), 10 ) || p.size;
     224                        $.data(table, 'pagerLastSize', p.size);
     225                        pagerArrows(p);
     226                        if ( !p.removeRows ) {
     227                                hideRows(table, p);
     228                                $(table).bind('sortEnd.pager filterEnd.pager', function(){
     229                                        hideRows(table, p);
     230                                });
     231                        }
     232                },
     233
     234                renderAjax = function(data, table, p, xhr, exception){
     235                        // process data
     236                        if ( typeof(p.ajaxProcessing) === "function" ) {
     237                                // ajaxProcessing result: [ total, rows, headers ]
     238                                var i, j, hsh, $f, $sh, t, th, d, l, $err, rr_count,
     239                                        c = table.config,
     240                                        $t = c.$table,
     241                                        tds = '',
     242                                        result = p.ajaxProcessing(data, table) || [ 0, [] ],
     243                                        hl = $t.find('thead th').length;
     244
     245                                $t.find('thead tr.' + p.cssErrorRow).remove(); // Clean up any previous error.
     246
     247                                if ( exception ) {
     248                                        if (c.debug) {
     249                                                ts.log('Ajax Error', xhr, exception);
     250                                        }
     251                                        $err = $('<tr class="' + p.cssErrorRow + '"><td style="text-align:center;" colspan="' + hl + '">' + (
     252                                                xhr.status === 0 ? 'Not connected, verify Network' :
     253                                                xhr.status === 404 ? 'Requested page not found [404]' :
     254                                                xhr.status === 500 ? 'Internal Server Error [500]' :
     255                                                exception === 'parsererror' ? 'Requested JSON parse failed' :
     256                                                exception === 'timeout' ? 'Time out error' :
     257                                                exception === 'abort' ? 'Ajax Request aborted' :
     258                                                'Uncaught error: ' + xhr.statusText + ' [' + xhr.status + ']' ) + '</td></tr>')
     259                                        .click(function(){
     260                                                $(this).remove();
     261                                        })
     262                                        // add error row to thead instead of tbody, or clicking on the header will result in a parser error
     263                                        .appendTo( $t.find('thead:first') );
     264                                        c.$tbodies.eq(0).empty();
     265                                } else {
     266                                        // process ajax object
     267                                        if (!$.isArray(result)) {
     268                                                p.ajaxData = result;
     269                                                p.totalRows = result.total;
     270                                                th = result.headers;
     271                                                d = result.rows;
     272                                        } else {
     273                                                // allow [ total, rows, headers ]  or [ rows, total, headers ]
     274                                                t = isNaN(result[0]) && !isNaN(result[1]);
     275                                                //ensure a zero returned row count doesn't fail the logical ||
     276                                                rr_count = result[t ? 1 : 0];
     277                                                p.totalRows = isNaN(rr_count) ? p.totalRows || 0 : rr_count;
     278                                                d = p.totalRows === 0 ? [""] : result[t ? 0 : 1] || []; // row data
     279                                                th = result[2]; // headers
     280                                        }
     281                                        l = d.length;
     282                                        if (d instanceof jQuery) {
     283                                                // append jQuery object
     284                                                c.$tbodies.eq(0).empty().append(d);
     285                                        } else if (l) {
     286                                                // build table from array
     287                                                for ( i = 0; i < l; i++ ) {
     288                                                        tds += '<tr>';
     289                                                        for ( j = 0; j < d[i].length; j++ ) {
     290                                                                // build tbody cells; watch for data containing HTML markup - see #434
     291                                                                tds += /^\s*<td/.test(d[i][j]) ? $.trim(d[i][j]) : '<td>' + d[i][j] + '</td>';
     292                                                        }
     293                                                        tds += '</tr>';
     294                                                }
     295                                                // add rows to first tbody
     296                                                p.processAjaxOnInit ? c.$tbodies.eq(0).html( tds ) : p.processAjaxOnInit = true;
     297                                        }
     298                                        // only add new header text if the length matches
     299                                        if ( th && th.length === hl ) {
     300                                                hsh = $t.hasClass('hasStickyHeaders');
     301                                                $sh = hsh ? c.widgetOptions.$sticky.children('thead:first').children().children() : '';
     302                                                $f = $t.find('tfoot tr:first').children();
     303                                                // don't change td headers (may contain pager)
     304                                                c.$headers.filter('th').each(function(j){
     305                                                        var $t = $(this), icn;
     306                                                        // add new test within the first span it finds, or just in the header
     307                                                        if ( $t.find('.' + ts.css.icon).length ) {
     308                                                                icn = $t.find('.' + ts.css.icon).clone(true);
     309                                                                $t.find('.tablesorter-header-inner').html( th[j] ).append(icn);
     310                                                                if ( hsh && $sh.length ) {
     311                                                                        icn = $sh.eq(j).find('.' + ts.css.icon).clone(true);
     312                                                                        $sh.eq(j).find('.tablesorter-header-inner').html( th[j] ).append(icn);
     313                                                                }
     314                                                        } else {
     315                                                                $t.find('.tablesorter-header-inner').html( th[j] );
     316                                                                if (hsh && $sh.length) {
     317                                                                        $sh.eq(j).find('.tablesorter-header-inner').html( th[j] );
     318                                                                }
     319                                                        }
     320                                                        $f.eq(j).html( th[j] );
    26321                                                });
    27322                                        }
    28                                         c.pagerPositionSet = true;
    29                                 }
    30                         }
    31                        
    32                         function moveToFirstPage(table) {
    33                                 var c = table.config;
    34                                 c.page = 0;
    35                                 moveToPage(table);
    36                         }
    37                        
    38                         function moveToLastPage(table) {
    39                                 var c = table.config;
    40                                 c.page = (c.totalPages-1);
    41                                 moveToPage(table);
    42                         }
    43                        
    44                         function moveToNextPage(table) {
    45                                 var c = table.config;
    46                                 c.page++;
    47                                 if(c.page >= (c.totalPages-1)) {
    48                                         c.page = (c.totalPages-1);
    49                                 }
    50                                 moveToPage(table);
    51                         }
    52                        
    53                         function moveToPrevPage(table) {
    54                                 var c = table.config;
    55                                 c.page--;
    56                                 if(c.page <= 0) {
    57                                         c.page = 0;
    58                                 }
    59                                 moveToPage(table);
    60                         }
    61                                                
    62                        
    63                         function moveToPage(table) {
    64                                 var c = table.config;
    65                                 if(c.page < 0 || c.page > (c.totalPages-1)) {
    66                                         c.page = 0;
    67                                 }
    68                                
    69                                 renderTable(table,c.rowsCopy);
    70                         }
    71                        
    72                         function renderTable(table,rows) {
    73                                
    74                                 var c = table.config;
    75                                 var l = rows.length;
    76                                 var s = (c.page * c.size);
    77                                 var e = (s + c.size);
    78                                 if(e > rows.length ) {
     323                                }
     324                                if (c.showProcessing) {
     325                                        ts.isProcessing(table); // remove loading icon
     326                                }
     327                                // make sure last pager settings are saved, prevents multiple server side calls with
     328                                // the same parameters
     329                                p.last.totalPages =  p.totalPages = Math.ceil( p.totalRows / ( p.size || 10 ) );
     330                                p.last.currentFilters = p.currentFilters;
     331                                p.last.sortList = (c.sortList || []).join(',');
     332                                updatePageDisplay(table, p);
     333                                fixHeight(table, p);
     334                                // apply widgets after table has rendered
     335                                $t.trigger('applyWidgets');
     336                                $t.trigger('update', [false, function(){
     337                                        if (p.initialized) {
     338                                                $t.trigger('updateComplete');
     339                                                $t.trigger('pagerChange', p);
     340                                        }
     341                                }]);
     342                        }
     343                        if (!p.initialized) {
     344                                p.initialized = true;
     345                                $(table).trigger('pagerInitialized', p);
     346                        }
     347                },
     348
     349                getAjax = function(table, p){
     350                        var url = getAjaxUrl(table, p),
     351                        $doc = $(document),
     352                        counter,
     353                        c = table.config;
     354                        if ( url !== '' ) {
     355                                if (c.showProcessing) {
     356                                        ts.isProcessing(table, true); // show loading icon
     357                                }
     358                                $doc.bind('ajaxError.pager', function(e, xhr, settings, exception) {
     359                                        renderAjax(null, table, p, xhr, exception);
     360                                        $doc.unbind('ajaxError.pager');
     361                                });
     362
     363                                counter = ++p.ajaxCounter;
     364
     365                                p.ajaxObject.url = url; // from the ajaxUrl option and modified by customAjaxUrl
     366                                p.ajaxObject.success = function(data) {
     367                                        // Refuse to process old ajax commands that were overwritten by new ones - see #443
     368                                        if (counter < p.ajaxCounter){
     369                                                return;
     370                                        }
     371                                        renderAjax(data, table, p);
     372                                        $doc.unbind('ajaxError.pager');
     373                                        if (typeof p.oldAjaxSuccess === 'function') {
     374                                                p.oldAjaxSuccess(data);
     375                                        }
     376                                };
     377                                if (c.debug) {
     378                                        ts.log('ajax initialized', p.ajaxObject);
     379                                }
     380                                $.ajax(p.ajaxObject);
     381                        }
     382                },
     383
     384                getAjaxUrl = function(table, p) {
     385                        var c = table.config,
     386                                url = (p.ajaxUrl) ? p.ajaxUrl
     387                                // allow using "{page+1}" in the url string to switch to a non-zero based index
     388                                .replace(/\{page([\-+]\d+)?\}/, function(s,n){ return p.page + (n ? parseInt(n, 10) : 0); })
     389                                .replace(/\{size\}/g, p.size) : '',
     390                        sl = c.sortList,
     391                        fl = p.currentFilters || $(table).data('lastSearch') || [],
     392                        sortCol = url.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/),
     393                        filterCol = url.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/),
     394                        arry = [];
     395                        if (sortCol) {
     396                                sortCol = sortCol[1];
     397                                $.each(sl, function(i,v){
     398                                        arry.push(sortCol + '[' + v[0] + ']=' + v[1]);
     399                                });
     400                                // if the arry is empty, just add the col parameter... "&{sortList:col}" becomes "&col"
     401                                url = url.replace(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : sortCol );
     402                                arry = [];
     403                        }
     404                        if (filterCol) {
     405                                filterCol = filterCol[1];
     406                                $.each(fl, function(i,v){
     407                                        if (v) {
     408                                                arry.push(filterCol + '[' + i + ']=' + encodeURIComponent(v));
     409                                        }
     410                                });
     411                                // if the arry is empty, just add the fcol parameter... "&{filterList:fcol}" becomes "&fcol"
     412                                url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol );
     413                                p.currentFilters = fl;
     414                        }
     415                        if ( typeof(p.customAjaxUrl) === "function" ) {
     416                                url = p.customAjaxUrl(table, url);
     417                        }
     418                        if (c.debug) {
     419                                ts.log('Pager ajax url: ' + url);
     420                        }
     421                        return url;
     422                },
     423
     424                renderTable = function(table, rows, p) {
     425                        var i, $tb,
     426                                l = rows && rows.length || 0, // rows may be undefined
     427                                s = ( p.page * p.size ),
     428                                e = ( s + p.size );
     429                        if ( l < 1 ) { return; } // empty table, abort!
     430                        if ( p.page >= p.totalPages ) {
     431                                // lets not render the table more than once
     432                                moveToLastPage(table, p);
     433                        }
     434                        p.isDisabled = false; // needed because sorting will change the page and re-enable the pager
     435                        if (p.initialized) { $(table).trigger('pagerChange', p); }
     436
     437                        if ( !p.removeRows ) {
     438                                hideRows(table, p);
     439                        } else {
     440                                if ( e > rows.length ) {
    79441                                        e = rows.length;
    80442                                }
    81                                
    82                                
    83                                 var tableBody = $(table.tBodies[0]);
    84                                
    85                                 // clear the table body
    86                                
    87                                 $.tablesorter.clearTableBody(table);
    88                                
    89                                 for(var i = s; i < e; i++) {
    90                                        
    91                                         //tableBody.append(rows[i]);
    92                                        
    93                                         var o = rows[i];
    94                                         var l = o.length;
    95                                         for(var j=0; j < l; j++) {
    96                                                
    97                                                 tableBody[0].appendChild(o[j]);
    98 
    99                                         }
    100                                 }
    101                                
    102                                 fixPosition(table,tableBody);
    103                                
    104                                 $(table).trigger("applyWidgets");
    105                                
    106                                 if( c.page >= c.totalPages ) {
    107                                 moveToLastPage(table);
    108                                 }
    109                                
    110                                 updatePageDisplay(c);
    111                         }
    112                        
    113                         this.appender = function(table,rows) {
    114                                
    115                                 var c = table.config;
    116                                
     443                                ts.clearTableBody(table);
     444                                $tb = ts.processTbody(table, table.config.$tbodies.eq(0), true);
     445                                for ( i = s; i < e; i++ ) {
     446                                        $tb.append(rows[i]);
     447                                }
     448                                ts.processTbody(table, $tb, false);
     449                        }
     450
     451                        updatePageDisplay(table, p);
     452                        if ( !p.isDisabled ) { fixHeight(table, p); }
     453                        $(table).trigger('applyWidgets');
     454                },
     455
     456                showAllRows = function(table, p){
     457                        if ( p.ajax ) {
     458                                pagerArrows(p, true);
     459                        } else {
     460                                p.isDisabled = true;
     461                                $.data(table, 'pagerLastPage', p.page);
     462                                $.data(table, 'pagerLastSize', p.size);
     463                                p.page = 0;
     464                                p.size = p.totalRows;
     465                                p.totalPages = 1;
     466                                $(table).addClass('pagerDisabled').find('tr.pagerSavedHeightSpacer').remove();
     467                                renderTable(table, table.config.rowsCopy, p);
     468                                if (table.config.debug) {
     469                                        ts.log('pager disabled');
     470                                }
     471                        }
     472                        // disable size selector
     473                        p.$size.add(p.$goto).each(function(){
     474                                $(this).addClass(p.cssDisabled)[0].disabled = true;
     475                        });
     476                },
     477
     478                moveToPage = function(table, p, flag) {
     479                        if ( p.isDisabled ) { return; }
     480                        var c = table.config,
     481                                l = p.last,
     482                                pg = Math.min( p.totalPages, p.filteredPages );
     483                        if ( p.page < 0 ) { p.page = 0; }
     484                        if ( p.page > ( pg - 1 ) && pg !== 0 ) { p.page = pg - 1; }
     485                        // don't allow rendering multiple times on the same page/size/totalpages/filters/sorts
     486                        if ( l.page === p.page && l.size === p.size && l.totalPages === p.totalPages &&
     487                                (l.currentFilters || []).join(',') === (p.currentFilters || []).join(',') &&
     488                                l.sortList === (c.sortList || []).join(',') ) { return; }
     489                        if (c.debug) {
     490                                ts.log('Pager changing to page ' + p.page);
     491                        }
     492                        p.last = {
     493                                page : p.page,
     494                                size : p.size,
     495                                // fixes #408; modify sortList otherwise it auto-updates
     496                                sortList : (c.sortList || []).join(','),
     497                                totalPages : p.totalPages,
     498                                currentFilters : p.currentFilters || []
     499                        };
     500                        if (p.ajax) {
     501                                getAjax(table, p);
     502                        } else if (!p.ajax) {
     503                                renderTable(table, table.config.rowsCopy, p);
     504                        }
     505                        $.data(table, 'pagerLastPage', p.page);
     506                        if (p.initialized && flag !== false) {
     507                                c.$table.trigger('pageMoved', p);
     508                                c.$table.trigger('applyWidgets');
     509                        }
     510                },
     511
     512                setPageSize = function(table, size, p) {
     513                        p.size = size || p.size || 10;
     514                        p.$size.val(p.size);
     515                        $.data(table, 'pagerLastPage', p.page);
     516                        $.data(table, 'pagerLastSize', p.size);
     517                        p.totalPages = Math.ceil( p.totalRows / p.size );
     518                        moveToPage(table, p);
     519                },
     520
     521                moveToFirstPage = function(table, p) {
     522                        p.page = 0;
     523                        moveToPage(table, p);
     524                },
     525
     526                moveToLastPage = function(table, p) {
     527                        p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 );
     528                        moveToPage(table, p);
     529                },
     530
     531                moveToNextPage = function(table, p) {
     532                        p.page++;
     533                        if ( p.page >= ( Math.min( p.totalPages, p.filteredPages ) - 1 ) ) {
     534                                p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 );
     535                        }
     536                        moveToPage(table, p);
     537                },
     538
     539                moveToPrevPage = function(table, p) {
     540                        p.page--;
     541                        if ( p.page <= 0 ) {
     542                                p.page = 0;
     543                        }
     544                        moveToPage(table, p);
     545                },
     546
     547                destroyPager = function(table, p){
     548                        showAllRows(table, p);
     549                        p.$container.hide(); // hide pager
     550                        table.config.appender = null; // remove pager appender function
     551                        p.initialized = false;
     552                        $(table).unbind('destroy.pager sortEnd.pager filterEnd.pager enable.pager disable.pager');
     553                        if (ts.storage) {
     554                                ts.storage(table, 'tablesorter-pager', '');
     555                        }
     556                },
     557
     558                enablePager = function(table, p, triggered){
     559                        var pg = p.$size.removeClass(p.cssDisabled).removeAttr('disabled');
     560                        p.$goto.removeClass(p.cssDisabled).removeAttr('disabled');
     561                        p.isDisabled = false;
     562                        p.page = $.data(table, 'pagerLastPage') || p.page || 0;
     563                        p.size = $.data(table, 'pagerLastSize') || parseInt(pg.find('option[selected]').val(), 10) || p.size || 10;
     564                        pg.val(p.size); // set page size
     565                        p.totalPages = Math.ceil( Math.min( p.totalPages, p.filteredPages ) / p.size );
     566                        if ( triggered ) {
     567                                $(table).trigger('update');
     568                                setPageSize(table, p.size, p);
     569                                hideRowsSetup(table, p);
     570                                fixHeight(table, p);
     571                                if (table.config.debug) {
     572                                        ts.log('pager enabled');
     573                                }
     574                        }
     575                };
     576
     577                $this.appender = function(table, rows) {
     578                        var c = table.config,
     579                                p = c.pager;
     580                        if ( !p.ajax ) {
    117581                                c.rowsCopy = rows;
    118                                 c.totalRows = rows.length;
    119                                 c.totalPages = Math.ceil(c.totalRows / c.size);
    120                                
    121                                 renderTable(table,rows);
    122                         };
    123                        
    124                         this.defaults = {
    125                                 size: 10,
    126                                 offset: 0,
    127                                 page: 0,
    128                                 totalRows: 0,
    129                                 totalPages: 0,
    130                                 container: null,
    131                                 cssNext: '.next',
    132                                 cssPrev: '.prev',
    133                                 cssFirst: '.first',
    134                                 cssLast: '.last',
    135                                 cssPageDisplay: '.pagedisplay',
    136                                 cssPageSize: '.pagesize',
    137                                 seperator: "/",
    138                                 positionFixed: true,
    139                                 appender: this.appender
    140                         };
    141                        
    142                         this.construct = function(settings) {
    143                                
    144                                 return this.each(function() {   
    145                                        
    146                                         config = $.extend(this.config, $.tablesorterPager.defaults, settings);
    147                                        
    148                                         var table = this, pager = config.container;
    149                                
    150                                         $(this).trigger("appendCache");
    151                                        
    152                                         config.size = parseInt($(".pagesize",pager).val());
    153                                        
    154                                         $(config.cssFirst,pager).click(function() {
    155                                                 moveToFirstPage(table);
     582                                p.totalRows = p.countChildRows ? c.$tbodies.eq(0).children().length : rows.length;
     583                                p.size = $.data(table, 'pagerLastSize') || p.size || 10;
     584                                p.totalPages = Math.ceil( p.totalRows / p.size );
     585                                renderTable(table, rows, p);
     586                        }
     587                };
     588
     589                $this.construct = function(settings) {
     590                        return this.each(function() {
     591                                // check if tablesorter has initialized
     592                                if (!(this.config && this.hasInitialized)) { return; }
     593                                var t, ctrls, fxn,
     594                                        table = this,
     595                                        c = table.config,
     596                                        p = c.pager = $.extend( {}, $.tablesorterPager.defaults, settings ),
     597                                        $t = c.$table,
     598                                        // added in case the pager is reinitialized after being destroyed.
     599                                        pager = p.$container = $(p.container).addClass('tablesorter-pager').show();
     600                                if (c.debug) {
     601                                        ts.log('Pager initializing');
     602                                }
     603                                p.oldAjaxSuccess = p.oldAjaxSuccess || p.ajaxObject.success;
     604                                c.appender = $this.appender;
     605                                if (ts.filter && $.inArray('filter', c.widgets) >= 0) {
     606                                        // get any default filter settings (data-value attribute) fixes #388
     607                                        p.currentFilters = c.$table.data('lastSearch') || ts.filter.setDefaults(table, c, c.widgetOptions) || [];
     608                                        // set, but don't apply current filters
     609                                        ts.setFilters(table, p.currentFilters, false);
     610                                }
     611                                if (p.savePages && ts.storage) {
     612                                        t = ts.storage(table, 'tablesorter-pager') || {}; // fixes #387
     613                                        p.page = isNaN(t.page) ? p.page : t.page;
     614                                        p.size = ( isNaN(t.size) ? p.size : t.size ) || 10;
     615                                        $.data(table, 'pagerLastSize', p.size);
     616                                }
     617
     618                                $t
     619                                        .unbind('filterStart filterEnd sortEnd disable enable destroy update updateRows updateAll addRows pageSize '.split(' ').join('.pager '))
     620                                        .bind('filterStart.pager', function(e, filters) {
     621                                                p.currentFilters = filters;
     622                                                p.page = 0; // fixes #456
     623                                        })
     624                                        // update pager after filter widget completes
     625                                        .bind('filterEnd.pager sortEnd.pager', function() {
     626                                                if (p.initialized) {
     627                                                        moveToPage(table, p, false);
     628                                                        updatePageDisplay(table, p, false);
     629                                                        fixHeight(table, p);
     630                                                }
     631                                        })
     632                                        .bind('disable.pager', function(e){
     633                                                e.stopPropagation();
     634                                                showAllRows(table, p);
     635                                        })
     636                                        .bind('enable.pager', function(e){
     637                                                e.stopPropagation();
     638                                                enablePager(table, p, true);
     639                                        })
     640                                        .bind('destroy.pager', function(e){
     641                                                e.stopPropagation();
     642                                                destroyPager(table, p);
     643                                        })
     644                                        .bind('update updateRows updateAll addRows '.split(' ').join('.pager '), function(e){
     645                                                e.stopPropagation();
     646                                                hideRows(table, p);
     647                                        })
     648                                        .bind('pageSize.pager', function(e,v){
     649                                                e.stopPropagation();
     650                                                setPageSize(table, parseInt(v, 10) || 10, p);
     651                                                hideRows(table, p);
     652                                                updatePageDisplay(table, p, false);
     653                                                if (p.$size.length) { p.$size.val(p.size); } // twice?
     654                                        })
     655                                        .bind('pageSet.pager', function(e,v){
     656                                                e.stopPropagation();
     657                                                p.page = (parseInt(v, 10) || 1) - 1;
     658                                                if (p.$goto.length) { p.$goto.val(p.size); } // twice?
     659                                                moveToPage(table, p);
     660                                                updatePageDisplay(table, p, false);
     661                                        });
     662
     663                                // clicked controls
     664                                ctrls = [ p.cssFirst, p.cssPrev, p.cssNext, p.cssLast ];
     665                                fxn = [ moveToFirstPage, moveToPrevPage, moveToNextPage, moveToLastPage ];
     666                                pager.find(ctrls.join(','))
     667                                        .unbind('click.pager')
     668                                        .bind('click.pager', function(e){
     669                                                e.stopPropagation();
     670                                                var i, $t = $(this), l = ctrls.length;
     671                                                if ( !$t.hasClass(p.cssDisabled) ) {
     672                                                        for (i = 0; i < l; i++) {
     673                                                                if ($t.is(ctrls[i])) {
     674                                                                        fxn[i](table, p);
     675                                                                        break;
     676                                                                }
     677                                                        }
     678                                                }
     679                                        });
     680
     681                                // goto selector
     682                                p.$goto = pager.find(p.cssGoto);
     683                                if ( p.$goto.length ) {
     684                                        p.$goto
     685                                                .unbind('change')
     686                                                .bind('change', function(){
     687                                                        p.page = $(this).val() - 1;
     688                                                        moveToPage(table, p);
     689                                                        updatePageDisplay(table, p, false);
     690                                                });
     691                                }
     692
     693                                // page size selector
     694                                p.$size = pager.find(p.cssPageSize);
     695                                if ( p.$size.length ) {
     696                                        p.$size.unbind('change.pager').bind('change.pager', function() {
     697                                                p.$size.val( $(this).val() ); // in case there are more than one pagers
     698                                                if ( !$(this).hasClass(p.cssDisabled) ) {
     699                                                        setPageSize(table, parseInt( $(this).val(), 10 ), p);
     700                                                        changeHeight(table, p);
     701                                                }
    156702                                                return false;
    157703                                        });
    158                                         $(config.cssNext,pager).click(function() {
    159                                                 moveToNextPage(table);
    160                                                 return false;
    161                                         });
    162                                         $(config.cssPrev,pager).click(function() {
    163                                                 moveToPrevPage(table);
    164                                                 return false;
    165                                         });
    166                                         $(config.cssLast,pager).click(function() {
    167                                                 moveToLastPage(table);
    168                                                 return false;
    169                                         });
    170                                         $(config.cssPageSize,pager).change(function() {
    171                                                 setPageSize(table,parseInt($(this).val()));
    172                                                 return false;
    173                                         });
    174                                 });
    175                         };
    176                        
    177                 }
    178         });
    179         // extend plugin scope
    180         $.fn.extend({
    181         tablesorterPager: $.tablesorterPager.construct
    182         });
    183        
    184 })(jQuery);                             
     704                                }
     705
     706                                // clear initialized flag
     707                                p.initialized = false;
     708                                // before initialization event
     709                                $t.trigger('pagerBeforeInitialized', p);
     710
     711                                enablePager(table, p, false);
     712
     713                                if ( typeof(p.ajaxUrl) === 'string' ) {
     714                                        // ajax pager; interact with database
     715                                        p.ajax = true;
     716                                        //When filtering with ajax, allow only custom filtering function, disable default filtering since it will be done server side.
     717                                        c.widgetOptions.filter_serversideFiltering = true;
     718                                        c.serverSideSorting = true;
     719                                        moveToPage(table, p);
     720                                } else {
     721                                        p.ajax = false;
     722                                        // Regular pager; all rows stored in memory
     723                                        $(this).trigger("appendCache", true);
     724                                        hideRowsSetup(table, p);
     725                                }
     726
     727                                changeHeight(table, p);
     728
     729                                // pager initialized
     730                                if (!p.ajax) {
     731                                        p.initialized = true;
     732                                        $(table).trigger('pagerInitialized', p);
     733                                }
     734                        });
     735                };
     736
     737        }()
     738});
     739// extend plugin scope
     740$.fn.extend({
     741        tablesorterPager: $.tablesorterPager.construct
     742});
     743
     744})(jQuery);
Note: See TracChangeset for help on using the changeset viewer.