Filter: add nesting of "AND" & "OR" searches. Fixes #891 & #918

This commit is contained in:
Mottie 2015-06-12 20:24:36 -05:00
parent a79f3417ce
commit 449b985c60
12 changed files with 543 additions and 311 deletions

View File

@ -1,4 +1,4 @@
/*! tablesorter (FORK) - updated 06-10-2015 (v2.22.1)*/
/*! tablesorter (FORK) - updated 06-12-2015 (v2.22.1)*/
/* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
(function(factory) {
if (typeof define === 'function' && define.amd) {
@ -2648,6 +2648,66 @@ ts.filter = {
// data.index = column index; table = table element ( DOM )
// data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
types: {
or : function( c, data, vars ) {
if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
var indx, filterMatched, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.orSplit ),
iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// filterMatched = data2.filter === '' && indx > 0 ? true
// look for an exact match with the 'or' unless the 'filter-match' class is found
filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
if ( filterMatched ) {
return filterMatched;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data, vars ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var indx, filterMatched, result, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.andSplit ),
iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
// replace wild cards since /(a*)/i will match anything
.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// look for an exact match with the 'and' unless the 'filter-match' class is found
result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
if ( indx === 0 ) {
filterMatched = result;
} else {
filterMatched = filterMatched && result;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for regex
regex: function( c, data ) {
if ( ts.filter.regex.regex.test( data.filter ) ) {
@ -2735,23 +2795,6 @@ ts.filter = {
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var index = data.index,
parsed = data.parsed[index],
query = data.iFilter.split( ts.filter.regex.andSplit ),
result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
indx = query.length - 1;
while ( result && indx ) {
result = result &&
data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
indx--;
}
return result;
}
return null;
},
// Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
range : function( c, data ) {
if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
@ -2788,24 +2831,20 @@ ts.filter = {
},
// Look for wild card: ? = single, * = multiple, or | = logical OR
wild : function( c, data ) {
if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
if ( /[\?\*\|]/.test( data.iFilter ) ) {
var index = data.index,
parsed = data.parsed[ index ],
txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
query = '' + ( ts.filter.parseFilter( c, txt, index, parsed ) || '' );
query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
// look for an exact match with the 'or' unless the 'filter-match' class is found
if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
// show all results while using filter match. Fixes #727
if ( query[ query.length - 1 ] === '|' ) {
query += '*';
}
query = data.anyMatch && $.isArray( data.rowArray ) ?
'(' + query + ')' :
'^(' + query + ')$';
if ( !/\?\*/.test( query ) && data.nestedFilters ) {
query = data.isMatch ? query : '^(' + query + ')$';
}
// parsing the filter may not work properly when using wildcards =/
return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
.test( data.iExact );
return new RegExp(
query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
c.widgetOptions.filter_ignoreCase ? 'i' : ''
)
.test( data.exact );
}
return null;
},
@ -2859,7 +2898,7 @@ ts.filter = {
toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
iQuery : new RegExp( val, 'i' ),
igQuery : new RegExp( val, 'ig' )
});
@ -3079,7 +3118,6 @@ ts.filter = {
}
}
},
setDefaults: function( table, c, wo ) {
var isArray, saved, indx, col, $filters,
// get current ( default ) filters
@ -3424,8 +3462,22 @@ ts.filter = {
}
return columns;
},
processTypes: function( c, data, vars ) {
var ffxn,
filterMatched = null,
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data, vars );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
return filterMatched;
},
processRow: function( c, data, vars ) {
var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
var columnIndex, hasSelect, result, val, filterMatched,
fxn, ffxn, txt,
regex = ts.filter.regex,
wo = c.widgetOptions,
@ -3436,6 +3488,7 @@ ts.filter = {
// look for multiple columns '1-3,4-6,8'
columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
data.anyMatch = true;
data.isMatch = true;
data.rowArray = data.$cells.map( function( i ) {
if ( $.inArray( i, columnIndex ) > -1 ) {
if ( data.parsed[ i ] ) {
@ -3455,16 +3508,10 @@ ts.filter = {
data.exact = data.rowArray.join( ' ' );
data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
filterMatched = null;
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
vars.excludeMatch = vars.noAnyMatch;
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
showRow = filterMatched;
} else {
@ -3491,7 +3538,7 @@ ts.filter = {
data.index = columnIndex;
// filter types to exclude, per column
excludeMatch = vars.excludeFilter[ columnIndex ];
vars.excludeMatch = vars.excludeFilter[ columnIndex ];
// ignore if filter is empty or disabled
if ( data.filter ) {
@ -3505,6 +3552,9 @@ ts.filter = {
}
data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
data.exact.toLowerCase() : data.exact;
data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
result = showRow; // if showRow is true, show that row
// in case select filter option has a different value vs text 'a - z|A through Z'
@ -3529,13 +3579,12 @@ ts.filter = {
// data.filter = case sensitive
data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
fxn = vars.functions[ columnIndex ];
$cell = c.$headerIndexed[ columnIndex ];
hasSelect = $cell.hasClass( 'filter-select' );
hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
filterMatched = null;
if ( fxn || ( hasSelect && val ) ) {
if ( fxn === true || hasSelect ) {
// default selector uses exact match unless 'filter-match' class is found
filterMatched = $cell.hasClass( 'filter-match' ) ?
filterMatched = data.isMatch ?
data.iExact.search( data.iFilter ) >= 0 :
data.filter === data.exact;
} else if ( typeof fxn === 'function' ) {
@ -3552,15 +3601,7 @@ ts.filter = {
if ( filterMatched === null ) {
// cycle through the different filters
// filters return a boolean or null if nothing matches
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ ffxn ]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
result = filterMatched;
// Look for match, and add child row data for matching

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
/*! tablesorter (FORK) - updated 06-10-2015 (v2.22.1)*/
/*! tablesorter (FORK) - updated 06-12-2015 (v2.22.1)*/
/* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
(function(factory) {
if (typeof define === 'function' && define.amd) {
@ -470,6 +470,66 @@ ts.filter = {
// data.index = column index; table = table element ( DOM )
// data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
types: {
or : function( c, data, vars ) {
if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
var indx, filterMatched, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.orSplit ),
iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// filterMatched = data2.filter === '' && indx > 0 ? true
// look for an exact match with the 'or' unless the 'filter-match' class is found
filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
if ( filterMatched ) {
return filterMatched;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data, vars ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var indx, filterMatched, result, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.andSplit ),
iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
// replace wild cards since /(a*)/i will match anything
.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// look for an exact match with the 'and' unless the 'filter-match' class is found
result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
if ( indx === 0 ) {
filterMatched = result;
} else {
filterMatched = filterMatched && result;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for regex
regex: function( c, data ) {
if ( ts.filter.regex.regex.test( data.filter ) ) {
@ -557,23 +617,6 @@ ts.filter = {
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var index = data.index,
parsed = data.parsed[index],
query = data.iFilter.split( ts.filter.regex.andSplit ),
result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
indx = query.length - 1;
while ( result && indx ) {
result = result &&
data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
indx--;
}
return result;
}
return null;
},
// Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
range : function( c, data ) {
if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
@ -610,24 +653,20 @@ ts.filter = {
},
// Look for wild card: ? = single, * = multiple, or | = logical OR
wild : function( c, data ) {
if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
if ( /[\?\*\|]/.test( data.iFilter ) ) {
var index = data.index,
parsed = data.parsed[ index ],
txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
query = '' + ( ts.filter.parseFilter( c, txt, index, parsed ) || '' );
query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
// look for an exact match with the 'or' unless the 'filter-match' class is found
if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
// show all results while using filter match. Fixes #727
if ( query[ query.length - 1 ] === '|' ) {
query += '*';
}
query = data.anyMatch && $.isArray( data.rowArray ) ?
'(' + query + ')' :
'^(' + query + ')$';
if ( !/\?\*/.test( query ) && data.nestedFilters ) {
query = data.isMatch ? query : '^(' + query + ')$';
}
// parsing the filter may not work properly when using wildcards =/
return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
.test( data.iExact );
return new RegExp(
query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
c.widgetOptions.filter_ignoreCase ? 'i' : ''
)
.test( data.exact );
}
return null;
},
@ -681,7 +720,7 @@ ts.filter = {
toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
iQuery : new RegExp( val, 'i' ),
igQuery : new RegExp( val, 'ig' )
});
@ -901,7 +940,6 @@ ts.filter = {
}
}
},
setDefaults: function( table, c, wo ) {
var isArray, saved, indx, col, $filters,
// get current ( default ) filters
@ -1246,8 +1284,22 @@ ts.filter = {
}
return columns;
},
processTypes: function( c, data, vars ) {
var ffxn,
filterMatched = null,
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data, vars );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
return filterMatched;
},
processRow: function( c, data, vars ) {
var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
var columnIndex, hasSelect, result, val, filterMatched,
fxn, ffxn, txt,
regex = ts.filter.regex,
wo = c.widgetOptions,
@ -1258,6 +1310,7 @@ ts.filter = {
// look for multiple columns '1-3,4-6,8'
columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
data.anyMatch = true;
data.isMatch = true;
data.rowArray = data.$cells.map( function( i ) {
if ( $.inArray( i, columnIndex ) > -1 ) {
if ( data.parsed[ i ] ) {
@ -1277,16 +1330,10 @@ ts.filter = {
data.exact = data.rowArray.join( ' ' );
data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
filterMatched = null;
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
vars.excludeMatch = vars.noAnyMatch;
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
showRow = filterMatched;
} else {
@ -1313,7 +1360,7 @@ ts.filter = {
data.index = columnIndex;
// filter types to exclude, per column
excludeMatch = vars.excludeFilter[ columnIndex ];
vars.excludeMatch = vars.excludeFilter[ columnIndex ];
// ignore if filter is empty or disabled
if ( data.filter ) {
@ -1327,6 +1374,9 @@ ts.filter = {
}
data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
data.exact.toLowerCase() : data.exact;
data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
result = showRow; // if showRow is true, show that row
// in case select filter option has a different value vs text 'a - z|A through Z'
@ -1351,13 +1401,12 @@ ts.filter = {
// data.filter = case sensitive
data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
fxn = vars.functions[ columnIndex ];
$cell = c.$headerIndexed[ columnIndex ];
hasSelect = $cell.hasClass( 'filter-select' );
hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
filterMatched = null;
if ( fxn || ( hasSelect && val ) ) {
if ( fxn === true || hasSelect ) {
// default selector uses exact match unless 'filter-match' class is found
filterMatched = $cell.hasClass( 'filter-match' ) ?
filterMatched = data.isMatch ?
data.iExact.search( data.iFilter ) >= 0 :
data.filter === data.exact;
} else if ( typeof fxn === 'function' ) {
@ -1374,15 +1423,7 @@ ts.filter = {
if ( filterMatched === null ) {
// cycle through the different filters
// filters return a boolean or null if nothing matches
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ ffxn ]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
result = filterMatched;
// Look for match, and add child row data for matching

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,4 +2,4 @@
* Requires tablesorter v2.22.2+ and jQuery 1.4+
* by Rob Garrison
*/
!function(a){"use strict";var b=a.tablesorter;b.sortTbodies={init:function(c,d){var e,f,g,h,i,j=c.namespace+"sortTbody",k=c.$table.children("tbody"),l=k.length;for(d.sortTbody_original_serverSideSorting=c.serverSideSorting,d.sortTbody_original_cssInfoBlock=c.cssInfoBlock,c.cssInfoBlock=d.sortTbody_noSort,b.sortTbodies.setTbodies(c,d),e=0;l>e;e++)k.eq(e).attr("data-ts-original-order",e);for(c.$table.unbind("sortBegin updateComplete ".split(" ").join(j+" ")).bind("sortBegin"+j,function(){b.sortTbodies.sorter(c)}).bind("updateComplete"+j,function(){b.sortTbodies.setTbodies(c,d),c.$table.trigger("updateCache",[null,c.$tbodies])}),(a.isEmptyObject(c.parsers)||c.$tbodies.length!==k.length)&&(b.sortTbodies.setTbodies(c,d),c.$table.trigger("updateCache",[null,c.$tbodies])),i=k.children("tr"),l=i.length,e=0;e<c.columns;e++){if(h=0,"numeric"===c.parsers[e].type)for(f=0;l>f;f++)g=b.getParsedText(c,i.eq(f).children()[e],e),h=Math.max(Math.abs(g)||0,h);c.$headerIndexed[e].attr("data-ts-col-max-value",h)}},setTbodies:function(a,b){a.$tbodies=a.$table.children("tbody").not("."+b.sortTbody_noSort)},sorter:function(c){var d=c.$table,e=c.widgetOptions;if(e.sortTbody_busy!==!0){e.sortTbody_busy=!0;var f=d.children("tbody").not("."+e.sortTbody_noSort),g=e.sortTbody_primaryRow||"tr:eq(0)",h=c.sortList||[],i=h.length;i&&(c.serverSideSorting=!e.sortTbody_sortRows,f.sort(function(d,e){var f,j,k,l,m,n,o,p,q,r,s,t,u=c.table,v=c.parsers,w=c.textSorter||"",x=a(d),y=a(e),z=x.find(g).children("td, th"),A=y.find(g).children("td, th");for(f=0;i>f;f++){if(o=h[f][0],p=h[f][1],k=0===p,j=b.getElementText(c,z.eq(o),o),q=v[o].format(j,u,z[o],o),j=b.getElementText(c,A.eq(o),o),r=v[o].format(j,u,A[o],o),c.sortStable&&q===r&&1===i)return x.attr("data-ts-original-order")-y.attr("data-ts-original-order");if(l=/n/i.test(v&&v[o]?v[o].type||"":""),l&&c.strings[o]?(m=c.$headerIndexed[o].attr("data-ts-col-max-value")||1.79e308,l="boolean"==typeof c.string[c.strings[o]]?(k?1:-1)*(c.string[c.strings[o]]?-1:1):c.strings[o]?c.string[c.strings[o]]||0:0,n=c.numberSorter?c.numberSorter(q,r,k,m,u):b["sortNumeric"+(k?"Asc":"Desc")](q,r,l,m,o,u)):(s=k?q:r,t=k?r:q,n="function"==typeof w?w(s,t,k,o,u):"object"==typeof w&&w.hasOwnProperty(o)?w[o](s,t,k,o,u):b["sortNatural"+(k?"Asc":"Desc")](q,r,o,u,c)),n)return n}return x.attr("data-ts-original-order")-y.attr("data-ts-original-order")}),b.sortTbodies.restoreTbodies(c,e,f),e.sortTbody_busy=!1)}},restoreTbodies:function(a,b,c){var d,e,f,g,h,i,j,k,l,m=a.$table,i=!0,k=0;if(m.hide(),c.appendTo(m),e=m.children("tbody"),g=e.length,d=e.filter("."+b.sortTbody_noSort).appendTo(m),h=d.length)for(;i&&h>k;){for(i=!1,j=0;h>j;j++)l=parseInt(d.eq(j).attr("data-ts-original-order"),10),l=l>=g?g:0>l?0:l,l!==d.eq(j).index()&&(i=!0,f=d.eq(j).detach(),l>=g?f.appendTo(m):0===l?f.prependTo(m):f.insertBefore(m.children("tbody:eq("+l+")")));k++}m.show()}},b.addWidget({id:"sortTbody",priority:40,options:{sortTbody_primaryRow:null,sortTbody_sortRows:!1,sortTbody_noSort:"tablesorter-no-sort-tbody"},init:function(a,c,d,e){b.sortTbodies.init(d,e)},remove:function(a,b,c,d){b.$table.unbind("sortBegin updateComplete ".split(" ").join(b.namespace+"sortTbody ")),b.serverSideSorting=c.sortTbody_original_serverSideSorting,b.cssInfoBlock=c.sortTbody_original_cssInfoBlock}})}(jQuery);
!function(a){"use strict";var b=a.tablesorter;b.sortTbodies={init:function(c,d){var e,f,g,h,i,j=c.namespace+"sortTbody",k=c.$table.children("tbody"),l=k.length;for(d.sortTbody_original_serverSideSorting=c.serverSideSorting,d.sortTbody_original_cssInfoBlock=c.cssInfoBlock,c.cssInfoBlock=d.sortTbody_noSort,b.sortTbodies.setTbodies(c,d),e=0;l>e;e++)k.eq(e).attr("data-ts-original-order",e);for(c.$table.unbind("sortBegin updateComplete ".split(" ").join(j+" ")).bind("sortBegin"+j,function(){b.sortTbodies.sorter(c)}).bind("updateComplete"+j,function(){b.sortTbodies.setTbodies(c,d),c.$table.trigger("updateCache",[null,c.$tbodies])}),(a.isEmptyObject(c.parsers)||c.$tbodies.length!==k.length)&&(b.sortTbodies.setTbodies(c,d),c.$table.trigger("updateCache",[null,c.$tbodies])),i=k.children("tr"),l=i.length,e=0;e<c.columns;e++){if(h=0,"numeric"===c.parsers[e].type)for(f=0;l>f;f++)g=b.getParsedText(c,i.eq(f).children()[e],e),h=Math.max(Math.abs(g)||0,h);c.$headerIndexed[e].attr("data-ts-col-max-value",h)}},setTbodies:function(a,b){a.$tbodies=a.$table.children("tbody").not("."+b.sortTbody_noSort)},sorter:function(c){var d=c.$table,e=c.widgetOptions;if(e.sortTbody_busy!==!0){e.sortTbody_busy=!0;var f=d.children("tbody").not("."+e.sortTbody_noSort),g=e.sortTbody_primaryRow||"tr:eq(0)",h=c.sortList||[],i=h.length;i&&(c.serverSideSorting=!e.sortTbody_sortRows,f.sort(function(d,e){var f,j,k,l,m,n,o,p,q,r,s,t,u=c.table,v=c.parsers,w=c.textSorter||"",x=a(d),y=a(e),z=x.find(g).children("td, th"),A=y.find(g).children("td, th");for(f=0;i>f;f++){if(o=h[f][0],p=h[f][1],k=0===p,j=b.getElementText(c,z.eq(o),o),q=v[o].format(j,u,z[o],o),j=b.getElementText(c,A.eq(o),o),r=v[o].format(j,u,A[o],o),c.sortStable&&q===r&&1===i)return x.attr("data-ts-original-order")-y.attr("data-ts-original-order");if(l=/n/i.test(v&&v[o]?v[o].type||"":""),l&&c.strings[o]?(m=c.$headerIndexed[o].attr("data-ts-col-max-value")||1.79e308,l="boolean"==typeof c.string[c.strings[o]]?(k?1:-1)*(c.string[c.strings[o]]?-1:1):c.strings[o]?c.string[c.strings[o]]||0:0,n=c.numberSorter?c.numberSorter(q,r,k,m,u):b["sortNumeric"+(k?"Asc":"Desc")](q,r,l,m,o,u)):(s=k?q:r,t=k?r:q,n="function"==typeof w?w(s,t,k,o,u):"object"==typeof w&&w.hasOwnProperty(o)?w[o](s,t,k,o,u):b["sortNatural"+(k?"Asc":"Desc")](q,r,o,u,c)),n)return n}return x.attr("data-ts-original-order")-y.attr("data-ts-original-order")}),b.sortTbodies.restoreTbodies(c,e,f),e.sortTbody_busy=!1)}},restoreTbodies:function(a,b,c){var d,e,f,g,h,i,j,k=a.$table,l=!0,m=0;if(k.hide(),c.appendTo(k),e=k.children("tbody"),g=e.length,d=e.filter("."+b.sortTbody_noSort).appendTo(k),h=d.length)for(;l&&h>m;){for(l=!1,i=0;h>i;i++)j=parseInt(d.eq(i).attr("data-ts-original-order"),10),j=j>=g?g:0>j?0:j,j!==d.eq(i).index()&&(l=!0,f=d.eq(i).detach(),j>=g?f.appendTo(k):0===j?f.prependTo(k):f.insertBefore(k.children("tbody:eq("+j+")")));m++}k.show()}},b.addWidget({id:"sortTbody",priority:40,options:{sortTbody_primaryRow:null,sortTbody_sortRows:!1,sortTbody_noSort:"tablesorter-no-sort-tbody"},init:function(a,c,d,e){b.sortTbodies.init(d,e)},remove:function(a,b,c,d){b.$table.unbind("sortBegin updateComplete ".split(" ").join(b.namespace+"sortTbody ")),b.serverSideSorting=c.sortTbody_original_serverSideSorting,b.cssInfoBlock=c.sortTbody_original_cssInfoBlock}})}(jQuery);

View File

@ -225,6 +225,13 @@ $(function(){
<h3 id="notes"><a href="#">Notes</a></h3>
<div>
<ul>
<li>In <span class="version">v2.22.2</span>,
<ul>
<li>The <code>getFilters</code> function will now target the last used filter properly.</li>
<li>The <code>selectSource</code> option now ignores parsers if none are set.</li>
<li>Added the ability to nest filter types with a logical "OR" or a logical "AND". Try these filters: <button data-filter-column="1">a && !o</button> (<span class="label warning">*NOTE*</span> try this with and without the filter-match class applied), <button data-filter-column="3">&lt10 or &gt;40</button> or <button data-filter-column="3">&gt;20 && &lt;40</button></li>
</ul>
</li>
<li>In <span class="version">v2.22.0</span>
<ul>
<li>Regex filter searches now cache the created regex object for each query to optimize speed & a regex search now properly uses case-sensitive content.</li>
@ -264,7 +271,7 @@ $(function(){
</div>
<ul>
<li><span class="label label-info">*NOTE*</span> If using a custom theme, make sure to add the class set in the <code>filter_filteredRow</code> option (set to <code>filtered</code> by default) in the css, and set it to <code>display:none</code>. When filtering rows, the filter widget adds the "filtered" class to hide the row; all available themes include this definition.</li>
<li><span class="label alert">*NOTE*</span> If using a custom theme, make sure to add the class set in the <code>filter_filteredRow</code> option (set to <code>filtered</code> by default) in the css, and set it to <code>display:none</code>. When filtering rows, the filter widget adds the "filtered" class to hide the row; all available themes include this definition.</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>
<li>This widget <em>does NOT</em> work with tablesorter v2.0.5.</li>
@ -272,27 +279,28 @@ $(function(){
<table class="tablesorter-blue notes">
<thead>
<tr>
<th style="width:10%">Type <small class="bright">(1)(2)</small></th>
<th style="width:40%">Description</th>
<th style="width:50%">Examples</th>
<th style="width:1%">Priority</th>
<th style="width:9%">Type <small class="bright">(1)(2)</small></th>
<th style="width:30%">Description</th>
<th style="width:60%">Examples</th>
</tr>
</thead>
<tbody>
<tr><td class="center">text</td><td>Any text entered in the filter will <strong>match</strong> text found within the column</td><td><code>abc</code> (finds &quot;abc&quot;, &quot;abcd&quot;, &quot;abcde&quot;, etc);<button data-filter-column="1">Aaron</button> (finds &quot;Aaron&quot; and &quot;Philip Aaron Wong&quot;)</td></tr>
<tr><td class="center"><code>/\d/</code></td><td>Add any regex to the query to use in the query ("mig" flags can be included <code>/\w/mig</code>)</td><td><code>/b[aeiou]g/i</code> (finds &quot;bag&quot;, &quot;beg&quot;, &quot;BIG&quot;, &quot;Bug&quot;, etc);<button data-filter-column="1">/r$/</button> (matches text that ends with an &quot;r&quot;)</td></tr>
<tr><td class="center"><code>&lt; &lt;= &gt;= &gt;</code></td><td>Find alphabetical or numerical values less than or greater than or equal to the filtered query <small class="bright">(2)</small>.</td><td><button data-filter-column="5">&gt;= 10</button> (find values greater than or equal to 10)</td></tr>
<tr><td class="center"><code>!</code> or <code>!=</code></td><td>Not operator, or not exactly match. Filter the column with content that <strong>do not</strong> match the query. Include an equal (<code>=</code>), single (<code>'</code>) or double quote (<code>&quot;</code>) to exactly <em>not</em> match a filter (<span class="version">v2.17.1</span>).</td><td><code>!fe</code> (hide rows with &quot;female&quot; in that column, but shows rows with &quot;male&quot;);<button data-filter-column="1">!a</button> (find text that doesn't contain an &quot;a&quot;);<button data-filter-column="1">!"Bruce"</button> (find content that does not exactly match "Bruce")</td></tr>
<tr><td class="center"><code>&quot;</code> or <code>=</code></td><td>To exactly match the search query, add a quote, apostrophe or equal sign to the beginning and/or end of the query</td><td><code>abc&quot;</code> or <code>abc=</code> (exactly match &quot;abc&quot;);<button data-filter-column="1">John&quot;</button> or <button data-filter-column="1">John=</button> (exactly match &quot;John&quot;)</td></tr>
<tr><td class="center"><code>&nbsp;&&&nbsp;</code> or <code>&nbsp;AND&nbsp;</code></td><td>Logical &quot;and&quot;. Filter the column for content that matches text from either side of the operator.</td><td><code>box && bat</code> (matches a column cell that contains both &quot;box&quot; and &quot;bat&quot;);<button data-filter-column="1">Br && c</button> (Find text that contains both &quot;br&quot; and &quot;c&quot;)</td></tr>
<tr><td class="center"><code>&nbsp;-&nbsp;</code> or <code>&nbsp;to&nbsp;</code></td><td>Find a range of values. Make sure there is a space before and after the dash (or the word &quot;to&quot;) <small class="bright">(4)</small>.</td><td><button data-filter-column="3">10 - 30</button> or <button data-filter-column="4">10 to 30</button> (match values between 10 and 30)</td></tr>
<tr><td class="center"><code>?</code></td><td>Wildcard for a single, non-space character.</td><td><code>J?n</code> (finds &quot;Jan&quot; and &quot;Jun&quot;, but not &quot;Joan&quot;);<button data-filter-column="2">a?s</button> (finds &quot;Dumass&quot; and &quot;Evans&quot;, but not &quot;McMasters&quot;)</td></tr>
<tr><td class="center"><code>*</code></td><td>Wildcard for zero or more non-space characters.</td><td><code>B*k</code> (matches &quot;Black&quot; and &quot;Book&quot;);<button data-filter-column="2">a*s</button> (matches &quot;Dumass&quot;, &quot;Evans&quot; and &quot;McMasters&quot;)</td></tr>
<tr><td class="center"><code>|</code> or <code>&nbsp;OR&nbsp;</code></td><td>Logical &quot;or&quot; (Vertical bar). Filter the column for content that matches text from either side of the bar <small class="bright">(3)</small>.</td><td><code>box|bat</code> (matches a column cell with either &quot;box&quot; or &quot;bat&quot;);<button data-filter-column="1">Alex|Peter</button> (Find text that contains either &quot;Alex&quot; or &quot;Peter&quot;)</td></tr>
<tr><td class="center"><code>~</code></td><td>Perform a fuzzy search (matches sequential characters) by adding a tilde to the beginning of the query (<span class="version">v2.13.3</span>)</td><td><button data-filter-column="1">~bee</button> (matches &quot;Bruce Lee&quot; and &quot;Brenda Dexter&quot;), or <button data-filter-column="1">~piano</button> (matches &quot;Philip Aaron Wong&quot;)</td></tr>
<tr><td class="center">1</td><td class="center"><code>|</code> or <code>&nbsp;OR&nbsp;</code></td><td>Logical &quot;or&quot; (Vertical bar). Filter the column for content that matches text from either side of the bar <small class="bright">(3)</small>.</td><td><code>box|bat</code> (matches a column cell with either &quot;box&quot; or &quot;bat&quot;);<button data-filter-column="1">Alex|Peter</button> (Find text that contains either &quot;Alex&quot; or &quot;Peter&quot;); <button data-filter-column="3">&lt10 or &gt;40</button></td></tr>
<tr><td class="center">2</td><td class="center"><code>&nbsp;&&&nbsp;</code> or <code>&nbsp;AND&nbsp;</code></td><td>Logical &quot;and&quot;. Filter the column for content that matches text from either side of the operator.</td><td><code>box && bat</code> (matches a column cell that contains both &quot;box&quot; and &quot;bat&quot;), <button data-filter-column="1">Br && c</button> (Find text that contains both &quot;br&quot; and &quot;c&quot;), <button data-filter-column="3">&gt;20 && &lt;40</button> or <button data-filter-column="1">a and !o</button> (When "filter-match" is set, find content with the letter "a", but not the letter "o")</td></tr>
<tr><td class="center">3</td><td class="center"><code>/\d/</code></td><td>Add any regex to the query to use in the query ("mig" flags can be included <code>/\w/mig</code>)</td><td><code>/b[aeiou]g/i</code> (finds &quot;bag&quot;, &quot;beg&quot;, &quot;BIG&quot;, &quot;Bug&quot;, etc);<button data-filter-column="1">/r$/</button> (matches text that ends with an &quot;r&quot;)</td></tr>
<tr><td class="center">4</td><td class="center"><code>&lt; &lt;= &gt;= &gt;</code></td><td>Find alphabetical or numerical values less than or greater than or equal to the filtered query <small class="bright">(2)</small>.</td><td><button data-filter-column="5">&gt;= 10</button> (find values greater than or equal to 10)</td></tr>
<tr><td class="center">5</td><td class="center"><code>!</code> or <code>!=</code></td><td>Not operator, or not exactly match. Filter the column with content that <strong>do not</strong> match the query. Include an equal (<code>=</code>), single (<code>'</code>) or double quote (<code>&quot;</code>) to exactly <em>not</em> match a filter (<span class="version">v2.17.1</span>).</td><td><code>!fe</code> (hide rows with &quot;female&quot; in that column, but shows rows with &quot;male&quot;);<button data-filter-column="1">!a</button> (find text that doesn't contain an &quot;a&quot;);<button data-filter-column="1">!"Bruce"</button> (find content that does not exactly match "Bruce")</td></tr>
<tr><td class="center">6</td><td class="center"><code>&quot;</code> or <code>=</code></td><td>To exactly match the search query, add a quote, apostrophe or equal sign to the beginning and/or end of the query</td><td><code>abc&quot;</code> or <code>abc=</code> (exactly match &quot;abc&quot;);<button data-filter-column="1">John&quot;</button> or <button data-filter-column="1">John=</button> (exactly match &quot;John&quot;)</td></tr>
<tr><td class="center">7</td><td class="center"><code>&nbsp;-&nbsp;</code> or <code>&nbsp;to&nbsp;</code></td><td>Find a range of values. Make sure there is a space before and after the dash (or the word &quot;to&quot;) <small class="bright">(4)</small>.</td><td><button data-filter-column="3">10 - 30</button> or <button data-filter-column="4">10 to 30</button> (match values between 10 and 30)</td></tr>
<tr><td class="center">8</td><td class="center"><code>?</code></td><td>Wildcard for a single, non-space character.</td><td><code>J?n</code> (finds &quot;Jan&quot; and &quot;Jun&quot;, but not &quot;Joan&quot;);<button data-filter-column="2">a?s</button> (finds &quot;Dumass&quot; and &quot;Evans&quot;, but not &quot;McMasters&quot;)</td></tr>
<tr><td class="center">8</td><td class="center"><code>*</code></td><td>Wildcard for zero or more non-space characters.</td><td><code>B*k</code> (matches &quot;Black&quot; and &quot;Book&quot;);<button data-filter-column="2">a*s</button> (matches &quot;Dumass&quot;, &quot;Evans&quot; and &quot;McMasters&quot;)</td></tr>
<tr><td class="center">9</td><td class="center"><code>~</code></td><td>Perform a fuzzy search (matches sequential characters) by adding a tilde to the beginning of the query (<span class="version">v2.13.3</span>)</td><td><button data-filter-column="1">~bee</button> (matches &quot;Bruce Lee&quot; and &quot;Brenda Dexter&quot;), or <button data-filter-column="1">~piano</button> (matches &quot;Philip Aaron Wong&quot;)</td></tr>
<tr><td class="center">10</td><td class="center">text</td><td>Any text entered in the filter will <strong>match</strong> text found within the column</td><td><code>abc</code> (finds &quot;abc&quot;, &quot;abcd&quot;, &quot;abcde&quot;, etc);<button data-filter-column="1">Aaron</button> (finds &quot;Aaron&quot; and &quot;Philip Aaron Wong&quot;)</td></tr>
</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> not match (<code>!</code>) <strong>&gt;</strong> exact (<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>; order changed again in <span class="version updated">v2.17.1</span> to move "not match" before "exact" and allow for exact not matches; see <a href="https://github.com/Mottie/tablesorter/issues/628">issue #628</a>)<br>
<span class="bright">(2)</span> The filter order (or precendence) of how searches are checked in "priority" (first column) order; so an exact match will override "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>; order changed again in <span class="version updated">v2.17.1</span> to move "not match" before "exact" and allow for exact not matches; see <a href="https://github.com/Mottie/tablesorter/issues/628">issue #628</a>). In <span class="version updated">v2.22.2</span>, the "or" and "and" types can combine any of the other filter types together.<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>
@ -768,7 +776,14 @@ $.extend($.tablesorter.language, {
<h1>CSS</h1>
<div>
<pre class="prettyprint lang-css">/* This css is already contained within each theme file */
<pre class="prettyprint lang-css">/* REQUIRED in CUSTOM THEMES!
This is the only definition that MUST BE added to any custom themes.
This is how rows are hidden by filtering (included in provided themes) */
.tablesorter .filtered {
display: none;
}
/* All of the following css is already contained within each theme file; modify it as desired */
/* filter row */
.tablesorter-filter-row td {
background: #eee;

View File

@ -483,7 +483,7 @@
<li><span class="label label-info">Beta</span> <a href="example-dragtable.html">Dragtable mod</a> - (jQuery UI widget for column reordering [<a href="http://stackoverflow.com/a/27770224/145346">ref</a>]; <span class="version">v2.19.0</span>).</li>
<li><span class="results">&dagger;</span> Filter widget (<span class="version updated">v2.22.1</span>):
<ul>
<li><a href="example-widget-filter.html">basic</a> (v2.0.18; <span class="version updated">v2.18.1</span>)</li>
<li><a href="example-widget-filter.html">basic</a> (v2.0.18; <span class="version updated">v2.22.2</span>)</li>
<li><a href="example-widget-filter-any-match.html">external option (match any column)</a> (<span class="version">v2.13.3</span>; <span class="version updated">v2.22.0</span>)</li>
<li><a href="example-widget-filter-external-inputs.html">external inputs</a> (<span class="version">v2.14</span>; <span class="version updated">v2.18.0</span>)</li>
<li><a href="example-widget-filter-custom.html">custom</a> (v2.3.6; <span class="version updated">v2.22.0</span>)</li>

View File

@ -4,7 +4,7 @@
*/
/*! tablesorter (FORK) - updated 06-10-2015 (v2.22.1)*/
/*! tablesorter (FORK) - updated 06-12-2015 (v2.22.1)*/
/* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
(function(factory) {
if (typeof define === 'function' && define.amd) {
@ -2654,6 +2654,66 @@ ts.filter = {
// data.index = column index; table = table element ( DOM )
// data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
types: {
or : function( c, data, vars ) {
if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
var indx, filterMatched, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.orSplit ),
iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// filterMatched = data2.filter === '' && indx > 0 ? true
// look for an exact match with the 'or' unless the 'filter-match' class is found
filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
if ( filterMatched ) {
return filterMatched;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data, vars ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var indx, filterMatched, result, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.andSplit ),
iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
// replace wild cards since /(a*)/i will match anything
.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// look for an exact match with the 'and' unless the 'filter-match' class is found
result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
if ( indx === 0 ) {
filterMatched = result;
} else {
filterMatched = filterMatched && result;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for regex
regex: function( c, data ) {
if ( ts.filter.regex.regex.test( data.filter ) ) {
@ -2741,23 +2801,6 @@ ts.filter = {
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var index = data.index,
parsed = data.parsed[index],
query = data.iFilter.split( ts.filter.regex.andSplit ),
result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
indx = query.length - 1;
while ( result && indx ) {
result = result &&
data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
indx--;
}
return result;
}
return null;
},
// Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
range : function( c, data ) {
if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
@ -2794,24 +2837,20 @@ ts.filter = {
},
// Look for wild card: ? = single, * = multiple, or | = logical OR
wild : function( c, data ) {
if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
if ( /[\?\*\|]/.test( data.iFilter ) ) {
var index = data.index,
parsed = data.parsed[ index ],
txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
query = '' + ( ts.filter.parseFilter( c, txt, index, parsed ) || '' );
query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
// look for an exact match with the 'or' unless the 'filter-match' class is found
if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
// show all results while using filter match. Fixes #727
if ( query[ query.length - 1 ] === '|' ) {
query += '*';
}
query = data.anyMatch && $.isArray( data.rowArray ) ?
'(' + query + ')' :
'^(' + query + ')$';
if ( !/\?\*/.test( query ) && data.nestedFilters ) {
query = data.isMatch ? query : '^(' + query + ')$';
}
// parsing the filter may not work properly when using wildcards =/
return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
.test( data.iExact );
return new RegExp(
query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
c.widgetOptions.filter_ignoreCase ? 'i' : ''
)
.test( data.exact );
}
return null;
},
@ -2865,7 +2904,7 @@ ts.filter = {
toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
iQuery : new RegExp( val, 'i' ),
igQuery : new RegExp( val, 'ig' )
});
@ -3085,7 +3124,6 @@ ts.filter = {
}
}
},
setDefaults: function( table, c, wo ) {
var isArray, saved, indx, col, $filters,
// get current ( default ) filters
@ -3430,8 +3468,22 @@ ts.filter = {
}
return columns;
},
processTypes: function( c, data, vars ) {
var ffxn,
filterMatched = null,
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data, vars );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
return filterMatched;
},
processRow: function( c, data, vars ) {
var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
var columnIndex, hasSelect, result, val, filterMatched,
fxn, ffxn, txt,
regex = ts.filter.regex,
wo = c.widgetOptions,
@ -3442,6 +3494,7 @@ ts.filter = {
// look for multiple columns '1-3,4-6,8'
columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
data.anyMatch = true;
data.isMatch = true;
data.rowArray = data.$cells.map( function( i ) {
if ( $.inArray( i, columnIndex ) > -1 ) {
if ( data.parsed[ i ] ) {
@ -3461,16 +3514,10 @@ ts.filter = {
data.exact = data.rowArray.join( ' ' );
data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
filterMatched = null;
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
vars.excludeMatch = vars.noAnyMatch;
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
showRow = filterMatched;
} else {
@ -3497,7 +3544,7 @@ ts.filter = {
data.index = columnIndex;
// filter types to exclude, per column
excludeMatch = vars.excludeFilter[ columnIndex ];
vars.excludeMatch = vars.excludeFilter[ columnIndex ];
// ignore if filter is empty or disabled
if ( data.filter ) {
@ -3511,6 +3558,9 @@ ts.filter = {
}
data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
data.exact.toLowerCase() : data.exact;
data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
result = showRow; // if showRow is true, show that row
// in case select filter option has a different value vs text 'a - z|A through Z'
@ -3535,13 +3585,12 @@ ts.filter = {
// data.filter = case sensitive
data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
fxn = vars.functions[ columnIndex ];
$cell = c.$headerIndexed[ columnIndex ];
hasSelect = $cell.hasClass( 'filter-select' );
hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
filterMatched = null;
if ( fxn || ( hasSelect && val ) ) {
if ( fxn === true || hasSelect ) {
// default selector uses exact match unless 'filter-match' class is found
filterMatched = $cell.hasClass( 'filter-match' ) ?
filterMatched = data.isMatch ?
data.iExact.search( data.iFilter ) >= 0 :
data.filter === data.exact;
} else if ( typeof fxn === 'function' ) {
@ -3558,15 +3607,7 @@ ts.filter = {
if ( filterMatched === null ) {
// cycle through the different filters
// filters return a boolean or null if nothing matches
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ ffxn ]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
result = filterMatched;
// Look for match, and add child row data for matching

View File

@ -4,7 +4,7 @@
*/
/*! tablesorter (FORK) - updated 06-10-2015 (v2.22.1)*/
/*! tablesorter (FORK) - updated 06-12-2015 (v2.22.1)*/
/* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */
(function(factory) {
if (typeof define === 'function' && define.amd) {
@ -476,6 +476,66 @@ ts.filter = {
// data.index = column index; table = table element ( DOM )
// data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
types: {
or : function( c, data, vars ) {
if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
var indx, filterMatched, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.orSplit ),
iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// filterMatched = data2.filter === '' && indx > 0 ? true
// look for an exact match with the 'or' unless the 'filter-match' class is found
filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
if ( filterMatched ) {
return filterMatched;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data, vars ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var indx, filterMatched, result, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.andSplit ),
iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
// replace wild cards since /(a*)/i will match anything
.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// look for an exact match with the 'and' unless the 'filter-match' class is found
result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
if ( indx === 0 ) {
filterMatched = result;
} else {
filterMatched = filterMatched && result;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for regex
regex: function( c, data ) {
if ( ts.filter.regex.regex.test( data.filter ) ) {
@ -563,23 +623,6 @@ ts.filter = {
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var index = data.index,
parsed = data.parsed[index],
query = data.iFilter.split( ts.filter.regex.andSplit ),
result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
indx = query.length - 1;
while ( result && indx ) {
result = result &&
data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
indx--;
}
return result;
}
return null;
},
// Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
range : function( c, data ) {
if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
@ -616,24 +659,20 @@ ts.filter = {
},
// Look for wild card: ? = single, * = multiple, or | = logical OR
wild : function( c, data ) {
if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
if ( /[\?\*\|]/.test( data.iFilter ) ) {
var index = data.index,
parsed = data.parsed[ index ],
txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
query = '' + ( ts.filter.parseFilter( c, txt, index, parsed ) || '' );
query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
// look for an exact match with the 'or' unless the 'filter-match' class is found
if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
// show all results while using filter match. Fixes #727
if ( query[ query.length - 1 ] === '|' ) {
query += '*';
}
query = data.anyMatch && $.isArray( data.rowArray ) ?
'(' + query + ')' :
'^(' + query + ')$';
if ( !/\?\*/.test( query ) && data.nestedFilters ) {
query = data.isMatch ? query : '^(' + query + ')$';
}
// parsing the filter may not work properly when using wildcards =/
return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
.test( data.iExact );
return new RegExp(
query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
c.widgetOptions.filter_ignoreCase ? 'i' : ''
)
.test( data.exact );
}
return null;
},
@ -687,7 +726,7 @@ ts.filter = {
toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
iQuery : new RegExp( val, 'i' ),
igQuery : new RegExp( val, 'ig' )
});
@ -907,7 +946,6 @@ ts.filter = {
}
}
},
setDefaults: function( table, c, wo ) {
var isArray, saved, indx, col, $filters,
// get current ( default ) filters
@ -1252,8 +1290,22 @@ ts.filter = {
}
return columns;
},
processTypes: function( c, data, vars ) {
var ffxn,
filterMatched = null,
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data, vars );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
return filterMatched;
},
processRow: function( c, data, vars ) {
var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
var columnIndex, hasSelect, result, val, filterMatched,
fxn, ffxn, txt,
regex = ts.filter.regex,
wo = c.widgetOptions,
@ -1264,6 +1316,7 @@ ts.filter = {
// look for multiple columns '1-3,4-6,8'
columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
data.anyMatch = true;
data.isMatch = true;
data.rowArray = data.$cells.map( function( i ) {
if ( $.inArray( i, columnIndex ) > -1 ) {
if ( data.parsed[ i ] ) {
@ -1283,16 +1336,10 @@ ts.filter = {
data.exact = data.rowArray.join( ' ' );
data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
filterMatched = null;
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
vars.excludeMatch = vars.noAnyMatch;
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
showRow = filterMatched;
} else {
@ -1319,7 +1366,7 @@ ts.filter = {
data.index = columnIndex;
// filter types to exclude, per column
excludeMatch = vars.excludeFilter[ columnIndex ];
vars.excludeMatch = vars.excludeFilter[ columnIndex ];
// ignore if filter is empty or disabled
if ( data.filter ) {
@ -1333,6 +1380,9 @@ ts.filter = {
}
data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
data.exact.toLowerCase() : data.exact;
data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
result = showRow; // if showRow is true, show that row
// in case select filter option has a different value vs text 'a - z|A through Z'
@ -1357,13 +1407,12 @@ ts.filter = {
// data.filter = case sensitive
data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
fxn = vars.functions[ columnIndex ];
$cell = c.$headerIndexed[ columnIndex ];
hasSelect = $cell.hasClass( 'filter-select' );
hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
filterMatched = null;
if ( fxn || ( hasSelect && val ) ) {
if ( fxn === true || hasSelect ) {
// default selector uses exact match unless 'filter-match' class is found
filterMatched = $cell.hasClass( 'filter-match' ) ?
filterMatched = data.isMatch ?
data.iExact.search( data.iFilter ) >= 0 :
data.filter === data.exact;
} else if ( typeof fxn === 'function' ) {
@ -1380,15 +1429,7 @@ ts.filter = {
if ( filterMatched === null ) {
// cycle through the different filters
// filters return a boolean or null if nothing matches
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ ffxn ]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
result = filterMatched;
// Look for match, and add child row data for matching

View File

@ -103,6 +103,66 @@ ts.filter = {
// data.index = column index; table = table element ( DOM )
// data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class )
types: {
or : function( c, data, vars ) {
if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) {
var indx, filterMatched, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.orSplit ),
iFilter = data.iFilter.split( ts.filter.regex.orSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')';
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// filterMatched = data2.filter === '' && indx > 0 ? true
// look for an exact match with the 'or' unless the 'filter-match' class is found
filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars );
if ( filterMatched ) {
return filterMatched;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data, vars ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var indx, filterMatched, result, txt, query, regex,
// duplicate data but split filter
data2 = $.extend( {}, data ),
index = data.index,
parsed = data.parsed[ index ],
filter = data.filter.split( ts.filter.regex.andSplit ),
iFilter = data.iFilter.split( ts.filter.regex.andSplit ),
len = filter.length;
for ( indx = 0; indx < len; indx++ ) {
data2.nestedFilters = true;
data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' );
data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' );
query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' )
// replace wild cards since /(a*)/i will match anything
.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' );
regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' );
// look for an exact match with the 'and' unless the 'filter-match' class is found
result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) );
if ( indx === 0 ) {
filterMatched = result;
} else {
filterMatched = filterMatched && result;
}
}
// may be null from processing types
return filterMatched || false;
}
return null;
},
// Look for regex
regex: function( c, data ) {
if ( ts.filter.regex.regex.test( data.filter ) ) {
@ -190,23 +250,6 @@ ts.filter = {
}
return null;
},
// Look for an AND or && operator ( logical and )
and : function( c, data ) {
if ( ts.filter.regex.andTest.test( data.filter ) ) {
var index = data.index,
parsed = data.parsed[index],
query = data.iFilter.split( ts.filter.regex.andSplit ),
result = data.iExact.search( $.trim( ts.filter.parseFilter( c, query[0], index, parsed ) ) ) >= 0,
indx = query.length - 1;
while ( result && indx ) {
result = result &&
data.iExact.search( $.trim( ts.filter.parseFilter( c, query[indx], index, parsed ) ) ) >= 0;
indx--;
}
return result;
}
return null;
},
// Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu!
range : function( c, data ) {
if ( ts.filter.regex.toTest.test( data.iFilter ) ) {
@ -243,24 +286,20 @@ ts.filter = {
},
// Look for wild card: ? = single, * = multiple, or | = logical OR
wild : function( c, data ) {
if ( /[\?\*\|]/.test( data.iFilter ) || ts.filter.regex.orReplace.test( data.filter ) ) {
if ( /[\?\*\|]/.test( data.iFilter ) ) {
var index = data.index,
parsed = data.parsed[ index ],
txt = data.iFilter.replace( ts.filter.regex.orReplace, '|' ),
query = '' + ( ts.filter.parseFilter( c, txt, index, parsed ) || '' );
query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' );
// look for an exact match with the 'or' unless the 'filter-match' class is found
if ( !c.$headerIndexed[ index ].hasClass( 'filter-match' ) && /\|/.test( query ) ) {
// show all results while using filter match. Fixes #727
if ( query[ query.length - 1 ] === '|' ) {
query += '*';
}
query = data.anyMatch && $.isArray( data.rowArray ) ?
'(' + query + ')' :
'^(' + query + ')$';
if ( !/\?\*/.test( query ) && data.nestedFilters ) {
query = data.isMatch ? query : '^(' + query + ')$';
}
// parsing the filter may not work properly when using wildcards =/
return new RegExp( query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ) )
.test( data.iExact );
return new RegExp(
query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ),
c.widgetOptions.filter_ignoreCase ? 'i' : ''
)
.test( data.exact );
}
return null;
},
@ -314,7 +353,7 @@ ts.filter = {
toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ),
andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ),
andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ),
orReplace : new RegExp( '\\s+(' + ts.language.or + ')\\s+', 'gi' ),
orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ),
iQuery : new RegExp( val, 'i' ),
igQuery : new RegExp( val, 'ig' )
});
@ -534,7 +573,6 @@ ts.filter = {
}
}
},
setDefaults: function( table, c, wo ) {
var isArray, saved, indx, col, $filters,
// get current ( default ) filters
@ -879,8 +917,22 @@ ts.filter = {
}
return columns;
},
processTypes: function( c, data, vars ) {
var ffxn,
filterMatched = null,
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data, vars );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
return filterMatched;
},
processRow: function( c, data, vars ) {
var $cell, columnIndex, hasSelect, matches, result, val, filterMatched, excludeMatch,
var columnIndex, hasSelect, result, val, filterMatched,
fxn, ffxn, txt,
regex = ts.filter.regex,
wo = c.widgetOptions,
@ -891,6 +943,7 @@ ts.filter = {
// look for multiple columns '1-3,4-6,8'
columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch );
data.anyMatch = true;
data.isMatch = true;
data.rowArray = data.$cells.map( function( i ) {
if ( $.inArray( i, columnIndex ) > -1 ) {
if ( data.parsed[ i ] ) {
@ -910,16 +963,10 @@ ts.filter = {
data.exact = data.rowArray.join( ' ' );
data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact;
data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' );
filterMatched = null;
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, vars.noAnyMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ffxn]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
vars.excludeMatch = vars.noAnyMatch;
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
showRow = filterMatched;
} else {
@ -946,7 +993,7 @@ ts.filter = {
data.index = columnIndex;
// filter types to exclude, per column
excludeMatch = vars.excludeFilter[ columnIndex ];
vars.excludeMatch = vars.excludeFilter[ columnIndex ];
// ignore if filter is empty or disabled
if ( data.filter ) {
@ -960,6 +1007,9 @@ ts.filter = {
}
data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ?
data.exact.toLowerCase() : data.exact;
data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' );
result = showRow; // if showRow is true, show that row
// in case select filter option has a different value vs text 'a - z|A through Z'
@ -984,13 +1034,12 @@ ts.filter = {
// data.filter = case sensitive
data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter;
fxn = vars.functions[ columnIndex ];
$cell = c.$headerIndexed[ columnIndex ];
hasSelect = $cell.hasClass( 'filter-select' );
hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' );
filterMatched = null;
if ( fxn || ( hasSelect && val ) ) {
if ( fxn === true || hasSelect ) {
// default selector uses exact match unless 'filter-match' class is found
filterMatched = $cell.hasClass( 'filter-match' ) ?
filterMatched = data.isMatch ?
data.iExact.search( data.iFilter ) >= 0 :
data.filter === data.exact;
} else if ( typeof fxn === 'function' ) {
@ -1007,15 +1056,7 @@ ts.filter = {
if ( filterMatched === null ) {
// cycle through the different filters
// filters return a boolean or null if nothing matches
matches = null;
for ( ffxn in ts.filter.types ) {
if ( $.inArray( ffxn, excludeMatch ) < 0 && matches === null ) {
matches = ts.filter.types[ ffxn ]( c, data );
if ( matches !== null ) {
filterMatched = matches;
}
}
}
filterMatched = ts.filter.processTypes( c, data, vars );
if ( filterMatched !== null ) {
result = filterMatched;
// Look for match, and add child row data for matching

View File

@ -148,7 +148,7 @@ $(function(){
wo = this.wo,
$table = this.$table,
table = this.table;
expect(27);
expect(31);
return QUnit.SequentialRunner(
function(actions, assertions) {
@ -205,6 +205,18 @@ $(function(){
).nextTask(
function(){ ts.setFilters( table, ['', 'br && cl'], true ); },
function(){ assert.cacheCompare( table, 1, ['Brandon Clark'], 'search and match; ensure search filtered gets cleared', true ); }
).nextTask(
function(){ ts.setFilters( table, ['', 'c* && l && a'], true ); },
function(){ assert.cacheCompare( table, 1, ['Brandon Clark', 'Clark'], 'search "c* && l && a"', true ); }
).nextTask(
function(){ ts.setFilters( table, ['', 'a && !o'], true ); },
function(){ assert.cacheCompare( table, 1, ['Clark', 'Alex', 'Brenda Dexter', 'Martha'], 'search "a && !o"', true ); }
).nextTask(
function(){ ts.setFilters( table, ['', '', '' , '>20 && <40'], true ); },
function(){ assert.cacheCompare( table, 3, [25, 28, 33, 24, 22, 25], 'search ">20 && <40"', true ); }
).nextTask(
function(){ ts.setFilters( table, ['', '', '' , '<10 or >40'], true ); },
function(){ assert.cacheCompare( table, 3, [51, 45, 65], 'search "<10 or >40"', true ); }
).nextTask(
function(){ ts.setFilters( table, ['', 'alex|br*'], true ); },
function(){ assert.cacheCompare( table, 1, ['Brandon Clark', 'Bruce', 'Alex', 'Bruce Lee', 'Brenda Dexter'], 'search OR match', true ); }