jquery-tablesorter 1.10.2 → 1.10.3

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +6 -3
  3. data/Rakefile +30 -16
  4. data/lib/jquery-tablesorter/version.rb +1 -1
  5. data/vendor/assets/javascripts/jquery-tablesorter/extras/jquery.quicksearch.js +191 -0
  6. data/vendor/assets/javascripts/jquery-tablesorter/extras/semver-mod.js +1026 -0
  7. data/vendor/assets/javascripts/jquery-tablesorter/extras/semver.js +1011 -0
  8. data/vendor/assets/javascripts/jquery-tablesorter/jquery.tablesorter.js +2 -2
  9. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-iso8601.js +34 -0
  10. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-month.js +33 -0
  11. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-two-digit-year.js +74 -0
  12. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date-weekday.js +33 -0
  13. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-date.js +36 -0
  14. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-feet-inch-fraction.js +63 -0
  15. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-file-type.js +73 -0
  16. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-ignore-articles.js +47 -0
  17. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-input-select.js +86 -0
  18. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-ipv6.js +76 -0
  19. data/vendor/assets/javascripts/jquery-tablesorter/parsers/parser-metric.js +77 -0
  20. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-build-table.js +441 -0
  21. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-columnSelector.js +291 -0
  22. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-cssStickyHeaders.js +67 -0
  23. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-editable.js +89 -0
  24. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-grouping.js +183 -0
  25. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-pager.js +834 -0
  26. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-repeatheaders.js +50 -0
  27. data/vendor/assets/javascripts/jquery-tablesorter/widgets/widget-scroller.js +241 -0
  28. metadata +24 -2
@@ -0,0 +1,291 @@
1
+ /* Column Selector/Responsive table widget (beta) for TableSorter 12/17/2013 (v2.15.0)
2
+ * Requires tablesorter v2.8+ and jQuery 1.7+
3
+ * by Justin Hallett & Rob Garrison
4
+ */
5
+ /*jshint browser:true, jquery:true, unused:false */
6
+ /*global jQuery: false */
7
+ ;(function($){
8
+ "use strict";
9
+
10
+ var ts = $.tablesorter,
11
+ namespace = '.tscolsel',
12
+ tsColSel = ts.columnSelector = {
13
+
14
+ queryAll : '@media only all { [columns] { display: none; } }',
15
+ queryBreak : '@media screen and (min-width: [size]) { [columns] { display: table-cell; } }',
16
+
17
+ init: function(table, c, wo) {
18
+ var $t, colSel;
19
+
20
+ // abort if no input is contained within the layout
21
+ $t = $(wo.columnSelector_layout);
22
+ if (!$t.find('input').add( $t.filter('input') ).length) {
23
+ if (c.debug) {
24
+ ts.log('*** ERROR: Column Selector aborting, no input found in the layout! ***');
25
+ }
26
+ return;
27
+ }
28
+
29
+ // unique table class name
30
+ c.tableId = 'tablesorter' + new Date().getTime();
31
+ c.$table.addClass( c.tableId );
32
+
33
+ // build column selector/state array
34
+ colSel = c.selector = { $container : $(wo.columnSelector_container || '<div>') };
35
+ tsColSel.setupSelector(table, c, wo);
36
+
37
+ if (wo.columnSelector_mediaquery) {
38
+ tsColSel.setupBreakpoints(c, wo);
39
+ }
40
+
41
+ if (colSel.$container.length) {
42
+ colSel.$style = $('<style></style>').prop('disabled', true).appendTo('head');
43
+ tsColSel.updateCols(c, wo);
44
+ }
45
+
46
+ },
47
+
48
+ setupSelector: function(table, c, wo) {
49
+ var name,
50
+ colSel = c.selector,
51
+ $container = colSel.$container,
52
+ // get stored column states
53
+ saved = wo.columnSelector_saveColumns && ts.storage ? ts.storage( table, 'tablesorter-columnSelector' ) : [];
54
+
55
+ // initial states
56
+ colSel.states = [];
57
+ colSel.$column = [];
58
+ colSel.$wrapper = [];
59
+ colSel.$checkbox = [];
60
+ // populate the selector container
61
+ c.$table.children('thead').find('tr:first th', table).each(function() {
62
+ var $this = $(this),
63
+ // if no data-priority is assigned, default to 1, but don't remove it from the selector list
64
+ priority = $this.attr(wo.columnSelector_priority) || 1,
65
+ colId = $this.attr('data-column');
66
+
67
+ // if this column not hidable at all
68
+ // include getData check (includes "columnSelector-false" class, data attribute, etc)
69
+ if ( isNaN(priority) && priority.length > 0 || ts.getData(this, c.headers[colId], 'columnSelector') == 'false' ||
70
+ ( wo.columnSelector_columns[colId] && wo.columnSelector_columns[colId] === 'disable') ) {
71
+ return true; // goto next
72
+ }
73
+
74
+ // set default state
75
+ colSel.states[colId] = saved && typeof(saved[colId]) !== 'undefined' ?
76
+ saved[colId] : typeof(wo.columnSelector_columns[colId]) !== 'undefined' ? wo.columnSelector_columns[colId] : true;
77
+ colSel.$column[colId] = $(this);
78
+
79
+ // set default col title
80
+ name = $this.attr(wo.columnSelector_name) || $this.text();
81
+
82
+ if ($container.length) {
83
+ colSel.$wrapper[colId] = $(wo.columnSelector_layout.replace(/\{name\}/g, name)).appendTo($container);
84
+ colSel.$checkbox[colId] = colSel.$wrapper[colId]
85
+ // input may not be wrapped within the layout template
86
+ .find('input').add( colSel.$wrapper[colId].filter('input') )
87
+ .attr('data-column', colId)
88
+ .prop('checked', colSel.states[colId])
89
+ .bind('change', function(){
90
+ colSel.states[colId] = this.checked;
91
+ tsColSel.updateCols(c, wo);
92
+ }).change();
93
+ }
94
+ });
95
+
96
+ },
97
+
98
+ setupBreakpoints: function(c, wo){
99
+ var $auto, colSel = c.selector;
100
+
101
+ // add responsive breakpoints
102
+ if (wo.columnSelector_mediaquery) {
103
+ // used by window resize function
104
+ colSel.lastIndex = -1;
105
+ wo.columnSelector_breakpoints.sort();
106
+ colSel.$breakpoints = $('<style></style>').prop('disabled', true).appendTo('head');
107
+ tsColSel.updateBreakpoints(c, wo);
108
+ c.$table.unbind('updateAll' + namespace).bind('updateAll' + namespace, function(){
109
+ tsColSel.updateBreakpoints(c, wo);
110
+ tsColSel.updateCols(c, wo);
111
+ });
112
+ }
113
+
114
+ if (colSel.$container.length) {
115
+ // Add media queries toggle
116
+ if (wo.columnSelector_mediaquery && wo.columnSelector_mediaquery) {
117
+ $auto = $( wo.columnSelector_layout.replace(/\{name\}/g, wo.columnSelector_mediaqueryName) ).prependTo(colSel.$container);
118
+ $auto
119
+ // needed in case the input in the layout is not wrapped
120
+ .find('input').add( $auto.filter('input') )
121
+ .attr('data-column', 'auto')
122
+ .prop('checked', wo.columnSelector_mediaqueryState)
123
+ .bind('change', function(){
124
+ wo.columnSelector_mediaqueryState = this.checked;
125
+ $.each( colSel.$checkbox, function(i, $cb){
126
+ if ($cb) {
127
+ $cb[0].disabled = wo.columnSelector_mediaqueryState;
128
+ colSel.$wrapper[i].toggleClass('disabled', wo.columnSelector_mediaqueryState);
129
+ }
130
+ });
131
+ tsColSel.updateBreakpoints(c, wo);
132
+ tsColSel.updateCols(c, wo);
133
+ // copy the column selector to a popup/tooltip
134
+ if (c.selector.$popup) {
135
+ c.selector.$popup.find('.tablesorter-column-selector')
136
+ .html( colSel.$container.html() )
137
+ .find('input').each(function(){
138
+ var indx = $(this).attr('data-column')
139
+ $(this).prop( 'checked', indx === 'auto' ? wo.columnSelector_mediaqueryState : colSel.states[indx] )
140
+ });
141
+ }
142
+ }).change();
143
+ }
144
+ // Add a bind on update to re-run col setup
145
+ c.$table.unbind('update' + namespace).bind('update' + namespace, function() {
146
+ tsColSel.updateCols(c, wo);
147
+ });
148
+ }
149
+ },
150
+
151
+ updateBreakpoints: function(c, wo) {
152
+ var priority, column, breaks,
153
+ colSel = c.selector,
154
+ prefix = '.' + c.tableId,
155
+ mediaAll = [],
156
+ breakpts = '';
157
+ if (wo.columnSelector_mediaquery && !wo.columnSelector_mediaqueryState) {
158
+ colSel.$breakpoints.prop('disabled', true);
159
+ colSel.$style.prop('disabled', false);
160
+ return;
161
+ }
162
+
163
+ // only 6 breakpoints (same as jQuery Mobile)
164
+ for (priority = 0; priority < 6; priority++){
165
+ /*jshint loopfunc:true */
166
+ breaks = [];
167
+ c.$headers.filter('[' + wo.columnSelector_priority + '=' + (priority + 1) + ']').each(function(){
168
+ column = parseInt($(this).attr('data-column'), 10) + 1;
169
+ breaks.push(prefix + ' tr th:nth-child(' + column + ')');
170
+ breaks.push(prefix + ' tr td:nth-child(' + column + ')');
171
+ });
172
+ if (breaks.length) {
173
+ mediaAll = mediaAll.concat( breaks );
174
+ breakpts += tsColSel.queryBreak
175
+ .replace(/\[size\]/g, wo.columnSelector_breakpoints[priority])
176
+ .replace(/\[columns\]/g, breaks.join(','));
177
+ }
178
+ }
179
+ if (colSel.$style) {
180
+ colSel.$style.prop('disabled', true);
181
+ }
182
+ colSel.$breakpoints
183
+ .prop('disabled', false)
184
+ .html( tsColSel.queryAll.replace(/\[columns\]/g, mediaAll.join(',')) + breakpts );
185
+ },
186
+
187
+ updateCols: function(c, wo) {
188
+ if (wo.columnSelector_mediaquery && wo.columnSelector_mediaqueryState) {
189
+ return;
190
+ }
191
+ var column,
192
+ styles = [],
193
+ prefix = '.' + c.tableId;
194
+ c.selector.$container.find('input[data-column]').filter('[data-column!="auto"]').each(function(){
195
+ if (!this.checked) {
196
+ column = parseInt( $(this).attr('data-column'), 10 ) + 1;
197
+ styles.push(prefix + ' tr th:nth-child(' + column + ')');
198
+ styles.push(prefix + ' tr td:nth-child(' + column + ')');
199
+ }
200
+ });
201
+ if (wo.columnSelector_mediaquery){
202
+ c.selector.$breakpoints.prop('disabled', true);
203
+ }
204
+ if (c.selector.$style) {
205
+ c.selector.$style.prop('disabled', false).html( styles.length ? styles.join(',') + ' { display: none; }' : '' );
206
+ }
207
+ if (wo.columnSelector_saveColumns && ts.storage) {
208
+ ts.storage( c.$table[0], 'tablesorter-columnSelector', c.selector.states );
209
+ }
210
+ },
211
+
212
+ attachTo : function(table, elm) {
213
+ var colSel, wo, indx,
214
+ table = $(table)[0],
215
+ c = table.config,
216
+ $popup = $(elm);
217
+ if ($popup.length && c) {
218
+ if (!$popup.find('.tablesorter-column-selector').length) {
219
+ // add a wrapper to add the selector into, in case the popup has other content
220
+ $popup.append('<span class="tablesorter-column-selector"></span>');
221
+ }
222
+ colSel = c.selector;
223
+ wo = c.widgetOptions;
224
+ $popup.find('.tablesorter-column-selector')
225
+ .html( colSel.$container.html() )
226
+ .find('input').each(function(){
227
+ var indx = $(this).attr('data-column');
228
+ $(this).prop( 'checked', indx === 'auto' ? wo.columnSelector_mediaqueryState : colSel.states[indx] )
229
+ });
230
+ colSel.$popup = $popup.on('change', 'input', function(){
231
+ // data input
232
+ indx = $(this).attr('data-column');
233
+ // update original popup
234
+ colSel.$container.find('input[data-column="' + indx + '"]')
235
+ .prop('checked', this.checked)
236
+ .trigger('change');
237
+ });
238
+ }
239
+ }
240
+
241
+ };
242
+
243
+ ts.addWidget({
244
+ id: "columnSelector",
245
+ priority: 10,
246
+ options: {
247
+ // target the column selector markup
248
+ columnSelector_container : null,
249
+ // column status, true = display, false = hide
250
+ // disable = do not display on list
251
+ columnSelector_columns : {},
252
+ // remember selected columns
253
+ columnSelector_saveColumns: true,
254
+
255
+ // container layout
256
+ columnSelector_layout : '<label><input type="checkbox">{name}</label>',
257
+ // data attribute containing column name to use in the selector container
258
+ columnSelector_name : 'data-selector-name',
259
+
260
+ /* Responsive Media Query settings */
261
+ // enable/disable mediaquery breakpoints
262
+ columnSelector_mediaquery: true,
263
+ // toggle checkbox name
264
+ columnSelector_mediaqueryName: 'Auto: ',
265
+ // breakpoints checkbox initial setting
266
+ columnSelector_mediaqueryState: true,
267
+ // responsive table hides columns with priority 1-6 at these breakpoints
268
+ // see http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/#Applyingapresetbreakpoint
269
+ // *** set to false to disable ***
270
+ columnSelector_breakpoints : [ '20em', '30em', '40em', '50em', '60em', '70em' ],
271
+ // data attribute containing column priority
272
+ // duplicates how jQuery mobile uses priorities:
273
+ // http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/
274
+ columnSelector_priority : 'data-priority'
275
+
276
+ },
277
+ init: function(table, thisWidget, c, wo) {
278
+ tsColSel.init(table, c, wo);
279
+ },
280
+ remove: function(table, c){
281
+ var csel = c.selector;
282
+ csel.$container.empty();
283
+ csel.$popup.empty();
284
+ csel.$style.remove();
285
+ csel.$breakpoints.remove();
286
+ c.$table.unbind('updateAll' + namespace + ',update' + namespace);
287
+ }
288
+
289
+ });
290
+
291
+ })(jQuery);
@@ -0,0 +1,67 @@
1
+ /*! tablesorter CSS Sticky Headers widget - updated 12/17/2013 (v2.15.0)
2
+ * Requires a modern browser, tablesorter v2.8+
3
+ */
4
+ /*global jQuery: false, unused:false */
5
+ ;(function($){
6
+ "use strict";
7
+
8
+ $.tablesorter.addWidget({
9
+ id: "cssStickyHeaders",
10
+ priority: 10,
11
+ options: {
12
+ cssStickyHeaders_offset : 0,
13
+ cssStickyHeaders_addCaption : false,
14
+ cssStickyHeaders_attachTo : null,
15
+ cssStickyHeaders_zIndex : 10
16
+ },
17
+ init : function(table, thisWidget, c, wo) {
18
+ var $attach = $(wo.cssStickyHeaders_attachTo),
19
+ namespace = '.cssstickyheader',
20
+ $thead = c.$table.children('thead'),
21
+ $caption = c.$table.find('caption'),
22
+ $win = $attach.length ? $attach : $(window);
23
+ $win.bind('scroll resize '.split(' ').join(namespace + ' '), function() {
24
+ var top = $attach.length ? $attach.offset().top : $win.scrollTop(),
25
+ // add caption height; include table padding top & border-spacing or text may be above the fold (jQuery UI themes)
26
+ // border-spacing needed in Firefox, but not webkit... not sure if I should account for that
27
+ captionTop = wo.cssStickyHeaders_addCaption ? $caption.outerHeight(true) +
28
+ (parseInt(c.$table.css('padding-top'), 10) || 0) + (parseInt(c.$table.css('border-spacing'), 10) || 0) : 0,
29
+ bottom = c.$table.height() - $thead.height() - (c.$table.find('tfoot').height() || 0) - captionTop,
30
+ deltaY = top - $thead.offset().top + (parseInt(c.$table.css('border-top-width'), 10) || 0) +
31
+ (wo.cssStickyHeaders_offset || 0) + captionTop,
32
+ finalY = (deltaY > 0 && deltaY <= bottom ? deltaY : 0),
33
+ // IE can only transform header cells - fixes #447 thanks to @gakreol!
34
+ $cells = $thead.children().children();
35
+ if (wo.cssStickyHeaders_addCaption) {
36
+ $cells = $cells.add($caption);
37
+ }
38
+ $cells.css({
39
+ "position" : "relative",
40
+ "z-index" : wo.cssStickyHeaders_zIndex,
41
+ "transform" : finalY === 0 ? "" : "translate(0px," + finalY + "px)",
42
+ "-ms-transform" : finalY === 0 ? "" : "translate(0px," + finalY + "px)",
43
+ "-webkit-transform" : finalY === 0 ? "" : "translate(0px," + finalY + "px)"
44
+ });
45
+ });
46
+ c.$table.bind('filterEnd', function() {
47
+ // scroll top of table into view
48
+ window.scrollTo(0, c.$table.position().top);
49
+ });
50
+
51
+ },
52
+ remove: function(table, c, wo){
53
+ var namespace = '.cssstickyheader';
54
+ $(window).unbind('scroll resize '.split(' ').join(namespace + ' '));
55
+ c.$table
56
+ .unbind('update updateAll '.split(' ').join(namespace + ' '))
57
+ .children('thead, caption').css({
58
+ "position" : "",
59
+ "z-index" : "",
60
+ "transform" : "",
61
+ "-ms-transform" : "",
62
+ "-webkit-transform" : ""
63
+ });
64
+ }
65
+ });
66
+
67
+ })(jQuery);
@@ -0,0 +1,89 @@
1
+ /*! tablesorter Editable Content widget - updated 1/24/2014 (core v2.15.0)
2
+ * Requires tablesorter v2.8+ and jQuery 1.7+
3
+ * by Rob Garrison
4
+ */
5
+ /*jshint browser:true, jquery:true, unused:false */
6
+ /*global jQuery: false */
7
+ ;(function($){
8
+ "use strict";
9
+
10
+ $.tablesorter.addWidget({
11
+ id: 'editable',
12
+ options : {
13
+ editable_columns : [],
14
+ editable_enterToAccept : true,
15
+ editable_autoResort : false,
16
+ editable_noEdit : 'no-edit',
17
+ editable_editComplete : 'editComplete'
18
+ },
19
+ init: function(table, thisWidget, c, wo){
20
+ if (!wo.editable_columns.length) { return; }
21
+ var indx, tmp, $t, cols = [];
22
+ if (wo.editable_columns.indexOf('-') >= 0) {
23
+ // editable_columns can contain a range string (i.e. "2-4" )
24
+ tmp = wo.editable_columns.split('-');
25
+ indx = parseInt(tmp[0],10) || 0;
26
+ tmp = parseInt(tmp[1],10) || (c.columns - 1);
27
+ if (tmp > c.columns) { tmp = c.columns - 1; }
28
+ for (; indx <= tmp; indx++) {
29
+ cols.push('td:nth-child(' + (indx + 1) + ')');
30
+ }
31
+ } else if ($.isArray(wo.editable_columns)) {
32
+ $.each(wo.editable_columns, function(i, col){
33
+ cols.push('td:nth-child(' + (col + 1) + ')');
34
+ });
35
+ }
36
+ // IE does not allow making TR/TH/TD cells directly editable (issue #404)
37
+ // so add a div or span inside ( it's faster than using wrapInner() )
38
+ c.$tbodies.find( cols.join(',') ).not('.' + wo.editable_noEdit).each(function(){
39
+ // test for children, if they exist, then make the children editable
40
+ $t = $(this);
41
+ ( $t.children().length ? $t.children() : $t ).prop('contenteditable', true);
42
+ });
43
+ c.$tbodies
44
+ .on('mouseleave.tseditable', function(){
45
+ if (c.$table.data('contentFocused')) {
46
+ $(':focus').trigger('blur');
47
+ }
48
+ })
49
+ .on('focus.tseditable', '[contenteditable]', function(){
50
+ c.$table.data('contentFocused', true);
51
+ var $this = $(this), v = $this.html();
52
+ if (wo.editable_enterToAccept) {
53
+ // prevent enter from adding into the content
54
+ $this.on('keydown.tseditable', function(e){
55
+ if (e.which === 13) {
56
+ e.preventDefault();
57
+ }
58
+ });
59
+ }
60
+ $this.data({ before : v, original: v });
61
+ })
62
+ .on('blur focusout keyup '.split(' ').join('.tseditable '), '[contenteditable]', function(e){
63
+ if (!c.$table.data('contentFocused')) { return; }
64
+ var $this = $(e.target), t;
65
+ if (e.which === 27) {
66
+ // user cancelled
67
+ $this.html( $this.data('original') ).trigger('blur.tseditable');
68
+ c.$table.data('contentFocused', false);
69
+ return false;
70
+ }
71
+ t = e.type !== 'keyup' || (wo.editable_enterToAccept && e.which === 13);
72
+ // change if new or user hits enter (if option set)
73
+ if ($this.data('before') !== $this.html() || t) {
74
+ $this.data('before', $this.html()).trigger('change');
75
+ if (t) {
76
+ c.$table
77
+ .data('contentFocused', false)
78
+ .trigger('updateCell', [ $this.closest('td'), wo.editable_autoResort, function(table){
79
+ $this.trigger( wo.editable_editComplete );
80
+ c.$table.trigger('applyWidgets');
81
+ } ]);
82
+ $this.trigger('blur.tseditable');
83
+ }
84
+ }
85
+ });
86
+ }
87
+ });
88
+
89
+ })(jQuery);
@@ -0,0 +1,183 @@
1
+ /*! tablesorter Grouping widget - updated 12/18/2013 (core v2.15.0)
2
+ * Requires tablesorter v2.8+ and jQuery 1.7+
3
+ * by Rob Garrison
4
+ */
5
+ /*jshint browser:true, jquery:true, unused:false */
6
+ /*global jQuery: false */
7
+ ;(function($){
8
+ "use strict";
9
+ var ts = $.tablesorter;
10
+
11
+ ts.grouping = {
12
+
13
+ types : {
14
+ number : function(c, $column, txt, num, group){
15
+ var value, word;
16
+ if (num > 1 && txt !== '') {
17
+ if ($column.hasClass(ts.css.sortAsc)) {
18
+ value = Math.floor(parseFloat(txt)/num) * num;
19
+ return value > parseFloat(group || 0) ? value : parseFloat(group || 0);
20
+ } else {
21
+ value = Math.ceil(parseFloat(txt)/num) * num;
22
+ return value < parseFloat(group || num) - value ? parseFloat(group || num) - value : value;
23
+ }
24
+ } else {
25
+ word = (txt + '').match(/\d+/g);
26
+ return word && word.length >= num ? word[num - 1] : txt || '';
27
+ }
28
+ },
29
+ separator : function(c, $column, txt, num){
30
+ var word = (txt + '').split(c.widgetOptions.group_separator);
31
+ return $.trim(word && num > 0 && word.length >= num ? word[(num || 1) - 1] : '');
32
+ },
33
+ word : function(c, $column, txt, num){
34
+ var word = (txt + ' ').match(/\w+/g);
35
+ return word && word.length >= num ? word[num - 1] : txt || '';
36
+ },
37
+ letter : function(c, $column, txt, num){
38
+ return txt ? (txt + ' ').substring(0, num) : '';
39
+ },
40
+ date : function(c, $column, txt, part, group){
41
+ var wo = c.widgetOptions,
42
+ time = new Date(txt || ''),
43
+ hours = time.getHours();
44
+ return part === 'year' ? time.getFullYear() :
45
+ part === 'month' ? wo.group_months[time.getMonth()] :
46
+ part === 'day' ? wo.group_months[time.getMonth()] + ' ' + time.getDate() :
47
+ part === 'week' ? wo.group_week[time.getDay()] :
48
+ part === 'time' ? ('00' + (hours > 12 ? hours - 12 : hours === 0 ? hours + 12 : hours)).slice(-2) + ':' +
49
+ ('00' + time.getMinutes()).slice(-2) + ' ' + ('00' + wo.group_time[hours >= 12 ? 1 : 0]).slice(-2) :
50
+ wo.group_dateString(time);
51
+ }
52
+ },
53
+
54
+ update : function(table, c, wo){
55
+ if ($.isEmptyObject(c.cache)) { return; }
56
+ var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, time, cache,
57
+ lang = wo.grouping_language,
58
+ group = '',
59
+ column = c.sortList[0] ? c.sortList[0][0] : -1;
60
+ c.$table
61
+ .find('tr.group-hidden').removeClass('group-hidden').end()
62
+ .find('tr.group-header').remove();
63
+ if (wo.group_collapsible) {
64
+ // clear pager saved spacer height (in case the rows are collapsed)
65
+ c.$table.data('pagerSavedHeight', 0);
66
+ }
67
+ if (column >= 0 && !c.$headers.filter('[data-column="' + column + '"]:last').hasClass('group-false')) {
68
+ if (c.debug){ time = new Date(); }
69
+ for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) {
70
+ cache = c.cache[tbodyIndex].normalized;
71
+ group = ''; // clear grouping across tbodies
72
+ $rows = c.$tbodies.eq(tbodyIndex).children('tr').not('.' + c.cssChildRow);
73
+ if (wo.group_collapsed && wo.group_collapsible) {
74
+ $rows.addClass('group-hidden');
75
+ }
76
+ for (rowIndex = 0; rowIndex < $rows.length; rowIndex++) {
77
+ if ( $rows.eq(rowIndex).is(':visible') ) {
78
+ // group class finds "group-{word/separator/letter/number/date/false}-{optional:#/year/month/day/week/time}"
79
+ groupClass = (c.$headers.filter('[data-column="' + column + '"]:last').attr('class') || '').match(/(group-\w+(-\w+)?)/g);
80
+ // grouping = [ 'group', '{word/separator/letter/number/date/false}', '{#/year/month/day/week/time}' ]
81
+ grouping = groupClass ? groupClass[0].split('-') : ['','letter',1]; // default to letter 1
82
+ // fixes #438
83
+ if (ts.grouping.types[grouping[1]]) {
84
+ currentGroup = cache[rowIndex] ?
85
+ ts.grouping.types[grouping[1]]( c, c.$headers.filter('[data-column="' + column + '"]:last'), cache[rowIndex][column], /date/.test(groupClass) ?
86
+ grouping[2] : parseInt(grouping[2] || 1, 10) || 1, group, lang ) : currentGroup;
87
+ if (group !== currentGroup) {
88
+ group = currentGroup;
89
+ // show range if number > 1
90
+ if (grouping[1] === 'number' && grouping[2] > 1 && currentGroup !== '') {
91
+ currentGroup += ' - ' + (parseInt(currentGroup, 10) +
92
+ ((parseInt(grouping[2],10) - 1) * (c.$headers.filter('[data-column="' + column + '"]:last').hasClass(ts.css.sortAsc) ? 1 : -1)));
93
+ }
94
+ if ($.isFunction(wo.group_formatter)) {
95
+ currentGroup = wo.group_formatter((currentGroup || '').toString(), column, table, c, wo) || currentGroup;
96
+ }
97
+ $rows.eq(rowIndex).before('<tr class="group-header ' + c.selectorRemove.slice(1) +
98
+ (wo.group_collapsed && wo.group_collapsible ? ' collapsed' : '') + '" unselectable="on"><td colspan="' +
99
+ c.columns + '">' + (wo.group_collapsible ? '<i/>' : '') + '<span class="group-name">' +
100
+ currentGroup + '</span><span class="group-count"></span></td></tr>');
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ $rows = c.$table.find('tr.group-header').bind('selectstart', false);
107
+ if (wo.group_count || $.isFunction(wo.group_callback)) {
108
+ $rows.each(function(){
109
+ var $rows,
110
+ $row = $(this),
111
+ $label = $row.find('.group-count');
112
+ if ($label.length) {
113
+ $rows = $row.nextUntil('tr.group-header').filter(':visible');
114
+ if (wo.group_count) {
115
+ $label.html( wo.group_count.replace(/\{num\}/g, $rows.length) );
116
+ }
117
+ if ($.isFunction(wo.group_callback)) {
118
+ wo.group_callback($row.find('td'), $rows, column, table);
119
+ }
120
+ }
121
+ });
122
+ }
123
+ c.$table.trigger(wo.group_complete);
124
+ if (c.debug) {
125
+ $.tablesorter.benchmark("Applying groups widget: ", time);
126
+ }
127
+ }
128
+ }
129
+
130
+ };
131
+
132
+ ts.addWidget({
133
+ id: 'group',
134
+ priority: 100,
135
+ options: {
136
+ group_collapsible : true, // make the group header clickable and collapse the rows below it.
137
+ group_collapsed : false, // start with all groups collapsed
138
+ group_count : ' ({num})', // if not false, the "{num}" string is replaced with the number of rows in the group
139
+ group_separator : '-', // group name separator; used when group-separator-# class is used.
140
+ group_formatter : null, // function(txt, column, table, c, wo) { return txt; }
141
+ group_callback : null, // function($cell, $rows, column, table){}, callback allowing modification of the group header labels
142
+ group_complete : 'groupingComplete', // event triggered on the table when the grouping widget has finished work
143
+
144
+ // change these default date names based on your language preferences
145
+ group_months : [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
146
+ group_week : [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ],
147
+ group_time : [ 'AM', 'PM' ],
148
+ // this function is used when "group-date" is set to create the date string
149
+ // you can just return date, date.toLocaleString(), date.toLocaleDateString() or d.toLocaleTimeString()
150
+ // reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Conversion_getter
151
+ group_dateString : function(date) { return date.toLocaleString(); }
152
+ },
153
+ init: function(table, thisWidget, c, wo){
154
+ if (wo.group_collapsible) {
155
+ // .on() requires jQuery 1.7+
156
+ c.$table.on('click toggleGroup', 'tr.group-header', function(event){
157
+ event.stopPropagation();
158
+ var $this = $(this);
159
+ // use shift-click to toggle ALL groups
160
+ if (event.type === 'click' && event.shiftKey) {
161
+ $this.siblings('.group-header').trigger('toggleGroup');
162
+ }
163
+ $this.toggleClass('collapsed');
164
+ // nextUntil requires jQuery 1.4+
165
+ $this.nextUntil('tr.group-header').toggleClass('group-hidden', $this.hasClass('collapsed') );
166
+ });
167
+ }
168
+ c.$table.on('pagerChange', function(){
169
+ ts.grouping.update(table, c, wo);
170
+ });
171
+ },
172
+ format: function(table, c, wo) {
173
+ ts.grouping.update(table, c, wo);
174
+ },
175
+ remove : function(table, c, wo){
176
+ c.$table
177
+ .off('click', 'tr.group-header')
178
+ .find('.group-hidden').removeClass('group-hidden').end()
179
+ .find('tr.group-header').remove();
180
+ }
181
+ });
182
+
183
+ })(jQuery);