jquery-tablesorter 1.4.1 → 1.5.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.
@@ -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);