jquery-tablesorter 1.14.1 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/lib/jquery-tablesorter/version.rb +1 -1
  4. data/vendor/assets/javascripts/jquery-tablesorter.js +1 -1
  5. data/vendor/assets/javascripts/jquery-tablesorter/addons/pager/jquery.tablesorter.pager.js +5 -9
  6. data/vendor/assets/javascripts/jquery-tablesorter/extras/jquery.dragtable.mod.js +1 -4
  7. data/vendor/assets/javascripts/jquery-tablesorter/{jquery.metadata.js → extras/jquery.metadata.js} +1 -0
  8. data/vendor/assets/javascripts/jquery-tablesorter/extras/jquery.quicksearch.js +8 -4
  9. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +116 -107
  10. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.widgets.js +232 -171
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-file-type.js +1 -1
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +1 -1
  13. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-chart.js +2 -3
  14. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columns.js +78 -0
  15. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-cssStickyHeaders.js +14 -12
  16. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +4 -4
  17. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter-formatter-html5.js +429 -0
  18. data/vendor/assets/javascripts/jquery-tablesorter/{jquery.tablesorter.widgets-filter-formatter.js → widgets/widget-filter-formatter-jui.js} +1 -381
  19. data/vendor/assets/javascripts/jquery-tablesorter/{jquery.tablesorter.widgets-filter-formatter-select2.js → widgets/widget-filter-formatter-select2.js} +0 -0
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-filter.js +1307 -0
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-formatter.js +3 -3
  22. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +4 -4
  23. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-math.js +3 -3
  24. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +4 -8
  25. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-resizable.js +176 -0
  26. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-saveSort.js +68 -0
  27. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-staticRow.js +3 -3
  28. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-stickyHeaders.js +269 -0
  29. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-storage.js +76 -0
  30. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-uitheme.js +184 -0
  31. data/vendor/assets/stylesheets/jquery-tablesorter/theme.black-ice.css +1 -1
  32. data/vendor/assets/stylesheets/jquery-tablesorter/theme.blue.css +1 -1
  33. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dark.css +1 -1
  34. data/vendor/assets/stylesheets/jquery-tablesorter/theme.default.css +1 -1
  35. data/vendor/assets/stylesheets/jquery-tablesorter/theme.dropbox.css +1 -1
  36. data/vendor/assets/stylesheets/jquery-tablesorter/theme.green.css +1 -1
  37. data/vendor/assets/stylesheets/jquery-tablesorter/theme.grey.css +1 -1
  38. data/vendor/assets/stylesheets/jquery-tablesorter/theme.ice.css +1 -1
  39. data/vendor/assets/stylesheets/jquery-tablesorter/theme.jui.css +1 -1
  40. data/vendor/assets/stylesheets/jquery-tablesorter/theme.metro-dark.css +1 -1
  41. metadata +15 -7
@@ -10,11 +10,11 @@
10
10
 
11
11
  ts.formatter = {
12
12
  init : function( c ) {
13
- var events = $.trim( c.widgetOptions.formatter_event ) +
13
+ var events = c.widgetOptions.formatter_event +
14
14
  ' pagerComplete updateComplete '.split(' ').join('.tsformatter ');
15
15
  c.$table
16
- .off( $.trim(events) )
17
- .on( $.trim(events), function() {
16
+ .off( events.replace(/\s+/g, ' ') )
17
+ .on( events, function() {
18
18
  ts.formatter.setup( c );
19
19
  });
20
20
  ts.formatter.setup( c );
@@ -54,7 +54,7 @@ ts.grouping = {
54
54
 
55
55
  update : function(table, c, wo){
56
56
  if ($.isEmptyObject(c.cache)) { return; }
57
- var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, cache, saveName, direction,
57
+ var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, norm_rows, saveName, direction,
58
58
  lang = wo.grouping_language,
59
59
  group = '',
60
60
  savedGroup = false,
@@ -89,15 +89,15 @@ ts.grouping = {
89
89
  }
90
90
  }
91
91
  for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) {
92
- cache = c.cache[tbodyIndex].normalized;
92
+ norm_rows = c.cache[tbodyIndex].normalized;
93
93
  group = ''; // clear grouping across tbodies
94
94
  $rows = c.$tbodies.eq(tbodyIndex).children('tr').not('.' + c.cssChildRow);
95
95
  for (rowIndex = 0; rowIndex < $rows.length; rowIndex++) {
96
96
  if ( $rows.eq(rowIndex).is(':visible') ) {
97
97
  // fixes #438
98
98
  if (ts.grouping.types[grouping[1]]) {
99
- currentGroup = cache[rowIndex] ?
100
- ts.grouping.types[grouping[1]]( c, c.$headers.filter('[data-column="' + column + '"]:last'), cache[rowIndex][column], /date/.test(groupClass) ?
99
+ currentGroup = norm_rows[rowIndex] ?
100
+ ts.grouping.types[grouping[1]]( c, c.$headers.filter('[data-column="' + column + '"]:last'), norm_rows[rowIndex][column], /date/.test(groupClass) ?
101
101
  grouping[2] : parseInt(grouping[2] || 1, 10) || 1, group, lang ) : currentGroup;
102
102
  if (group !== currentGroup) {
103
103
  group = currentGroup;
@@ -393,8 +393,8 @@
393
393
  },
394
394
  init : function(table, thisWidget, c, wo){
395
395
  c.$table
396
- .off( $.trim(math.events) + ' ' + $.trim('updateComplete.tsmath ' + wo.math_event) )
397
- .on( $.trim(math.events) + ' ' + wo.math_event, function(e){
396
+ .off( (math.events + ' updateComplete.tsmath ' + wo.math_event).replace(/\s+/g, ' ') )
397
+ .on(math.events + ' ' + wo.math_event, function(e){
398
398
  var init = e.type === 'tablesorter-initialized';
399
399
  if (e.type === 'updateAll') {
400
400
  // redo data-column indexes in case columns were rearranged
@@ -415,7 +415,7 @@
415
415
  remove: function(table, c, wo, refreshing){
416
416
  if (refreshing) { return; }
417
417
  $(table)
418
- .off( $.trim(math.events) + ' ' + $.trim('updateComplete.tsmath ' + wo.math_event) )
418
+ .off( (math.events + ' updateComplete.tsmath ' + wo.math_event).replace(/\s+/g, ' ') )
419
419
  .find('[data-' + wo.math_data + ']').empty();
420
420
  }
421
421
  });
@@ -167,7 +167,7 @@ tsp = ts.pager = {
167
167
  p.initializing = true;
168
168
  if (wo.pager_savePages && ts.storage) {
169
169
  t = ts.storage(table, wo.pager_storageKey) || {}; // fixes #387
170
- p.page = ( isNaN(t.page) ? p.page : t.page ) || p.setPage || 1;
170
+ p.page = ( isNaN(t.page) ? p.page : t.page ) || p.setPage || 0;
171
171
  p.size = ( isNaN(t.size) ? p.size : t.size ) || p.setSize || 10;
172
172
  $.data(table, 'pagerLastSize', p.size);
173
173
  }
@@ -371,8 +371,7 @@ tsp = ts.pager = {
371
371
  },
372
372
 
373
373
  calcFilters: function(table, c) {
374
- var tbodyIndex,
375
- wo = c.widgetOptions,
374
+ var wo = c.widgetOptions,
376
375
  p = c.pager,
377
376
  hasFilters = c.$table.hasClass('hasFilters');
378
377
  if (hasFilters && !wo.pager_ajaxUrl) {
@@ -380,10 +379,8 @@ tsp = ts.pager = {
380
379
  // delayInit: true so nothing is in the cache
381
380
  p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not( wo.pager_countChildRows ? '' : '.' + c.cssChildRow ).length;
382
381
  } else {
383
- // just in case the pager tbody isn't the first tbody
384
- tbodyIndex = c.$table.children('tbody').index( c.$tbodies.eq(0) );
385
382
  p.filteredRows = 0;
386
- $.each(c.cache[tbodyIndex].normalized, function(i, el) {
383
+ $.each(c.cache[0].normalized, function(i, el) {
387
384
  p.filteredRows += p.regexRows.test(el[c.columns].$row[0].className) ? 0 : 1;
388
385
  });
389
386
  }
@@ -898,8 +895,7 @@ tsp = ts.pager = {
898
895
  if ( !$.isEmptyObject(table.config.cache) ) {
899
896
  var i,
900
897
  rows = [],
901
- tbodyIndex = c.$table.children('tbody').index( c.$tbodies.eq(0) ),
902
- n = table.config.cache[tbodyIndex].normalized;
898
+ n = table.config.cache[0].normalized;
903
899
  p.totalRows = n.length;
904
900
  for (i = 0; i < p.totalRows; i++) {
905
901
  rows.push(n[i][c.columns].$row);
@@ -0,0 +1,176 @@
1
+ /*! Widget: resizable */
2
+ ;(function ($, window) {
3
+ 'use strict';
4
+ var ts = $.tablesorter = $.tablesorter || {};
5
+
6
+ $.extend(ts.css, {
7
+ resizer : 'tablesorter-resizer' // resizable
8
+ });
9
+
10
+ // this widget saves the column widths if
11
+ // $.tablesorter.storage function is included
12
+ // **************************
13
+ ts.addWidget({
14
+ id: "resizable",
15
+ priority: 40,
16
+ options: {
17
+ resizable : true,
18
+ resizable_addLastColumn : false,
19
+ resizable_widths : [],
20
+ resizable_throttle : false // set to true (5ms) or any number 0-10 range
21
+ },
22
+ format: function(table, c, wo) {
23
+ if (c.$table.hasClass('hasResizable')) { return; }
24
+ c.$table.addClass('hasResizable');
25
+ ts.resizableReset(table, true); // set default widths
26
+ var $rows, $columns, $column, column, timer,
27
+ storedSizes = {},
28
+ $table = c.$table,
29
+ $wrap = $table.parent(),
30
+ overflow = $table.parent().css('overflow') === 'auto',
31
+ mouseXPosition = 0,
32
+ $target = null,
33
+ $next = null,
34
+ fullWidth = Math.abs($table.parent().width() - $table.width()) < 20,
35
+ mouseMove = function(event){
36
+ if (mouseXPosition === 0 || !$target) { return; }
37
+ // resize columns
38
+ var leftEdge = event.pageX - mouseXPosition,
39
+ targetWidth = $target.width();
40
+ $target.width( targetWidth + leftEdge );
41
+ if ($target.width() !== targetWidth && fullWidth) {
42
+ $next.width( $next.width() - leftEdge );
43
+ } else if (overflow) {
44
+ $table.width(function(i, w){
45
+ return w + leftEdge;
46
+ });
47
+ if (!$next.length) {
48
+ // if expanding right-most column, scroll the wrapper
49
+ $wrap[0].scrollLeft = $table.width();
50
+ }
51
+ }
52
+ mouseXPosition = event.pageX;
53
+ },
54
+ stopResize = function() {
55
+ if (ts.storage && $target && $next) {
56
+ storedSizes = {};
57
+ storedSizes[$target.index()] = $target.width();
58
+ storedSizes[$next.index()] = $next.width();
59
+ $target.width( storedSizes[$target.index()] );
60
+ $next.width( storedSizes[$next.index()] );
61
+ if (wo.resizable !== false) {
62
+ // save all column widths
63
+ ts.storage(table, 'tablesorter-resizable', c.$headers.map(function(){ return $(this).width(); }).get() );
64
+ }
65
+ }
66
+ mouseXPosition = 0;
67
+ $target = $next = null;
68
+ $(window).trigger('resize'); // will update stickyHeaders, just in case
69
+ };
70
+ storedSizes = (ts.storage && wo.resizable !== false) ? ts.storage(table, 'tablesorter-resizable') : {};
71
+ // process only if table ID or url match
72
+ if (storedSizes) {
73
+ for (column in storedSizes) {
74
+ if (!isNaN(column) && column < c.$headers.length) {
75
+ c.$headers.eq(column).width(storedSizes[column]); // set saved resizable widths
76
+ }
77
+ }
78
+ }
79
+ $rows = $table.children('thead:first').children('tr');
80
+ // add resizable-false class name to headers (across rows as needed)
81
+ $rows.children().each(function() {
82
+ var canResize,
83
+ $column = $(this);
84
+ column = $column.attr('data-column');
85
+ canResize = ts.getData( $column, ts.getColumnData( table, c.headers, column ), 'resizable') === "false";
86
+ $rows.children().filter('[data-column="' + column + '"]')[canResize ? 'addClass' : 'removeClass']('resizable-false');
87
+ });
88
+ // add wrapper inside each cell to allow for positioning of the resizable target block
89
+ $rows.each(function() {
90
+ $column = $(this).children().not('.resizable-false');
91
+ if (!$(this).find('.' + ts.css.wrapper).length) {
92
+ // Firefox needs this inner div to position the resizer correctly
93
+ $column.wrapInner('<div class="' + ts.css.wrapper + '" style="position:relative;height:100%;width:100%"></div>');
94
+ }
95
+ // don't include the last column of the row
96
+ if (!wo.resizable_addLastColumn) { $column = $column.slice(0,-1); }
97
+ $columns = $columns ? $columns.add($column) : $column;
98
+ });
99
+ $columns
100
+ .each(function() {
101
+ var $column = $(this),
102
+ padding = parseInt($column.css('padding-right'), 10) + 10; // 10 is 1/2 of the 20px wide resizer
103
+ $column
104
+ .find('.' + ts.css.wrapper)
105
+ .append('<div class="' + ts.css.resizer + '" style="cursor:w-resize;position:absolute;z-index:1;right:-' +
106
+ padding + 'px;top:0;height:100%;width:20px;"></div>');
107
+ })
108
+ .find('.' + ts.css.resizer)
109
+ .bind('mousedown', function(event) {
110
+ // save header cell and mouse position
111
+ $target = $(event.target).closest('th');
112
+ var $header = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]');
113
+ if ($header.length > 1) { $target = $target.add($header); }
114
+ // if table is not as wide as it's parent, then resize the table
115
+ $next = event.shiftKey ? $target.parent().find('th').not('.resizable-false').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0);
116
+ mouseXPosition = event.pageX;
117
+ });
118
+ $(document)
119
+ .bind('mousemove.tsresize', function(event) {
120
+ // ignore mousemove if no mousedown
121
+ if (mouseXPosition === 0 || !$target) { return; }
122
+ if (wo.resizable_throttle) {
123
+ clearTimeout(timer);
124
+ timer = setTimeout(function(){
125
+ mouseMove(event);
126
+ }, isNaN(wo.resizable_throttle) ? 5 : wo.resizable_throttle );
127
+ } else {
128
+ mouseMove(event);
129
+ }
130
+ })
131
+ .bind('mouseup.tsresize', function() {
132
+ stopResize();
133
+ });
134
+
135
+ // right click to reset columns to default widths
136
+ $table.find('thead:first').bind('contextmenu.tsresize', function() {
137
+ ts.resizableReset(table);
138
+ // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset
139
+ var allowClick = $.isEmptyObject ? $.isEmptyObject(storedSizes) : true;
140
+ storedSizes = {};
141
+ return allowClick;
142
+ });
143
+ },
144
+ remove: function(table, c) {
145
+ c.$table
146
+ .removeClass('hasResizable')
147
+ .children('thead')
148
+ .unbind('mouseup.tsresize mouseleave.tsresize contextmenu.tsresize')
149
+ .children('tr').children()
150
+ .unbind('mousemove.tsresize mouseup.tsresize')
151
+ // don't remove "tablesorter-wrapper" as uitheme uses it too
152
+ .find('.' + ts.css.resizer).remove();
153
+ ts.resizableReset(table);
154
+ }
155
+ });
156
+ ts.resizableReset = function(table, nosave) {
157
+ $(table).each(function(){
158
+ var $t,
159
+ c = this.config,
160
+ wo = c && c.widgetOptions;
161
+ if (table && c) {
162
+ c.$headers.each(function(i){
163
+ $t = $(this);
164
+ if (wo.resizable_widths && wo.resizable_widths[i]) {
165
+ $t.css('width', wo.resizable_widths[i]);
166
+ } else if (!$t.hasClass('resizable-false')) {
167
+ // don't clear the width of any column that is not resizable
168
+ $t.css('width','');
169
+ }
170
+ });
171
+ if (ts.storage && !nosave) { ts.storage(this, 'tablesorter-resizable', {}); }
172
+ }
173
+ });
174
+ };
175
+
176
+ })(jQuery, window);
@@ -0,0 +1,68 @@
1
+ /*! Widget: saveSort */
2
+ ;(function ($) {
3
+ 'use strict';
4
+ var ts = $.tablesorter = $.tablesorter || {};
5
+
6
+ // this widget saves the last sort only if the
7
+ // saveSort widget option is true AND the
8
+ // $.tablesorter.storage function is included
9
+ // **************************
10
+ ts.addWidget({
11
+ id: 'saveSort',
12
+ priority: 20,
13
+ options: {
14
+ saveSort : true
15
+ },
16
+ init: function(table, thisWidget, c, wo) {
17
+ // run widget format before all other widgets are applied to the table
18
+ thisWidget.format(table, c, wo, true);
19
+ },
20
+ format: function(table, c, wo, init) {
21
+ var stored, time,
22
+ $table = c.$table,
23
+ saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true
24
+ sortList = { "sortList" : c.sortList };
25
+ if (c.debug) {
26
+ time = new Date();
27
+ }
28
+ if ($table.hasClass('hasSaveSort')) {
29
+ if (saveSort && table.hasInitialized && ts.storage) {
30
+ ts.storage( table, 'tablesorter-savesort', sortList );
31
+ if (c.debug) {
32
+ ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time);
33
+ }
34
+ }
35
+ } else {
36
+ // set table sort on initial run of the widget
37
+ $table.addClass('hasSaveSort');
38
+ sortList = '';
39
+ // get data
40
+ if (ts.storage) {
41
+ stored = ts.storage( table, 'tablesorter-savesort' );
42
+ sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : '';
43
+ if (c.debug) {
44
+ ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time);
45
+ }
46
+ $table.bind('saveSortReset', function(event) {
47
+ event.stopPropagation();
48
+ ts.storage( table, 'tablesorter-savesort', '' );
49
+ });
50
+ }
51
+ // init is true when widget init is run, this will run this widget before all other widgets have initialized
52
+ // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice.
53
+ if (init && sortList && sortList.length > 0) {
54
+ c.sortList = sortList;
55
+ } else if (table.hasInitialized && sortList && sortList.length > 0) {
56
+ // update sort change
57
+ $table.trigger('sorton', [sortList]);
58
+ }
59
+ }
60
+ },
61
+ remove: function(table, c) {
62
+ c.$table.removeClass('hasSaveSort');
63
+ // clear storage
64
+ if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); }
65
+ }
66
+ });
67
+
68
+ })(jQuery);
@@ -59,8 +59,8 @@ ts.addWidget({
59
59
  addIndexes(table);
60
60
  // refresh static rows after updates
61
61
  c.$table
62
- .unbind( $.trim('updateComplete.tsstaticrows ' + wo.staticRow_event) )
63
- .bind( $.trim('updateComplete.tsstaticrows ' + wo.staticRow_event), function(){
62
+ .unbind( ('updateComplete.tsstaticrows ' + wo.staticRow_event).replace(/\s+/g, ' ') )
63
+ .bind('updateComplete.tsstaticrows ' + wo.staticRow_event, function(){
64
64
  addIndexes(table);
65
65
  c.$table.trigger('applyWidgets');
66
66
  });
@@ -113,7 +113,7 @@ ts.addWidget({
113
113
  },
114
114
 
115
115
  remove : function(table, c, wo){
116
- c.$table.unbind( $.trim('updateComplete.tsstaticrows ' + wo.staticRow_event) );
116
+ c.$table.unbind( ('updateComplete.tsstaticrows ' + wo.staticRow_event).replace(/\s+/g, ' ') );
117
117
  }
118
118
 
119
119
  });
@@ -0,0 +1,269 @@
1
+ /*! Widget: stickyHeaders */
2
+ ;(function ($, window) {
3
+ 'use strict';
4
+ var ts = $.tablesorter = $.tablesorter || {};
5
+
6
+ $.extend(ts.css, {
7
+ sticky : 'tablesorter-stickyHeader', // stickyHeader
8
+ stickyVis : 'tablesorter-sticky-visible',
9
+ stickyWrap: 'tablesorter-sticky-wrapper'
10
+ });
11
+
12
+ // Add a resize event to table headers
13
+ ts.addHeaderResizeEvent = function(table, disable, settings) {
14
+ table = $(table)[0]; // make sure we're using a dom element
15
+ var headers,
16
+ defaults = {
17
+ timer : 250
18
+ },
19
+ options = $.extend({}, defaults, settings),
20
+ c = table.config,
21
+ wo = c.widgetOptions,
22
+ checkSizes = function(triggerEvent) {
23
+ wo.resize_flag = true;
24
+ headers = [];
25
+ c.$headers.each(function() {
26
+ var $header = $(this),
27
+ sizes = $header.data('savedSizes') || [0,0], // fixes #394
28
+ width = this.offsetWidth,
29
+ height = this.offsetHeight;
30
+ if (width !== sizes[0] || height !== sizes[1]) {
31
+ $header.data('savedSizes', [ width, height ]);
32
+ headers.push(this);
33
+ }
34
+ });
35
+ if (headers.length && triggerEvent !== false) {
36
+ c.$table.trigger('resize', [ headers ]);
37
+ }
38
+ wo.resize_flag = false;
39
+ };
40
+ checkSizes(false);
41
+ clearInterval(wo.resize_timer);
42
+ if (disable) {
43
+ wo.resize_flag = false;
44
+ return false;
45
+ }
46
+ wo.resize_timer = setInterval(function() {
47
+ if (wo.resize_flag) { return; }
48
+ checkSizes();
49
+ }, options.timer);
50
+ };
51
+
52
+ // Sticky headers based on this awesome article:
53
+ // http://css-tricks.com/13465-persistent-headers/
54
+ // and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech
55
+ // **************************
56
+ ts.addWidget({
57
+ id: "stickyHeaders",
58
+ priority: 60, // sticky widget must be initialized after the filter widget!
59
+ options: {
60
+ stickyHeaders : '', // extra class name added to the sticky header row
61
+ stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to
62
+ stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window)
63
+ stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window)
64
+ stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element
65
+ stickyHeaders_filteredToTop: true, // scroll table top into view after filtering
66
+ stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists
67
+ stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers
68
+ stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header
69
+ stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs
70
+ },
71
+ format: function(table, c, wo) {
72
+ // filter widget doesn't initialize on an empty table. Fixes #449
73
+ if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) {
74
+ return;
75
+ }
76
+ var $table = c.$table,
77
+ $attach = $(wo.stickyHeaders_attachTo),
78
+ namespace = c.namespace + 'stickyheaders ',
79
+ // element to watch for the scroll event
80
+ $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window),
81
+ $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window),
82
+ $thead = $table.children('thead:first'),
83
+ $header = $thead.children('tr').not('.sticky-false').children(),
84
+ $tfoot = $table.children('tfoot'),
85
+ $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
86
+ stickyOffset = $attach.length ? 0 : $stickyOffset.length ?
87
+ $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
88
+ // is this table nested? If so, find parent sticky header wrapper (div, not table)
89
+ $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ?
90
+ $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [],
91
+ nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0,
92
+ // clone table, then wrap to make sticky header
93
+ $stickyTable = wo.$sticky = $table.clone()
94
+ .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders)
95
+ .wrap('<div class="' + ts.css.stickyWrap + '">'),
96
+ $stickyWrap = $stickyTable.parent().css({
97
+ position : $attach.length ? 'absolute' : 'fixed',
98
+ padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ),
99
+ top : stickyOffset + nestedStickyTop,
100
+ left : 0,
101
+ visibility : 'hidden',
102
+ zIndex : wo.stickyHeaders_zIndex || 2
103
+ }),
104
+ $stickyThead = $stickyTable.children('thead:first'),
105
+ $stickyCells,
106
+ laststate = '',
107
+ spacing = 0,
108
+ setWidth = function($orig, $clone){
109
+ $orig.filter(':visible').each(function(i) {
110
+ var width, border,
111
+ $cell = $clone.filter(':visible').eq(i),
112
+ $this = $(this);
113
+ // code from https://github.com/jmosbech/StickyTableHeaders
114
+ if ($this.css('box-sizing') === 'border-box') {
115
+ width = $this.outerWidth();
116
+ } else {
117
+ if ($cell.css('border-collapse') === 'collapse') {
118
+ if (window.getComputedStyle) {
119
+ width = parseFloat( window.getComputedStyle(this, null).width );
120
+ } else {
121
+ // ie8 only
122
+ border = parseFloat( $this.css('border-width') );
123
+ width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border;
124
+ }
125
+ } else {
126
+ width = $this.width();
127
+ }
128
+ }
129
+ $cell.css({
130
+ 'min-width': width,
131
+ 'max-width': width
132
+ });
133
+ });
134
+ },
135
+ resizeHeader = function() {
136
+ stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0;
137
+ spacing = 0;
138
+ $stickyWrap.css({
139
+ left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 :
140
+ $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing,
141
+ width: $table.outerWidth()
142
+ });
143
+ setWidth( $table, $stickyTable );
144
+ setWidth( $header, $stickyCells );
145
+ };
146
+ // save stickyTable element to config
147
+ // it is also saved to wo.$sticky
148
+ if (c.$extraTables && c.$extraTables.length) {
149
+ c.$extraTables.add($stickyTable);
150
+ } else {
151
+ c.$extraTables = $stickyTable;
152
+ }
153
+ // fix clone ID, if it exists - fixes #271
154
+ if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; }
155
+ // clear out cloned table, except for sticky header
156
+ // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing
157
+ $stickyTable.find('thead:gt(0), tr.sticky-false').hide();
158
+ $stickyTable.find('tbody, tfoot').remove();
159
+ $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption);
160
+ // issue #172 - find td/th in sticky header
161
+ $stickyCells = $stickyThead.children().children();
162
+ $stickyTable.css({ height:0, width:0, margin: 0 });
163
+ // remove resizable block
164
+ $stickyCells.find('.' + ts.css.resizer).remove();
165
+ // update sticky header class names to match real header after sorting
166
+ $table
167
+ .addClass('hasStickyHeaders')
168
+ .bind('pagerComplete' + namespace, function() {
169
+ resizeHeader();
170
+ });
171
+
172
+ ts.bindEvents(table, $stickyThead.children().children('.tablesorter-header'));
173
+
174
+ // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
175
+ $table.after( $stickyWrap );
176
+
177
+ // onRenderHeader is defined, we need to do something about it (fixes #641)
178
+ if (c.onRenderHeader) {
179
+ $stickyThead.children('tr').children().each(function(index){
180
+ // send second parameter
181
+ c.onRenderHeader.apply( $(this), [ index, c, $stickyTable ] );
182
+ });
183
+ }
184
+
185
+ // make it sticky!
186
+ $xScroll.add($yScroll)
187
+ .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') )
188
+ .bind('scroll resize '.split(' ').join( namespace ), function(event) {
189
+ if (!$table.is(':visible')) { return; } // fixes #278
190
+ // Detect nested tables - fixes #724
191
+ nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0;
192
+ var prefix = 'tablesorter-sticky-',
193
+ offset = $table.offset(),
194
+ yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3
195
+ xWindow = $.isWindow( $xScroll[0] ),
196
+ // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop,
197
+ scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop,
198
+ tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)),
199
+ isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden',
200
+ cssSettings = { visibility : isVisible };
201
+
202
+ if ($attach.length) {
203
+ cssSettings.top = yWindow ? scrollTop : $attach.scrollTop();
204
+ }
205
+ if (xWindow) {
206
+ // adjust when scrolling horizontally - fixes issue #143
207
+ cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing;
208
+ }
209
+ if ($nestedSticky.length) {
210
+ cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop;
211
+ }
212
+ $stickyWrap
213
+ .removeClass(prefix + 'visible ' + prefix + 'hidden')
214
+ .addClass(prefix + isVisible)
215
+ .css(cssSettings);
216
+ if (isVisible !== laststate || event.type === 'resize') {
217
+ // make sure the column widths match
218
+ resizeHeader();
219
+ laststate = isVisible;
220
+ }
221
+ });
222
+ if (wo.stickyHeaders_addResizeEvent) {
223
+ ts.addHeaderResizeEvent(table);
224
+ }
225
+
226
+ // look for filter widget
227
+ if ($table.hasClass('hasFilters') && wo.filter_columnFilters) {
228
+ // scroll table into view after filtering, if sticky header is active - #482
229
+ $table.bind('filterEnd' + namespace, function() {
230
+ // $(':focus') needs jQuery 1.6+
231
+ var $td = $(document.activeElement).closest('td'),
232
+ column = $td.parent().children().index($td);
233
+ // only scroll if sticky header is active
234
+ if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) {
235
+ // scroll to original table (not sticky clone)
236
+ window.scrollTo(0, $table.position().top);
237
+ // give same input/select focus; check if c.$filters exists; fixes #594
238
+ if (column >= 0 && c.$filters) {
239
+ c.$filters.eq(column).find('a, select, input').filter(':visible').focus();
240
+ }
241
+ }
242
+ });
243
+ ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) );
244
+ // support hideFilters
245
+ if (wo.filter_hideFilters) {
246
+ ts.filter.hideFilters($stickyTable, c);
247
+ }
248
+ }
249
+
250
+ $table.trigger('stickyHeadersInit');
251
+
252
+ },
253
+ remove: function(table, c, wo) {
254
+ var namespace = c.namespace + 'stickyheaders ';
255
+ c.$table
256
+ .removeClass('hasStickyHeaders')
257
+ .unbind( ('pagerComplete filterEnd '.split(' ').join(namespace)).replace(/\s+/g, ' ') )
258
+ .next('.' + ts.css.stickyWrap).remove();
259
+ if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table
260
+ $(window)
261
+ .add(wo.stickyHeaders_xScroll)
262
+ .add(wo.stickyHeaders_yScroll)
263
+ .add(wo.stickyHeaders_attachTo)
264
+ .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') );
265
+ ts.addHeaderResizeEvent(table, false);
266
+ }
267
+ });
268
+
269
+ })(jQuery, window);