jquery-tablesorter 1.10.10 → 1.11.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.
@@ -0,0 +1,134 @@
1
+ /*! Filter widget formatter functions - updated 4/22/2014 (v2.16.1-beta)
2
+ * requires: jQuery 1.7.2+, tableSorter 2.16+, filter widget 2.16+ and select2 v3.4.6+ plugin
3
+ */
4
+ /*jshint browser:true, jquery:true, unused:false */
5
+ /*global jQuery: false */
6
+ ;(function($){
7
+ "use strict";
8
+
9
+ var ts = $.tablesorter || {};
10
+ ts.filterFormatter = ts.filterFormatter || {};
11
+
12
+ /************************\
13
+ Select2 Filter Formatter
14
+ \************************/
15
+ ts.filterFormatter.select2 = function($cell, indx, select2Def) {
16
+ var o = $.extend({
17
+ // select2 filter formatter options
18
+ cellText : '', // Text (wrapped in a label element)
19
+ match : true, // adds "filter-match" to header
20
+ // include ANY select2 options below
21
+ multiple : true,
22
+ width : '100%'
23
+
24
+ }, select2Def ),
25
+ arry, data,
26
+ c = $cell.closest('table')[0].config,
27
+ wo = c.widgetOptions,
28
+ // Add a hidden input to hold the range values
29
+ $input = $('<input class="filter" type="hidden">')
30
+ .appendTo($cell)
31
+ // hidden filter update namespace trigger by filter widget
32
+ .bind('change' + c.namespace + 'filter', function(){
33
+ var val = this.value;
34
+ val = val.replace(/[/()$]/g, '').split('|');
35
+ updateSelect2(val);
36
+ }),
37
+ $header = c.$headers.filter('[data-column="' + indx + '"]:last'),
38
+ onlyAvail = $header.hasClass(wo.filter_onlyAvail),
39
+ $shcell = [],
40
+ match = o.match ? '' : '$',
41
+
42
+ // this function updates the hidden input and adds the current values to the header cell text
43
+ updateSelect2 = function(v, notrigger) {
44
+ v = typeof v === "undefined" || v === '' ? $cell.find('.select2').select2('val') || o.value || '' : v || '';
45
+ $input
46
+ // add equal to the beginning, so we filter exact numbers
47
+ .val( $.isArray(v) && v.length ? '/(' + (v || []).join(match + '|') + match + ')/' : '' )
48
+ .trigger( notrigger ? '' : 'search' ).end()
49
+ .find('.select2').select2('val', v);
50
+ // update sticky header cell
51
+ if ($shcell.length) {
52
+ $shcell
53
+ .find('.select2').select2('val', v);
54
+ }
55
+ },
56
+
57
+ // get options from table cell content or filter_selectSource (v2.16)
58
+ updateOptions = function(){
59
+ data = [];
60
+ arry = ts.filter.getOptionSource(c.$table[0], indx, onlyAvail) || [];
61
+ // build select2 data option
62
+ $.each(arry, function(i,v){
63
+ data.push({id: v, text: v});
64
+ });
65
+ o.data = data;
66
+ };
67
+
68
+ // get filter-match class from option
69
+ $header.toggleClass('filter-match', o.match);
70
+ if (o.cellText) {
71
+ $cell.prepend('<label>' + o.cellText + '</label>');
72
+ }
73
+
74
+ // don't add default in table options if either ajax or
75
+ // data options are already defined
76
+ if (!(o.ajax && !$.isEmptyObject(o.ajax)) && !o.data) {
77
+ updateOptions();
78
+ if (onlyAvail) {
79
+ c.$table.bind('filterEnd', function(){
80
+ updateOptions();
81
+ $cell.add($shcell).find('.select2').select2(o);
82
+ });
83
+ }
84
+ }
85
+
86
+ // add a select2 hidden input!
87
+ $('<input class="select2 select2-' + indx + '" type="hidden" />')
88
+ .val(o.value)
89
+ .appendTo($cell)
90
+ .select2(o)
91
+ .bind('change', function(){
92
+ updateSelect2();
93
+ });
94
+
95
+ // update select2 from filter hidden input, in case of saved filters
96
+ c.$table.bind('filterFomatterUpdate', function(){
97
+ // value = '/(x$|y$)/' => 'x,y'
98
+ var val = c.$table.data('lastSearch')[indx] || '';
99
+ val = val.replace(/[/()$]/g, '').split('|');
100
+ $cell.find('.select2').select2('val', val);
101
+ updateSelect2(val, true);
102
+ });
103
+
104
+ // has sticky headers?
105
+ c.$table.bind('stickyHeadersInit', function(){
106
+ $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
107
+ // add a select2!
108
+ $('<input class="select2 select2-' + indx + '" type="hidden">')
109
+ .val(o.value)
110
+ .appendTo($shcell)
111
+ .select2(o)
112
+ .bind('change', function(){
113
+ $cell.find('.select2').select2('val', $shcell.find('.select2').select2('val') );
114
+ updateSelect2();
115
+ });
116
+ if (o.cellText) {
117
+ $shcell.prepend('<label>' + o.cellText + '</label>');
118
+ }
119
+
120
+ });
121
+
122
+ // on reset
123
+ c.$table.bind('filterReset', function(){
124
+ $cell.find('.select2').select2('val', o.value || '');
125
+ setTimeout(function(){
126
+ updateSelect2();
127
+ }, 0);
128
+ });
129
+
130
+ updateSelect2();
131
+ return $input;
132
+ };
133
+
134
+ })(jQuery);
@@ -1,4 +1,4 @@
1
- /*! Filter widget formatter functions - updated 3/12/2014 (v2.15.9)
1
+ /*! Filter widget formatter functions - updated 4/23/2014 (v2.16.0)
2
2
  * requires: tableSorter 2.15+ and jQuery 1.4.3+
3
3
  *
4
4
  * uiSpinner (jQuery UI spinner)
@@ -20,6 +20,7 @@ var ts = $.tablesorter || {},
20
20
  // compare option selector class name (jQuery selector)
21
21
  compareSelect = '.compare-select',
22
22
 
23
+
23
24
  tsff = ts.filterFormatter = {
24
25
 
25
26
  addCompare: function($cell, indx, options){
@@ -87,7 +88,7 @@ tsff = ts.filterFormatter = {
87
88
  v = ui && ui.value && ts.formatFloat((ui.value + '').replace(/[><=]/g,'')) ||
88
89
  $cell.find('.spinner').val() || o.value,
89
90
  compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '',
90
- searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed : true;
91
+ searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '' : true;
91
92
  if (o.addToggle) {
92
93
  chkd = $cell.find('.toggle').is(':checked');
93
94
  }
@@ -244,7 +245,7 @@ tsff = ts.filterFormatter = {
244
245
  val = o.compare ? v : v === o.min ? o.allText : v,
245
246
  compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '',
246
247
  result = compare + val,
247
- searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed : true;
248
+ searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '' : true;
248
249
  if (o.valueToHeader) {
249
250
  // add range indication to the header cell above!
250
251
  $cell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(' (' + result + ')');
@@ -391,7 +392,7 @@ tsff = ts.filterFormatter = {
391
392
  result = val[0] + ' - ' + val[1],
392
393
  // make range an empty string if entire range is covered so the filter row will hide (if set)
393
394
  range = val[0] === o.min && val[1] === o.max ? '' : result,
394
- searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed : true;
395
+ searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '': true;
395
396
  if (o.valueToHeader) {
396
397
  // add range indication to the header cell above (if not using the css method)!
397
398
  $cell.closest('thead').find('th[data-column=' + indx + ']').find('.currange').html(' (' + result + ')');
@@ -511,12 +512,12 @@ tsff = ts.filterFormatter = {
511
512
  t, $shcell = [],
512
513
 
513
514
  // this function updates the hidden input
514
- date1Compare = function(v, notrigger) {
515
+ date1Compare = function(notrigger) {
515
516
  var date, query,
516
- getdate = v || $date.datepicker('getDate') || '',
517
+ getdate = $date.datepicker('getDate') || '',
517
518
  compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '',
518
- searchType = c.$table[0].hasInitialized ? o.delayed : true;
519
- $date.datepicker('setDate', getdate === '' ? o.defaultDate || '' : getdate);
519
+ searchType = c.$table[0].hasInitialized ? o.delayed || '': true;
520
+ $date.datepicker('setDate', getdate === '' ? '' : getdate);
520
521
  if (getdate === '') { notrigger = false; }
521
522
  date = $date.datepicker('getDate');
522
523
  query = date ? ( o.endOfDay && /<=/.test(compare) ? date.setHours(23, 59, 59) : date.getTime() ) || '' : '';
@@ -539,7 +540,7 @@ tsff = ts.filterFormatter = {
539
540
 
540
541
  // Add date range picker
541
542
  t = '<input type="text" class="date date' + indx + '" placeholder="' +
542
- ($hdr.data('placeholder') || $hdr.attr('data-placeholder') || '') + '" />';
543
+ ($hdr.data('placeholder') || $hdr.attr('data-placeholder') || c.widgetOptions.filter_placeholder.search || '') + '" />';
543
544
  $date = $(t).appendTo($cell);
544
545
 
545
546
  // add callbacks; preserve added callbacks
@@ -556,7 +557,7 @@ tsff = ts.filterFormatter = {
556
557
  if ($.isArray(o.compare)) {
557
558
  $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] );
558
559
  }
559
- $cell.add($shcell).find('.date').val(o.defaultDate).datepicker('setDate', '');
560
+ $cell.add($shcell).find('.date').val(o.defaultDate).datepicker('setDate', o.defaultDate);
560
561
  setTimeout(function(){
561
562
  date1Compare();
562
563
  }, 0);
@@ -568,15 +569,17 @@ tsff = ts.filterFormatter = {
568
569
  if (/\s+-\s+/.test(v)) {
569
570
  // date range found; assume an exact match on one day
570
571
  $cell.find(compareSelect).val('=');
571
- num = new Date ( Number( v.split(/\s+-\s+/)[0] ) );
572
+ num = v.split(/\s+-\s+/)[0];
572
573
  $date.datepicker( 'setDate', num );
573
574
  } else {
574
575
  num = (tsff.updateCompare($cell, $input, o)[1]).toString() || '';
575
576
  // differeniate 1388556000000 from 1/1/2014 using \d{5} regex
576
- num = num !== '' ? new Date( /\d{5}/g.test(num) ? Number(num) : num ) || '' : '';
577
+ num = num !== '' ? /\d{5}/g.test(num) ? Number(num) : num || '' : '';
577
578
  }
578
579
  $cell.add($shcell).find('.date').datepicker( 'setDate', num );
579
- date1Compare(num, true);
580
+ setTimeout(function(){
581
+ date1Compare(true);
582
+ }, 0);
580
583
  });
581
584
 
582
585
  if (o.compare) {
@@ -628,8 +631,11 @@ tsff = ts.filterFormatter = {
628
631
  changeYear : true,
629
632
  numberOfMonths : 1
630
633
  }, defDate),
631
- t, closeTo, closeFrom, $shcell = [],
634
+ t, closeDate, $shcell = [],
632
635
  c = $cell.closest('table')[0].config,
636
+ validDate = function(d){
637
+ return d instanceof Date && isFinite(d);
638
+ },
633
639
  // Add a hidden input to hold the range values
634
640
  $input = $('<input class="dateRange" type="hidden">')
635
641
  .appendTo($cell)
@@ -639,69 +645,60 @@ tsff = ts.filterFormatter = {
639
645
  if (v.match(' - ')) {
640
646
  v = v.split(' - ');
641
647
  $cell.find('.dateTo').val(v[1]);
642
- closeFrom(v[0]);
648
+ closeDate(v[0]);
643
649
  } else if (v.match('>=')) {
644
- closeFrom( v.replace('>=', '') );
650
+ closeDate( v.replace('>=', '') );
645
651
  } else if (v.match('<=')) {
646
- closeTo( v.replace('<=', '') );
652
+ closeDate( v.replace('<=', '') );
647
653
  }
648
- });
654
+ }),
649
655
 
650
656
  // make sure we're using parsed dates in the search
651
- $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed');
657
+ $hdr = $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed');
652
658
  // Add date range picker
653
- t = '<label>' + o.textFrom + '</label><input type="text" class="dateFrom" /><label>' + o.textTo + '</label><input type="text" class="dateTo" />';
659
+ t = '<label>' + o.textFrom + '</label><input type="text" class="dateFrom" placeholder="' +
660
+ ($hdr.data('placeholderFrom') || $hdr.attr('data-placeholder-from') || c.widgetOptions.filter_placeholder.from || '') + '" />' +
661
+ '<label>' + o.textTo + '</label><input type="text" class="dateTo" placeholder="' +
662
+ ($hdr.data('placeholderTo') || $hdr.attr('data-placeholder-to') || c.widgetOptions.filter_placeholder.to || '') + '" />';
654
663
  $(t).appendTo($cell);
655
664
 
656
665
  // add callbacks; preserve added callbacks
657
666
  o.oldonClose = o.onClose;
658
667
 
659
- var localfrom = o.defaultDate = o.from || o.defaultDate;
660
-
661
- closeFrom = o.onClose = function( selectedDate, ui ) {
668
+ closeDate = o.onClose = function( selectedDate, ui ) {
662
669
  var range,
663
- from = new Date( $cell.find('.dateFrom').datepicker('getDate') ).getTime() || '',
664
- to = $cell.find('.dateTo').datepicker('getDate') || '';
665
- to = to ? ( o.endOfDay ? to.setHours(23, 59, 59) : to.getTime() ) || '' : '';
670
+ from = $cell.find('.dateFrom').datepicker('getDate'),
671
+ to = $cell.find('.dateTo').datepicker('getDate');
672
+ from = validDate(from) ? from.getTime() : '';
673
+ to = validDate(to) ? ( o.endOfDay ? to.setHours(23, 59, 59) : to.getTime() ) || '' : '';
666
674
  range = from ? ( to ? from + ' - ' + to : '>=' + from ) : (to ? '<=' + to : '');
667
- $cell
675
+ $cell.add( $shcell )
668
676
  .find('.dateRange').val(range)
669
- .trigger('search').end()
670
- .find('.dateTo').datepicker('option', 'minDate', selectedDate ).end()
671
- .find('.dateFrom').val(selectedDate);
672
-
673
- // update sticky header cell
674
- if ($shcell.length) {
675
- $shcell
676
- .find('.dateTo').datepicker('option', 'minDate', selectedDate ).end()
677
- .find('.dateFrom').val(selectedDate);
677
+ .trigger('search');
678
+ // date picker needs date objects
679
+ from = from ? new Date(from) : '';
680
+ to = to ? new Date(to) : '';
681
+
682
+ if (/<=/.test(range)) {
683
+ $cell.add( $shcell )
684
+ .find('.dateFrom').datepicker('option', 'maxDate', to ).end()
685
+ .find('.dateTo').datepicker('option', 'minDate', null).datepicker('setDate', to);
686
+ } else if (/>=/.test(range)) {
687
+ $cell.add( $shcell )
688
+ .find('.dateFrom').datepicker('option', 'maxDate', null).datepicker('setDate', from).end()
689
+ .find('.dateTo').datepicker('option', 'minDate', from );
690
+ } else {
691
+ $cell.add( $shcell )
692
+ .find('.dateFrom').datepicker('option', 'maxDate', null).datepicker('setDate', from ).end()
693
+ .find('.dateTo').datepicker('option', 'minDate', null).datepicker('setDate', to);
678
694
  }
695
+
679
696
  if (typeof o.oldonClose === 'function') { o.oldonClose(selectedDate, ui); }
680
697
  };
681
698
 
699
+ o.defaultDate = o.from || '';
682
700
  $cell.find('.dateFrom').datepicker(o);
683
-
684
701
  o.defaultDate = o.to || '+7d'; // set to date +7 days from today (if not defined)
685
- closeTo = o.onClose = function( selectedDate, ui ) {
686
- var range,
687
- from = new Date( $cell.find('.dateFrom').datepicker('getDate') ).getTime() || '',
688
- to = $cell.find('.dateTo').datepicker('getDate') || '';
689
- to = to ? ( o.endOfDay ? to.setHours(23, 59, 59) : to.getTime() ) || '' : '';
690
- range = from ? ( to ? from + ' - ' + to : '>=' + from ) : (to ? '<=' + to : '');
691
- $cell
692
- .find('.dateRange').val(range)
693
- .trigger('search').end()
694
- .find('.dateFrom').datepicker('option', 'maxDate', selectedDate ).end()
695
- .find('.dateTo').val(selectedDate);
696
-
697
- // update sticky header cell
698
- if ($shcell.length) {
699
- $shcell
700
- .find('.dateFrom').datepicker('option', 'maxDate', selectedDate ).end()
701
- .find('.dateTo').val(selectedDate);
702
- }
703
- if (typeof o.oldonClose === 'function') { o.oldonClose(selectedDate, ui); }
704
- };
705
702
  $cell.find('.dateTo').datepicker(o);
706
703
 
707
704
  // update date compare from hidden input, in case of saved filters
@@ -709,7 +706,6 @@ tsff = ts.filterFormatter = {
709
706
  var val = $input.val() || '',
710
707
  from = '',
711
708
  to = '';
712
-
713
709
  // date range
714
710
  if (/\s+-\s+/.test(val)){
715
711
  val = val.split(/\s+-\s+/) || [];
@@ -717,14 +713,17 @@ tsff = ts.filterFormatter = {
717
713
  to = val[1] || '';
718
714
  } else if (/>=/.test(val)) {
719
715
  // greater than date (to date empty)
720
- from = new Date(Number( val.replace(/>=/, '') )) || '';
716
+ from = val.replace(/>=/, '') || '';
721
717
  } else if (/<=/.test(val)) {
722
718
  // less than date (from date empty)
723
- to = new Date(Number( val.replace(/<=/, '') )) || '';
719
+ to = val.replace(/<=/, '') || '';
724
720
  }
725
721
  $cell.add($shcell).find('.dateFrom').datepicker('setDate', from);
726
722
  $cell.add($shcell).find('.dateTo').datepicker('setDate', to);
727
- closeTo(to);
723
+ // give datepicker time to process
724
+ setTimeout(function(){
725
+ closeDate();
726
+ }, 0);
728
727
  });
729
728
 
730
729
  // has sticky headers?
@@ -733,18 +732,21 @@ tsff = ts.filterFormatter = {
733
732
  $shcell.append(t);
734
733
 
735
734
  // add a jQuery datepicker!
736
- o.onClose = closeTo;
735
+ o.defaultDate = o.from || '';
736
+ $shcell.find('.dateFrom').datepicker(o);
737
+
738
+ o.defaultDate = o.to || '+7d';
737
739
  $shcell.find('.dateTo').datepicker(o);
738
740
 
739
- o.defaultDate = localfrom;
740
- o.onClose = closeFrom;
741
- $shcell.find('.dateFrom').datepicker(o);
742
741
  });
743
742
 
744
743
  // on reset
745
744
  $cell.closest('table').bind('filterReset', function(){
746
745
  $cell.add($shcell).find('.dateFrom').val('').datepicker('setDate', o.from );
747
746
  $cell.add($shcell).find('.dateTo').val('').datepicker('setDate', o.to );
747
+ setTimeout(function(){
748
+ closeDate();
749
+ }, 0);
748
750
  });
749
751
 
750
752
  // return the hidden input so the filter widget has a reference to it
@@ -781,7 +783,7 @@ tsff = ts.filterFormatter = {
781
783
  var chkd = o.addToggle ? $cell.find('.toggle').is(':checked') : true,
782
784
  v = $cell.find('.number').val(),
783
785
  compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '',
784
- searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) : true;
786
+ searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) || '' : true;
785
787
  $input
786
788
  // add equal to the beginning, so we filter exact numbers
787
789
  .val( !o.addToggle || chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' )
@@ -913,7 +915,7 @@ tsff = ts.filterFormatter = {
913
915
  v = ( typeof v === "undefined" ? $input.val() : v ).toString().replace(/[<>=]/g,'') || o.value;
914
916
  var compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '',
915
917
  t = ' (' + (compare ? compare + v : v == o.min ? o.allText : v) + ')',
916
- searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) : true;
918
+ searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) || '' : true;
917
919
  $cell.find('input[type=hidden]')
918
920
  // add equal to the beginning, so we filter exact numbers
919
921
  .val( ( compare ? compare + v : ( v == o.min ? '' : ( o.exactMatch ? '=' : '' ) + v ) ) )
@@ -1,4 +1,4 @@
1
- /*! tableSorter 2.15+ widgets - updated 4/3/2014 (v2.15.13)
1
+ /*! tableSorter 2.16+ widgets - updated 4/23/2014 (v2.16.0)
2
2
  *
3
3
  * Column Styles
4
4
  * Column Filters
@@ -360,9 +360,11 @@ ts.addWidget({
360
360
  filter_ignoreCase : true, // if true, make all searches case-insensitive
361
361
  filter_liveSearch : true, // if true, search column content while the user types (with a delay)
362
362
  filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available (visible) options within the drop down
363
+ filter_placeholder : { search : '', select : '' }, // default placeholder text (overridden by any header "data-placeholder" setting)
363
364
  filter_reset : null, // jQuery selector string of an element used to reset the filters
364
365
  filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
365
366
  filter_searchDelay : 300, // typing delay in milliseconds before starting a search
367
+ filter_selectSource : null, // include a function to return an array of values to be added to the column filter select
366
368
  filter_startsWith : false, // if true, filter start from the beginning of the cell contents
367
369
  filter_useParsedData : false, // filter all data using parsed content
368
370
  filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used.
@@ -584,12 +586,20 @@ ts.filter = {
584
586
 
585
587
  // reset button/link
586
588
  if (wo.filter_reset) {
587
- $(document)
588
- .undelegate(wo.filter_reset, 'click.tsfilter')
589
- .delegate(wo.filter_reset, 'click.tsfilter', function() {
590
- // trigger a reset event, so other functions (filterFormatter) know when to reset
591
- c.$table.trigger('filterReset');
592
- });
589
+ if (wo.filter_reset instanceof $) {
590
+ // reset contains a jQuery object, bind to it
591
+ wo.filter_reset.click(function(){
592
+ c.$table.trigger('filterReset');
593
+ });
594
+ } else if ($(wo.filter_reset).length) {
595
+ // reset is a jQuery selector, use event delegation
596
+ $(document)
597
+ .undelegate(wo.filter_reset, 'click.tsfilter')
598
+ .delegate(wo.filter_reset, 'click.tsfilter', function() {
599
+ // trigger a reset event, so other functions (filterFormatter) know when to reset
600
+ c.$table.trigger('filterReset');
601
+ });
602
+ }
593
603
  }
594
604
  if (wo.filter_functions) {
595
605
  // column = column # (string)
@@ -604,7 +614,7 @@ ts.filter = {
604
614
  for (string in wo.filter_functions[column]) {
605
615
  if (typeof string === 'string') {
606
616
  options += options === '' ?
607
- '<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || '') + '</option>' : '';
617
+ '<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.select || '') + '</option>' : '';
608
618
  options += '<option value="' + string + '">' + string + '</option>';
609
619
  }
610
620
  }
@@ -715,7 +725,7 @@ ts.filter = {
715
725
  buildFilter = $('<input type="search">').appendTo( c.$filters.eq(column) );
716
726
  }
717
727
  if (buildFilter) {
718
- buildFilter.attr('placeholder', $header.data('placeholder') || $header.attr('data-placeholder') || '');
728
+ buildFilter.attr('placeholder', $header.data('placeholder') || $header.attr('data-placeholder') || wo.filter_placeholder.search || '');
719
729
  }
720
730
  }
721
731
  if (buildFilter) {
@@ -725,7 +735,7 @@ ts.filter = {
725
735
  wo.filter_cssFilter ) || '';
726
736
  buildFilter.addClass( ts.css.filter + ' ' + name ).attr('data-column', column);
727
737
  if (disabled) {
728
- buildFilter.addClass('disabled')[0].disabled = true; // disabled!
738
+ buildFilter.attr('placeholder', '').addClass('disabled')[0].disabled = true; // disabled!
729
739
  }
730
740
  }
731
741
  }
@@ -753,7 +763,7 @@ ts.filter = {
753
763
  .attr('data-lastSearchTime', new Date().getTime())
754
764
  .unbind('keyup search change '.split(' ').join(c.namespace + 'filter '))
755
765
  // include change for select - fixes #473
756
- .bind('keyup search change '.split(' ').join(c.namespace + 'filter '), function(event, filters) {
766
+ .bind('keyup search change '.split(' ').join(c.namespace + 'filter '), function(event) {
757
767
  $(this).attr('data-lastSearchTime', new Date().getTime());
758
768
  // emulate what webkit does.... escape clears the filter
759
769
  if (event.which === 27) {
@@ -865,20 +875,23 @@ ts.filter = {
865
875
  if ($tbodies.eq(tbodyIndex).hasClass(c.cssInfoBlock || ts.css.info)) { continue; } // ignore info blocks, issue #264
866
876
  $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true);
867
877
  // skip child rows & widget added (removable) rows - fixes #448 thanks to @hempel!
868
- $rows = $tbody.children('tr').not(c.selectorRemove);
878
+ // $rows = $tbody.children('tr').not(c.selectorRemove);
879
+ columnIndex = c.columns;
880
+ // convert stored rows into a jQuery object
881
+ $rows = true ? $( $.map(c.cache[tbodyIndex].normalized, function(el){ return el[columnIndex].$row.get(); }) ) : $tbody.children('tr').not(c.selectorRemove);
869
882
  len = $rows.length;
870
883
  if (combinedFilters === '' || wo.filter_serversideFiltering) {
871
- $tbody.children().removeClass(wo.filter_filteredRow).not('.' + c.cssChildRow).show();
884
+ $rows.removeClass(wo.filter_filteredRow).not('.' + c.cssChildRow).show();
872
885
  } else {
873
886
  // optimize searching only through already filtered rows - see #313
874
887
  searchFiltered = true;
875
888
  lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
876
889
  $.each(filters, function(indx, val) {
877
890
  // check for changes from beginning of filter; but ignore if there is a logical "or" in the string
878
- searchFiltered = (val || '').indexOf(lastSearch[indx] || '') === 0 && searchFiltered && !/(\s+or\s+|\|)/g.test(val || '');
891
+ searchFiltered = (val || '').indexOf(lastSearch[indx]) === 0 && searchFiltered && !/(\s+or\s+|\|)/g.test(val || '');
879
892
  });
880
893
  // can't search when all rows are hidden - this happens when looking for exact matches
881
- if (searchFiltered && $rows.filter(':visible').length === 0) { searchFiltered = false; }
894
+ if (searchFiltered && $rows.not('.' + wo.filter_filteredRow).length === 0) { searchFiltered = false; }
882
895
  if ((wo.filter_$anyMatch && wo.filter_$anyMatch.length) || filters[c.columns]) {
883
896
  anyMatch = wo.filter_$anyMatch && wo.filter_$anyMatch.val() || filters[c.columns] || '';
884
897
  if (c.sortLocaleCompare) {
@@ -887,7 +900,6 @@ ts.filter = {
887
900
  }
888
901
  iAnyMatch = anyMatch.toLowerCase();
889
902
  }
890
-
891
903
  // loop through the rows
892
904
  cacheIndex = 0;
893
905
  for (rowIndex = 0; rowIndex < len; rowIndex++) {
@@ -902,7 +914,7 @@ ts.filter = {
902
914
  // checked here so the option can be changed dynamically
903
915
  childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
904
916
  childRowText = wo.filter_ignoreCase ? childRowText.toLocaleLowerCase() : childRowText;
905
- $cells = $rows.eq(rowIndex).children('td');
917
+ $cells = $rows.eq(rowIndex).children();
906
918
 
907
919
  if (anyMatch) {
908
920
  rowArray = $cells.map(function(i){
@@ -919,7 +931,7 @@ ts.filter = {
919
931
  }).get();
920
932
  rowText = rowArray.join(' ');
921
933
  iRowText = rowText.toLowerCase();
922
- rowCache = c.cache[tbodyIndex].normalized[cacheIndex].join(' ');
934
+ rowCache = c.cache[tbodyIndex].normalized[cacheIndex].slice(0,-1).join(' ');
923
935
  filterMatched = null;
924
936
  $.each(ts.filter.types, function(type, typeFunction) {
925
937
  if ($.inArray(type, anyMatchNotAllowedTypes) < 0) {
@@ -1010,47 +1022,80 @@ ts.filter = {
1010
1022
  if (c.debug) {
1011
1023
  ts.benchmark("Completed filter widget search", time);
1012
1024
  }
1013
- c.$table.trigger('applyWidgets'); // make sure zebra widget is applied
1014
1025
  c.$table.trigger('filterEnd');
1026
+ setTimeout(function(){
1027
+ c.$table.trigger('applyWidgets'); // make sure zebra widget is applied
1028
+ }, 0);
1015
1029
  },
1016
- buildSelect: function(table, column, updating, onlyavail) {
1017
- if (!table.config.cache || $.isEmptyObject(table.config.cache)) { return; }
1018
- column = parseInt(column, 10);
1019
- var indx, rowIndex, tbodyIndex, len, currentValue, txt, $filters,
1020
- c = table.config,
1030
+ getOptionSource: function(table, column, onlyAvail) {
1031
+ var c = table.config,
1021
1032
  wo = c.widgetOptions,
1022
- $tbodies = c.$tbodies,
1023
- arry = [],
1024
- node = c.$headers.filter('[data-column="' + column + '"]:last'),
1025
- // t.data('placeholder') won't work in jQuery older than 1.4.3
1026
- options = '<option value="">' + ( node.data('placeholder') || node.attr('data-placeholder') || '' ) + '</option>';
1027
- for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
1028
- len = c.cache[tbodyIndex].row.length;
1029
- // loop through the rows
1030
- for (rowIndex = 0; rowIndex < len; rowIndex++) {
1031
- // check if has class filtered
1032
- if (onlyavail && c.cache[tbodyIndex].row[rowIndex][0].className.match(wo.filter_filteredRow)) { continue; }
1033
- // get non-normalized cell content
1034
- if (wo.filter_useParsedData) {
1035
- arry.push( '' + c.cache[tbodyIndex].normalized[rowIndex][column] );
1036
- } else {
1037
- node = c.cache[tbodyIndex].row[rowIndex][0].cells[column];
1038
- if (node) {
1039
- arry.push( $.trim( node.textContent || node.innerText || $(node).text() ) );
1040
- }
1041
- }
1042
- }
1033
+ arry = false,
1034
+ source = wo.filter_selectSource;
1035
+
1036
+ // filter select source option
1037
+ if ($.isFunction(source)) {
1038
+ // OVERALL source
1039
+ arry = source(table, column, onlyAvail);
1040
+ } else if ($.type(source) === 'object' && source.hasOwnProperty(column)) {
1041
+ // custom select source function for a SPECIFIC COLUMN
1042
+ arry = source[column](table, column, onlyAvail);
1043
1043
  }
1044
+ if (arry === false) {
1045
+ // fall back to original method
1046
+ arry = ts.filter.getOptions(table, column, onlyAvail);
1047
+ }
1048
+
1044
1049
  // get unique elements and sort the list
1045
1050
  // if $.tablesorter.sortText exists (not in the original tablesorter),
1046
1051
  // then natural sort the list otherwise use a basic sort
1047
1052
  arry = $.grep(arry, function(value, indx) {
1048
1053
  return $.inArray(value, arry) === indx;
1049
1054
  });
1050
- arry = (ts.sortNatural) ? arry.sort(function(a, b) { return ts.sortNatural(a, b); }) : arry.sort(true);
1051
-
1052
- // Get curent filter value
1053
- currentValue = c.$table.find('thead').find('select.' + ts.css.filter + '[data-column="' + column + '"]').val();
1055
+ return (ts.sortNatural) ? arry.sort(function(a, b) { return ts.sortNatural(a, b); }) : arry.sort(true);
1056
+ },
1057
+ getOptions: function(table, column, onlyAvail) {
1058
+ var rowIndex, tbodyIndex, len, row, cache, cell,
1059
+ c = table.config,
1060
+ wo = c.widgetOptions,
1061
+ $tbodies = c.$table.children('tbody'),
1062
+ arry = [];
1063
+ for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
1064
+ if (!$tbodies.eq(tbodyIndex).hasClass(c.cssInfoBlock)) {
1065
+ cache = c.cache[tbodyIndex];
1066
+ len = c.cache[tbodyIndex].normalized.length;
1067
+ // loop through the rows
1068
+ for (rowIndex = 0; rowIndex < len; rowIndex++) {
1069
+ // get cached row from cache.row (old) or row data object (new; last item in normalized array)
1070
+ row = cache.row ? cache.row[rowIndex] : cache.normalized[rowIndex][c.columns].$row[0];
1071
+ // check if has class filtered
1072
+ if (onlyAvail && row.className.match(wo.filter_filteredRow)) { continue; }
1073
+ // get non-normalized cell content
1074
+ if (wo.filter_useParsedData) {
1075
+ arry.push( '' + cache.normalized[rowIndex][column] );
1076
+ } else {
1077
+ cell = row.cells[column];
1078
+ if (cell) {
1079
+ arry.push( $.trim( cell.textContent || cell.innerText || $(cell).text() ) );
1080
+ }
1081
+ }
1082
+ }
1083
+ }
1084
+ }
1085
+ return arry;
1086
+ },
1087
+ buildSelect: function(table, column, updating, onlyAvail) {
1088
+ if (!table.config.cache || $.isEmptyObject(table.config.cache)) { return; }
1089
+ column = parseInt(column, 10);
1090
+ var indx, txt, $filters,
1091
+ c = table.config,
1092
+ wo = c.widgetOptions,
1093
+ node = c.$headers.filter('[data-column="' + column + '"]:last'),
1094
+ // t.data('placeholder') won't work in jQuery older than 1.4.3
1095
+ options = '<option value="">' + ( node.data('placeholder') || node.attr('data-placeholder') || wo.filter_placeholder.select || '' ) + '</option>',
1096
+ arry = ts.filter.getOptionSource(table, column, onlyAvail),
1097
+ // Get curent filter value
1098
+ currentValue = c.$table.find('thead').find('select.' + ts.css.filter + '[data-column="' + column + '"]').val();
1054
1099
 
1055
1100
  // build option list
1056
1101
  for (indx = 0; indx < arry.length; indx++) {
@@ -1150,7 +1195,8 @@ ts.setFilters = function(table, filter, apply, skipFirst) {
1150
1195
  if (c && apply) {
1151
1196
  // ensure new set filters are applied, even if the search is the same
1152
1197
  c.lastCombinedFilter = null;
1153
- c.$table.trigger('search', [filter, false]).trigger('filterFomatterUpdate');
1198
+ ts.filter.searching(c.$table[0], filter, skipFirst);
1199
+ c.$table.trigger('filterFomatterUpdate');
1154
1200
  }
1155
1201
  return !!valid;
1156
1202
  };
@@ -1235,7 +1281,8 @@ ts.addWidget({
1235
1281
  if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; }
1236
1282
  // clear out cloned table, except for sticky header
1237
1283
  // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing
1238
- $stickyTable.find('thead:gt(0), tr.sticky-false, tbody, tfoot').hide();
1284
+ $stickyTable.find('thead:gt(0), tr.sticky-false').hide();
1285
+ $stickyTable.find('tbody, tfoot').remove();
1239
1286
  if (!wo.stickyHeaders_includeCaption) {
1240
1287
  $stickyTable.find('caption').remove();
1241
1288
  } else {