Rewrite filter match any column code & replaced filter_anyMatch with filter_external. Fixes #490, #471, #370 & #114

This commit is contained in:
Mottie 2014-02-01 08:18:55 -06:00
parent b9238d3889
commit 55e5bdb225
3 changed files with 259 additions and 144 deletions

View File

@ -2,14 +2,14 @@
<html>
<head>
<meta charset="utf-8">
<title>jQuery plugin: Tablesorter 2.0 - Filter Widget Any Match</title>
<title>jQuery plugin: Tablesorter 2.0 - Filter Widget External Search</title>
<!-- jQuery -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.js"></script>
<!-- Demo stuff -->
<link class="ui-theme" rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/cupertino/jquery-ui.css">
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7/jquery-ui.min.js"></script>
<link class="ui-theme" rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/cupertino/jquery-ui.css">
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>
<link rel="stylesheet" href="css/jq.css">
<link href="css/prettify.css" rel="stylesheet">
<script src="js/prettify.js"></script>
@ -26,52 +26,60 @@
theme: 'blue',
widgets: ["zebra", "filter"],
widgetOptions : {
// if true overrides default find rows behaviours and if any column matches query it returns that row
filter_anyMatch : true,
filter_columnFilters: false,
// filter_anyMatch replaced! Instead use the filter_external option
// Set to use a jQuery selector (or jQuery object) pointing to the
// external filter (column specific or any match)
filter_external : '.search',
// include column filters
filter_columnFilters: true,
filter_saveFilters : true,
filter_reset: '.reset'
}
});
// Target the $('.search') input using built in functioning
// this binds to the search using "search" and "keyup"
// Allows using filter_liveSearch or delayed search &
// pressing escape to cancel the search
$.tablesorter.filter.bindSearch( $table, $('.search') );
// Basic search binding, alternate to the above
// bind to search - pressing enter and clicking on "x" to clear (Webkit)
// keyup allows dynamic searching
/*
$(".search").bind('search keyup', function (e) {
$('table').trigger('search', [ [this.value] ]);
});
*/
});</script>
</head>
<body>
<div id="banner">
<h1>table<em>sorter</em></h1>
<h2>Filter Widget Any Match</h2>
<h2>Filter Widget External Search</h2>
<h3>Flexible client-side table sorting</h3>
<a href="index.html">Back to documentation</a>
</div>
<div id="main">
<p class="tip">
<em>NOTE!</em>
<p></p>
<br>
<div class="accordion">
<h3><a href="#">Notes</a></h3>
<div>
<ul>
<li>This is a demo of the <code>filter_anyMatch</code> option (<span class="version">v2.13.3</span>).</li>
<li>This method has limitations in that it does not support all of the per column search features! So, at this time, the following types of queries are not allowed as the results will cause confusion:
<li>This is a demo of the <a href="index.html#widget-filter-external"><code>filter_external</code></a> option (added <span class="version">v2.15</span>).</li>
<li>In <span class="version">v2.15</span>
<ul>
<li>The <a class="alert" href="index.html#widget-filter-any-match"><code>filter_anyMatch</code></a> has been <span class="label alert">removed</span> (added <span class="version">v2.13.3</span>; removed in v2.15)</li>
<li>A new option <code>filter_external</code> has been added. It is set to a jQuery selector string (<code>'.search'</code>) or jQuery object (<code>$('.search')</code>) targetting an external input.</li>
<li>So now a table can include <em>both</em> a filter row (<code>filter_columnFilters</code> is <code>true</code>, i.e. the internal table filters) and any number of external search inputs (as set by the <code>filter_external</code> option).</li>
<li>The external search results must have a <code>data-column="#"</code> attribute set, where <code>#</code> is the index of the column (zero-based) that the input targets, to have an input search all table content, set the data column attribute to <code>"all"</code> to match any column.</li>
<li>The <a href="index.html#function-bindsearch"><code>$.tablesorter.bindSearch</code> function</a> (<a href="example-widget-filter-external-inputs.html">see demo</a>) does exactly the same thing as the <code>filter_external</code> option. The major difference is seen when using ajax to populate the table, the initial filter values can be set before tablesorter initialization when using teh <code>filter_external</code> option; whereas, the bind search function can not set initial filter values and is usually executed after tablesorter initialization.</li>
</ul>
</li>
</ul>
</div>
<h3><a href="#">Limitations</a></h3>
<div>
<ul>
<li>The any-match search method has limitations applied. It does not support all of the per column search features! So, at this time, the following types of queries are not allowed as the results will cause confusion:
<ul>
<li>Search operators - A search for values equal, greater or less than values (<code>&gt &gt= &lt;= &lt;</code>) is not allowed because tables that contain both numbers and text (in separate columns). For example:
<pre class="prettyprint lang-javascript">&quot;Fred&quot; > &quot;1&quot; // true*
&quot;Fred&quot; < &quot;10&quot; // false
&quot;Fred&quot; > 1 // false (numeric comparisons occur with parsed table data)
&quot;Fred&quot; > 1 // false
&quot;Fred&quot; < 10 // false</pre>* For comparisons, letters have a greater <a href="http://en.wikipedia.org/wiki/Ascii#ASCII_printable_characters">ASCII value</a> than numbers.
&quot;Fred&quot; &lt; &quot;10&quot; // false
&quot;Fred&quot; &gt; 1 // false (numeric comparisons occur with parsed table data)
&quot;Fred&quot; &gt; 1 // false
&quot;Fred&quot; &lt; 10 // false</pre>* For comparisons, letters have a greater <a href="http://en.wikipedia.org/wiki/Ascii#ASCII_printable_characters">ASCII value</a> than numbers.
</li>
<li>Range query - A search for any number range (<code>1 - 10</code>) is not allowed because, if any columns contain text, then no rows will result. The examples are the same as the search operators examples above.</li>
<li>Not Match query - A search for not matches (<code>!a</code>) is not allowed because tables that contain both numbers and text (in separate columns) will always show all rows. For example:
@ -91,15 +99,19 @@ columns[5] = '2?%';
$('table').trigger('search', [ columns ]);</pre></li>
</li>
</ul>
</p>
</div>
</div>
<br>
<h1>Demo</h1>
<div id="demo">
<input class="search" type="search">
<button type="button" class="reset">Reset Search</button> <!-- targeted by the "filter_reset" option -->
<div id="demo"><input class="search" type="search" data-column="all"> (Match any column)<br>
<input class="search" type="search" data-column="1"> (First Name)<br>
<table class="tablesorter">
<!-- targeted by the "filter_reset" option -->
<button type="button" class="reset">Reset Search</button>
<table class="tablesorter">
<thead>
<tr>
<th>Rank</th>

View File

@ -5,11 +5,11 @@
<title>jQuery plugin: Tablesorter 2.0 - Filter Widget</title>
<!-- jQuery -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.js"></script>
<!-- Demo stuff -->
<link class="ui-theme" rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/cupertino/jquery-ui.css">
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7/jquery-ui.min.js"></script>
<link class="ui-theme" rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/cupertino/jquery-ui.css">
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>
<link rel="stylesheet" href="css/jq.css">
<link href="css/prettify.css" rel="stylesheet">
<script src="js/prettify.js"></script>
@ -24,15 +24,6 @@
<script src="../js/jquery.tablesorter.js"></script>
<script src="../js/jquery.tablesorter.widgets.js"></script>
<script>
$(function(){
$('.accordion').accordion({
autoHeight: false,
collapsible : true
});
});
</script>
<script id="js">$(function() {
// call the tablesorter plugin
@ -49,6 +40,8 @@
widgetOptions : {
// filter_anyMatch options was removed in v2.15; it has been replaced by the filter_external option
// If there are child rows in the table (rows with class name from "cssChildRow" option)
// and this option is true and a match is found anywhere in the child row, then it will make that row
// visible; default is false
@ -59,8 +52,13 @@
// if you set this to false, make sure you perform a search using the second method below
filter_columnFilters : true,
// extra css class applied to the table row containing the filters & the inputs within that row
filter_cssFilter : '',
// extra css class name(s) applied to the table row containing the filters & the inputs within that row
// this option can either be a string (class applied to all filters) or an array (class applied to indexed filter)
filter_cssFilter : '', // or []
// jQuery selector (or object) pointing to an input to be used to match the contents of any column
// please refer to the filter-any-match demo for limitations - new in v2.15
filter_external : '',
// class added to filtered rows (rows that are not showing); needed by pager plugin
filter_filteredRow : 'filtered',
@ -83,6 +81,9 @@
// if true, search column content while the user types (with a delay)
filter_liveSearch : true,
// a header with a select dropdown & this class name will only show available (visible) options within that drop down.
filter_onlyAvail : 'filter-onlyAvail',
// jQuery selector string of an element used to reset the filters
filter_reset : 'button.reset',
@ -103,7 +104,10 @@
// Filter using parsed content for ALL columns
// be careful on using this on date columns as the date is parsed and stored as time in seconds
filter_useParsedData : false
filter_useParsedData : false,
// data attribute in the header cell that contains the default filter value
filter_defaultAttrib : 'data-value'
}
@ -147,15 +151,7 @@
<script>
$(function(){
/*
$('button').not('.toggle').click(function(){
var $t = $(this),
filter = [];
filter[$t.attr('data-filter-column')] = $t.attr('data-filter-column') || $t.text();
$.tablesorter.setFilters( $('table.tablesorter'), filter, true );
return false;
});
*/
// *** widgetfilter_startsWith toggle button ***
$('button.toggle').click(function(){
var c = $('table.tablesorter')[0].config,
@ -195,9 +191,15 @@ $(function(){
<br>
<div class="accordion">
<h3><a href="#">Notes</a></h3>
<h3 id="notes"><a href="#">Notes</a></h3>
<div>
<ul>
<li>In <span class="version updated">v2.15</span>,
<ul>
<li>The <code>filter_anyMatch</code> widget option was completely <span class="label alert">removed</span>. Sorry for not deprecating this option, but the filter any match code was completely rewritten.</li>
<li>Added a <code>filter_external</code> widget option to only accept a jQuery selector string/object; please see the updated <a href="example-widget-filter-any-match.html">filter any match</a> demo for more details.</li>
</ul>
</li>
<li>Added &amp; set <code>filter_saveFilters</code> to <code>true</code> (default is <code>false</code>) in this demo (<span class="version">v2.14</span>).</li>
<li>Hover over the grey bar below the table header to open the filter row. Disable this by setting <code>filter_hideFilters</code> option to <code>false</code>.</li>
<li>This widget uses jQuery's <code>.nextUntil()</code> function which is only available is jQuery version 1.4+.</li>
@ -220,7 +222,7 @@ $(function(){
</tbody>
</table>
<span class="bright">(1)</span> You cannot combine these operators with each other (except for the wildcards).<br>
<span class="bright">(2)</span> The filter order (or precendence) of how searches are checked is as follows: <span class="smallcode">regex (<code>/\d/</code>) <strong>&gt;</strong> operators (<code>&lt; &lt;= &gt;= &gt;</code>) <strong>&gt;</strong> exact (<code>"</code>) <strong>&gt;</strong> not match (<code>!</code>) <strong>&gt;</strong> and (<code>&nbsp;AND&nbsp;</code>) <strong>&gt;</strong> range (<code>&nbsp;-&nbsp;</code>) <strong>&gt;</strong> wild/or (<code>?</code>, <code>*</code> and <code>&nbsp;OR&nbsp;</code>) <strong>&gt;</strong> fuzzy (<code>~</code>); so an exact match will override "and", "or" and "range" searches </span> (*NOTE* order changed in <span class="version">v2.14.6</span>, operators prioritized before exact; see <a href="https://github.com/Mottie/tablesorter/issues/465">issue #465</a>) <br>
<span class="bright">(2)</span> The filter order (or precendence) of how searches are checked is as follows: <span class="smallcode">regex (<code>/\d/</code>) <strong>&gt;</strong> operators (<code>&lt; &lt;= &gt;= &gt;</code>) <strong>&gt;</strong> exact (<code>"</code>) <strong>&gt;</strong> not match (<code>!</code>) <strong>&gt;</strong> and (<code>&nbsp;AND&nbsp;</code>) <strong>&gt;</strong> range (<code>&nbsp;-&nbsp;</code>) <strong>&gt;</strong> wild/or (<code>?</code>, <code>*</code> and <code>&nbsp;OR&nbsp;</code>) <strong>&gt;</strong> fuzzy (<code>~</code>); so an exact match will override "and", "or" and "range" searches </span> (*NOTE* order changed in <span class="version updated">v2.15</span>, operators prioritized before exact; see <a href="https://github.com/Mottie/tablesorter/issues/465">issue #465</a>) <br>
<span class="bright">(3)</span> Logical "or" comparisons can now show exact matches (by default; <span class="version">v2.10.1</span>) or just match cell contents.<br>
<span class="bright">(4)</span> In tablesorter <span class="version">v2.10</span>, comparisons can be made in date columns (if properly parsed).
</li>
@ -231,11 +233,12 @@ $(function(){
<div>
<h3>Filter widget defaults (added inside of tablesorter <code>widgetOptions</code>)</h3>
<ul>
<li><code>filter_anyMatch : false</code> - if true, a match in any column will show the row (see <a href="example-widget-filter-any-match.html">the demo</a> for exceptions).</li>
<li><del><code>filter_anyMatch : null</code></del> - jQuery selector string (or jQuery object) of external anyMatch filter (<span class="label alert">removed</span> in v2.15; please see <a href="example-widget-filter-any-match.html">the demo</a> for more details).</li>
<li><code>filter_childRows : false</code> - if true, filter includes child row content in the search.</li>
<li><code>filter_columnFilters : true</code> - if true, a filter will be added to the top of each table column.</li>
<li><code>filter_cssFilter : ''</code> - extra css class name added to the filter row & each input in the row.</li>
<li><code>filter_cssFilter : ''</code> - extra css class name(s) applied to the table row containing the filters & the inputs within that row. This option can either be a string (class applied to all filters) or an array (class applied to indexed filter).</li>
<li><code>filter_defaultAttrib : 'data-value'</code> - this option contains the name of the data-attribute which contains the default (starting) filter value.</li>
<li><code>filter_external : ''</code> - jQuery selector (or object) pointing to an input to be used to match the contents of any column; please refer to the updated <a href="example-widget-filter-any-match.html">filter-any-match demo</a> for limitations (<span class="version">v2.15</span>).</li>
<li><code>filter_filteredRow : 'filtered'</code> - css class name added to filtered rows (rows that are not showing); needed by pager plugin.</li>
<li><code>filter_formatter : null</code> - add custom filter elements to the filter row.</li>
<li><code>filter_functions : null</code> - add custom filter functions using this option.</li>
@ -283,14 +286,25 @@ columns[5] = '2?%';
$('table').trigger('search', [ columns ]);</pre>
or, just pass <code>false</code> to refresh the current search:
<pre class="prettyprint lang-javascript">$('table').trigger('search', false);</pre>
* Note: using this search method <strong>will not</strong> update the contents of the filters within the filter row; use the <code>$.tablesorter.setFilter()</code> method below to do that.
* Note: using this search method will update the contents of the filters within the filter row.<br>
<br>
In <span class="version updated">v2.15</span>, one additional parameter can be added to the array to perform an "any-match" of the table; <span class="label warning">Warning!</span> please note that if no external input (with a <code>data-column="all"</code> is attached using <a href="index.html#function-bindsearch"><code>bindSearch</code></a> function) is visible, the user will not know that a filter has been applied. <pre class="prettyprint lang-js">// in a table with 4 columns; set the 5th parameter to any-match
$('table').trigger( 'search', [['', '', '', '', 'orange']] ); // find orange in any column</pre>
</blockquote>
<h3>Get current filters</h3>
<blockquote>
Get an array of the currently applied filters (<span class="version">v2.9</span>).
<pre class="prettyprint lang-javascript">$.tablesorter.getFilters( $('table') ); // or $('table.hasFilters')</pre>
This method returns an array of filter values or <code>false</code> if the selected table does not contain a filter row.
This method returns an array of filter values or <code>false</code> if the selected table does not contain a filter row.<br>
<br>
Internally, the get filters functions returns the filters stored within this table data <code>$('table').data('lastSearch')</code>, unless an additional parameter of <code>true</code> is passed to the function:
<pre class="prettyprint lang-javascript">$.tablesorter.getFilters( $('table'), true ); // get current input values instead of data stored filters</pre>
As of <span class="version updated">v2.15</span>, if an external "any-match" filter (see <code>filter_external</code> widget option) is included, one additional array parameter will be returned in the filter - the value of the any match search.
<pre class="prettyprint lang-js">// in a table with six columns, this function will return an array with seven
// parameters; something like this: [ '', '', '', '', '', '', '11' ],
// where the "11" will be obtained from the "any-match" input
var filters = $.tablesorter.getFilters( $('table') );</pre>
</blockquote>
<h3>Set current filters</h3>
@ -301,17 +315,27 @@ $.tablesorter.setFilters( $('table'), [ '', '', '', '', '', '2?%' ] );
// update filters, AND apply the search
$.tablesorter.setFilters( $('table'), [ '', '', '', '', '', '2?%' ], true );</pre>
This method returns <code>true</code> if the filters were sucessfully applied, or <code>false</code> if the table does have a filter row.
This method returns <code>true</code> if the filters were sucessfully applied, or <code>false</code> if the table does have a filter row.<br>
<br>
As of <span class="version updated">v2.15</span>, if an external "any-match" filter (see <code>filter_external</code> widget option) is included, add an additional array parameter to set that filter; so if a table has six columns, add the any-match search as the seventh value.
<pre class="prettyprint lang-js">// in a table with 6 columns, the 7th filter will apply
// to the any-match filter & match any table column with "11"
$.tablesorter.setFilters( $('table'), [ '', '', '', '', '', '', '11' ], true );</pre>
</blockquote>
<h3>Bind External filter</h3>
<blockquote>
Use this method in conjunction with the <code>filter_anyMatch</code> option (set to <code>true</code> (<span class="version">v2.13.3</span>).
Use this method to bind external search filters; include a data-attribute <code>data-column</code> with the column index to target, or use <code>data-column="all"</code> to preform an "any-match" search (<span class="version updated">v2.15</span>). If no <code>data-column</code> is added to the input, the input will be ignored.
<pre class="prettyprint lang-html">&lt;!-- will perform an "any-match" type of search (matches any column) --&gt;
&lt;input type="search" class="search" data-column="all"&gt;
&lt;!-- will only search the first column (zero-based index) --&gt;
&lt;input type="search" class="search" data-column="0"&gt;</pre>
<pre class="prettyprint lang-javascript">// Target the $('.search') input using built in functioning
// this binds to the search using "search" and "keyup"
// Allows using filter_liveSearch or delayed search &
// pressing escape to cancel the search
$.tablesorter.filter.bindSearch( $table, $('.search') );</pre>
// this binds to the search using "search" and "keyup" and allows using filter_liveSearch
// or delayed search & pressing escape to cancel the search
// pass an optional third parameter (false boolean) to force the inputs to update
// this allows changing the data-column for an input dynamically
$.tablesorter.filter.bindSearch( $table, $('.search'), false );</pre>
If you don't care about the enhanced search filter, then bind to both the &quot;keyup&quot; and &quot;search&quot; events
<pre class="prettyprint lang-javascript">// Basic search binding, alternate to the above
// bind to search - pressing enter and clicking on "x" to clear (Webkit)

View File

@ -1,4 +1,4 @@
/*! tableSorter 2.8+ widgets - updated 12/16/2013 (v2.14.5)
/*! tableSorter 2.8+ widgets - updated 12/16/2013 (v2.15.0)
*
* Column Styles
* Column Filters
@ -338,10 +338,10 @@ ts.addWidget({
id: "filter",
priority: 50,
options : {
filter_anyMatch : false, // if true overrides default find rows behaviours and if any column matches query it returns that row
filter_childRows : false, // if true, filter includes child row content in the search
filter_columnFilters : true, // if true, a filter will be added to the top of each table column
filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
filter_external : '', // jQuery selector string (or jQuery object) of external filters
filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
filter_formatter : null, // add custom filter elements to the filter row
filter_functions : null, // add custom filter functions using this option
@ -446,10 +446,11 @@ ts.filter = {
return null;
},
// Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
exact: function( filter, iFilter, exact, iExact ) {
exact: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed, rowArray ) {
/*jshint eqeqeq:false */
if (ts.filter.regex.exact.test(iFilter)) {
return iFilter.replace(ts.filter.regex.exact, '') == iExact;
var fltr = iFilter.replace(ts.filter.regex.exact, '');
return rowArray ? $.inArray(fltr, rowArray) >= 0 : fltr == iExact;
}
return null;
},
@ -500,13 +501,13 @@ ts.filter = {
return null;
},
// Look for wild card: ? = single, * = multiple, or | = logical OR
wild : function( filter, iFilter, exact, iExact, cached, index, table ) {
wild : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed, rowArray ) {
if ( /[\?|\*]/.test(iFilter) || /\s+OR\s+/i.test(filter) ) {
var c = table.config,
query = iFilter.replace(/\s+OR\s+/gi,"|");
// look for an exact match with the "or" unless the "filter-match" class is found
if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) {
query = '^(' + query + ')$';
query = $.isArray(rowArray) ? '(' + query + ')' : '^(' + query + ')$';
}
return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(iExact);
}
@ -560,11 +561,16 @@ ts.filter = {
} else {
// send false argument to force a new search; otherwise if the filter hasn't changed, it will return
filter = event.type === 'search' ? filter : event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
ts.filter.searching(table, filter);
// pass true (dontSkip) to prevent the tablesorter.setFilters function from skipping the first input
// ensures all inputs are updated when a search is triggered on the table $('table').trigger('search', [...]);
ts.filter.searching(table, filter, true);
}
return false;
});
ts.filter.bindSearch( table, c.$table.find('input.tablesorter-filter') );
ts.filter.bindSearch( table, c.$table.find('input.tablesorter-filter'), true );
if (wo.filter_external) {
ts.filter.bindSearch( table, wo.filter_external );
}
// reset button/link
if (wo.filter_reset) {
@ -600,7 +606,7 @@ ts.filter = {
ts.filter.buildDefault(table, true);
c.$table.find('select.tablesorter-filter').bind('change search', function(event, filter) {
ts.filter.checkFilters(table, filter);
ts.filter.checkFilters(table, filter, true);
});
if (wo.filter_hideFilters) {
@ -634,10 +640,9 @@ ts.filter = {
c.$table.trigger('filterInit');
},
setDefaults: function(table, c, wo) {
var indx, isArray, saved,
var isArray, saved,
// get current (default) filters
filters = ts.getFilters(table),
columns = c.columns;
filters = ts.getFilters(table);
if (wo.filter_saveFilters && ts.storage) {
saved = ts.storage( table, 'tablesorter-filters' ) || [];
isArray = $.isArray(saved);
@ -705,14 +710,30 @@ ts.filter = {
}
}
},
bindSearch: function(table, $el) {
bindSearch: function(table, $el, internal) {
table = $(table)[0];
$el = $($el); // allow passing a selector string
var external, wo = table.config.widgetOptions;
if (!$el.length) { return; }
var c = table.config,
wo = c.widgetOptions,
$ext = wo.filter_$externalFilters;
if (internal !== true) {
// save anyMatch element
wo.filter_$anyMatch = $el.filter('[data-column="all"]');
if ($ext && $ext.length) {
wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el );
} else {
wo.filter_$externalFilters = $el;
}
// update values (external filters added after table initialization)
ts.setFilters(table, c.$table.data('lastSearch') || [], internal === false);
}
$el
.data('lastSearchTime', new Date().getTime())
.unbind('keyup search change')
// include change for select - fixes #473
$el.unbind('keyup search change filterReset')
.bind('keyup search change', function(event, filter) {
var $this = $(this);
.bind('keyup search change', function(event, filters) {
$(this).data('lastSearchTime', new Date().getTime());
// emulate what webkit does.... escape clears the filter
if (event.which === 27) {
this.value = '';
@ -722,23 +743,14 @@ ts.filter = {
( event.which >= 37 && event.which <= 40 ) || (event.which !== 13 && wo.filter_liveSearch === false) ) ) ) {
return;
}
// external searches won't have a filter parameter, so grab the value
if ($this.hasClass('tablesorter-filter') && !$this.hasClass('tablesorter-external-filter')) {
external = filter;
} else {
external = [];
$el.each(function(){
// target the appropriate column if the external input has a data-column attribute
external[ $(this).data('column') || 0 ] = $(this).val();
});
}
ts.filter.searching(table, filter, external);
})
.bind('filterReset', function(){
// true flag in getFilters forces obtaining the latest values
ts.filter.searching( table, filters || ts.getFilters( table, true ), true );
});
c.$table.bind('filterReset', function(){
$el.val('');
});
},
checkFilters: function(table, filter) {
checkFilters: function(table, filter, dontSkip) {
var c = table.config,
wo = c.widgetOptions,
filterArray = $.isArray(filter),
@ -746,7 +758,7 @@ ts.filter = {
combinedFilters = (filters || []).join(''); // combined filter values
// add filter array back into inputs
if (filterArray) {
ts.setFilters( table, filters );
ts.setFilters( table, filters, false, dontSkip !== true );
}
if (wo.filter_hideFilters) {
// show/hide filter row as needed
@ -814,6 +826,7 @@ ts.filter = {
var cached, len, $rows, cacheIndex, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
childRow, childRowText, exact, iExact, iFilter, lastSearch, matches, result,
searchFiltered, filterMatched, showRow, time,
anyMatch, iAnyMatch, rowArray, rowText, iRowText, rowCache,
c = table.config,
wo = c.widgetOptions,
columns = c.columns,
@ -845,6 +858,15 @@ ts.filter = {
});
// can't search when all rows are hidden - this happens when looking for exact matches
if (searchFiltered && $rows.filter(':visible').length === 0) { searchFiltered = false; }
if ((wo.filter_$anyMatch && wo.filter_$anyMatch.length) || filters[c.columns]) {
anyMatch = wo.filter_$anyMatch && wo.filter_$anyMatch.val() || filters[c.columns] || '';
if (c.sortLocaleCompare) {
// replace accents
anyMatch = ts.replaceAccents(anyMatch);
}
iAnyMatch = anyMatch.toLowerCase();
}
// loop through the rows
cacheIndex = 0;
for (rowIndex = 0; rowIndex < len; rowIndex++) {
@ -860,9 +882,43 @@ ts.filter = {
childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
childRowText = wo.filter_ignoreCase ? childRowText.toLocaleLowerCase() : childRowText;
$cells = $rows.eq(rowIndex).children('td');
if (anyMatch) {
rowArray = $cells.map(function(i){
var txt;
if (parsed[i]) {
txt = c.cache[tbodyIndex].normalized[cacheIndex][i];
} else {
txt = wo.filter_ignoreCase ? $(this).text().toLowerCase() : $(this).text();
if (c.sortLocaleCompare) {
txt = ts.replaceAccents(txt);
}
}
return txt;
}).get();
rowText = rowArray.join(' ');
iRowText = rowText.toLowerCase();
rowCache = c.cache[tbodyIndex].normalized[cacheIndex].join(' ');
filterMatched = null;
$.each(ts.filter.types, function(type, typeFunction) {
if ($.inArray(type, anyMatchNotAllowedTypes) < 0) {
matches = typeFunction( anyMatch, iAnyMatch, rowText, iRowText, rowCache, columns, table, wo, parsed, rowArray );
if (matches !== null) {
filterMatched = matches;
return false;
}
}
});
if (filterMatched !== null) {
showRow = filterMatched;
} else {
showRow = (iRowText + childRowText).indexOf(iAnyMatch) >= 0;
}
}
for (columnIndex = 0; columnIndex < columns; columnIndex++) {
// ignore if filter is empty or disabled
if (filters[columnIndex] || wo.filter_anyMatch) {
if (filters[columnIndex]) {
cached = c.cache[tbodyIndex].normalized[cacheIndex][columnIndex];
// check if column data should be from the cell or from parsed data
if (wo.filter_useParsedData || parsed[columnIndex]) {
@ -875,14 +931,10 @@ ts.filter = {
iExact = !ts.filter.regex.type.test(typeof exact) && wo.filter_ignoreCase ? exact.toLocaleLowerCase() : exact;
result = showRow; // if showRow is true, show that row
if (typeof filters[columnIndex] === "undefined" || filters[columnIndex] === null) {
filters[columnIndex] = wo.filter_anyMatch ? combinedFilters : filters[columnIndex];
}
// replace accents - see #357
filters[columnIndex] = c.sortLocaleCompare ? ts.replaceAccents(filters[columnIndex]) : filters[columnIndex];
// val = case insensitive, filters[columnIndex] = case sensitive
iFilter = wo.filter_ignoreCase ? filters[columnIndex].toLocaleLowerCase() : filters[columnIndex];
iFilter = wo.filter_ignoreCase ? (filters[columnIndex] || '').toLocaleLowerCase() : filters[columnIndex];
if (wo.filter_functions && wo.filter_functions[columnIndex]) {
if (wo.filter_functions[columnIndex] === true) {
// default selector; no "filter-select" class
@ -900,12 +952,10 @@ ts.filter = {
// cycle through the different filters
// filters return a boolean or null if nothing matches
$.each(ts.filter.types, function(type, typeFunction) {
if (!wo.filter_anyMatch || (wo.filter_anyMatch && $.inArray(type, anyMatchNotAllowedTypes) < 0)) {
matches = typeFunction( filters[columnIndex], iFilter, exact, iExact, cached, columnIndex, table, wo, parsed );
if (matches !== null) {
filterMatched = matches;
return false;
}
matches = typeFunction( filters[columnIndex], iFilter, exact, iExact, cached, columnIndex, table, wo, parsed );
if (matches !== null) {
filterMatched = matches;
return false;
}
});
if (filterMatched !== null) {
@ -916,14 +966,7 @@ ts.filter = {
result = ( (!wo.filter_startsWith && exact >= 0) || (wo.filter_startsWith && exact === 0) );
}
}
if (wo.filter_anyMatch) {
showRow = result;
if (showRow){
break;
}
} else {
showRow = (result) ? showRow : false;
}
showRow = (result) ? showRow : false;
}
}
$rows[rowIndex].style.display = (showRow ? '' : 'none');
@ -1015,39 +1058,75 @@ ts.filter = {
}
}
},
searching: function(table, filter, external) {
if (typeof filter === 'undefined' || filter === true || external) {
searching: function(table, filter, dontSkip) {
if (typeof filter === 'undefined' || filter === true) {
var wo = table.config.widgetOptions;
// delay filtering
clearTimeout(wo.searchTimer);
wo.searchTimer = setTimeout(function() {
ts.filter.checkFilters(table, external || filter);
ts.filter.checkFilters(table, filter, dontSkip );
}, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
} else {
// skip delay
ts.filter.checkFilters(table, filter);
ts.filter.checkFilters(table, filter, dontSkip);
}
}
};
ts.getFilters = function(table) {
var c = table ? $(table)[0].config : {};
if (c && c.widgetOptions && !c.widgetOptions.filter_columnFilters) {
// no filter row
ts.getFilters = function(table, getRaw, setFilters, skipFirst) {
var i, $filters, $column,
filters = false,
c = table ? $(table)[0].config : '',
wo = table ? c.widgetOptions : '';
if (getRaw !== true && wo && !wo.filter_columnFilters) {
return $(table).data('lastSearch');
}
return c && c.$filters ? c.$filters.map(function(indx, el) {
return $(el).find('.tablesorter-filter').val() || '';
}).get() || [] : false;
if (c) {
if (c.$filters) {
$filters = c.$filters.find('.tablesorter-filter');
}
if (wo.filter_$externalFilters) {
$filters = $filters && $filters.length ? $filters.add(wo.filter_$externalFilters) : wo.filter_$externalFilters;
}
if ($filters && $filters.length) {
filters = setFilters || [];
for (i = 0; i < c.columns + 1; i++) {
$column = $filters.filter('[data-column="' + (i === c.columns ? 'all' : i) + '"]');
if ($column.length) {
// move the latest search to the first slot in the array
$column = $column.sort(function(a, b){
return $(a).data('lastSearchTime') <= $(b).data('lastSearchTime');
});
if ($.isArray(setFilters)) {
// skip first (latest input) to maintain cursor position while typing
(skipFirst ? $column.slice(1) : $column).val( setFilters[i] ).trigger('change.tsfilter');
} else {
filters[i] = $column.val() || '';
// don't change the first... it will move the cursor
$column.slice(1).val( filters[i] );
}
// save any match input dynamically
if (i === c.columns && $column.length) {
wo.filter_$anyMatch = $column;
}
}
}
}
}
if (filters.length === 0) {
filters = false;
}
return filters;
};
ts.setFilters = function(table, filter, apply) {
var $table = $(table),
c = $table.length ? $table[0].config : {},
valid = c && c.$filters ? c.$filters.each(function(indx, el) {
$(el).find('.tablesorter-filter').val(filter[indx] || '');
}).trigger('change.tsfilter') || false : false;
if (apply) { $table.trigger('search', [filter, false]); }
ts.setFilters = function(table, filter, apply, skipFirst) {
var c = table ? $(table)[0].config : '',
valid = ts.getFilters(table, true, filter, skipFirst);
if (apply) {
// ensure new set filters are applied, even if the search is the same
c.lastCombinedFilter = null;
$(table).trigger('search', [filter, false]);
}
return !!valid;
};
@ -1163,7 +1242,7 @@ ts.addWidget({
resizeHeader();
});
ts.bindEvents(table, $stickyThead.children().children());
ts.bindEvents(table, $stickyThead.children().children('.tablesorter-header'));
// add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
$table.after( $stickyTable );
@ -1209,7 +1288,7 @@ ts.addWidget({
}
});
ts.filter.bindSearch( $table, $stickyCells.find('.tablesorter-filter').addClass('tablesorter-external-filter') );
ts.filter.bindSearch( $table, $stickyCells.find('.tablesorter-filter') );
}
$table.trigger('stickyHeadersInit');