jquery-tablesorter 1.14.1 → 1.15.0

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