jquery-tablesorter 1.10.10 → 1.11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1591f84710f85ceb7162d750a39df47356371b14
4
- data.tar.gz: 1be6e92aa78bf994599a91a5c8ce4107442800fd
3
+ metadata.gz: 618934fed3abb717299bc8c54013a3b1d3d53863
4
+ data.tar.gz: afbd307142ac50a3577cee070ae22a9f8b1a2bea
5
5
  SHA512:
6
- metadata.gz: 6f844b8b9ea9af7ccdc7389b55e69ed24f727a7c565355de3a9212552400a1327498fd82466ca3deb5c537a3beee49d931833c8a31724a3bb2b23078901d9181
7
- data.tar.gz: 8eb94e20072ac16362d837190fd8407c50d17b290db3b46ea037549aab87e338ba1836068ac24279622250e372d030f5c2a075d33b973d496ebb8e17dc7d9fd8
6
+ metadata.gz: b5fd391890c8feef67fba3c66f00f9e9f1f45d2bc1bc78fad870fa2db6d10c54ce0b68269ce4afcb8c04fb5d5b0b402d9c086add0085b53d94ec4905570cb758
7
+ data.tar.gz: e29cceab65caa5c1a248d1b90ae61cdc46364dbadb9be1ae68e01097fe34428b8f4e462928452b98f1f659b41c7a472aa90df1defffcba228d237ae356cf2503
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Simple integration of jquery-tablesorter into the asset pipeline.
6
6
 
7
- Current tablesorter version: 2.15.14 (4/10/2014), [documentation]
7
+ Current tablesorter version: 2.16.1 (4/24/2014), [documentation]
8
8
 
9
9
  Any issue associate with the js/css files, please report to [Mottie's fork].
10
10
 
@@ -1,3 +1,3 @@
1
1
  module JqueryTablesorter
2
- VERSION = '1.10.10'
2
+ VERSION = '1.11.0'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * tablesorter pager plugin
3
- * updated 4/10/2014 (v2.15.14)
3
+ * updated 4/23/2014 (v2.16.0)
4
4
  */
5
5
  /*jshint browser:true, jquery:true, unused:false */
6
6
  ;(function($) {
@@ -58,6 +58,10 @@
58
58
  // starting page of the pager (zero based index)
59
59
  page: 0,
60
60
 
61
+ // reset pager after filtering; set to desired page #
62
+ // set to false to not change page at filter start
63
+ pageReset: 0,
64
+
61
65
  // Number of visible rows
62
66
  size: 10,
63
67
 
@@ -125,15 +129,23 @@
125
129
  },
126
130
 
127
131
  updatePageDisplay = function(table, p, completed) {
128
- var i, pg, s, out,
132
+ var i, pg, s, out, regex,
129
133
  c = table.config,
130
134
  f = c.$table.hasClass('hasFilters') && !p.ajaxUrl,
131
- t = (c.widgetOptions && c.widgetOptions.filter_filteredRow || 'filtered') + ',' + c.selectorRemove +
132
- (p.countChildRows ? '' : ',.' + c.cssChildRow),
135
+ t = [],
133
136
  sz = p.size || 10; // don't allow dividing by zero
137
+ t = [ (c.widgetOptions && c.widgetOptions.filter_filteredRow || 'filtered'), c.selectorRemove ];
138
+ if (p.countChildRows) { t.push(c.cssChildRow); }
139
+ regex = new RegExp( '(' + t.join('|') + ')' );
134
140
  p.totalPages = Math.ceil( p.totalRows / sz ); // needed for "pageSize" method
135
- p.filteredRows = (f) ? c.$tbodies.eq(0).children('tr').not('.' + t ).length : p.totalRows;
136
- p.filteredPages = (f) ? Math.ceil( p.filteredRows / sz ) || 1 : p.totalPages;
141
+ p.filteredRows = (f) ? 0 : p.totalRows;
142
+ p.filteredPages = p.totalPages;
143
+ if (f) {
144
+ $.each(c.cache[0].normalized, function(i, el) {
145
+ p.filteredRows += p.regexRows.test(el[c.columns].$row[0].className) ? 0 : 1;
146
+ });
147
+ p.filteredPages = Math.ceil( p.filteredRows / sz ) || 0;
148
+ }
137
149
  if ( Math.min( p.totalPages, p.filteredPages ) >= 0 ) {
138
150
  t = (p.size * p.page > p.filteredRows);
139
151
  p.startRow = (t) ? 1 : (p.filteredRows === 0 ? 0 : p.size * p.page + 1);
@@ -448,12 +460,13 @@
448
460
  },
449
461
 
450
462
  renderTable = function(table, rows, p) {
451
- var i, $tb,
463
+ var $tb, index, count, added,
452
464
  $t = $(table),
453
465
  c = table.config,
466
+ f = c.$table.hasClass('hasFilters'),
454
467
  l = rows && rows.length || 0, // rows may be undefined
455
468
  s = ( p.page * p.size ),
456
- e = ( s + p.size );
469
+ e = p.size;
457
470
  if ( l < 1 ) { return; } // empty table, abort!
458
471
  if ( p.page >= p.totalPages ) {
459
472
  // lets not render the table more than once
@@ -465,18 +478,25 @@
465
478
  if ( !p.removeRows ) {
466
479
  hideRows(table, p);
467
480
  } else {
468
- if ( e > rows.length ) {
469
- e = rows.length;
470
- }
471
481
  ts.clearTableBody(table);
472
482
  $tb = ts.processTbody(table, c.$tbodies.eq(0), true);
473
- for ( i = s; i < e; i++ ) {
474
- $tb.append(rows[i]);
483
+ // not filtered, start from the calculated starting point (s)
484
+ // if filtered, start from zero
485
+ index = f ? 0 : s;
486
+ count = f ? 0 : s;
487
+ added = 0;
488
+ while (added < e && index < rows.length) {
489
+ if (!f || !/filtered/.test(rows[index][0].className)){
490
+ count++;
491
+ if (count > s && added <= e) {
492
+ added++;
493
+ $tb.append(rows[index]);
494
+ }
495
+ }
496
+ index++;
475
497
  }
476
-
477
498
  ts.processTbody(table, $tb, false);
478
499
  }
479
-
480
500
  updatePageDisplay(table, p);
481
501
  if ( !p.isDisabled ) { fixHeight(table, p); }
482
502
  $t.trigger('applyWidgets');
@@ -594,6 +614,7 @@
594
614
  p.$container.hide(); // hide pager
595
615
  table.config.appender = null; // remove pager appender function
596
616
  p.initialized = false;
617
+ delete table.config.rowsCopy;
597
618
  $(table).unbind('destroy.pager sortEnd.pager filterEnd.pager enable.pager disable.pager');
598
619
  if (ts.storage) {
599
620
  ts.storage(table, p.storageKey, '');
@@ -647,7 +668,8 @@
647
668
  var t, ctrls, fxn,
648
669
  table = this,
649
670
  c = table.config,
650
- p = c.pager = $.extend( {}, $.tablesorterPager.defaults, settings ),
671
+ wo = c.widgetOptions,
672
+ p = c.pager = $.extend( true, {}, $.tablesorterPager.defaults, settings ),
651
673
  $t = c.$table,
652
674
  // added in case the pager is reinitialized after being destroyed.
653
675
  pager = p.$container = $(p.container).addClass('tablesorter-pager').show();
@@ -669,17 +691,24 @@
669
691
  $.data(table, 'pagerLastSize', p.size);
670
692
  }
671
693
 
694
+ // skipped rows
695
+ p.regexRows = new RegExp('(' + (wo.filter_filteredRow || 'filtered') + '|' + c.selectorRemove.substring(1) + '|' + c.cssChildRow + ')');
696
+
672
697
  $t
673
698
  .unbind('filterStart filterEnd sortEnd disable enable destroy update updateRows updateAll addRows pageSize '.split(' ').join('.pager '))
674
699
  .bind('filterStart.pager', function(e, filters) {
675
700
  p.currentFilters = filters;
676
- p.page = 0; // fixes #456
701
+ // don't change page is filters are the same (pager updating, etc)
702
+ if (p.pageReset !== false && (c.lastCombinedFilter || '') !== (filters || []).join('')) {
703
+ p.page = p.pageReset; // fixes #456 & #565
704
+ }
677
705
  })
678
706
  // update pager after filter widget completes
679
707
  .bind('filterEnd.pager sortEnd.pager', function() {
680
708
  if (p.initialized) {
681
- moveToPage(table, p, false);
709
+ // update page display first, so we update p.filteredPages
682
710
  updatePageDisplay(table, p, false);
711
+ moveToPage(table, p, false);
683
712
  fixHeight(table, p);
684
713
  }
685
714
  })
@@ -1,5 +1,5 @@
1
1
  /**!
2
- * TableSorter 2.15.14 - Client-side table sorting with ease!
2
+ * TableSorter 2.16.1 - Client-side table sorting with ease!
3
3
  * @requires jQuery v1.2.6+
4
4
  *
5
5
  * Copyright (c) 2007 Christian Bach
@@ -24,7 +24,7 @@
24
24
 
25
25
  var ts = this;
26
26
 
27
- ts.version = "2.15.14";
27
+ ts.version = "2.16.1";
28
28
 
29
29
  ts.parsers = [];
30
30
  ts.widgets = [];
@@ -64,7 +64,8 @@
64
64
 
65
65
  emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero
66
66
  stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero
67
- textExtraction : 'simple', // text extraction method/function - function(node, table, cellIndex){}
67
+ textExtraction : 'basic', // text extraction method/function - function(node, table, cellIndex){}
68
+ textAttribute : 'data-text',// data-attribute that contains alternate cell text (used in textExtraction function)
68
69
  textSorter : null, // choose overall or specific column sorter function(a, b, direction, table, columnIndex) [alt: ts.sortText]
69
70
  numberSorter : null, // choose overall numeric sorter function(a, b, direction, maxColumnValue)
70
71
 
@@ -167,20 +168,19 @@
167
168
  function getElementText(table, node, cellIndex) {
168
169
  if (!node) { return ""; }
169
170
  var c = table.config,
170
- t = c.textExtraction, text = "";
171
- if (t === "simple") {
172
- if (c.supportsTextContent) {
173
- text = node.textContent; // newer browsers support this
174
- } else {
175
- text = $(node).text();
176
- }
171
+ t = c.textExtraction || '',
172
+ text = "";
173
+ if (t === "basic") {
174
+ // check data-attribute first
175
+ text = $(node).attr(c.textAttribute) || node.textContent || node.innerText || $(node).text() || "";
177
176
  } else {
178
- if (typeof t === "function") {
177
+ if (typeof(t) === "function") {
179
178
  text = t(node, table, cellIndex);
180
- } else if (typeof t === "object" && t.hasOwnProperty(cellIndex)) {
179
+ } else if (typeof(t) === "object" && t.hasOwnProperty(cellIndex)) {
181
180
  text = t[cellIndex](node, table, cellIndex);
182
181
  } else {
183
- text = c.supportsTextContent ? node.textContent : $(node).text();
182
+ // previous "simple" method
183
+ text = node.textContent || node.innerText || $(node).text() || "";
184
184
  }
185
185
  }
186
186
  return $.trim(text);
@@ -219,41 +219,47 @@
219
219
  var c = table.config,
220
220
  // update table bodies in case we start with an empty table
221
221
  tb = c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')'),
222
- rows, list, l, i, h, ch, p, time, parsersDebug = "";
223
- if ( tb.length === 0) {
222
+ rows, list, l, i, h, ch, p, time,
223
+ j = 0,
224
+ parsersDebug = "",
225
+ len = tb.length;
226
+ if ( len === 0) {
224
227
  return c.debug ? log('Warning: *Empty table!* Not building a parser cache') : '';
225
228
  } else if (c.debug) {
226
229
  time = new Date();
227
230
  log('Detecting parsers for each column');
228
231
  }
229
- rows = tb[0].rows;
230
- if (rows[0]) {
231
- list = [];
232
- l = rows[0].cells.length;
233
- for (i = 0; i < l; i++) {
234
- // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
235
- // More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174)
236
- h = c.$headers.filter(':not([colspan])');
237
- h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix
238
- .filter('[data-column="' + i + '"]:last');
239
- ch = c.headers[i];
240
- // get column parser
241
- p = ts.getParserById( ts.getData(h, ch, 'sorter') );
242
- // empty cells behaviour - keeping emptyToBottom for backwards compatibility
243
- c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
244
- // text strings behaviour in numerical sorts
245
- c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
246
- if (!p) {
247
- p = detectParserForColumn(table, rows, -1, i);
248
- }
249
- if (c.debug) {
250
- parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
232
+ list = [];
233
+ while (j < len) {
234
+ rows = tb[j].rows;
235
+ if (rows[j]) {
236
+ l = rows[j].cells.length;
237
+ for (i = 0; i < l; i++) {
238
+ // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8!
239
+ // More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174)
240
+ h = c.$headers.filter(':not([colspan])');
241
+ h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix
242
+ .filter('[data-column="' + i + '"]:last');
243
+ ch = c.headers[i];
244
+ // get column parser
245
+ p = ts.getParserById( ts.getData(h, ch, 'sorter') );
246
+ // empty cells behaviour - keeping emptyToBottom for backwards compatibility
247
+ c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' );
248
+ // text strings behaviour in numerical sorts
249
+ c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max';
250
+ if (!p) {
251
+ p = detectParserForColumn(table, rows, -1, i);
252
+ }
253
+ if (c.debug) {
254
+ parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n";
255
+ }
256
+ list.push(p);
251
257
  }
252
- list.push(p);
253
258
  }
259
+ j += (list.length) ? len : 1;
254
260
  }
255
261
  if (c.debug) {
256
- log(parsersDebug);
262
+ log(parsersDebug ? parsersDebug : "No parsers detected");
257
263
  benchmark("Completed detecting parsers", time);
258
264
  }
259
265
  c.parsers = list;
@@ -261,72 +267,87 @@
261
267
 
262
268
  /* utils */
263
269
  function buildCache(table) {
264
- var b = table.tBodies,
265
- tc = table.config,
266
- totalRows,
267
- totalCells,
268
- parsers = tc.parsers,
269
- t, v, i, j, k, c, cols, cacheTime, colMax = [];
270
- tc.cache = {};
270
+ var cc, t, v, i, j, k, $row, rows, cols, cacheTime,
271
+ totalRows, rowData, colMax,
272
+ c = table.config,
273
+ $tb = c.$table.children('tbody'),
274
+ parsers = c.parsers;
275
+ c.cache = {};
271
276
  // if no parsers found, return - it's an empty table.
272
277
  if (!parsers) {
273
- return tc.debug ? log('Warning: *Empty table!* Not building a cache') : '';
278
+ return c.debug ? log('Warning: *Empty table!* Not building a cache') : '';
274
279
  }
275
- if (tc.debug) {
280
+ if (c.debug) {
276
281
  cacheTime = new Date();
277
282
  }
278
283
  // processing icon
279
- if (tc.showProcessing) {
284
+ if (c.showProcessing) {
280
285
  ts.isProcessing(table, true);
281
286
  }
282
- for (k = 0; k < b.length; k++) {
283
- tc.cache[k] = { row: [], normalized: [] };
287
+ for (k = 0; k < $tb.length; k++) {
288
+ colMax = []; // column max value per tbody
289
+ cc = c.cache[k] = {
290
+ normalized: [] // array of normalized row data; last entry contains "rowData" above
291
+ // colMax: # // added at the end
292
+ };
293
+
284
294
  // ignore tbodies with class name from c.cssInfoBlock
285
- if (!$(b[k]).hasClass(tc.cssInfoBlock)) {
286
- totalRows = (b[k] && b[k].rows.length) || 0;
287
- totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0;
295
+ if (!$tb.eq(k).hasClass(c.cssInfoBlock)) {
296
+ totalRows = ($tb[k] && $tb[k].rows.length) || 0;
288
297
  for (i = 0; i < totalRows; ++i) {
298
+ rowData = {
299
+ // order: original row order #
300
+ // $row : jQuery Object[]
301
+ child: [] // child row text (filter widget)
302
+ };
289
303
  /** Add the table data to main data array */
290
- c = $(b[k].rows[i]);
304
+ $row = $($tb[k].rows[i]);
305
+ rows = [ new Array(c.columns) ];
291
306
  cols = [];
292
307
  // if this is a child row, add it to the last row's children and continue to the next row
293
308
  // ignore child row class, if it is the first row
294
- if (c.hasClass(tc.cssChildRow) && i !== 0) {
295
- tc.cache[k].row[tc.cache[k].row.length - 1] = tc.cache[k].row[tc.cache[k].row.length - 1].add(c);
309
+ if ($row.hasClass(c.cssChildRow) && i !== 0) {
310
+ t = cc.normalized.length - 1;
311
+ cc.normalized[t][c.columns].$row = cc.normalized[t][c.columns].$row.add($row);
296
312
  // add "hasChild" class name to parent row
297
- if (!c.prev().hasClass(tc.cssChildRow)) {
298
- c.prev().addClass(ts.css.cssHasChild);
313
+ if (!$row.prev().hasClass(c.cssChildRow)) {
314
+ $row.prev().addClass(ts.css.cssHasChild);
299
315
  }
316
+ // save child row content (un-parsed!)
317
+ rowData.child[t] = $.trim( $row[0].textContent || $row[0].innerText || $row.text() || "" );
300
318
  // go to the next for loop
301
319
  continue;
302
320
  }
303
- tc.cache[k].row.push(c);
304
- for (j = 0; j < totalCells; ++j) {
321
+ rowData.$row = $row;
322
+ rowData.order = i; // add original row position to rowCache
323
+ for (j = 0; j < c.columns; ++j) {
305
324
  if (typeof parsers[j] === 'undefined') {
306
- if (tc.debug) {
307
- log('No parser found for cell:', c[0].cells[j], 'does it have a header?');
325
+ if (c.debug) {
326
+ log('No parser found for cell:', $row[0].cells[j], 'does it have a header?');
308
327
  }
309
328
  continue;
310
329
  }
311
- t = getElementText(table, c[0].cells[j], j);
330
+ t = getElementText(table, $row[0].cells[j], j);
312
331
  // allow parsing if the string is empty, previously parsing would change it to zero,
313
332
  // in case the parser needs to extract data from the table cell attributes
314
- v = parsers[j].format(t, table, c[0].cells[j], j);
333
+ v = parsers[j].format(t, table, $row[0].cells[j], j);
315
334
  cols.push(v);
316
335
  if ((parsers[j].type || '').toLowerCase() === "numeric") {
317
- colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0); // determine column max value (ignore sign)
336
+ // determine column max value (ignore sign)
337
+ colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0);
318
338
  }
319
339
  }
320
- cols.push(tc.cache[k].normalized.length); // add position for rowCache
321
- tc.cache[k].normalized.push(cols);
340
+ // ensure rowData is always in the same location (after the last column)
341
+ cols[c.columns] = rowData;
342
+ cc.normalized.push(cols);
322
343
  }
323
- tc.cache[k].colMax = colMax;
344
+ cc.colMax = colMax;
324
345
  }
325
346
  }
326
- if (tc.showProcessing) {
347
+ if (c.showProcessing) {
327
348
  ts.isProcessing(table); // remove processing icon
328
349
  }
329
- if (tc.debug) {
350
+ if (c.debug) {
330
351
  benchmark("Building cache for " + totalRows + " rows", cacheTime);
331
352
  }
332
353
  }
@@ -337,11 +358,11 @@
337
358
  wo = c.widgetOptions,
338
359
  b = table.tBodies,
339
360
  rows = [],
340
- c2 = c.cache,
341
- r, n, totalRows, checkCell, $bk, $tb,
342
- i, j, k, l, pos, appendTime;
361
+ cc = c.cache,
362
+ n, totalRows, $bk, $tb,
363
+ i, k, appendTime;
343
364
  // empty table - fixes #206/#346
344
- if (isEmptyObject(c2)) {
365
+ if (isEmptyObject(cc)) {
345
366
  // run pager appender in case the table was just emptied
346
367
  return c.appender ? c.appender(table, rows) :
347
368
  table.isUpdating ? c.$table.trigger("updateComplete", table) : ''; // Fixes #532
@@ -354,19 +375,13 @@
354
375
  if ($bk.length && !$bk.hasClass(c.cssInfoBlock)) {
355
376
  // get tbody
356
377
  $tb = ts.processTbody(table, $bk, true);
357
- r = c2[k].row;
358
- n = c2[k].normalized;
378
+ n = cc[k].normalized;
359
379
  totalRows = n.length;
360
- checkCell = totalRows ? (n[0].length - 1) : 0;
361
380
  for (i = 0; i < totalRows; i++) {
362
- pos = n[i][checkCell];
363
- rows.push(r[pos]);
381
+ rows.push(n[i][c.columns].$row);
364
382
  // removeRows used by the pager plugin; don't render if using ajax - fixes #411
365
383
  if (!c.appender || (c.pager && (!c.pager.removeRows || !wo.pager_removeRows) && !c.pager.ajax)) {
366
- l = r[pos].length;
367
- for (j = 0; j < l; j++) {
368
- $tb.append(r[pos][j]);
369
- }
384
+ $tb.append(n[i][c.columns].$row);
370
385
  }
371
386
  }
372
387
  // restore tbody
@@ -386,52 +401,6 @@
386
401
  }
387
402
  }
388
403
 
389
- // computeTableHeaderCellIndexes from:
390
- // http://www.javascripttoolbox.com/lib/table/examples.php
391
- // http://www.javascripttoolbox.com/temp/table_cellindex.html
392
- function computeThIndexes(t) {
393
- var matrix = [],
394
- lookup = {},
395
- cols = 0, // determine the number of columns
396
- trs = $(t).children('thead, tfoot').children('tr'), // children tr in tfoot - see issue #196 & #547
397
- i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
398
- for (i = 0; i < trs.length; i++) {
399
- cells = trs[i].cells;
400
- for (j = 0; j < cells.length; j++) {
401
- c = cells[j];
402
- rowIndex = c.parentNode.rowIndex;
403
- cellId = rowIndex + "-" + $(c).index();
404
- rowSpan = c.rowSpan || 1;
405
- colSpan = c.colSpan || 1;
406
- if (typeof(matrix[rowIndex]) === "undefined") {
407
- matrix[rowIndex] = [];
408
- }
409
- // Find first available column in the first row
410
- for (k = 0; k < matrix[rowIndex].length + 1; k++) {
411
- if (typeof(matrix[rowIndex][k]) === "undefined") {
412
- firstAvailCol = k;
413
- break;
414
- }
415
- }
416
- lookup[cellId] = firstAvailCol;
417
- cols = Math.max(firstAvailCol, cols);
418
- // add data-column
419
- $(c).attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
420
- for (k = rowIndex; k < rowIndex + rowSpan; k++) {
421
- if (typeof(matrix[k]) === "undefined") {
422
- matrix[k] = [];
423
- }
424
- matrixrow = matrix[k];
425
- for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
426
- matrixrow[l] = "x";
427
- }
428
- }
429
- }
430
- }
431
- // may not be accurate if # header columns !== # tbody columns
432
- t.config.columns = cols + 1; // add one because it's a zero-based index
433
- }
434
-
435
404
  function formatSortingOrder(v) {
436
405
  // look for "d" in "desc" order; return true
437
406
  return (/^d/i.test(v) || v === 1);
@@ -446,7 +415,8 @@
446
415
  if (c.debug) {
447
416
  time = new Date();
448
417
  }
449
- computeThIndexes(table);
418
+ // children tr in tfoot - see issue #196 & #547
419
+ c.columns = ts.computeColumnIndex( c.$table.children('thead, tfoot').children('tr') );
450
420
  // add icon if cssIcon option exists
451
421
  i = c.cssIcon ? '<i class="' + ( c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon ) + '"></i>' : '';
452
422
  c.$headers = $(table).find(c.selectorHeaders).each(function(index) {
@@ -701,8 +671,8 @@
701
671
 
702
672
  // sort multiple columns
703
673
  function multisort(table) { /*jshint loopfunc:true */
704
- var i, k, num, col, colMax, cache, lc,
705
- order, orgOrderCol, sortTime, sort, x, y,
674
+ var i, k, num, col, sortTime, colMax,
675
+ cache, order, sort, x, y,
706
676
  dir = 0,
707
677
  c = table.config,
708
678
  cts = c.textSorter || '',
@@ -716,8 +686,7 @@
716
686
  for (k = 0; k < bl; k++) {
717
687
  colMax = c.cache[k].colMax;
718
688
  cache = c.cache[k].normalized;
719
- lc = cache.length;
720
- orgOrderCol = (cache && cache[0]) ? cache[0].length - 1 : 0;
689
+
721
690
  cache.sort(function(a, b) {
722
691
  // cache is undefined here in IE, so don't use it!
723
692
  for (i = 0; i < l; i++) {
@@ -727,7 +696,7 @@
727
696
  dir = order === 0;
728
697
 
729
698
  if (c.sortStable && a[col] === b[col] && l === 1) {
730
- return a[orgOrderCol] - b[orgOrderCol];
699
+ return a[c.columns].order - b[c.columns].order;
731
700
  }
732
701
 
733
702
  // fallback to natural sort since it is more robust
@@ -761,7 +730,7 @@
761
730
  }
762
731
  if (sort) { return sort; }
763
732
  }
764
- return a[orgOrderCol] - b[orgOrderCol];
733
+ return a[c.columns].order - b[c.columns].order;
765
734
  });
766
735
  }
767
736
  if (c.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); }
@@ -772,7 +741,7 @@
772
741
  if (table.isUpdating) {
773
742
  $table.trigger('updateComplete');
774
743
  }
775
- if (typeof callback === "function") {
744
+ if ($.isFunction(callback)) {
776
745
  callback($table[0]);
777
746
  }
778
747
  }
@@ -786,8 +755,8 @@
786
755
  resortComplete($table, callback);
787
756
  }, true]);
788
757
  } else {
789
- $table.trigger('applyWidgets');
790
758
  resortComplete($table, callback);
759
+ ts.applyWidget($table[0], false);
791
760
  }
792
761
  }
793
762
 
@@ -797,12 +766,15 @@
797
766
  // apply easy methods that trigger bound events
798
767
  $table
799
768
  .unbind('sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '.split(' ').join(c.namespace + ' '))
800
- .bind("sortReset" + c.namespace, function(e){
769
+ .bind("sortReset" + c.namespace, function(e, callback){
801
770
  e.stopPropagation();
802
771
  c.sortList = [];
803
772
  setHeadersCss(table);
804
773
  multisort(table);
805
774
  appendToTable(table);
775
+ if ($.isFunction(callback)) {
776
+ callback(table);
777
+ }
806
778
  })
807
779
  .bind("updateAll" + c.namespace, function(e, resort, callback){
808
780
  e.stopPropagation();
@@ -826,20 +798,24 @@
826
798
  table.isUpdating = true;
827
799
  $table.find(c.selectorRemove).remove();
828
800
  // get position from the dom
829
- var l, row, icell,
801
+ var v, row, icell,
830
802
  $tb = $table.find('tbody'),
803
+ $cell = $(cell),
831
804
  // update cache - format: function(s, table, cell, cellIndex)
832
805
  // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
833
- tbdy = $tb.index( $(cell).parents('tbody').filter(':first') ),
834
- $row = $(cell).parents('tr').filter(':first');
835
- cell = $(cell)[0]; // in case cell is a jQuery object
806
+ tbdy = $tb.index( $cell.parents('tbody').filter(':first') ),
807
+ $row = $cell.parents('tr').filter(':first');
808
+ cell = $cell[0]; // in case cell is a jQuery object
836
809
  // tbody may not exist if update is initialized while tbody is removed for processing
837
810
  if ($tb.length && tbdy >= 0) {
838
811
  row = $tb.eq(tbdy).find('tr').index( $row );
839
- icell = $(cell).index();
840
- l = c.cache[tbdy].normalized[row].length - 1;
841
- c.cache[tbdy].row[ c.cache[tbdy].normalized[row][l] ] = $row;
842
- c.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(table, cell, icell), table, cell, icell );
812
+ icell = $cell.index();
813
+ c.cache[tbdy].normalized[row][c.columns].$row = $row;
814
+ v = c.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(table, cell, icell), table, cell, icell );
815
+ if ((c.parsers[icell].type || '').toLowerCase() === "numeric") {
816
+ // update column max value (ignore sign)
817
+ c.cache[tbdy].colMax[icell] = Math.max(Math.abs(v) || 0, c.cache[tbdy].colMax[icell] || 0);
818
+ }
843
819
  checkResort($table, resort, callback);
844
820
  }
845
821
  })
@@ -851,26 +827,34 @@
851
827
  updateHeader(table);
852
828
  commonUpdate(table, resort, callback);
853
829
  } else {
854
- var i, j,
830
+ var i, j, l, rowData, cells,
855
831
  rows = $row.filter('tr').length,
856
- dat = [], l = $row[0].cells.length,
857
832
  tbdy = $table.find('tbody').index( $row.parents('tbody').filter(':first') );
858
833
  // fixes adding rows to an empty table - see issue #179
859
- if (!c.parsers) {
834
+ if (!(c.parsers && c.parsers.length)) {
860
835
  buildParserCache(table);
861
836
  }
862
837
  // add each row
863
838
  for (i = 0; i < rows; i++) {
839
+ l = $row[i].cells.length;
840
+ cells = [];
841
+ rowData = {
842
+ child: [],
843
+ $row : $row.eq(i),
844
+ order: c.cache[tbdy].normalized.length
845
+ };
864
846
  // add each cell
865
847
  for (j = 0; j < l; j++) {
866
- dat[j] = c.parsers[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
848
+ cells[j] = c.parsers[j].format( getElementText(table, $row[i].cells[j], j), table, $row[i].cells[j], j );
849
+ if ((c.parsers[j].type || '').toLowerCase() === "numeric") {
850
+ // update column max value (ignore sign)
851
+ c.cache[tbdy].colMax[j] = Math.max(Math.abs(cells[j]) || 0, c.cache[tbdy].colMax[j] || 0);
852
+ }
867
853
  }
868
- // add the row index to the end
869
- dat.push(c.cache[tbdy].row.length);
854
+ // add the row data to the end
855
+ cells.push(rowData);
870
856
  // update cache
871
- c.cache[tbdy].row.push([$row[i]]);
872
- c.cache[tbdy].normalized.push(dat);
873
- dat = [];
857
+ c.cache[tbdy].normalized.push(cells);
874
858
  }
875
859
  // resort using current settings
876
860
  checkResort($table, resort, callback);
@@ -893,28 +877,27 @@
893
877
  // sort the table and append it to the dom
894
878
  multisort(table);
895
879
  appendToTable(table, init);
896
- $table
897
- .trigger("sortEnd", this)
898
- .trigger('applyWidgets');
899
- if (typeof callback === "function") {
880
+ $table.trigger("sortEnd", this);
881
+ ts.applyWidget(table);
882
+ if ($.isFunction(callback)) {
900
883
  callback(table);
901
884
  }
902
885
  })
903
886
  .bind("appendCache" + c.namespace, function(e, callback, init) {
904
887
  e.stopPropagation();
905
888
  appendToTable(table, init);
906
- if (typeof callback === "function") {
889
+ if ($.isFunction(callback)) {
907
890
  callback(table);
908
891
  }
909
892
  })
910
893
  .bind("updateCache" + c.namespace, function(e, callback){
911
894
  // rebuild parsers
912
- if (!c.parsers) {
895
+ if (!(c.parsers && c.parsers.length)) {
913
896
  buildParserCache(table);
914
897
  }
915
898
  // rebuild the cache map
916
899
  buildCache(table);
917
- if (typeof callback === "function") {
900
+ if ($.isFunction(callback)) {
918
901
  callback(table);
919
902
  }
920
903
  })
@@ -972,8 +955,6 @@
972
955
  $.data(table, "tablesorter", c);
973
956
  if (c.debug) { $.data( table, 'startoveralltimer', new Date()); }
974
957
 
975
- // constants
976
- c.supportsTextContent = $('<span>x</span>')[0].textContent === 'x';
977
958
  // removing this in version 3 (only supports jQuery 1.7+)
978
959
  c.supportsDataObject = (function(version) {
979
960
  version[0] = parseInt(version[0], 10);
@@ -1005,6 +986,8 @@
1005
986
  c.$table.attr('aria-labelledby', 'theCaption');
1006
987
  }
1007
988
  c.widgetInit = {}; // keep a list of initialized widgets
989
+ // change textExtraction via data-attribute
990
+ c.textExtraction = c.$table.attr('data-text-extraction') || c.textExtraction || 'basic';
1008
991
  // build headers
1009
992
  buildHeaders(table);
1010
993
  // fixate columns if the users supplies the fixedWidth option
@@ -1034,7 +1017,9 @@
1034
1017
  setHeadersCss(table);
1035
1018
  if (c.initWidgets) {
1036
1019
  // apply widget format
1037
- ts.applyWidget(table);
1020
+ setTimeout(function(){
1021
+ ts.applyWidget(table, false);
1022
+ }, 0);
1038
1023
  }
1039
1024
  }
1040
1025
 
@@ -1057,6 +1042,53 @@
1057
1042
  if (typeof c.initialized === 'function') { c.initialized(table); }
1058
1043
  };
1059
1044
 
1045
+
1046
+ // computeTableHeaderCellIndexes from:
1047
+ // http://www.javascripttoolbox.com/lib/table/examples.php
1048
+ // http://www.javascripttoolbox.com/temp/table_cellindex.html
1049
+ ts.computeColumnIndex = function(trs) {
1050
+ var matrix = [],
1051
+ lookup = {},
1052
+ cols = 0, // determine the number of columns
1053
+ i, j, k, l, $cell, cell, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow;
1054
+ for (i = 0; i < trs.length; i++) {
1055
+ cells = trs[i].cells;
1056
+ for (j = 0; j < cells.length; j++) {
1057
+ cell = cells[j];
1058
+ $cell = $(cell);
1059
+ rowIndex = cell.parentNode.rowIndex;
1060
+ cellId = rowIndex + "-" + $cell.index();
1061
+ rowSpan = cell.rowSpan || 1;
1062
+ colSpan = cell.colSpan || 1;
1063
+ if (typeof(matrix[rowIndex]) === "undefined") {
1064
+ matrix[rowIndex] = [];
1065
+ }
1066
+ // Find first available column in the first row
1067
+ for (k = 0; k < matrix[rowIndex].length + 1; k++) {
1068
+ if (typeof(matrix[rowIndex][k]) === "undefined") {
1069
+ firstAvailCol = k;
1070
+ break;
1071
+ }
1072
+ }
1073
+ lookup[cellId] = firstAvailCol;
1074
+ cols = Math.max(firstAvailCol, cols);
1075
+ // add data-column
1076
+ $cell.attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex
1077
+ for (k = rowIndex; k < rowIndex + rowSpan; k++) {
1078
+ if (typeof(matrix[k]) === "undefined") {
1079
+ matrix[k] = [];
1080
+ }
1081
+ matrixrow = matrix[k];
1082
+ for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
1083
+ matrixrow[l] = "x";
1084
+ }
1085
+ }
1086
+ }
1087
+ }
1088
+ // may not be accurate if # header columns !== # tbody columns
1089
+ return cols + 1; // add one because it's a zero-based index
1090
+ };
1091
+
1060
1092
  // *** Process table ***
1061
1093
  // add processing indicator
1062
1094
  ts.isProcessing = function(table, toggle, $ths) {
@@ -1186,6 +1218,7 @@
1186
1218
  $t.toggleClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false);
1187
1219
  // clear flag in case the plugin is initialized again
1188
1220
  table.hasInitialized = false;
1221
+ delete table.config.cache;
1189
1222
  if (typeof callback === 'function') {
1190
1223
  callback(table);
1191
1224
  }
@@ -1387,8 +1420,11 @@
1387
1420
  wo = c.widgetOptions,
1388
1421
  widgets = [],
1389
1422
  time, w, wd;
1423
+ // prevent numerous consecutive widget applications
1424
+ if (init !== false && table.hasInitialized && (table.isApplyingWidgets || table.isUpdating)) { return; }
1390
1425
  if (c.debug) { time = new Date(); }
1391
1426
  if (c.widgets.length) {
1427
+ table.isApplyingWidgets = true;
1392
1428
  // ensure unique widget ids
1393
1429
  c.widgets = $.grep(c.widgets, function(v, k){
1394
1430
  return $.inArray(v, c.widgets) === k;
@@ -1424,6 +1460,9 @@
1424
1460
  }
1425
1461
  });
1426
1462
  }
1463
+ setTimeout(function(){
1464
+ table.isApplyingWidgets = false;
1465
+ }, 0);
1427
1466
  if (c.debug) {
1428
1467
  w = c.widgets.length;
1429
1468
  benchmark("Completed " + (init === true ? "initializing " : "applying ") + w + " widget" + (w !== 1 ? "s" : ""), time);