jquery-tablesorter 1.8.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acfcfbdcbf520532b495a3aa784033d4ec287a30
4
- data.tar.gz: 002c5105872cb05cf0d4f7d2f78d027fc8fc7864
3
+ metadata.gz: e6f685058fd350d114a2f272290ad8a7e5c3f7ed
4
+ data.tar.gz: e9de584dc9a659b30b9bd79dec2a863cadf04749
5
5
  SHA512:
6
- metadata.gz: 171363daa41553a03cbca64f9c58bac9810d9c3b4f87509eda0713bee18bc0989ee2beca7b29247987085c379ccd97735382dd3f54f03ed4ffaf9f8df1938815
7
- data.tar.gz: 5c0a43f4f40e32e946ed3e500d9f7012ffffe50803fb6a880a51d4648672435cd5fcde8e3d5abb33abb57cf511d60acac63766db00f678f63015223266f79ed6
6
+ metadata.gz: 49d6f67cce6923d1ac1fb24d651673dc3162608e41dfb70349fb3ec37c61b815f5cd4462887d2f891cdf1a05a90603d09bd4d45289f6e605270590f2cb744958
7
+ data.tar.gz: d8c7d5b3890bea49ed049a17600ef2695a205450e432fac80d72b1835e69eb3c8edb7fddfd4517bef9d47207107186696c1fdee1b95992a10562871d357d21a2
@@ -4,7 +4,7 @@
4
4
 
5
5
  Simple integration of jquery-tablesorter into the asset pipeline.
6
6
 
7
- Current tablesorter version: 2.13.2 (11/2/2013), [documentation]
7
+ Current tablesorter version: 2.13.3 (11/9/2013), [documentation]
8
8
 
9
9
  Any issue associate with the js/css files, please report to [Mottie's fork].
10
10
 
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = "1.8.0"
2
+ VERSION = "1.8.1"
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * tablesorter pager plugin
3
- * updated 10/30/2013
3
+ * updated 11/9/2013
4
4
  */
5
5
  /*jshint browser:true, jquery:true, unused:false */
6
6
  ;(function($) {
@@ -236,6 +236,9 @@
236
236
  $t.find('thead tr.' + p.cssErrorRow).remove(); // Clean up any previous error.
237
237
 
238
238
  if ( exception ) {
239
+ if (c.debug) {
240
+ ts.log('Ajax Error', xhr, exception);
241
+ }
239
242
  $err = $('<tr class="' + p.cssErrorRow + '"><td style="text-align:center;" colspan="' + hl + '">' + (
240
243
  xhr.status === 0 ? 'Not connected, verify Network' :
241
244
  xhr.status === 404 ? 'Requested page not found [404]' :
@@ -272,15 +275,13 @@
272
275
  c.$tbodies.eq(0).empty().append(d);
273
276
  } else if (l) {
274
277
  // build table from array
275
- if ( l > 0 ) {
276
- for ( i = 0; i < l; i++ ) {
277
- tds += '<tr>';
278
- for ( j = 0; j < d[i].length; j++ ) {
279
- // build tbody cells
280
- tds += '<td>' + d[i][j] + '</td>';
281
- }
282
- tds += '</tr>';
278
+ for ( i = 0; i < l; i++ ) {
279
+ tds += '<tr>';
280
+ for ( j = 0; j < d[i].length; j++ ) {
281
+ // build tbody cells
282
+ tds += '<td>' + d[i][j] + '</td>';
283
283
  }
284
+ tds += '</tr>';
284
285
  }
285
286
  // add rows to first tbody
286
287
  c.$tbodies.eq(0).html( tds );
@@ -314,9 +315,15 @@
314
315
  if (c.showProcessing) {
315
316
  ts.isProcessing(table); // remove loading icon
316
317
  }
317
- p.totalPages = Math.ceil( p.totalRows / ( p.size || 10 ) );
318
+ // make sure last pager settings are saved, prevents multiple server side calls with
319
+ // the same parameters
320
+ p.last.totalPages = p.totalPages = Math.ceil( p.totalRows / ( p.size || 10 ) );
321
+ p.last.currentFilters = p.currentFilters;
322
+ p.last.sortList = (c.sortList || []).join(',');
318
323
  updatePageDisplay(table, p);
319
324
  fixHeight(table, p);
325
+ // apply widgets after table has rendered
326
+ $t.trigger('applyWidgets');
320
327
  if (p.initialized) {
321
328
  $t.trigger('pagerChange', p);
322
329
  $t.trigger('updateComplete');
@@ -350,17 +357,21 @@
350
357
  p.oldAjaxSuccess(data);
351
358
  }
352
359
  };
360
+ if (c.debug) {
361
+ ts.log('ajax initialized', p.ajaxObject);
362
+ }
353
363
  $.ajax(p.ajaxObject);
354
364
  }
355
365
  },
356
366
 
357
367
  getAjaxUrl = function(table, p) {
358
- var url = (p.ajaxUrl) ? p.ajaxUrl
368
+ var c = table.config,
369
+ url = (p.ajaxUrl) ? p.ajaxUrl
359
370
  // allow using "{page+1}" in the url string to switch to a non-zero based index
360
371
  .replace(/\{page([\-+]\d+)?\}/, function(s,n){ return p.page + (n ? parseInt(n, 10) : 0); })
361
372
  .replace(/\{size\}/g, p.size) : '',
362
- sl = table.config.sortList,
363
- fl = p.currentFilters || [],
373
+ sl = c.sortList,
374
+ fl = p.currentFilters || $(table).data('lastSearch') || [],
364
375
  sortCol = url.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/),
365
376
  filterCol = url.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/),
366
377
  arry = [];
@@ -382,10 +393,14 @@
382
393
  });
383
394
  // if the arry is empty, just add the fcol parameter... "&{filterList:fcol}" becomes "&fcol"
384
395
  url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol );
396
+ p.currentFilters = fl;
385
397
  }
386
398
  if ( typeof(p.customAjaxUrl) === "function" ) {
387
399
  url = p.customAjaxUrl(table, url);
388
400
  }
401
+ if (c.debug) {
402
+ ts.log('Pager ajax url: ' + url);
403
+ }
389
404
  return url;
390
405
  },
391
406
 
@@ -433,6 +448,9 @@
433
448
  p.totalPages = 1;
434
449
  $(table).addClass('pagerDisabled').find('tr.pagerSavedHeightSpacer').remove();
435
450
  renderTable(table, table.config.rowsCopy, p);
451
+ if (table.config.debug) {
452
+ ts.log('pager disabled');
453
+ }
436
454
  }
437
455
  // disable size selector
438
456
  p.$size.add(p.$goto).each(function(){
@@ -442,17 +460,25 @@
442
460
 
443
461
  moveToPage = function(table, p, flag) {
444
462
  if ( p.isDisabled ) { return; }
445
- var l = p.last,
463
+ var c = table.config,
464
+ l = p.last,
446
465
  pg = Math.min( p.totalPages, p.filteredPages );
447
466
  if ( p.page < 0 ) { p.page = 0; }
448
467
  if ( p.page > ( pg - 1 ) && pg !== 0 ) { p.page = pg - 1; }
449
- // don't allow rendering multiple times on the same page/size/totalpages/filters
450
- if (l.page === p.page && l.size === p.size && l.total === p.totalPages && l.filters === p.currentFilters ) { return; }
468
+ // don't allow rendering multiple times on the same page/size/totalpages/filters/sorts
469
+ if ( l.page === p.page && l.size === p.size && l.totalPages === p.totalPages &&
470
+ (l.currentFilters || []).join(',') === (p.currentFilters || []).join(',') &&
471
+ l.sortList === (c.sortList || []).join(',') ) { return; }
472
+ if (c.debug) {
473
+ ts.log('Pager changing to page ' + p.page);
474
+ }
451
475
  p.last = {
452
476
  page : p.page,
453
477
  size : p.size,
478
+ // fixes #408; modify sortList otherwise it auto-updates
479
+ sortList : (c.sortList || []).join(','),
454
480
  totalPages : p.totalPages,
455
- currentFilters : p.currentFilters
481
+ currentFilters : p.currentFilters || []
456
482
  };
457
483
  if (p.ajax) {
458
484
  getAjax(table, p);
@@ -460,18 +486,18 @@
460
486
  renderTable(table, table.config.rowsCopy, p);
461
487
  }
462
488
  $.data(table, 'pagerLastPage', p.page);
463
- $.data(table, 'pagerUpdateTriggered', true);
464
489
  if (p.initialized && flag !== false) {
465
- $(table).trigger('pageMoved', p);
490
+ c.$table.trigger('pageMoved', p);
491
+ c.$table.trigger('applyWidgets');
466
492
  }
467
493
  },
468
494
 
469
495
  setPageSize = function(table, size, p) {
470
- p.size = size;
471
- p.$size.val(size);
496
+ p.size = size || p.size || 10;
497
+ p.$size.val(p.size);
472
498
  $.data(table, 'pagerLastPage', p.page);
473
499
  $.data(table, 'pagerLastSize', p.size);
474
- p.totalPages = Math.ceil( p.totalRows / ( p.size || 10 ) );
500
+ p.totalPages = Math.ceil( p.totalRows / p.size );
475
501
  moveToPage(table, p);
476
502
  },
477
503
 
@@ -517,14 +543,17 @@
517
543
  p.$goto.removeClass(p.cssDisabled).removeAttr('disabled');
518
544
  p.isDisabled = false;
519
545
  p.page = $.data(table, 'pagerLastPage') || p.page || 0;
520
- p.size = $.data(table, 'pagerLastSize') || parseInt(pg.find('option[selected]').val(), 10) || p.size;
546
+ p.size = $.data(table, 'pagerLastSize') || parseInt(pg.find('option[selected]').val(), 10) || p.size || 10;
521
547
  pg.val(p.size); // set page size
522
- p.totalPages = Math.ceil( Math.min( p.totalPages, p.filteredPages ) / ( p.size || 10 ) );
548
+ p.totalPages = Math.ceil( Math.min( p.totalPages, p.filteredPages ) / p.size );
523
549
  if ( triggered ) {
524
550
  $(table).trigger('update');
525
551
  setPageSize(table, p.size, p);
526
552
  hideRowsSetup(table, p);
527
553
  fixHeight(table, p);
554
+ if (table.config.debug) {
555
+ ts.log('pager enabled');
556
+ }
528
557
  }
529
558
  };
530
559
 
@@ -534,8 +563,8 @@
534
563
  if ( !p.ajax ) {
535
564
  c.rowsCopy = rows;
536
565
  p.totalRows = p.countChildRows ? c.$tbodies.eq(0).children().length : rows.length;
537
- p.size = $.data(table, 'pagerLastSize') || p.size;
538
- p.totalPages = Math.ceil( p.totalRows / ( p.size || 10 ) );
566
+ p.size = $.data(table, 'pagerLastSize') || p.size || 10;
567
+ p.totalPages = Math.ceil( p.totalRows / p.size );
539
568
  renderTable(table, rows, p);
540
569
  }
541
570
  };
@@ -551,34 +580,36 @@
551
580
  $t = c.$table,
552
581
  // added in case the pager is reinitialized after being destroyed.
553
582
  pager = p.$container = $(p.container).addClass('tablesorter-pager').show();
583
+ if (c.debug) {
584
+ ts.log('Pager initializing');
585
+ }
554
586
  p.oldAjaxSuccess = p.oldAjaxSuccess || p.ajaxObject.success;
555
587
  c.appender = $this.appender;
556
-
588
+ if (ts.filter && c.widgets.indexOf('filter') >= 0) {
589
+ // get any default filter settings (data-value attribute) fixes #388
590
+ p.currentFilters = c.$table.data('lastSearch') || ts.filter.setDefaults(table, c, c.widgetOptions) || [];
591
+ // set, but don't apply current filters
592
+ ts.setFilters(table, p.currentFilters, false);
593
+ }
557
594
  if (p.savePages && ts.storage) {
558
595
  t = ts.storage(table, 'tablesorter-pager') || {}; // fixes #387
559
596
  p.page = isNaN(t.page) ? p.page : t.page;
560
597
  p.size = ( isNaN(t.size) ? p.size : t.size ) || 10;
598
+ $.data(table, 'pagerLastSize', p.size);
561
599
  }
562
600
 
563
601
  $t
564
- .unbind('filterStart.pager filterEnd.pager sortEnd.pager disable.pager enable.pager destroy.pager update.pager pageSize.pager')
602
+ .unbind('filterStart filterEnd sortEnd disable enable destroy update pageSize '.split(' ').join('.pager '))
565
603
  .bind('filterStart.pager', function(e, filters) {
566
- $.data(table, 'pagerUpdateTriggered', false);
567
604
  p.currentFilters = filters;
568
605
  })
569
606
  // update pager after filter widget completes
570
- .bind('filterEnd.pager sortEnd.pager', function(e) {
571
- //Prevent infinite event loops from occuring by setting this in all moveToPage calls and catching it here.
572
- if ($.data(table, 'pagerUpdateTriggered')) {
573
- $.data(table, 'pagerUpdateTriggered', false);
574
- return;
575
- }
576
- //only run the server side sorting if it has been enabled
577
- if (e.type === "filterEnd" || (e.type === "sortEnd" && c.serverSideSorting)) {
607
+ .bind('filterEnd.pager sortEnd.pager', function() {
608
+ if (p.initialized) {
578
609
  moveToPage(table, p, false);
610
+ updatePageDisplay(table, p, false);
611
+ fixHeight(table, p);
579
612
  }
580
- updatePageDisplay(table, p, false);
581
- fixHeight(table, p);
582
613
  })
583
614
  .bind('disable.pager', function(e){
584
615
  e.stopPropagation();
@@ -617,6 +648,7 @@
617
648
  pager.find(ctrls.join(','))
618
649
  .unbind('click.pager')
619
650
  .bind('click.pager', function(e){
651
+ e.stopPropagation();
620
652
  var i, $t = $(this), l = ctrls.length;
621
653
  if ( !$t.hasClass(p.cssDisabled) ) {
622
654
  for (i = 0; i < l; i++) {
@@ -626,7 +658,6 @@
626
658
  }
627
659
  }
628
660
  }
629
- return false;
630
661
  });
631
662
 
632
663
  // goto selector
@@ -1,5 +1,5 @@
1
1
  /**!
2
- * TableSorter 2.13.2 - Client-side table sorting with ease!
2
+ * TableSorter 2.13.3 - Client-side table sorting with ease!
3
3
  * @requires jQuery v1.2.6+
4
4
  *
5
5
  * Copyright (c) 2007 Christian Bach
@@ -24,7 +24,7 @@
24
24
 
25
25
  var ts = this;
26
26
 
27
- ts.version = "2.13.2";
27
+ ts.version = "2.13.3";
28
28
 
29
29
  ts.parsers = [];
30
30
  ts.widgets = [];
@@ -122,7 +122,8 @@
122
122
  };
123
123
 
124
124
  /* debuging utils */
125
- function log(s) {
125
+ function log() {
126
+ var s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : arguments[0];
126
127
  if (typeof console !== "undefined" && typeof console.log !== "undefined") {
127
128
  console.log(s);
128
129
  } else {
@@ -201,9 +202,12 @@
201
202
  var c = table.config,
202
203
  // update table bodies in case we start with an empty table
203
204
  tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
204
- rows, list, l, i, h, ch, p, parsersDebug = "";
205
+ rows, list, l, i, h, ch, p, time, parsersDebug = "";
205
206
  if ( tb.length === 0) {
206
207
  return c.debug ? log('*Empty table!* Not building a parser cache') : '';
208
+ } else if (c.debug) {
209
+ time = new Date();
210
+ log('Detecting parsers for each column');
207
211
  }
208
212
  rows = tb[0].rows;
209
213
  if (rows[0]) {
@@ -233,6 +237,7 @@
233
237
  }
234
238
  if (c.debug) {
235
239
  log(parsersDebug);
240
+ benchmark("Completed detecting parsers", time);
236
241
  }
237
242
  c.parsers = list;
238
243
  }
@@ -301,11 +306,12 @@
301
306
  // init flag (true) used by pager plugin to prevent widget application
302
307
  function appendToTable(table, init) {
303
308
  var c = table.config,
304
- b = table.tBodies,
305
- rows = [],
306
- c2 = c.cache,
307
- r, n, totalRows, checkCell, $bk, $tb,
308
- i, j, k, l, pos, appendTime;
309
+ wo = c.widgetOptions,
310
+ b = table.tBodies,
311
+ rows = [],
312
+ c2 = c.cache,
313
+ r, n, totalRows, checkCell, $bk, $tb,
314
+ i, j, k, l, pos, appendTime;
309
315
  if (isEmptyObject(c2)) { return; } // empty table - fixes #206/#346
310
316
  if (c.debug) {
311
317
  appendTime = new Date();
@@ -322,8 +328,8 @@
322
328
  for (i = 0; i < totalRows; i++) {
323
329
  pos = n[i][checkCell];
324
330
  rows.push(r[pos]);
325
- // removeRows used by the pager plugin
326
- if (!c.appender || !c.removeRows) {
331
+ // removeRows used by the pager plugin; don't render if using ajax - fixes #411
332
+ if (!c.appender || (c.pager && (!c.pager.removeRows || !wo.pager_removeRows) && !c.pager.ajax)) {
327
333
  l = r[pos].length;
328
334
  for (j = 0; j < l; j++) {
329
335
  $tb.append(r[pos][j]);
@@ -340,8 +346,8 @@
340
346
  if (c.debug) {
341
347
  benchmark("Rebuilt table", appendTime);
342
348
  }
343
- // apply table widgets
344
- if (!init) { ts.applyWidget(table); }
349
+ // apply table widgets; but not before ajax completes
350
+ if (!init && !c.appender) { ts.applyWidget(table); }
345
351
  // trigger sortend
346
352
  $(table).trigger("sortEnd", table);
347
353
  $(table).trigger("updateComplete", table);
@@ -517,7 +523,7 @@
517
523
  // ensure all sortList values are numeric - fixes #127
518
524
  s = [ parseInt(v[0], 10), parseInt(v[1], 10) ];
519
525
  // make sure header exists
520
- o = c.headerList[s[0]];
526
+ o = c.$headers[s[0]];
521
527
  if (o) { // prevents error if sorton array is wrong
522
528
  c.sortList.push(s);
523
529
  t = $.inArray(s[1], o.order); // fixes issue #167
@@ -584,12 +590,13 @@
584
590
  }
585
591
  // the user has clicked on an already sorted column
586
592
  if (ts.isValueInArray(i, c.sortList)) {
587
- // reverse the sorting direction for all tables
593
+ // reverse the sorting direction
588
594
  for (j = 0; j < c.sortList.length; j++) {
589
595
  s = c.sortList[j];
590
- o = c.headerList[s[0]];
596
+ o = c.$headers[s[0]];
591
597
  if (s[0] === i) {
592
- s[1] = o.order[o.count];
598
+ // o.count seems to be incorrect when compared to cell.count
599
+ s[1] = o.order[cell.count];
593
600
  if (s[1] === 2) {
594
601
  c.sortList.splice(j,1);
595
602
  o.count = -1;
@@ -1,4 +1,4 @@
1
- /*! Filter widget formatter functions - updated 10/30/2013
1
+ /*! Filter widget formatter functions - updated 11/9/2013
2
2
  * requires: tableSorter 2.7.7+ and jQuery 1.4.3+
3
3
  *
4
4
  * uiSpinner (jQuery UI spinner)
@@ -92,7 +92,7 @@ $.tablesorter.filterFormatter = {
92
92
  .val(o.value)
93
93
  .appendTo($cell)
94
94
  .spinner(o)
95
- .bind('change keyup', function(e){
95
+ .bind('change keyup', function(){
96
96
  updateSpinner();
97
97
  });
98
98
 
@@ -113,7 +113,7 @@ $.tablesorter.filterFormatter = {
113
113
  .val(o.value)
114
114
  .appendTo($shcell)
115
115
  .spinner(o)
116
- .bind('change keyup', function(e){
116
+ .bind('change keyup', function(){
117
117
  $cell.find('.spinner').val( this.value );
118
118
  updateSpinner();
119
119
  });
@@ -228,7 +228,7 @@ $.tablesorter.filterFormatter = {
228
228
  .val(o.value)
229
229
  .appendTo($shcell)
230
230
  .slider(o)
231
- .bind('change keyup', function(e){
231
+ .bind('change keyup', function(){
232
232
  $cell.find('.slider').val( this.value );
233
233
  updateSlider();
234
234
  });
@@ -337,7 +337,7 @@ $.tablesorter.filterFormatter = {
337
337
  .val(o.value)
338
338
  .appendTo($shcell)
339
339
  .slider(o)
340
- .bind('change keyup', function(e){
340
+ .bind('change keyup', function(){
341
341
  $cell.find('.range').val( this.value );
342
342
  updateUiRange();
343
343
  });
@@ -630,8 +630,7 @@ $.tablesorter.filterFormatter = {
630
630
  var compare = ( $cell.find('.compare').val() || o.compare);
631
631
  $cell.find('input[type=hidden]')
632
632
  // add equal to the beginning, so we filter exact numbers
633
- .val( !o.addToggle || chkd ? (o.compare ? o.compare : o.exactMatch ? '=' : '') + v : '' )
634
- .val( !o.addToggle || chkd ? compare + v : '' )
633
+ .val( !o.addToggle || chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' )
635
634
  .trigger('search', delayed ? delayed : o.delayed).end()
636
635
  .find('.number').val(v);
637
636
  if ($cell.find('.number').length) {
@@ -732,7 +731,7 @@ $.tablesorter.filterFormatter = {
732
731
  HTML5 range slider
733
732
  \**********************/
734
733
  html5Range : function($cell, indx, def5Range) {
735
- var t, o = $.extend({
734
+ var o = $.extend({
736
735
  value : 0,
737
736
  min : 0,
738
737
  max : 100,
@@ -1,4 +1,4 @@
1
- /*! tableSorter 2.8+ widgets - updated 11/2/2013
1
+ /*! tableSorter 2.8+ widgets - updated 11/9/2013
2
2
  *
3
3
  * Column Styles
4
4
  * Column Filters
@@ -17,6 +17,7 @@ var ts = $.tablesorter = $.tablesorter || {};
17
17
  ts.themes = {
18
18
  "bootstrap" : {
19
19
  table : 'table table-bordered table-striped',
20
+ caption : 'caption',
20
21
  header : 'bootstrap-header', // give the header a gradient background
21
22
  footerRow : '',
22
23
  footerCells: '',
@@ -32,6 +33,7 @@ ts.themes = {
32
33
  },
33
34
  "jui" : {
34
35
  table : 'ui-widget ui-widget-content ui-corner-all', // table classes
36
+ caption : 'ui-widget-content ui-corner-all',
35
37
  header : 'ui-widget-header ui-corner-all ui-state-default', // header classes
36
38
  footerRow : '',
37
39
  footerCells: '',
@@ -167,10 +169,13 @@ ts.addWidget({
167
169
  sh = 'tr.' + (wo.stickyHeaders || 'tablesorter-stickyHeader'),
168
170
  rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc;
169
171
  if (c.debug) { time = new Date(); }
172
+ // initialization code - run once
170
173
  if (!$t.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized){
171
174
  // update zebra stripes
172
175
  if (o.even !== '') { wo.zebra[0] += ' ' + o.even; }
173
176
  if (o.odd !== '') { wo.zebra[1] += ' ' + o.odd; }
177
+ // add caption style
178
+ $t.find('caption').addClass(o.caption);
174
179
  // add table/footer class names
175
180
  t = $t
176
181
  // remove other selected themes; use widgetOptions.theme_remove
@@ -191,7 +196,7 @@ ts.addWidget({
191
196
  $(this)[ e.type === 'mouseenter' ? 'addClass' : 'removeClass' ](o.hover);
192
197
  });
193
198
  if (!$h.find('.tablesorter-wrapper').length) {
194
- // Firefox needs this inner div to position the resizer correctly
199
+ // Firefox needs this inner div to position the icon/resizer correctly
195
200
  $h.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
196
201
  }
197
202
  if (c.cssIcon){
@@ -327,6 +332,7 @@ ts.addWidget({
327
332
  id: "filter",
328
333
  priority: 50,
329
334
  options : {
335
+ filter_anyMatch : false, // if true overrides default find rows behaviours and if any column matches query it returns that row
330
336
  filter_childRows : false, // if true, filter includes child row content in the search
331
337
  filter_columnFilters : true, // if true, a filter will be added to the top of each table column
332
338
  filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
@@ -342,504 +348,663 @@ ts.addWidget({
342
348
  filter_startsWith : false, // if true, filter start from the beginning of the cell contents
343
349
  filter_useParsedData : false, // filter all data using parsed content
344
350
  filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used.
345
- filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value
346
-
347
- // regex used in filter "check" functions - not for general use and not documented
348
- filter_regex : {
349
- "regex" : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
350
- "child" : /tablesorter-childRow/, // child row class name; this gets updated in the script
351
- "filtered" : /filtered/, // filtered (hidden) row class name; updated in the script
352
- "type" : /undefined|number/, // check type
353
- "exact" : /(^[\"|\'|=]+)|([\"|\'|=]+$)/g, // exact match (allow '==')
354
- "nondigit" : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
355
- "operators" : /[<>=]/g // replace operators
351
+ filter_defaultAttrib : 'data-value' // data attribute in the header cell that contains the default filter value
352
+ },
353
+ format: function(table, c, wo) {
354
+ if (!c.$table.hasClass('hasFilters')) {
355
+ if (c.parsers || !c.parsers && wo.filter_serversideFiltering) {
356
+ ts.filter.init(table, c, wo);
357
+ }
356
358
  }
357
359
  },
358
- format: function(table, c, wo){
359
- if (c.$table.hasClass('hasFilters')) { return; }
360
- // allow filter widget to work if it is being used
361
- if (c.parsers || !c.parsers && wo.filter_serversideFiltering){
362
- var i, j, k, l, val, ff, x, xi, st, sel, str,
363
- ft, ft2, $th, rg, s, t, dis, col,
364
- fmt = ts.formatFloat,
365
- last = '', // save last filter search
366
- $ths = c.$headers,
367
- $t = c.$table.addClass('hasFilters'),
368
- b = c.$tbodies,
369
- // c.columns defined in computeThIndexes()
370
- cols = c.columns || c.$headers.filter('th').length,
371
- parsed, time, timer,
360
+ remove: function(table, c, wo) {
361
+ var tbodyIndex, $tbody,
362
+ $table = c.$table,
363
+ $tbodies = c.$tbodies;
364
+ $table
365
+ .removeClass('hasFilters')
366
+ // add .tsfilter namespace to all BUT search
367
+ .unbind('addRows updateCell update updateComplete appendCache search filterStart filterEnd '.split(' ').join('.tsfilter '))
368
+ .find('.tablesorter-filter-row').remove();
369
+ for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
370
+ $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
371
+ $tbody.children().removeClass(wo.filter_filteredRow).show();
372
+ ts.processTbody(table, $tbody, false); // restore tbody
373
+ }
374
+ if (wo.filter_reset) {
375
+ $(document).undelegate(wo.filter_reset, 'click.tsfilter');
376
+ }
377
+ }
378
+ });
379
+
380
+ ts.filter = {
372
381
 
373
- // dig fer gold
374
- checkFilters = function(filter){
375
- var arry = $.isArray(filter),
376
- v = (arry) ? filter : ts.getFilters(table),
377
- cv = (v || []).join(''); // combined filter values
378
- // add filter array back into inputs
379
- if (arry) {
380
- ts.setFilters( $t, v );
382
+ // regex used in filter "check" functions - not for general use and not documented
383
+ regex: {
384
+ regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
385
+ child : /tablesorter-childRow/, // child row class name; this gets updated in the script
386
+ filtered : /filtered/, // filtered (hidden) row class name; updated in the script
387
+ type : /undefined|number/, // check type
388
+ exact : /(^[\"|\'|=]+)|([\"|\'|=]+$)/g, // exact match (allow '==')
389
+ nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
390
+ operators : /[<>=]/g // replace operators
391
+ },
392
+ // function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed )
393
+ // filter = array of filter input values; iFilter = same array, except lowercase
394
+ // exact = table cell text (or parsed data if column parser enabled)
395
+ // iExact = same as exact, except lowercase
396
+ // cached = table cell text from cache, so it has been parsed
397
+ // index = column index; table = table element (DOM)
398
+ // wo = widget options (table.config.widgetOptions)
399
+ // parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
400
+ types: {
401
+ // Look for regex
402
+ regex: function( filter, iFilter, exact, iExact ) {
403
+ if ( ts.filter.regex.regex.test(iFilter) ) {
404
+ var matches,
405
+ regex = ts.filter.regex.regex.exec(iFilter);
406
+ try {
407
+ matches = new RegExp(regex[1], regex[2]).test( iExact );
408
+ } catch (error) {
409
+ matches = false;
381
410
  }
382
- if (wo.filter_hideFilters){
383
- // show/hide filter row as needed
384
- $t.find('.tablesorter-filter-row').trigger( cv === '' ? 'mouseleave' : 'mouseenter' );
411
+ return matches;
412
+ }
413
+ return null;
414
+ },
415
+ // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
416
+ exact: function( filter, iFilter, exact, iExact ) {
417
+ /*jshint eqeqeq:false */
418
+ if ( iFilter.replace(ts.filter.regex.exact, '') == iExact ) {
419
+ return true;
420
+ }
421
+ return null;
422
+ },
423
+ // Look for a not match
424
+ notMatch: function( filter, iFilter, exact, iExact, cached, index, table, wo ) {
425
+ if ( /^\!/.test(iFilter) ) {
426
+ iFilter = iFilter.replace('!', '');
427
+ var indx = iExact.search( $.trim(iFilter) );
428
+ return iFilter === '' ? true : !(wo.filter_startsWith ? indx === 0 : indx >= 0);
429
+ }
430
+ return null;
431
+ },
432
+ // Look for operators >, >=, < or <=
433
+ operators: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
434
+ if ( /^[<>]=?/.test(iFilter) ) {
435
+ var cachedValue, result,
436
+ c = table.config,
437
+ query = ts.formatFloat( iFilter.replace(ts.filter.regex.operators, ''), table ),
438
+ parser = c.parsers[index],
439
+ savedSearch = query;
440
+ // parse filter value in case we're comparing numbers (dates)
441
+ if (parsed[index] || parser.type === 'numeric') {
442
+ cachedValue = parser.format( '' + iFilter.replace(ts.filter.regex.operators, ''), table, c.$headers.eq(index), index );
443
+ query = ( typeof query === "number" && cachedValue !== '' && !isNaN(cachedValue) ) ? cachedValue : query;
385
444
  }
386
- // return if the last search is the same; but filter === false when updating the search
387
- // see example-widget-filter.html filter toggle buttons
388
- if (last === cv && filter !== false) { return; }
389
- $t.trigger('filterStart', [v]);
390
- if (c.showProcessing) {
391
- // give it time for the processing icon to kick in
392
- setTimeout(function(){
393
- findRows(filter, v, cv);
394
- return false;
395
- }, 30);
396
- } else {
397
- findRows(filter, v, cv);
398
- return false;
445
+ // iExact may be numeric - see issue #149;
446
+ // check if cached is defined, because sometimes j goes out of range? (numeric columns)
447
+ cachedValue = ( parsed[index] || parser.type === 'numeric' ) && !isNaN(query) && cached ? cached :
448
+ isNaN(iExact) ? ts.formatFloat( iExact.replace(ts.filter.regex.nondigit, ''), table) :
449
+ ts.formatFloat( iExact, table );
450
+ if ( />/.test(iFilter) ) { result = />=/.test(iFilter) ? cachedValue >= query : cachedValue > query; }
451
+ if ( /</.test(iFilter) ) { result = /<=/.test(iFilter) ? cachedValue <= query : cachedValue < query; }
452
+ // keep showing all rows if nothing follows the operator
453
+ if ( !result && savedSearch === '' ) { result = true; }
454
+ return result;
455
+ }
456
+ return null;
457
+ },
458
+ // Look for an AND or && operator (logical and)
459
+ and : function( filter, iFilter, exact, iExact ) {
460
+ if ( /\s+(AND|&&)\s+/g.test(filter) ) {
461
+ var query = iFilter.split( /(?:\s+(?:and|&&)\s+)/g ),
462
+ result = iExact.search( $.trim(query[0]) ) >= 0,
463
+ indx = query.length - 1;
464
+ while (result && indx) {
465
+ result = result && iExact.search( $.trim(query[indx]) ) >= 0;
466
+ indx--;
399
467
  }
400
- },
401
- findRows = function(filter, v, cv){
402
- var $tb, $tr, $td, cr, r, l, ff, fr, time, r1, r2, searchFiltered;
403
- if (c.debug) { time = new Date(); }
404
- for (k = 0; k < b.length; k++ ){
405
- if (b.eq(k).hasClass(ts.css.info)) { continue; } // ignore info blocks, issue #264
406
- $tb = ts.processTbody(table, b.eq(k), true);
407
- $tr = $tb.children('tr:not(.' + c.cssChildRow + ')');
408
- l = $tr.length;
409
- if (cv === '' || wo.filter_serversideFiltering){
410
- $tb.children().show().removeClass(wo.filter_filteredRow);
411
- } else {
412
- // optimize searching only through already filtered rows - see #313
413
- searchFiltered = true;
414
- r = $t.data('lastSearch') || [];
415
- $.each(v, function(i,val){
416
- // check for changes from beginning of filter; but ignore if there is a logical "or" in the string
417
- searchFiltered = (val || '').indexOf(r[i] || '') === 0 && searchFiltered && !/(\s+or\s+|\|)/g.test(val || '');
418
- });
419
- // can't search when all rows are hidden - this happens when looking for exact matches
420
- if (searchFiltered && $tr.filter(':visible').length === 0) { searchFiltered = false; }
421
- // loop through the rows
422
- for (j = 0; j < l; j++){
423
- r = $tr[j].className;
424
- // skip child rows & already filtered rows
425
- if ( wo.filter_regex.child.test(r) || (searchFiltered && wo.filter_regex.filtered.test(r)) ) { continue; }
426
- r = true;
427
- cr = $tr.eq(j).nextUntil('tr:not(.' + c.cssChildRow + ')');
428
- // so, if "table.config.widgetOptions.filter_childRows" is true and there is
429
- // a match anywhere in the child row, then it will make the row visible
430
- // checked here so the option can be changed dynamically
431
- t = (cr.length && wo.filter_childRows) ? cr.text() : '';
432
- t = wo.filter_ignoreCase ? t.toLocaleLowerCase() : t;
433
- $td = $tr.eq(j).children('td');
434
- for (i = 0; i < cols; i++){
435
- // ignore if filter is empty or disabled
436
- if (v[i]){
437
- // check if column data should be from the cell or from parsed data
438
- if (wo.filter_useParsedData || parsed[i]){
439
- x = c.cache[k].normalized[j][i];
440
- } else {
441
- // using older or original tablesorter
442
- x = $.trim($td.eq(i).text());
443
- }
444
- xi = !wo.filter_regex.type.test(typeof x) && wo.filter_ignoreCase ? x.toLocaleLowerCase() : x;
445
- ff = r; // if r is true, show that row
446
- // replace accents - see #357
447
- v[i] = c.sortLocaleCompare ? ts.replaceAccents(v[i]) : v[i];
448
- // val = case insensitive, v[i] = case sensitive
449
- val = wo.filter_ignoreCase ? v[i].toLocaleLowerCase() : v[i];
450
- if (wo.filter_functions && wo.filter_functions[i]){
451
- if (wo.filter_functions[i] === true){
452
- // default selector; no "filter-select" class
453
- ff = ($ths.filter('[data-column="' + i + '"]:last').hasClass('filter-match')) ? xi.search(val) >= 0 : v[i] === x;
454
- } else if (typeof wo.filter_functions[i] === 'function'){
455
- // filter callback( exact cell content, parser normalized content, filter input value, column index )
456
- ff = wo.filter_functions[i](x, c.cache[k].normalized[j][i], v[i], i, $tr.eq(j));
457
- } else if (typeof wo.filter_functions[i][v[i]] === 'function'){
458
- // selector option function
459
- ff = wo.filter_functions[i][v[i]](x, c.cache[k].normalized[j][i], v[i], i, $tr.eq(j));
460
- }
461
- // Look for regex
462
- } else if (wo.filter_regex.regex.test(val)){
463
- rg = wo.filter_regex.regex.exec(val);
464
- try {
465
- ff = new RegExp(rg[1], rg[2]).test(xi);
466
- } catch (err){
467
- ff = false;
468
- }
469
- // Look for quotes or equals to get an exact match; ignore type since xi could be numeric
470
- /*jshint eqeqeq:false */
471
- } else if (val.replace(wo.filter_regex.exact, '') == xi){
472
- ff = true;
473
- // Look for a not match
474
- } else if (/^\!/.test(val)){
475
- val = val.replace('!','');
476
- s = xi.search($.trim(val));
477
- ff = val === '' ? true : !(wo.filter_startsWith ? s === 0 : s >= 0);
478
- // Look for operators >, >=, < or <=
479
- } else if (/^[<>]=?/.test(val)){
480
- s = fr = fmt(val.replace(wo.filter_regex.nondigit, '').replace(wo.filter_regex.operators,''), table);
481
- // parse filter value in case we're comparing numbers (dates)
482
- if (parsed[i] || c.parsers[i].type === 'numeric') {
483
- rg = c.parsers[i].format('' + val.replace(wo.filter_regex.operators,''), table, $ths.eq(i), i);
484
- s = (isNaN(s) && rg !== '' && !isNaN(rg)) ? rg : s;
485
- }
486
- // xi may be numeric - see issue #149;
487
- // check if c.cache[k].normalized[j] is defined, because sometimes j goes out of range? (numeric columns)
488
- rg = ( parsed[i] || c.parsers[i].type === 'numeric' ) && !isNaN(s) && c.cache[k].normalized[j] ? c.cache[k].normalized[j][i] :
489
- isNaN(xi) ? fmt(xi.replace(wo.filter_regex.nondigit, ''), table) : fmt(xi, table);
490
- if (/>/.test(val)) { ff = />=/.test(val) ? rg >= s : rg > s; }
491
- if (/</.test(val)) { ff = /<=/.test(val) ? rg <= s : rg < s; }
492
- if (!ff && fr === '') { ff = true; } // keep showing all rows if nothing follows the operator
493
- // Look for an AND or && operator (logical and)
494
- } else if (/\s+(AND|&&)\s+/g.test(v[i])) {
495
- s = val.split(/(?:\s+(?:and|&&)\s+)/g);
496
- ff = xi.search($.trim(s[0])) >= 0;
497
- r1 = s.length - 1;
498
- while (ff && r1) {
499
- ff = ff && xi.search($.trim(s[r1])) >= 0;
500
- r1--;
501
- }
502
- // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
503
- } else if (/\s+(-|to)\s+/.test(val)){
504
- s = val.split(/(?: - | to )/); // make sure the dash is for a range and not indicating a negative number
505
- r1 = fmt(s[0].replace(wo.filter_regex.nondigit, ''), table);
506
- r2 = fmt(s[1].replace(wo.filter_regex.nondigit, ''), table);
507
- // parse filter value in case we're comparing numbers (dates)
508
- if (parsed[i] || c.parsers[i].type === 'numeric') {
509
- rg = c.parsers[i].format('' + s[0], table, $ths.eq(i), i);
510
- r1 = (rg !== '' && !isNaN(rg)) ? rg : r1;
511
- rg = c.parsers[i].format('' + s[1], table, $ths.eq(i), i);
512
- r2 = (rg !== '' && !isNaN(rg)) ? rg : r2;
513
- }
514
- rg = ( parsed[i] || c.parsers[i].type === 'numeric' ) && !isNaN(r1) && !isNaN(r2) ? c.cache[k].normalized[j][i] :
515
- isNaN(xi) ? fmt(xi.replace(wo.filter_regex.nondigit, ''), table) : fmt(xi, table);
516
- if (r1 > r2) { ff = r1; r1 = r2; r2 = ff; } // swap
517
- ff = (rg >= r1 && rg <= r2) || (r1 === '' || r2 === '') ? true : false;
518
- // Look for wild card: ? = single, * = multiple, or | = logical OR
519
- } else if ( /[\?|\*]/.test(val) || /\s+OR\s+/.test(v[i]) ){
520
- s = val.replace(/\s+OR\s+/gi,"|");
521
- // look for an exact match with the "or" unless the "filter-match" class is found
522
- if (!$ths.filter('[data-column="' + i + '"]:last').hasClass('filter-match') && /\|/.test(s)) {
523
- s = '^(' + s + ')$';
524
- }
525
- ff = new RegExp( s.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(xi);
526
- // Look for match, and add child row data for matching
527
- } else {
528
- x = (xi + t).indexOf(val);
529
- ff = ( (!wo.filter_startsWith && x >= 0) || (wo.filter_startsWith && x === 0) );
530
- }
531
- r = (ff) ? (r ? true : false) : false;
532
- }
533
- }
534
- $tr[j].style.display = (r ? '' : 'none');
535
- $tr.eq(j)[r ? 'removeClass' : 'addClass'](wo.filter_filteredRow);
536
- if (cr.length) {
537
- if (c.pager && c.pager.countChildRows || wo.pager_countChildRows) {
538
- cr[r ? 'removeClass' : 'addClass'](wo.filter_filteredRow); // see issue #396
539
- }
540
- cr[r ? 'show' : 'hide']();
541
- }
542
- }
468
+ return result;
469
+ }
470
+ return null;
471
+ },
472
+ // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
473
+ range : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
474
+ if ( /\s+(-|to)\s+/.test(iFilter) ) {
475
+ var result,
476
+ c = table.config,
477
+ query = iFilter.split(/(?: - | to )/), // make sure the dash is for a range and not indicating a negative number
478
+ range1 = ts.formatFloat(query[0].replace(ts.filter.regex.nondigit, ''), table),
479
+ range2 = ts.formatFloat(query[1].replace(ts.filter.regex.nondigit, ''), table);
480
+ // parse filter value in case we're comparing numbers (dates)
481
+ if (parsed[index] || c.parsers[index].type === 'numeric') {
482
+ result = c.parsers[index].format('' + query[0], table, c.$headers.eq(index), index);
483
+ range1 = (result !== '' && !isNaN(result)) ? result : range1;
484
+ result = c.parsers[index].format('' + query[1], table, c.$headers.eq(index), index);
485
+ range2 = (result !== '' && !isNaN(result)) ? result : range2;
486
+ }
487
+ result = ( parsed[index] || c.parsers[index].type === 'numeric' ) && !isNaN(range1) && !isNaN(range2) ? cached :
488
+ isNaN(iExact) ? ts.formatFloat( iExact.replace(ts.filter.regex.nondigit, ''), table) :
489
+ ts.formatFloat( iExact, table );
490
+ if (range1 > range2) { result = range1; range1 = range2; range2 = result; } // swap
491
+ return (result >= range1 && result <= range2) || (range1 === '' || range2 === '');
492
+ }
493
+ return null;
494
+ },
495
+ // Look for wild card: ? = single, * = multiple, or | = logical OR
496
+ wild : function( filter, iFilter, exact, iExact, cached, index, table ) {
497
+ if ( /[\?|\*]/.test(iFilter) || /\s+OR\s+/.test(filter) ) {
498
+ var c = table.config,
499
+ query = iFilter.replace(/\s+OR\s+/gi,"|");
500
+ // look for an exact match with the "or" unless the "filter-match" class is found
501
+ if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) {
502
+ query = '^(' + query + ')$';
503
+ }
504
+ return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(iExact);
505
+ }
506
+ return null;
507
+ },
508
+ // fuzzy text search; modified from https://github.com/mattyork/fuzzy (MIT license)
509
+ fuzzy: function( filter, iFilter, exact, iExact ) {
510
+ if ( /^~/.test(iFilter) ) {
511
+ var indx,
512
+ patternIndx = 0,
513
+ len = iExact.length,
514
+ pattern = iFilter.slice(1);
515
+ for (indx = 0; indx < len; indx++) {
516
+ if (iExact[indx] === pattern[patternIndx]) {
517
+ patternIndx += 1;
543
518
  }
544
- ts.processTbody(table, $tb, false);
545
519
  }
546
- last = cv; // save last search
547
- $t.data('lastSearch', v);
548
- if (c.debug){
549
- ts.benchmark("Completed filter widget search", time);
520
+ if (patternIndx === pattern.length) {
521
+ return true;
550
522
  }
551
- $t.trigger('applyWidgets'); // make sure zebra widget is applied
552
- $t.trigger('filterEnd');
553
- },
554
- buildSelect = function(i, updating, onlyavail){
555
- var o, t, arry = [], currentVal;
556
- i = parseInt(i, 10);
557
- t = $ths.filter('[data-column="' + i + '"]:last');
558
- // t.data('placeholder') won't work in jQuery older than 1.4.3
559
- o = '<option value="">' + (t.data('placeholder') || t.attr('data-placeholder') || '') + '</option>';
560
- for (k = 0; k < b.length; k++ ){
561
- l = c.cache[k].row.length;
562
- // loop through the rows
563
- for (j = 0; j < l; j++){
564
- // check if has class filtered
565
- if (onlyavail && c.cache[k].row[j][0].className.match(wo.filter_filteredRow)) { continue; }
566
- // get non-normalized cell content
567
- if (wo.filter_useParsedData){
568
- arry.push( '' + c.cache[k].normalized[j][i] );
569
- } else {
570
- t = c.cache[k].row[j][0].cells[i];
571
- if (t){
572
- arry.push( $.trim(c.supportsTextContent ? t.textContent : $(t).text()) );
523
+ return false;
524
+ }
525
+ return null;
526
+ }
527
+ },
528
+ init: function(table, c, wo) {
529
+ var options, string, $header, column, filters, time;
530
+ if (c.debug) {
531
+ time = new Date();
532
+ }
533
+ c.$table.addClass('hasFilters');
534
+
535
+ ts.filter.regex.child = new RegExp(c.cssChildRow);
536
+ ts.filter.regex.filtered = new RegExp(wo.filter_filteredRow);
537
+
538
+ // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
539
+ if (wo.filter_columnFilters !== false && c.$headers.filter('.filter-false').length !== c.$headers.length) {
540
+ // build filter row
541
+ ts.filter.buildRow(table, c, wo);
542
+ }
543
+
544
+ c.$table.bind('addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join('.tsfilter '), function(event, filter) {
545
+ if ( !/(search|filterReset|filterEnd)/.test(event.type) ) {
546
+ event.stopPropagation();
547
+ ts.filter.buildDefault(table, true);
548
+ }
549
+ if (event.type === 'filterReset') {
550
+ ts.filter.searching(table, []);
551
+ }
552
+ if (event.type === 'filterEnd') {
553
+ ts.filter.buildDefault(table, true);
554
+ } else {
555
+ // send false argument to force a new search; otherwise if the filter hasn't changed, it will return
556
+ filter = event.type === 'search' ? filter : event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
557
+ ts.filter.searching(table, filter);
558
+ }
559
+ return false;
560
+ });
561
+ ts.filter.bindSearch( table, c.$table.find('input.tablesorter-filter') );
562
+
563
+ // reset button/link
564
+ if (wo.filter_reset) {
565
+ $(document).delegate(wo.filter_reset, 'click.tsfilter', function() {
566
+ ts.filter.searching(table, []);
567
+ });
568
+ }
569
+ if (wo.filter_functions) {
570
+ // column = column # (string)
571
+ for (column in wo.filter_functions) {
572
+ if (wo.filter_functions.hasOwnProperty(column) && typeof column === 'string') {
573
+ $header = c.$headers.filter('[data-column="' + column + '"]:last');
574
+ options = '';
575
+ if (wo.filter_functions[column] === true && !$header.hasClass('filter-false')) {
576
+ ts.filter.buildSelect(column);
577
+ } else if (typeof column === 'string' && !$header.hasClass('filter-false')) {
578
+ // add custom drop down list
579
+ for (string in wo.filter_functions[column]) {
580
+ if (typeof string === 'string') {
581
+ options += options === '' ?
582
+ '<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || '') + '</option>' : '';
583
+ options += '<option value="' + string + '">' + string + '</option>';
573
584
  }
574
585
  }
586
+ c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]').append(options);
575
587
  }
576
588
  }
589
+ }
590
+ }
591
+ // not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
592
+ // it would append the same options twice.
593
+ ts.filter.buildDefault(table, true);
577
594
 
578
- // get unique elements and sort the list
579
- // if $.tablesorter.sortText exists (not in the original tablesorter),
580
- // then natural sort the list otherwise use a basic sort
581
- arry = $.grep(arry, function(v, k){
582
- return $.inArray(v, arry) === k;
583
- });
584
- arry = (ts.sortNatural) ? arry.sort(function(a, b){ return ts.sortNatural(a, b); }) : arry.sort(true);
595
+ c.$table.find('select.tablesorter-filter').bind('change search', function(event, filter) {
596
+ ts.filter.checkFilters(table, filter);
597
+ });
585
598
 
586
- // Get curent filter value
587
- currentVal = $t.find('thead').find('select.tablesorter-filter[data-column="' + i + '"]').val();
599
+ if (wo.filter_hideFilters) {
600
+ ts.filter.hideFilters(table, c, wo);
601
+ }
588
602
 
589
- // build option list
590
- for (k = 0; k < arry.length; k++){
591
- t = arry[k].replace(/\"/g, "&quot;");
592
- // replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346
593
- o += arry[k] !== '' ? '<option value="' + t + '"' + (currentVal === t ? ' selected="selected"' : '') +'>' + arry[k] + '</option>' : '';
594
- }
595
- $t.find('thead').find('select.tablesorter-filter[data-column="' + i + '"]')[ updating ? 'html' : 'append' ](o);
596
- },
597
- buildDefault = function(updating){
598
- // build default select dropdown
599
- for (i = 0; i < cols; i++){
600
- t = $ths.filter('[data-column="' + i + '"]:last');
601
- // look for the filter-select class; build/update it if found
602
- if ((t.hasClass('filter-select') || wo.filter_functions && wo.filter_functions[i] === true) && !t.hasClass('filter-false')){
603
- if (!wo.filter_functions) { wo.filter_functions = {}; }
604
- wo.filter_functions[i] = true; // make sure this select gets processed by filter_functions
605
- buildSelect(i, updating, t.hasClass(wo.filter_onlyAvail));
603
+ // show processing icon
604
+ if (c.showProcessing) {
605
+ c.$table.bind('filterStart.tsfilter filterEnd.tsfilter', function(event, columns) {
606
+ // only add processing to certain columns to all columns
607
+ $header = (columns) ? c.$table.find('.' + ts.css.header).filter('[data-column]').filter(function() {
608
+ return columns[$(this).data('column')] !== '';
609
+ }) : '';
610
+ ts.isProcessing(table, event.type === 'filterStart', columns ? $header : '');
611
+ });
612
+ }
613
+ if (c.debug) {
614
+ ts.benchmark("Applying Filter widget", time);
615
+ }
616
+ // add default values
617
+ c.$table.bind('tablesorter-initialized pagerInitialized', function() {
618
+ filters = ts.filter.setDefaults(table, c, wo) || [];
619
+ if (filters.length) {
620
+ ts.setFilters(table, filters, true);
621
+ }
622
+ });
623
+ // filter widget initialized
624
+ wo.filter_Initialized = true;
625
+ c.$table.trigger('filterInit');
626
+ ts.filter.checkFilters(table);
627
+ },
628
+ setDefaults: function(table, c, wo){
629
+ var indx,
630
+ filters = [],
631
+ columns = c.columns;
632
+ for (indx = 0; indx < columns; indx++) {
633
+ filters[indx] = c.$headers.filter('[data-column="' + indx + '"]:last').attr(wo.filter_defaultAttrib) || filters[indx];
634
+ }
635
+ $(table).data('lastSearch', filters);
636
+ return filters;
637
+ },
638
+ buildRow: function(table, c, wo) {
639
+ var column, $header, buildSelect, disabled,
640
+ // c.columns defined in computeThIndexes()
641
+ columns = c.columns,
642
+ buildFilter = '<tr class="tablesorter-filter-row">';
643
+ for (column = 0; column < columns; column++) {
644
+ buildFilter += '<td></td>';
645
+ }
646
+ c.$filters = $(buildFilter += '</tr>').appendTo( c.$table.find('thead').eq(0) ).find('td');
647
+ // build each filter input
648
+ for (column = 0; column < columns; column++) {
649
+ disabled = false;
650
+ // assuming last cell of a column is the main column
651
+ $header = c.$headers.filter('[data-column="' + column + '"]:last');
652
+ buildSelect = (wo.filter_functions && wo.filter_functions[column] && typeof wo.filter_functions[column] !== 'function') ||
653
+ $header.hasClass('filter-select');
654
+ // get data from jQuery data, metadata, headers option or header class name
655
+ if (ts.getData) {
656
+ // get data from jQuery data, metadata, headers option or header class name
657
+ disabled = ts.getData($header[0], c.headers[column], 'filter') === 'false';
658
+ } else {
659
+ // only class names and header options - keep this for compatibility with tablesorter v2.0.5
660
+ disabled = (c.headers[column] && c.headers[column].hasOwnProperty('filter') && c.headers[column].filter === false) ||
661
+ $header.hasClass('filter-false');
662
+ }
663
+ if (buildSelect) {
664
+ buildFilter = $('<select>').appendTo( c.$filters.eq(column) );
665
+ } else {
666
+ if (wo.filter_formatter && $.isFunction(wo.filter_formatter[column])) {
667
+ buildFilter = wo.filter_formatter[column]( c.$filters.eq(column), column );
668
+ // no element returned, so lets go find it
669
+ if (buildFilter && buildFilter.length === 0) {
670
+ buildFilter = c.$filters.eq(column).children('input');
671
+ }
672
+ // element not in DOM, so lets attach it
673
+ if ( buildFilter && (buildFilter.parent().length === 0 ||
674
+ (buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column])) ) {
675
+ c.$filters.eq(column).append(buildFilter);
606
676
  }
607
- }
608
- },
609
- searching = function(filter){
610
- if (typeof filter === 'undefined' || filter === true){
611
- // delay filtering
612
- clearTimeout(timer);
613
- timer = setTimeout(function(){
614
- checkFilters(filter);
615
- }, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
616
677
  } else {
617
- // skip delay
618
- checkFilters(filter);
678
+ buildFilter = $('<input type="search">').appendTo( c.$filters.eq(column) );
679
+ }
680
+ if (buildFilter) {
681
+ buildFilter.attr('placeholder', $header.data('placeholder') || $header.attr('data-placeholder') || '');
619
682
  }
620
- };
621
- if (c.debug){
622
- time = new Date();
623
683
  }
624
- wo.filter_regex.child = new RegExp(c.cssChildRow);
625
- wo.filter_regex.filtered = new RegExp(wo.filter_filteredRow);
626
- // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
627
-
628
- if (wo.filter_columnFilters !== false && $ths.filter('.filter-false').length !== $ths.length){
629
- // build filter row
630
- t = '<tr class="tablesorter-filter-row">';
631
- for (i = 0; i < cols; i++){
632
- t += '<td></td>';
684
+ if (buildFilter) {
685
+ buildFilter.addClass('tablesorter-filter ' + wo.filter_cssFilter).attr('data-column', column);
686
+ if (disabled) {
687
+ buildFilter.addClass('disabled')[0].disabled = true; // disabled!
633
688
  }
634
- c.$filters = $(t += '</tr>').appendTo( $t.find('thead').eq(0) ).find('td');
635
- // build each filter input
636
- for (i = 0; i < cols; i++){
637
- dis = false;
638
- $th = $ths.filter('[data-column="' + i + '"]:last'); // assuming last cell of a column is the main column
639
- sel = (wo.filter_functions && wo.filter_functions[i] && typeof wo.filter_functions[i] !== 'function') || $th.hasClass('filter-select');
640
- // use header option - headers: { 1: { filter: false } } OR add class="filter-false"
641
- if (ts.getData){
642
- // get data from jQuery data, metadata, headers option or header class name
643
- dis = ts.getData($th[0], c.headers[i], 'filter') === 'false';
644
- } else {
645
- // only class names and header options - keep this for compatibility with tablesorter v2.0.5
646
- dis = (c.headers[i] && c.headers[i].hasOwnProperty('filter') && c.headers[i].filter === false) || $th.hasClass('filter-false');
647
- }
648
-
649
- if (sel){
650
- t = $('<select>').appendTo( c.$filters.eq(i) );
689
+ }
690
+ }
691
+ },
692
+ bindSearch: function(table, $el) {
693
+ table = $(table)[0];
694
+ var external, wo = table.config.widgetOptions;
695
+ $el.bind('keyup search', function(event, filter) {
696
+ // emulate what webkit does.... escape clears the filter
697
+ if (event.which === 27) {
698
+ this.value = '';
699
+ // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
700
+ } else if ( (typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch && this.value !== '') ||
701
+ ( event.type === 'keyup' && ( (event.which < 32 && event.which !== 8 && wo.filter_liveSearch === true && event.which !== 13) ||
702
+ ( event.which >= 37 && event.which <= 40 ) || (event.which !== 13 && wo.filter_liveSearch === false) ) ) ) {
703
+ return;
704
+ }
705
+ // external searches won't have a filter parameter, so grab the value
706
+ external = $(this).hasClass('tablesorter-filter') ? filter : [ $(this).val() ];
707
+ ts.filter.searching(table, filter, external);
708
+ });
709
+ },
710
+ checkFilters: function(table, filter) {
711
+ var c = table.config,
712
+ wo = c.widgetOptions,
713
+ filterArray = $.isArray(filter),
714
+ filters = (filterArray) ? filter : ts.getFilters(table),
715
+ combinedFilters = (filters || []).join(''); // combined filter values
716
+ // add filter array back into inputs
717
+ if (filterArray) {
718
+ ts.setFilters( table, filters );
719
+ }
720
+ if (wo.filter_hideFilters) {
721
+ // show/hide filter row as needed
722
+ c.$table.find('.tablesorter-filter-row').trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
723
+ }
724
+ // return if the last search is the same; but filter === false when updating the search
725
+ // see example-widget-filter.html filter toggle buttons
726
+ if (c.lastCombinedFilter === combinedFilters && filter !== false) { return; }
727
+ c.$table.trigger('filterStart', [filters]);
728
+ if (c.showProcessing) {
729
+ // give it time for the processing icon to kick in
730
+ setTimeout(function() {
731
+ ts.filter.findRows(table, filters, combinedFilters);
732
+ return false;
733
+ }, 30);
734
+ } else {
735
+ ts.filter.findRows(table, filters, combinedFilters);
736
+ return false;
737
+ }
738
+ },
739
+ hideFilters: function(table, c, wo) {
740
+ var $filterRow, $filterRow2, timer;
741
+ c.$table
742
+ .find('.tablesorter-filter-row')
743
+ .addClass('hideme')
744
+ .bind('mouseenter mouseleave', function(e) {
745
+ // save event object - http://bugs.jquery.com/ticket/12140
746
+ var event = e;
747
+ $filterRow = $(this);
748
+ clearTimeout(timer);
749
+ timer = setTimeout(function() {
750
+ if ( /enter|over/.test(event.type) ) {
751
+ $filterRow.removeClass('hideme');
651
752
  } else {
652
- if (wo.filter_formatter && $.isFunction(wo.filter_formatter[i])) {
653
- t = wo.filter_formatter[i]( c.$filters.eq(i), i );
654
- // no element returned, so lets go find it
655
- if (t && t.length === 0) { t = c.$filters.eq(i).children('input'); }
656
- // element not in DOM, so lets attach it
657
- if (t && (t.parent().length === 0 || (t.parent().length && t.parent()[0] !== c.$filters[i]))) {
658
- c.$filters.eq(i).append(t);
753
+ // don't hide if input has focus
754
+ // $(':focus') needs jQuery 1.6+
755
+ if ( $(document.activeElement).closest('tr')[0] !== $filterRow[0] ) {
756
+ // don't hide row if any filter has a value
757
+ if (ts.getFilters(table).join('') === '') {
758
+ $filterRow.addClass('hideme');
659
759
  }
660
- } else {
661
- t = $('<input type="search">').appendTo( c.$filters.eq(i) );
662
- }
663
- if (t) {
664
- t.attr('placeholder', $th.data('placeholder') || $th.attr('data-placeholder') || '');
665
- }
666
- }
667
- if (t) {
668
- t.addClass('tablesorter-filter ' + wo.filter_cssFilter).attr('data-column', i);
669
- if (dis) {
670
- t.addClass('disabled')[0].disabled = true; // disabled!
671
760
  }
672
761
  }
673
- }
674
- }
675
- $t
676
- .bind('addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join('.tsfilter '), function(e, filter){
677
- if (!/(search|filterReset|filterEnd)/.test(e.type)){
678
- e.stopPropagation();
679
- buildDefault(true);
680
- }
681
- if (e.type === 'filterReset') {
682
- searching([]);
683
- }
684
- if (e.type === 'filterEnd') {
685
- buildDefault(true);
686
- } else {
687
- // send false argument to force a new search; otherwise if the filter hasn't changed, it will return
688
- filter = e.type === 'search' ? filter : e.type === 'updateComplete' ? $t.data('lastSearch') : '';
689
- searching(filter);
690
- }
691
- return false;
762
+ }, 200);
692
763
  })
693
- .find('input.tablesorter-filter').bind('keyup search', function(e, filter){
694
- // emulate what webkit does.... escape clears the filter
695
- if (e.which === 27) {
696
- this.value = '';
697
- // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
698
- } else if ( (typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch && this.value !== '') || ( e.type === 'keyup' &&
699
- ( (e.which < 32 && e.which !== 8 && wo.filter_liveSearch === true && e.which !== 13) || (e.which >= 37 && e.which <=40) || (e.which !== 13 && wo.filter_liveSearch === false) ) ) ) {
700
- return;
701
- }
702
- searching(filter);
764
+ .find('input, select').bind('focus blur', function(e) {
765
+ $filterRow2 = $(this).closest('tr');
766
+ clearTimeout(timer);
767
+ var event = e;
768
+ timer = setTimeout(function() {
769
+ // don't hide row if any filter has a value
770
+ if (ts.getFilters(table).join('') === '') {
771
+ $filterRow2[ event.type === 'focus' ? 'removeClass' : 'addClass']('hideme');
772
+ }
773
+ }, 200);
703
774
  });
704
-
775
+ },
776
+ findRows: function(table, filters, combinedFilters) {
777
+ var cached, len, $rows, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
778
+ childRow, childRowText, exact, iExact, iFilter, lastSearch, matches, result,
779
+ searchFiltered, filterMatched, showRow, time,
780
+ c = table.config,
781
+ wo = c.widgetOptions,
782
+ columns = c.columns,
783
+ $tbodies = c.$tbodies,
784
+ // anyMatch really screws up with these types of filters
785
+ anyMatchNotAllowedTypes = [ 'range', 'operators' ],
705
786
  // parse columns after formatter, in case the class is added at that point
706
- parsed = $ths.map(function(i){
707
- return (ts.getData) ? ts.getData($ths.filter('[data-column="' + i + '"]:last'), c.headers[i], 'filter') === 'parsed' : $(this).hasClass('filter-parsed');
787
+ parsed = c.$headers.map(function(columnIndex) {
788
+ return (ts.getData) ?
789
+ ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), c.headers[columnIndex], 'filter') === 'parsed' :
790
+ $(this).hasClass('filter-parsed');
708
791
  }).get();
709
-
710
- // reset button/link
711
- if (wo.filter_reset){
712
- $(document).delegate(wo.filter_reset, 'click.tsfilter', function(){
713
- $t.trigger('filterReset');
792
+ if (c.debug) { time = new Date(); }
793
+ for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
794
+ if ($tbodies.eq(tbodyIndex).hasClass(ts.css.info)) { continue; } // ignore info blocks, issue #264
795
+ $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true);
796
+ $rows = $tbody.children('tr').not('.' + c.cssChildRow);
797
+ len = $rows.length;
798
+ if (combinedFilters === '' || wo.filter_serversideFiltering) {
799
+ $tbody.children().show().removeClass(wo.filter_filteredRow);
800
+ } else {
801
+ // optimize searching only through already filtered rows - see #313
802
+ searchFiltered = true;
803
+ lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
804
+ $.each(filters, function(indx, val) {
805
+ // check for changes from beginning of filter; but ignore if there is a logical "or" in the string
806
+ searchFiltered = (val || '').indexOf(lastSearch[indx] || '') === 0 && searchFiltered && !/(\s+or\s+|\|)/g.test(val || '');
714
807
  });
715
- }
716
- if (wo.filter_functions){
717
- // i = column # (string)
718
- for (col in wo.filter_functions){
719
- if (wo.filter_functions.hasOwnProperty(col) && typeof col === 'string'){
720
- t = $ths.filter('[data-column="' + col + '"]:last');
721
- ff = '';
722
- if (wo.filter_functions[col] === true && !t.hasClass('filter-false')){
723
- buildSelect(col);
724
- } else if (typeof col === 'string' && !t.hasClass('filter-false')){
725
- // add custom drop down list
726
- for (str in wo.filter_functions[col]){
727
- if (typeof str === 'string'){
728
- ff += ff === '' ? '<option value="">' + (t.data('placeholder') || t.attr('data-placeholder') || '') + '</option>' : '';
729
- ff += '<option value="' + str + '">' + str + '</option>';
730
- }
808
+ // can't search when all rows are hidden - this happens when looking for exact matches
809
+ if (searchFiltered && $rows.filter(':visible').length === 0) { searchFiltered = false; }
810
+ // loop through the rows
811
+ for (rowIndex = 0; rowIndex < len; rowIndex++) {
812
+ childRow = $rows[rowIndex].className;
813
+ // skip child rows & already filtered rows
814
+ if ( ts.filter.regex.child.test(childRow) || (searchFiltered && ts.filter.regex.filtered.test(childRow)) ) { continue; }
815
+ showRow = true;
816
+ // *** nextAll/nextUntil not supported by Zepto! ***
817
+ childRow = $rows.eq(rowIndex).nextUntil('tr:not(.' + c.cssChildRow + ')');
818
+ // so, if "table.config.widgetOptions.filter_childRows" is true and there is
819
+ // a match anywhere in the child row, then it will make the row visible
820
+ // checked here so the option can be changed dynamically
821
+ childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
822
+ childRowText = wo.filter_ignoreCase ? childRowText.toLocaleLowerCase() : childRowText;
823
+ $cells = $rows.eq(rowIndex).children('td');
824
+ for (columnIndex = 0; columnIndex < columns; columnIndex++) {
825
+ // ignore if filter is empty or disabled
826
+ if (filters[columnIndex] || wo.filter_anyMatch) {
827
+ cached = c.cache[tbodyIndex].normalized[rowIndex][columnIndex];
828
+ // check if column data should be from the cell or from parsed data
829
+ if (wo.filter_useParsedData || parsed[columnIndex]) {
830
+ exact = cached;
831
+ } else {
832
+ // using older or original tablesorter
833
+ exact = $.trim($cells.eq(columnIndex).text());
834
+ exact = c.sortLocaleCompare ? ts.replaceAccents(exact) : exact; // issue #405
731
835
  }
732
- $t.find('thead').find('select.tablesorter-filter[data-column="' + col + '"]').append(ff);
733
- }
734
- }
735
- }
736
- }
737
- // not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
738
- // it would append the same options twice.
739
- buildDefault(true);
836
+ iExact = !ts.filter.regex.type.test(typeof exact) && wo.filter_ignoreCase ? exact.toLocaleLowerCase() : exact;
837
+ result = showRow; // if showRow is true, show that row
740
838
 
741
- $t.find('select.tablesorter-filter').bind('change search', function(e, filter){
742
- checkFilters(filter);
743
- });
839
+ if (typeof filters[columnIndex] === "undefined" || filters[columnIndex] === null) {
840
+ filters[columnIndex] = wo.filter_anyMatch ? combinedFilters : filters[columnIndex];
841
+ }
744
842
 
745
- if (wo.filter_hideFilters){
746
- $t
747
- .find('.tablesorter-filter-row')
748
- .addClass('hideme')
749
- .bind('mouseenter mouseleave', function(e){
750
- // save event object - http://bugs.jquery.com/ticket/12140
751
- var all, evt = e;
752
- ft = $(this);
753
- clearTimeout(st);
754
- st = setTimeout(function(){
755
- if (/enter|over/.test(evt.type)){
756
- ft.removeClass('hideme');
843
+ // replace accents - see #357
844
+ filters[columnIndex] = c.sortLocaleCompare ? ts.replaceAccents(filters[columnIndex]) : filters[columnIndex];
845
+ // val = case insensitive, filters[columnIndex] = case sensitive
846
+ iFilter = wo.filter_ignoreCase ? filters[columnIndex].toLocaleLowerCase() : filters[columnIndex];
847
+ if (wo.filter_functions && wo.filter_functions[columnIndex]) {
848
+ if (wo.filter_functions[columnIndex] === true) {
849
+ // default selector; no "filter-select" class
850
+ result = (c.$headers.filter('[data-column="' + columnIndex + '"]:last').hasClass('filter-match')) ?
851
+ iExact.search(iFilter) >= 0 : filters[columnIndex] === exact;
852
+ } else if (typeof wo.filter_functions[columnIndex] === 'function') {
853
+ // filter callback( exact cell content, parser normalized content, filter input value, column index, jQuery row object )
854
+ result = wo.filter_functions[columnIndex](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
855
+ } else if (typeof wo.filter_functions[columnIndex][filters[columnIndex]] === 'function') {
856
+ // selector option function
857
+ result = wo.filter_functions[columnIndex][filters[columnIndex]](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
858
+ }
757
859
  } else {
758
- // don't hide if input has focus
759
- // $(':focus') needs jQuery 1.6+
760
- if ($(document.activeElement).closest('tr')[0] !== ft[0]){
761
- // get all filter values
762
- all = ts.getFilters(table).join('');
763
- // don't hide row if any filter has a value
764
- if (all === ''){
765
- ft.addClass('hideme');
860
+ filterMatched = null;
861
+ // cycle through the different filters
862
+ // filters return a boolean or null if nothing matches
863
+ $.each(ts.filter.types, function(type, typeFunction) {
864
+ if (!wo.filter_anyMatch || (wo.filter_anyMatch && anyMatchNotAllowedTypes.indexOf(type) < 0)) {
865
+ matches = typeFunction( filters[columnIndex], iFilter, exact, iExact, cached, columnIndex, table, wo, parsed );
866
+ if (matches !== null) {
867
+ filterMatched = matches;
868
+ return false;
869
+ }
766
870
  }
871
+ });
872
+ if (filterMatched !== null) {
873
+ result = filterMatched;
874
+ // Look for match, and add child row data for matching
875
+ } else {
876
+ exact = (iExact + childRowText).indexOf(iFilter);
877
+ result = ( (!wo.filter_startsWith && exact >= 0) || (wo.filter_startsWith && exact === 0) );
767
878
  }
768
879
  }
769
- }, 200);
770
- })
771
- .find('input, select').bind('focus blur', function(e){
772
- ft2 = $(this).closest('tr');
773
- clearTimeout(st);
774
- st = setTimeout(function(){
775
- // don't hide row if any filter has a value
776
- if (ts.getFilters(table).join('') === ''){
777
- ft2[ e.type === 'focus' ? 'removeClass' : 'addClass']('hideme');
880
+ if (wo.filter_anyMatch) {
881
+ showRow = result;
882
+ if (showRow){
883
+ break;
884
+ }
885
+ } else {
886
+ showRow = (result) ? showRow : false;
778
887
  }
779
- }, 200);
780
- });
888
+ }
889
+ }
890
+ $rows[rowIndex].style.display = (showRow ? '' : 'none');
891
+ $rows.eq(rowIndex)[showRow ? 'removeClass' : 'addClass'](wo.filter_filteredRow);
892
+ if (childRow.length) {
893
+ if (c.pager && c.pager.countChildRows || wo.pager_countChildRows) {
894
+ childRow[showRow ? 'removeClass' : 'addClass'](wo.filter_filteredRow); // see issue #396
895
+ }
896
+ childRow.toggle(showRow);
897
+ }
898
+ }
781
899
  }
782
-
783
- // show processing icon
784
- if (c.showProcessing) {
785
- $t.bind('filterStart.tsfilter filterEnd.tsfilter', function(e, v) {
786
- var fc = (v) ? $t.find('.' + ts.css.header).filter('[data-column]').filter(function(){
787
- return v[$(this).data('column')] !== '';
788
- }) : '';
789
- ts.isProcessing($t[0], e.type === 'filterStart', v ? fc : '');
790
- });
900
+ ts.processTbody(table, $tbody, false);
901
+ }
902
+ c.lastCombinedFilter = combinedFilters; // save last search
903
+ c.lastSearch = filters;
904
+ c.$table.data('lastSearch', filters);
905
+ if (c.debug) {
906
+ ts.benchmark("Completed filter widget search", time);
907
+ }
908
+ c.$table.trigger('applyWidgets'); // make sure zebra widget is applied
909
+ c.$table.trigger('filterEnd');
910
+ },
911
+ buildSelect: function(table, column, updating, onlyavail) {
912
+ column = parseInt(column, 10);
913
+ var indx, rowIndex, tbodyIndex, len, currentValue, txt,
914
+ c = table.config,
915
+ wo = c.widgetOptions,
916
+ $tbodies = c.$tbodies,
917
+ arry = [],
918
+ node = c.$headers.filter('[data-column="' + column + '"]:last'),
919
+ // t.data('placeholder') won't work in jQuery older than 1.4.3
920
+ options = '<option value="">' + ( node.data('placeholder') || node.attr('data-placeholder') || '' ) + '</option>';
921
+ for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
922
+ len = c.cache[tbodyIndex].row.length;
923
+ // loop through the rows
924
+ for (rowIndex = 0; rowIndex < len; rowIndex++) {
925
+ // check if has class filtered
926
+ if (onlyavail && c.cache[tbodyIndex].row[rowIndex][0].className.match(wo.filter_filteredRow)) { continue; }
927
+ // get non-normalized cell content
928
+ if (wo.filter_useParsedData) {
929
+ arry.push( '' + c.cache[tbodyIndex].normalized[rowIndex][column] );
930
+ } else {
931
+ node = c.cache[tbodyIndex].row[rowIndex][0].cells[column];
932
+ if (node) {
933
+ arry.push( $.trim( node.textContent || node.innerText || $(node).text() ) );
934
+ }
935
+ }
791
936
  }
937
+ }
938
+ // get unique elements and sort the list
939
+ // if $.tablesorter.sortText exists (not in the original tablesorter),
940
+ // then natural sort the list otherwise use a basic sort
941
+ arry = $.grep(arry, function(value, indx) {
942
+ return $.inArray(value, arry) === indx;
943
+ });
944
+ arry = (ts.sortNatural) ? arry.sort(function(a, b) { return ts.sortNatural(a, b); }) : arry.sort(true);
792
945
 
793
- if (c.debug){
794
- ts.benchmark("Applying Filter widget", time);
946
+ // Get curent filter value
947
+ currentValue = c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]').val();
948
+
949
+ // build option list
950
+ for (indx = 0; indx < arry.length; indx++) {
951
+ txt = arry[indx].replace(/\"/g, "&quot;");
952
+ // replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346
953
+ options += arry[indx] !== '' ? '<option value="' + txt + '"' + (currentValue === txt ? ' selected="selected"' : '') +
954
+ '>' + arry[indx] + '</option>' : '';
955
+ }
956
+ c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]')[ updating ? 'html' : 'append' ](options);
957
+ },
958
+ buildDefault: function(table, updating) {
959
+ var columnIndex, $header,
960
+ c = table.config,
961
+ wo = c.widgetOptions,
962
+ columns = c.columns;
963
+ // build default select dropdown
964
+ for (columnIndex = 0; columnIndex < columns; columnIndex++) {
965
+ $header = c.$headers.filter('[data-column="' + columnIndex + '"]:last');
966
+ // look for the filter-select class; build/update it if found
967
+ if (($header.hasClass('filter-select') || wo.filter_functions && wo.filter_functions[columnIndex] === true) &&
968
+ !$header.hasClass('filter-false')) {
969
+ if (!wo.filter_functions) { wo.filter_functions = {}; }
970
+ wo.filter_functions[columnIndex] = true; // make sure this select gets processed by filter_functions
971
+ ts.filter.buildSelect(table, columnIndex, updating, $header.hasClass(wo.filter_onlyAvail));
795
972
  }
796
- // add default values
797
- $t.bind('tablesorter-initialized', function(){
798
- ff = ts.getFilters(table);
799
- // ff is undefined when filter_columnFilters = false
800
- if (ff) {
801
- for (i = 0; i < ff.length; i++) {
802
- ff[i] = $ths.filter('[data-column="' + i + '"]:last').attr(wo.filter_defaultAttrib) || ff[i];
803
- }
804
- ts.setFilters(table, ff, true);
805
- }
806
- });
807
- // filter widget initialized
808
- $t.trigger('filterInit');
809
- checkFilters();
810
973
  }
811
974
  },
812
- remove: function(table, c, wo){
813
- var k, $tb,
814
- $t = c.$table,
815
- b = c.$tbodies;
816
- $t
817
- .removeClass('hasFilters')
818
- // add .tsfilter namespace to all BUT search
819
- .unbind('addRows updateCell update updateComplete appendCache search filterStart filterEnd '.split(' ').join('.tsfilter '))
820
- .find('.tablesorter-filter-row').remove();
821
- for (k = 0; k < b.length; k++ ){
822
- $tb = ts.processTbody(table, b.eq(k), true); // remove tbody
823
- $tb.children().removeClass(wo.filter_filteredRow).show();
824
- ts.processTbody(table, $tb, false); // restore tbody
975
+ searching: function(table, filter, external) {
976
+ if (typeof filter === 'undefined' || filter === true || external) {
977
+ var wo = table.config.widgetOptions;
978
+ // delay filtering
979
+ clearTimeout(wo.searchTimer);
980
+ wo.searchTimer = setTimeout(function() {
981
+ ts.filter.checkFilters(table, external || filter);
982
+ }, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
983
+ } else {
984
+ // skip delay
985
+ ts.filter.checkFilters(table, filter);
825
986
  }
826
- if (wo.filter_reset) { $(document).undelegate(wo.filter_reset, 'click.tsfilter'); }
827
987
  }
828
- });
988
+ };
989
+
829
990
  ts.getFilters = function(table) {
830
991
  var c = table ? $(table)[0].config : {};
831
- if (c && c.widgetOptions && !c.widgetOptions.filter_columnFilters) { return $(table).data('lastSearch'); }
832
- return c && c.$filters ? c.$filters.map(function(i, el) {
992
+ if (c && c.widgetOptions && !c.widgetOptions.filter_columnFilters) {
993
+ // no filter row
994
+ return $(table).data('lastSearch');
995
+ }
996
+ return c && c.$filters ? c.$filters.map(function(indx, el) {
833
997
  return $(el).find('.tablesorter-filter').val() || '';
834
998
  }).get() || [] : false;
835
999
  };
1000
+
836
1001
  ts.setFilters = function(table, filter, apply) {
837
- var $t = $(table),
838
- c = $t.length ? $t[0].config : {},
839
- valid = c && c.$filters ? c.$filters.each(function(i, el) {
840
- $(el).find('.tablesorter-filter').val(filter[i] || '');
841
- }) || false : false;
842
- if (apply) { $t.trigger('search', [filter, false]); }
1002
+ var $table = $(table),
1003
+ c = $table.length ? $table[0].config : {},
1004
+ valid = c && c.$filters ? c.$filters.each(function(indx, el) {
1005
+ $(el).find('.tablesorter-filter').val(filter[indx] || '');
1006
+ }).trigger('change.tsfilter') || false : false;
1007
+ if (apply) { $table.trigger('search', [filter, false]); }
843
1008
  return !!valid;
844
1009
  };
845
1010