jquery-tablesorter 1.10.2 → 1.10.3

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