1/*! 2* tablesorter (FORK) pager plugin 3* updated 2020-03-03 (v2.31.3) 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({ 12 tablesorterPager: new function() { 13 14 this.defaults = { 15 // target the pager markup 16 container: null, 17 18 // use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}" 19 // where {page} is replaced by the page number, {size} is replaced by the number of records to show, 20 // {sortList:col} adds the sortList to the url into a "col" array, and {filterList:fcol} adds 21 // the filterList to the url into an "fcol" array. 22 // So a sortList = [[2,0],[3,0]] becomes "&col[2]=0&col[3]=0" in the url 23 // and a filterList = [[2,Blue],[3,13]] becomes "&fcol[2]=Blue&fcol[3]=13" in the url 24 ajaxUrl: null, 25 26 // modify the url after all processing has been applied 27 customAjaxUrl: function(table, url) { return url; }, 28 29 // ajax error callback from $.tablesorter.showError function 30 // ajaxError: function( config, xhr, settings, exception ) { return exception; }; 31 // returning false will abort the error message 32 ajaxError: null, 33 34 // modify the $.ajax object to allow complete control over your ajax requests 35 ajaxObject: { 36 dataType: 'json' 37 }, 38 39 // set this to false if you want to block ajax loading on init 40 processAjaxOnInit: true, 41 42 // process ajax so that the following information is returned: 43 // [ total_rows (number), rows (array of arrays), headers (array; optional) ] 44 // example: 45 // [ 46 // 100, // total rows 47 // [ 48 // [ "row1cell1", "row1cell2", ... "row1cellN" ], 49 // [ "row2cell1", "row2cell2", ... "row2cellN" ], 50 // ... 51 // [ "rowNcell1", "rowNcell2", ... "rowNcellN" ] 52 // ], 53 // [ "header1", "header2", ... "headerN" ] // optional 54 // ] 55 ajaxProcessing: function(data) { return data; }, 56 57 // output default: '{page}/{totalPages}' 58 // possible variables: {size}, {page}, {totalPages}, {filteredPages}, {startRow}, 59 // {endRow}, {filteredRows} and {totalRows} 60 output: '{startRow} to {endRow} of {totalRows} rows', // '{page}/{totalPages}' 61 62 // apply disabled classname to the pager arrows when the rows at either extreme is visible 63 updateArrows: true, 64 65 // starting page of the pager (zero based index) 66 page: 0, 67 68 // reset pager after filtering; set to desired page # 69 // set to false to not change page at filter start 70 pageReset: 0, 71 72 // Number of visible rows 73 size: 10, 74 75 // Number of options to include in the pager number selector 76 maxOptionSize: 20, 77 78 // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js) 79 savePages: true, 80 81 // defines custom storage key 82 storageKey: 'tablesorter-pager', 83 84 // if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty
85 // table row set to a height to compensate; default is false 86 fixedHeight: false, 87 88 // count child rows towards the set page size? (set true if it is a visible table row within the pager) 89 // if true, child row(s) may not appear to be attached to its parent row, may be split across pages or 90 // may distort the table if rowspan or cellspans are included. 91 countChildRows: false, 92 93 // remove rows from the table to speed up the sort of large tables. 94 // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled. 95 removeRows: false, // removing rows in larger tables speeds up the sort 96 97 // css class names of pager arrows 98 cssFirst: '.first', // go to first page arrow 99 cssPrev: '.prev', // previous page arrow 100 cssNext: '.next', // next page arrow 101 cssLast: '.last', // go to last page arrow 102 cssGoto: '.gotoPage', // go to page selector - select dropdown that sets the current page 103 cssPageDisplay: '.pagedisplay', // location of where the "output" is displayed 104 cssPageSize: '.pagesize', // page size selector - select dropdown that sets the "size" option 105 cssErrorRow: 'tablesorter-errorRow', // error information row 106 107 // class added to arrows when at the extremes (i.e. prev/first arrows are "disabled" when on the first page) 108 cssDisabled: 'disabled', // Note there is no period "." in front of this class name 109 110 // stuff not set by the user 111 totalRows: 0, 112 totalPages: 0, 113 filteredRows: 0, 114 filteredPages: 0, 115 ajaxCounter: 0, 116 currentFilters: [], 117 startRow: 0, 118 endRow: 0, 119 $size: null, 120 last: {} 121 122 }; 123 124 var pagerEvents = 'filterInit filterStart filterEnd sortEnd disablePager enablePager destroyPager updateComplete ' + 125 'pageSize pageSet pageAndSize pagerUpdate refreshComplete ', 126 127 $this = this, 128 129 // hide arrows at extremes 130 pagerArrows = function( table, p, disable ) { 131 var tmp, 132 a = 'addClass', 133 r = 'removeClass', 134 d = p.cssDisabled, 135 dis = !!disable, 136 first = ( dis || p.page === 0 ), 137 tp = getTotalPages( table, p ), 138 last = ( dis || (p.page === tp - 1) || tp === 0 ); 139 if ( p.updateArrows ) { 140 tmp = p.$container.find(p.cssFirst + ',' + p.cssPrev); 141 tmp[ first ? a : r ](d); // toggle disabled class 142 tmp.each(function() { 143 this.ariaDisabled = first; 144 }); 145 tmp = p.$container.find(p.cssNext + ',' + p.cssLast); 146 tmp[ last ? a : r ](d); 147 tmp.each(function() { 148 this.ariaDisabled = last; 149 }); 150 } 151 }, 152 153 calcFilters = function(table, p) { 154 var normalized, indx, len, 155 c = table.config, 156 hasFilters = c.$table.hasClass('hasFilters'); 157 if (hasFilters && !p.ajax) { 158 if (ts.isEmptyObject(c.cache)) { 159 // delayInit: true so nothing is in the cache 160 p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not( p.countChildRows ? '' : '.' + c.cssChildRow ).length; 161 } else { 162 p.filteredRows = 0; 163 normalized = c.cache[0].normalized; 164 len = normalized.length; 165 for (indx = 0; indx < len; indx++) { 166 p.filteredRows += p.regexRows.test(normalized[indx][c.columns].$row[0].className) ? 0 : 1; 167 } 168 } 169 } else if (!hasFilters) { 170 p.filteredRows = p.totalRows; 171 } 172 }, 173 174 updatePageDisplay = function(table, p, completed) { 175 if ( p.initializing ) { return; } 176 var s, t, $out, $el, indx, len, options, output, 177 c = table.config, 178 namespace = c.namespace + 'pager', 179 sz = parsePageSize( p, p.size, 'get' ); // don't allow dividing by zero 180 if (sz === 'all') { sz = p.totalRows; } 181 if (p.countChildRows) { t[ t.length ] = c.cssChildRow; } 182 p.totalPages = Math.ceil( p.totalRows / sz ); // needed for "pageSize" method 183 c.totalRows = p.totalRows; 184 parsePageNumber( table, p ); 185 calcFilters(table, p); 186 c.filteredRows = p.filteredRows; 187 p.filteredPages = Math.ceil( p.filteredRows / sz ) || 0; 188 if ( getTotalPages( table, p ) >= 0 ) { 189 t = (sz * p.page > p.filteredRows) && completed; 190 p.page = (t) ? p.pageReset || 0 : p.page; 191 p.startRow = (t) ? sz * p.page + 1 : (p.filteredRows === 0 ? 0 : sz * p.page + 1); 192 p.endRow = Math.min( p.filteredRows, p.totalRows, sz * ( p.page + 1 ) ); 193 $out = p.$container.find(p.cssPageDisplay); 194 195 // Output param can be callback for custom rendering or string 196 if (typeof p.output === 'function') { 197 s = p.output(table, p); 198 } else { 199 output = $out 200 // get output template from data-pager-output or data-pager-output-filtered 201 .attr('data-pager-output' + (p.filteredRows < p.totalRows ? '-filtered' : '')) || 202 p.output; 203 // form the output string (can now get a new output string from the server) 204 s = ( p.ajaxData && p.ajaxData.output ? p.ajaxData.output || output : output ) 205 // {page} = one-based index; {page+#} = zero based index +/- value 206 .replace(/\{page([\-+]\d+)?\}/gi, function(m, n) { 207 return p.totalPages ? p.page + (n ? parseInt(n, 10) : 1) : 0; 208 }) 209 // {totalPages}, {extra}, {extra:0}
209 (array) or {extra : key} (object) 210 .replace(/\{\w+(\s*:\s*\w+)?\}/gi, function(m) { 211 var len, indx, 212 str = m.replace(/[{}\s]/g, ''), 213 extra = str.split(':'), 214 data = p.ajaxData, 215 // return zero for default page/row numbers 216 deflt = /(rows?|pages?)$/i.test(str) ? 0 : ''; 217 if (/(startRow|page)/.test(extra[0]) && extra[1] === 'input') { 218 len = ('' + (extra[0] === 'page' ? p.totalPages : p.totalRows)).length; 219 indx = extra[0] === 'page' ? p.page + 1 : p.startRow; 220 return '<input type="text" class="ts-' + extra[0] + '" style="max-width:' + len + 'em" value="' + indx + '"/>'; 221 } 222 return extra.length > 1 && data && data[extra[0]] ? data[extra[0]][extra[1]] : p[str] || (data ? data[str] : deflt) || deflt; 223 }); 224 } 225 $el = p.$container.find(p.cssGoto); 226 if ( $el.length ) { 227 t = ''; 228 options = buildPageSelect( table, p ); 229 len = options.length; 230 for (indx = 0; indx < len; indx++) { 231 t += '<option value="' + options[indx] + '">' + options[indx] + '</option>'; 232 } 233 // innerHTML doesn't work in IE9 - http://support2.microsoft.com/kb/276228 234 $el.html(t).val( p.page + 1 ); 235 } 236 if ($out.length) { 237 $out[ ($out[0].nodeName === 'INPUT') ? 'val' : 'html' ](s); 238 // rebind startRow/page inputs 239 $out.find('.ts-startRow, .ts-page').unbind('change' + namespace).bind('change' + namespace, function() { 240 var v = $(this).val(), 241 pg = $(this).hasClass('ts-startRow') ? Math.floor( v / sz ) + 1 : v; 242 c.$table.triggerHandler('pageSet' + namespace, [ pg ]); 243 }); 244 } 245 } 246 pagerArrows( table, p ); 247 fixHeight(table, p); 248 if (p.initialized && completed !== false) { 249 if (ts.debug(c, 'pager')) { 250 console.log('Pager >> Triggering pagerComplete'); 251 } 252 c.$table.triggerHandler('pagerComplete', p); 253 // save pager info to storage 254 if (p.savePages && ts.storage) { 255 ts.storage(table, p.storageKey, { 256 page : p.page, 257 size : sz === p.totalRows ? 'all' : sz 258 }); 259 } 260 } 261 }, 262 263 buildPageSelect = function( table, p ) { 264 // Filter the options page number link array if it's larger than 'maxOptionSize' 265 // as large page set links will slow the browser on large dom inserts 266 var i, central_focus_size, focus_option_pages, insert_index, option_length, focus_length, 267 pg = getTotalPages( table, p ) || 1, 268 // make skip set size multiples of 5 269 skip_set_size = Math.ceil( ( pg / p.maxOptionSize ) / 5 ) * 5, 270 large_collection = pg > p.maxOptionSize, 271 current_page = p.page + 1, 272 start_page = skip_set_size, 273 end_page = pg - skip_set_size, 274 option_pages = [ 1 ], 275 // construct default options pages array 276 option_pages_start_page = (large_collection) ? skip_set_size : 1; 277 278 for ( i = option_pages_start_page; i <= pg; ) { 279 option_pages[ option_pages.length ] = i; 280 i = i + ( large_collection ? skip_set_size : 1 ); 281 } 282 option_pages[ option_pages.length ] = pg; 283 if (large_collection) { 284 focus_option_pages = []; 285 // don't allow central focus size to be > 5 on either side of current page 286 central_focus_size = Math.max( Math.floor( p.maxOptionSize / skip_set_size ) - 1, 5 ); 287 288 start_page = current_page - central_focus_size; 289 if (start_page < 1) { start_page = 1; } 290 end_page = current_page + central_focus_size; 291 if (end_page > pg) { end_page = pg; } 292 // construct an array to get a focus set around the current page 293 for (i = start_page; i <= end_page ; i++) { 294 focus_option_pages[ focus_option_pages.length ] = i; 295 } 296 297 // keep unique values 298 option_pages = $.grep(option_pages, function(value, indx) { 299 return $.inArray(value, option_pages) === indx; 300 }); 301 302 option_length = option_pages.length; 303 focus_length = focus_option_pages.length; 304 305 // make sure at all option_pages aren't replaced 306 if (option_length - focus_length > skip_set_size / 2 && option_length + focus_length > p.maxOptionSize ) { 307 insert_index = Math.floor(option_length / 2) - Math.floor(focus_length / 2); 308 Array.prototype.splice.apply(option_pages, [ insert_index, focus_length ]); 309 } 310 option_pages = option_pages.concat(focus_option_pages); 311 312 } 313 314 // keep unique values again 315 option_pages = $.grep(option_pages, function(value, indx) { 316 return $.inArray(value, option_pages) === indx; 317 }) 318 .sort(function(a, b) { return a - b; }); 319 320 return option_pages; 321 }, 322 323 fixHeight = function(table, p) { 324 var d, h, bs, 325 c = table.config, 326 $b = c.$tbodies.eq(0);
327 $b.find('tr.pagerSavedHeightSpacer').remove(); 328 if (p.fixedHeight && !p.isDisabled) { 329 h = $.data(table, 'pagerSavedHeight'); 330 if (h) { 331 bs = 0; 332 if ($(table).css('border-spacing').split(' ').length > 1) { 333 bs = $(table).css('border-spacing').split(' ')[1].replace(/[^-\d\.]/g, ''); 334 } 335 d = h - $b.height() + (bs * p.size) - bs; 336 if ( 337 d > 5 && $.data(table, 'pagerLastSize') === p.size && 338 $b.children('tr:visible').length < (p.size === 'all' ? p.totalRows : p.size) 339 ) { 340 $b.append('<tr class="pagerSavedHeightSpacer ' + c.selectorRemove.slice(1) + '" style="height:' + d + 'px;"></tr>'); 341 } 342 } 343 } 344 }, 345 346 changeHeight = function(table, p) { 347 var h, 348 c = table.config, 349 $b = c.$tbodies.eq(0); 350 $b.find('tr.pagerSavedHeightSpacer').remove(); 351 if (!$b.children('tr:visible').length) { 352 $b.append('<tr class="pagerSavedHeightSpacer ' + c.selectorRemove.slice(1) + '"><td> </td></tr>'); 353 } 354 h = $b.children('tr').eq(0).height() * (p.size === 'all' ? p.totalRows : p.size); 355 $.data(table, 'pagerSavedHeight', h); 356 fixHeight(table, p); 357 $.data(table, 'pagerLastSize', p.size); 358 }, 359 360 hideRows = function(table, p) { 361 if (!p.ajaxUrl) { 362 var i, 363 lastIndex = 0, 364 c = table.config, 365 rows = c.$tbodies.eq(0).children('tr'), 366 l = rows.length, 367 sz = p.size === 'all' ? p.totalRows : p.size, 368 s = ( p.page * sz ), 369 e = s + sz, 370 last = -1, // for cache indexing 371 j = 0; // size counter 372 p.cacheIndex = []; 373 for ( i = 0; i < l; i++ ) { 374 if ( !p.regexFiltered.test(rows[i].className) ) { 375 if (j === s && rows[i].className.match(c.cssChildRow)) { 376 // hide child rows @ start of pager (if already visible) 377 rows[i].style.display = 'none'; 378 } else { 379 rows[i].style.display = ( j >= s && j < e ) ? '' : 'none'; 380 if (last !== j && j >= s && j < e) { 381 p.cacheIndex[ p.cacheIndex.length ] = i; 382 last = j; 383 } 384 // don't count child rows 385 j += rows[i].className.match(c.cssChildRow + '|' + c.selectorRemove.slice(1)) && !p.countChildRows ? 0 : 1; 386 if ( j === e && rows[i].style.display !== 'none' && rows[i].className.match(ts.css.cssHasChild) ) { 387 lastIndex = i; 388 } 389 } 390 } 391 } 392 // add any attached child rows to last row of pager. Fixes part of issue #396 393 if ( lastIndex > 0 && rows[lastIndex].className.match(ts.css.cssHasChild) ) { 394 while ( ++lastIndex < l && rows[lastIndex].className.match(c.cssChildRow) ) { 395 rows[lastIndex].style.display = ''; 396 } 397 } 398 } 399 }, 400 401 hideRowsSetup = function(table, p) { 402 p.size = parsePageSize( p, p.$container.find(p.cssPageSize).val(), 'get' ); 403 setPageSize( table, p.size, p ); 404 pagerArrows( table, p ); 405 if ( !p.removeRows ) { 406 hideRows(table, p); 407 $(table).bind('sortEnd filterEnd '.split(' ').join(table.config.namespace + 'pager '), function() { 408 hideRows(table, p); 409 }); 410 } 411 }, 412 413 renderAjax = function(data, table, p, xhr, settings, exception) { 414 // process data 415 if ( typeof p.ajaxProcessing === 'function' ) { 416 417 // in case nothing is returned by ajax, empty out the table; see #1032 418 // but do it before calling pager_ajaxProcessing because that function may add content 419 // directly to the table 420 table.config.$tbodies.eq(0).empty(); 421 422 // ajaxProcessing result: [ total, rows, headers ] 423 var i, j, t, hsh, $f, $sh, $headers, $h, icon, th, d, l, rr_count, len, sz, 424 c = table.config, 425 $table = c.$table, 426 tds = '', 427 result = p.ajaxProcessing(data, table, xhr) || [ 0, [] ]; 428 // Clean up any previous error. 429 ts.showError( table ); 430 431 if ( exception ) { 432 if (ts.debug(c, 'pager')) { 433 console.error('Pager >> Ajax Error', xhr, settings, exception); 434 } 435 ts.showError( table, xhr, settings, exception ); 436 c.$tbodies.eq(0).children('tr').detach(); 437 p.totalRows = 0; 438 } else { 439 // process ajax object 440 if (!$.isArray(result)) { 441 p.ajaxData = result; 442 c.totalRows = p.totalRows = result.total; 443 c.filteredRows = p.filteredRows = typeof result.filteredRows !== 'undefined' ? result.filteredRows : result.total; 444 th = result.headers; 445 d = result.rows || []; 446 } else { 447 // allow [ total, rows, headers ] or [ rows, total, headers ] 448 t = isNaN(result[0]) && !isNaN(result[1]); 449 // ensure a zero returned row count doesn't fail the logical || 450 rr_count = result[t ? 1 : 0]; 451 p.totalRows = isNaN(rr_count) ? p.totalRows || 0 : rr_count; 452 // can't set filtered rows when returning an array 453 c.totalRows = c.filteredRows = p.filteredRows = p.totalRows; 454 // set row data to empty array if nothing found - see http://stackoverflow.com/q/30875583/145346 455 d = p.totalRows === 0 ? [] : result[t ? 0 : 1] || []; // row data 456 th = result[2]; // headers 457 } 458 l = d && d.length; 459 if (d instanceof $) { 460 if (p.processAjaxOnInit) { 461 // append jQuery object 462 c.$tbodies.eq(0).empty(); 463 c.$tbodies.eq(0).append(d); 464 } 465 } else if (l) { 466 // build table from array 467 for ( i = 0; i < l; i++ ) { 468 tds += '<tr>'; 469 for ( j = 0; j < d[i].length; j++ ) { 470 // build tbody cells; watch for data containing HTML markup - see #434 471 tds += /^\s*<td/.test(d[i][j]) ? $.trim(d[i][j]) : '<td>' + d[i][j] + '</td>'; 472 } 473 tds += '</tr>'; 474 } 475 // add rows to first tbody 476 if (p.processAjaxOnInit) { 477 c.$tbodies.eq(0).html( tds ); 478 } 479 } 480 p.processAjaxOnInit = true; 481 // update new header text 482 if ( th ) { 483 hsh = $table.hasClass('hasStickyHeaders'); 484 $sh = hsh ? 485 c.widgetOptions.$sticky.children('thead:first').children('tr:not(.' + c.cssIgnoreRow + ')').children() : 486 ''; 487 $f = $table.find('tfoot tr:first').children(); 488 // don't change td headers (may contain pager) 489 $headers = c.$headers.filter( 'th ' ); 490 len = $headers.length; 491 for ( j = 0; j < len; j++ ) { 492 $h = $headers.eq( j ); 493 // add new test within the first span it finds, or just in the header 494 if ( $h.find('.' + ts.css.icon).length ) { 495 icon = $h.find('.' + ts.css.icon).clone(true); 496 $h.find('.' + ts.css.headerIn).html( th[j] ).append(icon); 497 if ( hsh && $sh.length ) { 498 icon = $sh.eq(j).find('.' + ts.css.icon).clone(true); 499 $sh.eq(j).find('.' + ts.css.headerIn).html( th[j] ).append(icon); 500 } 501 } else { 502 $h.find('.' + ts.css.headerIn).html( th[j] ); 503 if (hsh && $sh.length) { 504 // add sticky header to container just in case it contains pager controls 505 p.$container = p.$container.add( c.widgetOptions.$sticky ); 506 $sh.eq(j).find('.' + ts.css.headerIn).html( th[j] ); 507 } 508 } 509 $f.eq(j).html( th[j] ); 510 } 511 } 512 } 513 if (c.showProcessing) { 514 ts.isProcessing(table); // remove loading icon 515 } 516 sz = parsePageSize( p, p.size, 'get' ); 517 // make sure last pager settings are saved, prevents multiple server side calls with 518 // the same parameters 519 p.totalPages = sz === 'all' ? 1 : Math.ceil( p.totalRows / sz ); 520 p.last.totalRows = p.totalRows; 521 p.last.currentFilters = p.currentFilters; 522 p.last.sortList = (c.sortList || []).join(','); 523 updatePageDisplay(table, p, false); 524 // tablesorter core updateCache (not pager) 525 ts.updateCache( c, function() { 526 if (p.initialized) { 527 // apply widgets after table has rendered & after a delay to prevent 528 // multiple applyWidget blocking code from blocking this trigger 529 setTimeout(function() { 530 if (ts.debug(c, 'pager')) { 531 console.log('Pager >> Triggering pagerChange'); 532 } 533 $table.triggerHandler( 'pagerChange', p ); 534 ts.applyWidget( table ); 535 updatePageDisplay(table, p, true); 536 }, 0); 537 } 538 }); 539 540 } 541 if (!p.initialized) { 542 pagerInitialized(table, p); 543 } 544 }, 545 546 getAjax = function(table, p) { 547 var url = getAjaxUrl(table, p), 548 $doc = $(document), 549 counter, 550 c = table.config, 551 namespace = c.namespace + 'pager'; 552 if ( url !== '' ) { 553 if (c.showProcessing) { 554 ts.isProcessing(table, true); // show loading icon 555 } 556 $doc.bind('ajaxError' + namespace, function(e, xhr, settings, exception) { 557 renderAjax(null, table, p, xhr, settings, exception); 558 $doc.unbind('ajaxError' + namespace); 559 }); 560 561 counter = ++p.ajaxCounter; 562 563 p.last.ajaxUrl = url; // remember processed url 564 p.ajaxObject.url = url; // from the ajaxUrl option and modified by customAjaxUrl 565 p.ajaxObject.success = function(data, status, jqxhr) { 566 // Refuse to process old ajax commands that were overwritten by new ones - see #443
567 if (counter < p.ajaxCounter) { 568 return; 569 } 570 renderAjax(data, table, p, jqxhr); 571 $doc.unbind('ajaxError' + namespace); 572 if (typeof p.oldAjaxSuccess === 'function') { 573 p.oldAjaxSuccess(data); 574 } 575 }; 576 if (ts.debug(c, 'pager')) { 577 console.log('Pager >> Ajax initialized', p.ajaxObject); 578 } 579 $.ajax(p.ajaxObject); 580 } 581 }, 582 583 getAjaxUrl = function(table, p) { 584 var indx, len, 585 c = table.config, 586 url = (p.ajaxUrl) ? p.ajaxUrl 587 // allow using "{page+1}" in the url string to switch to a non-zero based index 588 .replace(/\{page([\-+]\d+)?\}/, function(s, n) { return p.page + (n ? parseInt(n, 10) : 0); }) 589 // this will pass "all" to server when size is set to "all" 590 .replace(/\{size\}/g, p.size) : '', 591 sortList = c.sortList, 592 filterList = p.currentFilters || $(table).data('lastSearch') || [], 593 sortCol = url.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/), 594 filterCol = url.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/), 595 arry = []; 596 if (sortCol) { 597 sortCol = sortCol[1]; 598 len = sortList.length; 599 for (indx = 0; indx < len; indx++) { 600 arry[ arry.length ] = sortCol + '[' + sortList[indx][0] + ']=' + sortList[indx][1]; 601 } 602 // if the arry is empty, just add the col parameter... "&{sortList:col}" becomes "&col" 603 url = url.replace(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : sortCol ); 604 arry = []; 605 } 606 if (filterCol) { 607 filterCol = filterCol[1]; 608 len = filterList.length; 609 for (indx = 0; indx < len; indx++) { 610 if (filterList[indx]) { 611 arry[ arry.length ] = filterCol + '[' + indx + ']=' + encodeURIComponent( filterList[indx] ); 612 } 613 } 614 // if the arry is empty, just add the fcol parameter... "&{filterList:fcol}" becomes "&fcol" 615 url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol ); 616 p.currentFilters = filterList; 617 } 618 if ( typeof p.customAjaxUrl === 'function' ) { 619 url = p.customAjaxUrl(table, url); 620 } 621 if (ts.debug(c, 'pager')) { 622 console.log('Pager >> Ajax url = ' + url); 623 } 624 return url; 625 }, 626 627 renderTable = function(table, rows, p) { 628 var $tb, index, count, added, 629 $t = $(table), 630 c = table.config, 631 debug = ts.debug(c, 'pager'), 632 f = c.$table.hasClass('hasFilters'), 633 l = rows && rows.length || 0, // rows may be undefined 634 e = p.size === 'all' ? p.totalRows : p.size, 635 s = ( p.page * e ); 636 if ( l < 1 ) { 637 if (debug) { 638 console.warn('Pager >> No rows for pager to render'); 639 } 640 // empty table, abort! 641 return; 642 } 643 if ( p.page >= p.totalPages ) { 644 // lets not render the table more than once 645 moveToLastPage(table, p); 646 } 647 p.cacheIndex = []; 648 p.isDisabled = false; // needed because sorting will change the page and re-enable the pager 649 if (p.initialized) { 650 if (debug) { 651 console.log('Pager >> Triggering pagerChange'); 652 } 653 $t.triggerHandler( 'pagerChange', p ); 654 } 655 if ( !p.removeRows ) { 656 hideRows(table, p); 657 } else { 658 ts.clearTableBody(table); 659 $tb = ts.processTbody(table, c.$tbodies.eq(0), true); 660 // not filtered, start from the calculated starting point (s) 661 // if filtered, start from zero 662 index = f ? 0 : s; 663 count = f ? 0 : s; 664 added = 0; 665 while (added < e && index < rows.length) { 666 if (!f || !p.regexFiltered.test(rows[index][0].className)) { 667 count++; 668 if (count > s && added <= e) { 669 added++; 670 p.cacheIndex[ p.cacheIndex.length ] = index; 671 $tb.append(rows[index]); 672 } 673 } 674 index++; 675 } 676 ts.processTbody(table, $tb, false); 677 } 678 updatePageDisplay(table, p); 679 if (table.isUpdating) { 680 if (debug) { 681 console.log('Pager >> Triggering updateComplete'); 682 } 683 $t.triggerHandler('updateComplete', [ table, true ]); 684 } 685 }, 686 687 showAllRows = function(table, p) { 688 var index, $controls, len; 689 if ( p.ajax ) { 690 pagerArrows( table, p, true ); 691 } else { 692 $.data(table, 'pagerLastPage', p.page); 693 $.data(table, 'pagerLastSize', p.size); 694 p.page = 0; 695 p.size = p.totalRows; 696 p.totalPages = 1; 697 $(table) 698 .addClass('pagerDisabled') 699 .removeAttr('aria-describedby') 700 .find('tr.pagerSavedHeightSpacer').remove(); 701 renderTable(table, table.config.rowsCopy, p); 702 p.isDisabled = true; 703 ts.applyWidget( table ); 704 if (ts.debug(table.config, 'pager')) { 705 console.log('Pager >> Disabled'); 706 } 707 } 708 // disable size selector 709 $controls = p.$container.find( p.cssGoto + ',' + p.cssPageSize + ', .ts-startRow, .ts-page' ); 710 len = $controls.length; 711 for ( index = 0; index < len; index++ ) { 712 $controls.eq( index ).addClass( p.cssDisabled )[0].disabled = true; 713 $controls[ index ].ariaDisabled = true; 714 } 715 }, 716 717 // updateCache if delayInit: true 718 updateCache = function(table) { 719 var c = table.config, 720 p = c.pager; 721 // tablesorter core updateCache (not pager) 722 ts.updateCache( c, function() { 723 var i, 724 rows = [], 725 n = table.config.cache[0].normalized; 726 p.totalRows = n.length; 727 for (i = 0; i < p.totalRows; i++) { 728 rows[ rows.length ] = n[i][c.columns].$row; 729 } 730 c.rowsCopy = rows; 731 moveToPage(table, p, true); 732 }); 733 }, 734 735 moveToPage = function(table, p, pageMoved) { 736 if ( p.isDisabled ) { return; } 737 var tmp, 738 c = table.config, 739 debug = ts.debug(c, 'pager'), 740 $t = $(table), 741 l = p.last; 742 if ( pageMoved !== false && p.initialized && ts.isEmptyObject(c.cache)) { 743 return updateCache(table); 744 } 745 // abort page move if the table has filters and has not been initialized 746 if (p.ajax && ts.hasWidget(table, 'filter') && !c.widgetOptions.filter_initialized) { return; } 747 parsePageNumber( table, p ); 748 calcFilters(table, p); 749 // fixes issue where one currentFilter is [] and the other is ['','',''], 750 // making the next if comparison think the filters are different (joined by commas). Fixes #202. 751 l.currentFilters = (l.currentFilters || []).join('') === '' ? [] : l.currentFilters; 752 p.currentFilters = (p.currentFilters || []).join('') === '' ? [] : p.currentFilters; 753 // don't allow rendering multiple times on the same page/size/totalRows/filters/sorts 754 if ( l.page === p.page && l.size === p.size && l.totalRows === p.totalRows && 755 (l.currentFilters || []).join(',') === (p.currentFilters || []).join(',') && 756 // check for ajax url changes see #730 757 (l.ajaxUrl || '') === (p.ajaxObject.url || '') && 758 // & ajax url option changes (dynamically add/remove/rename sort & filter parameters) 759 (l.optAjaxUrl || '') === (p.ajaxUrl || '') && 760 l.sortList === (c.sortList || []).join(',') ) { return; } 761 if (debug) { 762 console.log('Pager >> Changing to page ' + p.page); 763 } 764 p.last = { 765 page : p.page, 766 size : p.size, 767 // fixes #408; modify sortList otherwise it auto-updates 768 sortList : (c.sortList || []).join(','), 769 totalRows : p.totalRows, 770 currentFilters : p.currentFilters || [], 771 ajaxUrl : p.ajaxObject.url || '', 772 optAjaxUrl : p.ajaxUrl || '' 773 }; 774 if (p.ajax) { 775 if ( !p.processAjaxOnInit && !ts.isEmptyObject(p.initialRows) ) { 776 p.processAjaxOnInit = true; 777 tmp = p.initialRows;
778 p.totalRows = typeof tmp.total !== 'undefined' ? tmp.total : 779 ( debug ? console.error('Pager >> No initial total page set!') || 0 : 0 ); 780 p.filteredRows = typeof tmp.filtered !== 'undefined' ? tmp.filtered : 781 ( debug ? console.error('Pager >> No initial filtered page set!') || 0 : 0 ); 782 pagerInitialized( table, p ); 783 } else { 784 getAjax(table, p); 785 } 786 } else if (!p.ajax) { 787 renderTable(table, c.rowsCopy, p); 788 } 789 $.data(table, 'pagerLastPage', p.page); 790 if (p.initialized && pageMoved !== false) { 791 if (debug) { 792 console.log('Pager >> Triggering pageMoved'); 793 } 794 $t.triggerHandler('pageMoved', p); 795 ts.applyWidget( table ); 796 if (table.isUpdating) { 797 if (debug) { 798 console.log('Pager >> Triggering updateComplete'); 799 } 800 $t.triggerHandler('updateComplete', [ table, true ]); 801 } 802 } 803 }, 804 805 getTotalPages = function( table, p ) { 806 return ts.hasWidget( table, 'filter' ) ? 807 Math.min( p.totalPages, p.filteredPages ) : 808 p.totalPages; 809 }, 810 811 parsePageNumber = function( table, p ) { 812 var min = getTotalPages( table, p ) - 1; 813 p.page = parseInt( p.page, 10 ); 814 if ( p.page < 0 || isNaN( p.page ) ) { p.page = 0; } 815 if ( p.page > min && min >= 0 ) { p.page = min; } 816 return p.page; 817 }, 818 819 // set to either set or get value 820 parsePageSize = function( p, size, mode ) { 821 var s = parseInt( size, 10 ) || p.size || p.settings.size || 10; 822 if (p.initialized && (/all/i.test( s + ' ' + size ) || s === p.totalRows)) { 823 // Fixing #1364 & #1366 824 return p.$container.find(p.cssPageSize + ' option[value="all"]').length ? 825 'all' : p.totalRows; 826 } 827 // "get" to get `p.size` or "set" to set `pageSize.val()` 828 return mode === 'get' ? s : p.size; 829 }, 830 831 setPageSize = function(table, size, p) { 832 // "all" size is only returned if an "all" option exists - fixes #1366 833 p.size = parsePageSize( p, size, 'get' ); 834 p.$container.find( p.cssPageSize ).val( p.size ); 835 $.data(table, 'pagerLastPage', parsePageNumber( table, p ) ); 836 $.data(table, 'pagerLastSize', p.size); 837 p.totalPages = p.size === 'all' ? 1 : Math.ceil( p.totalRows / p.size ); 838 p.filteredPages = p.size === 'all' ? 1 : Math.ceil( p.filteredRows / p.size ); 839 }, 840 841 moveToFirstPage = function(table, p) { 842 p.page = 0; 843 moveToPage(table, p); 844 }, 845 846 moveToLastPage = function(table, p) { 847 p.page = getTotalPages( table, p ) - 1; 848 moveToPage(table, p); 849 }, 850 851 moveToNextPage = function(table, p) { 852 p.page++; 853 var last = getTotalPages( table, p ) - 1; 854 if ( p.page >= last ) { 855 p.page = last; 856 } 857 moveToPage(table, p); 858 }, 859 860 moveToPrevPage = function(table, p) { 861 p.page--; 862 if ( p.page <= 0 ) { 863 p.page = 0; 864 } 865 moveToPage(table, p); 866 }, 867 868 pagerInitialized = function(table, p) { 869 p.initialized = true; 870 p.initializing = false; 871 if (ts.debug(table.config, 'pager')) { 872 console.log('Pager >> Triggering pagerInitialized'); 873 } 874 $(table).triggerHandler( 'pagerInitialized', p ); 875 ts.applyWidget( table ); 876 updatePageDisplay(table, p); 877 }, 878 879 resetState = function(table, p) { 880 var c = table.config; 881 c.pager = $.extend( true, {}, $.tablesorterPager.defaults, p.settings ); 882 init(table, p.settings); 883 }, 884 885 destroyPager = function(table, p) { 886 var c = table.config, 887 namespace = c.namespace + 'pager', 888 ctrls = [ p.cssFirst, p.cssPrev, p.cssNext, p.cssLast, p.cssGoto, p.cssPageSize ].join( ',' ); 889 showAllRows(table, p); 890 p.$container 891 // hide pager controls 892 .hide() 893 // unbind 894 .find( ctrls ) 895 .unbind( namespace ); 896 c.appender = null; // remove pager appender function 897 c.$table.unbind( namespace ); 898 if (ts.storage) { 899 ts.storage(table, p.storageKey, ''); 900 } 901 delete c.pager; 902 delete c.rowsCopy; 903 }, 904 905 enablePager = function(table, p, triggered) { 906 var info, size, $el, 907 c = table.config; 908 p.$container.find(p.cssGoto + ',' + p.cssPageSize + ',.ts-startRow, .ts-page') 909 .removeClass(p.cssDisabled) 910 .removeAttr('disabled') 911 .each(function() { 912 this.ariaDisabled = false;
913 }); 914 p.isDisabled = false; 915 p.page = $.data(table, 'pagerLastPage') || p.page || 0; 916 $el = p.$container.find(p.cssPageSize); 917 size = $el.find('option[selected]').val(); 918 p.size = $.data(table, 'pagerLastSize') || parsePageSize( p, size, 'get' ); 919 p.totalPages = p.size === 'all' ? 1 : Math.ceil( getTotalPages( table, p ) / p.size ); 920 setPageSize(table, p.size, p); // set page size 921 // if table id exists, include page display with aria info 922 if ( table.id && !c.$table.attr( 'aria-describedby' ) ) { 923 $el = p.$container.find( p.cssPageDisplay ); 924 info = $el.attr( 'id' ); 925 if ( !info ) { 926 // only add pageDisplay id if it doesn't exist - see #1288 927 info = table.id + '_pager_info'; 928 $el.attr( 'id', info ); 929 } 930 c.$table.attr( 'aria-describedby', info ); 931 } 932 changeHeight(table, p); 933 if ( triggered ) { 934 // tablesorter core update table 935 ts.update( c ); 936 setPageSize(table, p.size, p); 937 moveToPage(table, p); 938 hideRowsSetup(table, p); 939 if (ts.debug(c, 'pager')) { 940 console.log('Pager >> Enabled'); 941 } 942 } 943 }, 944 945 init = function(table, settings) { 946 var t, ctrls, fxn, $el, 947 c = table.config, 948 wo = c.widgetOptions, 949 debug = ts.debug(c, 'pager'), 950 p = c.pager = $.extend( true, {}, $.tablesorterPager.defaults, settings ), 951 $t = c.$table, 952 namespace = c.namespace + 'pager', 953 // added in case the pager is reinitialized after being destroyed. 954 pager = p.$container = $(p.container).addClass('tablesorter-pager').show(); 955 // save a copy of the original settings 956 p.settings = $.extend( true, {}, $.tablesorterPager.defaults, settings ); 957 if (debug) { 958 console.log('Pager >> Initializing'); 959 } 960 p.oldAjaxSuccess = p.oldAjaxSuccess || p.ajaxObject.success; 961 c.appender = $this.appender; 962 p.initializing = true; 963 if (p.savePages && ts.storage) { 964 t = ts.storage(table, p.storageKey) || {}; // fixes #387 965 p.page = isNaN(t.page) ? p.page : t.page; 966 p.size = t.size === 'all' ? t.size : ( isNaN( t.size ) ? p.size : t.size ) || p.setSize || 10; 967 setPageSize(table, p.size, p); 968 } 969 // skipped rows 970 p.regexRows = new RegExp('(' + (wo.filter_filteredRow || 'filtered') + '|' + c.selectorRemove.slice(1) + '|' + c.cssChildRow + ')'); 971 p.regexFiltered = new RegExp(wo.filter_filteredRow || 'filtered'); 972 973 $t 974 // .unbind( namespace ) adding in jQuery 1.4.3 ( I think ) 975 .unbind( pagerEvents.split(' ').join(namespace + ' ').replace(/\s+/g, ' ') ) 976 .bind('filterInit filterStart '.split(' ').join(namespace + ' '), function(e, filters) { 977 p.currentFilters = $.isArray(filters) ? filters : c.$table.data('lastSearch'); 978 var filtersEqual; 979 if (p.ajax && e.type === 'filterInit') { 980 // ensure pager ajax is called after filter widget has initialized 981 return moveToPage( table, p, false ); 982 } 983 if (ts.filter.equalFilters) { 984 filtersEqual = ts.filter.equalFilters(c, c.lastSearch, p.currentFilters); 985 } else { 986 // will miss filter changes of the same value in a different column, see #1363 987 filtersEqual = (c.lastSearch || []).join('') !== (p.currentFilters || []).join(''); 988 } 989 // don't change page if filters are the same (pager updating, etc) 990 if (e.type === 'filterStart' && p.pageReset !== false && !filtersEqual) { 991 p.page = p.pageReset; // fixes #456 & #565 992 } 993 }) 994 // update pager after filter widget completes 995 .bind('filterEnd sortEnd '.split(' ').join(namespace + ' '), function() { 996 p.currentFilters = c.$table.data('lastSearch'); 997 if (p.initialized || p.initializing) { 998 if (c.delayInit && c.rowsCopy && c.rowsCopy.length === 0) { 999 // make sure we have a copy of all table rows once the cache has been built 1000 updateCache(table); 1001 } 1002 updatePageDisplay(table, p, false); 1003 moveToPage(table, p, false); 1004 ts.applyWidget( table ); 1005 } 1006 }) 1007 .bind('disablePager' + namespace, function(e) { 1008 e.stopPropagation(); 1009 showAllRows(table, p); 1010 }) 1011 .bind('enablePager' + namespace, function(e) { 1012 e.stopPropagation(); 1013 enablePager(table, p, true); 1014 }) 1015 .bind('destroyPager' + namespace, function(e) { 1016 e.stopPropagation(); 1017 destroyPager(table, p); 1018 }) 1019 .bind('resetToLoadState' + namespace, function(e) { 1020 e.stopPropagation(); 1021 resetState(table, p); 1022 }) 1023 .bind('updateComplete' + namespace, function(e, table, triggered) { 1024 e.stopPropagation(); 1025 // table can be unintentionally undefined in tablesorter v2.17.7 and earlier 1026 // don't recalculate total rows/pages if using ajax 1027 if ( !table || triggered || p.ajax ) { return; } 1028 var $rows = c.$tbodies.eq(0).children('tr').not(c.selectorRemove); 1029 p.totalRows = $rows.length - ( p.countChildRows ? 0 : $rows.filter('.' + c.cssChildRow).length ); 1030 p.totalPages = p.size === 'all' ? 1 : Math.ceil( p.totalRows / p.size ); 1031 if ($rows.length && c.rowsCopy && c.rowsCopy.length === 0) { 1032 // make a copy of all table rows once the cache has been built 1033 updateCache(table); 1034 } 1035 if ( p.page >= p.totalPages ) { 1036 moveToLastPage(table, p); 1037 } 1038 hideRows(table, p); 1039 changeHeight(table, p); 1040 updatePageDisplay(table, p, true); 1041 }) 1042 .bind('pageSize refreshComplete '.split(' ').join(namespace + ' '), function(e, size) { 1043 e.stopPropagation(); 1044 setPageSize(table, parsePageSize( p, size, 'get' ), p); 1045 moveToPage(table, p); 1046 hideRows(table, p); 1047 updatePageDisplay(table, p, false); 1048 }) 1049 .bind('pageSet pagerUpdate '.split(' ').join(namespace + ' '), function(e, num) { 1050 e.stopPropagation(); 1051 // force pager refresh 1052 if (e.type === 'pagerUpdate') { 1053 num = typeof num === 'undefined' ? p.page + 1 : num; 1054 p.last.page = true; 1055 } 1056 p.page = (parseInt(num, 10) || 1) - 1; 1057 moveToPage(table, p, true); 1058 updatePageDisplay(table, p, false); 1059 }) 1060 .bind('pageAndSize' + namespace, function(e, page, size) { 1061 e.stopPropagation(); 1062 p.page = (parseInt(page, 10) || 1) - 1; 1063 setPageSize(table, parsePageSize( p, size, 'get' ), p); 1064 moveToPage(table, p, true); 1065 hideRows(table, p); 1066 updatePageDisplay(table, p, false); 1067 }); 1068 1069 // clicked controls 1070 ctrls = [ p.cssFirst, p.cssPrev, p.cssNext, p.cssLast ]; 1071 fxn = [ moveToFirstPage, moveToPrevPage, moveToNextPage, moveToLastPage ]; 1072 if (debug && !pager.length) { 1073 console.warn('Pager >> "container" not found'); 1074 } 1075 pager.find(ctrls.join(',')) 1076 .attr('tabindex', 0)
1077 .unbind('click' + namespace) 1078 .bind('click' + namespace, function(e) { 1079 e.stopPropagation(); 1080 var i, $t = $(this), l = ctrls.length; 1081 if ( !$t.hasClass(p.cssDisabled) ) { 1082 for (i = 0; i < l; i++) { 1083 if ($t.is(ctrls[i])) { 1084 fxn[i](table, p); 1085 break; 1086 } 1087 } 1088 } 1089 }); 1090 1091 // goto selector 1092 $el = pager.find(p.cssGoto); 1093 if ( $el.length ) { 1094 $el 1095 .unbind('change' + namespace) 1096 .bind('change' + namespace, function() { 1097 p.page = $(this).val() - 1; 1098 moveToPage(table, p, true); 1099 updatePageDisplay(table, p, false); 1100 }); 1101 } else if (debug) { 1102 console.warn('Pager >> "goto" selector not found'); 1103 } 1104 // page size selector 1105 $el = pager.find(p.cssPageSize); 1106 if ( $el.length ) { 1107 // setting an option as selected appears to cause issues with initial page size 1108 $el.find('option').removeAttr('selected'); 1109 $el.unbind('change' + namespace).bind('change' + namespace, function() { 1110 if ( !$(this).hasClass(p.cssDisabled) ) { 1111 var size = $(this).val(); 1112 // in case there are more than one pager 1113 setPageSize(table, size, p); 1114 moveToPage(table, p); 1115 changeHeight(table, p); 1116 } 1117 return false; 1118 }); 1119 } else if (debug) { 1120 console.warn('Pager >> "size" selector not found'); 1121 } 1122 1123 // clear initialized flag 1124 p.initialized = false; 1125 // before initialization event 1126 $t.triggerHandler('pagerBeforeInitialized', p); 1127 1128 enablePager(table, p, false); 1129 if ( typeof p.ajaxUrl === 'string' ) { 1130 // ajax pager; interact with database 1131 p.ajax = true; 1132 // When filtering with ajax, allow only custom filtering function, disable default 1133 // filtering since it will be done server side. 1134 c.widgetOptions.filter_serversideFiltering = true; 1135 c.serverSideSorting = true; 1136 moveToPage(table, p); 1137 } else { 1138 p.ajax = false; 1139 // Regular pager; all rows stored in memory 1140 ts.appendCache( c, true ); // true = don't apply widgets 1141 hideRowsSetup(table, p); 1142 } 1143 1144 // pager initialized 1145 if (!p.ajax && !p.initialized) { 1146 p.initializing = false; 1147 p.initialized = true; 1148 // update page size on init 1149 setPageSize(table, p.size, p); 1150 moveToPage(table, p); 1151 if (debug) { 1152 console.log('Pager >> Triggering pagerInitialized'); 1153 } 1154 c.$table.triggerHandler( 'pagerInitialized', p ); 1155 if ( !( c.widgetOptions.filter_initialized && ts.hasWidget(table, 'filter') ) ) { 1156 updatePageDisplay(table, p, false); 1157 } 1158 } 1159 1160 // make the hasWidget function think that the pager widget is being used 1161 c.widgetInit.pager = true; 1162 }; 1163 1164 $this.appender = function(table, rows) { 1165 var c = table.config, 1166 p = c.pager; 1167 if ( !p.ajax ) { 1168 c.rowsCopy = rows; 1169 p.totalRows = p.countChildRows ? c.$tbodies.eq(0).children('tr').length : rows.length; 1170 p.size = $.data(table, 'pagerLastSize') || p.size || p.settings.size || 10; 1171 p.totalPages = p.size === 'all' ? 1 : Math.ceil( p.totalRows / p.size ); 1172 renderTable(table, rows, p); 1173 // update display here in case all rows are removed 1174 updatePageDisplay(table, p, false); 1175 } 1176 }; 1177 1178 $this.construct = function(settings) { 1179 return this.each(function() { 1180 // check if tablesorter has initialized 1181 if (!(this.config && this.hasInitialized)) { return; } 1182 init(this, settings); 1183 }); 1184 }; 1185 1186 }() 1187 }); 1188 1189 // see #486 1190 ts.showError = function( table, xhr, settings, exception ) { 1191 var $table = $( table ), 1192 c = $table[0].config, 1193 wo = c && c.widgetOptions, 1194 errorRow = c.pager && c.pager.cssErrorRow || 1195 wo && wo.pager_css && wo.pager_css.errorRow || 1196 'tablesorter-errorRow', 1197 typ = typeof xhr, 1198 valid = true, 1199 message = '', 1200 removeRow = function() { 1201 c.$table.find( 'thead' ).find( c.selectorRemove ).remove(); 1202 }; 1203 1204 if ( !$table.length ) { 1205 console.error('tablesorter showError: no table parameter passed'); 1206 return; 1207 } 1208 1209 // ajaxError callback for plugin or widget - see #992 1210 if ( typeof c.pager.ajaxError === 'function' ) { 1211 valid = c.pager.ajaxError( c, xhr, settings, exception ); 1212 if ( valid === false ) { 1213 return removeRow(); 1214 } else { 1215 message = valid; 1216 } 1217 } else if ( typeof wo.pager_ajaxError === 'function' ) { 1218 valid = wo.pager_ajaxError( c, xhr, settings, exception ); 1219 if ( valid === false ) { 1220 return removeRow(); 1221 } else { 1222 message = valid; 1223 } 1224 } 1225 1226 if ( message === '' ) { 1227 if ( typ === 'object' ) { 1228 message = 1229 xhr.status === 0 ? 'Not connected, verify Network' : 1230 xhr.status === 404 ? 'Requested page not found [404]' : 1231 xhr.status === 500 ? 'Internal Server Error [500]' : 1232 exception === 'parsererror' ? 'Requested JSON parse failed' : 1233 exception === 'timeout' ? 'Time out error' : 1234 exception === 'abort' ? 'Ajax Request aborted' : 1235 'Uncaught error: ' + xhr.statusText + ' [' + xhr.status + ']'; 1236 } else if ( typ === 'string' ) { 1237 // keep backward compatibility (external usage just passes a message string) 1238 message = xhr; 1239 } else { 1240 // remove all error rows 1241 return removeRow(); 1242 } 1243 } 1244 1245 // allow message to include entire row HTML! 1246 $( /tr\>/.test(message) ? message : '<tr>
1246<td colspan="' + c.columns + '">' + message + '</td></tr>' ) 1247 .click( function() { 1248 $( this ).remove(); 1249 }) 1250 // add error row to thead instead of tbody, or clicking on the header will result in a parser error 1251 .appendTo( c.$table.find( 'thead:first' ) ) 1252 .addClass( errorRow + ' ' + c.selectorRemove.slice(1) ) 1253 .attr({ 1254 role : 'alert', 1255 'aria-live' : 'assertive' 1256 }); 1257 1258 }; 1259 1260 // extend plugin scope 1261 $.fn.extend({ 1262 tablesorterPager: $.tablesorterPager.construct 1263 }); 1264 1265})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.