From 724422850020fd556e77026e54bfb56aa595eddc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 27 Dec 2012 16:28:43 -0600 Subject: Tablesorter JS upgrade Signed-off-by: Dan McGee --- main/templatetags/cdn.py | 2 +- sitestatic/archweb.css | 60 +- sitestatic/jquery.tablesorter-2.4.5.js | 1333 --------------------------- sitestatic/jquery.tablesorter-2.4.5.min.js | 6 - sitestatic/jquery.tablesorter-2.7.js | 1374 ++++++++++++++++++++++++++++ sitestatic/jquery.tablesorter-2.7.min.js | 5 + 6 files changed, 1404 insertions(+), 1376 deletions(-) delete mode 100644 sitestatic/jquery.tablesorter-2.4.5.js delete mode 100644 sitestatic/jquery.tablesorter-2.4.5.min.js create mode 100644 sitestatic/jquery.tablesorter-2.7.js create mode 100644 sitestatic/jquery.tablesorter-2.7.min.js diff --git a/main/templatetags/cdn.py b/main/templatetags/cdn.py index 771b4426..fc63fdd8 100644 --- a/main/templatetags/cdn.py +++ b/main/templatetags/cdn.py @@ -20,7 +20,7 @@ def jquery(): @register.simple_tag def jquery_tablesorter(): - version = '2.4.5' + version = '2.7' filename = 'jquery.tablesorter-%s.min.js' % version link = staticfiles_storage.url(filename) return '' % link diff --git a/sitestatic/archweb.css b/sitestatic/archweb.css index 50f0f488..45029f10 100644 --- a/sitestatic/archweb.css +++ b/sitestatic/archweb.css @@ -292,23 +292,6 @@ table.pretty2 { border: 1px solid #bbb; } - /* additional styles for JS sorting */ - table.pretty2 th.tablesorter-header { - padding-right: 20px; - background-image: url(nosort.gif); - background-repeat: no-repeat; - background-position: center right; - cursor: pointer; - } - - table.pretty2 th.tablesorter-headerSortDown { - background-image: url(desc.gif); - } - - table.pretty2 th.tablesorter-headerSortUp { - background-image: url(asc.gif); - } - table.pretty2 td { padding: 0.35em; border: 1px dotted #bbb; @@ -417,6 +400,30 @@ ul.errorlist { color: red; } +/* JS sorting via tablesorter */ +table th.tablesorter-header { + padding-right: 20px; + background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); + background-repeat: no-repeat; + background-position: center right; + cursor: pointer; +} + +table thead th.tablesorter-headerAsc { + background-color: #e4eeff; + background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); +} + +table thead th.tablesorter-headerDesc { + background-color: #e4eeff; + background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); +} + +table thead th.sorter-false { + background-image: url(); + cursor: default; +} + /** * PAGE SPECIFIC STYLES */ @@ -666,25 +673,6 @@ table.results { background-color:#fff; } - /* additional styles for JS sorting */ - table.results th.tablesorter-header { - padding-right: 20px; - background-image: url(nosort.gif); - background-repeat: no-repeat; - background-position: center right; - cursor: pointer; - } - - table.results th.tablesorter-headerSortDown { - background-color: #e4eeff; - background-image: url(desc.gif); - } - - table.results th.tablesorter-headerSortUp { - background-color: #e4eeff; - background-image: url(asc.gif); - } - table.results td { padding: .3em 1em .3em 3px; } diff --git a/sitestatic/jquery.tablesorter-2.4.5.js b/sitestatic/jquery.tablesorter-2.4.5.js deleted file mode 100644 index 723cb91e..00000000 --- a/sitestatic/jquery.tablesorter-2.4.5.js +++ /dev/null @@ -1,1333 +0,0 @@ -/*! -* TableSorter 2.4.5 - 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 -*/ -/*jshint evil:true, browser:true, jquery:true, unused:false */ -/*global console:false, alert:false */ -!(function($) { - "use strict"; - $.extend({ - tablesorter: new function() { - - var ts = this; - - ts.version = "2.4.5"; - - ts.parsers = []; - ts.widgets = []; - ts.defaults = { - - // appearance - theme : 'default', // adds tablesorter-{theme} to the table for styling - widthFixed : false, // adds colgroup to fix widths of columns - showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered. - - // 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 : 'tablesorter-childRow', // previously "expand-child" - cssDesc : 'tablesorter-headerSortDown', - cssHeader : 'tablesorter-header', - cssHeaderRow : 'tablesorter-headerRow', - cssIcon : 'tablesorter-icon', // if this class exists, a will be added to the header automatically - cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name - cssProcessing : 'tablesorter-processing', // processing icon applied to header during sort/filter - - // selectors - selectorHeaders : '> thead th, > thead td', - selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort - selectorRemove : '.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)"); - } - - ts.benchmark = benchmark; - - 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); - } - - function detectParserForColumn(table, rows, rowIndex, cellIndex) { - var i, l = ts.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 (ts.parsers[i].is(nodeValue, table, node)) { - return ts.parsers[i]; - } - } - // 0 is always the generic parser (text) - return ts.parsers[0]; - } - - function buildParserCache(table, $headers) { - var c = table.config, - tb = $(table.tBodies).filter(':not(.' + c.cssInfoBlock + ')'), - rows, list, l, i, h, ch, 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 = ts.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(); - } - // processing icon - if (tc.showProcessing) { - ts.isProcessing(table, true); - } - 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)) { - 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); - } - } - } - if (tc.showProcessing) { - ts.isProcessing(table); // remove processing icon - } - if (tc.debug) { - benchmark("Building cache for " + totalRows + " rows", cacheTime); - } - } - - // init flag (true) used by pager plugin to prevent widget application - function appendToTable(table, init) { - var c = table.config, - b = table.tBodies, - rows = [], - c2 = c.cache, - r, n, totalRows, checkCell, $bk, $tb, - i, j, k, l, pos, appendTime; - if (c.debug) { - appendTime = new Date(); - } - for (k = 0; k < b.length; k++) { - $bk = $(b[k]); - if (!$bk.hasClass(c.cssInfoBlock)) { - // get tbody - $tb = ts.processTbody(table, $bk, true); - 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++) { - $tb.append(r[pos][j]); - } - } - } - // restore tbody - ts.processTbody(table, $tb, false); - } - } - if (c.appender) { - c.appender(table, rows); - } - if (c.debug) { - benchmark("Rebuilt table", appendTime); - } - // apply table widgets - if (!init) { ts.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, tfoot 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, - t, lock, time, $tableHeaders, c = table.config; - c.headerList = []; - if (c.debug) { - time = new Date(); - } - $tableHeaders = $(table).find(c.selectorHeaders).each(function(index) { - $t = $(this); - ch = c.headers[index]; - t = c.cssIcon ? '' : ''; // add icon if cssIcon option exists - this.innerHTML = '
' + this.innerHTML + t + '
'; // 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; - $t.addClass('sorter-false'); - } else { - $t.removeClass('sorter-false'); - } - 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]; - } - $t.addClass( this.sortDisabled ? 'sorter-false' : c.cssHeader ); - // add cell to headerList - c.headerList[index] = this; - // add to parent in case there are multiple rows - $t.parent().addClass(c.cssHeaderRow); - }); - if (table.config.debug) { - benchmark("Built headers:", time); - log($tableHeaders); - } - return $tableHeaders; - } - - function setHeadersCss(table, $headers) { - var f, i, j, l, - c = table.config, - list = c.sortList, - css = [c.cssDesc, c.cssAsc], - // find the footer - $t = $(table).find('tfoot tr').children().removeClass(css.join(' ')); - // remove all header information - $headers.removeClass(css.join(' ')); - l = list.length; - for (i = 0; i < l; i++) { - // direction = 2 means reset! - if (list[i][1] !== 2) { - // multicolumn sorting updating - choose the :last in case there are nested columns - f = $headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (l === 1 ? ':last' : '') ); - if (f.length) { - for (j = 0; j < f.length; j++) { - if (!f[j].sortDisabled) { - f.eq(j).addClass(css[list[i][1]]); - // add sorted class to footer, if it exists - if ($t.length) { - $t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]); - } - } - } - } - } - } - } - - function fixColumnWidth(table) { - if (table.config.widthFixed && $(table).find('colgroup').length === 0) { - var colgroup = $(''), - overallWidth = $(table).width(); - $("tr:first td", table.tBodies[0]).each(function() { - colgroup.append($('').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%')); - }); - $(table).prepend(colgroup); - } - } - - function updateHeaderSortCount(table, list) { - var s, o, c = table.config, - l = c.headerList.length, - sl = list || c.sortList; - c.sortList = []; - $.each(sl, function(i,v){ - // ensure all sortList values are numeric - fixes #127 - s = [ parseInt(v[0], 10), parseInt(v[1], 10) ]; - // make sure header exists - o = c.headerList[s[0]]; - if (o) { // prevents error if sorton array is wrong - c.sortList.push(s); - o.count = s[1] % (c.sortReset ? 3 : 2); - } - }); - } - - function getCachedSortType(parsers, i) { - return (parsers && parsers[i]) ? parsers[i].type || '' : ''; - } - - // sort multiple columns - function multisort(table) { - var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config, - sortList = tc.sortList, 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 resortComplete($table, callback){ - var t = $table[0]; - $table.trigger('updateComplete'); - if (typeof callback === "function") { - callback(t); - } - } - - function checkResort($table, flag, callback) { - if (flag !== false) { - $table.trigger("sorton", [$table[0].config.sortList, function(){ - resortComplete($table, callback); - }]); - } else { - resortComplete($table, callback); - } - } - - /* public methods */ - ts.construct = function(settings) { - return this.each(function() { - // if no thead or tbody, or tablesorter is already present, quit - if (!this.tHead || this.tBodies.length === 0 || this.hasInitialized === true) { return; } - // declare - var $headers, $cell, $this = $(this), - c, i, j, k = '', a, s, o, downTime, - m = $.metadata; - // initialization flag - this.hasInitialized = false; - // new blank config object - this.config = {}; - // merge and extend - c = $.extend(true, this.config, ts.defaults, settings); - // save the settings where they read - $.data(this, "tablesorter", c); - if (c.debug) { $.data( this, 'startoveralltimer', new Date()); } - // constants - c.supportsTextContent = $('x')[0].textContent === 'x'; - c.supportsDataObject = parseFloat($.fn.jquery) >= 1.4; - // 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 }; - // add table theme class only if there isn't already one there - if (!/tablesorter\-/.test($this.attr('class'))) { - k = (c.theme !== '' ? ' tablesorter-' + c.theme : ''); - } - $this.addClass(c.tableClass + k); - // 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); } - // apply event handling to headers - // this is to big, perhaps break it out? - $headers - // http://stackoverflow.com/questions/5312849/jquery-find-self - .find('*').andSelf().filter(c.selectorSort) - .unbind('mousedown.tablesorter mouseup.tablesorter') - .bind('mousedown.tablesorter mouseup.tablesorter', function(e, external) { - // jQuery v1.2.6 doesn't have closest() - var $cell = this.tagName.match('TH|TD') ? $(this) : $(this).parents('th, td').filter(':last'), cell = $cell[0]; - // only recognize left clicks - if ((e.which || e.button) !== 1) { return false; } - // set timer on mousedown - if (e.type === 'mousedown') { - downTime = new Date().getTime(); - return e.target.tagName === "INPUT" ? '' : !c.cancelSelection; - } - // ignore long clicks (prevents resizable widget from initializing a sort) - if (external !== true && (new Date().getTime() - downTime > 250)) { return false; } - if (c.delayInit && !c.cache) { buildCache($this[0]); } - if (!cell.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 - cell.count = (cell.count + 1) % (c.sortReset ? 3 : 2); - // reset all sorts on non-current column - issue #30 - if (c.sortRestart) { - i = cell; - $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 = cell.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 = cell.order[cell.count]; - if (o < 2) { - c.sortList.push([i, o]); - // add other columns if header spans across multiple - if (cell.colSpan > 1) { - for (j = 1; j < cell.colSpan; j++) { - c.sortList.push([i + j, o]); - } - } - } - // multi column sorting - } else { - // get rid of the sortAppend before adding more - fixes issue #115 - if (c.sortAppend && c.sortList.length > 1) { - if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) { - c.sortList.pop(); - } - } - // the user has clicked on an already sorted column - if (ts.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 = cell.order[cell.count]; - if (o < 2) { - c.sortList.push([i, o]); - // add other columns if header spans across multiple - if (cell.colSpan > 1) { - for (j = 1; j < cell.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]); - // setTimeout needed so the processing icon shows up - setTimeout(function(){ - // set css for headers - setHeadersCss($this[0], $headers); - multisort($this[0]); - appendToTable($this[0]); - }, 1); - } - }); - if (c.cancelSelection) { - // cancel selection - $headers.each(function() { - this.onselectstart = function() { - return false; - }; - }); - } - // apply easy methods that trigger binded events - $this - .unbind('update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave') - .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 l, row, icell, - t = this, $tb = $(this).find('tbody'), - // update cache - format: function(s, table, cell, cellIndex) - // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr'); - tbdy = $tb.index( $(cell).parents('tbody').filter(':last') ), - $row = $(cell).parents('tr').filter(':last'); - // tbody may not exist if update is initialized while tbody is removed for processing - if ($tb.length && tbdy >= 0) { - row = $tb.eq(tbdy).find('tr').index( $row ); - icell = cell.cellIndex; - l = t.config.cache[tbdy].normalized[row].length - 1; - t.config.cache[tbdy].row[t.config.cache[tbdy].normalized[row][l]] = $row; - t.config.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(t, cell, icell), t, cell, icell ); - 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); - // update header count index - updateHeaderSortCount(this, list); - // set css for headers - setHeadersCss(this, $headers); - // sort the table and append it to the dom - multisort(this); - appendToTable(this, init); - if (typeof callback === "function") { - callback(this); - } - }) - .bind("appendCache", function(e, callback, init) { - appendToTable(this, init); - if (typeof callback === "function") { - callback(this); - } - }) - .bind("applyWidgetId", function(e, id) { - ts.getWidgetById(id).format(this, c, c.widgetOptions); - }) - .bind("applyWidgets", function(e, init) { - // apply widgets - ts.applyWidget(this, init); - }) - .bind("refreshWidgets", function(e, all, dontapply){ - ts.refreshWidgets(this, all, dontapply); - }) - .bind("destroy", function(e, c, cb){ - ts.destroy(this, c, cb); - }); - - // get sort list from jQuery data or metadata - // in jQuery < 1.4, an error occurs when calling $this.data() - if (c.supportsDataObject && 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 - ts.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 - ts.applyWidget(this); - } - - // fixate columns if the users supplies the fixedWidth option - // do this after theme has been applied - fixColumnWidth(this); - - // show processesing icon - if (c.showProcessing) { - $this - .unbind('sortBegin sortEnd') - .bind('sortBegin sortEnd', function(e) { - ts.isProcessing($this[0], e.type === 'sortBegin'); - }); - } - - // initialized - this.hasInitialized = true; - if (c.debug) { - ts.benchmark("Overall initialization time", $.data( this, 'startoveralltimer')); - } - $this.trigger('tablesorter-initialized', this); - if (typeof c.initialized === 'function') { c.initialized(this); } - }); - }; - - // *** Process table *** - // add processing indicator - ts.isProcessing = function(table, toggle, $ths) { - var c = table.config, - // default to all headers - $h = $ths || $(table).find('.' + c.cssHeader); - if (toggle) { - if (c.sortList.length > 0) { - // get headers from the sortList - $h = $h.filter(function(){ - // get data-column from attr to keep compatibility with jQuery 1.2.6 - return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList); - }); - } - $h.addClass(c.cssProcessing); - } else { - $h.removeClass(c.cssProcessing); - } - }; - - // detach tbody but save the position - // don't use tbody because there are portions that look for a tbody index (updateCell) - ts.processTbody = function(table, $tb, getIt){ - var t, holdr; - if (getIt) { - $tb.before(''); - holdr = ($.fn.detach) ? $tb.detach() : $tb.remove(); - return holdr; - } - holdr = $(table).find('span.tablesorter-savemyplace'); - $tb.insertAfter( holdr ); - holdr.remove(); - }; - - ts.clearTableBody = function(table) { - $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty(); - }; - - ts.destroy = function(table, removeClasses, callback){ - var $t = $(table), c = table.config, - $h = $t.find('thead:first'); - // clear flag in case the plugin is initialized again - table.hasInitialized = false; - // remove widget added rows - $h.find('tr:not(.' + c.cssHeaderRow + ')').remove(); - // remove resizer widget stuff - $h.find('.tablesorter-resizer').remove(); - // remove all widgets - ts.refreshWidgets(table, true, true); - // disable tablesorter - $t - .removeData('tablesorter') - .unbind('update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave') - .find('.' + c.cssHeader) - .unbind('click mousedown mousemove mouseup') - .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc) - .find('.tablesorter-header-inner').each(function(){ - if (c.cssIcon !== '') { $(this).find('.' + c.cssIcon).remove(); } - $(this).replaceWith( $(this).contents() ); - }); - if (removeClasses !== false) { - $t.removeClass(c.tableClass); - } - if (typeof callback === 'function') { - callback(table); - } - }; - - // *** sort functions *** - // regex used in natural sort - ts.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 - ]; - - // Natural sort - https://github.com/overset/javascript-natural-sort - ts.sortText = function(table, a, b, col) { - if (a === b) { return 0; } - var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ], - r = ts.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 - xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0; - yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(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; - }; - - ts.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 ts.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 - ts.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; - }; - - ts.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 = ts.getTextValue(a, mx, d); } - if (isNaN(b)) { b = ts.getTextValue(b, mx, d); } - return a - b; - }; - - ts.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 = ts.getTextValue(a, mx, d); } - if (isNaN(b)) { b = ts.getTextValue(b, mx, d); } - return b - a; - }; - - // used when replacing accented characters during sorting - ts.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" // ÚÙÛÜ - }; - ts.replaceAccents = function(s) { - var a, acc = '[', eq = ts.characterEquivalents; - if (!ts.characterRegex) { - ts.characterRegexArray = {}; - for (a in eq) { - if (typeof a === 'string') { - acc += eq[a]; - ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g'); - } - } - ts.characterRegex = new RegExp(acc + ']'); - } - if (ts.characterRegex.test(s)) { - for (a in eq) { - if (typeof a === 'string') { - s = s.replace( ts.characterRegexArray[a], a ); - } - } - } - return s; - }; - - // *** utilities *** - ts.isValueInArray = function(v, a) { - var i, l = a.length; - for (i = 0; i < l; i++) { - if (a[i][0] === v) { - return true; - } - } - return false; - }; - - ts.addParser = function(parser) { - var i, l = ts.parsers.length, a = true; - for (i = 0; i < l; i++) { - if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) { - a = false; - } - } - if (a) { - ts.parsers.push(parser); - } - }; - - ts.getParserById = function(name) { - var i, l = ts.parsers.length; - for (i = 0; i < l; i++) { - if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) { - return ts.parsers[i]; - } - } - return false; - }; - - ts.addWidget = function(widget) { - ts.widgets.push(widget); - }; - - ts.getWidgetById = function(name) { - var i, w, l = ts.widgets.length; - for (i = 0; i < l; i++) { - w = ts.widgets[i]; - if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) { - return w; - } - } - }; - - ts.applyWidget = function(table, init) { - var c = table.config, - wo = c.widgetOptions, - ws = c.widgets.sort().reverse(), // ensure that widgets are always applied in a certain order - time, i, w, l = ws.length; - // make zebra last - i = $.inArray('zebra', c.widgets); - if (i >= 0) { - c.widgets.splice(i,1); - c.widgets.push('zebra'); - } - if (c.debug) { - time = new Date(); - } - // add selected widgets - for (i = 0; i < l; i++) { - w = ts.getWidgetById(ws[i]); - if ( w ) { - if (init === true && w.hasOwnProperty('init')) { - w.init(table, w, c, wo); - } else if (!init && w.hasOwnProperty('format')) { - w.format(table, c, wo); - } - } - } - if (c.debug) { - benchmark("Completed " + (init === true ? "initializing" : "applying") + " widgets", time); - } - }; - - ts.refreshWidgets = function(table, doAll, dontapply) { - var i, c = table.config, - cw = c.widgets, - w = ts.widgets, l = w.length; - // remove previous widgets - for (i = 0; i < l; i++){ - if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) { - if (c.debug) { log( 'removing ' + w[i].id ); } - if (w[i].hasOwnProperty('remove')) { w[i].remove(table, c, c.widgetOptions); } - } - } - if (dontapply !== true) { - ts.applyWidget(table, doAll); - } - }; - - // 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 - ts.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 (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); - }; - - ts.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)) { - // make (#) into a negative number -> (10) = -10 - s = s.replace(/^\s*\(/,'-').replace(/\)/,''); - } - var i = parseFloat(s); - // return the text instead of zero - return isNaN(i) ? $.trim(s) : i; - }; - - ts.isDigit = function(s) { - // replace all unwanted chars and match - return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'\s]/g, '')) : true; - }; - - }() - }); - - // 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 (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4}|'?\d{2})\s+(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s); - }, - format: function(s, table) { - return ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).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]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s); - }, - format: function(s, table) { - return ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).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, c, wo) { - var $tb, $tv, $tr, row, even, time, k, l, - child = new RegExp(c.cssChildRow, 'i'), - b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'); - 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.children('tr:visible'); - // 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(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]); - }); - } - } - if (c.debug) { - ts.benchmark("Applying Zebra widget", time); - } - }, - remove: function(table, c, wo){ - var k, $tb, - b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'), - rmv = (c.widgetOptions.zebra || [ "even", "odd" ]).join(' '); - for (k = 0; k < b.length; k++ ){ - $tb = $.tablesorter.processTbody(table, $(b[k]), true); // remove tbody - $tb.children().removeClass(rmv); - $.tablesorter.processTbody(table, $tb, false); // restore tbody - } - } - }); - -})(jQuery); \ No newline at end of file diff --git a/sitestatic/jquery.tablesorter-2.4.5.min.js b/sitestatic/jquery.tablesorter-2.4.5.min.js deleted file mode 100644 index a52ce35f..00000000 --- a/sitestatic/jquery.tablesorter-2.4.5.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! -* TableSorter 2.4.5 - Client-side table sorting with ease! -* Minified using UglifyJS (http://jscompress.com/) -* Copyright (c) 2007 Christian Bach -*/ -!function($){"use strict";$.extend({tablesorter:new function(){function log(a){if(typeof console!=="undefined"&&typeof console.log!=="undefined"){console.log(a)}else{alert(a)}}function benchmark(a,b){log(a+" ("+((new Date).getTime()-b.getTime())+"ms)")}function getElementText(a,b,c){if(!b){return""}var d=a.config,e=d.textExtraction,f="";if(e==="simple"){if(d.supportsTextContent){f=b.textContent}else{f=$(b).text()}}else{if(typeof e==="function"){f=e(b,a,c)}else if(typeof e==="object"&&e.hasOwnProperty(c)){f=e[c](b,a,c)}else{f=d.supportsTextContent?b.textContent:$(b).text()}}return $.trim(f)}function detectParserForColumn(a,b,c,d){var e,f=ts.parsers.length,g=false,h="",i=true;while(h===""&&i){c++;if(b[c]){g=b[c].cells[d];h=getElementText(a,g,d);if(a.config.debug){log("Checking if value was empty on row "+c+", column: "+d+": "+h)}}else{i=false}}for(e=1;e
':"";this.innerHTML='
'+this.innerHTML+e+"
";if(i.onRenderHeader){i.onRenderHeader.apply(d,[a])}this.column=b[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(ts.getData(d,c,"sortInitialOrder")||i.sortInitialOrder)?[1,0,2]:[0,1,2];this.count=-1;if(ts.getData(d,c,"sorter")==="false"){this.sortDisabled=true;d.addClass("sorter-false")}else{d.removeClass("sorter-false")}this.lockedOrder=false;f=ts.getData(d,c,"lockedOrder")||false;if(typeof f!=="undefined"&&f!==false){this.order=this.lockedOrder=formatSortingOrder(f)?[1,1,1]:[0,0,0]}d.addClass(this.sortDisabled?"sorter-false":i.cssHeader);i.headerList[a]=this;d.parent().addClass(i.cssHeaderRow)});if(a.config.debug){benchmark("Built headers:",g);log(h)}return h}function setHeadersCss(a,b){var c,d,e,f,g=a.config,h=g.sortList,i=[g.cssDesc,g.cssAsc],j=$(a).find("tfoot tr").children().removeClass(i.join(" "));b.removeClass(i.join(" "));f=h.length;for(d=0;d"),c=$(a).width();$("tr:first td",a.tBodies[0]).each(function(){b.append($("").css("width",parseInt($(this).width()/c*1e3,10)/10+"%"))});$(a).prepend(b)}}function updateHeaderSortCount(a,b){var c,d,e=a.config,f=e.headerList.length,g=b||e.sortList;e.sortList=[];$.each(g,function(a,b){c=[parseInt(b[0],10),parseInt(b[1],10)];d=e.headerList[c[0]];if(d){e.sortList.push(c);d.count=c[1]%(e.sortReset?3:2)}})}function getCachedSortType(a,b){return a&&a[b]?a[b].type||"":""}function multisort(table){var dynamicExp,sortWrapper,col,mx=0,dir=0,tc=table.config,sortList=tc.sortList,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 thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:false,headerList:[],empties:{},strings:{},parsers:[]};ts.benchmark=benchmark;ts.construct=function(a){return this.each(function(){if(!this.tHead||this.tBodies.length===0||this.hasInitialized===true){return}var b,c,d=$(this),e,f,g,h="",i,j,k,l,m=$.metadata;this.hasInitialized=false;this.config={};e=$.extend(true,this.config,ts.defaults,a);$.data(this,"tablesorter",e);if(e.debug){$.data(this,"startoveralltimer",new Date)}e.supportsTextContent=$("x")[0].textContent==="x";e.supportsDataObject=parseFloat($.fn.jquery)>=1.4;e.string={max:1,min:-1,"max+":1,"max-":-1,zero:0,none:0,"null":0,top:true,bottom:false};if(!/tablesorter\-/.test(d.attr("class"))){h=e.theme!==""?" tablesorter-"+e.theme:""}d.addClass(e.tableClass+h);b=buildHeaders(this);e.parsers=buildParserCache(this,b);if(!e.delayInit){buildCache(this)}b.find("*").andSelf().filter(e.selectorSort).unbind("mousedown.tablesorter mouseup.tablesorter").bind("mousedown.tablesorter mouseup.tablesorter",function(a,c){var m=this.tagName.match("TH|TD")?$(this):$(this).parents("th, td").filter(":last"),n=m[0];if((a.which||a.button)!==1){return false}if(a.type==="mousedown"){l=(new Date).getTime();return a.target.tagName==="INPUT"?"":!e.cancelSelection}if(c!==true&&(new Date).getTime()-l>250){return false}if(e.delayInit&&!e.cache){buildCache(d[0])}if(!n.sortDisabled){d.trigger("sortStart",d[0]);h=!a[e.sortMultiSortKey];n.count=(n.count+1)%(e.sortReset?3:2);if(e.sortRestart){f=n;b.each(function(){if(this!==f&&(h||!$(this).is("."+e.cssDesc+",."+e.cssAsc))){this.count=-1}})}f=n.column;if(h){e.sortList=[];if(e.sortForce!==null){i=e.sortForce;for(g=0;g1){for(g=1;g1){if(ts.isValueInArray(e.sortAppend[0][0],e.sortList)){e.sortList.pop()}}if(ts.isValueInArray(f,e.sortList)){for(g=0;g1){for(g=1;g=0){h=k.eq(l).find("tr").index(m);i=b.cellIndex;g=j.config.cache[l].normalized[h].length-1;j.config.cache[l].row[j.config.cache[l].normalized[h][g]]=m;j.config.cache[l].normalized[h][i]=e.parsers[i].format(getElementText(j,b,i),j,b,i);checkResort(d,c,f)}}).bind("addRows",function(a,b,c,f){var h,i=b.filter("tr").length,j=[],k=b[0].cells.length,l=this,m=$(this).find("tbody").index(b.closest("tbody"));for(h=0;h0){d.trigger("sorton",[e.sortList,{},!e.initWidgets])}else if(e.initWidgets){ts.applyWidget(this)}fixColumnWidth(this);if(e.showProcessing){d.unbind("sortBegin sortEnd").bind("sortBegin sortEnd",function(a){ts.isProcessing(d[0],a.type==="sortBegin")})}this.hasInitialized=true;if(e.debug){ts.benchmark("Overall initialization time",$.data(this,"startoveralltimer"))}d.trigger("tablesorter-initialized",this);if(typeof e.initialized==="function"){e.initialized(this)}})};ts.isProcessing=function(a,b,c){var d=a.config,e=c||$(a).find("."+d.cssHeader);if(b){if(d.sortList.length>0){e=e.filter(function(){return this.sortDisabled?false:ts.isValueInArray(parseFloat($(this).attr("data-column")),d.sortList)})}e.addClass(d.cssProcessing)}else{e.removeClass(d.cssProcessing)}};ts.processTbody=function(a,b,c){var d,e;if(c){b.before('');e=$.fn.detach?b.detach():b.remove();return e}e=$(a).find("span.tablesorter-savemyplace");b.insertAfter(e);e.remove()};ts.clearTableBody=function(a){$(a.tBodies).filter(":not(."+a.config.cssInfoBlock+")").empty()};ts.destroy=function(a,b,c){var d=$(a),e=a.config,f=d.find("thead:first");a.hasInitialized=false;f.find("tr:not(."+e.cssHeaderRow+")").remove();f.find(".tablesorter-resizer").remove();ts.refreshWidgets(a,true,true);d.removeData("tablesorter").unbind("update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave").find("."+e.cssHeader).unbind("click mousedown mousemove mouseup").removeClass(e.cssHeader+" "+e.cssAsc+" "+e.cssDesc).find(".tablesorter-header-inner").each(function(){if(e.cssIcon!==""){$(this).find("."+e.cssIcon).remove()}$(this).replaceWith($(this).contents())});if(b!==false){d.removeClass(e.tableClass)}if(typeof c==="function"){c(a)}};ts.regex=[/(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,/^0x[0-9a-f]+$/i];ts.sortText=function(a,b,c,d){if(b===c){return 0}var e=a.config,f=e.string[e.empties[d]||e.emptyTo],g=ts.regex,h,i,j,k,l,m,n,o;if(b===""&&f!==0){return typeof f==="boolean"?f?-1:1:-f||-1}if(c===""&&f!==0){return typeof f==="boolean"?f?1:-1:f||1}if(typeof e.textSorter==="function"){return e.textSorter(b,c,a,d)}h=b.replace(g[0],"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");j=c.replace(g[0],"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");i=parseInt(b.match(g[2]),16)||h.length!==1&&b.match(g[1])&&Date.parse(b);k=parseInt(c.match(g[2]),16)||i&&c.match(g[1])&&Date.parse(c)||null;if(k){if(ik){return 1}}o=Math.max(h.length,j.length);for(n=0;nm){return 1}}return 0};ts.sortTextDesc=function(a,b,c,d){if(b===c){return 0}var e=a.config,f=e.string[e.empties[d]||e.emptyTo];if(b===""&&f!==0){return typeof f==="boolean"?f?-1:1:f||1}if(c===""&&f!==0){return typeof f==="boolean"?f?1:-1:-f||-1}if(typeof e.textSorter==="function"){return e.textSorter(c,b,a,d)}return ts.sortText(a,c,b)};ts.getTextValue=function(a,b,c){if(b){var d,e=a.length,f=b+c;for(d=0;d=0){c.widgets.splice(g,1);c.widgets.push("zebra")}if(c.debug){f=new Date}for(g=0;g1){g=0;e=d.children("tr:visible");e.each(function(){f=$(this);if(!l.test(this.className)){g++}h=g%2===0;f.removeClass(c.zebra[h?1:0]).addClass(c.zebra[h?0:1])})}}if(b.debug){ts.benchmark("Applying Zebra widget",i)}},remove:function(a,b,c){var d,e,f=$(a).children("tbody:not(."+b.cssInfoBlock+")"),g=(b.widgetOptions.zebra||["even","odd"]).join(" ");for(d=0;d (class from cssIcon) + onRenderTemplate : null, // function(index, template){ return template; }, (template is a string) + onRenderHeader : null, // function(index){}, (nothing to return) + + // *** functionality + cancelSelection : true, // prevent text selection in the header + dateFormat : 'mmddyyyy', // other options: "ddmmyyy" or "yyyymmdd" + sortMultiSortKey : 'shiftKey', // key used to select additional columns + sortResetKey : 'ctrlKey', // key used to remove sorting on a column + 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 + serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used. + + // *** 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){}, + + // *** css class names + tableClass : 'tablesorter', + cssAsc : 'tablesorter-headerAsc', + cssChildRow : 'tablesorter-childRow', // previously "expand-child" + cssDesc : 'tablesorter-headerDesc', + cssHeader : 'tablesorter-header', + cssHeaderRow : 'tablesorter-headerRow', + cssIcon : 'tablesorter-icon', // if this class exists, a will be added to the header automatically + cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name + cssProcessing : 'tablesorter-processing', // processing icon applied to header during sort/filter + + // *** selectors + selectorHeaders : '> thead th, > thead td', + selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort + selectorRemove : '.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)"); + } + + ts.benchmark = benchmark; + + 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); + } + + function detectParserForColumn(table, rows, rowIndex, cellIndex) { + var i, l = ts.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 (ts.parsers[i].is(nodeValue, table, node)) { + return ts.parsers[i]; + } + } + // 0 is always the generic parser (text) + return ts.parsers[0]; + } + + function buildParserCache(table) { + var c = table.config, + tb = $(table.tBodies).filter(':not(.' + c.cssInfoBlock + ')'), + rows, list, l, i, h, ch, p, parsersDebug = ""; + if ( tb.length === 0) { + return c.debug ? log('*Empty table!* Not building a parser cache') : ''; + } + 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! + // More fixes to this selector to work properly in iOS and jQuery 1.8+ (issue #132 & #174) + h = c.$headers.filter(':not([colspan])'); + h = h.add( c.$headers.filter('[colspan="1"]') ) // ie8 fix + .filter('[data-column="' + i + '"]:last'); + ch = c.headers[i]; + // get column parser + p = ts.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, v, i, j, k, c, cols, cacheTime, colMax = []; + tc.cache = {}; + // if no parsers found, return - it's an empty table. + if (!parsers) { + return tc.debug ? log('*Empty table!* Not building a cache') : ''; + } + if (tc.debug) { + cacheTime = new Date(); + } + // processing icon + if (tc.showProcessing) { + ts.isProcessing(table, true); + } + 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)) { + 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 + v = parsers[j].format(t, table, c[0].cells[j], j); + cols.push(v); + if ((parsers[j].type || '').toLowerCase() === "numeric") { + colMax[j] = Math.max(Math.abs(v), colMax[j] || 0); // determine column max value (ignore sign) + } + } + cols.push(tc.cache[k].normalized.length); // add position for rowCache + tc.cache[k].normalized.push(cols); + } + tc.cache[k].colMax = colMax; + } + } + if (tc.showProcessing) { + ts.isProcessing(table); // remove processing icon + } + if (tc.debug) { + benchmark("Building cache for " + totalRows + " rows", cacheTime); + } + } + + // init flag (true) used by pager plugin to prevent widget application + function appendToTable(table, init) { + var c = table.config, + b = table.tBodies, + rows = [], + c2 = c.cache, + r, n, totalRows, checkCell, $bk, $tb, + i, j, k, l, pos, appendTime; + if (!c2[0]) { return; } // empty table - fixes #206 + if (c.debug) { + appendTime = new Date(); + } + for (k = 0; k < b.length; k++) { + $bk = $(b[k]); + if (!$bk.hasClass(c.cssInfoBlock)) { + // get tbody + $tb = ts.processTbody(table, $bk, true); + 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++) { + $tb.append(r[pos][j]); + } + } + } + // restore tbody + ts.processTbody(table, $tb, false); + } + } + if (c.appender) { + c.appender(table, rows); + } + if (c.debug) { + benchmark("Rebuilt table", appendTime); + } + // apply table widgets + if (!init) { ts.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), tfoot').children('tr'), // children tr in tfoot - see issue #196 + 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, + h, i, t, lock, time, $tableHeaders, c = table.config; + c.headerList = [], c.headerContent = []; + if (c.debug) { + time = new Date(); + } + i = c.cssIcon ? '' : ''; // add icon if cssIcon option exists + $tableHeaders = $(table).find(c.selectorHeaders).each(function(index) { + $t = $(this); + ch = c.headers[index]; + c.headerContent[index] = this.innerHTML; // save original header content + // set up header template + t = c.headerTemplate.replace(/\{content\}/g, this.innerHTML).replace(/\{icon\}/g, i); + if (c.onRenderTemplate) { + h = c.onRenderTemplate.apply($t, [index, t]); + if (h && typeof h === 'string') { t = h; } // only change t if something is returned + } + this.innerHTML = '
' + t + '
'; // 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; + $t.addClass('sorter-false'); + } else { + $t.removeClass('sorter-false'); + } + 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]; + } + $t.addClass( (this.sortDisabled ? 'sorter-false ' : ' ') + c.cssHeader ); + // add cell to headerList + c.headerList[index] = this; + // add to parent in case there are multiple rows + $t.parent().addClass(c.cssHeaderRow); + }); + if (table.config.debug) { + benchmark("Built headers:", time); + log($tableHeaders); + } + return $tableHeaders; + } + + function setHeadersCss(table) { + var f, i, j, l, + c = table.config, + list = c.sortList, + css = [c.cssAsc, c.cssDesc], + // find the footer + $t = $(table).find('tfoot tr').children().removeClass(css.join(' ')); + // remove all header information + c.$headers.removeClass(css.join(' ')); + l = list.length; + for (i = 0; i < l; i++) { + // direction = 2 means reset! + if (list[i][1] !== 2) { + // multicolumn sorting updating - choose the :last in case there are nested columns + f = c.$headers.not('.sorter-false').filter('[data-column="' + list[i][0] + '"]' + (l === 1 ? ':last' : '') ); + if (f.length) { + for (j = 0; j < f.length; j++) { + if (!f[j].sortDisabled) { + f.eq(j).addClass(css[list[i][1]]); + // add sorted class to footer, if it exists + if ($t.length) { + $t.filter('[data-column="' + list[i][0] + '"]').eq(j).addClass(css[list[i][1]]); + } + } + } + } + } + } + } + + function fixColumnWidth(table) { + if (table.config.widthFixed && $(table).find('colgroup').length === 0) { + var colgroup = $(''), + overallWidth = $(table).width(); + $("tr:first td", table.tBodies[0]).each(function() { + colgroup.append($('').css('width', parseInt(($(this).width()/overallWidth)*1000, 10)/10 + '%')); + }); + $(table).prepend(colgroup); + } + } + + function updateHeaderSortCount(table, list) { + var s, t, o, c = table.config, + sl = list || c.sortList; + c.sortList = []; + $.each(sl, function(i,v){ + // ensure all sortList values are numeric - fixes #127 + s = [ parseInt(v[0], 10), parseInt(v[1], 10) ]; + // make sure header exists + o = c.headerList[s[0]]; + if (o) { // prevents error if sorton array is wrong + c.sortList.push(s); + t = $.inArray(s[1], o.order); // fixes issue #167 + o.count = t >= 0 ? t : s[1] % (c.sortReset ? 3 : 2); + } + }); + } + + function getCachedSortType(parsers, i) { + return (parsers && parsers[i]) ? parsers[i].type || '' : ''; + } + + // sort multiple columns + function multisort(table) { /*jshint loopfunc:true */ + var dynamicExp, sortWrapper, col, mx = 0, dir = 0, tc = table.config, + sortList = tc.sortList, l = sortList.length, bl = table.tBodies.length, + sortTime, i, j, k, c, colMax, cache, lc, s, e, order, orgOrderCol; + if (tc.serverSideSorting || !tc.cache[0]) { // empty table - fixes #206 + return; + } + if (tc.debug) { sortTime = new Date(); } + for (k = 0; k < bl; k++) { + colMax = tc.cache[k].colMax; + cache = tc.cache[k].normalized; + lc = cache.length; + orgOrderCol = (cache && cache[0]) ? cache[0].length - 1 : 0; + cache.sort(function(a, b) { + // cache is undefined here in IE, so don't use it! + 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"; + if (/Numeric/.test(s) && tc.strings[c]) { + // 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; + } + } + var sort = $.tablesorter["sort" + s](table, a[c], b[c], c, colMax[c], dir); + if (sort) { return sort; } + } + return a[orgOrderCol] - b[orgOrderCol]; + }); + } + if (tc.debug) { benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time", sortTime); } + } + + function resortComplete($table, callback){ + $table.trigger('updateComplete'); + if (typeof callback === "function") { + callback($table[0]); + } + } + + function checkResort($table, flag, callback) { + if (flag !== false) { + $table.trigger("sorton", [$table[0].config.sortList, function(){ + resortComplete($table, callback); + }]); + } else { + resortComplete($table, callback); + } + } + + /* public methods */ + ts.construct = function(settings) { + return this.each(function() { + // if no thead or tbody, or tablesorter is already present, quit + if (!this.tHead || this.tBodies.length === 0 || this.hasInitialized === true) { + return (this.config.debug) ? log('stopping initialization! No thead, tbody or tablesorter has already been initialized') : ''; + } + // declare + var $cell, $this = $(this), + c, i, j, k = '', a, s, o, downTime, + m = $.metadata; + // initialization flag + this.hasInitialized = false; + // new blank config object + this.config = {}; + // merge and extend + c = $.extend(true, this.config, ts.defaults, settings); + // save the settings where they read + $.data(this, "tablesorter", c); + if (c.debug) { $.data( this, 'startoveralltimer', new Date()); } + // constants + c.supportsTextContent = $('x')[0].textContent === 'x'; + c.supportsDataObject = parseFloat($.fn.jquery) >= 1.4; + // 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 }; + // add table theme class only if there isn't already one there + if (!/tablesorter\-/.test($this.attr('class'))) { + k = (c.theme !== '' ? ' tablesorter-' + c.theme : ''); + } + $this.addClass(c.tableClass + k); + // build headers + c.$headers = buildHeaders(this); + // try to auto detect column type, and store in tables config + c.parsers = buildParserCache(this); + // build the cache for the tbody cells + // delayInit will delay building the cache until the user starts a sort + if (!c.delayInit) { buildCache(this); } + // apply event handling to headers + // this is to big, perhaps break it out? + c.$headers + // http://stackoverflow.com/questions/5312849/jquery-find-self + .find('*').andSelf().filter(c.selectorSort) + .unbind('mousedown.tablesorter mouseup.tablesorter') + .bind('mousedown.tablesorter mouseup.tablesorter', function(e, external) { + // jQuery v1.2.6 doesn't have closest() + var $cell = this.tagName.match('TH|TD') ? $(this) : $(this).parents('th, td').filter(':last'), cell = $cell[0]; + // only recognize left clicks + if ((e.which || e.button) !== 1) { return false; } + // set timer on mousedown + if (e.type === 'mousedown') { + downTime = new Date().getTime(); + return e.target.tagName === "INPUT" ? '' : !c.cancelSelection; + } + // ignore long clicks (prevents resizable widget from initializing a sort) + if (external !== true && (new Date().getTime() - downTime > 250)) { return false; } + if (c.delayInit && !c.cache) { buildCache($this[0]); } + if (!cell.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 + cell.count = e[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2); + // reset all sorts on non-current column - issue #30 + if (c.sortRestart) { + i = cell; + c.$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 = cell.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 = cell.order[cell.count]; + if (o < 2) { + c.sortList.push([i, o]); + // add other columns if header spans across multiple + if (cell.colSpan > 1) { + for (j = 1; j < cell.colSpan; j++) { + c.sortList.push([i + j, o]); + } + } + } + // multi column sorting + } else { + // get rid of the sortAppend before adding more - fixes issue #115 + if (c.sortAppend && c.sortList.length > 1) { + if (ts.isValueInArray(c.sortAppend[0][0], c.sortList)) { + c.sortList.pop(); + } + } + // the user has clicked on an already sorted column + if (ts.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 = cell.order[cell.count]; + if (o < 2) { + c.sortList.push([i, o]); + // add other columns if header spans across multiple + if (cell.colSpan > 1) { + for (j = 1; j < cell.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]); + // setTimeout needed so the processing icon shows up + setTimeout(function(){ + // set css for headers + setHeadersCss($this[0]); + multisort($this[0]); + appendToTable($this[0]); + }, 1); + } + }); + if (c.cancelSelection) { + // cancel selection + c.$headers.each(function() { + this.onselectstart = function() { + return false; + }; + }); + } + // apply easy methods that trigger binded events + $this + .unbind('sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave') + .bind("sortReset", function(){ + c.sortList = []; + setHeadersCss(this); + multisort(this); + appendToTable(this); + }) + .bind("update", function(e, resort, callback) { + // remove rows/elements before update + $(c.selectorRemove, this).remove(); + // rebuild parsers + c.parsers = buildParserCache(this); + // rebuild the cache map + buildCache(this); + checkResort($this, resort, callback); + }) + .bind("updateCell", function(e, cell, resort, callback) { + // get position from the dom + var l, row, icell, + t = this, $tb = $(this).find('tbody'), + // update cache - format: function(s, table, cell, cellIndex) + // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr'); + tbdy = $tb.index( $(cell).parents('tbody').filter(':last') ), + $row = $(cell).parents('tr').filter(':last'); + cell = $(cell)[0]; // in case cell is a jQuery object + // tbody may not exist if update is initialized while tbody is removed for processing + if ($tb.length && tbdy >= 0) { + row = $tb.eq(tbdy).find('tr').index( $row ); + icell = cell.cellIndex; + l = t.config.cache[tbdy].normalized[row].length - 1; + t.config.cache[tbdy].row[t.config.cache[tbdy].normalized[row][l]] = $row; + t.config.cache[tbdy].normalized[row][icell] = c.parsers[icell].format( getElementText(t, cell, icell), t, cell, icell ); + 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') ); + // fixes adding rows to an empty table - see issue #179 + if (!c.parsers) { + c.parsers = buildParserCache(t); + } + // 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); + // update header count index + updateHeaderSortCount(this, list); + // set css for headers + setHeadersCss(this); + // sort the table and append it to the dom + multisort(this); + appendToTable(this, init); + if (typeof callback === "function") { + callback(this); + } + }) + .bind("appendCache", function(e, callback, init) { + appendToTable(this, init); + if (typeof callback === "function") { + callback(this); + } + }) + .bind("applyWidgetId", function(e, id) { + ts.getWidgetById(id).format(this, c, c.widgetOptions); + }) + .bind("applyWidgets", function(e, init) { + // apply widgets + ts.applyWidget(this, init); + }) + .bind("refreshWidgets", function(e, all, dontapply){ + ts.refreshWidgets(this, all, dontapply); + }) + .bind("destroy", function(e, c, cb){ + ts.destroy(this, c, cb); + }); + + // get sort list from jQuery data or metadata + // in jQuery < 1.4, an error occurs when calling $this.data() + if (c.supportsDataObject && 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 + ts.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 + ts.applyWidget(this); + } + + // fixate columns if the users supplies the fixedWidth option + // do this after theme has been applied + fixColumnWidth(this); + + // show processesing icon + if (c.showProcessing) { + $this + .unbind('sortBegin sortEnd') + .bind('sortBegin sortEnd', function(e) { + ts.isProcessing($this[0], e.type === 'sortBegin'); + }); + } + + // initialized + this.hasInitialized = true; + if (c.debug) { + ts.benchmark("Overall initialization time", $.data( this, 'startoveralltimer')); + } + $this.trigger('tablesorter-initialized', this); + if (typeof c.initialized === 'function') { c.initialized(this); } + }); + }; + + // *** Process table *** + // add processing indicator + ts.isProcessing = function(table, toggle, $ths) { + var c = table.config, + // default to all headers + $h = $ths || $(table).find('.' + c.cssHeader); + if (toggle) { + if (c.sortList.length > 0) { + // get headers from the sortList + $h = $h.filter(function(){ + // get data-column from attr to keep compatibility with jQuery 1.2.6 + return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList); + }); + } + $h.addClass(c.cssProcessing); + } else { + $h.removeClass(c.cssProcessing); + } + }; + + // detach tbody but save the position + // don't use tbody because there are portions that look for a tbody index (updateCell) + ts.processTbody = function(table, $tb, getIt){ + var t, holdr; + if (getIt) { + $tb.before(''); + holdr = ($.fn.detach) ? $tb.detach() : $tb.remove(); + return holdr; + } + holdr = $(table).find('span.tablesorter-savemyplace'); + $tb.insertAfter( holdr ); + holdr.remove(); + }; + + ts.clearTableBody = function(table) { + $(table.tBodies).filter(':not(.' + table.config.cssInfoBlock + ')').empty(); + }; + + ts.destroy = function(table, removeClasses, callback){ + var $t = $(table), c = table.config, + $h = $t.find('thead:first'); + // clear flag in case the plugin is initialized again + table.hasInitialized = false; + // remove widget added rows + $h.find('tr:not(.' + c.cssHeaderRow + ')').remove(); + // remove resizer widget stuff + $h.find('.tablesorter-resizer').remove(); + // remove all widgets + ts.refreshWidgets(table, true, true); + // disable tablesorter + $t + .removeData('tablesorter') + .unbind('sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave') + .find('.' + c.cssHeader) + .unbind('click mousedown mousemove mouseup') + .removeClass(c.cssHeader + ' ' + c.cssAsc + ' ' + c.cssDesc) + .find('.tablesorter-header-inner').each(function(){ + if (c.cssIcon !== '') { $(this).find('.' + c.cssIcon).remove(); } + $(this).replaceWith( $(this).contents() ); + }); + if (removeClasses !== false) { + $t.removeClass(c.tableClass); + } + if (typeof callback === 'function') { + callback(table); + } + }; + + // *** sort functions *** + // regex used in natural sort + ts.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 + ]; + + // Natural sort - https://github.com/overset/javascript-natural-sort + ts.sortText = function(table, a, b, col) { + if (a === b) { return 0; } + var c = table.config, e = c.string[ (c.empties[col] || c.emptyTo ) ], + r = ts.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 + xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0; + yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(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; + }; + + ts.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 ts.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 + ts.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; + }; + + ts.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 = ts.getTextValue(a, mx, d); } + if (isNaN(b)) { b = ts.getTextValue(b, mx, d); } + return a - b; + }; + + ts.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 = ts.getTextValue(a, mx, d); } + if (isNaN(b)) { b = ts.getTextValue(b, mx, d); } + return b - a; + }; + + // used when replacing accented characters during sorting + ts.characterEquivalents = { + "a" : "\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5", // áàâãäąå + "A" : "\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5", // ÁÀÂÃÄĄÅ + "c" : "\u00e7\u0107\u010d", // çćč + "C" : "\u00c7\u0106\u010c", // ÇĆČ + "e" : "\u00e9\u00e8\u00ea\u00eb\u011b\u0119", // éèêëěę + "E" : "\u00c9\u00c8\u00ca\u00cb\u011a\u0118", // ÉÈÊËĚĘ + "i" : "\u00ed\u00ec\u0130\u00ee\u00ef\u0131", // íìİîïı + "I" : "\u00cd\u00cc\u0130\u00ce\u00cf", // ÍÌİÎÏ + "o" : "\u00f3\u00f2\u00f4\u00f5\u00f6", // óòôõö + "O" : "\u00d3\u00d2\u00d4\u00d5\u00d6", // ÓÒÔÕÖ + "ss": "\u00df", // ß (s sharp) + "SS": "\u1e9e", // ẞ (Capital sharp s) + "u" : "\u00fa\u00f9\u00fb\u00fc\u016f", // úùûüů + "U" : "\u00da\u00d9\u00db\u00dc\u016e" // ÚÙÛÜŮ + }; + ts.replaceAccents = function(s) { + var a, acc = '[', eq = ts.characterEquivalents; + if (!ts.characterRegex) { + ts.characterRegexArray = {}; + for (a in eq) { + if (typeof a === 'string') { + acc += eq[a]; + ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g'); + } + } + ts.characterRegex = new RegExp(acc + ']'); + } + if (ts.characterRegex.test(s)) { + for (a in eq) { + if (typeof a === 'string') { + s = s.replace( ts.characterRegexArray[a], a ); + } + } + } + return s; + }; + + // *** utilities *** + ts.isValueInArray = function(v, a) { + var i, l = a.length; + for (i = 0; i < l; i++) { + if (a[i][0] === v) { + return true; + } + } + return false; + }; + + ts.addParser = function(parser) { + var i, l = ts.parsers.length, a = true; + for (i = 0; i < l; i++) { + if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) { + a = false; + } + } + if (a) { + ts.parsers.push(parser); + } + }; + + ts.getParserById = function(name) { + var i, l = ts.parsers.length; + for (i = 0; i < l; i++) { + if (ts.parsers[i].id.toLowerCase() === (name.toString()).toLowerCase()) { + return ts.parsers[i]; + } + } + return false; + }; + + ts.addWidget = function(widget) { + ts.widgets.push(widget); + }; + + ts.getWidgetById = function(name) { + var i, w, l = ts.widgets.length; + for (i = 0; i < l; i++) { + w = ts.widgets[i]; + if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) { + return w; + } + } + }; + + ts.applyWidget = function(table, init) { + var c = table.config, + wo = c.widgetOptions, + ws = c.widgets.sort().reverse(), // ensure that widgets are always applied in a certain order + time, i, w, l = ws.length; + // make zebra last + i = $.inArray('zebra', c.widgets); + if (i >= 0) { + c.widgets.splice(i,1); + c.widgets.push('zebra'); + } + if (c.debug) { + time = new Date(); + } + // add selected widgets + for (i = 0; i < l; i++) { + w = ts.getWidgetById(ws[i]); + if ( w ) { + if (init === true && w.hasOwnProperty('init')) { + w.init(table, w, c, wo); + } else if (!init && w.hasOwnProperty('format')) { + w.format(table, c, wo); + } + } + } + if (c.debug) { + benchmark("Completed " + (init === true ? "initializing" : "applying") + " widgets", time); + } + }; + + ts.refreshWidgets = function(table, doAll, dontapply) { + var i, c = table.config, + cw = c.widgets, + w = ts.widgets, l = w.length; + // remove previous widgets + for (i = 0; i < l; i++){ + if ( w[i] && w[i].id && (doAll || $.inArray( w[i].id, cw ) < 0) ) { + if (c.debug) { log( 'Refeshing widgets: Removing ' + w[i].id ); } + if (w[i].hasOwnProperty('remove')) { w[i].remove(table, c, c.widgetOptions); } + } + } + if (dontapply !== true) { + ts.applyWidget(table, doAll); + } + }; + + // 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 + ts.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 (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); + }; + + ts.formatFloat = function(s, table) { + if (typeof(s) !== 'string' || s === '') { return s; } + // allow using formatFloat without a table; defaults to US number format + var i, + t = table && table.config ? table.config.usNumberFormat !== false : + typeof table !== "undefined" ? table : true; + if (t) { + // 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)) { + // make (#) into a negative number -> (10) = -10 + s = s.replace(/^\s*\(/,'-').replace(/\)/,''); + } + i = parseFloat(s); + // return the text instead of zero + return isNaN(i) ? $.trim(s) : i; + }; + + ts.isDigit = function(s) { + // replace all unwanted chars and match + return isNaN(s) ? (/^[\-+(]?\d+[)]?$/).test(s.toString().replace(/[,.'"\s]/g, '')) : true; + }; + + }() + }); + + // 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 (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\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\s?%|%\s?\d)/).test(s); + }, + format: function(s, table) { + return ts.formatFloat(s.replace(/%/g, ""), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "usLongDate", + is: function(s) { + // two digit years are not allowed cross-browser + return (/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i).test(s); + }, + format: function(s, table) { + return ts.formatFloat( (new Date(s.replace(/(\S)([AP]M)$/i, "$1 $2")).getTime() || ''), table); + }, + type: "numeric" + }); + + ts.addParser({ + id: "shortDate", // "mmddyyyy", "ddmmyyyy" or "yyyymmdd" + is: function(s) { + // testing for ####-##-####, so it's not perfect + return (/^(\d{1,2}|\d{4})[\/\-\,\.\s+]\d{1,2}[\/\-\.\,\s+](\d{1,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]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i).test(s); + }, + format: function(s, table) { + return ts.formatFloat( (new Date("2000/01/01 " + s.replace(/(\S)([AP]M)$/i, "$1 $2")).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, c, wo) { + var $tb, $tv, $tr, row, even, time, k, l, + child = new RegExp(c.cssChildRow, 'i'), + b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'); + 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.children('tr:visible'); + // revered back to using jQuery each - strangely it's the fastest method + /*jshint loopfunc:true */ + $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(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1]); + }); + } + } + if (c.debug) { + ts.benchmark("Applying Zebra widget", time); + } + }, + remove: function(table, c, wo){ + var k, $tb, + b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'), + rmv = (c.widgetOptions.zebra || [ "even", "odd" ]).join(' '); + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, $(b[k]), true); // remove tbody + $tb.children().removeClass(rmv); + $.tablesorter.processTbody(table, $tb, false); // restore tbody + } + } + }); + +})(jQuery); \ No newline at end of file diff --git a/sitestatic/jquery.tablesorter-2.7.min.js b/sitestatic/jquery.tablesorter-2.7.min.js new file mode 100644 index 00000000..cf5e3068 --- /dev/null +++ b/sitestatic/jquery.tablesorter-2.7.min.js @@ -0,0 +1,5 @@ +/*! +* TableSorter 2.7 min - Client-side table sorting with ease! +* Copyright (c) 2007 Christian Bach +*/ +!function(g){g.extend({tablesorter:new function(){function d(c){"undefined"!==typeof console&&"undefined"!==typeof console.log?console.log(c):alert(c)}function v(c,b){d(c+" ("+((new Date).getTime()-b.getTime())+"ms)")}function p(c,b,a){if(!b)return"";var f=c.config,h=f.textExtraction,e="",e="simple"===h?f.supportsTextContent?b.textContent:g(b).text():"function"===typeof h?h(b,c,a):"object"===typeof h&&h.hasOwnProperty(a)?h[a](b,c,a):f.supportsTextContent?b.textContent:g(b).text();return g.trim(e)} function k(c){var b=c.config,a=g(c.tBodies).filter(":not(."+b.cssInfoBlock+")"),f,h,s,j,m,l,n="";if(0===a.length)return b.debug?d("*Empty table!* Not building a parser cache"):"";a=a[0].rows;if(a[0]){f=[];h=a[0].cells.length;for(s=0;s thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]};e.benchmark=v;e.construct=function(c){return this.each(function(){if(!this.tHead||0===this.tBodies.length||!0===this.hasInitialized)return this.config.debug?d("stopping initialization! No thead, tbody or tablesorter has already been initialized"):"";var b=g(this),a,f,h,s="",j,m,l,n,D=g.metadata;this.hasInitialized= !1;this.config={};a=g.extend(!0,this.config,e.defaults,c);g.data(this,"tablesorter",a);a.debug&&g.data(this,"startoveralltimer",new Date);a.supportsTextContent="x"===g("x")[0].textContent;a.supportsDataObject=1.4<=parseFloat(g.fn.jquery);a.string={max:1,min:-1,"max+":1,"max-":-1,zero:0,none:0,"null":0,top:!0,bottom:!1};/tablesorter\-/.test(b.attr("class"))||(s=""!==a.theme?" tablesorter-"+a.theme:"");b.addClass(a.tableClass+s);var r=[],P={},y=g(this).find("thead:eq(0), tfoot").children("tr"), I,J,x,z,N,B,K,Q,R,G;for(I=0;I
':"";r=g(this).find(w.selectorHeaders).each(function(a){A=g(this);L=w.headers[a];w.headerContent[a]=this.innerHTML;M=w.headerTemplate.replace(/\{content\}/g,this.innerHTML).replace(/\{icon\}/g,S);w.onRenderTemplate&&(O=w.onRenderTemplate.apply(A,[a,M]))&&"string"===typeof O&&(M=O);this.innerHTML='
'+M+"
";w.onRenderHeader&&w.onRenderHeader.apply(A,[a]);this.column=P[this.parentNode.rowIndex+"-"+this.cellIndex];var b=e.getData(A,L,"sortInitialOrder")|| w.sortInitialOrder;this.order=/^d/i.test(b)||1===b?[1,0,2]:[0,1,2];this.count=-1;"false"===e.getData(A,L,"sorter")?(this.sortDisabled=!0,A.addClass("sorter-false")):A.removeClass("sorter-false");this.lockedOrder=!1;H=e.getData(A,L,"lockedOrder")||!1;"undefined"!==typeof H&&!1!==H&&(this.order=this.lockedOrder=/^d/i.test(H)||1===H?[1,1,1]:[0,0,0]);A.addClass((this.sortDisabled?"sorter-false ":" ")+w.cssHeader);w.headerList[a]=this;A.parent().addClass(w.cssHeaderRow)});this.config.debug&&(v("Built headers:", T),d(r));a.$headers=r;a.parsers=k(this);a.delayInit||q(this);a.$headers.find("*").andSelf().filter(a.selectorSort).unbind("mousedown.tablesorter mouseup.tablesorter").bind("mousedown.tablesorter mouseup.tablesorter",function(c,d){var k=(this.tagName.match("TH|TD")?g(this):g(this).parents("th, td").filter(":last"))[0];if(1!==(c.which||c.button))return!1;if("mousedown"===c.type)return n=(new Date).getTime(),"INPUT"===c.target.tagName?"":!a.cancelSelection;if(!0!==d&&250<(new Date).getTime()-n)return!1; a.delayInit&&!a.cache&&q(b[0]);if(!k.sortDisabled){b.trigger("sortStart",b[0]);s=!c[a.sortMultiSortKey];k.count=c[a.sortResetKey]?2:(k.count+1)%(a.sortReset?3:2);a.sortRestart&&(f=k,a.$headers.each(function(){if(this!==f&&(s||!g(this).is("."+a.cssDesc+",."+a.cssAsc)))this.count=-1}));f=k.column;if(s){a.sortList=[];if(null!==a.sortForce){j=a.sortForce;for(h=0;hl&&(a.sortList.push([f,l]),1l&&(a.sortList.push([f,l]),1"),V=g(this).width();g("tr:first td",this.tBodies[0]).each(function(){U.append(g("").css("width",parseInt(1E3*(g(this).width()/V),10)/10+"%"))});g(this).prepend(U)}a.showProcessing&&b.unbind("sortBegin sortEnd").bind("sortBegin sortEnd", function(a){e.isProcessing(b[0],"sortBegin"===a.type)});this.hasInitialized=!0;a.debug&&e.benchmark("Overall initialization time",g.data(this,"startoveralltimer"));b.trigger("tablesorter-initialized",this);"function"===typeof a.initialized&&a.initialized(this)})};e.isProcessing=function(c,b,a){var f=c.config;c=a||g(c).find("."+f.cssHeader);b?(0'),c=g.fn.detach?b.detach():b.remove();c=g(c).find("span.tablesorter-savemyplace");b.insertAfter(c);c.remove()};e.clearTableBody=function(c){g(c.tBodies).filter(":not(."+c.config.cssInfoBlock+")").empty()};e.destroy=function(c,b,a){var f=g(c),h=c.config,d=f.find("thead:first");c.hasInitialized=!1;d.find("tr:not(."+h.cssHeaderRow+")").remove();d.find(".tablesorter-resizer").remove(); e.refreshWidgets(c,!0,!0);f.removeData("tablesorter").unbind("sortReset update updateCell addRows sorton appendCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave").find("."+h.cssHeader).unbind("click mousedown mousemove mouseup").removeClass(h.cssHeader+" "+h.cssAsc+" "+h.cssDesc).find(".tablesorter-header-inner").each(function(){""!==h.cssIcon&&g(this).find("."+h.cssIcon).remove();g(this).replaceWith(g(this).contents())});!1!==b&&f.removeClass(h.tableClass);"function"===typeof a&& a(c)};e.regex=[/(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,/^0x[0-9a-f]+$/i];e.sortText=function(c,b,a,f){if(b===a)return 0;var h=c.config,d=h.string[h.empties[f]||h.emptyTo],j=e.regex;if(""===b&&0!==d)return"boolean"===typeof d?d?-1:1:-d||-1;if(""===a&&0!==d)return"boolean"===typeof d?d?1:-1:d||1;if("function"===typeof h.textSorter)return h.textSorter(b,a,c,f);c=b.replace(j[0], "\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");f=a.replace(j[0],"\\0$1\\0").replace(/\\0$/,"").replace(/^\\0/,"").split("\\0");b=parseInt(b.match(j[2]),16)||1!==c.length&&b.match(j[1])&&Date.parse(b);if(a=parseInt(a.match(j[2]),16)||b&&a.match(j[1])&&Date.parse(a)||null){if(ba)return 1}h=Math.max(c.length,f.length);for(b=0;bj)return 1}return 0};e.sortTextDesc=function(c,b,a,f){if(b===a)return 0;var d=c.config,g=d.string[d.empties[f]||d.emptyTo];return""===b&&0!==g?"boolean"===typeof g?g?-1:1:g||1:""===a&&0!==g?"boolean"===typeof g?g?1:-1:-g||-1:"function"===typeof d.textSorter?d.textSorter(a,b,c,f):e.sortText(c,a,b)};e.getTextValue=function(c,b,a){if(b){var f=c.length,d=b+a;for(b=0;bg.inArray(j[f].id,k)))h.debug&&d("Refeshing widgets: Removing "+j[f].id),j[f].hasOwnProperty("remove")&&j[f].remove(c,h,h.widgetOptions);!0!==a&&e.applyWidget(c,b)};e.getData=function(c,b,a){var d="";c=g(c);var e,k;if(!c.length)return"";e=g.metadata?c.metadata():!1;k=" "+(c.attr("class")||"");"undefined"!==typeof c.data(a)||"undefined"!==typeof c.data(a.toLowerCase())?d+=c.data(a)||c.data(a.toLowerCase()):e&&"undefined"!==typeof e[a]?d+=e[a]:b&&"undefined"!== typeof b[a]?d+=b[a]:" "!==k&&k.match(" "+a+"-")&&(d=k.match(RegExp(" "+a+"-(\\w+)"))[1]||"");return g.trim(d)};e.formatFloat=function(c,b){if("string"!==typeof c||""===c)return c;var a;c=(b&&b.config?!1!==b.config.usNumberFormat:"undefined"!==typeof b?b:1)?c.replace(/,/g,""):c.replace(/[\s|\.]/g,"").replace(/,/g,".");/^\s*\([.\d]+\)/.test(c)&&(c=c.replace(/^\s*\(/,"-").replace(/\)/,""));a=parseFloat(c);return isNaN(a)?g.trim(c):a};e.isDigit=function(c){return isNaN(c)?/^[\-+(]?\d+[)]?$/.test(c.toString().replace(/[,.'"\s]/g, "")):!0}}});var k=g.tablesorter;g.fn.extend({tablesorter:k.construct});k.addParser({id:"text",is:function(){return!0},format:function(d,v){var p=v.config;d=g.trim(p.ignoreCase?d.toLocaleLowerCase():d);return p.sortLocaleCompare?k.replaceAccents(d):d},type:"text"});k.addParser({id:"currency",is:function(d){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test(d)},format:function(d,g){return k.formatFloat(d.replace(/[^\w,. \-()]/g,""),g)},type:"numeric"}); k.addParser({id:"ipAddress",is:function(d){return/^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/.test(d)},format:function(d,g){var p,u=d.split("."),q="",t=u.length;for(p=0;p