From 2a221fa72fd8491ca1a0633ba62b6a846267b4fc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 9 Jul 2012 00:40:17 -0500 Subject: Upgrade to jQuery 1.7.2 and a maintained tablesorter This touches a wide variety of files as well as makes updates to some of our own code to be fully compatible. We also use some of the newer locale/accent sorting features of tablesorter to make tables with developer names sort in a more sane fashion. Signed-off-by: Dan McGee --- sitestatic/jquery.tablesorter-2.3.11.js | 1181 +++++++++++++++++++++++++++++++ 1 file changed, 1181 insertions(+) create mode 100644 sitestatic/jquery.tablesorter-2.3.11.js (limited to 'sitestatic/jquery.tablesorter-2.3.11.js') diff --git a/sitestatic/jquery.tablesorter-2.3.11.js b/sitestatic/jquery.tablesorter-2.3.11.js new file mode 100644 index 00000000..e52e66dc --- /dev/null +++ b/sitestatic/jquery.tablesorter-2.3.11.js @@ -0,0 +1,1181 @@ +/*! +* TableSorter 2.3.11 - Client-side table sorting with ease! +* @requires jQuery v1.2.6+ +* +* Copyright (c) 2007 Christian Bach +* Examples and docs at: http://tablesorter.com +* Dual licensed under the MIT and GPL licenses: +* http://www.opensource.org/licenses/mit-license.php +* http://www.gnu.org/licenses/gpl.html +* +* @type jQuery +* @name tablesorter +* @cat Plugins/Tablesorter +* @author Christian Bach/christian.bach@polyester.se +* @contributor Rob Garrison/https://github.com/Mottie/tablesorter +*/ +!(function($) { + $.extend({ + tablesorter: new function() { + + this.version = "2.3.11"; + + var parsers = [], widgets = []; + this.defaults = { + + // appearance + widthFixed : false, // adds colgroup to fix widths of columns + + // functionality + cancelSelection : true, // prevent text selection in the header + dateFormat : "mmddyyyy", // other options: "ddmmyyy" or "yyyymmdd" + sortMultiSortKey : "shiftKey", // key used to select additional columns + usNumberFormat : true, // false for German "1.234.567,89" or French "1 234 567,89" + delayInit : false, // if false, the parsed table contents will not update until the first sort. + + // sort options + headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc. + ignoreCase : true, // ignore case while sorting + sortForce : null, // column(s) first sorted; always applied + sortList : [], // Initial sort order; applied initially; updated when manually sorted + sortAppend : null, // column(s) sorted last; always applied + + sortInitialOrder : "asc", // sort direction on first click + sortLocaleCompare: false, // replace equivalent character (accented characters) + sortReset : false, // third click on the header will reset column to default - unsorted + sortRestart : false, // restart sort to "sortInitialOrder" when clicking on previously unsorted columns + + emptyTo : "bottom", // sort empty cell to bottom, top, none, zero + stringTo : "max", // sort strings in numerical column as max, min, top, bottom, zero + textExtraction : "simple", // text extraction method/function - function(node, table, cellIndex){} + textSorter : null, // use custom text sorter - function(a,b){ return a.sort(b); } // basic sort + + // widget options + widgets: [], // method to add widgets, e.g. widgets: ['zebra'] + widgetOptions : { + zebra : [ "even", "odd" ] // zebra widget alternating row class names + }, + initWidgets : true, // apply widgets on tablesorter initialization + + // callbacks + initialized : null, // function(table){}, + onRenderHeader : null, // function(index){}, + + // css class names + tableClass : 'tablesorter', + cssAsc : "tablesorter-headerSortUp", + cssChildRow : "expand-child", + cssDesc : "tablesorter-headerSortDown", + cssHeader : "tablesorter-header", + cssInfoBlock : "tablesorter-infoOnly", // don't sort tbody with this class name + + // selectors + selectorHeaders : '> thead th', + selectorRemove : "tr.remove-me", + + // advanced + debug : false, + + // Internal variables + headerList: [], + empties: {}, + strings: {}, + parsers: [] + + // deprecated; but retained for backwards compatibility + // widgetZebra: { css: ["even", "odd"] } + + }; + + /* debuging utils */ + function log(s) { + if (typeof console !== "undefined" && typeof console.log !== "undefined") { + console.log(s); + } else { + alert(s); + } + } + + function benchmark(s, d) { + log(s + " (" + (new Date().getTime() - d.getTime()) + "ms)"); + } + + this.benchmark = benchmark; + this.hasInitialized = false; + + function getElementText(table, node, cellIndex) { + if (!node) { return ""; } + var c = table.config, + t = c.textExtraction, text = ""; + if (t === "simple") { + if (c.supportsTextContent) { + text = node.textContent; // newer browsers support this + } else { + text = $(node).text(); + } + } else { + if (typeof(t) === "function") { + text = t(node, table, cellIndex); + } else if (typeof(t) === "object" && t.hasOwnProperty(cellIndex)) { + text = t[cellIndex](node, table, cellIndex); + } else { + text = c.supportsTextContent ? node.textContent : $(node).text(); + } + } + return $.trim(text); + } + + /* parsers utils */ + function getParserById(name) { + var i, l = parsers.length; + for (i = 0; i < l; i++) { + if (parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) { + return parsers[i]; + } + } + return false; + } + + function detectParserForColumn(table, rows, rowIndex, cellIndex) { + var i, l = parsers.length, + node = false, + nodeValue = '', + keepLooking = true; + while (nodeValue === '' && keepLooking) { + rowIndex++; + if (rows[rowIndex]) { + node = rows[rowIndex].cells[cellIndex]; + nodeValue = getElementText(table, node, cellIndex); + if (table.config.debug) { + log('Checking if value was empty on row ' + rowIndex + ', column: ' + cellIndex + ': ' + nodeValue); + } + } else { + keepLooking = false; + } + } + for (i = 1; i < l; i++) { + if (parsers[i].is(nodeValue, table, node)) { + return parsers[i]; + } + } + // 0 is always the generic parser (text) + return parsers[0]; + } + + function buildParserCache(table, $headers) { + var c = table.config, + tb = $(table.tBodies).filter(':not(.' + c.cssInfoBlock + ')'), + ts = $.tablesorter, rows, list, l, i, h, m, ch, cl, p, parsersDebug = ""; + if ( tb.length === 0) { return; } // In the case of empty tables + rows = tb[0].rows; + if (rows[0]) { + list = []; + l = rows[0].cells.length; + for (i = 0; i < l; i++) { + // tons of thanks to AnthonyM1229 for working out the following selector (issue #74) to make this work in IE8! + h = $headers.filter(':not([colspan])[data-column="' + i + '"]:last,[colspan="1"][data-column="' + i + '"]:last'); + ch = c.headers[i]; + // get column parser + p = getParserById( ts.getData(h, ch, 'sorter') ); + // empty cells behaviour - keeping emptyToBottom for backwards compatibility. + c.empties[i] = ts.getData(h, ch, 'empty') || c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top' ); + // text strings behaviour in numerical sorts + c.strings[i] = ts.getData(h, ch, 'string') || c.stringTo || 'max'; + if (!p) { + p = detectParserForColumn(table, rows, -1, i); + } + if (c.debug) { + parsersDebug += "column:" + i + "; parser:" + p.id + "; string:" + c.strings[i] + '; empty: ' + c.empties[i] + "\n"; + } + list.push(p); + } + } + if (c.debug) { + log(parsersDebug); + } + return list; + } + + /* utils */ + function buildCache(table) { + var b = table.tBodies, + tc = table.config, + totalRows, + totalCells, + parsers = tc.parsers, + t, i, j, k, c, cols, cacheTime; + tc.cache = {}; + if (tc.debug) { + cacheTime = new Date(); + } + for (k = 0; k < b.length; k++) { + tc.cache[k] = { row: [], normalized: [] }; + // ignore tbodies with class name from css.cssInfoBlock + if (!$(b[k]).hasClass(tc.cssInfoBlock)) { + $(b[k]).addClass('tablesorter-hidden'); + 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 = getElementText(table, c[0].cells[j], j); + // allow parsing if the string is empty, previously parsing would change it to zero, + // in case the parser needs to extract data from the table cell attributes + 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); + } + $(b[k]).removeClass('tablesorter-hidden'); + } + } + if (tc.debug) { + benchmark("Building cache for " + totalRows + " rows", cacheTime); + } + } + + function getWidgetById(name) { + var i, w, l = widgets.length; + for (i = 0; i < l; i++) { + w = widgets[i]; + if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) { + return w; + } + } + } + + function applyWidget(table, init) { + var tc = table.config, c = tc.widgets, + time, i, w, l = c.length; + if (tc.debug) { + time = new Date(); + } + for (i = 0; i < l; i++) { + w = getWidgetById(c[i]); + if ( w ) { + if (init === true && w.hasOwnProperty('init')) { + w.init(table, widgets, w); + } else if (!init && w.hasOwnProperty('format')) { + w.format(table); + } + } + } + if (tc.debug) { + benchmark("Completed " + (init === true ? "initializing" : "applying") + " widgets", time); + } + } + + // init flag (true) used by pager plugin to prevent widget application + function appendToTable(table, init) { + var c = table.config, + b = table.tBodies, + rows = [], + r, n, totalRows, checkCell, c2 = c.cache, + f, i, j, k, l, pos, appendTime; + if (c.debug) { + appendTime = new Date(); + } + for (k = 0; k < b.length; k++) { + if (!$(b[k]).hasClass(c.cssInfoBlock)){ + $(b[k]).addClass('tablesorter-hidden'); + f = document.createDocumentFragment(); + r = c2[k].row; + n = c2[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); + $(b[k]).removeClass('tablesorter-hidden'); + } + } + if (c.appender) { + c.appender(table, rows); + } + if (c.debug) { + benchmark("Rebuilt table", appendTime); + } + // apply table widgets + if (!init) { applyWidget(table); } + // trigger sortend + $(table).trigger("sortEnd", table); + } + + // computeTableHeaderCellIndexes from: + // http://www.javascripttoolbox.com/lib/table/examples.php + // http://www.javascripttoolbox.com/temp/table_cellindex.html + function computeThIndexes(t) { + var matrix = [], + lookup = {}, + trs = $(t).find('thead:eq(0) tr'), + i, j, k, l, c, cells, rowIndex, cellId, rowSpan, colSpan, firstAvailCol, matrixrow; + for (i = 0; i < trs.length; i++) { + cells = trs[i].cells; + for (j = 0; j < cells.length; j++) { + c = cells[j]; + rowIndex = c.parentNode.rowIndex; + cellId = rowIndex + "-" + c.cellIndex; + rowSpan = c.rowSpan || 1; + colSpan = c.colSpan || 1; + if (typeof(matrix[rowIndex]) === "undefined") { + matrix[rowIndex] = []; + } + // Find first available column in the first row + for (k = 0; k < matrix[rowIndex].length + 1; k++) { + if (typeof(matrix[rowIndex][k]) === "undefined") { + firstAvailCol = k; + break; + } + } + lookup[cellId] = firstAvailCol; + // add data-column + $(c).attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex + for (k = rowIndex; k < rowIndex + rowSpan; k++) { + if (typeof(matrix[k]) === "undefined") { + matrix[k] = []; + } + matrixrow = matrix[k]; + for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) { + matrixrow[l] = "x"; + } + } + } + } + return lookup; + } + + function formatSortingOrder(v) { + // look for "d" in "desc" order; return true + return (/^d/i.test(v) || v === 1); + } + + + function buildHeaders(table) { + var header_index = computeThIndexes(table), ch, $t, + $th, lock, time, $tableHeaders, c = table.config, ts = $.tablesorter; + c.headerList = []; + if (c.debug) { + time = new Date(); + } + $tableHeaders = $(table).find(c.selectorHeaders) + .each(function(index) { + $t = $(this); + ch = c.headers[index]; + this.innerHTML = '
' + this.innerHTML + '
'; // faster than wrapInner + if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index]); } + this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex]; + this.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2]; + this.count = -1; // set to -1 because clicking on the header automatically adds one + if (ts.getData($t, ch, 'sorter') === 'false') { this.sortDisabled = true; } + this.lockedOrder = false; + lock = ts.getData($t, ch, 'lockedOrder') || false; + if (typeof(lock) !== 'undefined' && lock !== false) { + this.order = this.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0]; + } + if (!this.sortDisabled) { + $th = $t.addClass(c.cssHeader); + } + // add cell to headerList + c.headerList[index] = this; + // add to parent in case there are multiple rows + $t.parent().addClass(c.cssHeader); + }); + if (table.config.debug) { + benchmark("Built headers:", time); + log($tableHeaders); + } + return $tableHeaders; + } + + function isValueInArray(v, a) { + var i, l = a.length; + for (i = 0; i < l; i++) { + if (a[i][0] === v) { + return true; + } + } + return false; + } + + function setHeadersCss(table, $headers, list) { + var f, h = [], i, j, l, css = [table.config.cssDesc, table.config.cssAsc]; + // remove all header information + $headers + .removeClass(css.join(' ')) + .each(function() { + if (!this.sortDisabled) { + h[this.column] = $(this); + } + }); + l = list.length; + for (i = 0; i < l; i++) { + if (list[i][1] === 2) { continue; } // direction = 2 means reset! + if (h[list[i][0]]) { + // add class if cell exists - fix for issue #78 + h[list[i][0]].addClass(css[list[i][1]]); + } + // multicolumn sorting updating + f = $headers.filter('[data-column="' + list[i][0] + '"]'); + if (l > 1 && f.length) { + for (j = 0; j < f.length; j++) { + if (!f[j].sortDisabled) { + $(f[j]).addClass(css[list[i][1]]); + } + } + } + } + } + + function fixColumnWidth(table) { + if (table.config.widthFixed) { + var colgroup = $(''); + $("tr:first td", table.tBodies[0]).each(function() { + colgroup.append($('').css('width', $(this).width())); + }); + $(table).prepend(colgroup); + } + } + + function updateHeaderSortCount(table, sortList) { + var i, s, o, c = table.config, + l = sortList.length; + for (i = 0; i < l; i++) { + s = sortList[i]; + o = c.headerList[s[0]]; + if (o) { // prevents error if sorton array is wrong + o.count = s[1] % (c.sortReset ? 3 : 2); + } + } + } + + function getCachedSortType(parsers, i) { + return (parsers && parsers[i]) ? parsers[i].type || '' : ''; + } + + /* sorting methods - reverted sorting method back to version 2.0.3 */ + function multisort(table, sortList) { + var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config, + l = sortList.length, bl = table.tBodies.length, + sortTime, i, j, k, c, cache, lc, s, e, order, orgOrderCol; + if (tc.debug) { sortTime = new Date(); } + for (k = 0; k < bl; k++) { + dynamicExp = "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]; + // fallback to natural sort since it is more robust + s = /n/i.test(getCachedSortType(tc.parsers, c)) ? "Numeric" : "Text"; + 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 + " = $.tablesorter.sort" + s + "(table,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++) { + dynamicExp += "}; "; + } + dynamicExp += "return 0; "; + dynamicExp += "}; "; + cache.normalized.sort(eval(dynamicExp)); // sort using eval expression + } + if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order+ " time", sortTime); } + } + + function checkResort($table, flag, callback) { + var t = $table[0]; + if (flag !== false) { + $table.trigger("sorton", [t.config.sortList, function(){ + $table.trigger('updateComplete'); + if (typeof callback === "function") { + callback(t); + } + }]); + } else { + $table.trigger('updateComplete'); + if (typeof callback === "function") { + callback(t); + } + } + } + + /* public methods */ + this.construct = function(settings) { + return this.each(function() { + // if no thead or tbody quit. + if (!this.tHead || this.tBodies.length === 0) { return; } + // declare + var $headers, $cell, $this, + c, i, j, k, a, s, o, downTime, + m = $.metadata; + // new blank config object + this.config = {}; + // merge and extend. + c = $.extend(true, this.config, $.tablesorter.defaults, settings); + + if (c.debug) { $.data( this, 'startoveralltimer', new Date()); } + // store common expression for speed + $this = $(this).addClass(c.tableClass); + // save the settings where they read + $.data(this, "tablesorter", c); + c.supportsTextContent = $('x')[0].textContent === 'x'; + // digit sort text location; keeping max+/- for backwards compatibility + c.string = { 'max': 1, 'min': -1, 'max+': 1, 'max-': -1, 'zero': 0, 'none': 0, 'null': 0, 'top': true, 'bottom': false }; + // build headers + $headers = buildHeaders(this); + // try to auto detect column type, and store in tables config + c.parsers = buildParserCache(this, $headers); + // build the cache for the tbody cells + // delayInit will delay building the cache until the user starts a sort + if (!c.delayInit) { 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.bind('mousedown.tablesorter mouseup.tablesorter', function(e, external) { + if (e.type === 'mousedown') { + downTime = new Date().getTime(); + return !c.cancelSelection; + } + // prevent resizable widget from initializing a sort (long clicks are ignored) + if (external !== true && (new Date().getTime() - downTime > 500)) { return false; } + if (c.delayInit && !c.cache) { buildCache($this[0]); } + if (!this.sortDisabled) { + // Only call sortStart if sorting is enabled. + $this.trigger("sortStart", $this[0]); + // store exp, for speed + $cell = $(this); + k = !e[c.sortMultiSortKey]; + // get current column sort order + this.count = (this.count + 1) % (c.sortReset ? 3 : 2); + // reset all sorts on non-current column - issue #30 + if (c.sortRestart) { + i = this; + $headers.each(function() { + // only reset counts on columns that weren't just clicked on and if not included in a multisort + if (this !== i && (k || !$(this).is('.' + c.cssDesc + ',.' + c.cssAsc))) { + this.count = -1; + } + }); + } + // get current column index + i = this.column; + // user only wants to sort on one column + if (k) { + // flush the sort list + c.sortList = []; + if (c.sortForce !== null) { + a = c.sortForce; + for (j = 0; j < a.length; j++) { + if (a[j][0] !== i) { + c.sortList.push(a[j]); + } + } + } + // add column to sort list + o = this.order[this.count]; + if (o < 2) { + c.sortList.push([i, o]); + // add other columns if header spans across multiple + if (this.colSpan > 1) { + for (j = 1; j < this.colSpan; j++) { + c.sortList.push([i + j, o]); + } + } + } + // multi column sorting + } else { + // the user has clicked on an already sorted column. + if (isValueInArray(i, c.sortList)) { + // reverse the sorting direction for all tables. + for (j = 0; j < c.sortList.length; j++) { + s = c.sortList[j]; + o = c.headerList[s[0]]; + if (s[0] === i) { + s[1] = o.order[o.count]; + if (s[1] === 2) { + c.sortList.splice(j,1); + o.count = -1; + } + } + } + } else { + // add column to sort list array + o = this.order[this.count]; + if (o < 2) { + c.sortList.push([i, o]); + // add other columns if header spans across multiple + if (this.colSpan > 1) { + for (j = 1; j < this.colSpan; j++) { + c.sortList.push([i + j, o]); + } + } + } + } + } + if (c.sortAppend !== null) { + a = c.sortAppend; + for (j = 0; j < a.length; j++) { + if (a[j][0] !== i) { + c.sortList.push(a[j]); + } + } + } + // sortBegin event triggered immediately before the sort + $this.trigger("sortBegin", $this[0]); + // set css for headers + setHeadersCss($this[0], $headers, c.sortList); + multisort($this[0], c.sortList); + appendToTable($this[0]); + } + }); + if (c.cancelSelection) { + // cancel selection + $headers.each(function() { + this.onselectstart = function() { + return false; + }; + }); + } + // apply easy methods that trigger binded events + $this + .bind("update", function(e, resort, callback) { + // remove rows/elements before update + $(c.selectorRemove, this).remove(); + // rebuild parsers. + c.parsers = buildParserCache(this, $headers); + // rebuild the cache map + buildCache(this); + checkResort($this, resort, callback); + }) + .bind("updateCell", function(e, cell, resort, callback) { + // get position from the dom. + var t = this, $tb = $(this).find('tbody'), row, pos, + // update cache - format: function(s, table, cell, cellIndex) + tbdy = $tb.index( $(cell).closest('tbody') ); + row = $tb.eq(tbdy).find('tr').index( $(cell).closest('tr') ); + pos = [ row, cell.cellIndex]; + t.config.cache[tbdy].normalized[pos[0]][pos[1]] = c.parsers[pos[1]].format( getElementText(t, cell, pos[1]), t, cell, pos[1] ); + checkResort($this, resort, callback); + }) + .bind("addRows", function(e, $row, resort, callback) { + var i, rows = $row.filter('tr').length, + dat = [], l = $row[0].cells.length, t = this, + tbdy = $(this).find('tbody').index( $row.closest('tbody') ); + // 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(t, $row[i].cells[j], j), t, $row[i].cells[j], j ); + } + // add the row index to the end + dat.push(c.cache[tbdy].row.length); + // update cache + c.cache[tbdy].row.push([$row[i]]); + c.cache[tbdy].normalized.push(dat); + dat = []; + } + // resort using current settings + checkResort($this, resort, callback); + }) + .bind("sorton", function(e, list, callback, init) { + $(this).trigger("sortStart", this); + var l = c.headerList.length; + c.sortList = []; + $.each(list, function(i,v){ + // make sure column exists + if (v[0] < l) { c.sortList.push(list[i]); } + }); + // update header count index + updateHeaderSortCount(this, c.sortList); + // set css for headers + setHeadersCss(this, $headers, c.sortList); + // sort the table and append it to the dom + multisort(this, c.sortList); + appendToTable(this, init); + if (typeof callback === "function") { + callback(this); + } + }) + .bind("appendCache", function(e, init) { + appendToTable(this, init); + }) + .bind("applyWidgetId", function(e, id) { + getWidgetById(id).format(this); + }) + .bind("applyWidgets", function(e, init) { + // apply widgets + applyWidget(this, init); + }) + .bind("destroy", function(e,c){ + $.tablesorter.destroy(this, c); + }); + + // get sort list from jQuery data or metadata + if ($this.data() && typeof $this.data().sortlist !== 'undefined') { + c.sortList = $this.data().sortlist; + } else if (m && ($this.metadata() && $this.metadata().sortlist)) { + c.sortList = $this.metadata().sortlist; + } + // apply widget init code + applyWidget(this, true); + // if user has supplied a sort list to constructor. + if (c.sortList.length > 0) { + $this.trigger("sorton", [c.sortList, {}, !c.initWidgets]); + } else if (c.initWidgets) { + // apply widget format + applyWidget(this); + } + + // initialized + this.hasInitialized = true; + if (c.debug) { + $.tablesorter.benchmark("Overall initialization time", $.data( this, 'startoveralltimer')); + } + $this.trigger('tablesorter-initialized', this); + if (typeof c.initialized === 'function') { c.initialized(this); } + }); + }; + + // Natural sort - https://github.com/overset/javascript-natural-sort + this.sortText = function(table, a, b, col) { + if (a === b) { return 0; } + var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ], + r = $.tablesorter.regex, xN, xD, yN, yD, xF, yF, i, mx; + if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; } + if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; } + if (typeof c.textSorter === 'function') { return c.textSorter(a, b, table, col); } + // chunk/tokenize + xN = a.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0'); + yN = b.replace(r[0], '\\0$1\\0').replace(/\\0$/, '').replace(/^\\0/, '').split('\\0'); + // numeric, hex or date detection + xD = parseInt(a.match(r[2]),16) || (xN.length !== 1 && a.match(r[1]) && Date.parse(a)); + yD = parseInt(b.match(r[2]),16) || (xD && b.match(r[1]) && Date.parse(b)) || null; + // first try and sort Hex codes or Dates + if (yD) { + if ( xD < yD ) { return -1; } + if ( xD > yD ) { return 1; } + } + mx = Math.max(xN.length, yN.length); + // natural sorting through split numeric strings and default strings + for (i = 0; i < mx; i++) { + // find floats not starting with '0', string or 0 if not defined (Clint Priest) + xF = (!(xN[i] || '').match(r[3]) && parseFloat(xN[i])) || xN[i] || 0; + yF = (!(yN[i] || '').match(r[3]) && parseFloat(yN[i])) || yN[i] || 0; + // handle numeric vs string comparison - number < string - (Kyle Adams) + if (isNaN(xF) !== isNaN(yF)) { return (isNaN(xF)) ? 1 : -1; } + // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2' + if (typeof xF !== typeof yF) { + xF += ''; + yF += ''; + } + if (xF < yF) { return -1; } + if (xF > yF) { return 1; } + } + return 0; + }; + + this.sortTextDesc = function(table, a, b, col) { + if (a === b) { return 0; } + var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ]; + if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; } + if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; } + if (typeof c.textSorter === 'function') { return c.textSorter(b, a, table, col); } + return this.sortText(table, b, a); + }; + + // return text string value by adding up ascii value + // so the text is somewhat sorted when using a digital sort + // this is NOT an alphanumeric sort + this.getTextValue = function(a, mx, d) { + if (mx) { + // make sure the text value is greater than the max numerical value (mx) + var i, l = a.length, n = mx + d; + for (i = 0; i < l; i++) { + n += a.charCodeAt(i); + } + return d * n; + } + return 0; + }; + + this.sortNumeric = function(table, a, b, col, mx, d) { + if (a === b) { return 0; } + var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ]; + if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : -e || -1; } + if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : e || 1; } + if (isNaN(a)) { a = this.getTextValue(a, mx, d); } + if (isNaN(b)) { b = this.getTextValue(b, mx, d); } + return a - b; + }; + + this.sortNumericDesc = function(table, a, b, col, mx, d) { + if (a === b) { return 0; } + var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ]; + if (a === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? -1 : 1) : e || 1; } + if (b === '' && e !== 0) { return (typeof(e) === 'boolean') ? (e ? 1 : -1) : -e || -1; } + if (isNaN(a)) { a = this.getTextValue(a, mx, d); } + if (isNaN(b)) { b = this.getTextValue(b, mx, d); } + return b - a; + }; + + this.destroy = function(table, removeClasses){ + var $t = $(table), c = table.config; + // remove widget added rows + $t.find('thead:first tr:not(.' + c.cssHeader + ')').remove(); + // remove resizer widget stuff + $t.find('thead:first .tablesorter-resizer').remove(); + // disable tablesorter + $t + .unbind('update updateCell addRows sorton appendCache applyWidgetId applyWidgets destroy mouseup mouseleave') + .find(c.selectorHeaders) + .unbind('click mousedown mousemove mouseup') + .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc); + if (removeClasses !== false) { + $t.removeClass(c.tableClass); + } + }; + + this.addParser = function(parser) { + var i, l = parsers.length, a = true; + for (i = 0; i < l; i++) { + if (parsers[i].id.toLowerCase() === parser.id.toLowerCase()) { + a = false; + } + } + if (a) { + parsers.push(parser); + } + }; + this.addWidget = function(widget) { + widgets.push(widget); + }; + + this.formatFloat = function(s, table) { + if (typeof(s) !== 'string' || s === '') { return s; } + if (table.config.usNumberFormat !== false) { + // US Format - 1,234,567.89 -> 1234567.89 + s = s.replace(/,/g,''); + } else { + // German Format = 1.234.567,89 -> 1234567.89 + // French Format = 1 234 567,89 -> 1234567.89 + s = s.replace(/[\s|\.]/g,'').replace(/,/g,'.'); + } + if(/^\s*\([.\d]+\)/.test(s)) { + s = s.replace(/^\s*\(/,'-').replace(/\)/,''); + } + var i = parseFloat(s); + // return the text instead of zero + return isNaN(i) ? $.trim(s) : i; + }; + this.isDigit = function(s) { + // replace all unwanted chars and match. + return (/^[\-+(]?\d+[)]?$/).test(s.replace(/[,.'\s]/g, '')); + }; + + // regex used in natural sort + this.regex = [ + /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, // chunk/tokenize numbers & letters + /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, //date + /^0x[0-9a-f]+$/i, // hex + /^0/ // leading zeros + ]; + // used when replacing accented characters during sorting + this.characterEquivalents = { + "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4", // áàâãä + "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4", // ÁÀÂÃÄ + "c" : "\u00e7", // ç + "C" : "\u00c7", // Ç + "e" : "\u00e9\u00e8\u00ea\u00eb", // éèêë + "E" : "\u00c9\u00c8\u00ca\u00cb", // ÉÈÊË + "i" : "\u00ed\u00ec\u0130\u00ee\u00ef", // íìİîï + "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ + "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö + "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ + "S" : "\u00df", // ß + "u" : "\u00fa\u00f9\u00fb\u00fc", // úùûü + "U" : "\u00da\u00d9\u00db\u00dc" // ÚÙÛÜ + }; + this.replaceAccents = function(s) { + var a, acc = '[', eq = this.characterEquivalents; + if (!this.characterRegex) { + this.characterRegexArray = {}; + for (a in eq) { + if (typeof a === 'string') { + acc += eq[a]; + this.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g'); + } + } + this.characterRegex = new RegExp(acc + ']'); + } + if (this.characterRegex.test(s)) { + for (a in eq) { + if (typeof a === 'string') { + s = s.replace( this.characterRegexArray[a], a ); + } + } + } + return s; + }; + + // get sorter, string, empty, etc options for each column from + // jQuery data, metadata, header option or header class name ("sorter-false") + // priority = jQuery data > meta > headers option > header class name + this.getData = function(h, ch, key) { + var val = '', $h = $(h), m, cl; + if (!$h.length) { return ''; } + m = $.metadata ? $h.metadata() : false; + cl = ' ' + ($h.attr('class') || ''); + if ($h.data() && ( typeof $h.data(key) !== 'undefined' || typeof $h.data(key.toLowerCase()) !== 'undefined') ){ + // "data-lockedOrder" is assigned to "lockedorder"; but "data-locked-order" is assigned to "lockedOrder" + // "data-sort-initial-order" is assigned to "sortInitialOrder" + val += $h.data(key) || $h.data(key.toLowerCase()); + } else if (m && typeof m[key] !== 'undefined') { + val += m[key]; + } else if (ch && typeof ch[key] !== 'undefined') { + val += ch[key]; + } else if (cl && cl.match(' ' + key + '-')) { + // include sorter class name "sorter-text", etc + val = cl.match( new RegExp(' ' + key + '-(\\w+)') )[1] || ''; + } + return $.trim(val); + }; + + this.clearTableBody = function(table) { + $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty(); + }; + + } + })(); + + // make shortcut + var ts = $.tablesorter; + + // extend plugin scope + $.fn.extend({ + tablesorter: ts.construct + }); + + // add default parsers + ts.addParser({ + id: "text", + is: function(s, table, node) { + return true; + }, + format: function(s, table, cell, cellIndex) { + var c = table.config; + s = $.trim( c.ignoreCase ? s.toLocaleLowerCase() : s ); + return c.sortLocaleCompare ? ts.replaceAccents(s) : s; + }, + type: "text" + }); + + ts.addParser({ + id: "currency", + is: function(s) { + return (/^\(?[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+/).test(s); // £$€¤¥¢ + }, + format: function(s, table) { + return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "ipAddress", + is: function(s) { + return (/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/).test(s); + }, + format: function(s, table) { + var i, a = s.split("."), + r = "", + l = a.length; + for (i = 0; i < l; i++) { + r += ("00" + a[i]).slice(-3); + } + return ts.formatFloat(r, table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "url", + is: function(s) { + return (/^(https?|ftp|file):\/\//).test(s); + }, + format: function(s) { + return $.trim(s.replace(/(https?|ftp|file):\/\//, '')); + }, + type: "text" + }); + + ts.addParser({ + id: "isoDate", + is: function(s) { + return (/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/).test(s); + }, + format: function(s, table) { + return ts.formatFloat((s !== "") ? (new Date(s.replace(/-/g, "/")).getTime() || "") : "", table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "percent", + is: function(s) { + return (/\d%\)?$/).test(s); + }, + format: function(s, table) { + return ts.formatFloat(s.replace(/%/g, ""), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "usLongDate", + is: function(s) { + return s.match(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/); + }, + format: function(s, table) { + return ts.formatFloat( (new Date(s).getTime() || ''), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd" + is: function(s) { + // testing for ####-##-#### - so it's not perfect + return (/^(\d{2}|\d{4})[\/\-\,\.\s+]\d{2}[\/\-\.\,\s+](\d{2}|\d{4})$/).test(s); + }, + format: function(s, table, cell, cellIndex) { + var c = table.config, ci = c.headerList[cellIndex], + format = ci.shortDateFormat; + if (typeof format === 'undefined') { + // cache header formatting so it doesn't getData for every cell in the column + format = ci.shortDateFormat = ts.getData( ci, c.headers[cellIndex], 'dateFormat') || c.dateFormat; + } + s = s.replace(/\s+/g," ").replace(/[\-|\.|\,]/g, "/"); + if (format === "mmddyyyy") { + s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$1/$2"); + } else if (format === "ddmmyyyy") { + s = s.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, "$3/$2/$1"); + } else if (format === "yyyymmdd") { + s = s.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, "$1/$2/$3"); + } + return ts.formatFloat( (new Date(s).getTime() || ''), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "time", + is: function(s) { + return (/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/).test(s); + }, + format: function(s, table) { + return ts.formatFloat( (new Date("2000/01/01 " + s).getTime() || ''), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "digit", + is: function(s) { + return ts.isDigit(s); + }, + format: function(s, table) { + return ts.formatFloat(s.replace(/[^\w,. \-()]/g, ""), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "metadata", + is: function(s) { + return false; + }, + format: function(s, table, cell) { + var c = table.config, + p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName; + return $(cell).metadata()[p]; + }, + type: "numeric" + }); + + // add default widgets + ts.addWidget({ + id: "zebra", + format: function(table) { + var $tb, $tv, $tr, row, even, time, k, l, + c = table.config, + child = new RegExp(c.cssChildRow, 'i'), + b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'), + css = [ "even", "odd" ]; + // maintain backwards compatibility + css = c.widgetZebra && c.hasOwnProperty('css') ? c.widgetZebra.css : + (c.widgetOptions && c.widgetOptions.hasOwnProperty('zebra')) ? c.widgetOptions.zebra : css; + if (c.debug) { + time = new Date(); + } + for (k = 0; k < b.length; k++ ) { + // loop through the visible rows + $tb = $(b[k]); + l = $tb.children('tr').length; + if (l > 1) { + row = 0; + $tv = $tb.find('tr:visible'); + $tb.addClass('tablesorter-hidden'); + // revered back to using jQuery each - strangely it's the fastest method + $tv.each(function(){ + $tr = $(this); + // style children rows the same way the parent row was styled + if (!child.test(this.className)) { row++; } + even = (row % 2 === 0); + $tr.removeClass(css[even ? 1 : 0]).addClass(css[even ? 0 : 1]); + }); + $tb.removeClass('tablesorter-hidden'); + } + } + if (c.debug) { + ts.benchmark("Applying Zebra widget", time); + } + } + }); + +})(jQuery); \ No newline at end of file -- cgit v1.2.3-54-g00ecf