jquery-tablesorter 1.17.2 → 1.17.3

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +76 -71
  5. data/vendor/assets/javascripts/jquery-tablesorter/extras/jquery.dragtable.mod.js +1 -1
  6. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.combined.js +2647 -2576
  7. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +174 -119
  8. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +2487 -2471
  9. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-extract.js +15 -15
  10. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-iso8601.js +1 -1
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-month.js +4 -4
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-range.js +1 -1
  13. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-two-digit-year.js +12 -12
  14. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-weekday.js +4 -4
  15. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date.js +1 -1
  16. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-duration.js +1 -1
  17. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-feet-inch-fraction.js +6 -6
  18. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-file-type.js +22 -22
  19. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-globalize.js +1 -1
  20. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-ignore-articles.js +15 -15
  21. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-image.js +3 -3
  22. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +10 -3
  23. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-metric.js +2 -2
  24. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-named-numbers.js +3 -3
  25. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-network.js +1 -1
  26. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-roman.js +4 -4
  27. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-alignChar.js +122 -121
  28. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-build-table.js +13 -13
  29. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-chart.js +2 -2
  30. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columnSelector.js +324 -324
  31. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columns.js +60 -60
  32. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +219 -219
  33. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter-formatter-html5.js +360 -361
  34. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter-formatter-jui.js +666 -666
  35. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter-formatter-select2.js +124 -124
  36. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter-type-insideRange.js +1 -1
  37. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter.js +1448 -1433
  38. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-formatter.js +1 -1
  39. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +213 -213
  40. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-headerTitles.js +3 -3
  41. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +271 -216
  42. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-output.js +339 -320
  43. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +1057 -1045
  44. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-print.js +109 -109
  45. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-reflow.js +114 -115
  46. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-resizable.js +360 -359
  47. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-saveSort.js +59 -59
  48. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +818 -806
  49. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-sort2Hash.js +128 -0
  50. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-sortTbodies.js +195 -195
  51. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-staticRow.js +90 -90
  52. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-stickyHeaders.js +257 -257
  53. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-storage.js +76 -76
  54. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-uitheme.js +170 -170
  55. metadata +3 -3
  56. data/vendor/assets/javascripts/jquery-tablesorter/extras/jquery.quicksearch.js +0 -195
@@ -4,146 +4,146 @@
4
4
  /*jshint browser:true, jquery:true, unused:false */
5
5
  /*global jQuery: false */
6
6
  ;(function($){
7
- "use strict";
7
+ 'use strict';
8
8
 
9
- var ts = $.tablesorter || {};
10
- ts.filterFormatter = ts.filterFormatter || {};
9
+ var ts = $.tablesorter || {};
10
+ ts.filterFormatter = ts.filterFormatter || {};
11
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
- value : '',
21
- // include ANY select2 options below
22
- multiple : true,
23
- width : '100%'
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
+ value : '',
21
+ // include ANY select2 options below
22
+ multiple : true,
23
+ width : '100%'
24
24
 
25
- }, select2Def ),
26
- arry, data,
27
- c = $cell.closest('table')[0].config,
28
- wo = c.widgetOptions,
29
- // Add a hidden input to hold the range values
30
- $input = $('<input class="filter" type="hidden">')
31
- .appendTo($cell)
32
- // hidden filter update namespace trigger by filter widget
33
- .bind('change' + c.namespace + 'filter', function(){
34
- var val = this.value;
35
- val = val.replace(/[/()$^]/g, '').split('|');
36
- $cell.find('.select2').select2('val', val);
37
- updateSelect2();
38
- }),
39
- $header = c.$headerIndexed[indx],
40
- onlyAvail = $header.hasClass(wo.filter_onlyAvail),
41
- $shcell = [],
42
- matchPrefix = o.match ? '' : '^',
43
- matchSuffix = o.match ? '' : '$',
44
-
45
- // this function updates the hidden input and adds the current values to the header cell text
46
- updateSelect2 = function() {
47
- var arry = false,
48
- v = $cell.find('.select2').select2('val') || o.value || '';
49
- // convert array to string
50
- if ($.isArray(v)) {
51
- arry = true;
52
- v = v.join('\u0000');
53
- }
54
- // escape special regex characters (http://stackoverflow.com/a/9310752/145346)
55
- v = v.replace(/[-[\]{}()*+?.,/\\^$|#\s]/g, '\\$&');
56
- // convert string back into an array
57
- if (arry) {
58
- v = v.split('\u0000');
59
- }
60
- $input
61
- // add regex, so we filter exact numbers
62
- .val( $.isArray(v) && v.length && v.join('') !== '' ? '/(' + matchPrefix + (v || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' : '' )
63
- .trigger('search').end()
64
- .find('.select2').select2('val', v);
65
- // update sticky header cell
66
- if ($shcell.length) {
67
- $shcell.find('.select2').select2('val', v);
68
- }
69
- },
70
-
71
- // get options from table cell content or filter_selectSource (v2.16)
72
- updateOptions = function(){
73
- data = [];
74
- arry = ts.filter.getOptionSource(c.$table[0], indx, onlyAvail) || [];
75
- // build select2 data option
76
- $.each(arry, function(i,v){
77
- data.push({id: v, text: v});
78
- });
79
- o.data = data;
80
- };
25
+ }, select2Def ),
26
+ arry, data,
27
+ c = $cell.closest('table')[0].config,
28
+ wo = c.widgetOptions,
29
+ // Add a hidden input to hold the range values
30
+ $input = $('<input class="filter" type="hidden">')
31
+ .appendTo($cell)
32
+ // hidden filter update namespace trigger by filter widget
33
+ .bind('change' + c.namespace + 'filter', function(){
34
+ var val = this.value;
35
+ val = val.replace(/[/()$^]/g, '').split('|');
36
+ $cell.find('.select2').select2('val', val);
37
+ updateSelect2();
38
+ }),
39
+ $header = c.$headerIndexed[indx],
40
+ onlyAvail = $header.hasClass(wo.filter_onlyAvail),
41
+ $shcell = [],
42
+ matchPrefix = o.match ? '' : '^',
43
+ matchSuffix = o.match ? '' : '$',
81
44
 
82
- // get filter-match class from option
83
- $header.toggleClass('filter-match', o.match);
84
- if (o.cellText) {
85
- $cell.prepend('<label>' + o.cellText + '</label>');
86
- }
45
+ // this function updates the hidden input and adds the current values to the header cell text
46
+ updateSelect2 = function() {
47
+ var arry = false,
48
+ v = $cell.find('.select2').select2('val') || o.value || '';
49
+ // convert array to string
50
+ if ($.isArray(v)) {
51
+ arry = true;
52
+ v = v.join('\u0000');
53
+ }
54
+ // escape special regex characters (http://stackoverflow.com/a/9310752/145346)
55
+ v = v.replace(/[-[\]{}()*+?.,/\\^$|#\s]/g, '\\$&');
56
+ // convert string back into an array
57
+ if (arry) {
58
+ v = v.split('\u0000');
59
+ }
60
+ $input
61
+ // add regex, so we filter exact numbers
62
+ .val( $.isArray(v) && v.length && v.join('') !== '' ? '/(' + matchPrefix + (v || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' : '' )
63
+ .trigger('search').end()
64
+ .find('.select2').select2('val', v);
65
+ // update sticky header cell
66
+ if ($shcell.length) {
67
+ $shcell.find('.select2').select2('val', v);
68
+ }
69
+ },
87
70
 
88
- // don't add default in table options if either ajax or
89
- // data options are already defined
90
- if (!(o.ajax && !$.isEmptyObject(o.ajax)) && !o.data) {
91
- updateOptions();
92
- if (onlyAvail) {
93
- c.$table.bind('filterEnd', function(){
94
- updateOptions();
95
- $cell.add($shcell).find('.select2').select2(o);
71
+ // get options from table cell content or filter_selectSource (v2.16)
72
+ updateOptions = function(){
73
+ data = [];
74
+ arry = ts.filter.getOptionSource(c.$table[0], indx, onlyAvail) || [];
75
+ // build select2 data option
76
+ $.each(arry, function(i, v){
77
+ data.push({id: v, text: v});
96
78
  });
97
- }
98
- }
79
+ o.data = data;
80
+ };
99
81
 
100
- // add a select2 hidden input!
101
- $('<input class="select2 select2-' + indx + '" type="hidden" />')
102
- .val(o.value)
103
- .appendTo($cell)
104
- .select2(o)
105
- .bind('change', function(){
106
- updateSelect2();
107
- });
82
+ // get filter-match class from option
83
+ $header.toggleClass('filter-match', o.match);
84
+ if (o.cellText) {
85
+ $cell.prepend('<label>' + o.cellText + '</label>');
86
+ }
108
87
 
109
- // update select2 from filter hidden input, in case of saved filters
110
- c.$table.bind('filterFomatterUpdate', function(){
111
- // value = '/(^x$|^y$)/' => 'x,y'
112
- var val = c.$table.data('lastSearch')[indx] || '';
113
- val = val.replace(/^\/\(\^?/,'').replace(/\$\|\^/g, '|').replace(/\$?\)\/$/g,'').split('|');
114
- $cell.find('.select2').select2('val', val);
115
- updateSelect2();
116
- ts.filter.formatterUpdated($cell, indx);
117
- });
88
+ // don't add default in table options if either ajax or
89
+ // data options are already defined
90
+ if (!(o.ajax && !$.isEmptyObject(o.ajax)) && !o.data) {
91
+ updateOptions();
92
+ if (onlyAvail) {
93
+ c.$table.bind('filterEnd', function(){
94
+ updateOptions();
95
+ $cell.add($shcell).find('.select2').select2(o);
96
+ });
97
+ }
98
+ }
118
99
 
119
- // has sticky headers?
120
- c.$table.bind('stickyHeadersInit', function(){
121
- $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
122
- // add a select2!
123
- $('<input class="select2 select2-' + indx + '" type="hidden">')
100
+ // add a select2 hidden input!
101
+ $('<input class="select2 select2-' + indx + '" type="hidden" />')
124
102
  .val(o.value)
125
- .appendTo($shcell)
103
+ .appendTo($cell)
126
104
  .select2(o)
127
105
  .bind('change', function(){
128
- $cell.find('.select2').select2('val', $shcell.find('.select2').select2('val') );
129
106
  updateSelect2();
130
107
  });
131
- if (o.cellText) {
132
- $shcell.prepend('<label>' + o.cellText + '</label>');
133
- }
134
108
 
135
- });
136
-
137
- // on reset
138
- c.$table.bind('filterReset', function(){
139
- $cell.find('.select2').select2('val', o.value || '');
140
- setTimeout(function(){
109
+ // update select2 from filter hidden input, in case of saved filters
110
+ c.$table.bind('filterFomatterUpdate', function(){
111
+ // value = '/(^x$|^y$)/' => 'x,y'
112
+ var val = c.$table.data('lastSearch')[indx] || '';
113
+ val = val.replace(/^\/\(\^?/, '').replace(/\$\|\^/g, '|').replace(/\$?\)\/$/g, '').split('|');
114
+ $cell.find('.select2').select2('val', val);
141
115
  updateSelect2();
142
- }, 0);
143
- });
116
+ ts.filter.formatterUpdated($cell, indx);
117
+ });
118
+
119
+ // has sticky headers?
120
+ c.$table.bind('stickyHeadersInit', function(){
121
+ $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty();
122
+ // add a select2!
123
+ $('<input class="select2 select2-' + indx + '" type="hidden">')
124
+ .val(o.value)
125
+ .appendTo($shcell)
126
+ .select2(o)
127
+ .bind('change', function(){
128
+ $cell.find('.select2').select2('val', $shcell.find('.select2').select2('val') );
129
+ updateSelect2();
130
+ });
131
+ if (o.cellText) {
132
+ $shcell.prepend('<label>' + o.cellText + '</label>');
133
+ }
144
134
 
145
- updateSelect2();
146
- return $input;
147
- };
135
+ });
136
+
137
+ // on reset
138
+ c.$table.bind('filterReset', function(){
139
+ $cell.find('.select2').select2('val', o.value || '');
140
+ setTimeout(function(){
141
+ updateSelect2();
142
+ }, 0);
143
+ });
144
+
145
+ updateSelect2();
146
+ return $input;
147
+ };
148
148
 
149
149
  })(jQuery);
@@ -1,6 +1,6 @@
1
1
  /*! Widget: filter, insideRange filter type - updated 2/23/2015 (v2.21.0) */
2
2
  ;(function($){
3
- 'use strict';
3
+ 'use strict';
4
4
 
5
5
  // Add insideRange filter type
6
6
  // ============================
@@ -1,94 +1,94 @@
1
- /*! Widget: filter - updated 5/17/2015 (v2.22.1) *//*
1
+ /*! Widget: filter - updated 7/28/2015 (v2.22.4) *//*
2
2
  * Requires tablesorter v2.8+ and jQuery 1.7+
3
3
  * by Rob Garrison
4
4
  */
5
5
  ;( function ( $ ) {
6
- 'use strict';
7
- var ts = $.tablesorter || {},
6
+ 'use strict';
7
+ var ts = $.tablesorter || {},
8
8
  tscss = ts.css;
9
9
 
10
- $.extend( tscss, {
11
- filterRow : 'tablesorter-filter-row',
12
- filter : 'tablesorter-filter',
13
- filterDisabled : 'disabled',
14
- filterRowHide : 'hideme'
15
- });
10
+ $.extend( tscss, {
11
+ filterRow : 'tablesorter-filter-row',
12
+ filter : 'tablesorter-filter',
13
+ filterDisabled : 'disabled',
14
+ filterRowHide : 'hideme'
15
+ });
16
16
 
17
- ts.addWidget({
18
- id: 'filter',
19
- priority: 50,
20
- options : {
21
- filter_childRows : false, // if true, filter includes child row content in the search
22
- filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped
23
- filter_columnFilters : true, // if true, a filter will be added to the top of each table column
24
- filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query )
25
- filter_cellFilter : '', // css class name added to the filter cell ( string or array )
26
- filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added )
27
- filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND.
28
- filter_excludeFilter : {}, // filters to exclude, per column
29
- filter_external : '', // jQuery selector string ( or jQuery object ) of external filters
30
- filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
31
- filter_formatter : null, // add custom filter elements to the filter row
32
- filter_functions : null, // add custom filter functions using this option
33
- filter_hideEmpty : true, // hide filter row when table is empty
34
- filter_hideFilters : false, // collapse filter row when mouse leaves the area
35
- filter_ignoreCase : true, // if true, make all searches case-insensitive
36
- filter_liveSearch : true, // if true, search column content while the user types ( with a delay )
37
- filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down
38
- filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting )
39
- filter_reset : null, // jQuery selector string of an element used to reset the filters
40
- filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
41
- filter_searchDelay : 300, // typing delay in milliseconds before starting a search
42
- filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true
43
- filter_selectSource : null, // include a function to return an array of values to be added to the column filter select
44
- filter_startsWith : false, // if true, filter start from the beginning of the cell contents
45
- filter_useParsedData : false, // filter all data using parsed content
46
- 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.
47
- filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
48
- filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text
49
- },
50
- format: function( table, c, wo ) {
51
- if ( !c.$table.hasClass( 'hasFilters' ) ) {
52
- ts.filter.init( table, c, wo );
53
- }
54
- },
55
- remove: function( table, c, wo, refreshing ) {
56
- var tbodyIndex, $tbody,
57
- $table = c.$table,
58
- $tbodies = c.$tbodies,
59
- events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
60
- .split( ' ' ).join( c.namespace + 'filter ' );
61
- $table
62
- .removeClass( 'hasFilters' )
63
- // add .tsfilter namespace to all BUT search
64
- .unbind( events.replace( /\s+/g, ' ' ) )
65
- // remove the filter row even if refreshing, because the column might have been moved
66
- .find( '.' + tscss.filterRow ).remove();
67
- if ( refreshing ) { return; }
68
- for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
69
- $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody
70
- $tbody.children().removeClass( wo.filter_filteredRow ).show();
71
- ts.processTbody( table, $tbody, false ); // restore tbody
72
- }
73
- if ( wo.filter_reset ) {
74
- $( document ).undelegate( wo.filter_reset, 'click.tsfilter' );
17
+ ts.addWidget({
18
+ id: 'filter',
19
+ priority: 50,
20
+ options : {
21
+ filter_childRows : false, // if true, filter includes child row content in the search
22
+ filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped
23
+ filter_columnFilters : true, // if true, a filter will be added to the top of each table column
24
+ filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query )
25
+ filter_cellFilter : '', // css class name added to the filter cell ( string or array )
26
+ filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added )
27
+ filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND.
28
+ filter_excludeFilter : {}, // filters to exclude, per column
29
+ filter_external : '', // jQuery selector string ( or jQuery object ) of external filters
30
+ filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
31
+ filter_formatter : null, // add custom filter elements to the filter row
32
+ filter_functions : null, // add custom filter functions using this option
33
+ filter_hideEmpty : true, // hide filter row when table is empty
34
+ filter_hideFilters : false, // collapse filter row when mouse leaves the area
35
+ filter_ignoreCase : true, // if true, make all searches case-insensitive
36
+ filter_liveSearch : true, // if true, search column content while the user types ( with a delay )
37
+ filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down
38
+ filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting )
39
+ filter_reset : null, // jQuery selector string of an element used to reset the filters
40
+ filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
41
+ filter_searchDelay : 300, // typing delay in milliseconds before starting a search
42
+ filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true
43
+ filter_selectSource : null, // include a function to return an array of values to be added to the column filter select
44
+ filter_startsWith : false, // if true, filter start from the beginning of the cell contents
45
+ filter_useParsedData : false, // filter all data using parsed content
46
+ filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used.
47
+ filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
48
+ filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text
49
+ },
50
+ format: function( table, c, wo ) {
51
+ if ( !c.$table.hasClass( 'hasFilters' ) ) {
52
+ ts.filter.init( table, c, wo );
53
+ }
54
+ },
55
+ remove: function( table, c, wo, refreshing ) {
56
+ var tbodyIndex, $tbody,
57
+ $table = c.$table,
58
+ $tbodies = c.$tbodies,
59
+ events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
60
+ .split( ' ' ).join( c.namespace + 'filter ' );
61
+ $table
62
+ .removeClass( 'hasFilters' )
63
+ // add .tsfilter namespace to all BUT search
64
+ .unbind( events.replace( /\s+/g, ' ' ) )
65
+ // remove the filter row even if refreshing, because the column might have been moved
66
+ .find( '.' + tscss.filterRow ).remove();
67
+ if ( refreshing ) { return; }
68
+ for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
69
+ $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody
70
+ $tbody.children().removeClass( wo.filter_filteredRow ).show();
71
+ ts.processTbody( table, $tbody, false ); // restore tbody
72
+ }
73
+ if ( wo.filter_reset ) {
74
+ $( document ).undelegate( wo.filter_reset, 'click.tsfilter' );
75
+ }
75
76
  }
76
- }
77
- });
77
+ });
78
78
 
79
- ts.filter = {
79
+ ts.filter = {
80
80
 
81
- // regex used in filter 'check' functions - not for general use and not documented
82
- regex: {
83
- regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
84
- child : /tablesorter-childRow/, // child row class name; this gets updated in the script
85
- filtered : /filtered/, // filtered (hidden) row class name; updated in the script
86
- type : /undefined|number/, // check type
87
- exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==')
88
- nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
89
- operators : /[<>=]/g, // replace operators
90
- query : '(q|query)' // replace filter queries
91
- },
81
+ // regex used in filter 'check' functions - not for general use and not documented
82
+ regex: {
83
+ regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
84
+ child : /tablesorter-childRow/, // child row class name; this gets updated in the script
85
+ filtered : /filtered/, // filtered (hidden) row class name; updated in the script
86
+ type : /undefined|number/, // check type
87
+ exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==')
88
+ nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
89
+ operators : /[<>=]/g, // replace operators
90
+ query : '(q|query)' // replace filter queries
91
+ },
92
92
  // function( c, data ) { }
93
93
  // c = table.config
94
94
  // data.$row = jQuery object of the row currently being processed
@@ -102,1507 +102,1522 @@ ts.filter = {
102
102
  // data.cacheArray = An array of parsed content from each table cell in the row being processed
103
103
  // data.index = column index; table = table element ( DOM )
104
104
  // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
105
- types: {
106
- or : function( c, data, vars ) {
107
- if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
108
- var indx, filterMatched, txt, query, regex,
109
- // duplicate data but split filter
110
- data2 = $.extend( {}, data ),
111
- index = data.index,
112
- parsed = data.parsed[ index ],
113
- filter = data.filter.split( ts.filter.regex.orSplit ),
114
- iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
115
- len = filter.length;
116
- for ( indx = 0; indx < len; indx++ ) {
117
- data2.nestedFilters = true;
118
- data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
119
- data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
120
- query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
121
- regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
122
- // filterMatched = data2.filter === '' && indx > 0 ? true
123
- // look for an exact match with the 'or' unless the 'filter-match' class is found
124
- filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
125
- if ( filterMatched ) {
126
- return filterMatched;
105
+ types: {
106
+ or : function( c, data, vars ) {
107
+ if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
108
+ var indx, filterMatched, txt, query, regex,
109
+ // duplicate data but split filter
110
+ data2 = $.extend( {}, data ),
111
+ index = data.index,
112
+ parsed = data.parsed[ index ],
113
+ filter = data.filter.split( ts.filter.regex.orSplit ),
114
+ iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
115
+ len = filter.length;
116
+ for ( indx = 0; indx < len; indx++ ) {
117
+ data2.nestedFilters = true;
118
+ data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
119
+ data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
120
+ query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
121
+ try {
122
+ // use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search,
123
+ // e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group
124
+ regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
125
+ // filterMatched = data2.filter === '' && indx > 0 ? true
126
+ // look for an exact match with the 'or' unless the 'filter-match' class is found
127
+ filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
128
+ if ( filterMatched ) {
129
+ return filterMatched;
130
+ }
131
+ } catch ( error ) {
132
+ return null;
133
+ }
127
134
  }
135
+ // may be null from processing types
136
+ return filterMatched || false;
128
137
  }
129
- // may be null from processing types
130
- return filterMatched || false;
131
- }
132
- return null;
133
- },
134
- // Look for an AND or && operator ( logical and )
135
- and : function( c, data, vars ) {
136
- if ( ts.filter.regex.andTest.test( data.filter ) ) {
137
- var indx, filterMatched, result, txt, query, regex,
138
- // duplicate data but split filter
139
- data2 = $.extend( {}, data ),
140
- index = data.index,
141
- parsed = data.parsed[ index ],
142
- filter = data.filter.split( ts.filter.regex.andSplit ),
143
- iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
144
- len = filter.length;
145
- for ( indx = 0; indx < len; indx++ ) {
146
- data2.nestedFilters = true;
147
- data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
148
- data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
149
- query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
150
- // replace wild cards since /(a*)/i will match anything
151
- .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
152
- regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
153
- // look for an exact match with the 'and' unless the 'filter-match' class is found
154
- result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
155
- if ( indx === 0 ) {
156
- filterMatched = result;
157
- } else {
158
- filterMatched = filterMatched && result;
138
+ return null;
139
+ },
140
+ // Look for an AND or && operator ( logical and )
141
+ and : function( c, data, vars ) {
142
+ if ( ts.filter.regex.andTest.test( data.filter ) ) {
143
+ var indx, filterMatched, result, txt, query, regex,
144
+ // duplicate data but split filter
145
+ data2 = $.extend( {}, data ),
146
+ index = data.index,
147
+ parsed = data.parsed[ index ],
148
+ filter = data.filter.split( ts.filter.regex.andSplit ),
149
+ iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
150
+ len = filter.length;
151
+ for ( indx = 0; indx < len; indx++ ) {
152
+ data2.nestedFilters = true;
153
+ data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
154
+ data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
155
+ query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
156
+ // replace wild cards since /(a*)/i will match anything
157
+ .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
158
+ try {
159
+ // use try/catch just in case RegExp is invalid
160
+ regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
161
+ // look for an exact match with the 'and' unless the 'filter-match' class is found
162
+ result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
163
+ if ( indx === 0 ) {
164
+ filterMatched = result;
165
+ } else {
166
+ filterMatched = filterMatched && result;
167
+ }
168
+ } catch ( error ) {
169
+ return null;
170
+ }
159
171
  }
172
+ // may be null from processing types
173
+ return filterMatched || false;
160
174
  }
161
- // may be null from processing types
162
- return filterMatched || false;
163
- }
164
- return null;
165
- },
166
- // Look for regex
167
- regex: function( c, data ) {
168
- if ( ts.filter.regex.regex.test( data.filter ) ) {
169
- var matches,
170
- // cache regex per column for optimal speed
171
- regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ),
172
- isRegex = regex instanceof RegExp;
173
- try {
174
- if ( !isRegex ) {
175
- // force case insensitive search if ignoreCase option set?
176
- // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; }
177
- data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] );
175
+ return null;
176
+ },
177
+ // Look for regex
178
+ regex: function( c, data ) {
179
+ if ( ts.filter.regex.regex.test( data.filter ) ) {
180
+ var matches,
181
+ // cache regex per column for optimal speed
182
+ regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ),
183
+ isRegex = regex instanceof RegExp;
184
+ try {
185
+ if ( !isRegex ) {
186
+ // force case insensitive search if ignoreCase option set?
187
+ // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; }
188
+ data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] );
189
+ }
190
+ matches = regex.test( data.exact );
191
+ } catch ( error ) {
192
+ matches = false;
178
193
  }
179
- matches = regex.test( data.exact );
180
- } catch ( error ) {
181
- matches = false;
182
- }
183
- return matches;
184
- }
185
- return null;
186
- },
187
- // Look for operators >, >=, < or <=
188
- operators: function( c, data ) {
189
- // ignore empty strings... because '' < 10 is true
190
- if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) {
191
- var cachedValue, result, txt,
192
- table = c.table,
193
- index = data.index,
194
- parsed = data.parsed[index],
195
- query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ),
196
- parser = c.parsers[index],
197
- savedSearch = query;
198
- // parse filter value in case we're comparing numbers ( dates )
199
- if ( parsed || parser.type === 'numeric' ) {
200
- txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) );
201
- result = ts.filter.parseFilter( c, txt, index, true );
202
- query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
194
+ return matches;
203
195
  }
204
- // iExact may be numeric - see issue #149;
205
- // check if cached is defined, because sometimes j goes out of range? ( numeric columns )
206
- if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) &&
207
- typeof data.cache !== 'undefined' ) {
208
- cachedValue = data.cache;
209
- } else {
210
- txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
211
- cachedValue = ts.formatFloat( txt, table );
212
- }
213
- if ( />/.test( data.iFilter ) ) {
214
- result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
215
- } else if ( /</.test( data.iFilter ) ) {
216
- result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
196
+ return null;
197
+ },
198
+ // Look for operators >, >=, < or <=
199
+ operators: function( c, data ) {
200
+ // ignore empty strings... because '' < 10 is true
201
+ if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) {
202
+ var cachedValue, result, txt,
203
+ table = c.table,
204
+ index = data.index,
205
+ parsed = data.parsed[index],
206
+ query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ),
207
+ parser = c.parsers[index],
208
+ savedSearch = query;
209
+ // parse filter value in case we're comparing numbers ( dates )
210
+ if ( parsed || parser.type === 'numeric' ) {
211
+ txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) );
212
+ result = ts.filter.parseFilter( c, txt, index, true );
213
+ query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query;
214
+ }
215
+ // iExact may be numeric - see issue #149;
216
+ // check if cached is defined, because sometimes j goes out of range? ( numeric columns )
217
+ if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) &&
218
+ typeof data.cache !== 'undefined' ) {
219
+ cachedValue = data.cache;
220
+ } else {
221
+ txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
222
+ cachedValue = ts.formatFloat( txt, table );
223
+ }
224
+ if ( />/.test( data.iFilter ) ) {
225
+ result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query;
226
+ } else if ( /</.test( data.iFilter ) ) {
227
+ result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query;
228
+ }
229
+ // keep showing all rows if nothing follows the operator
230
+ if ( !result && savedSearch === '' ) {
231
+ result = true;
232
+ }
233
+ return result;
217
234
  }
218
- // keep showing all rows if nothing follows the operator
219
- if ( !result && savedSearch === '' ) {
220
- result = true;
235
+ return null;
236
+ },
237
+ // Look for a not match
238
+ notMatch: function( c, data ) {
239
+ if ( /^\!/.test( data.iFilter ) ) {
240
+ var indx,
241
+ txt = data.iFilter.replace( '!', '' ),
242
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
243
+ if ( ts.filter.regex.exact.test( filter ) ) {
244
+ // look for exact not matches - see #628
245
+ filter = filter.replace( ts.filter.regex.exact, '' );
246
+ return filter === '' ? true : $.trim( filter ) !== data.iExact;
247
+ } else {
248
+ indx = data.iExact.search( $.trim( filter ) );
249
+ return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 );
250
+ }
221
251
  }
222
- return result;
223
- }
224
- return null;
225
- },
226
- // Look for a not match
227
- notMatch: function( c, data ) {
228
- if ( /^\!/.test( data.iFilter ) ) {
229
- var indx,
230
- txt = data.iFilter.replace( '!', '' ),
231
- filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
232
- if ( ts.filter.regex.exact.test( filter ) ) {
233
- // look for exact not matches - see #628
234
- filter = filter.replace( ts.filter.regex.exact, '' );
235
- return filter === '' ? true : $.trim( filter ) !== data.iExact;
236
- } else {
237
- indx = data.iExact.search( $.trim( filter ) );
238
- return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 );
252
+ return null;
253
+ },
254
+ // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
255
+ exact: function( c, data ) {
256
+ /*jshint eqeqeq:false */
257
+ if ( ts.filter.regex.exact.test( data.iFilter ) ) {
258
+ var txt = data.iFilter.replace( ts.filter.regex.exact, '' ),
259
+ filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
260
+ return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
239
261
  }
240
- }
241
- return null;
242
- },
243
- // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
244
- exact: function( c, data ) {
245
- /*jshint eqeqeq:false */
246
- if ( ts.filter.regex.exact.test( data.iFilter ) ) {
247
- var txt = data.iFilter.replace( ts.filter.regex.exact, '' ),
248
- filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
249
- return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact;
250
- }
251
- return null;
252
- },
253
- // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
254
- range : function( c, data ) {
255
- if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
256
- var result, tmp, range1, range2,
257
- table = c.table,
258
- index = data.index,
259
- parsed = data.parsed[index],
260
- // make sure the dash is for a range and not indicating a negative number
261
- query = data.iFilter.split( ts.filter.regex.toSplit );
262
+ return null;
263
+ },
264
+ // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
265
+ range : function( c, data ) {
266
+ if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
267
+ var result, tmp, range1, range2,
268
+ table = c.table,
269
+ index = data.index,
270
+ parsed = data.parsed[index],
271
+ // make sure the dash is for a range and not indicating a negative number
272
+ query = data.iFilter.split( ts.filter.regex.toSplit );
262
273
 
263
- tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || '';
264
- range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
265
- tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || '';
266
- range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
267
- // parse filter value in case we're comparing numbers ( dates )
268
- if ( parsed || c.parsers[index].type === 'numeric' ) {
269
- result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
270
- range1 = ( result !== '' && !isNaN( result ) ) ? result : range1;
271
- result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index );
272
- range2 = ( result !== '' && !isNaN( result ) ) ? result : range2;
273
- }
274
- if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
275
- result = data.cache;
276
- } else {
277
- tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
278
- result = ts.formatFloat( tmp, table );
279
- }
280
- if ( range1 > range2 ) {
281
- tmp = range1; range1 = range2; range2 = tmp; // swap
282
- }
283
- return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' );
284
- }
285
- return null;
286
- },
287
- // Look for wild card: ? = single, * = multiple, or | = logical OR
288
- wild : function( c, data ) {
289
- if ( /[\?\*\|]/.test( data.iFilter ) ) {
290
- var index = data.index,
291
- parsed = data.parsed[ index ],
292
- query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
293
- // look for an exact match with the 'or' unless the 'filter-match' class is found
294
- if ( !/\?\*/.test( query ) && data.nestedFilters ) {
295
- query = data.isMatch ? query : '^(' + query + ')$';
274
+ tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || '';
275
+ range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
276
+ tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || '';
277
+ range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table );
278
+ // parse filter value in case we're comparing numbers ( dates )
279
+ if ( parsed || c.parsers[index].type === 'numeric' ) {
280
+ result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index );
281
+ range1 = ( result !== '' && !isNaN( result ) ) ? result : range1;
282
+ result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index );
283
+ range2 = ( result !== '' && !isNaN( result ) ) ? result : range2;
284
+ }
285
+ if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) {
286
+ result = data.cache;
287
+ } else {
288
+ tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact;
289
+ result = ts.formatFloat( tmp, table );
290
+ }
291
+ if ( range1 > range2 ) {
292
+ tmp = range1; range1 = range2; range2 = tmp; // swap
293
+ }
294
+ return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' );
296
295
  }
297
- // parsing the filter may not work properly when using wildcards =/
298
- return new RegExp(
299
- query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
300
- c.widgetOptions.filter_ignoreCase ? 'i' : ''
301
- )
302
- .test( data.exact );
303
- }
304
- return null;
305
- },
306
- // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
307
- fuzzy: function( c, data ) {
308
- if ( /^~/.test( data.iFilter ) ) {
309
- var indx,
310
- patternIndx = 0,
311
- len = data.iExact.length,
312
- txt = data.iFilter.slice( 1 ),
313
- pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
314
- for ( indx = 0; indx < len; indx++ ) {
315
- if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
316
- patternIndx += 1;
296
+ return null;
297
+ },
298
+ // Look for wild card: ? = single, * = multiple, or | = logical OR
299
+ wild : function( c, data ) {
300
+ if ( /[\?\*\|]/.test( data.iFilter ) ) {
301
+ var index = data.index,
302
+ parsed = data.parsed[ index ],
303
+ query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
304
+ // look for an exact match with the 'or' unless the 'filter-match' class is found
305
+ if ( !/\?\*/.test( query ) && data.nestedFilters ) {
306
+ query = data.isMatch ? query : '^(' + query + ')$';
307
+ }
308
+ // parsing the filter may not work properly when using wildcards =/
309
+ try {
310
+ return new RegExp(
311
+ query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
312
+ c.widgetOptions.filter_ignoreCase ? 'i' : ''
313
+ )
314
+ .test( data.exact );
315
+ } catch ( error ) {
316
+ return null;
317
317
  }
318
318
  }
319
- if ( patternIndx === pattern.length ) {
320
- return true;
319
+ return null;
320
+ },
321
+ // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license )
322
+ fuzzy: function( c, data ) {
323
+ if ( /^~/.test( data.iFilter ) ) {
324
+ var indx,
325
+ patternIndx = 0,
326
+ len = data.iExact.length,
327
+ txt = data.iFilter.slice( 1 ),
328
+ pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || '';
329
+ for ( indx = 0; indx < len; indx++ ) {
330
+ if ( data.iExact[ indx ] === pattern[ patternIndx ] ) {
331
+ patternIndx += 1;
332
+ }
333
+ }
334
+ if ( patternIndx === pattern.length ) {
335
+ return true;
336
+ }
337
+ return false;
321
338
  }
322
- return false;
339
+ return null;
323
340
  }
324
- return null;
325
- }
326
- },
327
- init: function( table, c, wo ) {
328
- // filter language options
329
- ts.language = $.extend( true, {}, {
330
- to : 'to',
331
- or : 'or',
332
- and : 'and'
333
- }, ts.language );
334
-
335
- var options, string, txt, $header, column, filters, val, fxn, noSelect,
336
- regex = ts.filter.regex;
337
- c.$table.addClass( 'hasFilters' );
341
+ },
342
+ init: function( table, c, wo ) {
343
+ // filter language options
344
+ ts.language = $.extend( true, {}, {
345
+ to : 'to',
346
+ or : 'or',
347
+ and : 'and'
348
+ }, ts.language );
338
349
 
339
- // define timers so using clearTimeout won't cause an undefined error
340
- wo.searchTimer = null;
341
- wo.filter_initTimer = null;
342
- wo.filter_formatterCount = 0;
343
- wo.filter_formatterInit = [];
344
- wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
345
- wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
350
+ var options, string, txt, $header, column, filters, val, fxn, noSelect,
351
+ regex = ts.filter.regex;
352
+ c.$table.addClass( 'hasFilters' );
346
353
 
347
- val = '\\{' + ts.filter.regex.query + '\\}';
348
- $.extend( regex, {
349
- child : new RegExp( c.cssChildRow ),
350
- filtered : new RegExp( wo.filter_filteredRow ),
351
- alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ),
352
- toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ),
353
- toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
354
- andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
355
- andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
356
- orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
357
- iQuery : new RegExp( val, 'i' ),
358
- igQuery : new RegExp( val, 'ig' )
359
- });
354
+ // define timers so using clearTimeout won't cause an undefined error
355
+ wo.searchTimer = null;
356
+ wo.filter_initTimer = null;
357
+ wo.filter_formatterCount = 0;
358
+ wo.filter_formatterInit = [];
359
+ wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]';
360
+ wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]';
360
361
 
361
- // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
362
- // see issue #156
363
- val = c.$headers.filter( '.filter-false, .parser-false' ).length;
364
- if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
365
- // build filter row
366
- ts.filter.buildRow( table, c, wo );
367
- }
362
+ val = '\\{' + ts.filter.regex.query + '\\}';
363
+ $.extend( regex, {
364
+ child : new RegExp( c.cssChildRow ),
365
+ filtered : new RegExp( wo.filter_filteredRow ),
366
+ alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ),
367
+ toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ),
368
+ toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ),
369
+ andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
370
+ andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
371
+ orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
372
+ iQuery : new RegExp( val, 'i' ),
373
+ igQuery : new RegExp( val, 'ig' )
374
+ });
368
375
 
369
- txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
370
- .split( ' ' ).join( c.namespace + 'filter ' );
371
- c.$table.bind( txt, function( event, filter ) {
372
- val = wo.filter_hideEmpty &&
373
- $.isEmptyObject( c.cache ) &&
374
- !( c.delayInit && event.type === 'appendCache' );
375
- // hide filter row using the 'filtered' class name
376
- c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
377
- if ( !/(search|filter)/.test( event.type ) ) {
378
- event.stopPropagation();
379
- ts.filter.buildDefault( table, true );
376
+ // don't build filter row if columnFilters is false or all columns are set to 'filter-false'
377
+ // see issue #156
378
+ val = c.$headers.filter( '.filter-false, .parser-false' ).length;
379
+ if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) {
380
+ // build filter row
381
+ ts.filter.buildRow( table, c, wo );
380
382
  }
381
- if ( event.type === 'filterReset' ) {
382
- c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
383
- ts.filter.searching( table, [] );
384
- } else if ( event.type === 'filterEnd' ) {
385
- ts.filter.buildDefault( table, true );
386
- } else {
387
- // send false argument to force a new search; otherwise if the filter hasn't changed,
388
- // it will return
389
- filter = event.type === 'search' ? filter :
390
- event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : '';
391
- if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) {
392
- // force a new search since content has changed
393
- c.lastCombinedFilter = null;
394
- c.lastSearch = [];
383
+
384
+ txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '
385
+ .split( ' ' ).join( c.namespace + 'filter ' );
386
+ c.$table.bind( txt, function( event, filter ) {
387
+ val = wo.filter_hideEmpty &&
388
+ $.isEmptyObject( c.cache ) &&
389
+ !( c.delayInit && event.type === 'appendCache' );
390
+ // hide filter row using the 'filtered' class name
391
+ c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450
392
+ if ( !/(search|filter)/.test( event.type ) ) {
393
+ event.stopPropagation();
394
+ ts.filter.buildDefault( table, true );
395
395
  }
396
- // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
397
- // input ensures all inputs are updated when a search is triggered on the table
398
- // $( 'table' ).trigger( 'search', [...] );
399
- ts.filter.searching( table, filter, true );
400
- }
401
- return false;
402
- });
396
+ if ( event.type === 'filterReset' ) {
397
+ c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' );
398
+ ts.filter.searching( table, [] );
399
+ } else if ( event.type === 'filterEnd' ) {
400
+ ts.filter.buildDefault( table, true );
401
+ } else {
402
+ // send false argument to force a new search; otherwise if the filter hasn't changed,
403
+ // it will return
404
+ filter = event.type === 'search' ? filter :
405
+ event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : '';
406
+ if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) {
407
+ // force a new search since content has changed
408
+ c.lastCombinedFilter = null;
409
+ c.lastSearch = [];
410
+ }
411
+ // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first
412
+ // input ensures all inputs are updated when a search is triggered on the table
413
+ // $( 'table' ).trigger( 'search', [...] );
414
+ ts.filter.searching( table, filter, true );
415
+ }
416
+ return false;
417
+ });
403
418
 
404
- // reset button/link
405
- if ( wo.filter_reset ) {
406
- if ( wo.filter_reset instanceof $ ) {
407
- // reset contains a jQuery object, bind to it
408
- wo.filter_reset.click( function() {
409
- c.$table.trigger( 'filterReset' );
410
- });
411
- } else if ( $( wo.filter_reset ).length ) {
412
- // reset is a jQuery selector, use event delegation
413
- $( document )
414
- .undelegate( wo.filter_reset, 'click.tsfilter' )
415
- .delegate( wo.filter_reset, 'click.tsfilter', function() {
416
- // trigger a reset event, so other functions ( filter_formatter ) know when to reset
419
+ // reset button/link
420
+ if ( wo.filter_reset ) {
421
+ if ( wo.filter_reset instanceof $ ) {
422
+ // reset contains a jQuery object, bind to it
423
+ wo.filter_reset.click( function() {
417
424
  c.$table.trigger( 'filterReset' );
418
425
  });
426
+ } else if ( $( wo.filter_reset ).length ) {
427
+ // reset is a jQuery selector, use event delegation
428
+ $( document )
429
+ .undelegate( wo.filter_reset, 'click.tsfilter' )
430
+ .delegate( wo.filter_reset, 'click.tsfilter', function() {
431
+ // trigger a reset event, so other functions ( filter_formatter ) know when to reset
432
+ c.$table.trigger( 'filterReset' );
433
+ });
434
+ }
419
435
  }
420
- }
421
- if ( wo.filter_functions ) {
422
- for ( column = 0; column < c.columns; column++ ) {
423
- fxn = ts.getColumnData( table, wo.filter_functions, column );
424
- if ( fxn ) {
425
- // remove 'filter-select' from header otherwise the options added here are replaced with
426
- // all options
427
- $header = c.$headerIndexed[ column ].removeClass( 'filter-select' );
428
- // don't build select if 'filter-false' or 'parser-false' set
429
- noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
430
- options = '';
431
- if ( fxn === true && noSelect ) {
432
- ts.filter.buildSelect( table, column );
433
- } else if ( typeof fxn === 'object' && noSelect ) {
434
- // add custom drop down list
435
- for ( string in fxn ) {
436
- if ( typeof string === 'string' ) {
437
- options += options === '' ?
438
- '<option value="">' +
439
- ( $header.data( 'placeholder' ) ||
440
- $header.attr( 'data-placeholder' ) ||
441
- wo.filter_placeholder.select ||
442
- ''
443
- ) +
444
- '</option>' : '';
445
- val = string;
446
- txt = string;
447
- if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
448
- val = string.split( wo.filter_selectSourceSeparator );
449
- txt = val[1];
450
- val = val[0];
436
+ if ( wo.filter_functions ) {
437
+ for ( column = 0; column < c.columns; column++ ) {
438
+ fxn = ts.getColumnData( table, wo.filter_functions, column );
439
+ if ( fxn ) {
440
+ // remove 'filter-select' from header otherwise the options added here are replaced with
441
+ // all options
442
+ $header = c.$headerIndexed[ column ].removeClass( 'filter-select' );
443
+ // don't build select if 'filter-false' or 'parser-false' set
444
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
445
+ options = '';
446
+ if ( fxn === true && noSelect ) {
447
+ ts.filter.buildSelect( table, column );
448
+ } else if ( typeof fxn === 'object' && noSelect ) {
449
+ // add custom drop down list
450
+ for ( string in fxn ) {
451
+ if ( typeof string === 'string' ) {
452
+ options += options === '' ?
453
+ '<option value="">' +
454
+ ( $header.data( 'placeholder' ) ||
455
+ $header.attr( 'data-placeholder' ) ||
456
+ wo.filter_placeholder.select ||
457
+ ''
458
+ ) +
459
+ '</option>' : '';
460
+ val = string;
461
+ txt = string;
462
+ if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
463
+ val = string.split( wo.filter_selectSourceSeparator );
464
+ txt = val[1];
465
+ val = val[0];
466
+ }
467
+ options += '<option ' +
468
+ ( txt === val ? '' : 'data-function-name="' + string + '" ' ) +
469
+ 'value="' + val + '">' + txt + '</option>';
451
470
  }
452
- options += '<option ' +
453
- ( txt === val ? '' : 'data-function-name="' + string + '" ' ) +
454
- 'value="' + val + '">' + txt + '</option>';
455
471
  }
456
- }
457
- c.$table
458
- .find( 'thead' )
459
- .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
460
- .append( options );
461
- txt = wo.filter_selectSource;
462
- fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column );
463
- if ( fxn ) {
464
- // updating so the extra options are appended
465
- ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
472
+ c.$table
473
+ .find( 'thead' )
474
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
475
+ .append( options );
476
+ txt = wo.filter_selectSource;
477
+ fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column );
478
+ if ( fxn ) {
479
+ // updating so the extra options are appended
480
+ ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) );
481
+ }
466
482
  }
467
483
  }
468
484
  }
469
485
  }
470
- }
471
- // not really updating, but if the column has both the 'filter-select' class &
472
- // filter_functions set to true, it would append the same options twice.
473
- ts.filter.buildDefault( table, true );
486
+ // not really updating, but if the column has both the 'filter-select' class &
487
+ // filter_functions set to true, it would append the same options twice.
488
+ ts.filter.buildDefault( table, true );
474
489
 
475
- ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
476
- if ( wo.filter_external ) {
477
- ts.filter.bindSearch( table, wo.filter_external );
478
- }
490
+ ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true );
491
+ if ( wo.filter_external ) {
492
+ ts.filter.bindSearch( table, wo.filter_external );
493
+ }
479
494
 
480
- if ( wo.filter_hideFilters ) {
481
- ts.filter.hideFilters( table, c );
482
- }
495
+ if ( wo.filter_hideFilters ) {
496
+ ts.filter.hideFilters( table, c );
497
+ }
483
498
 
484
- // show processing icon
485
- if ( c.showProcessing ) {
486
- txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
487
- c.$table
488
- .unbind( txt.replace( /\s+/g, ' ' ) )
489
- .bind( txt, function( event, columns ) {
490
- // only add processing to certain columns to all columns
491
- $header = ( columns ) ?
492
- c.$table
493
- .find( '.' + tscss.header )
494
- .filter( '[data-column]' )
495
- .filter( function() {
496
- return columns[ $( this ).data( 'column' ) ] !== '';
497
- }) : '';
498
- ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' );
499
- });
500
- }
499
+ // show processing icon
500
+ if ( c.showProcessing ) {
501
+ txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' );
502
+ c.$table
503
+ .unbind( txt.replace( /\s+/g, ' ' ) )
504
+ .bind( txt, function( event, columns ) {
505
+ // only add processing to certain columns to all columns
506
+ $header = ( columns ) ?
507
+ c.$table
508
+ .find( '.' + tscss.header )
509
+ .filter( '[data-column]' )
510
+ .filter( function() {
511
+ return columns[ $( this ).data( 'column' ) ] !== '';
512
+ }) : '';
513
+ ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' );
514
+ });
515
+ }
501
516
 
502
- // set filtered rows count ( intially unfiltered )
503
- c.filteredRows = c.totalRows;
517
+ // set filtered rows count ( intially unfiltered )
518
+ c.filteredRows = c.totalRows;
504
519
 
505
- // add default values
506
- txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
507
- c.$table
508
- .unbind( txt.replace( /\s+/g, ' ' ) )
509
- .bind( txt, function() {
510
- // redefine 'wo' as it does not update properly inside this callback
511
- var wo = this.config.widgetOptions;
512
- filters = ts.filter.setDefaults( table, c, wo ) || [];
513
- if ( filters.length ) {
514
- // prevent delayInit from triggering a cache build if filters are empty
515
- if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
516
- ts.setFilters( table, filters, true );
520
+ // add default values
521
+ txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' );
522
+ c.$table
523
+ .unbind( txt.replace( /\s+/g, ' ' ) )
524
+ .bind( txt, function() {
525
+ // redefine 'wo' as it does not update properly inside this callback
526
+ var wo = this.config.widgetOptions;
527
+ filters = ts.filter.setDefaults( table, c, wo ) || [];
528
+ if ( filters.length ) {
529
+ // prevent delayInit from triggering a cache build if filters are empty
530
+ if ( !( c.delayInit && filters.join( '' ) === '' ) ) {
531
+ ts.setFilters( table, filters, true );
532
+ }
517
533
  }
518
- }
519
- c.$table.trigger( 'filterFomatterUpdate' );
520
- // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
521
- setTimeout( function() {
522
- if ( !wo.filter_initialized ) {
534
+ c.$table.trigger( 'filterFomatterUpdate' );
535
+ // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers
536
+ setTimeout( function() {
537
+ if ( !wo.filter_initialized ) {
538
+ ts.filter.filterInitComplete( c );
539
+ }
540
+ }, 100 );
541
+ });
542
+ // if filter widget is added after pager has initialized; then set filter init flag
543
+ if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
544
+ c.$table.trigger( 'filterFomatterUpdate' );
545
+ setTimeout( function() {
523
546
  ts.filter.filterInitComplete( c );
524
- }
525
- }, 100 );
526
- });
527
- // if filter widget is added after pager has initialized; then set filter init flag
528
- if ( c.pager && c.pager.initialized && !wo.filter_initialized ) {
529
- c.$table.trigger( 'filterFomatterUpdate' );
530
- setTimeout( function() {
531
- ts.filter.filterInitComplete( c );
532
- }, 100 );
533
- }
534
- },
535
- // $cell parameter, but not the config, is passed to the filter_formatters,
536
- // so we have to work with it instead
537
- formatterUpdated: function( $cell, column ) {
538
- var wo = $cell.closest( 'table' )[0].config.widgetOptions;
539
- if ( !wo.filter_initialized ) {
540
- // add updates by column since this function
541
- // may be called numerous times before initialization
542
- wo.filter_formatterInit[ column ] = 1;
543
- }
544
- },
545
- filterInitComplete: function( c ) {
546
- var indx, len,
547
- wo = c.widgetOptions,
548
- count = 0,
549
- completed = function() {
550
- wo.filter_initialized = true;
551
- c.$table.trigger( 'filterInit', c );
552
- ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
553
- };
554
- if ( $.isEmptyObject( wo.filter_formatter ) ) {
555
- completed();
556
- } else {
557
- len = wo.filter_formatterInit.length;
558
- for ( indx = 0; indx < len; indx++ ) {
559
- if ( wo.filter_formatterInit[ indx ] === 1 ) {
560
- count++;
561
- }
547
+ }, 100 );
548
+ }
549
+ },
550
+ // $cell parameter, but not the config, is passed to the filter_formatters,
551
+ // so we have to work with it instead
552
+ formatterUpdated: function( $cell, column ) {
553
+ var wo = $cell.closest( 'table' )[0].config.widgetOptions;
554
+ if ( !wo.filter_initialized ) {
555
+ // add updates by column since this function
556
+ // may be called numerous times before initialization
557
+ wo.filter_formatterInit[ column ] = 1;
562
558
  }
563
- clearTimeout( wo.filter_initTimer );
564
- if ( !wo.filter_initialized && count === wo.filter_formatterCount ) {
565
- // filter widget initialized
559
+ },
560
+ filterInitComplete: function( c ) {
561
+ var indx, len,
562
+ wo = c.widgetOptions,
563
+ count = 0,
564
+ completed = function() {
565
+ wo.filter_initialized = true;
566
+ c.$table.trigger( 'filterInit', c );
567
+ ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] );
568
+ };
569
+ if ( $.isEmptyObject( wo.filter_formatter ) ) {
566
570
  completed();
567
- } else if ( !wo.filter_initialized ) {
568
- // fall back in case a filter_formatter doesn't call
569
- // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off
570
- wo.filter_initTimer = setTimeout( function() {
571
+ } else {
572
+ len = wo.filter_formatterInit.length;
573
+ for ( indx = 0; indx < len; indx++ ) {
574
+ if ( wo.filter_formatterInit[ indx ] === 1 ) {
575
+ count++;
576
+ }
577
+ }
578
+ clearTimeout( wo.filter_initTimer );
579
+ if ( !wo.filter_initialized && count === wo.filter_formatterCount ) {
580
+ // filter widget initialized
571
581
  completed();
572
- }, 500 );
582
+ } else if ( !wo.filter_initialized ) {
583
+ // fall back in case a filter_formatter doesn't call
584
+ // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off
585
+ wo.filter_initTimer = setTimeout( function() {
586
+ completed();
587
+ }, 500 );
588
+ }
573
589
  }
574
- }
575
- },
576
- setDefaults: function( table, c, wo ) {
577
- var isArray, saved, indx, col, $filters,
578
- // get current ( default ) filters
579
- filters = ts.getFilters( table ) || [];
580
- if ( wo.filter_saveFilters && ts.storage ) {
581
- saved = ts.storage( table, 'tablesorter-filters' ) || [];
582
- isArray = $.isArray( saved );
583
- // make sure we're not just getting an empty array
584
- if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) {
585
- filters = saved;
590
+ },
591
+ setDefaults: function( table, c, wo ) {
592
+ var isArray, saved, indx, col, $filters,
593
+ // get current ( default ) filters
594
+ filters = ts.getFilters( table ) || [];
595
+ if ( wo.filter_saveFilters && ts.storage ) {
596
+ saved = ts.storage( table, 'tablesorter-filters' ) || [];
597
+ isArray = $.isArray( saved );
598
+ // make sure we're not just getting an empty array
599
+ if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) {
600
+ filters = saved;
601
+ }
586
602
  }
587
- }
588
- // if no filters saved, then check default settings
589
- if ( filters.join( '' ) === '' ) {
590
- // allow adding default setting to external filters
591
- $filters = c.$headers.add( wo.filter_$externalFilters )
592
- .filter( '[' + wo.filter_defaultAttrib + ']' );
593
- for ( indx = 0; indx <= c.columns; indx++ ) {
594
- // include data-column='all' external filters
595
- col = indx === c.columns ? 'all' : indx;
596
- filters[indx] = $filters
597
- .filter( '[data-column="' + col + '"]' )
598
- .attr( wo.filter_defaultAttrib ) || filters[indx] || '';
603
+ // if no filters saved, then check default settings
604
+ if ( filters.join( '' ) === '' ) {
605
+ // allow adding default setting to external filters
606
+ $filters = c.$headers.add( wo.filter_$externalFilters )
607
+ .filter( '[' + wo.filter_defaultAttrib + ']' );
608
+ for ( indx = 0; indx <= c.columns; indx++ ) {
609
+ // include data-column='all' external filters
610
+ col = indx === c.columns ? 'all' : indx;
611
+ filters[indx] = $filters
612
+ .filter( '[data-column="' + col + '"]' )
613
+ .attr( wo.filter_defaultAttrib ) || filters[indx] || '';
614
+ }
599
615
  }
600
- }
601
- c.$table.data( 'lastSearch', filters );
602
- return filters;
603
- },
604
- parseFilter: function( c, filter, column, parsed ) {
605
- return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter;
606
- },
607
- buildRow: function( table, c, wo ) {
608
- var col, column, $header, buildSelect, disabled, name, ffxn, tmp,
609
- // c.columns defined in computeThIndexes()
610
- cellFilter = wo.filter_cellFilter,
611
- columns = c.columns,
612
- arry = $.isArray( cellFilter ),
613
- buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
614
- for ( column = 0; column < columns; column++ ) {
615
- buildFilter += '<td';
616
- if ( arry ) {
617
- buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
618
- } else {
619
- buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
616
+ c.$table.data( 'lastSearch', filters );
617
+ return filters;
618
+ },
619
+ parseFilter: function( c, filter, column, parsed ) {
620
+ return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter;
621
+ },
622
+ buildRow: function( table, c, wo ) {
623
+ var col, column, $header, buildSelect, disabled, name, ffxn, tmp,
624
+ // c.columns defined in computeThIndexes()
625
+ cellFilter = wo.filter_cellFilter,
626
+ columns = c.columns,
627
+ arry = $.isArray( cellFilter ),
628
+ buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">';
629
+ for ( column = 0; column < columns; column++ ) {
630
+ buildFilter += '<td';
631
+ if ( arry ) {
632
+ buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' );
633
+ } else {
634
+ buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' );
635
+ }
636
+ buildFilter += '></td>';
620
637
  }
621
- buildFilter += '></td>';
622
- }
623
- c.$filters = $( buildFilter += '</tr>' )
624
- .appendTo( c.$table.children( 'thead' ).eq( 0 ) )
625
- .find( 'td' );
626
- // build each filter input
627
- for ( column = 0; column < columns; column++ ) {
628
- disabled = false;
629
- // assuming last cell of a column is the main column
630
- $header = c.$headerIndexed[ column ];
631
- ffxn = ts.getColumnData( table, wo.filter_functions, column );
632
- buildSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
633
- $header.hasClass( 'filter-select' );
634
- // get data from jQuery data, metadata, headers option or header class name
635
- col = ts.getColumnData( table, c.headers, column );
636
- disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
637
- ts.getData( $header[0], col, 'parser' ) === 'false';
638
+ c.$filters = $( buildFilter += '</tr>' )
639
+ .appendTo( c.$table.children( 'thead' ).eq( 0 ) )
640
+ .find( 'td' );
641
+ // build each filter input
642
+ for ( column = 0; column < columns; column++ ) {
643
+ disabled = false;
644
+ // assuming last cell of a column is the main column
645
+ $header = c.$headerIndexed[ column ];
646
+ ffxn = ts.getColumnData( table, wo.filter_functions, column );
647
+ buildSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) ||
648
+ $header.hasClass( 'filter-select' );
649
+ // get data from jQuery data, metadata, headers option or header class name
650
+ col = ts.getColumnData( table, c.headers, column );
651
+ disabled = ts.getData( $header[0], col, 'filter' ) === 'false' ||
652
+ ts.getData( $header[0], col, 'parser' ) === 'false';
638
653
 
639
- if ( buildSelect ) {
640
- buildFilter = $( '<select>' ).appendTo( c.$filters.eq( column ) );
641
- } else {
642
- ffxn = ts.getColumnData( table, wo.filter_formatter, column );
643
- if ( ffxn ) {
644
- wo.filter_formatterCount++;
645
- buildFilter = ffxn( c.$filters.eq( column ), column );
646
- // no element returned, so lets go find it
647
- if ( buildFilter && buildFilter.length === 0 ) {
648
- buildFilter = c.$filters.eq( column ).children( 'input' );
654
+ if ( buildSelect ) {
655
+ buildFilter = $( '<select>' ).appendTo( c.$filters.eq( column ) );
656
+ } else {
657
+ ffxn = ts.getColumnData( table, wo.filter_formatter, column );
658
+ if ( ffxn ) {
659
+ wo.filter_formatterCount++;
660
+ buildFilter = ffxn( c.$filters.eq( column ), column );
661
+ // no element returned, so lets go find it
662
+ if ( buildFilter && buildFilter.length === 0 ) {
663
+ buildFilter = c.$filters.eq( column ).children( 'input' );
664
+ }
665
+ // element not in DOM, so lets attach it
666
+ if ( buildFilter && ( buildFilter.parent().length === 0 ||
667
+ ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) {
668
+ c.$filters.eq( column ).append( buildFilter );
669
+ }
670
+ } else {
671
+ buildFilter = $( '<input type="search">' ).appendTo( c.$filters.eq( column ) );
649
672
  }
650
- // element not in DOM, so lets attach it
651
- if ( buildFilter && ( buildFilter.parent().length === 0 ||
652
- ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) {
653
- c.$filters.eq( column ).append( buildFilter );
673
+ if ( buildFilter ) {
674
+ tmp = $header.data( 'placeholder' ) ||
675
+ $header.attr( 'data-placeholder' ) ||
676
+ wo.filter_placeholder.search || '';
677
+ buildFilter.attr( 'placeholder', tmp );
654
678
  }
655
- } else {
656
- buildFilter = $( '<input type="search">' ).appendTo( c.$filters.eq( column ) );
657
679
  }
658
680
  if ( buildFilter ) {
659
- tmp = $header.data( 'placeholder' ) ||
660
- $header.attr( 'data-placeholder' ) ||
661
- wo.filter_placeholder.search || '';
662
- buildFilter.attr( 'placeholder', tmp );
681
+ // add filter class name
682
+ name = ( $.isArray( wo.filter_cssFilter ) ?
683
+ ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
684
+ wo.filter_cssFilter ) || '';
685
+ buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
686
+ if ( disabled ) {
687
+ buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
688
+ }
663
689
  }
664
690
  }
665
- if ( buildFilter ) {
666
- // add filter class name
667
- name = ( $.isArray( wo.filter_cssFilter ) ?
668
- ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) :
669
- wo.filter_cssFilter ) || '';
670
- buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column );
671
- if ( disabled ) {
672
- buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true;
691
+ },
692
+ bindSearch: function( table, $el, internal ) {
693
+ table = $( table )[0];
694
+ $el = $( $el ); // allow passing a selector string
695
+ if ( !$el.length ) { return; }
696
+ var tmp,
697
+ c = table.config,
698
+ wo = c.widgetOptions,
699
+ namespace = c.namespace + 'filter',
700
+ $ext = wo.filter_$externalFilters;
701
+ if ( internal !== true ) {
702
+ // save anyMatch element
703
+ tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector;
704
+ wo.filter_$anyMatch = $el.filter( tmp );
705
+ if ( $ext && $ext.length ) {
706
+ wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
707
+ } else {
708
+ wo.filter_$externalFilters = $el;
673
709
  }
710
+ // update values ( external filters added after table initialization )
711
+ ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false );
674
712
  }
675
- }
676
- },
677
- bindSearch: function( table, $el, internal ) {
678
- table = $( table )[0];
679
- $el = $( $el ); // allow passing a selector string
680
- if ( !$el.length ) { return; }
681
- var tmp,
682
- c = table.config,
683
- wo = c.widgetOptions,
684
- namespace = c.namespace + 'filter',
685
- $ext = wo.filter_$externalFilters;
686
- if ( internal !== true ) {
687
- // save anyMatch element
688
- tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector;
689
- wo.filter_$anyMatch = $el.filter( tmp );
690
- if ( $ext && $ext.length ) {
691
- wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
713
+ // unbind events
714
+ tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) );
715
+ $el
716
+ // use data attribute instead of jQuery data since the head is cloned without including
717
+ // the data/binding
718
+ .attr( 'data-lastSearchTime', new Date().getTime() )
719
+ .unbind( tmp.replace( /\s+/g, ' ' ) )
720
+ // include change for select - fixes #473
721
+ .bind( 'keyup' + namespace, function( event ) {
722
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
723
+ // emulate what webkit does.... escape clears the filter
724
+ if ( event.which === 27 ) {
725
+ this.value = '';
726
+ // live search
727
+ } else if ( wo.filter_liveSearch === false ) {
728
+ return;
729
+ // don't return if the search value is empty ( all rows need to be revealed )
730
+ } else if ( this.value !== '' && (
731
+ // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
732
+ ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) ||
733
+ // let return & backspace continue on, but ignore arrows & non-valid characters
734
+ ( event.which !== 13 && event.which !== 8 &&
735
+ ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) {
736
+ return;
737
+ }
738
+ // change event = no delay; last true flag tells getFilters to skip newest timed input
739
+ ts.filter.searching( table, true, true );
740
+ })
741
+ .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) {
742
+ var column = $( this ).data( 'column' );
743
+ // don't allow 'change' event to process if the input value is the same - fixes #685
744
+ if ( event.which === 13 || event.type === 'search' ||
745
+ event.type === 'change' && this.value !== c.lastSearch[column] ) {
746
+ event.preventDefault();
747
+ // init search with no delay
748
+ $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
749
+ ts.filter.searching( table, false, true );
750
+ }
751
+ });
752
+ },
753
+ searching: function( table, filter, skipFirst ) {
754
+ var wo = table.config.widgetOptions;
755
+ clearTimeout( wo.searchTimer );
756
+ if ( typeof filter === 'undefined' || filter === true ) {
757
+ // delay filtering
758
+ wo.searchTimer = setTimeout( function() {
759
+ ts.filter.checkFilters( table, filter, skipFirst );
760
+ }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 );
692
761
  } else {
693
- wo.filter_$externalFilters = $el;
762
+ // skip delay
763
+ ts.filter.checkFilters( table, filter, skipFirst );
694
764
  }
695
- // update values ( external filters added after table initialization )
696
- ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false );
697
- }
698
- // unbind events
699
- tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) );
700
- $el
701
- // use data attribute instead of jQuery data since the head is cloned without including
702
- // the data/binding
703
- .attr( 'data-lastSearchTime', new Date().getTime() )
704
- .unbind( tmp.replace( /\s+/g, ' ' ) )
705
- // include change for select - fixes #473
706
- .bind( 'keyup' + namespace, function( event ) {
707
- $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
708
- // emulate what webkit does.... escape clears the filter
709
- if ( event.which === 27 ) {
710
- this.value = '';
711
- // live search
712
- } else if ( wo.filter_liveSearch === false ) {
765
+ },
766
+ checkFilters: function( table, filter, skipFirst ) {
767
+ var c = table.config,
768
+ wo = c.widgetOptions,
769
+ filterArray = $.isArray( filter ),
770
+ filters = ( filterArray ) ? filter : ts.getFilters( table, true ),
771
+ combinedFilters = ( filters || [] ).join( '' ); // combined filter values
772
+ // prevent errors if delay init is set
773
+ if ( $.isEmptyObject( c.cache ) ) {
774
+ // update cache if delayInit set & pager has initialized ( after user initiates a search )
775
+ if ( c.delayInit && c.pager && c.pager.initialized ) {
776
+ c.$table.trigger( 'updateCache', [ function() {
777
+ ts.filter.checkFilters( table, false, skipFirst );
778
+ } ] );
779
+ }
713
780
  return;
714
- // don't return if the search value is empty ( all rows need to be revealed )
715
- } else if ( this.value !== '' && (
716
- // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
717
- ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) ||
718
- // let return & backspace continue on, but ignore arrows & non-valid characters
719
- ( event.which !== 13 && event.which !== 8 &&
720
- ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) {
781
+ }
782
+ // add filter array back into inputs
783
+ if ( filterArray ) {
784
+ ts.setFilters( table, filters, false, skipFirst !== true );
785
+ if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; }
786
+ }
787
+ if ( wo.filter_hideFilters ) {
788
+ // show/hide filter row as needed
789
+ c.$table
790
+ .find( '.' + tscss.filterRow )
791
+ .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
792
+ }
793
+ // return if the last search is the same; but filter === false when updating the search
794
+ // see example-widget-filter.html filter toggle buttons
795
+ if ( c.lastCombinedFilter === combinedFilters && filter !== false ) {
721
796
  return;
797
+ } else if ( filter === false ) {
798
+ // force filter refresh
799
+ c.lastCombinedFilter = null;
800
+ c.lastSearch = [];
722
801
  }
723
- // change event = no delay; last true flag tells getFilters to skip newest timed input
724
- ts.filter.searching( table, true, true );
725
- })
726
- .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) {
727
- var column = $( this ).data( 'column' );
728
- // don't allow 'change' event to process if the input value is the same - fixes #685
729
- if ( event.which === 13 || event.type === 'search' ||
730
- event.type === 'change' && this.value !== c.lastSearch[column] ) {
731
- event.preventDefault();
732
- // init search with no delay
733
- $( this ).attr( 'data-lastSearchTime', new Date().getTime() );
734
- ts.filter.searching( table, false, true );
802
+ if ( wo.filter_initialized ) {
803
+ c.$table.trigger( 'filterStart', [ filters ] );
735
804
  }
736
- });
737
- },
738
- searching: function( table, filter, skipFirst ) {
739
- var wo = table.config.widgetOptions;
740
- clearTimeout( wo.searchTimer );
741
- if ( typeof filter === 'undefined' || filter === true ) {
742
- // delay filtering
743
- wo.searchTimer = setTimeout( function() {
744
- ts.filter.checkFilters( table, filter, skipFirst );
745
- }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 );
746
- } else {
747
- // skip delay
748
- ts.filter.checkFilters( table, filter, skipFirst );
749
- }
750
- },
751
- checkFilters: function( table, filter, skipFirst ) {
752
- var c = table.config,
753
- wo = c.widgetOptions,
754
- filterArray = $.isArray( filter ),
755
- filters = ( filterArray ) ? filter : ts.getFilters( table, true ),
756
- combinedFilters = ( filters || [] ).join( '' ); // combined filter values
757
- // prevent errors if delay init is set
758
- if ( $.isEmptyObject( c.cache ) ) {
759
- // update cache if delayInit set & pager has initialized ( after user initiates a search )
760
- if ( c.delayInit && c.pager && c.pager.initialized ) {
761
- c.$table.trigger( 'updateCache', [ function() {
762
- ts.filter.checkFilters( table, false, skipFirst );
763
- } ] );
805
+ if ( c.showProcessing ) {
806
+ // give it time for the processing icon to kick in
807
+ setTimeout( function() {
808
+ ts.filter.findRows( table, filters, combinedFilters );
809
+ return false;
810
+ }, 30 );
811
+ } else {
812
+ ts.filter.findRows( table, filters, combinedFilters );
813
+ return false;
764
814
  }
765
- return;
766
- }
767
- // add filter array back into inputs
768
- if ( filterArray ) {
769
- ts.setFilters( table, filters, false, skipFirst !== true );
770
- if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; }
771
- }
772
- if ( wo.filter_hideFilters ) {
773
- // show/hide filter row as needed
815
+ },
816
+ hideFilters: function( table, c ) {
817
+ var timer;
774
818
  c.$table
775
819
  .find( '.' + tscss.filterRow )
776
- .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
777
- }
778
- // return if the last search is the same; but filter === false when updating the search
779
- // see example-widget-filter.html filter toggle buttons
780
- if ( c.lastCombinedFilter === combinedFilters && filter !== false ) {
781
- return;
782
- } else if ( filter === false ) {
783
- // force filter refresh
784
- c.lastCombinedFilter = null;
785
- c.lastSearch = [];
786
- }
787
- if ( wo.filter_initialized ) {
788
- c.$table.trigger( 'filterStart', [filters] );
789
- }
790
- if ( c.showProcessing ) {
791
- // give it time for the processing icon to kick in
792
- setTimeout( function() {
793
- ts.filter.findRows( table, filters, combinedFilters );
794
- return false;
795
- }, 30 );
796
- } else {
797
- ts.filter.findRows( table, filters, combinedFilters );
798
- return false;
799
- }
800
- },
801
- hideFilters: function( table, c ) {
802
- var timer;
803
- c.$table
804
- .find( '.' + tscss.filterRow )
805
- .bind( 'mouseenter mouseleave', function( e ) {
806
- // save event object - http://bugs.jquery.com/ticket/12140
807
- var event = e,
808
- $filterRow = $( this );
809
- clearTimeout( timer );
810
- timer = setTimeout( function() {
811
- if ( /enter|over/.test( event.type ) ) {
812
- $filterRow.removeClass( tscss.filterRowHide );
813
- } else {
814
- // don't hide if input has focus
815
- // $( ':focus' ) needs jQuery 1.6+
816
- if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) {
817
- // don't hide row if any filter has a value
818
- if ( c.lastCombinedFilter === '' ) {
819
- $filterRow.addClass( tscss.filterRowHide );
820
+ .bind( 'mouseenter mouseleave', function( e ) {
821
+ // save event object - http://bugs.jquery.com/ticket/12140
822
+ var event = e,
823
+ $filterRow = $( this );
824
+ clearTimeout( timer );
825
+ timer = setTimeout( function() {
826
+ if ( /enter|over/.test( event.type ) ) {
827
+ $filterRow.removeClass( tscss.filterRowHide );
828
+ } else {
829
+ // don't hide if input has focus
830
+ // $( ':focus' ) needs jQuery 1.6+
831
+ if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) {
832
+ // don't hide row if any filter has a value
833
+ if ( c.lastCombinedFilter === '' ) {
834
+ $filterRow.addClass( tscss.filterRowHide );
835
+ }
820
836
  }
821
837
  }
822
- }
823
- }, 200 );
824
- })
825
- .find( 'input, select' ).bind( 'focus blur', function( e ) {
826
- var event = e,
827
- $row = $( this ).closest( 'tr' );
828
- clearTimeout( timer );
829
- timer = setTimeout( function() {
838
+ }, 200 );
839
+ })
840
+ .find( 'input, select' ).bind( 'focus blur', function( e ) {
841
+ var event = e,
842
+ $row = $( this ).closest( 'tr' );
830
843
  clearTimeout( timer );
831
- // don't hide row if any filter has a value
832
- if ( ts.getFilters( c.$table ).join( '' ) === '' ) {
833
- $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' );
834
- }
835
- }, 200 );
836
- });
837
- },
838
- defaultFilter: function( filter, mask ) {
839
- if ( filter === '' ) { return filter; }
840
- var regex = ts.filter.regex.iQuery,
841
- maskLen = mask.match( ts.filter.regex.igQuery ).length,
842
- query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
843
- len = query.length - 1,
844
- indx = 0,
845
- val = mask;
846
- if ( len < 1 && maskLen > 1 ) {
847
- // only one 'word' in query but mask has >1 slots
848
- query[1] = query[0];
849
- }
850
- // replace all {query} with query words...
851
- // if query = 'Bob', then convert mask from '!{query}' to '!Bob'
852
- // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank'
853
- while ( regex.test( val ) ) {
854
- val = val.replace( regex, query[indx++] || '' );
855
- if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) {
856
- val = mask.replace( regex, val );
844
+ timer = setTimeout( function() {
845
+ clearTimeout( timer );
846
+ // don't hide row if any filter has a value
847
+ if ( ts.getFilters( c.$table ).join( '' ) === '' ) {
848
+ $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' );
849
+ }
850
+ }, 200 );
851
+ });
852
+ },
853
+ defaultFilter: function( filter, mask ) {
854
+ if ( filter === '' ) { return filter; }
855
+ var regex = ts.filter.regex.iQuery,
856
+ maskLen = mask.match( ts.filter.regex.igQuery ).length,
857
+ query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ],
858
+ len = query.length - 1,
859
+ indx = 0,
860
+ val = mask;
861
+ if ( len < 1 && maskLen > 1 ) {
862
+ // only one 'word' in query but mask has >1 slots
863
+ query[1] = query[0];
857
864
  }
858
- }
859
- return val;
860
- },
861
- getLatestSearch: function( $input ) {
862
- if ( $input ) {
863
- return $input.sort( function( a, b ) {
864
- return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' );
865
- });
866
- }
867
- return $input || $();
868
- },
869
- multipleColumns: function( c, $input ) {
870
- // look for multiple columns '1-3,4-6,8' in data-column
871
- var temp, ranges, range, start, end, singles, i, indx, len,
872
- wo = c.widgetOptions,
873
- // only target 'all' column inputs on initialization
874
- // & don't target 'all' column inputs if they don't exist
875
- targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
876
- columns = [],
877
- val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' );
878
- // process column range
879
- if ( targets && /-/.test( val ) ) {
880
- ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
881
- len = ranges.length;
882
- for ( indx = 0; indx < len; indx++ ) {
883
- range = ranges[indx].split( /\s*-\s*/ );
884
- start = parseInt( range[0], 10 ) || 0;
885
- end = parseInt( range[1], 10 ) || ( c.columns - 1 );
886
- if ( start > end ) {
887
- temp = start; start = end; end = temp; // swap
865
+ // replace all {query} with query words...
866
+ // if query = 'Bob', then convert mask from '!{query}' to '!Bob'
867
+ // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank'
868
+ while ( regex.test( val ) ) {
869
+ val = val.replace( regex, query[indx++] || '' );
870
+ if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) {
871
+ val = mask.replace( regex, val );
888
872
  }
889
- if ( end >= c.columns ) {
890
- end = c.columns - 1;
891
- }
892
- for ( ; start <= end; start++ ) {
893
- columns.push( start );
873
+ }
874
+ return val;
875
+ },
876
+ getLatestSearch: function( $input ) {
877
+ if ( $input ) {
878
+ return $input.sort( function( a, b ) {
879
+ return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' );
880
+ });
881
+ }
882
+ return $input || $();
883
+ },
884
+ multipleColumns: function( c, $input ) {
885
+ // look for multiple columns '1-3,4-6,8' in data-column
886
+ var temp, ranges, range, start, end, singles, i, indx, len,
887
+ wo = c.widgetOptions,
888
+ // only target 'all' column inputs on initialization
889
+ // & don't target 'all' column inputs if they don't exist
890
+ targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length,
891
+ columns = [],
892
+ val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' );
893
+ // process column range
894
+ if ( targets && /-/.test( val ) ) {
895
+ ranges = val.match( /(\d+)\s*-\s*(\d+)/g );
896
+ len = ranges.length;
897
+ for ( indx = 0; indx < len; indx++ ) {
898
+ range = ranges[indx].split( /\s*-\s*/ );
899
+ start = parseInt( range[0], 10 ) || 0;
900
+ end = parseInt( range[1], 10 ) || ( c.columns - 1 );
901
+ if ( start > end ) {
902
+ temp = start; start = end; end = temp; // swap
903
+ }
904
+ if ( end >= c.columns ) {
905
+ end = c.columns - 1;
906
+ }
907
+ for ( ; start <= end; start++ ) {
908
+ columns.push( start );
909
+ }
910
+ // remove processed range from val
911
+ val = val.replace( ranges[ indx ], '' );
894
912
  }
895
- // remove processed range from val
896
- val = val.replace( ranges[ indx ], '' );
897
913
  }
898
- }
899
- // process single columns
900
- if ( targets && /,/.test( val ) ) {
901
- singles = val.split( /\s*,\s*/ );
902
- len = singles.length;
903
- for ( i = 0; i < len; i++ ) {
904
- if ( singles[ i ] !== '' ) {
905
- indx = parseInt( singles[ i ], 10 );
906
- if ( indx < c.columns ) {
907
- columns.push( indx );
914
+ // process single columns
915
+ if ( targets && /,/.test( val ) ) {
916
+ singles = val.split( /\s*,\s*/ );
917
+ len = singles.length;
918
+ for ( i = 0; i < len; i++ ) {
919
+ if ( singles[ i ] !== '' ) {
920
+ indx = parseInt( singles[ i ], 10 );
921
+ if ( indx < c.columns ) {
922
+ columns.push( indx );
923
+ }
908
924
  }
909
925
  }
910
926
  }
911
- }
912
- // return all columns
913
- if ( !columns.length ) {
914
- for ( indx = 0; indx < c.columns; indx++ ) {
915
- columns.push( indx );
927
+ // return all columns
928
+ if ( !columns.length ) {
929
+ for ( indx = 0; indx < c.columns; indx++ ) {
930
+ columns.push( indx );
931
+ }
916
932
  }
917
- }
918
- return columns;
919
- },
920
- processTypes: function( c, data, vars ) {
921
- var ffxn,
922
- filterMatched = null,
923
- matches = null;
924
- for ( ffxn in ts.filter.types ) {
925
- if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
926
- matches = ts.filter.types[ffxn]( c, data, vars );
927
- if ( matches !== null ) {
928
- filterMatched = matches;
933
+ return columns;
934
+ },
935
+ processTypes: function( c, data, vars ) {
936
+ var ffxn,
937
+ filterMatched = null,
938
+ matches = null;
939
+ for ( ffxn in ts.filter.types ) {
940
+ if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
941
+ matches = ts.filter.types[ffxn]( c, data, vars );
942
+ if ( matches !== null ) {
943
+ filterMatched = matches;
944
+ }
929
945
  }
930
946
  }
931
- }
932
- return filterMatched;
933
- },
934
- processRow: function( c, data, vars ) {
935
- var columnIndex, hasSelect, result, val, filterMatched,
936
- fxn, ffxn, txt,
937
- regex = ts.filter.regex,
938
- wo = c.widgetOptions,
939
- showRow = true;
940
- data.$cells = data.$row.children();
947
+ return filterMatched;
948
+ },
949
+ processRow: function( c, data, vars ) {
950
+ var columnIndex, hasSelect, result, val, filterMatched,
951
+ fxn, ffxn, txt,
952
+ regex = ts.filter.regex,
953
+ wo = c.widgetOptions,
954
+ showRow = true;
955
+ data.$cells = data.$row.children();
941
956
 
942
- if ( data.anyMatchFlag ) {
943
- // look for multiple columns '1-3,4-6,8'
944
- columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
945
- data.anyMatch = true;
946
- data.isMatch = true;
947
- data.rowArray = data.$cells.map( function( i ) {
948
- if ( $.inArray( i, columnIndex ) > -1 ) {
949
- if ( data.parsed[ i ] ) {
950
- txt = data.cacheArray[ i ];
951
- } else {
952
- txt = data.rawArray[ i ];
953
- txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
954
- if ( c.sortLocaleCompare ) {
955
- txt = ts.replaceAccents( txt );
957
+ if ( data.anyMatchFlag ) {
958
+ // look for multiple columns '1-3,4-6,8'
959
+ columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
960
+ data.anyMatch = true;
961
+ data.isMatch = true;
962
+ data.rowArray = data.$cells.map( function( i ) {
963
+ if ( $.inArray( i, columnIndex ) > -1 ) {
964
+ if ( data.parsed[ i ] ) {
965
+ txt = data.cacheArray[ i ];
966
+ } else {
967
+ txt = data.rawArray[ i ];
968
+ txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt );
969
+ if ( c.sortLocaleCompare ) {
970
+ txt = ts.replaceAccents( txt );
971
+ }
956
972
  }
973
+ return txt;
957
974
  }
958
- return txt;
959
- }
960
- }).get();
961
- data.filter = data.anyMatchFilter;
962
- data.iFilter = data.iAnyMatchFilter;
963
- data.exact = data.rowArray.join( ' ' );
964
- data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
965
- data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
975
+ }).get();
976
+ data.filter = data.anyMatchFilter;
977
+ data.iFilter = data.iAnyMatchFilter;
978
+ data.exact = data.rowArray.join( ' ' );
979
+ data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
980
+ data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
966
981
 
967
- vars.excludeMatch = vars.noAnyMatch;
968
- filterMatched = ts.filter.processTypes( c, data, vars );
982
+ vars.excludeMatch = vars.noAnyMatch;
983
+ filterMatched = ts.filter.processTypes( c, data, vars );
969
984
 
970
- if ( filterMatched !== null ) {
971
- showRow = filterMatched;
972
- } else {
973
- if ( wo.filter_startsWith ) {
974
- showRow = false;
975
- columnIndex = c.columns;
976
- while ( !showRow && columnIndex > 0 ) {
977
- columnIndex--;
978
- showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0;
979
- }
985
+ if ( filterMatched !== null ) {
986
+ showRow = filterMatched;
980
987
  } else {
981
- showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0;
988
+ if ( wo.filter_startsWith ) {
989
+ showRow = false;
990
+ columnIndex = c.columns;
991
+ while ( !showRow && columnIndex > 0 ) {
992
+ columnIndex--;
993
+ showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0;
994
+ }
995
+ } else {
996
+ showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0;
997
+ }
998
+ }
999
+ data.anyMatch = false;
1000
+ // no other filters to process
1001
+ if ( data.filters.join( '' ) === data.filter ) {
1002
+ return showRow;
982
1003
  }
983
1004
  }
984
- data.anyMatch = false;
985
- // no other filters to process
986
- if ( data.filters.join( '' ) === data.filter ) {
987
- return showRow;
988
- }
989
- }
990
1005
 
991
- for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
992
- data.filter = data.filters[ columnIndex ];
993
- data.index = columnIndex;
1006
+ for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
1007
+ data.filter = data.filters[ columnIndex ];
1008
+ data.index = columnIndex;
994
1009
 
995
- // filter types to exclude, per column
996
- vars.excludeMatch = vars.excludeFilter[ columnIndex ];
1010
+ // filter types to exclude, per column
1011
+ vars.excludeMatch = vars.excludeFilter[ columnIndex ];
997
1012
 
998
- // ignore if filter is empty or disabled
999
- if ( data.filter ) {
1000
- data.cache = data.cacheArray[ columnIndex ];
1001
- // check if column data should be from the cell or from parsed data
1002
- if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) {
1003
- data.exact = data.cache;
1004
- } else {
1005
- result = data.rawArray[ columnIndex ] || '';
1006
- data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405
1007
- }
1008
- data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
1009
- data.exact.toLowerCase() : data.exact;
1013
+ // ignore if filter is empty or disabled
1014
+ if ( data.filter ) {
1015
+ data.cache = data.cacheArray[ columnIndex ];
1016
+ // check if column data should be from the cell or from parsed data
1017
+ if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) {
1018
+ data.exact = data.cache;
1019
+ } else {
1020
+ result = data.rawArray[ columnIndex ] || '';
1021
+ data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405
1022
+ }
1023
+ data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
1024
+ data.exact.toLowerCase() : data.exact;
1010
1025
 
1011
- data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
1026
+ data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
1012
1027
 
1013
- result = showRow; // if showRow is true, show that row
1028
+ result = showRow; // if showRow is true, show that row
1014
1029
 
1015
- // in case select filter option has a different value vs text 'a - z|A through Z'
1016
- ffxn = wo.filter_columnFilters ?
1017
- c.$filters.add( c.$externalFilters )
1018
- .filter( '[data-column="'+ columnIndex + '"]' )
1019
- .find( 'select option:selected' )
1020
- .attr( 'data-function-name' ) || '' : '';
1021
- // replace accents - see #357
1022
- if ( c.sortLocaleCompare ) {
1023
- data.filter = ts.replaceAccents( data.filter );
1024
- }
1030
+ // in case select filter option has a different value vs text 'a - z|A through Z'
1031
+ ffxn = wo.filter_columnFilters ?
1032
+ c.$filters.add( c.$externalFilters )
1033
+ .filter( '[data-column="' + columnIndex + '"]' )
1034
+ .find( 'select option:selected' )
1035
+ .attr( 'data-function-name' ) || '' : '';
1036
+ // replace accents - see #357
1037
+ if ( c.sortLocaleCompare ) {
1038
+ data.filter = ts.replaceAccents( data.filter );
1039
+ }
1025
1040
 
1026
- val = true;
1027
- if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
1028
- data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
1029
- // val is used to indicate that a filter select is using a default filter;
1030
- // so we override the exact & partial matches
1031
- val = false;
1032
- }
1033
- // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
1034
- // data.filter = case sensitive
1035
- data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
1036
- fxn = vars.functions[ columnIndex ];
1037
- hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
1038
- filterMatched = null;
1039
- if ( fxn || ( hasSelect && val ) ) {
1040
- if ( fxn === true || hasSelect ) {
1041
- // default selector uses exact match unless 'filter-match' class is found
1042
- filterMatched = data.isMatch ?
1043
- data.iExact.search( data.iFilter ) >= 0 :
1044
- data.filter === data.exact;
1045
- } else if ( typeof fxn === 'function' ) {
1046
- // filter callback( exact cell content, parser normalized content,
1047
- // filter input value, column index, jQuery row object )
1048
- filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1049
- } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) {
1050
- // selector option function
1051
- txt = ffxn || data.filter;
1052
- filterMatched =
1053
- fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1041
+ val = true;
1042
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) {
1043
+ data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] );
1044
+ // val is used to indicate that a filter select is using a default filter;
1045
+ // so we override the exact & partial matches
1046
+ val = false;
1054
1047
  }
1055
- }
1056
- if ( filterMatched === null ) {
1057
- // cycle through the different filters
1058
- // filters return a boolean or null if nothing matches
1059
- filterMatched = ts.filter.processTypes( c, data, vars );
1060
- if ( filterMatched !== null ) {
1061
- result = filterMatched;
1062
- // Look for match, and add child row data for matching
1048
+ // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ),
1049
+ // data.filter = case sensitive
1050
+ data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
1051
+ fxn = vars.functions[ columnIndex ];
1052
+ hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
1053
+ filterMatched = null;
1054
+ if ( fxn || ( hasSelect && val ) ) {
1055
+ if ( fxn === true || hasSelect ) {
1056
+ // default selector uses exact match unless 'filter-match' class is found
1057
+ filterMatched = data.isMatch ?
1058
+ data.iExact.search( data.iFilter ) >= 0 :
1059
+ data.filter === data.exact;
1060
+ } else if ( typeof fxn === 'function' ) {
1061
+ // filter callback( exact cell content, parser normalized content,
1062
+ // filter input value, column index, jQuery row object )
1063
+ filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1064
+ } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) {
1065
+ // selector option function
1066
+ txt = ffxn || data.filter;
1067
+ filterMatched =
1068
+ fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data );
1069
+ }
1070
+ }
1071
+ if ( filterMatched === null ) {
1072
+ // cycle through the different filters
1073
+ // filters return a boolean or null if nothing matches
1074
+ filterMatched = ts.filter.processTypes( c, data, vars );
1075
+ if ( filterMatched !== null ) {
1076
+ result = filterMatched;
1077
+ // Look for match, and add child row data for matching
1078
+ } else {
1079
+ txt = ( data.iExact + data.childRowText )
1080
+ .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
1081
+ result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
1082
+ }
1063
1083
  } else {
1064
- txt = ( data.iExact + data.childRowText )
1065
- .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) );
1066
- result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) );
1084
+ result = filterMatched;
1067
1085
  }
1068
- } else {
1069
- result = filterMatched;
1086
+ showRow = ( result ) ? showRow : false;
1070
1087
  }
1071
- showRow = ( result ) ? showRow : false;
1072
1088
  }
1073
- }
1074
- return showRow;
1075
- },
1076
- findRows: function( table, filters, combinedFilters ) {
1077
- if ( table.config.lastCombinedFilter === combinedFilters ||
1078
- !table.config.widgetOptions.filter_initialized ) {
1079
- return;
1080
- }
1081
- var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex,
1082
- isChild, childRow, lastSearch, showRow, time, val, indx,
1083
- notFiltered, searchFiltered, query, injected, res, id, txt,
1084
- storedFilters = $.extend( [], filters ),
1085
- regex = ts.filter.regex,
1086
- c = table.config,
1087
- wo = c.widgetOptions,
1088
- // data object passed to filters; anyMatch is a flag for the filters
1089
- data = {
1090
- anyMatch: false,
1091
- filters: filters,
1092
- // regex filter type cache
1093
- filter_regexCache : []
1094
- },
1095
- vars = {
1096
- // anyMatch really screws up with these types of filters
1097
- noAnyMatch: [ 'range', 'notMatch', 'operators' ],
1098
- // cache filter variables that use ts.getColumnData in the main loop
1099
- functions : [],
1100
- excludeFilter : [],
1101
- defaultColFilter : [],
1102
- defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
1103
- };
1089
+ return showRow;
1090
+ },
1091
+ findRows: function( table, filters, combinedFilters ) {
1092
+ if ( table.config.lastCombinedFilter === combinedFilters ||
1093
+ !table.config.widgetOptions.filter_initialized ) {
1094
+ return;
1095
+ }
1096
+ var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex,
1097
+ isChild, childRow, lastSearch, showRow, time, val, indx,
1098
+ notFiltered, searchFiltered, query, injected, res, id, txt,
1099
+ storedFilters = $.extend( [], filters ),
1100
+ regex = ts.filter.regex,
1101
+ c = table.config,
1102
+ wo = c.widgetOptions,
1103
+ // data object passed to filters; anyMatch is a flag for the filters
1104
+ data = {
1105
+ anyMatch: false,
1106
+ filters: filters,
1107
+ // regex filter type cache
1108
+ filter_regexCache : []
1109
+ },
1110
+ vars = {
1111
+ // anyMatch really screws up with these types of filters
1112
+ noAnyMatch: [ 'range', 'notMatch', 'operators' ],
1113
+ // cache filter variables that use ts.getColumnData in the main loop
1114
+ functions : [],
1115
+ excludeFilter : [],
1116
+ defaultColFilter : [],
1117
+ defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || ''
1118
+ };
1104
1119
 
1105
- // parse columns after formatter, in case the class is added at that point
1106
- data.parsed = c.$headers.map( function( columnIndex ) {
1107
- return c.parsers && c.parsers[ columnIndex ] &&
1108
- // force parsing if parser type is numeric
1109
- c.parsers[ columnIndex ].parsed ||
1110
- // getData won't return 'parsed' if other 'filter-' class names exist
1111
- // ( e.g. <th class="filter-select filter-parsed"> )
1112
- ts.getData && ts.getData( c.$headerIndexed[ columnIndex ],
1113
- ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' ||
1114
- $( this ).hasClass( 'filter-parsed' );
1115
- }).get();
1120
+ // parse columns after formatter, in case the class is added at that point
1121
+ data.parsed = c.$headers.map( function( columnIndex ) {
1122
+ return c.parsers && c.parsers[ columnIndex ] &&
1123
+ // force parsing if parser type is numeric
1124
+ c.parsers[ columnIndex ].parsed ||
1125
+ // getData won't return 'parsed' if other 'filter-' class names exist
1126
+ // ( e.g. <th class="filter-select filter-parsed"> )
1127
+ ts.getData && ts.getData( c.$headerIndexed[ columnIndex ],
1128
+ ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' ||
1129
+ $( this ).hasClass( 'filter-parsed' );
1130
+ }).get();
1116
1131
 
1117
- for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
1118
- vars.functions[ columnIndex ] =
1119
- ts.getColumnData( table, wo.filter_functions, columnIndex );
1120
- vars.defaultColFilter[ columnIndex ] =
1121
- ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
1122
- vars.excludeFilter[ columnIndex ] =
1123
- ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ );
1124
- }
1132
+ for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) {
1133
+ vars.functions[ columnIndex ] =
1134
+ ts.getColumnData( table, wo.filter_functions, columnIndex );
1135
+ vars.defaultColFilter[ columnIndex ] =
1136
+ ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || '';
1137
+ vars.excludeFilter[ columnIndex ] =
1138
+ ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ );
1139
+ }
1125
1140
 
1126
- if ( c.debug ) {
1127
- ts.log( 'Filter: Starting filter widget search', filters );
1128
- time = new Date();
1129
- }
1130
- // filtered rows count
1131
- c.filteredRows = 0;
1132
- c.totalRows = 0;
1133
- // combindedFilters are undefined on init
1134
- combinedFilters = ( storedFilters || [] ).join( '' );
1141
+ if ( c.debug ) {
1142
+ console.log( 'Filter: Starting filter widget search', filters );
1143
+ time = new Date();
1144
+ }
1145
+ // filtered rows count
1146
+ c.filteredRows = 0;
1147
+ c.totalRows = 0;
1148
+ // combindedFilters are undefined on init
1149
+ combinedFilters = ( storedFilters || [] ).join( '' );
1135
1150
 
1136
- for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1137
- $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true );
1138
- // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel!
1139
- // $rows = $tbody.children( 'tr' ).not( c.selectorRemove );
1140
- columnIndex = c.columns;
1141
- // convert stored rows into a jQuery object
1142
- norm_rows = c.cache[ tbodyIndex ].normalized;
1143
- $rows = $( $.map( norm_rows, function( el ) {
1144
- return el[ columnIndex ].$row.get();
1145
- }) );
1151
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1152
+ $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true );
1153
+ // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel!
1154
+ // $rows = $tbody.children( 'tr' ).not( c.selectorRemove );
1155
+ columnIndex = c.columns;
1156
+ // convert stored rows into a jQuery object
1157
+ norm_rows = c.cache[ tbodyIndex ].normalized;
1158
+ $rows = $( $.map( norm_rows, function( el ) {
1159
+ return el[ columnIndex ].$row.get();
1160
+ }) );
1146
1161
 
1147
- if ( combinedFilters === '' || wo.filter_serversideFiltering ) {
1148
- $rows
1149
- .removeClass( wo.filter_filteredRow )
1150
- .not( '.' + c.cssChildRow )
1151
- .css( 'display', '' );
1152
- } else {
1153
- // filter out child rows
1154
- $rows = $rows.not( '.' + c.cssChildRow );
1155
- len = $rows.length;
1162
+ if ( combinedFilters === '' || wo.filter_serversideFiltering ) {
1163
+ $rows
1164
+ .removeClass( wo.filter_filteredRow )
1165
+ .not( '.' + c.cssChildRow )
1166
+ .css( 'display', '' );
1167
+ } else {
1168
+ // filter out child rows
1169
+ $rows = $rows.not( '.' + c.cssChildRow );
1170
+ len = $rows.length;
1156
1171
 
1157
- if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) ||
1158
- typeof filters[c.columns] !== 'undefined' ) {
1159
- data.anyMatchFlag = true;
1160
- data.anyMatchFilter = '' + (
1161
- filters[ c.columns ] ||
1162
- wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() ||
1163
- ''
1164
- );
1165
- if ( wo.filter_columnAnyMatch ) {
1166
- // specific columns search
1167
- query = data.anyMatchFilter.split( regex.andSplit );
1168
- injected = false;
1169
- for ( indx = 0; indx < query.length; indx++ ) {
1170
- res = query[ indx ].split( ':' );
1171
- if ( res.length > 1 ) {
1172
- // make the column a one-based index ( non-developers start counting from one :P )
1173
- id = parseInt( res[0], 10 ) - 1;
1174
- if ( id >= 0 && id < c.columns ) { // if id is an integer
1175
- filters[ id ] = res[1];
1176
- query.splice( indx, 1 );
1177
- indx--;
1178
- injected = true;
1172
+ if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) ||
1173
+ typeof filters[c.columns] !== 'undefined' ) {
1174
+ data.anyMatchFlag = true;
1175
+ data.anyMatchFilter = '' + (
1176
+ filters[ c.columns ] ||
1177
+ wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() ||
1178
+ ''
1179
+ );
1180
+ if ( wo.filter_columnAnyMatch ) {
1181
+ // specific columns search
1182
+ query = data.anyMatchFilter.split( regex.andSplit );
1183
+ injected = false;
1184
+ for ( indx = 0; indx < query.length; indx++ ) {
1185
+ res = query[ indx ].split( ':' );
1186
+ if ( res.length > 1 ) {
1187
+ // make the column a one-based index ( non-developers start counting from one :P )
1188
+ id = parseInt( res[0], 10 ) - 1;
1189
+ if ( id >= 0 && id < c.columns ) { // if id is an integer
1190
+ filters[ id ] = res[1];
1191
+ query.splice( indx, 1 );
1192
+ indx--;
1193
+ injected = true;
1194
+ }
1179
1195
  }
1180
1196
  }
1181
- }
1182
- if ( injected ) {
1183
- data.anyMatchFilter = query.join( ' && ' );
1197
+ if ( injected ) {
1198
+ data.anyMatchFilter = query.join( ' && ' );
1199
+ }
1184
1200
  }
1185
1201
  }
1186
- }
1187
1202
 
1188
- // optimize searching only through already filtered rows - see #313
1189
- searchFiltered = wo.filter_searchFiltered;
1190
- lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || [];
1191
- if ( searchFiltered ) {
1192
- // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669
1193
- for ( indx = 0; indx < columnIndex + 1; indx++ ) {
1194
- val = filters[indx] || '';
1195
- // break out of loop if we've already determined not to search filtered rows
1196
- if ( !searchFiltered ) { indx = columnIndex; }
1197
- // search already filtered rows if...
1198
- searchFiltered = searchFiltered && lastSearch.length &&
1199
- // there are no changes from beginning of filter
1200
- val.indexOf( lastSearch[indx] || '' ) === 0 &&
1201
- // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
1202
- !regex.alreadyFiltered.test( val ) &&
1203
- // if we are not doing exact matches, using '|' ( logical or ) or not '!'
1204
- !/[=\"\|!]/.test( val ) &&
1205
- // don't search only filtered if the value is negative
1206
- // ( '> -10' => '> -100' will ignore hidden rows )
1207
- !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) &&
1208
- // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
1209
- !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
1210
- !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
1203
+ // optimize searching only through already filtered rows - see #313
1204
+ searchFiltered = wo.filter_searchFiltered;
1205
+ lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || [];
1206
+ if ( searchFiltered ) {
1207
+ // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669
1208
+ for ( indx = 0; indx < columnIndex + 1; indx++ ) {
1209
+ val = filters[indx] || '';
1210
+ // break out of loop if we've already determined not to search filtered rows
1211
+ if ( !searchFiltered ) { indx = columnIndex; }
1212
+ // search already filtered rows if...
1213
+ searchFiltered = searchFiltered && lastSearch.length &&
1214
+ // there are no changes from beginning of filter
1215
+ val.indexOf( lastSearch[indx] || '' ) === 0 &&
1216
+ // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string
1217
+ !regex.alreadyFiltered.test( val ) &&
1218
+ // if we are not doing exact matches, using '|' ( logical or ) or not '!'
1219
+ !/[=\"\|!]/.test( val ) &&
1220
+ // don't search only filtered if the value is negative
1221
+ // ( '> -10' => '> -100' will ignore hidden rows )
1222
+ !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) &&
1223
+ // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593
1224
+ !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length &&
1225
+ !c.$headerIndexed[indx].hasClass( 'filter-match' ) );
1226
+ }
1211
1227
  }
1212
- }
1213
- notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length;
1214
- // can't search when all rows are hidden - this happens when looking for exact matches
1215
- if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; }
1216
- if ( c.debug ) {
1217
- ts.log( 'Filter: Searching through ' +
1218
- ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
1219
- }
1220
- if ( data.anyMatchFlag ) {
1221
- if ( c.sortLocaleCompare ) {
1222
- // replace accents
1223
- data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
1228
+ notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length;
1229
+ // can't search when all rows are hidden - this happens when looking for exact matches
1230
+ if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; }
1231
+ if ( c.debug ) {
1232
+ console.log( 'Filter: Searching through ' +
1233
+ ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' );
1224
1234
  }
1225
- if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
1226
- data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
1227
- // clear search filtered flag because default filters are not saved to the last search
1228
- searchFiltered = false;
1235
+ if ( data.anyMatchFlag ) {
1236
+ if ( c.sortLocaleCompare ) {
1237
+ // replace accents
1238
+ data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter );
1239
+ }
1240
+ if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) {
1241
+ data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter );
1242
+ // clear search filtered flag because default filters are not saved to the last search
1243
+ searchFiltered = false;
1244
+ }
1245
+ // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
1246
+ // when c.ignoreCase is true, the cache contains all lower case data
1247
+ data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ?
1248
+ data.anyMatchFilter :
1249
+ data.anyMatchFilter.toLowerCase();
1229
1250
  }
1230
- // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true
1231
- // when c.ignoreCase is true, the cache contains all lower case data
1232
- data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ?
1233
- data.anyMatchFilter :
1234
- data.anyMatchFilter.toLowerCase();
1235
- }
1236
1251
 
1237
- // loop through the rows
1238
- for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
1252
+ // loop through the rows
1253
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
1239
1254
 
1240
- txt = $rows[ rowIndex ].className;
1241
- // the first row can never be a child row
1242
- isChild = rowIndex && regex.child.test( txt );
1243
- // skip child rows & already filtered rows
1244
- if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) {
1245
- continue;
1246
- }
1255
+ txt = $rows[ rowIndex ].className;
1256
+ // the first row can never be a child row
1257
+ isChild = rowIndex && regex.child.test( txt );
1258
+ // skip child rows & already filtered rows
1259
+ if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) {
1260
+ continue;
1261
+ }
1247
1262
 
1248
- data.$row = $rows.eq( rowIndex );
1249
- data.cacheArray = norm_rows[ rowIndex ];
1250
- rowData = data.cacheArray[ c.columns ];
1251
- data.rawArray = rowData.raw;
1252
- data.childRowText = '';
1263
+ data.$row = $rows.eq( rowIndex );
1264
+ data.cacheArray = norm_rows[ rowIndex ];
1265
+ rowData = data.cacheArray[ c.columns ];
1266
+ data.rawArray = rowData.raw;
1267
+ data.childRowText = '';
1253
1268
 
1254
- if ( !wo.filter_childByColumn ) {
1255
- txt = '';
1256
- // child row cached text
1257
- childRow = rowData.child;
1258
- // so, if 'table.config.widgetOptions.filter_childRows' is true and there is
1259
- // a match anywhere in the child row, then it will make the row visible
1260
- // checked here so the option can be changed dynamically
1261
- for ( indx = 0; indx < childRow.length; indx++ ) {
1262
- txt += ' ' + childRow[indx].join( '' ) || '';
1269
+ if ( !wo.filter_childByColumn ) {
1270
+ txt = '';
1271
+ // child row cached text
1272
+ childRow = rowData.child;
1273
+ // so, if 'table.config.widgetOptions.filter_childRows' is true and there is
1274
+ // a match anywhere in the child row, then it will make the row visible
1275
+ // checked here so the option can be changed dynamically
1276
+ for ( indx = 0; indx < childRow.length; indx++ ) {
1277
+ txt += ' ' + childRow[indx].join( '' ) || '';
1278
+ }
1279
+ data.childRowText = wo.filter_childRows ?
1280
+ ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) :
1281
+ '';
1263
1282
  }
1264
- data.childRowText = wo.filter_childRows ?
1265
- ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) :
1266
- '';
1267
- }
1268
1283
 
1269
- showRow = ts.filter.processRow( c, data, vars );
1270
- childRow = rowData.$row.filter( ':gt( 0 )' );
1284
+ showRow = ts.filter.processRow( c, data, vars );
1285
+ childRow = rowData.$row.filter( ':gt( 0 )' );
1271
1286
 
1272
- if ( wo.filter_childRows && childRow.length ) {
1273
- if ( wo.filter_childByColumn ) {
1274
- // cycle through each child row
1275
- for ( indx = 0; indx < childRow.length; indx++ ) {
1276
- data.$row = childRow.eq( indx );
1277
- data.cacheArray = rowData.child[ indx ];
1278
- data.rawArray = data.cacheArray;
1279
- // use OR comparison on child rows
1280
- showRow = showRow || ts.filter.processRow( c, data, vars );
1287
+ if ( wo.filter_childRows && childRow.length ) {
1288
+ if ( wo.filter_childByColumn ) {
1289
+ // cycle through each child row
1290
+ for ( indx = 0; indx < childRow.length; indx++ ) {
1291
+ data.$row = childRow.eq( indx );
1292
+ data.cacheArray = rowData.child[ indx ];
1293
+ data.rawArray = data.cacheArray;
1294
+ // use OR comparison on child rows
1295
+ showRow = showRow || ts.filter.processRow( c, data, vars );
1296
+ }
1281
1297
  }
1298
+ childRow.toggleClass( wo.filter_filteredRow, !showRow );
1282
1299
  }
1283
- childRow.toggleClass( wo.filter_filteredRow, !showRow );
1284
- }
1285
1300
 
1286
- rowData.$row
1287
- .toggleClass( wo.filter_filteredRow, !showRow )[0]
1288
- .display = showRow ? '' : 'none';
1301
+ rowData.$row
1302
+ .toggleClass( wo.filter_filteredRow, !showRow )[0]
1303
+ .display = showRow ? '' : 'none';
1304
+ }
1289
1305
  }
1306
+ c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length;
1307
+ c.totalRows += $rows.length;
1308
+ ts.processTbody( table, $tbody, false );
1290
1309
  }
1291
- c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length;
1292
- c.totalRows += $rows.length;
1293
- ts.processTbody( table, $tbody, false );
1294
- }
1295
- c.lastCombinedFilter = combinedFilters; // save last search
1296
- // don't save 'filters' directly since it may have altered ( AnyMatch column searches )
1297
- c.lastSearch = storedFilters;
1298
- c.$table.data( 'lastSearch', storedFilters );
1299
- if ( wo.filter_saveFilters && ts.storage ) {
1300
- ts.storage( table, 'tablesorter-filters', storedFilters );
1301
- }
1302
- if ( c.debug ) {
1303
- ts.benchmark( 'Completed filter widget search', time );
1304
- }
1305
- if ( wo.filter_initialized ) {
1306
- c.$table.trigger( 'filterEnd', c );
1307
- }
1308
- setTimeout( function() {
1309
- c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied
1310
- }, 0 );
1311
- },
1312
- getOptionSource: function( table, column, onlyAvail ) {
1313
- table = $( table )[0];
1314
- var cts, txt, indx, len,
1315
- c = table.config,
1316
- wo = c.widgetOptions,
1317
- parsed = [],
1318
- arry = false,
1319
- source = wo.filter_selectSource,
1320
- last = c.$table.data( 'lastSearch' ) || [],
1321
- fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column );
1310
+ c.lastCombinedFilter = combinedFilters; // save last search
1311
+ // don't save 'filters' directly since it may have altered ( AnyMatch column searches )
1312
+ c.lastSearch = storedFilters;
1313
+ c.$table.data( 'lastSearch', storedFilters );
1314
+ if ( wo.filter_saveFilters && ts.storage ) {
1315
+ ts.storage( table, 'tablesorter-filters', storedFilters );
1316
+ }
1317
+ if ( c.debug ) {
1318
+ console.log( 'Completed filter widget search' + ts.benchmark(time) );
1319
+ }
1320
+ if ( wo.filter_initialized ) {
1321
+ c.$table.trigger( 'filterEnd', c );
1322
+ }
1323
+ setTimeout( function() {
1324
+ c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied
1325
+ }, 0 );
1326
+ },
1327
+ getOptionSource: function( table, column, onlyAvail ) {
1328
+ table = $( table )[0];
1329
+ var cts, txt, indx, len,
1330
+ c = table.config,
1331
+ wo = c.widgetOptions,
1332
+ parsed = [],
1333
+ arry = false,
1334
+ source = wo.filter_selectSource,
1335
+ last = c.$table.data( 'lastSearch' ) || [],
1336
+ fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column );
1322
1337
 
1323
- if ( onlyAvail && last[column] !== '' ) {
1324
- onlyAvail = false;
1325
- }
1338
+ if ( onlyAvail && last[column] !== '' ) {
1339
+ onlyAvail = false;
1340
+ }
1326
1341
 
1327
- // filter select source option
1328
- if ( fxn === true ) {
1329
- // OVERALL source
1330
- arry = source( table, column, onlyAvail );
1331
- } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '</option>' ) >= 0 ) ) {
1332
- // selectSource is a jQuery object or string of options
1333
- return fxn;
1334
- } else if ( $.isArray( fxn ) ) {
1335
- arry = fxn;
1336
- } else if ( $.type( source ) === 'object' && fxn ) {
1337
- // custom select source function for a SPECIFIC COLUMN
1338
- arry = fxn( table, column, onlyAvail );
1339
- }
1340
- if ( arry === false ) {
1341
- // fall back to original method
1342
- arry = ts.filter.getOptions( table, column, onlyAvail );
1343
- }
1342
+ // filter select source option
1343
+ if ( fxn === true ) {
1344
+ // OVERALL source
1345
+ arry = source( table, column, onlyAvail );
1346
+ } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '</option>' ) >= 0 ) ) {
1347
+ // selectSource is a jQuery object or string of options
1348
+ return fxn;
1349
+ } else if ( $.isArray( fxn ) ) {
1350
+ arry = fxn;
1351
+ } else if ( $.type( source ) === 'object' && fxn ) {
1352
+ // custom select source function for a SPECIFIC COLUMN
1353
+ arry = fxn( table, column, onlyAvail );
1354
+ }
1355
+ if ( arry === false ) {
1356
+ // fall back to original method
1357
+ arry = ts.filter.getOptions( table, column, onlyAvail );
1358
+ }
1344
1359
 
1345
- // get unique elements and sort the list
1346
- // if $.tablesorter.sortText exists ( not in the original tablesorter ),
1347
- // then natural sort the list otherwise use a basic sort
1348
- arry = $.grep( arry, function( value, indx ) {
1349
- return $.inArray( value, arry ) === indx;
1350
- });
1360
+ // get unique elements and sort the list
1361
+ // if $.tablesorter.sortText exists ( not in the original tablesorter ),
1362
+ // then natural sort the list otherwise use a basic sort
1363
+ arry = $.grep( arry, function( value, indx ) {
1364
+ return $.inArray( value, arry ) === indx;
1365
+ });
1351
1366
 
1352
- if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) {
1353
- // unsorted select options
1354
- return arry;
1355
- } else {
1356
- len = arry.length;
1357
- // parse select option values
1358
- for ( indx = 0; indx < len; indx++ ) {
1359
- txt = arry[ indx ];
1360
- // parse array data using set column parser; this DOES NOT pass the original
1361
- // table cell to the parser format function
1362
- parsed.push({
1363
- t : txt,
1364
- // check parser length - fixes #934
1365
- p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt
1367
+ if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) {
1368
+ // unsorted select options
1369
+ return arry;
1370
+ } else {
1371
+ len = arry.length;
1372
+ // parse select option values
1373
+ for ( indx = 0; indx < len; indx++ ) {
1374
+ txt = arry[ indx ];
1375
+ // parse array data using set column parser; this DOES NOT pass the original
1376
+ // table cell to the parser format function
1377
+ parsed.push({
1378
+ t : txt,
1379
+ // check parser length - fixes #934
1380
+ p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt
1381
+ });
1382
+ }
1383
+
1384
+ // sort parsed select options
1385
+ cts = c.textSorter || '';
1386
+ parsed.sort( function( a, b ) {
1387
+ // sortNatural breaks if you don't pass it strings
1388
+ var x = a.p.toString(),
1389
+ y = b.p.toString();
1390
+ if ( $.isFunction( cts ) ) {
1391
+ // custom OVERALL text sorter
1392
+ return cts( x, y, true, column, table );
1393
+ } else if ( typeof cts === 'object' && cts.hasOwnProperty( column ) ) {
1394
+ // custom text sorter for a SPECIFIC COLUMN
1395
+ return cts[column]( x, y, true, column, table );
1396
+ } else if ( ts.sortNatural ) {
1397
+ // fall back to natural sort
1398
+ return ts.sortNatural( x, y );
1399
+ }
1400
+ // using an older version! do a basic sort
1401
+ return true;
1366
1402
  });
1403
+ // rebuild arry from sorted parsed data
1404
+ arry = [];
1405
+ len = parsed.length;
1406
+ for ( indx = 0; indx < len; indx++ ) {
1407
+ arry.push( parsed[indx].t );
1408
+ }
1409
+ return arry;
1367
1410
  }
1368
-
1369
- // sort parsed select options
1370
- cts = c.textSorter || '';
1371
- parsed.sort( function( a, b ) {
1372
- // sortNatural breaks if you don't pass it strings
1373
- var x = a.p.toString(),
1374
- y = b.p.toString();
1375
- if ( $.isFunction( cts ) ) {
1376
- // custom OVERALL text sorter
1377
- return cts( x, y, true, column, table );
1378
- } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) {
1379
- // custom text sorter for a SPECIFIC COLUMN
1380
- return cts[column]( x, y, true, column, table );
1381
- } else if ( ts.sortNatural ) {
1382
- // fall back to natural sort
1383
- return ts.sortNatural( x, y );
1411
+ },
1412
+ getOptions: function( table, column, onlyAvail ) {
1413
+ table = $( table )[0];
1414
+ var rowIndex, tbodyIndex, len, row, cache,
1415
+ c = table.config,
1416
+ wo = c.widgetOptions,
1417
+ arry = [];
1418
+ for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1419
+ cache = c.cache[tbodyIndex];
1420
+ len = c.cache[tbodyIndex].normalized.length;
1421
+ // loop through the rows
1422
+ for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
1423
+ // get cached row from cache.row ( old ) or row data object
1424
+ // ( new; last item in normalized array )
1425
+ row = cache.row ?
1426
+ cache.row[ rowIndex ] :
1427
+ cache.normalized[ rowIndex ][ c.columns ].$row[0];
1428
+ // check if has class filtered
1429
+ if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) {
1430
+ continue;
1431
+ }
1432
+ // get non-normalized cell content
1433
+ if ( wo.filter_useParsedData ||
1434
+ c.parsers[column].parsed ||
1435
+ c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) {
1436
+ arry.push( '' + cache.normalized[ rowIndex ][ column ] );
1437
+ } else {
1438
+ // get raw cached data instead of content directly from the cells
1439
+ arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] );
1440
+ }
1384
1441
  }
1385
- // using an older version! do a basic sort
1386
- return true;
1387
- });
1388
- // rebuild arry from sorted parsed data
1389
- arry = [];
1390
- len = parsed.length;
1391
- for ( indx = 0; indx < len; indx++ ) {
1392
- arry.push( parsed[indx].t );
1393
1442
  }
1394
1443
  return arry;
1395
- }
1396
- },
1397
- getOptions: function( table, column, onlyAvail ) {
1398
- table = $( table )[0];
1399
- var rowIndex, tbodyIndex, len, row, cache,
1400
- c = table.config,
1401
- wo = c.widgetOptions,
1402
- arry = [];
1403
- for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) {
1404
- cache = c.cache[tbodyIndex];
1405
- len = c.cache[tbodyIndex].normalized.length;
1406
- // loop through the rows
1407
- for ( rowIndex = 0; rowIndex < len; rowIndex++ ) {
1408
- // get cached row from cache.row ( old ) or row data object
1409
- // ( new; last item in normalized array )
1410
- row = cache.row ?
1411
- cache.row[ rowIndex ] :
1412
- cache.normalized[ rowIndex ][ c.columns ].$row[0];
1413
- // check if has class filtered
1414
- if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) {
1415
- continue;
1416
- }
1417
- // get non-normalized cell content
1418
- if ( wo.filter_useParsedData ||
1419
- c.parsers[column].parsed ||
1420
- c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) {
1421
- arry.push( '' + cache.normalized[ rowIndex ][ column ] );
1422
- } else {
1423
- // get raw cached data instead of content directly from the cells
1424
- arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] );
1425
- }
1444
+ },
1445
+ buildSelect: function( table, column, arry, updating, onlyAvail ) {
1446
+ table = $( table )[0];
1447
+ column = parseInt( column, 10 );
1448
+ if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) {
1449
+ return;
1450
+ }
1451
+ var indx, val, txt, t, $filters, $filter,
1452
+ c = table.config,
1453
+ wo = c.widgetOptions,
1454
+ node = c.$headerIndexed[ column ],
1455
+ // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3
1456
+ options = '<option value="">' +
1457
+ ( node.data( 'placeholder' ) ||
1458
+ node.attr( 'data-placeholder' ) ||
1459
+ wo.filter_placeholder.select || ''
1460
+ ) + '</option>',
1461
+ // Get curent filter value
1462
+ currentValue = c.$table
1463
+ .find( 'thead' )
1464
+ .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
1465
+ .val();
1466
+ // nothing included in arry ( external source ), so get the options from
1467
+ // filter_selectSource or column data
1468
+ if ( typeof arry === 'undefined' || arry === '' ) {
1469
+ arry = ts.filter.getOptionSource( table, column, onlyAvail );
1426
1470
  }
1427
- }
1428
- return arry;
1429
- },
1430
- buildSelect: function( table, column, arry, updating, onlyAvail ) {
1431
- table = $( table )[0];
1432
- column = parseInt( column, 10 );
1433
- if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) {
1434
- return;
1435
- }
1436
- var indx, val, txt, t, $filters, $filter,
1437
- c = table.config,
1438
- wo = c.widgetOptions,
1439
- node = c.$headerIndexed[ column ],
1440
- // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3
1441
- options = '<option value="">' +
1442
- ( node.data( 'placeholder' ) ||
1443
- node.attr( 'data-placeholder' ) ||
1444
- wo.filter_placeholder.select || ''
1445
- ) + '</option>',
1446
- // Get curent filter value
1447
- currentValue = c.$table
1448
- .find( 'thead' )
1449
- .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' )
1450
- .val();
1451
- // nothing included in arry ( external source ), so get the options from
1452
- // filter_selectSource or column data
1453
- if ( typeof arry === 'undefined' || arry === '' ) {
1454
- arry = ts.filter.getOptionSource( table, column, onlyAvail );
1455
- }
1456
1471
 
1457
- if ( $.isArray( arry ) ) {
1458
- // build option list
1459
- for ( indx = 0; indx < arry.length; indx++ ) {
1460
- txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '&quot;' );
1461
- val = txt;
1462
- // allow including a symbol in the selectSource array
1463
- // 'a-z|A through Z' so that 'a-z' becomes the option value
1464
- // and 'A through Z' becomes the option text
1465
- if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
1466
- t = txt.split( wo.filter_selectSourceSeparator );
1467
- val = t[0];
1468
- txt = t[1];
1472
+ if ( $.isArray( arry ) ) {
1473
+ // build option list
1474
+ for ( indx = 0; indx < arry.length; indx++ ) {
1475
+ txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '&quot;' );
1476
+ val = txt;
1477
+ // allow including a symbol in the selectSource array
1478
+ // 'a-z|A through Z' so that 'a-z' becomes the option value
1479
+ // and 'A through Z' becomes the option text
1480
+ if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) {
1481
+ t = txt.split( wo.filter_selectSourceSeparator );
1482
+ val = t[0];
1483
+ txt = t[1];
1484
+ }
1485
+ // replace quotes - fixes #242 & ignore empty strings
1486
+ // see http://stackoverflow.com/q/14990971/145346
1487
+ options += arry[indx] !== '' ?
1488
+ '<option ' +
1489
+ ( val === txt ? '' : 'data-function-name="' + arry[indx] + '" ' ) +
1490
+ 'value="' + val + '">' + txt +
1491
+ '</option>' : '';
1469
1492
  }
1470
- // replace quotes - fixes #242 & ignore empty strings
1471
- // see http://stackoverflow.com/q/14990971/145346
1472
- options += arry[indx] !== '' ?
1473
- '<option ' +
1474
- ( val === txt ? '' : 'data-function-name="' + arry[indx] + '" ' ) +
1475
- 'value="' + val + '">' + txt +
1476
- '</option>' : '';
1493
+ // clear arry so it doesn't get appended twice
1494
+ arry = [];
1477
1495
  }
1478
- // clear arry so it doesn't get appended twice
1479
- arry = [];
1480
- }
1481
1496
 
1482
- // update all selects in the same column ( clone thead in sticky headers &
1483
- // any external selects ) - fixes 473
1484
- $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) )
1485
- .find( '.' + tscss.filter );
1486
- if ( wo.filter_$externalFilters ) {
1487
- $filters = $filters && $filters.length ?
1488
- $filters.add( wo.filter_$externalFilters ) :
1489
- wo.filter_$externalFilters;
1490
- }
1491
- $filter = $filters.filter( 'select[data-column="' + column + '"]' );
1497
+ // update all selects in the same column ( clone thead in sticky headers &
1498
+ // any external selects ) - fixes 473
1499
+ $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) )
1500
+ .find( '.' + tscss.filter );
1501
+ if ( wo.filter_$externalFilters ) {
1502
+ $filters = $filters && $filters.length ?
1503
+ $filters.add( wo.filter_$externalFilters ) :
1504
+ wo.filter_$externalFilters;
1505
+ }
1506
+ $filter = $filters.filter( 'select[data-column="' + column + '"]' );
1492
1507
 
1493
- // make sure there is a select there!
1494
- if ( $filter.length ) {
1495
- $filter[ updating ? 'html' : 'append' ]( options );
1496
- if ( !$.isArray( arry ) ) {
1497
- // append options if arry is provided externally as a string or jQuery object
1498
- // options ( default value ) was already added
1499
- $filter.append( arry ).val( currentValue );
1508
+ // make sure there is a select there!
1509
+ if ( $filter.length ) {
1510
+ $filter[ updating ? 'html' : 'append' ]( options );
1511
+ if ( !$.isArray( arry ) ) {
1512
+ // append options if arry is provided externally as a string or jQuery object
1513
+ // options ( default value ) was already added
1514
+ $filter.append( arry ).val( currentValue );
1515
+ }
1516
+ $filter.val( currentValue );
1500
1517
  }
1501
- $filter.val( currentValue );
1502
- }
1503
- },
1504
- buildDefault: function( table, updating ) {
1505
- var columnIndex, $header, noSelect,
1506
- c = table.config,
1507
- wo = c.widgetOptions,
1508
- columns = c.columns;
1509
- // build default select dropdown
1510
- for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) {
1511
- $header = c.$headerIndexed[columnIndex];
1512
- noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
1513
- // look for the filter-select class; build/update it if found
1514
- if ( ( $header.hasClass( 'filter-select' ) ||
1515
- ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
1516
- ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
1518
+ },
1519
+ buildDefault: function( table, updating ) {
1520
+ var columnIndex, $header, noSelect,
1521
+ c = table.config,
1522
+ wo = c.widgetOptions,
1523
+ columns = c.columns;
1524
+ // build default select dropdown
1525
+ for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) {
1526
+ $header = c.$headerIndexed[columnIndex];
1527
+ noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) );
1528
+ // look for the filter-select class; build/update it if found
1529
+ if ( ( $header.hasClass( 'filter-select' ) ||
1530
+ ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) {
1531
+ ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) );
1532
+ }
1517
1533
  }
1518
1534
  }
1519
- }
1520
- };
1535
+ };
1521
1536
 
1522
- ts.getFilters = function( table, getRaw, setFilters, skipFirst ) {
1523
- var i, $filters, $column, cols,
1524
- filters = false,
1525
- c = table ? $( table )[0].config : '',
1526
- wo = c ? c.widgetOptions : '';
1527
- if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) ||
1528
- // setFilters called, but last search is exactly the same as the current
1529
- // fixes issue #733 & #903 where calling update causes the input values to reset
1530
- ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) {
1531
- return $( table ).data( 'lastSearch' );
1532
- }
1533
- if ( c ) {
1534
- if ( c.$filters ) {
1535
- $filters = c.$filters.find( '.' + tscss.filter );
1537
+ ts.getFilters = function( table, getRaw, setFilters, skipFirst ) {
1538
+ var i, $filters, $column, cols,
1539
+ filters = false,
1540
+ c = table ? $( table )[0].config : '',
1541
+ wo = c ? c.widgetOptions : '';
1542
+ if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) ||
1543
+ // setFilters called, but last search is exactly the same as the current
1544
+ // fixes issue #733 & #903 where calling update causes the input values to reset
1545
+ ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) {
1546
+ return $( table ).data( 'lastSearch' );
1536
1547
  }
1537
- if ( wo.filter_$externalFilters ) {
1538
- $filters = $filters && $filters.length ?
1539
- $filters.add( wo.filter_$externalFilters ) :
1540
- wo.filter_$externalFilters;
1541
- }
1542
- if ( $filters && $filters.length ) {
1543
- filters = setFilters || [];
1544
- for ( i = 0; i < c.columns + 1; i++ ) {
1545
- cols = ( i === c.columns ?
1546
- // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' )
1547
- wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
1548
- '[data-column="' + i + '"]' );
1549
- $column = $filters.filter( cols );
1550
- if ( $column.length ) {
1551
- // move the latest search to the first slot in the array
1552
- $column = ts.filter.getLatestSearch( $column );
1553
- if ( $.isArray( setFilters ) ) {
1554
- // skip first ( latest input ) to maintain cursor position while typing
1555
- if ( skipFirst && $column.length > 1 ) {
1556
- $column = $column.slice( 1 );
1557
- }
1558
- if ( i === c.columns ) {
1559
- // prevent data-column='all' from filling data-column='0,1' ( etc )
1560
- cols = $column.filter( wo.filter_anyColumnSelector );
1561
- $column = cols.length ? cols : $column;
1562
- }
1563
- $column
1564
- .val( setFilters[ i ] )
1565
- .trigger( 'change.tsfilter' );
1566
- } else {
1567
- filters[i] = $column.val() || '';
1568
- // don't change the first... it will move the cursor
1569
- if ( i === c.columns ) {
1570
- // don't update range columns from 'all' setting
1548
+ if ( c ) {
1549
+ if ( c.$filters ) {
1550
+ $filters = c.$filters.find( '.' + tscss.filter );
1551
+ }
1552
+ if ( wo.filter_$externalFilters ) {
1553
+ $filters = $filters && $filters.length ?
1554
+ $filters.add( wo.filter_$externalFilters ) :
1555
+ wo.filter_$externalFilters;
1556
+ }
1557
+ if ( $filters && $filters.length ) {
1558
+ filters = setFilters || [];
1559
+ for ( i = 0; i < c.columns + 1; i++ ) {
1560
+ cols = ( i === c.columns ?
1561
+ // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' )
1562
+ wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector :
1563
+ '[data-column="' + i + '"]' );
1564
+ $column = $filters.filter( cols );
1565
+ if ( $column.length ) {
1566
+ // move the latest search to the first slot in the array
1567
+ $column = ts.filter.getLatestSearch( $column );
1568
+ if ( $.isArray( setFilters ) ) {
1569
+ // skip first ( latest input ) to maintain cursor position while typing
1570
+ if ( skipFirst && $column.length > 1 ) {
1571
+ $column = $column.slice( 1 );
1572
+ }
1573
+ if ( i === c.columns ) {
1574
+ // prevent data-column='all' from filling data-column='0,1' ( etc )
1575
+ cols = $column.filter( wo.filter_anyColumnSelector );
1576
+ $column = cols.length ? cols : $column;
1577
+ }
1571
1578
  $column
1572
- .slice( 1 )
1573
- .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' )
1574
- .val( filters[ i ] );
1579
+ .val( setFilters[ i ] )
1580
+ .trigger( 'change.tsfilter' );
1575
1581
  } else {
1576
- $column
1577
- .slice( 1 )
1578
- .val( filters[ i ] );
1582
+ filters[i] = $column.val() || '';
1583
+ // don't change the first... it will move the cursor
1584
+ if ( i === c.columns ) {
1585
+ // don't update range columns from 'all' setting
1586
+ $column
1587
+ .slice( 1 )
1588
+ .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' )
1589
+ .val( filters[ i ] );
1590
+ } else {
1591
+ $column
1592
+ .slice( 1 )
1593
+ .val( filters[ i ] );
1594
+ }
1595
+ }
1596
+ // save any match input dynamically
1597
+ if ( i === c.columns && $column.length ) {
1598
+ wo.filter_$anyMatch = $column;
1579
1599
  }
1580
- }
1581
- // save any match input dynamically
1582
- if ( i === c.columns && $column.length ) {
1583
- wo.filter_$anyMatch = $column;
1584
1600
  }
1585
1601
  }
1586
1602
  }
1587
1603
  }
1588
- }
1589
- if ( filters.length === 0 ) {
1590
- filters = false;
1591
- }
1592
- return filters;
1593
- };
1604
+ if ( filters.length === 0 ) {
1605
+ filters = false;
1606
+ }
1607
+ return filters;
1608
+ };
1594
1609
 
1595
- ts.setFilters = function( table, filter, apply, skipFirst ) {
1596
- var c = table ? $( table )[0].config : '',
1597
- valid = ts.getFilters( table, true, filter, skipFirst );
1598
- if ( c && apply ) {
1599
- // ensure new set filters are applied, even if the search is the same
1600
- c.lastCombinedFilter = null;
1601
- c.lastSearch = [];
1602
- ts.filter.searching( c.table, filter, skipFirst );
1603
- c.$table.trigger( 'filterFomatterUpdate' );
1604
- }
1605
- return !!valid;
1606
- };
1610
+ ts.setFilters = function( table, filter, apply, skipFirst ) {
1611
+ var c = table ? $( table )[0].config : '',
1612
+ valid = ts.getFilters( table, true, filter, skipFirst );
1613
+ if ( c && apply ) {
1614
+ // ensure new set filters are applied, even if the search is the same
1615
+ c.lastCombinedFilter = null;
1616
+ c.lastSearch = [];
1617
+ ts.filter.searching( c.table, filter, skipFirst );
1618
+ c.$table.trigger( 'filterFomatterUpdate' );
1619
+ }
1620
+ return !!valid;
1621
+ };
1607
1622
 
1608
1623
  })( jQuery );