jquery-tablesorter 1.9.5 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb4781c0bbb4cbf4d45341758149bf54411cc4f5
4
- data.tar.gz: 6b2078616a673b55edd62f274261db6db7f75700
3
+ metadata.gz: 52e8ad84b6ff1a950b33ee5b7079c1ec4729ee5a
4
+ data.tar.gz: e1327f89930c43ee5f0f3c998e9211d1ea7b4fae
5
5
  SHA512:
6
- metadata.gz: 993255aa82f9918f3147c32708731415dd879195e0bf042da888e63df63e84b71c3c523186eaa5258a8fa5b413a61b16c44308d38275c94eeac2d9b185fbb10e
7
- data.tar.gz: a9707e32ab2dba89ba98697d0c7edee23310f779412b6152f139d1c92b7c67d2c28bc879a978bfe8ca56aa45bbb39518ae76c56e8a48b5bac2ffdd5060064265
6
+ metadata.gz: 745b66745f32af6cd49bf7b1d2be20776f4a5d171fb468718a07e914ce330515f88fc4549a2d1c2645b3ff086c52bb6d8ba0271676586ae81482a31a3856331b
7
+ data.tar.gz: 3ee46d3579ed98b99e42e82ebcbae2841a6ecb3bb4643bec2e81469373e34532551f1b141cb8e7949bf0ceea9af160b25a5e1f0c96d8f51513e462acf9d7c05d
@@ -1,4 +1,4 @@
1
- Copyright 2013 Jun Lin, Erik-B. Ernst
1
+ Copyright 2014 Jun Lin, Erik-B. Ernst
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -4,7 +4,7 @@
4
4
 
5
5
  Simple integration of jquery-tablesorter into the asset pipeline.
6
6
 
7
- Current tablesorter version: 2.14.5 (12/16/2013), [documentation]
7
+ Current tablesorter version: 2.15.0 (2/19/2014), [documentation]
8
8
 
9
9
  Any issue associate with the js/css files, please report to [Mottie's fork].
10
10
 
@@ -62,6 +62,7 @@ Avaliable theme names:
62
62
  * theme.black-ice
63
63
  * theme.blue
64
64
  * theme.bootstrap
65
+ * theme.bootstrap_2
65
66
  * theme.dark
66
67
  * theme.default
67
68
  * theme.dropbox
@@ -94,6 +95,10 @@ pager theme:
94
95
  4. Update `README.md` and `CHANGELOG.md`
95
96
 
96
97
 
98
+ ### Licensing
99
+
100
+ * Licensed under the [MIT](http://www.opensource.org/licenses/mit-license.php) license.
101
+ * Original jquery-tablesorter code is dual licensed under the [MIT](http://www.opensource.org/licenses/mit-license.php) and [GPL](http://www.gnu.org/licenses/gpl.html) licenses (see [Mottie's fork]).
102
+
97
103
  [Mottie's fork]: https://github.com/Mottie/tablesorter
98
104
  [documentation]: http://mottie.github.com/tablesorter/docs/index.html
99
-
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = "1.9.5"
2
+ VERSION = "1.10.0"
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * tablesorter pager plugin
3
- * updated 12/16/2013 (v2.14.5)
3
+ * updated 2/19/2014 (v2.15.0)
4
4
  */
5
5
  /*jshint browser:true, jquery:true, unused:false */
6
6
  ;(function($) {
@@ -63,6 +63,9 @@
63
63
 
64
64
  // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js)
65
65
  savePages: true,
66
+
67
+ // defines custom storage key
68
+ storageKey: 'tablesorter-pager',
66
69
 
67
70
  // 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
71
  // table row set to a height to compensate; default is false
@@ -112,10 +115,12 @@
112
115
  r = 'removeClass',
113
116
  d = p.cssDisabled,
114
117
  dis = !!disable,
115
- tp = Math.min( p.totalPages, p.filteredPages );
118
+ first = ( dis || p.page === 0 ),
119
+ tp = Math.min( p.totalPages, p.filteredPages ),
120
+ last = ( dis || (p.page === tp - 1) || p.totalPages === 0 );
116
121
  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);
122
+ p.$container.find(p.cssFirst + ',' + p.cssPrev)[ first ? a : r ](d).attr('aria-disabled', first);
123
+ p.$container.find(p.cssNext + ',' + p.cssLast)[ last ? a : r ](d).attr('aria-disabled', last);
119
124
  }
120
125
  },
121
126
 
@@ -167,7 +172,7 @@
167
172
  c.$table.trigger('pagerComplete', p);
168
173
  // save pager info to storage
169
174
  if (p.savePages && ts.storage) {
170
- ts.storage(table, 'tablesorter-pager', {
175
+ ts.storage(table, p.storageKey, {
171
176
  page : p.page,
172
177
  size : p.size
173
178
  });
@@ -235,32 +240,28 @@
235
240
  // process data
236
241
  if ( typeof(p.ajaxProcessing) === "function" ) {
237
242
  // ajaxProcessing result: [ total, rows, headers ]
238
- var i, j, hsh, $f, $sh, t, th, d, l, $err, rr_count,
243
+ var i, j, hsh, $f, $sh, t, th, d, l, rr_count,
239
244
  c = table.config,
240
245
  $t = c.$table,
241
246
  tds = '',
242
247
  result = p.ajaxProcessing(data, table) || [ 0, [] ],
243
248
  hl = $t.find('thead th').length;
244
249
 
245
- $t.find('thead tr.' + p.cssErrorRow).remove(); // Clean up any previous error.
250
+ // Clean up any previous error.
251
+ ts.showError(table);
246
252
 
247
253
  if ( exception ) {
248
254
  if (c.debug) {
249
255
  ts.log('Ajax Error', xhr, exception);
250
256
  }
251
- $err = $('<tr class="' + p.cssErrorRow + '"><td style="text-align:center;" colspan="' + hl + '">' + (
257
+ ts.showError(table,
252
258
  xhr.status === 0 ? 'Not connected, verify Network' :
253
259
  xhr.status === 404 ? 'Requested page not found [404]' :
254
260
  xhr.status === 500 ? 'Internal Server Error [500]' :
255
261
  exception === 'parsererror' ? 'Requested JSON parse failed' :
256
262
  exception === 'timeout' ? 'Time out error' :
257
263
  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
+ 'Uncaught error: ' + xhr.statusText + ' [' + xhr.status + ']' );
264
265
  c.$tbodies.eq(0).empty();
265
266
  } else {
266
267
  // process ajax object
@@ -293,7 +294,11 @@
293
294
  tds += '</tr>';
294
295
  }
295
296
  // add rows to first tbody
296
- p.processAjaxOnInit ? c.$tbodies.eq(0).html( tds ) : p.processAjaxOnInit = true;
297
+ if (p.processAjaxOnInit) {
298
+ c.$tbodies.eq(0).html( tds );
299
+ } else {
300
+ p.processAjaxOnInit = true;
301
+ }
297
302
  }
298
303
  // only add new header text if the length matches
299
304
  if ( th && th.length === hl ) {
@@ -333,7 +338,7 @@
333
338
  fixHeight(table, p);
334
339
  // apply widgets after table has rendered
335
340
  $t.trigger('applyWidgets');
336
- $t.trigger('update', [false, function(){
341
+ $t.trigger('updateRows', [false, function(){
337
342
  if (p.initialized) {
338
343
  $t.trigger('updateComplete');
339
344
  $t.trigger('pagerChange', p);
@@ -463,7 +468,10 @@
463
468
  p.page = 0;
464
469
  p.size = p.totalRows;
465
470
  p.totalPages = 1;
466
- $(table).addClass('pagerDisabled').find('tr.pagerSavedHeightSpacer').remove();
471
+ $(table)
472
+ .addClass('pagerDisabled')
473
+ .removeAttr('aria-describedby')
474
+ .find('tr.pagerSavedHeightSpacer').remove();
467
475
  renderTable(table, table.config.rowsCopy, p);
468
476
  if (table.config.debug) {
469
477
  ts.log('pager disabled');
@@ -471,7 +479,7 @@
471
479
  }
472
480
  // disable size selector
473
481
  p.$size.add(p.$goto).each(function(){
474
- $(this).addClass(p.cssDisabled)[0].disabled = true;
482
+ $(this).attr('aria-disabled', 'true').addClass(p.cssDisabled)[0].disabled = true;
475
483
  });
476
484
  },
477
485
 
@@ -551,24 +559,31 @@
551
559
  p.initialized = false;
552
560
  $(table).unbind('destroy.pager sortEnd.pager filterEnd.pager enable.pager disable.pager');
553
561
  if (ts.storage) {
554
- ts.storage(table, 'tablesorter-pager', '');
562
+ ts.storage(table, p.storageKey, '');
555
563
  }
556
564
  },
557
565
 
558
566
  enablePager = function(table, p, triggered){
559
- var pg = p.$size.removeClass(p.cssDisabled).removeAttr('disabled');
560
- p.$goto.removeClass(p.cssDisabled).removeAttr('disabled');
567
+ var info,
568
+ c = table.config;
569
+ p.$size.add(p.$goto).removeClass(p.cssDisabled).removeAttr('disabled').attr('aria-disabled', 'false');
561
570
  p.isDisabled = false;
562
571
  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 );
572
+ p.size = $.data(table, 'pagerLastSize') || parseInt(p.$size.find('option[selected]').val(), 10) || p.size || 10;
573
+ p.$size.val(p.size); // set page size
574
+ p.totalPages = Math.ceil( Math.min( p.totalRows, p.filteredRows ) / p.size );
575
+ // if table id exists, include page display with aria info
576
+ if ( table.id ) {
577
+ info = table.id + '_pager_info';
578
+ p.$container.find(p.cssPageDisplay).attr('id', info);
579
+ c.$table.attr('aria-describedby', info);
580
+ }
566
581
  if ( triggered ) {
567
- $(table).trigger('update');
582
+ c.$table.trigger('updateRows');
568
583
  setPageSize(table, p.size, p);
569
584
  hideRowsSetup(table, p);
570
585
  fixHeight(table, p);
571
- if (table.config.debug) {
586
+ if (c.debug) {
572
587
  ts.log('pager enabled');
573
588
  }
574
589
  }
@@ -583,6 +598,8 @@
583
598
  p.size = $.data(table, 'pagerLastSize') || p.size || 10;
584
599
  p.totalPages = Math.ceil( p.totalRows / p.size );
585
600
  renderTable(table, rows, p);
601
+ // update display here in case all rows are removed
602
+ updatePageDisplay(table, p, false);
586
603
  }
587
604
  };
588
605
 
@@ -609,7 +626,7 @@
609
626
  ts.setFilters(table, p.currentFilters, false);
610
627
  }
611
628
  if (p.savePages && ts.storage) {
612
- t = ts.storage(table, 'tablesorter-pager') || {}; // fixes #387
629
+ t = ts.storage(table, p.storageKey) || {}; // fixes #387
613
630
  p.page = isNaN(t.page) ? p.page : t.page;
614
631
  p.size = ( isNaN(t.size) ? p.size : t.size ) || 10;
615
632
  $.data(table, 'pagerLastSize', p.size);
@@ -664,6 +681,7 @@
664
681
  ctrls = [ p.cssFirst, p.cssPrev, p.cssNext, p.cssLast ];
665
682
  fxn = [ moveToFirstPage, moveToPrevPage, moveToNextPage, moveToLastPage ];
666
683
  pager.find(ctrls.join(','))
684
+ .attr("tabindex", 0)
667
685
  .unbind('click.pager')
668
686
  .bind('click.pager', function(e){
669
687
  e.stopPropagation();
@@ -734,8 +752,34 @@
734
752
  });
735
753
  };
736
754
 
737
- }()
738
- });
755
+ }() });
756
+
757
+ // see #486
758
+ ts.showError = function(table, message){
759
+ $(table).each(function(){
760
+ var $row,
761
+ c = this.config,
762
+ errorRow = c.pager && c.pager.cssErrorRow || c.widgetOptions.pager_css && c.widgetOptions.pager_css.errorRow || 'tablesorter-errorRow';
763
+ if (c) {
764
+ if (typeof message === 'undefined') {
765
+ c.$table.find('thead').find(c.selectorRemove).remove();
766
+ } else {
767
+ $row = ( /tr\>/.test(message) ? $(message) : $('<tr><td colspan="' + c.columns + '">' + message + '</td></tr>') )
768
+ .click(function(){
769
+ $(this).remove();
770
+ })
771
+ // add error row to thead instead of tbody, or clicking on the header will result in a parser error
772
+ .appendTo( c.$table.find('thead:first') )
773
+ .addClass( errorRow + ' ' + c.selectorRemove.replace(/^[.#]/, '') )
774
+ .attr({
775
+ role : 'alert',
776
+ 'aria-live' : 'assertive'
777
+ });
778
+ }
779
+ }
780
+ });
781
+ };
782
+
739
783
  // extend plugin scope
740
784
  $.fn.extend({
741
785
  tablesorterPager: $.tablesorterPager.construct
@@ -1,5 +1,5 @@
1
1
  /**!
2
- * TableSorter 2.14.5 - Client-side table sorting with ease!
2
+ * TableSorter 2.15.0 - Client-side table sorting with ease!
3
3
  * @requires jQuery v1.2.6+
4
4
  *
5
5
  * Copyright (c) 2007 Christian Bach
@@ -24,7 +24,7 @@
24
24
 
25
25
  var ts = this;
26
26
 
27
- ts.version = "2.14.5";
27
+ ts.version = "2.15.0";
28
28
 
29
29
  ts.parsers = [];
30
30
  ts.widgets = [];
@@ -82,6 +82,7 @@
82
82
  tableClass : '',
83
83
  cssAsc : '',
84
84
  cssDesc : '',
85
+ cssNone : '',
85
86
  cssHeader : '',
86
87
  cssHeaderRow : '',
87
88
  cssProcessing : '', // processing icon applied to header during sort/filter
@@ -116,18 +117,31 @@
116
117
  childRow : 'tablesorter-childRow',
117
118
  header : 'tablesorter-header',
118
119
  headerRow : 'tablesorter-headerRow',
120
+ headerIn : 'tablesorter-header-inner',
119
121
  icon : 'tablesorter-icon',
120
122
  info : 'tablesorter-infoOnly',
121
123
  processing : 'tablesorter-processing',
122
124
  sortAsc : 'tablesorter-headerAsc',
123
- sortDesc : 'tablesorter-headerDesc'
125
+ sortDesc : 'tablesorter-headerDesc',
126
+ sortNone : 'tablesorter-headerUnSorted'
127
+ };
128
+
129
+ // labels applied to sortable headers for accessibility (aria) support
130
+ ts.language = {
131
+ sortAsc : 'Ascending sort applied, ',
132
+ sortDesc : 'Descending sort applied, ',
133
+ sortNone : 'No sort applied, ',
134
+ nextAsc : 'activate to apply an ascending sort',
135
+ nextDesc : 'activate to apply a descending sort',
136
+ nextNone : 'activate to remove the sort'
124
137
  };
125
138
 
126
139
  /* debuging utils */
127
140
  function log() {
128
- var s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : arguments[0];
141
+ var a = arguments[0],
142
+ s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : a;
129
143
  if (typeof console !== "undefined" && typeof console.log !== "undefined") {
130
- console.log(s);
144
+ console[ /error/i.test(a) ? 'error' : /warn/i.test(a) ? 'warn' : 'log' ](s);
131
145
  } else {
132
146
  alert(s);
133
147
  }
@@ -206,7 +220,7 @@
206
220
  tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
207
221
  rows, list, l, i, h, ch, p, time, parsersDebug = "";
208
222
  if ( tb.length === 0) {
209
- return c.debug ? log('*Empty table!* Not building a parser cache') : '';
223
+ return c.debug ? log('Warning: *Empty table!* Not building a parser cache') : '';
210
224
  } else if (c.debug) {
211
225
  time = new Date();
212
226
  log('Detecting parsers for each column');
@@ -255,7 +269,7 @@
255
269
  tc.cache = {};
256
270
  // if no parsers found, return - it's an empty table.
257
271
  if (!parsers) {
258
- return tc.debug ? log('*Empty table!* Not building a cache') : '';
272
+ return tc.debug ? log('Warning: *Empty table!* Not building a cache') : '';
259
273
  }
260
274
  if (tc.debug) {
261
275
  cacheTime = new Date();
@@ -314,7 +328,11 @@
314
328
  c2 = c.cache,
315
329
  r, n, totalRows, checkCell, $bk, $tb,
316
330
  i, j, k, l, pos, appendTime;
317
- if (isEmptyObject(c2)) { return; } // empty table - fixes #206/#346
331
+ // empty table - fixes #206/#346
332
+ if (isEmptyObject(c2)) {
333
+ // run pager appender in case the table was just emptied
334
+ return c.appender ? c.appender(table, rows) : '';
335
+ }
318
336
  if (c.debug) {
319
337
  appendTime = new Date();
320
338
  }
@@ -427,7 +445,7 @@
427
445
  h = c.onRenderTemplate.apply($t, [index, t]);
428
446
  if (h && typeof h === 'string') { t = h; } // only change t if something is returned
429
447
  }
430
- $(this).html('<div class="tablesorter-header-inner">' + t + '</div>'); // faster than wrapInner
448
+ $(this).html('<div class="' + ts.css.headerIn + '">' + t + '</div>'); // faster than wrapInner
431
449
 
432
450
  if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); }
433
451
 
@@ -443,9 +461,12 @@
443
461
  // add cell to headerList
444
462
  c.headerList[index] = this;
445
463
  // add to parent in case there are multiple rows
446
- $t.parent().addClass(ts.css.headerRow + ' ' + c.cssHeaderRow);
464
+ $t.parent().addClass(ts.css.headerRow + ' ' + c.cssHeaderRow).attr('role', 'row');
447
465
  // allow keyboard cursor to focus on element
448
466
  if (c.tabIndex) { $t.attr("tabindex", 0); }
467
+ }).attr({
468
+ scope: 'col',
469
+ role : 'columnheader'
449
470
  });
450
471
  // enable/disable sorting
451
472
  updateHeader(table);
@@ -467,11 +488,20 @@
467
488
  }
468
489
 
469
490
  function updateHeader(table) {
470
- var s, c = table.config;
491
+ var s, $th, c = table.config;
471
492
  c.$headers.each(function(index, th){
493
+ $th = $(th);
472
494
  s = ts.getData( th, c.headers[index], 'sorter' ) === 'false';
473
495
  th.sortDisabled = s;
474
- $(th)[ s ? 'addClass' : 'removeClass' ]('sorter-false');
496
+ $th[ s ? 'addClass' : 'removeClass' ]('sorter-false').attr('aria-disabled', '' + s);
497
+ // aria-controls - requires table ID
498
+ if (table.id) {
499
+ if (s) {
500
+ $th.removeAttr('aria-controls');
501
+ } else {
502
+ $th.attr('aria-controls', table.id);
503
+ }
504
+ }
475
505
  });
476
506
  }
477
507
 
@@ -479,11 +509,15 @@
479
509
  var f, i, j, l,
480
510
  c = table.config,
481
511
  list = c.sortList,
512
+ none = ts.css.sortNone + ' ' + c.cssNone,
482
513
  css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc],
514
+ aria = ['ascending', 'descending'],
483
515
  // find the footer
484
516
  $t = $(table).find('tfoot tr').children().removeClass(css.join(' '));
485
517
  // remove all header information
486
- c.$headers.removeClass(css.join(' '));
518
+ c.$headers
519
+ .removeClass(css.join(' '))
520
+ .addClass(none).attr('aria-sort', 'none');
487
521
  l = list.length;
488
522
  for (i = 0; i < l; i++) {
489
523
  // direction = 2 means reset!
@@ -493,7 +527,7 @@
493
527
  if (f.length) {
494
528
  for (j = 0; j < f.length; j++) {
495
529
  if (!f[j].sortDisabled) {
496
- f.eq(j).addClass(css[list[i][1]]);
530
+ f.eq(j).removeClass(none).addClass(css[list[i][1]]).attr('aria-sort', aria[list[i][1]]);
497
531
  // add sorted class to footer, if it exists
498
532
  if ($t.length) {
499
533
  $t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]);
@@ -503,6 +537,15 @@
503
537
  }
504
538
  }
505
539
  }
540
+ // add verbose aria labels
541
+ c.$headers.not('.sorter-false').each(function(){
542
+ var $this = $(this),
543
+ nextSort = this.order[(this.count + 1) % (c.sortReset ? 3 : 2)],
544
+ txt = $this.text() + ': ' +
545
+ ts.language[ $this.hasClass(ts.css.sortAsc) ? 'sortAsc' : $this.hasClass(ts.css.sortDesc) ? 'sortDesc' : 'sortNone' ] +
546
+ ts.language[ nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone' ];
547
+ $this.attr('aria-label', txt );
548
+ });
506
549
  }
507
550
 
508
551
  // automatically add col group, and column sizes if set
@@ -543,9 +586,9 @@
543
586
  var a, i, j, o, s,
544
587
  c = table.config,
545
588
  k = !e[c.sortMultiSortKey],
546
- $this = $(table);
589
+ $table = $(table);
547
590
  // Only call sortStart if sorting is enabled
548
- $this.trigger("sortStart", table);
591
+ $table.trigger("sortStart", table);
549
592
  // get current column sort order
550
593
  cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
551
594
  // reset all sorts on non-current column - issue #30
@@ -629,7 +672,7 @@
629
672
  }
630
673
  }
631
674
  // sortBegin event triggered immediately before the sort
632
- $this.trigger("sortBegin", table);
675
+ $table.trigger("sortBegin", table);
633
676
  // setTimeout needed so the processing icon shows up
634
677
  setTimeout(function(){
635
678
  // set css for headers
@@ -729,46 +772,11 @@
729
772
  }
730
773
  }
731
774
 
732
- function bindEvents(table){
775
+ function bindMethods(table){
733
776
  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
- }
777
+ $table = c.$table;
770
778
  // apply easy methods that trigger bound events
771
- $this
779
+ $table
772
780
  .unbind('sortReset update updateRows updateCell updateAll addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join('.tablesorter '))
773
781
  .bind("sortReset.tablesorter", function(e){
774
782
  e.stopPropagation();
@@ -782,7 +790,8 @@
782
790
  ts.refreshWidgets(table, true, true);
783
791
  ts.restoreHeaders(table);
784
792
  buildHeaders(table);
785
- bindEvents(table);
793
+ ts.bindEvents(table, c.$headers);
794
+ bindMethods(table);
786
795
  commonUpdate(table, resort, callback);
787
796
  })
788
797
  .bind("update.tablesorter updateRows.tablesorter", function(e, resort, callback) {
@@ -793,10 +802,10 @@
793
802
  })
794
803
  .bind("updateCell.tablesorter", function(e, cell, resort, callback) {
795
804
  e.stopPropagation();
796
- $this.find(c.selectorRemove).remove();
805
+ $table.find(c.selectorRemove).remove();
797
806
  // get position from the dom
798
807
  var l, row, icell,
799
- $tb = $this.find('tbody'),
808
+ $tb = $table.find('tbody'),
800
809
  // update cache - format: function(s, table, cell, cellIndex)
801
810
  // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
802
811
  tbdy = $tb.index( $(cell).parents('tbody').filter(':first') ),
@@ -809,7 +818,7 @@
809
818
  l = c.cache[tbdy].normalized[row].length - 1;
810
819
  c.cache[tbdy].row[table.config.cache[tbdy].normalized[row][l]] = $row;
811
820
  c.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(table, cell, icell), table, cell, icell );
812
- checkResort($this, resort, callback);
821
+ checkResort($table, resort, callback);
813
822
  }
814
823
  })
815
824
  .bind("addRows.tablesorter", function(e, $row, resort, callback) {
@@ -819,9 +828,10 @@
819
828
  updateHeader(table);
820
829
  commonUpdate(table, resort, callback);
821
830
  } else {
822
- var i, rows = $row.filter('tr').length,
831
+ var i, j,
832
+ rows = $row.filter('tr').length,
823
833
  dat = [], l = $row[0].cells.length,
824
- tbdy = $this.find('tbody').index( $row.parents('tbody').filter(':first') );
834
+ tbdy = $table.find('tbody').index( $row.parents('tbody').filter(':first') );
825
835
  // fixes adding rows to an empty table - see issue #179
826
836
  if (!c.parsers) {
827
837
  buildParserCache(table);
@@ -840,20 +850,20 @@
840
850
  dat = [];
841
851
  }
842
852
  // resort using current settings
843
- checkResort($this, resort, callback);
853
+ checkResort($table, resort, callback);
844
854
  }
845
855
  })
846
856
  .bind("sorton.tablesorter", function(e, list, callback, init) {
847
857
  var c = table.config;
848
858
  e.stopPropagation();
849
- $this.trigger("sortStart", this);
859
+ $table.trigger("sortStart", this);
850
860
  // update header count index
851
861
  updateHeaderSortCount(table, list);
852
862
  // set css for headers
853
863
  setHeadersCss(table);
854
864
  // fixes #346
855
865
  if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
856
- $this.trigger("sortBegin", this);
866
+ $table.trigger("sortBegin", this);
857
867
  // sort the table and append it to the dom
858
868
  multisort(table);
859
869
  appendToTable(table, init);
@@ -905,11 +915,11 @@
905
915
  ts.setup = function(table, c) {
906
916
  // if no thead or tbody, or tablesorter is already present, quit
907
917
  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') : '';
918
+ return c.debug ? log('ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized') : '';
909
919
  }
910
920
 
911
921
  var k = '',
912
- $this = $(table),
922
+ $table = $(table),
913
923
  m = $.metadata;
914
924
  // initialization flag
915
925
  table.hasInitialized = false;
@@ -931,11 +941,20 @@
931
941
  // digit sort text location; keeping max+/- for backwards compatibility
932
942
  c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false };
933
943
  // add table theme class only if there isn't already one there
934
- if (!/tablesorter\-/.test($this.attr('class'))) {
944
+ if (!/tablesorter\-/.test($table.attr('class'))) {
935
945
  k = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
936
946
  }
937
- c.$table = $this.addClass(ts.css.table + ' ' + c.tableClass + k);
938
- c.$tbodies = $this.children('tbody:not(.' + c.cssInfoBlock + ')');
947
+ c.$table = $table
948
+ .addClass(ts.css.table + ' ' + c.tableClass + k)
949
+ .attr({ role : 'grid'});
950
+ c.$tbodies = $table.children('tbody:not(.' + c.cssInfoBlock + ')').attr({
951
+ 'aria-live' : 'polite',
952
+ 'aria-relevant' : 'all'
953
+ });
954
+ //
955
+ if (c.$table.find('caption').length) {
956
+ c.$table.attr('aria-labelledby', 'theCaption');
957
+ }
939
958
  c.widgetInit = {}; // keep a list of initialized widgets
940
959
  // build headers
941
960
  buildHeaders(table);
@@ -948,27 +967,31 @@
948
967
  // delayInit will delay building the cache until the user starts a sort
949
968
  if (!c.delayInit) { buildCache(table); }
950
969
  // bind all header events and methods
951
- bindEvents(table);
970
+ ts.bindEvents(table, c.$headers);
971
+ bindMethods(table);
952
972
  // 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;
973
+ // in jQuery < 1.4, an error occurs when calling $table.data()
974
+ if (c.supportsDataObject && typeof $table.data().sortlist !== 'undefined') {
975
+ c.sortList = $table.data().sortlist;
976
+ } else if (m && ($table.metadata() && $table.metadata().sortlist)) {
977
+ c.sortList = $table.metadata().sortlist;
958
978
  }
959
979
  // apply widget init code
960
980
  ts.applyWidget(table, true);
961
981
  // if user has supplied a sort list to constructor
962
982
  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);
983
+ $table.trigger("sorton", [c.sortList, {}, !c.initWidgets]);
984
+ } else {
985
+ setHeadersCss(table);
986
+ if (c.initWidgets) {
987
+ // apply widget format
988
+ ts.applyWidget(table);
989
+ }
967
990
  }
968
991
 
969
992
  // show processesing icon
970
993
  if (c.showProcessing) {
971
- $this
994
+ $table
972
995
  .unbind('sortBegin.tablesorter sortEnd.tablesorter')
973
996
  .bind('sortBegin.tablesorter sortEnd.tablesorter', function(e) {
974
997
  ts.isProcessing(table, e.type === 'sortBegin');
@@ -981,7 +1004,7 @@
981
1004
  if (c.debug) {
982
1005
  ts.benchmark("Overall initialization time", $.data( table, 'startoveralltimer'));
983
1006
  }
984
- $this.trigger('tablesorter-initialized', table);
1007
+ $table.trigger('tablesorter-initialized', table);
985
1008
  if (typeof c.initialized === 'function') { c.initialized(table); }
986
1009
  };
987
1010
 
@@ -993,7 +1016,8 @@
993
1016
  // default to all headers
994
1017
  $h = $ths || table.find('.' + ts.css.header);
995
1018
  if (toggle) {
996
- if (c.sortList.length > 0) {
1019
+ // don't use sortList if custom $ths used
1020
+ if (typeof $ths !== 'undefined' && c.sortList.length > 0) {
997
1021
  // get headers from the sortList
998
1022
  $h = $h.filter(function(){
999
1023
  // get data-column from attr to keep compatibility with jQuery 1.2.6
@@ -1009,6 +1033,7 @@
1009
1033
  // detach tbody but save the position
1010
1034
  // don't use tbody because there are portions that look for a tbody index (updateCell)
1011
1035
  ts.processTbody = function(table, $tb, getIt){
1036
+ table = $(table)[0];
1012
1037
  var holdr;
1013
1038
  if (getIt) {
1014
1039
  table.isProcessing = true;
@@ -1026,14 +1051,55 @@
1026
1051
  $(table)[0].config.$tbodies.empty();
1027
1052
  };
1028
1053
 
1054
+ ts.bindEvents = function(table, $headers){
1055
+ table = $(table)[0];
1056
+ var downTime,
1057
+ c = table.config;
1058
+ // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
1059
+ $headers
1060
+ // http://stackoverflow.com/questions/5312849/jquery-find-self;
1061
+ .find(c.selectorSort).add( $headers.filter(c.selectorSort) )
1062
+ .unbind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keyup.tablesorter')
1063
+ .bind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keyup.tablesorter', function(e, external) {
1064
+ var cell, type = e.type;
1065
+ // only recognize left clicks or enter
1066
+ if ( ((e.which || e.button) !== 1 && !/sort|keyup/.test(type)) || (type === 'keyup' && e.which !== 13) ) {
1067
+ return;
1068
+ }
1069
+ // ignore long clicks (prevents resizable widget from initializing a sort)
1070
+ if (type === 'mouseup' && external !== true && (new Date().getTime() - downTime > 250)) { return; }
1071
+ // set timer on mousedown
1072
+ if (type === 'mousedown') {
1073
+ downTime = new Date().getTime();
1074
+ return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
1075
+ }
1076
+ if (c.delayInit && isEmptyObject(c.cache)) { buildCache(table); }
1077
+ // jQuery v1.2.6 doesn't have closest()
1078
+ cell = /TH|TD/.test(this.tagName) ? this : $(this).parents('th, td')[0];
1079
+ if (!cell.sortDisabled) {
1080
+ initSort(table, cell, e);
1081
+ }
1082
+ });
1083
+ if (c.cancelSelection) {
1084
+ // cancel selection
1085
+ $headers
1086
+ .attr('unselectable', 'on')
1087
+ .bind('selectstart', false)
1088
+ .css({
1089
+ 'user-select': 'none',
1090
+ 'MozUserSelect': 'none' // not needed for jQuery 1.8+
1091
+ });
1092
+ }
1093
+ };
1094
+
1029
1095
  // restore headers
1030
1096
  ts.restoreHeaders = function(table){
1031
- var c = table.config;
1097
+ var c = $(table)[0].config;
1032
1098
  // don't use c.$headers here in case header cells were swapped
1033
1099
  c.$table.find(c.selectorHeaders).each(function(i){
1034
1100
  // only restore header cells if it is wrapped
1035
1101
  // because this is also used by the updateAll method
1036
- if ($(this).find('.tablesorter-header-inner').length){
1102
+ if ($(this).find('.' + ts.css.headerIn).length){
1037
1103
  $(this).html( c.headerContent[i] );
1038
1104
  }
1039
1105
  });
@@ -1055,7 +1121,7 @@
1055
1121
  .removeData('tablesorter')
1056
1122
  .unbind('sortReset update updateAll updateRows updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd '.split(' ').join('.tablesorter '));
1057
1123
  c.$headers.add($f)
1058
- .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc].join(' ') )
1124
+ .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') )
1059
1125
  .removeAttr('data-column');
1060
1126
  $r.find(c.selectorSort).unbind('mousedown.tablesorter mouseup.tablesorter keypress.tablesorter');
1061
1127
  ts.restoreHeaders(table);
@@ -1088,7 +1154,6 @@
1088
1154
  if ( xD < yD ) { return -1; }
1089
1155
  if ( xD > yD ) { return 1; }
1090
1156
  }
1091
-
1092
1157
  // chunk/tokenize
1093
1158
  xN = a.replace(r.chunk, '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
1094
1159
  yN = b.replace(r.chunk, '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
@@ -1316,7 +1381,7 @@
1316
1381
  // remove previous widgets
1317
1382
  for (i = 0; i < l; i++){
1318
1383
  if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) {
1319
- if (c.debug) { log( 'Refeshing widgets: Removing ' + w[i].id ); }
1384
+ if (c.debug) { log( 'Refeshing widgets: Removing "' + w[i].id + '"' ); }
1320
1385
  // only remove widgets that have been initialized - fixes #442
1321
1386
  if (w[i].hasOwnProperty('remove') && c.widgetInit[w[i].id]) {
1322
1387
  w[i].remove(table, c, c.widgetOptions);
@@ -1423,7 +1488,7 @@
1423
1488
  ts.addParser({
1424
1489
  id: "currency",
1425
1490
  is: function(s) {
1426
- return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[,. ]/g,'')); // £$€¤¥¢
1491
+ return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g,'')); // £$€¤¥¢
1427
1492
  },
1428
1493
  format: function(s, table) {
1429
1494
  var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
@@ -1503,8 +1568,9 @@
1503
1568
  },
1504
1569
  format: function(s, table, cell, cellIndex) {
1505
1570
  if (s) {
1506
- var c = table.config, ci = c.headerList[cellIndex],
1507
- format = ci.dateFormat || ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
1571
+ var c = table.config,
1572
+ ci = c.$headers.filter('[data-column=' + cellIndex + ']:last'),
1573
+ format = ci.length && ci[0].dateFormat || ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
1508
1574
  s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
1509
1575
  if (format === "mmddyyyy") {
1510
1576
  s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");