jquery-tablesorter 1.9.5 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
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");