jquery-tablesorter 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- /*
1
+ /*
2
2
  * Metadata - jQuery plugin for parsing metadata from elements
3
3
  *
4
4
  * Copyright (c) 2006 John Resig, Yehuda Katz, Jörn Zaefferer, Paul McLanahan
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * TableSorter 2.7.5 - Client-side table sorting with ease!
2
+ * TableSorter 2.10.8 - 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.7.5";
27
+ ts.version = "2.10.8";
28
28
 
29
29
  ts.parsers = [];
30
30
  ts.widgets = [];
@@ -118,6 +118,7 @@
118
118
  log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)");
119
119
  }
120
120
 
121
+ ts.log = log;
121
122
  ts.benchmark = benchmark;
122
123
 
123
124
  function getElementText(table, node, cellIndex) {
@@ -131,9 +132,9 @@
131
132
  text = $(node).text();
132
133
  }
133
134
  } else {
134
- if (typeof(t) === "function") {
135
+ if (typeof t === "function") {
135
136
  text = t(node, table, cellIndex);
136
- } else if (typeof(t) === "object" && t.hasOwnProperty(cellIndex)) {
137
+ } else if (typeof t === "object" && t.hasOwnProperty(cellIndex)) {
137
138
  text = t[cellIndex](node, table, cellIndex);
138
139
  } else {
139
140
  text = c.supportsTextContent ? node.textContent : $(node).text();
@@ -143,7 +144,8 @@
143
144
  }
144
145
 
145
146
  function detectParserForColumn(table, rows, rowIndex, cellIndex) {
146
- var i, l = ts.parsers.length,
147
+ var cur,
148
+ i = ts.parsers.length,
147
149
  node = false,
148
150
  nodeValue = '',
149
151
  keepLooking = true;
@@ -153,19 +155,21 @@
153
155
  node = rows[rowIndex].cells[cellIndex];
154
156
  nodeValue = getElementText(table, node, cellIndex);
155
157
  if (table.config.debug) {
156
- log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': ' + nodeValue);
158
+ log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': "' + nodeValue + '"');
157
159
  }
158
160
  } else {
159
161
  keepLooking = false;
160
162
  }
161
163
  }
162
- for (i = 1; i < l; i++) {
163
- if (ts.parsers[i].is && ts.parsers[i].is(nodeValue, table, node)) {
164
- return ts.parsers[i];
164
+ while (--i >= 0) {
165
+ cur = ts.parsers[i];
166
+ // ignore the default text parser because it will always be true
167
+ if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, table, node)) {
168
+ return cur;
165
169
  }
166
170
  }
167
- // 0 is always the generic parser (text)
168
- return ts.parsers[0];
171
+ // nothing found, return the generic parser (text)
172
+ return ts.getParserById('text');
169
173
  }
170
174
 
171
175
  function buildParserCache(table) {
@@ -205,7 +209,7 @@
205
209
  if (c.debug) {
206
210
  log(parsersDebug);
207
211
  }
208
- return list;
212
+ c.parsers = list;
209
213
  }
210
214
 
211
215
  /* utils */
@@ -252,7 +256,7 @@
252
256
  v = parsers[j].format(t, table, c[0].cells[j], j);
253
257
  cols.push(v);
254
258
  if ((parsers[j].type || '').toLowerCase() === "numeric") {
255
- colMax[j] = Math.max(Math.abs(v), colMax[j] || 0); // determine column max value (ignore sign)
259
+ colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0); // determine column max value (ignore sign)
256
260
  }
257
261
  }
258
262
  cols.push(tc.cache[k].normalized.length); // add position for rowCache
@@ -283,7 +287,7 @@
283
287
  }
284
288
  for (k = 0; k < b.length; k++) {
285
289
  $bk = $(b[k]);
286
- if (!$bk.hasClass(c.cssInfoBlock)) {
290
+ if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
287
291
  // get tbody
288
292
  $tb = ts.processTbody(table, $bk, true);
289
293
  r = c2[k].row;
@@ -370,13 +374,14 @@
370
374
 
371
375
  function buildHeaders(table) {
372
376
  var header_index = computeThIndexes(table), ch, $t,
373
- h, i, t, lock, time, $tableHeaders, c = table.config;
374
- c.headerList = [], c.headerContent = [];
377
+ h, i, t, lock, time, c = table.config;
378
+ c.headerList = [];
379
+ c.headerContent = [];
375
380
  if (c.debug) {
376
381
  time = new Date();
377
382
  }
378
383
  i = c.cssIcon ? '<i class="' + c.cssIcon + '"></i>' : ''; // add icon if cssIcon option exists
379
- $tableHeaders = $(table).find(c.selectorHeaders).each(function(index) {
384
+ c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
380
385
  $t = $(this);
381
386
  ch = c.headers[index];
382
387
  c.headerContent[index] = this.innerHTML; // save original header content
@@ -393,28 +398,45 @@
393
398
  this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex];
394
399
  this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2];
395
400
  this.count = -1; // set to -1 because clicking on the header automatically adds one
396
- if (ts.getData($t, ch, 'sorter') === 'false') {
397
- this.sortDisabled = true;
398
- $t.addClass('sorter-false');
399
- } else {
400
- $t.removeClass('sorter-false');
401
- }
402
401
  this.lockedOrder = false;
403
402
  lock = ts.getData($t, ch, 'lockedOrder') || false;
404
- if (typeof(lock) !== 'undefined' && lock !== false) {
403
+ if (typeof lock !== 'undefined' && lock !== false) {
405
404
  this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0];
406
405
  }
407
- $t.addClass( (this.sortDisabled ? 'sorter-false ' : ' ') + c.cssHeader );
406
+ $t.addClass(c.cssHeader);
408
407
  // add cell to headerList
409
408
  c.headerList[index] = this;
410
409
  // add to parent in case there are multiple rows
411
410
  $t.parent().addClass(c.cssHeaderRow);
411
+ // allow keyboard cursor to focus on element
412
+ $t.attr("tabindex", 0);
412
413
  });
413
- if (table.config.debug) {
414
+ // enable/disable sorting
415
+ updateHeader(table);
416
+ if (c.debug) {
414
417
  benchmark("Built headers:", time);
415
- log($tableHeaders);
418
+ log(c.$headers);
416
419
  }
417
- return $tableHeaders;
420
+ }
421
+
422
+ function commonUpdate(table, resort, callback) {
423
+ var c = table.config;
424
+ // remove rows/elements before update
425
+ c.$table.find(c.selectorRemove).remove();
426
+ // rebuild parsers
427
+ buildParserCache(table);
428
+ // rebuild the cache map
429
+ buildCache(table);
430
+ checkResort(c.$table, resort, callback);
431
+ }
432
+
433
+ function updateHeader(table) {
434
+ var s, c = table.config;
435
+ c.$headers.each(function(index, th){
436
+ s = ts.getData( th, c.headers[index], 'sorter' ) === 'false';
437
+ th.sortDisabled = s;
438
+ $(th)[ s ? 'addClass' : 'removeClass' ]('sorter-false');
439
+ });
418
440
  }
419
441
 
420
442
  function setHeadersCss(table) {
@@ -449,23 +471,13 @@
449
471
 
450
472
  // automatically add col group, and column sizes if set
451
473
  function fixColumnWidth(table) {
452
- var $c, c = table.config,
453
- $cg = $('<colgroup>'),
454
- $cgo = c.$table.find('colgroup'),
455
- n = c.columns.length,
456
- overallWidth = c.$table.width();
457
- $("tr:first td", table.tBodies[0]).each(function(i) {
458
- $c = $('<col>');
459
- if (c.widthFixed) {
460
- $c.css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%');
461
- }
462
- $cg.append($c);
463
- });
464
- // replace colgroup contents
465
- if ($cgo.length) {
466
- $cgo.html( $cg.html() );
467
- } else {
468
- c.$table.prepend( $cg );
474
+ if (table.config.widthFixed && $(table).find('colgroup').length === 0) {
475
+ var colgroup = $('<colgroup>'),
476
+ overallWidth = $(table).width();
477
+ $(table.tBodies[0]).find("tr:first").children("td").each(function() {
478
+ colgroup.append($('<col>').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%'));
479
+ });
480
+ $(table).prepend(colgroup);
469
481
  }
470
482
  }
471
483
 
@@ -490,11 +502,110 @@
490
502
  return (parsers && parsers[i]) ? parsers[i].type || '' : '';
491
503
  }
492
504
 
505
+ function initSort(table, cell, e){
506
+ var a, i, j, o, s,
507
+ c = table.config,
508
+ k = !e[c.sortMultiSortKey],
509
+ $this = $(table);
510
+ // Only call sortStart if sorting is enabled
511
+ $this.trigger("sortStart", table);
512
+ // get current column sort order
513
+ cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
514
+ // reset all sorts on non-current column - issue #30
515
+ if (c.sortRestart) {
516
+ i = cell;
517
+ c.$headers.each(function() {
518
+ // only reset counts on columns that weren't just clicked on and if not included in a multisort
519
+ if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) {
520
+ this.count = -1;
521
+ }
522
+ });
523
+ }
524
+ // get current column index
525
+ i = cell.column;
526
+ // user only wants to sort on one column
527
+ if (k) {
528
+ // flush the sort list
529
+ c.sortList = [];
530
+ if (c.sortForce !== null) {
531
+ a = c.sortForce;
532
+ for (j = 0; j < a.length; j++) {
533
+ if (a[j][0] !== i) {
534
+ c.sortList.push(a[j]);
535
+ }
536
+ }
537
+ }
538
+ // add column to sort list
539
+ o = cell.order[cell.count];
540
+ if (o < 2) {
541
+ c.sortList.push([i, o]);
542
+ // add other columns if header spans across multiple
543
+ if (cell.colSpan > 1) {
544
+ for (j = 1; j < cell.colSpan; j++) {
545
+ c.sortList.push([i + j, o]);
546
+ }
547
+ }
548
+ }
549
+ // multi column sorting
550
+ } else {
551
+ // get rid of the sortAppend before adding more - fixes issue #115
552
+ if (c.sortAppend && c.sortList.length > 1) {
553
+ if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) {
554
+ c.sortList.pop();
555
+ }
556
+ }
557
+ // the user has clicked on an already sorted column
558
+ if (ts.isValueInArray(i, c.sortList)) {
559
+ // reverse the sorting direction for all tables
560
+ for (j = 0; j < c.sortList.length; j++) {
561
+ s = c.sortList[j];
562
+ o = c.headerList[s[0]];
563
+ if (s[0] === i) {
564
+ s[1] = o.order[o.count];
565
+ if (s[1] === 2) {
566
+ c.sortList.splice(j,1);
567
+ o.count = -1;
568
+ }
569
+ }
570
+ }
571
+ } else {
572
+ // add column to sort list array
573
+ o = cell.order[cell.count];
574
+ if (o < 2) {
575
+ c.sortList.push([i, o]);
576
+ // add other columns if header spans across multiple
577
+ if (cell.colSpan > 1) {
578
+ for (j = 1; j < cell.colSpan; j++) {
579
+ c.sortList.push([i + j, o]);
580
+ }
581
+ }
582
+ }
583
+ }
584
+ }
585
+ if (c.sortAppend !== null) {
586
+ a = c.sortAppend;
587
+ for (j = 0; j < a.length; j++) {
588
+ if (a[j][0] !== i) {
589
+ c.sortList.push(a[j]);
590
+ }
591
+ }
592
+ }
593
+ // sortBegin event triggered immediately before the sort
594
+ $this.trigger("sortBegin", table);
595
+ // setTimeout needed so the processing icon shows up
596
+ setTimeout(function(){
597
+ // set css for headers
598
+ setHeadersCss(table);
599
+ multisort(table);
600
+ appendToTable(table);
601
+ }, 1);
602
+ }
603
+
493
604
  // sort multiple columns
494
605
  function multisort(table) { /*jshint loopfunc:true */
495
- var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config,
606
+ var dir = 0, tc = table.config,
496
607
  sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length,
497
- sortTime, i, j, k, c, colMax, cache, lc, s, e, order, orgOrderCol;
608
+ sortTime, i, k, c, colMax, cache, lc, s, order, orgOrderCol;
498
609
  if (tc.serverSideSorting || !tc.cache[0]) { // empty table - fixes #206
499
610
  return;
500
611
  }
@@ -537,7 +648,9 @@
537
648
  }
538
649
 
539
650
  function checkResort($table, flag, callback) {
540
- if (flag !== false) {
651
+ // don't try to resort if the table is still processing
652
+ // this will catch spamming of the updateCell method
653
+ if (flag !== false && !$table[0].isProcessing) {
541
654
  $table.trigger("sorton", [$table[0].config.sortList, function(){
542
655
  resortComplete($table, callback);
543
656
  }]);
@@ -546,26 +659,177 @@
546
659
  }
547
660
  }
548
661
 
662
+ function bindEvents(table){
663
+ var c = table.config,
664
+ $this = c.$table,
665
+ j, downTime;
666
+ // apply event handling to headers
667
+ c.$headers
668
+ // http://stackoverflow.com/questions/5312849/jquery-find-self;
669
+ .find(c.selectorSort).add( c.$headers.filter(c.selectorSort) )
670
+ .unbind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter')
671
+ .bind('mousedown.tablesorter mouseup.tablesorter sort.tablesorter keypress.tablesorter', function(e, external) {
672
+ // only recognize left clicks or enter
673
+ if ( ((e.which || e.button) !== 1 && !/sort|keypress/.test(e.type)) || (e.type === 'keypress' && e.which !== 13) ) {
674
+ return false;
675
+ }
676
+ // ignore long clicks (prevents resizable widget from initializing a sort)
677
+ if (e.type === 'mouseup' && external !== true && (new Date().getTime() - downTime > 250)) { return false; }
678
+ // set timer on mousedown
679
+ if (e.type === 'mousedown') {
680
+ downTime = new Date().getTime();
681
+ return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
682
+ }
683
+ if (c.delayInit && !c.cache) { buildCache(table); }
684
+ // jQuery v1.2.6 doesn't have closest()
685
+ var $cell = /TH|TD/.test(this.tagName) ? $(this) : $(this).parents('th, td').filter(':first'), cell = $cell[0];
686
+ if (!cell.sortDisabled) {
687
+ initSort(table, cell, e);
688
+ }
689
+ });
690
+ if (c.cancelSelection) {
691
+ // cancel selection
692
+ c.$headers
693
+ .attr('unselectable', 'on')
694
+ .bind('selectstart', false)
695
+ .css({
696
+ 'user-select': 'none',
697
+ 'MozUserSelect': 'none' // not needed for jQuery 1.8+
698
+ });
699
+ }
700
+ // apply easy methods that trigger bound events
701
+ $this
702
+ .unbind('sortReset update updateRows updateCell updateAll addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join('.tablesorter '))
703
+ .bind("sortReset.tablesorter", function(e){
704
+ e.stopPropagation();
705
+ c.sortList = [];
706
+ setHeadersCss(table);
707
+ multisort(table);
708
+ appendToTable(table);
709
+ })
710
+ .bind("updateAll.tablesorter", function(e, resort, callback){
711
+ e.stopPropagation();
712
+ ts.refreshWidgets(table, true, true);
713
+ ts.restoreHeaders(table);
714
+ buildHeaders(table);
715
+ bindEvents(table);
716
+ commonUpdate(table, resort, callback);
717
+ })
718
+ .bind("update.tablesorter updateRows.tablesorter", function(e, resort, callback) {
719
+ e.stopPropagation();
720
+ // update sorting (if enabled/disabled)
721
+ updateHeader(table);
722
+ commonUpdate(table, resort, callback);
723
+ })
724
+ .bind("updateCell.tablesorter", function(e, cell, resort, callback) {
725
+ e.stopPropagation();
726
+ $this.find(c.selectorRemove).remove();
727
+ // get position from the dom
728
+ var l, row, icell,
729
+ $tb = $this.find('tbody'),
730
+ // update cache - format: function(s, table, cell, cellIndex)
731
+ // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
732
+ tbdy = $tb.index( $(cell).parents('tbody').filter(':first') ),
733
+ $row = $(cell).parents('tr').filter(':first');
734
+ cell = $(cell)[0]; // in case cell is a jQuery object
735
+ // tbody may not exist if update is initialized while tbody is removed for processing
736
+ if ($tb.length && tbdy >= 0) {
737
+ row = $tb.eq(tbdy).find('tr').index( $row );
738
+ icell = cell.cellIndex;
739
+ l = c.cache[tbdy].normalized[row].length - 1;
740
+ c.cache[tbdy].row[table.config.cache[tbdy].normalized[row][l]] = $row;
741
+ c.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(table, cell, icell), table, cell, icell );
742
+ checkResort($this, resort, callback);
743
+ }
744
+ })
745
+ .bind("addRows.tablesorter", function(e, $row, resort, callback) {
746
+ e.stopPropagation();
747
+ var i, rows = $row.filter('tr').length,
748
+ dat = [], l = $row[0].cells.length,
749
+ tbdy = $this.find('tbody').index( $row.parents('tbody').filter(':first') );
750
+ // fixes adding rows to an empty table - see issue #179
751
+ if (!c.parsers) {
752
+ buildParserCache(table);
753
+ }
754
+ // add each row
755
+ for (i = 0; i < rows; i++) {
756
+ // add each cell
757
+ for (j = 0; j < l; j++) {
758
+ dat[j] = c.parsers[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
759
+ }
760
+ // add the row index to the end
761
+ dat.push(c.cache[tbdy].row.length);
762
+ // update cache
763
+ c.cache[tbdy].row.push([$row[i]]);
764
+ c.cache[tbdy].normalized.push(dat);
765
+ dat = [];
766
+ }
767
+ // resort using current settings
768
+ checkResort($this, resort, callback);
769
+ })
770
+ .bind("sorton.tablesorter", function(e, list, callback, init) {
771
+ e.stopPropagation();
772
+ $this.trigger("sortStart", this);
773
+ // update header count index
774
+ updateHeaderSortCount(table, list);
775
+ // set css for headers
776
+ setHeadersCss(table);
777
+ $this.trigger("sortBegin", this);
778
+ // sort the table and append it to the dom
779
+ multisort(table);
780
+ appendToTable(table, init);
781
+ if (typeof callback === "function") {
782
+ callback(table);
783
+ }
784
+ })
785
+ .bind("appendCache.tablesorter", function(e, callback, init) {
786
+ e.stopPropagation();
787
+ appendToTable(table, init);
788
+ if (typeof callback === "function") {
789
+ callback(table);
790
+ }
791
+ })
792
+ .bind("applyWidgetId.tablesorter", function(e, id) {
793
+ e.stopPropagation();
794
+ ts.getWidgetById(id).format(table, c, c.widgetOptions);
795
+ })
796
+ .bind("applyWidgets.tablesorter", function(e, init) {
797
+ e.stopPropagation();
798
+ // apply widgets
799
+ ts.applyWidget(table, init);
800
+ })
801
+ .bind("refreshWidgets.tablesorter", function(e, all, dontapply){
802
+ e.stopPropagation();
803
+ ts.refreshWidgets(table, all, dontapply);
804
+ })
805
+ .bind("destroy.tablesorter", function(e, c, cb){
806
+ e.stopPropagation();
807
+ ts.destroy(table, c, cb);
808
+ });
809
+ }
810
+
549
811
  /* public methods */
550
812
  ts.construct = function(settings) {
551
813
  return this.each(function() {
552
814
  // if no thead or tbody, or tablesorter is already present, quit
553
815
  if (!this.tHead || this.tBodies.length === 0 || this.hasInitialized === true) {
554
- return (this.config.debug) ? log('stopping initialization! No thead, tbody or tablesorter has already been initialized') : '';
816
+ return (this.config && this.config.debug) ? log('stopping initialization! No thead, tbody or tablesorter has already been initialized') : '';
555
817
  }
556
818
  // declare
557
- var $cell, $this = $(this), $t0 = this,
558
- c, i, j, k = '', a, s, o, downTime,
819
+ var $this = $(this), table = this,
820
+ c, k = '',
559
821
  m = $.metadata;
560
822
  // initialization flag
561
- $t0.hasInitialized = false;
823
+ table.hasInitialized = false;
824
+ // table is being processed flag
825
+ table.isProcessing = true;
562
826
  // new blank config object
563
- $t0.config = {};
827
+ table.config = {};
564
828
  // merge and extend
565
- c = $.extend(true, $t0.config, ts.defaults, settings);
829
+ c = $.extend(true, table.config, ts.defaults, settings);
566
830
  // save the settings where they read
567
- $.data($t0, "tablesorter", c);
568
- if (c.debug) { $.data( $t0, 'startoveralltimer', new Date()); }
831
+ $.data(table, "tablesorter", c);
832
+ if (c.debug) { $.data( table, 'startoveralltimer', new Date()); }
569
833
  // constants
570
834
  c.supportsTextContent = $('<span>x</span>')[0].textContent === 'x';
571
835
  c.supportsDataObject = parseFloat($.fn.jquery) >= 1.4;
@@ -578,234 +842,17 @@
578
842
  c.$table = $this.addClass(c.tableClass + k);
579
843
  c.$tbodies = $this.children('tbody:not(.' + c.cssInfoBlock + ')');
580
844
  // build headers
581
- c.$headers = buildHeaders($t0);
845
+ buildHeaders(table);
582
846
  // fixate columns if the users supplies the fixedWidth option
583
847
  // do this after theme has been applied
584
- fixColumnWidth($t0);
848
+ fixColumnWidth(table);
585
849
  // try to auto detect column type, and store in tables config
586
- c.parsers = buildParserCache($t0);
850
+ buildParserCache(table);
587
851
  // build the cache for the tbody cells
588
852
  // delayInit will delay building the cache until the user starts a sort
589
- if (!c.delayInit) { buildCache($t0); }
590
- // apply event handling to headers
591
- // this is to big, perhaps break it out?
592
- c.$headers
593
- // http://stackoverflow.com/questions/5312849/jquery-find-self
594
- .find('*').andSelf().filter(c.selectorSort)
595
- .unbind('mousedown.tablesorter mouseup.tablesorter')
596
- .bind('mousedown.tablesorter mouseup.tablesorter', function(e, external) {
597
- // jQuery v1.2.6 doesn't have closest()
598
- var $cell = this.tagName.match('TH|TD') ? $(this) : $(this).parents('th, td').filter(':last'), cell = $cell[0];
599
- // only recognize left clicks
600
- if ((e.which || e.button) !== 1) { return false; }
601
- // set timer on mousedown
602
- if (e.type === 'mousedown') {
603
- downTime = new Date().getTime();
604
- return e.target.tagName === "INPUT" ? '' : !c.cancelSelection;
605
- }
606
- // ignore long clicks (prevents resizable widget from initializing a sort)
607
- if (external !== true && (new Date().getTime() - downTime > 250)) { return false; }
608
- if (c.delayInit && !c.cache) { buildCache($t0); }
609
- if (!cell.sortDisabled) {
610
- // Only call sortStart if sorting is enabled
611
- $this.trigger("sortStart", $t0);
612
- // store exp, for speed
613
- // $cell = $(this);
614
- k = !e[c.sortMultiSortKey];
615
- // get current column sort order
616
- cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2);
617
- // reset all sorts on non-current column - issue #30
618
- if (c.sortRestart) {
619
- i = cell;
620
- c.$headers.each(function() {
621
- // only reset counts on columns that weren't just clicked on and if not included in a multisort
622
- if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) {
623
- this.count = -1;
624
- }
625
- });
626
- }
627
- // get current column index
628
- i = cell.column;
629
- // user only wants to sort on one column
630
- if (k) {
631
- // flush the sort list
632
- c.sortList = [];
633
- if (c.sortForce !== null) {
634
- a = c.sortForce;
635
- for (j = 0; j < a.length; j++) {
636
- if (a[j][0] !== i) {
637
- c.sortList.push(a[j]);
638
- }
639
- }
640
- }
641
- // add column to sort list
642
- o = cell.order[cell.count];
643
- if (o < 2) {
644
- c.sortList.push([i, o]);
645
- // add other columns if header spans across multiple
646
- if (cell.colSpan > 1) {
647
- for (j = 1; j < cell.colSpan; j++) {
648
- c.sortList.push([i + j, o]);
649
- }
650
- }
651
- }
652
- // multi column sorting
653
- } else {
654
- // get rid of the sortAppend before adding more - fixes issue #115
655
- if (c.sortAppend && c.sortList.length > 1) {
656
- if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) {
657
- c.sortList.pop();
658
- }
659
- }
660
- // the user has clicked on an already sorted column
661
- if (ts.isValueInArray(i, c.sortList)) {
662
- // reverse the sorting direction for all tables
663
- for (j = 0; j < c.sortList.length; j++) {
664
- s = c.sortList[j];
665
- o = c.headerList[s[0]];
666
- if (s[0] === i) {
667
- s[1] = o.order[o.count];
668
- if (s[1] === 2) {
669
- c.sortList.splice(j,1);
670
- o.count = -1;
671
- }
672
- }
673
- }
674
- } else {
675
- // add column to sort list array
676
- o = cell.order[cell.count];
677
- if (o < 2) {
678
- c.sortList.push([i, o]);
679
- // add other columns if header spans across multiple
680
- if (cell.colSpan > 1) {
681
- for (j = 1; j < cell.colSpan; j++) {
682
- c.sortList.push([i + j, o]);
683
- }
684
- }
685
- }
686
- }
687
- }
688
- if (c.sortAppend !== null) {
689
- a = c.sortAppend;
690
- for (j = 0; j < a.length; j++) {
691
- if (a[j][0] !== i) {
692
- c.sortList.push(a[j]);
693
- }
694
- }
695
- }
696
- // sortBegin event triggered immediately before the sort
697
- $this.trigger("sortBegin", $t0);
698
- // setTimeout needed so the processing icon shows up
699
- setTimeout(function(){
700
- // set css for headers
701
- setHeadersCss($t0);
702
- multisort($t0);
703
- appendToTable($t0);
704
- }, 1);
705
- }
706
- });
707
- if (c.cancelSelection) {
708
- // cancel selection
709
- c.$headers.each(function() {
710
- this.onselectstart = function() {
711
- return false;
712
- };
713
- });
714
- }
715
- // apply easy methods that trigger binded events
716
- $this
717
- .unbind('sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave')
718
- .bind("sortReset", function(){
719
- c.sortList = [];
720
- setHeadersCss($t0);
721
- multisort($t0);
722
- appendToTable($t0);
723
- })
724
- .bind("update updateRows", function(e, resort, callback) {
725
- // remove rows/elements before update
726
- $(c.selectorRemove, $t0).remove();
727
- // rebuild parsers
728
- c.parsers = buildParserCache($t0);
729
- // rebuild the cache map
730
- buildCache($t0);
731
- checkResort($this, resort, callback);
732
- })
733
- .bind("updateCell", function(e, cell, resort, callback) {
734
- // get position from the dom
735
- var l, row, icell,
736
- $tb = $this.find('tbody'),
737
- // update cache - format: function(s, table, cell, cellIndex)
738
- // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
739
- tbdy = $tb.index( $(cell).parents('tbody').filter(':last') ),
740
- $row = $(cell).parents('tr').filter(':last');
741
- cell = $(cell)[0]; // in case cell is a jQuery object
742
- // tbody may not exist if update is initialized while tbody is removed for processing
743
- if ($tb.length && tbdy >= 0) {
744
- row = $tb.eq(tbdy).find('tr').index( $row );
745
- icell = cell.cellIndex;
746
- l = $t0.config.cache[tbdy].normalized[row].length - 1;
747
- $t0.config.cache[tbdy].row[$t0.config.cache[tbdy].normalized[row][l]] = $row;
748
- $t0.config.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText($t0, cell, icell), $t0, cell, icell );
749
- checkResort($this, resort, callback);
750
- }
751
- })
752
- .bind("addRows", function(e, $row, resort, callback) {
753
- var i, rows = $row.filter('tr').length,
754
- dat = [], l = $row[0].cells.length,
755
- tbdy = $this.find('tbody').index( $row.closest('tbody') );
756
- // fixes adding rows to an empty table - see issue #179
757
- if (!c.parsers) {
758
- c.parsers = buildParserCache($t0);
759
- }
760
- // add each row
761
- for (i = 0; i < rows; i++) {
762
- // add each cell
763
- for (j = 0; j < l; j++) {
764
- dat[j] = c.parsers[j].format( getElementText($t0, $row[i].cells[j], j), $t0, $row[i].cells[j], j );
765
- }
766
- // add the row index to the end
767
- dat.push(c.cache[tbdy].row.length);
768
- // update cache
769
- c.cache[tbdy].row.push([$row[i]]);
770
- c.cache[tbdy].normalized.push(dat);
771
- dat = [];
772
- }
773
- // resort using current settings
774
- checkResort($this, resort, callback);
775
- })
776
- .bind("sorton", function(e, list, callback, init) {
777
- $this.trigger("sortStart", this);
778
- // update header count index
779
- updateHeaderSortCount($t0, list);
780
- // set css for headers
781
- setHeadersCss($t0);
782
- // sort the table and append it to the dom
783
- multisort($t0);
784
- appendToTable($t0, init);
785
- if (typeof callback === "function") {
786
- callback($t0);
787
- }
788
- })
789
- .bind("appendCache", function(e, callback, init) {
790
- appendToTable($t0, init);
791
- if (typeof callback === "function") {
792
- callback($t0);
793
- }
794
- })
795
- .bind("applyWidgetId", function(e, id) {
796
- ts.getWidgetById(id).format($t0, c, c.widgetOptions);
797
- })
798
- .bind("applyWidgets", function(e, init) {
799
- // apply widgets
800
- ts.applyWidget($t0, init);
801
- })
802
- .bind("refreshWidgets", function(e, all, dontapply){
803
- ts.refreshWidgets($t0, all, dontapply);
804
- })
805
- .bind("destroy", function(e, c, cb){
806
- ts.destroy($t0, c, cb);
807
- });
808
-
853
+ if (!c.delayInit) { buildCache(table); }
854
+ // bind all header events and methods
855
+ bindEvents(table);
809
856
  // get sort list from jQuery data or metadata
810
857
  // in jQuery < 1.4, an error occurs when calling $this.data()
811
858
  if (c.supportsDataObject && typeof $this.data().sortlist !== 'undefined') {
@@ -814,40 +861,42 @@
814
861
  c.sortList = $this.metadata().sortlist;
815
862
  }
816
863
  // apply widget init code
817
- ts.applyWidget($t0, true);
864
+ ts.applyWidget(table, true);
818
865
  // if user has supplied a sort list to constructor
819
866
  if (c.sortList.length > 0) {
820
867
  $this.trigger("sorton", [c.sortList, {}, !c.initWidgets]);
821
868
  } else if (c.initWidgets) {
822
869
  // apply widget format
823
- ts.applyWidget($t0);
870
+ ts.applyWidget(table);
824
871
  }
825
872
 
826
873
  // show processesing icon
827
874
  if (c.showProcessing) {
828
875
  $this
829
- .unbind('sortBegin sortEnd')
830
- .bind('sortBegin sortEnd', function(e) {
831
- ts.isProcessing($t0, e.type === 'sortBegin');
876
+ .unbind('sortBegin.tablesorter sortEnd.tablesorter')
877
+ .bind('sortBegin.tablesorter sortEnd.tablesorter', function(e) {
878
+ ts.isProcessing(table, e.type === 'sortBegin');
832
879
  });
833
880
  }
834
881
 
835
882
  // initialized
836
- $t0.hasInitialized = true;
883
+ table.hasInitialized = true;
884
+ table.isProcessing = false;
837
885
  if (c.debug) {
838
- ts.benchmark("Overall initialization time", $.data( $t0, 'startoveralltimer'));
886
+ ts.benchmark("Overall initialization time", $.data( table, 'startoveralltimer'));
839
887
  }
840
- $this.trigger('tablesorter-initialized', $t0);
841
- if (typeof c.initialized === 'function') { c.initialized($t0); }
888
+ $this.trigger('tablesorter-initialized', table);
889
+ if (typeof c.initialized === 'function') { c.initialized(table); }
842
890
  });
843
891
  };
844
892
 
845
893
  // *** Process table ***
846
894
  // add processing indicator
847
895
  ts.isProcessing = function(table, toggle, $ths) {
848
- var c = table.config,
896
+ table = $(table);
897
+ var c = table[0].config,
849
898
  // default to all headers
850
- $h = $ths || $(table).find('.' + c.cssHeader);
899
+ $h = $ths || table.find('.' + c.cssHeader);
851
900
  if (toggle) {
852
901
  if (c.sortList.length > 0) {
853
902
  // get headers from the sortList
@@ -865,8 +914,9 @@
865
914
  // detach tbody but save the position
866
915
  // don't use tbody because there are portions that look for a tbody index (updateCell)
867
916
  ts.processTbody = function(table, $tb, getIt){
868
- var t, holdr;
917
+ var holdr;
869
918
  if (getIt) {
919
+ table.isProcessing = true;
870
920
  $tb.before('<span class="tablesorter-savemyplace"/>');
871
921
  holdr = ($.fn.detach) ? $tb.detach() : $tb.remove();
872
922
  return holdr;
@@ -874,13 +924,28 @@
874
924
  holdr = $(table).find('span.tablesorter-savemyplace');
875
925
  $tb.insertAfter( holdr );
876
926
  holdr.remove();
927
+ table.isProcessing = false;
877
928
  };
878
929
 
879
930
  ts.clearTableBody = function(table) {
880
- table.config.$tbodies.empty();
931
+ $(table)[0].config.$tbodies.empty();
932
+ };
933
+
934
+ // restore headers
935
+ ts.restoreHeaders = function(table){
936
+ var c = table.config;
937
+ // don't use c.$headers here in case header cells were swapped
938
+ c.$table.find(c.selectorHeaders).each(function(i){
939
+ // only restore header cells if it is wrapped
940
+ // because this is also used by the updateAll method
941
+ if ($(this).find('.tablesorter-header-inner').length){
942
+ $(this).html( c.headerContent[i] );
943
+ }
944
+ });
881
945
  };
882
946
 
883
947
  ts.destroy = function(table, removeClasses, callback){
948
+ table = $(table)[0];
884
949
  if (!table.hasInitialized) { return; }
885
950
  // remove all widgets
886
951
  ts.refreshWidgets(table, true, true);
@@ -893,15 +958,12 @@
893
958
  // disable tablesorter
894
959
  $t
895
960
  .removeData('tablesorter')
896
- .unbind('sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave');
961
+ .unbind('sortReset update updateAll updateRows updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd '.split(' ').join('.tablesorter '));
897
962
  c.$headers.add($f)
898
963
  .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc)
899
964
  .removeAttr('data-column');
900
- $r.find(c.selectorSort).unbind('mousedown.tablesorter mouseup.tablesorter');
901
- // restore headers
902
- $r.children().each(function(i){
903
- $(this).html( c.headerContent[i] );
904
- });
965
+ $r.find(c.selectorSort).unbind('mousedown.tablesorter mouseup.tablesorter keypress.tablesorter');
966
+ ts.restoreHeaders(table);
905
967
  if (removeClasses !== false) {
906
968
  $t.removeClass(c.tableClass + ' tablesorter-' + c.theme);
907
969
  }
@@ -915,7 +977,7 @@
915
977
  // *** sort functions ***
916
978
  // regex used in natural sort
917
979
  ts.regex = [
918
- /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, // chunk/tokenize numbers & letters
980
+ /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
919
981
  /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date
920
982
  /^0x[0-9a-f]+$/i // hex
921
983
  ];
@@ -925,8 +987,8 @@
925
987
  if (a === b) { return 0; }
926
988
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ],
927
989
  r = ts.regex, xN, xD, yN, yD, xF, yF, i, mx;
928
- if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
929
- if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
990
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
991
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
930
992
  if (typeof c.textSorter === 'function') { return c.textSorter(a, b, table, col); }
931
993
  // chunk/tokenize
932
994
  xN = a.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0');
@@ -961,8 +1023,8 @@
961
1023
  ts.sortTextDesc = function(table, a, b, col) {
962
1024
  if (a === b) { return 0; }
963
1025
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
964
- if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
965
- if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
1026
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1027
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
966
1028
  if (typeof c.textSorter === 'function') { return c.textSorter(b, a, table, col); }
967
1029
  return ts.sortText(table, b, a);
968
1030
  };
@@ -973,7 +1035,7 @@
973
1035
  ts.getTextValue = function(a, mx, d) {
974
1036
  if (mx) {
975
1037
  // make sure the text value is greater than the max numerical value (mx)
976
- var i, l = a.length, n = mx + d;
1038
+ var i, l = a ? a.length : 0, n = mx + d;
977
1039
  for (i = 0; i < l; i++) {
978
1040
  n += a.charCodeAt(i);
979
1041
  }
@@ -985,8 +1047,8 @@
985
1047
  ts.sortNumeric = function(table, a, b, col, mx, d) {
986
1048
  if (a === b) { return 0; }
987
1049
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
988
- if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; }
989
- if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; }
1050
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1; }
1051
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1; }
990
1052
  if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
991
1053
  if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
992
1054
  return a - b;
@@ -995,8 +1057,8 @@
995
1057
  ts.sortNumericDesc = function(table, a, b, col, mx, d) {
996
1058
  if (a === b) { return 0; }
997
1059
  var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ];
998
- if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; }
999
- if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; }
1060
+ if (a === '' && e !== 0) { return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1; }
1061
+ if (b === '' && e !== 0) { return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1; }
1000
1062
  if (isNaN(a)) { a = ts.getTextValue(a, mx, d); }
1001
1063
  if (isNaN(b)) { b = ts.getTextValue(b, mx, d); }
1002
1064
  return b - a;
@@ -1089,36 +1151,55 @@
1089
1151
  };
1090
1152
 
1091
1153
  ts.applyWidget = function(table, init) {
1154
+ table = $(table)[0]; // in case this is called externally
1092
1155
  var c = table.config,
1093
1156
  wo = c.widgetOptions,
1094
- ws = c.widgets.sort().reverse(), // ensure that widgets are always applied in a certain order
1095
- time, i, w, l = ws.length;
1096
- // make zebra last
1097
- i = $.inArray('zebra', c.widgets);
1098
- if (i >= 0) {
1099
- c.widgets.splice(i,1);
1100
- c.widgets.push('zebra');
1101
- }
1102
- if (c.debug) {
1103
- time = new Date();
1104
- }
1105
- // add selected widgets
1106
- for (i = 0; i < l; i++) {
1107
- w = ts.getWidgetById(ws[i]);
1108
- if ( w ) {
1109
- if (init === true && w.hasOwnProperty('init')) {
1110
- w.init(table, w, c, wo);
1111
- } else if (!init && w.hasOwnProperty('format')) {
1112
- w.format(table, c, wo);
1157
+ widgets = [],
1158
+ time, i, w, wd;
1159
+ if (c.debug) { time = new Date(); }
1160
+ if (c.widgets.length) {
1161
+ // ensure unique widget ids
1162
+ c.widgets = $.grep(c.widgets, function(v, k){
1163
+ return $.inArray(v, c.widgets) === k;
1164
+ });
1165
+ // build widget array & add priority as needed
1166
+ $.each(c.widgets || [], function(i,n){
1167
+ wd = ts.getWidgetById(n);
1168
+ if (wd && wd.id) {
1169
+ // set priority to 10 if not defined
1170
+ if (!wd.priority) { wd.priority = 10; }
1171
+ widgets[i] = wd;
1113
1172
  }
1114
- }
1173
+ });
1174
+ // sort widgets by priority
1175
+ widgets.sort(function(a, b){
1176
+ return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
1177
+ });
1178
+
1179
+ // add/update selected widgets
1180
+ $.each(widgets, function(i,w){
1181
+ if (w) {
1182
+ if (init) {
1183
+ if (w.hasOwnProperty('options')) {
1184
+ wo = table.config.widgetOptions = $.extend( true, {}, w.options, wo );
1185
+ }
1186
+ if (w.hasOwnProperty('init')) {
1187
+ w.init(table, w, c, wo);
1188
+ }
1189
+ } else if (!init && w.hasOwnProperty('format')) {
1190
+ w.format(table, c, wo, false);
1191
+ }
1192
+ }
1193
+ });
1115
1194
  }
1116
1195
  if (c.debug) {
1117
- benchmark("Completed " + (init === true ? "initializing" : "applying") + " widgets", time);
1196
+ w = c.widgets.length;
1197
+ benchmark("Completed " + (init === true ? "initializing " : "applying ") + w + " widget" + (w !== 1 ? "s" : ""), time);
1118
1198
  }
1119
1199
  };
1120
1200
 
1121
1201
  ts.refreshWidgets = function(table, doAll, dontapply) {
1202
+ table = $(table)[0]; // see issue #243
1122
1203
  var i, c = table.config,
1123
1204
  cw = c.widgets,
1124
1205
  w = ts.widgets, l = w.length;
@@ -1151,14 +1232,14 @@
1151
1232
  } else if (ch && typeof ch[key] !== 'undefined') {
1152
1233
  val += ch[key];
1153
1234
  } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
1154
- // include sorter class name "sorter-text", etc
1155
- val = cl.match( new RegExp(' ' + key + '-(\\w+)') )[1] || '';
1235
+ // include sorter class name "sorter-text", etc; now works with "sorter-my-custom-parser"
1236
+ val = cl.match( new RegExp('\\s' + key + '-([\\w-]+)') )[1] || '';
1156
1237
  }
1157
1238
  return $.trim(val);
1158
1239
  };
1159
1240
 
1160
1241
  ts.formatFloat = function(s, table) {
1161
- if (typeof(s) !== 'string' || s === '') { return s; }
1242
+ if (typeof s !== 'string' || s === '') { return s; }
1162
1243
  // allow using formatFloat without a table; defaults to US number format
1163
1244
  var i,
1164
1245
  t = table && table.config ? table.config.usNumberFormat !== false :
@@ -1199,24 +1280,40 @@
1199
1280
  // add default parsers
1200
1281
  ts.addParser({
1201
1282
  id: "text",
1202
- is: function(s, table, node) {
1283
+ is: function() {
1203
1284
  return true;
1204
1285
  },
1205
- format: function(s, table, cell, cellIndex) {
1286
+ format: function(s, table) {
1206
1287
  var c = table.config;
1207
- s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
1208
- return c.sortLocaleCompare ? ts.replaceAccents(s) : s;
1288
+ if (s) {
1289
+ s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s );
1290
+ s = c.sortLocaleCompare ? ts.replaceAccents(s) : s;
1291
+ }
1292
+ return s;
1209
1293
  },
1210
1294
  type: "text"
1211
1295
  });
1212
1296
 
1297
+ ts.addParser({
1298
+ id: "digit",
1299
+ is: function(s) {
1300
+ return ts.isDigit(s);
1301
+ },
1302
+ format: function(s, table) {
1303
+ var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
1304
+ return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
1305
+ },
1306
+ type: "numeric"
1307
+ });
1308
+
1213
1309
  ts.addParser({
1214
1310
  id: "currency",
1215
1311
  is: function(s) {
1216
- return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test(s); // £$€¤¥¢
1312
+ return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[,. ]/g,'')); // £$€¤¥¢
1217
1313
  },
1218
1314
  format: function(s, table) {
1219
- return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table);
1315
+ var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ""), table);
1316
+ return s && typeof n === 'number' ? n : s ? $.trim( s && table.config.ignoreCase ? s.toLocaleLowerCase() : s ) : s;
1220
1317
  },
1221
1318
  type: "numeric"
1222
1319
  });
@@ -1227,13 +1324,13 @@
1227
1324
  return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s);
1228
1325
  },
1229
1326
  format: function(s, table) {
1230
- var i, a = s.split("."),
1327
+ var i, a = s ? s.split(".") : '',
1231
1328
  r = "",
1232
1329
  l = a.length;
1233
1330
  for (i = 0; i < l; i++) {
1234
1331
  r += ("00" + a[i]).slice(-3);
1235
1332
  }
1236
- return ts.formatFloat(r, table);
1333
+ return s ? ts.formatFloat(r, table) : s;
1237
1334
  },
1238
1335
  type: "numeric"
1239
1336
  });
@@ -1244,7 +1341,7 @@
1244
1341
  return (/^(https?|ftp|file):\/\//).test(s);
1245
1342
  },
1246
1343
  format: function(s) {
1247
- return $.trim(s.replace(/(https?|ftp|file):\/\//, ''));
1344
+ return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s;
1248
1345
  },
1249
1346
  type: "text"
1250
1347
  });
@@ -1255,7 +1352,7 @@
1255
1352
  return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/).test(s);
1256
1353
  },
1257
1354
  format: function(s, table) {
1258
- return ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table);
1355
+ return s ? ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table) : s;
1259
1356
  },
1260
1357
  type: "numeric"
1261
1358
  });
@@ -1263,10 +1360,10 @@
1263
1360
  ts.addParser({
1264
1361
  id: "percent",
1265
1362
  is: function(s) {
1266
- return (/(\d\s?%|%\s?\d)/).test(s);
1363
+ return (/(\d\s*?%|%\s*?\d)/).test(s) && s.length < 15;
1267
1364
  },
1268
1365
  format: function(s, table) {
1269
- return ts.formatFloat(s.replace(/%/g, ""), table);
1366
+ return s ? ts.formatFloat(s.replace(/%/g, ""), table) : s;
1270
1367
  },
1271
1368
  type: "numeric"
1272
1369
  });
@@ -1279,7 +1376,7 @@
1279
1376
  return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s) || (/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i).test(s);
1280
1377
  },
1281
1378
  format: function(s, table) {
1282
- return ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table);
1379
+ return s ? ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table) : s;
1283
1380
  },
1284
1381
  type: "numeric"
1285
1382
  });
@@ -1287,25 +1384,23 @@
1287
1384
  ts.addParser({
1288
1385
  id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd"
1289
1386
  is: function(s) {
1290
- // testing for ####-##-####, so it's not perfect
1291
- return (/^(\d{1,2}|\d{4})[\/\-\,\.\s+]\d{1,2}[\/\-\.\,\s+](\d{1,2}|\d{4})$/).test(s);
1387
+ // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
1388
+ return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g," ").replace(/[\-.,]/g, "/"));
1292
1389
  },
1293
1390
  format: function(s, table, cell, cellIndex) {
1294
- var c = table.config, ci = c.headerList[cellIndex],
1295
- format = ci.shortDateFormat;
1296
- if (typeof format === 'undefined') {
1297
- // cache header formatting so it doesn't getData for every cell in the column
1298
- format = ci.shortDateFormat = ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
1299
- }
1300
- s = s.replace(/\s+/g," ").replace(/[\-|\.|\,]/g, "/");
1301
- if (format === "mmddyyyy") {
1302
- s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
1303
- } else if (format === "ddmmyyyy") {
1304
- s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
1305
- } else if (format === "yyyymmdd") {
1306
- s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
1391
+ if (s) {
1392
+ var c = table.config, ci = c.headerList[cellIndex],
1393
+ format = ci.dateFormat || ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat;
1394
+ s = s.replace(/\s+/g," ").replace(/[\-.,]/g, "/"); // escaped - because JSHint in Firefox was showing it as an error
1395
+ if (format === "mmddyyyy") {
1396
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2");
1397
+ } else if (format === "ddmmyyyy") {
1398
+ s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1");
1399
+ } else if (format === "yyyymmdd") {
1400
+ s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3");
1401
+ }
1307
1402
  }
1308
- return ts.formatFloat( (new Date(s).getTime() || ''), table);
1403
+ return s ? ts.formatFloat( (new Date(s).getTime() || ''), table) : s;
1309
1404
  },
1310
1405
  type: "numeric"
1311
1406
  });
@@ -1316,25 +1411,14 @@
1316
1411
  return (/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s);
1317
1412
  },
1318
1413
  format: function(s, table) {
1319
- return ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ""), table);
1320
- },
1321
- type: "numeric"
1322
- });
1323
-
1324
- ts.addParser({
1325
- id: "digit",
1326
- is: function(s) {
1327
- return ts.isDigit(s);
1328
- },
1329
- format: function(s, table) {
1330
- return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table);
1414
+ return s ? ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ""), table) : s;
1331
1415
  },
1332
1416
  type: "numeric"
1333
1417
  });
1334
1418
 
1335
1419
  ts.addParser({
1336
1420
  id: "metadata",
1337
- is: function(s) {
1421
+ is: function() {
1338
1422
  return false;
1339
1423
  },
1340
1424
  format: function(s, table, cell) {
@@ -1348,6 +1432,7 @@
1348
1432
  // add default widgets
1349
1433
  ts.addWidget({
1350
1434
  id: "zebra",
1435
+ priority: 90,
1351
1436
  format: function(table, c, wo) {
1352
1437
  var $tb, $tv, $tr, row, even, time, k, l,
1353
1438
  child = new RegExp(c.cssChildRow, 'i'),
@@ -1380,7 +1465,7 @@
1380
1465
  remove: function(table, c, wo){
1381
1466
  var k, $tb,
1382
1467
  b = c.$tbodies,
1383
- rmv = (c.widgetOptions.zebra || [ "even", "odd" ]).join(' ');
1468
+ rmv = (wo.zebra || [ "even", "odd" ]).join(' ');
1384
1469
  for (k = 0; k < b.length; k++ ){
1385
1470
  $tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody
1386
1471
  $tb.children().removeClass(rmv);
@@ -1389,4 +1474,4 @@
1389
1474
  }
1390
1475
  });
1391
1476
 
1392
- })(jQuery);
1477
+ })(jQuery);