jquery-tablesorter 1.17.2 → 1.17.3

Sign up to get free protection for your applications and to get access to all the features.
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 );