From 28e9a07d597c5a618a026b7319b592c73683ca98 Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Thu, 3 May 2012 09:46:31 -0500 Subject: [PATCH] sort multiple tbodies --- addons/pager/jquery.tablesorter.pager.js | 2 +- js/jquery.tablesorter.js | 246 ++++++++++++----------- js/jquery.tablesorter.widgets.js | 36 ++-- 3 files changed, 155 insertions(+), 129 deletions(-) diff --git a/addons/pager/jquery.tablesorter.pager.js b/addons/pager/jquery.tablesorter.pager.js index d505fa6d..32c4925d 100644 --- a/addons/pager/jquery.tablesorter.pager.js +++ b/addons/pager/jquery.tablesorter.pager.js @@ -235,7 +235,7 @@ if (e > rows.length ) { e = rows.length; } - $(table.tBodies[0]).empty(); + $.tablesorter.clearTableBody(table); for (i = s; i < e; i++) { o = rows[i]; l = o.length; diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index b5340762..41817cfe 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -26,6 +26,7 @@ cssAsc: "tablesorter-headerSortUp", cssDesc: "tablesorter-headerSortDown", cssChildRow: "expand-child", + cssInfoBlock: "tablesorter-infoOnly", sortInitialOrder: "asc", sortMultiSortKey: "shiftKey", sortForce: null, @@ -196,42 +197,44 @@ /* utils */ function buildCache(table) { - var b = table.tBodies[0], - totalRows = (b && b.rows.length) || 0, - totalCells = (b.rows[0] && b.rows[0].cells.length) || 0, - parsers = table.config.parsers, - cache = { - row: [], - normalized: [] - }, + var b = table.tBodies, + tc = table.config, + totalRows, + totalCells, + parsers = tc.parsers, t, i, j, c, cols, cacheTime; - if (table.config.debug) { + tc.cache = {}; + if (tc.debug) { cacheTime = new Date(); } - for (i = 0; i < totalRows; ++i) { - /** Add the table data to main data array */ - c = $(b.rows[i]); - cols = []; - // if this is a child row, add it to the last row's children and continue to the next row - if (c.hasClass(table.config.cssChildRow)) { - cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); - // go to the next for loop - continue; + for (k = 0; k < b.length; k++) { + tc.cache[k] = { row: [], normalized: [] }; + totalRows = (b[k] && b[k].rows.length) || 0; + totalCells = (b[k].rows[0] && b[k].rows[0].cells.length) || 0; + + for (i = 0; i < totalRows; ++i) { + /** Add the table data to main data array */ + c = $(b[k].rows[i]); + cols = []; + // if this is a child row, add it to the last row's children and continue to the next row + if (c.hasClass(tc.cssChildRow)) { + tc.cache[k].row[tc.cache[k].row.length - 1] = tc.cache[k].row[tc.cache[k].row.length - 1].add(c); + // go to the next for loop + continue; + } + tc.cache[k].row.push(c); + for (j = 0; j < totalCells; ++j) { + t = trimAndGetNodeText(tc, c[0].cells[j], j); + // don't bother parsing if the string is empty - previously parsing would change it to zero + cols.push( parsers[j].format(t, table, c[0].cells[j], j) ); + } + cols.push(tc.cache[k].normalized.length); // add position for rowCache + tc.cache[k].normalized.push(cols); } - cache.row.push(c); - for (j = 0; j < totalCells; ++j) { - t = trimAndGetNodeText(table.config, c[0].cells[j], j); - // don't bother parsing if the string is empty - previously parsing would change it to zero - cols.push( parsers[j].format(t, table, c[0].cells[j], j) ); - } - cols.push(cache.normalized.length); // add position for rowCache - cache.normalized.push(cols); } - if (table.config.debug) { + if (tc.debug) { benchmark("Building cache for " + totalRows + " rows", cacheTime); } - table.config.cache = cache; - return cache; } function getWidgetById(name) { @@ -259,30 +262,35 @@ } } - function appendToTable(table, cache) { + function appendToTable(table) { var c = table.config, - r = cache.row, - n = cache.normalized, - totalRows = n.length, - checkCell = totalRows ? (n[0].length - 1) : 0, + b = table.tBodies, rows = [], - f = document.createDocumentFragment(), - i, j, l, pos, appendTime; + r, n, totalRows, checkCell, + f, i, j, l, pos, appendTime; if (c.debug) { appendTime = new Date(); } - for (i = 0; i < totalRows; i++) { - pos = n[i][checkCell]; - rows.push(r[pos]); - // removeRows used by the pager plugin - if (!c.appender || !c.removeRows) { - l = r[pos].length; - for (j = 0; j < l; j++) { - f.appendChild(r[pos][j]); + for (k = 0; k < b.length; k++) { + f = document.createDocumentFragment(); + r = c.cache[k].row; + n = c.cache[k].normalized; + totalRows = n.length; + checkCell = totalRows ? (n[0].length - 1) : 0; + + for (i = 0; i < totalRows; i++) { + pos = n[i][checkCell]; + rows.push(r[pos]); + // removeRows used by the pager plugin + if (!c.appender || !c.removeRows) { + l = r[pos].length; + for (j = 0; j < l; j++) { + f.appendChild(r[pos][j]); + } } } + table.tBodies[k].appendChild(f); } - table.tBodies[0].appendChild(f); if (c.appender) { c.appender(table, rows); } @@ -464,46 +472,50 @@ } /* sorting methods - reverted sorting method back to version 2.0.3 */ - function multisort(table,sortList,cache) { - var dynamicExp = "var sortWrapper = function(a,b) {", - col, mx = 0, dir = 0, tc = table.config, lc = cache.normalized.length, - l = sortList.length, sortTime, i, j, c, s, e, order, orgOrderCol; + function multisort(table, sortList) { + var dynamicExp, col, mx = 0, dir = 0, tc = table.config, + l = sortList.length, bl = table.tBodies.length, + sortTime, i, j, c, cache, lc, s, e, order, orgOrderCol; if (tc.debug) { sortTime = new Date(); } - for (i=0; i < l; i++) { - c = sortList[i][0]; - order = sortList[i][1]; - s = getCachedSortType(tc.parsers,c) === "text" ? "Text" : "Numeric"; - s += order === 0 ? "" : "Desc"; - e = "e" + i; - // get max column value (ignore sign) - if (/Numeric/.test(s) && tc.strings[c]) { - for (j=0; j < lc; j++) { - col = Math.abs(parseFloat(cache.normalized[j][c])); - mx = Math.max( mx, isNaN(col) ? 0 : col ); - } - // sort strings in numerical columns - if (typeof(tc.string[tc.strings[c]]) === 'boolean') { - dir = (order === 0 ? 1 : -1) * (tc.string[tc.strings[c]] ? -1 : 1); - } else { - dir = (tc.strings[c]) ? tc.string[tc.strings[c]] || 0 : 0; + for (k = 0; k < bl; k++) { + dynamicExp = "var sortWrapper = function(a,b) {"; + cache = tc.cache[k]; + lc = cache.normalized.length; + for (i = 0; i < l; i++) { + c = sortList[i][0]; + order = sortList[i][1]; + s = getCachedSortType(tc.parsers,c) === "text" ? "Text" : "Numeric"; + s += order === 0 ? "" : "Desc"; + e = "e" + i; + // get max column value (ignore sign) + if (/Numeric/.test(s) && tc.strings[c]) { + for (j = 0; j < lc; j++) { + col = Math.abs(parseFloat(cache.normalized[j][c])); + mx = Math.max( mx, isNaN(col) ? 0 : col ); + } + // sort strings in numerical columns + if (typeof(tc.string[tc.strings[c]]) === 'boolean') { + dir = (order === 0 ? 1 : -1) * (tc.string[tc.strings[c]] ? -1 : 1); + } else { + dir = (tc.strings[c]) ? tc.string[tc.strings[c]] || 0 : 0; + } } + dynamicExp += "var " + e + " = sort" + s + "(a[" + c + "],b[" + c + "]," + c + "," + mx + "," + dir + "); "; + dynamicExp += "if (" + e + ") { return " + e + "; } "; + dynamicExp += "else { "; } - dynamicExp += "var " + e + " = sort" + s + "(a[" + c + "],b[" + c + "]," + c + "," + mx + "," + dir + "); "; - dynamicExp += "if (" + e + ") { return " + e + "; } "; - dynamicExp += "else { "; - } - // if value is the same keep orignal order - orgOrderCol = (cache.normalized && cache.normalized[0]) ? cache.normalized[0].length - 1 : 0; - dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; - for (i=0; i < l; i++) { + // if value is the same keep orignal order + orgOrderCol = (cache.normalized && cache.normalized[0]) ? cache.normalized[0].length - 1 : 0; + dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; + for (i=0; i < l; i++) { + dynamicExp += "}; "; + } + dynamicExp += "return 0; "; dynamicExp += "}; "; + eval(dynamicExp); + cache.normalized.sort(sortWrapper); // sort using eval expression } - dynamicExp += "return 0; "; - dynamicExp += "}; "; - eval(dynamicExp); - cache.normalized.sort(sortWrapper); // sort using eval expression if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order+ " time", sortTime); } - return cache; } // Natural sort modified from: http://www.webdeveloper.com/forum/showthread.php?t=107909 @@ -588,7 +600,7 @@ // if no thead or tbody quit. if (!this.tHead || this.tBodies.length === 0) { return; } // declare - var $this, $headers, cache, config, + var $this, $headers, config, totalRows, $cell, c, i, j, k, a, s, o; // new blank config object this.config = {}; @@ -605,14 +617,14 @@ // try to auto detect column type, and store in tables config c.parsers = buildParserCache(this, $headers); // build the cache for the tbody cells - cache = buildCache(this); + buildCache(this); // fixate columns if the users supplies the fixedWidth option fixColumnWidth(this); // apply event handling to headers // this is to big, perhaps break it out? $headers .click(function(e) { - totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; + // totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; if (!this.sortDisabled) { // Only call sortStart if sorting is enabled. $this.trigger("sortStart", tbl[0]); @@ -680,7 +692,7 @@ $this.trigger("sortBegin", tbl[0]); // set css for headers setHeadersCss($this[0], $headers, c.sortList); - appendToTable($this[0], multisort($this[0], c.sortList, cache)); + appendToTable($this[0], multisort($this[0], c.sortList)); // stop normal event by returning false return false; } @@ -697,45 +709,44 @@ // apply easy methods that trigger binded events $this .bind("update", function(e, resort) { - var t = this, c = t.config; // remove rows/elements before update - $(c.selectorRemove, t.tBodies[0]).remove(); + $(c.selectorRemove, this).remove(); // rebuild parsers. - c.parsers = buildParserCache(t, $headers); + c.parsers = buildParserCache(this, $headers); // rebuild the cache map - cache = buildCache(t); + buildCache(this); if (resort !== false) { $this.trigger("sorton", [c.sortList]); } }) .bind("updateCell", function(e, cell, resort) { // get position from the dom. - var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex]; + var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex], // update cache - format: function(s, table, cell, cellIndex) - cache.normalized[pos[0]][pos[1]] = c.parsers[pos[1]].format(getElementText(c, cell, pos[1]), $this, cell, pos[1]); - c.cache = cache; + tbodyindex = $(cell).closest('tbody').index(); + table.cache[tbodyindex].normalized[pos[0]][pos[1]] = c.parsers[pos[1]].format(getElementText(c, cell, pos[1]), $this, cell, pos[1]); if (resort !== false) { $this.trigger("sorton", [c.sortList]); } }) - .bind("addRows", function(e, row, resort) { - var i, rows = row.filter('tr').length, - dat = [], l = row[0].cells.length; + .bind("addRows", function(e, $row, resort) { + var i, rows = $row.filter('tr').length, + dat = [], l = $row[0].cells.length, + tbodyindex = $row.closest('tbody').index(); // add each row for (i = 0; i < rows; i++) { // add each cell for (j = 0; j < l; j++) { - dat[j] = c.parsers[j].format(getElementText(c, row[i].cells[j], j), $this, row[i].cells[j], j ); + dat[j] = c.parsers[j].format(getElementText(c, $row[i].cells[j], j), $this, $row[i].cells[j], j ); } // add the row index to the end - dat.push(cache.row.length); + dat.push(c.cache[tbodyindex].row.length); // update cache - cache.row.push([row[i]]); - cache.normalized.push(dat); + c.cache[tbodyindex].row.push([$row[i]]); + c.cache[tbodyindex].normalized.push(dat); dat = []; } - c.cache = cache; // resort using current settings if (resort !== false) { $this.trigger("sorton", [c.sortList]); } }) .bind("sorton", function(e, list) { - $(this).trigger("sortStart", tbl[0]); + $this.trigger("sortStart", tbl[0]); c.sortList = list; // update and store the sortlist var sortList = c.sortList; @@ -744,10 +755,10 @@ // set css for headers setHeadersCss(this, $headers, sortList); // sort the table and append it to the dom - appendToTable(this, multisort(this, sortList, cache)); + appendToTable(this, multisort(this, sortList)); }) .bind("appendCache", function () { - appendToTable(this, cache); + appendToTable(this); }) .bind("applyWidgetId", function (e, id) { getWidgetById(id).format(this); @@ -769,6 +780,8 @@ applyWidget(this); } this.hasInitialized = true; + $this.trigger('tablesorter-initialized', this); + if (typeof c.initialized === 'function') { c.initialized(this); } }); }; this.addParser = function(parser) { @@ -807,7 +820,7 @@ return (/^[\-+(]?\d*[)]?$/).test($.trim(s.replace(/[,.'\s]/g, ''))); }; this.clearTableBody = function (table) { - $(table.tBodies[0]).empty(); + $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty(); }; } })(); @@ -970,9 +983,10 @@ ts.addWidget({ id: "zebra", format: function(table) { - var $tr, row = 0, even, time, + var $tr, row, even, time, k, c = table.config, child = c.cssChildRow, + b = table.tBodies, css = [ "even", "odd" ]; // maintain backwards compatibility css = c.widgetZebra && c.hasOwnProperty('css') ? c.widgetZebra.css : @@ -980,16 +994,22 @@ if (table.config.debug) { time = new Date(); } - // loop through the visible rows - $("tr:visible", table.tBodies[0]).each(function() { - $tr = $(this); - // style children rows the same way the parent row was styled - if (!$tr.hasClass(child)) { row++; } - even = (row % 2 === 0); - $tr - .removeClass(css[even ? 1 : 0]) - .addClass(css[even ? 0 : 1]); - }); + for (k = 0; k < b.length; k++ ) { + row = 0; + // loop through the visible rows + $tr = $(b[k]).filter(':not(' + c.cssInfoBlock + ')').find('tr:visible:not(.' + c.cssInfoBlock + ')'); + if ($tr.length > 1) { + $tr.each(function() { + $tr = $(this); + // style children rows the same way the parent row was styled + if (!$tr.hasClass(child)) { row++; } + even = (row % 2 === 0); + $tr + .removeClass(css[even ? 1 : 0]) + .addClass(css[even ? 0 : 1]); + }); + } + } if (table.config.debug) { $.tablesorter.benchmark("Applying Zebra widget", time); } diff --git a/js/jquery.tablesorter.widgets.js b/js/jquery.tablesorter.widgets.js index b97be769..8dadead9 100644 --- a/js/jquery.tablesorter.widgets.js +++ b/js/jquery.tablesorter.widgets.js @@ -125,8 +125,9 @@ $.tablesorter.addWidget({ $.tablesorter.addWidget({ id: "columns", format: function(table) { - var $td, time, i, last, rmv, + var $tr, $td, time, i, last, rmv, k, c = table.config, + b = table.tBodies, list = c.sortList, len = list.length, css = [ "primary", "secondary", "tertiary" ]; // default options @@ -140,21 +141,24 @@ $.tablesorter.addWidget({ } // check if there is a sort (on initialization there may not be one) if (list && list[0]) { - // loop through the visible rows - $("tr:visible", table.tBodies[0]).each(function (i) { - $td = $(this).children().removeClass(rmv); - // primary sort column class - $td.eq(list[0][0]).addClass(css[0]); - if (len > 1) { - for (i=1; i 1) { + for (i=1; i offset.top) && (sTop < offset.top + $table.find('tbody').height())) ? 'visible' : 'hidden'; + tableHt = $table.height() - (firstCell.height() + (tfoot.height() || 0)), + vis = (sTop > offset.top) && (sTop < offset.top + tableHt) ? 'visible' : 'hidden'; sticky.css({ left : offset.left - win.scrollLeft(), visibility : vis