jquery-tablesorter 1.9.5 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- /*! tableSorter 2.8+ widgets - updated 12/16/2013 (v2.14.5)
1
+ /*! tableSorter 2.8+ widgets - updated 2/19/2014 (v2.15.0)
2
2
  *
3
3
  * Column Styles
4
4
  * Column Filters
@@ -49,6 +49,16 @@ ts.themes = {
49
49
  }
50
50
  };
51
51
 
52
+ $.extend(ts.css, {
53
+ filterRow : 'tablesorter-filter-row', // filter
54
+ filter : 'tablesorter-filter',
55
+ wrapper : 'tablesorter-wrapper', // ui theme & resizable
56
+ resizer : 'tablesorter-resizer', // resizable
57
+ grip : 'tablesorter-resizer-grip',
58
+ sticky : 'tablesorter-stickyHeader', // stickyHeader
59
+ stickyVis : 'tablesorter-sticky-visible'
60
+ });
61
+
52
62
  // *** Store data in local storage, with a cookie fallback ***
53
63
  /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
54
64
  if you need it, then include https://github.com/douglascrockford/JSON-js
@@ -200,16 +210,16 @@ ts.addWidget({
200
210
  // toggleClass with switch added in jQuery 1.3
201
211
  $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover);
202
212
  });
203
- if (!$headers.find('.tablesorter-wrapper').length) {
213
+ if (!$headers.find('.' + ts.css.wrapper).length) {
204
214
  // Firefox needs this inner div to position the resizer correctly
205
- $headers.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
215
+ $headers.wrapInner('<div class="' + ts.css.wrapper + '" style="position:relative;height:100%;width:100%"></div>');
206
216
  }
207
217
  if (c.cssIcon) {
208
218
  // if c.cssIcon is '', then no <i> is added to the header
209
219
  $headers.find('.' + ts.css.icon).addClass(themes.icons);
210
220
  }
211
221
  if ($table.hasClass('hasFilters')) {
212
- $headers.find('.tablesorter-filter-row').addClass(themes.filterRow);
222
+ $headers.find('.' + ts.css.filterRow).addClass(themes.filterRow);
213
223
  }
214
224
  }
215
225
  $.each($headers, function() {
@@ -218,7 +228,7 @@ ts.addWidget({
218
228
  if (this.sortDisabled) {
219
229
  // no sort arrows for disabled columns!
220
230
  $header.removeClass(remove);
221
- $icon.removeClass(remove + ' tablesorter-icon ' + themes.icons);
231
+ $icon.removeClass(remove + ' ' + themes.icons);
222
232
  } else {
223
233
  classes = ($header.hasClass(ts.css.sortAsc)) ?
224
234
  themes.sortAsc :
@@ -244,9 +254,9 @@ ts.addWidget({
244
254
  $headers
245
255
  .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover
246
256
  .removeClass(themes.hover + ' ' + remove + ' ' + themes.active)
247
- .find('.tablesorter-filter-row')
257
+ .find('.' + ts.css.filterRow)
248
258
  .removeClass(themes.filterRow);
249
- $headers.find('.tablesorter-icon').removeClass(themes.icons);
259
+ $headers.find('.' + ts.css.icon).removeClass(themes.icons);
250
260
  }
251
261
  });
252
262
 
@@ -338,13 +348,14 @@ ts.addWidget({
338
348
  id: "filter",
339
349
  priority: 50,
340
350
  options : {
341
- filter_anyMatch : false, // if true overrides default find rows behaviours and if any column matches query it returns that row
342
351
  filter_childRows : false, // if true, filter includes child row content in the search
343
352
  filter_columnFilters : true, // if true, a filter will be added to the top of each table column
344
353
  filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
354
+ filter_external : '', // jQuery selector string (or jQuery object) of external filters
345
355
  filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
346
356
  filter_formatter : null, // add custom filter elements to the filter row
347
357
  filter_functions : null, // add custom filter functions using this option
358
+ filter_hideEmpty : true, // hide filter row when table is empty
348
359
  filter_hideFilters : false, // collapse filter row when mouse leaves the area
349
360
  filter_ignoreCase : true, // if true, make all searches case-insensitive
350
361
  filter_liveSearch : true, // if true, search column content while the user types (with a delay)
@@ -359,9 +370,7 @@ ts.addWidget({
359
370
  },
360
371
  format: function(table, c, wo) {
361
372
  if (!c.$table.hasClass('hasFilters')) {
362
- if (c.parsers || !c.parsers && wo.filter_serversideFiltering) {
363
- ts.filter.init(table, c, wo);
364
- }
373
+ ts.filter.init(table, c, wo);
365
374
  }
366
375
  },
367
376
  remove: function(table, c, wo) {
@@ -372,7 +381,7 @@ ts.addWidget({
372
381
  .removeClass('hasFilters')
373
382
  // add .tsfilter namespace to all BUT search
374
383
  .unbind('addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join('.tsfilter '))
375
- .find('.tablesorter-filter-row').remove();
384
+ .find('.' + ts.css.filterRow).remove();
376
385
  for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
377
386
  $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
378
387
  $tbody.children().removeClass(wo.filter_filteredRow).show();
@@ -419,23 +428,6 @@ ts.filter = {
419
428
  }
420
429
  return null;
421
430
  },
422
- // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
423
- exact: function( filter, iFilter, exact, iExact ) {
424
- /*jshint eqeqeq:false */
425
- if (ts.filter.regex.exact.test(iFilter)) {
426
- return iFilter.replace(ts.filter.regex.exact, '') == iExact;
427
- }
428
- return null;
429
- },
430
- // Look for a not match
431
- notMatch: function( filter, iFilter, exact, iExact, cached, index, table, wo ) {
432
- if ( /^\!/.test(iFilter) ) {
433
- iFilter = iFilter.replace('!', '');
434
- var indx = iExact.search( $.trim(iFilter) );
435
- return iFilter === '' ? true : !(wo.filter_startsWith ? indx === 0 : indx >= 0);
436
- }
437
- return null;
438
- },
439
431
  // Look for operators >, >=, < or <=
440
432
  operators: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
441
433
  if ( /^[<>]=?/.test(iFilter) ) {
@@ -462,6 +454,24 @@ ts.filter = {
462
454
  }
463
455
  return null;
464
456
  },
457
+ // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
458
+ exact: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed, rowArray ) {
459
+ /*jshint eqeqeq:false */
460
+ if (ts.filter.regex.exact.test(iFilter)) {
461
+ var fltr = iFilter.replace(ts.filter.regex.exact, '');
462
+ return rowArray ? $.inArray(fltr, rowArray) >= 0 : fltr == iExact;
463
+ }
464
+ return null;
465
+ },
466
+ // Look for a not match
467
+ notMatch: function( filter, iFilter, exact, iExact, cached, index, table, wo ) {
468
+ if ( /^\!/.test(iFilter) ) {
469
+ iFilter = iFilter.replace('!', '');
470
+ var indx = iExact.search( $.trim(iFilter) );
471
+ return iFilter === '' ? true : !(wo.filter_startsWith ? indx === 0 : indx >= 0);
472
+ }
473
+ return null;
474
+ },
465
475
  // Look for an AND or && operator (logical and)
466
476
  and : function( filter, iFilter, exact, iExact ) {
467
477
  if ( /\s+(AND|&&)\s+/g.test(filter) ) {
@@ -500,13 +510,13 @@ ts.filter = {
500
510
  return null;
501
511
  },
502
512
  // Look for wild card: ? = single, * = multiple, or | = logical OR
503
- wild : function( filter, iFilter, exact, iExact, cached, index, table ) {
504
- if ( /[\?|\*]/.test(iFilter) || /\s+OR\s+/.test(filter) ) {
513
+ wild : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed, rowArray ) {
514
+ if ( /[\?|\*]/.test(iFilter) || /\s+OR\s+/i.test(filter) ) {
505
515
  var c = table.config,
506
516
  query = iFilter.replace(/\s+OR\s+/gi,"|");
507
517
  // look for an exact match with the "or" unless the "filter-match" class is found
508
518
  if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) {
509
- query = '^(' + query + ')$';
519
+ query = $.isArray(rowArray) ? '(' + query + ')' : '^(' + query + ')$';
510
520
  }
511
521
  return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(iExact);
512
522
  }
@@ -549,7 +559,8 @@ ts.filter = {
549
559
  }
550
560
 
551
561
  c.$table.bind('addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join('.tsfilter '), function(event, filter) {
552
- if ( !/(search|filterReset|filterEnd)/.test(event.type) ) {
562
+ c.$table.find('.' + ts.css.filterRow).toggle( !(wo.filter_hideEmpty && $.isEmptyObject(c.cache)) ); // fixes #450
563
+ if ( !/(search|filter)/.test(event.type) ) {
553
564
  event.stopPropagation();
554
565
  ts.filter.buildDefault(table, true);
555
566
  }
@@ -560,15 +571,26 @@ ts.filter = {
560
571
  } else {
561
572
  // send false argument to force a new search; otherwise if the filter hasn't changed, it will return
562
573
  filter = event.type === 'search' ? filter : event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
563
- ts.filter.searching(table, filter);
574
+ if (/(update|add)/.test(event.type)) {
575
+ // force a new search since content has changed
576
+ c.lastCombinedFilter = null;
577
+ }
578
+ // pass true (dontSkip) to prevent the tablesorter.setFilters function from skipping the first input
579
+ // ensures all inputs are updated when a search is triggered on the table $('table').trigger('search', [...]);
580
+ ts.filter.searching(table, filter, true);
564
581
  }
565
582
  return false;
566
583
  });
567
- ts.filter.bindSearch( table, c.$table.find('input.tablesorter-filter') );
584
+ ts.filter.bindSearch( table, c.$table.find('input.' + ts.css.filter), true );
585
+ if (wo.filter_external) {
586
+ ts.filter.bindSearch( table, wo.filter_external );
587
+ }
568
588
 
569
589
  // reset button/link
570
590
  if (wo.filter_reset) {
571
- $(document).delegate(wo.filter_reset, 'click.tsfilter', function() {
591
+ $(document)
592
+ .undelegate(wo.filter_reset, 'click.tsfilter')
593
+ .delegate(wo.filter_reset, 'click.tsfilter', function() {
572
594
  // trigger a reset event, so other functions (filterFormatter) know when to reset
573
595
  c.$table.trigger('filterReset');
574
596
  });
@@ -590,7 +612,7 @@ ts.filter = {
590
612
  options += '<option value="' + string + '">' + string + '</option>';
591
613
  }
592
614
  }
593
- c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]').append(options);
615
+ c.$table.find('thead').find('select.' + ts.css.filter + '[data-column="' + column + '"]').append(options);
594
616
  }
595
617
  }
596
618
  }
@@ -599,8 +621,8 @@ ts.filter = {
599
621
  // it would append the same options twice.
600
622
  ts.filter.buildDefault(table, true);
601
623
 
602
- c.$table.find('select.tablesorter-filter').bind('change search', function(event, filter) {
603
- ts.filter.checkFilters(table, filter);
624
+ c.$table.find('select.' + ts.css.filter).bind('change search', function(event, filter) {
625
+ ts.filter.checkFilters(table, filter, true);
604
626
  });
605
627
 
606
628
  if (wo.filter_hideFilters) {
@@ -627,36 +649,31 @@ ts.filter = {
627
649
  if (filters.length) {
628
650
  ts.setFilters(table, filters, true);
629
651
  }
652
+ c.$table.trigger('filterFomatterUpdate');
630
653
  ts.filter.checkFilters(table, filters);
631
654
  });
632
655
  // filter widget initialized
633
- wo.filter_Initialized = true;
656
+ wo.filter_initialized = true;
634
657
  c.$table.trigger('filterInit');
635
658
  },
636
659
  setDefaults: function(table, c, wo) {
637
- var indx, isArray,
638
- filters = [],
639
- columns = c.columns;
660
+ var isArray, saved,
661
+ // get current (default) filters
662
+ filters = ts.getFilters(table);
640
663
  if (wo.filter_saveFilters && ts.storage) {
641
- filters = ts.storage( table, 'tablesorter-filters' ) || [];
642
- isArray = $.isArray(filters);
643
- // make sure we're not just saving an empty array
644
- if (isArray && filters.join('') === '' || !isArray ) { filters = []; }
645
- }
646
- // if not filters saved, then check default settings
647
- if (!filters.length) {
648
- for (indx = 0; indx < columns; indx++) {
649
- filters[indx] = c.$headers.filter('[data-column="' + indx + '"]:last').attr(wo.filter_defaultAttrib) || filters[indx];
650
- }
664
+ saved = ts.storage( table, 'tablesorter-filters' ) || [];
665
+ isArray = $.isArray(saved);
666
+ // make sure we're not just getting an empty array
667
+ if ( !(isArray && saved.join('') === '' || !isArray) ) { filters = saved; }
651
668
  }
652
- $(table).data('lastSearch', filters);
669
+ c.$table.data('lastSearch', filters);
653
670
  return filters;
654
671
  },
655
672
  buildRow: function(table, c, wo) {
656
- var column, $header, buildSelect, disabled,
673
+ var column, $header, buildSelect, disabled, name,
657
674
  // c.columns defined in computeThIndexes()
658
675
  columns = c.columns,
659
- buildFilter = '<tr class="tablesorter-filter-row">';
676
+ buildFilter = '<tr class="' + ts.css.filterRow + '">';
660
677
  for (column = 0; column < columns; column++) {
661
678
  buildFilter += '<td></td>';
662
679
  }
@@ -699,19 +716,41 @@ ts.filter = {
699
716
  }
700
717
  }
701
718
  if (buildFilter) {
702
- buildFilter.addClass('tablesorter-filter ' + wo.filter_cssFilter).attr('data-column', column);
719
+ // add filter class name
720
+ name = ( $.isArray(wo.filter_cssFilter) ?
721
+ (typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '') :
722
+ wo.filter_cssFilter ) || '';
723
+ buildFilter.addClass( ts.css.filter + ' ' + name ).attr('data-column', column);
703
724
  if (disabled) {
704
725
  buildFilter.addClass('disabled')[0].disabled = true; // disabled!
705
726
  }
706
727
  }
707
728
  }
708
729
  },
709
- bindSearch: function(table, $el) {
730
+ bindSearch: function(table, $el, internal) {
710
731
  table = $(table)[0];
711
- var external, wo = table.config.widgetOptions;
712
- $el.unbind('keyup search filterReset')
713
- .bind('keyup search', function(event, filter) {
714
- var $this = $(this);
732
+ $el = $($el); // allow passing a selector string
733
+ if (!$el.length) { return; }
734
+ var c = table.config,
735
+ wo = c.widgetOptions,
736
+ $ext = wo.filter_$externalFilters;
737
+ if (internal !== true) {
738
+ // save anyMatch element
739
+ wo.filter_$anyMatch = $el.filter('[data-column="all"]');
740
+ if ($ext && $ext.length) {
741
+ wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
742
+ } else {
743
+ wo.filter_$externalFilters = $el;
744
+ }
745
+ // update values (external filters added after table initialization)
746
+ ts.setFilters(table, c.$table.data('lastSearch') || [], internal === false);
747
+ }
748
+ $el
749
+ .data('lastSearchTime', new Date().getTime())
750
+ .unbind('keyup search change')
751
+ // include change for select - fixes #473
752
+ .bind('keyup search change', function(event, filters) {
753
+ $(this).data('lastSearchTime', new Date().getTime());
715
754
  // emulate what webkit does.... escape clears the filter
716
755
  if (event.which === 27) {
717
756
  this.value = '';
@@ -721,23 +760,14 @@ ts.filter = {
721
760
  ( event.which >= 37 && event.which <= 40 ) || (event.which !== 13 && wo.filter_liveSearch === false) ) ) ) {
722
761
  return;
723
762
  }
724
- // external searches won't have a filter parameter, so grab the value
725
- if ($this.hasClass('tablesorter-filter') && !$this.hasClass('tablesorter-external-filter')) {
726
- external = filter;
727
- } else {
728
- external = [];
729
- $el.each(function(){
730
- // target the appropriate column if the external input has a data-column attribute
731
- external[ $(this).data('column') || 0 ] = $(this).val();
732
- });
733
- }
734
- ts.filter.searching(table, filter, external);
735
- })
736
- .bind('filterReset', function(){
763
+ // true flag in getFilters forces obtaining the latest values
764
+ ts.filter.searching( table, filters || ts.getFilters( table, true ), true );
765
+ });
766
+ c.$table.bind('filterReset', function(){
737
767
  $el.val('');
738
768
  });
739
769
  },
740
- checkFilters: function(table, filter) {
770
+ checkFilters: function(table, filter, dontSkip) {
741
771
  var c = table.config,
742
772
  wo = c.widgetOptions,
743
773
  filterArray = $.isArray(filter),
@@ -745,11 +775,11 @@ ts.filter = {
745
775
  combinedFilters = (filters || []).join(''); // combined filter values
746
776
  // add filter array back into inputs
747
777
  if (filterArray) {
748
- ts.setFilters( table, filters );
778
+ ts.setFilters( table, filters, false, dontSkip !== true );
749
779
  }
750
780
  if (wo.filter_hideFilters) {
751
781
  // show/hide filter row as needed
752
- c.$table.find('.tablesorter-filter-row').trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
782
+ c.$table.find('.' + ts.css.filterRow).trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
753
783
  }
754
784
  // return if the last search is the same; but filter === false when updating the search
755
785
  // see example-widget-filter.html filter toggle buttons
@@ -774,7 +804,7 @@ ts.filter = {
774
804
  hideFilters: function(table, c) {
775
805
  var $filterRow, $filterRow2, timer;
776
806
  c.$table
777
- .find('.tablesorter-filter-row')
807
+ .find('.' + ts.css.filterRow)
778
808
  .addClass('hideme')
779
809
  .bind('mouseenter mouseleave', function(e) {
780
810
  // save event object - http://bugs.jquery.com/ticket/12140
@@ -810,9 +840,10 @@ ts.filter = {
810
840
  },
811
841
  findRows: function(table, filters, combinedFilters) {
812
842
  if (table.config.lastCombinedFilter === combinedFilters) { return; }
813
- var cached, len, $rows, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
843
+ var cached, len, $rows, cacheIndex, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
814
844
  childRow, childRowText, exact, iExact, iFilter, lastSearch, matches, result,
815
845
  searchFiltered, filterMatched, showRow, time,
846
+ anyMatch, iAnyMatch, rowArray, rowText, iRowText, rowCache,
816
847
  c = table.config,
817
848
  wo = c.widgetOptions,
818
849
  columns = c.columns,
@@ -821,19 +852,19 @@ ts.filter = {
821
852
  anyMatchNotAllowedTypes = [ 'range', 'notMatch', 'operators' ],
822
853
  // parse columns after formatter, in case the class is added at that point
823
854
  parsed = c.$headers.map(function(columnIndex) {
824
- return (ts.getData) ?
855
+ return c.parsers[columnIndex].parsed || ( ts.getData ?
825
856
  ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), c.headers[columnIndex], 'filter') === 'parsed' :
826
- $(this).hasClass('filter-parsed');
857
+ $(this).hasClass('filter-parsed') );
827
858
  }).get();
828
859
  if (c.debug) { time = new Date(); }
829
860
  for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
830
861
  if ($tbodies.eq(tbodyIndex).hasClass(ts.css.info)) { continue; } // ignore info blocks, issue #264
831
862
  $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true);
832
863
  // skip child rows & widget added (removable) rows - fixes #448 thanks to @hempel!
833
- $rows = $tbody.children('tr').not('.' + c.cssChildRow).not(c.selectorRemove);
864
+ $rows = $tbody.children('tr').not(c.selectorRemove);
834
865
  len = $rows.length;
835
866
  if (combinedFilters === '' || wo.filter_serversideFiltering) {
836
- $tbody.children().show().removeClass(wo.filter_filteredRow);
867
+ $tbody.children().not('.' + c.cssChildRow).show().removeClass(wo.filter_filteredRow);
837
868
  } else {
838
869
  // optimize searching only through already filtered rows - see #313
839
870
  searchFiltered = true;
@@ -844,7 +875,17 @@ ts.filter = {
844
875
  });
845
876
  // can't search when all rows are hidden - this happens when looking for exact matches
846
877
  if (searchFiltered && $rows.filter(':visible').length === 0) { searchFiltered = false; }
878
+ if ((wo.filter_$anyMatch && wo.filter_$anyMatch.length) || filters[c.columns]) {
879
+ anyMatch = wo.filter_$anyMatch && wo.filter_$anyMatch.val() || filters[c.columns] || '';
880
+ if (c.sortLocaleCompare) {
881
+ // replace accents
882
+ anyMatch = ts.replaceAccents(anyMatch);
883
+ }
884
+ iAnyMatch = anyMatch.toLowerCase();
885
+ }
886
+
847
887
  // loop through the rows
888
+ cacheIndex = 0;
848
889
  for (rowIndex = 0; rowIndex < len; rowIndex++) {
849
890
  childRow = $rows[rowIndex].className;
850
891
  // skip child rows & already filtered rows
@@ -858,10 +899,44 @@ ts.filter = {
858
899
  childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
859
900
  childRowText = wo.filter_ignoreCase ? childRowText.toLocaleLowerCase() : childRowText;
860
901
  $cells = $rows.eq(rowIndex).children('td');
902
+
903
+ if (anyMatch) {
904
+ rowArray = $cells.map(function(i){
905
+ var txt;
906
+ if (parsed[i]) {
907
+ txt = c.cache[tbodyIndex].normalized[cacheIndex][i];
908
+ } else {
909
+ txt = wo.filter_ignoreCase ? $(this).text().toLowerCase() : $(this).text();
910
+ if (c.sortLocaleCompare) {
911
+ txt = ts.replaceAccents(txt);
912
+ }
913
+ }
914
+ return txt;
915
+ }).get();
916
+ rowText = rowArray.join(' ');
917
+ iRowText = rowText.toLowerCase();
918
+ rowCache = c.cache[tbodyIndex].normalized[cacheIndex].join(' ');
919
+ filterMatched = null;
920
+ $.each(ts.filter.types, function(type, typeFunction) {
921
+ if ($.inArray(type, anyMatchNotAllowedTypes) < 0) {
922
+ matches = typeFunction( anyMatch, iAnyMatch, rowText, iRowText, rowCache, columns, table, wo, parsed, rowArray );
923
+ if (matches !== null) {
924
+ filterMatched = matches;
925
+ return false;
926
+ }
927
+ }
928
+ });
929
+ if (filterMatched !== null) {
930
+ showRow = filterMatched;
931
+ } else {
932
+ showRow = (iRowText + childRowText).indexOf(iAnyMatch) >= 0;
933
+ }
934
+ }
935
+
861
936
  for (columnIndex = 0; columnIndex < columns; columnIndex++) {
862
937
  // ignore if filter is empty or disabled
863
- if (filters[columnIndex] || wo.filter_anyMatch) {
864
- cached = c.cache[tbodyIndex].normalized[rowIndex][columnIndex];
938
+ if (filters[columnIndex]) {
939
+ cached = c.cache[tbodyIndex].normalized[cacheIndex][columnIndex];
865
940
  // check if column data should be from the cell or from parsed data
866
941
  if (wo.filter_useParsedData || parsed[columnIndex]) {
867
942
  exact = cached;
@@ -873,14 +948,10 @@ ts.filter = {
873
948
  iExact = !ts.filter.regex.type.test(typeof exact) && wo.filter_ignoreCase ? exact.toLocaleLowerCase() : exact;
874
949
  result = showRow; // if showRow is true, show that row
875
950
 
876
- if (typeof filters[columnIndex] === "undefined" || filters[columnIndex] === null) {
877
- filters[columnIndex] = wo.filter_anyMatch ? combinedFilters : filters[columnIndex];
878
- }
879
-
880
951
  // replace accents - see #357
881
952
  filters[columnIndex] = c.sortLocaleCompare ? ts.replaceAccents(filters[columnIndex]) : filters[columnIndex];
882
953
  // val = case insensitive, filters[columnIndex] = case sensitive
883
- iFilter = wo.filter_ignoreCase ? filters[columnIndex].toLocaleLowerCase() : filters[columnIndex];
954
+ iFilter = wo.filter_ignoreCase ? (filters[columnIndex] || '').toLocaleLowerCase() : filters[columnIndex];
884
955
  if (wo.filter_functions && wo.filter_functions[columnIndex]) {
885
956
  if (wo.filter_functions[columnIndex] === true) {
886
957
  // default selector; no "filter-select" class
@@ -898,12 +969,10 @@ ts.filter = {
898
969
  // cycle through the different filters
899
970
  // filters return a boolean or null if nothing matches
900
971
  $.each(ts.filter.types, function(type, typeFunction) {
901
- if (!wo.filter_anyMatch || (wo.filter_anyMatch && $.inArray(type, anyMatchNotAllowedTypes) < 0)) {
902
- matches = typeFunction( filters[columnIndex], iFilter, exact, iExact, cached, columnIndex, table, wo, parsed );
903
- if (matches !== null) {
904
- filterMatched = matches;
905
- return false;
906
- }
972
+ matches = typeFunction( filters[columnIndex], iFilter, exact, iExact, cached, columnIndex, table, wo, parsed );
973
+ if (matches !== null) {
974
+ filterMatched = matches;
975
+ return false;
907
976
  }
908
977
  });
909
978
  if (filterMatched !== null) {
@@ -914,14 +983,7 @@ ts.filter = {
914
983
  result = ( (!wo.filter_startsWith && exact >= 0) || (wo.filter_startsWith && exact === 0) );
915
984
  }
916
985
  }
917
- if (wo.filter_anyMatch) {
918
- showRow = result;
919
- if (showRow){
920
- break;
921
- }
922
- } else {
923
- showRow = (result) ? showRow : false;
924
- }
986
+ showRow = (result) ? showRow : false;
925
987
  }
926
988
  }
927
989
  $rows[rowIndex].style.display = (showRow ? '' : 'none');
@@ -932,6 +994,7 @@ ts.filter = {
932
994
  }
933
995
  childRow.toggle(showRow);
934
996
  }
997
+ cacheIndex++;
935
998
  }
936
999
  }
937
1000
  ts.processTbody(table, $tbody, false);
@@ -984,7 +1047,7 @@ ts.filter = {
984
1047
  arry = (ts.sortNatural) ? arry.sort(function(a, b) { return ts.sortNatural(a, b); }) : arry.sort(true);
985
1048
 
986
1049
  // Get curent filter value
987
- currentValue = c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]').val();
1050
+ currentValue = c.$table.find('thead').find('select.' + ts.css.filter + '[data-column="' + column + '"]').val();
988
1051
 
989
1052
  // build option list
990
1053
  for (indx = 0; indx < arry.length; indx++) {
@@ -993,7 +1056,7 @@ ts.filter = {
993
1056
  options += arry[indx] !== '' ? '<option value="' + txt + '"' + (currentValue === txt ? ' selected="selected"' : '') +
994
1057
  '>' + arry[indx] + '</option>' : '';
995
1058
  }
996
- c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]')[ updating ? 'html' : 'append' ](options);
1059
+ c.$table.find('thead').find('select.' + ts.css.filter + '[data-column="' + column + '"]')[ updating ? 'html' : 'append' ](options);
997
1060
  },
998
1061
  buildDefault: function(table, updating) {
999
1062
  var columnIndex, $header,
@@ -1012,39 +1075,75 @@ ts.filter = {
1012
1075
  }
1013
1076
  }
1014
1077
  },
1015
- searching: function(table, filter, external) {
1016
- if (typeof filter === 'undefined' || filter === true || external) {
1078
+ searching: function(table, filter, dontSkip) {
1079
+ if (typeof filter === 'undefined' || filter === true) {
1017
1080
  var wo = table.config.widgetOptions;
1018
1081
  // delay filtering
1019
1082
  clearTimeout(wo.searchTimer);
1020
1083
  wo.searchTimer = setTimeout(function() {
1021
- ts.filter.checkFilters(table, external || filter);
1084
+ ts.filter.checkFilters(table, filter, dontSkip );
1022
1085
  }, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
1023
1086
  } else {
1024
1087
  // skip delay
1025
- ts.filter.checkFilters(table, filter);
1088
+ ts.filter.checkFilters(table, filter, dontSkip);
1026
1089
  }
1027
1090
  }
1028
1091
  };
1029
1092
 
1030
- ts.getFilters = function(table) {
1031
- var c = table ? $(table)[0].config : {};
1032
- if (c && c.widgetOptions && !c.widgetOptions.filter_columnFilters) {
1033
- // no filter row
1093
+ ts.getFilters = function(table, getRaw, setFilters, skipFirst) {
1094
+ var i, $filters, $column,
1095
+ filters = false,
1096
+ c = table ? $(table)[0].config : '',
1097
+ wo = table ? c.widgetOptions : '';
1098
+ if (getRaw !== true && wo && !wo.filter_columnFilters) {
1034
1099
  return $(table).data('lastSearch');
1035
1100
  }
1036
- return c && c.$filters ? c.$filters.map(function(indx, el) {
1037
- return $(el).find('.tablesorter-filter').val() || '';
1038
- }).get() || [] : false;
1101
+ if (c) {
1102
+ if (c.$filters) {
1103
+ $filters = c.$filters.find('.' + ts.css.filter);
1104
+ }
1105
+ if (wo.filter_$externalFilters) {
1106
+ $filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
1107
+ }
1108
+ if ($filters && $filters.length) {
1109
+ filters = setFilters || [];
1110
+ for (i = 0; i < c.columns + 1; i++) {
1111
+ $column = $filters.filter('[data-column="' + (i === c.columns ? 'all' : i) + '"]');
1112
+ if ($column.length) {
1113
+ // move the latest search to the first slot in the array
1114
+ $column = $column.sort(function(a, b){
1115
+ return $(a).data('lastSearchTime') <= $(b).data('lastSearchTime');
1116
+ });
1117
+ if ($.isArray(setFilters)) {
1118
+ // skip first (latest input) to maintain cursor position while typing
1119
+ (skipFirst ? $column.slice(1) : $column).val( setFilters[i] ).trigger('change.tsfilter');
1120
+ } else {
1121
+ filters[i] = $column.val() || '';
1122
+ // don't change the first... it will move the cursor
1123
+ $column.slice(1).val( filters[i] );
1124
+ }
1125
+ // save any match input dynamically
1126
+ if (i === c.columns && $column.length) {
1127
+ wo.filter_$anyMatch = $column;
1128
+ }
1129
+ }
1130
+ }
1131
+ }
1132
+ }
1133
+ if (filters.length === 0) {
1134
+ filters = false;
1135
+ }
1136
+ return filters;
1039
1137
  };
1040
1138
 
1041
- ts.setFilters = function(table, filter, apply) {
1042
- var $table = $(table),
1043
- c = $table.length ? $table[0].config : {},
1044
- valid = c && c.$filters ? c.$filters.each(function(indx, el) {
1045
- $(el).find('.tablesorter-filter').val(filter[indx] || '');
1046
- }).trigger('change.tsfilter') || false : false;
1047
- if (apply) { $table.trigger('search', [filter, false]); }
1139
+ ts.setFilters = function(table, filter, apply, skipFirst) {
1140
+ var c = table ? $(table)[0].config : '',
1141
+ valid = ts.getFilters(table, true, filter, skipFirst);
1142
+ if (c && apply) {
1143
+ // ensure new set filters are applied, even if the search is the same
1144
+ c.lastCombinedFilter = null;
1145
+ c.$table.trigger('search', [filter, false]).trigger('filterFomatterUpdate');
1146
+ }
1048
1147
  return !!valid;
1049
1148
  };
1050
1149
 
@@ -1076,9 +1175,8 @@ ts.addWidget({
1076
1175
  $thead = $table.children('thead:first'),
1077
1176
  $win = $attach.length ? $attach : $(window),
1078
1177
  $header = $thead.children('tr').not('.sticky-false').children(),
1079
- innerHeader = '.tablesorter-header-inner',
1178
+ innerHeader = '.' + ts.css.headerIn,
1080
1179
  $tfoot = $table.find('tfoot'),
1081
- filterInputs = '.tablesorter-filter',
1082
1180
  $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
1083
1181
  stickyOffset = $attach.length ? 0 : $stickyOffset.length ?
1084
1182
  $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
@@ -1092,7 +1190,7 @@ ts.addWidget({
1092
1190
  visibility : 'hidden',
1093
1191
  zIndex : wo.stickyHeaders_zIndex ? wo.stickyHeaders_zIndex : 2
1094
1192
  }),
1095
- $stickyThead = $stickyTable.children('thead:first').addClass('tablesorter-stickyHeader ' + wo.stickyHeaders),
1193
+ $stickyThead = $stickyTable.children('thead:first').addClass(ts.css.sticky + ' ' + wo.stickyHeaders),
1096
1194
  $stickyCells,
1097
1195
  laststate = '',
1098
1196
  spacing = 0,
@@ -1108,8 +1206,8 @@ ts.addWidget({
1108
1206
  spacing = parseInt($header.eq(0).css('border-left-width'), 10) * 2;
1109
1207
  }
1110
1208
  $stickyTable.css({
1111
- left : $attach.length ? parseInt($attach.css('padding-left'), 10) +
1112
- parseInt($attach.css('margin-left'), 10) + parseInt($table.css('border-left-width'), 10) :
1209
+ left : $attach.length ? (parseInt($attach.css('padding-left'), 10) || 0) + parseInt(c.$table.css('padding-left'), 10) +
1210
+ parseInt(c.$table.css('margin-left'), 10) + parseInt($table.css('border-left-width'), 10) :
1113
1211
  $thead.offset().left - $win.scrollLeft() - spacing,
1114
1212
  width: $table.width()
1115
1213
  });
@@ -1139,7 +1237,7 @@ ts.addWidget({
1139
1237
  $stickyCells = $stickyThead.children().children();
1140
1238
  $stickyTable.css({ height:0, width:0, padding:0, margin:0, border:0 });
1141
1239
  // remove resizable block
1142
- $stickyCells.find('.tablesorter-resizer').remove();
1240
+ $stickyCells.find('.' + ts.css.resizer).remove();
1143
1241
  // update sticky header class names to match real header after sorting
1144
1242
  $table
1145
1243
  .addClass('hasStickyHeaders')
@@ -1159,27 +1257,11 @@ ts.addWidget({
1159
1257
  .bind('pagerComplete.tsSticky', function() {
1160
1258
  resizeHeader();
1161
1259
  });
1162
- // http://stackoverflow.com/questions/5312849/jquery-find-self;
1163
- $header.find(c.selectorSort).add( c.$headers.filter(c.selectorSort) ).each(function(indx) {
1164
- var $header = $(this),
1165
- // clicking on sticky will trigger sort
1166
- $cell = $stickyThead.children('tr.tablesorter-headerRow').children().eq(indx).bind('mouseup', function(event) {
1167
- $header.trigger(event, true); // external mouseup flag (click timer is ignored)
1168
- });
1169
- // prevent sticky header text selection
1170
- if (c.cancelSelection) {
1171
- $cell
1172
- .attr('unselectable', 'on')
1173
- .bind('selectstart', false)
1174
- .css({
1175
- 'user-select': 'none',
1176
- 'MozUserSelect': 'none'
1177
- });
1178
- }
1179
- });
1260
+
1261
+ ts.bindEvents(table, $stickyThead.children().children('.tablesorter-header'));
1262
+
1180
1263
  // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
1181
1264
  $table.after( $stickyTable );
1182
-
1183
1265
  // make it sticky!
1184
1266
  $win.bind('scroll.tsSticky resize.tsSticky', function(event) {
1185
1267
  if (!$table.is(':visible')) { return; } // fixes #278
@@ -1212,17 +1294,22 @@ ts.addWidget({
1212
1294
 
1213
1295
  // look for filter widget
1214
1296
  if ($table.hasClass('hasFilters')) {
1297
+ // scroll table into view after filtering, if sticky header is active - #482
1215
1298
  $table.bind('filterEnd', function() {
1216
1299
  // $(':focus') needs jQuery 1.6+
1217
- if ( $(document.activeElement).closest('thead')[0] !== $stickyThead[0] ) {
1218
- // don't update the stickyheader filter row if it already has focus
1219
- $stickyThead.find('.tablesorter-filter-row').children().each(function(indx) {
1220
- $(this).find(filterInputs).val( c.$filters.find(filterInputs).eq(indx).val() );
1221
- });
1300
+ var $td = $(document.activeElement).closest('td'),
1301
+ column = $td.parent().children().index($td);
1302
+ // only scroll if sticky header is active
1303
+ if ($stickyTable.hasClass(ts.css.stickyVis)) {
1304
+ // scroll to original table (not sticky clone)
1305
+ window.scrollTo(0, $table.position().top);
1306
+ // give same input/select focus
1307
+ if (column >= 0) {
1308
+ c.$filters.eq(column).find('a, select, input').filter(':visible').focus();
1309
+ }
1222
1310
  }
1223
1311
  });
1224
-
1225
- ts.filter.bindSearch( $table, $stickyCells.find('.tablesorter-filter').addClass('tablesorter-external-filter') );
1312
+ ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) );
1226
1313
  }
1227
1314
 
1228
1315
  $table.trigger('stickyHeadersInit');
@@ -1232,7 +1319,7 @@ ts.addWidget({
1232
1319
  c.$table
1233
1320
  .removeClass('hasStickyHeaders')
1234
1321
  .unbind('sortEnd.tsSticky pagerComplete.tsSticky')
1235
- .find('.tablesorter-stickyHeader').remove();
1322
+ .find('.' + ts.css.sticky).remove();
1236
1323
  if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table
1237
1324
  // don't unbind if any table on the page still has stickyheaders applied
1238
1325
  if (!$('.hasStickyHeaders').length) {
@@ -1264,7 +1351,8 @@ ts.addWidget({
1264
1351
  $next = null,
1265
1352
  fullWidth = Math.abs($table.parent().width() - $table.width()) < 20,
1266
1353
  stopResize = function() {
1267
- if (ts.storage && $target) {
1354
+ if (ts.storage && $target && $next) {
1355
+ storedSizes = {};
1268
1356
  storedSizes[$target.index()] = $target.width();
1269
1357
  storedSizes[$next.index()] = $next.width();
1270
1358
  $target.width( storedSizes[$target.index()] );
@@ -1298,9 +1386,9 @@ ts.addWidget({
1298
1386
  // add wrapper inside each cell to allow for positioning of the resizable target block
1299
1387
  $rows.each(function() {
1300
1388
  $column = $(this).children().not('.resizable-false');
1301
- if (!$(this).find('.tablesorter-wrapper').length) {
1389
+ if (!$(this).find('.' + ts.css.wrapper).length) {
1302
1390
  // Firefox needs this inner div to position the resizer correctly
1303
- $column.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
1391
+ $column.wrapInner('<div class="' + ts.css.wrapper + '" style="position:relative;height:100%;width:100%"></div>');
1304
1392
  }
1305
1393
  // don't include the last column of the row
1306
1394
  if (!wo.resizable_addLastColumn) { $column = $column.slice(0,-1); }
@@ -1311,8 +1399,8 @@ ts.addWidget({
1311
1399
  var $column = $(this),
1312
1400
  padding = parseInt($column.css('padding-right'), 10) + 10; // 10 is 1/2 of the 20px wide resizer grip
1313
1401
  $column
1314
- .find('.tablesorter-wrapper')
1315
- .append('<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;z-index:1;right:-' +
1402
+ .find('.' + ts.css.wrapper)
1403
+ .append('<div class="' + ts.css.resizer + '" style="cursor:w-resize;position:absolute;z-index:1;right:-' +
1316
1404
  padding + 'px;top:0;height:100%;width:20px;"></div>');
1317
1405
  })
1318
1406
  .bind('mousemove.tsresize', function(event) {
@@ -1330,7 +1418,7 @@ ts.addWidget({
1330
1418
  .bind('mouseup.tsresize', function() {
1331
1419
  stopResize();
1332
1420
  })
1333
- .find('.tablesorter-resizer,.tablesorter-resizer-grip')
1421
+ .find('.' + ts.css.resizer + ',.' + ts.css.grip)
1334
1422
  .bind('mousedown', function(event) {
1335
1423
  // save header cell and mouse position; closest() not supported by jQuery v1.2.6
1336
1424
  $target = $(event.target).closest('th');
@@ -1361,13 +1449,15 @@ ts.addWidget({
1361
1449
  .children('tr').children()
1362
1450
  .unbind('mousemove.tsresize mouseup.tsresize')
1363
1451
  // don't remove "tablesorter-wrapper" as uitheme uses it too
1364
- .find('.tablesorter-resizer,.tablesorter-resizer-grip').remove();
1452
+ .find('.' + ts.css.resizer + ',.' + ts.css.grip).remove();
1365
1453
  ts.resizableReset(table);
1366
1454
  }
1367
1455
  });
1368
1456
  ts.resizableReset = function(table) {
1369
- table.config.$headers.not('.resizable-false').css('width','');
1370
- if (ts.storage) { ts.storage(table, 'tablesorter-resizable', {}); }
1457
+ $(table).each(function(){
1458
+ this.config.$headers.not('.resizable-false').css('width','');
1459
+ if (ts.storage) { ts.storage(this, 'tablesorter-resizable', {}); }
1460
+ });
1371
1461
  };
1372
1462
 
1373
1463
  // Save table sort widget