diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 00000000..d0bf1c21 --- /dev/null +++ b/.jscsrc @@ -0,0 +1,65 @@ +{ + "disallowDanglingUnderscores": true, + "disallowEmptyBlocks": true, + "disallowKeywords": [ + "with" + ], + "disallowMixedSpacesAndTabs": "smart", + "disallowMultipleLineBreaks": true, + "disallowMultipleLineStrings": true, + "disallowNewlineBeforeBlockStatements": true, + "disallowOperatorBeforeLineBreak": [ + "." + ], + "disallowSpaceAfterPrefixUnaryOperators": true, + "disallowSpaceBeforeBinaryOperators": [ + "," + ], + "disallowSpaceBeforePostfixUnaryOperators": true, + "disallowSpacesInCallExpression": true, + + "disallowTrailingComma": true, + "maximumLineLength": { + "allowRegex": true, + "allowUrlComments": true, + "tabSize": 2, + "value": 180 + }, + "requireBlocksOnNewline": 1, + "requireCommaBeforeLineBreak": true, + "requireCurlyBraces": [ + "do" + ], + "requireDollarBeforejQueryAssignment": true, + "requireDotNotation": true, + "requireLineFeedAtFileEnd": true, + "requireParenthesesAroundIIFE": true, + "requireSemicolons": true, + "requireSpaceAfterBinaryOperators": true, + "requireSpaceAfterKeywords": [ + "if", + "else", + "for", + "while", + "do", + "switch", + "case", + "return", + "try", + "catch", + "typeof" + ], + "requireSpaceAfterLineComment": true, + "requireSpaceBeforeBinaryOperators": true, + "requireSpacesInConditionalExpression": true, + "requireSpacesInForStatement": true, + "requireSpacesInsideArrayBrackets": "all", + "validateIndentation": { + "includeEmptyLines": false, + "value": "\t" + }, + "validateQuoteMarks": { + "escape": true, + "mark": "'" + } +} \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index bacec65e..60beaebb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -209,6 +209,26 @@ module.exports = function( grunt ) { } }, + jscs: { + src: [ + 'addons/pager/*.js', + '!addons/pager/*.min.js', + 'js/jquery.*.js', + 'js/**/*.js', + '!js/_test-*.js', + '!js/jquery.tablesorter.combined.js', + '!js/jquery.tablesorter.widgets.js', + '!js/extras/jquery.dragtable.mod.js', // indents with spaces; keeping original formatting to make diffs easier + '!js/extras/jquery.metadata.js', // phasing this one out anyway + '!js/**/_test-*.js', + '!js/*.min.js', + '!js/**/semver*.js' + ], + options: { + config: '.jscsrc' + } + }, + jshint: { files: { src: [ @@ -317,8 +337,9 @@ module.exports = function( grunt ) { grunt.loadNpmTasks( 'grunt-contrib-copy' ); grunt.loadNpmTasks( 'grunt-contrib-watch' ); grunt.loadNpmTasks( 'grunt-contrib-cssmin' ); + grunt.loadNpmTasks( 'grunt-jscs' ); - grunt.registerTask( 'test', [ 'jshint', 'qunit' ] ); + grunt.registerTask( 'test', [ 'jscs', 'jshint', 'qunit' ] ); tasks = [ 'clean:build', diff --git a/addons/pager/jquery.tablesorter.pager.js b/addons/pager/jquery.tablesorter.pager.js index e8db92ca..006143fb 100644 --- a/addons/pager/jquery.tablesorter.pager.js +++ b/addons/pager/jquery.tablesorter.pager.js @@ -4,7 +4,7 @@ */ /*jshint browser:true, jquery:true, unused:false */ ;(function($) { - "use strict"; + 'use strict'; /*jshint supernew:true */ var ts = $.tablesorter; @@ -176,13 +176,13 @@ // form the output string (can now get a new output string from the server) s = ( p.ajaxData && p.ajaxData.output ? p.ajaxData.output || p.output : p.output ) // {page} = one-based index; {page+#} = zero based index +/- value - .replace(/\{page([\-+]\d+)?\}/gi, function(m,n){ + .replace(/\{page([\-+]\d+)?\}/gi, function(m, n){ return p.totalPages ? p.page + (n ? parseInt(n, 10) : 1) : 0; }) // {totalPages}, {extra}, {extra:0} (array) or {extra : key} (object) .replace(/\{\w+(\s*:\s*\w+)?\}/gi, function(m){ var len, indx, - str = m.replace(/[{}\s]/g,''), + str = m.replace(/[{}\s]/g, ''), extra = str.split(':'), data = p.ajaxData, // return zero for default page/row numbers @@ -209,7 +209,7 @@ // rebind startRow/page inputs $out.find('.ts-startRow, .ts-page').unbind('change' + namespace).bind('change' + namespace, function(){ var v = $(this).val(), - pg = $(this).hasClass('ts-startRow') ? Math.floor( v/p.size ) + 1 : v; + pg = $(this).hasClass('ts-startRow') ? Math.floor( v / p.size ) + 1 : v; c.$table.trigger('pageSet' + namespace, [ pg ]); }); } @@ -242,7 +242,7 @@ current_page = p.page + 1, start_page = skip_set_size, end_page = pg - skip_set_size, - option_pages = [1], + option_pages = [ 1 ], // construct default options pages array option_pages_start_page = (large_collection) ? skip_set_size : 1; @@ -286,7 +286,7 @@ option_pages = $.grep(option_pages, function(value, indx) { return $.inArray(value, option_pages) === indx; }) - .sort(function(a,b) { return a - b; }); + .sort(function(a, b) { return a - b; }); return option_pages; }, @@ -376,7 +376,7 @@ renderAjax = function(data, table, p, xhr, exception){ // process data - if ( typeof(p.ajaxProcessing) === "function" ) { + if ( typeof p.ajaxProcessing === 'function' ) { // ajaxProcessing result: [ total, rows, headers ] var i, j, t, hsh, $f, $sh, $headers, $h, icon, th, d, l, rr_count, len, c = table.config, @@ -483,7 +483,7 @@ p.last.currentFilters = p.currentFilters; p.last.sortList = (c.sortList || []).join(','); updatePageDisplay(table, p, false); - $table.trigger('updateCache', [function(){ + $table.trigger('updateCache', [ function(){ if (p.initialized) { // apply widgets after table has rendered & after a delay to prevent // multiple applyWidget blocking code from blocking this trigger @@ -495,9 +495,9 @@ .trigger('applyWidgets') .trigger('pagerChange', p); updatePageDisplay(table, p, true); - }, 0); + }, 0); } - }]); + } ]); } if (!p.initialized) { @@ -555,7 +555,7 @@ c = table.config, url = (p.ajaxUrl) ? p.ajaxUrl // allow using "{page+1}" in the url string to switch to a non-zero based index - .replace(/\{page([\-+]\d+)?\}/, function(s,n){ return p.page + (n ? parseInt(n, 10) : 0); }) + .replace(/\{page([\-+]\d+)?\}/, function(s, n){ return p.page + (n ? parseInt(n, 10) : 0); }) .replace(/\{size\}/g, p.size) : '', sortList = c.sortList, filterList = p.currentFilters || $(table).data('lastSearch') || [], @@ -584,7 +584,7 @@ url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol ); p.currentFilters = filterList; } - if ( typeof(p.customAjaxUrl) === "function" ) { + if ( typeof p.customAjaxUrl === 'function' ) { url = p.customAjaxUrl(table, url); } if (c.debug) { @@ -940,13 +940,13 @@ changeHeight(table, p); updatePageDisplay(table, p, true); }) - .bind('pageSize refreshComplete '.split(' ').join(namespace + ' '), function(e,v){ + .bind('pageSize refreshComplete '.split(' ').join(namespace + ' '), function(e, v){ e.stopPropagation(); setPageSize(table, parseInt(v, 10) || p.settings.size || 10, p); hideRows(table, p); updatePageDisplay(table, p, false); }) - .bind('pageSet pagerUpdate '.split(' ').join(namespace + ' '), function(e,v){ + .bind('pageSet pagerUpdate '.split(' ').join(namespace + ' '), function(e, v){ e.stopPropagation(); // force pager refresh if (e.type === 'pagerUpdate') { @@ -973,7 +973,7 @@ ts.log('Pager: >> Container not found'); } pager.find(ctrls.join(',')) - .attr("tabindex", 0) + .attr('tabindex', 0) .unbind('click' + namespace) .bind('click' + namespace, function(e){ e.stopPropagation(); @@ -1024,17 +1024,18 @@ $t.trigger('pagerBeforeInitialized', p); enablePager(table, p, false); - if ( typeof(p.ajaxUrl) === 'string' ) { + if ( typeof p.ajaxUrl === 'string' ) { // ajax pager; interact with database p.ajax = true; - //When filtering with ajax, allow only custom filtering function, disable default filtering since it will be done server side. + // When filtering with ajax, allow only custom filtering function, disable default + // filtering since it will be done server side. c.widgetOptions.filter_serversideFiltering = true; c.serverSideSorting = true; moveToPage(table, p); } else { p.ajax = false; // Regular pager; all rows stored in memory - $(this).trigger("appendCache", true); + $(this).trigger('appendCache', true); hideRowsSetup(table, p); } @@ -1087,9 +1088,9 @@ } }; -// extend plugin scope -$.fn.extend({ - tablesorterPager: $.tablesorterPager.construct -}); + // extend plugin scope + $.fn.extend({ + tablesorterPager: $.tablesorterPager.construct + }); })(jQuery); diff --git a/dist/js/jquery.tablesorter.combined.js b/dist/js/jquery.tablesorter.combined.js index 06d57b36..c90703f4 100644 --- a/dist/js/jquery.tablesorter.combined.js +++ b/dist/js/jquery.tablesorter.combined.js @@ -200,7 +200,7 @@ // node could be a jquery object // http://jsperf.com/jquery-vs-instanceof-jquery/2 $node = node.jquery ? node : $(node); - if (typeof(t) === 'string') { + if (typeof t === 'string') { // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow! // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/ if ( t === 'basic' && typeof ( te = $node.attr(c.textAttribute) ) !== 'undefined' ) { @@ -208,7 +208,7 @@ } return $.trim( node.textContent || $node.text() ); } else { - if (typeof(t) === 'function') { + if (typeof t === 'function') { return $.trim( t($node[0], c.table, cellIndex) ); } else if (typeof (te = ts.getColumnData( c.table, t, cellIndex )) === 'function') { return $.trim( te($node[0], c.table, cellIndex) ); @@ -268,7 +268,7 @@ // make sure txt is a string (extractor may have converted it) parser.format( '' + txt, c.table, cell, colIndex ); if ( c.ignoreCase && typeof val === 'string' ) { - val = val.toLowerCase(); + val = val.toLowerCase(); } } return val; @@ -512,20 +512,22 @@ // set up header template t = c.headerTemplate.replace(/\{content\}/g, $t.html()).replace(/\{icon\}/g, $t.find('.' + ts.css.icon).length ? '' : i); if (c.onRenderTemplate) { - h = c.onRenderTemplate.apply($t, [index, t]); + h = c.onRenderTemplate.apply( $t, [ index, t ] ); if (h && typeof h === 'string') { t = h; } // only change t if something is returned } $t.html('
' + t + '
'); // faster than wrapInner } - if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index, c, c.$table]); } + if (c.onRenderHeader) { c.onRenderHeader.apply( $t, [ index, c, c.$table ] ); } // *** remove this.column value if no conflicts found elem.column = parseInt( $t.attr('data-column'), 10); - elem.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2]; + elem.order = formatSortingOrder( ts.getData( $t, ch, 'sortInitialOrder' ) || c.sortInitialOrder ) ? + [ 1, 0, 2 ] : // desc, asc, unsorted + [ 0, 1, 2 ]; // asc, desc, unsorted elem.count = -1; // set to -1 because clicking on the header automatically adds one elem.lockedOrder = false; lock = ts.getData($t, ch, 'lockedOrder') || false; if (typeof lock !== 'undefined' && lock !== false) { - elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0]; + elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [ 1, 1, 1 ] : [ 0, 0, 0 ]; } $t.addClass(ts.css.header + ' ' + c.cssHeader); // add cell to headerList @@ -595,9 +597,9 @@ list = c.sortList, len = list.length, none = ts.css.sortNone + ' ' + c.cssNone, - css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc], + css = [ ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc ], cssIcon = [ c.cssIconAsc, c.cssIconDesc, c.cssIconNone ], - aria = ['ascending', 'descending'], + aria = [ 'ascending', 'descending' ], // find the footer $t = $(table).find('tfoot tr').children() .add( $( c.namespace + '_extra_headers' ) ) @@ -667,7 +669,7 @@ dir = ('' + val[1]).match(/^(1|d|s|o|n)/); dir = dir ? dir[0] : ''; // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext - switch(dir) { + switch (dir) { case '1': case 'd': // descending dir = 1; break; @@ -743,11 +745,11 @@ // add column to sort list order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -758,7 +760,7 @@ for (col = 0; col < c.sortAppend.length; col++) { s = ts.isValueInArray(c.sortAppend[col][0], c.sortList); if (s >= 0) { - c.sortList.splice(s,1); + c.sortList.splice(s, 1); } } } @@ -772,7 +774,7 @@ // order.count seems to be incorrect when compared to cell.count s[1] = order.order[cell.count]; if (s[1] === 2) { - c.sortList.splice(col,1); + c.sortList.splice(col, 1); order.count = -1; } } @@ -781,11 +783,11 @@ // add column to sort list array order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -859,10 +861,10 @@ x = dir ? a : b; y = dir ? b : a; // text sort function - if (typeof(cts) === 'function') { + if (typeof cts === 'function') { // custom OVERALL text sorter sort = cts(x[col], y[col], dir, col, table); - } else if (typeof(cts) === 'object' && cts.hasOwnProperty(col)) { + } else if (typeof cts === 'object' && cts.hasOwnProperty(col)) { // custom text sorter for a SPECIFIC COLUMN sort = cts[col](x[col], y[col], dir, col, table); } else { @@ -895,14 +897,14 @@ // this will catch spamming of the updateCell method if (resrt !== false && !c.serverSideSorting && !c.table.isProcessing) { if (sl.length) { - c.$table.trigger('sorton', [sl, function(){ + c.$table.trigger('sorton', [ sl, function(){ resortComplete(c, callback); - }, true]); + }, true ]); } else { - c.$table.trigger('sortReset', [function(){ + c.$table.trigger('sortReset', [ function(){ resortComplete(c, callback); ts.applyWidget(c.table, false); - }]); + } ]); } } else { resortComplete(c, callback); @@ -1102,10 +1104,10 @@ ts.construct = function(settings) { return this.each(function() { var table = this, - // merge & extend config options - c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); - // save initial settings - c.originalSettings = settings; + // merge & extend config options + c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); + // save initial settings + c.originalSettings = settings; // create a table from data (build table widget) if (!table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE') { // return the table (in case the original target is the table's container) @@ -1160,7 +1162,7 @@ c.namespace = '.tablesorter' + Math.random().toString(16).slice(2); } else { // make sure namespace starts with a period & doesn't have weird characters - c.namespace = '.' + c.namespace.replace(/\W/g,''); + c.namespace = '.' + c.namespace.replace(/\W/g, ''); } c.$table.children().children('tr').attr('role', 'row'); @@ -1204,7 +1206,7 @@ ts.applyWidget(table, true); // if user has supplied a sort list to constructor if (c.sortList.length > 0) { - $table.trigger('sorton', [c.sortList, {}, !c.initWidgets, true]); + $table.trigger('sorton', [ c.sortList, {}, !c.initWidgets, true ]); } else { setHeadersCss(table); if (c.initWidgets) { @@ -1243,22 +1245,22 @@ table = $(table)[0]; var overallWidth, percent, $tbodies, len, index, c = table.config, - colgroup = c.$table.children('colgroup'); + $colgroup = c.$table.children('colgroup'); // remove plugin-added colgroup, in case we need to refresh the widths - if (colgroup.length && colgroup.hasClass(ts.css.colgroup)) { - colgroup.remove(); + if ($colgroup.length && $colgroup.hasClass(ts.css.colgroup)) { + $colgroup.remove(); } if (c.widthFixed && c.$table.children('colgroup').length === 0) { - colgroup = $(''); + $colgroup = $(''); overallWidth = c.$table.width(); // only add col for visible columns - fixes #371 - $tbodies = c.$tbodies.find('tr:first').children(':visible'); //.each(function() + $tbodies = c.$tbodies.find('tr:first').children(':visible'); // .each(function() len = $tbodies.length; for ( index = 0; index < len; index++ ) { percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%'; - colgroup.append( $('').css('width', percent) ); + $colgroup.append( $('').css('width', percent) ); } - c.$table.prepend(colgroup); + c.$table.prepend($colgroup); } }; @@ -1305,12 +1307,12 @@ cellId = rowIndex + '-' + $cell.index(); rowSpan = cell.rowSpan || 1; colSpan = cell.colSpan || 1; - if (typeof(matrix[rowIndex]) === 'undefined') { + 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') { + if (typeof matrix[rowIndex][k] === 'undefined') { firstAvailCol = k; break; } @@ -1319,7 +1321,7 @@ // add data-column $cell.attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex for (k = rowIndex; k < rowIndex + rowSpan; k++) { - if (typeof(matrix[k]) === 'undefined') { + if (typeof matrix[k] === 'undefined') { matrix[k] = []; } matrixrow = matrix[k]; @@ -1335,10 +1337,10 @@ // *** Process table *** // add processing indicator ts.isProcessing = function(table, toggle, $ths) { - table = $(table); - var c = table[0].config, + $table = $(table); + var c = $table[0].config, // default to all headers - $h = $ths || table.find('.' + ts.css.header); + $h = $ths || $table.find('.' + ts.css.header); if (toggle) { // don't use sortList if custom $ths used if (typeof $ths !== 'undefined' && c.sortList.length > 0) { @@ -1348,9 +1350,9 @@ return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList) >= 0; }); } - table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); } else { - table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); } }; @@ -1480,8 +1482,8 @@ $f = $t.find('tfoot:first > tr').children('th, td'); if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) { // reapply uitheme classes, in case we want to maintain appearance - $t.trigger('applyWidgetId', ['uitheme']); - $t.trigger('applyWidgetId', ['zebra']); + $t.trigger('applyWidgetId', [ 'uitheme' ]); + $t.trigger('applyWidgetId', [ 'zebra' ]); } // remove widget added rows, just in case $h.find('tr').not($r).remove(); @@ -1493,7 +1495,7 @@ .removeData('tablesorter') .unbind( events.replace(/\s+/g, ' ') ); c.$headers.add($f) - .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') ) + .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join(' ') ) .removeAttr('data-column') .removeAttr('aria-label') .attr('aria-disabled', 'true'); @@ -1699,8 +1701,8 @@ }; ts.hasWidget = function(table, name){ - table = $(table); - return table.length && table[0].config && table[0].config.widgetInit[name] || false; + $table = $(table); + return $table.length && $table[0].config && $table[0].config.widgetInit[name] || false; }; ts.getWidgetById = function(name) { @@ -1740,7 +1742,7 @@ if (c.debug) { time = new Date(); } // look for widgets to apply from in table class // stop using \b otherwise this matches 'ui-widget-content' & adds 'content' widget - wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' )+ '\\s', 'g' ); + wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' ) + '\\s', 'g' ); if ( tableClass.match( wd ) ) { // extract out the widget id from the table class (widget id's can include dashes) w = tableClass.match( wd ); @@ -1948,13 +1950,13 @@ typeof table !== 'undefined' ? table : true; if (t) { // US Format - 1,234,567.89 -> 1234567.89 - s = s.replace(/,/g,''); + 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,'.'); + s = s.replace(/[\s|\.]/g, '').replace(/,/g, '.'); } - if(/^\s*\([.\d]+\)/.test(s)) { + if (/^\s*\([.\d]+\)/.test(s)) { // make (#) into a negative number -> (10) = -10 s = s.replace(/^\s*\(([.\d]+)\)/, '-$1'); } @@ -2022,7 +2024,7 @@ ts.addParser({ id: 'currency', is: function(s) { - return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g,'')); // £$€¤¥¢ + return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g, '')); // £$€¤¥¢ }, format: function(s, table) { var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table); @@ -2097,7 +2099,7 @@ id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd' is: function(s) { // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included - return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g,' ').replace(/[\-.,]/g, '/')); + return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g, ' ').replace(/[\-.,]/g, '/')); }, format: function(s, table, cell, cellIndex) { if (s) { @@ -2190,356 +2192,356 @@ /*! Widget: storage - updated 3/26/2015 (v2.21.3) */ ;(function ($, window, document) { -'use strict'; + 'use strict'; -var ts = $.tablesorter || {}; -// *** Store data in local storage, with a cookie fallback *** -/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) - if you need it, then include https://github.com/douglascrockford/JSON-js + var ts = $.tablesorter || {}; + // *** Store data in local storage, with a cookie fallback *** + /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) + if you need it, then include https://github.com/douglascrockford/JSON-js - $.parseJSON is not available is jQuery versions older than 1.4.1, using older - versions will only allow storing information for one page at a time + $.parseJSON is not available is jQuery versions older than 1.4.1, using older + versions will only allow storing information for one page at a time - // *** Save data (JSON format only) *** - // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid - var val = { "mywidget" : "data1" }; // valid JSON uses double quotes - // $.tablesorter.storage(table, key, val); - $.tablesorter.storage(table, 'tablesorter-mywidget', val); + // *** Save data (JSON format only) *** + // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid + var val = { "mywidget" : "data1" }; // valid JSON uses double quotes + // $.tablesorter.storage(table, key, val); + $.tablesorter.storage(table, 'tablesorter-mywidget', val); - // *** Get data: $.tablesorter.storage(table, key); *** - v = $.tablesorter.storage(table, 'tablesorter-mywidget'); - // val may be empty, so also check for your data - val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; - alert(val); // "data1" if saved, or "" if not -*/ -ts.storage = function(table, key, value, options) { - table = $(table)[0]; - var cookieIndex, cookies, date, - hasStorage = false, - values = {}, - c = table.config, - wo = c && c.widgetOptions, - storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? - 'sessionStorage' : 'localStorage', - $table = $(table), - // id from (1) options ID, (2) table "data-table-group" attribute, (3) widgetOptions.storage_tableId, - // (4) table ID, then (5) table index - id = options && options.id || - $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || - wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), - // url from (1) options url, (2) table "data-table-page" attribute, (3) widgetOptions.storage_fixedUrl, - // (4) table.config.fixedUrl (deprecated), then (5) window location path - url = options && options.url || - $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || - wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; - // https://gist.github.com/paulirish/5558557 - if (storageType in window) { - try { - window[storageType].setItem('_tmptest', 'temp'); - hasStorage = true; - window[storageType].removeItem('_tmptest'); - } catch(error) { - if (c && c.debug) { - ts.log( storageType + ' is not supported in this browser' ); + // *** Get data: $.tablesorter.storage(table, key); *** + v = $.tablesorter.storage(table, 'tablesorter-mywidget'); + // val may be empty, so also check for your data + val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; + alert(val); // 'data1' if saved, or '' if not + */ + ts.storage = function(table, key, value, options) { + table = $(table)[0]; + var cookieIndex, cookies, date, + hasStorage = false, + values = {}, + c = table.config, + wo = c && c.widgetOptions, + storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? + 'sessionStorage' : 'localStorage', + $table = $(table), + // id from (1) options ID, (2) table 'data-table-group' attribute, (3) widgetOptions.storage_tableId, + // (4) table ID, then (5) table index + id = options && options.id || + $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || + wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), + // url from (1) options url, (2) table 'data-table-page' attribute, (3) widgetOptions.storage_fixedUrl, + // (4) table.config.fixedUrl (deprecated), then (5) window location path + url = options && options.url || + $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || + wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; + // https://gist.github.com/paulirish/5558557 + if (storageType in window) { + try { + window[storageType].setItem('_tmptest', 'temp'); + hasStorage = true; + window[storageType].removeItem('_tmptest'); + } catch (error) { + if (c && c.debug) { + ts.log( storageType + ' is not supported in this browser' ); + } } } - } - // *** get value *** - if ($.parseJSON) { - if (hasStorage) { - values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + // *** get value *** + if ($.parseJSON) { + if (hasStorage) { + values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + } else { + // old browser, using cookies + cookies = document.cookie.split(/[;\s|=]/); + // add one to get from the key to the value + cookieIndex = $.inArray(key, cookies) + 1; + values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + } + } + // allow value to be an empty string too + if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { + // add unique identifiers = url pathname > table ID/index on page > data + if (!values[url]) { + values[url] = {}; + } + values[url][id] = value; + // *** set value *** + if (hasStorage) { + window[storageType][key] = JSON.stringify(values); + } else { + date = new Date(); + date.setTime(date.getTime() + (31536e+6)); // 365 days + document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/'; + } } else { - // old browser, using cookies - cookies = document.cookie.split(/[;\s|=]/); - // add one to get from the key to the value - cookieIndex = $.inArray(key, cookies) + 1; - values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + return values && values[url] ? values[url][id] : ''; } - } - // allow value to be an empty string too - if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { - // add unique identifiers = url pathname > table ID/index on page > data - if (!values[url]) { - values[url] = {}; - } - values[url][id] = value; - // *** set value *** - if (hasStorage) { - window[storageType][key] = JSON.stringify(values); - } else { - date = new Date(); - date.setTime(date.getTime() + (31536e+6)); // 365 days - document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/'; - } - } else { - return values && values[url] ? values[url][id] : ''; - } -}; + }; })(jQuery, window, document); /*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.themes = { - 'bootstrap' : { - table : 'table table-bordered table-striped', - caption : 'caption', - // header class names - header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) - sortNone : '', - sortAsc : '', - sortDesc : '', - active : '', // applied when column is sorted - hover : '', // custom css required - a defined bootstrap style may not override other classes - // icon class names - icons : '', // add "icon-white" to make them white; this icon class is added to the in the header - iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted - iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort - iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort - filterRow : '', // filter row class - footerRow : '', - footerCells : '', - even : '', // even row zebra striping - odd : '' // odd row zebra striping - }, - 'jui' : { - table : 'ui-widget ui-widget-content ui-corner-all', // table classes - caption : 'ui-widget-content', - // header class names - header : 'ui-widget-header ui-corner-all ui-state-default', // header classes - sortNone : '', - sortAsc : '', - sortDesc : '', - active : 'ui-state-active', // applied when column is sorted - hover : 'ui-state-hover', // hover class - // icon class names - icons : 'ui-icon', // icon class added to the in the header - iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted - iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort - iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort - filterRow : '', - footerRow : '', - footerCells : '', - even : 'ui-widget-content', // even row zebra striping - odd : 'ui-state-default' // odd row zebra striping - } -}; + ts.themes = { + 'bootstrap' : { + table : 'table table-bordered table-striped', + caption : 'caption', + // header class names + header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) + sortNone : '', + sortAsc : '', + sortDesc : '', + active : '', // applied when column is sorted + hover : '', // custom css required - a defined bootstrap style may not override other classes + // icon class names + icons : '', // add 'icon-white' to make them white; this icon class is added to the in the header + iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted + iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort + iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort + filterRow : '', // filter row class + footerRow : '', + footerCells : '', + even : '', // even row zebra striping + odd : '' // odd row zebra striping + }, + 'jui' : { + table : 'ui-widget ui-widget-content ui-corner-all', // table classes + caption : 'ui-widget-content', + // header class names + header : 'ui-widget-header ui-corner-all ui-state-default', // header classes + sortNone : '', + sortAsc : '', + sortDesc : '', + active : 'ui-state-active', // applied when column is sorted + hover : 'ui-state-hover', // hover class + // icon class names + icons : 'ui-icon', // icon class added to the in the header + iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted + iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort + iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort + filterRow : '', + footerRow : '', + footerCells : '', + even : 'ui-widget-content', // even row zebra striping + odd : 'ui-state-default' // odd row zebra striping + } + }; -$.extend(ts.css, { - wrapper : 'tablesorter-wrapper' // ui theme & resizable -}); + $.extend(ts.css, { + wrapper : 'tablesorter-wrapper' // ui theme & resizable + }); -ts.addWidget({ - id: "uitheme", - priority: 10, - format: function(table, c, wo) { - var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, - themesAll = ts.themes, - $table = c.$table.add( $( c.namespace + '_extra_table' ) ), - $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), - theme = c.theme || 'jui', - themes = themesAll[theme] || {}, - remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), - iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); - if (c.debug) { time = new Date(); } - // initialization code - run once - if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { - wo.uitheme_applied = true; - oldtheme = themesAll[c.appliedTheme] || {}; - hasOldTheme = !$.isEmptyObject(oldtheme); - oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; - oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; - if (hasOldTheme) { - wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); - wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); - c.$tbodies.children().removeClass( [oldtheme.even, oldtheme.odd].join(' ') ); - } - // update zebra stripes - if (themes.even) { wo.zebra[0] += ' ' + themes.even; } - if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } - // add caption style - $table.children('caption') - .removeClass(oldtheme.caption || '') - .addClass(themes.caption); - // add table/footer class names - $tfoot = $table - // remove other selected themes - .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) - .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name - .children('tfoot'); - c.appliedTheme = c.theme; - - if ($tfoot.length) { - $tfoot - // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed - .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) - .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); - } - // update header classes - $headers - .removeClass( (hasOldTheme ? [oldtheme.header, oldtheme.hover, oldremove].join(' ') : '') || '' ) - .addClass(themes.header) - .not('.sorter-false') - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') - .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { - // toggleClass with switch added in jQuery 1.3 - $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); - }); - - $headers.each(function(){ - var $this = $(this); - if (!$this.find('.' + ts.css.wrapper).length) { - // Firefox needs this inner div to position the icon & resizer correctly - $this.wrapInner('
'); + ts.addWidget({ + id: 'uitheme', + priority: 10, + format: function(table, c, wo) { + var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, + themesAll = ts.themes, + $table = c.$table.add( $( c.namespace + '_extra_table' ) ), + $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), + theme = c.theme || 'jui', + themes = themesAll[theme] || {}, + remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), + iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); + if (c.debug) { time = new Date(); } + // initialization code - run once + if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { + wo.uitheme_applied = true; + oldtheme = themesAll[c.appliedTheme] || {}; + hasOldTheme = !$.isEmptyObject(oldtheme); + oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; + oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; + if (hasOldTheme) { + wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); + wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); + c.$tbodies.children().removeClass( [ oldtheme.even, oldtheme.odd ].join(' ') ); } - }); - if (c.cssIcon) { - // if c.cssIcon is '', then no is added to the header + // update zebra stripes + if (themes.even) { wo.zebra[0] += ' ' + themes.even; } + if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } + // add caption style + $table.children('caption') + .removeClass(oldtheme.caption || '') + .addClass(themes.caption); + // add table/footer class names + $tfoot = $table + // remove other selected themes + .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) + .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name + .children('tfoot'); + c.appliedTheme = c.theme; + + if ($tfoot.length) { + $tfoot + // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed + .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) + .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); + } + // update header classes $headers - .find('.' + ts.css.icon) - .removeClass(hasOldTheme ? [oldtheme.icons, oldIconRmv].join(' ') : '') - .addClass(themes.icons || ''); - } - if ($table.hasClass('hasFilters')) { - $table.children('thead').children('.' + ts.css.filterRow) - .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') - .addClass(themes.filterRow || ''); - } - } - for (i = 0; i < c.columns; i++) { - $header = c.$headers - .add($(c.namespace + '_extra_headers')) - .not('.sorter-false') - .filter('[data-column="' + i + '"]'); - $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); - $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); - if ($h.length) { - $header.removeClass(remove); - $icon.removeClass(iconRmv); - if ($h[0].sortDisabled) { - // no sort arrows for disabled columns! - $icon.removeClass(themes.icons || ''); - } else { - hdr = themes.sortNone; - icon = themes.iconSortNone; - if ($h.hasClass(ts.css.sortAsc)) { - hdr = [themes.sortAsc, themes.active].join(' '); - icon = themes.iconSortAsc; - } else if ($h.hasClass(ts.css.sortDesc)) { - hdr = [themes.sortDesc, themes.active].join(' '); - icon = themes.iconSortDesc; + .removeClass( (hasOldTheme ? [ oldtheme.header, oldtheme.hover, oldremove ].join(' ') : '') || '' ) + .addClass(themes.header) + .not('.sorter-false') + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') + .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { + // toggleClass with switch added in jQuery 1.3 + $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); + }); + + $headers.each(function(){ + var $this = $(this); + if (!$this.find('.' + ts.css.wrapper).length) { + // Firefox needs this inner div to position the icon & resizer correctly + $this.wrapInner('
'); } - $header.addClass(hdr); - $icon.addClass(icon || ''); + }); + if (c.cssIcon) { + // if c.cssIcon is '', then no is added to the header + $headers + .find('.' + ts.css.icon) + .removeClass(hasOldTheme ? [ oldtheme.icons, oldIconRmv ].join(' ') : '') + .addClass(themes.icons || ''); + } + if ($table.hasClass('hasFilters')) { + $table.children('thead').children('.' + ts.css.filterRow) + .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') + .addClass(themes.filterRow || ''); } } + for (i = 0; i < c.columns; i++) { + $header = c.$headers + .add($(c.namespace + '_extra_headers')) + .not('.sorter-false') + .filter('[data-column="' + i + '"]'); + $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); + $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); + if ($h.length) { + $header.removeClass(remove); + $icon.removeClass(iconRmv); + if ($h[0].sortDisabled) { + // no sort arrows for disabled columns! + $icon.removeClass(themes.icons || ''); + } else { + hdr = themes.sortNone; + icon = themes.iconSortNone; + if ($h.hasClass(ts.css.sortAsc)) { + hdr = [ themes.sortAsc, themes.active ].join(' '); + icon = themes.iconSortAsc; + } else if ($h.hasClass(ts.css.sortDesc)) { + hdr = [ themes.sortDesc, themes.active ].join(' '); + icon = themes.iconSortDesc; + } + $header.addClass(hdr); + $icon.addClass(icon || ''); + } + } + } + if (c.debug) { + ts.benchmark('Applying ' + theme + ' theme', time); + } + }, + remove: function(table, c, wo, refreshing) { + if (!wo.uitheme_applied) { return; } + var $table = c.$table, + theme = c.appliedTheme || 'jui', + themes = ts.themes[ theme ] || ts.themes.jui, + $headers = $table.children('thead').children(), + remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, + iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; + $table.removeClass('tablesorter-' + theme + ' ' + themes.table); + wo.uitheme_applied = false; + if (refreshing) { return; } + $table.find(ts.css.header).removeClass(themes.header); + $headers + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover + .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) + .filter('.' + ts.css.filterRow) + .removeClass(themes.filterRow); + $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); } - if (c.debug) { - ts.benchmark("Applying " + theme + " theme", time); - } - }, - remove: function(table, c, wo, refreshing) { - if (!wo.uitheme_applied) { return; } - var $table = c.$table, - theme = c.appliedTheme || 'jui', - themes = ts.themes[ theme ] || ts.themes.jui, - $headers = $table.children('thead').children(), - remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, - iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; - $table.removeClass('tablesorter-' + theme + ' ' + themes.table); - wo.uitheme_applied = false; - if (refreshing) { return; } - $table.find(ts.css.header).removeClass(themes.header); - $headers - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover - .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) - .filter('.' + ts.css.filterRow) - .removeClass(themes.filterRow); - $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); - } -}); + }); })(jQuery); /*! Widget: columns */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.addWidget({ - id: "columns", - priority: 30, - options : { - columns : [ "primary", "secondary", "tertiary" ] - }, - format: function(table, c, wo) { - var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, + ts.addWidget({ + id: 'columns', + priority: 30, + options : { + columns : [ 'primary', 'secondary', 'tertiary' ] + }, + format: function(table, c, wo) { + var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, $table = c.$table, $tbodies = c.$tbodies, sortList = c.sortList, len = sortList.length, // removed c.widgetColumns support - css = wo && wo.columns || [ "primary", "secondary", "tertiary" ], + css = wo && wo.columns || [ 'primary', 'secondary', 'tertiary' ], last = css.length - 1; remove = css.join(' '); - // check if there is a sort (on initialization there may not be one) - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody - $rows = $tbody.children('tr'); - // loop through the visible rows - $rows.each(function() { - $row = $(this); - if (this.style.display !== 'none') { - // remove all columns class names - $cells = $row.children().removeClass(remove); - // add appropriate column class names - if (sortList && sortList[0]) { - // primary sort column class - $cells.eq(sortList[0][0]).addClass(css[0]); - if (len > 1) { - for (indx = 1; indx < len; indx++) { - // secondary, tertiary, etc sort column classes - $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + // check if there is a sort (on initialization there may not be one) + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody + $rows = $tbody.children('tr'); + // loop through the visible rows + $rows.each(function() { + $row = $(this); + if (this.style.display !== 'none') { + // remove all columns class names + $cells = $row.children().removeClass(remove); + // add appropriate column class names + if (sortList && sortList[0]) { + // primary sort column class + $cells.eq(sortList[0][0]).addClass(css[0]); + if (len > 1) { + for (indx = 1; indx < len; indx++) { + // secondary, tertiary, etc sort column classes + $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + } } } } - } - }); - ts.processTbody(table, $tbody, false); - } - // add classes to thead and tfoot - rows = wo.columns_thead !== false ? ['thead tr'] : []; - if (wo.columns_tfoot !== false) { - rows.push('tfoot tr'); - } - if (rows.length) { - $rows = $table.find( rows.join(',') ).children().removeClass(remove); - if (len) { - for (indx = 0; indx < len; indx++) { - // add primary. secondary, tertiary, etc sort column classes - $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + }); + ts.processTbody(table, $tbody, false); + } + // add classes to thead and tfoot + rows = wo.columns_thead !== false ? [ 'thead tr' ] : []; + if (wo.columns_tfoot !== false) { + rows.push('tfoot tr'); + } + if (rows.length) { + $rows = $table.find( rows.join(',') ).children().removeClass(remove); + if (len) { + for (indx = 0; indx < len; indx++) { + // add primary. secondary, tertiary, etc sort column classes + $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + } } } + }, + remove: function(table, c, wo) { + var tbodyIndex, $tbody, + $tbodies = c.$tbodies, + remove = (wo.columns || [ 'primary', 'secondary', 'tertiary' ]).join(' '); + c.$headers.removeClass(remove); + c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody + $tbody.children('tr').each(function() { + $(this).children().removeClass(remove); + }); + ts.processTbody(table, $tbody, false); // restore tbody + } } - }, - remove: function(table, c, wo) { - var tbodyIndex, $tbody, - $tbodies = c.$tbodies, - remove = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' '); - c.$headers.removeClass(remove); - c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody - $tbody.children('tr').each(function() { - $(this).children().removeClass(remove); - }); - ts.processTbody(table, $tbody, false); // restore tbody - } - } -}); + }); })(jQuery); @@ -2548,92 +2550,92 @@ ts.addWidget({ * by Rob Garrison */ ;( function ( $ ) { -'use strict'; -var ts = $.tablesorter || {}, + 'use strict'; + var ts = $.tablesorter || {}, tscss = ts.css; -$.extend( tscss, { - filterRow : 'tablesorter-filter-row', - filter : 'tablesorter-filter', - filterDisabled : 'disabled', - filterRowHide : 'hideme' -}); + $.extend( tscss, { + filterRow : 'tablesorter-filter-row', + filter : 'tablesorter-filter', + filterDisabled : 'disabled', + filterRowHide : 'hideme' + }); -ts.addWidget({ - id: 'filter', - priority: 50, - options : { - filter_childRows : false, // if true, filter includes child row content in the search - filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped - filter_columnFilters : true, // if true, a filter will be added to the top of each table column - filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) - filter_cellFilter : '', // css class name added to the filter cell ( string or array ) - filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) - filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. - filter_excludeFilter : {}, // filters to exclude, per column - filter_external : '', // jQuery selector string ( or jQuery object ) of external filters - filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin - filter_formatter : null, // add custom filter elements to the filter row - filter_functions : null, // add custom filter functions using this option - filter_hideEmpty : true, // hide filter row when table is empty - filter_hideFilters : false, // collapse filter row when mouse leaves the area - filter_ignoreCase : true, // if true, make all searches case-insensitive - filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) - filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down - filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) - filter_reset : null, // jQuery selector string of an element used to reset the filters - filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters - filter_searchDelay : 300, // typing delay in milliseconds before starting a search - filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true - filter_selectSource : null, // include a function to return an array of values to be added to the column filter select - filter_startsWith : false, // if true, filter start from the beginning of the cell contents - filter_useParsedData : false, // filter all data using parsed content - filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used. - filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value - filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text - }, - format: function( table, c, wo ) { - if ( !c.$table.hasClass( 'hasFilters' ) ) { - ts.filter.init( table, c, wo ); + ts.addWidget({ + id: 'filter', + priority: 50, + options : { + filter_childRows : false, // if true, filter includes child row content in the search + filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped + filter_columnFilters : true, // if true, a filter will be added to the top of each table column + filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) + filter_cellFilter : '', // css class name added to the filter cell ( string or array ) + filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) + filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. + filter_excludeFilter : {}, // filters to exclude, per column + filter_external : '', // jQuery selector string ( or jQuery object ) of external filters + filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin + filter_formatter : null, // add custom filter elements to the filter row + filter_functions : null, // add custom filter functions using this option + filter_hideEmpty : true, // hide filter row when table is empty + filter_hideFilters : false, // collapse filter row when mouse leaves the area + filter_ignoreCase : true, // if true, make all searches case-insensitive + filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) + filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down + filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) + filter_reset : null, // jQuery selector string of an element used to reset the filters + filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters + filter_searchDelay : 300, // typing delay in milliseconds before starting a search + filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true + filter_selectSource : null, // include a function to return an array of values to be added to the column filter select + filter_startsWith : false, // if true, filter start from the beginning of the cell contents + filter_useParsedData : false, // filter all data using parsed content + filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used. + filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value + filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text + }, + format: function( table, c, wo ) { + if ( !c.$table.hasClass( 'hasFilters' ) ) { + ts.filter.init( table, c, wo ); + } + }, + remove: function( table, c, wo, refreshing ) { + var tbodyIndex, $tbody, + $table = c.$table, + $tbodies = c.$tbodies, + events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + $table + .removeClass( 'hasFilters' ) + // add .tsfilter namespace to all BUT search + .unbind( events.replace( /\s+/g, ' ' ) ) + // remove the filter row even if refreshing, because the column might have been moved + .find( '.' + tscss.filterRow ).remove(); + if ( refreshing ) { return; } + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody + $tbody.children().removeClass( wo.filter_filteredRow ).show(); + ts.processTbody( table, $tbody, false ); // restore tbody + } + if ( wo.filter_reset ) { + $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); + } } - }, - remove: function( table, c, wo, refreshing ) { - var tbodyIndex, $tbody, - $table = c.$table, - $tbodies = c.$tbodies, - events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - $table - .removeClass( 'hasFilters' ) - // add .tsfilter namespace to all BUT search - .unbind( events.replace( /\s+/g, ' ' ) ) - // remove the filter row even if refreshing, because the column might have been moved - .find( '.' + tscss.filterRow ).remove(); - if ( refreshing ) { return; } - for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody - $tbody.children().removeClass( wo.filter_filteredRow ).show(); - ts.processTbody( table, $tbody, false ); // restore tbody - } - if ( wo.filter_reset ) { - $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); - } - } -}); + }); -ts.filter = { + ts.filter = { - // regex used in filter 'check' functions - not for general use and not documented - regex: { - regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex - child : /tablesorter-childRow/, // child row class name; this gets updated in the script - filtered : /filtered/, // filtered (hidden) row class name; updated in the script - type : /undefined|number/, // check type - exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') - nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) - operators : /[<>=]/g, // replace operators - query : '(q|query)' // replace filter queries - }, + // regex used in filter 'check' functions - not for general use and not documented + regex: { + regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex + child : /tablesorter-childRow/, // child row class name; this gets updated in the script + filtered : /filtered/, // filtered (hidden) row class name; updated in the script + type : /undefined|number/, // check type + exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') + nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) + operators : /[<>=]/g, // replace operators + query : '(q|query)' // replace filter queries + }, // function( c, data ) { } // c = table.config // data.$row = jQuery object of the row currently being processed @@ -2647,1508 +2649,1508 @@ ts.filter = { // data.cacheArray = An array of parsed content from each table cell in the row being processed // data.index = column index; table = table element ( DOM ) // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class ) - types: { - or : function( c, data, vars ) { - if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { - var indx, filterMatched, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.orSplit ), - iFilter = data.iFilter.split( ts.filter.regex.orSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // filterMatched = data2.filter === '' && indx > 0 ? true - // look for an exact match with the 'or' unless the 'filter-match' class is found - filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); - if ( filterMatched ) { - return filterMatched; + types: { + or : function( c, data, vars ) { + if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { + var indx, filterMatched, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.orSplit ), + iFilter = data.iFilter.split( ts.filter.regex.orSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // filterMatched = data2.filter === '' && indx > 0 ? true + // look for an exact match with the 'or' unless the 'filter-match' class is found + filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); + if ( filterMatched ) { + return filterMatched; + } } + // may be null from processing types + return filterMatched || false; } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for an AND or && operator ( logical and ) - and : function( c, data, vars ) { - if ( ts.filter.regex.andTest.test( data.filter ) ) { - var indx, filterMatched, result, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.andSplit ), - iFilter = data.iFilter.split( ts.filter.regex.andSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) - // replace wild cards since /(a*)/i will match anything - .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // look for an exact match with the 'and' unless the 'filter-match' class is found - result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); - if ( indx === 0 ) { - filterMatched = result; + return null; + }, + // Look for an AND or && operator ( logical and ) + and : function( c, data, vars ) { + if ( ts.filter.regex.andTest.test( data.filter ) ) { + var indx, filterMatched, result, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.andSplit ), + iFilter = data.iFilter.split( ts.filter.regex.andSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) + // replace wild cards since /(a*)/i will match anything + .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // look for an exact match with the 'and' unless the 'filter-match' class is found + result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); + if ( indx === 0 ) { + filterMatched = result; + } else { + filterMatched = filterMatched && result; + } + } + // may be null from processing types + return filterMatched || false; + } + return null; + }, + // Look for regex + regex: function( c, data ) { + if ( ts.filter.regex.regex.test( data.filter ) ) { + var matches, + // cache regex per column for optimal speed + regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), + isRegex = regex instanceof RegExp; + try { + if ( !isRegex ) { + // force case insensitive search if ignoreCase option set? + // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } + data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); + } + matches = regex.test( data.exact ); + } catch ( error ) { + matches = false; + } + return matches; + } + return null; + }, + // Look for operators >, >=, < or <= + operators: function( c, data ) { + // ignore empty strings... because '' < 10 is true + if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { + var cachedValue, result, txt, + table = c.table, + index = data.index, + parsed = data.parsed[index], + query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), + parser = c.parsers[index], + savedSearch = query; + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || parser.type === 'numeric' ) { + txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); + result = ts.filter.parseFilter( c, txt, index, true ); + query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; + } + // iExact may be numeric - see issue #149; + // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) + if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && + typeof data.cache !== 'undefined' ) { + cachedValue = data.cache; } else { - filterMatched = filterMatched && result; + txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + cachedValue = ts.formatFloat( txt, table ); + } + if ( />/.test( data.iFilter ) ) { + result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; + } else if ( /= 0 ); } } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for regex - regex: function( c, data ) { - if ( ts.filter.regex.regex.test( data.filter ) ) { - var matches, - // cache regex per column for optimal speed - regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), - isRegex = regex instanceof RegExp; - try { - if ( !isRegex ) { - // force case insensitive search if ignoreCase option set? - // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } - data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); - } - matches = regex.test( data.exact ); - } catch ( error ) { - matches = false; + return null; + }, + // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric + exact: function( c, data ) { + /*jshint eqeqeq:false */ + if ( ts.filter.regex.exact.test( data.iFilter ) ) { + var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), + filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; } - return matches; - } - return null; - }, - // Look for operators >, >=, < or <= - operators: function( c, data ) { - // ignore empty strings... because '' < 10 is true - if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { - var cachedValue, result, txt, - table = c.table, - index = data.index, - parsed = data.parsed[index], - query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), - parser = c.parsers[index], - savedSearch = query; - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || parser.type === 'numeric' ) { - txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); - result = ts.filter.parseFilter( c, txt, index, true ); - query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; - } - // iExact may be numeric - see issue #149; - // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) - if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && - typeof data.cache !== 'undefined' ) { - cachedValue = data.cache; - } else { - txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - cachedValue = ts.formatFloat( txt, table ); - } - if ( />/.test( data.iFilter ) ) { - result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; - } else if ( /= 0 ); - } - } - return null; - }, - // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric - exact: function( c, data ) { - /*jshint eqeqeq:false */ - if ( ts.filter.regex.exact.test( data.iFilter ) ) { - var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), - filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; - } - return null; - }, - // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! - range : function( c, data ) { - if ( ts.filter.regex.toTest.test( data.iFilter ) ) { - var result, tmp, range1, range2, - table = c.table, - index = data.index, - parsed = data.parsed[index], - // make sure the dash is for a range and not indicating a negative number - query = data.iFilter.split( ts.filter.regex.toSplit ); + return null; + }, + // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! + range : function( c, data ) { + if ( ts.filter.regex.toTest.test( data.iFilter ) ) { + var result, tmp, range1, range2, + table = c.table, + index = data.index, + parsed = data.parsed[index], + // make sure the dash is for a range and not indicating a negative number + query = data.iFilter.split( ts.filter.regex.toSplit ); - tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; - range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; - range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || c.parsers[index].type === 'numeric' ) { - result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); - range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; - result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); - range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; - } - if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { - result = data.cache; - } else { - tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - result = ts.formatFloat( tmp, table ); - } - if ( range1 > range2 ) { - tmp = range1; range1 = range2; range2 = tmp; // swap - } - return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); - } - return null; - }, - // Look for wild card: ? = single, * = multiple, or | = logical OR - wild : function( c, data ) { - if ( /[\?\*\|]/.test( data.iFilter ) ) { - var index = data.index, - parsed = data.parsed[ index ], - query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); - // look for an exact match with the 'or' unless the 'filter-match' class is found - if ( !/\?\*/.test( query ) && data.nestedFilters ) { - query = data.isMatch ? query : '^(' + query + ')$'; - } - // parsing the filter may not work properly when using wildcards =/ - return new RegExp( - query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), - c.widgetOptions.filter_ignoreCase ? 'i' : '' - ) - .test( data.exact ); - } - return null; - }, - // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) - fuzzy: function( c, data ) { - if ( /^~/.test( data.iFilter ) ) { - var indx, - patternIndx = 0, - len = data.iExact.length, - txt = data.iFilter.slice( 1 ), - pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - for ( indx = 0; indx < len; indx++ ) { - if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { - patternIndx += 1; + tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; + range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; + range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || c.parsers[index].type === 'numeric' ) { + result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); + range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; + result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); + range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; } + if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { + result = data.cache; + } else { + tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + result = ts.formatFloat( tmp, table ); + } + if ( range1 > range2 ) { + tmp = range1; range1 = range2; range2 = tmp; // swap + } + return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); } - if ( patternIndx === pattern.length ) { - return true; + return null; + }, + // Look for wild card: ? = single, * = multiple, or | = logical OR + wild : function( c, data ) { + if ( /[\?\*\|]/.test( data.iFilter ) ) { + var index = data.index, + parsed = data.parsed[ index ], + query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); + // look for an exact match with the 'or' unless the 'filter-match' class is found + if ( !/\?\*/.test( query ) && data.nestedFilters ) { + query = data.isMatch ? query : '^(' + query + ')$'; + } + // parsing the filter may not work properly when using wildcards =/ + return new RegExp( + query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), + c.widgetOptions.filter_ignoreCase ? 'i' : '' + ) + .test( data.exact ); + } + return null; + }, + // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) + fuzzy: function( c, data ) { + if ( /^~/.test( data.iFilter ) ) { + var indx, + patternIndx = 0, + len = data.iExact.length, + txt = data.iFilter.slice( 1 ), + pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + for ( indx = 0; indx < len; indx++ ) { + if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { + patternIndx += 1; + } + } + if ( patternIndx === pattern.length ) { + return true; + } + return false; + } + return null; + } + }, + init: function( table, c, wo ) { + // filter language options + ts.language = $.extend( true, {}, { + to : 'to', + or : 'or', + and : 'and' + }, ts.language ); + + var options, string, txt, $header, column, filters, val, fxn, noSelect, + regex = ts.filter.regex; + c.$table.addClass( 'hasFilters' ); + + // define timers so using clearTimeout won't cause an undefined error + wo.searchTimer = null; + wo.filter_initTimer = null; + wo.filter_formatterCount = 0; + wo.filter_formatterInit = []; + wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; + wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; + + val = '\\{' + ts.filter.regex.query + '\\}'; + $.extend( regex, { + child : new RegExp( c.cssChildRow ), + filtered : new RegExp( wo.filter_filteredRow ), + alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), + toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), + toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ), + andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), + andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), + orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), + iQuery : new RegExp( val, 'i' ), + igQuery : new RegExp( val, 'ig' ) + }); + + // don't build filter row if columnFilters is false or all columns are set to 'filter-false' + // see issue #156 + val = c.$headers.filter( '.filter-false, .parser-false' ).length; + if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { + // build filter row + ts.filter.buildRow( table, c, wo ); + } + + txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + c.$table.bind( txt, function( event, filter ) { + val = wo.filter_hideEmpty && + $.isEmptyObject( c.cache ) && + !( c.delayInit && event.type === 'appendCache' ); + // hide filter row using the 'filtered' class name + c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 + if ( !/(search|filter)/.test( event.type ) ) { + event.stopPropagation(); + ts.filter.buildDefault( table, true ); + } + if ( event.type === 'filterReset' ) { + c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); + ts.filter.searching( table, [] ); + } else if ( event.type === 'filterEnd' ) { + ts.filter.buildDefault( table, true ); + } else { + // send false argument to force a new search; otherwise if the filter hasn't changed, + // it will return + filter = event.type === 'search' ? filter : + event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; + if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { + // force a new search since content has changed + c.lastCombinedFilter = null; + c.lastSearch = []; + } + // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first + // input ensures all inputs are updated when a search is triggered on the table + // $( 'table' ).trigger( 'search', [...] ); + ts.filter.searching( table, filter, true ); } return false; - } - return null; - } - }, - init: function( table, c, wo ) { - // filter language options - ts.language = $.extend( true, {}, { - to : 'to', - or : 'or', - and : 'and' - }, ts.language ); + }); - var options, string, txt, $header, column, filters, val, fxn, noSelect, - regex = ts.filter.regex; - c.$table.addClass( 'hasFilters' ); - - // define timers so using clearTimeout won't cause an undefined error - wo.searchTimer = null; - wo.filter_initTimer = null; - wo.filter_formatterCount = 0; - wo.filter_formatterInit = []; - wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; - wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; - - val = '\\{' + ts.filter.regex.query + '\\}'; - $.extend( regex, { - child : new RegExp( c.cssChildRow ), - filtered : new RegExp( wo.filter_filteredRow ), - alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), - toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), - toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ), - andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), - andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), - orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), - iQuery : new RegExp( val, 'i' ), - igQuery : new RegExp( val, 'ig' ) - }); - - // don't build filter row if columnFilters is false or all columns are set to 'filter-false' - // see issue #156 - val = c.$headers.filter( '.filter-false, .parser-false' ).length; - if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { - // build filter row - ts.filter.buildRow( table, c, wo ); - } - - txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - c.$table.bind( txt, function( event, filter ) { - val = wo.filter_hideEmpty && - $.isEmptyObject( c.cache ) && - !( c.delayInit && event.type === 'appendCache' ); - // hide filter row using the 'filtered' class name - c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 - if ( !/(search|filter)/.test( event.type ) ) { - event.stopPropagation(); - ts.filter.buildDefault( table, true ); - } - if ( event.type === 'filterReset' ) { - c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); - ts.filter.searching( table, [] ); - } else if ( event.type === 'filterEnd' ) { - ts.filter.buildDefault( table, true ); - } else { - // send false argument to force a new search; otherwise if the filter hasn't changed, - // it will return - filter = event.type === 'search' ? filter : - event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; - if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { - // force a new search since content has changed - c.lastCombinedFilter = null; - c.lastSearch = []; - } - // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first - // input ensures all inputs are updated when a search is triggered on the table - // $( 'table' ).trigger( 'search', [...] ); - ts.filter.searching( table, filter, true ); - } - return false; - }); - - // reset button/link - if ( wo.filter_reset ) { - if ( wo.filter_reset instanceof $ ) { - // reset contains a jQuery object, bind to it - wo.filter_reset.click( function() { - c.$table.trigger( 'filterReset' ); - }); - } else if ( $( wo.filter_reset ).length ) { - // reset is a jQuery selector, use event delegation - $( document ) - .undelegate( wo.filter_reset, 'click.tsfilter' ) - .delegate( wo.filter_reset, 'click.tsfilter', function() { - // trigger a reset event, so other functions ( filter_formatter ) know when to reset + // reset button/link + if ( wo.filter_reset ) { + if ( wo.filter_reset instanceof $ ) { + // reset contains a jQuery object, bind to it + wo.filter_reset.click( function() { c.$table.trigger( 'filterReset' ); }); + } else if ( $( wo.filter_reset ).length ) { + // reset is a jQuery selector, use event delegation + $( document ) + .undelegate( wo.filter_reset, 'click.tsfilter' ) + .delegate( wo.filter_reset, 'click.tsfilter', function() { + // trigger a reset event, so other functions ( filter_formatter ) know when to reset + c.$table.trigger( 'filterReset' ); + }); + } } - } - if ( wo.filter_functions ) { - for ( column = 0; column < c.columns; column++ ) { - fxn = ts.getColumnData( table, wo.filter_functions, column ); - if ( fxn ) { - // remove 'filter-select' from header otherwise the options added here are replaced with - // all options - $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); - // don't build select if 'filter-false' or 'parser-false' set - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - options = ''; - if ( fxn === true && noSelect ) { - ts.filter.buildSelect( table, column ); - } else if ( typeof fxn === 'object' && noSelect ) { - // add custom drop down list - for ( string in fxn ) { - if ( typeof string === 'string' ) { - options += options === '' ? - '' : ''; - val = string; - txt = string; - if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - val = string.split( wo.filter_selectSourceSeparator ); - txt = val[1]; - val = val[0]; + if ( wo.filter_functions ) { + for ( column = 0; column < c.columns; column++ ) { + fxn = ts.getColumnData( table, wo.filter_functions, column ); + if ( fxn ) { + // remove 'filter-select' from header otherwise the options added here are replaced with + // all options + $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); + // don't build select if 'filter-false' or 'parser-false' set + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + options = ''; + if ( fxn === true && noSelect ) { + ts.filter.buildSelect( table, column ); + } else if ( typeof fxn === 'object' && noSelect ) { + // add custom drop down list + for ( string in fxn ) { + if ( typeof string === 'string' ) { + options += options === '' ? + '' : ''; + val = string; + txt = string; + if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + val = string.split( wo.filter_selectSourceSeparator ); + txt = val[1]; + val = val[0]; + } + options += ''; } - options += ''; + } + c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .append( options ); + txt = wo.filter_selectSource; + fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); + if ( fxn ) { + // updating so the extra options are appended + ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); } } + } + } + } + // not really updating, but if the column has both the 'filter-select' class & + // filter_functions set to true, it would append the same options twice. + ts.filter.buildDefault( table, true ); + + ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); + if ( wo.filter_external ) { + ts.filter.bindSearch( table, wo.filter_external ); + } + + if ( wo.filter_hideFilters ) { + ts.filter.hideFilters( table, c ); + } + + // show processing icon + if ( c.showProcessing ) { + txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function( event, columns ) { + // only add processing to certain columns to all columns + $header = ( columns ) ? c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .append( options ); - txt = wo.filter_selectSource; - fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); - if ( fxn ) { - // updating so the extra options are appended - ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } - } - } - // not really updating, but if the column has both the 'filter-select' class & - // filter_functions set to true, it would append the same options twice. - ts.filter.buildDefault( table, true ); - - ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); - if ( wo.filter_external ) { - ts.filter.bindSearch( table, wo.filter_external ); - } - - if ( wo.filter_hideFilters ) { - ts.filter.hideFilters( table, c ); - } - - // show processing icon - if ( c.showProcessing ) { - txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function( event, columns ) { - // only add processing to certain columns to all columns - $header = ( columns ) ? - c.$table - .find( '.' + tscss.header ) - .filter( '[data-column]' ) - .filter( function() { - return columns[ $( this ).data( 'column' ) ] !== ''; - }) : ''; - ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); - }); - } - - // set filtered rows count ( intially unfiltered ) - c.filteredRows = c.totalRows; - - // add default values - txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function() { - // redefine 'wo' as it does not update properly inside this callback - var wo = this.config.widgetOptions; - filters = ts.filter.setDefaults( table, c, wo ) || []; - if ( filters.length ) { - // prevent delayInit from triggering a cache build if filters are empty - if ( !( c.delayInit && filters.join( '' ) === '' ) ) { - ts.setFilters( table, filters, true ); - } - } - c.$table.trigger( 'filterFomatterUpdate' ); - // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers - setTimeout( function() { - if ( !wo.filter_initialized ) { - ts.filter.filterInitComplete( c ); - } - }, 100 ); - }); - // if filter widget is added after pager has initialized; then set filter init flag - if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { - c.$table.trigger( 'filterFomatterUpdate' ); - setTimeout( function() { - ts.filter.filterInitComplete( c ); - }, 100 ); - } - }, - // $cell parameter, but not the config, is passed to the filter_formatters, - // so we have to work with it instead - formatterUpdated: function( $cell, column ) { - var wo = $cell.closest( 'table' )[0].config.widgetOptions; - if ( !wo.filter_initialized ) { - // add updates by column since this function - // may be called numerous times before initialization - wo.filter_formatterInit[ column ] = 1; - } - }, - filterInitComplete: function( c ) { - var indx, len, - wo = c.widgetOptions, - count = 0, - completed = function() { - wo.filter_initialized = true; - c.$table.trigger( 'filterInit', c ); - ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); - }; - if ( $.isEmptyObject( wo.filter_formatter ) ) { - completed(); - } else { - len = wo.filter_formatterInit.length; - for ( indx = 0; indx < len; indx++ ) { - if ( wo.filter_formatterInit[ indx ] === 1 ) { - count++; - } - } - clearTimeout( wo.filter_initTimer ); - if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { - // filter widget initialized - completed(); - } else if ( !wo.filter_initialized ) { - // fall back in case a filter_formatter doesn't call - // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off - wo.filter_initTimer = setTimeout( function() { - completed(); - }, 500 ); - } - } - }, - setDefaults: function( table, c, wo ) { - var isArray, saved, indx, col, $filters, - // get current ( default ) filters - filters = ts.getFilters( table ) || []; - if ( wo.filter_saveFilters && ts.storage ) { - saved = ts.storage( table, 'tablesorter-filters' ) || []; - isArray = $.isArray( saved ); - // make sure we're not just getting an empty array - if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { - filters = saved; - } - } - // if no filters saved, then check default settings - if ( filters.join( '' ) === '' ) { - // allow adding default setting to external filters - $filters = c.$headers.add( wo.filter_$externalFilters ) - .filter( '[' + wo.filter_defaultAttrib + ']' ); - for ( indx = 0; indx <= c.columns; indx++ ) { - // include data-column='all' external filters - col = indx === c.columns ? 'all' : indx; - filters[indx] = $filters - .filter( '[data-column="' + col + '"]' ) - .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; - } - } - c.$table.data( 'lastSearch', filters ); - return filters; - }, - parseFilter: function( c, filter, column, parsed ) { - return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; - }, - buildRow: function( table, c, wo ) { - var col, column, $header, buildSelect, disabled, name, ffxn, tmp, - // c.columns defined in computeThIndexes() - cellFilter = wo.filter_cellFilter, - columns = c.columns, - arry = $.isArray( cellFilter ), - buildFilter = ''; - for ( column = 0; column < columns; column++ ) { - buildFilter += '' ).appendTo( c.$filters.eq( column ) ); - } else { - ffxn = ts.getColumnData( table, wo.filter_formatter, column ); - if ( ffxn ) { - wo.filter_formatterCount++; - buildFilter = ffxn( c.$filters.eq( column ), column ); - // no element returned, so lets go find it - if ( buildFilter && buildFilter.length === 0 ) { - buildFilter = c.$filters.eq( column ).children( 'input' ); - } - // element not in DOM, so lets attach it - if ( buildFilter && ( buildFilter.parent().length === 0 || - ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { - c.$filters.eq( column ).append( buildFilter ); - } - } else { - buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); - } - if ( buildFilter ) { - tmp = $header.data( 'placeholder' ) || - $header.attr( 'data-placeholder' ) || - wo.filter_placeholder.search || ''; - buildFilter.attr( 'placeholder', tmp ); - } - } - if ( buildFilter ) { - // add filter class name - name = ( $.isArray( wo.filter_cssFilter ) ? - ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : - wo.filter_cssFilter ) || ''; - buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); - if ( disabled ) { - buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; - } - } - } - }, - bindSearch: function( table, $el, internal ) { - table = $( table )[0]; - $el = $( $el ); // allow passing a selector string - if ( !$el.length ) { return; } - var tmp, - c = table.config, - wo = c.widgetOptions, - namespace = c.namespace + 'filter', - $ext = wo.filter_$externalFilters; - if ( internal !== true ) { - // save anyMatch element - tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; - wo.filter_$anyMatch = $el.filter( tmp ); - if ( $ext && $ext.length ) { - wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); - } else { - wo.filter_$externalFilters = $el; - } - // update values ( external filters added after table initialization ) - ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); - } - // unbind events - tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); - $el - // use data attribute instead of jQuery data since the head is cloned without including - // the data/binding - .attr( 'data-lastSearchTime', new Date().getTime() ) - .unbind( tmp.replace( /\s+/g, ' ' ) ) - // include change for select - fixes #473 - .bind( 'keyup' + namespace, function( event ) { - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - // emulate what webkit does.... escape clears the filter - if ( event.which === 27 ) { - this.value = ''; - // live search - } else if ( wo.filter_liveSearch === false ) { - return; - // don't return if the search value is empty ( all rows need to be revealed ) - } else if ( this.value !== '' && ( - // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace - ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || - // let return & backspace continue on, but ignore arrows & non-valid characters - ( event.which !== 13 && event.which !== 8 && - ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { - return; - } - // change event = no delay; last true flag tells getFilters to skip newest timed input - ts.filter.searching( table, true, true ); - }) - .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { - var column = $( this ).data( 'column' ); - // don't allow 'change' event to process if the input value is the same - fixes #685 - if ( event.which === 13 || event.type === 'search' || - event.type === 'change' && this.value !== c.lastSearch[column] ) { - event.preventDefault(); - // init search with no delay - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - ts.filter.searching( table, false, true ); - } - }); - }, - searching: function( table, filter, skipFirst ) { - var wo = table.config.widgetOptions; - clearTimeout( wo.searchTimer ); - if ( typeof filter === 'undefined' || filter === true ) { - // delay filtering - wo.searchTimer = setTimeout( function() { - ts.filter.checkFilters( table, filter, skipFirst ); - }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); - } else { - // skip delay - ts.filter.checkFilters( table, filter, skipFirst ); - } - }, - checkFilters: function( table, filter, skipFirst ) { - var c = table.config, - wo = c.widgetOptions, - filterArray = $.isArray( filter ), - filters = ( filterArray ) ? filter : ts.getFilters( table, true ), - combinedFilters = ( filters || [] ).join( '' ); // combined filter values - // prevent errors if delay init is set - if ( $.isEmptyObject( c.cache ) ) { - // update cache if delayInit set & pager has initialized ( after user initiates a search ) - if ( c.delayInit && c.pager && c.pager.initialized ) { - c.$table.trigger( 'updateCache', [ function() { - ts.filter.checkFilters( table, false, skipFirst ); - } ] ); - } - return; - } - // add filter array back into inputs - if ( filterArray ) { - ts.setFilters( table, filters, false, skipFirst !== true ); - if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } - } - if ( wo.filter_hideFilters ) { - // show/hide filter row as needed - c.$table - .find( '.' + tscss.filterRow ) - .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); - } - // return if the last search is the same; but filter === false when updating the search - // see example-widget-filter.html filter toggle buttons - if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { - return; - } else if ( filter === false ) { - // force filter refresh - c.lastCombinedFilter = null; - c.lastSearch = []; - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterStart', [filters] ); - } - if ( c.showProcessing ) { - // give it time for the processing icon to kick in - setTimeout( function() { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - }, 30 ); - } else { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - } - }, - hideFilters: function( table, c ) { - var timer; - c.$table - .find( '.' + tscss.filterRow ) - .bind( 'mouseenter mouseleave', function( e ) { - // save event object - http://bugs.jquery.com/ticket/12140 - var event = e, - $filterRow = $( this ); - clearTimeout( timer ); - timer = setTimeout( function() { - if ( /enter|over/.test( event.type ) ) { - $filterRow.removeClass( tscss.filterRowHide ); - } else { - // don't hide if input has focus - // $( ':focus' ) needs jQuery 1.6+ - if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { - // don't hide row if any filter has a value - if ( c.lastCombinedFilter === '' ) { - $filterRow.addClass( tscss.filterRowHide ); - } - } - } - }, 200 ); - }) - .find( 'input, select' ).bind( 'focus blur', function( e ) { - var event = e, - $row = $( this ).closest( 'tr' ); - clearTimeout( timer ); - timer = setTimeout( function() { - clearTimeout( timer ); - // don't hide row if any filter has a value - if ( ts.getFilters( c.$table ).join( '' ) === '' ) { - $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); - } - }, 200 ); - }); - }, - defaultFilter: function( filter, mask ) { - if ( filter === '' ) { return filter; } - var regex = ts.filter.regex.iQuery, - maskLen = mask.match( ts.filter.regex.igQuery ).length, - query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], - len = query.length - 1, - indx = 0, - val = mask; - if ( len < 1 && maskLen > 1 ) { - // only one 'word' in query but mask has >1 slots - query[1] = query[0]; - } - // replace all {query} with query words... - // if query = 'Bob', then convert mask from '!{query}' to '!Bob' - // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' - while ( regex.test( val ) ) { - val = val.replace( regex, query[indx++] || '' ); - if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { - val = mask.replace( regex, val ); - } - } - return val; - }, - getLatestSearch: function( $input ) { - if ( $input ) { - return $input.sort( function( a, b ) { - return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); - }); - } - return $input || $(); - }, - multipleColumns: function( c, $input ) { - // look for multiple columns '1-3,4-6,8' in data-column - var temp, ranges, range, start, end, singles, i, indx, len, - wo = c.widgetOptions, - // only target 'all' column inputs on initialization - // & don't target 'all' column inputs if they don't exist - targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, - columns = [], - val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); - // process column range - if ( targets && /-/.test( val ) ) { - ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); - len = ranges.length; - for ( indx = 0; indx < len; indx++ ) { - range = ranges[indx].split( /\s*-\s*/ ); - start = parseInt( range[0], 10 ) || 0; - end = parseInt( range[1], 10 ) || ( c.columns - 1 ); - if ( start > end ) { - temp = start; start = end; end = temp; // swap - } - if ( end >= c.columns ) { - end = c.columns - 1; - } - for ( ; start <= end; start++ ) { - columns.push( start ); - } - // remove processed range from val - val = val.replace( ranges[ indx ], '' ); - } - } - // process single columns - if ( targets && /,/.test( val ) ) { - singles = val.split( /\s*,\s*/ ); - len = singles.length; - for ( i = 0; i < len; i++ ) { - if ( singles[ i ] !== '' ) { - indx = parseInt( singles[ i ], 10 ); - if ( indx < c.columns ) { - columns.push( indx ); - } - } - } - } - // return all columns - if ( !columns.length ) { - for ( indx = 0; indx < c.columns; indx++ ) { - columns.push( indx ); - } - } - return columns; - }, - processTypes: function( c, data, vars ) { - var ffxn, - filterMatched = null, - matches = null; - for ( ffxn in ts.filter.types ) { - if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { - matches = ts.filter.types[ffxn]( c, data, vars ); - if ( matches !== null ) { - filterMatched = matches; - } - } - } - return filterMatched; - }, - processRow: function( c, data, vars ) { - var columnIndex, hasSelect, result, val, filterMatched, - fxn, ffxn, txt, - regex = ts.filter.regex, - wo = c.widgetOptions, - showRow = true; - data.$cells = data.$row.children(); - - if ( data.anyMatchFlag ) { - // look for multiple columns '1-3,4-6,8' - columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); - data.anyMatch = true; - data.isMatch = true; - data.rowArray = data.$cells.map( function( i ) { - if ( $.inArray( i, columnIndex ) > -1 ) { - if ( data.parsed[ i ] ) { - txt = data.cacheArray[ i ]; - } else { - txt = data.rawArray[ i ]; - txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); - if ( c.sortLocaleCompare ) { - txt = ts.replaceAccents( txt ); - } - } - return txt; - } - }).get(); - data.filter = data.anyMatchFilter; - data.iFilter = data.iAnyMatchFilter; - data.exact = data.rowArray.join( ' ' ); - data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; - data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); - - vars.excludeMatch = vars.noAnyMatch; - filterMatched = ts.filter.processTypes( c, data, vars ); - - if ( filterMatched !== null ) { - showRow = filterMatched; - } else { - if ( wo.filter_startsWith ) { - showRow = false; - columnIndex = c.columns; - while ( !showRow && columnIndex > 0 ) { - columnIndex--; - showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; - } - } else { - showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; - } - } - data.anyMatch = false; - // no other filters to process - if ( data.filters.join( '' ) === data.filter ) { - return showRow; - } - } - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - data.filter = data.filters[ columnIndex ]; - data.index = columnIndex; - - // filter types to exclude, per column - vars.excludeMatch = vars.excludeFilter[ columnIndex ]; - - // ignore if filter is empty or disabled - if ( data.filter ) { - data.cache = data.cacheArray[ columnIndex ]; - // check if column data should be from the cell or from parsed data - if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { - data.exact = data.cache; - } else { - result = data.rawArray[ columnIndex ] || ''; - data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 - } - data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? - data.exact.toLowerCase() : data.exact; - - data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); - - result = showRow; // if showRow is true, show that row - - // in case select filter option has a different value vs text 'a - z|A through Z' - ffxn = wo.filter_columnFilters ? - c.$filters.add( c.$externalFilters ) - .filter( '[data-column="'+ columnIndex + '"]' ) - .find( 'select option:selected' ) - .attr( 'data-function-name' ) || '' : ''; - // replace accents - see #357 - if ( c.sortLocaleCompare ) { - data.filter = ts.replaceAccents( data.filter ); - } - - val = true; - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { - data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); - // val is used to indicate that a filter select is using a default filter; - // so we override the exact & partial matches - val = false; - } - // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), - // data.filter = case sensitive - data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; - fxn = vars.functions[ columnIndex ]; - hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); - filterMatched = null; - if ( fxn || ( hasSelect && val ) ) { - if ( fxn === true || hasSelect ) { - // default selector uses exact match unless 'filter-match' class is found - filterMatched = data.isMatch ? - data.iExact.search( data.iFilter ) >= 0 : - data.filter === data.exact; - } else if ( typeof fxn === 'function' ) { - // filter callback( exact cell content, parser normalized content, - // filter input value, column index, jQuery row object ) - filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { - // selector option function - txt = ffxn || data.filter; - filterMatched = - fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } - } - if ( filterMatched === null ) { - // cycle through the different filters - // filters return a boolean or null if nothing matches - filterMatched = ts.filter.processTypes( c, data, vars ); - if ( filterMatched !== null ) { - result = filterMatched; - // Look for match, and add child row data for matching - } else { - txt = ( data.iExact + data.childRowText ) - .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); - result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); - } - } else { - result = filterMatched; - } - showRow = ( result ) ? showRow : false; - } - } - return showRow; - }, - findRows: function( table, filters, combinedFilters ) { - if ( table.config.lastCombinedFilter === combinedFilters || - !table.config.widgetOptions.filter_initialized ) { - return; - } - var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, - isChild, childRow, lastSearch, showRow, time, val, indx, - notFiltered, searchFiltered, query, injected, res, id, txt, - storedFilters = $.extend( [], filters ), - regex = ts.filter.regex, - c = table.config, - wo = c.widgetOptions, - // data object passed to filters; anyMatch is a flag for the filters - data = { - anyMatch: false, - filters: filters, - // regex filter type cache - filter_regexCache : [] - }, - vars = { - // anyMatch really screws up with these types of filters - noAnyMatch: [ 'range', 'notMatch', 'operators' ], - // cache filter variables that use ts.getColumnData in the main loop - functions : [], - excludeFilter : [], - defaultColFilter : [], - defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' - }; - - // parse columns after formatter, in case the class is added at that point - data.parsed = c.$headers.map( function( columnIndex ) { - return c.parsers && c.parsers[ columnIndex ] && - // force parsing if parser type is numeric - c.parsers[ columnIndex ].parsed || - // getData won't return 'parsed' if other 'filter-' class names exist - // ( e.g. ) - ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], - ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || - $( this ).hasClass( 'filter-parsed' ); - }).get(); - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - vars.functions[ columnIndex ] = - ts.getColumnData( table, wo.filter_functions, columnIndex ); - vars.defaultColFilter[ columnIndex ] = - ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; - vars.excludeFilter[ columnIndex ] = - ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); - } - - if ( c.debug ) { - ts.log( 'Filter: Starting filter widget search', filters ); - time = new Date(); - } - // filtered rows count - c.filteredRows = 0; - c.totalRows = 0; - // combindedFilters are undefined on init - combinedFilters = ( storedFilters || [] ).join( '' ); - - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); - // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! - // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); - columnIndex = c.columns; - // convert stored rows into a jQuery object - norm_rows = c.cache[ tbodyIndex ].normalized; - $rows = $( $.map( norm_rows, function( el ) { - return el[ columnIndex ].$row.get(); - }) ); - - if ( combinedFilters === '' || wo.filter_serversideFiltering ) { - $rows - .removeClass( wo.filter_filteredRow ) - .not( '.' + c.cssChildRow ) - .css( 'display', '' ); - } else { - // filter out child rows - $rows = $rows.not( '.' + c.cssChildRow ); - len = $rows.length; - - if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || - typeof filters[c.columns] !== 'undefined' ) { - data.anyMatchFlag = true; - data.anyMatchFilter = '' + ( - filters[ c.columns ] || - wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || - '' - ); - if ( wo.filter_columnAnyMatch ) { - // specific columns search - query = data.anyMatchFilter.split( regex.andSplit ); - injected = false; - for ( indx = 0; indx < query.length; indx++ ) { - res = query[ indx ].split( ':' ); - if ( res.length > 1 ) { - // make the column a one-based index ( non-developers start counting from one :P ) - id = parseInt( res[0], 10 ) - 1; - if ( id >= 0 && id < c.columns ) { // if id is an integer - filters[ id ] = res[1]; - query.splice( indx, 1 ); - indx--; - injected = true; - } - } - } - if ( injected ) { - data.anyMatchFilter = query.join( ' && ' ); - } - } - } - - // optimize searching only through already filtered rows - see #313 - searchFiltered = wo.filter_searchFiltered; - lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; - if ( searchFiltered ) { - // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 - for ( indx = 0; indx < columnIndex + 1; indx++ ) { - val = filters[indx] || ''; - // break out of loop if we've already determined not to search filtered rows - if ( !searchFiltered ) { indx = columnIndex; } - // search already filtered rows if... - searchFiltered = searchFiltered && lastSearch.length && - // there are no changes from beginning of filter - val.indexOf( lastSearch[indx] || '' ) === 0 && - // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string - !regex.alreadyFiltered.test( val ) && - // if we are not doing exact matches, using '|' ( logical or ) or not '!' - !/[=\"\|!]/.test( val ) && - // don't search only filtered if the value is negative - // ( '> -10' => '> -100' will ignore hidden rows ) - !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && - // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 - !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && - !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); - } - } - notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; - // can't search when all rows are hidden - this happens when looking for exact matches - if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } - if ( c.debug ) { - ts.log( 'Filter: Searching through ' + - ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); - } - if ( data.anyMatchFlag ) { - if ( c.sortLocaleCompare ) { - // replace accents - data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); - } - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { - data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); - // clear search filtered flag because default filters are not saved to the last search - searchFiltered = false; - } - // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true - // when c.ignoreCase is true, the cache contains all lower case data - data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? - data.anyMatchFilter : - data.anyMatchFilter.toLowerCase(); - } - - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - - txt = $rows[ rowIndex ].className; - // the first row can never be a child row - isChild = rowIndex && regex.child.test( txt ); - // skip child rows & already filtered rows - if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { - continue; - } - - data.$row = $rows.eq( rowIndex ); - data.cacheArray = norm_rows[ rowIndex ]; - rowData = data.cacheArray[ c.columns ]; - data.rawArray = rowData.raw; - data.childRowText = ''; - - if ( !wo.filter_childByColumn ) { - txt = ''; - // child row cached text - childRow = rowData.child; - // so, if 'table.config.widgetOptions.filter_childRows' is true and there is - // a match anywhere in the child row, then it will make the row visible - // checked here so the option can be changed dynamically - for ( indx = 0; indx < childRow.length; indx++ ) { - txt += ' ' + childRow[indx].join( '' ) || ''; - } - data.childRowText = wo.filter_childRows ? - ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : - ''; - } - - showRow = ts.filter.processRow( c, data, vars ); - childRow = rowData.$row.filter( ':gt( 0 )' ); - - if ( wo.filter_childRows && childRow.length ) { - if ( wo.filter_childByColumn ) { - // cycle through each child row - for ( indx = 0; indx < childRow.length; indx++ ) { - data.$row = childRow.eq( indx ); - data.cacheArray = rowData.child[ indx ]; - data.rawArray = data.cacheArray; - // use OR comparison on child rows - showRow = showRow || ts.filter.processRow( c, data, vars ); - } - } - childRow.toggleClass( wo.filter_filteredRow, !showRow ); - } - - rowData.$row - .toggleClass( wo.filter_filteredRow, !showRow )[0] - .display = showRow ? '' : 'none'; - } - } - c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; - c.totalRows += $rows.length; - ts.processTbody( table, $tbody, false ); - } - c.lastCombinedFilter = combinedFilters; // save last search - // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) - c.lastSearch = storedFilters; - c.$table.data( 'lastSearch', storedFilters ); - if ( wo.filter_saveFilters && ts.storage ) { - ts.storage( table, 'tablesorter-filters', storedFilters ); - } - if ( c.debug ) { - ts.benchmark( 'Completed filter widget search', time ); - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterEnd', c ); - } - setTimeout( function() { - c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied - }, 0 ); - }, - getOptionSource: function( table, column, onlyAvail ) { - table = $( table )[0]; - var cts, txt, indx, len, - c = table.config, - wo = c.widgetOptions, - parsed = [], - arry = false, - source = wo.filter_selectSource, - last = c.$table.data( 'lastSearch' ) || [], - fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); - - if ( onlyAvail && last[column] !== '' ) { - onlyAvail = false; - } - - // filter select source option - if ( fxn === true ) { - // OVERALL source - arry = source( table, column, onlyAvail ); - } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { - // selectSource is a jQuery object or string of options - return fxn; - } else if ( $.isArray( fxn ) ) { - arry = fxn; - } else if ( $.type( source ) === 'object' && fxn ) { - // custom select source function for a SPECIFIC COLUMN - arry = fxn( table, column, onlyAvail ); - } - if ( arry === false ) { - // fall back to original method - arry = ts.filter.getOptions( table, column, onlyAvail ); - } - - // get unique elements and sort the list - // if $.tablesorter.sortText exists ( not in the original tablesorter ), - // then natural sort the list otherwise use a basic sort - arry = $.grep( arry, function( value, indx ) { - return $.inArray( value, arry ) === indx; - }); - - if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { - // unsorted select options - return arry; - } else { - len = arry.length; - // parse select option values - for ( indx = 0; indx < len; indx++ ) { - txt = arry[ indx ]; - // parse array data using set column parser; this DOES NOT pass the original - // table cell to the parser format function - parsed.push({ - t : txt, - // check parser length - fixes #934 - p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + .find( '.' + tscss.header ) + .filter( '[data-column]' ) + .filter( function() { + return columns[ $( this ).data( 'column' ) ] !== ''; + }) : ''; + ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); }); } - // sort parsed select options - cts = c.textSorter || ''; - parsed.sort( function( a, b ) { - // sortNatural breaks if you don't pass it strings - var x = a.p.toString(), - y = b.p.toString(); - if ( $.isFunction( cts ) ) { - // custom OVERALL text sorter - return cts( x, y, true, column, table ); - } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) { - // custom text sorter for a SPECIFIC COLUMN - return cts[column]( x, y, true, column, table ); - } else if ( ts.sortNatural ) { - // fall back to natural sort - return ts.sortNatural( x, y ); + // set filtered rows count ( intially unfiltered ) + c.filteredRows = c.totalRows; + + // add default values + txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function() { + // redefine 'wo' as it does not update properly inside this callback + var wo = this.config.widgetOptions; + filters = ts.filter.setDefaults( table, c, wo ) || []; + if ( filters.length ) { + // prevent delayInit from triggering a cache build if filters are empty + if ( !( c.delayInit && filters.join( '' ) === '' ) ) { + ts.setFilters( table, filters, true ); + } } - // using an older version! do a basic sort - return true; + c.$table.trigger( 'filterFomatterUpdate' ); + // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers + setTimeout( function() { + if ( !wo.filter_initialized ) { + ts.filter.filterInitComplete( c ); + } + }, 100 ); }); - // rebuild arry from sorted parsed data - arry = []; - len = parsed.length; - for ( indx = 0; indx < len; indx++ ) { - arry.push( parsed[indx].t ); + // if filter widget is added after pager has initialized; then set filter init flag + if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { + c.$table.trigger( 'filterFomatterUpdate' ); + setTimeout( function() { + ts.filter.filterInitComplete( c ); + }, 100 ); + } + }, + // $cell parameter, but not the config, is passed to the filter_formatters, + // so we have to work with it instead + formatterUpdated: function( $cell, column ) { + var wo = $cell.closest( 'table' )[0].config.widgetOptions; + if ( !wo.filter_initialized ) { + // add updates by column since this function + // may be called numerous times before initialization + wo.filter_formatterInit[ column ] = 1; + } + }, + filterInitComplete: function( c ) { + var indx, len, + wo = c.widgetOptions, + count = 0, + completed = function() { + wo.filter_initialized = true; + c.$table.trigger( 'filterInit', c ); + ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); + }; + if ( $.isEmptyObject( wo.filter_formatter ) ) { + completed(); + } else { + len = wo.filter_formatterInit.length; + for ( indx = 0; indx < len; indx++ ) { + if ( wo.filter_formatterInit[ indx ] === 1 ) { + count++; + } + } + clearTimeout( wo.filter_initTimer ); + if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { + // filter widget initialized + completed(); + } else if ( !wo.filter_initialized ) { + // fall back in case a filter_formatter doesn't call + // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off + wo.filter_initTimer = setTimeout( function() { + completed(); + }, 500 ); + } + } + }, + setDefaults: function( table, c, wo ) { + var isArray, saved, indx, col, $filters, + // get current ( default ) filters + filters = ts.getFilters( table ) || []; + if ( wo.filter_saveFilters && ts.storage ) { + saved = ts.storage( table, 'tablesorter-filters' ) || []; + isArray = $.isArray( saved ); + // make sure we're not just getting an empty array + if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { + filters = saved; + } + } + // if no filters saved, then check default settings + if ( filters.join( '' ) === '' ) { + // allow adding default setting to external filters + $filters = c.$headers.add( wo.filter_$externalFilters ) + .filter( '[' + wo.filter_defaultAttrib + ']' ); + for ( indx = 0; indx <= c.columns; indx++ ) { + // include data-column='all' external filters + col = indx === c.columns ? 'all' : indx; + filters[indx] = $filters + .filter( '[data-column="' + col + '"]' ) + .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; + } + } + c.$table.data( 'lastSearch', filters ); + return filters; + }, + parseFilter: function( c, filter, column, parsed ) { + return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; + }, + buildRow: function( table, c, wo ) { + var col, column, $header, buildSelect, disabled, name, ffxn, tmp, + // c.columns defined in computeThIndexes() + cellFilter = wo.filter_cellFilter, + columns = c.columns, + arry = $.isArray( cellFilter ), + buildFilter = ''; + for ( column = 0; column < columns; column++ ) { + buildFilter += '' ).appendTo( c.$filters.eq( column ) ); + } else { + ffxn = ts.getColumnData( table, wo.filter_formatter, column ); + if ( ffxn ) { + wo.filter_formatterCount++; + buildFilter = ffxn( c.$filters.eq( column ), column ); + // no element returned, so lets go find it + if ( buildFilter && buildFilter.length === 0 ) { + buildFilter = c.$filters.eq( column ).children( 'input' ); + } + // element not in DOM, so lets attach it + if ( buildFilter && ( buildFilter.parent().length === 0 || + ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { + c.$filters.eq( column ).append( buildFilter ); + } + } else { + buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); + } + if ( buildFilter ) { + tmp = $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.search || ''; + buildFilter.attr( 'placeholder', tmp ); + } + } + if ( buildFilter ) { + // add filter class name + name = ( $.isArray( wo.filter_cssFilter ) ? + ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : + wo.filter_cssFilter ) || ''; + buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); + if ( disabled ) { + buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; + } + } + } + }, + bindSearch: function( table, $el, internal ) { + table = $( table )[0]; + $el = $( $el ); // allow passing a selector string + if ( !$el.length ) { return; } + var tmp, + c = table.config, + wo = c.widgetOptions, + namespace = c.namespace + 'filter', + $ext = wo.filter_$externalFilters; + if ( internal !== true ) { + // save anyMatch element + tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; + wo.filter_$anyMatch = $el.filter( tmp ); + if ( $ext && $ext.length ) { + wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); + } else { + wo.filter_$externalFilters = $el; + } + // update values ( external filters added after table initialization ) + ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); + } + // unbind events + tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); + $el + // use data attribute instead of jQuery data since the head is cloned without including + // the data/binding + .attr( 'data-lastSearchTime', new Date().getTime() ) + .unbind( tmp.replace( /\s+/g, ' ' ) ) + // include change for select - fixes #473 + .bind( 'keyup' + namespace, function( event ) { + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + // emulate what webkit does.... escape clears the filter + if ( event.which === 27 ) { + this.value = ''; + // live search + } else if ( wo.filter_liveSearch === false ) { + return; + // don't return if the search value is empty ( all rows need to be revealed ) + } else if ( this.value !== '' && ( + // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace + ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || + // let return & backspace continue on, but ignore arrows & non-valid characters + ( event.which !== 13 && event.which !== 8 && + ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { + return; + } + // change event = no delay; last true flag tells getFilters to skip newest timed input + ts.filter.searching( table, true, true ); + }) + .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { + var column = $( this ).data( 'column' ); + // don't allow 'change' event to process if the input value is the same - fixes #685 + if ( event.which === 13 || event.type === 'search' || + event.type === 'change' && this.value !== c.lastSearch[column] ) { + event.preventDefault(); + // init search with no delay + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + ts.filter.searching( table, false, true ); + } + }); + }, + searching: function( table, filter, skipFirst ) { + var wo = table.config.widgetOptions; + clearTimeout( wo.searchTimer ); + if ( typeof filter === 'undefined' || filter === true ) { + // delay filtering + wo.searchTimer = setTimeout( function() { + ts.filter.checkFilters( table, filter, skipFirst ); + }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); + } else { + // skip delay + ts.filter.checkFilters( table, filter, skipFirst ); + } + }, + checkFilters: function( table, filter, skipFirst ) { + var c = table.config, + wo = c.widgetOptions, + filterArray = $.isArray( filter ), + filters = ( filterArray ) ? filter : ts.getFilters( table, true ), + combinedFilters = ( filters || [] ).join( '' ); // combined filter values + // prevent errors if delay init is set + if ( $.isEmptyObject( c.cache ) ) { + // update cache if delayInit set & pager has initialized ( after user initiates a search ) + if ( c.delayInit && c.pager && c.pager.initialized ) { + c.$table.trigger( 'updateCache', [ function() { + ts.filter.checkFilters( table, false, skipFirst ); + } ] ); + } + return; + } + // add filter array back into inputs + if ( filterArray ) { + ts.setFilters( table, filters, false, skipFirst !== true ); + if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } + } + if ( wo.filter_hideFilters ) { + // show/hide filter row as needed + c.$table + .find( '.' + tscss.filterRow ) + .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); + } + // return if the last search is the same; but filter === false when updating the search + // see example-widget-filter.html filter toggle buttons + if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { + return; + } else if ( filter === false ) { + // force filter refresh + c.lastCombinedFilter = null; + c.lastSearch = []; + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterStart', [ filters ] ); + } + if ( c.showProcessing ) { + // give it time for the processing icon to kick in + setTimeout( function() { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + }, 30 ); + } else { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + } + }, + hideFilters: function( table, c ) { + var timer; + c.$table + .find( '.' + tscss.filterRow ) + .bind( 'mouseenter mouseleave', function( e ) { + // save event object - http://bugs.jquery.com/ticket/12140 + var event = e, + $filterRow = $( this ); + clearTimeout( timer ); + timer = setTimeout( function() { + if ( /enter|over/.test( event.type ) ) { + $filterRow.removeClass( tscss.filterRowHide ); + } else { + // don't hide if input has focus + // $( ':focus' ) needs jQuery 1.6+ + if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { + // don't hide row if any filter has a value + if ( c.lastCombinedFilter === '' ) { + $filterRow.addClass( tscss.filterRowHide ); + } + } + } + }, 200 ); + }) + .find( 'input, select' ).bind( 'focus blur', function( e ) { + var event = e, + $row = $( this ).closest( 'tr' ); + clearTimeout( timer ); + timer = setTimeout( function() { + clearTimeout( timer ); + // don't hide row if any filter has a value + if ( ts.getFilters( c.$table ).join( '' ) === '' ) { + $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); + } + }, 200 ); + }); + }, + defaultFilter: function( filter, mask ) { + if ( filter === '' ) { return filter; } + var regex = ts.filter.regex.iQuery, + maskLen = mask.match( ts.filter.regex.igQuery ).length, + query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], + len = query.length - 1, + indx = 0, + val = mask; + if ( len < 1 && maskLen > 1 ) { + // only one 'word' in query but mask has >1 slots + query[1] = query[0]; + } + // replace all {query} with query words... + // if query = 'Bob', then convert mask from '!{query}' to '!Bob' + // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' + while ( regex.test( val ) ) { + val = val.replace( regex, query[indx++] || '' ); + if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { + val = mask.replace( regex, val ); + } + } + return val; + }, + getLatestSearch: function( $input ) { + if ( $input ) { + return $input.sort( function( a, b ) { + return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); + }); + } + return $input || $(); + }, + multipleColumns: function( c, $input ) { + // look for multiple columns '1-3,4-6,8' in data-column + var temp, ranges, range, start, end, singles, i, indx, len, + wo = c.widgetOptions, + // only target 'all' column inputs on initialization + // & don't target 'all' column inputs if they don't exist + targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, + columns = [], + val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); + // process column range + if ( targets && /-/.test( val ) ) { + ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); + len = ranges.length; + for ( indx = 0; indx < len; indx++ ) { + range = ranges[indx].split( /\s*-\s*/ ); + start = parseInt( range[0], 10 ) || 0; + end = parseInt( range[1], 10 ) || ( c.columns - 1 ); + if ( start > end ) { + temp = start; start = end; end = temp; // swap + } + if ( end >= c.columns ) { + end = c.columns - 1; + } + for ( ; start <= end; start++ ) { + columns.push( start ); + } + // remove processed range from val + val = val.replace( ranges[ indx ], '' ); + } + } + // process single columns + if ( targets && /,/.test( val ) ) { + singles = val.split( /\s*,\s*/ ); + len = singles.length; + for ( i = 0; i < len; i++ ) { + if ( singles[ i ] !== '' ) { + indx = parseInt( singles[ i ], 10 ); + if ( indx < c.columns ) { + columns.push( indx ); + } + } + } + } + // return all columns + if ( !columns.length ) { + for ( indx = 0; indx < c.columns; indx++ ) { + columns.push( indx ); + } + } + return columns; + }, + processTypes: function( c, data, vars ) { + var ffxn, + filterMatched = null, + matches = null; + for ( ffxn in ts.filter.types ) { + if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { + matches = ts.filter.types[ffxn]( c, data, vars ); + if ( matches !== null ) { + filterMatched = matches; + } + } + } + return filterMatched; + }, + processRow: function( c, data, vars ) { + var columnIndex, hasSelect, result, val, filterMatched, + fxn, ffxn, txt, + regex = ts.filter.regex, + wo = c.widgetOptions, + showRow = true; + data.$cells = data.$row.children(); + + if ( data.anyMatchFlag ) { + // look for multiple columns '1-3,4-6,8' + columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); + data.anyMatch = true; + data.isMatch = true; + data.rowArray = data.$cells.map( function( i ) { + if ( $.inArray( i, columnIndex ) > -1 ) { + if ( data.parsed[ i ] ) { + txt = data.cacheArray[ i ]; + } else { + txt = data.rawArray[ i ]; + txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); + if ( c.sortLocaleCompare ) { + txt = ts.replaceAccents( txt ); + } + } + return txt; + } + }).get(); + data.filter = data.anyMatchFilter; + data.iFilter = data.iAnyMatchFilter; + data.exact = data.rowArray.join( ' ' ); + data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; + data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); + + vars.excludeMatch = vars.noAnyMatch; + filterMatched = ts.filter.processTypes( c, data, vars ); + + if ( filterMatched !== null ) { + showRow = filterMatched; + } else { + if ( wo.filter_startsWith ) { + showRow = false; + columnIndex = c.columns; + while ( !showRow && columnIndex > 0 ) { + columnIndex--; + showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; + } + } else { + showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; + } + } + data.anyMatch = false; + // no other filters to process + if ( data.filters.join( '' ) === data.filter ) { + return showRow; + } + } + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.filter = data.filters[ columnIndex ]; + data.index = columnIndex; + + // filter types to exclude, per column + vars.excludeMatch = vars.excludeFilter[ columnIndex ]; + + // ignore if filter is empty or disabled + if ( data.filter ) { + data.cache = data.cacheArray[ columnIndex ]; + // check if column data should be from the cell or from parsed data + if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { + data.exact = data.cache; + } else { + result = data.rawArray[ columnIndex ] || ''; + data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 + } + data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? + data.exact.toLowerCase() : data.exact; + + data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); + + result = showRow; // if showRow is true, show that row + + // in case select filter option has a different value vs text 'a - z|A through Z' + ffxn = wo.filter_columnFilters ? + c.$filters.add( c.$externalFilters ) + .filter( '[data-column="' + columnIndex + '"]' ) + .find( 'select option:selected' ) + .attr( 'data-function-name' ) || '' : ''; + // replace accents - see #357 + if ( c.sortLocaleCompare ) { + data.filter = ts.replaceAccents( data.filter ); + } + + val = true; + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { + data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); + // val is used to indicate that a filter select is using a default filter; + // so we override the exact & partial matches + val = false; + } + // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), + // data.filter = case sensitive + data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; + fxn = vars.functions[ columnIndex ]; + hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); + filterMatched = null; + if ( fxn || ( hasSelect && val ) ) { + if ( fxn === true || hasSelect ) { + // default selector uses exact match unless 'filter-match' class is found + filterMatched = data.isMatch ? + data.iExact.search( data.iFilter ) >= 0 : + data.filter === data.exact; + } else if ( typeof fxn === 'function' ) { + // filter callback( exact cell content, parser normalized content, + // filter input value, column index, jQuery row object ) + filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { + // selector option function + txt = ffxn || data.filter; + filterMatched = + fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } + } + if ( filterMatched === null ) { + // cycle through the different filters + // filters return a boolean or null if nothing matches + filterMatched = ts.filter.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + result = filterMatched; + // Look for match, and add child row data for matching + } else { + txt = ( data.iExact + data.childRowText ) + .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); + result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); + } + } else { + result = filterMatched; + } + showRow = ( result ) ? showRow : false; + } + } + return showRow; + }, + findRows: function( table, filters, combinedFilters ) { + if ( table.config.lastCombinedFilter === combinedFilters || + !table.config.widgetOptions.filter_initialized ) { + return; + } + var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, + isChild, childRow, lastSearch, showRow, time, val, indx, + notFiltered, searchFiltered, query, injected, res, id, txt, + storedFilters = $.extend( [], filters ), + regex = ts.filter.regex, + c = table.config, + wo = c.widgetOptions, + // data object passed to filters; anyMatch is a flag for the filters + data = { + anyMatch: false, + filters: filters, + // regex filter type cache + filter_regexCache : [] + }, + vars = { + // anyMatch really screws up with these types of filters + noAnyMatch: [ 'range', 'notMatch', 'operators' ], + // cache filter variables that use ts.getColumnData in the main loop + functions : [], + excludeFilter : [], + defaultColFilter : [], + defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' + }; + + // parse columns after formatter, in case the class is added at that point + data.parsed = c.$headers.map( function( columnIndex ) { + return c.parsers && c.parsers[ columnIndex ] && + // force parsing if parser type is numeric + c.parsers[ columnIndex ].parsed || + // getData won't return 'parsed' if other 'filter-' class names exist + // ( e.g. ) + ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], + ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || + $( this ).hasClass( 'filter-parsed' ); + }).get(); + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + vars.functions[ columnIndex ] = + ts.getColumnData( table, wo.filter_functions, columnIndex ); + vars.defaultColFilter[ columnIndex ] = + ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; + vars.excludeFilter[ columnIndex ] = + ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); + } + + if ( c.debug ) { + ts.log( 'Filter: Starting filter widget search', filters ); + time = new Date(); + } + // filtered rows count + c.filteredRows = 0; + c.totalRows = 0; + // combindedFilters are undefined on init + combinedFilters = ( storedFilters || [] ).join( '' ); + + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); + // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! + // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); + columnIndex = c.columns; + // convert stored rows into a jQuery object + norm_rows = c.cache[ tbodyIndex ].normalized; + $rows = $( $.map( norm_rows, function( el ) { + return el[ columnIndex ].$row.get(); + }) ); + + if ( combinedFilters === '' || wo.filter_serversideFiltering ) { + $rows + .removeClass( wo.filter_filteredRow ) + .not( '.' + c.cssChildRow ) + .css( 'display', '' ); + } else { + // filter out child rows + $rows = $rows.not( '.' + c.cssChildRow ); + len = $rows.length; + + if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || + typeof filters[c.columns] !== 'undefined' ) { + data.anyMatchFlag = true; + data.anyMatchFilter = '' + ( + filters[ c.columns ] || + wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || + '' + ); + if ( wo.filter_columnAnyMatch ) { + // specific columns search + query = data.anyMatchFilter.split( regex.andSplit ); + injected = false; + for ( indx = 0; indx < query.length; indx++ ) { + res = query[ indx ].split( ':' ); + if ( res.length > 1 ) { + // make the column a one-based index ( non-developers start counting from one :P ) + id = parseInt( res[0], 10 ) - 1; + if ( id >= 0 && id < c.columns ) { // if id is an integer + filters[ id ] = res[1]; + query.splice( indx, 1 ); + indx--; + injected = true; + } + } + } + if ( injected ) { + data.anyMatchFilter = query.join( ' && ' ); + } + } + } + + // optimize searching only through already filtered rows - see #313 + searchFiltered = wo.filter_searchFiltered; + lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; + if ( searchFiltered ) { + // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 + for ( indx = 0; indx < columnIndex + 1; indx++ ) { + val = filters[indx] || ''; + // break out of loop if we've already determined not to search filtered rows + if ( !searchFiltered ) { indx = columnIndex; } + // search already filtered rows if... + searchFiltered = searchFiltered && lastSearch.length && + // there are no changes from beginning of filter + val.indexOf( lastSearch[indx] || '' ) === 0 && + // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string + !regex.alreadyFiltered.test( val ) && + // if we are not doing exact matches, using '|' ( logical or ) or not '!' + !/[=\"\|!]/.test( val ) && + // don't search only filtered if the value is negative + // ( '> -10' => '> -100' will ignore hidden rows ) + !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && + // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 + !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && + !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); + } + } + notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; + // can't search when all rows are hidden - this happens when looking for exact matches + if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } + if ( c.debug ) { + ts.log( 'Filter: Searching through ' + + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); + } + if ( data.anyMatchFlag ) { + if ( c.sortLocaleCompare ) { + // replace accents + data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); + } + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { + data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); + // clear search filtered flag because default filters are not saved to the last search + searchFiltered = false; + } + // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true + // when c.ignoreCase is true, the cache contains all lower case data + data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? + data.anyMatchFilter : + data.anyMatchFilter.toLowerCase(); + } + + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + + txt = $rows[ rowIndex ].className; + // the first row can never be a child row + isChild = rowIndex && regex.child.test( txt ); + // skip child rows & already filtered rows + if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { + continue; + } + + data.$row = $rows.eq( rowIndex ); + data.cacheArray = norm_rows[ rowIndex ]; + rowData = data.cacheArray[ c.columns ]; + data.rawArray = rowData.raw; + data.childRowText = ''; + + if ( !wo.filter_childByColumn ) { + txt = ''; + // child row cached text + childRow = rowData.child; + // so, if 'table.config.widgetOptions.filter_childRows' is true and there is + // a match anywhere in the child row, then it will make the row visible + // checked here so the option can be changed dynamically + for ( indx = 0; indx < childRow.length; indx++ ) { + txt += ' ' + childRow[indx].join( '' ) || ''; + } + data.childRowText = wo.filter_childRows ? + ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : + ''; + } + + showRow = ts.filter.processRow( c, data, vars ); + childRow = rowData.$row.filter( ':gt( 0 )' ); + + if ( wo.filter_childRows && childRow.length ) { + if ( wo.filter_childByColumn ) { + // cycle through each child row + for ( indx = 0; indx < childRow.length; indx++ ) { + data.$row = childRow.eq( indx ); + data.cacheArray = rowData.child[ indx ]; + data.rawArray = data.cacheArray; + // use OR comparison on child rows + showRow = showRow || ts.filter.processRow( c, data, vars ); + } + } + childRow.toggleClass( wo.filter_filteredRow, !showRow ); + } + + rowData.$row + .toggleClass( wo.filter_filteredRow, !showRow )[0] + .display = showRow ? '' : 'none'; + } + } + c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; + c.totalRows += $rows.length; + ts.processTbody( table, $tbody, false ); + } + c.lastCombinedFilter = combinedFilters; // save last search + // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) + c.lastSearch = storedFilters; + c.$table.data( 'lastSearch', storedFilters ); + if ( wo.filter_saveFilters && ts.storage ) { + ts.storage( table, 'tablesorter-filters', storedFilters ); + } + if ( c.debug ) { + ts.benchmark( 'Completed filter widget search', time ); + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterEnd', c ); + } + setTimeout( function() { + c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied + }, 0 ); + }, + getOptionSource: function( table, column, onlyAvail ) { + table = $( table )[0]; + var cts, txt, indx, len, + c = table.config, + wo = c.widgetOptions, + parsed = [], + arry = false, + source = wo.filter_selectSource, + last = c.$table.data( 'lastSearch' ) || [], + fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); + + if ( onlyAvail && last[column] !== '' ) { + onlyAvail = false; + } + + // filter select source option + if ( fxn === true ) { + // OVERALL source + arry = source( table, column, onlyAvail ); + } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { + // selectSource is a jQuery object or string of options + return fxn; + } else if ( $.isArray( fxn ) ) { + arry = fxn; + } else if ( $.type( source ) === 'object' && fxn ) { + // custom select source function for a SPECIFIC COLUMN + arry = fxn( table, column, onlyAvail ); + } + if ( arry === false ) { + // fall back to original method + arry = ts.filter.getOptions( table, column, onlyAvail ); + } + + // get unique elements and sort the list + // if $.tablesorter.sortText exists ( not in the original tablesorter ), + // then natural sort the list otherwise use a basic sort + arry = $.grep( arry, function( value, indx ) { + return $.inArray( value, arry ) === indx; + }); + + if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { + // unsorted select options + return arry; + } else { + len = arry.length; + // parse select option values + for ( indx = 0; indx < len; indx++ ) { + txt = arry[ indx ]; + // parse array data using set column parser; this DOES NOT pass the original + // table cell to the parser format function + parsed.push({ + t : txt, + // check parser length - fixes #934 + p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + }); + } + + // sort parsed select options + cts = c.textSorter || ''; + parsed.sort( function( a, b ) { + // sortNatural breaks if you don't pass it strings + var x = a.p.toString(), + y = b.p.toString(); + if ( $.isFunction( cts ) ) { + // custom OVERALL text sorter + return cts( x, y, true, column, table ); + } else if ( typeof cts === 'object' && cts.hasOwnProperty( column ) ) { + // custom text sorter for a SPECIFIC COLUMN + return cts[column]( x, y, true, column, table ); + } else if ( ts.sortNatural ) { + // fall back to natural sort + return ts.sortNatural( x, y ); + } + // using an older version! do a basic sort + return true; + }); + // rebuild arry from sorted parsed data + arry = []; + len = parsed.length; + for ( indx = 0; indx < len; indx++ ) { + arry.push( parsed[indx].t ); + } + return arry; + } + }, + getOptions: function( table, column, onlyAvail ) { + table = $( table )[0]; + var rowIndex, tbodyIndex, len, row, cache, + c = table.config, + wo = c.widgetOptions, + arry = []; + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + cache = c.cache[tbodyIndex]; + len = c.cache[tbodyIndex].normalized.length; + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + // get cached row from cache.row ( old ) or row data object + // ( new; last item in normalized array ) + row = cache.row ? + cache.row[ rowIndex ] : + cache.normalized[ rowIndex ][ c.columns ].$row[0]; + // check if has class filtered + if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { + continue; + } + // get non-normalized cell content + if ( wo.filter_useParsedData || + c.parsers[column].parsed || + c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { + arry.push( '' + cache.normalized[ rowIndex ][ column ] ); + } else { + // get raw cached data instead of content directly from the cells + arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + } + } } return arry; - } - }, - getOptions: function( table, column, onlyAvail ) { - table = $( table )[0]; - var rowIndex, tbodyIndex, len, row, cache, - c = table.config, - wo = c.widgetOptions, - arry = []; - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - cache = c.cache[tbodyIndex]; - len = c.cache[tbodyIndex].normalized.length; - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - // get cached row from cache.row ( old ) or row data object - // ( new; last item in normalized array ) - row = cache.row ? - cache.row[ rowIndex ] : - cache.normalized[ rowIndex ][ c.columns ].$row[0]; - // check if has class filtered - if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { - continue; + }, + buildSelect: function( table, column, arry, updating, onlyAvail ) { + table = $( table )[0]; + column = parseInt( column, 10 ); + if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { + return; + } + var indx, val, txt, t, $filters, $filter, + c = table.config, + wo = c.widgetOptions, + node = c.$headerIndexed[ column ], + // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 + options = '', + // Get curent filter value + currentValue = c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .val(); + // nothing included in arry ( external source ), so get the options from + // filter_selectSource or column data + if ( typeof arry === 'undefined' || arry === '' ) { + arry = ts.filter.getOptionSource( table, column, onlyAvail ); + } + + if ( $.isArray( arry ) ) { + // build option list + for ( indx = 0; indx < arry.length; indx++ ) { + txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); + val = txt; + // allow including a symbol in the selectSource array + // 'a-z|A through Z' so that 'a-z' becomes the option value + // and 'A through Z' becomes the option text + if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + t = txt.split( wo.filter_selectSourceSeparator ); + val = t[0]; + txt = t[1]; + } + // replace quotes - fixes #242 & ignore empty strings + // see http://stackoverflow.com/q/14990971/145346 + options += arry[indx] !== '' ? + '' : ''; } - // get non-normalized cell content - if ( wo.filter_useParsedData || - c.parsers[column].parsed || - c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { - arry.push( '' + cache.normalized[ rowIndex ][ column ] ); - } else { - // get raw cached data instead of content directly from the cells - arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + // clear arry so it doesn't get appended twice + arry = []; + } + + // update all selects in the same column ( clone thead in sticky headers & + // any external selects ) - fixes 473 + $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) + .find( '.' + tscss.filter ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; + } + $filter = $filters.filter( 'select[data-column="' + column + '"]' ); + + // make sure there is a select there! + if ( $filter.length ) { + $filter[ updating ? 'html' : 'append' ]( options ); + if ( !$.isArray( arry ) ) { + // append options if arry is provided externally as a string or jQuery object + // options ( default value ) was already added + $filter.append( arry ).val( currentValue ); + } + $filter.val( currentValue ); + } + }, + buildDefault: function( table, updating ) { + var columnIndex, $header, noSelect, + c = table.config, + wo = c.widgetOptions, + columns = c.columns; + // build default select dropdown + for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { + $header = c.$headerIndexed[columnIndex]; + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + // look for the filter-select class; build/update it if found + if ( ( $header.hasClass( 'filter-select' ) || + ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { + ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); } } } - return arry; - }, - buildSelect: function( table, column, arry, updating, onlyAvail ) { - table = $( table )[0]; - column = parseInt( column, 10 ); - if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { - return; - } - var indx, val, txt, t, $filters, $filter, - c = table.config, - wo = c.widgetOptions, - node = c.$headerIndexed[ column ], - // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 - options = '', - // Get curent filter value - currentValue = c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .val(); - // nothing included in arry ( external source ), so get the options from - // filter_selectSource or column data - if ( typeof arry === 'undefined' || arry === '' ) { - arry = ts.filter.getOptionSource( table, column, onlyAvail ); - } + }; - if ( $.isArray( arry ) ) { - // build option list - for ( indx = 0; indx < arry.length; indx++ ) { - txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); - val = txt; - // allow including a symbol in the selectSource array - // 'a-z|A through Z' so that 'a-z' becomes the option value - // and 'A through Z' becomes the option text - if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - t = txt.split( wo.filter_selectSourceSeparator ); - val = t[0]; - txt = t[1]; - } - // replace quotes - fixes #242 & ignore empty strings - // see http://stackoverflow.com/q/14990971/145346 - options += arry[indx] !== '' ? - '' : ''; + ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { + var i, $filters, $column, cols, + filters = false, + c = table ? $( table )[0].config : '', + wo = c ? c.widgetOptions : ''; + if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || + // setFilters called, but last search is exactly the same as the current + // fixes issue #733 & #903 where calling update causes the input values to reset + ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { + return $( table ).data( 'lastSearch' ); + } + if ( c ) { + if ( c.$filters ) { + $filters = c.$filters.find( '.' + tscss.filter ); } - // clear arry so it doesn't get appended twice - arry = []; - } - - // update all selects in the same column ( clone thead in sticky headers & - // any external selects ) - fixes 473 - $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) - .find( '.' + tscss.filter ); - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - $filter = $filters.filter( 'select[data-column="' + column + '"]' ); - - // make sure there is a select there! - if ( $filter.length ) { - $filter[ updating ? 'html' : 'append' ]( options ); - if ( !$.isArray( arry ) ) { - // append options if arry is provided externally as a string or jQuery object - // options ( default value ) was already added - $filter.append( arry ).val( currentValue ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; } - $filter.val( currentValue ); - } - }, - buildDefault: function( table, updating ) { - var columnIndex, $header, noSelect, - c = table.config, - wo = c.widgetOptions, - columns = c.columns; - // build default select dropdown - for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { - $header = c.$headerIndexed[columnIndex]; - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - // look for the filter-select class; build/update it if found - if ( ( $header.hasClass( 'filter-select' ) || - ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { - ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } -}; - -ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { - var i, $filters, $column, cols, - filters = false, - c = table ? $( table )[0].config : '', - wo = c ? c.widgetOptions : ''; - if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || - // setFilters called, but last search is exactly the same as the current - // fixes issue #733 & #903 where calling update causes the input values to reset - ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { - return $( table ).data( 'lastSearch' ); - } - if ( c ) { - if ( c.$filters ) { - $filters = c.$filters.find( '.' + tscss.filter ); - } - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - if ( $filters && $filters.length ) { - filters = setFilters || []; - for ( i = 0; i < c.columns + 1; i++ ) { - cols = ( i === c.columns ? - // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) - wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : - '[data-column="' + i + '"]' ); - $column = $filters.filter( cols ); - if ( $column.length ) { - // move the latest search to the first slot in the array - $column = ts.filter.getLatestSearch( $column ); - if ( $.isArray( setFilters ) ) { - // skip first ( latest input ) to maintain cursor position while typing - if ( skipFirst && $column.length > 1 ) { - $column = $column.slice( 1 ); - } - if ( i === c.columns ) { - // prevent data-column='all' from filling data-column='0,1' ( etc ) - cols = $column.filter( wo.filter_anyColumnSelector ); - $column = cols.length ? cols : $column; - } - $column - .val( setFilters[ i ] ) - .trigger( 'change.tsfilter' ); - } else { - filters[i] = $column.val() || ''; - // don't change the first... it will move the cursor - if ( i === c.columns ) { - // don't update range columns from 'all' setting + if ( $filters && $filters.length ) { + filters = setFilters || []; + for ( i = 0; i < c.columns + 1; i++ ) { + cols = ( i === c.columns ? + // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) + wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : + '[data-column="' + i + '"]' ); + $column = $filters.filter( cols ); + if ( $column.length ) { + // move the latest search to the first slot in the array + $column = ts.filter.getLatestSearch( $column ); + if ( $.isArray( setFilters ) ) { + // skip first ( latest input ) to maintain cursor position while typing + if ( skipFirst && $column.length > 1 ) { + $column = $column.slice( 1 ); + } + if ( i === c.columns ) { + // prevent data-column='all' from filling data-column='0,1' ( etc ) + cols = $column.filter( wo.filter_anyColumnSelector ); + $column = cols.length ? cols : $column; + } $column - .slice( 1 ) - .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) - .val( filters[ i ] ); + .val( setFilters[ i ] ) + .trigger( 'change.tsfilter' ); } else { - $column - .slice( 1 ) - .val( filters[ i ] ); + filters[i] = $column.val() || ''; + // don't change the first... it will move the cursor + if ( i === c.columns ) { + // don't update range columns from 'all' setting + $column + .slice( 1 ) + .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) + .val( filters[ i ] ); + } else { + $column + .slice( 1 ) + .val( filters[ i ] ); + } + } + // save any match input dynamically + if ( i === c.columns && $column.length ) { + wo.filter_$anyMatch = $column; } - } - // save any match input dynamically - if ( i === c.columns && $column.length ) { - wo.filter_$anyMatch = $column; } } } } - } - if ( filters.length === 0 ) { - filters = false; - } - return filters; -}; + if ( filters.length === 0 ) { + filters = false; + } + return filters; + }; -ts.setFilters = function( table, filter, apply, skipFirst ) { - var c = table ? $( table )[0].config : '', - valid = ts.getFilters( table, true, filter, skipFirst ); - if ( c && apply ) { - // ensure new set filters are applied, even if the search is the same - c.lastCombinedFilter = null; - c.lastSearch = []; - ts.filter.searching( c.table, filter, skipFirst ); - c.$table.trigger( 'filterFomatterUpdate' ); - } - return !!valid; -}; + ts.setFilters = function( table, filter, apply, skipFirst ) { + var c = table ? $( table )[0].config : '', + valid = ts.getFilters( table, true, filter, skipFirst ); + if ( c && apply ) { + // ensure new set filters are applied, even if the search is the same + c.lastCombinedFilter = null; + c.lastSearch = []; + ts.filter.searching( c.table, filter, skipFirst ); + c.$table.trigger( 'filterFomatterUpdate' ); + } + return !!valid; + }; })( jQuery ); @@ -4157,750 +4159,751 @@ ts.setFilters = function( table, filter, apply, skipFirst ) { * by Rob Garrison */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - sticky : 'tablesorter-stickyHeader', // stickyHeader - stickyVis : 'tablesorter-sticky-visible', - stickyHide: 'tablesorter-sticky-hidden', - stickyWrap: 'tablesorter-sticky-wrapper' -}); + $.extend(ts.css, { + sticky : 'tablesorter-stickyHeader', // stickyHeader + stickyVis : 'tablesorter-sticky-visible', + stickyHide: 'tablesorter-sticky-hidden', + stickyWrap: 'tablesorter-sticky-wrapper' + }); -// Add a resize event to table headers -ts.addHeaderResizeEvent = function(table, disable, settings) { - table = $(table)[0]; // make sure we're using a dom element - if ( !table.config ) { return; } - var defaults = { - timer : 250 + // Add a resize event to table headers + ts.addHeaderResizeEvent = function(table, disable, settings) { + table = $(table)[0]; // make sure we're using a dom element + if ( !table.config ) { return; } + var defaults = { + timer : 250 + }, + options = $.extend({}, defaults, settings), + c = table.config, + wo = c.widgetOptions, + checkSizes = function( triggerEvent ) { + var index, headers, $header, sizes, width, height, + len = c.$headers.length; + wo.resize_flag = true; + headers = []; + for ( index = 0; index < len; index++ ) { + $header = c.$headers.eq( index ); + sizes = $header.data( 'savedSizes' ) || [ 0, 0 ]; // fixes #394 + width = $header[0].offsetWidth; + height = $header[0].offsetHeight; + if ( width !== sizes[0] || height !== sizes[1] ) { + $header.data( 'savedSizes', [ width, height ] ); + headers.push( $header[0] ); + } + } + if ( headers.length && triggerEvent !== false ) { + c.$table.trigger( 'resize', [ headers ] ); + } + wo.resize_flag = false; + }; + checkSizes( false ); + clearInterval(wo.resize_timer); + if (disable) { + wo.resize_flag = false; + return false; + } + wo.resize_timer = setInterval(function() { + if (wo.resize_flag) { return; } + checkSizes(); + }, options.timer); + }; + + // Sticky headers based on this awesome article: + // http://css-tricks.com/13465-persistent-headers/ + // and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech + // ************************** + ts.addWidget({ + id: 'stickyHeaders', + priority: 60, // sticky widget must be initialized after the filter widget! + options: { + stickyHeaders : '', // extra class name added to the sticky header row + stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to + stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) + stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) + stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element + stickyHeaders_filteredToTop: true, // scroll table top into view after filtering + stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists + stickyHeaders_addResizeEvent : true, // trigger 'resize' event on headers + stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header + stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs }, - options = $.extend({}, defaults, settings), - c = table.config, - wo = c.widgetOptions, - checkSizes = function( triggerEvent ) { - var index, headers, $header, sizes, width, height, - len = c.$headers.length; - wo.resize_flag = true; - headers = []; - for ( index = 0; index < len; index++ ) { - $header = c.$headers.eq( index ); - sizes = $header.data( 'savedSizes' ) || [ 0,0 ]; // fixes #394 - width = $header[0].offsetWidth; - height = $header[0].offsetHeight; - if ( width !== sizes[0] || height !== sizes[1] ) { - $header.data( 'savedSizes', [ width, height ] ); - headers.push( $header[0] ); + format: function(table, c, wo) { + // filter widget doesn't initialize on an empty table. Fixes #449 + if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { + return; + } + var index, len, $t, + $table = c.$table, + // add position: relative to attach element, hopefully it won't cause trouble. + $attach = $(wo.stickyHeaders_attachTo), + namespace = c.namespace + 'stickyheaders ', + // element to watch for the scroll event + $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), + $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), + $thead = $table.children('thead:first'), + $header = $thead.children('tr').not('.sticky-false').children(), + $tfoot = $table.children('tfoot'), + $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, + // is this table nested? If so, find parent sticky header wrapper (div, not table) + $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? + $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], + nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, + // clone table, then wrap to make sticky header + $stickyTable = wo.$sticky = $table.clone() + .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) + .wrap('
'), + $stickyWrap = $stickyTable.parent() + .addClass(ts.css.stickyHide) + .css({ + position : $attach.length ? 'absolute' : 'fixed', + padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), + top : stickyOffset + nestedStickyTop, + left : 0, + visibility : 'hidden', + zIndex : wo.stickyHeaders_zIndex || 2 + }), + $stickyThead = $stickyTable.children('thead:first'), + $stickyCells, + laststate = '', + spacing = 0, + setWidth = function($orig, $clone){ + var index, width, border, $cell, $this, + $cells = $orig.filter(':visible'), + len = $cells.length; + for ( index = 0; index < len; index++ ) { + $cell = $clone.filter(':visible').eq(index); + $this = $cells.eq(index); + // code from https://github.com/jmosbech/StickyTableHeaders + if ($this.css('box-sizing') === 'border-box') { + width = $this.outerWidth(); + } else { + if ($cell.css('border-collapse') === 'collapse') { + if (window.getComputedStyle) { + width = parseFloat( window.getComputedStyle($this[0], null).width ); + } else { + // ie8 only + border = parseFloat( $this.css('border-width') ); + width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; + } + } else { + width = $this.width(); + } + } + $cell.css({ + 'width': width, + 'min-width': width, + 'max-width': width + }); + } + }, + resizeHeader = function() { + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; + spacing = 0; + $stickyWrap.css({ + left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : + $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, + width: $table.outerWidth() + }); + setWidth( $table, $stickyTable ); + setWidth( $header, $stickyCells ); + }, + scrollSticky = function( resizing ) { + if (!$table.is(':visible')) { return; } // fixes #278 + // Detect nested tables - fixes #724 + nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; + var offset = $table.offset(), + yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 + xWindow = $.isWindow( $xScroll[0] ), + // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), + isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', + cssSettings = { visibility : isVisible }; + + if ($attach.length) { + cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + } + if (xWindow) { + // adjust when scrolling horizontally - fixes issue #143 + cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; + } + if ($nestedSticky.length) { + cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; + } + $stickyWrap + .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) + .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) + .css(cssSettings); + if (isVisible !== laststate || resizing) { + // make sure the column widths match + resizeHeader(); + laststate = isVisible; + } + }; + // only add a position relative if a position isn't already defined + if ($attach.length && !$attach.css('position')) { + $attach.css('position', 'relative'); + } + // fix clone ID, if it exists - fixes #271 + if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } + // clear out cloned table, except for sticky header + // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing + $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); + $stickyTable.find('tbody, tfoot').remove(); + $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); + // issue #172 - find td/th in sticky header + $stickyCells = $stickyThead.children().children(); + $stickyTable.css({ height:0, width:0, margin: 0 }); + // remove resizable block + $stickyCells.find('.' + ts.css.resizer).remove(); + // update sticky header class names to match real header after sorting + $table + .addClass('hasStickyHeaders') + .bind('pagerComplete' + namespace, function() { + resizeHeader(); + }); + + ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); + + // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. + $table.after( $stickyWrap ); + + // onRenderHeader is defined, we need to do something about it (fixes #641) + if (c.onRenderHeader) { + $t = $stickyThead.children('tr').children(); + len = $t.length; + for ( index = 0; index < len; index++ ) { + // send second parameter + c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } } - if ( headers.length && triggerEvent !== false ) { - c.$table.trigger( 'resize', [ headers ] ); - } - wo.resize_flag = false; - }; - checkSizes( false ); - clearInterval(wo.resize_timer); - if (disable) { - wo.resize_flag = false; - return false; - } - wo.resize_timer = setInterval(function() { - if (wo.resize_flag) { return; } - checkSizes(); - }, options.timer); -}; -// Sticky headers based on this awesome article: -// http://css-tricks.com/13465-persistent-headers/ -// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech -// ************************** -ts.addWidget({ - id: "stickyHeaders", - priority: 60, // sticky widget must be initialized after the filter widget! - options: { - stickyHeaders : '', // extra class name added to the sticky header row - stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to - stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) - stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) - stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element - stickyHeaders_filteredToTop: true, // scroll table top into view after filtering - stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists - stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers - stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header - stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs - }, - format: function(table, c, wo) { - // filter widget doesn't initialize on an empty table. Fixes #449 - if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { - return; - } - var index, len, $t, - $table = c.$table, - // add position: relative to attach element, hopefully it won't cause trouble. - $attach = $(wo.stickyHeaders_attachTo), - namespace = c.namespace + 'stickyheaders ', - // element to watch for the scroll event - $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), - $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), - $thead = $table.children('thead:first'), - $header = $thead.children('tr').not('.sticky-false').children(), - $tfoot = $table.children('tfoot'), - $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, - // is this table nested? If so, find parent sticky header wrapper (div, not table) - $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? - $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], - nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, - // clone table, then wrap to make sticky header - $stickyTable = wo.$sticky = $table.clone() - .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) - .wrap('
'), - $stickyWrap = $stickyTable.parent() - .addClass(ts.css.stickyHide) - .css({ - position : $attach.length ? 'absolute' : 'fixed', - padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), - top : stickyOffset + nestedStickyTop, - left : 0, - visibility : 'hidden', - zIndex : wo.stickyHeaders_zIndex || 2 - }), - $stickyThead = $stickyTable.children('thead:first'), - $stickyCells, - laststate = '', - spacing = 0, - setWidth = function($orig, $clone){ - var index, width, border, $cell, $this, - $cells = $orig.filter(':visible'), - len = $cells.length; - for ( index = 0; index < len; index++ ) { - $cell = $clone.filter(':visible').eq(index); - $this = $cells.eq(index); - // code from https://github.com/jmosbech/StickyTableHeaders - if ($this.css('box-sizing') === 'border-box') { - width = $this.outerWidth(); - } else { - if ($cell.css('border-collapse') === 'collapse') { - if (window.getComputedStyle) { - width = parseFloat( window.getComputedStyle($this[0], null).width ); - } else { - // ie8 only - border = parseFloat( $this.css('border-width') ); - width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; - } - } else { - width = $this.width(); + // make it sticky! + $xScroll.add($yScroll) + .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) + .bind('scroll resize '.split(' ').join( namespace ), function( event ) { + scrollSticky( event.type === 'resize' ); + }); + c.$table + .unbind('stickyHeadersUpdate' + namespace) + .bind('stickyHeadersUpdate' + namespace, function(){ + scrollSticky( true ); + }); + + if (wo.stickyHeaders_addResizeEvent) { + ts.addHeaderResizeEvent(table); + } + + // look for filter widget + if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { + // scroll table into view after filtering, if sticky header is active - #482 + $table.bind('filterEnd' + namespace, function() { + // $(':focus') needs jQuery 1.6+ + var $td = $(document.activeElement).closest('td'), + column = $td.parent().children().index($td); + // only scroll if sticky header is active + if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { + // scroll to original table (not sticky clone) + window.scrollTo(0, $table.position().top); + // give same input/select focus; check if c.$filters exists; fixes #594 + if (column >= 0 && c.$filters) { + c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); } } - $cell.css({ - 'width': width, - 'min-width': width, - 'max-width': width - }); - } - }, - resizeHeader = function() { - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; - spacing = 0; - $stickyWrap.css({ - left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : - $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, - width: $table.outerWidth() }); - setWidth( $table, $stickyTable ); - setWidth( $header, $stickyCells ); - }, - scrollSticky = function( resizing ) { - if (!$table.is(':visible')) { return; } // fixes #278 - // Detect nested tables - fixes #724 - nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; - var offset = $table.offset(), - yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 - xWindow = $.isWindow( $xScroll[0] ), - // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), - isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', - cssSettings = { visibility : isVisible }; - - if ($attach.length) { - cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); + // support hideFilters + if (wo.filter_hideFilters) { + ts.filter.hideFilters($stickyTable, c); } - if (xWindow) { - // adjust when scrolling horizontally - fixes issue #143 - cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; - } - if ($nestedSticky.length) { - cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; - } - $stickyWrap - .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) - .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) - .css(cssSettings); - if (isVisible !== laststate || resizing) { - // make sure the column widths match - resizeHeader(); - laststate = isVisible; - } - }; - // only add a position relative if a position isn't already defined - if ($attach.length && !$attach.css('position')) { - $attach.css('position', 'relative'); - } - // fix clone ID, if it exists - fixes #271 - if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } - // clear out cloned table, except for sticky header - // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing - $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); - $stickyTable.find('tbody, tfoot').remove(); - $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); - // issue #172 - find td/th in sticky header - $stickyCells = $stickyThead.children().children(); - $stickyTable.css({ height:0, width:0, margin: 0 }); - // remove resizable block - $stickyCells.find('.' + ts.css.resizer).remove(); - // update sticky header class names to match real header after sorting - $table - .addClass('hasStickyHeaders') - .bind('pagerComplete' + namespace, function() { - resizeHeader(); - }); - - ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); - - // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. - $table.after( $stickyWrap ); - - // onRenderHeader is defined, we need to do something about it (fixes #641) - if (c.onRenderHeader) { - $t = $stickyThead.children('tr').children(); - len = $t.length; - for ( index = 0; index < len; index++ ) { - // send second parameter - c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } + + $table.trigger('stickyHeadersInit'); + + }, + remove: function(table, c, wo) { + var namespace = c.namespace + 'stickyheaders '; + c.$table + .removeClass('hasStickyHeaders') + .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) + .next('.' + ts.css.stickyWrap).remove(); + if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table + $(window) + .add(wo.stickyHeaders_xScroll) + .add(wo.stickyHeaders_yScroll) + .add(wo.stickyHeaders_attachTo) + .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); + ts.addHeaderResizeEvent(table, false); } - - // make it sticky! - $xScroll.add($yScroll) - .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) - .bind('scroll resize '.split(' ').join( namespace ), function( event ) { - scrollSticky( event.type === 'resize' ); - }); - c.$table - .unbind('stickyHeadersUpdate' + namespace) - .bind('stickyHeadersUpdate' + namespace, function(){ - scrollSticky( true ); - }); - - if (wo.stickyHeaders_addResizeEvent) { - ts.addHeaderResizeEvent(table); - } - - // look for filter widget - if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { - // scroll table into view after filtering, if sticky header is active - #482 - $table.bind('filterEnd' + namespace, function() { - // $(':focus') needs jQuery 1.6+ - var $td = $(document.activeElement).closest('td'), - column = $td.parent().children().index($td); - // only scroll if sticky header is active - if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { - // scroll to original table (not sticky clone) - window.scrollTo(0, $table.position().top); - // give same input/select focus; check if c.$filters exists; fixes #594 - if (column >= 0 && c.$filters) { - c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); - } - } - }); - ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); - // support hideFilters - if (wo.filter_hideFilters) { - ts.filter.hideFilters($stickyTable, c); - } - } - - $table.trigger('stickyHeadersInit'); - - }, - remove: function(table, c, wo) { - var namespace = c.namespace + 'stickyheaders '; - c.$table - .removeClass('hasStickyHeaders') - .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) - .next('.' + ts.css.stickyWrap).remove(); - if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table - $(window) - .add(wo.stickyHeaders_xScroll) - .add(wo.stickyHeaders_yScroll) - .add(wo.stickyHeaders_attachTo) - .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); - ts.addHeaderResizeEvent(table, false); - } -}); + }); })(jQuery, window); /*! Widget: resizable - updated 6/26/2015 (v2.22.2) */ /*jshint browser:true, jquery:true, unused:false */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - resizableContainer : 'tablesorter-resizable-container', - resizableHandle : 'tablesorter-resizable-handle', - resizableNoSelect : 'tablesorter-disableSelection', - resizableStorage : 'tablesorter-resizable' -}); + $.extend(ts.css, { + resizableContainer : 'tablesorter-resizable-container', + resizableHandle : 'tablesorter-resizable-handle', + resizableNoSelect : 'tablesorter-disableSelection', + resizableStorage : 'tablesorter-resizable' + }); -// Add extra scroller css -$(function(){ - var s = ''; - $(s).appendTo('body'); -}); + // Add extra scroller css + $(function(){ + var s = ''; + $(s).appendTo('body'); + }); -ts.resizable = { - init : function( c, wo ) { - if ( c.$table.hasClass( 'hasResizable' ) ) { return; } - c.$table.addClass( 'hasResizable' ); + ts.resizable = { + init : function( c, wo ) { + if ( c.$table.hasClass( 'hasResizable' ) ) { return; } + c.$table.addClass( 'hasResizable' ); - var noResize, $header, column, storedSizes, tmp, - $table = c.$table, - $parent = $table.parent(), - marginTop = parseInt( $table.css( 'margin-top' ), 10 ), + var noResize, $header, column, storedSizes, tmp, + $table = c.$table, + $parent = $table.parent(), + marginTop = parseInt( $table.css( 'margin-top' ), 10 ), - // internal variables - vars = wo.resizable_ = { - useStorage : ts.storage && wo.resizable !== false, - $wrap : $parent, - mouseXPosition : 0, - $target : null, - $next : null, - overflow : $parent.css('overflow') === 'auto' || - $parent.css('overflow') === 'scroll' || - $parent.css('overflow-x') === 'auto' || - $parent.css('overflow-x') === 'scroll', - storedSizes : [] - }; + // internal variables + vars = wo.resizable_vars = { + useStorage : ts.storage && wo.resizable !== false, + $wrap : $parent, + mouseXPosition : 0, + $target : null, + $next : null, + overflow : $parent.css('overflow') === 'auto' || + $parent.css('overflow') === 'scroll' || + $parent.css('overflow-x') === 'auto' || + $parent.css('overflow-x') === 'scroll', + storedSizes : [] + }; - // set default widths - ts.resizableReset( c.table, true ); + // set default widths + ts.resizableReset( c.table, true ); - // now get measurements! - vars.tableWidth = $table.width(); - // attempt to autodetect - vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; + // now get measurements! + vars.tableWidth = $table.width(); + // attempt to autodetect + vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; - /* - // Hacky method to determine if table width is set to "auto" - // http://stackoverflow.com/a/20892048/145346 - if ( !vars.fullWidth ) { - tmp = $table.width(); - $header = $table.wrap('').parent(); // temp variable - storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; - $table.css( 'margin-left', storedSizes + 50 ); - vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; - $table.css( 'margin-left', storedSizes ? storedSizes : '' ); - $header = null; - $table.unwrap(''); - } - */ - - if ( vars.useStorage && vars.overflow ) { - // save table width - ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); - tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; - ts.resizable.setWidth( $table, tmp, true ); - } - wo.resizable_.storedSizes = storedSizes = ( vars.useStorage ? - ts.storage( c.table, ts.css.resizableStorage ) : - [] ) || []; - ts.resizable.setWidths( c, wo, storedSizes ); - ts.resizable.updateStoredSizes( c, wo ); - - wo.$resizable_container = $( '
' ) - .css({ top : marginTop }) - .insertBefore( $table ); - // add container - for ( column = 0; column < c.columns; column++ ) { - $header = c.$headerIndexed[ column ]; - tmp = ts.getColumnData( c.table, c.headers, column ); - noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; - if ( !noResize ) { - $( '
' ) - .appendTo( wo.$resizable_container ) - .attr({ - 'data-column' : column, - 'unselectable' : 'on' - }) - .data( 'header', $header ) - .bind( 'selectstart', false ); + /* + // Hacky method to determine if table width is set to 'auto' + // http://stackoverflow.com/a/20892048/145346 + if ( !vars.fullWidth ) { + tmp = $table.width(); + $header = $table.wrap('').parent(); // temp variable + storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; + $table.css( 'margin-left', storedSizes + 50 ); + vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; + $table.css( 'margin-left', storedSizes ? storedSizes : '' ); + $header = null; + $table.unwrap(''); } - } - $table.one('tablesorter-initialized', function() { - ts.resizable.setHandlePosition( c, wo ); - ts.resizable.bindings( this.config, this.config.widgetOptions ); - }); - }, + */ - updateStoredSizes : function( c, wo ) { - var column, $header, - len = c.columns, - vars = wo.resizable_; - vars.storedSizes = []; - for ( column = 0; column < len; column++ ) { - $header = c.$headerIndexed[ column ]; - vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; - } - }, + if ( vars.useStorage && vars.overflow ) { + // save table width + ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); + tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; + ts.resizable.setWidth( $table, tmp, true ); + } + wo.resizable_vars.storedSizes = storedSizes = ( vars.useStorage ? + ts.storage( c.table, ts.css.resizableStorage ) : + [] ) || []; + ts.resizable.setWidths( c, wo, storedSizes ); + ts.resizable.updateStoredSizes( c, wo ); - setWidth : function( $el, width, overflow ) { - // overflow tables need min & max width set as well - $el.css({ - 'width' : width, - 'min-width' : overflow ? width : '', - 'max-width' : overflow ? width : '' - }); - }, - - setWidths : function( c, wo, storedSizes ) { - var column, $temp, - vars = wo.resizable_, - $extra = $( c.namespace + '_extra_headers' ), - $col = c.$table.children( 'colgroup' ).children( 'col' ); - storedSizes = storedSizes || vars.storedSizes || []; - // process only if table ID or url match - if ( storedSizes.length ) { + wo.$resizable_container = $( '
' ) + .css({ top : marginTop }) + .insertBefore( $table ); + // add container for ( column = 0; column < c.columns; column++ ) { - // set saved resizable widths - ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); - if ( $extra.length ) { - // stickyHeaders needs to modify min & max width as well - $temp = $extra.eq( column ).add( $col.eq( column ) ); - ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + $header = c.$headerIndexed[ column ]; + tmp = ts.getColumnData( c.table, c.headers, column ); + noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; + if ( !noResize ) { + $( '
' ) + .appendTo( wo.$resizable_container ) + .attr({ + 'data-column' : column, + 'unselectable' : 'on' + }) + .data( 'header', $header ) + .bind( 'selectstart', false ); } } - $temp = $( c.namespace + '_extra_table' ); - if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { - ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); - } - } - }, - - setHandlePosition : function( c, wo ) { - var startPosition, - hasScroller = ts.hasWidget( c.table, 'scroller' ), - tableHeight = c.$table.height(), - $handles = wo.$resizable_container.children(), - handleCenter = Math.floor( $handles.width() / 2 ); - - if ( hasScroller ) { - tableHeight = 0; - c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ - var $this = $(this); - // center table has a max-height set - tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); + $table.one('tablesorter-initialized', function() { + ts.resizable.setHandlePosition( c, wo ); + ts.resizable.bindings( this.config, this.config.widgetOptions ); }); - } - // subtract out table left position from resizable handles. Fixes #864 - startPosition = c.$table.position().left; - $handles.each( function() { - var $this = $(this), - column = parseInt( $this.attr( 'data-column' ), 10 ), - columns = c.columns - 1, - $header = $this.data( 'header' ); - if ( !$header ) { return; } // see #859 - if ( !$header.is(':visible') ) { - $this.hide(); - } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { - $this.css({ - display: 'inline-block', - height : tableHeight, - left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }, + + updateStoredSizes : function( c, wo ) { + var column, $header, + len = c.columns, + vars = wo.resizable_vars; + vars.storedSizes = []; + for ( column = 0; column < len; column++ ) { + $header = c.$headerIndexed[ column ]; + vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; + } + }, + + setWidth : function( $el, width, overflow ) { + // overflow tables need min & max width set as well + $el.css({ + 'width' : width, + 'min-width' : overflow ? width : '', + 'max-width' : overflow ? width : '' + }); + }, + + setWidths : function( c, wo, storedSizes ) { + var column, $temp, + vars = wo.resizable_vars, + $extra = $( c.namespace + '_extra_headers' ), + $col = c.$table.children( 'colgroup' ).children( 'col' ); + storedSizes = storedSizes || vars.storedSizes || []; + // process only if table ID or url match + if ( storedSizes.length ) { + for ( column = 0; column < c.columns; column++ ) { + // set saved resizable widths + ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); + if ( $extra.length ) { + // stickyHeaders needs to modify min & max width as well + $temp = $extra.eq( column ).add( $col.eq( column ) ); + ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + } + } + $temp = $( c.namespace + '_extra_table' ); + if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { + ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); + } + } + }, + + setHandlePosition : function( c, wo ) { + var startPosition, + hasScroller = ts.hasWidget( c.table, 'scroller' ), + tableHeight = c.$table.height(), + $handles = wo.$resizable_container.children(), + handleCenter = Math.floor( $handles.width() / 2 ); + + if ( hasScroller ) { + tableHeight = 0; + c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ + var $this = $(this); + // center table has a max-height set + tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); }); } - }); - }, - - // prevent text selection while dragging resize bar - toggleTextSelection : function( c, toggle ) { - var namespace = c.namespace + 'tsresize'; - c.widgetOptions.resizable_.disabled = toggle; - $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); - if ( toggle ) { - $( 'body' ) - .attr( 'unselectable', 'on' ) - .bind( 'selectstart' + namespace, false ); - } else { - $( 'body' ) - .removeAttr( 'unselectable' ) - .unbind( 'selectstart' + namespace ); - } - }, - - bindings : function( c, wo ) { - var namespace = c.namespace + 'tsresize'; - wo.$resizable_container.children().bind( 'mousedown', function( event ) { - // save header cell and mouse position - var column, - vars = wo.resizable_, - $extras = $( c.namespace + '_extra_headers' ), - $header = $( event.target ).data( 'header' ); - - column = parseInt( $header.attr( 'data-column' ), 10 ); - vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); - vars.target = column; - - // if table is not as wide as it's parent, then resize the table - vars.$next = event.shiftKey || wo.resizable_targetLast ? - $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : - $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); - - column = parseInt( vars.$next.attr( 'data-column' ), 10 ); - vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); - vars.next = column; - - vars.mouseXPosition = event.pageX; - ts.resizable.updateStoredSizes( c, wo ); - ts.resizable.toggleTextSelection( c, true ); - }); - - $( document ) - .bind( 'mousemove' + namespace, function( event ) { - var vars = wo.resizable_; - // ignore mousemove if no mousedown - if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } - if ( wo.resizable_throttle ) { - clearTimeout( vars.timer ); - vars.timer = setTimeout( function() { - ts.resizable.mouseMove( c, wo, event ); - }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); - } else { - ts.resizable.mouseMove( c, wo, event ); + // subtract out table left position from resizable handles. Fixes #864 + startPosition = c.$table.position().left; + $handles.each( function() { + var $this = $(this), + column = parseInt( $this.attr( 'data-column' ), 10 ), + columns = c.columns - 1, + $header = $this.data( 'header' ); + if ( !$header ) { return; } // see #859 + if ( !$header.is(':visible') ) { + $this.hide(); + } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { + $this.css({ + display: 'inline-block', + height : tableHeight, + left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }); } - }) - .bind( 'mouseup' + namespace, function() { - if (!wo.resizable_.disabled) { return; } - ts.resizable.toggleTextSelection( c, false ); - ts.resizable.stopResize( c, wo ); - ts.resizable.setHandlePosition( c, wo ); }); + }, - // resizeEnd event triggered by scroller widget - $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }); - - // right click to reset columns to default widths - c.$table - .bind( 'columnUpdate' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }) - .find( 'thead:first' ) - .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) - .bind( 'contextmenu' + namespace, function() { - // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset - var allowClick = wo.resizable_.storedSizes.length === 0; - ts.resizableReset( c.table ); - ts.resizable.setHandlePosition( c, wo ); - wo.resizable_.storedSizes = []; - return allowClick; - }); - - }, - - mouseMove : function( c, wo, event ) { - if ( wo.resizable_.mouseXPosition === 0 || !wo.resizable_.$target ) { return; } - // resize columns - var column, - total = 0, - vars = wo.resizable_, - $next = vars.$next, - tar = vars.storedSizes[ vars.target ], - leftEdge = event.pageX - vars.mouseXPosition; - if ( vars.overflow ) { - if ( tar + leftEdge > 0 ) { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); - // update the entire table width - for ( column = 0; column < c.columns; column++ ) { - total += vars.storedSizes[ column ]; - } - ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); - } - if ( !$next.length ) { - // if expanding right-most column, scroll the wrapper - vars.$wrap[0].scrollLeft = c.$table.width(); - } - } else if ( vars.fullWidth ) { - vars.storedSizes[ vars.target ] += leftEdge; - vars.storedSizes[ vars.next ] -= leftEdge; - ts.resizable.setWidths( c, wo ); - } else { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidths( c, wo ); - } - vars.mouseXPosition = event.pageX; - // dynamically update sticky header widths - c.$table.trigger('stickyHeadersUpdate'); - }, - - stopResize : function( c, wo ) { - var vars = wo.resizable_; - ts.resizable.updateStoredSizes( c, wo ); - if ( vars.useStorage ) { - // save all column widths - ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); - ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); - } - vars.mouseXPosition = 0; - vars.$target = vars.$next = null; - // will update stickyHeaders, just in case, see #912 - c.$table.trigger('stickyHeadersUpdate'); - } -}; - -// this widget saves the column widths if -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: "resizable", - priority: 40, - options: { - resizable : true, // save column widths to storage - resizable_addLastColumn : false, - resizable_widths : [], - resizable_throttle : false, // set to true (5ms) or any number 0-10 range - resizable_targetLast : false, - resizable_fullWidth : null - }, - init: function(table, thisWidget, c, wo) { - ts.resizable.init( c, wo ); - }, - remove: function( table, c, wo, refreshing ) { - if (wo.$resizable_container) { + // prevent text selection while dragging resize bar + toggleTextSelection : function( c, toggle ) { var namespace = c.namespace + 'tsresize'; - c.$table.add( $( c.namespace + '_extra_table' ) ) - .removeClass('hasResizable') - .children( 'thead' ).unbind( 'contextmenu' + namespace ); + c.widgetOptions.resizable_vars.disabled = toggle; + $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); + if ( toggle ) { + $( 'body' ) + .attr( 'unselectable', 'on' ) + .bind( 'selectstart' + namespace, false ); + } else { + $( 'body' ) + .removeAttr( 'unselectable' ) + .unbind( 'selectstart' + namespace ); + } + }, + + bindings : function( c, wo ) { + var namespace = c.namespace + 'tsresize'; + wo.$resizable_container.children().bind( 'mousedown', function( event ) { + // save header cell and mouse position + var column, + vars = wo.resizable_vars, + $extras = $( c.namespace + '_extra_headers' ), + $header = $( event.target ).data( 'header' ); + + column = parseInt( $header.attr( 'data-column' ), 10 ); + vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); + vars.target = column; + + // if table is not as wide as it's parent, then resize the table + vars.$next = event.shiftKey || wo.resizable_targetLast ? + $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : + $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); + + column = parseInt( vars.$next.attr( 'data-column' ), 10 ); + vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); + vars.next = column; + + vars.mouseXPosition = event.pageX; + ts.resizable.updateStoredSizes( c, wo ); + ts.resizable.toggleTextSelection( c, true ); + }); + + $( document ) + .bind( 'mousemove' + namespace, function( event ) { + var vars = wo.resizable_vars; + // ignore mousemove if no mousedown + if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } + if ( wo.resizable_throttle ) { + clearTimeout( vars.timer ); + vars.timer = setTimeout( function() { + ts.resizable.mouseMove( c, wo, event ); + }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); + } else { + ts.resizable.mouseMove( c, wo, event ); + } + }) + .bind( 'mouseup' + namespace, function() { + if (!wo.resizable_vars.disabled) { return; } + ts.resizable.toggleTextSelection( c, false ); + ts.resizable.stopResize( c, wo ); + ts.resizable.setHandlePosition( c, wo ); + }); + + // resizeEnd event triggered by scroller widget + $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }); + + // right click to reset columns to default widths + c.$table + .bind( 'columnUpdate' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }) + .find( 'thead:first' ) + .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) + .bind( 'contextmenu' + namespace, function() { + // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset + var allowClick = wo.resizable_vars.storedSizes.length === 0; + ts.resizableReset( c.table ); + ts.resizable.setHandlePosition( c, wo ); + wo.resizable_vars.storedSizes = []; + return allowClick; + }); + + }, + + mouseMove : function( c, wo, event ) { + if ( wo.resizable_vars.mouseXPosition === 0 || !wo.resizable_vars.$target ) { return; } + // resize columns + var column, + total = 0, + vars = wo.resizable_vars, + $next = vars.$next, + tar = vars.storedSizes[ vars.target ], + leftEdge = event.pageX - vars.mouseXPosition; + if ( vars.overflow ) { + if ( tar + leftEdge > 0 ) { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); + // update the entire table width + for ( column = 0; column < c.columns; column++ ) { + total += vars.storedSizes[ column ]; + } + ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); + } + if ( !$next.length ) { + // if expanding right-most column, scroll the wrapper + vars.$wrap[0].scrollLeft = c.$table.width(); + } + } else if ( vars.fullWidth ) { + vars.storedSizes[ vars.target ] += leftEdge; + vars.storedSizes[ vars.next ] -= leftEdge; + ts.resizable.setWidths( c, wo ); + } else { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidths( c, wo ); + } + vars.mouseXPosition = event.pageX; + // dynamically update sticky header widths + c.$table.trigger('stickyHeadersUpdate'); + }, + + stopResize : function( c, wo ) { + var vars = wo.resizable_vars; + ts.resizable.updateStoredSizes( c, wo ); + if ( vars.useStorage ) { + // save all column widths + ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); + ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); + } + vars.mouseXPosition = 0; + vars.$target = vars.$next = null; + // will update stickyHeaders, just in case, see #912 + c.$table.trigger('stickyHeadersUpdate'); + } + }; + + // this widget saves the column widths if + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'resizable', + priority: 40, + options: { + resizable : true, // save column widths to storage + resizable_addLastColumn : false, + resizable_widths : [], + resizable_throttle : false, // set to true (5ms) or any number 0-10 range + resizable_targetLast : false, + resizable_fullWidth : null + }, + init: function(table, thisWidget, c, wo) { + ts.resizable.init( c, wo ); + }, + remove: function( table, c, wo, refreshing ) { + if (wo.$resizable_container) { + var namespace = c.namespace + 'tsresize'; + c.$table.add( $( c.namespace + '_extra_table' ) ) + .removeClass('hasResizable') + .children( 'thead' ) + .unbind( 'contextmenu' + namespace ); wo.$resizable_container.remove(); - ts.resizable.toggleTextSelection( c, false ); - ts.resizableReset( table, refreshing ); - $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); - } - } -}); - -ts.resizableReset = function( table, refreshing ) { - $( table ).each(function(){ - var index, $t, - c = this.config, - wo = c && c.widgetOptions, - vars = wo.resizable_; - if ( table && c && c.$headerIndexed.length ) { - // restore the initial table width - if ( vars.overflow && vars.tableWidth ) { - ts.resizable.setWidth( c.$table, vars.tableWidth, true ); - if ( vars.useStorage ) { - ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); - } - } - for ( index = 0; index < c.columns; index++ ) { - $t = c.$headerIndexed[ index ]; - if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { - ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); - } else if ( !$t.hasClass( 'resizable-false' ) ) { - // don't clear the width of any column that is not resizable - ts.resizable.setWidth( $t, '', vars.overflow ); - } - } - - // reset stickyHeader widths - c.$table.trigger( 'stickyHeadersUpdate' ); - if ( ts.storage && !refreshing ) { - ts.storage( this, ts.css.resizableStorage, {} ); + ts.resizable.toggleTextSelection( c, false ); + ts.resizableReset( table, refreshing ); + $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); } } }); -}; + + ts.resizableReset = function( table, refreshing ) { + $( table ).each(function(){ + var index, $t, + c = this.config, + wo = c && c.widgetOptions, + vars = wo.resizable_vars; + if ( table && c && c.$headerIndexed.length ) { + // restore the initial table width + if ( vars.overflow && vars.tableWidth ) { + ts.resizable.setWidth( c.$table, vars.tableWidth, true ); + if ( vars.useStorage ) { + ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); + } + } + for ( index = 0; index < c.columns; index++ ) { + $t = c.$headerIndexed[ index ]; + if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { + ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); + } else if ( !$t.hasClass( 'resizable-false' ) ) { + // don't clear the width of any column that is not resizable + ts.resizable.setWidth( $t, '', vars.overflow ); + } + } + + // reset stickyHeader widths + c.$table.trigger( 'stickyHeadersUpdate' ); + if ( ts.storage && !refreshing ) { + ts.storage( this, ts.css.resizableStorage, {} ); + } + } + }); + }; })( jQuery, window ); /*! Widget: saveSort */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -// this widget saves the last sort only if the -// saveSort widget option is true AND the -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: 'saveSort', - priority: 20, - options: { - saveSort : true - }, - init: function(table, thisWidget, c, wo) { - // run widget format before all other widgets are applied to the table - thisWidget.format(table, c, wo, true); - }, - format: function(table, c, wo, init) { - var stored, time, - $table = c.$table, - saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true - sortList = { "sortList" : c.sortList }; - if (c.debug) { - time = new Date(); - } - if ($table.hasClass('hasSaveSort')) { - if (saveSort && table.hasInitialized && ts.storage) { - ts.storage( table, 'tablesorter-savesort', sortList ); - if (c.debug) { - ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + // this widget saves the last sort only if the + // saveSort widget option is true AND the + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'saveSort', + priority: 20, + options: { + saveSort : true + }, + init: function(table, thisWidget, c, wo) { + // run widget format before all other widgets are applied to the table + thisWidget.format(table, c, wo, true); + }, + format: function(table, c, wo, init) { + var stored, time, + $table = c.$table, + saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true + sortList = { 'sortList' : c.sortList }; + if (c.debug) { + time = new Date(); + } + if ($table.hasClass('hasSaveSort')) { + if (saveSort && table.hasInitialized && ts.storage) { + ts.storage( table, 'tablesorter-savesort', sortList ); + if (c.debug) { + ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + } + } + } else { + // set table sort on initial run of the widget + $table.addClass('hasSaveSort'); + sortList = ''; + // get data + if (ts.storage) { + stored = ts.storage( table, 'tablesorter-savesort' ); + sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; + if (c.debug) { + ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); + } + $table.bind('saveSortReset', function(event) { + event.stopPropagation(); + ts.storage( table, 'tablesorter-savesort', '' ); + }); + } + // init is true when widget init is run, this will run this widget before all other widgets have initialized + // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. + if (init && sortList && sortList.length > 0) { + c.sortList = sortList; + } else if (table.hasInitialized && sortList && sortList.length > 0) { + // update sort change + $table.trigger('sorton', [ sortList ]); } } - } else { - // set table sort on initial run of the widget - $table.addClass('hasSaveSort'); - sortList = ''; - // get data - if (ts.storage) { - stored = ts.storage( table, 'tablesorter-savesort' ); - sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; - if (c.debug) { - ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); - } - $table.bind('saveSortReset', function(event) { - event.stopPropagation(); - ts.storage( table, 'tablesorter-savesort', '' ); - }); - } - // init is true when widget init is run, this will run this widget before all other widgets have initialized - // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. - if (init && sortList && sortList.length > 0) { - c.sortList = sortList; - } else if (table.hasInitialized && sortList && sortList.length > 0) { - // update sort change - $table.trigger('sorton', [sortList]); - } + }, + remove: function(table, c) { + c.$table.removeClass('hasSaveSort'); + // clear storage + if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } } - }, - remove: function(table, c) { - c.$table.removeClass('hasSaveSort'); - // clear storage - if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } - } -}); + }); })(jQuery); diff --git a/dist/js/jquery.tablesorter.combined.min.js b/dist/js/jquery.tablesorter.combined.min.js index 8f3ab8da..3860ee02 100644 --- a/dist/js/jquery.tablesorter.combined.min.js +++ b/dist/js/jquery.tablesorter.combined.min.js @@ -1,4 +1,4 @@ /*! tablesorter (FORK) - updated 07-22-2015 (v2.22.2)*/ -!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&"object"==typeof module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(a){"use strict";a.extend({tablesorter:new function(){function b(){var a=arguments[0],b=arguments.length>1?Array.prototype.slice.call(arguments):a;"undefined"!=typeof console&&"undefined"!=typeof console.log?console[/error/i.test(a)?"error":/warn/i.test(a)?"warn":"log"](b):alert(b)}function c(a,c){b(a+" ("+((new Date).getTime()-c.getTime())+"ms)")}function d(a){for(var b in a)return!1;return!0}function e(c,d,e,f){for(var g,h,i=u.parsers.length,j=!1,k="",l=!0;""===k&&l;)e++,d[e]?(j=d[e].cells[f],k=u.getElementText(c,j,f),h=a(j),c.debug&&b("Checking if value was empty on row "+e+", column: "+f+': "'+k+'"')):l=!1;for(;--i>=0;)if(g=u.parsers[i],g&&"text"!==g.id&&g.is&&g.is(k,c.table,j,h))return g;return u.getParserById("text")}function f(a,d){var f,g,h,i,j,k,l,m,n,o,p,q,r=a.table,s=0,t="";if(a.$tbodies=a.$table.children("tbody:not(."+a.cssInfoBlock+")"),p="undefined"==typeof d?a.$tbodies:d,q=p.length,0===q)return a.debug?b("Warning: *Empty table!* Not building a parser cache"):"";for(a.debug&&(o=new Date,b("Detecting parsers for each column")),g={extractors:[],parsers:[]};q>s;){if(f=p[s].rows,f.length)for(h=a.columns,i=0;h>i;i++)j=a.$headerIndexed[i],k=u.getColumnData(r,a.headers,i),n=u.getParserById(u.getData(j,k,"extractor")),m=u.getParserById(u.getData(j,k,"sorter")),l="false"===u.getData(j,k,"parser"),a.empties[i]=(u.getData(j,k,"empty")||a.emptyTo||(a.emptyToBottom?"bottom":"top")).toLowerCase(),a.strings[i]=(u.getData(j,k,"string")||a.stringTo||"max").toLowerCase(),l&&(m=u.getParserById("no-parser")),n||(n=!1),m||(m=e(a,f,-1,i)),a.debug&&(t+="column:"+i+"; extractor:"+n.id+"; parser:"+m.id+"; string:"+a.strings[i]+"; empty: "+a.empties[i]+"\n"),g.parsers[i]=m,g.extractors[i]=n;s+=g.parsers.length?q:1}a.debug&&(b(t?t:"No parsers detected"),c("Completed detecting parsers",o)),a.parsers=g.parsers,a.extractors=g.extractors}function g(d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t=d.config,v=t.parsers;if(t.$tbodies=t.$table.children("tbody:not(."+t.cssInfoBlock+")"),l="undefined"==typeof e?t.$tbodies:e,t.cache={},t.totalRows=0,!v)return t.debug?b("Warning: *Empty table!* Not building a cache"):"";for(t.debug&&(o=new Date),t.showProcessing&&u.isProcessing(d,!0),k=0;ki;++i)if(q={child:[],raw:[]},m=a(l[k].rows[i]),n=[],m.hasClass(t.cssChildRow)&&0!==i)for(g=f.normalized.length-1,r=f.normalized[g][t.columns],r.$row=r.$row.add(m),m.prev().hasClass(t.cssChildRow)||m.prev().addClass(u.css.cssHasChild),h=m.children("th, td"),g=r.child.length,r.child[g]=[],j=0;ji;i++)o.push(e[i][l.columns].$row),l.appender&&(!l.pager||l.pager.removeRows&&m.pager_removeRows||l.pager.ajax)||h.append(e[i][l.columns].$row);u.processTbody(a,h,!1)}l.appender&&l.appender(a,o),l.debug&&c("Rebuilt table",k),b||l.appender||u.applyWidget(a),a.isUpdating&&l.$table.trigger("updateComplete",a)}function i(a){return/^d/i.test(a)||1===a}function j(d){var e,f,g,h,j,k,m,n,o=d.config;for(o.headerList=[],o.headerContent=[],o.debug&&(m=new Date),o.columns=u.computeColumnIndex(o.$table.children("thead, tfoot").children("tr")),h=o.cssIcon?'':"",o.$headers=a(a.map(a(d).find(o.selectorHeaders),function(b,c){return f=a(b),f.parent().hasClass(o.cssIgnoreRow)?void 0:(e=u.getColumnData(d,o.headers,c,!0),o.headerContent[c]=f.html(),""===o.headerTemplate||f.find("."+u.css.headerIn).length||(j=o.headerTemplate.replace(/\{content\}/g,f.html()).replace(/\{icon\}/g,f.find("."+u.css.icon).length?"":h),o.onRenderTemplate&&(g=o.onRenderTemplate.apply(f,[c,j]),g&&"string"==typeof g&&(j=g)),f.html('
'+j+"
")),o.onRenderHeader&&o.onRenderHeader.apply(f,[c,o,o.$table]),b.column=parseInt(f.attr("data-column"),10),b.order=i(u.getData(f,e,"sortInitialOrder")||o.sortInitialOrder)?[1,0,2]:[0,1,2],b.count=-1,b.lockedOrder=!1,k=u.getData(f,e,"lockedOrder")||!1,"undefined"!=typeof k&&k!==!1&&(b.order=b.lockedOrder=i(k)?[1,1,1]:[0,0,0]),f.addClass(u.css.header+" "+o.cssHeader),o.headerList[c]=b,f.parent().addClass(u.css.headerRow+" "+o.cssHeaderRow).attr("role","row"),o.tabIndex&&f.attr("tabindex",0),b)})),o.$headerIndexed=[],n=0;nb;b++)d=f.$headers.eq(b),e=u.getColumnData(a,f.headers,b,!0),c="false"===u.getData(d,e,"sorter")||"false"===u.getData(d,e,"parser"),d[0].sortDisabled=c,d[c?"addClass":"removeClass"]("sorter-false").attr("aria-disabled",""+c),a.id&&(c?d.removeAttr("aria-controls"):d.attr("aria-controls",a.id))}function m(b){var c,d,e,f,g,h,i,j,k=b.config,l=k.sortList,m=l.length,n=u.css.sortNone+" "+k.cssNone,o=[u.css.sortAsc+" "+k.cssAsc,u.css.sortDesc+" "+k.cssDesc],p=[k.cssIconAsc,k.cssIconDesc,k.cssIconNone],q=["ascending","descending"],r=a(b).find("tfoot tr").children().add(a(k.namespace+"_extra_headers")).removeClass(o.join(" "));for(k.$headers.removeClass(o.join(" ")).addClass(n).attr("aria-sort","none").find("."+u.css.icon).removeClass(p.join(" ")).addClass(p[2]),e=0;m>e;e++)if(2!==l[e][1]&&(c=k.$headers.not(".sorter-false").filter('[data-column="'+l[e][0]+'"]'+(1===m?":last":"")),c.length)){for(f=0;fe;e++)h=g.eq(e),h.length&&(d=g[e],i=d.order[(d.count+1)%(k.sortReset?3:2)],j=a.trim(h.text())+": "+u.language[h.hasClass(u.css.sortAsc)?"sortAsc":h.hasClass(u.css.sortDesc)?"sortDesc":"sortNone"]+u.language[0===i?"nextAsc":1===i?"nextDesc":"nextNone"],h.attr("aria-label",j))}function n(b,c){var d,e,f,g,h,i,j,k,l=b.config,m=c||l.sortList,n=m.length;for(l.sortList=[],h=0;n>h;h++)if(k=m[h],d=parseInt(k[0],10),d=0?e:f[1]%(l.sortReset?3:2)}}function o(a,b){return a&&a[b]?a[b].type||"":""}function p(b,c,d){if(b.isUpdating)return setTimeout(function(){p(b,c,d)},50);var e,f,g,i,j,k,l,n=b.config,o=!d[n.sortMultiSortKey],r=n.$table,s=n.$headers.length;if(r.trigger("sortStart",b),c.count=d[n.sortResetKey]?2:(c.count+1)%(n.sortReset?3:2),n.sortRestart)for(f=c,g=0;s>g;g++)l=n.$headers.eq(g),l[0]===f||!o&&l.is("."+u.css.sortDesc+",."+u.css.sortAsc)||(l[0].count=-1);if(f=parseInt(a(c).attr("data-column"),10),o){if(n.sortList=[],null!==n.sortForce)for(e=n.sortForce,i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;i1)for(i=0;i=0&&n.sortList.splice(k,1);if(u.isValueInArray(f,n.sortList)>=0)for(i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;ie;e++)i=q.cache[e].colMax,j=q.cache[e].normalized,j.sort(function(c,d){for(b=0;t>b;b++){if(g=s[b][0],k=s[b][1],p=0===k,q.sortStable&&c[g]===d[g]&&1===t)return c[q.columns].order-d[q.columns].order;if(f=/n/i.test(o(q.parsers,g)),f&&q.strings[g]?(f="boolean"==typeof q.string[q.strings[g]]?(p?1:-1)*(q.string[q.strings[g]]?-1:1):q.strings[g]?q.string[q.strings[g]]||0:0,l=q.numberSorter?q.numberSorter(c[g],d[g],p,i[g],a):u["sortNumeric"+(p?"Asc":"Desc")](c[g],d[g],f,i[g],g,a)):(m=p?c:d,n=p?d:c,l="function"==typeof r?r(m[g],n[g],p,g,a):"object"==typeof r&&r.hasOwnProperty(g)?r[g](m[g],n[g],p,g,a):u["sortNatural"+(p?"Asc":"Desc")](c[g],d[g],g,a,q)),l)return l}return c[q.columns].order-d[q.columns].order});q.debug&&c("Sorting on "+s.toString()+" and dir "+k+" time",h)}}function r(b,c){b.table.isUpdating&&b.$table.trigger("updateComplete",b.table),a.isFunction(c)&&c(b.table)}function s(b,c,d){var e=a.isArray(c)?c:b.sortList,f="undefined"==typeof c?b.resort:c;f===!1||b.serverSideSorting||b.table.isProcessing?(r(b,d),u.applyWidget(b.table,!1)):e.length?b.$table.trigger("sorton",[e,function(){r(b,d)},!0]):b.$table.trigger("sortReset",[function(){r(b,d),u.applyWidget(b.table,!1)}])}function t(b){var c=b.config,e=c.$table,i="sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave ".split(" ").join(c.namespace+" ");e.unbind(i.replace(/\s+/g," ")).bind("sortReset"+c.namespace,function(d,e){d.stopPropagation(),c.sortList=[],m(b),q(b),h(b),a.isFunction(e)&&e(b)}).bind("updateAll"+c.namespace,function(a,d,e){a.stopPropagation(),b.isUpdating=!0,u.refreshWidgets(b,!0,!0),j(b),u.bindEvents(b,c.$headers,!0),t(b),k(b,d,e)}).bind("update"+c.namespace+" updateRows"+c.namespace,function(a,c,d){a.stopPropagation(),b.isUpdating=!0,l(b),k(b,c,d)}).bind("updateCell"+c.namespace,function(d,f,g,h){d.stopPropagation(),b.isUpdating=!0,e.find(c.selectorRemove).remove();var i,j,k,l,m=c.$tbodies,n=a(f),o=m.index(a.fn.closest?n.closest("tbody"):n.parents("tbody").filter(":first")),p=c.cache[o],q=a.fn.closest?n.closest("tr"):n.parents("tr").filter(":first");f=n[0],m.length&&o>=0&&(j=m.eq(o).find("tr").index(q),l=p.normalized[j],k=n.index(),i=u.getParsedText(c,f,k),l[k]=i,l[c.columns].$row=q,"numeric"===(c.parsers[k].type||"").toLowerCase()&&(p.colMax[k]=Math.max(Math.abs(i)||0,p.colMax[k]||0)),i="undefined"!==g?g:c.resort,i!==!1?s(c,i,h):(a.isFunction(h)&&h(b),c.$table.trigger("updateComplete",c.table)))}).bind("addRows"+c.namespace,function(e,g,h,i){if(e.stopPropagation(),b.isUpdating=!0,d(c.cache))l(b),k(b,h,i);else{g=a(g).attr("role","row");var j,m,n,o,p,q=g.filter("tr").length,r=c.$tbodies.index(g.parents("tbody").filter(":first"));for(c.parsers&&c.parsers.length||f(c),j=0;q>j;j++){for(n=g[j].cells.length,p=[],o={child:[],$row:g.eq(j),order:c.cache[r].normalized.length},m=0;n>m;m++)p[m]=u.getParsedText(c,g[j].cells[m],m),"numeric"===(c.parsers[m].type||"").toLowerCase()&&(c.cache[r].colMax[m]=Math.max(Math.abs(p[m])||0,c.cache[r].colMax[m]||0));p.push(o),c.cache[r].normalized.push(p)}s(c,h,i)}}).bind("updateComplete"+c.namespace,function(){b.isUpdating=!1}).bind("sorton"+c.namespace,function(c,f,i,j){var k=b.config;c.stopPropagation(),e.trigger("sortStart",this),n(b,f),m(b),k.delayInit&&d(k.cache)&&g(b),e.trigger("sortBegin",this),q(b),h(b,j),e.trigger("sortEnd",this),u.applyWidget(b),a.isFunction(i)&&i(b)}).bind("appendCache"+c.namespace,function(c,d,e){c.stopPropagation(),h(b,e),a.isFunction(d)&&d(b)}).bind("updateCache"+c.namespace,function(d,e,h){c.parsers&&c.parsers.length||f(c,h),g(b,h),a.isFunction(e)&&e(b)}).bind("applyWidgetId"+c.namespace,function(a,d){a.stopPropagation(),u.getWidgetById(d).format(b,c,c.widgetOptions)}).bind("applyWidgets"+c.namespace,function(a,c){a.stopPropagation(),u.applyWidget(b,c)}).bind("refreshWidgets"+c.namespace,function(a,c,d){a.stopPropagation(),u.refreshWidgets(b,c,d)}).bind("destroy"+c.namespace,function(a,c,d){a.stopPropagation(),u.destroy(b,c,d)}).bind("resetToLoadState"+c.namespace,function(){u.removeWidget(b,!0,!1),c=a.extend(!0,u.defaults,c.originalSettings),b.hasInitialized=!1,u.setup(b,c)})}var u=this;u.version="2.22.3",u.parsers=[],u.widgets=[],u.defaults={theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,tabIndex:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,resort:!0,headers:{},ignoreCase:!0,sortForce:null,sortList:[],sortAppend:null,sortStable:!1,sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",textExtraction:"basic",textAttribute:"data-text",textSorter:null,numberSorter:null,widgets:[],widgetOptions:{zebra:["even","odd"]},initWidgets:!0,widgetClass:"widget-{name}",initialized:null,tableClass:"",cssAsc:"",cssDesc:"",cssNone:"",cssHeader:"",cssHeaderRow:"",cssProcessing:"",cssChildRow:"tablesorter-childRow",cssIcon:"tablesorter-icon",cssIconNone:"",cssIconAsc:"",cssIconDesc:"",cssInfoBlock:"tablesorter-infoOnly",cssNoSort:"tablesorter-noSort",cssIgnoreRow:"tablesorter-ignoreRow",pointerClick:"click",pointerDown:"mousedown",pointerUp:"mouseup",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]},u.css={table:"tablesorter",cssHasChild:"tablesorter-hasChildRow",childRow:"tablesorter-childRow",colgroup:"tablesorter-colgroup",header:"tablesorter-header",headerRow:"tablesorter-headerRow",headerIn:"tablesorter-header-inner",icon:"tablesorter-icon",processing:"tablesorter-processing",sortAsc:"tablesorter-headerAsc",sortDesc:"tablesorter-headerDesc",sortNone:"tablesorter-headerUnSorted"},u.language={sortAsc:"Ascending sort applied, ",sortDesc:"Descending sort applied, ",sortNone:"No sort applied, ",nextAsc:"activate to apply an ascending sort",nextDesc:"activate to apply a descending sort",nextNone:"activate to remove the sort"},u.instanceMethods={},u.log=b,u.benchmark=c,u.getElementText=function(b,c,d){if(!c)return"";var e,f=b.textExtraction||"",g=c.jquery?c:a(c);return"string"==typeof f?"basic"===f&&"undefined"!=typeof(e=g.attr(b.textAttribute))?a.trim(e):a.trim(c.textContent||g.text()):"function"==typeof f?a.trim(f(g[0],b.table,d)):"function"==typeof(e=u.getColumnData(b.table,f,d))?a.trim(e(g[0],b.table,d)):a.trim(g[0].textContent||g.text())},u.getParsedText=function(a,b,c,d){"undefined"==typeof d&&(d=u.getElementText(a,b,c));var e=""+d,f=a.parsers[c],g=a.extractors[c];return f&&(g&&"function"==typeof g.format&&(d=g.format(d,a.table,b,c)),e="no-parser"===f.id?"":f.format(""+d,a.table,b,c),a.ignoreCase&&"string"==typeof e&&(e=e.toLowerCase())),e},u.construct=function(b){return this.each(function(){var c=this,d=a.extend(!0,{},u.defaults,b,u.instanceMethods);d.originalSettings=b,!c.hasInitialized&&u.buildTable&&"TABLE"!==this.nodeName?u.buildTable(c,d):u.setup(c,d)})},u.setup=function(c,d){if(!c||!c.tHead||0===c.tBodies.length||c.hasInitialized===!0)return d.debug?b("ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized"):"";var e="",h=a(c),i=a.metadata;c.hasInitialized=!1,c.isProcessing=!0,c.config=d,a.data(c,"tablesorter",d),d.debug&&a.data(c,"startoveralltimer",new Date),d.supportsDataObject=function(a){return a[0]=parseInt(a[0],10),a[0]>1||1===a[0]&&parseInt(a[1],10)>=4}(a.fn.jquery.split(".")),d.string={max:1,min:-1,emptymin:1,emptymax:-1,zero:0,none:0,"null":0,top:!0,bottom:!1},d.emptyTo=d.emptyTo.toLowerCase(),d.stringTo=d.stringTo.toLowerCase(),/tablesorter\-/.test(h.attr("class"))||(e=""!==d.theme?" tablesorter-"+d.theme:""),d.table=c,d.$table=h.addClass(u.css.table+" "+d.tableClass+e).attr("role","grid"),d.$headers=h.find(d.selectorHeaders),d.namespace?d.namespace="."+d.namespace.replace(/\W/g,""):d.namespace=".tablesorter"+Math.random().toString(16).slice(2),d.$table.children().children("tr").attr("role","row"),d.$tbodies=h.children("tbody:not(."+d.cssInfoBlock+")").attr({"aria-live":"polite","aria-relevant":"all"}),d.$table.children("caption").length&&(e=d.$table.children("caption")[0],e.id||(e.id=d.namespace.slice(1)+"caption"),d.$table.attr("aria-labelledby",e.id)),d.widgetInit={},d.textExtraction=d.$table.attr("data-text-extraction")||d.textExtraction||"basic",j(c),u.fixColumnWidth(c),u.applyWidgetOptions(c,d),f(d),d.totalRows=0,d.delayInit||g(c),u.bindEvents(c,d.$headers,!0),t(c),d.supportsDataObject&&"undefined"!=typeof h.data().sortlist?d.sortList=h.data().sortlist:i&&h.metadata()&&h.metadata().sortlist&&(d.sortList=h.metadata().sortlist),u.applyWidget(c,!0),d.sortList.length>0?h.trigger("sorton",[d.sortList,{},!d.initWidgets,!0]):(m(c),d.initWidgets&&u.applyWidget(c,!1)),d.showProcessing&&h.unbind("sortBegin"+d.namespace+" sortEnd"+d.namespace).bind("sortBegin"+d.namespace+" sortEnd"+d.namespace,function(a){clearTimeout(d.processTimer),u.isProcessing(c),"sortBegin"===a.type&&(d.processTimer=setTimeout(function(){u.isProcessing(c,!0)},500))}),c.hasInitialized=!0,c.isProcessing=!1,d.debug&&u.benchmark("Overall initialization time",a.data(c,"startoveralltimer")),h.trigger("tablesorter-initialized",c),"function"==typeof d.initialized&&d.initialized(c)},u.fixColumnWidth=function(b){b=a(b)[0];var c,d,e,f,g,h=b.config,i=h.$table.children("colgroup");if(i.length&&i.hasClass(u.css.colgroup)&&i.remove(),h.widthFixed&&0===h.$table.children("colgroup").length){for(i=a(''),c=h.$table.width(),e=h.$tbodies.find("tr:first").children(":visible"),f=e.length,g=0;f>g;g++)d=parseInt(e.eq(g).width()/c*1e3,10)/10+"%",i.append(a("").css("width",d));h.$table.prepend(i)}},u.getColumnData=function(b,c,d,e,f){if("undefined"!=typeof c&&null!==c){b=a(b)[0];var g,h,i=b.config,j=f||i.$headers,k=i.$headerIndexed&&i.$headerIndexed[d]||j.filter('[data-column="'+d+'"]:last');if(c[d])return e?c[d]:c[j.index(k)];for(h in c)if("string"==typeof h&&(g=k.filter(h).add(k.find(h)),g.length))return c[h]}},u.computeColumnIndex=function(b){var c,d,e,f,g,h,i,j,k,l,m,n,o=[],p=[],q={};for(c=0;ce;e++)for("undefined"==typeof o[e]&&(o[e]=[]),p=o[e],f=n;n+m>f;f++)p[f]="x"}return p.length},u.isProcessing=function(b,c,d){b=a(b);var e=b[0].config,f=d||b.find("."+u.css.header);c?("undefined"!=typeof d&&e.sortList.length>0&&(f=f.filter(function(){return this.sortDisabled?!1:u.isValueInArray(parseFloat(a(this).attr("data-column")),e.sortList)>=0})),b.add(f).addClass(u.css.processing+" "+e.cssProcessing)):b.add(f).removeClass(u.css.processing+" "+e.cssProcessing)},u.processTbody=function(b,c,d){b=a(b)[0];var e;return d?(b.isProcessing=!0,c.before(''),e=a.fn.detach?c.detach():c.remove()):(e=a(b).find("colgroup.tablesorter-savemyplace"),c.insertAfter(e),e.remove(),void(b.isProcessing=!1))},u.clearTableBody=function(b){a(b)[0].config.$tbodies.children().detach()},u.bindEvents=function(b,c,e){b=a(b)[0];var f,h=null,i=b.config;e!==!0&&(c.addClass(i.namespace.slice(1)+"_extra_headers"),f=a.fn.closest?c.closest("table")[0]:c.parents("table")[0],f&&"TABLE"===f.nodeName&&f!==b&&a(f).addClass(i.namespace.slice(1)+"_extra_table")),f=(i.pointerDown+" "+i.pointerUp+" "+i.pointerClick+" sort keyup ").replace(/\s+/g," ").split(" ").join(i.namespace+" "),c.find(i.selectorSort).add(c.filter(i.selectorSort)).unbind(f).bind(f,function(e,f){var j,k,l=a(e.target),m=" "+e.type+" ";if(!(1!==(e.which||e.button)&&!m.match(" "+i.pointerClick+" | sort | keyup ")||" keyup "===m&&13!==e.which||m.match(" "+i.pointerClick+" ")&&"undefined"!=typeof e.which||m.match(" "+i.pointerUp+" ")&&h!==e.target&&f!==!0)){if(m.match(" "+i.pointerDown+" "))return h=e.target,k=l.jquery.split("."),void("1"===k[0]&&k[1]<4&&e.preventDefault());if(h=null,/(input|select|button|textarea)/i.test(e.target.nodeName)||l.hasClass(i.cssNoSort)||l.parents("."+i.cssNoSort).length>0||l.parents("button").length>0)return!i.cancelSelection;i.delayInit&&d(i.cache)&&g(b),j=a.fn.closest?a(this).closest("th, td")[0]:/TH|TD/.test(this.nodeName)?this:a(this).parents("th, td")[0],j=i.$headers[c.index(j)],j.sortDisabled||p(b,j,e)}}),i.cancelSelection&&c.attr("unselectable","on").bind("selectstart",!1).css({"user-select":"none",MozUserSelect:"none"})},u.restoreHeaders=function(b){var c,d,e=a(b)[0].config,f=e.$table.find(e.selectorHeaders),g=f.length;for(c=0;g>c;c++)d=f.eq(c),d.find("."+u.css.headerIn).length&&d.html(e.headerContent[c])},u.destroy=function(b,c,d){if(b=a(b)[0],b.hasInitialized){u.removeWidget(b,!0,!1);var e,f=a(b),g=b.config,h=f.find("thead:first"),i=h.find("tr."+u.css.headerRow).removeClass(u.css.headerRow+" "+g.cssHeaderRow),j=f.find("tfoot:first > tr").children("th, td");c===!1&&a.inArray("uitheme",g.widgets)>=0&&(f.trigger("applyWidgetId",["uitheme"]),f.trigger("applyWidgetId",["zebra"])),h.find("tr").not(i).remove(),e="sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache "+"applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState ".split(" ").join(g.namespace+" "),f.removeData("tablesorter").unbind(e.replace(/\s+/g," ")),g.$headers.add(j).removeClass([u.css.header,g.cssHeader,g.cssAsc,g.cssDesc,u.css.sortAsc,u.css.sortDesc,u.css.sortNone].join(" ")).removeAttr("data-column").removeAttr("aria-label").attr("aria-disabled","true"),i.find(g.selectorSort).unbind("mousedown mouseup keypress ".split(" ").join(g.namespace+" ").replace(/\s+/g," ")),u.restoreHeaders(b),f.toggleClass(u.css.table+" "+g.tableClass+" tablesorter-"+g.theme,c===!1),b.hasInitialized=!1,delete b.config.cache,"function"==typeof d&&d(b)}},u.regex={chunk:/(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,chunks:/(^\\0|\\0$)/,hex:/^0x[0-9a-f]+$/i},u.sortNatural=function(a,b){if(a===b)return 0;var c,d,e,f,g,h,i,j,k=u.regex;if(k.hex.test(b)){if(d=parseInt(a.match(k.hex),16),f=parseInt(b.match(k.hex),16),f>d)return-1;if(d>f)return 1}for(c=a.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),e=b.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),j=Math.max(c.length,e.length),i=0;j>i;i++){if(g=isNaN(c[i])?c[i]||0:parseFloat(c[i])||0,h=isNaN(e[i])?e[i]||0:parseFloat(e[i])||0,isNaN(g)!==isNaN(h))return isNaN(g)?1:-1;if(typeof g!=typeof h&&(g+="",h+=""),h>g)return-1;if(g>h)return 1}return 0},u.sortNaturalAsc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:-f||-1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:f||1:u.sortNatural(a,b)},u.sortNaturalDesc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:f||1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:-f||-1:u.sortNatural(b,a)},u.sortText=function(a,b){return a>b?1:b>a?-1:0},u.getTextValue=function(a,b,c){if(c){var d,e=a?a.length:0,f=c+b;for(d=0;e>d;d++)f+=a.charCodeAt(d);return b*f}return 0},u.sortNumericAsc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:-h||-1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:h||1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),a-b)},u.sortNumericDesc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:h||1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:-h||-1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),b-a)},u.sortNumeric=function(a,b){return a-b},u.characterEquivalents={a:"áàâãäąå",A:"ÁÀÂÃÄĄÅ",c:"çćč",C:"ÇĆČ",e:"éèêëěę",E:"ÉÈÊËĚĘ",i:"íìİîïı",I:"ÍÌİÎÏ",o:"óòôõöō",O:"ÓÒÔÕÖŌ",ss:"ß",SS:"ẞ",u:"úùûüů",U:"ÚÙÛÜŮ"},u.replaceAccents=function(a){var b,c="[",d=u.characterEquivalents;if(!u.characterRegex){u.characterRegexArray={};for(b in d)"string"==typeof b&&(c+=d[b],u.characterRegexArray[b]=new RegExp("["+d[b]+"]","g"));u.characterRegex=new RegExp(c+"]")}if(u.characterRegex.test(a))for(b in d)"string"==typeof b&&(a=a.replace(u.characterRegexArray[b],b));return a},u.isValueInArray=function(a,b){var c,d=b.length;for(c=0;d>c;c++)if(b[c][0]===a)return c;return-1},u.addParser=function(a){var b,c=u.parsers.length,d=!0;for(b=0;c>b;b++)u.parsers[b].id.toLowerCase()===a.id.toLowerCase()&&(d=!1);d&&u.parsers.push(a)},u.addInstanceMethods=function(b){a.extend(u.instanceMethods,b)},u.getParserById=function(a){if("false"==a)return!1;var b,c=u.parsers.length;for(b=0;c>b;b++)if(u.parsers[b].id.toLowerCase()===a.toString().toLowerCase())return u.parsers[b];return!1},u.addWidget=function(a){u.widgets.push(a)},u.hasWidget=function(b,c){return b=a(b),b.length&&b[0].config&&b[0].config.widgetInit[c]||!1},u.getWidgetById=function(a){var b,c,d=u.widgets.length;for(b=0;d>b;b++)if(c=u.widgets[b],c&&c.hasOwnProperty("id")&&c.id.toLowerCase()===a.toLowerCase())return c},u.applyWidgetOptions=function(b,c){var d,e,f=c.widgets.length,g=c.widgetOptions;if(f)for(d=0;f>d;d++)e=u.getWidgetById(c.widgets[d]),e&&"options"in e&&(g=b.config.widgetOptions=a.extend(!0,{},e.options,g))},u.applyWidget=function(b,d,e){b=a(b)[0];var f,g,h,i,j,k,l,m=b.config,n=m.widgetOptions,o=" "+m.table.className+" ",p=[];if(d===!1||!b.hasInitialized||!b.isApplyingWidgets&&!b.isUpdating){if(m.debug&&(i=new Date),l=new RegExp("\\s"+m.widgetClass.replace(/\{name\}/i,"([\\w-]+)")+"\\s","g"),o.match(l)&&(k=o.match(l)))for(g=k.length,f=0;g>f;f++)m.widgets.push(k[f].replace(l,"$1"));if(m.widgets.length){for(b.isApplyingWidgets=!0,m.widgets=a.grep(m.widgets,function(b,c){return a.inArray(b,m.widgets)===c}),h=m.widgets||[],g=h.length,f=0;g>f;f++)l=u.getWidgetById(h[f]),l&&l.id&&(l.priority||(l.priority=10),p[f]=l);for(p.sort(function(a,b){return a.priorityf;f++)p[f]&&((d||!m.widgetInit[p[f].id])&&(m.widgetInit[p[f].id]=!0,b.hasInitialized&&u.applyWidgetOptions(b,m),"init"in p[f]&&(m.debug&&(j=new Date),p[f].init(b,p[f],m,n),m.debug&&u.benchmark("Initializing "+p[f].id+" widget",j))),!d&&"format"in p[f]&&(m.debug&&(j=new Date),p[f].format(b,m,n,!1),m.debug&&u.benchmark((d?"Initializing ":"Applying ")+p[f].id+" widget",j)));d||"function"!=typeof e||e(b)}setTimeout(function(){b.isApplyingWidgets=!1,a.data(b,"lastWidgetApplication",new Date)},0),m.debug&&(k=m.widgets.length,c("Completed "+(d===!0?"initializing ":"applying ")+k+" widget"+(1!==k?"s":""),i))}},u.removeWidget=function(c,d,e){c=a(c)[0];var f,g,h,i,j=c.config;if(d===!0)for(d=[],i=u.widgets.length,h=0;i>h;h++)g=u.widgets[h],g&&g.id&&d.push(g.id);else d=(a.isArray(d)?d.join(","):d||"").toLowerCase().split(/[\s,]+/);for(i=d.length,f=0;i>f;f++)g=u.getWidgetById(d[f]),h=a.inArray(d[f],j.widgets),g&&"remove"in g&&(j.debug&&h>=0&&b('Removing "'+d[f]+'" widget'),g.remove(c,j,j.widgetOptions,e),j.widgetInit[d[f]]=!1),h>=0&&e!==!0&&j.widgets.splice(h,1)},u.refreshWidgets=function(b,c,d){b=a(b)[0];var e,f=b.config,g=f.widgets,h=u.widgets,i=h.length,j=[],k=function(b){a(b).trigger("refreshComplete")};for(e=0;i>e;e++)h[e]&&h[e].id&&(c||a.inArray(h[e].id,g)<0)&&j.push(h[e].id);u.removeWidget(b,j.join(","),!0),d!==!0?(u.applyWidget(b,c||!1,k),c&&u.applyWidget(b,!1,k)):k(b)},u.getColumnText=function(b,c,e){b=a(b)[0];var f,g,h,i,j,k,l,m,n,o,p="function"==typeof e,q="all"===c,r={raw:[],parsed:[],$cell:[]},s=b.config;if(!d(s)){for(j=s.$tbodies.length,f=0;j>f;f++)for(h=s.cache[f].normalized,k=h.length,g=0;k>g;g++)o=!0,i=h[g],m=q?i.slice(0,s.columns):i[c],i=i[s.columns],l=q?i.raw:i.raw[c],n=q?i.$row.children():i.$row.children().eq(c),p&&(o=e({tbodyIndex:f,rowIndex:g,parsed:m,raw:l,$row:i.$row,$cell:n})),o!==!1&&(r.parsed.push(m),r.raw.push(l),r.$cell.push(n));return r}},u.getData=function(b,c,d){var e,f,g="",h=a(b);return h.length?(e=a.metadata?h.metadata():!1,f=" "+(h.attr("class")||""),"undefined"!=typeof h.data(d)||"undefined"!=typeof h.data(d.toLowerCase())?g+=h.data(d)||h.data(d.toLowerCase()):e&&"undefined"!=typeof e[d]?g+=e[d]:c&&"undefined"!=typeof c[d]?g+=c[d]:" "!==f&&f.match(" "+d+"-")&&(g=f.match(new RegExp("\\s"+d+"-([\\w-]+)"))[1]||""),a.trim(g)):""},u.formatFloat=function(b,c){if("string"!=typeof b||""===b)return b;var d,e=c&&c.config?c.config.usNumberFormat!==!1:"undefined"!=typeof c?c:!0;return b=e?b.replace(/,/g,""):b.replace(/[\s|\.]/g,"").replace(/,/g,"."),/^\s*\([.\d]+\)/.test(b)&&(b=b.replace(/^\s*\(([.\d]+)\)/,"-$1")),d=parseFloat(b),isNaN(d)?a.trim(b):d},u.isDigit=function(a){return isNaN(a)?/^[\-+(]?\d+[)]?$/.test(a.toString().replace(/[,.'"\s]/g,"")):""!==a}}});var b=a.tablesorter;a.fn.extend({tablesorter:b.construct}),b.addParser({id:"no-parser",is:function(){return!1},format:function(){return""},type:"text"}),b.addParser({id:"text",is:function(){return!0},format:function(c,d){var e=d.config;return c&&(c=a.trim(e.ignoreCase?c.toLocaleLowerCase():c),c=e.sortLocaleCompare?b.replaceAccents(c):c),c},type:"text"}),b.addParser({id:"digit",is:function(a){return b.isDigit(a)},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"currency",is:function(a){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test((a||"").replace(/[+\-,. ]/g,""))},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"url",is:function(a){return/^(https?|ftp|file):\/\//.test(a)},format:function(b){return b?a.trim(b.replace(/(https?|ftp|file):\/\//,"")):b},parsed:!0,type:"text"}),b.addParser({id:"isoDate",is:function(a){return/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(a)},format:function(a,b){var c=a?new Date(a.replace(/-/g,"/")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"percent",is:function(a){return/(\d\s*?%|%\s*?\d)/.test(a)&&a.length<15},format:function(a,c){return a?b.formatFloat(a.replace(/%/g,""),c):a},type:"numeric"}),b.addParser({id:"image",is:function(a,b,c,d){return d.find("img").length>0},format:function(b,c,d){return a(d).find("img").attr(c.config.imgAttr||"alt")||b},parsed:!0,type:"text"}),b.addParser({id:"usLongDate",is:function(a){ -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(a)||/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(a)},format:function(a,b){var c=a?new Date(a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"shortDate",is:function(a){return/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/.test((a||"").replace(/\s+/g," ").replace(/[\-.,]/g,"/"))},format:function(a,c,d,e){if(a){var f,g,h=c.config,i=h.$headerIndexed[e],j=i.length&&i[0].dateFormat||b.getData(i,b.getColumnData(c,h.headers,e),"dateFormat")||h.dateFormat;return g=a.replace(/\s+/g," ").replace(/[\-.,]/g,"/"),"mmddyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$1/$2"):"ddmmyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$2/$1"):"yyyymmdd"===j&&(g=g.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,"$1/$2/$3")),f=new Date(g),f instanceof Date&&isFinite(f)?f.getTime():a}return a},type:"numeric"}),b.addParser({id:"time",is:function(a){return/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(a)},format:function(a,b){var c=a?new Date("2000/01/01 "+a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"metadata",is:function(){return!1},format:function(b,c,d){var e=c.config,f=e.parserMetadataName?e.parserMetadataName:"sortValue";return a(d).metadata()[f]},type:"numeric"}),b.addWidget({id:"zebra",priority:90,format:function(b,c,d){var e,f,g,h,i,j,k,l,m=new RegExp(c.cssChildRow,"i"),n=c.$tbodies.add(a(c.namespace+"_extra_table").children("tbody:not(."+c.cssInfoBlock+")"));for(c.debug&&(i=new Date),j=0;jk;k++)f=e.eq(k),m.test(f[0].className)||g++,h=g%2===0,f.removeClass(d.zebra[h?1:0]).addClass(d.zebra[h?0:1])},remove:function(a,c,d,e){if(!e){var f,g,h=c.$tbodies,i=(d.zebra||["even","odd"]).join(" ");for(f=0;f
')}),d.cssIcon&&t.find("."+b.css.icon).removeClass(q?[n.icons,p].join(" "):"").addClass(v.icons||""),s.hasClass("hasFilters")&&s.children("thead").children("."+b.css.filterRow).removeClass(q?n.filterRow||"":"").addClass(v.filterRow||"")),f=0;f1)))for(m=1;q>m;m++)k.eq(p[m][0]).addClass(r[m]||r[s])}),b.processTbody(c,f,!1);if(i=e.columns_thead!==!1?["thead tr"]:[],e.columns_tfoot!==!1&&i.push("tfoot tr"),i.length&&(h=n.find(i.join(",")).children().removeClass(l),q))for(m=0;q>m;m++)h.filter('[data-column="'+p[m][0]+'"]').addClass(r[m]||r[s])},remove:function(c,d,e){var f,g,h=d.$tbodies,i=(e.columns||["primary","secondary","tertiary"]).join(" ");for(d.$headers.removeClass(i),d.$table.children("tfoot").children("tr").children("th, td").removeClass(i),f=0;f=]/g,query:"(q|query)"},types:{or:function(c,d,e){if(/\|/.test(d.iFilter)||b.filter.regex.orSplit.test(d.filter)){var f,g,h,i,j=a.extend({},d),k=d.index,l=d.parsed[k],m=d.filter.split(b.filter.regex.orSplit),n=d.iFilter.split(b.filter.regex.orSplit),o=m.length;for(f=0;o>f;f++)if(j.nestedFilters=!0,j.filter=""+(b.filter.parseFilter(c,m[f],k,l)||""),j.iFilter=""+(b.filter.parseFilter(c,n[f],k,l)||""),h="("+(b.filter.parseFilter(c,j.filter,k,l)||"")+")",i=new RegExp(d.isMatch?h:"^"+h+"$",c.widgetOptions.filter_ignoreCase?"i":""),g=i.test(j.exact)||b.filter.processTypes(c,j,e))return g;return g||!1}return null},and:function(c,d,e){if(b.filter.regex.andTest.test(d.filter)){var f,g,h,i,j,k=a.extend({},d),l=d.index,m=d.parsed[l],n=d.filter.split(b.filter.regex.andSplit),o=d.iFilter.split(b.filter.regex.andSplit),p=n.length;for(f=0;p>f;f++)k.nestedFilters=!0,k.filter=""+(b.filter.parseFilter(c,n[f],l,m)||""),k.iFilter=""+(b.filter.parseFilter(c,o[f],l,m)||""),i=("("+(b.filter.parseFilter(c,k.filter,l,m)||"")+")").replace(/\?/g,"\\S{1}").replace(/\*/g,"\\S*"),j=new RegExp(d.isMatch?i:"^"+i+"$",c.widgetOptions.filter_ignoreCase?"i":""),h=j.test(k.exact)||b.filter.processTypes(c,k,e),g=0===f?h:g&&h;return g||!1}return null},regex:function(a,c){if(b.filter.regex.regex.test(c.filter)){var d,e=c.filter_regexCache[c.index]||b.filter.regex.regex.exec(c.filter),f=e instanceof RegExp;try{f||(c.filter_regexCache[c.index]=e=new RegExp(e[1],e[2])),d=e.test(c.exact)}catch(g){d=!1}return d}return null},operators:function(c,d){if(/^[<>]=?/.test(d.iFilter)&&""!==d.iExact){var e,f,g,h=c.table,i=d.index,j=d.parsed[i],k=b.formatFloat(d.iFilter.replace(b.filter.regex.operators,""),h),l=c.parsers[i],m=k;return(j||"numeric"===l.type)&&(g=a.trim(""+d.iFilter.replace(b.filter.regex.operators,"")),f=b.filter.parseFilter(c,g,i,!0),k="number"!=typeof f||""===f||isNaN(f)?k:f),!j&&"numeric"!==l.type||isNaN(k)||"undefined"==typeof d.cache?(g=isNaN(d.iExact)?d.iExact.replace(b.filter.regex.nondigit,""):d.iExact,e=b.formatFloat(g,h)):e=d.cache,/>/.test(d.iFilter)?f=/>=/.test(d.iFilter)?e>=k:e>k:/=e:k>e),f||""!==m||(f=!0),f}return null},notMatch:function(c,d){if(/^\!/.test(d.iFilter)){var e,f=d.iFilter.replace("!",""),g=b.filter.parseFilter(c,f,d.index,d.parsed[d.index])||"";return b.filter.regex.exact.test(g)?(g=g.replace(b.filter.regex.exact,""),""===g?!0:a.trim(g)!==d.iExact):(e=d.iExact.search(a.trim(g)),""===g?!0:!(c.widgetOptions.filter_startsWith?0===e:e>=0))}return null},exact:function(c,d){if(b.filter.regex.exact.test(d.iFilter)){var e=d.iFilter.replace(b.filter.regex.exact,""),f=b.filter.parseFilter(c,e,d.index,d.parsed[d.index])||"";return d.anyMatch?a.inArray(f,d.rowArray)>=0:f==d.iExact}return null},range:function(a,c){if(b.filter.regex.toTest.test(c.iFilter)){var d,e,f,g,h=a.table,i=c.index,j=c.parsed[i],k=c.iFilter.split(b.filter.regex.toSplit);return e=k[0].replace(b.filter.regex.nondigit,"")||"",f=b.formatFloat(b.filter.parseFilter(a,e,i,j),h),e=k[1].replace(b.filter.regex.nondigit,"")||"",g=b.formatFloat(b.filter.parseFilter(a,e,i,j),h),(j||"numeric"===a.parsers[i].type)&&(d=a.parsers[i].format(""+k[0],h,a.$headers.eq(i),i),f=""===d||isNaN(d)?f:d,d=a.parsers[i].format(""+k[1],h,a.$headers.eq(i),i),g=""===d||isNaN(d)?g:d),!j&&"numeric"!==a.parsers[i].type||isNaN(f)||isNaN(g)?(e=isNaN(c.iExact)?c.iExact.replace(b.filter.regex.nondigit,""):c.iExact,d=b.formatFloat(e,h)):d=c.cache,f>g&&(e=f,f=g,g=e),d>=f&&g>=d||""===f||""===g}return null},wild:function(a,c){if(/[\?\*\|]/.test(c.iFilter)){var d=c.index,e=c.parsed[d],f=""+(b.filter.parseFilter(a,c.iFilter,d,e)||"");return!/\?\*/.test(f)&&c.nestedFilters&&(f=c.isMatch?f:"^("+f+")$"),new RegExp(f.replace(/\?/g,"\\S{1}").replace(/\*/g,"\\S*"),a.widgetOptions.filter_ignoreCase?"i":"").test(c.exact)}return null},fuzzy:function(a,c){if(/^~/.test(c.iFilter)){var d,e=0,f=c.iExact.length,g=c.iFilter.slice(1),h=b.filter.parseFilter(a,g,c.index,c.parsed[c.index])||"";for(d=0;f>d;d++)c.iExact[d]===h[e]&&(e+=1);return e===h.length?!0:!1}return null}},init:function(d,e,f){b.language=a.extend(!0,{},{to:"to",or:"or",and:"and"},b.language);var g,h,i,j,k,l,m,n,o,p=b.filter.regex;if(e.$table.addClass("hasFilters"),f.searchTimer=null,f.filter_initTimer=null,f.filter_formatterCount=0,f.filter_formatterInit=[],f.filter_anyColumnSelector='[data-column="all"],[data-column="any"]',f.filter_multipleColumnSelector='[data-column*="-"],[data-column*=","]',m="\\{"+b.filter.regex.query+"\\}",a.extend(p,{child:new RegExp(e.cssChildRow),filtered:new RegExp(f.filter_filteredRow),alreadyFiltered:new RegExp("(\\s+("+b.language.or+"|-|"+b.language.to+")\\s+)","i"),toTest:new RegExp("\\s+(-|"+b.language.to+")\\s+","i"),toSplit:new RegExp("(?:\\s+(?:-|"+b.language.to+")\\s+)","gi"),andTest:new RegExp("\\s+("+b.language.and+"|&&)\\s+","i"),andSplit:new RegExp("(?:\\s+(?:"+b.language.and+"|&&)\\s+)","gi"),orSplit:new RegExp("(?:\\s+(?:"+b.language.or+")\\s+|\\|)","gi"),iQuery:new RegExp(m,"i"),igQuery:new RegExp(m,"ig")}),m=e.$headers.filter(".filter-false, .parser-false").length,f.filter_columnFilters!==!1&&m!==e.$headers.length&&b.filter.buildRow(d,e,f),i="addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ".split(" ").join(e.namespace+"filter "),e.$table.bind(i,function(g,h){return m=f.filter_hideEmpty&&a.isEmptyObject(e.cache)&&!(e.delayInit&&"appendCache"===g.type),e.$table.find("."+c.filterRow).toggleClass(f.filter_filteredRow,m),/(search|filter)/.test(g.type)||(g.stopPropagation(),b.filter.buildDefault(d,!0)),"filterReset"===g.type?(e.$table.find("."+c.filter).add(f.filter_$externalFilters).val(""),b.filter.searching(d,[])):"filterEnd"===g.type?b.filter.buildDefault(d,!0):(h="search"===g.type?h:"updateComplete"===g.type?e.$table.data("lastSearch"):"",/(update|add)/.test(g.type)&&"updateComplete"!==g.type&&(e.lastCombinedFilter=null,e.lastSearch=[]),b.filter.searching(d,h,!0)),!1}),f.filter_reset&&(f.filter_reset instanceof a?f.filter_reset.click(function(){e.$table.trigger("filterReset")}):a(f.filter_reset).length&&a(document).undelegate(f.filter_reset,"click.tsfilter").delegate(f.filter_reset,"click.tsfilter",function(){e.$table.trigger("filterReset")})),f.filter_functions)for(k=0;k'+(j.data("placeholder")||j.attr("data-placeholder")||f.filter_placeholder.select||"")+"":"",m=h,i=h,h.indexOf(f.filter_selectSourceSeparator)>=0&&(m=h.split(f.filter_selectSourceSeparator),i=m[1],m=m[0]),g+="");e.$table.find("thead").find("select."+c.filter+'[data-column="'+k+'"]').append(g),i=f.filter_selectSource,n=a.isFunction(i)?!0:b.getColumnData(d,i,k),n&&b.filter.buildSelect(e.table,k,"",!0,j.hasClass(f.filter_onlyAvail))}b.filter.buildDefault(d,!0),b.filter.bindSearch(d,e.$table.find("."+c.filter),!0),f.filter_external&&b.filter.bindSearch(d,f.filter_external),f.filter_hideFilters&&b.filter.hideFilters(d,e),e.showProcessing&&(i="filterStart filterEnd ".split(" ").join(e.namespace+"filter "),e.$table.unbind(i.replace(/\s+/g," ")).bind(i,function(f,g){j=g?e.$table.find("."+c.header).filter("[data-column]").filter(function(){return""!==g[a(this).data("column")]}):"",b.isProcessing(d,"filterStart"===f.type,g?j:"")})),e.filteredRows=e.totalRows,i="tablesorter-initialized pagerBeforeInitialized ".split(" ").join(e.namespace+"filter "),e.$table.unbind(i.replace(/\s+/g," ")).bind(i,function(){var a=this.config.widgetOptions;l=b.filter.setDefaults(d,e,a)||[],l.length&&(e.delayInit&&""===l.join("")||b.setFilters(d,l,!0)),e.$table.trigger("filterFomatterUpdate"),setTimeout(function(){a.filter_initialized||b.filter.filterInitComplete(e)},100)}),e.pager&&e.pager.initialized&&!f.filter_initialized&&(e.$table.trigger("filterFomatterUpdate"),setTimeout(function(){b.filter.filterInitComplete(e)},100))},formatterUpdated:function(a,b){var c=a.closest("table")[0].config.widgetOptions;c.filter_initialized||(c.filter_formatterInit[b]=1)},filterInitComplete:function(c){var d,e,f=c.widgetOptions,g=0,h=function(){f.filter_initialized=!0,c.$table.trigger("filterInit",c),b.filter.findRows(c.table,c.$table.data("lastSearch")||[])};if(a.isEmptyObject(f.filter_formatter))h();else{for(e=f.filter_formatterInit.length,d=0;e>d;d++)1===f.filter_formatterInit[d]&&g++;clearTimeout(f.filter_initTimer),f.filter_initialized||g!==f.filter_formatterCount?f.filter_initialized||(f.filter_initTimer=setTimeout(function(){h()},500)):h()}},setDefaults:function(c,d,e){var f,g,h,i,j,k=b.getFilters(c)||[];if(e.filter_saveFilters&&b.storage&&(g=b.storage(c,"tablesorter-filters")||[],f=a.isArray(g),f&&""===g.join("")||!f||(k=g)),""===k.join(""))for(j=d.$headers.add(e.filter_$externalFilters).filter("["+e.filter_defaultAttrib+"]"),h=0;h<=d.columns;h++)i=h===d.columns?"all":h,k[h]=j.filter('[data-column="'+i+'"]').attr(e.filter_defaultAttrib)||k[h]||"";return d.$table.data("lastSearch",k),k},parseFilter:function(a,b,c,d){return d?a.parsers[c].format(b,a.table,[],c):b},buildRow:function(d,e,f){var g,h,i,j,k,l,m,n,o=f.filter_cellFilter,p=e.columns,q=a.isArray(o),r='';for(h=0;p>h;h++)r+="").appendTo(e.$table.children("thead").eq(0)).find("td"),h=0;p>h;h++)k=!1,i=e.$headerIndexed[h],m=b.getColumnData(d,f.filter_functions,h),j=f.filter_functions&&m&&"function"!=typeof m||i.hasClass("filter-select"),g=b.getColumnData(d,e.headers,h),k="false"===b.getData(i[0],g,"filter")||"false"===b.getData(i[0],g,"parser"),j?r=a("').appendTo(e.$filters.eq(h)),r&&(n=i.data("placeholder")||i.attr("data-placeholder")||f.filter_placeholder.search||"",r.attr("placeholder",n))),r&&(l=(a.isArray(f.filter_cssFilter)?"undefined"!=typeof f.filter_cssFilter[h]?f.filter_cssFilter[h]||"":"":f.filter_cssFilter)||"",r.addClass(c.filter+" "+l).attr("data-column",h),k&&(r.attr("placeholder","").addClass(c.filterDisabled)[0].disabled=!0))},bindSearch:function(c,d,e){if(c=a(c)[0],d=a(d),d.length){var f,g=c.config,h=g.widgetOptions,i=g.namespace+"filter",j=h.filter_$externalFilters;e!==!0&&(f=h.filter_anyColumnSelector+","+h.filter_multipleColumnSelector,h.filter_$anyMatch=d.filter(f),j&&j.length?h.filter_$externalFilters=h.filter_$externalFilters.add(d):h.filter_$externalFilters=d,b.setFilters(c,g.$table.data("lastSearch")||[],e===!1)),f="keypress keyup search change ".split(" ").join(i+" "),d.attr("data-lastSearchTime",(new Date).getTime()).unbind(f.replace(/\s+/g," ")).bind("keyup"+i,function(d){if(a(this).attr("data-lastSearchTime",(new Date).getTime()),27===d.which)this.value="";else{if(h.filter_liveSearch===!1)return;if(""!==this.value&&("number"==typeof h.filter_liveSearch&&this.value.length=37&&d.which<=40)))return}b.filter.searching(c,!0,!0)}).bind("search change keypress ".split(" ").join(i+" "),function(d){var e=a(this).data("column");(13===d.which||"search"===d.type||"change"===d.type&&this.value!==g.lastSearch[e])&&(d.preventDefault(),a(this).attr("data-lastSearchTime",(new Date).getTime()),b.filter.searching(c,!1,!0))})}},searching:function(a,c,d){var e=a.config.widgetOptions;clearTimeout(e.searchTimer),"undefined"==typeof c||c===!0?e.searchTimer=setTimeout(function(){b.filter.checkFilters(a,c,d)},e.filter_liveSearch?e.filter_searchDelay:10):b.filter.checkFilters(a,c,d)},checkFilters:function(d,e,f){var g=d.config,h=g.widgetOptions,i=a.isArray(e),j=i?e:b.getFilters(d,!0),k=(j||[]).join("");return a.isEmptyObject(g.cache)?void(g.delayInit&&g.pager&&g.pager.initialized&&g.$table.trigger("updateCache",[function(){b.filter.checkFilters(d,!1,f)}])):(i&&(b.setFilters(d,j,!1,f!==!0),h.filter_initialized||(g.lastCombinedFilter="")),h.filter_hideFilters&&g.$table.find("."+c.filterRow).trigger(""===k?"mouseleave":"mouseenter"),g.lastCombinedFilter!==k||e===!1?(e===!1&&(g.lastCombinedFilter=null,g.lastSearch=[]),h.filter_initialized&&g.$table.trigger("filterStart",[j]),g.showProcessing?void setTimeout(function(){return b.filter.findRows(d,j,k),!1},30):(b.filter.findRows(d,j,k),!1)):void 0)},hideFilters:function(d,e){var f;e.$table.find("."+c.filterRow).bind("mouseenter mouseleave",function(b){var d=b,g=a(this);clearTimeout(f),f=setTimeout(function(){/enter|over/.test(d.type)?g.removeClass(c.filterRowHide):a(document.activeElement).closest("tr")[0]!==g[0]&&""===e.lastCombinedFilter&&g.addClass(c.filterRowHide)},200)}).find("input, select").bind("focus blur",function(d){var g=d,h=a(this).closest("tr");clearTimeout(f),f=setTimeout(function(){clearTimeout(f),""===b.getFilters(e.$table).join("")&&h.toggleClass(c.filterRowHide,"focus"!==g.type)},200)})},defaultFilter:function(c,d){if(""===c)return c;var e=b.filter.regex.iQuery,f=d.match(b.filter.regex.igQuery).length,g=f>1?a.trim(c).split(/\s/):[a.trim(c)],h=g.length-1,i=0,j=d;for(1>h&&f>1&&(g[1]=g[0]);e.test(j);)j=j.replace(e,g[i++]||""),e.test(j)&&h>i&&""!==(g[i]||"")&&(j=d.replace(e,j));return j},getLatestSearch:function(b){return b?b.sort(function(b,c){return a(c).attr("data-lastSearchTime")-a(b).attr("data-lastSearchTime")}):b||a()},multipleColumns:function(c,d){var e,f,g,h,i,j,k,l,m,n=c.widgetOptions,o=n.filter_initialized||!d.filter(n.filter_anyColumnSelector).length,p=[],q=a.trim(b.filter.getLatestSearch(d).attr("data-column")||"");if(o&&/-/.test(q))for(f=q.match(/(\d+)\s*-\s*(\d+)/g),m=f.length,l=0;m>l;l++){for(g=f[l].split(/\s*-\s*/),h=parseInt(g[0],10)||0,i=parseInt(g[1],10)||c.columns-1,h>i&&(e=h,h=i,i=e),i>=c.columns&&(i=c.columns-1);i>=h;h++)p.push(h);q=q.replace(f[l],"")}if(o&&/,/.test(q))for(j=q.split(/\s*,\s*/),m=j.length,k=0;m>k;k++)""!==j[k]&&(l=parseInt(j[k],10),l-1?(d.parsed[e]?m=d.cacheArray[e]:(m=d.rawArray[e],m=a.trim(o.filter_ignoreCase?m.toLowerCase():m),c.sortLocaleCompare&&(m=b.replaceAccents(m))),m):void 0}).get(),d.filter=d.anyMatchFilter,d.iFilter=d.iAnyMatchFilter,d.exact=d.rowArray.join(" "),d.iExact=o.filter_ignoreCase?d.exact.toLowerCase():d.exact,d.cache=d.cacheArray.slice(0,-1).join(" "),e.excludeMatch=e.noAnyMatch,j=b.filter.processTypes(c,d,e),null!==j)p=j;else if(o.filter_startsWith)for(p=!1,f=c.columns;!p&&f>0;)f--,p=p||0===d.rowArray[f].indexOf(d.iFilter);else p=(d.iExact+d.childRowText).indexOf(d.iFilter)>=0;if(d.anyMatch=!1,d.filters.join("")===d.filter)return p}for(f=0;f=0:d.filter===d.exact:"function"==typeof k?j=k(d.exact,d.cache,d.filter,f,d.$row,c,d):"function"==typeof k[l||d.filter]&&(m=l||d.filter,j=k[m](d.exact,d.cache,d.filter,f,d.$row,c,d))),null===j?(j=b.filter.processTypes(c,d,e),null!==j?h=j:(m=(d.iExact+d.childRowText).indexOf(b.filter.parseFilter(c,d.iFilter,f,d.parsed[f])),h=!o.filter_startsWith&&m>=0||o.filter_startsWith&&0===m)):h=j,p=h?p:!1);return p},findRows:function(c,d,e){if(c.config.lastCombinedFilter!==e&&c.config.widgetOptions.filter_initialized){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B=a.extend([],d),C=b.filter.regex,D=c.config,E=D.widgetOptions,F={anyMatch:!1,filters:d,filter_regexCache:[]},G={noAnyMatch:["range","notMatch","operators"],functions:[],excludeFilter:[],defaultColFilter:[],defaultAnyFilter:b.getColumnData(c,E.filter_defaultFilter,D.columns,!0)||""};for(F.parsed=D.$headers.map(function(d){return D.parsers&&D.parsers[d]&&D.parsers[d].parsed||b.getData&&"parsed"===b.getData(D.$headerIndexed[d],b.getColumnData(c,D.headers,d),"filter")||a(this).hasClass("filter-parsed")}).get(),m=0;m1&&(z=parseInt(y[0],10)-1,z>=0&&zt;t++)s=d[t]||"",v||(t=m),v=!(!v||!p.length||0!==s.indexOf(p[t]||"")||C.alreadyFiltered.test(s)||/[=\"\|!]/.test(s)||/(>=?\s*-\d)/.test(s)||/(<=?\s*\d)/.test(s)||""!==s&&D.$filters&&D.$filters.eq(t).find("select").length&&!D.$headerIndexed[t].hasClass("filter-match"));for(u=i.not("."+E.filter_filteredRow).length,v&&0===u&&(v=!1),D.debug&&b.log("Filter: Searching through "+(v&&f>u?u:"all")+" rows"),F.anyMatchFlag&&(D.sortLocaleCompare&&(F.anyMatchFilter=b.replaceAccents(F.anyMatchFilter)),E.filter_defaultFilter&&C.iQuery.test(G.defaultAnyFilter)&&(F.anyMatchFilter=b.filter.defaultFilter(F.anyMatchFilter,G.defaultAnyFilter),v=!1),F.iAnyMatchFilter=E.filter_ignoreCase&&D.ignoreCase?F.anyMatchFilter.toLowerCase():F.anyMatchFilter),j=0;f>j;j++)if(A=i[j].className,n=j&&C.child.test(A),!(n||v&&C.filtered.test(A))){if(F.$row=i.eq(j),F.cacheArray=g[j],h=F.cacheArray[D.columns],F.rawArray=h.raw,F.childRowText="",!E.filter_childByColumn){for(A="",o=h.child,t=0;t")>=0)return p;a.isArray(p)?m=p:"object"===a.type(n)&&p&&(m=p(c,d,e))}if(m===!1&&(m=b.filter.getOptions(c,d,e)),m=a.grep(m,function(b,c){return a.inArray(b,m)===c}),j.$headerIndexed[d].hasClass("filter-select-nosort"))return m;for(i=m.length,h=0;i>h;h++)g=m[h],l.push({t:g,p:j.parsers&&j.parsers.length&&j.parsers[d].format(g,c,[],d)||g});for(f=j.textSorter||"",l.sort(function(e,g){var h=e.p.toString(),i=g.p.toString();return a.isFunction(f)?f(h,i,!0,d,c):"object"==typeof f&&f.hasOwnProperty(d)?f[d](h,i,!0,d,c):b.sortNatural?b.sortNatural(h,i):!0}),m=[],i=l.length,h=0;i>h;h++)m.push(l[h].t);return m},getOptions:function(b,c,d){b=a(b)[0];var e,f,g,h,i,j=b.config,k=j.widgetOptions,l=[];for(f=0;fe;e++)h=i.row?i.row[e]:i.normalized[e][j.columns].$row[0],d&&h.className.match(k.filter_filteredRow)||(k.filter_useParsedData||j.parsers[c].parsed||j.$headerIndexed[c].hasClass("filter-parsed")?l.push(""+i.normalized[e][c]):l.push(i.normalized[e][j.columns].raw[c])); -return l},buildSelect:function(d,e,f,g,h){if(d=a(d)[0],e=parseInt(e,10),d.config.cache&&!a.isEmptyObject(d.config.cache)){var i,j,k,l,m,n,o=d.config,p=o.widgetOptions,q=o.$headerIndexed[e],r='",s=o.$table.find("thead").find("select."+c.filter+'[data-column="'+e+'"]').val();if(("undefined"==typeof f||""===f)&&(f=b.filter.getOptionSource(d,e,h)),a.isArray(f)){for(i=0;i=0&&(l=k.split(p.filter_selectSourceSeparator),j=l[0],k=l[1]),r+=""!==f[i]?"":"";f=[]}m=(o.$filters?o.$filters:o.$table.children("thead")).find("."+c.filter),p.filter_$externalFilters&&(m=m&&m.length?m.add(p.filter_$externalFilters):p.filter_$externalFilters),n=m.filter('select[data-column="'+e+'"]'),n.length&&(n[g?"html":"append"](r),a.isArray(f)||n.append(f).val(s),n.val(s))}},buildDefault:function(a,c){var d,e,f,g=a.config,h=g.widgetOptions,i=g.columns;for(d=0;i>d;d++)e=g.$headerIndexed[d],f=!(e.hasClass("filter-false")||e.hasClass("parser-false")),(e.hasClass("filter-select")||b.getColumnData(a,h.filter_functions,d)===!0)&&f&&b.filter.buildSelect(a,d,"",c,e.hasClass(h.filter_onlyAvail))}},b.getFilters=function(d,e,f,g){var h,i,j,k,l=!1,m=d?a(d)[0].config:"",n=m?m.widgetOptions:"";if(e!==!0&&n&&!n.filter_columnFilters||a.isArray(f)&&f.join("")===m.lastCombinedFilter)return a(d).data("lastSearch");if(m&&(m.$filters&&(i=m.$filters.find("."+c.filter)),n.filter_$externalFilters&&(i=i&&i.length?i.add(n.filter_$externalFilters):n.filter_$externalFilters),i&&i.length))for(l=f||[],h=0;h1&&(j=j.slice(1)),h===m.columns&&(k=j.filter(n.filter_anyColumnSelector),j=k.length?k:j),j.val(f[h]).trigger("change.tsfilter")):(l[h]=j.val()||"",h===m.columns?j.slice(1).filter('[data-column*="'+j.attr("data-column")+'"]').val(l[h]):j.slice(1).val(l[h])),h===m.columns&&j.length&&(n.filter_$anyMatch=j));return 0===l.length&&(l=!1),l},b.setFilters=function(c,d,e,f){var g=c?a(c)[0].config:"",h=b.getFilters(c,!0,d,f);return g&&e&&(g.lastCombinedFilter=null,g.lastSearch=[],b.filter.searching(g.table,d,f),g.$table.trigger("filterFomatterUpdate")),!!h}}(jQuery),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{sticky:"tablesorter-stickyHeader",stickyVis:"tablesorter-sticky-visible",stickyHide:"tablesorter-sticky-hidden",stickyWrap:"tablesorter-sticky-wrapper"}),c.addHeaderResizeEvent=function(b,c,d){if(b=a(b)[0],b.config){var e={timer:250},f=a.extend({},e,d),g=b.config,h=g.widgetOptions,i=function(a){var b,c,d,e,f,i,j=g.$headers.length;for(h.resize_flag=!0,c=[],b=0;j>b;b++)d=g.$headers.eq(b),e=d.data("savedSizes")||[0,0],f=d[0].offsetWidth,i=d[0].offsetHeight,(f!==e[0]||i!==e[1])&&(d.data("savedSizes",[f,i]),c.push(d[0]));c.length&&a!==!1&&g.$table.trigger("resize",[c]),h.resize_flag=!1};return i(!1),clearInterval(h.resize_timer),c?(h.resize_flag=!1,!1):void(h.resize_timer=setInterval(function(){h.resize_flag||i()},f.timer))}},c.addWidget({id:"stickyHeaders",priority:60,options:{stickyHeaders:"",stickyHeaders_attachTo:null,stickyHeaders_xScroll:null,stickyHeaders_yScroll:null,stickyHeaders_offset:0,stickyHeaders_filteredToTop:!0,stickyHeaders_cloneId:"-sticky",stickyHeaders_addResizeEvent:!0,stickyHeaders_includeCaption:!0,stickyHeaders_zIndex:2},format:function(d,e,f){if(!(e.$table.hasClass("hasStickyHeaders")||a.inArray("filter",e.widgets)>=0&&!e.$table.hasClass("hasFilters"))){var g,h,i,j,k=e.$table,l=a(f.stickyHeaders_attachTo),m=e.namespace+"stickyheaders ",n=a(f.stickyHeaders_yScroll||f.stickyHeaders_attachTo||b),o=a(f.stickyHeaders_xScroll||f.stickyHeaders_attachTo||b),p=k.children("thead:first"),q=p.children("tr").not(".sticky-false").children(),r=k.children("tfoot"),s=isNaN(f.stickyHeaders_offset)?a(f.stickyHeaders_offset):"",t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,u=k.parent().closest("."+c.css.table).hasClass("hasStickyHeaders")?k.parent().closest("table.tablesorter")[0].config.widgetOptions.$sticky.parent():[],v=u.length?u.height():0,w=f.$sticky=k.clone().addClass("containsStickyHeaders "+c.css.sticky+" "+f.stickyHeaders+" "+e.namespace.slice(1)+"_extra_table").wrap('
'),x=w.parent().addClass(c.css.stickyHide).css({position:l.length?"absolute":"fixed",padding:parseInt(w.parent().parent().css("padding-left"),10),top:t+v,left:0,visibility:"hidden",zIndex:f.stickyHeaders_zIndex||2}),y=w.children("thead:first"),z="",A=0,B=function(a,c){var d,e,f,g,h,i=a.filter(":visible"),j=i.length;for(d=0;j>d;d++)g=c.filter(":visible").eq(d),h=i.eq(d),"border-box"===h.css("box-sizing")?e=h.outerWidth():"collapse"===g.css("border-collapse")?b.getComputedStyle?e=parseFloat(b.getComputedStyle(h[0],null).width):(f=parseFloat(h.css("border-width")),e=h.outerWidth()-parseFloat(h.css("padding-left"))-parseFloat(h.css("padding-right"))-f):e=h.width(),g.css({width:e,"min-width":e,"max-width":e})},C=function(){t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,A=0,x.css({left:l.length?parseInt(l.css("padding-left"),10)||0:k.offset().left-parseInt(k.css("margin-left"),10)-o.scrollLeft()-A,width:k.outerWidth()}),B(k,w),B(q,j)},D=function(b){if(k.is(":visible")){v=u.length?u.offset().top-n.scrollTop()+u.height():0;var d=k.offset(),e=a.isWindow(n[0]),f=a.isWindow(o[0]),g=(l.length?e?n.scrollTop():n.offset().top:n.scrollTop())+t+v,h=k.height()-(x.height()+(r.height()||0)),i=g>d.top&&gg;g++)e.onRenderHeader.apply(i.eq(g),[g,e,w]);o.add(n).unbind("scroll resize ".split(" ").join(m).replace(/\s+/g," ")).bind("scroll resize ".split(" ").join(m),function(a){D("resize"===a.type)}),e.$table.unbind("stickyHeadersUpdate"+m).bind("stickyHeadersUpdate"+m,function(){D(!0)}),f.stickyHeaders_addResizeEvent&&c.addHeaderResizeEvent(d),k.hasClass("hasFilters")&&f.filter_columnFilters&&(k.bind("filterEnd"+m,function(){var d=a(document.activeElement).closest("td"),g=d.parent().children().index(d);x.hasClass(c.css.stickyVis)&&f.stickyHeaders_filteredToTop&&(b.scrollTo(0,k.position().top),g>=0&&e.$filters&&e.$filters.eq(g).find("a, select, input").filter(":visible").focus())}),c.filter.bindSearch(k,j.find("."+c.css.filter)),f.filter_hideFilters&&c.filter.hideFilters(w,e)),k.trigger("stickyHeadersInit")}},remove:function(d,e,f){var g=e.namespace+"stickyheaders ";e.$table.removeClass("hasStickyHeaders").unbind("pagerComplete filterEnd stickyHeadersUpdate ".split(" ").join(g).replace(/\s+/g," ")).next("."+c.css.stickyWrap).remove(),f.$sticky&&f.$sticky.length&&f.$sticky.remove(),a(b).add(f.stickyHeaders_xScroll).add(f.stickyHeaders_yScroll).add(f.stickyHeaders_attachTo).unbind("scroll resize ".split(" ").join(g).replace(/\s+/g," ")),c.addHeaderResizeEvent(d,!1)}})}(jQuery,window),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{resizableContainer:"tablesorter-resizable-container",resizableHandle:"tablesorter-resizable-handle",resizableNoSelect:"tablesorter-disableSelection",resizableStorage:"tablesorter-resizable"}),a(function(){var b="";a(b).appendTo("body")}),c.resizable={init:function(b,d){if(!b.$table.hasClass("hasResizable")){b.$table.addClass("hasResizable");var e,f,g,h,i,j=b.$table,k=j.parent(),l=parseInt(j.css("margin-top"),10),m=d.resizable_={useStorage:c.storage&&d.resizable!==!1,$wrap:k,mouseXPosition:0,$target:null,$next:null,overflow:"auto"===k.css("overflow")||"scroll"===k.css("overflow")||"auto"===k.css("overflow-x")||"scroll"===k.css("overflow-x"),storedSizes:[]};for(c.resizableReset(b.table,!0),m.tableWidth=j.width(),m.fullWidth=Math.abs(k.width()-m.tableWidth)<20,m.useStorage&&m.overflow&&(c.storage(b.table,"tablesorter-table-original-css-width",m.tableWidth),i=c.storage(b.table,"tablesorter-table-resized-width")||"auto",c.resizable.setWidth(j,i,!0)),d.resizable_.storedSizes=h=(m.useStorage?c.storage(b.table,c.css.resizableStorage):[])||[],c.resizable.setWidths(b,d,h),c.resizable.updateStoredSizes(b,d),d.$resizable_container=a('
').css({top:l}).insertBefore(j),g=0;g').appendTo(d.$resizable_container).attr({"data-column":g,unselectable:"on"}).data("header",f).bind("selectstart",!1);j.one("tablesorter-initialized",function(){c.resizable.setHandlePosition(b,d),c.resizable.bindings(this.config,this.config.widgetOptions)})}},updateStoredSizes:function(a,b){var c,d,e=a.columns,f=b.resizable_;for(f.storedSizes=[],c=0;e>c;c++)d=a.$headerIndexed[c],f.storedSizes[c]=d.is(":visible")?d.width():0},setWidth:function(a,b,c){a.css({width:b,"min-width":c?b:"","max-width":c?b:""})},setWidths:function(b,d,e){var f,g,h=d.resizable_,i=a(b.namespace+"_extra_headers"),j=b.$table.children("colgroup").children("col");if(e=e||h.storedSizes||[],e.length){for(f=0;ff||f===h&&d.resizable_addLastColumn)&&c.css({display:"inline-block",height:g,left:j.position().left-e+j.outerWidth()-i}):c.hide())})},toggleTextSelection:function(b,d){var e=b.namespace+"tsresize";b.widgetOptions.resizable_.disabled=d,a("body").toggleClass(c.css.resizableNoSelect,d),d?a("body").attr("unselectable","on").bind("selectstart"+e,!1):a("body").removeAttr("unselectable").unbind("selectstart"+e)},bindings:function(d,e){var f=d.namespace+"tsresize";e.$resizable_container.children().bind("mousedown",function(b){var f,g=e.resizable_,h=a(d.namespace+"_extra_headers"),i=a(b.target).data("header");f=parseInt(i.attr("data-column"),10),g.$target=i=i.add(h.filter('[data-column="'+f+'"]')),g.target=f,g.$next=b.shiftKey||e.resizable_targetLast?i.parent().children().not(".resizable-false").filter(":last"):i.nextAll(":not(.resizable-false)").eq(0),f=parseInt(g.$next.attr("data-column"),10),g.$next=g.$next.add(h.filter('[data-column="'+f+'"]')),g.next=f,g.mouseXPosition=b.pageX,c.resizable.updateStoredSizes(d,e),c.resizable.toggleTextSelection(d,!0)}),a(document).bind("mousemove"+f,function(a){var b=e.resizable_;b.disabled&&0!==b.mouseXPosition&&b.$target&&(e.resizable_throttle?(clearTimeout(b.timer),b.timer=setTimeout(function(){c.resizable.mouseMove(d,e,a)},isNaN(e.resizable_throttle)?5:e.resizable_throttle)):c.resizable.mouseMove(d,e,a))}).bind("mouseup"+f,function(){e.resizable_.disabled&&(c.resizable.toggleTextSelection(d,!1),c.resizable.stopResize(d,e),c.resizable.setHandlePosition(d,e))}),a(b).bind("resize"+f+" resizeEnd"+f,function(){c.resizable.setHandlePosition(d,e)}),d.$table.bind("columnUpdate"+f,function(){c.resizable.setHandlePosition(d,e)}).find("thead:first").add(a(d.namespace+"_extra_table").find("thead:first")).bind("contextmenu"+f,function(){var a=0===e.resizable_.storedSizes.length;return c.resizableReset(d.table),c.resizable.setHandlePosition(d,e),e.resizable_.storedSizes=[],a})},mouseMove:function(b,d,e){if(0!==d.resizable_.mouseXPosition&&d.resizable_.$target){var f,g=0,h=d.resizable_,i=h.$next,j=h.storedSizes[h.target],k=e.pageX-h.mouseXPosition;if(h.overflow){if(j+k>0){for(h.storedSizes[h.target]+=k,c.resizable.setWidth(h.$target,h.storedSizes[h.target],!0),f=0;f0?d.sortList=k:c.hasInitialized&&k&&k.length>0&&i.trigger("sorton",[k]))},remove:function(a,c){c.$table.removeClass("hasSaveSort"),b.storage&&b.storage(a,"tablesorter-savesort","")}})}(jQuery),a.tablesorter}); \ No newline at end of file +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&"object"==typeof module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(a){"use strict";a.extend({tablesorter:new function(){function b(){var a=arguments[0],b=arguments.length>1?Array.prototype.slice.call(arguments):a;"undefined"!=typeof console&&"undefined"!=typeof console.log?console[/error/i.test(a)?"error":/warn/i.test(a)?"warn":"log"](b):alert(b)}function c(a,c){b(a+" ("+((new Date).getTime()-c.getTime())+"ms)")}function d(a){for(var b in a)return!1;return!0}function e(c,d,e,f){for(var g,h,i=u.parsers.length,j=!1,k="",l=!0;""===k&&l;)e++,d[e]?(j=d[e].cells[f],k=u.getElementText(c,j,f),h=a(j),c.debug&&b("Checking if value was empty on row "+e+", column: "+f+': "'+k+'"')):l=!1;for(;--i>=0;)if(g=u.parsers[i],g&&"text"!==g.id&&g.is&&g.is(k,c.table,j,h))return g;return u.getParserById("text")}function f(a,d){var f,g,h,i,j,k,l,m,n,o,p,q,r=a.table,s=0,t="";if(a.$tbodies=a.$table.children("tbody:not(."+a.cssInfoBlock+")"),p="undefined"==typeof d?a.$tbodies:d,q=p.length,0===q)return a.debug?b("Warning: *Empty table!* Not building a parser cache"):"";for(a.debug&&(o=new Date,b("Detecting parsers for each column")),g={extractors:[],parsers:[]};q>s;){if(f=p[s].rows,f.length)for(h=a.columns,i=0;h>i;i++)j=a.$headerIndexed[i],k=u.getColumnData(r,a.headers,i),n=u.getParserById(u.getData(j,k,"extractor")),m=u.getParserById(u.getData(j,k,"sorter")),l="false"===u.getData(j,k,"parser"),a.empties[i]=(u.getData(j,k,"empty")||a.emptyTo||(a.emptyToBottom?"bottom":"top")).toLowerCase(),a.strings[i]=(u.getData(j,k,"string")||a.stringTo||"max").toLowerCase(),l&&(m=u.getParserById("no-parser")),n||(n=!1),m||(m=e(a,f,-1,i)),a.debug&&(t+="column:"+i+"; extractor:"+n.id+"; parser:"+m.id+"; string:"+a.strings[i]+"; empty: "+a.empties[i]+"\n"),g.parsers[i]=m,g.extractors[i]=n;s+=g.parsers.length?q:1}a.debug&&(b(t?t:"No parsers detected"),c("Completed detecting parsers",o)),a.parsers=g.parsers,a.extractors=g.extractors}function g(d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t=d.config,v=t.parsers;if(t.$tbodies=t.$table.children("tbody:not(."+t.cssInfoBlock+")"),l="undefined"==typeof e?t.$tbodies:e,t.cache={},t.totalRows=0,!v)return t.debug?b("Warning: *Empty table!* Not building a cache"):"";for(t.debug&&(o=new Date),t.showProcessing&&u.isProcessing(d,!0),k=0;ki;++i)if(q={child:[],raw:[]},m=a(l[k].rows[i]),n=[],m.hasClass(t.cssChildRow)&&0!==i)for(g=f.normalized.length-1,r=f.normalized[g][t.columns],r.$row=r.$row.add(m),m.prev().hasClass(t.cssChildRow)||m.prev().addClass(u.css.cssHasChild),h=m.children("th, td"),g=r.child.length,r.child[g]=[],j=0;ji;i++)o.push(e[i][l.columns].$row),l.appender&&(!l.pager||l.pager.removeRows&&m.pager_removeRows||l.pager.ajax)||h.append(e[i][l.columns].$row);u.processTbody(a,h,!1)}l.appender&&l.appender(a,o),l.debug&&c("Rebuilt table",k),b||l.appender||u.applyWidget(a),a.isUpdating&&l.$table.trigger("updateComplete",a)}function i(a){return/^d/i.test(a)||1===a}function j(d){var e,f,g,h,j,k,m,n,o=d.config;for(o.headerList=[],o.headerContent=[],o.debug&&(m=new Date),o.columns=u.computeColumnIndex(o.$table.children("thead, tfoot").children("tr")),h=o.cssIcon?'':"",o.$headers=a(a.map(a(d).find(o.selectorHeaders),function(b,c){return f=a(b),f.parent().hasClass(o.cssIgnoreRow)?void 0:(e=u.getColumnData(d,o.headers,c,!0),o.headerContent[c]=f.html(),""===o.headerTemplate||f.find("."+u.css.headerIn).length||(j=o.headerTemplate.replace(/\{content\}/g,f.html()).replace(/\{icon\}/g,f.find("."+u.css.icon).length?"":h),o.onRenderTemplate&&(g=o.onRenderTemplate.apply(f,[c,j]),g&&"string"==typeof g&&(j=g)),f.html('
'+j+"
")),o.onRenderHeader&&o.onRenderHeader.apply(f,[c,o,o.$table]),b.column=parseInt(f.attr("data-column"),10),b.order=i(u.getData(f,e,"sortInitialOrder")||o.sortInitialOrder)?[1,0,2]:[0,1,2],b.count=-1,b.lockedOrder=!1,k=u.getData(f,e,"lockedOrder")||!1,"undefined"!=typeof k&&k!==!1&&(b.order=b.lockedOrder=i(k)?[1,1,1]:[0,0,0]),f.addClass(u.css.header+" "+o.cssHeader),o.headerList[c]=b,f.parent().addClass(u.css.headerRow+" "+o.cssHeaderRow).attr("role","row"),o.tabIndex&&f.attr("tabindex",0),b)})),o.$headerIndexed=[],n=0;nb;b++)d=f.$headers.eq(b),e=u.getColumnData(a,f.headers,b,!0),c="false"===u.getData(d,e,"sorter")||"false"===u.getData(d,e,"parser"),d[0].sortDisabled=c,d[c?"addClass":"removeClass"]("sorter-false").attr("aria-disabled",""+c),a.id&&(c?d.removeAttr("aria-controls"):d.attr("aria-controls",a.id))}function m(b){var c,d,e,f,g,h,i,j,k=b.config,l=k.sortList,m=l.length,n=u.css.sortNone+" "+k.cssNone,o=[u.css.sortAsc+" "+k.cssAsc,u.css.sortDesc+" "+k.cssDesc],p=[k.cssIconAsc,k.cssIconDesc,k.cssIconNone],q=["ascending","descending"],r=a(b).find("tfoot tr").children().add(a(k.namespace+"_extra_headers")).removeClass(o.join(" "));for(k.$headers.removeClass(o.join(" ")).addClass(n).attr("aria-sort","none").find("."+u.css.icon).removeClass(p.join(" ")).addClass(p[2]),e=0;m>e;e++)if(2!==l[e][1]&&(c=k.$headers.not(".sorter-false").filter('[data-column="'+l[e][0]+'"]'+(1===m?":last":"")),c.length)){for(f=0;fe;e++)h=g.eq(e),h.length&&(d=g[e],i=d.order[(d.count+1)%(k.sortReset?3:2)],j=a.trim(h.text())+": "+u.language[h.hasClass(u.css.sortAsc)?"sortAsc":h.hasClass(u.css.sortDesc)?"sortDesc":"sortNone"]+u.language[0===i?"nextAsc":1===i?"nextDesc":"nextNone"],h.attr("aria-label",j))}function n(b,c){var d,e,f,g,h,i,j,k,l=b.config,m=c||l.sortList,n=m.length;for(l.sortList=[],h=0;n>h;h++)if(k=m[h],d=parseInt(k[0],10),d=0?e:f[1]%(l.sortReset?3:2)}}function o(a,b){return a&&a[b]?a[b].type||"":""}function p(b,c,d){if(b.isUpdating)return setTimeout(function(){p(b,c,d)},50);var e,f,g,i,j,k,l,n=b.config,o=!d[n.sortMultiSortKey],r=n.$table,s=n.$headers.length;if(r.trigger("sortStart",b),c.count=d[n.sortResetKey]?2:(c.count+1)%(n.sortReset?3:2),n.sortRestart)for(f=c,g=0;s>g;g++)l=n.$headers.eq(g),l[0]===f||!o&&l.is("."+u.css.sortDesc+",."+u.css.sortAsc)||(l[0].count=-1);if(f=parseInt(a(c).attr("data-column"),10),o){if(n.sortList=[],null!==n.sortForce)for(e=n.sortForce,i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;i1)for(i=0;i=0&&n.sortList.splice(k,1);if(u.isValueInArray(f,n.sortList)>=0)for(i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;ie;e++)i=q.cache[e].colMax,j=q.cache[e].normalized,j.sort(function(c,d){for(b=0;t>b;b++){if(g=s[b][0],k=s[b][1],p=0===k,q.sortStable&&c[g]===d[g]&&1===t)return c[q.columns].order-d[q.columns].order;if(f=/n/i.test(o(q.parsers,g)),f&&q.strings[g]?(f="boolean"==typeof q.string[q.strings[g]]?(p?1:-1)*(q.string[q.strings[g]]?-1:1):q.strings[g]?q.string[q.strings[g]]||0:0,l=q.numberSorter?q.numberSorter(c[g],d[g],p,i[g],a):u["sortNumeric"+(p?"Asc":"Desc")](c[g],d[g],f,i[g],g,a)):(m=p?c:d,n=p?d:c,l="function"==typeof r?r(m[g],n[g],p,g,a):"object"==typeof r&&r.hasOwnProperty(g)?r[g](m[g],n[g],p,g,a):u["sortNatural"+(p?"Asc":"Desc")](c[g],d[g],g,a,q)),l)return l}return c[q.columns].order-d[q.columns].order});q.debug&&c("Sorting on "+s.toString()+" and dir "+k+" time",h)}}function r(b,c){b.table.isUpdating&&b.$table.trigger("updateComplete",b.table),a.isFunction(c)&&c(b.table)}function s(b,c,d){var e=a.isArray(c)?c:b.sortList,f="undefined"==typeof c?b.resort:c;f===!1||b.serverSideSorting||b.table.isProcessing?(r(b,d),u.applyWidget(b.table,!1)):e.length?b.$table.trigger("sorton",[e,function(){r(b,d)},!0]):b.$table.trigger("sortReset",[function(){r(b,d),u.applyWidget(b.table,!1)}])}function t(b){var c=b.config,e=c.$table,i="sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave ".split(" ").join(c.namespace+" ");e.unbind(i.replace(/\s+/g," ")).bind("sortReset"+c.namespace,function(d,e){d.stopPropagation(),c.sortList=[],m(b),q(b),h(b),a.isFunction(e)&&e(b)}).bind("updateAll"+c.namespace,function(a,d,e){a.stopPropagation(),b.isUpdating=!0,u.refreshWidgets(b,!0,!0),j(b),u.bindEvents(b,c.$headers,!0),t(b),k(b,d,e)}).bind("update"+c.namespace+" updateRows"+c.namespace,function(a,c,d){a.stopPropagation(),b.isUpdating=!0,l(b),k(b,c,d)}).bind("updateCell"+c.namespace,function(d,f,g,h){d.stopPropagation(),b.isUpdating=!0,e.find(c.selectorRemove).remove();var i,j,k,l,m=c.$tbodies,n=a(f),o=m.index(a.fn.closest?n.closest("tbody"):n.parents("tbody").filter(":first")),p=c.cache[o],q=a.fn.closest?n.closest("tr"):n.parents("tr").filter(":first");f=n[0],m.length&&o>=0&&(j=m.eq(o).find("tr").index(q),l=p.normalized[j],k=n.index(),i=u.getParsedText(c,f,k),l[k]=i,l[c.columns].$row=q,"numeric"===(c.parsers[k].type||"").toLowerCase()&&(p.colMax[k]=Math.max(Math.abs(i)||0,p.colMax[k]||0)),i="undefined"!==g?g:c.resort,i!==!1?s(c,i,h):(a.isFunction(h)&&h(b),c.$table.trigger("updateComplete",c.table)))}).bind("addRows"+c.namespace,function(e,g,h,i){if(e.stopPropagation(),b.isUpdating=!0,d(c.cache))l(b),k(b,h,i);else{g=a(g).attr("role","row");var j,m,n,o,p,q=g.filter("tr").length,r=c.$tbodies.index(g.parents("tbody").filter(":first"));for(c.parsers&&c.parsers.length||f(c),j=0;q>j;j++){for(n=g[j].cells.length,p=[],o={child:[],$row:g.eq(j),order:c.cache[r].normalized.length},m=0;n>m;m++)p[m]=u.getParsedText(c,g[j].cells[m],m),"numeric"===(c.parsers[m].type||"").toLowerCase()&&(c.cache[r].colMax[m]=Math.max(Math.abs(p[m])||0,c.cache[r].colMax[m]||0));p.push(o),c.cache[r].normalized.push(p)}s(c,h,i)}}).bind("updateComplete"+c.namespace,function(){b.isUpdating=!1}).bind("sorton"+c.namespace,function(c,f,i,j){var k=b.config;c.stopPropagation(),e.trigger("sortStart",this),n(b,f),m(b),k.delayInit&&d(k.cache)&&g(b),e.trigger("sortBegin",this),q(b),h(b,j),e.trigger("sortEnd",this),u.applyWidget(b),a.isFunction(i)&&i(b)}).bind("appendCache"+c.namespace,function(c,d,e){c.stopPropagation(),h(b,e),a.isFunction(d)&&d(b)}).bind("updateCache"+c.namespace,function(d,e,h){c.parsers&&c.parsers.length||f(c,h),g(b,h),a.isFunction(e)&&e(b)}).bind("applyWidgetId"+c.namespace,function(a,d){a.stopPropagation(),u.getWidgetById(d).format(b,c,c.widgetOptions)}).bind("applyWidgets"+c.namespace,function(a,c){a.stopPropagation(),u.applyWidget(b,c)}).bind("refreshWidgets"+c.namespace,function(a,c,d){a.stopPropagation(),u.refreshWidgets(b,c,d)}).bind("destroy"+c.namespace,function(a,c,d){a.stopPropagation(),u.destroy(b,c,d)}).bind("resetToLoadState"+c.namespace,function(){u.removeWidget(b,!0,!1),c=a.extend(!0,u.defaults,c.originalSettings),b.hasInitialized=!1,u.setup(b,c)})}var u=this;u.version="2.22.3",u.parsers=[],u.widgets=[],u.defaults={theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,tabIndex:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,resort:!0,headers:{},ignoreCase:!0,sortForce:null,sortList:[],sortAppend:null,sortStable:!1,sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",textExtraction:"basic",textAttribute:"data-text",textSorter:null,numberSorter:null,widgets:[],widgetOptions:{zebra:["even","odd"]},initWidgets:!0,widgetClass:"widget-{name}",initialized:null,tableClass:"",cssAsc:"",cssDesc:"",cssNone:"",cssHeader:"",cssHeaderRow:"",cssProcessing:"",cssChildRow:"tablesorter-childRow",cssIcon:"tablesorter-icon",cssIconNone:"",cssIconAsc:"",cssIconDesc:"",cssInfoBlock:"tablesorter-infoOnly",cssNoSort:"tablesorter-noSort",cssIgnoreRow:"tablesorter-ignoreRow",pointerClick:"click",pointerDown:"mousedown",pointerUp:"mouseup",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]},u.css={table:"tablesorter",cssHasChild:"tablesorter-hasChildRow",childRow:"tablesorter-childRow",colgroup:"tablesorter-colgroup",header:"tablesorter-header",headerRow:"tablesorter-headerRow",headerIn:"tablesorter-header-inner",icon:"tablesorter-icon",processing:"tablesorter-processing",sortAsc:"tablesorter-headerAsc",sortDesc:"tablesorter-headerDesc",sortNone:"tablesorter-headerUnSorted"},u.language={sortAsc:"Ascending sort applied, ",sortDesc:"Descending sort applied, ",sortNone:"No sort applied, ",nextAsc:"activate to apply an ascending sort",nextDesc:"activate to apply a descending sort",nextNone:"activate to remove the sort"},u.instanceMethods={},u.log=b,u.benchmark=c,u.getElementText=function(b,c,d){if(!c)return"";var e,f=b.textExtraction||"",g=c.jquery?c:a(c);return"string"==typeof f?"basic"===f&&"undefined"!=typeof(e=g.attr(b.textAttribute))?a.trim(e):a.trim(c.textContent||g.text()):"function"==typeof f?a.trim(f(g[0],b.table,d)):"function"==typeof(e=u.getColumnData(b.table,f,d))?a.trim(e(g[0],b.table,d)):a.trim(g[0].textContent||g.text())},u.getParsedText=function(a,b,c,d){"undefined"==typeof d&&(d=u.getElementText(a,b,c));var e=""+d,f=a.parsers[c],g=a.extractors[c];return f&&(g&&"function"==typeof g.format&&(d=g.format(d,a.table,b,c)),e="no-parser"===f.id?"":f.format(""+d,a.table,b,c),a.ignoreCase&&"string"==typeof e&&(e=e.toLowerCase())),e},u.construct=function(b){return this.each(function(){var c=this,d=a.extend(!0,{},u.defaults,b,u.instanceMethods);d.originalSettings=b,!c.hasInitialized&&u.buildTable&&"TABLE"!==this.nodeName?u.buildTable(c,d):u.setup(c,d)})},u.setup=function(c,d){if(!c||!c.tHead||0===c.tBodies.length||c.hasInitialized===!0)return d.debug?b("ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized"):"";var e="",h=a(c),i=a.metadata;c.hasInitialized=!1,c.isProcessing=!0,c.config=d,a.data(c,"tablesorter",d),d.debug&&a.data(c,"startoveralltimer",new Date),d.supportsDataObject=function(a){return a[0]=parseInt(a[0],10),a[0]>1||1===a[0]&&parseInt(a[1],10)>=4}(a.fn.jquery.split(".")),d.string={max:1,min:-1,emptymin:1,emptymax:-1,zero:0,none:0,"null":0,top:!0,bottom:!1},d.emptyTo=d.emptyTo.toLowerCase(),d.stringTo=d.stringTo.toLowerCase(),/tablesorter\-/.test(h.attr("class"))||(e=""!==d.theme?" tablesorter-"+d.theme:""),d.table=c,d.$table=h.addClass(u.css.table+" "+d.tableClass+e).attr("role","grid"),d.$headers=h.find(d.selectorHeaders),d.namespace?d.namespace="."+d.namespace.replace(/\W/g,""):d.namespace=".tablesorter"+Math.random().toString(16).slice(2),d.$table.children().children("tr").attr("role","row"),d.$tbodies=h.children("tbody:not(."+d.cssInfoBlock+")").attr({"aria-live":"polite","aria-relevant":"all"}),d.$table.children("caption").length&&(e=d.$table.children("caption")[0],e.id||(e.id=d.namespace.slice(1)+"caption"),d.$table.attr("aria-labelledby",e.id)),d.widgetInit={},d.textExtraction=d.$table.attr("data-text-extraction")||d.textExtraction||"basic",j(c),u.fixColumnWidth(c),u.applyWidgetOptions(c,d),f(d),d.totalRows=0,d.delayInit||g(c),u.bindEvents(c,d.$headers,!0),t(c),d.supportsDataObject&&"undefined"!=typeof h.data().sortlist?d.sortList=h.data().sortlist:i&&h.metadata()&&h.metadata().sortlist&&(d.sortList=h.metadata().sortlist),u.applyWidget(c,!0),d.sortList.length>0?h.trigger("sorton",[d.sortList,{},!d.initWidgets,!0]):(m(c),d.initWidgets&&u.applyWidget(c,!1)),d.showProcessing&&h.unbind("sortBegin"+d.namespace+" sortEnd"+d.namespace).bind("sortBegin"+d.namespace+" sortEnd"+d.namespace,function(a){clearTimeout(d.processTimer),u.isProcessing(c),"sortBegin"===a.type&&(d.processTimer=setTimeout(function(){u.isProcessing(c,!0)},500))}),c.hasInitialized=!0,c.isProcessing=!1,d.debug&&u.benchmark("Overall initialization time",a.data(c,"startoveralltimer")),h.trigger("tablesorter-initialized",c),"function"==typeof d.initialized&&d.initialized(c)},u.fixColumnWidth=function(b){b=a(b)[0];var c,d,e,f,g,h=b.config,i=h.$table.children("colgroup");if(i.length&&i.hasClass(u.css.colgroup)&&i.remove(),h.widthFixed&&0===h.$table.children("colgroup").length){for(i=a(''),c=h.$table.width(),e=h.$tbodies.find("tr:first").children(":visible"),f=e.length,g=0;f>g;g++)d=parseInt(e.eq(g).width()/c*1e3,10)/10+"%",i.append(a("").css("width",d));h.$table.prepend(i)}},u.getColumnData=function(b,c,d,e,f){if("undefined"!=typeof c&&null!==c){b=a(b)[0];var g,h,i=b.config,j=f||i.$headers,k=i.$headerIndexed&&i.$headerIndexed[d]||j.filter('[data-column="'+d+'"]:last');if(c[d])return e?c[d]:c[j.index(k)];for(h in c)if("string"==typeof h&&(g=k.filter(h).add(k.find(h)),g.length))return c[h]}},u.computeColumnIndex=function(b){var c,d,e,f,g,h,i,j,k,l,m,n,o=[],p=[],q={};for(c=0;ce;e++)for("undefined"==typeof o[e]&&(o[e]=[]),p=o[e],f=n;n+m>f;f++)p[f]="x"}return p.length},u.isProcessing=function(b,c,d){$table=a(b);var e=$table[0].config,f=d||$table.find("."+u.css.header);c?("undefined"!=typeof d&&e.sortList.length>0&&(f=f.filter(function(){return this.sortDisabled?!1:u.isValueInArray(parseFloat(a(this).attr("data-column")),e.sortList)>=0})),$table.add(f).addClass(u.css.processing+" "+e.cssProcessing)):$table.add(f).removeClass(u.css.processing+" "+e.cssProcessing)},u.processTbody=function(b,c,d){b=a(b)[0];var e;return d?(b.isProcessing=!0,c.before(''),e=a.fn.detach?c.detach():c.remove()):(e=a(b).find("colgroup.tablesorter-savemyplace"),c.insertAfter(e),e.remove(),void(b.isProcessing=!1))},u.clearTableBody=function(b){a(b)[0].config.$tbodies.children().detach()},u.bindEvents=function(b,c,e){b=a(b)[0];var f,h=null,i=b.config;e!==!0&&(c.addClass(i.namespace.slice(1)+"_extra_headers"),f=a.fn.closest?c.closest("table")[0]:c.parents("table")[0],f&&"TABLE"===f.nodeName&&f!==b&&a(f).addClass(i.namespace.slice(1)+"_extra_table")),f=(i.pointerDown+" "+i.pointerUp+" "+i.pointerClick+" sort keyup ").replace(/\s+/g," ").split(" ").join(i.namespace+" "),c.find(i.selectorSort).add(c.filter(i.selectorSort)).unbind(f).bind(f,function(e,f){var j,k,l=a(e.target),m=" "+e.type+" ";if(!(1!==(e.which||e.button)&&!m.match(" "+i.pointerClick+" | sort | keyup ")||" keyup "===m&&13!==e.which||m.match(" "+i.pointerClick+" ")&&"undefined"!=typeof e.which||m.match(" "+i.pointerUp+" ")&&h!==e.target&&f!==!0)){if(m.match(" "+i.pointerDown+" "))return h=e.target,k=l.jquery.split("."),void("1"===k[0]&&k[1]<4&&e.preventDefault());if(h=null,/(input|select|button|textarea)/i.test(e.target.nodeName)||l.hasClass(i.cssNoSort)||l.parents("."+i.cssNoSort).length>0||l.parents("button").length>0)return!i.cancelSelection;i.delayInit&&d(i.cache)&&g(b),j=a.fn.closest?a(this).closest("th, td")[0]:/TH|TD/.test(this.nodeName)?this:a(this).parents("th, td")[0],j=i.$headers[c.index(j)],j.sortDisabled||p(b,j,e)}}),i.cancelSelection&&c.attr("unselectable","on").bind("selectstart",!1).css({"user-select":"none",MozUserSelect:"none"})},u.restoreHeaders=function(b){var c,d,e=a(b)[0].config,f=e.$table.find(e.selectorHeaders),g=f.length;for(c=0;g>c;c++)d=f.eq(c),d.find("."+u.css.headerIn).length&&d.html(e.headerContent[c])},u.destroy=function(b,c,d){if(b=a(b)[0],b.hasInitialized){u.removeWidget(b,!0,!1);var e,f=a(b),g=b.config,h=f.find("thead:first"),i=h.find("tr."+u.css.headerRow).removeClass(u.css.headerRow+" "+g.cssHeaderRow),j=f.find("tfoot:first > tr").children("th, td");c===!1&&a.inArray("uitheme",g.widgets)>=0&&(f.trigger("applyWidgetId",["uitheme"]),f.trigger("applyWidgetId",["zebra"])),h.find("tr").not(i).remove(),e="sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache "+"applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState ".split(" ").join(g.namespace+" "),f.removeData("tablesorter").unbind(e.replace(/\s+/g," ")),g.$headers.add(j).removeClass([u.css.header,g.cssHeader,g.cssAsc,g.cssDesc,u.css.sortAsc,u.css.sortDesc,u.css.sortNone].join(" ")).removeAttr("data-column").removeAttr("aria-label").attr("aria-disabled","true"),i.find(g.selectorSort).unbind("mousedown mouseup keypress ".split(" ").join(g.namespace+" ").replace(/\s+/g," ")),u.restoreHeaders(b),f.toggleClass(u.css.table+" "+g.tableClass+" tablesorter-"+g.theme,c===!1),b.hasInitialized=!1,delete b.config.cache,"function"==typeof d&&d(b)}},u.regex={chunk:/(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,chunks:/(^\\0|\\0$)/,hex:/^0x[0-9a-f]+$/i},u.sortNatural=function(a,b){if(a===b)return 0;var c,d,e,f,g,h,i,j,k=u.regex;if(k.hex.test(b)){if(d=parseInt(a.match(k.hex),16),f=parseInt(b.match(k.hex),16),f>d)return-1;if(d>f)return 1}for(c=a.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),e=b.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),j=Math.max(c.length,e.length),i=0;j>i;i++){if(g=isNaN(c[i])?c[i]||0:parseFloat(c[i])||0,h=isNaN(e[i])?e[i]||0:parseFloat(e[i])||0,isNaN(g)!==isNaN(h))return isNaN(g)?1:-1;if(typeof g!=typeof h&&(g+="",h+=""),h>g)return-1;if(g>h)return 1}return 0},u.sortNaturalAsc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:-f||-1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:f||1:u.sortNatural(a,b)},u.sortNaturalDesc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:f||1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:-f||-1:u.sortNatural(b,a)},u.sortText=function(a,b){return a>b?1:b>a?-1:0},u.getTextValue=function(a,b,c){if(c){var d,e=a?a.length:0,f=c+b;for(d=0;e>d;d++)f+=a.charCodeAt(d);return b*f}return 0},u.sortNumericAsc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:-h||-1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:h||1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),a-b)},u.sortNumericDesc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:h||1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:-h||-1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),b-a)},u.sortNumeric=function(a,b){return a-b},u.characterEquivalents={a:"áàâãäąå",A:"ÁÀÂÃÄĄÅ",c:"çćč",C:"ÇĆČ",e:"éèêëěę",E:"ÉÈÊËĚĘ",i:"íìİîïı",I:"ÍÌİÎÏ",o:"óòôõöō",O:"ÓÒÔÕÖŌ",ss:"ß",SS:"ẞ",u:"úùûüů",U:"ÚÙÛÜŮ"},u.replaceAccents=function(a){var b,c="[",d=u.characterEquivalents;if(!u.characterRegex){u.characterRegexArray={};for(b in d)"string"==typeof b&&(c+=d[b],u.characterRegexArray[b]=new RegExp("["+d[b]+"]","g"));u.characterRegex=new RegExp(c+"]")}if(u.characterRegex.test(a))for(b in d)"string"==typeof b&&(a=a.replace(u.characterRegexArray[b],b));return a},u.isValueInArray=function(a,b){var c,d=b.length;for(c=0;d>c;c++)if(b[c][0]===a)return c;return-1},u.addParser=function(a){var b,c=u.parsers.length,d=!0;for(b=0;c>b;b++)u.parsers[b].id.toLowerCase()===a.id.toLowerCase()&&(d=!1);d&&u.parsers.push(a)},u.addInstanceMethods=function(b){a.extend(u.instanceMethods,b)},u.getParserById=function(a){if("false"==a)return!1;var b,c=u.parsers.length;for(b=0;c>b;b++)if(u.parsers[b].id.toLowerCase()===a.toString().toLowerCase())return u.parsers[b];return!1},u.addWidget=function(a){u.widgets.push(a)},u.hasWidget=function(b,c){return $table=a(b),$table.length&&$table[0].config&&$table[0].config.widgetInit[c]||!1},u.getWidgetById=function(a){var b,c,d=u.widgets.length;for(b=0;d>b;b++)if(c=u.widgets[b],c&&c.hasOwnProperty("id")&&c.id.toLowerCase()===a.toLowerCase())return c},u.applyWidgetOptions=function(b,c){var d,e,f=c.widgets.length,g=c.widgetOptions;if(f)for(d=0;f>d;d++)e=u.getWidgetById(c.widgets[d]),e&&"options"in e&&(g=b.config.widgetOptions=a.extend(!0,{},e.options,g))},u.applyWidget=function(b,d,e){b=a(b)[0];var f,g,h,i,j,k,l,m=b.config,n=m.widgetOptions,o=" "+m.table.className+" ",p=[];if(d===!1||!b.hasInitialized||!b.isApplyingWidgets&&!b.isUpdating){if(m.debug&&(i=new Date),l=new RegExp("\\s"+m.widgetClass.replace(/\{name\}/i,"([\\w-]+)")+"\\s","g"),o.match(l)&&(k=o.match(l)))for(g=k.length,f=0;g>f;f++)m.widgets.push(k[f].replace(l,"$1"));if(m.widgets.length){for(b.isApplyingWidgets=!0,m.widgets=a.grep(m.widgets,function(b,c){return a.inArray(b,m.widgets)===c}),h=m.widgets||[],g=h.length,f=0;g>f;f++)l=u.getWidgetById(h[f]),l&&l.id&&(l.priority||(l.priority=10),p[f]=l);for(p.sort(function(a,b){return a.priorityf;f++)p[f]&&((d||!m.widgetInit[p[f].id])&&(m.widgetInit[p[f].id]=!0,b.hasInitialized&&u.applyWidgetOptions(b,m),"init"in p[f]&&(m.debug&&(j=new Date),p[f].init(b,p[f],m,n),m.debug&&u.benchmark("Initializing "+p[f].id+" widget",j))),!d&&"format"in p[f]&&(m.debug&&(j=new Date),p[f].format(b,m,n,!1),m.debug&&u.benchmark((d?"Initializing ":"Applying ")+p[f].id+" widget",j)));d||"function"!=typeof e||e(b)}setTimeout(function(){b.isApplyingWidgets=!1,a.data(b,"lastWidgetApplication",new Date)},0),m.debug&&(k=m.widgets.length,c("Completed "+(d===!0?"initializing ":"applying ")+k+" widget"+(1!==k?"s":""),i))}},u.removeWidget=function(c,d,e){c=a(c)[0];var f,g,h,i,j=c.config;if(d===!0)for(d=[],i=u.widgets.length,h=0;i>h;h++)g=u.widgets[h],g&&g.id&&d.push(g.id);else d=(a.isArray(d)?d.join(","):d||"").toLowerCase().split(/[\s,]+/);for(i=d.length,f=0;i>f;f++)g=u.getWidgetById(d[f]),h=a.inArray(d[f],j.widgets),g&&"remove"in g&&(j.debug&&h>=0&&b('Removing "'+d[f]+'" widget'),g.remove(c,j,j.widgetOptions,e),j.widgetInit[d[f]]=!1),h>=0&&e!==!0&&j.widgets.splice(h,1)},u.refreshWidgets=function(b,c,d){b=a(b)[0];var e,f=b.config,g=f.widgets,h=u.widgets,i=h.length,j=[],k=function(b){a(b).trigger("refreshComplete")};for(e=0;i>e;e++)h[e]&&h[e].id&&(c||a.inArray(h[e].id,g)<0)&&j.push(h[e].id);u.removeWidget(b,j.join(","),!0),d!==!0?(u.applyWidget(b,c||!1,k),c&&u.applyWidget(b,!1,k)):k(b)},u.getColumnText=function(b,c,e){b=a(b)[0];var f,g,h,i,j,k,l,m,n,o,p="function"==typeof e,q="all"===c,r={raw:[],parsed:[],$cell:[]},s=b.config;if(!d(s)){for(j=s.$tbodies.length,f=0;j>f;f++)for(h=s.cache[f].normalized,k=h.length,g=0;k>g;g++)o=!0,i=h[g],m=q?i.slice(0,s.columns):i[c],i=i[s.columns],l=q?i.raw:i.raw[c],n=q?i.$row.children():i.$row.children().eq(c),p&&(o=e({tbodyIndex:f,rowIndex:g,parsed:m,raw:l,$row:i.$row,$cell:n})),o!==!1&&(r.parsed.push(m),r.raw.push(l),r.$cell.push(n));return r}},u.getData=function(b,c,d){var e,f,g="",h=a(b);return h.length?(e=a.metadata?h.metadata():!1,f=" "+(h.attr("class")||""),"undefined"!=typeof h.data(d)||"undefined"!=typeof h.data(d.toLowerCase())?g+=h.data(d)||h.data(d.toLowerCase()):e&&"undefined"!=typeof e[d]?g+=e[d]:c&&"undefined"!=typeof c[d]?g+=c[d]:" "!==f&&f.match(" "+d+"-")&&(g=f.match(new RegExp("\\s"+d+"-([\\w-]+)"))[1]||""),a.trim(g)):""},u.formatFloat=function(b,c){if("string"!=typeof b||""===b)return b;var d,e=c&&c.config?c.config.usNumberFormat!==!1:"undefined"!=typeof c?c:!0;return b=e?b.replace(/,/g,""):b.replace(/[\s|\.]/g,"").replace(/,/g,"."),/^\s*\([.\d]+\)/.test(b)&&(b=b.replace(/^\s*\(([.\d]+)\)/,"-$1")),d=parseFloat(b),isNaN(d)?a.trim(b):d},u.isDigit=function(a){return isNaN(a)?/^[\-+(]?\d+[)]?$/.test(a.toString().replace(/[,.'"\s]/g,"")):""!==a}}});var b=a.tablesorter;a.fn.extend({tablesorter:b.construct}),b.addParser({id:"no-parser",is:function(){return!1},format:function(){return""},type:"text"}),b.addParser({id:"text",is:function(){return!0},format:function(c,d){var e=d.config;return c&&(c=a.trim(e.ignoreCase?c.toLocaleLowerCase():c),c=e.sortLocaleCompare?b.replaceAccents(c):c),c},type:"text"}),b.addParser({id:"digit",is:function(a){return b.isDigit(a)},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"currency",is:function(a){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test((a||"").replace(/[+\-,. ]/g,""))},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"url",is:function(a){return/^(https?|ftp|file):\/\//.test(a)},format:function(b){return b?a.trim(b.replace(/(https?|ftp|file):\/\//,"")):b},parsed:!0,type:"text"}),b.addParser({id:"isoDate",is:function(a){return/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(a)},format:function(a,b){var c=a?new Date(a.replace(/-/g,"/")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"percent",is:function(a){return/(\d\s*?%|%\s*?\d)/.test(a)&&a.length<15},format:function(a,c){return a?b.formatFloat(a.replace(/%/g,""),c):a},type:"numeric"}),b.addParser({id:"image",is:function(a,b,c,d){return d.find("img").length>0},format:function(b,c,d){return a(d).find("img").attr(c.config.imgAttr||"alt")||b},parsed:!0,type:"text" +}),b.addParser({id:"usLongDate",is:function(a){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(a)||/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(a)},format:function(a,b){var c=a?new Date(a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"shortDate",is:function(a){return/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/.test((a||"").replace(/\s+/g," ").replace(/[\-.,]/g,"/"))},format:function(a,c,d,e){if(a){var f,g,h=c.config,i=h.$headerIndexed[e],j=i.length&&i[0].dateFormat||b.getData(i,b.getColumnData(c,h.headers,e),"dateFormat")||h.dateFormat;return g=a.replace(/\s+/g," ").replace(/[\-.,]/g,"/"),"mmddyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$1/$2"):"ddmmyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$2/$1"):"yyyymmdd"===j&&(g=g.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,"$1/$2/$3")),f=new Date(g),f instanceof Date&&isFinite(f)?f.getTime():a}return a},type:"numeric"}),b.addParser({id:"time",is:function(a){return/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(a)},format:function(a,b){var c=a?new Date("2000/01/01 "+a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"metadata",is:function(){return!1},format:function(b,c,d){var e=c.config,f=e.parserMetadataName?e.parserMetadataName:"sortValue";return a(d).metadata()[f]},type:"numeric"}),b.addWidget({id:"zebra",priority:90,format:function(b,c,d){var e,f,g,h,i,j,k,l,m=new RegExp(c.cssChildRow,"i"),n=c.$tbodies.add(a(c.namespace+"_extra_table").children("tbody:not(."+c.cssInfoBlock+")"));for(c.debug&&(i=new Date),j=0;jk;k++)f=e.eq(k),m.test(f[0].className)||g++,h=g%2===0,f.removeClass(d.zebra[h?1:0]).addClass(d.zebra[h?0:1])},remove:function(a,c,d,e){if(!e){var f,g,h=c.$tbodies,i=(d.zebra||["even","odd"]).join(" ");for(f=0;f
')}),d.cssIcon&&t.find("."+b.css.icon).removeClass(q?[n.icons,p].join(" "):"").addClass(v.icons||""),s.hasClass("hasFilters")&&s.children("thead").children("."+b.css.filterRow).removeClass(q?n.filterRow||"":"").addClass(v.filterRow||"")),f=0;f1)))for(m=1;q>m;m++)k.eq(p[m][0]).addClass(r[m]||r[s])}),b.processTbody(c,f,!1);if(i=e.columns_thead!==!1?["thead tr"]:[],e.columns_tfoot!==!1&&i.push("tfoot tr"),i.length&&(h=n.find(i.join(",")).children().removeClass(l),q))for(m=0;q>m;m++)h.filter('[data-column="'+p[m][0]+'"]').addClass(r[m]||r[s])},remove:function(c,d,e){var f,g,h=d.$tbodies,i=(e.columns||["primary","secondary","tertiary"]).join(" ");for(d.$headers.removeClass(i),d.$table.children("tfoot").children("tr").children("th, td").removeClass(i),f=0;f=]/g,query:"(q|query)"},types:{or:function(c,d,e){if(/\|/.test(d.iFilter)||b.filter.regex.orSplit.test(d.filter)){var f,g,h,i,j=a.extend({},d),k=d.index,l=d.parsed[k],m=d.filter.split(b.filter.regex.orSplit),n=d.iFilter.split(b.filter.regex.orSplit),o=m.length;for(f=0;o>f;f++)if(j.nestedFilters=!0,j.filter=""+(b.filter.parseFilter(c,m[f],k,l)||""),j.iFilter=""+(b.filter.parseFilter(c,n[f],k,l)||""),h="("+(b.filter.parseFilter(c,j.filter,k,l)||"")+")",i=new RegExp(d.isMatch?h:"^"+h+"$",c.widgetOptions.filter_ignoreCase?"i":""),g=i.test(j.exact)||b.filter.processTypes(c,j,e))return g;return g||!1}return null},and:function(c,d,e){if(b.filter.regex.andTest.test(d.filter)){var f,g,h,i,j,k=a.extend({},d),l=d.index,m=d.parsed[l],n=d.filter.split(b.filter.regex.andSplit),o=d.iFilter.split(b.filter.regex.andSplit),p=n.length;for(f=0;p>f;f++)k.nestedFilters=!0,k.filter=""+(b.filter.parseFilter(c,n[f],l,m)||""),k.iFilter=""+(b.filter.parseFilter(c,o[f],l,m)||""),i=("("+(b.filter.parseFilter(c,k.filter,l,m)||"")+")").replace(/\?/g,"\\S{1}").replace(/\*/g,"\\S*"),j=new RegExp(d.isMatch?i:"^"+i+"$",c.widgetOptions.filter_ignoreCase?"i":""),h=j.test(k.exact)||b.filter.processTypes(c,k,e),g=0===f?h:g&&h;return g||!1}return null},regex:function(a,c){if(b.filter.regex.regex.test(c.filter)){var d,e=c.filter_regexCache[c.index]||b.filter.regex.regex.exec(c.filter),f=e instanceof RegExp;try{f||(c.filter_regexCache[c.index]=e=new RegExp(e[1],e[2])),d=e.test(c.exact)}catch(g){d=!1}return d}return null},operators:function(c,d){if(/^[<>]=?/.test(d.iFilter)&&""!==d.iExact){var e,f,g,h=c.table,i=d.index,j=d.parsed[i],k=b.formatFloat(d.iFilter.replace(b.filter.regex.operators,""),h),l=c.parsers[i],m=k;return(j||"numeric"===l.type)&&(g=a.trim(""+d.iFilter.replace(b.filter.regex.operators,"")),f=b.filter.parseFilter(c,g,i,!0),k="number"!=typeof f||""===f||isNaN(f)?k:f),!j&&"numeric"!==l.type||isNaN(k)||"undefined"==typeof d.cache?(g=isNaN(d.iExact)?d.iExact.replace(b.filter.regex.nondigit,""):d.iExact,e=b.formatFloat(g,h)):e=d.cache,/>/.test(d.iFilter)?f=/>=/.test(d.iFilter)?e>=k:e>k:/=e:k>e),f||""!==m||(f=!0),f}return null},notMatch:function(c,d){if(/^\!/.test(d.iFilter)){var e,f=d.iFilter.replace("!",""),g=b.filter.parseFilter(c,f,d.index,d.parsed[d.index])||"";return b.filter.regex.exact.test(g)?(g=g.replace(b.filter.regex.exact,""),""===g?!0:a.trim(g)!==d.iExact):(e=d.iExact.search(a.trim(g)),""===g?!0:!(c.widgetOptions.filter_startsWith?0===e:e>=0))}return null},exact:function(c,d){if(b.filter.regex.exact.test(d.iFilter)){var e=d.iFilter.replace(b.filter.regex.exact,""),f=b.filter.parseFilter(c,e,d.index,d.parsed[d.index])||"";return d.anyMatch?a.inArray(f,d.rowArray)>=0:f==d.iExact}return null},range:function(a,c){if(b.filter.regex.toTest.test(c.iFilter)){var d,e,f,g,h=a.table,i=c.index,j=c.parsed[i],k=c.iFilter.split(b.filter.regex.toSplit);return e=k[0].replace(b.filter.regex.nondigit,"")||"",f=b.formatFloat(b.filter.parseFilter(a,e,i,j),h),e=k[1].replace(b.filter.regex.nondigit,"")||"",g=b.formatFloat(b.filter.parseFilter(a,e,i,j),h),(j||"numeric"===a.parsers[i].type)&&(d=a.parsers[i].format(""+k[0],h,a.$headers.eq(i),i),f=""===d||isNaN(d)?f:d,d=a.parsers[i].format(""+k[1],h,a.$headers.eq(i),i),g=""===d||isNaN(d)?g:d),!j&&"numeric"!==a.parsers[i].type||isNaN(f)||isNaN(g)?(e=isNaN(c.iExact)?c.iExact.replace(b.filter.regex.nondigit,""):c.iExact,d=b.formatFloat(e,h)):d=c.cache,f>g&&(e=f,f=g,g=e),d>=f&&g>=d||""===f||""===g}return null},wild:function(a,c){if(/[\?\*\|]/.test(c.iFilter)){var d=c.index,e=c.parsed[d],f=""+(b.filter.parseFilter(a,c.iFilter,d,e)||"");return!/\?\*/.test(f)&&c.nestedFilters&&(f=c.isMatch?f:"^("+f+")$"),new RegExp(f.replace(/\?/g,"\\S{1}").replace(/\*/g,"\\S*"),a.widgetOptions.filter_ignoreCase?"i":"").test(c.exact)}return null},fuzzy:function(a,c){if(/^~/.test(c.iFilter)){var d,e=0,f=c.iExact.length,g=c.iFilter.slice(1),h=b.filter.parseFilter(a,g,c.index,c.parsed[c.index])||"";for(d=0;f>d;d++)c.iExact[d]===h[e]&&(e+=1);return e===h.length?!0:!1}return null}},init:function(d,e,f){b.language=a.extend(!0,{},{to:"to",or:"or",and:"and"},b.language);var g,h,i,j,k,l,m,n,o,p=b.filter.regex;if(e.$table.addClass("hasFilters"),f.searchTimer=null,f.filter_initTimer=null,f.filter_formatterCount=0,f.filter_formatterInit=[],f.filter_anyColumnSelector='[data-column="all"],[data-column="any"]',f.filter_multipleColumnSelector='[data-column*="-"],[data-column*=","]',m="\\{"+b.filter.regex.query+"\\}",a.extend(p,{child:new RegExp(e.cssChildRow),filtered:new RegExp(f.filter_filteredRow),alreadyFiltered:new RegExp("(\\s+("+b.language.or+"|-|"+b.language.to+")\\s+)","i"),toTest:new RegExp("\\s+(-|"+b.language.to+")\\s+","i"),toSplit:new RegExp("(?:\\s+(?:-|"+b.language.to+")\\s+)","gi"),andTest:new RegExp("\\s+("+b.language.and+"|&&)\\s+","i"),andSplit:new RegExp("(?:\\s+(?:"+b.language.and+"|&&)\\s+)","gi"),orSplit:new RegExp("(?:\\s+(?:"+b.language.or+")\\s+|\\|)","gi"),iQuery:new RegExp(m,"i"),igQuery:new RegExp(m,"ig")}),m=e.$headers.filter(".filter-false, .parser-false").length,f.filter_columnFilters!==!1&&m!==e.$headers.length&&b.filter.buildRow(d,e,f),i="addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ".split(" ").join(e.namespace+"filter "),e.$table.bind(i,function(g,h){return m=f.filter_hideEmpty&&a.isEmptyObject(e.cache)&&!(e.delayInit&&"appendCache"===g.type),e.$table.find("."+c.filterRow).toggleClass(f.filter_filteredRow,m),/(search|filter)/.test(g.type)||(g.stopPropagation(),b.filter.buildDefault(d,!0)),"filterReset"===g.type?(e.$table.find("."+c.filter).add(f.filter_$externalFilters).val(""),b.filter.searching(d,[])):"filterEnd"===g.type?b.filter.buildDefault(d,!0):(h="search"===g.type?h:"updateComplete"===g.type?e.$table.data("lastSearch"):"",/(update|add)/.test(g.type)&&"updateComplete"!==g.type&&(e.lastCombinedFilter=null,e.lastSearch=[]),b.filter.searching(d,h,!0)),!1}),f.filter_reset&&(f.filter_reset instanceof a?f.filter_reset.click(function(){e.$table.trigger("filterReset")}):a(f.filter_reset).length&&a(document).undelegate(f.filter_reset,"click.tsfilter").delegate(f.filter_reset,"click.tsfilter",function(){e.$table.trigger("filterReset")})),f.filter_functions)for(k=0;k'+(j.data("placeholder")||j.attr("data-placeholder")||f.filter_placeholder.select||"")+"":"",m=h,i=h,h.indexOf(f.filter_selectSourceSeparator)>=0&&(m=h.split(f.filter_selectSourceSeparator),i=m[1],m=m[0]),g+="");e.$table.find("thead").find("select."+c.filter+'[data-column="'+k+'"]').append(g),i=f.filter_selectSource,n=a.isFunction(i)?!0:b.getColumnData(d,i,k),n&&b.filter.buildSelect(e.table,k,"",!0,j.hasClass(f.filter_onlyAvail))}b.filter.buildDefault(d,!0),b.filter.bindSearch(d,e.$table.find("."+c.filter),!0),f.filter_external&&b.filter.bindSearch(d,f.filter_external),f.filter_hideFilters&&b.filter.hideFilters(d,e),e.showProcessing&&(i="filterStart filterEnd ".split(" ").join(e.namespace+"filter "),e.$table.unbind(i.replace(/\s+/g," ")).bind(i,function(f,g){j=g?e.$table.find("."+c.header).filter("[data-column]").filter(function(){return""!==g[a(this).data("column")]}):"",b.isProcessing(d,"filterStart"===f.type,g?j:"")})),e.filteredRows=e.totalRows,i="tablesorter-initialized pagerBeforeInitialized ".split(" ").join(e.namespace+"filter "),e.$table.unbind(i.replace(/\s+/g," ")).bind(i,function(){var a=this.config.widgetOptions;l=b.filter.setDefaults(d,e,a)||[],l.length&&(e.delayInit&&""===l.join("")||b.setFilters(d,l,!0)),e.$table.trigger("filterFomatterUpdate"),setTimeout(function(){a.filter_initialized||b.filter.filterInitComplete(e)},100)}),e.pager&&e.pager.initialized&&!f.filter_initialized&&(e.$table.trigger("filterFomatterUpdate"),setTimeout(function(){b.filter.filterInitComplete(e)},100))},formatterUpdated:function(a,b){var c=a.closest("table")[0].config.widgetOptions;c.filter_initialized||(c.filter_formatterInit[b]=1)},filterInitComplete:function(c){var d,e,f=c.widgetOptions,g=0,h=function(){f.filter_initialized=!0,c.$table.trigger("filterInit",c),b.filter.findRows(c.table,c.$table.data("lastSearch")||[])};if(a.isEmptyObject(f.filter_formatter))h();else{for(e=f.filter_formatterInit.length,d=0;e>d;d++)1===f.filter_formatterInit[d]&&g++;clearTimeout(f.filter_initTimer),f.filter_initialized||g!==f.filter_formatterCount?f.filter_initialized||(f.filter_initTimer=setTimeout(function(){h()},500)):h()}},setDefaults:function(c,d,e){var f,g,h,i,j,k=b.getFilters(c)||[];if(e.filter_saveFilters&&b.storage&&(g=b.storage(c,"tablesorter-filters")||[],f=a.isArray(g),f&&""===g.join("")||!f||(k=g)),""===k.join(""))for(j=d.$headers.add(e.filter_$externalFilters).filter("["+e.filter_defaultAttrib+"]"),h=0;h<=d.columns;h++)i=h===d.columns?"all":h,k[h]=j.filter('[data-column="'+i+'"]').attr(e.filter_defaultAttrib)||k[h]||"";return d.$table.data("lastSearch",k),k},parseFilter:function(a,b,c,d){return d?a.parsers[c].format(b,a.table,[],c):b},buildRow:function(d,e,f){var g,h,i,j,k,l,m,n,o=f.filter_cellFilter,p=e.columns,q=a.isArray(o),r='';for(h=0;p>h;h++)r+="").appendTo(e.$table.children("thead").eq(0)).find("td"),h=0;p>h;h++)k=!1,i=e.$headerIndexed[h],m=b.getColumnData(d,f.filter_functions,h),j=f.filter_functions&&m&&"function"!=typeof m||i.hasClass("filter-select"),g=b.getColumnData(d,e.headers,h),k="false"===b.getData(i[0],g,"filter")||"false"===b.getData(i[0],g,"parser"),j?r=a("').appendTo(e.$filters.eq(h)),r&&(n=i.data("placeholder")||i.attr("data-placeholder")||f.filter_placeholder.search||"",r.attr("placeholder",n))),r&&(l=(a.isArray(f.filter_cssFilter)?"undefined"!=typeof f.filter_cssFilter[h]?f.filter_cssFilter[h]||"":"":f.filter_cssFilter)||"",r.addClass(c.filter+" "+l).attr("data-column",h),k&&(r.attr("placeholder","").addClass(c.filterDisabled)[0].disabled=!0))},bindSearch:function(c,d,e){if(c=a(c)[0],d=a(d),d.length){var f,g=c.config,h=g.widgetOptions,i=g.namespace+"filter",j=h.filter_$externalFilters;e!==!0&&(f=h.filter_anyColumnSelector+","+h.filter_multipleColumnSelector,h.filter_$anyMatch=d.filter(f),j&&j.length?h.filter_$externalFilters=h.filter_$externalFilters.add(d):h.filter_$externalFilters=d,b.setFilters(c,g.$table.data("lastSearch")||[],e===!1)),f="keypress keyup search change ".split(" ").join(i+" "),d.attr("data-lastSearchTime",(new Date).getTime()).unbind(f.replace(/\s+/g," ")).bind("keyup"+i,function(d){if(a(this).attr("data-lastSearchTime",(new Date).getTime()),27===d.which)this.value="";else{if(h.filter_liveSearch===!1)return;if(""!==this.value&&("number"==typeof h.filter_liveSearch&&this.value.length=37&&d.which<=40)))return}b.filter.searching(c,!0,!0)}).bind("search change keypress ".split(" ").join(i+" "),function(d){var e=a(this).data("column");(13===d.which||"search"===d.type||"change"===d.type&&this.value!==g.lastSearch[e])&&(d.preventDefault(),a(this).attr("data-lastSearchTime",(new Date).getTime()),b.filter.searching(c,!1,!0))})}},searching:function(a,c,d){var e=a.config.widgetOptions;clearTimeout(e.searchTimer),"undefined"==typeof c||c===!0?e.searchTimer=setTimeout(function(){b.filter.checkFilters(a,c,d)},e.filter_liveSearch?e.filter_searchDelay:10):b.filter.checkFilters(a,c,d)},checkFilters:function(d,e,f){var g=d.config,h=g.widgetOptions,i=a.isArray(e),j=i?e:b.getFilters(d,!0),k=(j||[]).join("");return a.isEmptyObject(g.cache)?void(g.delayInit&&g.pager&&g.pager.initialized&&g.$table.trigger("updateCache",[function(){b.filter.checkFilters(d,!1,f)}])):(i&&(b.setFilters(d,j,!1,f!==!0),h.filter_initialized||(g.lastCombinedFilter="")),h.filter_hideFilters&&g.$table.find("."+c.filterRow).trigger(""===k?"mouseleave":"mouseenter"),g.lastCombinedFilter!==k||e===!1?(e===!1&&(g.lastCombinedFilter=null,g.lastSearch=[]),h.filter_initialized&&g.$table.trigger("filterStart",[j]),g.showProcessing?void setTimeout(function(){return b.filter.findRows(d,j,k),!1},30):(b.filter.findRows(d,j,k),!1)):void 0)},hideFilters:function(d,e){var f;e.$table.find("."+c.filterRow).bind("mouseenter mouseleave",function(b){var d=b,g=a(this);clearTimeout(f),f=setTimeout(function(){/enter|over/.test(d.type)?g.removeClass(c.filterRowHide):a(document.activeElement).closest("tr")[0]!==g[0]&&""===e.lastCombinedFilter&&g.addClass(c.filterRowHide)},200)}).find("input, select").bind("focus blur",function(d){var g=d,h=a(this).closest("tr");clearTimeout(f),f=setTimeout(function(){clearTimeout(f),""===b.getFilters(e.$table).join("")&&h.toggleClass(c.filterRowHide,"focus"!==g.type)},200)})},defaultFilter:function(c,d){if(""===c)return c;var e=b.filter.regex.iQuery,f=d.match(b.filter.regex.igQuery).length,g=f>1?a.trim(c).split(/\s/):[a.trim(c)],h=g.length-1,i=0,j=d;for(1>h&&f>1&&(g[1]=g[0]);e.test(j);)j=j.replace(e,g[i++]||""),e.test(j)&&h>i&&""!==(g[i]||"")&&(j=d.replace(e,j));return j},getLatestSearch:function(b){return b?b.sort(function(b,c){return a(c).attr("data-lastSearchTime")-a(b).attr("data-lastSearchTime")}):b||a()},multipleColumns:function(c,d){var e,f,g,h,i,j,k,l,m,n=c.widgetOptions,o=n.filter_initialized||!d.filter(n.filter_anyColumnSelector).length,p=[],q=a.trim(b.filter.getLatestSearch(d).attr("data-column")||"");if(o&&/-/.test(q))for(f=q.match(/(\d+)\s*-\s*(\d+)/g),m=f.length,l=0;m>l;l++){for(g=f[l].split(/\s*-\s*/),h=parseInt(g[0],10)||0,i=parseInt(g[1],10)||c.columns-1,h>i&&(e=h,h=i,i=e),i>=c.columns&&(i=c.columns-1);i>=h;h++)p.push(h);q=q.replace(f[l],"")}if(o&&/,/.test(q))for(j=q.split(/\s*,\s*/),m=j.length,k=0;m>k;k++)""!==j[k]&&(l=parseInt(j[k],10),l-1?(d.parsed[e]?m=d.cacheArray[e]:(m=d.rawArray[e],m=a.trim(o.filter_ignoreCase?m.toLowerCase():m),c.sortLocaleCompare&&(m=b.replaceAccents(m))),m):void 0}).get(),d.filter=d.anyMatchFilter,d.iFilter=d.iAnyMatchFilter,d.exact=d.rowArray.join(" "),d.iExact=o.filter_ignoreCase?d.exact.toLowerCase():d.exact,d.cache=d.cacheArray.slice(0,-1).join(" "),e.excludeMatch=e.noAnyMatch,j=b.filter.processTypes(c,d,e),null!==j)p=j;else if(o.filter_startsWith)for(p=!1,f=c.columns;!p&&f>0;)f--,p=p||0===d.rowArray[f].indexOf(d.iFilter);else p=(d.iExact+d.childRowText).indexOf(d.iFilter)>=0;if(d.anyMatch=!1,d.filters.join("")===d.filter)return p}for(f=0;f=0:d.filter===d.exact:"function"==typeof k?j=k(d.exact,d.cache,d.filter,f,d.$row,c,d):"function"==typeof k[l||d.filter]&&(m=l||d.filter,j=k[m](d.exact,d.cache,d.filter,f,d.$row,c,d))),null===j?(j=b.filter.processTypes(c,d,e),null!==j?h=j:(m=(d.iExact+d.childRowText).indexOf(b.filter.parseFilter(c,d.iFilter,f,d.parsed[f])),h=!o.filter_startsWith&&m>=0||o.filter_startsWith&&0===m)):h=j,p=h?p:!1);return p},findRows:function(c,d,e){if(c.config.lastCombinedFilter!==e&&c.config.widgetOptions.filter_initialized){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B=a.extend([],d),C=b.filter.regex,D=c.config,E=D.widgetOptions,F={anyMatch:!1,filters:d,filter_regexCache:[]},G={noAnyMatch:["range","notMatch","operators"],functions:[],excludeFilter:[],defaultColFilter:[],defaultAnyFilter:b.getColumnData(c,E.filter_defaultFilter,D.columns,!0)||""};for(F.parsed=D.$headers.map(function(d){return D.parsers&&D.parsers[d]&&D.parsers[d].parsed||b.getData&&"parsed"===b.getData(D.$headerIndexed[d],b.getColumnData(c,D.headers,d),"filter")||a(this).hasClass("filter-parsed")}).get(),m=0;m1&&(z=parseInt(y[0],10)-1,z>=0&&zt;t++)s=d[t]||"",v||(t=m),v=!(!v||!p.length||0!==s.indexOf(p[t]||"")||C.alreadyFiltered.test(s)||/[=\"\|!]/.test(s)||/(>=?\s*-\d)/.test(s)||/(<=?\s*\d)/.test(s)||""!==s&&D.$filters&&D.$filters.eq(t).find("select").length&&!D.$headerIndexed[t].hasClass("filter-match"));for(u=i.not("."+E.filter_filteredRow).length,v&&0===u&&(v=!1),D.debug&&b.log("Filter: Searching through "+(v&&f>u?u:"all")+" rows"),F.anyMatchFlag&&(D.sortLocaleCompare&&(F.anyMatchFilter=b.replaceAccents(F.anyMatchFilter)),E.filter_defaultFilter&&C.iQuery.test(G.defaultAnyFilter)&&(F.anyMatchFilter=b.filter.defaultFilter(F.anyMatchFilter,G.defaultAnyFilter),v=!1),F.iAnyMatchFilter=E.filter_ignoreCase&&D.ignoreCase?F.anyMatchFilter.toLowerCase():F.anyMatchFilter),j=0;f>j;j++)if(A=i[j].className,n=j&&C.child.test(A),!(n||v&&C.filtered.test(A))){if(F.$row=i.eq(j),F.cacheArray=g[j],h=F.cacheArray[D.columns],F.rawArray=h.raw,F.childRowText="",!E.filter_childByColumn){for(A="",o=h.child,t=0;t")>=0)return p;a.isArray(p)?m=p:"object"===a.type(n)&&p&&(m=p(c,d,e))}if(m===!1&&(m=b.filter.getOptions(c,d,e)),m=a.grep(m,function(b,c){return a.inArray(b,m)===c}),j.$headerIndexed[d].hasClass("filter-select-nosort"))return m;for(i=m.length,h=0;i>h;h++)g=m[h],l.push({t:g,p:j.parsers&&j.parsers.length&&j.parsers[d].format(g,c,[],d)||g});for(f=j.textSorter||"",l.sort(function(e,g){var h=e.p.toString(),i=g.p.toString();return a.isFunction(f)?f(h,i,!0,d,c):"object"==typeof f&&f.hasOwnProperty(d)?f[d](h,i,!0,d,c):b.sortNatural?b.sortNatural(h,i):!0}),m=[],i=l.length,h=0;i>h;h++)m.push(l[h].t);return m},getOptions:function(b,c,d){b=a(b)[0];var e,f,g,h,i,j=b.config,k=j.widgetOptions,l=[];for(f=0;fe;e++)h=i.row?i.row[e]:i.normalized[e][j.columns].$row[0],d&&h.className.match(k.filter_filteredRow)||(k.filter_useParsedData||j.parsers[c].parsed||j.$headerIndexed[c].hasClass("filter-parsed")?l.push(""+i.normalized[e][c]):l.push(i.normalized[e][j.columns].raw[c])); +return l},buildSelect:function(d,e,f,g,h){if(d=a(d)[0],e=parseInt(e,10),d.config.cache&&!a.isEmptyObject(d.config.cache)){var i,j,k,l,m,n,o=d.config,p=o.widgetOptions,q=o.$headerIndexed[e],r='",s=o.$table.find("thead").find("select."+c.filter+'[data-column="'+e+'"]').val();if(("undefined"==typeof f||""===f)&&(f=b.filter.getOptionSource(d,e,h)),a.isArray(f)){for(i=0;i=0&&(l=k.split(p.filter_selectSourceSeparator),j=l[0],k=l[1]),r+=""!==f[i]?"":"";f=[]}m=(o.$filters?o.$filters:o.$table.children("thead")).find("."+c.filter),p.filter_$externalFilters&&(m=m&&m.length?m.add(p.filter_$externalFilters):p.filter_$externalFilters),n=m.filter('select[data-column="'+e+'"]'),n.length&&(n[g?"html":"append"](r),a.isArray(f)||n.append(f).val(s),n.val(s))}},buildDefault:function(a,c){var d,e,f,g=a.config,h=g.widgetOptions,i=g.columns;for(d=0;i>d;d++)e=g.$headerIndexed[d],f=!(e.hasClass("filter-false")||e.hasClass("parser-false")),(e.hasClass("filter-select")||b.getColumnData(a,h.filter_functions,d)===!0)&&f&&b.filter.buildSelect(a,d,"",c,e.hasClass(h.filter_onlyAvail))}},b.getFilters=function(d,e,f,g){var h,i,j,k,l=!1,m=d?a(d)[0].config:"",n=m?m.widgetOptions:"";if(e!==!0&&n&&!n.filter_columnFilters||a.isArray(f)&&f.join("")===m.lastCombinedFilter)return a(d).data("lastSearch");if(m&&(m.$filters&&(i=m.$filters.find("."+c.filter)),n.filter_$externalFilters&&(i=i&&i.length?i.add(n.filter_$externalFilters):n.filter_$externalFilters),i&&i.length))for(l=f||[],h=0;h1&&(j=j.slice(1)),h===m.columns&&(k=j.filter(n.filter_anyColumnSelector),j=k.length?k:j),j.val(f[h]).trigger("change.tsfilter")):(l[h]=j.val()||"",h===m.columns?j.slice(1).filter('[data-column*="'+j.attr("data-column")+'"]').val(l[h]):j.slice(1).val(l[h])),h===m.columns&&j.length&&(n.filter_$anyMatch=j));return 0===l.length&&(l=!1),l},b.setFilters=function(c,d,e,f){var g=c?a(c)[0].config:"",h=b.getFilters(c,!0,d,f);return g&&e&&(g.lastCombinedFilter=null,g.lastSearch=[],b.filter.searching(g.table,d,f),g.$table.trigger("filterFomatterUpdate")),!!h}}(jQuery),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{sticky:"tablesorter-stickyHeader",stickyVis:"tablesorter-sticky-visible",stickyHide:"tablesorter-sticky-hidden",stickyWrap:"tablesorter-sticky-wrapper"}),c.addHeaderResizeEvent=function(b,c,d){if(b=a(b)[0],b.config){var e={timer:250},f=a.extend({},e,d),g=b.config,h=g.widgetOptions,i=function(a){var b,c,d,e,f,i,j=g.$headers.length;for(h.resize_flag=!0,c=[],b=0;j>b;b++)d=g.$headers.eq(b),e=d.data("savedSizes")||[0,0],f=d[0].offsetWidth,i=d[0].offsetHeight,(f!==e[0]||i!==e[1])&&(d.data("savedSizes",[f,i]),c.push(d[0]));c.length&&a!==!1&&g.$table.trigger("resize",[c]),h.resize_flag=!1};return i(!1),clearInterval(h.resize_timer),c?(h.resize_flag=!1,!1):void(h.resize_timer=setInterval(function(){h.resize_flag||i()},f.timer))}},c.addWidget({id:"stickyHeaders",priority:60,options:{stickyHeaders:"",stickyHeaders_attachTo:null,stickyHeaders_xScroll:null,stickyHeaders_yScroll:null,stickyHeaders_offset:0,stickyHeaders_filteredToTop:!0,stickyHeaders_cloneId:"-sticky",stickyHeaders_addResizeEvent:!0,stickyHeaders_includeCaption:!0,stickyHeaders_zIndex:2},format:function(d,e,f){if(!(e.$table.hasClass("hasStickyHeaders")||a.inArray("filter",e.widgets)>=0&&!e.$table.hasClass("hasFilters"))){var g,h,i,j,k=e.$table,l=a(f.stickyHeaders_attachTo),m=e.namespace+"stickyheaders ",n=a(f.stickyHeaders_yScroll||f.stickyHeaders_attachTo||b),o=a(f.stickyHeaders_xScroll||f.stickyHeaders_attachTo||b),p=k.children("thead:first"),q=p.children("tr").not(".sticky-false").children(),r=k.children("tfoot"),s=isNaN(f.stickyHeaders_offset)?a(f.stickyHeaders_offset):"",t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,u=k.parent().closest("."+c.css.table).hasClass("hasStickyHeaders")?k.parent().closest("table.tablesorter")[0].config.widgetOptions.$sticky.parent():[],v=u.length?u.height():0,w=f.$sticky=k.clone().addClass("containsStickyHeaders "+c.css.sticky+" "+f.stickyHeaders+" "+e.namespace.slice(1)+"_extra_table").wrap('
'),x=w.parent().addClass(c.css.stickyHide).css({position:l.length?"absolute":"fixed",padding:parseInt(w.parent().parent().css("padding-left"),10),top:t+v,left:0,visibility:"hidden",zIndex:f.stickyHeaders_zIndex||2}),y=w.children("thead:first"),z="",A=0,B=function(a,c){var d,e,f,g,h,i=a.filter(":visible"),j=i.length;for(d=0;j>d;d++)g=c.filter(":visible").eq(d),h=i.eq(d),"border-box"===h.css("box-sizing")?e=h.outerWidth():"collapse"===g.css("border-collapse")?b.getComputedStyle?e=parseFloat(b.getComputedStyle(h[0],null).width):(f=parseFloat(h.css("border-width")),e=h.outerWidth()-parseFloat(h.css("padding-left"))-parseFloat(h.css("padding-right"))-f):e=h.width(),g.css({width:e,"min-width":e,"max-width":e})},C=function(){t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,A=0,x.css({left:l.length?parseInt(l.css("padding-left"),10)||0:k.offset().left-parseInt(k.css("margin-left"),10)-o.scrollLeft()-A,width:k.outerWidth()}),B(k,w),B(q,j)},D=function(b){if(k.is(":visible")){v=u.length?u.offset().top-n.scrollTop()+u.height():0;var d=k.offset(),e=a.isWindow(n[0]),f=a.isWindow(o[0]),g=(l.length?e?n.scrollTop():n.offset().top:n.scrollTop())+t+v,h=k.height()-(x.height()+(r.height()||0)),i=g>d.top&&gg;g++)e.onRenderHeader.apply(i.eq(g),[g,e,w]);o.add(n).unbind("scroll resize ".split(" ").join(m).replace(/\s+/g," ")).bind("scroll resize ".split(" ").join(m),function(a){D("resize"===a.type)}),e.$table.unbind("stickyHeadersUpdate"+m).bind("stickyHeadersUpdate"+m,function(){D(!0)}),f.stickyHeaders_addResizeEvent&&c.addHeaderResizeEvent(d),k.hasClass("hasFilters")&&f.filter_columnFilters&&(k.bind("filterEnd"+m,function(){var d=a(document.activeElement).closest("td"),g=d.parent().children().index(d);x.hasClass(c.css.stickyVis)&&f.stickyHeaders_filteredToTop&&(b.scrollTo(0,k.position().top),g>=0&&e.$filters&&e.$filters.eq(g).find("a, select, input").filter(":visible").focus())}),c.filter.bindSearch(k,j.find("."+c.css.filter)),f.filter_hideFilters&&c.filter.hideFilters(w,e)),k.trigger("stickyHeadersInit")}},remove:function(d,e,f){var g=e.namespace+"stickyheaders ";e.$table.removeClass("hasStickyHeaders").unbind("pagerComplete filterEnd stickyHeadersUpdate ".split(" ").join(g).replace(/\s+/g," ")).next("."+c.css.stickyWrap).remove(),f.$sticky&&f.$sticky.length&&f.$sticky.remove(),a(b).add(f.stickyHeaders_xScroll).add(f.stickyHeaders_yScroll).add(f.stickyHeaders_attachTo).unbind("scroll resize ".split(" ").join(g).replace(/\s+/g," ")),c.addHeaderResizeEvent(d,!1)}})}(jQuery,window),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{resizableContainer:"tablesorter-resizable-container",resizableHandle:"tablesorter-resizable-handle",resizableNoSelect:"tablesorter-disableSelection",resizableStorage:"tablesorter-resizable"}),a(function(){var b="";a(b).appendTo("body")}),c.resizable={init:function(b,d){if(!b.$table.hasClass("hasResizable")){b.$table.addClass("hasResizable");var e,f,g,h,i,j=b.$table,k=j.parent(),l=parseInt(j.css("margin-top"),10),m=d.resizable_vars={useStorage:c.storage&&d.resizable!==!1,$wrap:k,mouseXPosition:0,$target:null,$next:null,overflow:"auto"===k.css("overflow")||"scroll"===k.css("overflow")||"auto"===k.css("overflow-x")||"scroll"===k.css("overflow-x"),storedSizes:[]};for(c.resizableReset(b.table,!0),m.tableWidth=j.width(),m.fullWidth=Math.abs(k.width()-m.tableWidth)<20,m.useStorage&&m.overflow&&(c.storage(b.table,"tablesorter-table-original-css-width",m.tableWidth),i=c.storage(b.table,"tablesorter-table-resized-width")||"auto",c.resizable.setWidth(j,i,!0)),d.resizable_vars.storedSizes=h=(m.useStorage?c.storage(b.table,c.css.resizableStorage):[])||[],c.resizable.setWidths(b,d,h),c.resizable.updateStoredSizes(b,d),d.$resizable_container=a('
').css({top:l}).insertBefore(j),g=0;g').appendTo(d.$resizable_container).attr({"data-column":g,unselectable:"on"}).data("header",f).bind("selectstart",!1);j.one("tablesorter-initialized",function(){c.resizable.setHandlePosition(b,d),c.resizable.bindings(this.config,this.config.widgetOptions)})}},updateStoredSizes:function(a,b){var c,d,e=a.columns,f=b.resizable_vars;for(f.storedSizes=[],c=0;e>c;c++)d=a.$headerIndexed[c],f.storedSizes[c]=d.is(":visible")?d.width():0},setWidth:function(a,b,c){a.css({width:b,"min-width":c?b:"","max-width":c?b:""})},setWidths:function(b,d,e){var f,g,h=d.resizable_vars,i=a(b.namespace+"_extra_headers"),j=b.$table.children("colgroup").children("col");if(e=e||h.storedSizes||[],e.length){for(f=0;ff||f===h&&d.resizable_addLastColumn)&&c.css({display:"inline-block",height:g,left:j.position().left-e+j.outerWidth()-i}):c.hide())})},toggleTextSelection:function(b,d){var e=b.namespace+"tsresize";b.widgetOptions.resizable_vars.disabled=d,a("body").toggleClass(c.css.resizableNoSelect,d),d?a("body").attr("unselectable","on").bind("selectstart"+e,!1):a("body").removeAttr("unselectable").unbind("selectstart"+e)},bindings:function(d,e){var f=d.namespace+"tsresize";e.$resizable_container.children().bind("mousedown",function(b){var f,g=e.resizable_vars,h=a(d.namespace+"_extra_headers"),i=a(b.target).data("header");f=parseInt(i.attr("data-column"),10),g.$target=i=i.add(h.filter('[data-column="'+f+'"]')),g.target=f,g.$next=b.shiftKey||e.resizable_targetLast?i.parent().children().not(".resizable-false").filter(":last"):i.nextAll(":not(.resizable-false)").eq(0),f=parseInt(g.$next.attr("data-column"),10),g.$next=g.$next.add(h.filter('[data-column="'+f+'"]')),g.next=f,g.mouseXPosition=b.pageX,c.resizable.updateStoredSizes(d,e),c.resizable.toggleTextSelection(d,!0)}),a(document).bind("mousemove"+f,function(a){var b=e.resizable_vars;b.disabled&&0!==b.mouseXPosition&&b.$target&&(e.resizable_throttle?(clearTimeout(b.timer),b.timer=setTimeout(function(){c.resizable.mouseMove(d,e,a)},isNaN(e.resizable_throttle)?5:e.resizable_throttle)):c.resizable.mouseMove(d,e,a))}).bind("mouseup"+f,function(){e.resizable_vars.disabled&&(c.resizable.toggleTextSelection(d,!1),c.resizable.stopResize(d,e),c.resizable.setHandlePosition(d,e))}),a(b).bind("resize"+f+" resizeEnd"+f,function(){c.resizable.setHandlePosition(d,e)}),d.$table.bind("columnUpdate"+f,function(){c.resizable.setHandlePosition(d,e)}).find("thead:first").add(a(d.namespace+"_extra_table").find("thead:first")).bind("contextmenu"+f,function(){var a=0===e.resizable_vars.storedSizes.length;return c.resizableReset(d.table),c.resizable.setHandlePosition(d,e),e.resizable_vars.storedSizes=[],a})},mouseMove:function(b,d,e){if(0!==d.resizable_vars.mouseXPosition&&d.resizable_vars.$target){var f,g=0,h=d.resizable_vars,i=h.$next,j=h.storedSizes[h.target],k=e.pageX-h.mouseXPosition;if(h.overflow){if(j+k>0){for(h.storedSizes[h.target]+=k,c.resizable.setWidth(h.$target,h.storedSizes[h.target],!0),f=0;f0?d.sortList=k:c.hasInitialized&&k&&k.length>0&&i.trigger("sorton",[k]))},remove:function(a,c){c.$table.removeClass("hasSaveSort"),b.storage&&b.storage(a,"tablesorter-savesort","")}})}(jQuery),a.tablesorter}); \ No newline at end of file diff --git a/dist/js/jquery.tablesorter.js b/dist/js/jquery.tablesorter.js index 25581f29..53f85954 100644 --- a/dist/js/jquery.tablesorter.js +++ b/dist/js/jquery.tablesorter.js @@ -198,7 +198,7 @@ // node could be a jquery object // http://jsperf.com/jquery-vs-instanceof-jquery/2 $node = node.jquery ? node : $(node); - if (typeof(t) === 'string') { + if (typeof t === 'string') { // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow! // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/ if ( t === 'basic' && typeof ( te = $node.attr(c.textAttribute) ) !== 'undefined' ) { @@ -206,7 +206,7 @@ } return $.trim( node.textContent || $node.text() ); } else { - if (typeof(t) === 'function') { + if (typeof t === 'function') { return $.trim( t($node[0], c.table, cellIndex) ); } else if (typeof (te = ts.getColumnData( c.table, t, cellIndex )) === 'function') { return $.trim( te($node[0], c.table, cellIndex) ); @@ -266,7 +266,7 @@ // make sure txt is a string (extractor may have converted it) parser.format( '' + txt, c.table, cell, colIndex ); if ( c.ignoreCase && typeof val === 'string' ) { - val = val.toLowerCase(); + val = val.toLowerCase(); } } return val; @@ -510,20 +510,22 @@ // set up header template t = c.headerTemplate.replace(/\{content\}/g, $t.html()).replace(/\{icon\}/g, $t.find('.' + ts.css.icon).length ? '' : i); if (c.onRenderTemplate) { - h = c.onRenderTemplate.apply($t, [index, t]); + h = c.onRenderTemplate.apply( $t, [ index, t ] ); if (h && typeof h === 'string') { t = h; } // only change t if something is returned } $t.html('
' + t + '
'); // faster than wrapInner } - if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index, c, c.$table]); } + if (c.onRenderHeader) { c.onRenderHeader.apply( $t, [ index, c, c.$table ] ); } // *** remove this.column value if no conflicts found elem.column = parseInt( $t.attr('data-column'), 10); - elem.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2]; + elem.order = formatSortingOrder( ts.getData( $t, ch, 'sortInitialOrder' ) || c.sortInitialOrder ) ? + [ 1, 0, 2 ] : // desc, asc, unsorted + [ 0, 1, 2 ]; // asc, desc, unsorted elem.count = -1; // set to -1 because clicking on the header automatically adds one elem.lockedOrder = false; lock = ts.getData($t, ch, 'lockedOrder') || false; if (typeof lock !== 'undefined' && lock !== false) { - elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0]; + elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [ 1, 1, 1 ] : [ 0, 0, 0 ]; } $t.addClass(ts.css.header + ' ' + c.cssHeader); // add cell to headerList @@ -593,9 +595,9 @@ list = c.sortList, len = list.length, none = ts.css.sortNone + ' ' + c.cssNone, - css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc], + css = [ ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc ], cssIcon = [ c.cssIconAsc, c.cssIconDesc, c.cssIconNone ], - aria = ['ascending', 'descending'], + aria = [ 'ascending', 'descending' ], // find the footer $t = $(table).find('tfoot tr').children() .add( $( c.namespace + '_extra_headers' ) ) @@ -665,7 +667,7 @@ dir = ('' + val[1]).match(/^(1|d|s|o|n)/); dir = dir ? dir[0] : ''; // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext - switch(dir) { + switch (dir) { case '1': case 'd': // descending dir = 1; break; @@ -741,11 +743,11 @@ // add column to sort list order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -756,7 +758,7 @@ for (col = 0; col < c.sortAppend.length; col++) { s = ts.isValueInArray(c.sortAppend[col][0], c.sortList); if (s >= 0) { - c.sortList.splice(s,1); + c.sortList.splice(s, 1); } } } @@ -770,7 +772,7 @@ // order.count seems to be incorrect when compared to cell.count s[1] = order.order[cell.count]; if (s[1] === 2) { - c.sortList.splice(col,1); + c.sortList.splice(col, 1); order.count = -1; } } @@ -779,11 +781,11 @@ // add column to sort list array order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -857,10 +859,10 @@ x = dir ? a : b; y = dir ? b : a; // text sort function - if (typeof(cts) === 'function') { + if (typeof cts === 'function') { // custom OVERALL text sorter sort = cts(x[col], y[col], dir, col, table); - } else if (typeof(cts) === 'object' && cts.hasOwnProperty(col)) { + } else if (typeof cts === 'object' && cts.hasOwnProperty(col)) { // custom text sorter for a SPECIFIC COLUMN sort = cts[col](x[col], y[col], dir, col, table); } else { @@ -893,14 +895,14 @@ // this will catch spamming of the updateCell method if (resrt !== false && !c.serverSideSorting && !c.table.isProcessing) { if (sl.length) { - c.$table.trigger('sorton', [sl, function(){ + c.$table.trigger('sorton', [ sl, function(){ resortComplete(c, callback); - }, true]); + }, true ]); } else { - c.$table.trigger('sortReset', [function(){ + c.$table.trigger('sortReset', [ function(){ resortComplete(c, callback); ts.applyWidget(c.table, false); - }]); + } ]); } } else { resortComplete(c, callback); @@ -1100,10 +1102,10 @@ ts.construct = function(settings) { return this.each(function() { var table = this, - // merge & extend config options - c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); - // save initial settings - c.originalSettings = settings; + // merge & extend config options + c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); + // save initial settings + c.originalSettings = settings; // create a table from data (build table widget) if (!table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE') { // return the table (in case the original target is the table's container) @@ -1158,7 +1160,7 @@ c.namespace = '.tablesorter' + Math.random().toString(16).slice(2); } else { // make sure namespace starts with a period & doesn't have weird characters - c.namespace = '.' + c.namespace.replace(/\W/g,''); + c.namespace = '.' + c.namespace.replace(/\W/g, ''); } c.$table.children().children('tr').attr('role', 'row'); @@ -1202,7 +1204,7 @@ ts.applyWidget(table, true); // if user has supplied a sort list to constructor if (c.sortList.length > 0) { - $table.trigger('sorton', [c.sortList, {}, !c.initWidgets, true]); + $table.trigger('sorton', [ c.sortList, {}, !c.initWidgets, true ]); } else { setHeadersCss(table); if (c.initWidgets) { @@ -1241,22 +1243,22 @@ table = $(table)[0]; var overallWidth, percent, $tbodies, len, index, c = table.config, - colgroup = c.$table.children('colgroup'); + $colgroup = c.$table.children('colgroup'); // remove plugin-added colgroup, in case we need to refresh the widths - if (colgroup.length && colgroup.hasClass(ts.css.colgroup)) { - colgroup.remove(); + if ($colgroup.length && $colgroup.hasClass(ts.css.colgroup)) { + $colgroup.remove(); } if (c.widthFixed && c.$table.children('colgroup').length === 0) { - colgroup = $(''); + $colgroup = $(''); overallWidth = c.$table.width(); // only add col for visible columns - fixes #371 - $tbodies = c.$tbodies.find('tr:first').children(':visible'); //.each(function() + $tbodies = c.$tbodies.find('tr:first').children(':visible'); // .each(function() len = $tbodies.length; for ( index = 0; index < len; index++ ) { percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%'; - colgroup.append( $('').css('width', percent) ); + $colgroup.append( $('').css('width', percent) ); } - c.$table.prepend(colgroup); + c.$table.prepend($colgroup); } }; @@ -1303,12 +1305,12 @@ cellId = rowIndex + '-' + $cell.index(); rowSpan = cell.rowSpan || 1; colSpan = cell.colSpan || 1; - if (typeof(matrix[rowIndex]) === 'undefined') { + 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') { + if (typeof matrix[rowIndex][k] === 'undefined') { firstAvailCol = k; break; } @@ -1317,7 +1319,7 @@ // add data-column $cell.attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex for (k = rowIndex; k < rowIndex + rowSpan; k++) { - if (typeof(matrix[k]) === 'undefined') { + if (typeof matrix[k] === 'undefined') { matrix[k] = []; } matrixrow = matrix[k]; @@ -1333,10 +1335,10 @@ // *** Process table *** // add processing indicator ts.isProcessing = function(table, toggle, $ths) { - table = $(table); - var c = table[0].config, + $table = $(table); + var c = $table[0].config, // default to all headers - $h = $ths || table.find('.' + ts.css.header); + $h = $ths || $table.find('.' + ts.css.header); if (toggle) { // don't use sortList if custom $ths used if (typeof $ths !== 'undefined' && c.sortList.length > 0) { @@ -1346,9 +1348,9 @@ return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList) >= 0; }); } - table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); } else { - table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); } }; @@ -1478,8 +1480,8 @@ $f = $t.find('tfoot:first > tr').children('th, td'); if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) { // reapply uitheme classes, in case we want to maintain appearance - $t.trigger('applyWidgetId', ['uitheme']); - $t.trigger('applyWidgetId', ['zebra']); + $t.trigger('applyWidgetId', [ 'uitheme' ]); + $t.trigger('applyWidgetId', [ 'zebra' ]); } // remove widget added rows, just in case $h.find('tr').not($r).remove(); @@ -1491,7 +1493,7 @@ .removeData('tablesorter') .unbind( events.replace(/\s+/g, ' ') ); c.$headers.add($f) - .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') ) + .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join(' ') ) .removeAttr('data-column') .removeAttr('aria-label') .attr('aria-disabled', 'true'); @@ -1697,8 +1699,8 @@ }; ts.hasWidget = function(table, name){ - table = $(table); - return table.length && table[0].config && table[0].config.widgetInit[name] || false; + $table = $(table); + return $table.length && $table[0].config && $table[0].config.widgetInit[name] || false; }; ts.getWidgetById = function(name) { @@ -1738,7 +1740,7 @@ if (c.debug) { time = new Date(); } // look for widgets to apply from in table class // stop using \b otherwise this matches 'ui-widget-content' & adds 'content' widget - wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' )+ '\\s', 'g' ); + wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' ) + '\\s', 'g' ); if ( tableClass.match( wd ) ) { // extract out the widget id from the table class (widget id's can include dashes) w = tableClass.match( wd ); @@ -1946,13 +1948,13 @@ typeof table !== 'undefined' ? table : true; if (t) { // US Format - 1,234,567.89 -> 1234567.89 - s = s.replace(/,/g,''); + 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,'.'); + s = s.replace(/[\s|\.]/g, '').replace(/,/g, '.'); } - if(/^\s*\([.\d]+\)/.test(s)) { + if (/^\s*\([.\d]+\)/.test(s)) { // make (#) into a negative number -> (10) = -10 s = s.replace(/^\s*\(([.\d]+)\)/, '-$1'); } @@ -2020,7 +2022,7 @@ ts.addParser({ id: 'currency', is: function(s) { - return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g,'')); // £$€¤¥¢ + return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g, '')); // £$€¤¥¢ }, format: function(s, table) { var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table); @@ -2095,7 +2097,7 @@ id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd' is: function(s) { // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included - return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g,' ').replace(/[\-.,]/g, '/')); + return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g, ' ').replace(/[\-.,]/g, '/')); }, format: function(s, table, cell, cellIndex) { if (s) { diff --git a/dist/js/jquery.tablesorter.min.js b/dist/js/jquery.tablesorter.min.js index da142e13..5ae9decb 100644 --- a/dist/js/jquery.tablesorter.min.js +++ b/dist/js/jquery.tablesorter.min.js @@ -1,2 +1,2 @@ -!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&"object"==typeof module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(a){"use strict";a.extend({tablesorter:new function(){function b(){var a=arguments[0],b=arguments.length>1?Array.prototype.slice.call(arguments):a;"undefined"!=typeof console&&"undefined"!=typeof console.log?console[/error/i.test(a)?"error":/warn/i.test(a)?"warn":"log"](b):alert(b)}function c(a,c){b(a+" ("+((new Date).getTime()-c.getTime())+"ms)")}function d(a){for(var b in a)return!1;return!0}function e(c,d,e,f){for(var g,h,i=u.parsers.length,j=!1,k="",l=!0;""===k&&l;)e++,d[e]?(j=d[e].cells[f],k=u.getElementText(c,j,f),h=a(j),c.debug&&b("Checking if value was empty on row "+e+", column: "+f+': "'+k+'"')):l=!1;for(;--i>=0;)if(g=u.parsers[i],g&&"text"!==g.id&&g.is&&g.is(k,c.table,j,h))return g;return u.getParserById("text")}function f(a,d){var f,g,h,i,j,k,l,m,n,o,p,q,r=a.table,s=0,t="";if(a.$tbodies=a.$table.children("tbody:not(."+a.cssInfoBlock+")"),p="undefined"==typeof d?a.$tbodies:d,q=p.length,0===q)return a.debug?b("Warning: *Empty table!* Not building a parser cache"):"";for(a.debug&&(o=new Date,b("Detecting parsers for each column")),g={extractors:[],parsers:[]};q>s;){if(f=p[s].rows,f.length)for(h=a.columns,i=0;h>i;i++)j=a.$headerIndexed[i],k=u.getColumnData(r,a.headers,i),n=u.getParserById(u.getData(j,k,"extractor")),m=u.getParserById(u.getData(j,k,"sorter")),l="false"===u.getData(j,k,"parser"),a.empties[i]=(u.getData(j,k,"empty")||a.emptyTo||(a.emptyToBottom?"bottom":"top")).toLowerCase(),a.strings[i]=(u.getData(j,k,"string")||a.stringTo||"max").toLowerCase(),l&&(m=u.getParserById("no-parser")),n||(n=!1),m||(m=e(a,f,-1,i)),a.debug&&(t+="column:"+i+"; extractor:"+n.id+"; parser:"+m.id+"; string:"+a.strings[i]+"; empty: "+a.empties[i]+"\n"),g.parsers[i]=m,g.extractors[i]=n;s+=g.parsers.length?q:1}a.debug&&(b(t?t:"No parsers detected"),c("Completed detecting parsers",o)),a.parsers=g.parsers,a.extractors=g.extractors}function g(d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t=d.config,v=t.parsers;if(t.$tbodies=t.$table.children("tbody:not(."+t.cssInfoBlock+")"),l="undefined"==typeof e?t.$tbodies:e,t.cache={},t.totalRows=0,!v)return t.debug?b("Warning: *Empty table!* Not building a cache"):"";for(t.debug&&(o=new Date),t.showProcessing&&u.isProcessing(d,!0),k=0;ki;++i)if(q={child:[],raw:[]},m=a(l[k].rows[i]),n=[],m.hasClass(t.cssChildRow)&&0!==i)for(g=f.normalized.length-1,r=f.normalized[g][t.columns],r.$row=r.$row.add(m),m.prev().hasClass(t.cssChildRow)||m.prev().addClass(u.css.cssHasChild),h=m.children("th, td"),g=r.child.length,r.child[g]=[],j=0;ji;i++)o.push(e[i][l.columns].$row),l.appender&&(!l.pager||l.pager.removeRows&&m.pager_removeRows||l.pager.ajax)||h.append(e[i][l.columns].$row);u.processTbody(a,h,!1)}l.appender&&l.appender(a,o),l.debug&&c("Rebuilt table",k),b||l.appender||u.applyWidget(a),a.isUpdating&&l.$table.trigger("updateComplete",a)}function i(a){return/^d/i.test(a)||1===a}function j(d){var e,f,g,h,j,k,m,n,o=d.config;for(o.headerList=[],o.headerContent=[],o.debug&&(m=new Date),o.columns=u.computeColumnIndex(o.$table.children("thead, tfoot").children("tr")),h=o.cssIcon?'':"",o.$headers=a(a.map(a(d).find(o.selectorHeaders),function(b,c){return f=a(b),f.parent().hasClass(o.cssIgnoreRow)?void 0:(e=u.getColumnData(d,o.headers,c,!0),o.headerContent[c]=f.html(),""===o.headerTemplate||f.find("."+u.css.headerIn).length||(j=o.headerTemplate.replace(/\{content\}/g,f.html()).replace(/\{icon\}/g,f.find("."+u.css.icon).length?"":h),o.onRenderTemplate&&(g=o.onRenderTemplate.apply(f,[c,j]),g&&"string"==typeof g&&(j=g)),f.html('
'+j+"
")),o.onRenderHeader&&o.onRenderHeader.apply(f,[c,o,o.$table]),b.column=parseInt(f.attr("data-column"),10),b.order=i(u.getData(f,e,"sortInitialOrder")||o.sortInitialOrder)?[1,0,2]:[0,1,2],b.count=-1,b.lockedOrder=!1,k=u.getData(f,e,"lockedOrder")||!1,"undefined"!=typeof k&&k!==!1&&(b.order=b.lockedOrder=i(k)?[1,1,1]:[0,0,0]),f.addClass(u.css.header+" "+o.cssHeader),o.headerList[c]=b,f.parent().addClass(u.css.headerRow+" "+o.cssHeaderRow).attr("role","row"),o.tabIndex&&f.attr("tabindex",0),b)})),o.$headerIndexed=[],n=0;nb;b++)d=f.$headers.eq(b),e=u.getColumnData(a,f.headers,b,!0),c="false"===u.getData(d,e,"sorter")||"false"===u.getData(d,e,"parser"),d[0].sortDisabled=c,d[c?"addClass":"removeClass"]("sorter-false").attr("aria-disabled",""+c),a.id&&(c?d.removeAttr("aria-controls"):d.attr("aria-controls",a.id))}function m(b){var c,d,e,f,g,h,i,j,k=b.config,l=k.sortList,m=l.length,n=u.css.sortNone+" "+k.cssNone,o=[u.css.sortAsc+" "+k.cssAsc,u.css.sortDesc+" "+k.cssDesc],p=[k.cssIconAsc,k.cssIconDesc,k.cssIconNone],q=["ascending","descending"],r=a(b).find("tfoot tr").children().add(a(k.namespace+"_extra_headers")).removeClass(o.join(" "));for(k.$headers.removeClass(o.join(" ")).addClass(n).attr("aria-sort","none").find("."+u.css.icon).removeClass(p.join(" ")).addClass(p[2]),e=0;m>e;e++)if(2!==l[e][1]&&(c=k.$headers.not(".sorter-false").filter('[data-column="'+l[e][0]+'"]'+(1===m?":last":"")),c.length)){for(f=0;fe;e++)h=g.eq(e),h.length&&(d=g[e],i=d.order[(d.count+1)%(k.sortReset?3:2)],j=a.trim(h.text())+": "+u.language[h.hasClass(u.css.sortAsc)?"sortAsc":h.hasClass(u.css.sortDesc)?"sortDesc":"sortNone"]+u.language[0===i?"nextAsc":1===i?"nextDesc":"nextNone"],h.attr("aria-label",j))}function n(b,c){var d,e,f,g,h,i,j,k,l=b.config,m=c||l.sortList,n=m.length;for(l.sortList=[],h=0;n>h;h++)if(k=m[h],d=parseInt(k[0],10),d=0?e:f[1]%(l.sortReset?3:2)}}function o(a,b){return a&&a[b]?a[b].type||"":""}function p(b,c,d){if(b.isUpdating)return setTimeout(function(){p(b,c,d)},50);var e,f,g,i,j,k,l,n=b.config,o=!d[n.sortMultiSortKey],r=n.$table,s=n.$headers.length;if(r.trigger("sortStart",b),c.count=d[n.sortResetKey]?2:(c.count+1)%(n.sortReset?3:2),n.sortRestart)for(f=c,g=0;s>g;g++)l=n.$headers.eq(g),l[0]===f||!o&&l.is("."+u.css.sortDesc+",."+u.css.sortAsc)||(l[0].count=-1);if(f=parseInt(a(c).attr("data-column"),10),o){if(n.sortList=[],null!==n.sortForce)for(e=n.sortForce,i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;i1)for(i=0;i=0&&n.sortList.splice(k,1);if(u.isValueInArray(f,n.sortList)>=0)for(i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;ie;e++)i=q.cache[e].colMax,j=q.cache[e].normalized,j.sort(function(c,d){for(b=0;t>b;b++){if(g=s[b][0],k=s[b][1],p=0===k,q.sortStable&&c[g]===d[g]&&1===t)return c[q.columns].order-d[q.columns].order;if(f=/n/i.test(o(q.parsers,g)),f&&q.strings[g]?(f="boolean"==typeof q.string[q.strings[g]]?(p?1:-1)*(q.string[q.strings[g]]?-1:1):q.strings[g]?q.string[q.strings[g]]||0:0,l=q.numberSorter?q.numberSorter(c[g],d[g],p,i[g],a):u["sortNumeric"+(p?"Asc":"Desc")](c[g],d[g],f,i[g],g,a)):(m=p?c:d,n=p?d:c,l="function"==typeof r?r(m[g],n[g],p,g,a):"object"==typeof r&&r.hasOwnProperty(g)?r[g](m[g],n[g],p,g,a):u["sortNatural"+(p?"Asc":"Desc")](c[g],d[g],g,a,q)),l)return l}return c[q.columns].order-d[q.columns].order});q.debug&&c("Sorting on "+s.toString()+" and dir "+k+" time",h)}}function r(b,c){b.table.isUpdating&&b.$table.trigger("updateComplete",b.table),a.isFunction(c)&&c(b.table)}function s(b,c,d){var e=a.isArray(c)?c:b.sortList,f="undefined"==typeof c?b.resort:c;f===!1||b.serverSideSorting||b.table.isProcessing?(r(b,d),u.applyWidget(b.table,!1)):e.length?b.$table.trigger("sorton",[e,function(){r(b,d)},!0]):b.$table.trigger("sortReset",[function(){r(b,d),u.applyWidget(b.table,!1)}])}function t(b){var c=b.config,e=c.$table,i="sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave ".split(" ").join(c.namespace+" ");e.unbind(i.replace(/\s+/g," ")).bind("sortReset"+c.namespace,function(d,e){d.stopPropagation(),c.sortList=[],m(b),q(b),h(b),a.isFunction(e)&&e(b)}).bind("updateAll"+c.namespace,function(a,d,e){a.stopPropagation(),b.isUpdating=!0,u.refreshWidgets(b,!0,!0),j(b),u.bindEvents(b,c.$headers,!0),t(b),k(b,d,e)}).bind("update"+c.namespace+" updateRows"+c.namespace,function(a,c,d){a.stopPropagation(),b.isUpdating=!0,l(b),k(b,c,d)}).bind("updateCell"+c.namespace,function(d,f,g,h){d.stopPropagation(),b.isUpdating=!0,e.find(c.selectorRemove).remove();var i,j,k,l,m=c.$tbodies,n=a(f),o=m.index(a.fn.closest?n.closest("tbody"):n.parents("tbody").filter(":first")),p=c.cache[o],q=a.fn.closest?n.closest("tr"):n.parents("tr").filter(":first");f=n[0],m.length&&o>=0&&(j=m.eq(o).find("tr").index(q),l=p.normalized[j],k=n.index(),i=u.getParsedText(c,f,k),l[k]=i,l[c.columns].$row=q,"numeric"===(c.parsers[k].type||"").toLowerCase()&&(p.colMax[k]=Math.max(Math.abs(i)||0,p.colMax[k]||0)),i="undefined"!==g?g:c.resort,i!==!1?s(c,i,h):(a.isFunction(h)&&h(b),c.$table.trigger("updateComplete",c.table)))}).bind("addRows"+c.namespace,function(e,g,h,i){if(e.stopPropagation(),b.isUpdating=!0,d(c.cache))l(b),k(b,h,i);else{g=a(g).attr("role","row");var j,m,n,o,p,q=g.filter("tr").length,r=c.$tbodies.index(g.parents("tbody").filter(":first"));for(c.parsers&&c.parsers.length||f(c),j=0;q>j;j++){for(n=g[j].cells.length,p=[],o={child:[],$row:g.eq(j),order:c.cache[r].normalized.length},m=0;n>m;m++)p[m]=u.getParsedText(c,g[j].cells[m],m),"numeric"===(c.parsers[m].type||"").toLowerCase()&&(c.cache[r].colMax[m]=Math.max(Math.abs(p[m])||0,c.cache[r].colMax[m]||0));p.push(o),c.cache[r].normalized.push(p)}s(c,h,i)}}).bind("updateComplete"+c.namespace,function(){b.isUpdating=!1}).bind("sorton"+c.namespace,function(c,f,i,j){var k=b.config;c.stopPropagation(),e.trigger("sortStart",this),n(b,f),m(b),k.delayInit&&d(k.cache)&&g(b),e.trigger("sortBegin",this),q(b),h(b,j),e.trigger("sortEnd",this),u.applyWidget(b),a.isFunction(i)&&i(b)}).bind("appendCache"+c.namespace,function(c,d,e){c.stopPropagation(),h(b,e),a.isFunction(d)&&d(b)}).bind("updateCache"+c.namespace,function(d,e,h){c.parsers&&c.parsers.length||f(c,h),g(b,h),a.isFunction(e)&&e(b)}).bind("applyWidgetId"+c.namespace,function(a,d){a.stopPropagation(),u.getWidgetById(d).format(b,c,c.widgetOptions)}).bind("applyWidgets"+c.namespace,function(a,c){a.stopPropagation(),u.applyWidget(b,c)}).bind("refreshWidgets"+c.namespace,function(a,c,d){a.stopPropagation(),u.refreshWidgets(b,c,d)}).bind("destroy"+c.namespace,function(a,c,d){a.stopPropagation(),u.destroy(b,c,d)}).bind("resetToLoadState"+c.namespace,function(){u.removeWidget(b,!0,!1),c=a.extend(!0,u.defaults,c.originalSettings),b.hasInitialized=!1,u.setup(b,c)})}var u=this;u.version="2.22.3",u.parsers=[],u.widgets=[],u.defaults={theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,tabIndex:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,resort:!0,headers:{},ignoreCase:!0,sortForce:null,sortList:[],sortAppend:null,sortStable:!1,sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",textExtraction:"basic",textAttribute:"data-text",textSorter:null,numberSorter:null,widgets:[],widgetOptions:{zebra:["even","odd"]},initWidgets:!0,widgetClass:"widget-{name}",initialized:null,tableClass:"",cssAsc:"",cssDesc:"",cssNone:"",cssHeader:"",cssHeaderRow:"",cssProcessing:"",cssChildRow:"tablesorter-childRow",cssIcon:"tablesorter-icon",cssIconNone:"",cssIconAsc:"",cssIconDesc:"",cssInfoBlock:"tablesorter-infoOnly",cssNoSort:"tablesorter-noSort",cssIgnoreRow:"tablesorter-ignoreRow",pointerClick:"click",pointerDown:"mousedown",pointerUp:"mouseup",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]},u.css={table:"tablesorter",cssHasChild:"tablesorter-hasChildRow",childRow:"tablesorter-childRow",colgroup:"tablesorter-colgroup",header:"tablesorter-header",headerRow:"tablesorter-headerRow",headerIn:"tablesorter-header-inner",icon:"tablesorter-icon",processing:"tablesorter-processing",sortAsc:"tablesorter-headerAsc",sortDesc:"tablesorter-headerDesc",sortNone:"tablesorter-headerUnSorted"},u.language={sortAsc:"Ascending sort applied, ",sortDesc:"Descending sort applied, ",sortNone:"No sort applied, ",nextAsc:"activate to apply an ascending sort",nextDesc:"activate to apply a descending sort",nextNone:"activate to remove the sort"},u.instanceMethods={},u.log=b,u.benchmark=c,u.getElementText=function(b,c,d){if(!c)return"";var e,f=b.textExtraction||"",g=c.jquery?c:a(c);return"string"==typeof f?"basic"===f&&"undefined"!=typeof(e=g.attr(b.textAttribute))?a.trim(e):a.trim(c.textContent||g.text()):"function"==typeof f?a.trim(f(g[0],b.table,d)):"function"==typeof(e=u.getColumnData(b.table,f,d))?a.trim(e(g[0],b.table,d)):a.trim(g[0].textContent||g.text())},u.getParsedText=function(a,b,c,d){"undefined"==typeof d&&(d=u.getElementText(a,b,c));var e=""+d,f=a.parsers[c],g=a.extractors[c];return f&&(g&&"function"==typeof g.format&&(d=g.format(d,a.table,b,c)),e="no-parser"===f.id?"":f.format(""+d,a.table,b,c),a.ignoreCase&&"string"==typeof e&&(e=e.toLowerCase())),e},u.construct=function(b){return this.each(function(){var c=this,d=a.extend(!0,{},u.defaults,b,u.instanceMethods);d.originalSettings=b,!c.hasInitialized&&u.buildTable&&"TABLE"!==this.nodeName?u.buildTable(c,d):u.setup(c,d)})},u.setup=function(c,d){if(!c||!c.tHead||0===c.tBodies.length||c.hasInitialized===!0)return d.debug?b("ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized"):"";var e="",h=a(c),i=a.metadata;c.hasInitialized=!1,c.isProcessing=!0,c.config=d,a.data(c,"tablesorter",d),d.debug&&a.data(c,"startoveralltimer",new Date),d.supportsDataObject=function(a){return a[0]=parseInt(a[0],10),a[0]>1||1===a[0]&&parseInt(a[1],10)>=4}(a.fn.jquery.split(".")),d.string={max:1,min:-1,emptymin:1,emptymax:-1,zero:0,none:0,"null":0,top:!0,bottom:!1},d.emptyTo=d.emptyTo.toLowerCase(),d.stringTo=d.stringTo.toLowerCase(),/tablesorter\-/.test(h.attr("class"))||(e=""!==d.theme?" tablesorter-"+d.theme:""),d.table=c,d.$table=h.addClass(u.css.table+" "+d.tableClass+e).attr("role","grid"),d.$headers=h.find(d.selectorHeaders),d.namespace?d.namespace="."+d.namespace.replace(/\W/g,""):d.namespace=".tablesorter"+Math.random().toString(16).slice(2),d.$table.children().children("tr").attr("role","row"),d.$tbodies=h.children("tbody:not(."+d.cssInfoBlock+")").attr({"aria-live":"polite","aria-relevant":"all"}),d.$table.children("caption").length&&(e=d.$table.children("caption")[0],e.id||(e.id=d.namespace.slice(1)+"caption"),d.$table.attr("aria-labelledby",e.id)),d.widgetInit={},d.textExtraction=d.$table.attr("data-text-extraction")||d.textExtraction||"basic",j(c),u.fixColumnWidth(c),u.applyWidgetOptions(c,d),f(d),d.totalRows=0,d.delayInit||g(c),u.bindEvents(c,d.$headers,!0),t(c),d.supportsDataObject&&"undefined"!=typeof h.data().sortlist?d.sortList=h.data().sortlist:i&&h.metadata()&&h.metadata().sortlist&&(d.sortList=h.metadata().sortlist),u.applyWidget(c,!0),d.sortList.length>0?h.trigger("sorton",[d.sortList,{},!d.initWidgets,!0]):(m(c),d.initWidgets&&u.applyWidget(c,!1)),d.showProcessing&&h.unbind("sortBegin"+d.namespace+" sortEnd"+d.namespace).bind("sortBegin"+d.namespace+" sortEnd"+d.namespace,function(a){clearTimeout(d.processTimer),u.isProcessing(c),"sortBegin"===a.type&&(d.processTimer=setTimeout(function(){u.isProcessing(c,!0)},500))}),c.hasInitialized=!0,c.isProcessing=!1,d.debug&&u.benchmark("Overall initialization time",a.data(c,"startoveralltimer")),h.trigger("tablesorter-initialized",c),"function"==typeof d.initialized&&d.initialized(c)},u.fixColumnWidth=function(b){b=a(b)[0];var c,d,e,f,g,h=b.config,i=h.$table.children("colgroup");if(i.length&&i.hasClass(u.css.colgroup)&&i.remove(),h.widthFixed&&0===h.$table.children("colgroup").length){for(i=a(''),c=h.$table.width(),e=h.$tbodies.find("tr:first").children(":visible"),f=e.length,g=0;f>g;g++)d=parseInt(e.eq(g).width()/c*1e3,10)/10+"%",i.append(a("").css("width",d));h.$table.prepend(i)}},u.getColumnData=function(b,c,d,e,f){if("undefined"!=typeof c&&null!==c){b=a(b)[0];var g,h,i=b.config,j=f||i.$headers,k=i.$headerIndexed&&i.$headerIndexed[d]||j.filter('[data-column="'+d+'"]:last');if(c[d])return e?c[d]:c[j.index(k)];for(h in c)if("string"==typeof h&&(g=k.filter(h).add(k.find(h)),g.length))return c[h]}},u.computeColumnIndex=function(b){var c,d,e,f,g,h,i,j,k,l,m,n,o=[],p=[],q={};for(c=0;ce;e++)for("undefined"==typeof o[e]&&(o[e]=[]),p=o[e],f=n;n+m>f;f++)p[f]="x"}return p.length},u.isProcessing=function(b,c,d){b=a(b);var e=b[0].config,f=d||b.find("."+u.css.header);c?("undefined"!=typeof d&&e.sortList.length>0&&(f=f.filter(function(){return this.sortDisabled?!1:u.isValueInArray(parseFloat(a(this).attr("data-column")),e.sortList)>=0})),b.add(f).addClass(u.css.processing+" "+e.cssProcessing)):b.add(f).removeClass(u.css.processing+" "+e.cssProcessing)},u.processTbody=function(b,c,d){b=a(b)[0];var e;return d?(b.isProcessing=!0,c.before(''),e=a.fn.detach?c.detach():c.remove()):(e=a(b).find("colgroup.tablesorter-savemyplace"),c.insertAfter(e),e.remove(),void(b.isProcessing=!1))},u.clearTableBody=function(b){a(b)[0].config.$tbodies.children().detach()},u.bindEvents=function(b,c,e){b=a(b)[0];var f,h=null,i=b.config;e!==!0&&(c.addClass(i.namespace.slice(1)+"_extra_headers"),f=a.fn.closest?c.closest("table")[0]:c.parents("table")[0],f&&"TABLE"===f.nodeName&&f!==b&&a(f).addClass(i.namespace.slice(1)+"_extra_table")),f=(i.pointerDown+" "+i.pointerUp+" "+i.pointerClick+" sort keyup ").replace(/\s+/g," ").split(" ").join(i.namespace+" "),c.find(i.selectorSort).add(c.filter(i.selectorSort)).unbind(f).bind(f,function(e,f){var j,k,l=a(e.target),m=" "+e.type+" ";if(!(1!==(e.which||e.button)&&!m.match(" "+i.pointerClick+" | sort | keyup ")||" keyup "===m&&13!==e.which||m.match(" "+i.pointerClick+" ")&&"undefined"!=typeof e.which||m.match(" "+i.pointerUp+" ")&&h!==e.target&&f!==!0)){if(m.match(" "+i.pointerDown+" "))return h=e.target,k=l.jquery.split("."),void("1"===k[0]&&k[1]<4&&e.preventDefault());if(h=null,/(input|select|button|textarea)/i.test(e.target.nodeName)||l.hasClass(i.cssNoSort)||l.parents("."+i.cssNoSort).length>0||l.parents("button").length>0)return!i.cancelSelection;i.delayInit&&d(i.cache)&&g(b),j=a.fn.closest?a(this).closest("th, td")[0]:/TH|TD/.test(this.nodeName)?this:a(this).parents("th, td")[0],j=i.$headers[c.index(j)],j.sortDisabled||p(b,j,e)}}),i.cancelSelection&&c.attr("unselectable","on").bind("selectstart",!1).css({"user-select":"none",MozUserSelect:"none"})},u.restoreHeaders=function(b){var c,d,e=a(b)[0].config,f=e.$table.find(e.selectorHeaders),g=f.length;for(c=0;g>c;c++)d=f.eq(c),d.find("."+u.css.headerIn).length&&d.html(e.headerContent[c])},u.destroy=function(b,c,d){if(b=a(b)[0],b.hasInitialized){u.removeWidget(b,!0,!1);var e,f=a(b),g=b.config,h=f.find("thead:first"),i=h.find("tr."+u.css.headerRow).removeClass(u.css.headerRow+" "+g.cssHeaderRow),j=f.find("tfoot:first > tr").children("th, td");c===!1&&a.inArray("uitheme",g.widgets)>=0&&(f.trigger("applyWidgetId",["uitheme"]),f.trigger("applyWidgetId",["zebra"])),h.find("tr").not(i).remove(),e="sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache "+"applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState ".split(" ").join(g.namespace+" "),f.removeData("tablesorter").unbind(e.replace(/\s+/g," ")),g.$headers.add(j).removeClass([u.css.header,g.cssHeader,g.cssAsc,g.cssDesc,u.css.sortAsc,u.css.sortDesc,u.css.sortNone].join(" ")).removeAttr("data-column").removeAttr("aria-label").attr("aria-disabled","true"),i.find(g.selectorSort).unbind("mousedown mouseup keypress ".split(" ").join(g.namespace+" ").replace(/\s+/g," ")),u.restoreHeaders(b),f.toggleClass(u.css.table+" "+g.tableClass+" tablesorter-"+g.theme,c===!1),b.hasInitialized=!1,delete b.config.cache,"function"==typeof d&&d(b)}},u.regex={chunk:/(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,chunks:/(^\\0|\\0$)/,hex:/^0x[0-9a-f]+$/i},u.sortNatural=function(a,b){if(a===b)return 0;var c,d,e,f,g,h,i,j,k=u.regex;if(k.hex.test(b)){if(d=parseInt(a.match(k.hex),16),f=parseInt(b.match(k.hex),16),f>d)return-1;if(d>f)return 1}for(c=a.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),e=b.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),j=Math.max(c.length,e.length),i=0;j>i;i++){if(g=isNaN(c[i])?c[i]||0:parseFloat(c[i])||0,h=isNaN(e[i])?e[i]||0:parseFloat(e[i])||0,isNaN(g)!==isNaN(h))return isNaN(g)?1:-1;if(typeof g!=typeof h&&(g+="",h+=""),h>g)return-1;if(g>h)return 1}return 0},u.sortNaturalAsc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:-f||-1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:f||1:u.sortNatural(a,b)},u.sortNaturalDesc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:f||1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:-f||-1:u.sortNatural(b,a)},u.sortText=function(a,b){return a>b?1:b>a?-1:0},u.getTextValue=function(a,b,c){if(c){var d,e=a?a.length:0,f=c+b;for(d=0;e>d;d++)f+=a.charCodeAt(d);return b*f}return 0},u.sortNumericAsc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:-h||-1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:h||1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),a-b)},u.sortNumericDesc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:h||1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:-h||-1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),b-a)},u.sortNumeric=function(a,b){return a-b},u.characterEquivalents={a:"áàâãäąå",A:"ÁÀÂÃÄĄÅ",c:"çćč",C:"ÇĆČ",e:"éèêëěę",E:"ÉÈÊËĚĘ",i:"íìİîïı",I:"ÍÌİÎÏ",o:"óòôõöō",O:"ÓÒÔÕÖŌ",ss:"ß",SS:"ẞ",u:"úùûüů",U:"ÚÙÛÜŮ"},u.replaceAccents=function(a){var b,c="[",d=u.characterEquivalents;if(!u.characterRegex){u.characterRegexArray={};for(b in d)"string"==typeof b&&(c+=d[b],u.characterRegexArray[b]=new RegExp("["+d[b]+"]","g"));u.characterRegex=new RegExp(c+"]")}if(u.characterRegex.test(a))for(b in d)"string"==typeof b&&(a=a.replace(u.characterRegexArray[b],b));return a},u.isValueInArray=function(a,b){var c,d=b.length;for(c=0;d>c;c++)if(b[c][0]===a)return c;return-1},u.addParser=function(a){var b,c=u.parsers.length,d=!0;for(b=0;c>b;b++)u.parsers[b].id.toLowerCase()===a.id.toLowerCase()&&(d=!1);d&&u.parsers.push(a)},u.addInstanceMethods=function(b){a.extend(u.instanceMethods,b)},u.getParserById=function(a){if("false"==a)return!1;var b,c=u.parsers.length;for(b=0;c>b;b++)if(u.parsers[b].id.toLowerCase()===a.toString().toLowerCase())return u.parsers[b];return!1},u.addWidget=function(a){u.widgets.push(a)},u.hasWidget=function(b,c){return b=a(b),b.length&&b[0].config&&b[0].config.widgetInit[c]||!1},u.getWidgetById=function(a){var b,c,d=u.widgets.length;for(b=0;d>b;b++)if(c=u.widgets[b],c&&c.hasOwnProperty("id")&&c.id.toLowerCase()===a.toLowerCase())return c},u.applyWidgetOptions=function(b,c){var d,e,f=c.widgets.length,g=c.widgetOptions;if(f)for(d=0;f>d;d++)e=u.getWidgetById(c.widgets[d]),e&&"options"in e&&(g=b.config.widgetOptions=a.extend(!0,{},e.options,g))},u.applyWidget=function(b,d,e){b=a(b)[0];var f,g,h,i,j,k,l,m=b.config,n=m.widgetOptions,o=" "+m.table.className+" ",p=[];if(d===!1||!b.hasInitialized||!b.isApplyingWidgets&&!b.isUpdating){if(m.debug&&(i=new Date),l=new RegExp("\\s"+m.widgetClass.replace(/\{name\}/i,"([\\w-]+)")+"\\s","g"),o.match(l)&&(k=o.match(l)))for(g=k.length,f=0;g>f;f++)m.widgets.push(k[f].replace(l,"$1"));if(m.widgets.length){for(b.isApplyingWidgets=!0,m.widgets=a.grep(m.widgets,function(b,c){return a.inArray(b,m.widgets)===c}),h=m.widgets||[],g=h.length,f=0;g>f;f++)l=u.getWidgetById(h[f]),l&&l.id&&(l.priority||(l.priority=10),p[f]=l);for(p.sort(function(a,b){return a.priorityf;f++)p[f]&&((d||!m.widgetInit[p[f].id])&&(m.widgetInit[p[f].id]=!0,b.hasInitialized&&u.applyWidgetOptions(b,m),"init"in p[f]&&(m.debug&&(j=new Date),p[f].init(b,p[f],m,n),m.debug&&u.benchmark("Initializing "+p[f].id+" widget",j))),!d&&"format"in p[f]&&(m.debug&&(j=new Date),p[f].format(b,m,n,!1),m.debug&&u.benchmark((d?"Initializing ":"Applying ")+p[f].id+" widget",j)));d||"function"!=typeof e||e(b)}setTimeout(function(){b.isApplyingWidgets=!1,a.data(b,"lastWidgetApplication",new Date)},0),m.debug&&(k=m.widgets.length,c("Completed "+(d===!0?"initializing ":"applying ")+k+" widget"+(1!==k?"s":""),i))}},u.removeWidget=function(c,d,e){c=a(c)[0];var f,g,h,i,j=c.config;if(d===!0)for(d=[],i=u.widgets.length,h=0;i>h;h++)g=u.widgets[h],g&&g.id&&d.push(g.id);else d=(a.isArray(d)?d.join(","):d||"").toLowerCase().split(/[\s,]+/);for(i=d.length,f=0;i>f;f++)g=u.getWidgetById(d[f]),h=a.inArray(d[f],j.widgets),g&&"remove"in g&&(j.debug&&h>=0&&b('Removing "'+d[f]+'" widget'),g.remove(c,j,j.widgetOptions,e),j.widgetInit[d[f]]=!1),h>=0&&e!==!0&&j.widgets.splice(h,1)},u.refreshWidgets=function(b,c,d){b=a(b)[0];var e,f=b.config,g=f.widgets,h=u.widgets,i=h.length,j=[],k=function(b){a(b).trigger("refreshComplete")};for(e=0;i>e;e++)h[e]&&h[e].id&&(c||a.inArray(h[e].id,g)<0)&&j.push(h[e].id);u.removeWidget(b,j.join(","),!0),d!==!0?(u.applyWidget(b,c||!1,k),c&&u.applyWidget(b,!1,k)):k(b)},u.getColumnText=function(b,c,e){b=a(b)[0];var f,g,h,i,j,k,l,m,n,o,p="function"==typeof e,q="all"===c,r={raw:[],parsed:[],$cell:[]},s=b.config;if(!d(s)){for(j=s.$tbodies.length,f=0;j>f;f++)for(h=s.cache[f].normalized,k=h.length,g=0;k>g;g++)o=!0,i=h[g],m=q?i.slice(0,s.columns):i[c],i=i[s.columns],l=q?i.raw:i.raw[c],n=q?i.$row.children():i.$row.children().eq(c),p&&(o=e({tbodyIndex:f,rowIndex:g,parsed:m,raw:l,$row:i.$row,$cell:n})),o!==!1&&(r.parsed.push(m),r.raw.push(l),r.$cell.push(n));return r}},u.getData=function(b,c,d){var e,f,g="",h=a(b);return h.length?(e=a.metadata?h.metadata():!1,f=" "+(h.attr("class")||""),"undefined"!=typeof h.data(d)||"undefined"!=typeof h.data(d.toLowerCase())?g+=h.data(d)||h.data(d.toLowerCase()):e&&"undefined"!=typeof e[d]?g+=e[d]:c&&"undefined"!=typeof c[d]?g+=c[d]:" "!==f&&f.match(" "+d+"-")&&(g=f.match(new RegExp("\\s"+d+"-([\\w-]+)"))[1]||""),a.trim(g)):""},u.formatFloat=function(b,c){if("string"!=typeof b||""===b)return b;var d,e=c&&c.config?c.config.usNumberFormat!==!1:"undefined"!=typeof c?c:!0;return b=e?b.replace(/,/g,""):b.replace(/[\s|\.]/g,"").replace(/,/g,"."),/^\s*\([.\d]+\)/.test(b)&&(b=b.replace(/^\s*\(([.\d]+)\)/,"-$1")),d=parseFloat(b),isNaN(d)?a.trim(b):d},u.isDigit=function(a){return isNaN(a)?/^[\-+(]?\d+[)]?$/.test(a.toString().replace(/[,.'"\s]/g,"")):""!==a}}});var b=a.tablesorter;a.fn.extend({tablesorter:b.construct}),b.addParser({id:"no-parser",is:function(){return!1},format:function(){return""},type:"text"}),b.addParser({id:"text",is:function(){return!0},format:function(c,d){var e=d.config;return c&&(c=a.trim(e.ignoreCase?c.toLocaleLowerCase():c),c=e.sortLocaleCompare?b.replaceAccents(c):c),c},type:"text"}),b.addParser({id:"digit",is:function(a){return b.isDigit(a)},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"currency",is:function(a){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test((a||"").replace(/[+\-,. ]/g,""))},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"url",is:function(a){return/^(https?|ftp|file):\/\//.test(a)},format:function(b){return b?a.trim(b.replace(/(https?|ftp|file):\/\//,"")):b},parsed:!0,type:"text"}),b.addParser({id:"isoDate",is:function(a){return/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(a)},format:function(a,b){var c=a?new Date(a.replace(/-/g,"/")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"percent",is:function(a){return/(\d\s*?%|%\s*?\d)/.test(a)&&a.length<15},format:function(a,c){return a?b.formatFloat(a.replace(/%/g,""),c):a},type:"numeric"}),b.addParser({id:"image",is:function(a,b,c,d){return d.find("img").length>0},format:function(b,c,d){return a(d).find("img").attr(c.config.imgAttr||"alt")||b},parsed:!0,type:"text"}),b.addParser({id:"usLongDate",is:function(a){ -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(a)||/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(a)},format:function(a,b){var c=a?new Date(a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"shortDate",is:function(a){return/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/.test((a||"").replace(/\s+/g," ").replace(/[\-.,]/g,"/"))},format:function(a,c,d,e){if(a){var f,g,h=c.config,i=h.$headerIndexed[e],j=i.length&&i[0].dateFormat||b.getData(i,b.getColumnData(c,h.headers,e),"dateFormat")||h.dateFormat;return g=a.replace(/\s+/g," ").replace(/[\-.,]/g,"/"),"mmddyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$1/$2"):"ddmmyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$2/$1"):"yyyymmdd"===j&&(g=g.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,"$1/$2/$3")),f=new Date(g),f instanceof Date&&isFinite(f)?f.getTime():a}return a},type:"numeric"}),b.addParser({id:"time",is:function(a){return/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(a)},format:function(a,b){var c=a?new Date("2000/01/01 "+a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"metadata",is:function(){return!1},format:function(b,c,d){var e=c.config,f=e.parserMetadataName?e.parserMetadataName:"sortValue";return a(d).metadata()[f]},type:"numeric"}),b.addWidget({id:"zebra",priority:90,format:function(b,c,d){var e,f,g,h,i,j,k,l,m=new RegExp(c.cssChildRow,"i"),n=c.$tbodies.add(a(c.namespace+"_extra_table").children("tbody:not(."+c.cssInfoBlock+")"));for(c.debug&&(i=new Date),j=0;jk;k++)f=e.eq(k),m.test(f[0].className)||g++,h=g%2===0,f.removeClass(d.zebra[h?1:0]).addClass(d.zebra[h?0:1])},remove:function(a,c,d,e){if(!e){var f,g,h=c.$tbodies,i=(d.zebra||["even","odd"]).join(" ");for(f=0;f1?Array.prototype.slice.call(arguments):a;"undefined"!=typeof console&&"undefined"!=typeof console.log?console[/error/i.test(a)?"error":/warn/i.test(a)?"warn":"log"](b):alert(b)}function c(a,c){b(a+" ("+((new Date).getTime()-c.getTime())+"ms)")}function d(a){for(var b in a)return!1;return!0}function e(c,d,e,f){for(var g,h,i=u.parsers.length,j=!1,k="",l=!0;""===k&&l;)e++,d[e]?(j=d[e].cells[f],k=u.getElementText(c,j,f),h=a(j),c.debug&&b("Checking if value was empty on row "+e+", column: "+f+': "'+k+'"')):l=!1;for(;--i>=0;)if(g=u.parsers[i],g&&"text"!==g.id&&g.is&&g.is(k,c.table,j,h))return g;return u.getParserById("text")}function f(a,d){var f,g,h,i,j,k,l,m,n,o,p,q,r=a.table,s=0,t="";if(a.$tbodies=a.$table.children("tbody:not(."+a.cssInfoBlock+")"),p="undefined"==typeof d?a.$tbodies:d,q=p.length,0===q)return a.debug?b("Warning: *Empty table!* Not building a parser cache"):"";for(a.debug&&(o=new Date,b("Detecting parsers for each column")),g={extractors:[],parsers:[]};q>s;){if(f=p[s].rows,f.length)for(h=a.columns,i=0;h>i;i++)j=a.$headerIndexed[i],k=u.getColumnData(r,a.headers,i),n=u.getParserById(u.getData(j,k,"extractor")),m=u.getParserById(u.getData(j,k,"sorter")),l="false"===u.getData(j,k,"parser"),a.empties[i]=(u.getData(j,k,"empty")||a.emptyTo||(a.emptyToBottom?"bottom":"top")).toLowerCase(),a.strings[i]=(u.getData(j,k,"string")||a.stringTo||"max").toLowerCase(),l&&(m=u.getParserById("no-parser")),n||(n=!1),m||(m=e(a,f,-1,i)),a.debug&&(t+="column:"+i+"; extractor:"+n.id+"; parser:"+m.id+"; string:"+a.strings[i]+"; empty: "+a.empties[i]+"\n"),g.parsers[i]=m,g.extractors[i]=n;s+=g.parsers.length?q:1}a.debug&&(b(t?t:"No parsers detected"),c("Completed detecting parsers",o)),a.parsers=g.parsers,a.extractors=g.extractors}function g(d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t=d.config,v=t.parsers;if(t.$tbodies=t.$table.children("tbody:not(."+t.cssInfoBlock+")"),l="undefined"==typeof e?t.$tbodies:e,t.cache={},t.totalRows=0,!v)return t.debug?b("Warning: *Empty table!* Not building a cache"):"";for(t.debug&&(o=new Date),t.showProcessing&&u.isProcessing(d,!0),k=0;ki;++i)if(q={child:[],raw:[]},m=a(l[k].rows[i]),n=[],m.hasClass(t.cssChildRow)&&0!==i)for(g=f.normalized.length-1,r=f.normalized[g][t.columns],r.$row=r.$row.add(m),m.prev().hasClass(t.cssChildRow)||m.prev().addClass(u.css.cssHasChild),h=m.children("th, td"),g=r.child.length,r.child[g]=[],j=0;ji;i++)o.push(e[i][l.columns].$row),l.appender&&(!l.pager||l.pager.removeRows&&m.pager_removeRows||l.pager.ajax)||h.append(e[i][l.columns].$row);u.processTbody(a,h,!1)}l.appender&&l.appender(a,o),l.debug&&c("Rebuilt table",k),b||l.appender||u.applyWidget(a),a.isUpdating&&l.$table.trigger("updateComplete",a)}function i(a){return/^d/i.test(a)||1===a}function j(d){var e,f,g,h,j,k,m,n,o=d.config;for(o.headerList=[],o.headerContent=[],o.debug&&(m=new Date),o.columns=u.computeColumnIndex(o.$table.children("thead, tfoot").children("tr")),h=o.cssIcon?'':"",o.$headers=a(a.map(a(d).find(o.selectorHeaders),function(b,c){return f=a(b),f.parent().hasClass(o.cssIgnoreRow)?void 0:(e=u.getColumnData(d,o.headers,c,!0),o.headerContent[c]=f.html(),""===o.headerTemplate||f.find("."+u.css.headerIn).length||(j=o.headerTemplate.replace(/\{content\}/g,f.html()).replace(/\{icon\}/g,f.find("."+u.css.icon).length?"":h),o.onRenderTemplate&&(g=o.onRenderTemplate.apply(f,[c,j]),g&&"string"==typeof g&&(j=g)),f.html('
'+j+"
")),o.onRenderHeader&&o.onRenderHeader.apply(f,[c,o,o.$table]),b.column=parseInt(f.attr("data-column"),10),b.order=i(u.getData(f,e,"sortInitialOrder")||o.sortInitialOrder)?[1,0,2]:[0,1,2],b.count=-1,b.lockedOrder=!1,k=u.getData(f,e,"lockedOrder")||!1,"undefined"!=typeof k&&k!==!1&&(b.order=b.lockedOrder=i(k)?[1,1,1]:[0,0,0]),f.addClass(u.css.header+" "+o.cssHeader),o.headerList[c]=b,f.parent().addClass(u.css.headerRow+" "+o.cssHeaderRow).attr("role","row"),o.tabIndex&&f.attr("tabindex",0),b)})),o.$headerIndexed=[],n=0;nb;b++)d=f.$headers.eq(b),e=u.getColumnData(a,f.headers,b,!0),c="false"===u.getData(d,e,"sorter")||"false"===u.getData(d,e,"parser"),d[0].sortDisabled=c,d[c?"addClass":"removeClass"]("sorter-false").attr("aria-disabled",""+c),a.id&&(c?d.removeAttr("aria-controls"):d.attr("aria-controls",a.id))}function m(b){var c,d,e,f,g,h,i,j,k=b.config,l=k.sortList,m=l.length,n=u.css.sortNone+" "+k.cssNone,o=[u.css.sortAsc+" "+k.cssAsc,u.css.sortDesc+" "+k.cssDesc],p=[k.cssIconAsc,k.cssIconDesc,k.cssIconNone],q=["ascending","descending"],r=a(b).find("tfoot tr").children().add(a(k.namespace+"_extra_headers")).removeClass(o.join(" "));for(k.$headers.removeClass(o.join(" ")).addClass(n).attr("aria-sort","none").find("."+u.css.icon).removeClass(p.join(" ")).addClass(p[2]),e=0;m>e;e++)if(2!==l[e][1]&&(c=k.$headers.not(".sorter-false").filter('[data-column="'+l[e][0]+'"]'+(1===m?":last":"")),c.length)){for(f=0;fe;e++)h=g.eq(e),h.length&&(d=g[e],i=d.order[(d.count+1)%(k.sortReset?3:2)],j=a.trim(h.text())+": "+u.language[h.hasClass(u.css.sortAsc)?"sortAsc":h.hasClass(u.css.sortDesc)?"sortDesc":"sortNone"]+u.language[0===i?"nextAsc":1===i?"nextDesc":"nextNone"],h.attr("aria-label",j))}function n(b,c){var d,e,f,g,h,i,j,k,l=b.config,m=c||l.sortList,n=m.length;for(l.sortList=[],h=0;n>h;h++)if(k=m[h],d=parseInt(k[0],10),d=0?e:f[1]%(l.sortReset?3:2)}}function o(a,b){return a&&a[b]?a[b].type||"":""}function p(b,c,d){if(b.isUpdating)return setTimeout(function(){p(b,c,d)},50);var e,f,g,i,j,k,l,n=b.config,o=!d[n.sortMultiSortKey],r=n.$table,s=n.$headers.length;if(r.trigger("sortStart",b),c.count=d[n.sortResetKey]?2:(c.count+1)%(n.sortReset?3:2),n.sortRestart)for(f=c,g=0;s>g;g++)l=n.$headers.eq(g),l[0]===f||!o&&l.is("."+u.css.sortDesc+",."+u.css.sortAsc)||(l[0].count=-1);if(f=parseInt(a(c).attr("data-column"),10),o){if(n.sortList=[],null!==n.sortForce)for(e=n.sortForce,i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;i1)for(i=0;i=0&&n.sortList.splice(k,1);if(u.isValueInArray(f,n.sortList)>=0)for(i=0;ij&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;ie;e++)i=q.cache[e].colMax,j=q.cache[e].normalized,j.sort(function(c,d){for(b=0;t>b;b++){if(g=s[b][0],k=s[b][1],p=0===k,q.sortStable&&c[g]===d[g]&&1===t)return c[q.columns].order-d[q.columns].order;if(f=/n/i.test(o(q.parsers,g)),f&&q.strings[g]?(f="boolean"==typeof q.string[q.strings[g]]?(p?1:-1)*(q.string[q.strings[g]]?-1:1):q.strings[g]?q.string[q.strings[g]]||0:0,l=q.numberSorter?q.numberSorter(c[g],d[g],p,i[g],a):u["sortNumeric"+(p?"Asc":"Desc")](c[g],d[g],f,i[g],g,a)):(m=p?c:d,n=p?d:c,l="function"==typeof r?r(m[g],n[g],p,g,a):"object"==typeof r&&r.hasOwnProperty(g)?r[g](m[g],n[g],p,g,a):u["sortNatural"+(p?"Asc":"Desc")](c[g],d[g],g,a,q)),l)return l}return c[q.columns].order-d[q.columns].order});q.debug&&c("Sorting on "+s.toString()+" and dir "+k+" time",h)}}function r(b,c){b.table.isUpdating&&b.$table.trigger("updateComplete",b.table),a.isFunction(c)&&c(b.table)}function s(b,c,d){var e=a.isArray(c)?c:b.sortList,f="undefined"==typeof c?b.resort:c;f===!1||b.serverSideSorting||b.table.isProcessing?(r(b,d),u.applyWidget(b.table,!1)):e.length?b.$table.trigger("sorton",[e,function(){r(b,d)},!0]):b.$table.trigger("sortReset",[function(){r(b,d),u.applyWidget(b.table,!1)}])}function t(b){var c=b.config,e=c.$table,i="sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave ".split(" ").join(c.namespace+" ");e.unbind(i.replace(/\s+/g," ")).bind("sortReset"+c.namespace,function(d,e){d.stopPropagation(),c.sortList=[],m(b),q(b),h(b),a.isFunction(e)&&e(b)}).bind("updateAll"+c.namespace,function(a,d,e){a.stopPropagation(),b.isUpdating=!0,u.refreshWidgets(b,!0,!0),j(b),u.bindEvents(b,c.$headers,!0),t(b),k(b,d,e)}).bind("update"+c.namespace+" updateRows"+c.namespace,function(a,c,d){a.stopPropagation(),b.isUpdating=!0,l(b),k(b,c,d)}).bind("updateCell"+c.namespace,function(d,f,g,h){d.stopPropagation(),b.isUpdating=!0,e.find(c.selectorRemove).remove();var i,j,k,l,m=c.$tbodies,n=a(f),o=m.index(a.fn.closest?n.closest("tbody"):n.parents("tbody").filter(":first")),p=c.cache[o],q=a.fn.closest?n.closest("tr"):n.parents("tr").filter(":first");f=n[0],m.length&&o>=0&&(j=m.eq(o).find("tr").index(q),l=p.normalized[j],k=n.index(),i=u.getParsedText(c,f,k),l[k]=i,l[c.columns].$row=q,"numeric"===(c.parsers[k].type||"").toLowerCase()&&(p.colMax[k]=Math.max(Math.abs(i)||0,p.colMax[k]||0)),i="undefined"!==g?g:c.resort,i!==!1?s(c,i,h):(a.isFunction(h)&&h(b),c.$table.trigger("updateComplete",c.table)))}).bind("addRows"+c.namespace,function(e,g,h,i){if(e.stopPropagation(),b.isUpdating=!0,d(c.cache))l(b),k(b,h,i);else{g=a(g).attr("role","row");var j,m,n,o,p,q=g.filter("tr").length,r=c.$tbodies.index(g.parents("tbody").filter(":first"));for(c.parsers&&c.parsers.length||f(c),j=0;q>j;j++){for(n=g[j].cells.length,p=[],o={child:[],$row:g.eq(j),order:c.cache[r].normalized.length},m=0;n>m;m++)p[m]=u.getParsedText(c,g[j].cells[m],m),"numeric"===(c.parsers[m].type||"").toLowerCase()&&(c.cache[r].colMax[m]=Math.max(Math.abs(p[m])||0,c.cache[r].colMax[m]||0));p.push(o),c.cache[r].normalized.push(p)}s(c,h,i)}}).bind("updateComplete"+c.namespace,function(){b.isUpdating=!1}).bind("sorton"+c.namespace,function(c,f,i,j){var k=b.config;c.stopPropagation(),e.trigger("sortStart",this),n(b,f),m(b),k.delayInit&&d(k.cache)&&g(b),e.trigger("sortBegin",this),q(b),h(b,j),e.trigger("sortEnd",this),u.applyWidget(b),a.isFunction(i)&&i(b)}).bind("appendCache"+c.namespace,function(c,d,e){c.stopPropagation(),h(b,e),a.isFunction(d)&&d(b)}).bind("updateCache"+c.namespace,function(d,e,h){c.parsers&&c.parsers.length||f(c,h),g(b,h),a.isFunction(e)&&e(b)}).bind("applyWidgetId"+c.namespace,function(a,d){a.stopPropagation(),u.getWidgetById(d).format(b,c,c.widgetOptions)}).bind("applyWidgets"+c.namespace,function(a,c){a.stopPropagation(),u.applyWidget(b,c)}).bind("refreshWidgets"+c.namespace,function(a,c,d){a.stopPropagation(),u.refreshWidgets(b,c,d)}).bind("destroy"+c.namespace,function(a,c,d){a.stopPropagation(),u.destroy(b,c,d)}).bind("resetToLoadState"+c.namespace,function(){u.removeWidget(b,!0,!1),c=a.extend(!0,u.defaults,c.originalSettings),b.hasInitialized=!1,u.setup(b,c)})}var u=this;u.version="2.22.3",u.parsers=[],u.widgets=[],u.defaults={theme:"default",widthFixed:!1,showProcessing:!1,headerTemplate:"{content}",onRenderTemplate:null,onRenderHeader:null,cancelSelection:!0,tabIndex:!0,dateFormat:"mmddyyyy",sortMultiSortKey:"shiftKey",sortResetKey:"ctrlKey",usNumberFormat:!0,delayInit:!1,serverSideSorting:!1,resort:!0,headers:{},ignoreCase:!0,sortForce:null,sortList:[],sortAppend:null,sortStable:!1,sortInitialOrder:"asc",sortLocaleCompare:!1,sortReset:!1,sortRestart:!1,emptyTo:"bottom",stringTo:"max",textExtraction:"basic",textAttribute:"data-text",textSorter:null,numberSorter:null,widgets:[],widgetOptions:{zebra:["even","odd"]},initWidgets:!0,widgetClass:"widget-{name}",initialized:null,tableClass:"",cssAsc:"",cssDesc:"",cssNone:"",cssHeader:"",cssHeaderRow:"",cssProcessing:"",cssChildRow:"tablesorter-childRow",cssIcon:"tablesorter-icon",cssIconNone:"",cssIconAsc:"",cssIconDesc:"",cssInfoBlock:"tablesorter-infoOnly",cssNoSort:"tablesorter-noSort",cssIgnoreRow:"tablesorter-ignoreRow",pointerClick:"click",pointerDown:"mousedown",pointerUp:"mouseup",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]},u.css={table:"tablesorter",cssHasChild:"tablesorter-hasChildRow",childRow:"tablesorter-childRow",colgroup:"tablesorter-colgroup",header:"tablesorter-header",headerRow:"tablesorter-headerRow",headerIn:"tablesorter-header-inner",icon:"tablesorter-icon",processing:"tablesorter-processing",sortAsc:"tablesorter-headerAsc",sortDesc:"tablesorter-headerDesc",sortNone:"tablesorter-headerUnSorted"},u.language={sortAsc:"Ascending sort applied, ",sortDesc:"Descending sort applied, ",sortNone:"No sort applied, ",nextAsc:"activate to apply an ascending sort",nextDesc:"activate to apply a descending sort",nextNone:"activate to remove the sort"},u.instanceMethods={},u.log=b,u.benchmark=c,u.getElementText=function(b,c,d){if(!c)return"";var e,f=b.textExtraction||"",g=c.jquery?c:a(c);return"string"==typeof f?"basic"===f&&"undefined"!=typeof(e=g.attr(b.textAttribute))?a.trim(e):a.trim(c.textContent||g.text()):"function"==typeof f?a.trim(f(g[0],b.table,d)):"function"==typeof(e=u.getColumnData(b.table,f,d))?a.trim(e(g[0],b.table,d)):a.trim(g[0].textContent||g.text())},u.getParsedText=function(a,b,c,d){"undefined"==typeof d&&(d=u.getElementText(a,b,c));var e=""+d,f=a.parsers[c],g=a.extractors[c];return f&&(g&&"function"==typeof g.format&&(d=g.format(d,a.table,b,c)),e="no-parser"===f.id?"":f.format(""+d,a.table,b,c),a.ignoreCase&&"string"==typeof e&&(e=e.toLowerCase())),e},u.construct=function(b){return this.each(function(){var c=this,d=a.extend(!0,{},u.defaults,b,u.instanceMethods);d.originalSettings=b,!c.hasInitialized&&u.buildTable&&"TABLE"!==this.nodeName?u.buildTable(c,d):u.setup(c,d)})},u.setup=function(c,d){if(!c||!c.tHead||0===c.tBodies.length||c.hasInitialized===!0)return d.debug?b("ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized"):"";var e="",h=a(c),i=a.metadata;c.hasInitialized=!1,c.isProcessing=!0,c.config=d,a.data(c,"tablesorter",d),d.debug&&a.data(c,"startoveralltimer",new Date),d.supportsDataObject=function(a){return a[0]=parseInt(a[0],10),a[0]>1||1===a[0]&&parseInt(a[1],10)>=4}(a.fn.jquery.split(".")),d.string={max:1,min:-1,emptymin:1,emptymax:-1,zero:0,none:0,"null":0,top:!0,bottom:!1},d.emptyTo=d.emptyTo.toLowerCase(),d.stringTo=d.stringTo.toLowerCase(),/tablesorter\-/.test(h.attr("class"))||(e=""!==d.theme?" tablesorter-"+d.theme:""),d.table=c,d.$table=h.addClass(u.css.table+" "+d.tableClass+e).attr("role","grid"),d.$headers=h.find(d.selectorHeaders),d.namespace?d.namespace="."+d.namespace.replace(/\W/g,""):d.namespace=".tablesorter"+Math.random().toString(16).slice(2),d.$table.children().children("tr").attr("role","row"),d.$tbodies=h.children("tbody:not(."+d.cssInfoBlock+")").attr({"aria-live":"polite","aria-relevant":"all"}),d.$table.children("caption").length&&(e=d.$table.children("caption")[0],e.id||(e.id=d.namespace.slice(1)+"caption"),d.$table.attr("aria-labelledby",e.id)),d.widgetInit={},d.textExtraction=d.$table.attr("data-text-extraction")||d.textExtraction||"basic",j(c),u.fixColumnWidth(c),u.applyWidgetOptions(c,d),f(d),d.totalRows=0,d.delayInit||g(c),u.bindEvents(c,d.$headers,!0),t(c),d.supportsDataObject&&"undefined"!=typeof h.data().sortlist?d.sortList=h.data().sortlist:i&&h.metadata()&&h.metadata().sortlist&&(d.sortList=h.metadata().sortlist),u.applyWidget(c,!0),d.sortList.length>0?h.trigger("sorton",[d.sortList,{},!d.initWidgets,!0]):(m(c),d.initWidgets&&u.applyWidget(c,!1)),d.showProcessing&&h.unbind("sortBegin"+d.namespace+" sortEnd"+d.namespace).bind("sortBegin"+d.namespace+" sortEnd"+d.namespace,function(a){clearTimeout(d.processTimer),u.isProcessing(c),"sortBegin"===a.type&&(d.processTimer=setTimeout(function(){u.isProcessing(c,!0)},500))}),c.hasInitialized=!0,c.isProcessing=!1,d.debug&&u.benchmark("Overall initialization time",a.data(c,"startoveralltimer")),h.trigger("tablesorter-initialized",c),"function"==typeof d.initialized&&d.initialized(c)},u.fixColumnWidth=function(b){b=a(b)[0];var c,d,e,f,g,h=b.config,i=h.$table.children("colgroup");if(i.length&&i.hasClass(u.css.colgroup)&&i.remove(),h.widthFixed&&0===h.$table.children("colgroup").length){for(i=a(''),c=h.$table.width(),e=h.$tbodies.find("tr:first").children(":visible"),f=e.length,g=0;f>g;g++)d=parseInt(e.eq(g).width()/c*1e3,10)/10+"%",i.append(a("").css("width",d));h.$table.prepend(i)}},u.getColumnData=function(b,c,d,e,f){if("undefined"!=typeof c&&null!==c){b=a(b)[0];var g,h,i=b.config,j=f||i.$headers,k=i.$headerIndexed&&i.$headerIndexed[d]||j.filter('[data-column="'+d+'"]:last');if(c[d])return e?c[d]:c[j.index(k)];for(h in c)if("string"==typeof h&&(g=k.filter(h).add(k.find(h)),g.length))return c[h]}},u.computeColumnIndex=function(b){var c,d,e,f,g,h,i,j,k,l,m,n,o=[],p=[],q={};for(c=0;ce;e++)for("undefined"==typeof o[e]&&(o[e]=[]),p=o[e],f=n;n+m>f;f++)p[f]="x"}return p.length},u.isProcessing=function(b,c,d){$table=a(b);var e=$table[0].config,f=d||$table.find("."+u.css.header);c?("undefined"!=typeof d&&e.sortList.length>0&&(f=f.filter(function(){return this.sortDisabled?!1:u.isValueInArray(parseFloat(a(this).attr("data-column")),e.sortList)>=0})),$table.add(f).addClass(u.css.processing+" "+e.cssProcessing)):$table.add(f).removeClass(u.css.processing+" "+e.cssProcessing)},u.processTbody=function(b,c,d){b=a(b)[0];var e;return d?(b.isProcessing=!0,c.before(''),e=a.fn.detach?c.detach():c.remove()):(e=a(b).find("colgroup.tablesorter-savemyplace"),c.insertAfter(e),e.remove(),void(b.isProcessing=!1))},u.clearTableBody=function(b){a(b)[0].config.$tbodies.children().detach()},u.bindEvents=function(b,c,e){b=a(b)[0];var f,h=null,i=b.config;e!==!0&&(c.addClass(i.namespace.slice(1)+"_extra_headers"),f=a.fn.closest?c.closest("table")[0]:c.parents("table")[0],f&&"TABLE"===f.nodeName&&f!==b&&a(f).addClass(i.namespace.slice(1)+"_extra_table")),f=(i.pointerDown+" "+i.pointerUp+" "+i.pointerClick+" sort keyup ").replace(/\s+/g," ").split(" ").join(i.namespace+" "),c.find(i.selectorSort).add(c.filter(i.selectorSort)).unbind(f).bind(f,function(e,f){var j,k,l=a(e.target),m=" "+e.type+" ";if(!(1!==(e.which||e.button)&&!m.match(" "+i.pointerClick+" | sort | keyup ")||" keyup "===m&&13!==e.which||m.match(" "+i.pointerClick+" ")&&"undefined"!=typeof e.which||m.match(" "+i.pointerUp+" ")&&h!==e.target&&f!==!0)){if(m.match(" "+i.pointerDown+" "))return h=e.target,k=l.jquery.split("."),void("1"===k[0]&&k[1]<4&&e.preventDefault());if(h=null,/(input|select|button|textarea)/i.test(e.target.nodeName)||l.hasClass(i.cssNoSort)||l.parents("."+i.cssNoSort).length>0||l.parents("button").length>0)return!i.cancelSelection;i.delayInit&&d(i.cache)&&g(b),j=a.fn.closest?a(this).closest("th, td")[0]:/TH|TD/.test(this.nodeName)?this:a(this).parents("th, td")[0],j=i.$headers[c.index(j)],j.sortDisabled||p(b,j,e)}}),i.cancelSelection&&c.attr("unselectable","on").bind("selectstart",!1).css({"user-select":"none",MozUserSelect:"none"})},u.restoreHeaders=function(b){var c,d,e=a(b)[0].config,f=e.$table.find(e.selectorHeaders),g=f.length;for(c=0;g>c;c++)d=f.eq(c),d.find("."+u.css.headerIn).length&&d.html(e.headerContent[c])},u.destroy=function(b,c,d){if(b=a(b)[0],b.hasInitialized){u.removeWidget(b,!0,!1);var e,f=a(b),g=b.config,h=f.find("thead:first"),i=h.find("tr."+u.css.headerRow).removeClass(u.css.headerRow+" "+g.cssHeaderRow),j=f.find("tfoot:first > tr").children("th, td");c===!1&&a.inArray("uitheme",g.widgets)>=0&&(f.trigger("applyWidgetId",["uitheme"]),f.trigger("applyWidgetId",["zebra"])),h.find("tr").not(i).remove(),e="sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache "+"applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState ".split(" ").join(g.namespace+" "),f.removeData("tablesorter").unbind(e.replace(/\s+/g," ")),g.$headers.add(j).removeClass([u.css.header,g.cssHeader,g.cssAsc,g.cssDesc,u.css.sortAsc,u.css.sortDesc,u.css.sortNone].join(" ")).removeAttr("data-column").removeAttr("aria-label").attr("aria-disabled","true"),i.find(g.selectorSort).unbind("mousedown mouseup keypress ".split(" ").join(g.namespace+" ").replace(/\s+/g," ")),u.restoreHeaders(b),f.toggleClass(u.css.table+" "+g.tableClass+" tablesorter-"+g.theme,c===!1),b.hasInitialized=!1,delete b.config.cache,"function"==typeof d&&d(b)}},u.regex={chunk:/(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,chunks:/(^\\0|\\0$)/,hex:/^0x[0-9a-f]+$/i},u.sortNatural=function(a,b){if(a===b)return 0;var c,d,e,f,g,h,i,j,k=u.regex;if(k.hex.test(b)){if(d=parseInt(a.match(k.hex),16),f=parseInt(b.match(k.hex),16),f>d)return-1;if(d>f)return 1}for(c=a.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),e=b.replace(k.chunk,"\\0$1\\0").replace(k.chunks,"").split("\\0"),j=Math.max(c.length,e.length),i=0;j>i;i++){if(g=isNaN(c[i])?c[i]||0:parseFloat(c[i])||0,h=isNaN(e[i])?e[i]||0:parseFloat(e[i])||0,isNaN(g)!==isNaN(h))return isNaN(g)?1:-1;if(typeof g!=typeof h&&(g+="",h+=""),h>g)return-1;if(g>h)return 1}return 0},u.sortNaturalAsc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:-f||-1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:f||1:u.sortNatural(a,b)},u.sortNaturalDesc=function(a,b,c,d,e){if(a===b)return 0;var f=e.string[e.empties[c]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:f||1:""===b&&0!==f?"boolean"==typeof f?f?1:-1:-f||-1:u.sortNatural(b,a)},u.sortText=function(a,b){return a>b?1:b>a?-1:0},u.getTextValue=function(a,b,c){if(c){var d,e=a?a.length:0,f=c+b;for(d=0;e>d;d++)f+=a.charCodeAt(d);return b*f}return 0},u.sortNumericAsc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:-h||-1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:h||1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),a-b)},u.sortNumericDesc=function(a,b,c,d,e,f){if(a===b)return 0;var g=f.config,h=g.string[g.empties[e]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:h||1:""===b&&0!==h?"boolean"==typeof h?h?1:-1:-h||-1:(isNaN(a)&&(a=u.getTextValue(a,c,d)),isNaN(b)&&(b=u.getTextValue(b,c,d)),b-a)},u.sortNumeric=function(a,b){return a-b},u.characterEquivalents={a:"áàâãäąå",A:"ÁÀÂÃÄĄÅ",c:"çćč",C:"ÇĆČ",e:"éèêëěę",E:"ÉÈÊËĚĘ",i:"íìİîïı",I:"ÍÌİÎÏ",o:"óòôõöō",O:"ÓÒÔÕÖŌ",ss:"ß",SS:"ẞ",u:"úùûüů",U:"ÚÙÛÜŮ"},u.replaceAccents=function(a){var b,c="[",d=u.characterEquivalents;if(!u.characterRegex){u.characterRegexArray={};for(b in d)"string"==typeof b&&(c+=d[b],u.characterRegexArray[b]=new RegExp("["+d[b]+"]","g"));u.characterRegex=new RegExp(c+"]")}if(u.characterRegex.test(a))for(b in d)"string"==typeof b&&(a=a.replace(u.characterRegexArray[b],b));return a},u.isValueInArray=function(a,b){var c,d=b.length;for(c=0;d>c;c++)if(b[c][0]===a)return c;return-1},u.addParser=function(a){var b,c=u.parsers.length,d=!0;for(b=0;c>b;b++)u.parsers[b].id.toLowerCase()===a.id.toLowerCase()&&(d=!1);d&&u.parsers.push(a)},u.addInstanceMethods=function(b){a.extend(u.instanceMethods,b)},u.getParserById=function(a){if("false"==a)return!1;var b,c=u.parsers.length;for(b=0;c>b;b++)if(u.parsers[b].id.toLowerCase()===a.toString().toLowerCase())return u.parsers[b];return!1},u.addWidget=function(a){u.widgets.push(a)},u.hasWidget=function(b,c){return $table=a(b),$table.length&&$table[0].config&&$table[0].config.widgetInit[c]||!1},u.getWidgetById=function(a){var b,c,d=u.widgets.length;for(b=0;d>b;b++)if(c=u.widgets[b],c&&c.hasOwnProperty("id")&&c.id.toLowerCase()===a.toLowerCase())return c},u.applyWidgetOptions=function(b,c){var d,e,f=c.widgets.length,g=c.widgetOptions;if(f)for(d=0;f>d;d++)e=u.getWidgetById(c.widgets[d]),e&&"options"in e&&(g=b.config.widgetOptions=a.extend(!0,{},e.options,g))},u.applyWidget=function(b,d,e){b=a(b)[0];var f,g,h,i,j,k,l,m=b.config,n=m.widgetOptions,o=" "+m.table.className+" ",p=[];if(d===!1||!b.hasInitialized||!b.isApplyingWidgets&&!b.isUpdating){if(m.debug&&(i=new Date),l=new RegExp("\\s"+m.widgetClass.replace(/\{name\}/i,"([\\w-]+)")+"\\s","g"),o.match(l)&&(k=o.match(l)))for(g=k.length,f=0;g>f;f++)m.widgets.push(k[f].replace(l,"$1"));if(m.widgets.length){for(b.isApplyingWidgets=!0,m.widgets=a.grep(m.widgets,function(b,c){return a.inArray(b,m.widgets)===c}),h=m.widgets||[],g=h.length,f=0;g>f;f++)l=u.getWidgetById(h[f]),l&&l.id&&(l.priority||(l.priority=10),p[f]=l);for(p.sort(function(a,b){return a.priorityf;f++)p[f]&&((d||!m.widgetInit[p[f].id])&&(m.widgetInit[p[f].id]=!0,b.hasInitialized&&u.applyWidgetOptions(b,m),"init"in p[f]&&(m.debug&&(j=new Date),p[f].init(b,p[f],m,n),m.debug&&u.benchmark("Initializing "+p[f].id+" widget",j))),!d&&"format"in p[f]&&(m.debug&&(j=new Date),p[f].format(b,m,n,!1),m.debug&&u.benchmark((d?"Initializing ":"Applying ")+p[f].id+" widget",j)));d||"function"!=typeof e||e(b)}setTimeout(function(){b.isApplyingWidgets=!1,a.data(b,"lastWidgetApplication",new Date)},0),m.debug&&(k=m.widgets.length,c("Completed "+(d===!0?"initializing ":"applying ")+k+" widget"+(1!==k?"s":""),i))}},u.removeWidget=function(c,d,e){c=a(c)[0];var f,g,h,i,j=c.config;if(d===!0)for(d=[],i=u.widgets.length,h=0;i>h;h++)g=u.widgets[h],g&&g.id&&d.push(g.id);else d=(a.isArray(d)?d.join(","):d||"").toLowerCase().split(/[\s,]+/);for(i=d.length,f=0;i>f;f++)g=u.getWidgetById(d[f]),h=a.inArray(d[f],j.widgets),g&&"remove"in g&&(j.debug&&h>=0&&b('Removing "'+d[f]+'" widget'),g.remove(c,j,j.widgetOptions,e),j.widgetInit[d[f]]=!1),h>=0&&e!==!0&&j.widgets.splice(h,1)},u.refreshWidgets=function(b,c,d){b=a(b)[0];var e,f=b.config,g=f.widgets,h=u.widgets,i=h.length,j=[],k=function(b){a(b).trigger("refreshComplete")};for(e=0;i>e;e++)h[e]&&h[e].id&&(c||a.inArray(h[e].id,g)<0)&&j.push(h[e].id);u.removeWidget(b,j.join(","),!0),d!==!0?(u.applyWidget(b,c||!1,k),c&&u.applyWidget(b,!1,k)):k(b)},u.getColumnText=function(b,c,e){b=a(b)[0];var f,g,h,i,j,k,l,m,n,o,p="function"==typeof e,q="all"===c,r={raw:[],parsed:[],$cell:[]},s=b.config;if(!d(s)){for(j=s.$tbodies.length,f=0;j>f;f++)for(h=s.cache[f].normalized,k=h.length,g=0;k>g;g++)o=!0,i=h[g],m=q?i.slice(0,s.columns):i[c],i=i[s.columns],l=q?i.raw:i.raw[c],n=q?i.$row.children():i.$row.children().eq(c),p&&(o=e({tbodyIndex:f,rowIndex:g,parsed:m,raw:l,$row:i.$row,$cell:n})),o!==!1&&(r.parsed.push(m),r.raw.push(l),r.$cell.push(n));return r}},u.getData=function(b,c,d){var e,f,g="",h=a(b);return h.length?(e=a.metadata?h.metadata():!1,f=" "+(h.attr("class")||""),"undefined"!=typeof h.data(d)||"undefined"!=typeof h.data(d.toLowerCase())?g+=h.data(d)||h.data(d.toLowerCase()):e&&"undefined"!=typeof e[d]?g+=e[d]:c&&"undefined"!=typeof c[d]?g+=c[d]:" "!==f&&f.match(" "+d+"-")&&(g=f.match(new RegExp("\\s"+d+"-([\\w-]+)"))[1]||""),a.trim(g)):""},u.formatFloat=function(b,c){if("string"!=typeof b||""===b)return b;var d,e=c&&c.config?c.config.usNumberFormat!==!1:"undefined"!=typeof c?c:!0;return b=e?b.replace(/,/g,""):b.replace(/[\s|\.]/g,"").replace(/,/g,"."),/^\s*\([.\d]+\)/.test(b)&&(b=b.replace(/^\s*\(([.\d]+)\)/,"-$1")),d=parseFloat(b),isNaN(d)?a.trim(b):d},u.isDigit=function(a){return isNaN(a)?/^[\-+(]?\d+[)]?$/.test(a.toString().replace(/[,.'"\s]/g,"")):""!==a}}});var b=a.tablesorter;a.fn.extend({tablesorter:b.construct}),b.addParser({id:"no-parser",is:function(){return!1},format:function(){return""},type:"text"}),b.addParser({id:"text",is:function(){return!0},format:function(c,d){var e=d.config;return c&&(c=a.trim(e.ignoreCase?c.toLocaleLowerCase():c),c=e.sortLocaleCompare?b.replaceAccents(c):c),c},type:"text"}),b.addParser({id:"digit",is:function(a){return b.isDigit(a)},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"currency",is:function(a){return/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test((a||"").replace(/[+\-,. ]/g,""))},format:function(c,d){var e=b.formatFloat((c||"").replace(/[^\w,. \-()]/g,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.addParser({id:"url",is:function(a){return/^(https?|ftp|file):\/\//.test(a)},format:function(b){return b?a.trim(b.replace(/(https?|ftp|file):\/\//,"")):b},parsed:!0,type:"text"}),b.addParser({id:"isoDate",is:function(a){return/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(a)},format:function(a,b){var c=a?new Date(a.replace(/-/g,"/")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"percent",is:function(a){return/(\d\s*?%|%\s*?\d)/.test(a)&&a.length<15},format:function(a,c){return a?b.formatFloat(a.replace(/%/g,""),c):a},type:"numeric"}),b.addParser({id:"image",is:function(a,b,c,d){return d.find("img").length>0},format:function(b,c,d){return a(d).find("img").attr(c.config.imgAttr||"alt")||b},parsed:!0,type:"text" +}),b.addParser({id:"usLongDate",is:function(a){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(a)||/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(a)},format:function(a,b){var c=a?new Date(a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"shortDate",is:function(a){return/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/.test((a||"").replace(/\s+/g," ").replace(/[\-.,]/g,"/"))},format:function(a,c,d,e){if(a){var f,g,h=c.config,i=h.$headerIndexed[e],j=i.length&&i[0].dateFormat||b.getData(i,b.getColumnData(c,h.headers,e),"dateFormat")||h.dateFormat;return g=a.replace(/\s+/g," ").replace(/[\-.,]/g,"/"),"mmddyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$1/$2"):"ddmmyyyy"===j?g=g.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,"$3/$2/$1"):"yyyymmdd"===j&&(g=g.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,"$1/$2/$3")),f=new Date(g),f instanceof Date&&isFinite(f)?f.getTime():a}return a},type:"numeric"}),b.addParser({id:"time",is:function(a){return/^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(a)},format:function(a,b){var c=a?new Date("2000/01/01 "+a.replace(/(\S)([AP]M)$/i,"$1 $2")):a;return c instanceof Date&&isFinite(c)?c.getTime():a},type:"numeric"}),b.addParser({id:"metadata",is:function(){return!1},format:function(b,c,d){var e=c.config,f=e.parserMetadataName?e.parserMetadataName:"sortValue";return a(d).metadata()[f]},type:"numeric"}),b.addWidget({id:"zebra",priority:90,format:function(b,c,d){var e,f,g,h,i,j,k,l,m=new RegExp(c.cssChildRow,"i"),n=c.$tbodies.add(a(c.namespace+"_extra_table").children("tbody:not(."+c.cssInfoBlock+")"));for(c.debug&&(i=new Date),j=0;jk;k++)f=e.eq(k),m.test(f[0].className)||g++,h=g%2===0,f.removeClass(d.zebra[h?1:0]).addClass(d.zebra[h?0:1])},remove:function(a,c,d,e){if(!e){var f,g,h=c.$tbodies,i=(d.zebra||["even","odd"]).join(" ");for(f=0;f table ID/index on page > data + if (!values[url]) { + values[url] = {}; + } + values[url][id] = value; + // *** set value *** + if (hasStorage) { + window[storageType][key] = JSON.stringify(values); + } else { + date = new Date(); + date.setTime(date.getTime() + (31536e+6)); // 365 days + document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/'; + } } else { - // old browser, using cookies - cookies = document.cookie.split(/[;\s|=]/); - // add one to get from the key to the value - cookieIndex = $.inArray(key, cookies) + 1; - values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + return values && values[url] ? values[url][id] : ''; } - } - // allow value to be an empty string too - if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { - // add unique identifiers = url pathname > table ID/index on page > data - if (!values[url]) { - values[url] = {}; - } - values[url][id] = value; - // *** set value *** - if (hasStorage) { - window[storageType][key] = JSON.stringify(values); - } else { - date = new Date(); - date.setTime(date.getTime() + (31536e+6)); // 365 days - document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/'; - } - } else { - return values && values[url] ? values[url][id] : ''; - } -}; + }; })(jQuery, window, document); /*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.themes = { - 'bootstrap' : { - table : 'table table-bordered table-striped', - caption : 'caption', - // header class names - header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) - sortNone : '', - sortAsc : '', - sortDesc : '', - active : '', // applied when column is sorted - hover : '', // custom css required - a defined bootstrap style may not override other classes - // icon class names - icons : '', // add "icon-white" to make them white; this icon class is added to the in the header - iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted - iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort - iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort - filterRow : '', // filter row class - footerRow : '', - footerCells : '', - even : '', // even row zebra striping - odd : '' // odd row zebra striping - }, - 'jui' : { - table : 'ui-widget ui-widget-content ui-corner-all', // table classes - caption : 'ui-widget-content', - // header class names - header : 'ui-widget-header ui-corner-all ui-state-default', // header classes - sortNone : '', - sortAsc : '', - sortDesc : '', - active : 'ui-state-active', // applied when column is sorted - hover : 'ui-state-hover', // hover class - // icon class names - icons : 'ui-icon', // icon class added to the in the header - iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted - iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort - iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort - filterRow : '', - footerRow : '', - footerCells : '', - even : 'ui-widget-content', // even row zebra striping - odd : 'ui-state-default' // odd row zebra striping - } -}; + ts.themes = { + 'bootstrap' : { + table : 'table table-bordered table-striped', + caption : 'caption', + // header class names + header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) + sortNone : '', + sortAsc : '', + sortDesc : '', + active : '', // applied when column is sorted + hover : '', // custom css required - a defined bootstrap style may not override other classes + // icon class names + icons : '', // add 'icon-white' to make them white; this icon class is added to the in the header + iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted + iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort + iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort + filterRow : '', // filter row class + footerRow : '', + footerCells : '', + even : '', // even row zebra striping + odd : '' // odd row zebra striping + }, + 'jui' : { + table : 'ui-widget ui-widget-content ui-corner-all', // table classes + caption : 'ui-widget-content', + // header class names + header : 'ui-widget-header ui-corner-all ui-state-default', // header classes + sortNone : '', + sortAsc : '', + sortDesc : '', + active : 'ui-state-active', // applied when column is sorted + hover : 'ui-state-hover', // hover class + // icon class names + icons : 'ui-icon', // icon class added to the in the header + iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted + iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort + iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort + filterRow : '', + footerRow : '', + footerCells : '', + even : 'ui-widget-content', // even row zebra striping + odd : 'ui-state-default' // odd row zebra striping + } + }; -$.extend(ts.css, { - wrapper : 'tablesorter-wrapper' // ui theme & resizable -}); + $.extend(ts.css, { + wrapper : 'tablesorter-wrapper' // ui theme & resizable + }); -ts.addWidget({ - id: "uitheme", - priority: 10, - format: function(table, c, wo) { - var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, - themesAll = ts.themes, - $table = c.$table.add( $( c.namespace + '_extra_table' ) ), - $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), - theme = c.theme || 'jui', - themes = themesAll[theme] || {}, - remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), - iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); - if (c.debug) { time = new Date(); } - // initialization code - run once - if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { - wo.uitheme_applied = true; - oldtheme = themesAll[c.appliedTheme] || {}; - hasOldTheme = !$.isEmptyObject(oldtheme); - oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; - oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; - if (hasOldTheme) { - wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); - wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); - c.$tbodies.children().removeClass( [oldtheme.even, oldtheme.odd].join(' ') ); - } - // update zebra stripes - if (themes.even) { wo.zebra[0] += ' ' + themes.even; } - if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } - // add caption style - $table.children('caption') - .removeClass(oldtheme.caption || '') - .addClass(themes.caption); - // add table/footer class names - $tfoot = $table - // remove other selected themes - .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) - .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name - .children('tfoot'); - c.appliedTheme = c.theme; - - if ($tfoot.length) { - $tfoot - // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed - .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) - .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); - } - // update header classes - $headers - .removeClass( (hasOldTheme ? [oldtheme.header, oldtheme.hover, oldremove].join(' ') : '') || '' ) - .addClass(themes.header) - .not('.sorter-false') - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') - .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { - // toggleClass with switch added in jQuery 1.3 - $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); - }); - - $headers.each(function(){ - var $this = $(this); - if (!$this.find('.' + ts.css.wrapper).length) { - // Firefox needs this inner div to position the icon & resizer correctly - $this.wrapInner('
'); + ts.addWidget({ + id: 'uitheme', + priority: 10, + format: function(table, c, wo) { + var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, + themesAll = ts.themes, + $table = c.$table.add( $( c.namespace + '_extra_table' ) ), + $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), + theme = c.theme || 'jui', + themes = themesAll[theme] || {}, + remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), + iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); + if (c.debug) { time = new Date(); } + // initialization code - run once + if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { + wo.uitheme_applied = true; + oldtheme = themesAll[c.appliedTheme] || {}; + hasOldTheme = !$.isEmptyObject(oldtheme); + oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; + oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; + if (hasOldTheme) { + wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); + wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); + c.$tbodies.children().removeClass( [ oldtheme.even, oldtheme.odd ].join(' ') ); } - }); - if (c.cssIcon) { - // if c.cssIcon is '', then no is added to the header + // update zebra stripes + if (themes.even) { wo.zebra[0] += ' ' + themes.even; } + if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } + // add caption style + $table.children('caption') + .removeClass(oldtheme.caption || '') + .addClass(themes.caption); + // add table/footer class names + $tfoot = $table + // remove other selected themes + .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) + .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name + .children('tfoot'); + c.appliedTheme = c.theme; + + if ($tfoot.length) { + $tfoot + // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed + .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) + .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); + } + // update header classes $headers - .find('.' + ts.css.icon) - .removeClass(hasOldTheme ? [oldtheme.icons, oldIconRmv].join(' ') : '') - .addClass(themes.icons || ''); - } - if ($table.hasClass('hasFilters')) { - $table.children('thead').children('.' + ts.css.filterRow) - .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') - .addClass(themes.filterRow || ''); - } - } - for (i = 0; i < c.columns; i++) { - $header = c.$headers - .add($(c.namespace + '_extra_headers')) - .not('.sorter-false') - .filter('[data-column="' + i + '"]'); - $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); - $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); - if ($h.length) { - $header.removeClass(remove); - $icon.removeClass(iconRmv); - if ($h[0].sortDisabled) { - // no sort arrows for disabled columns! - $icon.removeClass(themes.icons || ''); - } else { - hdr = themes.sortNone; - icon = themes.iconSortNone; - if ($h.hasClass(ts.css.sortAsc)) { - hdr = [themes.sortAsc, themes.active].join(' '); - icon = themes.iconSortAsc; - } else if ($h.hasClass(ts.css.sortDesc)) { - hdr = [themes.sortDesc, themes.active].join(' '); - icon = themes.iconSortDesc; + .removeClass( (hasOldTheme ? [ oldtheme.header, oldtheme.hover, oldremove ].join(' ') : '') || '' ) + .addClass(themes.header) + .not('.sorter-false') + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') + .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { + // toggleClass with switch added in jQuery 1.3 + $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); + }); + + $headers.each(function(){ + var $this = $(this); + if (!$this.find('.' + ts.css.wrapper).length) { + // Firefox needs this inner div to position the icon & resizer correctly + $this.wrapInner('
'); } - $header.addClass(hdr); - $icon.addClass(icon || ''); + }); + if (c.cssIcon) { + // if c.cssIcon is '', then no is added to the header + $headers + .find('.' + ts.css.icon) + .removeClass(hasOldTheme ? [ oldtheme.icons, oldIconRmv ].join(' ') : '') + .addClass(themes.icons || ''); + } + if ($table.hasClass('hasFilters')) { + $table.children('thead').children('.' + ts.css.filterRow) + .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') + .addClass(themes.filterRow || ''); } } + for (i = 0; i < c.columns; i++) { + $header = c.$headers + .add($(c.namespace + '_extra_headers')) + .not('.sorter-false') + .filter('[data-column="' + i + '"]'); + $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); + $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); + if ($h.length) { + $header.removeClass(remove); + $icon.removeClass(iconRmv); + if ($h[0].sortDisabled) { + // no sort arrows for disabled columns! + $icon.removeClass(themes.icons || ''); + } else { + hdr = themes.sortNone; + icon = themes.iconSortNone; + if ($h.hasClass(ts.css.sortAsc)) { + hdr = [ themes.sortAsc, themes.active ].join(' '); + icon = themes.iconSortAsc; + } else if ($h.hasClass(ts.css.sortDesc)) { + hdr = [ themes.sortDesc, themes.active ].join(' '); + icon = themes.iconSortDesc; + } + $header.addClass(hdr); + $icon.addClass(icon || ''); + } + } + } + if (c.debug) { + ts.benchmark('Applying ' + theme + ' theme', time); + } + }, + remove: function(table, c, wo, refreshing) { + if (!wo.uitheme_applied) { return; } + var $table = c.$table, + theme = c.appliedTheme || 'jui', + themes = ts.themes[ theme ] || ts.themes.jui, + $headers = $table.children('thead').children(), + remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, + iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; + $table.removeClass('tablesorter-' + theme + ' ' + themes.table); + wo.uitheme_applied = false; + if (refreshing) { return; } + $table.find(ts.css.header).removeClass(themes.header); + $headers + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover + .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) + .filter('.' + ts.css.filterRow) + .removeClass(themes.filterRow); + $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); } - if (c.debug) { - ts.benchmark("Applying " + theme + " theme", time); - } - }, - remove: function(table, c, wo, refreshing) { - if (!wo.uitheme_applied) { return; } - var $table = c.$table, - theme = c.appliedTheme || 'jui', - themes = ts.themes[ theme ] || ts.themes.jui, - $headers = $table.children('thead').children(), - remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, - iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; - $table.removeClass('tablesorter-' + theme + ' ' + themes.table); - wo.uitheme_applied = false; - if (refreshing) { return; } - $table.find(ts.css.header).removeClass(themes.header); - $headers - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover - .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) - .filter('.' + ts.css.filterRow) - .removeClass(themes.filterRow); - $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); - } -}); + }); })(jQuery); /*! Widget: columns */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.addWidget({ - id: "columns", - priority: 30, - options : { - columns : [ "primary", "secondary", "tertiary" ] - }, - format: function(table, c, wo) { - var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, + ts.addWidget({ + id: 'columns', + priority: 30, + options : { + columns : [ 'primary', 'secondary', 'tertiary' ] + }, + format: function(table, c, wo) { + var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, $table = c.$table, $tbodies = c.$tbodies, sortList = c.sortList, len = sortList.length, // removed c.widgetColumns support - css = wo && wo.columns || [ "primary", "secondary", "tertiary" ], + css = wo && wo.columns || [ 'primary', 'secondary', 'tertiary' ], last = css.length - 1; remove = css.join(' '); - // check if there is a sort (on initialization there may not be one) - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody - $rows = $tbody.children('tr'); - // loop through the visible rows - $rows.each(function() { - $row = $(this); - if (this.style.display !== 'none') { - // remove all columns class names - $cells = $row.children().removeClass(remove); - // add appropriate column class names - if (sortList && sortList[0]) { - // primary sort column class - $cells.eq(sortList[0][0]).addClass(css[0]); - if (len > 1) { - for (indx = 1; indx < len; indx++) { - // secondary, tertiary, etc sort column classes - $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + // check if there is a sort (on initialization there may not be one) + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody + $rows = $tbody.children('tr'); + // loop through the visible rows + $rows.each(function() { + $row = $(this); + if (this.style.display !== 'none') { + // remove all columns class names + $cells = $row.children().removeClass(remove); + // add appropriate column class names + if (sortList && sortList[0]) { + // primary sort column class + $cells.eq(sortList[0][0]).addClass(css[0]); + if (len > 1) { + for (indx = 1; indx < len; indx++) { + // secondary, tertiary, etc sort column classes + $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + } } } } - } - }); - ts.processTbody(table, $tbody, false); - } - // add classes to thead and tfoot - rows = wo.columns_thead !== false ? ['thead tr'] : []; - if (wo.columns_tfoot !== false) { - rows.push('tfoot tr'); - } - if (rows.length) { - $rows = $table.find( rows.join(',') ).children().removeClass(remove); - if (len) { - for (indx = 0; indx < len; indx++) { - // add primary. secondary, tertiary, etc sort column classes - $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + }); + ts.processTbody(table, $tbody, false); + } + // add classes to thead and tfoot + rows = wo.columns_thead !== false ? [ 'thead tr' ] : []; + if (wo.columns_tfoot !== false) { + rows.push('tfoot tr'); + } + if (rows.length) { + $rows = $table.find( rows.join(',') ).children().removeClass(remove); + if (len) { + for (indx = 0; indx < len; indx++) { + // add primary. secondary, tertiary, etc sort column classes + $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + } } } + }, + remove: function(table, c, wo) { + var tbodyIndex, $tbody, + $tbodies = c.$tbodies, + remove = (wo.columns || [ 'primary', 'secondary', 'tertiary' ]).join(' '); + c.$headers.removeClass(remove); + c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody + $tbody.children('tr').each(function() { + $(this).children().removeClass(remove); + }); + ts.processTbody(table, $tbody, false); // restore tbody + } } - }, - remove: function(table, c, wo) { - var tbodyIndex, $tbody, - $tbodies = c.$tbodies, - remove = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' '); - c.$headers.removeClass(remove); - c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody - $tbody.children('tr').each(function() { - $(this).children().removeClass(remove); - }); - ts.processTbody(table, $tbody, false); // restore tbody - } - } -}); + }); })(jQuery); @@ -370,92 +370,92 @@ ts.addWidget({ * by Rob Garrison */ ;( function ( $ ) { -'use strict'; -var ts = $.tablesorter || {}, + 'use strict'; + var ts = $.tablesorter || {}, tscss = ts.css; -$.extend( tscss, { - filterRow : 'tablesorter-filter-row', - filter : 'tablesorter-filter', - filterDisabled : 'disabled', - filterRowHide : 'hideme' -}); + $.extend( tscss, { + filterRow : 'tablesorter-filter-row', + filter : 'tablesorter-filter', + filterDisabled : 'disabled', + filterRowHide : 'hideme' + }); -ts.addWidget({ - id: 'filter', - priority: 50, - options : { - filter_childRows : false, // if true, filter includes child row content in the search - filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped - filter_columnFilters : true, // if true, a filter will be added to the top of each table column - filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) - filter_cellFilter : '', // css class name added to the filter cell ( string or array ) - filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) - filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. - filter_excludeFilter : {}, // filters to exclude, per column - filter_external : '', // jQuery selector string ( or jQuery object ) of external filters - filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin - filter_formatter : null, // add custom filter elements to the filter row - filter_functions : null, // add custom filter functions using this option - filter_hideEmpty : true, // hide filter row when table is empty - filter_hideFilters : false, // collapse filter row when mouse leaves the area - filter_ignoreCase : true, // if true, make all searches case-insensitive - filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) - filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down - filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) - filter_reset : null, // jQuery selector string of an element used to reset the filters - filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters - filter_searchDelay : 300, // typing delay in milliseconds before starting a search - filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true - filter_selectSource : null, // include a function to return an array of values to be added to the column filter select - filter_startsWith : false, // if true, filter start from the beginning of the cell contents - filter_useParsedData : false, // filter all data using parsed content - filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used. - filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value - filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text - }, - format: function( table, c, wo ) { - if ( !c.$table.hasClass( 'hasFilters' ) ) { - ts.filter.init( table, c, wo ); + ts.addWidget({ + id: 'filter', + priority: 50, + options : { + filter_childRows : false, // if true, filter includes child row content in the search + filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped + filter_columnFilters : true, // if true, a filter will be added to the top of each table column + filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) + filter_cellFilter : '', // css class name added to the filter cell ( string or array ) + filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) + filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. + filter_excludeFilter : {}, // filters to exclude, per column + filter_external : '', // jQuery selector string ( or jQuery object ) of external filters + filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin + filter_formatter : null, // add custom filter elements to the filter row + filter_functions : null, // add custom filter functions using this option + filter_hideEmpty : true, // hide filter row when table is empty + filter_hideFilters : false, // collapse filter row when mouse leaves the area + filter_ignoreCase : true, // if true, make all searches case-insensitive + filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) + filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down + filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) + filter_reset : null, // jQuery selector string of an element used to reset the filters + filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters + filter_searchDelay : 300, // typing delay in milliseconds before starting a search + filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true + filter_selectSource : null, // include a function to return an array of values to be added to the column filter select + filter_startsWith : false, // if true, filter start from the beginning of the cell contents + filter_useParsedData : false, // filter all data using parsed content + filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used. + filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value + filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text + }, + format: function( table, c, wo ) { + if ( !c.$table.hasClass( 'hasFilters' ) ) { + ts.filter.init( table, c, wo ); + } + }, + remove: function( table, c, wo, refreshing ) { + var tbodyIndex, $tbody, + $table = c.$table, + $tbodies = c.$tbodies, + events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + $table + .removeClass( 'hasFilters' ) + // add .tsfilter namespace to all BUT search + .unbind( events.replace( /\s+/g, ' ' ) ) + // remove the filter row even if refreshing, because the column might have been moved + .find( '.' + tscss.filterRow ).remove(); + if ( refreshing ) { return; } + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody + $tbody.children().removeClass( wo.filter_filteredRow ).show(); + ts.processTbody( table, $tbody, false ); // restore tbody + } + if ( wo.filter_reset ) { + $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); + } } - }, - remove: function( table, c, wo, refreshing ) { - var tbodyIndex, $tbody, - $table = c.$table, - $tbodies = c.$tbodies, - events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - $table - .removeClass( 'hasFilters' ) - // add .tsfilter namespace to all BUT search - .unbind( events.replace( /\s+/g, ' ' ) ) - // remove the filter row even if refreshing, because the column might have been moved - .find( '.' + tscss.filterRow ).remove(); - if ( refreshing ) { return; } - for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody - $tbody.children().removeClass( wo.filter_filteredRow ).show(); - ts.processTbody( table, $tbody, false ); // restore tbody - } - if ( wo.filter_reset ) { - $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); - } - } -}); + }); -ts.filter = { + ts.filter = { - // regex used in filter 'check' functions - not for general use and not documented - regex: { - regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex - child : /tablesorter-childRow/, // child row class name; this gets updated in the script - filtered : /filtered/, // filtered (hidden) row class name; updated in the script - type : /undefined|number/, // check type - exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') - nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) - operators : /[<>=]/g, // replace operators - query : '(q|query)' // replace filter queries - }, + // regex used in filter 'check' functions - not for general use and not documented + regex: { + regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex + child : /tablesorter-childRow/, // child row class name; this gets updated in the script + filtered : /filtered/, // filtered (hidden) row class name; updated in the script + type : /undefined|number/, // check type + exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') + nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) + operators : /[<>=]/g, // replace operators + query : '(q|query)' // replace filter queries + }, // function( c, data ) { } // c = table.config // data.$row = jQuery object of the row currently being processed @@ -469,1508 +469,1508 @@ ts.filter = { // data.cacheArray = An array of parsed content from each table cell in the row being processed // data.index = column index; table = table element ( DOM ) // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class ) - types: { - or : function( c, data, vars ) { - if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { - var indx, filterMatched, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.orSplit ), - iFilter = data.iFilter.split( ts.filter.regex.orSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // filterMatched = data2.filter === '' && indx > 0 ? true - // look for an exact match with the 'or' unless the 'filter-match' class is found - filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); - if ( filterMatched ) { - return filterMatched; + types: { + or : function( c, data, vars ) { + if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { + var indx, filterMatched, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.orSplit ), + iFilter = data.iFilter.split( ts.filter.regex.orSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // filterMatched = data2.filter === '' && indx > 0 ? true + // look for an exact match with the 'or' unless the 'filter-match' class is found + filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); + if ( filterMatched ) { + return filterMatched; + } } + // may be null from processing types + return filterMatched || false; } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for an AND or && operator ( logical and ) - and : function( c, data, vars ) { - if ( ts.filter.regex.andTest.test( data.filter ) ) { - var indx, filterMatched, result, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.andSplit ), - iFilter = data.iFilter.split( ts.filter.regex.andSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) - // replace wild cards since /(a*)/i will match anything - .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // look for an exact match with the 'and' unless the 'filter-match' class is found - result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); - if ( indx === 0 ) { - filterMatched = result; + return null; + }, + // Look for an AND or && operator ( logical and ) + and : function( c, data, vars ) { + if ( ts.filter.regex.andTest.test( data.filter ) ) { + var indx, filterMatched, result, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.andSplit ), + iFilter = data.iFilter.split( ts.filter.regex.andSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) + // replace wild cards since /(a*)/i will match anything + .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // look for an exact match with the 'and' unless the 'filter-match' class is found + result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); + if ( indx === 0 ) { + filterMatched = result; + } else { + filterMatched = filterMatched && result; + } + } + // may be null from processing types + return filterMatched || false; + } + return null; + }, + // Look for regex + regex: function( c, data ) { + if ( ts.filter.regex.regex.test( data.filter ) ) { + var matches, + // cache regex per column for optimal speed + regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), + isRegex = regex instanceof RegExp; + try { + if ( !isRegex ) { + // force case insensitive search if ignoreCase option set? + // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } + data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); + } + matches = regex.test( data.exact ); + } catch ( error ) { + matches = false; + } + return matches; + } + return null; + }, + // Look for operators >, >=, < or <= + operators: function( c, data ) { + // ignore empty strings... because '' < 10 is true + if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { + var cachedValue, result, txt, + table = c.table, + index = data.index, + parsed = data.parsed[index], + query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), + parser = c.parsers[index], + savedSearch = query; + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || parser.type === 'numeric' ) { + txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); + result = ts.filter.parseFilter( c, txt, index, true ); + query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; + } + // iExact may be numeric - see issue #149; + // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) + if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && + typeof data.cache !== 'undefined' ) { + cachedValue = data.cache; } else { - filterMatched = filterMatched && result; + txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + cachedValue = ts.formatFloat( txt, table ); + } + if ( />/.test( data.iFilter ) ) { + result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; + } else if ( /= 0 ); } } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for regex - regex: function( c, data ) { - if ( ts.filter.regex.regex.test( data.filter ) ) { - var matches, - // cache regex per column for optimal speed - regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), - isRegex = regex instanceof RegExp; - try { - if ( !isRegex ) { - // force case insensitive search if ignoreCase option set? - // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } - data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); - } - matches = regex.test( data.exact ); - } catch ( error ) { - matches = false; + return null; + }, + // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric + exact: function( c, data ) { + /*jshint eqeqeq:false */ + if ( ts.filter.regex.exact.test( data.iFilter ) ) { + var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), + filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; } - return matches; - } - return null; - }, - // Look for operators >, >=, < or <= - operators: function( c, data ) { - // ignore empty strings... because '' < 10 is true - if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { - var cachedValue, result, txt, - table = c.table, - index = data.index, - parsed = data.parsed[index], - query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), - parser = c.parsers[index], - savedSearch = query; - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || parser.type === 'numeric' ) { - txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); - result = ts.filter.parseFilter( c, txt, index, true ); - query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; - } - // iExact may be numeric - see issue #149; - // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) - if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && - typeof data.cache !== 'undefined' ) { - cachedValue = data.cache; - } else { - txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - cachedValue = ts.formatFloat( txt, table ); - } - if ( />/.test( data.iFilter ) ) { - result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; - } else if ( /= 0 ); - } - } - return null; - }, - // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric - exact: function( c, data ) { - /*jshint eqeqeq:false */ - if ( ts.filter.regex.exact.test( data.iFilter ) ) { - var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), - filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; - } - return null; - }, - // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! - range : function( c, data ) { - if ( ts.filter.regex.toTest.test( data.iFilter ) ) { - var result, tmp, range1, range2, - table = c.table, - index = data.index, - parsed = data.parsed[index], - // make sure the dash is for a range and not indicating a negative number - query = data.iFilter.split( ts.filter.regex.toSplit ); + return null; + }, + // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! + range : function( c, data ) { + if ( ts.filter.regex.toTest.test( data.iFilter ) ) { + var result, tmp, range1, range2, + table = c.table, + index = data.index, + parsed = data.parsed[index], + // make sure the dash is for a range and not indicating a negative number + query = data.iFilter.split( ts.filter.regex.toSplit ); - tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; - range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; - range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || c.parsers[index].type === 'numeric' ) { - result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); - range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; - result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); - range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; - } - if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { - result = data.cache; - } else { - tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - result = ts.formatFloat( tmp, table ); - } - if ( range1 > range2 ) { - tmp = range1; range1 = range2; range2 = tmp; // swap - } - return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); - } - return null; - }, - // Look for wild card: ? = single, * = multiple, or | = logical OR - wild : function( c, data ) { - if ( /[\?\*\|]/.test( data.iFilter ) ) { - var index = data.index, - parsed = data.parsed[ index ], - query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); - // look for an exact match with the 'or' unless the 'filter-match' class is found - if ( !/\?\*/.test( query ) && data.nestedFilters ) { - query = data.isMatch ? query : '^(' + query + ')$'; - } - // parsing the filter may not work properly when using wildcards =/ - return new RegExp( - query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), - c.widgetOptions.filter_ignoreCase ? 'i' : '' - ) - .test( data.exact ); - } - return null; - }, - // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) - fuzzy: function( c, data ) { - if ( /^~/.test( data.iFilter ) ) { - var indx, - patternIndx = 0, - len = data.iExact.length, - txt = data.iFilter.slice( 1 ), - pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - for ( indx = 0; indx < len; indx++ ) { - if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { - patternIndx += 1; + tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; + range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; + range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || c.parsers[index].type === 'numeric' ) { + result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); + range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; + result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); + range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; } + if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { + result = data.cache; + } else { + tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + result = ts.formatFloat( tmp, table ); + } + if ( range1 > range2 ) { + tmp = range1; range1 = range2; range2 = tmp; // swap + } + return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); } - if ( patternIndx === pattern.length ) { - return true; + return null; + }, + // Look for wild card: ? = single, * = multiple, or | = logical OR + wild : function( c, data ) { + if ( /[\?\*\|]/.test( data.iFilter ) ) { + var index = data.index, + parsed = data.parsed[ index ], + query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); + // look for an exact match with the 'or' unless the 'filter-match' class is found + if ( !/\?\*/.test( query ) && data.nestedFilters ) { + query = data.isMatch ? query : '^(' + query + ')$'; + } + // parsing the filter may not work properly when using wildcards =/ + return new RegExp( + query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), + c.widgetOptions.filter_ignoreCase ? 'i' : '' + ) + .test( data.exact ); + } + return null; + }, + // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) + fuzzy: function( c, data ) { + if ( /^~/.test( data.iFilter ) ) { + var indx, + patternIndx = 0, + len = data.iExact.length, + txt = data.iFilter.slice( 1 ), + pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + for ( indx = 0; indx < len; indx++ ) { + if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { + patternIndx += 1; + } + } + if ( patternIndx === pattern.length ) { + return true; + } + return false; + } + return null; + } + }, + init: function( table, c, wo ) { + // filter language options + ts.language = $.extend( true, {}, { + to : 'to', + or : 'or', + and : 'and' + }, ts.language ); + + var options, string, txt, $header, column, filters, val, fxn, noSelect, + regex = ts.filter.regex; + c.$table.addClass( 'hasFilters' ); + + // define timers so using clearTimeout won't cause an undefined error + wo.searchTimer = null; + wo.filter_initTimer = null; + wo.filter_formatterCount = 0; + wo.filter_formatterInit = []; + wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; + wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; + + val = '\\{' + ts.filter.regex.query + '\\}'; + $.extend( regex, { + child : new RegExp( c.cssChildRow ), + filtered : new RegExp( wo.filter_filteredRow ), + alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), + toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), + toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ), + andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), + andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), + orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), + iQuery : new RegExp( val, 'i' ), + igQuery : new RegExp( val, 'ig' ) + }); + + // don't build filter row if columnFilters is false or all columns are set to 'filter-false' + // see issue #156 + val = c.$headers.filter( '.filter-false, .parser-false' ).length; + if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { + // build filter row + ts.filter.buildRow( table, c, wo ); + } + + txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + c.$table.bind( txt, function( event, filter ) { + val = wo.filter_hideEmpty && + $.isEmptyObject( c.cache ) && + !( c.delayInit && event.type === 'appendCache' ); + // hide filter row using the 'filtered' class name + c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 + if ( !/(search|filter)/.test( event.type ) ) { + event.stopPropagation(); + ts.filter.buildDefault( table, true ); + } + if ( event.type === 'filterReset' ) { + c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); + ts.filter.searching( table, [] ); + } else if ( event.type === 'filterEnd' ) { + ts.filter.buildDefault( table, true ); + } else { + // send false argument to force a new search; otherwise if the filter hasn't changed, + // it will return + filter = event.type === 'search' ? filter : + event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; + if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { + // force a new search since content has changed + c.lastCombinedFilter = null; + c.lastSearch = []; + } + // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first + // input ensures all inputs are updated when a search is triggered on the table + // $( 'table' ).trigger( 'search', [...] ); + ts.filter.searching( table, filter, true ); } return false; - } - return null; - } - }, - init: function( table, c, wo ) { - // filter language options - ts.language = $.extend( true, {}, { - to : 'to', - or : 'or', - and : 'and' - }, ts.language ); + }); - var options, string, txt, $header, column, filters, val, fxn, noSelect, - regex = ts.filter.regex; - c.$table.addClass( 'hasFilters' ); - - // define timers so using clearTimeout won't cause an undefined error - wo.searchTimer = null; - wo.filter_initTimer = null; - wo.filter_formatterCount = 0; - wo.filter_formatterInit = []; - wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; - wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; - - val = '\\{' + ts.filter.regex.query + '\\}'; - $.extend( regex, { - child : new RegExp( c.cssChildRow ), - filtered : new RegExp( wo.filter_filteredRow ), - alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), - toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), - toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ), - andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), - andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), - orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), - iQuery : new RegExp( val, 'i' ), - igQuery : new RegExp( val, 'ig' ) - }); - - // don't build filter row if columnFilters is false or all columns are set to 'filter-false' - // see issue #156 - val = c.$headers.filter( '.filter-false, .parser-false' ).length; - if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { - // build filter row - ts.filter.buildRow( table, c, wo ); - } - - txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - c.$table.bind( txt, function( event, filter ) { - val = wo.filter_hideEmpty && - $.isEmptyObject( c.cache ) && - !( c.delayInit && event.type === 'appendCache' ); - // hide filter row using the 'filtered' class name - c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 - if ( !/(search|filter)/.test( event.type ) ) { - event.stopPropagation(); - ts.filter.buildDefault( table, true ); - } - if ( event.type === 'filterReset' ) { - c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); - ts.filter.searching( table, [] ); - } else if ( event.type === 'filterEnd' ) { - ts.filter.buildDefault( table, true ); - } else { - // send false argument to force a new search; otherwise if the filter hasn't changed, - // it will return - filter = event.type === 'search' ? filter : - event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; - if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { - // force a new search since content has changed - c.lastCombinedFilter = null; - c.lastSearch = []; - } - // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first - // input ensures all inputs are updated when a search is triggered on the table - // $( 'table' ).trigger( 'search', [...] ); - ts.filter.searching( table, filter, true ); - } - return false; - }); - - // reset button/link - if ( wo.filter_reset ) { - if ( wo.filter_reset instanceof $ ) { - // reset contains a jQuery object, bind to it - wo.filter_reset.click( function() { - c.$table.trigger( 'filterReset' ); - }); - } else if ( $( wo.filter_reset ).length ) { - // reset is a jQuery selector, use event delegation - $( document ) - .undelegate( wo.filter_reset, 'click.tsfilter' ) - .delegate( wo.filter_reset, 'click.tsfilter', function() { - // trigger a reset event, so other functions ( filter_formatter ) know when to reset + // reset button/link + if ( wo.filter_reset ) { + if ( wo.filter_reset instanceof $ ) { + // reset contains a jQuery object, bind to it + wo.filter_reset.click( function() { c.$table.trigger( 'filterReset' ); }); + } else if ( $( wo.filter_reset ).length ) { + // reset is a jQuery selector, use event delegation + $( document ) + .undelegate( wo.filter_reset, 'click.tsfilter' ) + .delegate( wo.filter_reset, 'click.tsfilter', function() { + // trigger a reset event, so other functions ( filter_formatter ) know when to reset + c.$table.trigger( 'filterReset' ); + }); + } } - } - if ( wo.filter_functions ) { - for ( column = 0; column < c.columns; column++ ) { - fxn = ts.getColumnData( table, wo.filter_functions, column ); - if ( fxn ) { - // remove 'filter-select' from header otherwise the options added here are replaced with - // all options - $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); - // don't build select if 'filter-false' or 'parser-false' set - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - options = ''; - if ( fxn === true && noSelect ) { - ts.filter.buildSelect( table, column ); - } else if ( typeof fxn === 'object' && noSelect ) { - // add custom drop down list - for ( string in fxn ) { - if ( typeof string === 'string' ) { - options += options === '' ? - '' : ''; - val = string; - txt = string; - if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - val = string.split( wo.filter_selectSourceSeparator ); - txt = val[1]; - val = val[0]; + if ( wo.filter_functions ) { + for ( column = 0; column < c.columns; column++ ) { + fxn = ts.getColumnData( table, wo.filter_functions, column ); + if ( fxn ) { + // remove 'filter-select' from header otherwise the options added here are replaced with + // all options + $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); + // don't build select if 'filter-false' or 'parser-false' set + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + options = ''; + if ( fxn === true && noSelect ) { + ts.filter.buildSelect( table, column ); + } else if ( typeof fxn === 'object' && noSelect ) { + // add custom drop down list + for ( string in fxn ) { + if ( typeof string === 'string' ) { + options += options === '' ? + '' : ''; + val = string; + txt = string; + if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + val = string.split( wo.filter_selectSourceSeparator ); + txt = val[1]; + val = val[0]; + } + options += ''; } - options += ''; + } + c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .append( options ); + txt = wo.filter_selectSource; + fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); + if ( fxn ) { + // updating so the extra options are appended + ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); } } + } + } + } + // not really updating, but if the column has both the 'filter-select' class & + // filter_functions set to true, it would append the same options twice. + ts.filter.buildDefault( table, true ); + + ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); + if ( wo.filter_external ) { + ts.filter.bindSearch( table, wo.filter_external ); + } + + if ( wo.filter_hideFilters ) { + ts.filter.hideFilters( table, c ); + } + + // show processing icon + if ( c.showProcessing ) { + txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function( event, columns ) { + // only add processing to certain columns to all columns + $header = ( columns ) ? c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .append( options ); - txt = wo.filter_selectSource; - fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); - if ( fxn ) { - // updating so the extra options are appended - ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } - } - } - // not really updating, but if the column has both the 'filter-select' class & - // filter_functions set to true, it would append the same options twice. - ts.filter.buildDefault( table, true ); - - ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); - if ( wo.filter_external ) { - ts.filter.bindSearch( table, wo.filter_external ); - } - - if ( wo.filter_hideFilters ) { - ts.filter.hideFilters( table, c ); - } - - // show processing icon - if ( c.showProcessing ) { - txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function( event, columns ) { - // only add processing to certain columns to all columns - $header = ( columns ) ? - c.$table - .find( '.' + tscss.header ) - .filter( '[data-column]' ) - .filter( function() { - return columns[ $( this ).data( 'column' ) ] !== ''; - }) : ''; - ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); - }); - } - - // set filtered rows count ( intially unfiltered ) - c.filteredRows = c.totalRows; - - // add default values - txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function() { - // redefine 'wo' as it does not update properly inside this callback - var wo = this.config.widgetOptions; - filters = ts.filter.setDefaults( table, c, wo ) || []; - if ( filters.length ) { - // prevent delayInit from triggering a cache build if filters are empty - if ( !( c.delayInit && filters.join( '' ) === '' ) ) { - ts.setFilters( table, filters, true ); - } - } - c.$table.trigger( 'filterFomatterUpdate' ); - // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers - setTimeout( function() { - if ( !wo.filter_initialized ) { - ts.filter.filterInitComplete( c ); - } - }, 100 ); - }); - // if filter widget is added after pager has initialized; then set filter init flag - if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { - c.$table.trigger( 'filterFomatterUpdate' ); - setTimeout( function() { - ts.filter.filterInitComplete( c ); - }, 100 ); - } - }, - // $cell parameter, but not the config, is passed to the filter_formatters, - // so we have to work with it instead - formatterUpdated: function( $cell, column ) { - var wo = $cell.closest( 'table' )[0].config.widgetOptions; - if ( !wo.filter_initialized ) { - // add updates by column since this function - // may be called numerous times before initialization - wo.filter_formatterInit[ column ] = 1; - } - }, - filterInitComplete: function( c ) { - var indx, len, - wo = c.widgetOptions, - count = 0, - completed = function() { - wo.filter_initialized = true; - c.$table.trigger( 'filterInit', c ); - ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); - }; - if ( $.isEmptyObject( wo.filter_formatter ) ) { - completed(); - } else { - len = wo.filter_formatterInit.length; - for ( indx = 0; indx < len; indx++ ) { - if ( wo.filter_formatterInit[ indx ] === 1 ) { - count++; - } - } - clearTimeout( wo.filter_initTimer ); - if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { - // filter widget initialized - completed(); - } else if ( !wo.filter_initialized ) { - // fall back in case a filter_formatter doesn't call - // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off - wo.filter_initTimer = setTimeout( function() { - completed(); - }, 500 ); - } - } - }, - setDefaults: function( table, c, wo ) { - var isArray, saved, indx, col, $filters, - // get current ( default ) filters - filters = ts.getFilters( table ) || []; - if ( wo.filter_saveFilters && ts.storage ) { - saved = ts.storage( table, 'tablesorter-filters' ) || []; - isArray = $.isArray( saved ); - // make sure we're not just getting an empty array - if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { - filters = saved; - } - } - // if no filters saved, then check default settings - if ( filters.join( '' ) === '' ) { - // allow adding default setting to external filters - $filters = c.$headers.add( wo.filter_$externalFilters ) - .filter( '[' + wo.filter_defaultAttrib + ']' ); - for ( indx = 0; indx <= c.columns; indx++ ) { - // include data-column='all' external filters - col = indx === c.columns ? 'all' : indx; - filters[indx] = $filters - .filter( '[data-column="' + col + '"]' ) - .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; - } - } - c.$table.data( 'lastSearch', filters ); - return filters; - }, - parseFilter: function( c, filter, column, parsed ) { - return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; - }, - buildRow: function( table, c, wo ) { - var col, column, $header, buildSelect, disabled, name, ffxn, tmp, - // c.columns defined in computeThIndexes() - cellFilter = wo.filter_cellFilter, - columns = c.columns, - arry = $.isArray( cellFilter ), - buildFilter = ''; - for ( column = 0; column < columns; column++ ) { - buildFilter += '' ).appendTo( c.$filters.eq( column ) ); - } else { - ffxn = ts.getColumnData( table, wo.filter_formatter, column ); - if ( ffxn ) { - wo.filter_formatterCount++; - buildFilter = ffxn( c.$filters.eq( column ), column ); - // no element returned, so lets go find it - if ( buildFilter && buildFilter.length === 0 ) { - buildFilter = c.$filters.eq( column ).children( 'input' ); - } - // element not in DOM, so lets attach it - if ( buildFilter && ( buildFilter.parent().length === 0 || - ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { - c.$filters.eq( column ).append( buildFilter ); - } - } else { - buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); - } - if ( buildFilter ) { - tmp = $header.data( 'placeholder' ) || - $header.attr( 'data-placeholder' ) || - wo.filter_placeholder.search || ''; - buildFilter.attr( 'placeholder', tmp ); - } - } - if ( buildFilter ) { - // add filter class name - name = ( $.isArray( wo.filter_cssFilter ) ? - ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : - wo.filter_cssFilter ) || ''; - buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); - if ( disabled ) { - buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; - } - } - } - }, - bindSearch: function( table, $el, internal ) { - table = $( table )[0]; - $el = $( $el ); // allow passing a selector string - if ( !$el.length ) { return; } - var tmp, - c = table.config, - wo = c.widgetOptions, - namespace = c.namespace + 'filter', - $ext = wo.filter_$externalFilters; - if ( internal !== true ) { - // save anyMatch element - tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; - wo.filter_$anyMatch = $el.filter( tmp ); - if ( $ext && $ext.length ) { - wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); - } else { - wo.filter_$externalFilters = $el; - } - // update values ( external filters added after table initialization ) - ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); - } - // unbind events - tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); - $el - // use data attribute instead of jQuery data since the head is cloned without including - // the data/binding - .attr( 'data-lastSearchTime', new Date().getTime() ) - .unbind( tmp.replace( /\s+/g, ' ' ) ) - // include change for select - fixes #473 - .bind( 'keyup' + namespace, function( event ) { - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - // emulate what webkit does.... escape clears the filter - if ( event.which === 27 ) { - this.value = ''; - // live search - } else if ( wo.filter_liveSearch === false ) { - return; - // don't return if the search value is empty ( all rows need to be revealed ) - } else if ( this.value !== '' && ( - // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace - ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || - // let return & backspace continue on, but ignore arrows & non-valid characters - ( event.which !== 13 && event.which !== 8 && - ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { - return; - } - // change event = no delay; last true flag tells getFilters to skip newest timed input - ts.filter.searching( table, true, true ); - }) - .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { - var column = $( this ).data( 'column' ); - // don't allow 'change' event to process if the input value is the same - fixes #685 - if ( event.which === 13 || event.type === 'search' || - event.type === 'change' && this.value !== c.lastSearch[column] ) { - event.preventDefault(); - // init search with no delay - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - ts.filter.searching( table, false, true ); - } - }); - }, - searching: function( table, filter, skipFirst ) { - var wo = table.config.widgetOptions; - clearTimeout( wo.searchTimer ); - if ( typeof filter === 'undefined' || filter === true ) { - // delay filtering - wo.searchTimer = setTimeout( function() { - ts.filter.checkFilters( table, filter, skipFirst ); - }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); - } else { - // skip delay - ts.filter.checkFilters( table, filter, skipFirst ); - } - }, - checkFilters: function( table, filter, skipFirst ) { - var c = table.config, - wo = c.widgetOptions, - filterArray = $.isArray( filter ), - filters = ( filterArray ) ? filter : ts.getFilters( table, true ), - combinedFilters = ( filters || [] ).join( '' ); // combined filter values - // prevent errors if delay init is set - if ( $.isEmptyObject( c.cache ) ) { - // update cache if delayInit set & pager has initialized ( after user initiates a search ) - if ( c.delayInit && c.pager && c.pager.initialized ) { - c.$table.trigger( 'updateCache', [ function() { - ts.filter.checkFilters( table, false, skipFirst ); - } ] ); - } - return; - } - // add filter array back into inputs - if ( filterArray ) { - ts.setFilters( table, filters, false, skipFirst !== true ); - if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } - } - if ( wo.filter_hideFilters ) { - // show/hide filter row as needed - c.$table - .find( '.' + tscss.filterRow ) - .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); - } - // return if the last search is the same; but filter === false when updating the search - // see example-widget-filter.html filter toggle buttons - if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { - return; - } else if ( filter === false ) { - // force filter refresh - c.lastCombinedFilter = null; - c.lastSearch = []; - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterStart', [filters] ); - } - if ( c.showProcessing ) { - // give it time for the processing icon to kick in - setTimeout( function() { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - }, 30 ); - } else { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - } - }, - hideFilters: function( table, c ) { - var timer; - c.$table - .find( '.' + tscss.filterRow ) - .bind( 'mouseenter mouseleave', function( e ) { - // save event object - http://bugs.jquery.com/ticket/12140 - var event = e, - $filterRow = $( this ); - clearTimeout( timer ); - timer = setTimeout( function() { - if ( /enter|over/.test( event.type ) ) { - $filterRow.removeClass( tscss.filterRowHide ); - } else { - // don't hide if input has focus - // $( ':focus' ) needs jQuery 1.6+ - if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { - // don't hide row if any filter has a value - if ( c.lastCombinedFilter === '' ) { - $filterRow.addClass( tscss.filterRowHide ); - } - } - } - }, 200 ); - }) - .find( 'input, select' ).bind( 'focus blur', function( e ) { - var event = e, - $row = $( this ).closest( 'tr' ); - clearTimeout( timer ); - timer = setTimeout( function() { - clearTimeout( timer ); - // don't hide row if any filter has a value - if ( ts.getFilters( c.$table ).join( '' ) === '' ) { - $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); - } - }, 200 ); - }); - }, - defaultFilter: function( filter, mask ) { - if ( filter === '' ) { return filter; } - var regex = ts.filter.regex.iQuery, - maskLen = mask.match( ts.filter.regex.igQuery ).length, - query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], - len = query.length - 1, - indx = 0, - val = mask; - if ( len < 1 && maskLen > 1 ) { - // only one 'word' in query but mask has >1 slots - query[1] = query[0]; - } - // replace all {query} with query words... - // if query = 'Bob', then convert mask from '!{query}' to '!Bob' - // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' - while ( regex.test( val ) ) { - val = val.replace( regex, query[indx++] || '' ); - if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { - val = mask.replace( regex, val ); - } - } - return val; - }, - getLatestSearch: function( $input ) { - if ( $input ) { - return $input.sort( function( a, b ) { - return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); - }); - } - return $input || $(); - }, - multipleColumns: function( c, $input ) { - // look for multiple columns '1-3,4-6,8' in data-column - var temp, ranges, range, start, end, singles, i, indx, len, - wo = c.widgetOptions, - // only target 'all' column inputs on initialization - // & don't target 'all' column inputs if they don't exist - targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, - columns = [], - val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); - // process column range - if ( targets && /-/.test( val ) ) { - ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); - len = ranges.length; - for ( indx = 0; indx < len; indx++ ) { - range = ranges[indx].split( /\s*-\s*/ ); - start = parseInt( range[0], 10 ) || 0; - end = parseInt( range[1], 10 ) || ( c.columns - 1 ); - if ( start > end ) { - temp = start; start = end; end = temp; // swap - } - if ( end >= c.columns ) { - end = c.columns - 1; - } - for ( ; start <= end; start++ ) { - columns.push( start ); - } - // remove processed range from val - val = val.replace( ranges[ indx ], '' ); - } - } - // process single columns - if ( targets && /,/.test( val ) ) { - singles = val.split( /\s*,\s*/ ); - len = singles.length; - for ( i = 0; i < len; i++ ) { - if ( singles[ i ] !== '' ) { - indx = parseInt( singles[ i ], 10 ); - if ( indx < c.columns ) { - columns.push( indx ); - } - } - } - } - // return all columns - if ( !columns.length ) { - for ( indx = 0; indx < c.columns; indx++ ) { - columns.push( indx ); - } - } - return columns; - }, - processTypes: function( c, data, vars ) { - var ffxn, - filterMatched = null, - matches = null; - for ( ffxn in ts.filter.types ) { - if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { - matches = ts.filter.types[ffxn]( c, data, vars ); - if ( matches !== null ) { - filterMatched = matches; - } - } - } - return filterMatched; - }, - processRow: function( c, data, vars ) { - var columnIndex, hasSelect, result, val, filterMatched, - fxn, ffxn, txt, - regex = ts.filter.regex, - wo = c.widgetOptions, - showRow = true; - data.$cells = data.$row.children(); - - if ( data.anyMatchFlag ) { - // look for multiple columns '1-3,4-6,8' - columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); - data.anyMatch = true; - data.isMatch = true; - data.rowArray = data.$cells.map( function( i ) { - if ( $.inArray( i, columnIndex ) > -1 ) { - if ( data.parsed[ i ] ) { - txt = data.cacheArray[ i ]; - } else { - txt = data.rawArray[ i ]; - txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); - if ( c.sortLocaleCompare ) { - txt = ts.replaceAccents( txt ); - } - } - return txt; - } - }).get(); - data.filter = data.anyMatchFilter; - data.iFilter = data.iAnyMatchFilter; - data.exact = data.rowArray.join( ' ' ); - data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; - data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); - - vars.excludeMatch = vars.noAnyMatch; - filterMatched = ts.filter.processTypes( c, data, vars ); - - if ( filterMatched !== null ) { - showRow = filterMatched; - } else { - if ( wo.filter_startsWith ) { - showRow = false; - columnIndex = c.columns; - while ( !showRow && columnIndex > 0 ) { - columnIndex--; - showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; - } - } else { - showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; - } - } - data.anyMatch = false; - // no other filters to process - if ( data.filters.join( '' ) === data.filter ) { - return showRow; - } - } - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - data.filter = data.filters[ columnIndex ]; - data.index = columnIndex; - - // filter types to exclude, per column - vars.excludeMatch = vars.excludeFilter[ columnIndex ]; - - // ignore if filter is empty or disabled - if ( data.filter ) { - data.cache = data.cacheArray[ columnIndex ]; - // check if column data should be from the cell or from parsed data - if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { - data.exact = data.cache; - } else { - result = data.rawArray[ columnIndex ] || ''; - data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 - } - data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? - data.exact.toLowerCase() : data.exact; - - data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); - - result = showRow; // if showRow is true, show that row - - // in case select filter option has a different value vs text 'a - z|A through Z' - ffxn = wo.filter_columnFilters ? - c.$filters.add( c.$externalFilters ) - .filter( '[data-column="'+ columnIndex + '"]' ) - .find( 'select option:selected' ) - .attr( 'data-function-name' ) || '' : ''; - // replace accents - see #357 - if ( c.sortLocaleCompare ) { - data.filter = ts.replaceAccents( data.filter ); - } - - val = true; - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { - data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); - // val is used to indicate that a filter select is using a default filter; - // so we override the exact & partial matches - val = false; - } - // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), - // data.filter = case sensitive - data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; - fxn = vars.functions[ columnIndex ]; - hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); - filterMatched = null; - if ( fxn || ( hasSelect && val ) ) { - if ( fxn === true || hasSelect ) { - // default selector uses exact match unless 'filter-match' class is found - filterMatched = data.isMatch ? - data.iExact.search( data.iFilter ) >= 0 : - data.filter === data.exact; - } else if ( typeof fxn === 'function' ) { - // filter callback( exact cell content, parser normalized content, - // filter input value, column index, jQuery row object ) - filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { - // selector option function - txt = ffxn || data.filter; - filterMatched = - fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } - } - if ( filterMatched === null ) { - // cycle through the different filters - // filters return a boolean or null if nothing matches - filterMatched = ts.filter.processTypes( c, data, vars ); - if ( filterMatched !== null ) { - result = filterMatched; - // Look for match, and add child row data for matching - } else { - txt = ( data.iExact + data.childRowText ) - .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); - result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); - } - } else { - result = filterMatched; - } - showRow = ( result ) ? showRow : false; - } - } - return showRow; - }, - findRows: function( table, filters, combinedFilters ) { - if ( table.config.lastCombinedFilter === combinedFilters || - !table.config.widgetOptions.filter_initialized ) { - return; - } - var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, - isChild, childRow, lastSearch, showRow, time, val, indx, - notFiltered, searchFiltered, query, injected, res, id, txt, - storedFilters = $.extend( [], filters ), - regex = ts.filter.regex, - c = table.config, - wo = c.widgetOptions, - // data object passed to filters; anyMatch is a flag for the filters - data = { - anyMatch: false, - filters: filters, - // regex filter type cache - filter_regexCache : [] - }, - vars = { - // anyMatch really screws up with these types of filters - noAnyMatch: [ 'range', 'notMatch', 'operators' ], - // cache filter variables that use ts.getColumnData in the main loop - functions : [], - excludeFilter : [], - defaultColFilter : [], - defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' - }; - - // parse columns after formatter, in case the class is added at that point - data.parsed = c.$headers.map( function( columnIndex ) { - return c.parsers && c.parsers[ columnIndex ] && - // force parsing if parser type is numeric - c.parsers[ columnIndex ].parsed || - // getData won't return 'parsed' if other 'filter-' class names exist - // ( e.g. ) - ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], - ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || - $( this ).hasClass( 'filter-parsed' ); - }).get(); - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - vars.functions[ columnIndex ] = - ts.getColumnData( table, wo.filter_functions, columnIndex ); - vars.defaultColFilter[ columnIndex ] = - ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; - vars.excludeFilter[ columnIndex ] = - ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); - } - - if ( c.debug ) { - ts.log( 'Filter: Starting filter widget search', filters ); - time = new Date(); - } - // filtered rows count - c.filteredRows = 0; - c.totalRows = 0; - // combindedFilters are undefined on init - combinedFilters = ( storedFilters || [] ).join( '' ); - - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); - // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! - // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); - columnIndex = c.columns; - // convert stored rows into a jQuery object - norm_rows = c.cache[ tbodyIndex ].normalized; - $rows = $( $.map( norm_rows, function( el ) { - return el[ columnIndex ].$row.get(); - }) ); - - if ( combinedFilters === '' || wo.filter_serversideFiltering ) { - $rows - .removeClass( wo.filter_filteredRow ) - .not( '.' + c.cssChildRow ) - .css( 'display', '' ); - } else { - // filter out child rows - $rows = $rows.not( '.' + c.cssChildRow ); - len = $rows.length; - - if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || - typeof filters[c.columns] !== 'undefined' ) { - data.anyMatchFlag = true; - data.anyMatchFilter = '' + ( - filters[ c.columns ] || - wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || - '' - ); - if ( wo.filter_columnAnyMatch ) { - // specific columns search - query = data.anyMatchFilter.split( regex.andSplit ); - injected = false; - for ( indx = 0; indx < query.length; indx++ ) { - res = query[ indx ].split( ':' ); - if ( res.length > 1 ) { - // make the column a one-based index ( non-developers start counting from one :P ) - id = parseInt( res[0], 10 ) - 1; - if ( id >= 0 && id < c.columns ) { // if id is an integer - filters[ id ] = res[1]; - query.splice( indx, 1 ); - indx--; - injected = true; - } - } - } - if ( injected ) { - data.anyMatchFilter = query.join( ' && ' ); - } - } - } - - // optimize searching only through already filtered rows - see #313 - searchFiltered = wo.filter_searchFiltered; - lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; - if ( searchFiltered ) { - // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 - for ( indx = 0; indx < columnIndex + 1; indx++ ) { - val = filters[indx] || ''; - // break out of loop if we've already determined not to search filtered rows - if ( !searchFiltered ) { indx = columnIndex; } - // search already filtered rows if... - searchFiltered = searchFiltered && lastSearch.length && - // there are no changes from beginning of filter - val.indexOf( lastSearch[indx] || '' ) === 0 && - // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string - !regex.alreadyFiltered.test( val ) && - // if we are not doing exact matches, using '|' ( logical or ) or not '!' - !/[=\"\|!]/.test( val ) && - // don't search only filtered if the value is negative - // ( '> -10' => '> -100' will ignore hidden rows ) - !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && - // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 - !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && - !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); - } - } - notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; - // can't search when all rows are hidden - this happens when looking for exact matches - if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } - if ( c.debug ) { - ts.log( 'Filter: Searching through ' + - ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); - } - if ( data.anyMatchFlag ) { - if ( c.sortLocaleCompare ) { - // replace accents - data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); - } - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { - data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); - // clear search filtered flag because default filters are not saved to the last search - searchFiltered = false; - } - // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true - // when c.ignoreCase is true, the cache contains all lower case data - data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? - data.anyMatchFilter : - data.anyMatchFilter.toLowerCase(); - } - - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - - txt = $rows[ rowIndex ].className; - // the first row can never be a child row - isChild = rowIndex && regex.child.test( txt ); - // skip child rows & already filtered rows - if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { - continue; - } - - data.$row = $rows.eq( rowIndex ); - data.cacheArray = norm_rows[ rowIndex ]; - rowData = data.cacheArray[ c.columns ]; - data.rawArray = rowData.raw; - data.childRowText = ''; - - if ( !wo.filter_childByColumn ) { - txt = ''; - // child row cached text - childRow = rowData.child; - // so, if 'table.config.widgetOptions.filter_childRows' is true and there is - // a match anywhere in the child row, then it will make the row visible - // checked here so the option can be changed dynamically - for ( indx = 0; indx < childRow.length; indx++ ) { - txt += ' ' + childRow[indx].join( '' ) || ''; - } - data.childRowText = wo.filter_childRows ? - ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : - ''; - } - - showRow = ts.filter.processRow( c, data, vars ); - childRow = rowData.$row.filter( ':gt( 0 )' ); - - if ( wo.filter_childRows && childRow.length ) { - if ( wo.filter_childByColumn ) { - // cycle through each child row - for ( indx = 0; indx < childRow.length; indx++ ) { - data.$row = childRow.eq( indx ); - data.cacheArray = rowData.child[ indx ]; - data.rawArray = data.cacheArray; - // use OR comparison on child rows - showRow = showRow || ts.filter.processRow( c, data, vars ); - } - } - childRow.toggleClass( wo.filter_filteredRow, !showRow ); - } - - rowData.$row - .toggleClass( wo.filter_filteredRow, !showRow )[0] - .display = showRow ? '' : 'none'; - } - } - c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; - c.totalRows += $rows.length; - ts.processTbody( table, $tbody, false ); - } - c.lastCombinedFilter = combinedFilters; // save last search - // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) - c.lastSearch = storedFilters; - c.$table.data( 'lastSearch', storedFilters ); - if ( wo.filter_saveFilters && ts.storage ) { - ts.storage( table, 'tablesorter-filters', storedFilters ); - } - if ( c.debug ) { - ts.benchmark( 'Completed filter widget search', time ); - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterEnd', c ); - } - setTimeout( function() { - c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied - }, 0 ); - }, - getOptionSource: function( table, column, onlyAvail ) { - table = $( table )[0]; - var cts, txt, indx, len, - c = table.config, - wo = c.widgetOptions, - parsed = [], - arry = false, - source = wo.filter_selectSource, - last = c.$table.data( 'lastSearch' ) || [], - fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); - - if ( onlyAvail && last[column] !== '' ) { - onlyAvail = false; - } - - // filter select source option - if ( fxn === true ) { - // OVERALL source - arry = source( table, column, onlyAvail ); - } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { - // selectSource is a jQuery object or string of options - return fxn; - } else if ( $.isArray( fxn ) ) { - arry = fxn; - } else if ( $.type( source ) === 'object' && fxn ) { - // custom select source function for a SPECIFIC COLUMN - arry = fxn( table, column, onlyAvail ); - } - if ( arry === false ) { - // fall back to original method - arry = ts.filter.getOptions( table, column, onlyAvail ); - } - - // get unique elements and sort the list - // if $.tablesorter.sortText exists ( not in the original tablesorter ), - // then natural sort the list otherwise use a basic sort - arry = $.grep( arry, function( value, indx ) { - return $.inArray( value, arry ) === indx; - }); - - if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { - // unsorted select options - return arry; - } else { - len = arry.length; - // parse select option values - for ( indx = 0; indx < len; indx++ ) { - txt = arry[ indx ]; - // parse array data using set column parser; this DOES NOT pass the original - // table cell to the parser format function - parsed.push({ - t : txt, - // check parser length - fixes #934 - p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + .find( '.' + tscss.header ) + .filter( '[data-column]' ) + .filter( function() { + return columns[ $( this ).data( 'column' ) ] !== ''; + }) : ''; + ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); }); } - // sort parsed select options - cts = c.textSorter || ''; - parsed.sort( function( a, b ) { - // sortNatural breaks if you don't pass it strings - var x = a.p.toString(), - y = b.p.toString(); - if ( $.isFunction( cts ) ) { - // custom OVERALL text sorter - return cts( x, y, true, column, table ); - } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) { - // custom text sorter for a SPECIFIC COLUMN - return cts[column]( x, y, true, column, table ); - } else if ( ts.sortNatural ) { - // fall back to natural sort - return ts.sortNatural( x, y ); + // set filtered rows count ( intially unfiltered ) + c.filteredRows = c.totalRows; + + // add default values + txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function() { + // redefine 'wo' as it does not update properly inside this callback + var wo = this.config.widgetOptions; + filters = ts.filter.setDefaults( table, c, wo ) || []; + if ( filters.length ) { + // prevent delayInit from triggering a cache build if filters are empty + if ( !( c.delayInit && filters.join( '' ) === '' ) ) { + ts.setFilters( table, filters, true ); + } } - // using an older version! do a basic sort - return true; + c.$table.trigger( 'filterFomatterUpdate' ); + // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers + setTimeout( function() { + if ( !wo.filter_initialized ) { + ts.filter.filterInitComplete( c ); + } + }, 100 ); }); - // rebuild arry from sorted parsed data - arry = []; - len = parsed.length; - for ( indx = 0; indx < len; indx++ ) { - arry.push( parsed[indx].t ); + // if filter widget is added after pager has initialized; then set filter init flag + if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { + c.$table.trigger( 'filterFomatterUpdate' ); + setTimeout( function() { + ts.filter.filterInitComplete( c ); + }, 100 ); + } + }, + // $cell parameter, but not the config, is passed to the filter_formatters, + // so we have to work with it instead + formatterUpdated: function( $cell, column ) { + var wo = $cell.closest( 'table' )[0].config.widgetOptions; + if ( !wo.filter_initialized ) { + // add updates by column since this function + // may be called numerous times before initialization + wo.filter_formatterInit[ column ] = 1; + } + }, + filterInitComplete: function( c ) { + var indx, len, + wo = c.widgetOptions, + count = 0, + completed = function() { + wo.filter_initialized = true; + c.$table.trigger( 'filterInit', c ); + ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); + }; + if ( $.isEmptyObject( wo.filter_formatter ) ) { + completed(); + } else { + len = wo.filter_formatterInit.length; + for ( indx = 0; indx < len; indx++ ) { + if ( wo.filter_formatterInit[ indx ] === 1 ) { + count++; + } + } + clearTimeout( wo.filter_initTimer ); + if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { + // filter widget initialized + completed(); + } else if ( !wo.filter_initialized ) { + // fall back in case a filter_formatter doesn't call + // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off + wo.filter_initTimer = setTimeout( function() { + completed(); + }, 500 ); + } + } + }, + setDefaults: function( table, c, wo ) { + var isArray, saved, indx, col, $filters, + // get current ( default ) filters + filters = ts.getFilters( table ) || []; + if ( wo.filter_saveFilters && ts.storage ) { + saved = ts.storage( table, 'tablesorter-filters' ) || []; + isArray = $.isArray( saved ); + // make sure we're not just getting an empty array + if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { + filters = saved; + } + } + // if no filters saved, then check default settings + if ( filters.join( '' ) === '' ) { + // allow adding default setting to external filters + $filters = c.$headers.add( wo.filter_$externalFilters ) + .filter( '[' + wo.filter_defaultAttrib + ']' ); + for ( indx = 0; indx <= c.columns; indx++ ) { + // include data-column='all' external filters + col = indx === c.columns ? 'all' : indx; + filters[indx] = $filters + .filter( '[data-column="' + col + '"]' ) + .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; + } + } + c.$table.data( 'lastSearch', filters ); + return filters; + }, + parseFilter: function( c, filter, column, parsed ) { + return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; + }, + buildRow: function( table, c, wo ) { + var col, column, $header, buildSelect, disabled, name, ffxn, tmp, + // c.columns defined in computeThIndexes() + cellFilter = wo.filter_cellFilter, + columns = c.columns, + arry = $.isArray( cellFilter ), + buildFilter = ''; + for ( column = 0; column < columns; column++ ) { + buildFilter += '' ).appendTo( c.$filters.eq( column ) ); + } else { + ffxn = ts.getColumnData( table, wo.filter_formatter, column ); + if ( ffxn ) { + wo.filter_formatterCount++; + buildFilter = ffxn( c.$filters.eq( column ), column ); + // no element returned, so lets go find it + if ( buildFilter && buildFilter.length === 0 ) { + buildFilter = c.$filters.eq( column ).children( 'input' ); + } + // element not in DOM, so lets attach it + if ( buildFilter && ( buildFilter.parent().length === 0 || + ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { + c.$filters.eq( column ).append( buildFilter ); + } + } else { + buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); + } + if ( buildFilter ) { + tmp = $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.search || ''; + buildFilter.attr( 'placeholder', tmp ); + } + } + if ( buildFilter ) { + // add filter class name + name = ( $.isArray( wo.filter_cssFilter ) ? + ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : + wo.filter_cssFilter ) || ''; + buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); + if ( disabled ) { + buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; + } + } + } + }, + bindSearch: function( table, $el, internal ) { + table = $( table )[0]; + $el = $( $el ); // allow passing a selector string + if ( !$el.length ) { return; } + var tmp, + c = table.config, + wo = c.widgetOptions, + namespace = c.namespace + 'filter', + $ext = wo.filter_$externalFilters; + if ( internal !== true ) { + // save anyMatch element + tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; + wo.filter_$anyMatch = $el.filter( tmp ); + if ( $ext && $ext.length ) { + wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); + } else { + wo.filter_$externalFilters = $el; + } + // update values ( external filters added after table initialization ) + ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); + } + // unbind events + tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); + $el + // use data attribute instead of jQuery data since the head is cloned without including + // the data/binding + .attr( 'data-lastSearchTime', new Date().getTime() ) + .unbind( tmp.replace( /\s+/g, ' ' ) ) + // include change for select - fixes #473 + .bind( 'keyup' + namespace, function( event ) { + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + // emulate what webkit does.... escape clears the filter + if ( event.which === 27 ) { + this.value = ''; + // live search + } else if ( wo.filter_liveSearch === false ) { + return; + // don't return if the search value is empty ( all rows need to be revealed ) + } else if ( this.value !== '' && ( + // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace + ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || + // let return & backspace continue on, but ignore arrows & non-valid characters + ( event.which !== 13 && event.which !== 8 && + ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { + return; + } + // change event = no delay; last true flag tells getFilters to skip newest timed input + ts.filter.searching( table, true, true ); + }) + .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { + var column = $( this ).data( 'column' ); + // don't allow 'change' event to process if the input value is the same - fixes #685 + if ( event.which === 13 || event.type === 'search' || + event.type === 'change' && this.value !== c.lastSearch[column] ) { + event.preventDefault(); + // init search with no delay + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + ts.filter.searching( table, false, true ); + } + }); + }, + searching: function( table, filter, skipFirst ) { + var wo = table.config.widgetOptions; + clearTimeout( wo.searchTimer ); + if ( typeof filter === 'undefined' || filter === true ) { + // delay filtering + wo.searchTimer = setTimeout( function() { + ts.filter.checkFilters( table, filter, skipFirst ); + }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); + } else { + // skip delay + ts.filter.checkFilters( table, filter, skipFirst ); + } + }, + checkFilters: function( table, filter, skipFirst ) { + var c = table.config, + wo = c.widgetOptions, + filterArray = $.isArray( filter ), + filters = ( filterArray ) ? filter : ts.getFilters( table, true ), + combinedFilters = ( filters || [] ).join( '' ); // combined filter values + // prevent errors if delay init is set + if ( $.isEmptyObject( c.cache ) ) { + // update cache if delayInit set & pager has initialized ( after user initiates a search ) + if ( c.delayInit && c.pager && c.pager.initialized ) { + c.$table.trigger( 'updateCache', [ function() { + ts.filter.checkFilters( table, false, skipFirst ); + } ] ); + } + return; + } + // add filter array back into inputs + if ( filterArray ) { + ts.setFilters( table, filters, false, skipFirst !== true ); + if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } + } + if ( wo.filter_hideFilters ) { + // show/hide filter row as needed + c.$table + .find( '.' + tscss.filterRow ) + .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); + } + // return if the last search is the same; but filter === false when updating the search + // see example-widget-filter.html filter toggle buttons + if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { + return; + } else if ( filter === false ) { + // force filter refresh + c.lastCombinedFilter = null; + c.lastSearch = []; + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterStart', [ filters ] ); + } + if ( c.showProcessing ) { + // give it time for the processing icon to kick in + setTimeout( function() { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + }, 30 ); + } else { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + } + }, + hideFilters: function( table, c ) { + var timer; + c.$table + .find( '.' + tscss.filterRow ) + .bind( 'mouseenter mouseleave', function( e ) { + // save event object - http://bugs.jquery.com/ticket/12140 + var event = e, + $filterRow = $( this ); + clearTimeout( timer ); + timer = setTimeout( function() { + if ( /enter|over/.test( event.type ) ) { + $filterRow.removeClass( tscss.filterRowHide ); + } else { + // don't hide if input has focus + // $( ':focus' ) needs jQuery 1.6+ + if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { + // don't hide row if any filter has a value + if ( c.lastCombinedFilter === '' ) { + $filterRow.addClass( tscss.filterRowHide ); + } + } + } + }, 200 ); + }) + .find( 'input, select' ).bind( 'focus blur', function( e ) { + var event = e, + $row = $( this ).closest( 'tr' ); + clearTimeout( timer ); + timer = setTimeout( function() { + clearTimeout( timer ); + // don't hide row if any filter has a value + if ( ts.getFilters( c.$table ).join( '' ) === '' ) { + $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); + } + }, 200 ); + }); + }, + defaultFilter: function( filter, mask ) { + if ( filter === '' ) { return filter; } + var regex = ts.filter.regex.iQuery, + maskLen = mask.match( ts.filter.regex.igQuery ).length, + query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], + len = query.length - 1, + indx = 0, + val = mask; + if ( len < 1 && maskLen > 1 ) { + // only one 'word' in query but mask has >1 slots + query[1] = query[0]; + } + // replace all {query} with query words... + // if query = 'Bob', then convert mask from '!{query}' to '!Bob' + // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' + while ( regex.test( val ) ) { + val = val.replace( regex, query[indx++] || '' ); + if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { + val = mask.replace( regex, val ); + } + } + return val; + }, + getLatestSearch: function( $input ) { + if ( $input ) { + return $input.sort( function( a, b ) { + return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); + }); + } + return $input || $(); + }, + multipleColumns: function( c, $input ) { + // look for multiple columns '1-3,4-6,8' in data-column + var temp, ranges, range, start, end, singles, i, indx, len, + wo = c.widgetOptions, + // only target 'all' column inputs on initialization + // & don't target 'all' column inputs if they don't exist + targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, + columns = [], + val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); + // process column range + if ( targets && /-/.test( val ) ) { + ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); + len = ranges.length; + for ( indx = 0; indx < len; indx++ ) { + range = ranges[indx].split( /\s*-\s*/ ); + start = parseInt( range[0], 10 ) || 0; + end = parseInt( range[1], 10 ) || ( c.columns - 1 ); + if ( start > end ) { + temp = start; start = end; end = temp; // swap + } + if ( end >= c.columns ) { + end = c.columns - 1; + } + for ( ; start <= end; start++ ) { + columns.push( start ); + } + // remove processed range from val + val = val.replace( ranges[ indx ], '' ); + } + } + // process single columns + if ( targets && /,/.test( val ) ) { + singles = val.split( /\s*,\s*/ ); + len = singles.length; + for ( i = 0; i < len; i++ ) { + if ( singles[ i ] !== '' ) { + indx = parseInt( singles[ i ], 10 ); + if ( indx < c.columns ) { + columns.push( indx ); + } + } + } + } + // return all columns + if ( !columns.length ) { + for ( indx = 0; indx < c.columns; indx++ ) { + columns.push( indx ); + } + } + return columns; + }, + processTypes: function( c, data, vars ) { + var ffxn, + filterMatched = null, + matches = null; + for ( ffxn in ts.filter.types ) { + if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { + matches = ts.filter.types[ffxn]( c, data, vars ); + if ( matches !== null ) { + filterMatched = matches; + } + } + } + return filterMatched; + }, + processRow: function( c, data, vars ) { + var columnIndex, hasSelect, result, val, filterMatched, + fxn, ffxn, txt, + regex = ts.filter.regex, + wo = c.widgetOptions, + showRow = true; + data.$cells = data.$row.children(); + + if ( data.anyMatchFlag ) { + // look for multiple columns '1-3,4-6,8' + columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); + data.anyMatch = true; + data.isMatch = true; + data.rowArray = data.$cells.map( function( i ) { + if ( $.inArray( i, columnIndex ) > -1 ) { + if ( data.parsed[ i ] ) { + txt = data.cacheArray[ i ]; + } else { + txt = data.rawArray[ i ]; + txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); + if ( c.sortLocaleCompare ) { + txt = ts.replaceAccents( txt ); + } + } + return txt; + } + }).get(); + data.filter = data.anyMatchFilter; + data.iFilter = data.iAnyMatchFilter; + data.exact = data.rowArray.join( ' ' ); + data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; + data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); + + vars.excludeMatch = vars.noAnyMatch; + filterMatched = ts.filter.processTypes( c, data, vars ); + + if ( filterMatched !== null ) { + showRow = filterMatched; + } else { + if ( wo.filter_startsWith ) { + showRow = false; + columnIndex = c.columns; + while ( !showRow && columnIndex > 0 ) { + columnIndex--; + showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; + } + } else { + showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; + } + } + data.anyMatch = false; + // no other filters to process + if ( data.filters.join( '' ) === data.filter ) { + return showRow; + } + } + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.filter = data.filters[ columnIndex ]; + data.index = columnIndex; + + // filter types to exclude, per column + vars.excludeMatch = vars.excludeFilter[ columnIndex ]; + + // ignore if filter is empty or disabled + if ( data.filter ) { + data.cache = data.cacheArray[ columnIndex ]; + // check if column data should be from the cell or from parsed data + if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { + data.exact = data.cache; + } else { + result = data.rawArray[ columnIndex ] || ''; + data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 + } + data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? + data.exact.toLowerCase() : data.exact; + + data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); + + result = showRow; // if showRow is true, show that row + + // in case select filter option has a different value vs text 'a - z|A through Z' + ffxn = wo.filter_columnFilters ? + c.$filters.add( c.$externalFilters ) + .filter( '[data-column="' + columnIndex + '"]' ) + .find( 'select option:selected' ) + .attr( 'data-function-name' ) || '' : ''; + // replace accents - see #357 + if ( c.sortLocaleCompare ) { + data.filter = ts.replaceAccents( data.filter ); + } + + val = true; + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { + data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); + // val is used to indicate that a filter select is using a default filter; + // so we override the exact & partial matches + val = false; + } + // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), + // data.filter = case sensitive + data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; + fxn = vars.functions[ columnIndex ]; + hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); + filterMatched = null; + if ( fxn || ( hasSelect && val ) ) { + if ( fxn === true || hasSelect ) { + // default selector uses exact match unless 'filter-match' class is found + filterMatched = data.isMatch ? + data.iExact.search( data.iFilter ) >= 0 : + data.filter === data.exact; + } else if ( typeof fxn === 'function' ) { + // filter callback( exact cell content, parser normalized content, + // filter input value, column index, jQuery row object ) + filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { + // selector option function + txt = ffxn || data.filter; + filterMatched = + fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } + } + if ( filterMatched === null ) { + // cycle through the different filters + // filters return a boolean or null if nothing matches + filterMatched = ts.filter.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + result = filterMatched; + // Look for match, and add child row data for matching + } else { + txt = ( data.iExact + data.childRowText ) + .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); + result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); + } + } else { + result = filterMatched; + } + showRow = ( result ) ? showRow : false; + } + } + return showRow; + }, + findRows: function( table, filters, combinedFilters ) { + if ( table.config.lastCombinedFilter === combinedFilters || + !table.config.widgetOptions.filter_initialized ) { + return; + } + var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, + isChild, childRow, lastSearch, showRow, time, val, indx, + notFiltered, searchFiltered, query, injected, res, id, txt, + storedFilters = $.extend( [], filters ), + regex = ts.filter.regex, + c = table.config, + wo = c.widgetOptions, + // data object passed to filters; anyMatch is a flag for the filters + data = { + anyMatch: false, + filters: filters, + // regex filter type cache + filter_regexCache : [] + }, + vars = { + // anyMatch really screws up with these types of filters + noAnyMatch: [ 'range', 'notMatch', 'operators' ], + // cache filter variables that use ts.getColumnData in the main loop + functions : [], + excludeFilter : [], + defaultColFilter : [], + defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' + }; + + // parse columns after formatter, in case the class is added at that point + data.parsed = c.$headers.map( function( columnIndex ) { + return c.parsers && c.parsers[ columnIndex ] && + // force parsing if parser type is numeric + c.parsers[ columnIndex ].parsed || + // getData won't return 'parsed' if other 'filter-' class names exist + // ( e.g. ) + ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], + ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || + $( this ).hasClass( 'filter-parsed' ); + }).get(); + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + vars.functions[ columnIndex ] = + ts.getColumnData( table, wo.filter_functions, columnIndex ); + vars.defaultColFilter[ columnIndex ] = + ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; + vars.excludeFilter[ columnIndex ] = + ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); + } + + if ( c.debug ) { + ts.log( 'Filter: Starting filter widget search', filters ); + time = new Date(); + } + // filtered rows count + c.filteredRows = 0; + c.totalRows = 0; + // combindedFilters are undefined on init + combinedFilters = ( storedFilters || [] ).join( '' ); + + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); + // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! + // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); + columnIndex = c.columns; + // convert stored rows into a jQuery object + norm_rows = c.cache[ tbodyIndex ].normalized; + $rows = $( $.map( norm_rows, function( el ) { + return el[ columnIndex ].$row.get(); + }) ); + + if ( combinedFilters === '' || wo.filter_serversideFiltering ) { + $rows + .removeClass( wo.filter_filteredRow ) + .not( '.' + c.cssChildRow ) + .css( 'display', '' ); + } else { + // filter out child rows + $rows = $rows.not( '.' + c.cssChildRow ); + len = $rows.length; + + if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || + typeof filters[c.columns] !== 'undefined' ) { + data.anyMatchFlag = true; + data.anyMatchFilter = '' + ( + filters[ c.columns ] || + wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || + '' + ); + if ( wo.filter_columnAnyMatch ) { + // specific columns search + query = data.anyMatchFilter.split( regex.andSplit ); + injected = false; + for ( indx = 0; indx < query.length; indx++ ) { + res = query[ indx ].split( ':' ); + if ( res.length > 1 ) { + // make the column a one-based index ( non-developers start counting from one :P ) + id = parseInt( res[0], 10 ) - 1; + if ( id >= 0 && id < c.columns ) { // if id is an integer + filters[ id ] = res[1]; + query.splice( indx, 1 ); + indx--; + injected = true; + } + } + } + if ( injected ) { + data.anyMatchFilter = query.join( ' && ' ); + } + } + } + + // optimize searching only through already filtered rows - see #313 + searchFiltered = wo.filter_searchFiltered; + lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; + if ( searchFiltered ) { + // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 + for ( indx = 0; indx < columnIndex + 1; indx++ ) { + val = filters[indx] || ''; + // break out of loop if we've already determined not to search filtered rows + if ( !searchFiltered ) { indx = columnIndex; } + // search already filtered rows if... + searchFiltered = searchFiltered && lastSearch.length && + // there are no changes from beginning of filter + val.indexOf( lastSearch[indx] || '' ) === 0 && + // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string + !regex.alreadyFiltered.test( val ) && + // if we are not doing exact matches, using '|' ( logical or ) or not '!' + !/[=\"\|!]/.test( val ) && + // don't search only filtered if the value is negative + // ( '> -10' => '> -100' will ignore hidden rows ) + !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && + // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 + !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && + !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); + } + } + notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; + // can't search when all rows are hidden - this happens when looking for exact matches + if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } + if ( c.debug ) { + ts.log( 'Filter: Searching through ' + + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); + } + if ( data.anyMatchFlag ) { + if ( c.sortLocaleCompare ) { + // replace accents + data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); + } + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { + data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); + // clear search filtered flag because default filters are not saved to the last search + searchFiltered = false; + } + // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true + // when c.ignoreCase is true, the cache contains all lower case data + data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? + data.anyMatchFilter : + data.anyMatchFilter.toLowerCase(); + } + + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + + txt = $rows[ rowIndex ].className; + // the first row can never be a child row + isChild = rowIndex && regex.child.test( txt ); + // skip child rows & already filtered rows + if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { + continue; + } + + data.$row = $rows.eq( rowIndex ); + data.cacheArray = norm_rows[ rowIndex ]; + rowData = data.cacheArray[ c.columns ]; + data.rawArray = rowData.raw; + data.childRowText = ''; + + if ( !wo.filter_childByColumn ) { + txt = ''; + // child row cached text + childRow = rowData.child; + // so, if 'table.config.widgetOptions.filter_childRows' is true and there is + // a match anywhere in the child row, then it will make the row visible + // checked here so the option can be changed dynamically + for ( indx = 0; indx < childRow.length; indx++ ) { + txt += ' ' + childRow[indx].join( '' ) || ''; + } + data.childRowText = wo.filter_childRows ? + ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : + ''; + } + + showRow = ts.filter.processRow( c, data, vars ); + childRow = rowData.$row.filter( ':gt( 0 )' ); + + if ( wo.filter_childRows && childRow.length ) { + if ( wo.filter_childByColumn ) { + // cycle through each child row + for ( indx = 0; indx < childRow.length; indx++ ) { + data.$row = childRow.eq( indx ); + data.cacheArray = rowData.child[ indx ]; + data.rawArray = data.cacheArray; + // use OR comparison on child rows + showRow = showRow || ts.filter.processRow( c, data, vars ); + } + } + childRow.toggleClass( wo.filter_filteredRow, !showRow ); + } + + rowData.$row + .toggleClass( wo.filter_filteredRow, !showRow )[0] + .display = showRow ? '' : 'none'; + } + } + c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; + c.totalRows += $rows.length; + ts.processTbody( table, $tbody, false ); + } + c.lastCombinedFilter = combinedFilters; // save last search + // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) + c.lastSearch = storedFilters; + c.$table.data( 'lastSearch', storedFilters ); + if ( wo.filter_saveFilters && ts.storage ) { + ts.storage( table, 'tablesorter-filters', storedFilters ); + } + if ( c.debug ) { + ts.benchmark( 'Completed filter widget search', time ); + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterEnd', c ); + } + setTimeout( function() { + c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied + }, 0 ); + }, + getOptionSource: function( table, column, onlyAvail ) { + table = $( table )[0]; + var cts, txt, indx, len, + c = table.config, + wo = c.widgetOptions, + parsed = [], + arry = false, + source = wo.filter_selectSource, + last = c.$table.data( 'lastSearch' ) || [], + fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); + + if ( onlyAvail && last[column] !== '' ) { + onlyAvail = false; + } + + // filter select source option + if ( fxn === true ) { + // OVERALL source + arry = source( table, column, onlyAvail ); + } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { + // selectSource is a jQuery object or string of options + return fxn; + } else if ( $.isArray( fxn ) ) { + arry = fxn; + } else if ( $.type( source ) === 'object' && fxn ) { + // custom select source function for a SPECIFIC COLUMN + arry = fxn( table, column, onlyAvail ); + } + if ( arry === false ) { + // fall back to original method + arry = ts.filter.getOptions( table, column, onlyAvail ); + } + + // get unique elements and sort the list + // if $.tablesorter.sortText exists ( not in the original tablesorter ), + // then natural sort the list otherwise use a basic sort + arry = $.grep( arry, function( value, indx ) { + return $.inArray( value, arry ) === indx; + }); + + if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { + // unsorted select options + return arry; + } else { + len = arry.length; + // parse select option values + for ( indx = 0; indx < len; indx++ ) { + txt = arry[ indx ]; + // parse array data using set column parser; this DOES NOT pass the original + // table cell to the parser format function + parsed.push({ + t : txt, + // check parser length - fixes #934 + p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + }); + } + + // sort parsed select options + cts = c.textSorter || ''; + parsed.sort( function( a, b ) { + // sortNatural breaks if you don't pass it strings + var x = a.p.toString(), + y = b.p.toString(); + if ( $.isFunction( cts ) ) { + // custom OVERALL text sorter + return cts( x, y, true, column, table ); + } else if ( typeof cts === 'object' && cts.hasOwnProperty( column ) ) { + // custom text sorter for a SPECIFIC COLUMN + return cts[column]( x, y, true, column, table ); + } else if ( ts.sortNatural ) { + // fall back to natural sort + return ts.sortNatural( x, y ); + } + // using an older version! do a basic sort + return true; + }); + // rebuild arry from sorted parsed data + arry = []; + len = parsed.length; + for ( indx = 0; indx < len; indx++ ) { + arry.push( parsed[indx].t ); + } + return arry; + } + }, + getOptions: function( table, column, onlyAvail ) { + table = $( table )[0]; + var rowIndex, tbodyIndex, len, row, cache, + c = table.config, + wo = c.widgetOptions, + arry = []; + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + cache = c.cache[tbodyIndex]; + len = c.cache[tbodyIndex].normalized.length; + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + // get cached row from cache.row ( old ) or row data object + // ( new; last item in normalized array ) + row = cache.row ? + cache.row[ rowIndex ] : + cache.normalized[ rowIndex ][ c.columns ].$row[0]; + // check if has class filtered + if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { + continue; + } + // get non-normalized cell content + if ( wo.filter_useParsedData || + c.parsers[column].parsed || + c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { + arry.push( '' + cache.normalized[ rowIndex ][ column ] ); + } else { + // get raw cached data instead of content directly from the cells + arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + } + } } return arry; - } - }, - getOptions: function( table, column, onlyAvail ) { - table = $( table )[0]; - var rowIndex, tbodyIndex, len, row, cache, - c = table.config, - wo = c.widgetOptions, - arry = []; - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - cache = c.cache[tbodyIndex]; - len = c.cache[tbodyIndex].normalized.length; - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - // get cached row from cache.row ( old ) or row data object - // ( new; last item in normalized array ) - row = cache.row ? - cache.row[ rowIndex ] : - cache.normalized[ rowIndex ][ c.columns ].$row[0]; - // check if has class filtered - if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { - continue; + }, + buildSelect: function( table, column, arry, updating, onlyAvail ) { + table = $( table )[0]; + column = parseInt( column, 10 ); + if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { + return; + } + var indx, val, txt, t, $filters, $filter, + c = table.config, + wo = c.widgetOptions, + node = c.$headerIndexed[ column ], + // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 + options = '', + // Get curent filter value + currentValue = c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .val(); + // nothing included in arry ( external source ), so get the options from + // filter_selectSource or column data + if ( typeof arry === 'undefined' || arry === '' ) { + arry = ts.filter.getOptionSource( table, column, onlyAvail ); + } + + if ( $.isArray( arry ) ) { + // build option list + for ( indx = 0; indx < arry.length; indx++ ) { + txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); + val = txt; + // allow including a symbol in the selectSource array + // 'a-z|A through Z' so that 'a-z' becomes the option value + // and 'A through Z' becomes the option text + if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + t = txt.split( wo.filter_selectSourceSeparator ); + val = t[0]; + txt = t[1]; + } + // replace quotes - fixes #242 & ignore empty strings + // see http://stackoverflow.com/q/14990971/145346 + options += arry[indx] !== '' ? + '' : ''; } - // get non-normalized cell content - if ( wo.filter_useParsedData || - c.parsers[column].parsed || - c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { - arry.push( '' + cache.normalized[ rowIndex ][ column ] ); - } else { - // get raw cached data instead of content directly from the cells - arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + // clear arry so it doesn't get appended twice + arry = []; + } + + // update all selects in the same column ( clone thead in sticky headers & + // any external selects ) - fixes 473 + $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) + .find( '.' + tscss.filter ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; + } + $filter = $filters.filter( 'select[data-column="' + column + '"]' ); + + // make sure there is a select there! + if ( $filter.length ) { + $filter[ updating ? 'html' : 'append' ]( options ); + if ( !$.isArray( arry ) ) { + // append options if arry is provided externally as a string or jQuery object + // options ( default value ) was already added + $filter.append( arry ).val( currentValue ); + } + $filter.val( currentValue ); + } + }, + buildDefault: function( table, updating ) { + var columnIndex, $header, noSelect, + c = table.config, + wo = c.widgetOptions, + columns = c.columns; + // build default select dropdown + for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { + $header = c.$headerIndexed[columnIndex]; + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + // look for the filter-select class; build/update it if found + if ( ( $header.hasClass( 'filter-select' ) || + ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { + ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); } } } - return arry; - }, - buildSelect: function( table, column, arry, updating, onlyAvail ) { - table = $( table )[0]; - column = parseInt( column, 10 ); - if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { - return; - } - var indx, val, txt, t, $filters, $filter, - c = table.config, - wo = c.widgetOptions, - node = c.$headerIndexed[ column ], - // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 - options = '', - // Get curent filter value - currentValue = c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .val(); - // nothing included in arry ( external source ), so get the options from - // filter_selectSource or column data - if ( typeof arry === 'undefined' || arry === '' ) { - arry = ts.filter.getOptionSource( table, column, onlyAvail ); - } + }; - if ( $.isArray( arry ) ) { - // build option list - for ( indx = 0; indx < arry.length; indx++ ) { - txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); - val = txt; - // allow including a symbol in the selectSource array - // 'a-z|A through Z' so that 'a-z' becomes the option value - // and 'A through Z' becomes the option text - if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - t = txt.split( wo.filter_selectSourceSeparator ); - val = t[0]; - txt = t[1]; - } - // replace quotes - fixes #242 & ignore empty strings - // see http://stackoverflow.com/q/14990971/145346 - options += arry[indx] !== '' ? - '' : ''; + ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { + var i, $filters, $column, cols, + filters = false, + c = table ? $( table )[0].config : '', + wo = c ? c.widgetOptions : ''; + if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || + // setFilters called, but last search is exactly the same as the current + // fixes issue #733 & #903 where calling update causes the input values to reset + ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { + return $( table ).data( 'lastSearch' ); + } + if ( c ) { + if ( c.$filters ) { + $filters = c.$filters.find( '.' + tscss.filter ); } - // clear arry so it doesn't get appended twice - arry = []; - } - - // update all selects in the same column ( clone thead in sticky headers & - // any external selects ) - fixes 473 - $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) - .find( '.' + tscss.filter ); - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - $filter = $filters.filter( 'select[data-column="' + column + '"]' ); - - // make sure there is a select there! - if ( $filter.length ) { - $filter[ updating ? 'html' : 'append' ]( options ); - if ( !$.isArray( arry ) ) { - // append options if arry is provided externally as a string or jQuery object - // options ( default value ) was already added - $filter.append( arry ).val( currentValue ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; } - $filter.val( currentValue ); - } - }, - buildDefault: function( table, updating ) { - var columnIndex, $header, noSelect, - c = table.config, - wo = c.widgetOptions, - columns = c.columns; - // build default select dropdown - for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { - $header = c.$headerIndexed[columnIndex]; - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - // look for the filter-select class; build/update it if found - if ( ( $header.hasClass( 'filter-select' ) || - ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { - ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } -}; - -ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { - var i, $filters, $column, cols, - filters = false, - c = table ? $( table )[0].config : '', - wo = c ? c.widgetOptions : ''; - if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || - // setFilters called, but last search is exactly the same as the current - // fixes issue #733 & #903 where calling update causes the input values to reset - ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { - return $( table ).data( 'lastSearch' ); - } - if ( c ) { - if ( c.$filters ) { - $filters = c.$filters.find( '.' + tscss.filter ); - } - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - if ( $filters && $filters.length ) { - filters = setFilters || []; - for ( i = 0; i < c.columns + 1; i++ ) { - cols = ( i === c.columns ? - // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) - wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : - '[data-column="' + i + '"]' ); - $column = $filters.filter( cols ); - if ( $column.length ) { - // move the latest search to the first slot in the array - $column = ts.filter.getLatestSearch( $column ); - if ( $.isArray( setFilters ) ) { - // skip first ( latest input ) to maintain cursor position while typing - if ( skipFirst && $column.length > 1 ) { - $column = $column.slice( 1 ); - } - if ( i === c.columns ) { - // prevent data-column='all' from filling data-column='0,1' ( etc ) - cols = $column.filter( wo.filter_anyColumnSelector ); - $column = cols.length ? cols : $column; - } - $column - .val( setFilters[ i ] ) - .trigger( 'change.tsfilter' ); - } else { - filters[i] = $column.val() || ''; - // don't change the first... it will move the cursor - if ( i === c.columns ) { - // don't update range columns from 'all' setting + if ( $filters && $filters.length ) { + filters = setFilters || []; + for ( i = 0; i < c.columns + 1; i++ ) { + cols = ( i === c.columns ? + // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) + wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : + '[data-column="' + i + '"]' ); + $column = $filters.filter( cols ); + if ( $column.length ) { + // move the latest search to the first slot in the array + $column = ts.filter.getLatestSearch( $column ); + if ( $.isArray( setFilters ) ) { + // skip first ( latest input ) to maintain cursor position while typing + if ( skipFirst && $column.length > 1 ) { + $column = $column.slice( 1 ); + } + if ( i === c.columns ) { + // prevent data-column='all' from filling data-column='0,1' ( etc ) + cols = $column.filter( wo.filter_anyColumnSelector ); + $column = cols.length ? cols : $column; + } $column - .slice( 1 ) - .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) - .val( filters[ i ] ); + .val( setFilters[ i ] ) + .trigger( 'change.tsfilter' ); } else { - $column - .slice( 1 ) - .val( filters[ i ] ); + filters[i] = $column.val() || ''; + // don't change the first... it will move the cursor + if ( i === c.columns ) { + // don't update range columns from 'all' setting + $column + .slice( 1 ) + .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) + .val( filters[ i ] ); + } else { + $column + .slice( 1 ) + .val( filters[ i ] ); + } + } + // save any match input dynamically + if ( i === c.columns && $column.length ) { + wo.filter_$anyMatch = $column; } - } - // save any match input dynamically - if ( i === c.columns && $column.length ) { - wo.filter_$anyMatch = $column; } } } } - } - if ( filters.length === 0 ) { - filters = false; - } - return filters; -}; + if ( filters.length === 0 ) { + filters = false; + } + return filters; + }; -ts.setFilters = function( table, filter, apply, skipFirst ) { - var c = table ? $( table )[0].config : '', - valid = ts.getFilters( table, true, filter, skipFirst ); - if ( c && apply ) { - // ensure new set filters are applied, even if the search is the same - c.lastCombinedFilter = null; - c.lastSearch = []; - ts.filter.searching( c.table, filter, skipFirst ); - c.$table.trigger( 'filterFomatterUpdate' ); - } - return !!valid; -}; + ts.setFilters = function( table, filter, apply, skipFirst ) { + var c = table ? $( table )[0].config : '', + valid = ts.getFilters( table, true, filter, skipFirst ); + if ( c && apply ) { + // ensure new set filters are applied, even if the search is the same + c.lastCombinedFilter = null; + c.lastSearch = []; + ts.filter.searching( c.table, filter, skipFirst ); + c.$table.trigger( 'filterFomatterUpdate' ); + } + return !!valid; + }; })( jQuery ); @@ -1979,750 +1979,751 @@ ts.setFilters = function( table, filter, apply, skipFirst ) { * by Rob Garrison */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - sticky : 'tablesorter-stickyHeader', // stickyHeader - stickyVis : 'tablesorter-sticky-visible', - stickyHide: 'tablesorter-sticky-hidden', - stickyWrap: 'tablesorter-sticky-wrapper' -}); + $.extend(ts.css, { + sticky : 'tablesorter-stickyHeader', // stickyHeader + stickyVis : 'tablesorter-sticky-visible', + stickyHide: 'tablesorter-sticky-hidden', + stickyWrap: 'tablesorter-sticky-wrapper' + }); -// Add a resize event to table headers -ts.addHeaderResizeEvent = function(table, disable, settings) { - table = $(table)[0]; // make sure we're using a dom element - if ( !table.config ) { return; } - var defaults = { - timer : 250 + // Add a resize event to table headers + ts.addHeaderResizeEvent = function(table, disable, settings) { + table = $(table)[0]; // make sure we're using a dom element + if ( !table.config ) { return; } + var defaults = { + timer : 250 + }, + options = $.extend({}, defaults, settings), + c = table.config, + wo = c.widgetOptions, + checkSizes = function( triggerEvent ) { + var index, headers, $header, sizes, width, height, + len = c.$headers.length; + wo.resize_flag = true; + headers = []; + for ( index = 0; index < len; index++ ) { + $header = c.$headers.eq( index ); + sizes = $header.data( 'savedSizes' ) || [ 0, 0 ]; // fixes #394 + width = $header[0].offsetWidth; + height = $header[0].offsetHeight; + if ( width !== sizes[0] || height !== sizes[1] ) { + $header.data( 'savedSizes', [ width, height ] ); + headers.push( $header[0] ); + } + } + if ( headers.length && triggerEvent !== false ) { + c.$table.trigger( 'resize', [ headers ] ); + } + wo.resize_flag = false; + }; + checkSizes( false ); + clearInterval(wo.resize_timer); + if (disable) { + wo.resize_flag = false; + return false; + } + wo.resize_timer = setInterval(function() { + if (wo.resize_flag) { return; } + checkSizes(); + }, options.timer); + }; + + // Sticky headers based on this awesome article: + // http://css-tricks.com/13465-persistent-headers/ + // and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech + // ************************** + ts.addWidget({ + id: 'stickyHeaders', + priority: 60, // sticky widget must be initialized after the filter widget! + options: { + stickyHeaders : '', // extra class name added to the sticky header row + stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to + stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) + stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) + stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element + stickyHeaders_filteredToTop: true, // scroll table top into view after filtering + stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists + stickyHeaders_addResizeEvent : true, // trigger 'resize' event on headers + stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header + stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs }, - options = $.extend({}, defaults, settings), - c = table.config, - wo = c.widgetOptions, - checkSizes = function( triggerEvent ) { - var index, headers, $header, sizes, width, height, - len = c.$headers.length; - wo.resize_flag = true; - headers = []; - for ( index = 0; index < len; index++ ) { - $header = c.$headers.eq( index ); - sizes = $header.data( 'savedSizes' ) || [ 0,0 ]; // fixes #394 - width = $header[0].offsetWidth; - height = $header[0].offsetHeight; - if ( width !== sizes[0] || height !== sizes[1] ) { - $header.data( 'savedSizes', [ width, height ] ); - headers.push( $header[0] ); + format: function(table, c, wo) { + // filter widget doesn't initialize on an empty table. Fixes #449 + if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { + return; + } + var index, len, $t, + $table = c.$table, + // add position: relative to attach element, hopefully it won't cause trouble. + $attach = $(wo.stickyHeaders_attachTo), + namespace = c.namespace + 'stickyheaders ', + // element to watch for the scroll event + $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), + $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), + $thead = $table.children('thead:first'), + $header = $thead.children('tr').not('.sticky-false').children(), + $tfoot = $table.children('tfoot'), + $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, + // is this table nested? If so, find parent sticky header wrapper (div, not table) + $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? + $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], + nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, + // clone table, then wrap to make sticky header + $stickyTable = wo.$sticky = $table.clone() + .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) + .wrap('
'), + $stickyWrap = $stickyTable.parent() + .addClass(ts.css.stickyHide) + .css({ + position : $attach.length ? 'absolute' : 'fixed', + padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), + top : stickyOffset + nestedStickyTop, + left : 0, + visibility : 'hidden', + zIndex : wo.stickyHeaders_zIndex || 2 + }), + $stickyThead = $stickyTable.children('thead:first'), + $stickyCells, + laststate = '', + spacing = 0, + setWidth = function($orig, $clone){ + var index, width, border, $cell, $this, + $cells = $orig.filter(':visible'), + len = $cells.length; + for ( index = 0; index < len; index++ ) { + $cell = $clone.filter(':visible').eq(index); + $this = $cells.eq(index); + // code from https://github.com/jmosbech/StickyTableHeaders + if ($this.css('box-sizing') === 'border-box') { + width = $this.outerWidth(); + } else { + if ($cell.css('border-collapse') === 'collapse') { + if (window.getComputedStyle) { + width = parseFloat( window.getComputedStyle($this[0], null).width ); + } else { + // ie8 only + border = parseFloat( $this.css('border-width') ); + width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; + } + } else { + width = $this.width(); + } + } + $cell.css({ + 'width': width, + 'min-width': width, + 'max-width': width + }); + } + }, + resizeHeader = function() { + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; + spacing = 0; + $stickyWrap.css({ + left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : + $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, + width: $table.outerWidth() + }); + setWidth( $table, $stickyTable ); + setWidth( $header, $stickyCells ); + }, + scrollSticky = function( resizing ) { + if (!$table.is(':visible')) { return; } // fixes #278 + // Detect nested tables - fixes #724 + nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; + var offset = $table.offset(), + yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 + xWindow = $.isWindow( $xScroll[0] ), + // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), + isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', + cssSettings = { visibility : isVisible }; + + if ($attach.length) { + cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + } + if (xWindow) { + // adjust when scrolling horizontally - fixes issue #143 + cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; + } + if ($nestedSticky.length) { + cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; + } + $stickyWrap + .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) + .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) + .css(cssSettings); + if (isVisible !== laststate || resizing) { + // make sure the column widths match + resizeHeader(); + laststate = isVisible; + } + }; + // only add a position relative if a position isn't already defined + if ($attach.length && !$attach.css('position')) { + $attach.css('position', 'relative'); + } + // fix clone ID, if it exists - fixes #271 + if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } + // clear out cloned table, except for sticky header + // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing + $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); + $stickyTable.find('tbody, tfoot').remove(); + $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); + // issue #172 - find td/th in sticky header + $stickyCells = $stickyThead.children().children(); + $stickyTable.css({ height:0, width:0, margin: 0 }); + // remove resizable block + $stickyCells.find('.' + ts.css.resizer).remove(); + // update sticky header class names to match real header after sorting + $table + .addClass('hasStickyHeaders') + .bind('pagerComplete' + namespace, function() { + resizeHeader(); + }); + + ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); + + // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. + $table.after( $stickyWrap ); + + // onRenderHeader is defined, we need to do something about it (fixes #641) + if (c.onRenderHeader) { + $t = $stickyThead.children('tr').children(); + len = $t.length; + for ( index = 0; index < len; index++ ) { + // send second parameter + c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } } - if ( headers.length && triggerEvent !== false ) { - c.$table.trigger( 'resize', [ headers ] ); - } - wo.resize_flag = false; - }; - checkSizes( false ); - clearInterval(wo.resize_timer); - if (disable) { - wo.resize_flag = false; - return false; - } - wo.resize_timer = setInterval(function() { - if (wo.resize_flag) { return; } - checkSizes(); - }, options.timer); -}; -// Sticky headers based on this awesome article: -// http://css-tricks.com/13465-persistent-headers/ -// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech -// ************************** -ts.addWidget({ - id: "stickyHeaders", - priority: 60, // sticky widget must be initialized after the filter widget! - options: { - stickyHeaders : '', // extra class name added to the sticky header row - stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to - stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) - stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) - stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element - stickyHeaders_filteredToTop: true, // scroll table top into view after filtering - stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists - stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers - stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header - stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs - }, - format: function(table, c, wo) { - // filter widget doesn't initialize on an empty table. Fixes #449 - if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { - return; - } - var index, len, $t, - $table = c.$table, - // add position: relative to attach element, hopefully it won't cause trouble. - $attach = $(wo.stickyHeaders_attachTo), - namespace = c.namespace + 'stickyheaders ', - // element to watch for the scroll event - $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), - $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), - $thead = $table.children('thead:first'), - $header = $thead.children('tr').not('.sticky-false').children(), - $tfoot = $table.children('tfoot'), - $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, - // is this table nested? If so, find parent sticky header wrapper (div, not table) - $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? - $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], - nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, - // clone table, then wrap to make sticky header - $stickyTable = wo.$sticky = $table.clone() - .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) - .wrap('
'), - $stickyWrap = $stickyTable.parent() - .addClass(ts.css.stickyHide) - .css({ - position : $attach.length ? 'absolute' : 'fixed', - padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), - top : stickyOffset + nestedStickyTop, - left : 0, - visibility : 'hidden', - zIndex : wo.stickyHeaders_zIndex || 2 - }), - $stickyThead = $stickyTable.children('thead:first'), - $stickyCells, - laststate = '', - spacing = 0, - setWidth = function($orig, $clone){ - var index, width, border, $cell, $this, - $cells = $orig.filter(':visible'), - len = $cells.length; - for ( index = 0; index < len; index++ ) { - $cell = $clone.filter(':visible').eq(index); - $this = $cells.eq(index); - // code from https://github.com/jmosbech/StickyTableHeaders - if ($this.css('box-sizing') === 'border-box') { - width = $this.outerWidth(); - } else { - if ($cell.css('border-collapse') === 'collapse') { - if (window.getComputedStyle) { - width = parseFloat( window.getComputedStyle($this[0], null).width ); - } else { - // ie8 only - border = parseFloat( $this.css('border-width') ); - width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; - } - } else { - width = $this.width(); + // make it sticky! + $xScroll.add($yScroll) + .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) + .bind('scroll resize '.split(' ').join( namespace ), function( event ) { + scrollSticky( event.type === 'resize' ); + }); + c.$table + .unbind('stickyHeadersUpdate' + namespace) + .bind('stickyHeadersUpdate' + namespace, function(){ + scrollSticky( true ); + }); + + if (wo.stickyHeaders_addResizeEvent) { + ts.addHeaderResizeEvent(table); + } + + // look for filter widget + if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { + // scroll table into view after filtering, if sticky header is active - #482 + $table.bind('filterEnd' + namespace, function() { + // $(':focus') needs jQuery 1.6+ + var $td = $(document.activeElement).closest('td'), + column = $td.parent().children().index($td); + // only scroll if sticky header is active + if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { + // scroll to original table (not sticky clone) + window.scrollTo(0, $table.position().top); + // give same input/select focus; check if c.$filters exists; fixes #594 + if (column >= 0 && c.$filters) { + c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); } } - $cell.css({ - 'width': width, - 'min-width': width, - 'max-width': width - }); - } - }, - resizeHeader = function() { - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; - spacing = 0; - $stickyWrap.css({ - left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : - $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, - width: $table.outerWidth() }); - setWidth( $table, $stickyTable ); - setWidth( $header, $stickyCells ); - }, - scrollSticky = function( resizing ) { - if (!$table.is(':visible')) { return; } // fixes #278 - // Detect nested tables - fixes #724 - nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; - var offset = $table.offset(), - yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 - xWindow = $.isWindow( $xScroll[0] ), - // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), - isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', - cssSettings = { visibility : isVisible }; - - if ($attach.length) { - cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); + // support hideFilters + if (wo.filter_hideFilters) { + ts.filter.hideFilters($stickyTable, c); } - if (xWindow) { - // adjust when scrolling horizontally - fixes issue #143 - cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; - } - if ($nestedSticky.length) { - cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; - } - $stickyWrap - .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) - .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) - .css(cssSettings); - if (isVisible !== laststate || resizing) { - // make sure the column widths match - resizeHeader(); - laststate = isVisible; - } - }; - // only add a position relative if a position isn't already defined - if ($attach.length && !$attach.css('position')) { - $attach.css('position', 'relative'); - } - // fix clone ID, if it exists - fixes #271 - if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } - // clear out cloned table, except for sticky header - // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing - $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); - $stickyTable.find('tbody, tfoot').remove(); - $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); - // issue #172 - find td/th in sticky header - $stickyCells = $stickyThead.children().children(); - $stickyTable.css({ height:0, width:0, margin: 0 }); - // remove resizable block - $stickyCells.find('.' + ts.css.resizer).remove(); - // update sticky header class names to match real header after sorting - $table - .addClass('hasStickyHeaders') - .bind('pagerComplete' + namespace, function() { - resizeHeader(); - }); - - ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); - - // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. - $table.after( $stickyWrap ); - - // onRenderHeader is defined, we need to do something about it (fixes #641) - if (c.onRenderHeader) { - $t = $stickyThead.children('tr').children(); - len = $t.length; - for ( index = 0; index < len; index++ ) { - // send second parameter - c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } + + $table.trigger('stickyHeadersInit'); + + }, + remove: function(table, c, wo) { + var namespace = c.namespace + 'stickyheaders '; + c.$table + .removeClass('hasStickyHeaders') + .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) + .next('.' + ts.css.stickyWrap).remove(); + if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table + $(window) + .add(wo.stickyHeaders_xScroll) + .add(wo.stickyHeaders_yScroll) + .add(wo.stickyHeaders_attachTo) + .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); + ts.addHeaderResizeEvent(table, false); } - - // make it sticky! - $xScroll.add($yScroll) - .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) - .bind('scroll resize '.split(' ').join( namespace ), function( event ) { - scrollSticky( event.type === 'resize' ); - }); - c.$table - .unbind('stickyHeadersUpdate' + namespace) - .bind('stickyHeadersUpdate' + namespace, function(){ - scrollSticky( true ); - }); - - if (wo.stickyHeaders_addResizeEvent) { - ts.addHeaderResizeEvent(table); - } - - // look for filter widget - if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { - // scroll table into view after filtering, if sticky header is active - #482 - $table.bind('filterEnd' + namespace, function() { - // $(':focus') needs jQuery 1.6+ - var $td = $(document.activeElement).closest('td'), - column = $td.parent().children().index($td); - // only scroll if sticky header is active - if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { - // scroll to original table (not sticky clone) - window.scrollTo(0, $table.position().top); - // give same input/select focus; check if c.$filters exists; fixes #594 - if (column >= 0 && c.$filters) { - c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); - } - } - }); - ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); - // support hideFilters - if (wo.filter_hideFilters) { - ts.filter.hideFilters($stickyTable, c); - } - } - - $table.trigger('stickyHeadersInit'); - - }, - remove: function(table, c, wo) { - var namespace = c.namespace + 'stickyheaders '; - c.$table - .removeClass('hasStickyHeaders') - .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) - .next('.' + ts.css.stickyWrap).remove(); - if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table - $(window) - .add(wo.stickyHeaders_xScroll) - .add(wo.stickyHeaders_yScroll) - .add(wo.stickyHeaders_attachTo) - .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); - ts.addHeaderResizeEvent(table, false); - } -}); + }); })(jQuery, window); /*! Widget: resizable - updated 6/26/2015 (v2.22.2) */ /*jshint browser:true, jquery:true, unused:false */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - resizableContainer : 'tablesorter-resizable-container', - resizableHandle : 'tablesorter-resizable-handle', - resizableNoSelect : 'tablesorter-disableSelection', - resizableStorage : 'tablesorter-resizable' -}); + $.extend(ts.css, { + resizableContainer : 'tablesorter-resizable-container', + resizableHandle : 'tablesorter-resizable-handle', + resizableNoSelect : 'tablesorter-disableSelection', + resizableStorage : 'tablesorter-resizable' + }); -// Add extra scroller css -$(function(){ - var s = ''; - $(s).appendTo('body'); -}); + // Add extra scroller css + $(function(){ + var s = ''; + $(s).appendTo('body'); + }); -ts.resizable = { - init : function( c, wo ) { - if ( c.$table.hasClass( 'hasResizable' ) ) { return; } - c.$table.addClass( 'hasResizable' ); + ts.resizable = { + init : function( c, wo ) { + if ( c.$table.hasClass( 'hasResizable' ) ) { return; } + c.$table.addClass( 'hasResizable' ); - var noResize, $header, column, storedSizes, tmp, - $table = c.$table, - $parent = $table.parent(), - marginTop = parseInt( $table.css( 'margin-top' ), 10 ), + var noResize, $header, column, storedSizes, tmp, + $table = c.$table, + $parent = $table.parent(), + marginTop = parseInt( $table.css( 'margin-top' ), 10 ), - // internal variables - vars = wo.resizable_ = { - useStorage : ts.storage && wo.resizable !== false, - $wrap : $parent, - mouseXPosition : 0, - $target : null, - $next : null, - overflow : $parent.css('overflow') === 'auto' || - $parent.css('overflow') === 'scroll' || - $parent.css('overflow-x') === 'auto' || - $parent.css('overflow-x') === 'scroll', - storedSizes : [] - }; + // internal variables + vars = wo.resizable_vars = { + useStorage : ts.storage && wo.resizable !== false, + $wrap : $parent, + mouseXPosition : 0, + $target : null, + $next : null, + overflow : $parent.css('overflow') === 'auto' || + $parent.css('overflow') === 'scroll' || + $parent.css('overflow-x') === 'auto' || + $parent.css('overflow-x') === 'scroll', + storedSizes : [] + }; - // set default widths - ts.resizableReset( c.table, true ); + // set default widths + ts.resizableReset( c.table, true ); - // now get measurements! - vars.tableWidth = $table.width(); - // attempt to autodetect - vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; + // now get measurements! + vars.tableWidth = $table.width(); + // attempt to autodetect + vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; - /* - // Hacky method to determine if table width is set to "auto" - // http://stackoverflow.com/a/20892048/145346 - if ( !vars.fullWidth ) { - tmp = $table.width(); - $header = $table.wrap('').parent(); // temp variable - storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; - $table.css( 'margin-left', storedSizes + 50 ); - vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; - $table.css( 'margin-left', storedSizes ? storedSizes : '' ); - $header = null; - $table.unwrap(''); - } - */ - - if ( vars.useStorage && vars.overflow ) { - // save table width - ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); - tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; - ts.resizable.setWidth( $table, tmp, true ); - } - wo.resizable_.storedSizes = storedSizes = ( vars.useStorage ? - ts.storage( c.table, ts.css.resizableStorage ) : - [] ) || []; - ts.resizable.setWidths( c, wo, storedSizes ); - ts.resizable.updateStoredSizes( c, wo ); - - wo.$resizable_container = $( '
' ) - .css({ top : marginTop }) - .insertBefore( $table ); - // add container - for ( column = 0; column < c.columns; column++ ) { - $header = c.$headerIndexed[ column ]; - tmp = ts.getColumnData( c.table, c.headers, column ); - noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; - if ( !noResize ) { - $( '
' ) - .appendTo( wo.$resizable_container ) - .attr({ - 'data-column' : column, - 'unselectable' : 'on' - }) - .data( 'header', $header ) - .bind( 'selectstart', false ); + /* + // Hacky method to determine if table width is set to 'auto' + // http://stackoverflow.com/a/20892048/145346 + if ( !vars.fullWidth ) { + tmp = $table.width(); + $header = $table.wrap('').parent(); // temp variable + storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; + $table.css( 'margin-left', storedSizes + 50 ); + vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; + $table.css( 'margin-left', storedSizes ? storedSizes : '' ); + $header = null; + $table.unwrap(''); } - } - $table.one('tablesorter-initialized', function() { - ts.resizable.setHandlePosition( c, wo ); - ts.resizable.bindings( this.config, this.config.widgetOptions ); - }); - }, + */ - updateStoredSizes : function( c, wo ) { - var column, $header, - len = c.columns, - vars = wo.resizable_; - vars.storedSizes = []; - for ( column = 0; column < len; column++ ) { - $header = c.$headerIndexed[ column ]; - vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; - } - }, + if ( vars.useStorage && vars.overflow ) { + // save table width + ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); + tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; + ts.resizable.setWidth( $table, tmp, true ); + } + wo.resizable_vars.storedSizes = storedSizes = ( vars.useStorage ? + ts.storage( c.table, ts.css.resizableStorage ) : + [] ) || []; + ts.resizable.setWidths( c, wo, storedSizes ); + ts.resizable.updateStoredSizes( c, wo ); - setWidth : function( $el, width, overflow ) { - // overflow tables need min & max width set as well - $el.css({ - 'width' : width, - 'min-width' : overflow ? width : '', - 'max-width' : overflow ? width : '' - }); - }, - - setWidths : function( c, wo, storedSizes ) { - var column, $temp, - vars = wo.resizable_, - $extra = $( c.namespace + '_extra_headers' ), - $col = c.$table.children( 'colgroup' ).children( 'col' ); - storedSizes = storedSizes || vars.storedSizes || []; - // process only if table ID or url match - if ( storedSizes.length ) { + wo.$resizable_container = $( '
' ) + .css({ top : marginTop }) + .insertBefore( $table ); + // add container for ( column = 0; column < c.columns; column++ ) { - // set saved resizable widths - ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); - if ( $extra.length ) { - // stickyHeaders needs to modify min & max width as well - $temp = $extra.eq( column ).add( $col.eq( column ) ); - ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + $header = c.$headerIndexed[ column ]; + tmp = ts.getColumnData( c.table, c.headers, column ); + noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; + if ( !noResize ) { + $( '
' ) + .appendTo( wo.$resizable_container ) + .attr({ + 'data-column' : column, + 'unselectable' : 'on' + }) + .data( 'header', $header ) + .bind( 'selectstart', false ); } } - $temp = $( c.namespace + '_extra_table' ); - if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { - ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); - } - } - }, - - setHandlePosition : function( c, wo ) { - var startPosition, - hasScroller = ts.hasWidget( c.table, 'scroller' ), - tableHeight = c.$table.height(), - $handles = wo.$resizable_container.children(), - handleCenter = Math.floor( $handles.width() / 2 ); - - if ( hasScroller ) { - tableHeight = 0; - c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ - var $this = $(this); - // center table has a max-height set - tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); + $table.one('tablesorter-initialized', function() { + ts.resizable.setHandlePosition( c, wo ); + ts.resizable.bindings( this.config, this.config.widgetOptions ); }); - } - // subtract out table left position from resizable handles. Fixes #864 - startPosition = c.$table.position().left; - $handles.each( function() { - var $this = $(this), - column = parseInt( $this.attr( 'data-column' ), 10 ), - columns = c.columns - 1, - $header = $this.data( 'header' ); - if ( !$header ) { return; } // see #859 - if ( !$header.is(':visible') ) { - $this.hide(); - } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { - $this.css({ - display: 'inline-block', - height : tableHeight, - left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }, + + updateStoredSizes : function( c, wo ) { + var column, $header, + len = c.columns, + vars = wo.resizable_vars; + vars.storedSizes = []; + for ( column = 0; column < len; column++ ) { + $header = c.$headerIndexed[ column ]; + vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; + } + }, + + setWidth : function( $el, width, overflow ) { + // overflow tables need min & max width set as well + $el.css({ + 'width' : width, + 'min-width' : overflow ? width : '', + 'max-width' : overflow ? width : '' + }); + }, + + setWidths : function( c, wo, storedSizes ) { + var column, $temp, + vars = wo.resizable_vars, + $extra = $( c.namespace + '_extra_headers' ), + $col = c.$table.children( 'colgroup' ).children( 'col' ); + storedSizes = storedSizes || vars.storedSizes || []; + // process only if table ID or url match + if ( storedSizes.length ) { + for ( column = 0; column < c.columns; column++ ) { + // set saved resizable widths + ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); + if ( $extra.length ) { + // stickyHeaders needs to modify min & max width as well + $temp = $extra.eq( column ).add( $col.eq( column ) ); + ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + } + } + $temp = $( c.namespace + '_extra_table' ); + if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { + ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); + } + } + }, + + setHandlePosition : function( c, wo ) { + var startPosition, + hasScroller = ts.hasWidget( c.table, 'scroller' ), + tableHeight = c.$table.height(), + $handles = wo.$resizable_container.children(), + handleCenter = Math.floor( $handles.width() / 2 ); + + if ( hasScroller ) { + tableHeight = 0; + c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ + var $this = $(this); + // center table has a max-height set + tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); }); } - }); - }, - - // prevent text selection while dragging resize bar - toggleTextSelection : function( c, toggle ) { - var namespace = c.namespace + 'tsresize'; - c.widgetOptions.resizable_.disabled = toggle; - $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); - if ( toggle ) { - $( 'body' ) - .attr( 'unselectable', 'on' ) - .bind( 'selectstart' + namespace, false ); - } else { - $( 'body' ) - .removeAttr( 'unselectable' ) - .unbind( 'selectstart' + namespace ); - } - }, - - bindings : function( c, wo ) { - var namespace = c.namespace + 'tsresize'; - wo.$resizable_container.children().bind( 'mousedown', function( event ) { - // save header cell and mouse position - var column, - vars = wo.resizable_, - $extras = $( c.namespace + '_extra_headers' ), - $header = $( event.target ).data( 'header' ); - - column = parseInt( $header.attr( 'data-column' ), 10 ); - vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); - vars.target = column; - - // if table is not as wide as it's parent, then resize the table - vars.$next = event.shiftKey || wo.resizable_targetLast ? - $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : - $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); - - column = parseInt( vars.$next.attr( 'data-column' ), 10 ); - vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); - vars.next = column; - - vars.mouseXPosition = event.pageX; - ts.resizable.updateStoredSizes( c, wo ); - ts.resizable.toggleTextSelection( c, true ); - }); - - $( document ) - .bind( 'mousemove' + namespace, function( event ) { - var vars = wo.resizable_; - // ignore mousemove if no mousedown - if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } - if ( wo.resizable_throttle ) { - clearTimeout( vars.timer ); - vars.timer = setTimeout( function() { - ts.resizable.mouseMove( c, wo, event ); - }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); - } else { - ts.resizable.mouseMove( c, wo, event ); + // subtract out table left position from resizable handles. Fixes #864 + startPosition = c.$table.position().left; + $handles.each( function() { + var $this = $(this), + column = parseInt( $this.attr( 'data-column' ), 10 ), + columns = c.columns - 1, + $header = $this.data( 'header' ); + if ( !$header ) { return; } // see #859 + if ( !$header.is(':visible') ) { + $this.hide(); + } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { + $this.css({ + display: 'inline-block', + height : tableHeight, + left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }); } - }) - .bind( 'mouseup' + namespace, function() { - if (!wo.resizable_.disabled) { return; } - ts.resizable.toggleTextSelection( c, false ); - ts.resizable.stopResize( c, wo ); - ts.resizable.setHandlePosition( c, wo ); }); + }, - // resizeEnd event triggered by scroller widget - $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }); - - // right click to reset columns to default widths - c.$table - .bind( 'columnUpdate' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }) - .find( 'thead:first' ) - .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) - .bind( 'contextmenu' + namespace, function() { - // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset - var allowClick = wo.resizable_.storedSizes.length === 0; - ts.resizableReset( c.table ); - ts.resizable.setHandlePosition( c, wo ); - wo.resizable_.storedSizes = []; - return allowClick; - }); - - }, - - mouseMove : function( c, wo, event ) { - if ( wo.resizable_.mouseXPosition === 0 || !wo.resizable_.$target ) { return; } - // resize columns - var column, - total = 0, - vars = wo.resizable_, - $next = vars.$next, - tar = vars.storedSizes[ vars.target ], - leftEdge = event.pageX - vars.mouseXPosition; - if ( vars.overflow ) { - if ( tar + leftEdge > 0 ) { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); - // update the entire table width - for ( column = 0; column < c.columns; column++ ) { - total += vars.storedSizes[ column ]; - } - ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); - } - if ( !$next.length ) { - // if expanding right-most column, scroll the wrapper - vars.$wrap[0].scrollLeft = c.$table.width(); - } - } else if ( vars.fullWidth ) { - vars.storedSizes[ vars.target ] += leftEdge; - vars.storedSizes[ vars.next ] -= leftEdge; - ts.resizable.setWidths( c, wo ); - } else { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidths( c, wo ); - } - vars.mouseXPosition = event.pageX; - // dynamically update sticky header widths - c.$table.trigger('stickyHeadersUpdate'); - }, - - stopResize : function( c, wo ) { - var vars = wo.resizable_; - ts.resizable.updateStoredSizes( c, wo ); - if ( vars.useStorage ) { - // save all column widths - ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); - ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); - } - vars.mouseXPosition = 0; - vars.$target = vars.$next = null; - // will update stickyHeaders, just in case, see #912 - c.$table.trigger('stickyHeadersUpdate'); - } -}; - -// this widget saves the column widths if -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: "resizable", - priority: 40, - options: { - resizable : true, // save column widths to storage - resizable_addLastColumn : false, - resizable_widths : [], - resizable_throttle : false, // set to true (5ms) or any number 0-10 range - resizable_targetLast : false, - resizable_fullWidth : null - }, - init: function(table, thisWidget, c, wo) { - ts.resizable.init( c, wo ); - }, - remove: function( table, c, wo, refreshing ) { - if (wo.$resizable_container) { + // prevent text selection while dragging resize bar + toggleTextSelection : function( c, toggle ) { var namespace = c.namespace + 'tsresize'; - c.$table.add( $( c.namespace + '_extra_table' ) ) - .removeClass('hasResizable') - .children( 'thead' ).unbind( 'contextmenu' + namespace ); + c.widgetOptions.resizable_vars.disabled = toggle; + $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); + if ( toggle ) { + $( 'body' ) + .attr( 'unselectable', 'on' ) + .bind( 'selectstart' + namespace, false ); + } else { + $( 'body' ) + .removeAttr( 'unselectable' ) + .unbind( 'selectstart' + namespace ); + } + }, + + bindings : function( c, wo ) { + var namespace = c.namespace + 'tsresize'; + wo.$resizable_container.children().bind( 'mousedown', function( event ) { + // save header cell and mouse position + var column, + vars = wo.resizable_vars, + $extras = $( c.namespace + '_extra_headers' ), + $header = $( event.target ).data( 'header' ); + + column = parseInt( $header.attr( 'data-column' ), 10 ); + vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); + vars.target = column; + + // if table is not as wide as it's parent, then resize the table + vars.$next = event.shiftKey || wo.resizable_targetLast ? + $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : + $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); + + column = parseInt( vars.$next.attr( 'data-column' ), 10 ); + vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); + vars.next = column; + + vars.mouseXPosition = event.pageX; + ts.resizable.updateStoredSizes( c, wo ); + ts.resizable.toggleTextSelection( c, true ); + }); + + $( document ) + .bind( 'mousemove' + namespace, function( event ) { + var vars = wo.resizable_vars; + // ignore mousemove if no mousedown + if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } + if ( wo.resizable_throttle ) { + clearTimeout( vars.timer ); + vars.timer = setTimeout( function() { + ts.resizable.mouseMove( c, wo, event ); + }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); + } else { + ts.resizable.mouseMove( c, wo, event ); + } + }) + .bind( 'mouseup' + namespace, function() { + if (!wo.resizable_vars.disabled) { return; } + ts.resizable.toggleTextSelection( c, false ); + ts.resizable.stopResize( c, wo ); + ts.resizable.setHandlePosition( c, wo ); + }); + + // resizeEnd event triggered by scroller widget + $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }); + + // right click to reset columns to default widths + c.$table + .bind( 'columnUpdate' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }) + .find( 'thead:first' ) + .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) + .bind( 'contextmenu' + namespace, function() { + // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset + var allowClick = wo.resizable_vars.storedSizes.length === 0; + ts.resizableReset( c.table ); + ts.resizable.setHandlePosition( c, wo ); + wo.resizable_vars.storedSizes = []; + return allowClick; + }); + + }, + + mouseMove : function( c, wo, event ) { + if ( wo.resizable_vars.mouseXPosition === 0 || !wo.resizable_vars.$target ) { return; } + // resize columns + var column, + total = 0, + vars = wo.resizable_vars, + $next = vars.$next, + tar = vars.storedSizes[ vars.target ], + leftEdge = event.pageX - vars.mouseXPosition; + if ( vars.overflow ) { + if ( tar + leftEdge > 0 ) { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); + // update the entire table width + for ( column = 0; column < c.columns; column++ ) { + total += vars.storedSizes[ column ]; + } + ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); + } + if ( !$next.length ) { + // if expanding right-most column, scroll the wrapper + vars.$wrap[0].scrollLeft = c.$table.width(); + } + } else if ( vars.fullWidth ) { + vars.storedSizes[ vars.target ] += leftEdge; + vars.storedSizes[ vars.next ] -= leftEdge; + ts.resizable.setWidths( c, wo ); + } else { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidths( c, wo ); + } + vars.mouseXPosition = event.pageX; + // dynamically update sticky header widths + c.$table.trigger('stickyHeadersUpdate'); + }, + + stopResize : function( c, wo ) { + var vars = wo.resizable_vars; + ts.resizable.updateStoredSizes( c, wo ); + if ( vars.useStorage ) { + // save all column widths + ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); + ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); + } + vars.mouseXPosition = 0; + vars.$target = vars.$next = null; + // will update stickyHeaders, just in case, see #912 + c.$table.trigger('stickyHeadersUpdate'); + } + }; + + // this widget saves the column widths if + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'resizable', + priority: 40, + options: { + resizable : true, // save column widths to storage + resizable_addLastColumn : false, + resizable_widths : [], + resizable_throttle : false, // set to true (5ms) or any number 0-10 range + resizable_targetLast : false, + resizable_fullWidth : null + }, + init: function(table, thisWidget, c, wo) { + ts.resizable.init( c, wo ); + }, + remove: function( table, c, wo, refreshing ) { + if (wo.$resizable_container) { + var namespace = c.namespace + 'tsresize'; + c.$table.add( $( c.namespace + '_extra_table' ) ) + .removeClass('hasResizable') + .children( 'thead' ) + .unbind( 'contextmenu' + namespace ); wo.$resizable_container.remove(); - ts.resizable.toggleTextSelection( c, false ); - ts.resizableReset( table, refreshing ); - $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); - } - } -}); - -ts.resizableReset = function( table, refreshing ) { - $( table ).each(function(){ - var index, $t, - c = this.config, - wo = c && c.widgetOptions, - vars = wo.resizable_; - if ( table && c && c.$headerIndexed.length ) { - // restore the initial table width - if ( vars.overflow && vars.tableWidth ) { - ts.resizable.setWidth( c.$table, vars.tableWidth, true ); - if ( vars.useStorage ) { - ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); - } - } - for ( index = 0; index < c.columns; index++ ) { - $t = c.$headerIndexed[ index ]; - if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { - ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); - } else if ( !$t.hasClass( 'resizable-false' ) ) { - // don't clear the width of any column that is not resizable - ts.resizable.setWidth( $t, '', vars.overflow ); - } - } - - // reset stickyHeader widths - c.$table.trigger( 'stickyHeadersUpdate' ); - if ( ts.storage && !refreshing ) { - ts.storage( this, ts.css.resizableStorage, {} ); + ts.resizable.toggleTextSelection( c, false ); + ts.resizableReset( table, refreshing ); + $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); } } }); -}; + + ts.resizableReset = function( table, refreshing ) { + $( table ).each(function(){ + var index, $t, + c = this.config, + wo = c && c.widgetOptions, + vars = wo.resizable_vars; + if ( table && c && c.$headerIndexed.length ) { + // restore the initial table width + if ( vars.overflow && vars.tableWidth ) { + ts.resizable.setWidth( c.$table, vars.tableWidth, true ); + if ( vars.useStorage ) { + ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); + } + } + for ( index = 0; index < c.columns; index++ ) { + $t = c.$headerIndexed[ index ]; + if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { + ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); + } else if ( !$t.hasClass( 'resizable-false' ) ) { + // don't clear the width of any column that is not resizable + ts.resizable.setWidth( $t, '', vars.overflow ); + } + } + + // reset stickyHeader widths + c.$table.trigger( 'stickyHeadersUpdate' ); + if ( ts.storage && !refreshing ) { + ts.storage( this, ts.css.resizableStorage, {} ); + } + } + }); + }; })( jQuery, window ); /*! Widget: saveSort */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -// this widget saves the last sort only if the -// saveSort widget option is true AND the -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: 'saveSort', - priority: 20, - options: { - saveSort : true - }, - init: function(table, thisWidget, c, wo) { - // run widget format before all other widgets are applied to the table - thisWidget.format(table, c, wo, true); - }, - format: function(table, c, wo, init) { - var stored, time, - $table = c.$table, - saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true - sortList = { "sortList" : c.sortList }; - if (c.debug) { - time = new Date(); - } - if ($table.hasClass('hasSaveSort')) { - if (saveSort && table.hasInitialized && ts.storage) { - ts.storage( table, 'tablesorter-savesort', sortList ); - if (c.debug) { - ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + // this widget saves the last sort only if the + // saveSort widget option is true AND the + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'saveSort', + priority: 20, + options: { + saveSort : true + }, + init: function(table, thisWidget, c, wo) { + // run widget format before all other widgets are applied to the table + thisWidget.format(table, c, wo, true); + }, + format: function(table, c, wo, init) { + var stored, time, + $table = c.$table, + saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true + sortList = { 'sortList' : c.sortList }; + if (c.debug) { + time = new Date(); + } + if ($table.hasClass('hasSaveSort')) { + if (saveSort && table.hasInitialized && ts.storage) { + ts.storage( table, 'tablesorter-savesort', sortList ); + if (c.debug) { + ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + } + } + } else { + // set table sort on initial run of the widget + $table.addClass('hasSaveSort'); + sortList = ''; + // get data + if (ts.storage) { + stored = ts.storage( table, 'tablesorter-savesort' ); + sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; + if (c.debug) { + ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); + } + $table.bind('saveSortReset', function(event) { + event.stopPropagation(); + ts.storage( table, 'tablesorter-savesort', '' ); + }); + } + // init is true when widget init is run, this will run this widget before all other widgets have initialized + // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. + if (init && sortList && sortList.length > 0) { + c.sortList = sortList; + } else if (table.hasInitialized && sortList && sortList.length > 0) { + // update sort change + $table.trigger('sorton', [ sortList ]); } } - } else { - // set table sort on initial run of the widget - $table.addClass('hasSaveSort'); - sortList = ''; - // get data - if (ts.storage) { - stored = ts.storage( table, 'tablesorter-savesort' ); - sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; - if (c.debug) { - ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); - } - $table.bind('saveSortReset', function(event) { - event.stopPropagation(); - ts.storage( table, 'tablesorter-savesort', '' ); - }); - } - // init is true when widget init is run, this will run this widget before all other widgets have initialized - // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. - if (init && sortList && sortList.length > 0) { - c.sortList = sortList; - } else if (table.hasInitialized && sortList && sortList.length > 0) { - // update sort change - $table.trigger('sorton', [sortList]); - } + }, + remove: function(table, c) { + c.$table.removeClass('hasSaveSort'); + // clear storage + if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } } - }, - remove: function(table, c) { - c.$table.removeClass('hasSaveSort'); - // clear storage - if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } - } -}); + }); })(jQuery); diff --git a/dist/js/jquery.tablesorter.widgets.min.js b/dist/js/jquery.tablesorter.widgets.min.js index 6b45d80f..bb104057 100644 --- a/dist/js/jquery.tablesorter.widgets.min.js +++ b/dist/js/jquery.tablesorter.widgets.min.js @@ -1,3 +1,3 @@ /*! tablesorter (FORK) - updated 07-22-2015 (v2.22.2)*/ !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&"object"==typeof module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(a,b,c){"use strict";var d=a.tablesorter||{};d.storage=function(e,f,g,h){e=a(e)[0];var i,j,k,l=!1,m={},n=e.config,o=n&&n.widgetOptions,p=h&&h.useSessionStorage||o&&o.storage_useSessionStorage?"sessionStorage":"localStorage",q=a(e),r=h&&h.id||q.attr(h&&h.group||o&&o.storage_group||"data-table-group")||o&&o.storage_tableId||e.id||a(".tablesorter").index(q),s=h&&h.url||q.attr(h&&h.page||o&&o.storage_page||"data-table-page")||o&&o.storage_fixedUrl||n&&n.fixedUrl||b.location.pathname;if(p in b)try{b[p].setItem("_tmptest","temp"),l=!0,b[p].removeItem("_tmptest")}catch(t){n&&n.debug&&d.log(p+" is not supported in this browser")}return a.parseJSON&&(l?m=a.parseJSON(b[p][f]||"null")||{}:(j=c.cookie.split(/[;\s|=]/),i=a.inArray(f,j)+1,m=0!==i?a.parseJSON(j[i]||"null")||{}:{})),(g||""===g)&&b.JSON&&JSON.hasOwnProperty("stringify")?(m[s]||(m[s]={}),m[s][r]=g,l?b[p][f]=JSON.stringify(m):(k=new Date,k.setTime(k.getTime()+31536e6),c.cookie=f+"="+JSON.stringify(m).replace(/\"/g,'"')+"; expires="+k.toGMTString()+"; path=/"),void 0):m&&m[s]?m[s][r]:""}}(jQuery,window,document),function(a){"use strict";var b=a.tablesorter||{};b.themes={bootstrap:{table:"table table-bordered table-striped",caption:"caption",header:"bootstrap-header",sortNone:"",sortAsc:"",sortDesc:"",active:"",hover:"",icons:"",iconSortNone:"bootstrap-icon-unsorted",iconSortAsc:"icon-chevron-up glyphicon glyphicon-chevron-up",iconSortDesc:"icon-chevron-down glyphicon glyphicon-chevron-down",filterRow:"",footerRow:"",footerCells:"",even:"",odd:""},jui:{table:"ui-widget ui-widget-content ui-corner-all",caption:"ui-widget-content",header:"ui-widget-header ui-corner-all ui-state-default",sortNone:"",sortAsc:"",sortDesc:"",active:"ui-state-active",hover:"ui-state-hover",icons:"ui-icon",iconSortNone:"ui-icon-carat-2-n-s",iconSortAsc:"ui-icon-carat-1-n",iconSortDesc:"ui-icon-carat-1-s",filterRow:"",footerRow:"",footerCells:"",even:"ui-widget-content",odd:"ui-state-default"}},a.extend(b.css,{wrapper:"tablesorter-wrapper"}),b.addWidget({id:"uitheme",priority:10,format:function(c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r=b.themes,s=d.$table.add(a(d.namespace+"_extra_table")),t=d.$headers.add(a(d.namespace+"_extra_headers")),u=d.theme||"jui",v=r[u]||{},w=a.trim([v.sortNone,v.sortDesc,v.sortAsc,v.active].join(" ")),x=a.trim([v.iconSortNone,v.iconSortDesc,v.iconSortAsc].join(" "));for(d.debug&&(i=new Date),s.hasClass("tablesorter-"+u)&&d.theme===d.appliedTheme&&e.uitheme_applied||(e.uitheme_applied=!0,n=r[d.appliedTheme]||{},q=!a.isEmptyObject(n),o=q?[n.sortNone,n.sortDesc,n.sortAsc,n.active].join(" "):"",p=q?[n.iconSortNone,n.iconSortDesc,n.iconSortAsc].join(" "):"",q&&(e.zebra[0]=a.trim(" "+e.zebra[0].replace(" "+n.even,"")),e.zebra[1]=a.trim(" "+e.zebra[1].replace(" "+n.odd,"")),d.$tbodies.children().removeClass([n.even,n.odd].join(" "))),v.even&&(e.zebra[0]+=" "+v.even),v.odd&&(e.zebra[1]+=" "+v.odd),s.children("caption").removeClass(n.caption||"").addClass(v.caption),l=s.removeClass((d.appliedTheme?"tablesorter-"+(d.appliedTheme||""):"")+" "+(n.table||"")).addClass("tablesorter-"+u+" "+(v.table||"")).children("tfoot"),d.appliedTheme=d.theme,l.length&&l.children("tr").removeClass(n.footerRow||"").addClass(v.footerRow).children("th, td").removeClass(n.footerCells||"").addClass(v.footerCells),t.removeClass((q?[n.header,n.hover,o].join(" "):"")||"").addClass(v.header).not(".sorter-false").unbind("mouseenter.tsuitheme mouseleave.tsuitheme").bind("mouseenter.tsuitheme mouseleave.tsuitheme",function(b){a(this)["mouseenter"===b.type?"addClass":"removeClass"](v.hover||"")}),t.each(function(){var c=a(this);c.find("."+b.css.wrapper).length||c.wrapInner('
')}),d.cssIcon&&t.find("."+b.css.icon).removeClass(q?[n.icons,p].join(" "):"").addClass(v.icons||""),s.hasClass("hasFilters")&&s.children("thead").children("."+b.css.filterRow).removeClass(q?n.filterRow||"":"").addClass(v.filterRow||"")),f=0;f1)))for(m=1;q>m;m++)k.eq(p[m][0]).addClass(r[m]||r[s])}),b.processTbody(c,f,!1);if(i=e.columns_thead!==!1?["thead tr"]:[],e.columns_tfoot!==!1&&i.push("tfoot tr"),i.length&&(h=n.find(i.join(",")).children().removeClass(l),q))for(m=0;q>m;m++)h.filter('[data-column="'+p[m][0]+'"]').addClass(r[m]||r[s])},remove:function(c,d,e){var f,g,h=d.$tbodies,i=(e.columns||["primary","secondary","tertiary"]).join(" ");for(d.$headers.removeClass(i),d.$table.children("tfoot").children("tr").children("th, td").removeClass(i),f=0;f=]/g,query:"(q|query)"},types:{or:function(c,d,e){if(/\|/.test(d.iFilter)||b.filter.regex.orSplit.test(d.filter)){var f,g,h,i,j=a.extend({},d),k=d.index,l=d.parsed[k],m=d.filter.split(b.filter.regex.orSplit),n=d.iFilter.split(b.filter.regex.orSplit),o=m.length;for(f=0;o>f;f++)if(j.nestedFilters=!0,j.filter=""+(b.filter.parseFilter(c,m[f],k,l)||""),j.iFilter=""+(b.filter.parseFilter(c,n[f],k,l)||""),h="("+(b.filter.parseFilter(c,j.filter,k,l)||"")+")",i=new RegExp(d.isMatch?h:"^"+h+"$",c.widgetOptions.filter_ignoreCase?"i":""),g=i.test(j.exact)||b.filter.processTypes(c,j,e))return g;return g||!1}return null},and:function(c,d,e){if(b.filter.regex.andTest.test(d.filter)){var f,g,h,i,j,k=a.extend({},d),l=d.index,m=d.parsed[l],n=d.filter.split(b.filter.regex.andSplit),o=d.iFilter.split(b.filter.regex.andSplit),p=n.length;for(f=0;p>f;f++)k.nestedFilters=!0,k.filter=""+(b.filter.parseFilter(c,n[f],l,m)||""),k.iFilter=""+(b.filter.parseFilter(c,o[f],l,m)||""),i=("("+(b.filter.parseFilter(c,k.filter,l,m)||"")+")").replace(/\?/g,"\\S{1}").replace(/\*/g,"\\S*"),j=new RegExp(d.isMatch?i:"^"+i+"$",c.widgetOptions.filter_ignoreCase?"i":""),h=j.test(k.exact)||b.filter.processTypes(c,k,e),g=0===f?h:g&&h;return g||!1}return null},regex:function(a,c){if(b.filter.regex.regex.test(c.filter)){var d,e=c.filter_regexCache[c.index]||b.filter.regex.regex.exec(c.filter),f=e instanceof RegExp;try{f||(c.filter_regexCache[c.index]=e=new RegExp(e[1],e[2])),d=e.test(c.exact)}catch(g){d=!1}return d}return null},operators:function(c,d){if(/^[<>]=?/.test(d.iFilter)&&""!==d.iExact){var e,f,g,h=c.table,i=d.index,j=d.parsed[i],k=b.formatFloat(d.iFilter.replace(b.filter.regex.operators,""),h),l=c.parsers[i],m=k;return(j||"numeric"===l.type)&&(g=a.trim(""+d.iFilter.replace(b.filter.regex.operators,"")),f=b.filter.parseFilter(c,g,i,!0),k="number"!=typeof f||""===f||isNaN(f)?k:f),!j&&"numeric"!==l.type||isNaN(k)||"undefined"==typeof d.cache?(g=isNaN(d.iExact)?d.iExact.replace(b.filter.regex.nondigit,""):d.iExact,e=b.formatFloat(g,h)):e=d.cache,/>/.test(d.iFilter)?f=/>=/.test(d.iFilter)?e>=k:e>k:/=e:k>e),f||""!==m||(f=!0),f}return null},notMatch:function(c,d){if(/^\!/.test(d.iFilter)){var e,f=d.iFilter.replace("!",""),g=b.filter.parseFilter(c,f,d.index,d.parsed[d.index])||"";return b.filter.regex.exact.test(g)?(g=g.replace(b.filter.regex.exact,""),""===g?!0:a.trim(g)!==d.iExact):(e=d.iExact.search(a.trim(g)),""===g?!0:!(c.widgetOptions.filter_startsWith?0===e:e>=0))}return null},exact:function(c,d){if(b.filter.regex.exact.test(d.iFilter)){var e=d.iFilter.replace(b.filter.regex.exact,""),f=b.filter.parseFilter(c,e,d.index,d.parsed[d.index])||"";return d.anyMatch?a.inArray(f,d.rowArray)>=0:f==d.iExact}return null},range:function(a,c){if(b.filter.regex.toTest.test(c.iFilter)){var d,e,f,g,h=a.table,i=c.index,j=c.parsed[i],k=c.iFilter.split(b.filter.regex.toSplit);return e=k[0].replace(b.filter.regex.nondigit,"")||"",f=b.formatFloat(b.filter.parseFilter(a,e,i,j),h),e=k[1].replace(b.filter.regex.nondigit,"")||"",g=b.formatFloat(b.filter.parseFilter(a,e,i,j),h),(j||"numeric"===a.parsers[i].type)&&(d=a.parsers[i].format(""+k[0],h,a.$headers.eq(i),i),f=""===d||isNaN(d)?f:d,d=a.parsers[i].format(""+k[1],h,a.$headers.eq(i),i),g=""===d||isNaN(d)?g:d),!j&&"numeric"!==a.parsers[i].type||isNaN(f)||isNaN(g)?(e=isNaN(c.iExact)?c.iExact.replace(b.filter.regex.nondigit,""):c.iExact,d=b.formatFloat(e,h)):d=c.cache,f>g&&(e=f,f=g,g=e),d>=f&&g>=d||""===f||""===g}return null},wild:function(a,c){if(/[\?\*\|]/.test(c.iFilter)){var d=c.index,e=c.parsed[d],f=""+(b.filter.parseFilter(a,c.iFilter,d,e)||"");return!/\?\*/.test(f)&&c.nestedFilters&&(f=c.isMatch?f:"^("+f+")$"),new RegExp(f.replace(/\?/g,"\\S{1}").replace(/\*/g,"\\S*"),a.widgetOptions.filter_ignoreCase?"i":"").test(c.exact)}return null},fuzzy:function(a,c){if(/^~/.test(c.iFilter)){var d,e=0,f=c.iExact.length,g=c.iFilter.slice(1),h=b.filter.parseFilter(a,g,c.index,c.parsed[c.index])||"";for(d=0;f>d;d++)c.iExact[d]===h[e]&&(e+=1);return e===h.length?!0:!1}return null}},init:function(d,e,f){b.language=a.extend(!0,{},{to:"to",or:"or",and:"and"},b.language);var g,h,i,j,k,l,m,n,o,p=b.filter.regex;if(e.$table.addClass("hasFilters"),f.searchTimer=null,f.filter_initTimer=null,f.filter_formatterCount=0,f.filter_formatterInit=[],f.filter_anyColumnSelector='[data-column="all"],[data-column="any"]',f.filter_multipleColumnSelector='[data-column*="-"],[data-column*=","]',m="\\{"+b.filter.regex.query+"\\}",a.extend(p,{child:new RegExp(e.cssChildRow),filtered:new RegExp(f.filter_filteredRow),alreadyFiltered:new RegExp("(\\s+("+b.language.or+"|-|"+b.language.to+")\\s+)","i"),toTest:new RegExp("\\s+(-|"+b.language.to+")\\s+","i"),toSplit:new RegExp("(?:\\s+(?:-|"+b.language.to+")\\s+)","gi"),andTest:new RegExp("\\s+("+b.language.and+"|&&)\\s+","i"),andSplit:new RegExp("(?:\\s+(?:"+b.language.and+"|&&)\\s+)","gi"),orSplit:new RegExp("(?:\\s+(?:"+b.language.or+")\\s+|\\|)","gi"),iQuery:new RegExp(m,"i"),igQuery:new RegExp(m,"ig")}),m=e.$headers.filter(".filter-false, .parser-false").length,f.filter_columnFilters!==!1&&m!==e.$headers.length&&b.filter.buildRow(d,e,f),i="addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ".split(" ").join(e.namespace+"filter "),e.$table.bind(i,function(g,h){return m=f.filter_hideEmpty&&a.isEmptyObject(e.cache)&&!(e.delayInit&&"appendCache"===g.type),e.$table.find("."+c.filterRow).toggleClass(f.filter_filteredRow,m),/(search|filter)/.test(g.type)||(g.stopPropagation(),b.filter.buildDefault(d,!0)),"filterReset"===g.type?(e.$table.find("."+c.filter).add(f.filter_$externalFilters).val(""),b.filter.searching(d,[])):"filterEnd"===g.type?b.filter.buildDefault(d,!0):(h="search"===g.type?h:"updateComplete"===g.type?e.$table.data("lastSearch"):"",/(update|add)/.test(g.type)&&"updateComplete"!==g.type&&(e.lastCombinedFilter=null,e.lastSearch=[]),b.filter.searching(d,h,!0)),!1}),f.filter_reset&&(f.filter_reset instanceof a?f.filter_reset.click(function(){e.$table.trigger("filterReset")}):a(f.filter_reset).length&&a(document).undelegate(f.filter_reset,"click.tsfilter").delegate(f.filter_reset,"click.tsfilter",function(){e.$table.trigger("filterReset")})),f.filter_functions)for(k=0;k'+(j.data("placeholder")||j.attr("data-placeholder")||f.filter_placeholder.select||"")+"":"",m=h,i=h,h.indexOf(f.filter_selectSourceSeparator)>=0&&(m=h.split(f.filter_selectSourceSeparator),i=m[1],m=m[0]),g+="");e.$table.find("thead").find("select."+c.filter+'[data-column="'+k+'"]').append(g),i=f.filter_selectSource,n=a.isFunction(i)?!0:b.getColumnData(d,i,k),n&&b.filter.buildSelect(e.table,k,"",!0,j.hasClass(f.filter_onlyAvail))}b.filter.buildDefault(d,!0),b.filter.bindSearch(d,e.$table.find("."+c.filter),!0),f.filter_external&&b.filter.bindSearch(d,f.filter_external),f.filter_hideFilters&&b.filter.hideFilters(d,e),e.showProcessing&&(i="filterStart filterEnd ".split(" ").join(e.namespace+"filter "),e.$table.unbind(i.replace(/\s+/g," ")).bind(i,function(f,g){j=g?e.$table.find("."+c.header).filter("[data-column]").filter(function(){return""!==g[a(this).data("column")]}):"",b.isProcessing(d,"filterStart"===f.type,g?j:"")})),e.filteredRows=e.totalRows,i="tablesorter-initialized pagerBeforeInitialized ".split(" ").join(e.namespace+"filter "),e.$table.unbind(i.replace(/\s+/g," ")).bind(i,function(){var a=this.config.widgetOptions;l=b.filter.setDefaults(d,e,a)||[],l.length&&(e.delayInit&&""===l.join("")||b.setFilters(d,l,!0)),e.$table.trigger("filterFomatterUpdate"),setTimeout(function(){a.filter_initialized||b.filter.filterInitComplete(e)},100)}),e.pager&&e.pager.initialized&&!f.filter_initialized&&(e.$table.trigger("filterFomatterUpdate"),setTimeout(function(){b.filter.filterInitComplete(e)},100))},formatterUpdated:function(a,b){var c=a.closest("table")[0].config.widgetOptions;c.filter_initialized||(c.filter_formatterInit[b]=1)},filterInitComplete:function(c){var d,e,f=c.widgetOptions,g=0,h=function(){f.filter_initialized=!0,c.$table.trigger("filterInit",c),b.filter.findRows(c.table,c.$table.data("lastSearch")||[])};if(a.isEmptyObject(f.filter_formatter))h();else{for(e=f.filter_formatterInit.length,d=0;e>d;d++)1===f.filter_formatterInit[d]&&g++;clearTimeout(f.filter_initTimer),f.filter_initialized||g!==f.filter_formatterCount?f.filter_initialized||(f.filter_initTimer=setTimeout(function(){h()},500)):h()}},setDefaults:function(c,d,e){var f,g,h,i,j,k=b.getFilters(c)||[];if(e.filter_saveFilters&&b.storage&&(g=b.storage(c,"tablesorter-filters")||[],f=a.isArray(g),f&&""===g.join("")||!f||(k=g)),""===k.join(""))for(j=d.$headers.add(e.filter_$externalFilters).filter("["+e.filter_defaultAttrib+"]"),h=0;h<=d.columns;h++)i=h===d.columns?"all":h,k[h]=j.filter('[data-column="'+i+'"]').attr(e.filter_defaultAttrib)||k[h]||"";return d.$table.data("lastSearch",k),k},parseFilter:function(a,b,c,d){return d?a.parsers[c].format(b,a.table,[],c):b},buildRow:function(d,e,f){var g,h,i,j,k,l,m,n,o=f.filter_cellFilter,p=e.columns,q=a.isArray(o),r='';for(h=0;p>h;h++)r+="").appendTo(e.$table.children("thead").eq(0)).find("td"),h=0;p>h;h++)k=!1,i=e.$headerIndexed[h],m=b.getColumnData(d,f.filter_functions,h),j=f.filter_functions&&m&&"function"!=typeof m||i.hasClass("filter-select"),g=b.getColumnData(d,e.headers,h),k="false"===b.getData(i[0],g,"filter")||"false"===b.getData(i[0],g,"parser"),j?r=a("').appendTo(e.$filters.eq(h)),r&&(n=i.data("placeholder")||i.attr("data-placeholder")||f.filter_placeholder.search||"",r.attr("placeholder",n))),r&&(l=(a.isArray(f.filter_cssFilter)?"undefined"!=typeof f.filter_cssFilter[h]?f.filter_cssFilter[h]||"":"":f.filter_cssFilter)||"",r.addClass(c.filter+" "+l).attr("data-column",h),k&&(r.attr("placeholder","").addClass(c.filterDisabled)[0].disabled=!0))},bindSearch:function(c,d,e){if(c=a(c)[0],d=a(d),d.length){var f,g=c.config,h=g.widgetOptions,i=g.namespace+"filter",j=h.filter_$externalFilters;e!==!0&&(f=h.filter_anyColumnSelector+","+h.filter_multipleColumnSelector,h.filter_$anyMatch=d.filter(f),j&&j.length?h.filter_$externalFilters=h.filter_$externalFilters.add(d):h.filter_$externalFilters=d,b.setFilters(c,g.$table.data("lastSearch")||[],e===!1)),f="keypress keyup search change ".split(" ").join(i+" "),d.attr("data-lastSearchTime",(new Date).getTime()).unbind(f.replace(/\s+/g," ")).bind("keyup"+i,function(d){if(a(this).attr("data-lastSearchTime",(new Date).getTime()),27===d.which)this.value="";else{if(h.filter_liveSearch===!1)return;if(""!==this.value&&("number"==typeof h.filter_liveSearch&&this.value.length=37&&d.which<=40)))return}b.filter.searching(c,!0,!0)}).bind("search change keypress ".split(" ").join(i+" "),function(d){var e=a(this).data("column");(13===d.which||"search"===d.type||"change"===d.type&&this.value!==g.lastSearch[e])&&(d.preventDefault(),a(this).attr("data-lastSearchTime",(new Date).getTime()),b.filter.searching(c,!1,!0))})}},searching:function(a,c,d){var e=a.config.widgetOptions;clearTimeout(e.searchTimer),"undefined"==typeof c||c===!0?e.searchTimer=setTimeout(function(){b.filter.checkFilters(a,c,d)},e.filter_liveSearch?e.filter_searchDelay:10):b.filter.checkFilters(a,c,d)},checkFilters:function(d,e,f){var g=d.config,h=g.widgetOptions,i=a.isArray(e),j=i?e:b.getFilters(d,!0),k=(j||[]).join("");return a.isEmptyObject(g.cache)?void(g.delayInit&&g.pager&&g.pager.initialized&&g.$table.trigger("updateCache",[function(){b.filter.checkFilters(d,!1,f)}])):(i&&(b.setFilters(d,j,!1,f!==!0),h.filter_initialized||(g.lastCombinedFilter="")),h.filter_hideFilters&&g.$table.find("."+c.filterRow).trigger(""===k?"mouseleave":"mouseenter"),g.lastCombinedFilter!==k||e===!1?(e===!1&&(g.lastCombinedFilter=null,g.lastSearch=[]),h.filter_initialized&&g.$table.trigger("filterStart",[j]),g.showProcessing?void setTimeout(function(){return b.filter.findRows(d,j,k),!1},30):(b.filter.findRows(d,j,k),!1)):void 0)},hideFilters:function(d,e){var f;e.$table.find("."+c.filterRow).bind("mouseenter mouseleave",function(b){var d=b,g=a(this);clearTimeout(f),f=setTimeout(function(){/enter|over/.test(d.type)?g.removeClass(c.filterRowHide):a(document.activeElement).closest("tr")[0]!==g[0]&&""===e.lastCombinedFilter&&g.addClass(c.filterRowHide)},200)}).find("input, select").bind("focus blur",function(d){var g=d,h=a(this).closest("tr");clearTimeout(f),f=setTimeout(function(){clearTimeout(f),""===b.getFilters(e.$table).join("")&&h.toggleClass(c.filterRowHide,"focus"!==g.type)},200)})},defaultFilter:function(c,d){if(""===c)return c;var e=b.filter.regex.iQuery,f=d.match(b.filter.regex.igQuery).length,g=f>1?a.trim(c).split(/\s/):[a.trim(c)],h=g.length-1,i=0,j=d;for(1>h&&f>1&&(g[1]=g[0]);e.test(j);)j=j.replace(e,g[i++]||""),e.test(j)&&h>i&&""!==(g[i]||"")&&(j=d.replace(e,j));return j},getLatestSearch:function(b){return b?b.sort(function(b,c){return a(c).attr("data-lastSearchTime")-a(b).attr("data-lastSearchTime")}):b||a()},multipleColumns:function(c,d){var e,f,g,h,i,j,k,l,m,n=c.widgetOptions,o=n.filter_initialized||!d.filter(n.filter_anyColumnSelector).length,p=[],q=a.trim(b.filter.getLatestSearch(d).attr("data-column")||"");if(o&&/-/.test(q))for(f=q.match(/(\d+)\s*-\s*(\d+)/g),m=f.length,l=0;m>l;l++){for(g=f[l].split(/\s*-\s*/),h=parseInt(g[0],10)||0,i=parseInt(g[1],10)||c.columns-1,h>i&&(e=h,h=i,i=e),i>=c.columns&&(i=c.columns-1);i>=h;h++)p.push(h);q=q.replace(f[l],"")}if(o&&/,/.test(q))for(j=q.split(/\s*,\s*/),m=j.length,k=0;m>k;k++)""!==j[k]&&(l=parseInt(j[k],10),l-1?(d.parsed[e]?m=d.cacheArray[e]:(m=d.rawArray[e],m=a.trim(o.filter_ignoreCase?m.toLowerCase():m),c.sortLocaleCompare&&(m=b.replaceAccents(m))),m):void 0}).get(),d.filter=d.anyMatchFilter,d.iFilter=d.iAnyMatchFilter,d.exact=d.rowArray.join(" "),d.iExact=o.filter_ignoreCase?d.exact.toLowerCase():d.exact,d.cache=d.cacheArray.slice(0,-1).join(" "),e.excludeMatch=e.noAnyMatch,j=b.filter.processTypes(c,d,e),null!==j)p=j;else if(o.filter_startsWith)for(p=!1,f=c.columns;!p&&f>0;)f--,p=p||0===d.rowArray[f].indexOf(d.iFilter);else p=(d.iExact+d.childRowText).indexOf(d.iFilter)>=0;if(d.anyMatch=!1,d.filters.join("")===d.filter)return p}for(f=0;f=0:d.filter===d.exact:"function"==typeof k?j=k(d.exact,d.cache,d.filter,f,d.$row,c,d):"function"==typeof k[l||d.filter]&&(m=l||d.filter,j=k[m](d.exact,d.cache,d.filter,f,d.$row,c,d))),null===j?(j=b.filter.processTypes(c,d,e),null!==j?h=j:(m=(d.iExact+d.childRowText).indexOf(b.filter.parseFilter(c,d.iFilter,f,d.parsed[f])),h=!o.filter_startsWith&&m>=0||o.filter_startsWith&&0===m)):h=j,p=h?p:!1);return p},findRows:function(c,d,e){if(c.config.lastCombinedFilter!==e&&c.config.widgetOptions.filter_initialized){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B=a.extend([],d),C=b.filter.regex,D=c.config,E=D.widgetOptions,F={anyMatch:!1,filters:d,filter_regexCache:[]},G={noAnyMatch:["range","notMatch","operators"],functions:[],excludeFilter:[],defaultColFilter:[],defaultAnyFilter:b.getColumnData(c,E.filter_defaultFilter,D.columns,!0)||""};for(F.parsed=D.$headers.map(function(d){return D.parsers&&D.parsers[d]&&D.parsers[d].parsed||b.getData&&"parsed"===b.getData(D.$headerIndexed[d],b.getColumnData(c,D.headers,d),"filter")||a(this).hasClass("filter-parsed")}).get(),m=0;m1&&(z=parseInt(y[0],10)-1,z>=0&&zt;t++)s=d[t]||"",v||(t=m),v=!(!v||!p.length||0!==s.indexOf(p[t]||"")||C.alreadyFiltered.test(s)||/[=\"\|!]/.test(s)||/(>=?\s*-\d)/.test(s)||/(<=?\s*\d)/.test(s)||""!==s&&D.$filters&&D.$filters.eq(t).find("select").length&&!D.$headerIndexed[t].hasClass("filter-match"));for(u=i.not("."+E.filter_filteredRow).length,v&&0===u&&(v=!1),D.debug&&b.log("Filter: Searching through "+(v&&f>u?u:"all")+" rows"),F.anyMatchFlag&&(D.sortLocaleCompare&&(F.anyMatchFilter=b.replaceAccents(F.anyMatchFilter)),E.filter_defaultFilter&&C.iQuery.test(G.defaultAnyFilter)&&(F.anyMatchFilter=b.filter.defaultFilter(F.anyMatchFilter,G.defaultAnyFilter),v=!1),F.iAnyMatchFilter=E.filter_ignoreCase&&D.ignoreCase?F.anyMatchFilter.toLowerCase():F.anyMatchFilter),j=0;f>j;j++)if(A=i[j].className,n=j&&C.child.test(A),!(n||v&&C.filtered.test(A))){if(F.$row=i.eq(j),F.cacheArray=g[j],h=F.cacheArray[D.columns],F.rawArray=h.raw,F.childRowText="",!E.filter_childByColumn){for(A="",o=h.child,t=0;t")>=0)return p;a.isArray(p)?m=p:"object"===a.type(n)&&p&&(m=p(c,d,e))}if(m===!1&&(m=b.filter.getOptions(c,d,e)),m=a.grep(m,function(b,c){return a.inArray(b,m)===c}),j.$headerIndexed[d].hasClass("filter-select-nosort"))return m;for(i=m.length,h=0;i>h;h++)g=m[h],l.push({t:g,p:j.parsers&&j.parsers.length&&j.parsers[d].format(g,c,[],d)||g});for(f=j.textSorter||"",l.sort(function(e,g){var h=e.p.toString(),i=g.p.toString();return a.isFunction(f)?f(h,i,!0,d,c):"object"==typeof f&&f.hasOwnProperty(d)?f[d](h,i,!0,d,c):b.sortNatural?b.sortNatural(h,i):!0}),m=[],i=l.length,h=0;i>h;h++)m.push(l[h].t);return m},getOptions:function(b,c,d){b=a(b)[0];var e,f,g,h,i,j=b.config,k=j.widgetOptions,l=[];for(f=0;fe;e++)h=i.row?i.row[e]:i.normalized[e][j.columns].$row[0],d&&h.className.match(k.filter_filteredRow)||(k.filter_useParsedData||j.parsers[c].parsed||j.$headerIndexed[c].hasClass("filter-parsed")?l.push(""+i.normalized[e][c]):l.push(i.normalized[e][j.columns].raw[c]));return l},buildSelect:function(d,e,f,g,h){if(d=a(d)[0],e=parseInt(e,10),d.config.cache&&!a.isEmptyObject(d.config.cache)){var i,j,k,l,m,n,o=d.config,p=o.widgetOptions,q=o.$headerIndexed[e],r='",s=o.$table.find("thead").find("select."+c.filter+'[data-column="'+e+'"]').val();if(("undefined"==typeof f||""===f)&&(f=b.filter.getOptionSource(d,e,h)),a.isArray(f)){for(i=0;i=0&&(l=k.split(p.filter_selectSourceSeparator),j=l[0],k=l[1]),r+=""!==f[i]?"":"";f=[]}m=(o.$filters?o.$filters:o.$table.children("thead")).find("."+c.filter),p.filter_$externalFilters&&(m=m&&m.length?m.add(p.filter_$externalFilters):p.filter_$externalFilters),n=m.filter('select[data-column="'+e+'"]'),n.length&&(n[g?"html":"append"](r),a.isArray(f)||n.append(f).val(s),n.val(s))}},buildDefault:function(a,c){var d,e,f,g=a.config,h=g.widgetOptions,i=g.columns;for(d=0;i>d;d++)e=g.$headerIndexed[d],f=!(e.hasClass("filter-false")||e.hasClass("parser-false")),(e.hasClass("filter-select")||b.getColumnData(a,h.filter_functions,d)===!0)&&f&&b.filter.buildSelect(a,d,"",c,e.hasClass(h.filter_onlyAvail))}},b.getFilters=function(d,e,f,g){var h,i,j,k,l=!1,m=d?a(d)[0].config:"",n=m?m.widgetOptions:"";if(e!==!0&&n&&!n.filter_columnFilters||a.isArray(f)&&f.join("")===m.lastCombinedFilter)return a(d).data("lastSearch");if(m&&(m.$filters&&(i=m.$filters.find("."+c.filter)),n.filter_$externalFilters&&(i=i&&i.length?i.add(n.filter_$externalFilters):n.filter_$externalFilters),i&&i.length))for(l=f||[],h=0;h1&&(j=j.slice(1)),h===m.columns&&(k=j.filter(n.filter_anyColumnSelector),j=k.length?k:j),j.val(f[h]).trigger("change.tsfilter")):(l[h]=j.val()||"",h===m.columns?j.slice(1).filter('[data-column*="'+j.attr("data-column")+'"]').val(l[h]):j.slice(1).val(l[h])),h===m.columns&&j.length&&(n.filter_$anyMatch=j));return 0===l.length&&(l=!1),l},b.setFilters=function(c,d,e,f){var g=c?a(c)[0].config:"",h=b.getFilters(c,!0,d,f);return g&&e&&(g.lastCombinedFilter=null,g.lastSearch=[],b.filter.searching(g.table,d,f),g.$table.trigger("filterFomatterUpdate")),!!h}}(jQuery),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{sticky:"tablesorter-stickyHeader",stickyVis:"tablesorter-sticky-visible",stickyHide:"tablesorter-sticky-hidden",stickyWrap:"tablesorter-sticky-wrapper"}),c.addHeaderResizeEvent=function(b,c,d){if(b=a(b)[0],b.config){var e={timer:250},f=a.extend({},e,d),g=b.config,h=g.widgetOptions,i=function(a){var b,c,d,e,f,i,j=g.$headers.length;for(h.resize_flag=!0,c=[],b=0;j>b;b++)d=g.$headers.eq(b),e=d.data("savedSizes")||[0,0],f=d[0].offsetWidth,i=d[0].offsetHeight,(f!==e[0]||i!==e[1])&&(d.data("savedSizes",[f,i]),c.push(d[0]));c.length&&a!==!1&&g.$table.trigger("resize",[c]),h.resize_flag=!1};return i(!1),clearInterval(h.resize_timer),c?(h.resize_flag=!1,!1):void(h.resize_timer=setInterval(function(){h.resize_flag||i()},f.timer))}},c.addWidget({id:"stickyHeaders",priority:60,options:{stickyHeaders:"",stickyHeaders_attachTo:null,stickyHeaders_xScroll:null,stickyHeaders_yScroll:null,stickyHeaders_offset:0,stickyHeaders_filteredToTop:!0,stickyHeaders_cloneId:"-sticky",stickyHeaders_addResizeEvent:!0,stickyHeaders_includeCaption:!0,stickyHeaders_zIndex:2},format:function(d,e,f){if(!(e.$table.hasClass("hasStickyHeaders")||a.inArray("filter",e.widgets)>=0&&!e.$table.hasClass("hasFilters"))){var g,h,i,j,k=e.$table,l=a(f.stickyHeaders_attachTo),m=e.namespace+"stickyheaders ",n=a(f.stickyHeaders_yScroll||f.stickyHeaders_attachTo||b),o=a(f.stickyHeaders_xScroll||f.stickyHeaders_attachTo||b),p=k.children("thead:first"),q=p.children("tr").not(".sticky-false").children(),r=k.children("tfoot"),s=isNaN(f.stickyHeaders_offset)?a(f.stickyHeaders_offset):"",t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,u=k.parent().closest("."+c.css.table).hasClass("hasStickyHeaders")?k.parent().closest("table.tablesorter")[0].config.widgetOptions.$sticky.parent():[],v=u.length?u.height():0,w=f.$sticky=k.clone().addClass("containsStickyHeaders "+c.css.sticky+" "+f.stickyHeaders+" "+e.namespace.slice(1)+"_extra_table").wrap('
'),x=w.parent().addClass(c.css.stickyHide).css({position:l.length?"absolute":"fixed",padding:parseInt(w.parent().parent().css("padding-left"),10),top:t+v,left:0,visibility:"hidden",zIndex:f.stickyHeaders_zIndex||2}),y=w.children("thead:first"),z="",A=0,B=function(a,c){var d,e,f,g,h,i=a.filter(":visible"),j=i.length;for(d=0;j>d;d++)g=c.filter(":visible").eq(d),h=i.eq(d),"border-box"===h.css("box-sizing")?e=h.outerWidth():"collapse"===g.css("border-collapse")?b.getComputedStyle?e=parseFloat(b.getComputedStyle(h[0],null).width):(f=parseFloat(h.css("border-width")),e=h.outerWidth()-parseFloat(h.css("padding-left"))-parseFloat(h.css("padding-right"))-f):e=h.width(),g.css({width:e,"min-width":e,"max-width":e})},C=function(){t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,A=0,x.css({left:l.length?parseInt(l.css("padding-left"),10)||0:k.offset().left-parseInt(k.css("margin-left"),10)-o.scrollLeft()-A,width:k.outerWidth()}),B(k,w),B(q,j)},D=function(b){if(k.is(":visible")){v=u.length?u.offset().top-n.scrollTop()+u.height():0;var d=k.offset(),e=a.isWindow(n[0]),f=a.isWindow(o[0]),g=(l.length?e?n.scrollTop():n.offset().top:n.scrollTop())+t+v,h=k.height()-(x.height()+(r.height()||0)),i=g>d.top&&gg;g++)e.onRenderHeader.apply(i.eq(g),[g,e,w]);o.add(n).unbind("scroll resize ".split(" ").join(m).replace(/\s+/g," ")).bind("scroll resize ".split(" ").join(m),function(a){D("resize"===a.type)}),e.$table.unbind("stickyHeadersUpdate"+m).bind("stickyHeadersUpdate"+m,function(){D(!0)}),f.stickyHeaders_addResizeEvent&&c.addHeaderResizeEvent(d),k.hasClass("hasFilters")&&f.filter_columnFilters&&(k.bind("filterEnd"+m,function(){var d=a(document.activeElement).closest("td"),g=d.parent().children().index(d);x.hasClass(c.css.stickyVis)&&f.stickyHeaders_filteredToTop&&(b.scrollTo(0,k.position().top),g>=0&&e.$filters&&e.$filters.eq(g).find("a, select, input").filter(":visible").focus())}),c.filter.bindSearch(k,j.find("."+c.css.filter)),f.filter_hideFilters&&c.filter.hideFilters(w,e)),k.trigger("stickyHeadersInit")}},remove:function(d,e,f){var g=e.namespace+"stickyheaders ";e.$table.removeClass("hasStickyHeaders").unbind("pagerComplete filterEnd stickyHeadersUpdate ".split(" ").join(g).replace(/\s+/g," ")).next("."+c.css.stickyWrap).remove(),f.$sticky&&f.$sticky.length&&f.$sticky.remove(),a(b).add(f.stickyHeaders_xScroll).add(f.stickyHeaders_yScroll).add(f.stickyHeaders_attachTo).unbind("scroll resize ".split(" ").join(g).replace(/\s+/g," ")),c.addHeaderResizeEvent(d,!1)}})}(jQuery,window),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{resizableContainer:"tablesorter-resizable-container",resizableHandle:"tablesorter-resizable-handle",resizableNoSelect:"tablesorter-disableSelection",resizableStorage:"tablesorter-resizable"}),a(function(){var b="";a(b).appendTo("body")}),c.resizable={init:function(b,d){if(!b.$table.hasClass("hasResizable")){b.$table.addClass("hasResizable");var e,f,g,h,i,j=b.$table,k=j.parent(),l=parseInt(j.css("margin-top"),10),m=d.resizable_={useStorage:c.storage&&d.resizable!==!1,$wrap:k,mouseXPosition:0,$target:null,$next:null,overflow:"auto"===k.css("overflow")||"scroll"===k.css("overflow")||"auto"===k.css("overflow-x")||"scroll"===k.css("overflow-x"),storedSizes:[]};for(c.resizableReset(b.table,!0),m.tableWidth=j.width(),m.fullWidth=Math.abs(k.width()-m.tableWidth)<20,m.useStorage&&m.overflow&&(c.storage(b.table,"tablesorter-table-original-css-width",m.tableWidth),i=c.storage(b.table,"tablesorter-table-resized-width")||"auto",c.resizable.setWidth(j,i,!0)),d.resizable_.storedSizes=h=(m.useStorage?c.storage(b.table,c.css.resizableStorage):[])||[],c.resizable.setWidths(b,d,h),c.resizable.updateStoredSizes(b,d),d.$resizable_container=a('
').css({top:l}).insertBefore(j),g=0;g').appendTo(d.$resizable_container).attr({"data-column":g,unselectable:"on"}).data("header",f).bind("selectstart",!1);j.one("tablesorter-initialized",function(){c.resizable.setHandlePosition(b,d),c.resizable.bindings(this.config,this.config.widgetOptions)})}},updateStoredSizes:function(a,b){var c,d,e=a.columns,f=b.resizable_;for(f.storedSizes=[],c=0;e>c;c++)d=a.$headerIndexed[c],f.storedSizes[c]=d.is(":visible")?d.width():0},setWidth:function(a,b,c){a.css({width:b,"min-width":c?b:"","max-width":c?b:""})},setWidths:function(b,d,e){var f,g,h=d.resizable_,i=a(b.namespace+"_extra_headers"),j=b.$table.children("colgroup").children("col");if(e=e||h.storedSizes||[],e.length){for(f=0;ff||f===h&&d.resizable_addLastColumn)&&c.css({display:"inline-block",height:g,left:j.position().left-e+j.outerWidth()-i}):c.hide())})},toggleTextSelection:function(b,d){var e=b.namespace+"tsresize";b.widgetOptions.resizable_.disabled=d,a("body").toggleClass(c.css.resizableNoSelect,d),d?a("body").attr("unselectable","on").bind("selectstart"+e,!1):a("body").removeAttr("unselectable").unbind("selectstart"+e)},bindings:function(d,e){var f=d.namespace+"tsresize";e.$resizable_container.children().bind("mousedown",function(b){var f,g=e.resizable_,h=a(d.namespace+"_extra_headers"),i=a(b.target).data("header");f=parseInt(i.attr("data-column"),10),g.$target=i=i.add(h.filter('[data-column="'+f+'"]')),g.target=f,g.$next=b.shiftKey||e.resizable_targetLast?i.parent().children().not(".resizable-false").filter(":last"):i.nextAll(":not(.resizable-false)").eq(0),f=parseInt(g.$next.attr("data-column"),10),g.$next=g.$next.add(h.filter('[data-column="'+f+'"]')),g.next=f,g.mouseXPosition=b.pageX,c.resizable.updateStoredSizes(d,e),c.resizable.toggleTextSelection(d,!0)}),a(document).bind("mousemove"+f,function(a){var b=e.resizable_;b.disabled&&0!==b.mouseXPosition&&b.$target&&(e.resizable_throttle?(clearTimeout(b.timer),b.timer=setTimeout(function(){c.resizable.mouseMove(d,e,a)},isNaN(e.resizable_throttle)?5:e.resizable_throttle)):c.resizable.mouseMove(d,e,a))}).bind("mouseup"+f,function(){e.resizable_.disabled&&(c.resizable.toggleTextSelection(d,!1),c.resizable.stopResize(d,e),c.resizable.setHandlePosition(d,e))}),a(b).bind("resize"+f+" resizeEnd"+f,function(){c.resizable.setHandlePosition(d,e)}),d.$table.bind("columnUpdate"+f,function(){c.resizable.setHandlePosition(d,e)}).find("thead:first").add(a(d.namespace+"_extra_table").find("thead:first")).bind("contextmenu"+f,function(){var a=0===e.resizable_.storedSizes.length;return c.resizableReset(d.table),c.resizable.setHandlePosition(d,e),e.resizable_.storedSizes=[],a})},mouseMove:function(b,d,e){if(0!==d.resizable_.mouseXPosition&&d.resizable_.$target){var f,g=0,h=d.resizable_,i=h.$next,j=h.storedSizes[h.target],k=e.pageX-h.mouseXPosition;if(h.overflow){if(j+k>0){for(h.storedSizes[h.target]+=k,c.resizable.setWidth(h.$target,h.storedSizes[h.target],!0),f=0;f0?d.sortList=k:c.hasInitialized&&k&&k.length>0&&i.trigger("sorton",[k]))},remove:function(a,c){c.$table.removeClass("hasSaveSort"),b.storage&&b.storage(a,"tablesorter-savesort","")}})}(jQuery),a.tablesorter}); \ No newline at end of file +j=i.filter(k),j.length&&(j=b.filter.getLatestSearch(j),a.isArray(f)?(g&&j.length>1&&(j=j.slice(1)),h===m.columns&&(k=j.filter(n.filter_anyColumnSelector),j=k.length?k:j),j.val(f[h]).trigger("change.tsfilter")):(l[h]=j.val()||"",h===m.columns?j.slice(1).filter('[data-column*="'+j.attr("data-column")+'"]').val(l[h]):j.slice(1).val(l[h])),h===m.columns&&j.length&&(n.filter_$anyMatch=j));return 0===l.length&&(l=!1),l},b.setFilters=function(c,d,e,f){var g=c?a(c)[0].config:"",h=b.getFilters(c,!0,d,f);return g&&e&&(g.lastCombinedFilter=null,g.lastSearch=[],b.filter.searching(g.table,d,f),g.$table.trigger("filterFomatterUpdate")),!!h}}(jQuery),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{sticky:"tablesorter-stickyHeader",stickyVis:"tablesorter-sticky-visible",stickyHide:"tablesorter-sticky-hidden",stickyWrap:"tablesorter-sticky-wrapper"}),c.addHeaderResizeEvent=function(b,c,d){if(b=a(b)[0],b.config){var e={timer:250},f=a.extend({},e,d),g=b.config,h=g.widgetOptions,i=function(a){var b,c,d,e,f,i,j=g.$headers.length;for(h.resize_flag=!0,c=[],b=0;j>b;b++)d=g.$headers.eq(b),e=d.data("savedSizes")||[0,0],f=d[0].offsetWidth,i=d[0].offsetHeight,(f!==e[0]||i!==e[1])&&(d.data("savedSizes",[f,i]),c.push(d[0]));c.length&&a!==!1&&g.$table.trigger("resize",[c]),h.resize_flag=!1};return i(!1),clearInterval(h.resize_timer),c?(h.resize_flag=!1,!1):void(h.resize_timer=setInterval(function(){h.resize_flag||i()},f.timer))}},c.addWidget({id:"stickyHeaders",priority:60,options:{stickyHeaders:"",stickyHeaders_attachTo:null,stickyHeaders_xScroll:null,stickyHeaders_yScroll:null,stickyHeaders_offset:0,stickyHeaders_filteredToTop:!0,stickyHeaders_cloneId:"-sticky",stickyHeaders_addResizeEvent:!0,stickyHeaders_includeCaption:!0,stickyHeaders_zIndex:2},format:function(d,e,f){if(!(e.$table.hasClass("hasStickyHeaders")||a.inArray("filter",e.widgets)>=0&&!e.$table.hasClass("hasFilters"))){var g,h,i,j,k=e.$table,l=a(f.stickyHeaders_attachTo),m=e.namespace+"stickyheaders ",n=a(f.stickyHeaders_yScroll||f.stickyHeaders_attachTo||b),o=a(f.stickyHeaders_xScroll||f.stickyHeaders_attachTo||b),p=k.children("thead:first"),q=p.children("tr").not(".sticky-false").children(),r=k.children("tfoot"),s=isNaN(f.stickyHeaders_offset)?a(f.stickyHeaders_offset):"",t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,u=k.parent().closest("."+c.css.table).hasClass("hasStickyHeaders")?k.parent().closest("table.tablesorter")[0].config.widgetOptions.$sticky.parent():[],v=u.length?u.height():0,w=f.$sticky=k.clone().addClass("containsStickyHeaders "+c.css.sticky+" "+f.stickyHeaders+" "+e.namespace.slice(1)+"_extra_table").wrap('
'),x=w.parent().addClass(c.css.stickyHide).css({position:l.length?"absolute":"fixed",padding:parseInt(w.parent().parent().css("padding-left"),10),top:t+v,left:0,visibility:"hidden",zIndex:f.stickyHeaders_zIndex||2}),y=w.children("thead:first"),z="",A=0,B=function(a,c){var d,e,f,g,h,i=a.filter(":visible"),j=i.length;for(d=0;j>d;d++)g=c.filter(":visible").eq(d),h=i.eq(d),"border-box"===h.css("box-sizing")?e=h.outerWidth():"collapse"===g.css("border-collapse")?b.getComputedStyle?e=parseFloat(b.getComputedStyle(h[0],null).width):(f=parseFloat(h.css("border-width")),e=h.outerWidth()-parseFloat(h.css("padding-left"))-parseFloat(h.css("padding-right"))-f):e=h.width(),g.css({width:e,"min-width":e,"max-width":e})},C=function(){t=s.length?s.height()||0:parseInt(f.stickyHeaders_offset,10)||0,A=0,x.css({left:l.length?parseInt(l.css("padding-left"),10)||0:k.offset().left-parseInt(k.css("margin-left"),10)-o.scrollLeft()-A,width:k.outerWidth()}),B(k,w),B(q,j)},D=function(b){if(k.is(":visible")){v=u.length?u.offset().top-n.scrollTop()+u.height():0;var d=k.offset(),e=a.isWindow(n[0]),f=a.isWindow(o[0]),g=(l.length?e?n.scrollTop():n.offset().top:n.scrollTop())+t+v,h=k.height()-(x.height()+(r.height()||0)),i=g>d.top&&gg;g++)e.onRenderHeader.apply(i.eq(g),[g,e,w]);o.add(n).unbind("scroll resize ".split(" ").join(m).replace(/\s+/g," ")).bind("scroll resize ".split(" ").join(m),function(a){D("resize"===a.type)}),e.$table.unbind("stickyHeadersUpdate"+m).bind("stickyHeadersUpdate"+m,function(){D(!0)}),f.stickyHeaders_addResizeEvent&&c.addHeaderResizeEvent(d),k.hasClass("hasFilters")&&f.filter_columnFilters&&(k.bind("filterEnd"+m,function(){var d=a(document.activeElement).closest("td"),g=d.parent().children().index(d);x.hasClass(c.css.stickyVis)&&f.stickyHeaders_filteredToTop&&(b.scrollTo(0,k.position().top),g>=0&&e.$filters&&e.$filters.eq(g).find("a, select, input").filter(":visible").focus())}),c.filter.bindSearch(k,j.find("."+c.css.filter)),f.filter_hideFilters&&c.filter.hideFilters(w,e)),k.trigger("stickyHeadersInit")}},remove:function(d,e,f){var g=e.namespace+"stickyheaders ";e.$table.removeClass("hasStickyHeaders").unbind("pagerComplete filterEnd stickyHeadersUpdate ".split(" ").join(g).replace(/\s+/g," ")).next("."+c.css.stickyWrap).remove(),f.$sticky&&f.$sticky.length&&f.$sticky.remove(),a(b).add(f.stickyHeaders_xScroll).add(f.stickyHeaders_yScroll).add(f.stickyHeaders_attachTo).unbind("scroll resize ".split(" ").join(g).replace(/\s+/g," ")),c.addHeaderResizeEvent(d,!1)}})}(jQuery,window),function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{resizableContainer:"tablesorter-resizable-container",resizableHandle:"tablesorter-resizable-handle",resizableNoSelect:"tablesorter-disableSelection",resizableStorage:"tablesorter-resizable"}),a(function(){var b="";a(b).appendTo("body")}),c.resizable={init:function(b,d){if(!b.$table.hasClass("hasResizable")){b.$table.addClass("hasResizable");var e,f,g,h,i,j=b.$table,k=j.parent(),l=parseInt(j.css("margin-top"),10),m=d.resizable_vars={useStorage:c.storage&&d.resizable!==!1,$wrap:k,mouseXPosition:0,$target:null,$next:null,overflow:"auto"===k.css("overflow")||"scroll"===k.css("overflow")||"auto"===k.css("overflow-x")||"scroll"===k.css("overflow-x"),storedSizes:[]};for(c.resizableReset(b.table,!0),m.tableWidth=j.width(),m.fullWidth=Math.abs(k.width()-m.tableWidth)<20,m.useStorage&&m.overflow&&(c.storage(b.table,"tablesorter-table-original-css-width",m.tableWidth),i=c.storage(b.table,"tablesorter-table-resized-width")||"auto",c.resizable.setWidth(j,i,!0)),d.resizable_vars.storedSizes=h=(m.useStorage?c.storage(b.table,c.css.resizableStorage):[])||[],c.resizable.setWidths(b,d,h),c.resizable.updateStoredSizes(b,d),d.$resizable_container=a('
').css({top:l}).insertBefore(j),g=0;g').appendTo(d.$resizable_container).attr({"data-column":g,unselectable:"on"}).data("header",f).bind("selectstart",!1);j.one("tablesorter-initialized",function(){c.resizable.setHandlePosition(b,d),c.resizable.bindings(this.config,this.config.widgetOptions)})}},updateStoredSizes:function(a,b){var c,d,e=a.columns,f=b.resizable_vars;for(f.storedSizes=[],c=0;e>c;c++)d=a.$headerIndexed[c],f.storedSizes[c]=d.is(":visible")?d.width():0},setWidth:function(a,b,c){a.css({width:b,"min-width":c?b:"","max-width":c?b:""})},setWidths:function(b,d,e){var f,g,h=d.resizable_vars,i=a(b.namespace+"_extra_headers"),j=b.$table.children("colgroup").children("col");if(e=e||h.storedSizes||[],e.length){for(f=0;ff||f===h&&d.resizable_addLastColumn)&&c.css({display:"inline-block",height:g,left:j.position().left-e+j.outerWidth()-i}):c.hide())})},toggleTextSelection:function(b,d){var e=b.namespace+"tsresize";b.widgetOptions.resizable_vars.disabled=d,a("body").toggleClass(c.css.resizableNoSelect,d),d?a("body").attr("unselectable","on").bind("selectstart"+e,!1):a("body").removeAttr("unselectable").unbind("selectstart"+e)},bindings:function(d,e){var f=d.namespace+"tsresize";e.$resizable_container.children().bind("mousedown",function(b){var f,g=e.resizable_vars,h=a(d.namespace+"_extra_headers"),i=a(b.target).data("header");f=parseInt(i.attr("data-column"),10),g.$target=i=i.add(h.filter('[data-column="'+f+'"]')),g.target=f,g.$next=b.shiftKey||e.resizable_targetLast?i.parent().children().not(".resizable-false").filter(":last"):i.nextAll(":not(.resizable-false)").eq(0),f=parseInt(g.$next.attr("data-column"),10),g.$next=g.$next.add(h.filter('[data-column="'+f+'"]')),g.next=f,g.mouseXPosition=b.pageX,c.resizable.updateStoredSizes(d,e),c.resizable.toggleTextSelection(d,!0)}),a(document).bind("mousemove"+f,function(a){var b=e.resizable_vars;b.disabled&&0!==b.mouseXPosition&&b.$target&&(e.resizable_throttle?(clearTimeout(b.timer),b.timer=setTimeout(function(){c.resizable.mouseMove(d,e,a)},isNaN(e.resizable_throttle)?5:e.resizable_throttle)):c.resizable.mouseMove(d,e,a))}).bind("mouseup"+f,function(){e.resizable_vars.disabled&&(c.resizable.toggleTextSelection(d,!1),c.resizable.stopResize(d,e),c.resizable.setHandlePosition(d,e))}),a(b).bind("resize"+f+" resizeEnd"+f,function(){c.resizable.setHandlePosition(d,e)}),d.$table.bind("columnUpdate"+f,function(){c.resizable.setHandlePosition(d,e)}).find("thead:first").add(a(d.namespace+"_extra_table").find("thead:first")).bind("contextmenu"+f,function(){var a=0===e.resizable_vars.storedSizes.length;return c.resizableReset(d.table),c.resizable.setHandlePosition(d,e),e.resizable_vars.storedSizes=[],a})},mouseMove:function(b,d,e){if(0!==d.resizable_vars.mouseXPosition&&d.resizable_vars.$target){var f,g=0,h=d.resizable_vars,i=h.$next,j=h.storedSizes[h.target],k=e.pageX-h.mouseXPosition;if(h.overflow){if(j+k>0){for(h.storedSizes[h.target]+=k,c.resizable.setWidth(h.$target,h.storedSizes[h.target],!0),f=0;f0?d.sortList=k:c.hasInitialized&&k&&k.length>0&&i.trigger("sorton",[k]))},remove:function(a,c){c.$table.removeClass("hasSaveSort"),b.storage&&b.storage(a,"tablesorter-savesort","")}})}(jQuery),a.tablesorter}); \ No newline at end of file diff --git a/dist/js/parsers/parser-feet-inch-fraction.min.js b/dist/js/parsers/parser-feet-inch-fraction.min.js index b471e51d..a1f72aba 100644 --- a/dist/js/parsers/parser-feet-inch-fraction.min.js +++ b/dist/js/parsers/parser-feet-inch-fraction.min.js @@ -1,2 +1,2 @@ /*! Parser: distance */ -!function(a){"use strict";var b=a.tablesorter;b.symbolRegex=/[\u215b\u215c\u215d\u215e\u00bc\u00bd\u00be]/g,b.processFractions=function(c,d){if(c){var e,f=0;c=a.trim(c.replace(/\"/,"")),/\s/.test(c)&&(f=b.formatFloat(c.split(" ")[0],d),c=a.trim(c.substring(c.indexOf(" "),c.length))),/\//g.test(c)?(e=c.split("/"),c=f+parseInt(e[0],10)/parseInt(e[1]||1,10)):b.symbolRegex.test(c)&&(c=f+c.replace(b.symbolRegex,function(a){return{"⅛":".125","⅜":".375","⅝":".625","⅞":".875","¼":".25","½":".5","¾":".75"}[a]}))}return c||0},a.tablesorter.addParser({id:"distance",is:function(){return!1},format:function(a,c){if(""===a)return"";var d=/^\s*\S*(\s+\S+)?\s*\'/.test(a)?a.split("'"):[0,a],e=b.processFractions(d[0],c),f=b.processFractions(d[1],c);return/[\'\"]/.test(a)?parseFloat(e)+(parseFloat(f)/12||0):parseFloat(e)+parseFloat(f)},type:"numeric"})}(jQuery); \ No newline at end of file +!function(a){"use strict";var b=a.tablesorter;b.symbolRegex=/[\u215b\u215c\u215d\u215e\u00bc\u00bd\u00be]/g,b.processFractions=function(c,d){if(c){var e,f=0;c=a.trim(c.replace(/\"/,"")),/\s/.test(c)&&(f=b.formatFloat(c.split(" ")[0],d),c=a.trim(c.substring(c.indexOf(" "),c.length))),/\//g.test(c)?(e=c.split("/"),c=f+parseInt(e[0],10)/parseInt(e[1]||1,10)):b.symbolRegex.test(c)&&(c=f+c.replace(b.symbolRegex,function(a){return{"⅛":".125","⅜":".375","⅝":".625","⅞":".875","¼":".25","½":".5","¾":".75"}[a]}))}return c||0},a.tablesorter.addParser({id:"distance",is:function(){return!1},format:function(a,c){if(""===a)return"";var d=/^\s*\S*(\s+\S+)?\s*\'/.test(a)?a.split(/\'/):[0,a],e=b.processFractions(d[0],c),f=b.processFractions(d[1],c);return/[\'\"]/.test(a)?parseFloat(e)+(parseFloat(f)/12||0):parseFloat(e)+parseFloat(f)},type:"numeric"})}(jQuery); \ No newline at end of file diff --git a/dist/js/widgets/widget-resizable.min.js b/dist/js/widgets/widget-resizable.min.js index b5499b6a..75febd40 100644 --- a/dist/js/widgets/widget-resizable.min.js +++ b/dist/js/widgets/widget-resizable.min.js @@ -1,2 +1,2 @@ /*! Widget: resizable - updated 6/26/2015 (v2.22.2) */ -!function(a,b){"use strict";var c=a.tablesorter||{};a.extend(c.css,{resizableContainer:"tablesorter-resizable-container",resizableHandle:"tablesorter-resizable-handle",resizableNoSelect:"tablesorter-disableSelection",resizableStorage:"tablesorter-resizable"}),a(function(){var b="";a(b).appendTo("body")}),c.resizable={init:function(b,d){if(!b.$table.hasClass("hasResizable")){b.$table.addClass("hasResizable");var e,f,g,h,i,j=b.$table,k=j.parent(),l=parseInt(j.css("margin-top"),10),m=d.resizable_={useStorage:c.storage&&d.resizable!==!1,$wrap:k,mouseXPosition:0,$target:null,$next:null,overflow:"auto"===k.css("overflow")||"scroll"===k.css("overflow")||"auto"===k.css("overflow-x")||"scroll"===k.css("overflow-x"),storedSizes:[]};for(c.resizableReset(b.table,!0),m.tableWidth=j.width(),m.fullWidth=Math.abs(k.width()-m.tableWidth)<20,m.useStorage&&m.overflow&&(c.storage(b.table,"tablesorter-table-original-css-width",m.tableWidth),i=c.storage(b.table,"tablesorter-table-resized-width")||"auto",c.resizable.setWidth(j,i,!0)),d.resizable_.storedSizes=h=(m.useStorage?c.storage(b.table,c.css.resizableStorage):[])||[],c.resizable.setWidths(b,d,h),c.resizable.updateStoredSizes(b,d),d.$resizable_container=a('
').css({top:l}).insertBefore(j),g=0;g').appendTo(d.$resizable_container).attr({"data-column":g,unselectable:"on"}).data("header",f).bind("selectstart",!1);j.one("tablesorter-initialized",function(){c.resizable.setHandlePosition(b,d),c.resizable.bindings(this.config,this.config.widgetOptions)})}},updateStoredSizes:function(a,b){var c,d,e=a.columns,f=b.resizable_;for(f.storedSizes=[],c=0;e>c;c++)d=a.$headerIndexed[c],f.storedSizes[c]=d.is(":visible")?d.width():0},setWidth:function(a,b,c){a.css({width:b,"min-width":c?b:"","max-width":c?b:""})},setWidths:function(b,d,e){var f,g,h=d.resizable_,i=a(b.namespace+"_extra_headers"),j=b.$table.children("colgroup").children("col");if(e=e||h.storedSizes||[],e.length){for(f=0;ff||f===h&&d.resizable_addLastColumn)&&c.css({display:"inline-block",height:g,left:j.position().left-e+j.outerWidth()-i}):c.hide())})},toggleTextSelection:function(b,d){var e=b.namespace+"tsresize";b.widgetOptions.resizable_.disabled=d,a("body").toggleClass(c.css.resizableNoSelect,d),d?a("body").attr("unselectable","on").bind("selectstart"+e,!1):a("body").removeAttr("unselectable").unbind("selectstart"+e)},bindings:function(d,e){var f=d.namespace+"tsresize";e.$resizable_container.children().bind("mousedown",function(b){var f,g=e.resizable_,h=a(d.namespace+"_extra_headers"),i=a(b.target).data("header");f=parseInt(i.attr("data-column"),10),g.$target=i=i.add(h.filter('[data-column="'+f+'"]')),g.target=f,g.$next=b.shiftKey||e.resizable_targetLast?i.parent().children().not(".resizable-false").filter(":last"):i.nextAll(":not(.resizable-false)").eq(0),f=parseInt(g.$next.attr("data-column"),10),g.$next=g.$next.add(h.filter('[data-column="'+f+'"]')),g.next=f,g.mouseXPosition=b.pageX,c.resizable.updateStoredSizes(d,e),c.resizable.toggleTextSelection(d,!0)}),a(document).bind("mousemove"+f,function(a){var b=e.resizable_;b.disabled&&0!==b.mouseXPosition&&b.$target&&(e.resizable_throttle?(clearTimeout(b.timer),b.timer=setTimeout(function(){c.resizable.mouseMove(d,e,a)},isNaN(e.resizable_throttle)?5:e.resizable_throttle)):c.resizable.mouseMove(d,e,a))}).bind("mouseup"+f,function(){e.resizable_.disabled&&(c.resizable.toggleTextSelection(d,!1),c.resizable.stopResize(d,e),c.resizable.setHandlePosition(d,e))}),a(b).bind("resize"+f+" resizeEnd"+f,function(){c.resizable.setHandlePosition(d,e)}),d.$table.bind("columnUpdate"+f,function(){c.resizable.setHandlePosition(d,e)}).find("thead:first").add(a(d.namespace+"_extra_table").find("thead:first")).bind("contextmenu"+f,function(){var a=0===e.resizable_.storedSizes.length;return c.resizableReset(d.table),c.resizable.setHandlePosition(d,e),e.resizable_.storedSizes=[],a})},mouseMove:function(b,d,e){if(0!==d.resizable_.mouseXPosition&&d.resizable_.$target){var f,g=0,h=d.resizable_,i=h.$next,j=h.storedSizes[h.target],k=e.pageX-h.mouseXPosition;if(h.overflow){if(j+k>0){for(h.storedSizes[h.target]+=k,c.resizable.setWidth(h.$target,h.storedSizes[h.target],!0),f=0;f";a(b).appendTo("body")}),c.resizable={init:function(b,d){if(!b.$table.hasClass("hasResizable")){b.$table.addClass("hasResizable");var e,f,g,h,i,j=b.$table,k=j.parent(),l=parseInt(j.css("margin-top"),10),m=d.resizable_vars={useStorage:c.storage&&d.resizable!==!1,$wrap:k,mouseXPosition:0,$target:null,$next:null,overflow:"auto"===k.css("overflow")||"scroll"===k.css("overflow")||"auto"===k.css("overflow-x")||"scroll"===k.css("overflow-x"),storedSizes:[]};for(c.resizableReset(b.table,!0),m.tableWidth=j.width(),m.fullWidth=Math.abs(k.width()-m.tableWidth)<20,m.useStorage&&m.overflow&&(c.storage(b.table,"tablesorter-table-original-css-width",m.tableWidth),i=c.storage(b.table,"tablesorter-table-resized-width")||"auto",c.resizable.setWidth(j,i,!0)),d.resizable_vars.storedSizes=h=(m.useStorage?c.storage(b.table,c.css.resizableStorage):[])||[],c.resizable.setWidths(b,d,h),c.resizable.updateStoredSizes(b,d),d.$resizable_container=a('
').css({top:l}).insertBefore(j),g=0;g').appendTo(d.$resizable_container).attr({"data-column":g,unselectable:"on"}).data("header",f).bind("selectstart",!1);j.one("tablesorter-initialized",function(){c.resizable.setHandlePosition(b,d),c.resizable.bindings(this.config,this.config.widgetOptions)})}},updateStoredSizes:function(a,b){var c,d,e=a.columns,f=b.resizable_vars;for(f.storedSizes=[],c=0;e>c;c++)d=a.$headerIndexed[c],f.storedSizes[c]=d.is(":visible")?d.width():0},setWidth:function(a,b,c){a.css({width:b,"min-width":c?b:"","max-width":c?b:""})},setWidths:function(b,d,e){var f,g,h=d.resizable_vars,i=a(b.namespace+"_extra_headers"),j=b.$table.children("colgroup").children("col");if(e=e||h.storedSizes||[],e.length){for(f=0;ff||f===h&&d.resizable_addLastColumn)&&c.css({display:"inline-block",height:g,left:j.position().left-e+j.outerWidth()-i}):c.hide())})},toggleTextSelection:function(b,d){var e=b.namespace+"tsresize";b.widgetOptions.resizable_vars.disabled=d,a("body").toggleClass(c.css.resizableNoSelect,d),d?a("body").attr("unselectable","on").bind("selectstart"+e,!1):a("body").removeAttr("unselectable").unbind("selectstart"+e)},bindings:function(d,e){var f=d.namespace+"tsresize";e.$resizable_container.children().bind("mousedown",function(b){var f,g=e.resizable_vars,h=a(d.namespace+"_extra_headers"),i=a(b.target).data("header");f=parseInt(i.attr("data-column"),10),g.$target=i=i.add(h.filter('[data-column="'+f+'"]')),g.target=f,g.$next=b.shiftKey||e.resizable_targetLast?i.parent().children().not(".resizable-false").filter(":last"):i.nextAll(":not(.resizable-false)").eq(0),f=parseInt(g.$next.attr("data-column"),10),g.$next=g.$next.add(h.filter('[data-column="'+f+'"]')),g.next=f,g.mouseXPosition=b.pageX,c.resizable.updateStoredSizes(d,e),c.resizable.toggleTextSelection(d,!0)}),a(document).bind("mousemove"+f,function(a){var b=e.resizable_vars;b.disabled&&0!==b.mouseXPosition&&b.$target&&(e.resizable_throttle?(clearTimeout(b.timer),b.timer=setTimeout(function(){c.resizable.mouseMove(d,e,a)},isNaN(e.resizable_throttle)?5:e.resizable_throttle)):c.resizable.mouseMove(d,e,a))}).bind("mouseup"+f,function(){e.resizable_vars.disabled&&(c.resizable.toggleTextSelection(d,!1),c.resizable.stopResize(d,e),c.resizable.setHandlePosition(d,e))}),a(b).bind("resize"+f+" resizeEnd"+f,function(){c.resizable.setHandlePosition(d,e)}),d.$table.bind("columnUpdate"+f,function(){c.resizable.setHandlePosition(d,e)}).find("thead:first").add(a(d.namespace+"_extra_table").find("thead:first")).bind("contextmenu"+f,function(){var a=0===e.resizable_vars.storedSizes.length;return c.resizableReset(d.table),c.resizable.setHandlePosition(d,e),e.resizable_vars.storedSizes=[],a})},mouseMove:function(b,d,e){if(0!==d.resizable_vars.mouseXPosition&&d.resizable_vars.$target){var f,g=0,h=d.resizable_vars,i=h.$next,j=h.storedSizes[h.target],k=e.pageX-h.mouseXPosition;if(h.overflow){if(j+k>0){for(h.storedSizes[h.target]+=k,c.resizable.setWidth(h.$target,h.storedSizes[h.target],!0),f=0;f div { pointer-events: all; }."+d.scrollerWrap+" ."+d.scrollerFixed+" { position: absolute; top: 0; z-index: 1; left: 0 } ."+d.scrollerWrap+" ."+d.scrollerFixed+"."+d.scrollerRtl+" { left: auto; right: 0 } ."+d.scrollerWrap+"."+d.scrollerHasFix+" > ."+d.scrollerTable+" { overflow: auto; }."+d.scrollerFixed+" ."+d.scrollerFooter+" { position: absolute; bottom: 0; }."+d.scrollerFixed+" ."+d.scrollerTable+" { position: relative; left: 0; overflow: auto; -ms-overflow-style: none; }."+d.scrollerFixed+" ."+d.scrollerTable+"::-webkit-scrollbar { display: none; }."+d.scrollerWrap+" ."+d.scrollerFixedPanel+" { position: absolute; top: 0; bottom: 0; z-index: 2; left: 0; right: 0; } ";a(b).appendTo("body")}),c.scroller={isFirefox:navigator.userAgent.toLowerCase().indexOf("firefox")>-1,isOldIE:document.all&&!b.atob,isIE:document.all&&!b.atob||navigator.appVersion.indexOf("Trident/")>0,isSafari:navigator.userAgent.toLowerCase().indexOf("safari")>-1&&-1===navigator.userAgent.toLowerCase().indexOf("chrome"),hasScrollBar:function(a,b){return b?a.get(0).scrollWidth>a.width():a.get(0).scrollHeight>a.height()},setWidth:function(a,b){a.css({width:b,"min-width":b,"max-width":b})},getBarWidth:function(){var b=a("
").css({position:"absolute",top:"-9999px",left:0,width:"100px",height:"100px",overflow:"scroll",visibility:"hidden"}).appendTo("body"),c=b[0],d=c.offsetWidth-c.clientWidth;return b.remove(),d},setup:function(e,f){var g,h,i,j,k,l,m,n,o=a(b),p=c.scroller,q=e.namespace+"tsscroller",r=a(),s=e.namespace.slice(1)+"tsscroller",t=e.$table;e.widthFixed=!0,f.scroller_calcWidths=[],f.scroller_saved=[0,0],f.scroller_isBusy=!0,f.scroller_barSetWidth=null!==f.scroller_barWidth?f.scroller_barWidth:p.getBarWidth()||15,g=f.scroller_height||300,i=a(''+t.children("thead")[0].outerHTML+"
"),f.scroller_$header=i.addClass(e.namespace.slice(1)+"_extra_table"),j=t.children("tfoot"),j.length&&(r=a('
').addClass(e.namespace.slice(1)+"_extra_table").append(j.clone(!0)).wrap('
'),l=r.children("tfoot").eq(0).children("tr").children()),f.scroller_$footer=r,t.wrap('
').before(i).find("."+d.filterRow).addClass(d.filterRowHide),f.scroller_$container=t.parent(),r.length&&t.after(r.parent()),k=i.wrap('
').find("."+d.header),t.wrap('
'),m=t.parent(),c.bindEvents(e.table,k),t.hasClass("hasFilters")&&c.filter.bindSearch(t,i.find("."+d.filter)),t.find("thead").addClass(d.scrollerHideElement),h=m.parent().height(),m.off("scroll"+q).on("scroll"+q,function(){if(f.scroller_jumpToHeader){var b=o.scrollTop()-i.offset().top;0!==a(this).scrollTop()&&h>b&&b>0&&o.scrollTop(i.offset().top)}i.parent().add(r.parent()).scrollLeft(a(this).scrollLeft())}),n=((c.hasWidget(e.table,"filter")?"filterEnd":"tablesorter-initialized")+" updateComplete pagerComplete columnUpdate ").split(" ").join(q+" "),t.off(q).on("sortEnd filterEnd".split(" ").join(q+" "),function(a){"sortEnd"===a.type&&f.scroller_upAfterSort?m.animate({scrollTop:0},"fast"):f.scroller_fixedColumns&&setTimeout(function(){m.scrollTop(f.scroller_saved[1]).scrollLeft(f.scroller_saved[0]),p.updateFixed(e,f,!1)},0)}).on("setFixedColumnSize"+q,function(a,b){var c=f.scroller_$container;"undefined"==typeof b||isNaN(b)||(f.scroller_fixedColumns=parseInt(b,10)),p.removeFixed(e,f),b=f.scroller_fixedColumns,b>0&&b0&&p.updateFixed(e,f,!1),p.resize(e,f))}),o.off("resize resizeEnd ".split(" ").join(q+" ")).on("resize"+q,c.window_resize).on("resizeEnd"+q,function(){o.off("resize"+q,c.window_resize),p.resize(e,f),o.on("resize"+q,c.window_resize),m.trigger("scroll"+q)}),e.isScrolling=!0,p.updateFixed(e,f)},resize:function(e,f){if(!f.scroller_isBusy){var g,h,i,j,k,l,m,n=c.scroller,o=f.scroller_$container,p=e.$table,q=p.parent(),r=f.scroller_$header,s=f.scroller_$footer,t=e.namespace.slice(1)+"tsscroller",u=a("div."+d.scrollerWrap+'[id!="'+t+'"]').addClass(d.scrollerHideElement),v='';for(f.scroller_calcWidths=[],n.removeFixed(e,f),o.find("."+d.scrollerSpacerRow).remove(),o.find("."+c.css.colgroup).remove(),p.find("."+d.scrollerHideElement).removeClass(d.scrollerHideElement),h=parseInt(p.css("border-left-width"),10),k=e.$headerIndexed,g=0;g',f.scroller_calcWidths[g]=i;v+="",e.$tbodies.eq(0).prepend(v),r.children("thead").append(v),s.children("tfoot").append(v),c.fixColumnWidth(e.table),v=e.$table.children("colgroup")[0].outerHTML,r.prepend(v),s.prepend(v),m=q.parent().innerWidth()-(n.hasScrollBar(q)?f.scroller_barSetWidth:0),q.width(m),m=(n.hasScrollBar(q)?f.scroller_barSetWidth:0)+h,i=q.innerWidth()-m,r.parent().add(s.parent()).width(i),q.width(i+m),p.children("thead").addClass(d.scrollerHideElement),n.updateFixed(e,f),u.removeClass(d.scrollerHideElement),q.scrollTop(f.scroller_saved[1]),f.scroller_$container.find("."+d.scrollerFixed).find("."+d.scrollerTable).scrollTop(f.scroller_saved[1]),setTimeout(function(){e.$table.trigger("resizableUpdate")},100)}},setupFixed:function(a,b){var e,f,g,h,i,j,k,l=a.$table,m=b.scroller_$container,n=b.scroller_fixedColumns;for(j=m.addClass(d.scrollerHasFix).clone().addClass(d.scrollerFixed).removeClass(d.scrollerWrap).attr("id",""),b.scroller_addFixedOverlay&&j.append('
'),k=j.find("."+d.scrollerTable),k.children("table").addClass(a.namespace.slice(1)+"_extra_table").attr("id","").children("thead, tfoot").remove(),b.scroller_$fixedColumns=j,l.hasClass(d.scrollerRtl)&&j.addClass(d.scrollerRtl),g=j.find("tr"),h=g.length,e=0;h>e;e++)g.eq(e).children(":gt("+(n-1)+")").remove();if(j.addClass(d.scrollerHideElement).prependTo(m),a.$table.hasClass("hasFilters"))for(g=j.find("."+d.filter).not("."+d.filterDisabled).prop("disabled",!1),c.filter.bindSearch(l,j.find("."+d.filter)),g=m.children("."+d.scrollerHeader).find("."+d.filter),h=g.length,e=0;h>e;e++)g.eq(e).hasClass(d.filterDisabled||"disabled")||g.eq(e).prop("disabled",n>e);for(a.$table.add("."+d.scrollerFooter+" table").children("thead").children("tr."+d.headerRow).children().attr("tabindex",-1),g=b.scroller_$header.add(j.find("."+d.scrollerTable+" table")).children("thead").children("tr."+d.headerRow),h=g.length,e=0;h>e;e++)for(i=g.eq(e).children(),f=0;ff?-1:0);c.bindEvents(a.table,j.find("."+d.header)),c.scroller.bindFixedColumnEvents(a,b),(c.scroller.isFirefox||c.scroller.isOldIE)&&k.wrap('
')},bindFixedColumnEvents:function(b,e){var f=c.scroller,g=b.namespace+"tsscrollerFixed",h="scroll"+g,i=e.scroller_$fixedColumns.find("."+d.scrollerTable),j=!0,k=!0;b.$table.parent().off(h).on(h,function(){if(!e.scroller_isBusy&&!e.scroller_isBusy&&(j||!f.isFirefox&&!f.isIE)){k=!1;var b=a(this);i[0].scrollTop=e.scroller_saved[1]=b.scrollTop(),e.scroller_saved[0]=b.scrollLeft(),setTimeout(function(){k=!0},20)}}),i.off(h).on(h,function(){if(!e.scroller_isBusy&&(k||!f.isFirefox&&!f.isIE)){j=!1;var c=a(this);b.$table.parent()[0].scrollTop=e.scroller_saved[1]=c.scrollTop(),setTimeout(function(){j=!0},20)}}).scroll(),""!==e.scroller_rowHighlight&&(h="mouseover mouseleave ".split(" ").join(g+" "),b.$table.off(h,"tbody > tr").on(h,"tbody > tr",function(a){var c=b.$table.children("tbody").children("tr").index(this);i.children("table").children("tbody").children("tr").eq(c).add(this).toggleClass(e.scroller_rowHighlight,"mouseover"===a.type)}),i.find("table").off(h,"tbody > tr").on(h,"tbody > tr",function(a){var c=i.children("table").children("tbody").children("tr"),d=c.index(this);b.$table.children("tbody").children("tr").eq(d).add(this).toggleClass(e.scroller_rowHighlight,"mouseover"===a.type)}))},adjustWidth:function(a,b,e,f,g){var h=b.scroller_$container;h.children("."+d.scrollerTable).css(g?"right":"left",e),h.children("."+d.scrollerHeader+", ."+d.scrollerFooter).css(g?"right":"left",e+(g&&c.scroller.isSafari?f:0))},updateFixed:function(b,e){var f,g,h=e.scroller_$container,i=e.scroller_$header,j=e.scroller_$footer,k=b.$table,l=k.parent(),m=e.scroller_barSetWidth,n=k.hasClass(d.scrollerRtl);if(0===e.scroller_fixedColumns)return e.scroller_isBusy=!1,c.scroller.removeFixed(b,e),f=h.width(),l.width(f),g=c.scroller.hasScrollBar(l)?m:0,void i.parent().add(j.parent()).width(f-g);if(b.isScrolling){e.scroller_isBusy=!0,h.find("."+d.scrollerFixed).length||c.scroller.setupFixed(b,e);var o,p,q,r,s,t,u,v=e.scroller_$container.children("."+d.scrollerTable).children("table").children("tbody"),w=e.scroller_$header.children("thead").children("."+d.headerRow),x=e.scroller_$fixedColumns.addClass(d.scrollerHideElement),y=x.find("."+d.scrollerTable).children("table"),z=y.children("tbody"),A=c.scroller,B=e.scroller_fixedColumns,C=k.find("tbody td"),D=parseInt(C.css("border-right-width"),10)||1,E=parseInt((C.css("border-spacing")||"").split(/\s/)[0],10)/2||0,F=parseInt(k.css("padding-left"),10)+parseInt(k.css("padding-right"),10)-D,G=e.scroller_calcWidths;for(c.scroller.removeFixed(b,e,!1),o=0;B>o;o++)F+=G[o]+E;for(F=F+2*D-E,A.setWidth(x.add(x.children()),F),A.setWidth(x.children().children("table"),F),p=0;pq;q++)s=a(w[q].outerHTML),s.children("td, th").slice(B).remove(),t.append(s);c.processTbody(y,t,!1)}for(g=c.scroller.hasScrollBar(l)?m:0,(A.isFirefox||A.isOldIE)&&y.css("width",F).parent().css("width",F+g),x.removeClass(d.scrollerHideElement),o=0;B>o;o++)h.children("div").children("table").find("th:nth-child("+(o+1)+"), td:nth-child("+(o+1)+")").addClass(d.scrollerHideColumn);F-=D,f=l.parent().innerWidth()-F,l.width(f),h.children("."+d.scrollerTable).css(n?"right":"left",F),h.children("."+d.scrollerHeader+", ."+d.scrollerFooter).css(n?"right":"left",F+(n&&c.scroller.isSafari?g:0)),i.parent().add(j.parent()).width(f-g),f=c.scroller.hasScrollBar(l,!0),g=f?m:0,!x.find("."+d.scrollerBarSpacer).length&&f?(C=a('
').css("height",g+"px"),x.find("."+d.scrollerTable).append(C)):f||x.find("."+d.scrollerBarSpacer).remove(),c.scroller.updateRowHeight(b,e),x.height(h.height()),x.removeClass(d.scrollerHideElement),e.scroller_isBusy=!1}},fixHeight:function(a,b){var c,e,f,g,h,i=a.length;for(c=0;i>c;c++)g=a.eq(c),h=b.eq(c),e=g.height(),f=h.height(),e>f?h.addClass(d.scrollerAddedHeight).height(e):f>e&&g.addClass(d.scrollerAddedHeight).height(f)},updateRowHeight:function(a,b){var e,f,g=b.scroller_$fixedColumns;b.scroller_$container.find("."+d.scrollerAddedHeight).removeClass(d.scrollerAddedHeight).height(""),e=b.scroller_$header.children("thead").children("tr"),f=g.children("."+d.scrollerHeader).children("table").children("thead").children("tr"),c.scroller.fixHeight(e,f),e=b.scroller_$footer.children("tfoot").children("tr"),f=g.children("."+d.scrollerFooter).children("table").children("tfoot").children("tr"),c.scroller.fixHeight(e,f),(c.scroller.isFirefox||c.scroller.isOldIE)&&(g=g.find("."+d.scrollerHack)),e=a.$table.children("tbody").children("tr"),f=g.children("."+d.scrollerTable).children("table").children("tbody").children("tr"),c.scroller.fixHeight(e,f)},removeFixed:function(a,b,c){var e=a.$table,f=b.scroller_$container,g=e.hasClass(d.scrollerRtl);(c||"undefined"==typeof c)&&f.find("."+d.scrollerFixed).remove(),f.find("."+d.scrollerHideColumn).removeClass(d.scrollerHideColumn),f.children(":not(."+d.scrollerFixed+")").css(g?"right":"left",0)},remove:function(c,e){var f=e.scroller_$container,g=c.namespace+"tsscroller";c.$table.off(g).insertBefore(f).find("thead").removeClass(d.scrollerHideElement).children("tr."+d.headerRow).children().attr("tabindex",0).end().find("."+d.filterRow).removeClass(d.scrollerHideElement+" "+d.filterRowHide),c.$table.find("."+d.filter).not("."+d.filterDisabled).prop("disabled",!1),f.remove(),a(b).off(g),c.isScrolling=!1}}}(jQuery,window); \ No newline at end of file +!function(a,b){"use strict";var c=a.tablesorter,d=c.css;a.extend(c.css,{scrollerWrap:"tablesorter-scroller",scrollerHeader:"tablesorter-scroller-header",scrollerTable:"tablesorter-scroller-table",scrollerFooter:"tablesorter-scroller-footer",scrollerFixed:"tablesorter-scroller-fixed",scrollerFixedPanel:"tablesorter-scroller-fixed-panel",scrollerHasFix:"tablesorter-scroller-has-fixed-columns",scrollerHideColumn:"tablesorter-scroller-hidden-column",scrollerHideElement:"tablesorter-scroller-hidden",scrollerSpacerRow:"tablesorter-scroller-spacer",scrollerBarSpacer:"tablesorter-scroller-bar-spacer",scrollerAddedHeight:"tablesorter-scroller-added-height",scrollerHack:"tablesorter-scroller-scrollbar-hack",scrollerRtl:"ts-scroller-rtl"}),c.addWidget({id:"scroller",priority:60,options:{scroller_height:300,scroller_jumpToHeader:!0,scroller_upAfterSort:!0,scroller_fixedColumns:0,scroller_rowHighlight:"hover",scroller_addFixedOverlay:!1,scroller_barWidth:null},format:function(a,b,d){b.isScrolling||c.scroller.setup(b,d)},remove:function(a,b,d){c.scroller.remove(b,d)}}),c.window_resize=function(){c.timer_resize&&clearTimeout(c.timer_resize),c.timer_resize=setTimeout(function(){a(b).trigger("resizeEnd")},250)},a(function(){var b="";a(b).appendTo("body")}),c.scroller={isFirefox:navigator.userAgent.toLowerCase().indexOf("firefox")>-1,isOldIE:document.all&&!b.atob,isIE:document.all&&!b.atob||navigator.appVersion.indexOf("Trident/")>0,isSafari:navigator.userAgent.toLowerCase().indexOf("safari")>-1&&-1===navigator.userAgent.toLowerCase().indexOf("chrome"),hasScrollBar:function(a,b){return b?a.get(0).scrollWidth>a.width():a.get(0).scrollHeight>a.height()},setWidth:function(a,b){a.css({width:b,"min-width":b,"max-width":b})},getBarWidth:function(){var b=a("
").css({position:"absolute",top:"-9999px",left:0,width:"100px",height:"100px",overflow:"scroll",visibility:"hidden"}).appendTo("body"),c=b[0],d=c.offsetWidth-c.clientWidth;return b.remove(),d},setup:function(e,f){var g,h,i,j,k,l,m,n,o=a(b),p=c.scroller,q=e.namespace+"tsscroller",r=a(),s=e.namespace.slice(1)+"tsscroller",t=e.$table;e.widthFixed=!0,f.scroller_calcWidths=[],f.scroller_saved=[0,0],f.scroller_isBusy=!0,f.scroller_barSetWidth=null!==f.scroller_barWidth?f.scroller_barWidth:p.getBarWidth()||15,g=f.scroller_height||300,i=a(''+t.children("thead")[0].outerHTML+"
"),f.scroller_$header=i.addClass(e.namespace.slice(1)+"_extra_table"),j=t.children("tfoot"),j.length&&(r=a('
').addClass(e.namespace.slice(1)+"_extra_table").append(j.clone(!0)).wrap('
'),l=r.children("tfoot").eq(0).children("tr").children()),f.scroller_$footer=r,t.wrap('
').before(i).find("."+d.filterRow).addClass(d.filterRowHide),f.scroller_$container=t.parent(),r.length&&t.after(r.parent()),k=i.wrap('
').find("."+d.header),t.wrap('
'),m=t.parent(),c.bindEvents(e.table,k),t.hasClass("hasFilters")&&c.filter.bindSearch(t,i.find("."+d.filter)),t.find("thead").addClass(d.scrollerHideElement),h=m.parent().height(),m.off("scroll"+q).on("scroll"+q,function(){if(f.scroller_jumpToHeader){var b=o.scrollTop()-i.offset().top;0!==a(this).scrollTop()&&h>b&&b>0&&o.scrollTop(i.offset().top)}i.parent().add(r.parent()).scrollLeft(a(this).scrollLeft())}),n=((c.hasWidget(e.table,"filter")?"filterEnd":"tablesorter-initialized")+" updateComplete pagerComplete columnUpdate ").split(" ").join(q+" "),t.off(q).on("sortEnd filterEnd".split(" ").join(q+" "),function(a){"sortEnd"===a.type&&f.scroller_upAfterSort?m.animate({scrollTop:0},"fast"):f.scroller_fixedColumns&&setTimeout(function(){m.scrollTop(f.scroller_saved[1]).scrollLeft(f.scroller_saved[0]),p.updateFixed(e,f,!1)},0)}).on("setFixedColumnSize"+q,function(a,b){var c=f.scroller_$container;"undefined"==typeof b||isNaN(b)||(f.scroller_fixedColumns=parseInt(b,10)),p.removeFixed(e,f),b=f.scroller_fixedColumns,b>0&&b0&&p.updateFixed(e,f,!1),p.resize(e,f))}),o.off("resize resizeEnd ".split(" ").join(q+" ")).on("resize"+q,c.window_resize).on("resizeEnd"+q,function(){o.off("resize"+q,c.window_resize),p.resize(e,f),o.on("resize"+q,c.window_resize),m.trigger("scroll"+q)}),e.isScrolling=!0,p.updateFixed(e,f),e.table.hasInitialized&&e.isScrolling&&setTimeout(function(){c.scroller.resize(e,f)},50)},resize:function(e,f){if(!f.scroller_isBusy){var g,h,i,j,k,l,m=c.scroller,n=f.scroller_$container,o=e.$table,p=o.parent(),q=f.scroller_$header,r=f.scroller_$footer,s=e.namespace.slice(1)+"tsscroller",t=a("div."+d.scrollerWrap+'[id!="'+s+'"]').addClass(d.scrollerHideElement),u='';for(f.scroller_calcWidths=[],m.removeFixed(e,f),n.find("."+d.scrollerSpacerRow).remove(),n.find("."+c.css.colgroup).remove(),o.find("."+d.scrollerHideElement).removeClass(d.scrollerHideElement),h=parseInt(o.css("border-left-width"),10),j=e.$headerIndexed,g=0;g',f.scroller_calcWidths[g]=i;u+="",e.$tbodies.eq(0).prepend(u),q.children("thead").append(u),r.children("tfoot").append(u),c.fixColumnWidth(e.table),u=e.$table.children("colgroup")[0].outerHTML,q.prepend(u),r.prepend(u),l=p.parent().innerWidth()-(m.hasScrollBar(p)?f.scroller_barSetWidth:0),p.width(l),l=(m.hasScrollBar(p)?f.scroller_barSetWidth:0)+h,i=p.innerWidth()-l,q.parent().add(r.parent()).width(i),p.width(i+l),o.children("thead").addClass(d.scrollerHideElement),m.updateFixed(e,f),t.removeClass(d.scrollerHideElement),p.scrollTop(f.scroller_saved[1]),f.scroller_$container.find("."+d.scrollerFixed).find("."+d.scrollerTable).scrollTop(f.scroller_saved[1]),setTimeout(function(){e.$table.trigger("resizableUpdate")},100)}},setupFixed:function(a,b){var e,f,g,h,i,j,k,l=a.$table,m=b.scroller_$container,n=b.scroller_fixedColumns;for(j=m.addClass(d.scrollerHasFix).clone().addClass(d.scrollerFixed).removeClass(d.scrollerWrap).attr("id",""),b.scroller_addFixedOverlay&&j.append('
'),k=j.find("."+d.scrollerTable),k.children("table").addClass(a.namespace.slice(1)+"_extra_table").attr("id","").children("thead, tfoot").remove(),b.scroller_$fixedColumns=j,l.hasClass(d.scrollerRtl)&&j.addClass(d.scrollerRtl),g=j.find("tr"),h=g.length,e=0;h>e;e++)g.eq(e).children(":gt("+(n-1)+")").remove();if(j.addClass(d.scrollerHideElement).prependTo(m),a.$table.hasClass("hasFilters"))for(g=j.find("."+d.filter).not("."+d.filterDisabled).prop("disabled",!1),c.filter.bindSearch(l,j.find("."+d.filter)),g=m.children("."+d.scrollerHeader).find("."+d.filter),h=g.length,e=0;h>e;e++)g.eq(e).hasClass(d.filterDisabled||"disabled")||g.eq(e).prop("disabled",n>e);for(a.$table.add("."+d.scrollerFooter+" table").children("thead").children("tr."+d.headerRow).children().attr("tabindex",-1),g=b.scroller_$header.add(j.find("."+d.scrollerTable+" table")).children("thead").children("tr."+d.headerRow),h=g.length,e=0;h>e;e++)for(i=g.eq(e).children(),f=0;ff?-1:0);c.bindEvents(a.table,j.find("."+d.header)),c.scroller.bindFixedColumnEvents(a,b),(c.scroller.isFirefox||c.scroller.isOldIE)&&k.wrap('
')},bindFixedColumnEvents:function(b,e){var f=c.scroller,g=b.namespace+"tsscrollerFixed",h="scroll"+g,i=e.scroller_$fixedColumns.find("."+d.scrollerTable),j=!0,k=!0;b.$table.parent().off(h).on(h,function(){if(!e.scroller_isBusy&&!e.scroller_isBusy&&(j||!f.isFirefox&&!f.isIE)){k=!1;var b=a(this);i[0].scrollTop=e.scroller_saved[1]=b.scrollTop(),e.scroller_saved[0]=b.scrollLeft(),setTimeout(function(){k=!0},20)}}),i.off(h).on(h,function(){if(!e.scroller_isBusy&&(k||!f.isFirefox&&!f.isIE)){j=!1;var c=a(this);b.$table.parent()[0].scrollTop=e.scroller_saved[1]=c.scrollTop(),setTimeout(function(){j=!0},20)}}).scroll(),""!==e.scroller_rowHighlight&&(h="mouseover mouseleave ".split(" ").join(g+" "),b.$table.off(h,"tbody > tr").on(h,"tbody > tr",function(a){var c=b.$table.children("tbody").children("tr").index(this);i.children("table").children("tbody").children("tr").eq(c).add(this).toggleClass(e.scroller_rowHighlight,"mouseover"===a.type)}),i.find("table").off(h,"tbody > tr").on(h,"tbody > tr",function(a){var c=i.children("table").children("tbody").children("tr"),d=c.index(this);b.$table.children("tbody").children("tr").eq(d).add(this).toggleClass(e.scroller_rowHighlight,"mouseover"===a.type)}))},adjustWidth:function(a,b,e,f,g){var h=b.scroller_$container;h.children("."+d.scrollerTable).css(g?"right":"left",e),h.children("."+d.scrollerHeader+", ."+d.scrollerFooter).css(g?"right":"left",e+(g&&c.scroller.isSafari?f:0))},updateFixed:function(b,e){var f,g,h=e.scroller_$container,i=e.scroller_$header,j=e.scroller_$footer,k=b.$table,l=k.parent(),m=e.scroller_barSetWidth,n=k.hasClass(d.scrollerRtl);if(0===e.scroller_fixedColumns)return e.scroller_isBusy=!1,c.scroller.removeFixed(b,e),f=h.width(),l.width(f),g=c.scroller.hasScrollBar(l)?m:0,void i.parent().add(j.parent()).width(f-g);if(b.isScrolling){e.scroller_isBusy=!0,h.find("."+d.scrollerFixed).length||c.scroller.setupFixed(b,e);var o,p,q,r,s,t,u,v=e.scroller_$container.children("."+d.scrollerTable).children("table").children("tbody"),w=e.scroller_$header.children("thead").children("."+d.headerRow),x=e.scroller_$fixedColumns.addClass(d.scrollerHideElement),y=x.find("."+d.scrollerTable).children("table"),z=y.children("tbody"),A=c.scroller,B=e.scroller_fixedColumns,C=k.find("tbody td"),D=parseInt(C.css("border-right-width"),10)||1,E=parseInt((C.css("border-spacing")||"").split(/\s/)[0],10)/2||0,F=parseInt(k.css("padding-left"),10)+parseInt(k.css("padding-right"),10)-D,G=e.scroller_calcWidths;for(c.scroller.removeFixed(b,e,!1),o=0;B>o;o++)F+=G[o]+E;for(F=F+2*D-E,A.setWidth(x.add(x.children()),F),A.setWidth(x.children().children("table"),F),p=0;pq;q++)s=a(w[q].outerHTML),s.children("td, th").slice(B).remove(),t.append(s);c.processTbody(y,t,!1)}for(g=c.scroller.hasScrollBar(l)?m:0,(A.isFirefox||A.isOldIE)&&y.css("width",F).parent().css("width",F+g),x.removeClass(d.scrollerHideElement),o=0;B>o;o++)h.children("div").children("table").find("th:nth-child("+(o+1)+"), td:nth-child("+(o+1)+")").addClass(d.scrollerHideColumn);F-=D,f=l.parent().innerWidth()-F,l.width(f),h.children("."+d.scrollerTable).css(n?"right":"left",F),h.children("."+d.scrollerHeader+", ."+d.scrollerFooter).css(n?"right":"left",F+(n&&c.scroller.isSafari?g:0)),i.parent().add(j.parent()).width(f-g),f=c.scroller.hasScrollBar(l,!0),g=f?m:0,!x.find("."+d.scrollerBarSpacer).length&&f?(C=a('
').css("height",g+"px"),x.find("."+d.scrollerTable).append(C)):f||x.find("."+d.scrollerBarSpacer).remove(),c.scroller.updateRowHeight(b,e),x.height(h.height()),x.removeClass(d.scrollerHideElement),e.scroller_isBusy=!1}},fixHeight:function(a,b){var c,e,f,g,h,i=a.length;for(c=0;i>c;c++)g=a.eq(c),h=b.eq(c),e=g.height(),f=h.height(),e>f?h.addClass(d.scrollerAddedHeight).height(e):f>e&&g.addClass(d.scrollerAddedHeight).height(f)},updateRowHeight:function(a,b){var e,f,g=b.scroller_$fixedColumns;b.scroller_$container.find("."+d.scrollerAddedHeight).removeClass(d.scrollerAddedHeight).height(""),e=b.scroller_$header.children("thead").children("tr"),f=g.children("."+d.scrollerHeader).children("table").children("thead").children("tr"),c.scroller.fixHeight(e,f),e=b.scroller_$footer.children("tfoot").children("tr"),f=g.children("."+d.scrollerFooter).children("table").children("tfoot").children("tr"),c.scroller.fixHeight(e,f),(c.scroller.isFirefox||c.scroller.isOldIE)&&(g=g.find("."+d.scrollerHack)),e=a.$table.children("tbody").children("tr"),f=g.children("."+d.scrollerTable).children("table").children("tbody").children("tr"),c.scroller.fixHeight(e,f)},removeFixed:function(a,b,c){var e=a.$table,f=b.scroller_$container,g=e.hasClass(d.scrollerRtl);(c||"undefined"==typeof c)&&f.find("."+d.scrollerFixed).remove(),f.find("."+d.scrollerHideColumn).removeClass(d.scrollerHideColumn),f.children(":not(."+d.scrollerFixed+")").css(g?"right":"left",0)},remove:function(c,e){var f=e.scroller_$container,g=c.namespace+"tsscroller";c.$table.off(g),a(b).off(g),f&&(c.$table.insertBefore(f).find("thead").removeClass(d.scrollerHideElement).children("tr."+d.headerRow).children().attr("tabindex",0).end().find("."+d.filterRow).removeClass(d.scrollerHideElement+" "+d.filterRowHide),c.$table.find("."+d.filter).not("."+d.filterDisabled).prop("disabled",!1),f.remove(),c.isScrolling=!1)}}}(jQuery,window); \ No newline at end of file diff --git a/js/jquery.tablesorter.combined.js b/js/jquery.tablesorter.combined.js index 633c679d..0c06a726 100644 --- a/js/jquery.tablesorter.combined.js +++ b/js/jquery.tablesorter.combined.js @@ -206,7 +206,7 @@ // node could be a jquery object // http://jsperf.com/jquery-vs-instanceof-jquery/2 $node = node.jquery ? node : $(node); - if (typeof(t) === 'string') { + if (typeof t === 'string') { // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow! // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/ if ( t === 'basic' && typeof ( te = $node.attr(c.textAttribute) ) !== 'undefined' ) { @@ -214,7 +214,7 @@ } return $.trim( node.textContent || $node.text() ); } else { - if (typeof(t) === 'function') { + if (typeof t === 'function') { return $.trim( t($node[0], c.table, cellIndex) ); } else if (typeof (te = ts.getColumnData( c.table, t, cellIndex )) === 'function') { return $.trim( te($node[0], c.table, cellIndex) ); @@ -274,7 +274,7 @@ // make sure txt is a string (extractor may have converted it) parser.format( '' + txt, c.table, cell, colIndex ); if ( c.ignoreCase && typeof val === 'string' ) { - val = val.toLowerCase(); + val = val.toLowerCase(); } } return val; @@ -518,20 +518,22 @@ // set up header template t = c.headerTemplate.replace(/\{content\}/g, $t.html()).replace(/\{icon\}/g, $t.find('.' + ts.css.icon).length ? '' : i); if (c.onRenderTemplate) { - h = c.onRenderTemplate.apply($t, [index, t]); + h = c.onRenderTemplate.apply( $t, [ index, t ] ); if (h && typeof h === 'string') { t = h; } // only change t if something is returned } $t.html('
' + t + '
'); // faster than wrapInner } - if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index, c, c.$table]); } + if (c.onRenderHeader) { c.onRenderHeader.apply( $t, [ index, c, c.$table ] ); } // *** remove this.column value if no conflicts found elem.column = parseInt( $t.attr('data-column'), 10); - elem.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2]; + elem.order = formatSortingOrder( ts.getData( $t, ch, 'sortInitialOrder' ) || c.sortInitialOrder ) ? + [ 1, 0, 2 ] : // desc, asc, unsorted + [ 0, 1, 2 ]; // asc, desc, unsorted elem.count = -1; // set to -1 because clicking on the header automatically adds one elem.lockedOrder = false; lock = ts.getData($t, ch, 'lockedOrder') || false; if (typeof lock !== 'undefined' && lock !== false) { - elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0]; + elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [ 1, 1, 1 ] : [ 0, 0, 0 ]; } $t.addClass(ts.css.header + ' ' + c.cssHeader); // add cell to headerList @@ -601,9 +603,9 @@ list = c.sortList, len = list.length, none = ts.css.sortNone + ' ' + c.cssNone, - css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc], + css = [ ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc ], cssIcon = [ c.cssIconAsc, c.cssIconDesc, c.cssIconNone ], - aria = ['ascending', 'descending'], + aria = [ 'ascending', 'descending' ], // find the footer $t = $(table).find('tfoot tr').children() .add( $( c.namespace + '_extra_headers' ) ) @@ -673,7 +675,7 @@ dir = ('' + val[1]).match(/^(1|d|s|o|n)/); dir = dir ? dir[0] : ''; // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext - switch(dir) { + switch (dir) { case '1': case 'd': // descending dir = 1; break; @@ -749,11 +751,11 @@ // add column to sort list order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -764,7 +766,7 @@ for (col = 0; col < c.sortAppend.length; col++) { s = ts.isValueInArray(c.sortAppend[col][0], c.sortList); if (s >= 0) { - c.sortList.splice(s,1); + c.sortList.splice(s, 1); } } } @@ -778,7 +780,7 @@ // order.count seems to be incorrect when compared to cell.count s[1] = order.order[cell.count]; if (s[1] === 2) { - c.sortList.splice(col,1); + c.sortList.splice(col, 1); order.count = -1; } } @@ -787,11 +789,11 @@ // add column to sort list array order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -865,10 +867,10 @@ x = dir ? a : b; y = dir ? b : a; // text sort function - if (typeof(cts) === 'function') { + if (typeof cts === 'function') { // custom OVERALL text sorter sort = cts(x[col], y[col], dir, col, table); - } else if (typeof(cts) === 'object' && cts.hasOwnProperty(col)) { + } else if (typeof cts === 'object' && cts.hasOwnProperty(col)) { // custom text sorter for a SPECIFIC COLUMN sort = cts[col](x[col], y[col], dir, col, table); } else { @@ -901,14 +903,14 @@ // this will catch spamming of the updateCell method if (resrt !== false && !c.serverSideSorting && !c.table.isProcessing) { if (sl.length) { - c.$table.trigger('sorton', [sl, function(){ + c.$table.trigger('sorton', [ sl, function(){ resortComplete(c, callback); - }, true]); + }, true ]); } else { - c.$table.trigger('sortReset', [function(){ + c.$table.trigger('sortReset', [ function(){ resortComplete(c, callback); ts.applyWidget(c.table, false); - }]); + } ]); } } else { resortComplete(c, callback); @@ -1108,10 +1110,10 @@ ts.construct = function(settings) { return this.each(function() { var table = this, - // merge & extend config options - c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); - // save initial settings - c.originalSettings = settings; + // merge & extend config options + c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); + // save initial settings + c.originalSettings = settings; // create a table from data (build table widget) if (!table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE') { // return the table (in case the original target is the table's container) @@ -1166,7 +1168,7 @@ c.namespace = '.tablesorter' + Math.random().toString(16).slice(2); } else { // make sure namespace starts with a period & doesn't have weird characters - c.namespace = '.' + c.namespace.replace(/\W/g,''); + c.namespace = '.' + c.namespace.replace(/\W/g, ''); } c.$table.children().children('tr').attr('role', 'row'); @@ -1210,7 +1212,7 @@ ts.applyWidget(table, true); // if user has supplied a sort list to constructor if (c.sortList.length > 0) { - $table.trigger('sorton', [c.sortList, {}, !c.initWidgets, true]); + $table.trigger('sorton', [ c.sortList, {}, !c.initWidgets, true ]); } else { setHeadersCss(table); if (c.initWidgets) { @@ -1249,22 +1251,22 @@ table = $(table)[0]; var overallWidth, percent, $tbodies, len, index, c = table.config, - colgroup = c.$table.children('colgroup'); + $colgroup = c.$table.children('colgroup'); // remove plugin-added colgroup, in case we need to refresh the widths - if (colgroup.length && colgroup.hasClass(ts.css.colgroup)) { - colgroup.remove(); + if ($colgroup.length && $colgroup.hasClass(ts.css.colgroup)) { + $colgroup.remove(); } if (c.widthFixed && c.$table.children('colgroup').length === 0) { - colgroup = $(''); + $colgroup = $(''); overallWidth = c.$table.width(); // only add col for visible columns - fixes #371 - $tbodies = c.$tbodies.find('tr:first').children(':visible'); //.each(function() + $tbodies = c.$tbodies.find('tr:first').children(':visible'); // .each(function() len = $tbodies.length; for ( index = 0; index < len; index++ ) { percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%'; - colgroup.append( $('').css('width', percent) ); + $colgroup.append( $('').css('width', percent) ); } - c.$table.prepend(colgroup); + c.$table.prepend($colgroup); } }; @@ -1311,12 +1313,12 @@ cellId = rowIndex + '-' + $cell.index(); rowSpan = cell.rowSpan || 1; colSpan = cell.colSpan || 1; - if (typeof(matrix[rowIndex]) === 'undefined') { + 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') { + if (typeof matrix[rowIndex][k] === 'undefined') { firstAvailCol = k; break; } @@ -1325,7 +1327,7 @@ // add data-column $cell.attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex for (k = rowIndex; k < rowIndex + rowSpan; k++) { - if (typeof(matrix[k]) === 'undefined') { + if (typeof matrix[k] === 'undefined') { matrix[k] = []; } matrixrow = matrix[k]; @@ -1341,10 +1343,10 @@ // *** Process table *** // add processing indicator ts.isProcessing = function(table, toggle, $ths) { - table = $(table); - var c = table[0].config, + $table = $(table); + var c = $table[0].config, // default to all headers - $h = $ths || table.find('.' + ts.css.header); + $h = $ths || $table.find('.' + ts.css.header); if (toggle) { // don't use sortList if custom $ths used if (typeof $ths !== 'undefined' && c.sortList.length > 0) { @@ -1354,9 +1356,9 @@ return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList) >= 0; }); } - table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); } else { - table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); } }; @@ -1486,8 +1488,8 @@ $f = $t.find('tfoot:first > tr').children('th, td'); if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) { // reapply uitheme classes, in case we want to maintain appearance - $t.trigger('applyWidgetId', ['uitheme']); - $t.trigger('applyWidgetId', ['zebra']); + $t.trigger('applyWidgetId', [ 'uitheme' ]); + $t.trigger('applyWidgetId', [ 'zebra' ]); } // remove widget added rows, just in case $h.find('tr').not($r).remove(); @@ -1499,7 +1501,7 @@ .removeData('tablesorter') .unbind( events.replace(/\s+/g, ' ') ); c.$headers.add($f) - .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') ) + .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join(' ') ) .removeAttr('data-column') .removeAttr('aria-label') .attr('aria-disabled', 'true'); @@ -1705,8 +1707,8 @@ }; ts.hasWidget = function(table, name){ - table = $(table); - return table.length && table[0].config && table[0].config.widgetInit[name] || false; + $table = $(table); + return $table.length && $table[0].config && $table[0].config.widgetInit[name] || false; }; ts.getWidgetById = function(name) { @@ -1746,7 +1748,7 @@ if (c.debug) { time = new Date(); } // look for widgets to apply from in table class // stop using \b otherwise this matches 'ui-widget-content' & adds 'content' widget - wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' )+ '\\s', 'g' ); + wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' ) + '\\s', 'g' ); if ( tableClass.match( wd ) ) { // extract out the widget id from the table class (widget id's can include dashes) w = tableClass.match( wd ); @@ -1954,13 +1956,13 @@ typeof table !== 'undefined' ? table : true; if (t) { // US Format - 1,234,567.89 -> 1234567.89 - s = s.replace(/,/g,''); + 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,'.'); + s = s.replace(/[\s|\.]/g, '').replace(/,/g, '.'); } - if(/^\s*\([.\d]+\)/.test(s)) { + if (/^\s*\([.\d]+\)/.test(s)) { // make (#) into a negative number -> (10) = -10 s = s.replace(/^\s*\(([.\d]+)\)/, '-$1'); } @@ -2028,7 +2030,7 @@ ts.addParser({ id: 'currency', is: function(s) { - return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g,'')); // £$€¤¥¢ + return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g, '')); // £$€¤¥¢ }, format: function(s, table) { var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table); @@ -2103,7 +2105,7 @@ id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd' is: function(s) { // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included - return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g,' ').replace(/[\-.,]/g, '/')); + return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g, ' ').replace(/[\-.,]/g, '/')); }, format: function(s, table, cell, cellIndex) { if (s) { @@ -2196,356 +2198,356 @@ /*! Widget: storage - updated 3/26/2015 (v2.21.3) */ ;(function ($, window, document) { -'use strict'; + 'use strict'; -var ts = $.tablesorter || {}; -// *** Store data in local storage, with a cookie fallback *** -/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) - if you need it, then include https://github.com/douglascrockford/JSON-js + var ts = $.tablesorter || {}; + // *** Store data in local storage, with a cookie fallback *** + /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) + if you need it, then include https://github.com/douglascrockford/JSON-js - $.parseJSON is not available is jQuery versions older than 1.4.1, using older - versions will only allow storing information for one page at a time + $.parseJSON is not available is jQuery versions older than 1.4.1, using older + versions will only allow storing information for one page at a time - // *** Save data (JSON format only) *** - // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid - var val = { "mywidget" : "data1" }; // valid JSON uses double quotes - // $.tablesorter.storage(table, key, val); - $.tablesorter.storage(table, 'tablesorter-mywidget', val); + // *** Save data (JSON format only) *** + // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid + var val = { "mywidget" : "data1" }; // valid JSON uses double quotes + // $.tablesorter.storage(table, key, val); + $.tablesorter.storage(table, 'tablesorter-mywidget', val); - // *** Get data: $.tablesorter.storage(table, key); *** - v = $.tablesorter.storage(table, 'tablesorter-mywidget'); - // val may be empty, so also check for your data - val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; - alert(val); // "data1" if saved, or "" if not -*/ -ts.storage = function(table, key, value, options) { - table = $(table)[0]; - var cookieIndex, cookies, date, - hasStorage = false, - values = {}, - c = table.config, - wo = c && c.widgetOptions, - storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? - 'sessionStorage' : 'localStorage', - $table = $(table), - // id from (1) options ID, (2) table "data-table-group" attribute, (3) widgetOptions.storage_tableId, - // (4) table ID, then (5) table index - id = options && options.id || - $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || - wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), - // url from (1) options url, (2) table "data-table-page" attribute, (3) widgetOptions.storage_fixedUrl, - // (4) table.config.fixedUrl (deprecated), then (5) window location path - url = options && options.url || - $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || - wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; - // https://gist.github.com/paulirish/5558557 - if (storageType in window) { - try { - window[storageType].setItem('_tmptest', 'temp'); - hasStorage = true; - window[storageType].removeItem('_tmptest'); - } catch(error) { - if (c && c.debug) { - ts.log( storageType + ' is not supported in this browser' ); + // *** Get data: $.tablesorter.storage(table, key); *** + v = $.tablesorter.storage(table, 'tablesorter-mywidget'); + // val may be empty, so also check for your data + val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; + alert(val); // 'data1' if saved, or '' if not + */ + ts.storage = function(table, key, value, options) { + table = $(table)[0]; + var cookieIndex, cookies, date, + hasStorage = false, + values = {}, + c = table.config, + wo = c && c.widgetOptions, + storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? + 'sessionStorage' : 'localStorage', + $table = $(table), + // id from (1) options ID, (2) table 'data-table-group' attribute, (3) widgetOptions.storage_tableId, + // (4) table ID, then (5) table index + id = options && options.id || + $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || + wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), + // url from (1) options url, (2) table 'data-table-page' attribute, (3) widgetOptions.storage_fixedUrl, + // (4) table.config.fixedUrl (deprecated), then (5) window location path + url = options && options.url || + $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || + wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; + // https://gist.github.com/paulirish/5558557 + if (storageType in window) { + try { + window[storageType].setItem('_tmptest', 'temp'); + hasStorage = true; + window[storageType].removeItem('_tmptest'); + } catch (error) { + if (c && c.debug) { + ts.log( storageType + ' is not supported in this browser' ); + } } } - } - // *** get value *** - if ($.parseJSON) { - if (hasStorage) { - values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + // *** get value *** + if ($.parseJSON) { + if (hasStorage) { + values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + } else { + // old browser, using cookies + cookies = document.cookie.split(/[;\s|=]/); + // add one to get from the key to the value + cookieIndex = $.inArray(key, cookies) + 1; + values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + } + } + // allow value to be an empty string too + if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { + // add unique identifiers = url pathname > table ID/index on page > data + if (!values[url]) { + values[url] = {}; + } + values[url][id] = value; + // *** set value *** + if (hasStorage) { + window[storageType][key] = JSON.stringify(values); + } else { + date = new Date(); + date.setTime(date.getTime() + (31536e+6)); // 365 days + document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/'; + } } else { - // old browser, using cookies - cookies = document.cookie.split(/[;\s|=]/); - // add one to get from the key to the value - cookieIndex = $.inArray(key, cookies) + 1; - values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + return values && values[url] ? values[url][id] : ''; } - } - // allow value to be an empty string too - if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { - // add unique identifiers = url pathname > table ID/index on page > data - if (!values[url]) { - values[url] = {}; - } - values[url][id] = value; - // *** set value *** - if (hasStorage) { - window[storageType][key] = JSON.stringify(values); - } else { - date = new Date(); - date.setTime(date.getTime() + (31536e+6)); // 365 days - document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/'; - } - } else { - return values && values[url] ? values[url][id] : ''; - } -}; + }; })(jQuery, window, document); /*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.themes = { - 'bootstrap' : { - table : 'table table-bordered table-striped', - caption : 'caption', - // header class names - header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) - sortNone : '', - sortAsc : '', - sortDesc : '', - active : '', // applied when column is sorted - hover : '', // custom css required - a defined bootstrap style may not override other classes - // icon class names - icons : '', // add "icon-white" to make them white; this icon class is added to the in the header - iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted - iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort - iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort - filterRow : '', // filter row class - footerRow : '', - footerCells : '', - even : '', // even row zebra striping - odd : '' // odd row zebra striping - }, - 'jui' : { - table : 'ui-widget ui-widget-content ui-corner-all', // table classes - caption : 'ui-widget-content', - // header class names - header : 'ui-widget-header ui-corner-all ui-state-default', // header classes - sortNone : '', - sortAsc : '', - sortDesc : '', - active : 'ui-state-active', // applied when column is sorted - hover : 'ui-state-hover', // hover class - // icon class names - icons : 'ui-icon', // icon class added to the in the header - iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted - iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort - iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort - filterRow : '', - footerRow : '', - footerCells : '', - even : 'ui-widget-content', // even row zebra striping - odd : 'ui-state-default' // odd row zebra striping - } -}; + ts.themes = { + 'bootstrap' : { + table : 'table table-bordered table-striped', + caption : 'caption', + // header class names + header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) + sortNone : '', + sortAsc : '', + sortDesc : '', + active : '', // applied when column is sorted + hover : '', // custom css required - a defined bootstrap style may not override other classes + // icon class names + icons : '', // add 'icon-white' to make them white; this icon class is added to the in the header + iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted + iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort + iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort + filterRow : '', // filter row class + footerRow : '', + footerCells : '', + even : '', // even row zebra striping + odd : '' // odd row zebra striping + }, + 'jui' : { + table : 'ui-widget ui-widget-content ui-corner-all', // table classes + caption : 'ui-widget-content', + // header class names + header : 'ui-widget-header ui-corner-all ui-state-default', // header classes + sortNone : '', + sortAsc : '', + sortDesc : '', + active : 'ui-state-active', // applied when column is sorted + hover : 'ui-state-hover', // hover class + // icon class names + icons : 'ui-icon', // icon class added to the in the header + iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted + iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort + iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort + filterRow : '', + footerRow : '', + footerCells : '', + even : 'ui-widget-content', // even row zebra striping + odd : 'ui-state-default' // odd row zebra striping + } + }; -$.extend(ts.css, { - wrapper : 'tablesorter-wrapper' // ui theme & resizable -}); + $.extend(ts.css, { + wrapper : 'tablesorter-wrapper' // ui theme & resizable + }); -ts.addWidget({ - id: "uitheme", - priority: 10, - format: function(table, c, wo) { - var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, - themesAll = ts.themes, - $table = c.$table.add( $( c.namespace + '_extra_table' ) ), - $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), - theme = c.theme || 'jui', - themes = themesAll[theme] || {}, - remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), - iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); - if (c.debug) { time = new Date(); } - // initialization code - run once - if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { - wo.uitheme_applied = true; - oldtheme = themesAll[c.appliedTheme] || {}; - hasOldTheme = !$.isEmptyObject(oldtheme); - oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; - oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; - if (hasOldTheme) { - wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); - wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); - c.$tbodies.children().removeClass( [oldtheme.even, oldtheme.odd].join(' ') ); - } - // update zebra stripes - if (themes.even) { wo.zebra[0] += ' ' + themes.even; } - if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } - // add caption style - $table.children('caption') - .removeClass(oldtheme.caption || '') - .addClass(themes.caption); - // add table/footer class names - $tfoot = $table - // remove other selected themes - .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) - .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name - .children('tfoot'); - c.appliedTheme = c.theme; - - if ($tfoot.length) { - $tfoot - // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed - .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) - .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); - } - // update header classes - $headers - .removeClass( (hasOldTheme ? [oldtheme.header, oldtheme.hover, oldremove].join(' ') : '') || '' ) - .addClass(themes.header) - .not('.sorter-false') - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') - .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { - // toggleClass with switch added in jQuery 1.3 - $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); - }); - - $headers.each(function(){ - var $this = $(this); - if (!$this.find('.' + ts.css.wrapper).length) { - // Firefox needs this inner div to position the icon & resizer correctly - $this.wrapInner('
'); + ts.addWidget({ + id: 'uitheme', + priority: 10, + format: function(table, c, wo) { + var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, + themesAll = ts.themes, + $table = c.$table.add( $( c.namespace + '_extra_table' ) ), + $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), + theme = c.theme || 'jui', + themes = themesAll[theme] || {}, + remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), + iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); + if (c.debug) { time = new Date(); } + // initialization code - run once + if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { + wo.uitheme_applied = true; + oldtheme = themesAll[c.appliedTheme] || {}; + hasOldTheme = !$.isEmptyObject(oldtheme); + oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; + oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; + if (hasOldTheme) { + wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); + wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); + c.$tbodies.children().removeClass( [ oldtheme.even, oldtheme.odd ].join(' ') ); } - }); - if (c.cssIcon) { - // if c.cssIcon is '', then no is added to the header + // update zebra stripes + if (themes.even) { wo.zebra[0] += ' ' + themes.even; } + if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } + // add caption style + $table.children('caption') + .removeClass(oldtheme.caption || '') + .addClass(themes.caption); + // add table/footer class names + $tfoot = $table + // remove other selected themes + .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) + .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name + .children('tfoot'); + c.appliedTheme = c.theme; + + if ($tfoot.length) { + $tfoot + // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed + .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) + .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); + } + // update header classes $headers - .find('.' + ts.css.icon) - .removeClass(hasOldTheme ? [oldtheme.icons, oldIconRmv].join(' ') : '') - .addClass(themes.icons || ''); - } - if ($table.hasClass('hasFilters')) { - $table.children('thead').children('.' + ts.css.filterRow) - .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') - .addClass(themes.filterRow || ''); - } - } - for (i = 0; i < c.columns; i++) { - $header = c.$headers - .add($(c.namespace + '_extra_headers')) - .not('.sorter-false') - .filter('[data-column="' + i + '"]'); - $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); - $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); - if ($h.length) { - $header.removeClass(remove); - $icon.removeClass(iconRmv); - if ($h[0].sortDisabled) { - // no sort arrows for disabled columns! - $icon.removeClass(themes.icons || ''); - } else { - hdr = themes.sortNone; - icon = themes.iconSortNone; - if ($h.hasClass(ts.css.sortAsc)) { - hdr = [themes.sortAsc, themes.active].join(' '); - icon = themes.iconSortAsc; - } else if ($h.hasClass(ts.css.sortDesc)) { - hdr = [themes.sortDesc, themes.active].join(' '); - icon = themes.iconSortDesc; + .removeClass( (hasOldTheme ? [ oldtheme.header, oldtheme.hover, oldremove ].join(' ') : '') || '' ) + .addClass(themes.header) + .not('.sorter-false') + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') + .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { + // toggleClass with switch added in jQuery 1.3 + $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); + }); + + $headers.each(function(){ + var $this = $(this); + if (!$this.find('.' + ts.css.wrapper).length) { + // Firefox needs this inner div to position the icon & resizer correctly + $this.wrapInner('
'); } - $header.addClass(hdr); - $icon.addClass(icon || ''); + }); + if (c.cssIcon) { + // if c.cssIcon is '', then no is added to the header + $headers + .find('.' + ts.css.icon) + .removeClass(hasOldTheme ? [ oldtheme.icons, oldIconRmv ].join(' ') : '') + .addClass(themes.icons || ''); + } + if ($table.hasClass('hasFilters')) { + $table.children('thead').children('.' + ts.css.filterRow) + .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') + .addClass(themes.filterRow || ''); } } + for (i = 0; i < c.columns; i++) { + $header = c.$headers + .add($(c.namespace + '_extra_headers')) + .not('.sorter-false') + .filter('[data-column="' + i + '"]'); + $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); + $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); + if ($h.length) { + $header.removeClass(remove); + $icon.removeClass(iconRmv); + if ($h[0].sortDisabled) { + // no sort arrows for disabled columns! + $icon.removeClass(themes.icons || ''); + } else { + hdr = themes.sortNone; + icon = themes.iconSortNone; + if ($h.hasClass(ts.css.sortAsc)) { + hdr = [ themes.sortAsc, themes.active ].join(' '); + icon = themes.iconSortAsc; + } else if ($h.hasClass(ts.css.sortDesc)) { + hdr = [ themes.sortDesc, themes.active ].join(' '); + icon = themes.iconSortDesc; + } + $header.addClass(hdr); + $icon.addClass(icon || ''); + } + } + } + if (c.debug) { + ts.benchmark('Applying ' + theme + ' theme', time); + } + }, + remove: function(table, c, wo, refreshing) { + if (!wo.uitheme_applied) { return; } + var $table = c.$table, + theme = c.appliedTheme || 'jui', + themes = ts.themes[ theme ] || ts.themes.jui, + $headers = $table.children('thead').children(), + remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, + iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; + $table.removeClass('tablesorter-' + theme + ' ' + themes.table); + wo.uitheme_applied = false; + if (refreshing) { return; } + $table.find(ts.css.header).removeClass(themes.header); + $headers + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover + .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) + .filter('.' + ts.css.filterRow) + .removeClass(themes.filterRow); + $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); } - if (c.debug) { - ts.benchmark("Applying " + theme + " theme", time); - } - }, - remove: function(table, c, wo, refreshing) { - if (!wo.uitheme_applied) { return; } - var $table = c.$table, - theme = c.appliedTheme || 'jui', - themes = ts.themes[ theme ] || ts.themes.jui, - $headers = $table.children('thead').children(), - remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, - iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; - $table.removeClass('tablesorter-' + theme + ' ' + themes.table); - wo.uitheme_applied = false; - if (refreshing) { return; } - $table.find(ts.css.header).removeClass(themes.header); - $headers - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover - .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) - .filter('.' + ts.css.filterRow) - .removeClass(themes.filterRow); - $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); - } -}); + }); })(jQuery); /*! Widget: columns */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.addWidget({ - id: "columns", - priority: 30, - options : { - columns : [ "primary", "secondary", "tertiary" ] - }, - format: function(table, c, wo) { - var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, + ts.addWidget({ + id: 'columns', + priority: 30, + options : { + columns : [ 'primary', 'secondary', 'tertiary' ] + }, + format: function(table, c, wo) { + var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, $table = c.$table, $tbodies = c.$tbodies, sortList = c.sortList, len = sortList.length, // removed c.widgetColumns support - css = wo && wo.columns || [ "primary", "secondary", "tertiary" ], + css = wo && wo.columns || [ 'primary', 'secondary', 'tertiary' ], last = css.length - 1; remove = css.join(' '); - // check if there is a sort (on initialization there may not be one) - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody - $rows = $tbody.children('tr'); - // loop through the visible rows - $rows.each(function() { - $row = $(this); - if (this.style.display !== 'none') { - // remove all columns class names - $cells = $row.children().removeClass(remove); - // add appropriate column class names - if (sortList && sortList[0]) { - // primary sort column class - $cells.eq(sortList[0][0]).addClass(css[0]); - if (len > 1) { - for (indx = 1; indx < len; indx++) { - // secondary, tertiary, etc sort column classes - $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + // check if there is a sort (on initialization there may not be one) + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody + $rows = $tbody.children('tr'); + // loop through the visible rows + $rows.each(function() { + $row = $(this); + if (this.style.display !== 'none') { + // remove all columns class names + $cells = $row.children().removeClass(remove); + // add appropriate column class names + if (sortList && sortList[0]) { + // primary sort column class + $cells.eq(sortList[0][0]).addClass(css[0]); + if (len > 1) { + for (indx = 1; indx < len; indx++) { + // secondary, tertiary, etc sort column classes + $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + } } } } - } - }); - ts.processTbody(table, $tbody, false); - } - // add classes to thead and tfoot - rows = wo.columns_thead !== false ? ['thead tr'] : []; - if (wo.columns_tfoot !== false) { - rows.push('tfoot tr'); - } - if (rows.length) { - $rows = $table.find( rows.join(',') ).children().removeClass(remove); - if (len) { - for (indx = 0; indx < len; indx++) { - // add primary. secondary, tertiary, etc sort column classes - $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + }); + ts.processTbody(table, $tbody, false); + } + // add classes to thead and tfoot + rows = wo.columns_thead !== false ? [ 'thead tr' ] : []; + if (wo.columns_tfoot !== false) { + rows.push('tfoot tr'); + } + if (rows.length) { + $rows = $table.find( rows.join(',') ).children().removeClass(remove); + if (len) { + for (indx = 0; indx < len; indx++) { + // add primary. secondary, tertiary, etc sort column classes + $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + } } } + }, + remove: function(table, c, wo) { + var tbodyIndex, $tbody, + $tbodies = c.$tbodies, + remove = (wo.columns || [ 'primary', 'secondary', 'tertiary' ]).join(' '); + c.$headers.removeClass(remove); + c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody + $tbody.children('tr').each(function() { + $(this).children().removeClass(remove); + }); + ts.processTbody(table, $tbody, false); // restore tbody + } } - }, - remove: function(table, c, wo) { - var tbodyIndex, $tbody, - $tbodies = c.$tbodies, - remove = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' '); - c.$headers.removeClass(remove); - c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody - $tbody.children('tr').each(function() { - $(this).children().removeClass(remove); - }); - ts.processTbody(table, $tbody, false); // restore tbody - } - } -}); + }); })(jQuery); @@ -2554,92 +2556,92 @@ ts.addWidget({ * by Rob Garrison */ ;( function ( $ ) { -'use strict'; -var ts = $.tablesorter || {}, + 'use strict'; + var ts = $.tablesorter || {}, tscss = ts.css; -$.extend( tscss, { - filterRow : 'tablesorter-filter-row', - filter : 'tablesorter-filter', - filterDisabled : 'disabled', - filterRowHide : 'hideme' -}); + $.extend( tscss, { + filterRow : 'tablesorter-filter-row', + filter : 'tablesorter-filter', + filterDisabled : 'disabled', + filterRowHide : 'hideme' + }); -ts.addWidget({ - id: 'filter', - priority: 50, - options : { - filter_childRows : false, // if true, filter includes child row content in the search - filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped - filter_columnFilters : true, // if true, a filter will be added to the top of each table column - filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) - filter_cellFilter : '', // css class name added to the filter cell ( string or array ) - filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) - filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. - filter_excludeFilter : {}, // filters to exclude, per column - filter_external : '', // jQuery selector string ( or jQuery object ) of external filters - filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin - filter_formatter : null, // add custom filter elements to the filter row - filter_functions : null, // add custom filter functions using this option - filter_hideEmpty : true, // hide filter row when table is empty - filter_hideFilters : false, // collapse filter row when mouse leaves the area - filter_ignoreCase : true, // if true, make all searches case-insensitive - filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) - filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down - filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) - filter_reset : null, // jQuery selector string of an element used to reset the filters - filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters - filter_searchDelay : 300, // typing delay in milliseconds before starting a search - filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true - filter_selectSource : null, // include a function to return an array of values to be added to the column filter select - filter_startsWith : false, // if true, filter start from the beginning of the cell contents - filter_useParsedData : false, // filter all data using parsed content - filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used. - filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value - filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text - }, - format: function( table, c, wo ) { - if ( !c.$table.hasClass( 'hasFilters' ) ) { - ts.filter.init( table, c, wo ); + ts.addWidget({ + id: 'filter', + priority: 50, + options : { + filter_childRows : false, // if true, filter includes child row content in the search + filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped + filter_columnFilters : true, // if true, a filter will be added to the top of each table column + filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) + filter_cellFilter : '', // css class name added to the filter cell ( string or array ) + filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) + filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. + filter_excludeFilter : {}, // filters to exclude, per column + filter_external : '', // jQuery selector string ( or jQuery object ) of external filters + filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin + filter_formatter : null, // add custom filter elements to the filter row + filter_functions : null, // add custom filter functions using this option + filter_hideEmpty : true, // hide filter row when table is empty + filter_hideFilters : false, // collapse filter row when mouse leaves the area + filter_ignoreCase : true, // if true, make all searches case-insensitive + filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) + filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down + filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) + filter_reset : null, // jQuery selector string of an element used to reset the filters + filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters + filter_searchDelay : 300, // typing delay in milliseconds before starting a search + filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true + filter_selectSource : null, // include a function to return an array of values to be added to the column filter select + filter_startsWith : false, // if true, filter start from the beginning of the cell contents + filter_useParsedData : false, // filter all data using parsed content + filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used. + filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value + filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text + }, + format: function( table, c, wo ) { + if ( !c.$table.hasClass( 'hasFilters' ) ) { + ts.filter.init( table, c, wo ); + } + }, + remove: function( table, c, wo, refreshing ) { + var tbodyIndex, $tbody, + $table = c.$table, + $tbodies = c.$tbodies, + events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + $table + .removeClass( 'hasFilters' ) + // add .tsfilter namespace to all BUT search + .unbind( events.replace( /\s+/g, ' ' ) ) + // remove the filter row even if refreshing, because the column might have been moved + .find( '.' + tscss.filterRow ).remove(); + if ( refreshing ) { return; } + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody + $tbody.children().removeClass( wo.filter_filteredRow ).show(); + ts.processTbody( table, $tbody, false ); // restore tbody + } + if ( wo.filter_reset ) { + $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); + } } - }, - remove: function( table, c, wo, refreshing ) { - var tbodyIndex, $tbody, - $table = c.$table, - $tbodies = c.$tbodies, - events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - $table - .removeClass( 'hasFilters' ) - // add .tsfilter namespace to all BUT search - .unbind( events.replace( /\s+/g, ' ' ) ) - // remove the filter row even if refreshing, because the column might have been moved - .find( '.' + tscss.filterRow ).remove(); - if ( refreshing ) { return; } - for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody - $tbody.children().removeClass( wo.filter_filteredRow ).show(); - ts.processTbody( table, $tbody, false ); // restore tbody - } - if ( wo.filter_reset ) { - $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); - } - } -}); + }); -ts.filter = { + ts.filter = { - // regex used in filter 'check' functions - not for general use and not documented - regex: { - regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex - child : /tablesorter-childRow/, // child row class name; this gets updated in the script - filtered : /filtered/, // filtered (hidden) row class name; updated in the script - type : /undefined|number/, // check type - exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') - nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) - operators : /[<>=]/g, // replace operators - query : '(q|query)' // replace filter queries - }, + // regex used in filter 'check' functions - not for general use and not documented + regex: { + regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex + child : /tablesorter-childRow/, // child row class name; this gets updated in the script + filtered : /filtered/, // filtered (hidden) row class name; updated in the script + type : /undefined|number/, // check type + exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') + nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) + operators : /[<>=]/g, // replace operators + query : '(q|query)' // replace filter queries + }, // function( c, data ) { } // c = table.config // data.$row = jQuery object of the row currently being processed @@ -2653,1508 +2655,1508 @@ ts.filter = { // data.cacheArray = An array of parsed content from each table cell in the row being processed // data.index = column index; table = table element ( DOM ) // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class ) - types: { - or : function( c, data, vars ) { - if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { - var indx, filterMatched, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.orSplit ), - iFilter = data.iFilter.split( ts.filter.regex.orSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // filterMatched = data2.filter === '' && indx > 0 ? true - // look for an exact match with the 'or' unless the 'filter-match' class is found - filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); - if ( filterMatched ) { - return filterMatched; + types: { + or : function( c, data, vars ) { + if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { + var indx, filterMatched, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.orSplit ), + iFilter = data.iFilter.split( ts.filter.regex.orSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // filterMatched = data2.filter === '' && indx > 0 ? true + // look for an exact match with the 'or' unless the 'filter-match' class is found + filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); + if ( filterMatched ) { + return filterMatched; + } } + // may be null from processing types + return filterMatched || false; } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for an AND or && operator ( logical and ) - and : function( c, data, vars ) { - if ( ts.filter.regex.andTest.test( data.filter ) ) { - var indx, filterMatched, result, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.andSplit ), - iFilter = data.iFilter.split( ts.filter.regex.andSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) - // replace wild cards since /(a*)/i will match anything - .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // look for an exact match with the 'and' unless the 'filter-match' class is found - result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); - if ( indx === 0 ) { - filterMatched = result; + return null; + }, + // Look for an AND or && operator ( logical and ) + and : function( c, data, vars ) { + if ( ts.filter.regex.andTest.test( data.filter ) ) { + var indx, filterMatched, result, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.andSplit ), + iFilter = data.iFilter.split( ts.filter.regex.andSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) + // replace wild cards since /(a*)/i will match anything + .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // look for an exact match with the 'and' unless the 'filter-match' class is found + result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); + if ( indx === 0 ) { + filterMatched = result; + } else { + filterMatched = filterMatched && result; + } + } + // may be null from processing types + return filterMatched || false; + } + return null; + }, + // Look for regex + regex: function( c, data ) { + if ( ts.filter.regex.regex.test( data.filter ) ) { + var matches, + // cache regex per column for optimal speed + regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), + isRegex = regex instanceof RegExp; + try { + if ( !isRegex ) { + // force case insensitive search if ignoreCase option set? + // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } + data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); + } + matches = regex.test( data.exact ); + } catch ( error ) { + matches = false; + } + return matches; + } + return null; + }, + // Look for operators >, >=, < or <= + operators: function( c, data ) { + // ignore empty strings... because '' < 10 is true + if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { + var cachedValue, result, txt, + table = c.table, + index = data.index, + parsed = data.parsed[index], + query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), + parser = c.parsers[index], + savedSearch = query; + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || parser.type === 'numeric' ) { + txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); + result = ts.filter.parseFilter( c, txt, index, true ); + query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; + } + // iExact may be numeric - see issue #149; + // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) + if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && + typeof data.cache !== 'undefined' ) { + cachedValue = data.cache; } else { - filterMatched = filterMatched && result; + txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + cachedValue = ts.formatFloat( txt, table ); + } + if ( />/.test( data.iFilter ) ) { + result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; + } else if ( /= 0 ); } } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for regex - regex: function( c, data ) { - if ( ts.filter.regex.regex.test( data.filter ) ) { - var matches, - // cache regex per column for optimal speed - regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), - isRegex = regex instanceof RegExp; - try { - if ( !isRegex ) { - // force case insensitive search if ignoreCase option set? - // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } - data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); - } - matches = regex.test( data.exact ); - } catch ( error ) { - matches = false; + return null; + }, + // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric + exact: function( c, data ) { + /*jshint eqeqeq:false */ + if ( ts.filter.regex.exact.test( data.iFilter ) ) { + var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), + filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; } - return matches; - } - return null; - }, - // Look for operators >, >=, < or <= - operators: function( c, data ) { - // ignore empty strings... because '' < 10 is true - if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { - var cachedValue, result, txt, - table = c.table, - index = data.index, - parsed = data.parsed[index], - query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), - parser = c.parsers[index], - savedSearch = query; - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || parser.type === 'numeric' ) { - txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); - result = ts.filter.parseFilter( c, txt, index, true ); - query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; - } - // iExact may be numeric - see issue #149; - // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) - if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && - typeof data.cache !== 'undefined' ) { - cachedValue = data.cache; - } else { - txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - cachedValue = ts.formatFloat( txt, table ); - } - if ( />/.test( data.iFilter ) ) { - result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; - } else if ( /= 0 ); - } - } - return null; - }, - // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric - exact: function( c, data ) { - /*jshint eqeqeq:false */ - if ( ts.filter.regex.exact.test( data.iFilter ) ) { - var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), - filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; - } - return null; - }, - // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! - range : function( c, data ) { - if ( ts.filter.regex.toTest.test( data.iFilter ) ) { - var result, tmp, range1, range2, - table = c.table, - index = data.index, - parsed = data.parsed[index], - // make sure the dash is for a range and not indicating a negative number - query = data.iFilter.split( ts.filter.regex.toSplit ); + return null; + }, + // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! + range : function( c, data ) { + if ( ts.filter.regex.toTest.test( data.iFilter ) ) { + var result, tmp, range1, range2, + table = c.table, + index = data.index, + parsed = data.parsed[index], + // make sure the dash is for a range and not indicating a negative number + query = data.iFilter.split( ts.filter.regex.toSplit ); - tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; - range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; - range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || c.parsers[index].type === 'numeric' ) { - result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); - range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; - result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); - range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; - } - if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { - result = data.cache; - } else { - tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - result = ts.formatFloat( tmp, table ); - } - if ( range1 > range2 ) { - tmp = range1; range1 = range2; range2 = tmp; // swap - } - return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); - } - return null; - }, - // Look for wild card: ? = single, * = multiple, or | = logical OR - wild : function( c, data ) { - if ( /[\?\*\|]/.test( data.iFilter ) ) { - var index = data.index, - parsed = data.parsed[ index ], - query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); - // look for an exact match with the 'or' unless the 'filter-match' class is found - if ( !/\?\*/.test( query ) && data.nestedFilters ) { - query = data.isMatch ? query : '^(' + query + ')$'; - } - // parsing the filter may not work properly when using wildcards =/ - return new RegExp( - query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), - c.widgetOptions.filter_ignoreCase ? 'i' : '' - ) - .test( data.exact ); - } - return null; - }, - // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) - fuzzy: function( c, data ) { - if ( /^~/.test( data.iFilter ) ) { - var indx, - patternIndx = 0, - len = data.iExact.length, - txt = data.iFilter.slice( 1 ), - pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - for ( indx = 0; indx < len; indx++ ) { - if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { - patternIndx += 1; + tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; + range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; + range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || c.parsers[index].type === 'numeric' ) { + result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); + range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; + result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); + range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; } + if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { + result = data.cache; + } else { + tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + result = ts.formatFloat( tmp, table ); + } + if ( range1 > range2 ) { + tmp = range1; range1 = range2; range2 = tmp; // swap + } + return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); } - if ( patternIndx === pattern.length ) { - return true; + return null; + }, + // Look for wild card: ? = single, * = multiple, or | = logical OR + wild : function( c, data ) { + if ( /[\?\*\|]/.test( data.iFilter ) ) { + var index = data.index, + parsed = data.parsed[ index ], + query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); + // look for an exact match with the 'or' unless the 'filter-match' class is found + if ( !/\?\*/.test( query ) && data.nestedFilters ) { + query = data.isMatch ? query : '^(' + query + ')$'; + } + // parsing the filter may not work properly when using wildcards =/ + return new RegExp( + query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), + c.widgetOptions.filter_ignoreCase ? 'i' : '' + ) + .test( data.exact ); + } + return null; + }, + // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) + fuzzy: function( c, data ) { + if ( /^~/.test( data.iFilter ) ) { + var indx, + patternIndx = 0, + len = data.iExact.length, + txt = data.iFilter.slice( 1 ), + pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + for ( indx = 0; indx < len; indx++ ) { + if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { + patternIndx += 1; + } + } + if ( patternIndx === pattern.length ) { + return true; + } + return false; + } + return null; + } + }, + init: function( table, c, wo ) { + // filter language options + ts.language = $.extend( true, {}, { + to : 'to', + or : 'or', + and : 'and' + }, ts.language ); + + var options, string, txt, $header, column, filters, val, fxn, noSelect, + regex = ts.filter.regex; + c.$table.addClass( 'hasFilters' ); + + // define timers so using clearTimeout won't cause an undefined error + wo.searchTimer = null; + wo.filter_initTimer = null; + wo.filter_formatterCount = 0; + wo.filter_formatterInit = []; + wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; + wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; + + val = '\\{' + ts.filter.regex.query + '\\}'; + $.extend( regex, { + child : new RegExp( c.cssChildRow ), + filtered : new RegExp( wo.filter_filteredRow ), + alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), + toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), + toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ), + andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), + andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), + orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), + iQuery : new RegExp( val, 'i' ), + igQuery : new RegExp( val, 'ig' ) + }); + + // don't build filter row if columnFilters is false or all columns are set to 'filter-false' + // see issue #156 + val = c.$headers.filter( '.filter-false, .parser-false' ).length; + if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { + // build filter row + ts.filter.buildRow( table, c, wo ); + } + + txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + c.$table.bind( txt, function( event, filter ) { + val = wo.filter_hideEmpty && + $.isEmptyObject( c.cache ) && + !( c.delayInit && event.type === 'appendCache' ); + // hide filter row using the 'filtered' class name + c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 + if ( !/(search|filter)/.test( event.type ) ) { + event.stopPropagation(); + ts.filter.buildDefault( table, true ); + } + if ( event.type === 'filterReset' ) { + c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); + ts.filter.searching( table, [] ); + } else if ( event.type === 'filterEnd' ) { + ts.filter.buildDefault( table, true ); + } else { + // send false argument to force a new search; otherwise if the filter hasn't changed, + // it will return + filter = event.type === 'search' ? filter : + event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; + if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { + // force a new search since content has changed + c.lastCombinedFilter = null; + c.lastSearch = []; + } + // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first + // input ensures all inputs are updated when a search is triggered on the table + // $( 'table' ).trigger( 'search', [...] ); + ts.filter.searching( table, filter, true ); } return false; - } - return null; - } - }, - init: function( table, c, wo ) { - // filter language options - ts.language = $.extend( true, {}, { - to : 'to', - or : 'or', - and : 'and' - }, ts.language ); + }); - var options, string, txt, $header, column, filters, val, fxn, noSelect, - regex = ts.filter.regex; - c.$table.addClass( 'hasFilters' ); - - // define timers so using clearTimeout won't cause an undefined error - wo.searchTimer = null; - wo.filter_initTimer = null; - wo.filter_formatterCount = 0; - wo.filter_formatterInit = []; - wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; - wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; - - val = '\\{' + ts.filter.regex.query + '\\}'; - $.extend( regex, { - child : new RegExp( c.cssChildRow ), - filtered : new RegExp( wo.filter_filteredRow ), - alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), - toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), - toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ), - andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), - andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), - orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), - iQuery : new RegExp( val, 'i' ), - igQuery : new RegExp( val, 'ig' ) - }); - - // don't build filter row if columnFilters is false or all columns are set to 'filter-false' - // see issue #156 - val = c.$headers.filter( '.filter-false, .parser-false' ).length; - if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { - // build filter row - ts.filter.buildRow( table, c, wo ); - } - - txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - c.$table.bind( txt, function( event, filter ) { - val = wo.filter_hideEmpty && - $.isEmptyObject( c.cache ) && - !( c.delayInit && event.type === 'appendCache' ); - // hide filter row using the 'filtered' class name - c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 - if ( !/(search|filter)/.test( event.type ) ) { - event.stopPropagation(); - ts.filter.buildDefault( table, true ); - } - if ( event.type === 'filterReset' ) { - c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); - ts.filter.searching( table, [] ); - } else if ( event.type === 'filterEnd' ) { - ts.filter.buildDefault( table, true ); - } else { - // send false argument to force a new search; otherwise if the filter hasn't changed, - // it will return - filter = event.type === 'search' ? filter : - event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; - if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { - // force a new search since content has changed - c.lastCombinedFilter = null; - c.lastSearch = []; - } - // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first - // input ensures all inputs are updated when a search is triggered on the table - // $( 'table' ).trigger( 'search', [...] ); - ts.filter.searching( table, filter, true ); - } - return false; - }); - - // reset button/link - if ( wo.filter_reset ) { - if ( wo.filter_reset instanceof $ ) { - // reset contains a jQuery object, bind to it - wo.filter_reset.click( function() { - c.$table.trigger( 'filterReset' ); - }); - } else if ( $( wo.filter_reset ).length ) { - // reset is a jQuery selector, use event delegation - $( document ) - .undelegate( wo.filter_reset, 'click.tsfilter' ) - .delegate( wo.filter_reset, 'click.tsfilter', function() { - // trigger a reset event, so other functions ( filter_formatter ) know when to reset + // reset button/link + if ( wo.filter_reset ) { + if ( wo.filter_reset instanceof $ ) { + // reset contains a jQuery object, bind to it + wo.filter_reset.click( function() { c.$table.trigger( 'filterReset' ); }); + } else if ( $( wo.filter_reset ).length ) { + // reset is a jQuery selector, use event delegation + $( document ) + .undelegate( wo.filter_reset, 'click.tsfilter' ) + .delegate( wo.filter_reset, 'click.tsfilter', function() { + // trigger a reset event, so other functions ( filter_formatter ) know when to reset + c.$table.trigger( 'filterReset' ); + }); + } } - } - if ( wo.filter_functions ) { - for ( column = 0; column < c.columns; column++ ) { - fxn = ts.getColumnData( table, wo.filter_functions, column ); - if ( fxn ) { - // remove 'filter-select' from header otherwise the options added here are replaced with - // all options - $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); - // don't build select if 'filter-false' or 'parser-false' set - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - options = ''; - if ( fxn === true && noSelect ) { - ts.filter.buildSelect( table, column ); - } else if ( typeof fxn === 'object' && noSelect ) { - // add custom drop down list - for ( string in fxn ) { - if ( typeof string === 'string' ) { - options += options === '' ? - '' : ''; - val = string; - txt = string; - if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - val = string.split( wo.filter_selectSourceSeparator ); - txt = val[1]; - val = val[0]; + if ( wo.filter_functions ) { + for ( column = 0; column < c.columns; column++ ) { + fxn = ts.getColumnData( table, wo.filter_functions, column ); + if ( fxn ) { + // remove 'filter-select' from header otherwise the options added here are replaced with + // all options + $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); + // don't build select if 'filter-false' or 'parser-false' set + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + options = ''; + if ( fxn === true && noSelect ) { + ts.filter.buildSelect( table, column ); + } else if ( typeof fxn === 'object' && noSelect ) { + // add custom drop down list + for ( string in fxn ) { + if ( typeof string === 'string' ) { + options += options === '' ? + '' : ''; + val = string; + txt = string; + if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + val = string.split( wo.filter_selectSourceSeparator ); + txt = val[1]; + val = val[0]; + } + options += ''; } - options += ''; + } + c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .append( options ); + txt = wo.filter_selectSource; + fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); + if ( fxn ) { + // updating so the extra options are appended + ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); } } + } + } + } + // not really updating, but if the column has both the 'filter-select' class & + // filter_functions set to true, it would append the same options twice. + ts.filter.buildDefault( table, true ); + + ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); + if ( wo.filter_external ) { + ts.filter.bindSearch( table, wo.filter_external ); + } + + if ( wo.filter_hideFilters ) { + ts.filter.hideFilters( table, c ); + } + + // show processing icon + if ( c.showProcessing ) { + txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function( event, columns ) { + // only add processing to certain columns to all columns + $header = ( columns ) ? c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .append( options ); - txt = wo.filter_selectSource; - fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); - if ( fxn ) { - // updating so the extra options are appended - ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } - } - } - // not really updating, but if the column has both the 'filter-select' class & - // filter_functions set to true, it would append the same options twice. - ts.filter.buildDefault( table, true ); - - ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); - if ( wo.filter_external ) { - ts.filter.bindSearch( table, wo.filter_external ); - } - - if ( wo.filter_hideFilters ) { - ts.filter.hideFilters( table, c ); - } - - // show processing icon - if ( c.showProcessing ) { - txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function( event, columns ) { - // only add processing to certain columns to all columns - $header = ( columns ) ? - c.$table - .find( '.' + tscss.header ) - .filter( '[data-column]' ) - .filter( function() { - return columns[ $( this ).data( 'column' ) ] !== ''; - }) : ''; - ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); - }); - } - - // set filtered rows count ( intially unfiltered ) - c.filteredRows = c.totalRows; - - // add default values - txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function() { - // redefine 'wo' as it does not update properly inside this callback - var wo = this.config.widgetOptions; - filters = ts.filter.setDefaults( table, c, wo ) || []; - if ( filters.length ) { - // prevent delayInit from triggering a cache build if filters are empty - if ( !( c.delayInit && filters.join( '' ) === '' ) ) { - ts.setFilters( table, filters, true ); - } - } - c.$table.trigger( 'filterFomatterUpdate' ); - // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers - setTimeout( function() { - if ( !wo.filter_initialized ) { - ts.filter.filterInitComplete( c ); - } - }, 100 ); - }); - // if filter widget is added after pager has initialized; then set filter init flag - if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { - c.$table.trigger( 'filterFomatterUpdate' ); - setTimeout( function() { - ts.filter.filterInitComplete( c ); - }, 100 ); - } - }, - // $cell parameter, but not the config, is passed to the filter_formatters, - // so we have to work with it instead - formatterUpdated: function( $cell, column ) { - var wo = $cell.closest( 'table' )[0].config.widgetOptions; - if ( !wo.filter_initialized ) { - // add updates by column since this function - // may be called numerous times before initialization - wo.filter_formatterInit[ column ] = 1; - } - }, - filterInitComplete: function( c ) { - var indx, len, - wo = c.widgetOptions, - count = 0, - completed = function() { - wo.filter_initialized = true; - c.$table.trigger( 'filterInit', c ); - ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); - }; - if ( $.isEmptyObject( wo.filter_formatter ) ) { - completed(); - } else { - len = wo.filter_formatterInit.length; - for ( indx = 0; indx < len; indx++ ) { - if ( wo.filter_formatterInit[ indx ] === 1 ) { - count++; - } - } - clearTimeout( wo.filter_initTimer ); - if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { - // filter widget initialized - completed(); - } else if ( !wo.filter_initialized ) { - // fall back in case a filter_formatter doesn't call - // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off - wo.filter_initTimer = setTimeout( function() { - completed(); - }, 500 ); - } - } - }, - setDefaults: function( table, c, wo ) { - var isArray, saved, indx, col, $filters, - // get current ( default ) filters - filters = ts.getFilters( table ) || []; - if ( wo.filter_saveFilters && ts.storage ) { - saved = ts.storage( table, 'tablesorter-filters' ) || []; - isArray = $.isArray( saved ); - // make sure we're not just getting an empty array - if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { - filters = saved; - } - } - // if no filters saved, then check default settings - if ( filters.join( '' ) === '' ) { - // allow adding default setting to external filters - $filters = c.$headers.add( wo.filter_$externalFilters ) - .filter( '[' + wo.filter_defaultAttrib + ']' ); - for ( indx = 0; indx <= c.columns; indx++ ) { - // include data-column='all' external filters - col = indx === c.columns ? 'all' : indx; - filters[indx] = $filters - .filter( '[data-column="' + col + '"]' ) - .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; - } - } - c.$table.data( 'lastSearch', filters ); - return filters; - }, - parseFilter: function( c, filter, column, parsed ) { - return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; - }, - buildRow: function( table, c, wo ) { - var col, column, $header, buildSelect, disabled, name, ffxn, tmp, - // c.columns defined in computeThIndexes() - cellFilter = wo.filter_cellFilter, - columns = c.columns, - arry = $.isArray( cellFilter ), - buildFilter = ''; - for ( column = 0; column < columns; column++ ) { - buildFilter += '' ).appendTo( c.$filters.eq( column ) ); - } else { - ffxn = ts.getColumnData( table, wo.filter_formatter, column ); - if ( ffxn ) { - wo.filter_formatterCount++; - buildFilter = ffxn( c.$filters.eq( column ), column ); - // no element returned, so lets go find it - if ( buildFilter && buildFilter.length === 0 ) { - buildFilter = c.$filters.eq( column ).children( 'input' ); - } - // element not in DOM, so lets attach it - if ( buildFilter && ( buildFilter.parent().length === 0 || - ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { - c.$filters.eq( column ).append( buildFilter ); - } - } else { - buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); - } - if ( buildFilter ) { - tmp = $header.data( 'placeholder' ) || - $header.attr( 'data-placeholder' ) || - wo.filter_placeholder.search || ''; - buildFilter.attr( 'placeholder', tmp ); - } - } - if ( buildFilter ) { - // add filter class name - name = ( $.isArray( wo.filter_cssFilter ) ? - ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : - wo.filter_cssFilter ) || ''; - buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); - if ( disabled ) { - buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; - } - } - } - }, - bindSearch: function( table, $el, internal ) { - table = $( table )[0]; - $el = $( $el ); // allow passing a selector string - if ( !$el.length ) { return; } - var tmp, - c = table.config, - wo = c.widgetOptions, - namespace = c.namespace + 'filter', - $ext = wo.filter_$externalFilters; - if ( internal !== true ) { - // save anyMatch element - tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; - wo.filter_$anyMatch = $el.filter( tmp ); - if ( $ext && $ext.length ) { - wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); - } else { - wo.filter_$externalFilters = $el; - } - // update values ( external filters added after table initialization ) - ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); - } - // unbind events - tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); - $el - // use data attribute instead of jQuery data since the head is cloned without including - // the data/binding - .attr( 'data-lastSearchTime', new Date().getTime() ) - .unbind( tmp.replace( /\s+/g, ' ' ) ) - // include change for select - fixes #473 - .bind( 'keyup' + namespace, function( event ) { - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - // emulate what webkit does.... escape clears the filter - if ( event.which === 27 ) { - this.value = ''; - // live search - } else if ( wo.filter_liveSearch === false ) { - return; - // don't return if the search value is empty ( all rows need to be revealed ) - } else if ( this.value !== '' && ( - // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace - ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || - // let return & backspace continue on, but ignore arrows & non-valid characters - ( event.which !== 13 && event.which !== 8 && - ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { - return; - } - // change event = no delay; last true flag tells getFilters to skip newest timed input - ts.filter.searching( table, true, true ); - }) - .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { - var column = $( this ).data( 'column' ); - // don't allow 'change' event to process if the input value is the same - fixes #685 - if ( event.which === 13 || event.type === 'search' || - event.type === 'change' && this.value !== c.lastSearch[column] ) { - event.preventDefault(); - // init search with no delay - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - ts.filter.searching( table, false, true ); - } - }); - }, - searching: function( table, filter, skipFirst ) { - var wo = table.config.widgetOptions; - clearTimeout( wo.searchTimer ); - if ( typeof filter === 'undefined' || filter === true ) { - // delay filtering - wo.searchTimer = setTimeout( function() { - ts.filter.checkFilters( table, filter, skipFirst ); - }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); - } else { - // skip delay - ts.filter.checkFilters( table, filter, skipFirst ); - } - }, - checkFilters: function( table, filter, skipFirst ) { - var c = table.config, - wo = c.widgetOptions, - filterArray = $.isArray( filter ), - filters = ( filterArray ) ? filter : ts.getFilters( table, true ), - combinedFilters = ( filters || [] ).join( '' ); // combined filter values - // prevent errors if delay init is set - if ( $.isEmptyObject( c.cache ) ) { - // update cache if delayInit set & pager has initialized ( after user initiates a search ) - if ( c.delayInit && c.pager && c.pager.initialized ) { - c.$table.trigger( 'updateCache', [ function() { - ts.filter.checkFilters( table, false, skipFirst ); - } ] ); - } - return; - } - // add filter array back into inputs - if ( filterArray ) { - ts.setFilters( table, filters, false, skipFirst !== true ); - if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } - } - if ( wo.filter_hideFilters ) { - // show/hide filter row as needed - c.$table - .find( '.' + tscss.filterRow ) - .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); - } - // return if the last search is the same; but filter === false when updating the search - // see example-widget-filter.html filter toggle buttons - if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { - return; - } else if ( filter === false ) { - // force filter refresh - c.lastCombinedFilter = null; - c.lastSearch = []; - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterStart', [filters] ); - } - if ( c.showProcessing ) { - // give it time for the processing icon to kick in - setTimeout( function() { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - }, 30 ); - } else { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - } - }, - hideFilters: function( table, c ) { - var timer; - c.$table - .find( '.' + tscss.filterRow ) - .bind( 'mouseenter mouseleave', function( e ) { - // save event object - http://bugs.jquery.com/ticket/12140 - var event = e, - $filterRow = $( this ); - clearTimeout( timer ); - timer = setTimeout( function() { - if ( /enter|over/.test( event.type ) ) { - $filterRow.removeClass( tscss.filterRowHide ); - } else { - // don't hide if input has focus - // $( ':focus' ) needs jQuery 1.6+ - if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { - // don't hide row if any filter has a value - if ( c.lastCombinedFilter === '' ) { - $filterRow.addClass( tscss.filterRowHide ); - } - } - } - }, 200 ); - }) - .find( 'input, select' ).bind( 'focus blur', function( e ) { - var event = e, - $row = $( this ).closest( 'tr' ); - clearTimeout( timer ); - timer = setTimeout( function() { - clearTimeout( timer ); - // don't hide row if any filter has a value - if ( ts.getFilters( c.$table ).join( '' ) === '' ) { - $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); - } - }, 200 ); - }); - }, - defaultFilter: function( filter, mask ) { - if ( filter === '' ) { return filter; } - var regex = ts.filter.regex.iQuery, - maskLen = mask.match( ts.filter.regex.igQuery ).length, - query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], - len = query.length - 1, - indx = 0, - val = mask; - if ( len < 1 && maskLen > 1 ) { - // only one 'word' in query but mask has >1 slots - query[1] = query[0]; - } - // replace all {query} with query words... - // if query = 'Bob', then convert mask from '!{query}' to '!Bob' - // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' - while ( regex.test( val ) ) { - val = val.replace( regex, query[indx++] || '' ); - if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { - val = mask.replace( regex, val ); - } - } - return val; - }, - getLatestSearch: function( $input ) { - if ( $input ) { - return $input.sort( function( a, b ) { - return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); - }); - } - return $input || $(); - }, - multipleColumns: function( c, $input ) { - // look for multiple columns '1-3,4-6,8' in data-column - var temp, ranges, range, start, end, singles, i, indx, len, - wo = c.widgetOptions, - // only target 'all' column inputs on initialization - // & don't target 'all' column inputs if they don't exist - targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, - columns = [], - val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); - // process column range - if ( targets && /-/.test( val ) ) { - ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); - len = ranges.length; - for ( indx = 0; indx < len; indx++ ) { - range = ranges[indx].split( /\s*-\s*/ ); - start = parseInt( range[0], 10 ) || 0; - end = parseInt( range[1], 10 ) || ( c.columns - 1 ); - if ( start > end ) { - temp = start; start = end; end = temp; // swap - } - if ( end >= c.columns ) { - end = c.columns - 1; - } - for ( ; start <= end; start++ ) { - columns.push( start ); - } - // remove processed range from val - val = val.replace( ranges[ indx ], '' ); - } - } - // process single columns - if ( targets && /,/.test( val ) ) { - singles = val.split( /\s*,\s*/ ); - len = singles.length; - for ( i = 0; i < len; i++ ) { - if ( singles[ i ] !== '' ) { - indx = parseInt( singles[ i ], 10 ); - if ( indx < c.columns ) { - columns.push( indx ); - } - } - } - } - // return all columns - if ( !columns.length ) { - for ( indx = 0; indx < c.columns; indx++ ) { - columns.push( indx ); - } - } - return columns; - }, - processTypes: function( c, data, vars ) { - var ffxn, - filterMatched = null, - matches = null; - for ( ffxn in ts.filter.types ) { - if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { - matches = ts.filter.types[ffxn]( c, data, vars ); - if ( matches !== null ) { - filterMatched = matches; - } - } - } - return filterMatched; - }, - processRow: function( c, data, vars ) { - var columnIndex, hasSelect, result, val, filterMatched, - fxn, ffxn, txt, - regex = ts.filter.regex, - wo = c.widgetOptions, - showRow = true; - data.$cells = data.$row.children(); - - if ( data.anyMatchFlag ) { - // look for multiple columns '1-3,4-6,8' - columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); - data.anyMatch = true; - data.isMatch = true; - data.rowArray = data.$cells.map( function( i ) { - if ( $.inArray( i, columnIndex ) > -1 ) { - if ( data.parsed[ i ] ) { - txt = data.cacheArray[ i ]; - } else { - txt = data.rawArray[ i ]; - txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); - if ( c.sortLocaleCompare ) { - txt = ts.replaceAccents( txt ); - } - } - return txt; - } - }).get(); - data.filter = data.anyMatchFilter; - data.iFilter = data.iAnyMatchFilter; - data.exact = data.rowArray.join( ' ' ); - data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; - data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); - - vars.excludeMatch = vars.noAnyMatch; - filterMatched = ts.filter.processTypes( c, data, vars ); - - if ( filterMatched !== null ) { - showRow = filterMatched; - } else { - if ( wo.filter_startsWith ) { - showRow = false; - columnIndex = c.columns; - while ( !showRow && columnIndex > 0 ) { - columnIndex--; - showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; - } - } else { - showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; - } - } - data.anyMatch = false; - // no other filters to process - if ( data.filters.join( '' ) === data.filter ) { - return showRow; - } - } - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - data.filter = data.filters[ columnIndex ]; - data.index = columnIndex; - - // filter types to exclude, per column - vars.excludeMatch = vars.excludeFilter[ columnIndex ]; - - // ignore if filter is empty or disabled - if ( data.filter ) { - data.cache = data.cacheArray[ columnIndex ]; - // check if column data should be from the cell or from parsed data - if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { - data.exact = data.cache; - } else { - result = data.rawArray[ columnIndex ] || ''; - data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 - } - data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? - data.exact.toLowerCase() : data.exact; - - data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); - - result = showRow; // if showRow is true, show that row - - // in case select filter option has a different value vs text 'a - z|A through Z' - ffxn = wo.filter_columnFilters ? - c.$filters.add( c.$externalFilters ) - .filter( '[data-column="'+ columnIndex + '"]' ) - .find( 'select option:selected' ) - .attr( 'data-function-name' ) || '' : ''; - // replace accents - see #357 - if ( c.sortLocaleCompare ) { - data.filter = ts.replaceAccents( data.filter ); - } - - val = true; - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { - data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); - // val is used to indicate that a filter select is using a default filter; - // so we override the exact & partial matches - val = false; - } - // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), - // data.filter = case sensitive - data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; - fxn = vars.functions[ columnIndex ]; - hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); - filterMatched = null; - if ( fxn || ( hasSelect && val ) ) { - if ( fxn === true || hasSelect ) { - // default selector uses exact match unless 'filter-match' class is found - filterMatched = data.isMatch ? - data.iExact.search( data.iFilter ) >= 0 : - data.filter === data.exact; - } else if ( typeof fxn === 'function' ) { - // filter callback( exact cell content, parser normalized content, - // filter input value, column index, jQuery row object ) - filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { - // selector option function - txt = ffxn || data.filter; - filterMatched = - fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } - } - if ( filterMatched === null ) { - // cycle through the different filters - // filters return a boolean or null if nothing matches - filterMatched = ts.filter.processTypes( c, data, vars ); - if ( filterMatched !== null ) { - result = filterMatched; - // Look for match, and add child row data for matching - } else { - txt = ( data.iExact + data.childRowText ) - .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); - result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); - } - } else { - result = filterMatched; - } - showRow = ( result ) ? showRow : false; - } - } - return showRow; - }, - findRows: function( table, filters, combinedFilters ) { - if ( table.config.lastCombinedFilter === combinedFilters || - !table.config.widgetOptions.filter_initialized ) { - return; - } - var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, - isChild, childRow, lastSearch, showRow, time, val, indx, - notFiltered, searchFiltered, query, injected, res, id, txt, - storedFilters = $.extend( [], filters ), - regex = ts.filter.regex, - c = table.config, - wo = c.widgetOptions, - // data object passed to filters; anyMatch is a flag for the filters - data = { - anyMatch: false, - filters: filters, - // regex filter type cache - filter_regexCache : [] - }, - vars = { - // anyMatch really screws up with these types of filters - noAnyMatch: [ 'range', 'notMatch', 'operators' ], - // cache filter variables that use ts.getColumnData in the main loop - functions : [], - excludeFilter : [], - defaultColFilter : [], - defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' - }; - - // parse columns after formatter, in case the class is added at that point - data.parsed = c.$headers.map( function( columnIndex ) { - return c.parsers && c.parsers[ columnIndex ] && - // force parsing if parser type is numeric - c.parsers[ columnIndex ].parsed || - // getData won't return 'parsed' if other 'filter-' class names exist - // ( e.g. ) - ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], - ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || - $( this ).hasClass( 'filter-parsed' ); - }).get(); - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - vars.functions[ columnIndex ] = - ts.getColumnData( table, wo.filter_functions, columnIndex ); - vars.defaultColFilter[ columnIndex ] = - ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; - vars.excludeFilter[ columnIndex ] = - ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); - } - - if ( c.debug ) { - ts.log( 'Filter: Starting filter widget search', filters ); - time = new Date(); - } - // filtered rows count - c.filteredRows = 0; - c.totalRows = 0; - // combindedFilters are undefined on init - combinedFilters = ( storedFilters || [] ).join( '' ); - - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); - // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! - // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); - columnIndex = c.columns; - // convert stored rows into a jQuery object - norm_rows = c.cache[ tbodyIndex ].normalized; - $rows = $( $.map( norm_rows, function( el ) { - return el[ columnIndex ].$row.get(); - }) ); - - if ( combinedFilters === '' || wo.filter_serversideFiltering ) { - $rows - .removeClass( wo.filter_filteredRow ) - .not( '.' + c.cssChildRow ) - .css( 'display', '' ); - } else { - // filter out child rows - $rows = $rows.not( '.' + c.cssChildRow ); - len = $rows.length; - - if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || - typeof filters[c.columns] !== 'undefined' ) { - data.anyMatchFlag = true; - data.anyMatchFilter = '' + ( - filters[ c.columns ] || - wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || - '' - ); - if ( wo.filter_columnAnyMatch ) { - // specific columns search - query = data.anyMatchFilter.split( regex.andSplit ); - injected = false; - for ( indx = 0; indx < query.length; indx++ ) { - res = query[ indx ].split( ':' ); - if ( res.length > 1 ) { - // make the column a one-based index ( non-developers start counting from one :P ) - id = parseInt( res[0], 10 ) - 1; - if ( id >= 0 && id < c.columns ) { // if id is an integer - filters[ id ] = res[1]; - query.splice( indx, 1 ); - indx--; - injected = true; - } - } - } - if ( injected ) { - data.anyMatchFilter = query.join( ' && ' ); - } - } - } - - // optimize searching only through already filtered rows - see #313 - searchFiltered = wo.filter_searchFiltered; - lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; - if ( searchFiltered ) { - // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 - for ( indx = 0; indx < columnIndex + 1; indx++ ) { - val = filters[indx] || ''; - // break out of loop if we've already determined not to search filtered rows - if ( !searchFiltered ) { indx = columnIndex; } - // search already filtered rows if... - searchFiltered = searchFiltered && lastSearch.length && - // there are no changes from beginning of filter - val.indexOf( lastSearch[indx] || '' ) === 0 && - // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string - !regex.alreadyFiltered.test( val ) && - // if we are not doing exact matches, using '|' ( logical or ) or not '!' - !/[=\"\|!]/.test( val ) && - // don't search only filtered if the value is negative - // ( '> -10' => '> -100' will ignore hidden rows ) - !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && - // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 - !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && - !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); - } - } - notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; - // can't search when all rows are hidden - this happens when looking for exact matches - if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } - if ( c.debug ) { - ts.log( 'Filter: Searching through ' + - ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); - } - if ( data.anyMatchFlag ) { - if ( c.sortLocaleCompare ) { - // replace accents - data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); - } - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { - data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); - // clear search filtered flag because default filters are not saved to the last search - searchFiltered = false; - } - // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true - // when c.ignoreCase is true, the cache contains all lower case data - data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? - data.anyMatchFilter : - data.anyMatchFilter.toLowerCase(); - } - - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - - txt = $rows[ rowIndex ].className; - // the first row can never be a child row - isChild = rowIndex && regex.child.test( txt ); - // skip child rows & already filtered rows - if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { - continue; - } - - data.$row = $rows.eq( rowIndex ); - data.cacheArray = norm_rows[ rowIndex ]; - rowData = data.cacheArray[ c.columns ]; - data.rawArray = rowData.raw; - data.childRowText = ''; - - if ( !wo.filter_childByColumn ) { - txt = ''; - // child row cached text - childRow = rowData.child; - // so, if 'table.config.widgetOptions.filter_childRows' is true and there is - // a match anywhere in the child row, then it will make the row visible - // checked here so the option can be changed dynamically - for ( indx = 0; indx < childRow.length; indx++ ) { - txt += ' ' + childRow[indx].join( '' ) || ''; - } - data.childRowText = wo.filter_childRows ? - ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : - ''; - } - - showRow = ts.filter.processRow( c, data, vars ); - childRow = rowData.$row.filter( ':gt( 0 )' ); - - if ( wo.filter_childRows && childRow.length ) { - if ( wo.filter_childByColumn ) { - // cycle through each child row - for ( indx = 0; indx < childRow.length; indx++ ) { - data.$row = childRow.eq( indx ); - data.cacheArray = rowData.child[ indx ]; - data.rawArray = data.cacheArray; - // use OR comparison on child rows - showRow = showRow || ts.filter.processRow( c, data, vars ); - } - } - childRow.toggleClass( wo.filter_filteredRow, !showRow ); - } - - rowData.$row - .toggleClass( wo.filter_filteredRow, !showRow )[0] - .display = showRow ? '' : 'none'; - } - } - c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; - c.totalRows += $rows.length; - ts.processTbody( table, $tbody, false ); - } - c.lastCombinedFilter = combinedFilters; // save last search - // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) - c.lastSearch = storedFilters; - c.$table.data( 'lastSearch', storedFilters ); - if ( wo.filter_saveFilters && ts.storage ) { - ts.storage( table, 'tablesorter-filters', storedFilters ); - } - if ( c.debug ) { - ts.benchmark( 'Completed filter widget search', time ); - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterEnd', c ); - } - setTimeout( function() { - c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied - }, 0 ); - }, - getOptionSource: function( table, column, onlyAvail ) { - table = $( table )[0]; - var cts, txt, indx, len, - c = table.config, - wo = c.widgetOptions, - parsed = [], - arry = false, - source = wo.filter_selectSource, - last = c.$table.data( 'lastSearch' ) || [], - fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); - - if ( onlyAvail && last[column] !== '' ) { - onlyAvail = false; - } - - // filter select source option - if ( fxn === true ) { - // OVERALL source - arry = source( table, column, onlyAvail ); - } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { - // selectSource is a jQuery object or string of options - return fxn; - } else if ( $.isArray( fxn ) ) { - arry = fxn; - } else if ( $.type( source ) === 'object' && fxn ) { - // custom select source function for a SPECIFIC COLUMN - arry = fxn( table, column, onlyAvail ); - } - if ( arry === false ) { - // fall back to original method - arry = ts.filter.getOptions( table, column, onlyAvail ); - } - - // get unique elements and sort the list - // if $.tablesorter.sortText exists ( not in the original tablesorter ), - // then natural sort the list otherwise use a basic sort - arry = $.grep( arry, function( value, indx ) { - return $.inArray( value, arry ) === indx; - }); - - if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { - // unsorted select options - return arry; - } else { - len = arry.length; - // parse select option values - for ( indx = 0; indx < len; indx++ ) { - txt = arry[ indx ]; - // parse array data using set column parser; this DOES NOT pass the original - // table cell to the parser format function - parsed.push({ - t : txt, - // check parser length - fixes #934 - p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + .find( '.' + tscss.header ) + .filter( '[data-column]' ) + .filter( function() { + return columns[ $( this ).data( 'column' ) ] !== ''; + }) : ''; + ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); }); } - // sort parsed select options - cts = c.textSorter || ''; - parsed.sort( function( a, b ) { - // sortNatural breaks if you don't pass it strings - var x = a.p.toString(), - y = b.p.toString(); - if ( $.isFunction( cts ) ) { - // custom OVERALL text sorter - return cts( x, y, true, column, table ); - } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) { - // custom text sorter for a SPECIFIC COLUMN - return cts[column]( x, y, true, column, table ); - } else if ( ts.sortNatural ) { - // fall back to natural sort - return ts.sortNatural( x, y ); + // set filtered rows count ( intially unfiltered ) + c.filteredRows = c.totalRows; + + // add default values + txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function() { + // redefine 'wo' as it does not update properly inside this callback + var wo = this.config.widgetOptions; + filters = ts.filter.setDefaults( table, c, wo ) || []; + if ( filters.length ) { + // prevent delayInit from triggering a cache build if filters are empty + if ( !( c.delayInit && filters.join( '' ) === '' ) ) { + ts.setFilters( table, filters, true ); + } } - // using an older version! do a basic sort - return true; + c.$table.trigger( 'filterFomatterUpdate' ); + // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers + setTimeout( function() { + if ( !wo.filter_initialized ) { + ts.filter.filterInitComplete( c ); + } + }, 100 ); }); - // rebuild arry from sorted parsed data - arry = []; - len = parsed.length; - for ( indx = 0; indx < len; indx++ ) { - arry.push( parsed[indx].t ); + // if filter widget is added after pager has initialized; then set filter init flag + if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { + c.$table.trigger( 'filterFomatterUpdate' ); + setTimeout( function() { + ts.filter.filterInitComplete( c ); + }, 100 ); + } + }, + // $cell parameter, but not the config, is passed to the filter_formatters, + // so we have to work with it instead + formatterUpdated: function( $cell, column ) { + var wo = $cell.closest( 'table' )[0].config.widgetOptions; + if ( !wo.filter_initialized ) { + // add updates by column since this function + // may be called numerous times before initialization + wo.filter_formatterInit[ column ] = 1; + } + }, + filterInitComplete: function( c ) { + var indx, len, + wo = c.widgetOptions, + count = 0, + completed = function() { + wo.filter_initialized = true; + c.$table.trigger( 'filterInit', c ); + ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); + }; + if ( $.isEmptyObject( wo.filter_formatter ) ) { + completed(); + } else { + len = wo.filter_formatterInit.length; + for ( indx = 0; indx < len; indx++ ) { + if ( wo.filter_formatterInit[ indx ] === 1 ) { + count++; + } + } + clearTimeout( wo.filter_initTimer ); + if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { + // filter widget initialized + completed(); + } else if ( !wo.filter_initialized ) { + // fall back in case a filter_formatter doesn't call + // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off + wo.filter_initTimer = setTimeout( function() { + completed(); + }, 500 ); + } + } + }, + setDefaults: function( table, c, wo ) { + var isArray, saved, indx, col, $filters, + // get current ( default ) filters + filters = ts.getFilters( table ) || []; + if ( wo.filter_saveFilters && ts.storage ) { + saved = ts.storage( table, 'tablesorter-filters' ) || []; + isArray = $.isArray( saved ); + // make sure we're not just getting an empty array + if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { + filters = saved; + } + } + // if no filters saved, then check default settings + if ( filters.join( '' ) === '' ) { + // allow adding default setting to external filters + $filters = c.$headers.add( wo.filter_$externalFilters ) + .filter( '[' + wo.filter_defaultAttrib + ']' ); + for ( indx = 0; indx <= c.columns; indx++ ) { + // include data-column='all' external filters + col = indx === c.columns ? 'all' : indx; + filters[indx] = $filters + .filter( '[data-column="' + col + '"]' ) + .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; + } + } + c.$table.data( 'lastSearch', filters ); + return filters; + }, + parseFilter: function( c, filter, column, parsed ) { + return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; + }, + buildRow: function( table, c, wo ) { + var col, column, $header, buildSelect, disabled, name, ffxn, tmp, + // c.columns defined in computeThIndexes() + cellFilter = wo.filter_cellFilter, + columns = c.columns, + arry = $.isArray( cellFilter ), + buildFilter = ''; + for ( column = 0; column < columns; column++ ) { + buildFilter += '' ).appendTo( c.$filters.eq( column ) ); + } else { + ffxn = ts.getColumnData( table, wo.filter_formatter, column ); + if ( ffxn ) { + wo.filter_formatterCount++; + buildFilter = ffxn( c.$filters.eq( column ), column ); + // no element returned, so lets go find it + if ( buildFilter && buildFilter.length === 0 ) { + buildFilter = c.$filters.eq( column ).children( 'input' ); + } + // element not in DOM, so lets attach it + if ( buildFilter && ( buildFilter.parent().length === 0 || + ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { + c.$filters.eq( column ).append( buildFilter ); + } + } else { + buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); + } + if ( buildFilter ) { + tmp = $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.search || ''; + buildFilter.attr( 'placeholder', tmp ); + } + } + if ( buildFilter ) { + // add filter class name + name = ( $.isArray( wo.filter_cssFilter ) ? + ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : + wo.filter_cssFilter ) || ''; + buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); + if ( disabled ) { + buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; + } + } + } + }, + bindSearch: function( table, $el, internal ) { + table = $( table )[0]; + $el = $( $el ); // allow passing a selector string + if ( !$el.length ) { return; } + var tmp, + c = table.config, + wo = c.widgetOptions, + namespace = c.namespace + 'filter', + $ext = wo.filter_$externalFilters; + if ( internal !== true ) { + // save anyMatch element + tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; + wo.filter_$anyMatch = $el.filter( tmp ); + if ( $ext && $ext.length ) { + wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); + } else { + wo.filter_$externalFilters = $el; + } + // update values ( external filters added after table initialization ) + ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); + } + // unbind events + tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); + $el + // use data attribute instead of jQuery data since the head is cloned without including + // the data/binding + .attr( 'data-lastSearchTime', new Date().getTime() ) + .unbind( tmp.replace( /\s+/g, ' ' ) ) + // include change for select - fixes #473 + .bind( 'keyup' + namespace, function( event ) { + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + // emulate what webkit does.... escape clears the filter + if ( event.which === 27 ) { + this.value = ''; + // live search + } else if ( wo.filter_liveSearch === false ) { + return; + // don't return if the search value is empty ( all rows need to be revealed ) + } else if ( this.value !== '' && ( + // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace + ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || + // let return & backspace continue on, but ignore arrows & non-valid characters + ( event.which !== 13 && event.which !== 8 && + ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { + return; + } + // change event = no delay; last true flag tells getFilters to skip newest timed input + ts.filter.searching( table, true, true ); + }) + .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { + var column = $( this ).data( 'column' ); + // don't allow 'change' event to process if the input value is the same - fixes #685 + if ( event.which === 13 || event.type === 'search' || + event.type === 'change' && this.value !== c.lastSearch[column] ) { + event.preventDefault(); + // init search with no delay + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + ts.filter.searching( table, false, true ); + } + }); + }, + searching: function( table, filter, skipFirst ) { + var wo = table.config.widgetOptions; + clearTimeout( wo.searchTimer ); + if ( typeof filter === 'undefined' || filter === true ) { + // delay filtering + wo.searchTimer = setTimeout( function() { + ts.filter.checkFilters( table, filter, skipFirst ); + }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); + } else { + // skip delay + ts.filter.checkFilters( table, filter, skipFirst ); + } + }, + checkFilters: function( table, filter, skipFirst ) { + var c = table.config, + wo = c.widgetOptions, + filterArray = $.isArray( filter ), + filters = ( filterArray ) ? filter : ts.getFilters( table, true ), + combinedFilters = ( filters || [] ).join( '' ); // combined filter values + // prevent errors if delay init is set + if ( $.isEmptyObject( c.cache ) ) { + // update cache if delayInit set & pager has initialized ( after user initiates a search ) + if ( c.delayInit && c.pager && c.pager.initialized ) { + c.$table.trigger( 'updateCache', [ function() { + ts.filter.checkFilters( table, false, skipFirst ); + } ] ); + } + return; + } + // add filter array back into inputs + if ( filterArray ) { + ts.setFilters( table, filters, false, skipFirst !== true ); + if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } + } + if ( wo.filter_hideFilters ) { + // show/hide filter row as needed + c.$table + .find( '.' + tscss.filterRow ) + .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); + } + // return if the last search is the same; but filter === false when updating the search + // see example-widget-filter.html filter toggle buttons + if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { + return; + } else if ( filter === false ) { + // force filter refresh + c.lastCombinedFilter = null; + c.lastSearch = []; + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterStart', [ filters ] ); + } + if ( c.showProcessing ) { + // give it time for the processing icon to kick in + setTimeout( function() { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + }, 30 ); + } else { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + } + }, + hideFilters: function( table, c ) { + var timer; + c.$table + .find( '.' + tscss.filterRow ) + .bind( 'mouseenter mouseleave', function( e ) { + // save event object - http://bugs.jquery.com/ticket/12140 + var event = e, + $filterRow = $( this ); + clearTimeout( timer ); + timer = setTimeout( function() { + if ( /enter|over/.test( event.type ) ) { + $filterRow.removeClass( tscss.filterRowHide ); + } else { + // don't hide if input has focus + // $( ':focus' ) needs jQuery 1.6+ + if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { + // don't hide row if any filter has a value + if ( c.lastCombinedFilter === '' ) { + $filterRow.addClass( tscss.filterRowHide ); + } + } + } + }, 200 ); + }) + .find( 'input, select' ).bind( 'focus blur', function( e ) { + var event = e, + $row = $( this ).closest( 'tr' ); + clearTimeout( timer ); + timer = setTimeout( function() { + clearTimeout( timer ); + // don't hide row if any filter has a value + if ( ts.getFilters( c.$table ).join( '' ) === '' ) { + $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); + } + }, 200 ); + }); + }, + defaultFilter: function( filter, mask ) { + if ( filter === '' ) { return filter; } + var regex = ts.filter.regex.iQuery, + maskLen = mask.match( ts.filter.regex.igQuery ).length, + query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], + len = query.length - 1, + indx = 0, + val = mask; + if ( len < 1 && maskLen > 1 ) { + // only one 'word' in query but mask has >1 slots + query[1] = query[0]; + } + // replace all {query} with query words... + // if query = 'Bob', then convert mask from '!{query}' to '!Bob' + // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' + while ( regex.test( val ) ) { + val = val.replace( regex, query[indx++] || '' ); + if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { + val = mask.replace( regex, val ); + } + } + return val; + }, + getLatestSearch: function( $input ) { + if ( $input ) { + return $input.sort( function( a, b ) { + return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); + }); + } + return $input || $(); + }, + multipleColumns: function( c, $input ) { + // look for multiple columns '1-3,4-6,8' in data-column + var temp, ranges, range, start, end, singles, i, indx, len, + wo = c.widgetOptions, + // only target 'all' column inputs on initialization + // & don't target 'all' column inputs if they don't exist + targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, + columns = [], + val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); + // process column range + if ( targets && /-/.test( val ) ) { + ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); + len = ranges.length; + for ( indx = 0; indx < len; indx++ ) { + range = ranges[indx].split( /\s*-\s*/ ); + start = parseInt( range[0], 10 ) || 0; + end = parseInt( range[1], 10 ) || ( c.columns - 1 ); + if ( start > end ) { + temp = start; start = end; end = temp; // swap + } + if ( end >= c.columns ) { + end = c.columns - 1; + } + for ( ; start <= end; start++ ) { + columns.push( start ); + } + // remove processed range from val + val = val.replace( ranges[ indx ], '' ); + } + } + // process single columns + if ( targets && /,/.test( val ) ) { + singles = val.split( /\s*,\s*/ ); + len = singles.length; + for ( i = 0; i < len; i++ ) { + if ( singles[ i ] !== '' ) { + indx = parseInt( singles[ i ], 10 ); + if ( indx < c.columns ) { + columns.push( indx ); + } + } + } + } + // return all columns + if ( !columns.length ) { + for ( indx = 0; indx < c.columns; indx++ ) { + columns.push( indx ); + } + } + return columns; + }, + processTypes: function( c, data, vars ) { + var ffxn, + filterMatched = null, + matches = null; + for ( ffxn in ts.filter.types ) { + if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { + matches = ts.filter.types[ffxn]( c, data, vars ); + if ( matches !== null ) { + filterMatched = matches; + } + } + } + return filterMatched; + }, + processRow: function( c, data, vars ) { + var columnIndex, hasSelect, result, val, filterMatched, + fxn, ffxn, txt, + regex = ts.filter.regex, + wo = c.widgetOptions, + showRow = true; + data.$cells = data.$row.children(); + + if ( data.anyMatchFlag ) { + // look for multiple columns '1-3,4-6,8' + columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); + data.anyMatch = true; + data.isMatch = true; + data.rowArray = data.$cells.map( function( i ) { + if ( $.inArray( i, columnIndex ) > -1 ) { + if ( data.parsed[ i ] ) { + txt = data.cacheArray[ i ]; + } else { + txt = data.rawArray[ i ]; + txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); + if ( c.sortLocaleCompare ) { + txt = ts.replaceAccents( txt ); + } + } + return txt; + } + }).get(); + data.filter = data.anyMatchFilter; + data.iFilter = data.iAnyMatchFilter; + data.exact = data.rowArray.join( ' ' ); + data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; + data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); + + vars.excludeMatch = vars.noAnyMatch; + filterMatched = ts.filter.processTypes( c, data, vars ); + + if ( filterMatched !== null ) { + showRow = filterMatched; + } else { + if ( wo.filter_startsWith ) { + showRow = false; + columnIndex = c.columns; + while ( !showRow && columnIndex > 0 ) { + columnIndex--; + showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; + } + } else { + showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; + } + } + data.anyMatch = false; + // no other filters to process + if ( data.filters.join( '' ) === data.filter ) { + return showRow; + } + } + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.filter = data.filters[ columnIndex ]; + data.index = columnIndex; + + // filter types to exclude, per column + vars.excludeMatch = vars.excludeFilter[ columnIndex ]; + + // ignore if filter is empty or disabled + if ( data.filter ) { + data.cache = data.cacheArray[ columnIndex ]; + // check if column data should be from the cell or from parsed data + if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { + data.exact = data.cache; + } else { + result = data.rawArray[ columnIndex ] || ''; + data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 + } + data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? + data.exact.toLowerCase() : data.exact; + + data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); + + result = showRow; // if showRow is true, show that row + + // in case select filter option has a different value vs text 'a - z|A through Z' + ffxn = wo.filter_columnFilters ? + c.$filters.add( c.$externalFilters ) + .filter( '[data-column="' + columnIndex + '"]' ) + .find( 'select option:selected' ) + .attr( 'data-function-name' ) || '' : ''; + // replace accents - see #357 + if ( c.sortLocaleCompare ) { + data.filter = ts.replaceAccents( data.filter ); + } + + val = true; + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { + data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); + // val is used to indicate that a filter select is using a default filter; + // so we override the exact & partial matches + val = false; + } + // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), + // data.filter = case sensitive + data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; + fxn = vars.functions[ columnIndex ]; + hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); + filterMatched = null; + if ( fxn || ( hasSelect && val ) ) { + if ( fxn === true || hasSelect ) { + // default selector uses exact match unless 'filter-match' class is found + filterMatched = data.isMatch ? + data.iExact.search( data.iFilter ) >= 0 : + data.filter === data.exact; + } else if ( typeof fxn === 'function' ) { + // filter callback( exact cell content, parser normalized content, + // filter input value, column index, jQuery row object ) + filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { + // selector option function + txt = ffxn || data.filter; + filterMatched = + fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } + } + if ( filterMatched === null ) { + // cycle through the different filters + // filters return a boolean or null if nothing matches + filterMatched = ts.filter.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + result = filterMatched; + // Look for match, and add child row data for matching + } else { + txt = ( data.iExact + data.childRowText ) + .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); + result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); + } + } else { + result = filterMatched; + } + showRow = ( result ) ? showRow : false; + } + } + return showRow; + }, + findRows: function( table, filters, combinedFilters ) { + if ( table.config.lastCombinedFilter === combinedFilters || + !table.config.widgetOptions.filter_initialized ) { + return; + } + var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, + isChild, childRow, lastSearch, showRow, time, val, indx, + notFiltered, searchFiltered, query, injected, res, id, txt, + storedFilters = $.extend( [], filters ), + regex = ts.filter.regex, + c = table.config, + wo = c.widgetOptions, + // data object passed to filters; anyMatch is a flag for the filters + data = { + anyMatch: false, + filters: filters, + // regex filter type cache + filter_regexCache : [] + }, + vars = { + // anyMatch really screws up with these types of filters + noAnyMatch: [ 'range', 'notMatch', 'operators' ], + // cache filter variables that use ts.getColumnData in the main loop + functions : [], + excludeFilter : [], + defaultColFilter : [], + defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' + }; + + // parse columns after formatter, in case the class is added at that point + data.parsed = c.$headers.map( function( columnIndex ) { + return c.parsers && c.parsers[ columnIndex ] && + // force parsing if parser type is numeric + c.parsers[ columnIndex ].parsed || + // getData won't return 'parsed' if other 'filter-' class names exist + // ( e.g. ) + ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], + ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || + $( this ).hasClass( 'filter-parsed' ); + }).get(); + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + vars.functions[ columnIndex ] = + ts.getColumnData( table, wo.filter_functions, columnIndex ); + vars.defaultColFilter[ columnIndex ] = + ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; + vars.excludeFilter[ columnIndex ] = + ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); + } + + if ( c.debug ) { + ts.log( 'Filter: Starting filter widget search', filters ); + time = new Date(); + } + // filtered rows count + c.filteredRows = 0; + c.totalRows = 0; + // combindedFilters are undefined on init + combinedFilters = ( storedFilters || [] ).join( '' ); + + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); + // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! + // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); + columnIndex = c.columns; + // convert stored rows into a jQuery object + norm_rows = c.cache[ tbodyIndex ].normalized; + $rows = $( $.map( norm_rows, function( el ) { + return el[ columnIndex ].$row.get(); + }) ); + + if ( combinedFilters === '' || wo.filter_serversideFiltering ) { + $rows + .removeClass( wo.filter_filteredRow ) + .not( '.' + c.cssChildRow ) + .css( 'display', '' ); + } else { + // filter out child rows + $rows = $rows.not( '.' + c.cssChildRow ); + len = $rows.length; + + if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || + typeof filters[c.columns] !== 'undefined' ) { + data.anyMatchFlag = true; + data.anyMatchFilter = '' + ( + filters[ c.columns ] || + wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || + '' + ); + if ( wo.filter_columnAnyMatch ) { + // specific columns search + query = data.anyMatchFilter.split( regex.andSplit ); + injected = false; + for ( indx = 0; indx < query.length; indx++ ) { + res = query[ indx ].split( ':' ); + if ( res.length > 1 ) { + // make the column a one-based index ( non-developers start counting from one :P ) + id = parseInt( res[0], 10 ) - 1; + if ( id >= 0 && id < c.columns ) { // if id is an integer + filters[ id ] = res[1]; + query.splice( indx, 1 ); + indx--; + injected = true; + } + } + } + if ( injected ) { + data.anyMatchFilter = query.join( ' && ' ); + } + } + } + + // optimize searching only through already filtered rows - see #313 + searchFiltered = wo.filter_searchFiltered; + lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; + if ( searchFiltered ) { + // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 + for ( indx = 0; indx < columnIndex + 1; indx++ ) { + val = filters[indx] || ''; + // break out of loop if we've already determined not to search filtered rows + if ( !searchFiltered ) { indx = columnIndex; } + // search already filtered rows if... + searchFiltered = searchFiltered && lastSearch.length && + // there are no changes from beginning of filter + val.indexOf( lastSearch[indx] || '' ) === 0 && + // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string + !regex.alreadyFiltered.test( val ) && + // if we are not doing exact matches, using '|' ( logical or ) or not '!' + !/[=\"\|!]/.test( val ) && + // don't search only filtered if the value is negative + // ( '> -10' => '> -100' will ignore hidden rows ) + !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && + // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 + !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && + !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); + } + } + notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; + // can't search when all rows are hidden - this happens when looking for exact matches + if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } + if ( c.debug ) { + ts.log( 'Filter: Searching through ' + + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); + } + if ( data.anyMatchFlag ) { + if ( c.sortLocaleCompare ) { + // replace accents + data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); + } + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { + data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); + // clear search filtered flag because default filters are not saved to the last search + searchFiltered = false; + } + // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true + // when c.ignoreCase is true, the cache contains all lower case data + data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? + data.anyMatchFilter : + data.anyMatchFilter.toLowerCase(); + } + + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + + txt = $rows[ rowIndex ].className; + // the first row can never be a child row + isChild = rowIndex && regex.child.test( txt ); + // skip child rows & already filtered rows + if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { + continue; + } + + data.$row = $rows.eq( rowIndex ); + data.cacheArray = norm_rows[ rowIndex ]; + rowData = data.cacheArray[ c.columns ]; + data.rawArray = rowData.raw; + data.childRowText = ''; + + if ( !wo.filter_childByColumn ) { + txt = ''; + // child row cached text + childRow = rowData.child; + // so, if 'table.config.widgetOptions.filter_childRows' is true and there is + // a match anywhere in the child row, then it will make the row visible + // checked here so the option can be changed dynamically + for ( indx = 0; indx < childRow.length; indx++ ) { + txt += ' ' + childRow[indx].join( '' ) || ''; + } + data.childRowText = wo.filter_childRows ? + ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : + ''; + } + + showRow = ts.filter.processRow( c, data, vars ); + childRow = rowData.$row.filter( ':gt( 0 )' ); + + if ( wo.filter_childRows && childRow.length ) { + if ( wo.filter_childByColumn ) { + // cycle through each child row + for ( indx = 0; indx < childRow.length; indx++ ) { + data.$row = childRow.eq( indx ); + data.cacheArray = rowData.child[ indx ]; + data.rawArray = data.cacheArray; + // use OR comparison on child rows + showRow = showRow || ts.filter.processRow( c, data, vars ); + } + } + childRow.toggleClass( wo.filter_filteredRow, !showRow ); + } + + rowData.$row + .toggleClass( wo.filter_filteredRow, !showRow )[0] + .display = showRow ? '' : 'none'; + } + } + c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; + c.totalRows += $rows.length; + ts.processTbody( table, $tbody, false ); + } + c.lastCombinedFilter = combinedFilters; // save last search + // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) + c.lastSearch = storedFilters; + c.$table.data( 'lastSearch', storedFilters ); + if ( wo.filter_saveFilters && ts.storage ) { + ts.storage( table, 'tablesorter-filters', storedFilters ); + } + if ( c.debug ) { + ts.benchmark( 'Completed filter widget search', time ); + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterEnd', c ); + } + setTimeout( function() { + c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied + }, 0 ); + }, + getOptionSource: function( table, column, onlyAvail ) { + table = $( table )[0]; + var cts, txt, indx, len, + c = table.config, + wo = c.widgetOptions, + parsed = [], + arry = false, + source = wo.filter_selectSource, + last = c.$table.data( 'lastSearch' ) || [], + fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); + + if ( onlyAvail && last[column] !== '' ) { + onlyAvail = false; + } + + // filter select source option + if ( fxn === true ) { + // OVERALL source + arry = source( table, column, onlyAvail ); + } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { + // selectSource is a jQuery object or string of options + return fxn; + } else if ( $.isArray( fxn ) ) { + arry = fxn; + } else if ( $.type( source ) === 'object' && fxn ) { + // custom select source function for a SPECIFIC COLUMN + arry = fxn( table, column, onlyAvail ); + } + if ( arry === false ) { + // fall back to original method + arry = ts.filter.getOptions( table, column, onlyAvail ); + } + + // get unique elements and sort the list + // if $.tablesorter.sortText exists ( not in the original tablesorter ), + // then natural sort the list otherwise use a basic sort + arry = $.grep( arry, function( value, indx ) { + return $.inArray( value, arry ) === indx; + }); + + if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { + // unsorted select options + return arry; + } else { + len = arry.length; + // parse select option values + for ( indx = 0; indx < len; indx++ ) { + txt = arry[ indx ]; + // parse array data using set column parser; this DOES NOT pass the original + // table cell to the parser format function + parsed.push({ + t : txt, + // check parser length - fixes #934 + p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + }); + } + + // sort parsed select options + cts = c.textSorter || ''; + parsed.sort( function( a, b ) { + // sortNatural breaks if you don't pass it strings + var x = a.p.toString(), + y = b.p.toString(); + if ( $.isFunction( cts ) ) { + // custom OVERALL text sorter + return cts( x, y, true, column, table ); + } else if ( typeof cts === 'object' && cts.hasOwnProperty( column ) ) { + // custom text sorter for a SPECIFIC COLUMN + return cts[column]( x, y, true, column, table ); + } else if ( ts.sortNatural ) { + // fall back to natural sort + return ts.sortNatural( x, y ); + } + // using an older version! do a basic sort + return true; + }); + // rebuild arry from sorted parsed data + arry = []; + len = parsed.length; + for ( indx = 0; indx < len; indx++ ) { + arry.push( parsed[indx].t ); + } + return arry; + } + }, + getOptions: function( table, column, onlyAvail ) { + table = $( table )[0]; + var rowIndex, tbodyIndex, len, row, cache, + c = table.config, + wo = c.widgetOptions, + arry = []; + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + cache = c.cache[tbodyIndex]; + len = c.cache[tbodyIndex].normalized.length; + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + // get cached row from cache.row ( old ) or row data object + // ( new; last item in normalized array ) + row = cache.row ? + cache.row[ rowIndex ] : + cache.normalized[ rowIndex ][ c.columns ].$row[0]; + // check if has class filtered + if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { + continue; + } + // get non-normalized cell content + if ( wo.filter_useParsedData || + c.parsers[column].parsed || + c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { + arry.push( '' + cache.normalized[ rowIndex ][ column ] ); + } else { + // get raw cached data instead of content directly from the cells + arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + } + } } return arry; - } - }, - getOptions: function( table, column, onlyAvail ) { - table = $( table )[0]; - var rowIndex, tbodyIndex, len, row, cache, - c = table.config, - wo = c.widgetOptions, - arry = []; - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - cache = c.cache[tbodyIndex]; - len = c.cache[tbodyIndex].normalized.length; - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - // get cached row from cache.row ( old ) or row data object - // ( new; last item in normalized array ) - row = cache.row ? - cache.row[ rowIndex ] : - cache.normalized[ rowIndex ][ c.columns ].$row[0]; - // check if has class filtered - if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { - continue; + }, + buildSelect: function( table, column, arry, updating, onlyAvail ) { + table = $( table )[0]; + column = parseInt( column, 10 ); + if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { + return; + } + var indx, val, txt, t, $filters, $filter, + c = table.config, + wo = c.widgetOptions, + node = c.$headerIndexed[ column ], + // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 + options = '', + // Get curent filter value + currentValue = c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .val(); + // nothing included in arry ( external source ), so get the options from + // filter_selectSource or column data + if ( typeof arry === 'undefined' || arry === '' ) { + arry = ts.filter.getOptionSource( table, column, onlyAvail ); + } + + if ( $.isArray( arry ) ) { + // build option list + for ( indx = 0; indx < arry.length; indx++ ) { + txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); + val = txt; + // allow including a symbol in the selectSource array + // 'a-z|A through Z' so that 'a-z' becomes the option value + // and 'A through Z' becomes the option text + if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + t = txt.split( wo.filter_selectSourceSeparator ); + val = t[0]; + txt = t[1]; + } + // replace quotes - fixes #242 & ignore empty strings + // see http://stackoverflow.com/q/14990971/145346 + options += arry[indx] !== '' ? + '' : ''; } - // get non-normalized cell content - if ( wo.filter_useParsedData || - c.parsers[column].parsed || - c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { - arry.push( '' + cache.normalized[ rowIndex ][ column ] ); - } else { - // get raw cached data instead of content directly from the cells - arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + // clear arry so it doesn't get appended twice + arry = []; + } + + // update all selects in the same column ( clone thead in sticky headers & + // any external selects ) - fixes 473 + $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) + .find( '.' + tscss.filter ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; + } + $filter = $filters.filter( 'select[data-column="' + column + '"]' ); + + // make sure there is a select there! + if ( $filter.length ) { + $filter[ updating ? 'html' : 'append' ]( options ); + if ( !$.isArray( arry ) ) { + // append options if arry is provided externally as a string or jQuery object + // options ( default value ) was already added + $filter.append( arry ).val( currentValue ); + } + $filter.val( currentValue ); + } + }, + buildDefault: function( table, updating ) { + var columnIndex, $header, noSelect, + c = table.config, + wo = c.widgetOptions, + columns = c.columns; + // build default select dropdown + for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { + $header = c.$headerIndexed[columnIndex]; + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + // look for the filter-select class; build/update it if found + if ( ( $header.hasClass( 'filter-select' ) || + ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { + ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); } } } - return arry; - }, - buildSelect: function( table, column, arry, updating, onlyAvail ) { - table = $( table )[0]; - column = parseInt( column, 10 ); - if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { - return; - } - var indx, val, txt, t, $filters, $filter, - c = table.config, - wo = c.widgetOptions, - node = c.$headerIndexed[ column ], - // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 - options = '', - // Get curent filter value - currentValue = c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .val(); - // nothing included in arry ( external source ), so get the options from - // filter_selectSource or column data - if ( typeof arry === 'undefined' || arry === '' ) { - arry = ts.filter.getOptionSource( table, column, onlyAvail ); - } + }; - if ( $.isArray( arry ) ) { - // build option list - for ( indx = 0; indx < arry.length; indx++ ) { - txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); - val = txt; - // allow including a symbol in the selectSource array - // 'a-z|A through Z' so that 'a-z' becomes the option value - // and 'A through Z' becomes the option text - if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - t = txt.split( wo.filter_selectSourceSeparator ); - val = t[0]; - txt = t[1]; - } - // replace quotes - fixes #242 & ignore empty strings - // see http://stackoverflow.com/q/14990971/145346 - options += arry[indx] !== '' ? - '' : ''; + ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { + var i, $filters, $column, cols, + filters = false, + c = table ? $( table )[0].config : '', + wo = c ? c.widgetOptions : ''; + if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || + // setFilters called, but last search is exactly the same as the current + // fixes issue #733 & #903 where calling update causes the input values to reset + ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { + return $( table ).data( 'lastSearch' ); + } + if ( c ) { + if ( c.$filters ) { + $filters = c.$filters.find( '.' + tscss.filter ); } - // clear arry so it doesn't get appended twice - arry = []; - } - - // update all selects in the same column ( clone thead in sticky headers & - // any external selects ) - fixes 473 - $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) - .find( '.' + tscss.filter ); - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - $filter = $filters.filter( 'select[data-column="' + column + '"]' ); - - // make sure there is a select there! - if ( $filter.length ) { - $filter[ updating ? 'html' : 'append' ]( options ); - if ( !$.isArray( arry ) ) { - // append options if arry is provided externally as a string or jQuery object - // options ( default value ) was already added - $filter.append( arry ).val( currentValue ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; } - $filter.val( currentValue ); - } - }, - buildDefault: function( table, updating ) { - var columnIndex, $header, noSelect, - c = table.config, - wo = c.widgetOptions, - columns = c.columns; - // build default select dropdown - for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { - $header = c.$headerIndexed[columnIndex]; - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - // look for the filter-select class; build/update it if found - if ( ( $header.hasClass( 'filter-select' ) || - ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { - ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } -}; - -ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { - var i, $filters, $column, cols, - filters = false, - c = table ? $( table )[0].config : '', - wo = c ? c.widgetOptions : ''; - if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || - // setFilters called, but last search is exactly the same as the current - // fixes issue #733 & #903 where calling update causes the input values to reset - ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { - return $( table ).data( 'lastSearch' ); - } - if ( c ) { - if ( c.$filters ) { - $filters = c.$filters.find( '.' + tscss.filter ); - } - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - if ( $filters && $filters.length ) { - filters = setFilters || []; - for ( i = 0; i < c.columns + 1; i++ ) { - cols = ( i === c.columns ? - // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) - wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : - '[data-column="' + i + '"]' ); - $column = $filters.filter( cols ); - if ( $column.length ) { - // move the latest search to the first slot in the array - $column = ts.filter.getLatestSearch( $column ); - if ( $.isArray( setFilters ) ) { - // skip first ( latest input ) to maintain cursor position while typing - if ( skipFirst && $column.length > 1 ) { - $column = $column.slice( 1 ); - } - if ( i === c.columns ) { - // prevent data-column='all' from filling data-column='0,1' ( etc ) - cols = $column.filter( wo.filter_anyColumnSelector ); - $column = cols.length ? cols : $column; - } - $column - .val( setFilters[ i ] ) - .trigger( 'change.tsfilter' ); - } else { - filters[i] = $column.val() || ''; - // don't change the first... it will move the cursor - if ( i === c.columns ) { - // don't update range columns from 'all' setting + if ( $filters && $filters.length ) { + filters = setFilters || []; + for ( i = 0; i < c.columns + 1; i++ ) { + cols = ( i === c.columns ? + // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) + wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : + '[data-column="' + i + '"]' ); + $column = $filters.filter( cols ); + if ( $column.length ) { + // move the latest search to the first slot in the array + $column = ts.filter.getLatestSearch( $column ); + if ( $.isArray( setFilters ) ) { + // skip first ( latest input ) to maintain cursor position while typing + if ( skipFirst && $column.length > 1 ) { + $column = $column.slice( 1 ); + } + if ( i === c.columns ) { + // prevent data-column='all' from filling data-column='0,1' ( etc ) + cols = $column.filter( wo.filter_anyColumnSelector ); + $column = cols.length ? cols : $column; + } $column - .slice( 1 ) - .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) - .val( filters[ i ] ); + .val( setFilters[ i ] ) + .trigger( 'change.tsfilter' ); } else { - $column - .slice( 1 ) - .val( filters[ i ] ); + filters[i] = $column.val() || ''; + // don't change the first... it will move the cursor + if ( i === c.columns ) { + // don't update range columns from 'all' setting + $column + .slice( 1 ) + .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) + .val( filters[ i ] ); + } else { + $column + .slice( 1 ) + .val( filters[ i ] ); + } + } + // save any match input dynamically + if ( i === c.columns && $column.length ) { + wo.filter_$anyMatch = $column; } - } - // save any match input dynamically - if ( i === c.columns && $column.length ) { - wo.filter_$anyMatch = $column; } } } } - } - if ( filters.length === 0 ) { - filters = false; - } - return filters; -}; + if ( filters.length === 0 ) { + filters = false; + } + return filters; + }; -ts.setFilters = function( table, filter, apply, skipFirst ) { - var c = table ? $( table )[0].config : '', - valid = ts.getFilters( table, true, filter, skipFirst ); - if ( c && apply ) { - // ensure new set filters are applied, even if the search is the same - c.lastCombinedFilter = null; - c.lastSearch = []; - ts.filter.searching( c.table, filter, skipFirst ); - c.$table.trigger( 'filterFomatterUpdate' ); - } - return !!valid; -}; + ts.setFilters = function( table, filter, apply, skipFirst ) { + var c = table ? $( table )[0].config : '', + valid = ts.getFilters( table, true, filter, skipFirst ); + if ( c && apply ) { + // ensure new set filters are applied, even if the search is the same + c.lastCombinedFilter = null; + c.lastSearch = []; + ts.filter.searching( c.table, filter, skipFirst ); + c.$table.trigger( 'filterFomatterUpdate' ); + } + return !!valid; + }; })( jQuery ); @@ -4163,750 +4165,751 @@ ts.setFilters = function( table, filter, apply, skipFirst ) { * by Rob Garrison */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - sticky : 'tablesorter-stickyHeader', // stickyHeader - stickyVis : 'tablesorter-sticky-visible', - stickyHide: 'tablesorter-sticky-hidden', - stickyWrap: 'tablesorter-sticky-wrapper' -}); + $.extend(ts.css, { + sticky : 'tablesorter-stickyHeader', // stickyHeader + stickyVis : 'tablesorter-sticky-visible', + stickyHide: 'tablesorter-sticky-hidden', + stickyWrap: 'tablesorter-sticky-wrapper' + }); -// Add a resize event to table headers -ts.addHeaderResizeEvent = function(table, disable, settings) { - table = $(table)[0]; // make sure we're using a dom element - if ( !table.config ) { return; } - var defaults = { - timer : 250 + // Add a resize event to table headers + ts.addHeaderResizeEvent = function(table, disable, settings) { + table = $(table)[0]; // make sure we're using a dom element + if ( !table.config ) { return; } + var defaults = { + timer : 250 + }, + options = $.extend({}, defaults, settings), + c = table.config, + wo = c.widgetOptions, + checkSizes = function( triggerEvent ) { + var index, headers, $header, sizes, width, height, + len = c.$headers.length; + wo.resize_flag = true; + headers = []; + for ( index = 0; index < len; index++ ) { + $header = c.$headers.eq( index ); + sizes = $header.data( 'savedSizes' ) || [ 0, 0 ]; // fixes #394 + width = $header[0].offsetWidth; + height = $header[0].offsetHeight; + if ( width !== sizes[0] || height !== sizes[1] ) { + $header.data( 'savedSizes', [ width, height ] ); + headers.push( $header[0] ); + } + } + if ( headers.length && triggerEvent !== false ) { + c.$table.trigger( 'resize', [ headers ] ); + } + wo.resize_flag = false; + }; + checkSizes( false ); + clearInterval(wo.resize_timer); + if (disable) { + wo.resize_flag = false; + return false; + } + wo.resize_timer = setInterval(function() { + if (wo.resize_flag) { return; } + checkSizes(); + }, options.timer); + }; + + // Sticky headers based on this awesome article: + // http://css-tricks.com/13465-persistent-headers/ + // and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech + // ************************** + ts.addWidget({ + id: 'stickyHeaders', + priority: 60, // sticky widget must be initialized after the filter widget! + options: { + stickyHeaders : '', // extra class name added to the sticky header row + stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to + stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) + stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) + stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element + stickyHeaders_filteredToTop: true, // scroll table top into view after filtering + stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists + stickyHeaders_addResizeEvent : true, // trigger 'resize' event on headers + stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header + stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs }, - options = $.extend({}, defaults, settings), - c = table.config, - wo = c.widgetOptions, - checkSizes = function( triggerEvent ) { - var index, headers, $header, sizes, width, height, - len = c.$headers.length; - wo.resize_flag = true; - headers = []; - for ( index = 0; index < len; index++ ) { - $header = c.$headers.eq( index ); - sizes = $header.data( 'savedSizes' ) || [ 0,0 ]; // fixes #394 - width = $header[0].offsetWidth; - height = $header[0].offsetHeight; - if ( width !== sizes[0] || height !== sizes[1] ) { - $header.data( 'savedSizes', [ width, height ] ); - headers.push( $header[0] ); + format: function(table, c, wo) { + // filter widget doesn't initialize on an empty table. Fixes #449 + if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { + return; + } + var index, len, $t, + $table = c.$table, + // add position: relative to attach element, hopefully it won't cause trouble. + $attach = $(wo.stickyHeaders_attachTo), + namespace = c.namespace + 'stickyheaders ', + // element to watch for the scroll event + $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), + $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), + $thead = $table.children('thead:first'), + $header = $thead.children('tr').not('.sticky-false').children(), + $tfoot = $table.children('tfoot'), + $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, + // is this table nested? If so, find parent sticky header wrapper (div, not table) + $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? + $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], + nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, + // clone table, then wrap to make sticky header + $stickyTable = wo.$sticky = $table.clone() + .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) + .wrap('
'), + $stickyWrap = $stickyTable.parent() + .addClass(ts.css.stickyHide) + .css({ + position : $attach.length ? 'absolute' : 'fixed', + padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), + top : stickyOffset + nestedStickyTop, + left : 0, + visibility : 'hidden', + zIndex : wo.stickyHeaders_zIndex || 2 + }), + $stickyThead = $stickyTable.children('thead:first'), + $stickyCells, + laststate = '', + spacing = 0, + setWidth = function($orig, $clone){ + var index, width, border, $cell, $this, + $cells = $orig.filter(':visible'), + len = $cells.length; + for ( index = 0; index < len; index++ ) { + $cell = $clone.filter(':visible').eq(index); + $this = $cells.eq(index); + // code from https://github.com/jmosbech/StickyTableHeaders + if ($this.css('box-sizing') === 'border-box') { + width = $this.outerWidth(); + } else { + if ($cell.css('border-collapse') === 'collapse') { + if (window.getComputedStyle) { + width = parseFloat( window.getComputedStyle($this[0], null).width ); + } else { + // ie8 only + border = parseFloat( $this.css('border-width') ); + width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; + } + } else { + width = $this.width(); + } + } + $cell.css({ + 'width': width, + 'min-width': width, + 'max-width': width + }); + } + }, + resizeHeader = function() { + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; + spacing = 0; + $stickyWrap.css({ + left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : + $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, + width: $table.outerWidth() + }); + setWidth( $table, $stickyTable ); + setWidth( $header, $stickyCells ); + }, + scrollSticky = function( resizing ) { + if (!$table.is(':visible')) { return; } // fixes #278 + // Detect nested tables - fixes #724 + nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; + var offset = $table.offset(), + yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 + xWindow = $.isWindow( $xScroll[0] ), + // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), + isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', + cssSettings = { visibility : isVisible }; + + if ($attach.length) { + cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + } + if (xWindow) { + // adjust when scrolling horizontally - fixes issue #143 + cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; + } + if ($nestedSticky.length) { + cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; + } + $stickyWrap + .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) + .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) + .css(cssSettings); + if (isVisible !== laststate || resizing) { + // make sure the column widths match + resizeHeader(); + laststate = isVisible; + } + }; + // only add a position relative if a position isn't already defined + if ($attach.length && !$attach.css('position')) { + $attach.css('position', 'relative'); + } + // fix clone ID, if it exists - fixes #271 + if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } + // clear out cloned table, except for sticky header + // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing + $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); + $stickyTable.find('tbody, tfoot').remove(); + $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); + // issue #172 - find td/th in sticky header + $stickyCells = $stickyThead.children().children(); + $stickyTable.css({ height:0, width:0, margin: 0 }); + // remove resizable block + $stickyCells.find('.' + ts.css.resizer).remove(); + // update sticky header class names to match real header after sorting + $table + .addClass('hasStickyHeaders') + .bind('pagerComplete' + namespace, function() { + resizeHeader(); + }); + + ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); + + // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. + $table.after( $stickyWrap ); + + // onRenderHeader is defined, we need to do something about it (fixes #641) + if (c.onRenderHeader) { + $t = $stickyThead.children('tr').children(); + len = $t.length; + for ( index = 0; index < len; index++ ) { + // send second parameter + c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } } - if ( headers.length && triggerEvent !== false ) { - c.$table.trigger( 'resize', [ headers ] ); - } - wo.resize_flag = false; - }; - checkSizes( false ); - clearInterval(wo.resize_timer); - if (disable) { - wo.resize_flag = false; - return false; - } - wo.resize_timer = setInterval(function() { - if (wo.resize_flag) { return; } - checkSizes(); - }, options.timer); -}; -// Sticky headers based on this awesome article: -// http://css-tricks.com/13465-persistent-headers/ -// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech -// ************************** -ts.addWidget({ - id: "stickyHeaders", - priority: 60, // sticky widget must be initialized after the filter widget! - options: { - stickyHeaders : '', // extra class name added to the sticky header row - stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to - stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) - stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) - stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element - stickyHeaders_filteredToTop: true, // scroll table top into view after filtering - stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists - stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers - stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header - stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs - }, - format: function(table, c, wo) { - // filter widget doesn't initialize on an empty table. Fixes #449 - if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { - return; - } - var index, len, $t, - $table = c.$table, - // add position: relative to attach element, hopefully it won't cause trouble. - $attach = $(wo.stickyHeaders_attachTo), - namespace = c.namespace + 'stickyheaders ', - // element to watch for the scroll event - $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), - $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), - $thead = $table.children('thead:first'), - $header = $thead.children('tr').not('.sticky-false').children(), - $tfoot = $table.children('tfoot'), - $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, - // is this table nested? If so, find parent sticky header wrapper (div, not table) - $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? - $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], - nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, - // clone table, then wrap to make sticky header - $stickyTable = wo.$sticky = $table.clone() - .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) - .wrap('
'), - $stickyWrap = $stickyTable.parent() - .addClass(ts.css.stickyHide) - .css({ - position : $attach.length ? 'absolute' : 'fixed', - padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), - top : stickyOffset + nestedStickyTop, - left : 0, - visibility : 'hidden', - zIndex : wo.stickyHeaders_zIndex || 2 - }), - $stickyThead = $stickyTable.children('thead:first'), - $stickyCells, - laststate = '', - spacing = 0, - setWidth = function($orig, $clone){ - var index, width, border, $cell, $this, - $cells = $orig.filter(':visible'), - len = $cells.length; - for ( index = 0; index < len; index++ ) { - $cell = $clone.filter(':visible').eq(index); - $this = $cells.eq(index); - // code from https://github.com/jmosbech/StickyTableHeaders - if ($this.css('box-sizing') === 'border-box') { - width = $this.outerWidth(); - } else { - if ($cell.css('border-collapse') === 'collapse') { - if (window.getComputedStyle) { - width = parseFloat( window.getComputedStyle($this[0], null).width ); - } else { - // ie8 only - border = parseFloat( $this.css('border-width') ); - width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; - } - } else { - width = $this.width(); + // make it sticky! + $xScroll.add($yScroll) + .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) + .bind('scroll resize '.split(' ').join( namespace ), function( event ) { + scrollSticky( event.type === 'resize' ); + }); + c.$table + .unbind('stickyHeadersUpdate' + namespace) + .bind('stickyHeadersUpdate' + namespace, function(){ + scrollSticky( true ); + }); + + if (wo.stickyHeaders_addResizeEvent) { + ts.addHeaderResizeEvent(table); + } + + // look for filter widget + if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { + // scroll table into view after filtering, if sticky header is active - #482 + $table.bind('filterEnd' + namespace, function() { + // $(':focus') needs jQuery 1.6+ + var $td = $(document.activeElement).closest('td'), + column = $td.parent().children().index($td); + // only scroll if sticky header is active + if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { + // scroll to original table (not sticky clone) + window.scrollTo(0, $table.position().top); + // give same input/select focus; check if c.$filters exists; fixes #594 + if (column >= 0 && c.$filters) { + c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); } } - $cell.css({ - 'width': width, - 'min-width': width, - 'max-width': width - }); - } - }, - resizeHeader = function() { - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; - spacing = 0; - $stickyWrap.css({ - left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : - $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, - width: $table.outerWidth() }); - setWidth( $table, $stickyTable ); - setWidth( $header, $stickyCells ); - }, - scrollSticky = function( resizing ) { - if (!$table.is(':visible')) { return; } // fixes #278 - // Detect nested tables - fixes #724 - nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; - var offset = $table.offset(), - yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 - xWindow = $.isWindow( $xScroll[0] ), - // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), - isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', - cssSettings = { visibility : isVisible }; - - if ($attach.length) { - cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); + // support hideFilters + if (wo.filter_hideFilters) { + ts.filter.hideFilters($stickyTable, c); } - if (xWindow) { - // adjust when scrolling horizontally - fixes issue #143 - cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; - } - if ($nestedSticky.length) { - cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; - } - $stickyWrap - .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) - .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) - .css(cssSettings); - if (isVisible !== laststate || resizing) { - // make sure the column widths match - resizeHeader(); - laststate = isVisible; - } - }; - // only add a position relative if a position isn't already defined - if ($attach.length && !$attach.css('position')) { - $attach.css('position', 'relative'); - } - // fix clone ID, if it exists - fixes #271 - if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } - // clear out cloned table, except for sticky header - // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing - $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); - $stickyTable.find('tbody, tfoot').remove(); - $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); - // issue #172 - find td/th in sticky header - $stickyCells = $stickyThead.children().children(); - $stickyTable.css({ height:0, width:0, margin: 0 }); - // remove resizable block - $stickyCells.find('.' + ts.css.resizer).remove(); - // update sticky header class names to match real header after sorting - $table - .addClass('hasStickyHeaders') - .bind('pagerComplete' + namespace, function() { - resizeHeader(); - }); - - ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); - - // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. - $table.after( $stickyWrap ); - - // onRenderHeader is defined, we need to do something about it (fixes #641) - if (c.onRenderHeader) { - $t = $stickyThead.children('tr').children(); - len = $t.length; - for ( index = 0; index < len; index++ ) { - // send second parameter - c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } + + $table.trigger('stickyHeadersInit'); + + }, + remove: function(table, c, wo) { + var namespace = c.namespace + 'stickyheaders '; + c.$table + .removeClass('hasStickyHeaders') + .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) + .next('.' + ts.css.stickyWrap).remove(); + if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table + $(window) + .add(wo.stickyHeaders_xScroll) + .add(wo.stickyHeaders_yScroll) + .add(wo.stickyHeaders_attachTo) + .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); + ts.addHeaderResizeEvent(table, false); } - - // make it sticky! - $xScroll.add($yScroll) - .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) - .bind('scroll resize '.split(' ').join( namespace ), function( event ) { - scrollSticky( event.type === 'resize' ); - }); - c.$table - .unbind('stickyHeadersUpdate' + namespace) - .bind('stickyHeadersUpdate' + namespace, function(){ - scrollSticky( true ); - }); - - if (wo.stickyHeaders_addResizeEvent) { - ts.addHeaderResizeEvent(table); - } - - // look for filter widget - if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { - // scroll table into view after filtering, if sticky header is active - #482 - $table.bind('filterEnd' + namespace, function() { - // $(':focus') needs jQuery 1.6+ - var $td = $(document.activeElement).closest('td'), - column = $td.parent().children().index($td); - // only scroll if sticky header is active - if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { - // scroll to original table (not sticky clone) - window.scrollTo(0, $table.position().top); - // give same input/select focus; check if c.$filters exists; fixes #594 - if (column >= 0 && c.$filters) { - c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); - } - } - }); - ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); - // support hideFilters - if (wo.filter_hideFilters) { - ts.filter.hideFilters($stickyTable, c); - } - } - - $table.trigger('stickyHeadersInit'); - - }, - remove: function(table, c, wo) { - var namespace = c.namespace + 'stickyheaders '; - c.$table - .removeClass('hasStickyHeaders') - .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) - .next('.' + ts.css.stickyWrap).remove(); - if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table - $(window) - .add(wo.stickyHeaders_xScroll) - .add(wo.stickyHeaders_yScroll) - .add(wo.stickyHeaders_attachTo) - .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); - ts.addHeaderResizeEvent(table, false); - } -}); + }); })(jQuery, window); /*! Widget: resizable - updated 6/26/2015 (v2.22.2) */ /*jshint browser:true, jquery:true, unused:false */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - resizableContainer : 'tablesorter-resizable-container', - resizableHandle : 'tablesorter-resizable-handle', - resizableNoSelect : 'tablesorter-disableSelection', - resizableStorage : 'tablesorter-resizable' -}); + $.extend(ts.css, { + resizableContainer : 'tablesorter-resizable-container', + resizableHandle : 'tablesorter-resizable-handle', + resizableNoSelect : 'tablesorter-disableSelection', + resizableStorage : 'tablesorter-resizable' + }); -// Add extra scroller css -$(function(){ - var s = ''; - $(s).appendTo('body'); -}); + // Add extra scroller css + $(function(){ + var s = ''; + $(s).appendTo('body'); + }); -ts.resizable = { - init : function( c, wo ) { - if ( c.$table.hasClass( 'hasResizable' ) ) { return; } - c.$table.addClass( 'hasResizable' ); + ts.resizable = { + init : function( c, wo ) { + if ( c.$table.hasClass( 'hasResizable' ) ) { return; } + c.$table.addClass( 'hasResizable' ); - var noResize, $header, column, storedSizes, tmp, - $table = c.$table, - $parent = $table.parent(), - marginTop = parseInt( $table.css( 'margin-top' ), 10 ), + var noResize, $header, column, storedSizes, tmp, + $table = c.$table, + $parent = $table.parent(), + marginTop = parseInt( $table.css( 'margin-top' ), 10 ), - // internal variables - vars = wo.resizable_ = { - useStorage : ts.storage && wo.resizable !== false, - $wrap : $parent, - mouseXPosition : 0, - $target : null, - $next : null, - overflow : $parent.css('overflow') === 'auto' || - $parent.css('overflow') === 'scroll' || - $parent.css('overflow-x') === 'auto' || - $parent.css('overflow-x') === 'scroll', - storedSizes : [] - }; + // internal variables + vars = wo.resizable_vars = { + useStorage : ts.storage && wo.resizable !== false, + $wrap : $parent, + mouseXPosition : 0, + $target : null, + $next : null, + overflow : $parent.css('overflow') === 'auto' || + $parent.css('overflow') === 'scroll' || + $parent.css('overflow-x') === 'auto' || + $parent.css('overflow-x') === 'scroll', + storedSizes : [] + }; - // set default widths - ts.resizableReset( c.table, true ); + // set default widths + ts.resizableReset( c.table, true ); - // now get measurements! - vars.tableWidth = $table.width(); - // attempt to autodetect - vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; + // now get measurements! + vars.tableWidth = $table.width(); + // attempt to autodetect + vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; - /* - // Hacky method to determine if table width is set to "auto" - // http://stackoverflow.com/a/20892048/145346 - if ( !vars.fullWidth ) { - tmp = $table.width(); - $header = $table.wrap('').parent(); // temp variable - storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; - $table.css( 'margin-left', storedSizes + 50 ); - vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; - $table.css( 'margin-left', storedSizes ? storedSizes : '' ); - $header = null; - $table.unwrap(''); - } - */ - - if ( vars.useStorage && vars.overflow ) { - // save table width - ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); - tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; - ts.resizable.setWidth( $table, tmp, true ); - } - wo.resizable_.storedSizes = storedSizes = ( vars.useStorage ? - ts.storage( c.table, ts.css.resizableStorage ) : - [] ) || []; - ts.resizable.setWidths( c, wo, storedSizes ); - ts.resizable.updateStoredSizes( c, wo ); - - wo.$resizable_container = $( '
' ) - .css({ top : marginTop }) - .insertBefore( $table ); - // add container - for ( column = 0; column < c.columns; column++ ) { - $header = c.$headerIndexed[ column ]; - tmp = ts.getColumnData( c.table, c.headers, column ); - noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; - if ( !noResize ) { - $( '
' ) - .appendTo( wo.$resizable_container ) - .attr({ - 'data-column' : column, - 'unselectable' : 'on' - }) - .data( 'header', $header ) - .bind( 'selectstart', false ); + /* + // Hacky method to determine if table width is set to 'auto' + // http://stackoverflow.com/a/20892048/145346 + if ( !vars.fullWidth ) { + tmp = $table.width(); + $header = $table.wrap('').parent(); // temp variable + storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; + $table.css( 'margin-left', storedSizes + 50 ); + vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; + $table.css( 'margin-left', storedSizes ? storedSizes : '' ); + $header = null; + $table.unwrap(''); } - } - $table.one('tablesorter-initialized', function() { - ts.resizable.setHandlePosition( c, wo ); - ts.resizable.bindings( this.config, this.config.widgetOptions ); - }); - }, + */ - updateStoredSizes : function( c, wo ) { - var column, $header, - len = c.columns, - vars = wo.resizable_; - vars.storedSizes = []; - for ( column = 0; column < len; column++ ) { - $header = c.$headerIndexed[ column ]; - vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; - } - }, + if ( vars.useStorage && vars.overflow ) { + // save table width + ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); + tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; + ts.resizable.setWidth( $table, tmp, true ); + } + wo.resizable_vars.storedSizes = storedSizes = ( vars.useStorage ? + ts.storage( c.table, ts.css.resizableStorage ) : + [] ) || []; + ts.resizable.setWidths( c, wo, storedSizes ); + ts.resizable.updateStoredSizes( c, wo ); - setWidth : function( $el, width, overflow ) { - // overflow tables need min & max width set as well - $el.css({ - 'width' : width, - 'min-width' : overflow ? width : '', - 'max-width' : overflow ? width : '' - }); - }, - - setWidths : function( c, wo, storedSizes ) { - var column, $temp, - vars = wo.resizable_, - $extra = $( c.namespace + '_extra_headers' ), - $col = c.$table.children( 'colgroup' ).children( 'col' ); - storedSizes = storedSizes || vars.storedSizes || []; - // process only if table ID or url match - if ( storedSizes.length ) { + wo.$resizable_container = $( '
' ) + .css({ top : marginTop }) + .insertBefore( $table ); + // add container for ( column = 0; column < c.columns; column++ ) { - // set saved resizable widths - ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); - if ( $extra.length ) { - // stickyHeaders needs to modify min & max width as well - $temp = $extra.eq( column ).add( $col.eq( column ) ); - ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + $header = c.$headerIndexed[ column ]; + tmp = ts.getColumnData( c.table, c.headers, column ); + noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; + if ( !noResize ) { + $( '
' ) + .appendTo( wo.$resizable_container ) + .attr({ + 'data-column' : column, + 'unselectable' : 'on' + }) + .data( 'header', $header ) + .bind( 'selectstart', false ); } } - $temp = $( c.namespace + '_extra_table' ); - if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { - ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); - } - } - }, - - setHandlePosition : function( c, wo ) { - var startPosition, - hasScroller = ts.hasWidget( c.table, 'scroller' ), - tableHeight = c.$table.height(), - $handles = wo.$resizable_container.children(), - handleCenter = Math.floor( $handles.width() / 2 ); - - if ( hasScroller ) { - tableHeight = 0; - c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ - var $this = $(this); - // center table has a max-height set - tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); + $table.one('tablesorter-initialized', function() { + ts.resizable.setHandlePosition( c, wo ); + ts.resizable.bindings( this.config, this.config.widgetOptions ); }); - } - // subtract out table left position from resizable handles. Fixes #864 - startPosition = c.$table.position().left; - $handles.each( function() { - var $this = $(this), - column = parseInt( $this.attr( 'data-column' ), 10 ), - columns = c.columns - 1, - $header = $this.data( 'header' ); - if ( !$header ) { return; } // see #859 - if ( !$header.is(':visible') ) { - $this.hide(); - } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { - $this.css({ - display: 'inline-block', - height : tableHeight, - left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }, + + updateStoredSizes : function( c, wo ) { + var column, $header, + len = c.columns, + vars = wo.resizable_vars; + vars.storedSizes = []; + for ( column = 0; column < len; column++ ) { + $header = c.$headerIndexed[ column ]; + vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; + } + }, + + setWidth : function( $el, width, overflow ) { + // overflow tables need min & max width set as well + $el.css({ + 'width' : width, + 'min-width' : overflow ? width : '', + 'max-width' : overflow ? width : '' + }); + }, + + setWidths : function( c, wo, storedSizes ) { + var column, $temp, + vars = wo.resizable_vars, + $extra = $( c.namespace + '_extra_headers' ), + $col = c.$table.children( 'colgroup' ).children( 'col' ); + storedSizes = storedSizes || vars.storedSizes || []; + // process only if table ID or url match + if ( storedSizes.length ) { + for ( column = 0; column < c.columns; column++ ) { + // set saved resizable widths + ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); + if ( $extra.length ) { + // stickyHeaders needs to modify min & max width as well + $temp = $extra.eq( column ).add( $col.eq( column ) ); + ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + } + } + $temp = $( c.namespace + '_extra_table' ); + if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { + ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); + } + } + }, + + setHandlePosition : function( c, wo ) { + var startPosition, + hasScroller = ts.hasWidget( c.table, 'scroller' ), + tableHeight = c.$table.height(), + $handles = wo.$resizable_container.children(), + handleCenter = Math.floor( $handles.width() / 2 ); + + if ( hasScroller ) { + tableHeight = 0; + c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ + var $this = $(this); + // center table has a max-height set + tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); }); } - }); - }, - - // prevent text selection while dragging resize bar - toggleTextSelection : function( c, toggle ) { - var namespace = c.namespace + 'tsresize'; - c.widgetOptions.resizable_.disabled = toggle; - $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); - if ( toggle ) { - $( 'body' ) - .attr( 'unselectable', 'on' ) - .bind( 'selectstart' + namespace, false ); - } else { - $( 'body' ) - .removeAttr( 'unselectable' ) - .unbind( 'selectstart' + namespace ); - } - }, - - bindings : function( c, wo ) { - var namespace = c.namespace + 'tsresize'; - wo.$resizable_container.children().bind( 'mousedown', function( event ) { - // save header cell and mouse position - var column, - vars = wo.resizable_, - $extras = $( c.namespace + '_extra_headers' ), - $header = $( event.target ).data( 'header' ); - - column = parseInt( $header.attr( 'data-column' ), 10 ); - vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); - vars.target = column; - - // if table is not as wide as it's parent, then resize the table - vars.$next = event.shiftKey || wo.resizable_targetLast ? - $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : - $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); - - column = parseInt( vars.$next.attr( 'data-column' ), 10 ); - vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); - vars.next = column; - - vars.mouseXPosition = event.pageX; - ts.resizable.updateStoredSizes( c, wo ); - ts.resizable.toggleTextSelection( c, true ); - }); - - $( document ) - .bind( 'mousemove' + namespace, function( event ) { - var vars = wo.resizable_; - // ignore mousemove if no mousedown - if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } - if ( wo.resizable_throttle ) { - clearTimeout( vars.timer ); - vars.timer = setTimeout( function() { - ts.resizable.mouseMove( c, wo, event ); - }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); - } else { - ts.resizable.mouseMove( c, wo, event ); + // subtract out table left position from resizable handles. Fixes #864 + startPosition = c.$table.position().left; + $handles.each( function() { + var $this = $(this), + column = parseInt( $this.attr( 'data-column' ), 10 ), + columns = c.columns - 1, + $header = $this.data( 'header' ); + if ( !$header ) { return; } // see #859 + if ( !$header.is(':visible') ) { + $this.hide(); + } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { + $this.css({ + display: 'inline-block', + height : tableHeight, + left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }); } - }) - .bind( 'mouseup' + namespace, function() { - if (!wo.resizable_.disabled) { return; } - ts.resizable.toggleTextSelection( c, false ); - ts.resizable.stopResize( c, wo ); - ts.resizable.setHandlePosition( c, wo ); }); + }, - // resizeEnd event triggered by scroller widget - $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }); - - // right click to reset columns to default widths - c.$table - .bind( 'columnUpdate' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }) - .find( 'thead:first' ) - .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) - .bind( 'contextmenu' + namespace, function() { - // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset - var allowClick = wo.resizable_.storedSizes.length === 0; - ts.resizableReset( c.table ); - ts.resizable.setHandlePosition( c, wo ); - wo.resizable_.storedSizes = []; - return allowClick; - }); - - }, - - mouseMove : function( c, wo, event ) { - if ( wo.resizable_.mouseXPosition === 0 || !wo.resizable_.$target ) { return; } - // resize columns - var column, - total = 0, - vars = wo.resizable_, - $next = vars.$next, - tar = vars.storedSizes[ vars.target ], - leftEdge = event.pageX - vars.mouseXPosition; - if ( vars.overflow ) { - if ( tar + leftEdge > 0 ) { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); - // update the entire table width - for ( column = 0; column < c.columns; column++ ) { - total += vars.storedSizes[ column ]; - } - ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); - } - if ( !$next.length ) { - // if expanding right-most column, scroll the wrapper - vars.$wrap[0].scrollLeft = c.$table.width(); - } - } else if ( vars.fullWidth ) { - vars.storedSizes[ vars.target ] += leftEdge; - vars.storedSizes[ vars.next ] -= leftEdge; - ts.resizable.setWidths( c, wo ); - } else { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidths( c, wo ); - } - vars.mouseXPosition = event.pageX; - // dynamically update sticky header widths - c.$table.trigger('stickyHeadersUpdate'); - }, - - stopResize : function( c, wo ) { - var vars = wo.resizable_; - ts.resizable.updateStoredSizes( c, wo ); - if ( vars.useStorage ) { - // save all column widths - ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); - ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); - } - vars.mouseXPosition = 0; - vars.$target = vars.$next = null; - // will update stickyHeaders, just in case, see #912 - c.$table.trigger('stickyHeadersUpdate'); - } -}; - -// this widget saves the column widths if -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: "resizable", - priority: 40, - options: { - resizable : true, // save column widths to storage - resizable_addLastColumn : false, - resizable_widths : [], - resizable_throttle : false, // set to true (5ms) or any number 0-10 range - resizable_targetLast : false, - resizable_fullWidth : null - }, - init: function(table, thisWidget, c, wo) { - ts.resizable.init( c, wo ); - }, - remove: function( table, c, wo, refreshing ) { - if (wo.$resizable_container) { + // prevent text selection while dragging resize bar + toggleTextSelection : function( c, toggle ) { var namespace = c.namespace + 'tsresize'; - c.$table.add( $( c.namespace + '_extra_table' ) ) - .removeClass('hasResizable') - .children( 'thead' ).unbind( 'contextmenu' + namespace ); + c.widgetOptions.resizable_vars.disabled = toggle; + $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); + if ( toggle ) { + $( 'body' ) + .attr( 'unselectable', 'on' ) + .bind( 'selectstart' + namespace, false ); + } else { + $( 'body' ) + .removeAttr( 'unselectable' ) + .unbind( 'selectstart' + namespace ); + } + }, + + bindings : function( c, wo ) { + var namespace = c.namespace + 'tsresize'; + wo.$resizable_container.children().bind( 'mousedown', function( event ) { + // save header cell and mouse position + var column, + vars = wo.resizable_vars, + $extras = $( c.namespace + '_extra_headers' ), + $header = $( event.target ).data( 'header' ); + + column = parseInt( $header.attr( 'data-column' ), 10 ); + vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); + vars.target = column; + + // if table is not as wide as it's parent, then resize the table + vars.$next = event.shiftKey || wo.resizable_targetLast ? + $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : + $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); + + column = parseInt( vars.$next.attr( 'data-column' ), 10 ); + vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); + vars.next = column; + + vars.mouseXPosition = event.pageX; + ts.resizable.updateStoredSizes( c, wo ); + ts.resizable.toggleTextSelection( c, true ); + }); + + $( document ) + .bind( 'mousemove' + namespace, function( event ) { + var vars = wo.resizable_vars; + // ignore mousemove if no mousedown + if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } + if ( wo.resizable_throttle ) { + clearTimeout( vars.timer ); + vars.timer = setTimeout( function() { + ts.resizable.mouseMove( c, wo, event ); + }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); + } else { + ts.resizable.mouseMove( c, wo, event ); + } + }) + .bind( 'mouseup' + namespace, function() { + if (!wo.resizable_vars.disabled) { return; } + ts.resizable.toggleTextSelection( c, false ); + ts.resizable.stopResize( c, wo ); + ts.resizable.setHandlePosition( c, wo ); + }); + + // resizeEnd event triggered by scroller widget + $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }); + + // right click to reset columns to default widths + c.$table + .bind( 'columnUpdate' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }) + .find( 'thead:first' ) + .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) + .bind( 'contextmenu' + namespace, function() { + // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset + var allowClick = wo.resizable_vars.storedSizes.length === 0; + ts.resizableReset( c.table ); + ts.resizable.setHandlePosition( c, wo ); + wo.resizable_vars.storedSizes = []; + return allowClick; + }); + + }, + + mouseMove : function( c, wo, event ) { + if ( wo.resizable_vars.mouseXPosition === 0 || !wo.resizable_vars.$target ) { return; } + // resize columns + var column, + total = 0, + vars = wo.resizable_vars, + $next = vars.$next, + tar = vars.storedSizes[ vars.target ], + leftEdge = event.pageX - vars.mouseXPosition; + if ( vars.overflow ) { + if ( tar + leftEdge > 0 ) { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); + // update the entire table width + for ( column = 0; column < c.columns; column++ ) { + total += vars.storedSizes[ column ]; + } + ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); + } + if ( !$next.length ) { + // if expanding right-most column, scroll the wrapper + vars.$wrap[0].scrollLeft = c.$table.width(); + } + } else if ( vars.fullWidth ) { + vars.storedSizes[ vars.target ] += leftEdge; + vars.storedSizes[ vars.next ] -= leftEdge; + ts.resizable.setWidths( c, wo ); + } else { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidths( c, wo ); + } + vars.mouseXPosition = event.pageX; + // dynamically update sticky header widths + c.$table.trigger('stickyHeadersUpdate'); + }, + + stopResize : function( c, wo ) { + var vars = wo.resizable_vars; + ts.resizable.updateStoredSizes( c, wo ); + if ( vars.useStorage ) { + // save all column widths + ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); + ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); + } + vars.mouseXPosition = 0; + vars.$target = vars.$next = null; + // will update stickyHeaders, just in case, see #912 + c.$table.trigger('stickyHeadersUpdate'); + } + }; + + // this widget saves the column widths if + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'resizable', + priority: 40, + options: { + resizable : true, // save column widths to storage + resizable_addLastColumn : false, + resizable_widths : [], + resizable_throttle : false, // set to true (5ms) or any number 0-10 range + resizable_targetLast : false, + resizable_fullWidth : null + }, + init: function(table, thisWidget, c, wo) { + ts.resizable.init( c, wo ); + }, + remove: function( table, c, wo, refreshing ) { + if (wo.$resizable_container) { + var namespace = c.namespace + 'tsresize'; + c.$table.add( $( c.namespace + '_extra_table' ) ) + .removeClass('hasResizable') + .children( 'thead' ) + .unbind( 'contextmenu' + namespace ); wo.$resizable_container.remove(); - ts.resizable.toggleTextSelection( c, false ); - ts.resizableReset( table, refreshing ); - $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); - } - } -}); - -ts.resizableReset = function( table, refreshing ) { - $( table ).each(function(){ - var index, $t, - c = this.config, - wo = c && c.widgetOptions, - vars = wo.resizable_; - if ( table && c && c.$headerIndexed.length ) { - // restore the initial table width - if ( vars.overflow && vars.tableWidth ) { - ts.resizable.setWidth( c.$table, vars.tableWidth, true ); - if ( vars.useStorage ) { - ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); - } - } - for ( index = 0; index < c.columns; index++ ) { - $t = c.$headerIndexed[ index ]; - if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { - ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); - } else if ( !$t.hasClass( 'resizable-false' ) ) { - // don't clear the width of any column that is not resizable - ts.resizable.setWidth( $t, '', vars.overflow ); - } - } - - // reset stickyHeader widths - c.$table.trigger( 'stickyHeadersUpdate' ); - if ( ts.storage && !refreshing ) { - ts.storage( this, ts.css.resizableStorage, {} ); + ts.resizable.toggleTextSelection( c, false ); + ts.resizableReset( table, refreshing ); + $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); } } }); -}; + + ts.resizableReset = function( table, refreshing ) { + $( table ).each(function(){ + var index, $t, + c = this.config, + wo = c && c.widgetOptions, + vars = wo.resizable_vars; + if ( table && c && c.$headerIndexed.length ) { + // restore the initial table width + if ( vars.overflow && vars.tableWidth ) { + ts.resizable.setWidth( c.$table, vars.tableWidth, true ); + if ( vars.useStorage ) { + ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); + } + } + for ( index = 0; index < c.columns; index++ ) { + $t = c.$headerIndexed[ index ]; + if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { + ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); + } else if ( !$t.hasClass( 'resizable-false' ) ) { + // don't clear the width of any column that is not resizable + ts.resizable.setWidth( $t, '', vars.overflow ); + } + } + + // reset stickyHeader widths + c.$table.trigger( 'stickyHeadersUpdate' ); + if ( ts.storage && !refreshing ) { + ts.storage( this, ts.css.resizableStorage, {} ); + } + } + }); + }; })( jQuery, window ); /*! Widget: saveSort */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -// this widget saves the last sort only if the -// saveSort widget option is true AND the -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: 'saveSort', - priority: 20, - options: { - saveSort : true - }, - init: function(table, thisWidget, c, wo) { - // run widget format before all other widgets are applied to the table - thisWidget.format(table, c, wo, true); - }, - format: function(table, c, wo, init) { - var stored, time, - $table = c.$table, - saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true - sortList = { "sortList" : c.sortList }; - if (c.debug) { - time = new Date(); - } - if ($table.hasClass('hasSaveSort')) { - if (saveSort && table.hasInitialized && ts.storage) { - ts.storage( table, 'tablesorter-savesort', sortList ); - if (c.debug) { - ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + // this widget saves the last sort only if the + // saveSort widget option is true AND the + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'saveSort', + priority: 20, + options: { + saveSort : true + }, + init: function(table, thisWidget, c, wo) { + // run widget format before all other widgets are applied to the table + thisWidget.format(table, c, wo, true); + }, + format: function(table, c, wo, init) { + var stored, time, + $table = c.$table, + saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true + sortList = { 'sortList' : c.sortList }; + if (c.debug) { + time = new Date(); + } + if ($table.hasClass('hasSaveSort')) { + if (saveSort && table.hasInitialized && ts.storage) { + ts.storage( table, 'tablesorter-savesort', sortList ); + if (c.debug) { + ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + } + } + } else { + // set table sort on initial run of the widget + $table.addClass('hasSaveSort'); + sortList = ''; + // get data + if (ts.storage) { + stored = ts.storage( table, 'tablesorter-savesort' ); + sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; + if (c.debug) { + ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); + } + $table.bind('saveSortReset', function(event) { + event.stopPropagation(); + ts.storage( table, 'tablesorter-savesort', '' ); + }); + } + // init is true when widget init is run, this will run this widget before all other widgets have initialized + // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. + if (init && sortList && sortList.length > 0) { + c.sortList = sortList; + } else if (table.hasInitialized && sortList && sortList.length > 0) { + // update sort change + $table.trigger('sorton', [ sortList ]); } } - } else { - // set table sort on initial run of the widget - $table.addClass('hasSaveSort'); - sortList = ''; - // get data - if (ts.storage) { - stored = ts.storage( table, 'tablesorter-savesort' ); - sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; - if (c.debug) { - ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); - } - $table.bind('saveSortReset', function(event) { - event.stopPropagation(); - ts.storage( table, 'tablesorter-savesort', '' ); - }); - } - // init is true when widget init is run, this will run this widget before all other widgets have initialized - // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. - if (init && sortList && sortList.length > 0) { - c.sortList = sortList; - } else if (table.hasInitialized && sortList && sortList.length > 0) { - // update sort change - $table.trigger('sorton', [sortList]); - } + }, + remove: function(table, c) { + c.$table.removeClass('hasSaveSort'); + // clear storage + if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } } - }, - remove: function(table, c) { - c.$table.removeClass('hasSaveSort'); - // clear storage - if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } - } -}); + }); })(jQuery); diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index 7d4342e4..a1d17d00 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -188,7 +188,7 @@ // node could be a jquery object // http://jsperf.com/jquery-vs-instanceof-jquery/2 $node = node.jquery ? node : $(node); - if (typeof(t) === 'string') { + if (typeof t === 'string') { // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow! // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/ if ( t === 'basic' && typeof ( te = $node.attr(c.textAttribute) ) !== 'undefined' ) { @@ -196,7 +196,7 @@ } return $.trim( node.textContent || $node.text() ); } else { - if (typeof(t) === 'function') { + if (typeof t === 'function') { return $.trim( t($node[0], c.table, cellIndex) ); } else if (typeof (te = ts.getColumnData( c.table, t, cellIndex )) === 'function') { return $.trim( te($node[0], c.table, cellIndex) ); @@ -256,7 +256,7 @@ // make sure txt is a string (extractor may have converted it) parser.format( '' + txt, c.table, cell, colIndex ); if ( c.ignoreCase && typeof val === 'string' ) { - val = val.toLowerCase(); + val = val.toLowerCase(); } } return val; @@ -500,20 +500,22 @@ // set up header template t = c.headerTemplate.replace(/\{content\}/g, $t.html()).replace(/\{icon\}/g, $t.find('.' + ts.css.icon).length ? '' : i); if (c.onRenderTemplate) { - h = c.onRenderTemplate.apply($t, [index, t]); + h = c.onRenderTemplate.apply( $t, [ index, t ] ); if (h && typeof h === 'string') { t = h; } // only change t if something is returned } $t.html('
' + t + '
'); // faster than wrapInner } - if (c.onRenderHeader) { c.onRenderHeader.apply($t, [index, c, c.$table]); } + if (c.onRenderHeader) { c.onRenderHeader.apply( $t, [ index, c, c.$table ] ); } // *** remove this.column value if no conflicts found elem.column = parseInt( $t.attr('data-column'), 10); - elem.order = formatSortingOrder( ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder ) ? [1,0,2] : [0,1,2]; + elem.order = formatSortingOrder( ts.getData( $t, ch, 'sortInitialOrder' ) || c.sortInitialOrder ) ? + [ 1, 0, 2 ] : // desc, asc, unsorted + [ 0, 1, 2 ]; // asc, desc, unsorted elem.count = -1; // set to -1 because clicking on the header automatically adds one elem.lockedOrder = false; lock = ts.getData($t, ch, 'lockedOrder') || false; if (typeof lock !== 'undefined' && lock !== false) { - elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [1,1,1] : [0,0,0]; + elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [ 1, 1, 1 ] : [ 0, 0, 0 ]; } $t.addClass(ts.css.header + ' ' + c.cssHeader); // add cell to headerList @@ -583,9 +585,9 @@ list = c.sortList, len = list.length, none = ts.css.sortNone + ' ' + c.cssNone, - css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc], + css = [ ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc ], cssIcon = [ c.cssIconAsc, c.cssIconDesc, c.cssIconNone ], - aria = ['ascending', 'descending'], + aria = [ 'ascending', 'descending' ], // find the footer $t = $(table).find('tfoot tr').children() .add( $( c.namespace + '_extra_headers' ) ) @@ -655,7 +657,7 @@ dir = ('' + val[1]).match(/^(1|d|s|o|n)/); dir = dir ? dir[0] : ''; // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext - switch(dir) { + switch (dir) { case '1': case 'd': // descending dir = 1; break; @@ -731,11 +733,11 @@ // add column to sort list order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -746,7 +748,7 @@ for (col = 0; col < c.sortAppend.length; col++) { s = ts.isValueInArray(c.sortAppend[col][0], c.sortList); if (s >= 0) { - c.sortList.splice(s,1); + c.sortList.splice(s, 1); } } } @@ -760,7 +762,7 @@ // order.count seems to be incorrect when compared to cell.count s[1] = order.order[cell.count]; if (s[1] === 2) { - c.sortList.splice(col,1); + c.sortList.splice(col, 1); order.count = -1; } } @@ -769,11 +771,11 @@ // add column to sort list array order = cell.order[cell.count]; if (order < 2) { - c.sortList.push([indx, order]); + c.sortList.push([ indx, order ]); // add other columns if header spans across multiple if (cell.colSpan > 1) { for (col = 1; col < cell.colSpan; col++) { - c.sortList.push([indx + col, order]); + c.sortList.push([ indx + col, order ]); } } } @@ -847,10 +849,10 @@ x = dir ? a : b; y = dir ? b : a; // text sort function - if (typeof(cts) === 'function') { + if (typeof cts === 'function') { // custom OVERALL text sorter sort = cts(x[col], y[col], dir, col, table); - } else if (typeof(cts) === 'object' && cts.hasOwnProperty(col)) { + } else if (typeof cts === 'object' && cts.hasOwnProperty(col)) { // custom text sorter for a SPECIFIC COLUMN sort = cts[col](x[col], y[col], dir, col, table); } else { @@ -883,14 +885,14 @@ // this will catch spamming of the updateCell method if (resrt !== false && !c.serverSideSorting && !c.table.isProcessing) { if (sl.length) { - c.$table.trigger('sorton', [sl, function(){ + c.$table.trigger('sorton', [ sl, function(){ resortComplete(c, callback); - }, true]); + }, true ]); } else { - c.$table.trigger('sortReset', [function(){ + c.$table.trigger('sortReset', [ function(){ resortComplete(c, callback); ts.applyWidget(c.table, false); - }]); + } ]); } } else { resortComplete(c, callback); @@ -1090,10 +1092,10 @@ ts.construct = function(settings) { return this.each(function() { var table = this, - // merge & extend config options - c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); - // save initial settings - c.originalSettings = settings; + // merge & extend config options + c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods); + // save initial settings + c.originalSettings = settings; // create a table from data (build table widget) if (!table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE') { // return the table (in case the original target is the table's container) @@ -1148,7 +1150,7 @@ c.namespace = '.tablesorter' + Math.random().toString(16).slice(2); } else { // make sure namespace starts with a period & doesn't have weird characters - c.namespace = '.' + c.namespace.replace(/\W/g,''); + c.namespace = '.' + c.namespace.replace(/\W/g, ''); } c.$table.children().children('tr').attr('role', 'row'); @@ -1192,7 +1194,7 @@ ts.applyWidget(table, true); // if user has supplied a sort list to constructor if (c.sortList.length > 0) { - $table.trigger('sorton', [c.sortList, {}, !c.initWidgets, true]); + $table.trigger('sorton', [ c.sortList, {}, !c.initWidgets, true ]); } else { setHeadersCss(table); if (c.initWidgets) { @@ -1231,22 +1233,22 @@ table = $(table)[0]; var overallWidth, percent, $tbodies, len, index, c = table.config, - colgroup = c.$table.children('colgroup'); + $colgroup = c.$table.children('colgroup'); // remove plugin-added colgroup, in case we need to refresh the widths - if (colgroup.length && colgroup.hasClass(ts.css.colgroup)) { - colgroup.remove(); + if ($colgroup.length && $colgroup.hasClass(ts.css.colgroup)) { + $colgroup.remove(); } if (c.widthFixed && c.$table.children('colgroup').length === 0) { - colgroup = $(''); + $colgroup = $(''); overallWidth = c.$table.width(); // only add col for visible columns - fixes #371 - $tbodies = c.$tbodies.find('tr:first').children(':visible'); //.each(function() + $tbodies = c.$tbodies.find('tr:first').children(':visible'); // .each(function() len = $tbodies.length; for ( index = 0; index < len; index++ ) { percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%'; - colgroup.append( $('').css('width', percent) ); + $colgroup.append( $('').css('width', percent) ); } - c.$table.prepend(colgroup); + c.$table.prepend($colgroup); } }; @@ -1293,12 +1295,12 @@ cellId = rowIndex + '-' + $cell.index(); rowSpan = cell.rowSpan || 1; colSpan = cell.colSpan || 1; - if (typeof(matrix[rowIndex]) === 'undefined') { + 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') { + if (typeof matrix[rowIndex][k] === 'undefined') { firstAvailCol = k; break; } @@ -1307,7 +1309,7 @@ // add data-column $cell.attr({ 'data-column' : firstAvailCol }); // 'data-row' : rowIndex for (k = rowIndex; k < rowIndex + rowSpan; k++) { - if (typeof(matrix[k]) === 'undefined') { + if (typeof matrix[k] === 'undefined') { matrix[k] = []; } matrixrow = matrix[k]; @@ -1323,10 +1325,10 @@ // *** Process table *** // add processing indicator ts.isProcessing = function(table, toggle, $ths) { - table = $(table); - var c = table[0].config, + $table = $(table); + var c = $table[0].config, // default to all headers - $h = $ths || table.find('.' + ts.css.header); + $h = $ths || $table.find('.' + ts.css.header); if (toggle) { // don't use sortList if custom $ths used if (typeof $ths !== 'undefined' && c.sortList.length > 0) { @@ -1336,9 +1338,9 @@ return this.sortDisabled ? false : ts.isValueInArray( parseFloat($(this).attr('data-column')), c.sortList) >= 0; }); } - table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing); } else { - table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); + $table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing); } }; @@ -1468,8 +1470,8 @@ $f = $t.find('tfoot:first > tr').children('th, td'); if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) { // reapply uitheme classes, in case we want to maintain appearance - $t.trigger('applyWidgetId', ['uitheme']); - $t.trigger('applyWidgetId', ['zebra']); + $t.trigger('applyWidgetId', [ 'uitheme' ]); + $t.trigger('applyWidgetId', [ 'zebra' ]); } // remove widget added rows, just in case $h.find('tr').not($r).remove(); @@ -1481,7 +1483,7 @@ .removeData('tablesorter') .unbind( events.replace(/\s+/g, ' ') ); c.$headers.add($f) - .removeClass( [ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' ') ) + .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join(' ') ) .removeAttr('data-column') .removeAttr('aria-label') .attr('aria-disabled', 'true'); @@ -1687,8 +1689,8 @@ }; ts.hasWidget = function(table, name){ - table = $(table); - return table.length && table[0].config && table[0].config.widgetInit[name] || false; + $table = $(table); + return $table.length && $table[0].config && $table[0].config.widgetInit[name] || false; }; ts.getWidgetById = function(name) { @@ -1728,7 +1730,7 @@ if (c.debug) { time = new Date(); } // look for widgets to apply from in table class // stop using \b otherwise this matches 'ui-widget-content' & adds 'content' widget - wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' )+ '\\s', 'g' ); + wd = new RegExp( '\\s' + c.widgetClass.replace( /\{name\}/i, '([\\w-]+)' ) + '\\s', 'g' ); if ( tableClass.match( wd ) ) { // extract out the widget id from the table class (widget id's can include dashes) w = tableClass.match( wd ); @@ -1936,13 +1938,13 @@ typeof table !== 'undefined' ? table : true; if (t) { // US Format - 1,234,567.89 -> 1234567.89 - s = s.replace(/,/g,''); + 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,'.'); + s = s.replace(/[\s|\.]/g, '').replace(/,/g, '.'); } - if(/^\s*\([.\d]+\)/.test(s)) { + if (/^\s*\([.\d]+\)/.test(s)) { // make (#) into a negative number -> (10) = -10 s = s.replace(/^\s*\(([.\d]+)\)/, '-$1'); } @@ -2010,7 +2012,7 @@ ts.addParser({ id: 'currency', is: function(s) { - return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g,'')); // £$€¤¥¢ + return (/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/).test((s || '').replace(/[+\-,. ]/g, '')); // £$€¤¥¢ }, format: function(s, table) { var n = ts.formatFloat((s || '').replace(/[^\w,. \-()]/g, ''), table); @@ -2085,7 +2087,7 @@ id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd' is: function(s) { // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included - return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g,' ').replace(/[\-.,]/g, '/')); + return (/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/).test((s || '').replace(/\s+/g, ' ').replace(/[\-.,]/g, '/')); }, format: function(s, table, cell, cellIndex) { if (s) { diff --git a/js/jquery.tablesorter.widgets.js b/js/jquery.tablesorter.widgets.js index 61746be2..2f2d7734 100644 --- a/js/jquery.tablesorter.widgets.js +++ b/js/jquery.tablesorter.widgets.js @@ -18,356 +18,356 @@ /*! Widget: storage - updated 3/26/2015 (v2.21.3) */ ;(function ($, window, document) { -'use strict'; + 'use strict'; -var ts = $.tablesorter || {}; -// *** Store data in local storage, with a cookie fallback *** -/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) - if you need it, then include https://github.com/douglascrockford/JSON-js + var ts = $.tablesorter || {}; + // *** Store data in local storage, with a cookie fallback *** + /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) + if you need it, then include https://github.com/douglascrockford/JSON-js - $.parseJSON is not available is jQuery versions older than 1.4.1, using older - versions will only allow storing information for one page at a time + $.parseJSON is not available is jQuery versions older than 1.4.1, using older + versions will only allow storing information for one page at a time - // *** Save data (JSON format only) *** - // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid - var val = { "mywidget" : "data1" }; // valid JSON uses double quotes - // $.tablesorter.storage(table, key, val); - $.tablesorter.storage(table, 'tablesorter-mywidget', val); + // *** Save data (JSON format only) *** + // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid + var val = { "mywidget" : "data1" }; // valid JSON uses double quotes + // $.tablesorter.storage(table, key, val); + $.tablesorter.storage(table, 'tablesorter-mywidget', val); - // *** Get data: $.tablesorter.storage(table, key); *** - v = $.tablesorter.storage(table, 'tablesorter-mywidget'); - // val may be empty, so also check for your data - val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; - alert(val); // "data1" if saved, or "" if not -*/ -ts.storage = function(table, key, value, options) { - table = $(table)[0]; - var cookieIndex, cookies, date, - hasStorage = false, - values = {}, - c = table.config, - wo = c && c.widgetOptions, - storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? - 'sessionStorage' : 'localStorage', - $table = $(table), - // id from (1) options ID, (2) table "data-table-group" attribute, (3) widgetOptions.storage_tableId, - // (4) table ID, then (5) table index - id = options && options.id || - $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || - wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), - // url from (1) options url, (2) table "data-table-page" attribute, (3) widgetOptions.storage_fixedUrl, - // (4) table.config.fixedUrl (deprecated), then (5) window location path - url = options && options.url || - $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || - wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; - // https://gist.github.com/paulirish/5558557 - if (storageType in window) { - try { - window[storageType].setItem('_tmptest', 'temp'); - hasStorage = true; - window[storageType].removeItem('_tmptest'); - } catch(error) { - if (c && c.debug) { - ts.log( storageType + ' is not supported in this browser' ); + // *** Get data: $.tablesorter.storage(table, key); *** + v = $.tablesorter.storage(table, 'tablesorter-mywidget'); + // val may be empty, so also check for your data + val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; + alert(val); // 'data1' if saved, or '' if not + */ + ts.storage = function(table, key, value, options) { + table = $(table)[0]; + var cookieIndex, cookies, date, + hasStorage = false, + values = {}, + c = table.config, + wo = c && c.widgetOptions, + storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? + 'sessionStorage' : 'localStorage', + $table = $(table), + // id from (1) options ID, (2) table 'data-table-group' attribute, (3) widgetOptions.storage_tableId, + // (4) table ID, then (5) table index + id = options && options.id || + $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || + wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), + // url from (1) options url, (2) table 'data-table-page' attribute, (3) widgetOptions.storage_fixedUrl, + // (4) table.config.fixedUrl (deprecated), then (5) window location path + url = options && options.url || + $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || + wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; + // https://gist.github.com/paulirish/5558557 + if (storageType in window) { + try { + window[storageType].setItem('_tmptest', 'temp'); + hasStorage = true; + window[storageType].removeItem('_tmptest'); + } catch (error) { + if (c && c.debug) { + ts.log( storageType + ' is not supported in this browser' ); + } } } - } - // *** get value *** - if ($.parseJSON) { - if (hasStorage) { - values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + // *** get value *** + if ($.parseJSON) { + if (hasStorage) { + values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + } else { + // old browser, using cookies + cookies = document.cookie.split(/[;\s|=]/); + // add one to get from the key to the value + cookieIndex = $.inArray(key, cookies) + 1; + values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + } + } + // allow value to be an empty string too + if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { + // add unique identifiers = url pathname > table ID/index on page > data + if (!values[url]) { + values[url] = {}; + } + values[url][id] = value; + // *** set value *** + if (hasStorage) { + window[storageType][key] = JSON.stringify(values); + } else { + date = new Date(); + date.setTime(date.getTime() + (31536e+6)); // 365 days + document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/'; + } } else { - // old browser, using cookies - cookies = document.cookie.split(/[;\s|=]/); - // add one to get from the key to the value - cookieIndex = $.inArray(key, cookies) + 1; - values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + return values && values[url] ? values[url][id] : ''; } - } - // allow value to be an empty string too - if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { - // add unique identifiers = url pathname > table ID/index on page > data - if (!values[url]) { - values[url] = {}; - } - values[url][id] = value; - // *** set value *** - if (hasStorage) { - window[storageType][key] = JSON.stringify(values); - } else { - date = new Date(); - date.setTime(date.getTime() + (31536e+6)); // 365 days - document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/'; - } - } else { - return values && values[url] ? values[url][id] : ''; - } -}; + }; })(jQuery, window, document); /*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.themes = { - 'bootstrap' : { - table : 'table table-bordered table-striped', - caption : 'caption', - // header class names - header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) - sortNone : '', - sortAsc : '', - sortDesc : '', - active : '', // applied when column is sorted - hover : '', // custom css required - a defined bootstrap style may not override other classes - // icon class names - icons : '', // add "icon-white" to make them white; this icon class is added to the in the header - iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted - iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort - iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort - filterRow : '', // filter row class - footerRow : '', - footerCells : '', - even : '', // even row zebra striping - odd : '' // odd row zebra striping - }, - 'jui' : { - table : 'ui-widget ui-widget-content ui-corner-all', // table classes - caption : 'ui-widget-content', - // header class names - header : 'ui-widget-header ui-corner-all ui-state-default', // header classes - sortNone : '', - sortAsc : '', - sortDesc : '', - active : 'ui-state-active', // applied when column is sorted - hover : 'ui-state-hover', // hover class - // icon class names - icons : 'ui-icon', // icon class added to the in the header - iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted - iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort - iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort - filterRow : '', - footerRow : '', - footerCells : '', - even : 'ui-widget-content', // even row zebra striping - odd : 'ui-state-default' // odd row zebra striping - } -}; + ts.themes = { + 'bootstrap' : { + table : 'table table-bordered table-striped', + caption : 'caption', + // header class names + header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) + sortNone : '', + sortAsc : '', + sortDesc : '', + active : '', // applied when column is sorted + hover : '', // custom css required - a defined bootstrap style may not override other classes + // icon class names + icons : '', // add 'icon-white' to make them white; this icon class is added to the in the header + iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted + iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort + iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort + filterRow : '', // filter row class + footerRow : '', + footerCells : '', + even : '', // even row zebra striping + odd : '' // odd row zebra striping + }, + 'jui' : { + table : 'ui-widget ui-widget-content ui-corner-all', // table classes + caption : 'ui-widget-content', + // header class names + header : 'ui-widget-header ui-corner-all ui-state-default', // header classes + sortNone : '', + sortAsc : '', + sortDesc : '', + active : 'ui-state-active', // applied when column is sorted + hover : 'ui-state-hover', // hover class + // icon class names + icons : 'ui-icon', // icon class added to the in the header + iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted + iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort + iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort + filterRow : '', + footerRow : '', + footerCells : '', + even : 'ui-widget-content', // even row zebra striping + odd : 'ui-state-default' // odd row zebra striping + } + }; -$.extend(ts.css, { - wrapper : 'tablesorter-wrapper' // ui theme & resizable -}); + $.extend(ts.css, { + wrapper : 'tablesorter-wrapper' // ui theme & resizable + }); -ts.addWidget({ - id: "uitheme", - priority: 10, - format: function(table, c, wo) { - var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, - themesAll = ts.themes, - $table = c.$table.add( $( c.namespace + '_extra_table' ) ), - $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), - theme = c.theme || 'jui', - themes = themesAll[theme] || {}, - remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), - iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); - if (c.debug) { time = new Date(); } - // initialization code - run once - if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { - wo.uitheme_applied = true; - oldtheme = themesAll[c.appliedTheme] || {}; - hasOldTheme = !$.isEmptyObject(oldtheme); - oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; - oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; - if (hasOldTheme) { - wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); - wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); - c.$tbodies.children().removeClass( [oldtheme.even, oldtheme.odd].join(' ') ); - } - // update zebra stripes - if (themes.even) { wo.zebra[0] += ' ' + themes.even; } - if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } - // add caption style - $table.children('caption') - .removeClass(oldtheme.caption || '') - .addClass(themes.caption); - // add table/footer class names - $tfoot = $table - // remove other selected themes - .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) - .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name - .children('tfoot'); - c.appliedTheme = c.theme; - - if ($tfoot.length) { - $tfoot - // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed - .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) - .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); - } - // update header classes - $headers - .removeClass( (hasOldTheme ? [oldtheme.header, oldtheme.hover, oldremove].join(' ') : '') || '' ) - .addClass(themes.header) - .not('.sorter-false') - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') - .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { - // toggleClass with switch added in jQuery 1.3 - $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); - }); - - $headers.each(function(){ - var $this = $(this); - if (!$this.find('.' + ts.css.wrapper).length) { - // Firefox needs this inner div to position the icon & resizer correctly - $this.wrapInner('
'); + ts.addWidget({ + id: 'uitheme', + priority: 10, + format: function(table, c, wo) { + var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, + themesAll = ts.themes, + $table = c.$table.add( $( c.namespace + '_extra_table' ) ), + $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), + theme = c.theme || 'jui', + themes = themesAll[theme] || {}, + remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), + iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); + if (c.debug) { time = new Date(); } + // initialization code - run once + if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { + wo.uitheme_applied = true; + oldtheme = themesAll[c.appliedTheme] || {}; + hasOldTheme = !$.isEmptyObject(oldtheme); + oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; + oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; + if (hasOldTheme) { + wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); + wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); + c.$tbodies.children().removeClass( [ oldtheme.even, oldtheme.odd ].join(' ') ); } - }); - if (c.cssIcon) { - // if c.cssIcon is '', then no is added to the header + // update zebra stripes + if (themes.even) { wo.zebra[0] += ' ' + themes.even; } + if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } + // add caption style + $table.children('caption') + .removeClass(oldtheme.caption || '') + .addClass(themes.caption); + // add table/footer class names + $tfoot = $table + // remove other selected themes + .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) + .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name + .children('tfoot'); + c.appliedTheme = c.theme; + + if ($tfoot.length) { + $tfoot + // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed + .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) + .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); + } + // update header classes $headers - .find('.' + ts.css.icon) - .removeClass(hasOldTheme ? [oldtheme.icons, oldIconRmv].join(' ') : '') - .addClass(themes.icons || ''); - } - if ($table.hasClass('hasFilters')) { - $table.children('thead').children('.' + ts.css.filterRow) - .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') - .addClass(themes.filterRow || ''); - } - } - for (i = 0; i < c.columns; i++) { - $header = c.$headers - .add($(c.namespace + '_extra_headers')) - .not('.sorter-false') - .filter('[data-column="' + i + '"]'); - $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); - $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); - if ($h.length) { - $header.removeClass(remove); - $icon.removeClass(iconRmv); - if ($h[0].sortDisabled) { - // no sort arrows for disabled columns! - $icon.removeClass(themes.icons || ''); - } else { - hdr = themes.sortNone; - icon = themes.iconSortNone; - if ($h.hasClass(ts.css.sortAsc)) { - hdr = [themes.sortAsc, themes.active].join(' '); - icon = themes.iconSortAsc; - } else if ($h.hasClass(ts.css.sortDesc)) { - hdr = [themes.sortDesc, themes.active].join(' '); - icon = themes.iconSortDesc; + .removeClass( (hasOldTheme ? [ oldtheme.header, oldtheme.hover, oldremove ].join(' ') : '') || '' ) + .addClass(themes.header) + .not('.sorter-false') + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') + .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { + // toggleClass with switch added in jQuery 1.3 + $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); + }); + + $headers.each(function(){ + var $this = $(this); + if (!$this.find('.' + ts.css.wrapper).length) { + // Firefox needs this inner div to position the icon & resizer correctly + $this.wrapInner('
'); } - $header.addClass(hdr); - $icon.addClass(icon || ''); + }); + if (c.cssIcon) { + // if c.cssIcon is '', then no is added to the header + $headers + .find('.' + ts.css.icon) + .removeClass(hasOldTheme ? [ oldtheme.icons, oldIconRmv ].join(' ') : '') + .addClass(themes.icons || ''); + } + if ($table.hasClass('hasFilters')) { + $table.children('thead').children('.' + ts.css.filterRow) + .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') + .addClass(themes.filterRow || ''); } } + for (i = 0; i < c.columns; i++) { + $header = c.$headers + .add($(c.namespace + '_extra_headers')) + .not('.sorter-false') + .filter('[data-column="' + i + '"]'); + $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); + $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); + if ($h.length) { + $header.removeClass(remove); + $icon.removeClass(iconRmv); + if ($h[0].sortDisabled) { + // no sort arrows for disabled columns! + $icon.removeClass(themes.icons || ''); + } else { + hdr = themes.sortNone; + icon = themes.iconSortNone; + if ($h.hasClass(ts.css.sortAsc)) { + hdr = [ themes.sortAsc, themes.active ].join(' '); + icon = themes.iconSortAsc; + } else if ($h.hasClass(ts.css.sortDesc)) { + hdr = [ themes.sortDesc, themes.active ].join(' '); + icon = themes.iconSortDesc; + } + $header.addClass(hdr); + $icon.addClass(icon || ''); + } + } + } + if (c.debug) { + ts.benchmark('Applying ' + theme + ' theme', time); + } + }, + remove: function(table, c, wo, refreshing) { + if (!wo.uitheme_applied) { return; } + var $table = c.$table, + theme = c.appliedTheme || 'jui', + themes = ts.themes[ theme ] || ts.themes.jui, + $headers = $table.children('thead').children(), + remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, + iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; + $table.removeClass('tablesorter-' + theme + ' ' + themes.table); + wo.uitheme_applied = false; + if (refreshing) { return; } + $table.find(ts.css.header).removeClass(themes.header); + $headers + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover + .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) + .filter('.' + ts.css.filterRow) + .removeClass(themes.filterRow); + $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); } - if (c.debug) { - ts.benchmark("Applying " + theme + " theme", time); - } - }, - remove: function(table, c, wo, refreshing) { - if (!wo.uitheme_applied) { return; } - var $table = c.$table, - theme = c.appliedTheme || 'jui', - themes = ts.themes[ theme ] || ts.themes.jui, - $headers = $table.children('thead').children(), - remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, - iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; - $table.removeClass('tablesorter-' + theme + ' ' + themes.table); - wo.uitheme_applied = false; - if (refreshing) { return; } - $table.find(ts.css.header).removeClass(themes.header); - $headers - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover - .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) - .filter('.' + ts.css.filterRow) - .removeClass(themes.filterRow); - $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); - } -}); + }); })(jQuery); /*! Widget: columns */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.addWidget({ - id: "columns", - priority: 30, - options : { - columns : [ "primary", "secondary", "tertiary" ] - }, - format: function(table, c, wo) { - var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, + ts.addWidget({ + id: 'columns', + priority: 30, + options : { + columns : [ 'primary', 'secondary', 'tertiary' ] + }, + format: function(table, c, wo) { + var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, $table = c.$table, $tbodies = c.$tbodies, sortList = c.sortList, len = sortList.length, // removed c.widgetColumns support - css = wo && wo.columns || [ "primary", "secondary", "tertiary" ], + css = wo && wo.columns || [ 'primary', 'secondary', 'tertiary' ], last = css.length - 1; remove = css.join(' '); - // check if there is a sort (on initialization there may not be one) - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody - $rows = $tbody.children('tr'); - // loop through the visible rows - $rows.each(function() { - $row = $(this); - if (this.style.display !== 'none') { - // remove all columns class names - $cells = $row.children().removeClass(remove); - // add appropriate column class names - if (sortList && sortList[0]) { - // primary sort column class - $cells.eq(sortList[0][0]).addClass(css[0]); - if (len > 1) { - for (indx = 1; indx < len; indx++) { - // secondary, tertiary, etc sort column classes - $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + // check if there is a sort (on initialization there may not be one) + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody + $rows = $tbody.children('tr'); + // loop through the visible rows + $rows.each(function() { + $row = $(this); + if (this.style.display !== 'none') { + // remove all columns class names + $cells = $row.children().removeClass(remove); + // add appropriate column class names + if (sortList && sortList[0]) { + // primary sort column class + $cells.eq(sortList[0][0]).addClass(css[0]); + if (len > 1) { + for (indx = 1; indx < len; indx++) { + // secondary, tertiary, etc sort column classes + $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + } } } } - } - }); - ts.processTbody(table, $tbody, false); - } - // add classes to thead and tfoot - rows = wo.columns_thead !== false ? ['thead tr'] : []; - if (wo.columns_tfoot !== false) { - rows.push('tfoot tr'); - } - if (rows.length) { - $rows = $table.find( rows.join(',') ).children().removeClass(remove); - if (len) { - for (indx = 0; indx < len; indx++) { - // add primary. secondary, tertiary, etc sort column classes - $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + }); + ts.processTbody(table, $tbody, false); + } + // add classes to thead and tfoot + rows = wo.columns_thead !== false ? [ 'thead tr' ] : []; + if (wo.columns_tfoot !== false) { + rows.push('tfoot tr'); + } + if (rows.length) { + $rows = $table.find( rows.join(',') ).children().removeClass(remove); + if (len) { + for (indx = 0; indx < len; indx++) { + // add primary. secondary, tertiary, etc sort column classes + $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + } } } + }, + remove: function(table, c, wo) { + var tbodyIndex, $tbody, + $tbodies = c.$tbodies, + remove = (wo.columns || [ 'primary', 'secondary', 'tertiary' ]).join(' '); + c.$headers.removeClass(remove); + c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody + $tbody.children('tr').each(function() { + $(this).children().removeClass(remove); + }); + ts.processTbody(table, $tbody, false); // restore tbody + } } - }, - remove: function(table, c, wo) { - var tbodyIndex, $tbody, - $tbodies = c.$tbodies, - remove = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' '); - c.$headers.removeClass(remove); - c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody - $tbody.children('tr').each(function() { - $(this).children().removeClass(remove); - }); - ts.processTbody(table, $tbody, false); // restore tbody - } - } -}); + }); })(jQuery); @@ -376,92 +376,92 @@ ts.addWidget({ * by Rob Garrison */ ;( function ( $ ) { -'use strict'; -var ts = $.tablesorter || {}, + 'use strict'; + var ts = $.tablesorter || {}, tscss = ts.css; -$.extend( tscss, { - filterRow : 'tablesorter-filter-row', - filter : 'tablesorter-filter', - filterDisabled : 'disabled', - filterRowHide : 'hideme' -}); + $.extend( tscss, { + filterRow : 'tablesorter-filter-row', + filter : 'tablesorter-filter', + filterDisabled : 'disabled', + filterRowHide : 'hideme' + }); -ts.addWidget({ - id: 'filter', - priority: 50, - options : { - filter_childRows : false, // if true, filter includes child row content in the search - filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped - filter_columnFilters : true, // if true, a filter will be added to the top of each table column - filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) - filter_cellFilter : '', // css class name added to the filter cell ( string or array ) - filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) - filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. - filter_excludeFilter : {}, // filters to exclude, per column - filter_external : '', // jQuery selector string ( or jQuery object ) of external filters - filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin - filter_formatter : null, // add custom filter elements to the filter row - filter_functions : null, // add custom filter functions using this option - filter_hideEmpty : true, // hide filter row when table is empty - filter_hideFilters : false, // collapse filter row when mouse leaves the area - filter_ignoreCase : true, // if true, make all searches case-insensitive - filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) - filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down - filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) - filter_reset : null, // jQuery selector string of an element used to reset the filters - filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters - filter_searchDelay : 300, // typing delay in milliseconds before starting a search - filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true - filter_selectSource : null, // include a function to return an array of values to be added to the column filter select - filter_startsWith : false, // if true, filter start from the beginning of the cell contents - filter_useParsedData : false, // filter all data using parsed content - filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used. - filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value - filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text - }, - format: function( table, c, wo ) { - if ( !c.$table.hasClass( 'hasFilters' ) ) { - ts.filter.init( table, c, wo ); + ts.addWidget({ + id: 'filter', + priority: 50, + options : { + filter_childRows : false, // if true, filter includes child row content in the search + filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped + filter_columnFilters : true, // if true, a filter will be added to the top of each table column + filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) + filter_cellFilter : '', // css class name added to the filter cell ( string or array ) + filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) + filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. + filter_excludeFilter : {}, // filters to exclude, per column + filter_external : '', // jQuery selector string ( or jQuery object ) of external filters + filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin + filter_formatter : null, // add custom filter elements to the filter row + filter_functions : null, // add custom filter functions using this option + filter_hideEmpty : true, // hide filter row when table is empty + filter_hideFilters : false, // collapse filter row when mouse leaves the area + filter_ignoreCase : true, // if true, make all searches case-insensitive + filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) + filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down + filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) + filter_reset : null, // jQuery selector string of an element used to reset the filters + filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters + filter_searchDelay : 300, // typing delay in milliseconds before starting a search + filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true + filter_selectSource : null, // include a function to return an array of values to be added to the column filter select + filter_startsWith : false, // if true, filter start from the beginning of the cell contents + filter_useParsedData : false, // filter all data using parsed content + filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used. + filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value + filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text + }, + format: function( table, c, wo ) { + if ( !c.$table.hasClass( 'hasFilters' ) ) { + ts.filter.init( table, c, wo ); + } + }, + remove: function( table, c, wo, refreshing ) { + var tbodyIndex, $tbody, + $table = c.$table, + $tbodies = c.$tbodies, + events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + $table + .removeClass( 'hasFilters' ) + // add .tsfilter namespace to all BUT search + .unbind( events.replace( /\s+/g, ' ' ) ) + // remove the filter row even if refreshing, because the column might have been moved + .find( '.' + tscss.filterRow ).remove(); + if ( refreshing ) { return; } + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody + $tbody.children().removeClass( wo.filter_filteredRow ).show(); + ts.processTbody( table, $tbody, false ); // restore tbody + } + if ( wo.filter_reset ) { + $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); + } } - }, - remove: function( table, c, wo, refreshing ) { - var tbodyIndex, $tbody, - $table = c.$table, - $tbodies = c.$tbodies, - events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - $table - .removeClass( 'hasFilters' ) - // add .tsfilter namespace to all BUT search - .unbind( events.replace( /\s+/g, ' ' ) ) - // remove the filter row even if refreshing, because the column might have been moved - .find( '.' + tscss.filterRow ).remove(); - if ( refreshing ) { return; } - for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody - $tbody.children().removeClass( wo.filter_filteredRow ).show(); - ts.processTbody( table, $tbody, false ); // restore tbody - } - if ( wo.filter_reset ) { - $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); - } - } -}); + }); -ts.filter = { + ts.filter = { - // regex used in filter 'check' functions - not for general use and not documented - regex: { - regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex - child : /tablesorter-childRow/, // child row class name; this gets updated in the script - filtered : /filtered/, // filtered (hidden) row class name; updated in the script - type : /undefined|number/, // check type - exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') - nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) - operators : /[<>=]/g, // replace operators - query : '(q|query)' // replace filter queries - }, + // regex used in filter 'check' functions - not for general use and not documented + regex: { + regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex + child : /tablesorter-childRow/, // child row class name; this gets updated in the script + filtered : /filtered/, // filtered (hidden) row class name; updated in the script + type : /undefined|number/, // check type + exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') + nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) + operators : /[<>=]/g, // replace operators + query : '(q|query)' // replace filter queries + }, // function( c, data ) { } // c = table.config // data.$row = jQuery object of the row currently being processed @@ -475,1508 +475,1508 @@ ts.filter = { // data.cacheArray = An array of parsed content from each table cell in the row being processed // data.index = column index; table = table element ( DOM ) // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class ) - types: { - or : function( c, data, vars ) { - if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { - var indx, filterMatched, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.orSplit ), - iFilter = data.iFilter.split( ts.filter.regex.orSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // filterMatched = data2.filter === '' && indx > 0 ? true - // look for an exact match with the 'or' unless the 'filter-match' class is found - filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); - if ( filterMatched ) { - return filterMatched; + types: { + or : function( c, data, vars ) { + if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { + var indx, filterMatched, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.orSplit ), + iFilter = data.iFilter.split( ts.filter.regex.orSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // filterMatched = data2.filter === '' && indx > 0 ? true + // look for an exact match with the 'or' unless the 'filter-match' class is found + filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); + if ( filterMatched ) { + return filterMatched; + } } + // may be null from processing types + return filterMatched || false; } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for an AND or && operator ( logical and ) - and : function( c, data, vars ) { - if ( ts.filter.regex.andTest.test( data.filter ) ) { - var indx, filterMatched, result, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.andSplit ), - iFilter = data.iFilter.split( ts.filter.regex.andSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) - // replace wild cards since /(a*)/i will match anything - .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // look for an exact match with the 'and' unless the 'filter-match' class is found - result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); - if ( indx === 0 ) { - filterMatched = result; + return null; + }, + // Look for an AND or && operator ( logical and ) + and : function( c, data, vars ) { + if ( ts.filter.regex.andTest.test( data.filter ) ) { + var indx, filterMatched, result, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.andSplit ), + iFilter = data.iFilter.split( ts.filter.regex.andSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) + // replace wild cards since /(a*)/i will match anything + .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // look for an exact match with the 'and' unless the 'filter-match' class is found + result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); + if ( indx === 0 ) { + filterMatched = result; + } else { + filterMatched = filterMatched && result; + } + } + // may be null from processing types + return filterMatched || false; + } + return null; + }, + // Look for regex + regex: function( c, data ) { + if ( ts.filter.regex.regex.test( data.filter ) ) { + var matches, + // cache regex per column for optimal speed + regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), + isRegex = regex instanceof RegExp; + try { + if ( !isRegex ) { + // force case insensitive search if ignoreCase option set? + // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } + data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); + } + matches = regex.test( data.exact ); + } catch ( error ) { + matches = false; + } + return matches; + } + return null; + }, + // Look for operators >, >=, < or <= + operators: function( c, data ) { + // ignore empty strings... because '' < 10 is true + if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { + var cachedValue, result, txt, + table = c.table, + index = data.index, + parsed = data.parsed[index], + query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), + parser = c.parsers[index], + savedSearch = query; + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || parser.type === 'numeric' ) { + txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); + result = ts.filter.parseFilter( c, txt, index, true ); + query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; + } + // iExact may be numeric - see issue #149; + // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) + if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && + typeof data.cache !== 'undefined' ) { + cachedValue = data.cache; } else { - filterMatched = filterMatched && result; + txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + cachedValue = ts.formatFloat( txt, table ); + } + if ( />/.test( data.iFilter ) ) { + result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; + } else if ( /= 0 ); } } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for regex - regex: function( c, data ) { - if ( ts.filter.regex.regex.test( data.filter ) ) { - var matches, - // cache regex per column for optimal speed - regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), - isRegex = regex instanceof RegExp; - try { - if ( !isRegex ) { - // force case insensitive search if ignoreCase option set? - // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } - data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); - } - matches = regex.test( data.exact ); - } catch ( error ) { - matches = false; + return null; + }, + // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric + exact: function( c, data ) { + /*jshint eqeqeq:false */ + if ( ts.filter.regex.exact.test( data.iFilter ) ) { + var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), + filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; } - return matches; - } - return null; - }, - // Look for operators >, >=, < or <= - operators: function( c, data ) { - // ignore empty strings... because '' < 10 is true - if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { - var cachedValue, result, txt, - table = c.table, - index = data.index, - parsed = data.parsed[index], - query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), - parser = c.parsers[index], - savedSearch = query; - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || parser.type === 'numeric' ) { - txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); - result = ts.filter.parseFilter( c, txt, index, true ); - query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; - } - // iExact may be numeric - see issue #149; - // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) - if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && - typeof data.cache !== 'undefined' ) { - cachedValue = data.cache; - } else { - txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - cachedValue = ts.formatFloat( txt, table ); - } - if ( />/.test( data.iFilter ) ) { - result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; - } else if ( /= 0 ); - } - } - return null; - }, - // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric - exact: function( c, data ) { - /*jshint eqeqeq:false */ - if ( ts.filter.regex.exact.test( data.iFilter ) ) { - var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), - filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; - } - return null; - }, - // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! - range : function( c, data ) { - if ( ts.filter.regex.toTest.test( data.iFilter ) ) { - var result, tmp, range1, range2, - table = c.table, - index = data.index, - parsed = data.parsed[index], - // make sure the dash is for a range and not indicating a negative number - query = data.iFilter.split( ts.filter.regex.toSplit ); + return null; + }, + // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! + range : function( c, data ) { + if ( ts.filter.regex.toTest.test( data.iFilter ) ) { + var result, tmp, range1, range2, + table = c.table, + index = data.index, + parsed = data.parsed[index], + // make sure the dash is for a range and not indicating a negative number + query = data.iFilter.split( ts.filter.regex.toSplit ); - tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; - range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; - range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || c.parsers[index].type === 'numeric' ) { - result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); - range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; - result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); - range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; - } - if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { - result = data.cache; - } else { - tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - result = ts.formatFloat( tmp, table ); - } - if ( range1 > range2 ) { - tmp = range1; range1 = range2; range2 = tmp; // swap - } - return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); - } - return null; - }, - // Look for wild card: ? = single, * = multiple, or | = logical OR - wild : function( c, data ) { - if ( /[\?\*\|]/.test( data.iFilter ) ) { - var index = data.index, - parsed = data.parsed[ index ], - query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); - // look for an exact match with the 'or' unless the 'filter-match' class is found - if ( !/\?\*/.test( query ) && data.nestedFilters ) { - query = data.isMatch ? query : '^(' + query + ')$'; - } - // parsing the filter may not work properly when using wildcards =/ - return new RegExp( - query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), - c.widgetOptions.filter_ignoreCase ? 'i' : '' - ) - .test( data.exact ); - } - return null; - }, - // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) - fuzzy: function( c, data ) { - if ( /^~/.test( data.iFilter ) ) { - var indx, - patternIndx = 0, - len = data.iExact.length, - txt = data.iFilter.slice( 1 ), - pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - for ( indx = 0; indx < len; indx++ ) { - if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { - patternIndx += 1; + tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; + range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; + range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || c.parsers[index].type === 'numeric' ) { + result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); + range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; + result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); + range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; } + if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { + result = data.cache; + } else { + tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + result = ts.formatFloat( tmp, table ); + } + if ( range1 > range2 ) { + tmp = range1; range1 = range2; range2 = tmp; // swap + } + return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); } - if ( patternIndx === pattern.length ) { - return true; + return null; + }, + // Look for wild card: ? = single, * = multiple, or | = logical OR + wild : function( c, data ) { + if ( /[\?\*\|]/.test( data.iFilter ) ) { + var index = data.index, + parsed = data.parsed[ index ], + query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); + // look for an exact match with the 'or' unless the 'filter-match' class is found + if ( !/\?\*/.test( query ) && data.nestedFilters ) { + query = data.isMatch ? query : '^(' + query + ')$'; + } + // parsing the filter may not work properly when using wildcards =/ + return new RegExp( + query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), + c.widgetOptions.filter_ignoreCase ? 'i' : '' + ) + .test( data.exact ); + } + return null; + }, + // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) + fuzzy: function( c, data ) { + if ( /^~/.test( data.iFilter ) ) { + var indx, + patternIndx = 0, + len = data.iExact.length, + txt = data.iFilter.slice( 1 ), + pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + for ( indx = 0; indx < len; indx++ ) { + if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { + patternIndx += 1; + } + } + if ( patternIndx === pattern.length ) { + return true; + } + return false; + } + return null; + } + }, + init: function( table, c, wo ) { + // filter language options + ts.language = $.extend( true, {}, { + to : 'to', + or : 'or', + and : 'and' + }, ts.language ); + + var options, string, txt, $header, column, filters, val, fxn, noSelect, + regex = ts.filter.regex; + c.$table.addClass( 'hasFilters' ); + + // define timers so using clearTimeout won't cause an undefined error + wo.searchTimer = null; + wo.filter_initTimer = null; + wo.filter_formatterCount = 0; + wo.filter_formatterInit = []; + wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; + wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; + + val = '\\{' + ts.filter.regex.query + '\\}'; + $.extend( regex, { + child : new RegExp( c.cssChildRow ), + filtered : new RegExp( wo.filter_filteredRow ), + alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), + toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), + toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ), + andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), + andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), + orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), + iQuery : new RegExp( val, 'i' ), + igQuery : new RegExp( val, 'ig' ) + }); + + // don't build filter row if columnFilters is false or all columns are set to 'filter-false' + // see issue #156 + val = c.$headers.filter( '.filter-false, .parser-false' ).length; + if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { + // build filter row + ts.filter.buildRow( table, c, wo ); + } + + txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + c.$table.bind( txt, function( event, filter ) { + val = wo.filter_hideEmpty && + $.isEmptyObject( c.cache ) && + !( c.delayInit && event.type === 'appendCache' ); + // hide filter row using the 'filtered' class name + c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 + if ( !/(search|filter)/.test( event.type ) ) { + event.stopPropagation(); + ts.filter.buildDefault( table, true ); + } + if ( event.type === 'filterReset' ) { + c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); + ts.filter.searching( table, [] ); + } else if ( event.type === 'filterEnd' ) { + ts.filter.buildDefault( table, true ); + } else { + // send false argument to force a new search; otherwise if the filter hasn't changed, + // it will return + filter = event.type === 'search' ? filter : + event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; + if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { + // force a new search since content has changed + c.lastCombinedFilter = null; + c.lastSearch = []; + } + // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first + // input ensures all inputs are updated when a search is triggered on the table + // $( 'table' ).trigger( 'search', [...] ); + ts.filter.searching( table, filter, true ); } return false; - } - return null; - } - }, - init: function( table, c, wo ) { - // filter language options - ts.language = $.extend( true, {}, { - to : 'to', - or : 'or', - and : 'and' - }, ts.language ); + }); - var options, string, txt, $header, column, filters, val, fxn, noSelect, - regex = ts.filter.regex; - c.$table.addClass( 'hasFilters' ); - - // define timers so using clearTimeout won't cause an undefined error - wo.searchTimer = null; - wo.filter_initTimer = null; - wo.filter_formatterCount = 0; - wo.filter_formatterInit = []; - wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; - wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; - - val = '\\{' + ts.filter.regex.query + '\\}'; - $.extend( regex, { - child : new RegExp( c.cssChildRow ), - filtered : new RegExp( wo.filter_filteredRow ), - alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), - toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), - toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ), - andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), - andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), - orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), - iQuery : new RegExp( val, 'i' ), - igQuery : new RegExp( val, 'ig' ) - }); - - // don't build filter row if columnFilters is false or all columns are set to 'filter-false' - // see issue #156 - val = c.$headers.filter( '.filter-false, .parser-false' ).length; - if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { - // build filter row - ts.filter.buildRow( table, c, wo ); - } - - txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - c.$table.bind( txt, function( event, filter ) { - val = wo.filter_hideEmpty && - $.isEmptyObject( c.cache ) && - !( c.delayInit && event.type === 'appendCache' ); - // hide filter row using the 'filtered' class name - c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 - if ( !/(search|filter)/.test( event.type ) ) { - event.stopPropagation(); - ts.filter.buildDefault( table, true ); - } - if ( event.type === 'filterReset' ) { - c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); - ts.filter.searching( table, [] ); - } else if ( event.type === 'filterEnd' ) { - ts.filter.buildDefault( table, true ); - } else { - // send false argument to force a new search; otherwise if the filter hasn't changed, - // it will return - filter = event.type === 'search' ? filter : - event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; - if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { - // force a new search since content has changed - c.lastCombinedFilter = null; - c.lastSearch = []; - } - // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first - // input ensures all inputs are updated when a search is triggered on the table - // $( 'table' ).trigger( 'search', [...] ); - ts.filter.searching( table, filter, true ); - } - return false; - }); - - // reset button/link - if ( wo.filter_reset ) { - if ( wo.filter_reset instanceof $ ) { - // reset contains a jQuery object, bind to it - wo.filter_reset.click( function() { - c.$table.trigger( 'filterReset' ); - }); - } else if ( $( wo.filter_reset ).length ) { - // reset is a jQuery selector, use event delegation - $( document ) - .undelegate( wo.filter_reset, 'click.tsfilter' ) - .delegate( wo.filter_reset, 'click.tsfilter', function() { - // trigger a reset event, so other functions ( filter_formatter ) know when to reset + // reset button/link + if ( wo.filter_reset ) { + if ( wo.filter_reset instanceof $ ) { + // reset contains a jQuery object, bind to it + wo.filter_reset.click( function() { c.$table.trigger( 'filterReset' ); }); + } else if ( $( wo.filter_reset ).length ) { + // reset is a jQuery selector, use event delegation + $( document ) + .undelegate( wo.filter_reset, 'click.tsfilter' ) + .delegate( wo.filter_reset, 'click.tsfilter', function() { + // trigger a reset event, so other functions ( filter_formatter ) know when to reset + c.$table.trigger( 'filterReset' ); + }); + } } - } - if ( wo.filter_functions ) { - for ( column = 0; column < c.columns; column++ ) { - fxn = ts.getColumnData( table, wo.filter_functions, column ); - if ( fxn ) { - // remove 'filter-select' from header otherwise the options added here are replaced with - // all options - $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); - // don't build select if 'filter-false' or 'parser-false' set - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - options = ''; - if ( fxn === true && noSelect ) { - ts.filter.buildSelect( table, column ); - } else if ( typeof fxn === 'object' && noSelect ) { - // add custom drop down list - for ( string in fxn ) { - if ( typeof string === 'string' ) { - options += options === '' ? - '' : ''; - val = string; - txt = string; - if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - val = string.split( wo.filter_selectSourceSeparator ); - txt = val[1]; - val = val[0]; + if ( wo.filter_functions ) { + for ( column = 0; column < c.columns; column++ ) { + fxn = ts.getColumnData( table, wo.filter_functions, column ); + if ( fxn ) { + // remove 'filter-select' from header otherwise the options added here are replaced with + // all options + $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); + // don't build select if 'filter-false' or 'parser-false' set + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + options = ''; + if ( fxn === true && noSelect ) { + ts.filter.buildSelect( table, column ); + } else if ( typeof fxn === 'object' && noSelect ) { + // add custom drop down list + for ( string in fxn ) { + if ( typeof string === 'string' ) { + options += options === '' ? + '' : ''; + val = string; + txt = string; + if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + val = string.split( wo.filter_selectSourceSeparator ); + txt = val[1]; + val = val[0]; + } + options += ''; } - options += ''; + } + c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .append( options ); + txt = wo.filter_selectSource; + fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); + if ( fxn ) { + // updating so the extra options are appended + ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); } } + } + } + } + // not really updating, but if the column has both the 'filter-select' class & + // filter_functions set to true, it would append the same options twice. + ts.filter.buildDefault( table, true ); + + ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); + if ( wo.filter_external ) { + ts.filter.bindSearch( table, wo.filter_external ); + } + + if ( wo.filter_hideFilters ) { + ts.filter.hideFilters( table, c ); + } + + // show processing icon + if ( c.showProcessing ) { + txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function( event, columns ) { + // only add processing to certain columns to all columns + $header = ( columns ) ? c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .append( options ); - txt = wo.filter_selectSource; - fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); - if ( fxn ) { - // updating so the extra options are appended - ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } - } - } - // not really updating, but if the column has both the 'filter-select' class & - // filter_functions set to true, it would append the same options twice. - ts.filter.buildDefault( table, true ); - - ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); - if ( wo.filter_external ) { - ts.filter.bindSearch( table, wo.filter_external ); - } - - if ( wo.filter_hideFilters ) { - ts.filter.hideFilters( table, c ); - } - - // show processing icon - if ( c.showProcessing ) { - txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function( event, columns ) { - // only add processing to certain columns to all columns - $header = ( columns ) ? - c.$table - .find( '.' + tscss.header ) - .filter( '[data-column]' ) - .filter( function() { - return columns[ $( this ).data( 'column' ) ] !== ''; - }) : ''; - ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); - }); - } - - // set filtered rows count ( intially unfiltered ) - c.filteredRows = c.totalRows; - - // add default values - txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function() { - // redefine 'wo' as it does not update properly inside this callback - var wo = this.config.widgetOptions; - filters = ts.filter.setDefaults( table, c, wo ) || []; - if ( filters.length ) { - // prevent delayInit from triggering a cache build if filters are empty - if ( !( c.delayInit && filters.join( '' ) === '' ) ) { - ts.setFilters( table, filters, true ); - } - } - c.$table.trigger( 'filterFomatterUpdate' ); - // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers - setTimeout( function() { - if ( !wo.filter_initialized ) { - ts.filter.filterInitComplete( c ); - } - }, 100 ); - }); - // if filter widget is added after pager has initialized; then set filter init flag - if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { - c.$table.trigger( 'filterFomatterUpdate' ); - setTimeout( function() { - ts.filter.filterInitComplete( c ); - }, 100 ); - } - }, - // $cell parameter, but not the config, is passed to the filter_formatters, - // so we have to work with it instead - formatterUpdated: function( $cell, column ) { - var wo = $cell.closest( 'table' )[0].config.widgetOptions; - if ( !wo.filter_initialized ) { - // add updates by column since this function - // may be called numerous times before initialization - wo.filter_formatterInit[ column ] = 1; - } - }, - filterInitComplete: function( c ) { - var indx, len, - wo = c.widgetOptions, - count = 0, - completed = function() { - wo.filter_initialized = true; - c.$table.trigger( 'filterInit', c ); - ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); - }; - if ( $.isEmptyObject( wo.filter_formatter ) ) { - completed(); - } else { - len = wo.filter_formatterInit.length; - for ( indx = 0; indx < len; indx++ ) { - if ( wo.filter_formatterInit[ indx ] === 1 ) { - count++; - } - } - clearTimeout( wo.filter_initTimer ); - if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { - // filter widget initialized - completed(); - } else if ( !wo.filter_initialized ) { - // fall back in case a filter_formatter doesn't call - // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off - wo.filter_initTimer = setTimeout( function() { - completed(); - }, 500 ); - } - } - }, - setDefaults: function( table, c, wo ) { - var isArray, saved, indx, col, $filters, - // get current ( default ) filters - filters = ts.getFilters( table ) || []; - if ( wo.filter_saveFilters && ts.storage ) { - saved = ts.storage( table, 'tablesorter-filters' ) || []; - isArray = $.isArray( saved ); - // make sure we're not just getting an empty array - if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { - filters = saved; - } - } - // if no filters saved, then check default settings - if ( filters.join( '' ) === '' ) { - // allow adding default setting to external filters - $filters = c.$headers.add( wo.filter_$externalFilters ) - .filter( '[' + wo.filter_defaultAttrib + ']' ); - for ( indx = 0; indx <= c.columns; indx++ ) { - // include data-column='all' external filters - col = indx === c.columns ? 'all' : indx; - filters[indx] = $filters - .filter( '[data-column="' + col + '"]' ) - .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; - } - } - c.$table.data( 'lastSearch', filters ); - return filters; - }, - parseFilter: function( c, filter, column, parsed ) { - return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; - }, - buildRow: function( table, c, wo ) { - var col, column, $header, buildSelect, disabled, name, ffxn, tmp, - // c.columns defined in computeThIndexes() - cellFilter = wo.filter_cellFilter, - columns = c.columns, - arry = $.isArray( cellFilter ), - buildFilter = ''; - for ( column = 0; column < columns; column++ ) { - buildFilter += '' ).appendTo( c.$filters.eq( column ) ); - } else { - ffxn = ts.getColumnData( table, wo.filter_formatter, column ); - if ( ffxn ) { - wo.filter_formatterCount++; - buildFilter = ffxn( c.$filters.eq( column ), column ); - // no element returned, so lets go find it - if ( buildFilter && buildFilter.length === 0 ) { - buildFilter = c.$filters.eq( column ).children( 'input' ); - } - // element not in DOM, so lets attach it - if ( buildFilter && ( buildFilter.parent().length === 0 || - ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { - c.$filters.eq( column ).append( buildFilter ); - } - } else { - buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); - } - if ( buildFilter ) { - tmp = $header.data( 'placeholder' ) || - $header.attr( 'data-placeholder' ) || - wo.filter_placeholder.search || ''; - buildFilter.attr( 'placeholder', tmp ); - } - } - if ( buildFilter ) { - // add filter class name - name = ( $.isArray( wo.filter_cssFilter ) ? - ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : - wo.filter_cssFilter ) || ''; - buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); - if ( disabled ) { - buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; - } - } - } - }, - bindSearch: function( table, $el, internal ) { - table = $( table )[0]; - $el = $( $el ); // allow passing a selector string - if ( !$el.length ) { return; } - var tmp, - c = table.config, - wo = c.widgetOptions, - namespace = c.namespace + 'filter', - $ext = wo.filter_$externalFilters; - if ( internal !== true ) { - // save anyMatch element - tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; - wo.filter_$anyMatch = $el.filter( tmp ); - if ( $ext && $ext.length ) { - wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); - } else { - wo.filter_$externalFilters = $el; - } - // update values ( external filters added after table initialization ) - ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); - } - // unbind events - tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); - $el - // use data attribute instead of jQuery data since the head is cloned without including - // the data/binding - .attr( 'data-lastSearchTime', new Date().getTime() ) - .unbind( tmp.replace( /\s+/g, ' ' ) ) - // include change for select - fixes #473 - .bind( 'keyup' + namespace, function( event ) { - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - // emulate what webkit does.... escape clears the filter - if ( event.which === 27 ) { - this.value = ''; - // live search - } else if ( wo.filter_liveSearch === false ) { - return; - // don't return if the search value is empty ( all rows need to be revealed ) - } else if ( this.value !== '' && ( - // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace - ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || - // let return & backspace continue on, but ignore arrows & non-valid characters - ( event.which !== 13 && event.which !== 8 && - ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { - return; - } - // change event = no delay; last true flag tells getFilters to skip newest timed input - ts.filter.searching( table, true, true ); - }) - .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { - var column = $( this ).data( 'column' ); - // don't allow 'change' event to process if the input value is the same - fixes #685 - if ( event.which === 13 || event.type === 'search' || - event.type === 'change' && this.value !== c.lastSearch[column] ) { - event.preventDefault(); - // init search with no delay - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - ts.filter.searching( table, false, true ); - } - }); - }, - searching: function( table, filter, skipFirst ) { - var wo = table.config.widgetOptions; - clearTimeout( wo.searchTimer ); - if ( typeof filter === 'undefined' || filter === true ) { - // delay filtering - wo.searchTimer = setTimeout( function() { - ts.filter.checkFilters( table, filter, skipFirst ); - }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); - } else { - // skip delay - ts.filter.checkFilters( table, filter, skipFirst ); - } - }, - checkFilters: function( table, filter, skipFirst ) { - var c = table.config, - wo = c.widgetOptions, - filterArray = $.isArray( filter ), - filters = ( filterArray ) ? filter : ts.getFilters( table, true ), - combinedFilters = ( filters || [] ).join( '' ); // combined filter values - // prevent errors if delay init is set - if ( $.isEmptyObject( c.cache ) ) { - // update cache if delayInit set & pager has initialized ( after user initiates a search ) - if ( c.delayInit && c.pager && c.pager.initialized ) { - c.$table.trigger( 'updateCache', [ function() { - ts.filter.checkFilters( table, false, skipFirst ); - } ] ); - } - return; - } - // add filter array back into inputs - if ( filterArray ) { - ts.setFilters( table, filters, false, skipFirst !== true ); - if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } - } - if ( wo.filter_hideFilters ) { - // show/hide filter row as needed - c.$table - .find( '.' + tscss.filterRow ) - .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); - } - // return if the last search is the same; but filter === false when updating the search - // see example-widget-filter.html filter toggle buttons - if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { - return; - } else if ( filter === false ) { - // force filter refresh - c.lastCombinedFilter = null; - c.lastSearch = []; - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterStart', [filters] ); - } - if ( c.showProcessing ) { - // give it time for the processing icon to kick in - setTimeout( function() { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - }, 30 ); - } else { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - } - }, - hideFilters: function( table, c ) { - var timer; - c.$table - .find( '.' + tscss.filterRow ) - .bind( 'mouseenter mouseleave', function( e ) { - // save event object - http://bugs.jquery.com/ticket/12140 - var event = e, - $filterRow = $( this ); - clearTimeout( timer ); - timer = setTimeout( function() { - if ( /enter|over/.test( event.type ) ) { - $filterRow.removeClass( tscss.filterRowHide ); - } else { - // don't hide if input has focus - // $( ':focus' ) needs jQuery 1.6+ - if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { - // don't hide row if any filter has a value - if ( c.lastCombinedFilter === '' ) { - $filterRow.addClass( tscss.filterRowHide ); - } - } - } - }, 200 ); - }) - .find( 'input, select' ).bind( 'focus blur', function( e ) { - var event = e, - $row = $( this ).closest( 'tr' ); - clearTimeout( timer ); - timer = setTimeout( function() { - clearTimeout( timer ); - // don't hide row if any filter has a value - if ( ts.getFilters( c.$table ).join( '' ) === '' ) { - $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); - } - }, 200 ); - }); - }, - defaultFilter: function( filter, mask ) { - if ( filter === '' ) { return filter; } - var regex = ts.filter.regex.iQuery, - maskLen = mask.match( ts.filter.regex.igQuery ).length, - query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], - len = query.length - 1, - indx = 0, - val = mask; - if ( len < 1 && maskLen > 1 ) { - // only one 'word' in query but mask has >1 slots - query[1] = query[0]; - } - // replace all {query} with query words... - // if query = 'Bob', then convert mask from '!{query}' to '!Bob' - // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' - while ( regex.test( val ) ) { - val = val.replace( regex, query[indx++] || '' ); - if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { - val = mask.replace( regex, val ); - } - } - return val; - }, - getLatestSearch: function( $input ) { - if ( $input ) { - return $input.sort( function( a, b ) { - return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); - }); - } - return $input || $(); - }, - multipleColumns: function( c, $input ) { - // look for multiple columns '1-3,4-6,8' in data-column - var temp, ranges, range, start, end, singles, i, indx, len, - wo = c.widgetOptions, - // only target 'all' column inputs on initialization - // & don't target 'all' column inputs if they don't exist - targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, - columns = [], - val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); - // process column range - if ( targets && /-/.test( val ) ) { - ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); - len = ranges.length; - for ( indx = 0; indx < len; indx++ ) { - range = ranges[indx].split( /\s*-\s*/ ); - start = parseInt( range[0], 10 ) || 0; - end = parseInt( range[1], 10 ) || ( c.columns - 1 ); - if ( start > end ) { - temp = start; start = end; end = temp; // swap - } - if ( end >= c.columns ) { - end = c.columns - 1; - } - for ( ; start <= end; start++ ) { - columns.push( start ); - } - // remove processed range from val - val = val.replace( ranges[ indx ], '' ); - } - } - // process single columns - if ( targets && /,/.test( val ) ) { - singles = val.split( /\s*,\s*/ ); - len = singles.length; - for ( i = 0; i < len; i++ ) { - if ( singles[ i ] !== '' ) { - indx = parseInt( singles[ i ], 10 ); - if ( indx < c.columns ) { - columns.push( indx ); - } - } - } - } - // return all columns - if ( !columns.length ) { - for ( indx = 0; indx < c.columns; indx++ ) { - columns.push( indx ); - } - } - return columns; - }, - processTypes: function( c, data, vars ) { - var ffxn, - filterMatched = null, - matches = null; - for ( ffxn in ts.filter.types ) { - if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { - matches = ts.filter.types[ffxn]( c, data, vars ); - if ( matches !== null ) { - filterMatched = matches; - } - } - } - return filterMatched; - }, - processRow: function( c, data, vars ) { - var columnIndex, hasSelect, result, val, filterMatched, - fxn, ffxn, txt, - regex = ts.filter.regex, - wo = c.widgetOptions, - showRow = true; - data.$cells = data.$row.children(); - - if ( data.anyMatchFlag ) { - // look for multiple columns '1-3,4-6,8' - columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); - data.anyMatch = true; - data.isMatch = true; - data.rowArray = data.$cells.map( function( i ) { - if ( $.inArray( i, columnIndex ) > -1 ) { - if ( data.parsed[ i ] ) { - txt = data.cacheArray[ i ]; - } else { - txt = data.rawArray[ i ]; - txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); - if ( c.sortLocaleCompare ) { - txt = ts.replaceAccents( txt ); - } - } - return txt; - } - }).get(); - data.filter = data.anyMatchFilter; - data.iFilter = data.iAnyMatchFilter; - data.exact = data.rowArray.join( ' ' ); - data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; - data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); - - vars.excludeMatch = vars.noAnyMatch; - filterMatched = ts.filter.processTypes( c, data, vars ); - - if ( filterMatched !== null ) { - showRow = filterMatched; - } else { - if ( wo.filter_startsWith ) { - showRow = false; - columnIndex = c.columns; - while ( !showRow && columnIndex > 0 ) { - columnIndex--; - showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; - } - } else { - showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; - } - } - data.anyMatch = false; - // no other filters to process - if ( data.filters.join( '' ) === data.filter ) { - return showRow; - } - } - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - data.filter = data.filters[ columnIndex ]; - data.index = columnIndex; - - // filter types to exclude, per column - vars.excludeMatch = vars.excludeFilter[ columnIndex ]; - - // ignore if filter is empty or disabled - if ( data.filter ) { - data.cache = data.cacheArray[ columnIndex ]; - // check if column data should be from the cell or from parsed data - if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { - data.exact = data.cache; - } else { - result = data.rawArray[ columnIndex ] || ''; - data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 - } - data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? - data.exact.toLowerCase() : data.exact; - - data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); - - result = showRow; // if showRow is true, show that row - - // in case select filter option has a different value vs text 'a - z|A through Z' - ffxn = wo.filter_columnFilters ? - c.$filters.add( c.$externalFilters ) - .filter( '[data-column="'+ columnIndex + '"]' ) - .find( 'select option:selected' ) - .attr( 'data-function-name' ) || '' : ''; - // replace accents - see #357 - if ( c.sortLocaleCompare ) { - data.filter = ts.replaceAccents( data.filter ); - } - - val = true; - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { - data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); - // val is used to indicate that a filter select is using a default filter; - // so we override the exact & partial matches - val = false; - } - // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), - // data.filter = case sensitive - data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; - fxn = vars.functions[ columnIndex ]; - hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); - filterMatched = null; - if ( fxn || ( hasSelect && val ) ) { - if ( fxn === true || hasSelect ) { - // default selector uses exact match unless 'filter-match' class is found - filterMatched = data.isMatch ? - data.iExact.search( data.iFilter ) >= 0 : - data.filter === data.exact; - } else if ( typeof fxn === 'function' ) { - // filter callback( exact cell content, parser normalized content, - // filter input value, column index, jQuery row object ) - filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { - // selector option function - txt = ffxn || data.filter; - filterMatched = - fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } - } - if ( filterMatched === null ) { - // cycle through the different filters - // filters return a boolean or null if nothing matches - filterMatched = ts.filter.processTypes( c, data, vars ); - if ( filterMatched !== null ) { - result = filterMatched; - // Look for match, and add child row data for matching - } else { - txt = ( data.iExact + data.childRowText ) - .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); - result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); - } - } else { - result = filterMatched; - } - showRow = ( result ) ? showRow : false; - } - } - return showRow; - }, - findRows: function( table, filters, combinedFilters ) { - if ( table.config.lastCombinedFilter === combinedFilters || - !table.config.widgetOptions.filter_initialized ) { - return; - } - var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, - isChild, childRow, lastSearch, showRow, time, val, indx, - notFiltered, searchFiltered, query, injected, res, id, txt, - storedFilters = $.extend( [], filters ), - regex = ts.filter.regex, - c = table.config, - wo = c.widgetOptions, - // data object passed to filters; anyMatch is a flag for the filters - data = { - anyMatch: false, - filters: filters, - // regex filter type cache - filter_regexCache : [] - }, - vars = { - // anyMatch really screws up with these types of filters - noAnyMatch: [ 'range', 'notMatch', 'operators' ], - // cache filter variables that use ts.getColumnData in the main loop - functions : [], - excludeFilter : [], - defaultColFilter : [], - defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' - }; - - // parse columns after formatter, in case the class is added at that point - data.parsed = c.$headers.map( function( columnIndex ) { - return c.parsers && c.parsers[ columnIndex ] && - // force parsing if parser type is numeric - c.parsers[ columnIndex ].parsed || - // getData won't return 'parsed' if other 'filter-' class names exist - // ( e.g. ) - ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], - ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || - $( this ).hasClass( 'filter-parsed' ); - }).get(); - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - vars.functions[ columnIndex ] = - ts.getColumnData( table, wo.filter_functions, columnIndex ); - vars.defaultColFilter[ columnIndex ] = - ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; - vars.excludeFilter[ columnIndex ] = - ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); - } - - if ( c.debug ) { - ts.log( 'Filter: Starting filter widget search', filters ); - time = new Date(); - } - // filtered rows count - c.filteredRows = 0; - c.totalRows = 0; - // combindedFilters are undefined on init - combinedFilters = ( storedFilters || [] ).join( '' ); - - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); - // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! - // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); - columnIndex = c.columns; - // convert stored rows into a jQuery object - norm_rows = c.cache[ tbodyIndex ].normalized; - $rows = $( $.map( norm_rows, function( el ) { - return el[ columnIndex ].$row.get(); - }) ); - - if ( combinedFilters === '' || wo.filter_serversideFiltering ) { - $rows - .removeClass( wo.filter_filteredRow ) - .not( '.' + c.cssChildRow ) - .css( 'display', '' ); - } else { - // filter out child rows - $rows = $rows.not( '.' + c.cssChildRow ); - len = $rows.length; - - if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || - typeof filters[c.columns] !== 'undefined' ) { - data.anyMatchFlag = true; - data.anyMatchFilter = '' + ( - filters[ c.columns ] || - wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || - '' - ); - if ( wo.filter_columnAnyMatch ) { - // specific columns search - query = data.anyMatchFilter.split( regex.andSplit ); - injected = false; - for ( indx = 0; indx < query.length; indx++ ) { - res = query[ indx ].split( ':' ); - if ( res.length > 1 ) { - // make the column a one-based index ( non-developers start counting from one :P ) - id = parseInt( res[0], 10 ) - 1; - if ( id >= 0 && id < c.columns ) { // if id is an integer - filters[ id ] = res[1]; - query.splice( indx, 1 ); - indx--; - injected = true; - } - } - } - if ( injected ) { - data.anyMatchFilter = query.join( ' && ' ); - } - } - } - - // optimize searching only through already filtered rows - see #313 - searchFiltered = wo.filter_searchFiltered; - lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; - if ( searchFiltered ) { - // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 - for ( indx = 0; indx < columnIndex + 1; indx++ ) { - val = filters[indx] || ''; - // break out of loop if we've already determined not to search filtered rows - if ( !searchFiltered ) { indx = columnIndex; } - // search already filtered rows if... - searchFiltered = searchFiltered && lastSearch.length && - // there are no changes from beginning of filter - val.indexOf( lastSearch[indx] || '' ) === 0 && - // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string - !regex.alreadyFiltered.test( val ) && - // if we are not doing exact matches, using '|' ( logical or ) or not '!' - !/[=\"\|!]/.test( val ) && - // don't search only filtered if the value is negative - // ( '> -10' => '> -100' will ignore hidden rows ) - !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && - // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 - !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && - !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); - } - } - notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; - // can't search when all rows are hidden - this happens when looking for exact matches - if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } - if ( c.debug ) { - ts.log( 'Filter: Searching through ' + - ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); - } - if ( data.anyMatchFlag ) { - if ( c.sortLocaleCompare ) { - // replace accents - data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); - } - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { - data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); - // clear search filtered flag because default filters are not saved to the last search - searchFiltered = false; - } - // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true - // when c.ignoreCase is true, the cache contains all lower case data - data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? - data.anyMatchFilter : - data.anyMatchFilter.toLowerCase(); - } - - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - - txt = $rows[ rowIndex ].className; - // the first row can never be a child row - isChild = rowIndex && regex.child.test( txt ); - // skip child rows & already filtered rows - if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { - continue; - } - - data.$row = $rows.eq( rowIndex ); - data.cacheArray = norm_rows[ rowIndex ]; - rowData = data.cacheArray[ c.columns ]; - data.rawArray = rowData.raw; - data.childRowText = ''; - - if ( !wo.filter_childByColumn ) { - txt = ''; - // child row cached text - childRow = rowData.child; - // so, if 'table.config.widgetOptions.filter_childRows' is true and there is - // a match anywhere in the child row, then it will make the row visible - // checked here so the option can be changed dynamically - for ( indx = 0; indx < childRow.length; indx++ ) { - txt += ' ' + childRow[indx].join( '' ) || ''; - } - data.childRowText = wo.filter_childRows ? - ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : - ''; - } - - showRow = ts.filter.processRow( c, data, vars ); - childRow = rowData.$row.filter( ':gt( 0 )' ); - - if ( wo.filter_childRows && childRow.length ) { - if ( wo.filter_childByColumn ) { - // cycle through each child row - for ( indx = 0; indx < childRow.length; indx++ ) { - data.$row = childRow.eq( indx ); - data.cacheArray = rowData.child[ indx ]; - data.rawArray = data.cacheArray; - // use OR comparison on child rows - showRow = showRow || ts.filter.processRow( c, data, vars ); - } - } - childRow.toggleClass( wo.filter_filteredRow, !showRow ); - } - - rowData.$row - .toggleClass( wo.filter_filteredRow, !showRow )[0] - .display = showRow ? '' : 'none'; - } - } - c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; - c.totalRows += $rows.length; - ts.processTbody( table, $tbody, false ); - } - c.lastCombinedFilter = combinedFilters; // save last search - // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) - c.lastSearch = storedFilters; - c.$table.data( 'lastSearch', storedFilters ); - if ( wo.filter_saveFilters && ts.storage ) { - ts.storage( table, 'tablesorter-filters', storedFilters ); - } - if ( c.debug ) { - ts.benchmark( 'Completed filter widget search', time ); - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterEnd', c ); - } - setTimeout( function() { - c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied - }, 0 ); - }, - getOptionSource: function( table, column, onlyAvail ) { - table = $( table )[0]; - var cts, txt, indx, len, - c = table.config, - wo = c.widgetOptions, - parsed = [], - arry = false, - source = wo.filter_selectSource, - last = c.$table.data( 'lastSearch' ) || [], - fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); - - if ( onlyAvail && last[column] !== '' ) { - onlyAvail = false; - } - - // filter select source option - if ( fxn === true ) { - // OVERALL source - arry = source( table, column, onlyAvail ); - } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { - // selectSource is a jQuery object or string of options - return fxn; - } else if ( $.isArray( fxn ) ) { - arry = fxn; - } else if ( $.type( source ) === 'object' && fxn ) { - // custom select source function for a SPECIFIC COLUMN - arry = fxn( table, column, onlyAvail ); - } - if ( arry === false ) { - // fall back to original method - arry = ts.filter.getOptions( table, column, onlyAvail ); - } - - // get unique elements and sort the list - // if $.tablesorter.sortText exists ( not in the original tablesorter ), - // then natural sort the list otherwise use a basic sort - arry = $.grep( arry, function( value, indx ) { - return $.inArray( value, arry ) === indx; - }); - - if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { - // unsorted select options - return arry; - } else { - len = arry.length; - // parse select option values - for ( indx = 0; indx < len; indx++ ) { - txt = arry[ indx ]; - // parse array data using set column parser; this DOES NOT pass the original - // table cell to the parser format function - parsed.push({ - t : txt, - // check parser length - fixes #934 - p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + .find( '.' + tscss.header ) + .filter( '[data-column]' ) + .filter( function() { + return columns[ $( this ).data( 'column' ) ] !== ''; + }) : ''; + ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); }); } - // sort parsed select options - cts = c.textSorter || ''; - parsed.sort( function( a, b ) { - // sortNatural breaks if you don't pass it strings - var x = a.p.toString(), - y = b.p.toString(); - if ( $.isFunction( cts ) ) { - // custom OVERALL text sorter - return cts( x, y, true, column, table ); - } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) { - // custom text sorter for a SPECIFIC COLUMN - return cts[column]( x, y, true, column, table ); - } else if ( ts.sortNatural ) { - // fall back to natural sort - return ts.sortNatural( x, y ); + // set filtered rows count ( intially unfiltered ) + c.filteredRows = c.totalRows; + + // add default values + txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function() { + // redefine 'wo' as it does not update properly inside this callback + var wo = this.config.widgetOptions; + filters = ts.filter.setDefaults( table, c, wo ) || []; + if ( filters.length ) { + // prevent delayInit from triggering a cache build if filters are empty + if ( !( c.delayInit && filters.join( '' ) === '' ) ) { + ts.setFilters( table, filters, true ); + } } - // using an older version! do a basic sort - return true; + c.$table.trigger( 'filterFomatterUpdate' ); + // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers + setTimeout( function() { + if ( !wo.filter_initialized ) { + ts.filter.filterInitComplete( c ); + } + }, 100 ); }); - // rebuild arry from sorted parsed data - arry = []; - len = parsed.length; - for ( indx = 0; indx < len; indx++ ) { - arry.push( parsed[indx].t ); + // if filter widget is added after pager has initialized; then set filter init flag + if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { + c.$table.trigger( 'filterFomatterUpdate' ); + setTimeout( function() { + ts.filter.filterInitComplete( c ); + }, 100 ); + } + }, + // $cell parameter, but not the config, is passed to the filter_formatters, + // so we have to work with it instead + formatterUpdated: function( $cell, column ) { + var wo = $cell.closest( 'table' )[0].config.widgetOptions; + if ( !wo.filter_initialized ) { + // add updates by column since this function + // may be called numerous times before initialization + wo.filter_formatterInit[ column ] = 1; + } + }, + filterInitComplete: function( c ) { + var indx, len, + wo = c.widgetOptions, + count = 0, + completed = function() { + wo.filter_initialized = true; + c.$table.trigger( 'filterInit', c ); + ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); + }; + if ( $.isEmptyObject( wo.filter_formatter ) ) { + completed(); + } else { + len = wo.filter_formatterInit.length; + for ( indx = 0; indx < len; indx++ ) { + if ( wo.filter_formatterInit[ indx ] === 1 ) { + count++; + } + } + clearTimeout( wo.filter_initTimer ); + if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { + // filter widget initialized + completed(); + } else if ( !wo.filter_initialized ) { + // fall back in case a filter_formatter doesn't call + // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off + wo.filter_initTimer = setTimeout( function() { + completed(); + }, 500 ); + } + } + }, + setDefaults: function( table, c, wo ) { + var isArray, saved, indx, col, $filters, + // get current ( default ) filters + filters = ts.getFilters( table ) || []; + if ( wo.filter_saveFilters && ts.storage ) { + saved = ts.storage( table, 'tablesorter-filters' ) || []; + isArray = $.isArray( saved ); + // make sure we're not just getting an empty array + if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { + filters = saved; + } + } + // if no filters saved, then check default settings + if ( filters.join( '' ) === '' ) { + // allow adding default setting to external filters + $filters = c.$headers.add( wo.filter_$externalFilters ) + .filter( '[' + wo.filter_defaultAttrib + ']' ); + for ( indx = 0; indx <= c.columns; indx++ ) { + // include data-column='all' external filters + col = indx === c.columns ? 'all' : indx; + filters[indx] = $filters + .filter( '[data-column="' + col + '"]' ) + .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; + } + } + c.$table.data( 'lastSearch', filters ); + return filters; + }, + parseFilter: function( c, filter, column, parsed ) { + return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; + }, + buildRow: function( table, c, wo ) { + var col, column, $header, buildSelect, disabled, name, ffxn, tmp, + // c.columns defined in computeThIndexes() + cellFilter = wo.filter_cellFilter, + columns = c.columns, + arry = $.isArray( cellFilter ), + buildFilter = ''; + for ( column = 0; column < columns; column++ ) { + buildFilter += '' ).appendTo( c.$filters.eq( column ) ); + } else { + ffxn = ts.getColumnData( table, wo.filter_formatter, column ); + if ( ffxn ) { + wo.filter_formatterCount++; + buildFilter = ffxn( c.$filters.eq( column ), column ); + // no element returned, so lets go find it + if ( buildFilter && buildFilter.length === 0 ) { + buildFilter = c.$filters.eq( column ).children( 'input' ); + } + // element not in DOM, so lets attach it + if ( buildFilter && ( buildFilter.parent().length === 0 || + ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { + c.$filters.eq( column ).append( buildFilter ); + } + } else { + buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); + } + if ( buildFilter ) { + tmp = $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.search || ''; + buildFilter.attr( 'placeholder', tmp ); + } + } + if ( buildFilter ) { + // add filter class name + name = ( $.isArray( wo.filter_cssFilter ) ? + ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : + wo.filter_cssFilter ) || ''; + buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); + if ( disabled ) { + buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; + } + } + } + }, + bindSearch: function( table, $el, internal ) { + table = $( table )[0]; + $el = $( $el ); // allow passing a selector string + if ( !$el.length ) { return; } + var tmp, + c = table.config, + wo = c.widgetOptions, + namespace = c.namespace + 'filter', + $ext = wo.filter_$externalFilters; + if ( internal !== true ) { + // save anyMatch element + tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; + wo.filter_$anyMatch = $el.filter( tmp ); + if ( $ext && $ext.length ) { + wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); + } else { + wo.filter_$externalFilters = $el; + } + // update values ( external filters added after table initialization ) + ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); + } + // unbind events + tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); + $el + // use data attribute instead of jQuery data since the head is cloned without including + // the data/binding + .attr( 'data-lastSearchTime', new Date().getTime() ) + .unbind( tmp.replace( /\s+/g, ' ' ) ) + // include change for select - fixes #473 + .bind( 'keyup' + namespace, function( event ) { + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + // emulate what webkit does.... escape clears the filter + if ( event.which === 27 ) { + this.value = ''; + // live search + } else if ( wo.filter_liveSearch === false ) { + return; + // don't return if the search value is empty ( all rows need to be revealed ) + } else if ( this.value !== '' && ( + // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace + ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || + // let return & backspace continue on, but ignore arrows & non-valid characters + ( event.which !== 13 && event.which !== 8 && + ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { + return; + } + // change event = no delay; last true flag tells getFilters to skip newest timed input + ts.filter.searching( table, true, true ); + }) + .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { + var column = $( this ).data( 'column' ); + // don't allow 'change' event to process if the input value is the same - fixes #685 + if ( event.which === 13 || event.type === 'search' || + event.type === 'change' && this.value !== c.lastSearch[column] ) { + event.preventDefault(); + // init search with no delay + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + ts.filter.searching( table, false, true ); + } + }); + }, + searching: function( table, filter, skipFirst ) { + var wo = table.config.widgetOptions; + clearTimeout( wo.searchTimer ); + if ( typeof filter === 'undefined' || filter === true ) { + // delay filtering + wo.searchTimer = setTimeout( function() { + ts.filter.checkFilters( table, filter, skipFirst ); + }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); + } else { + // skip delay + ts.filter.checkFilters( table, filter, skipFirst ); + } + }, + checkFilters: function( table, filter, skipFirst ) { + var c = table.config, + wo = c.widgetOptions, + filterArray = $.isArray( filter ), + filters = ( filterArray ) ? filter : ts.getFilters( table, true ), + combinedFilters = ( filters || [] ).join( '' ); // combined filter values + // prevent errors if delay init is set + if ( $.isEmptyObject( c.cache ) ) { + // update cache if delayInit set & pager has initialized ( after user initiates a search ) + if ( c.delayInit && c.pager && c.pager.initialized ) { + c.$table.trigger( 'updateCache', [ function() { + ts.filter.checkFilters( table, false, skipFirst ); + } ] ); + } + return; + } + // add filter array back into inputs + if ( filterArray ) { + ts.setFilters( table, filters, false, skipFirst !== true ); + if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } + } + if ( wo.filter_hideFilters ) { + // show/hide filter row as needed + c.$table + .find( '.' + tscss.filterRow ) + .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); + } + // return if the last search is the same; but filter === false when updating the search + // see example-widget-filter.html filter toggle buttons + if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { + return; + } else if ( filter === false ) { + // force filter refresh + c.lastCombinedFilter = null; + c.lastSearch = []; + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterStart', [ filters ] ); + } + if ( c.showProcessing ) { + // give it time for the processing icon to kick in + setTimeout( function() { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + }, 30 ); + } else { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + } + }, + hideFilters: function( table, c ) { + var timer; + c.$table + .find( '.' + tscss.filterRow ) + .bind( 'mouseenter mouseleave', function( e ) { + // save event object - http://bugs.jquery.com/ticket/12140 + var event = e, + $filterRow = $( this ); + clearTimeout( timer ); + timer = setTimeout( function() { + if ( /enter|over/.test( event.type ) ) { + $filterRow.removeClass( tscss.filterRowHide ); + } else { + // don't hide if input has focus + // $( ':focus' ) needs jQuery 1.6+ + if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { + // don't hide row if any filter has a value + if ( c.lastCombinedFilter === '' ) { + $filterRow.addClass( tscss.filterRowHide ); + } + } + } + }, 200 ); + }) + .find( 'input, select' ).bind( 'focus blur', function( e ) { + var event = e, + $row = $( this ).closest( 'tr' ); + clearTimeout( timer ); + timer = setTimeout( function() { + clearTimeout( timer ); + // don't hide row if any filter has a value + if ( ts.getFilters( c.$table ).join( '' ) === '' ) { + $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); + } + }, 200 ); + }); + }, + defaultFilter: function( filter, mask ) { + if ( filter === '' ) { return filter; } + var regex = ts.filter.regex.iQuery, + maskLen = mask.match( ts.filter.regex.igQuery ).length, + query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], + len = query.length - 1, + indx = 0, + val = mask; + if ( len < 1 && maskLen > 1 ) { + // only one 'word' in query but mask has >1 slots + query[1] = query[0]; + } + // replace all {query} with query words... + // if query = 'Bob', then convert mask from '!{query}' to '!Bob' + // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' + while ( regex.test( val ) ) { + val = val.replace( regex, query[indx++] || '' ); + if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { + val = mask.replace( regex, val ); + } + } + return val; + }, + getLatestSearch: function( $input ) { + if ( $input ) { + return $input.sort( function( a, b ) { + return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); + }); + } + return $input || $(); + }, + multipleColumns: function( c, $input ) { + // look for multiple columns '1-3,4-6,8' in data-column + var temp, ranges, range, start, end, singles, i, indx, len, + wo = c.widgetOptions, + // only target 'all' column inputs on initialization + // & don't target 'all' column inputs if they don't exist + targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, + columns = [], + val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); + // process column range + if ( targets && /-/.test( val ) ) { + ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); + len = ranges.length; + for ( indx = 0; indx < len; indx++ ) { + range = ranges[indx].split( /\s*-\s*/ ); + start = parseInt( range[0], 10 ) || 0; + end = parseInt( range[1], 10 ) || ( c.columns - 1 ); + if ( start > end ) { + temp = start; start = end; end = temp; // swap + } + if ( end >= c.columns ) { + end = c.columns - 1; + } + for ( ; start <= end; start++ ) { + columns.push( start ); + } + // remove processed range from val + val = val.replace( ranges[ indx ], '' ); + } + } + // process single columns + if ( targets && /,/.test( val ) ) { + singles = val.split( /\s*,\s*/ ); + len = singles.length; + for ( i = 0; i < len; i++ ) { + if ( singles[ i ] !== '' ) { + indx = parseInt( singles[ i ], 10 ); + if ( indx < c.columns ) { + columns.push( indx ); + } + } + } + } + // return all columns + if ( !columns.length ) { + for ( indx = 0; indx < c.columns; indx++ ) { + columns.push( indx ); + } + } + return columns; + }, + processTypes: function( c, data, vars ) { + var ffxn, + filterMatched = null, + matches = null; + for ( ffxn in ts.filter.types ) { + if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { + matches = ts.filter.types[ffxn]( c, data, vars ); + if ( matches !== null ) { + filterMatched = matches; + } + } + } + return filterMatched; + }, + processRow: function( c, data, vars ) { + var columnIndex, hasSelect, result, val, filterMatched, + fxn, ffxn, txt, + regex = ts.filter.regex, + wo = c.widgetOptions, + showRow = true; + data.$cells = data.$row.children(); + + if ( data.anyMatchFlag ) { + // look for multiple columns '1-3,4-6,8' + columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); + data.anyMatch = true; + data.isMatch = true; + data.rowArray = data.$cells.map( function( i ) { + if ( $.inArray( i, columnIndex ) > -1 ) { + if ( data.parsed[ i ] ) { + txt = data.cacheArray[ i ]; + } else { + txt = data.rawArray[ i ]; + txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); + if ( c.sortLocaleCompare ) { + txt = ts.replaceAccents( txt ); + } + } + return txt; + } + }).get(); + data.filter = data.anyMatchFilter; + data.iFilter = data.iAnyMatchFilter; + data.exact = data.rowArray.join( ' ' ); + data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; + data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); + + vars.excludeMatch = vars.noAnyMatch; + filterMatched = ts.filter.processTypes( c, data, vars ); + + if ( filterMatched !== null ) { + showRow = filterMatched; + } else { + if ( wo.filter_startsWith ) { + showRow = false; + columnIndex = c.columns; + while ( !showRow && columnIndex > 0 ) { + columnIndex--; + showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; + } + } else { + showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; + } + } + data.anyMatch = false; + // no other filters to process + if ( data.filters.join( '' ) === data.filter ) { + return showRow; + } + } + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.filter = data.filters[ columnIndex ]; + data.index = columnIndex; + + // filter types to exclude, per column + vars.excludeMatch = vars.excludeFilter[ columnIndex ]; + + // ignore if filter is empty or disabled + if ( data.filter ) { + data.cache = data.cacheArray[ columnIndex ]; + // check if column data should be from the cell or from parsed data + if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { + data.exact = data.cache; + } else { + result = data.rawArray[ columnIndex ] || ''; + data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 + } + data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? + data.exact.toLowerCase() : data.exact; + + data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); + + result = showRow; // if showRow is true, show that row + + // in case select filter option has a different value vs text 'a - z|A through Z' + ffxn = wo.filter_columnFilters ? + c.$filters.add( c.$externalFilters ) + .filter( '[data-column="' + columnIndex + '"]' ) + .find( 'select option:selected' ) + .attr( 'data-function-name' ) || '' : ''; + // replace accents - see #357 + if ( c.sortLocaleCompare ) { + data.filter = ts.replaceAccents( data.filter ); + } + + val = true; + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { + data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); + // val is used to indicate that a filter select is using a default filter; + // so we override the exact & partial matches + val = false; + } + // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), + // data.filter = case sensitive + data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; + fxn = vars.functions[ columnIndex ]; + hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); + filterMatched = null; + if ( fxn || ( hasSelect && val ) ) { + if ( fxn === true || hasSelect ) { + // default selector uses exact match unless 'filter-match' class is found + filterMatched = data.isMatch ? + data.iExact.search( data.iFilter ) >= 0 : + data.filter === data.exact; + } else if ( typeof fxn === 'function' ) { + // filter callback( exact cell content, parser normalized content, + // filter input value, column index, jQuery row object ) + filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { + // selector option function + txt = ffxn || data.filter; + filterMatched = + fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } + } + if ( filterMatched === null ) { + // cycle through the different filters + // filters return a boolean or null if nothing matches + filterMatched = ts.filter.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + result = filterMatched; + // Look for match, and add child row data for matching + } else { + txt = ( data.iExact + data.childRowText ) + .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); + result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); + } + } else { + result = filterMatched; + } + showRow = ( result ) ? showRow : false; + } + } + return showRow; + }, + findRows: function( table, filters, combinedFilters ) { + if ( table.config.lastCombinedFilter === combinedFilters || + !table.config.widgetOptions.filter_initialized ) { + return; + } + var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, + isChild, childRow, lastSearch, showRow, time, val, indx, + notFiltered, searchFiltered, query, injected, res, id, txt, + storedFilters = $.extend( [], filters ), + regex = ts.filter.regex, + c = table.config, + wo = c.widgetOptions, + // data object passed to filters; anyMatch is a flag for the filters + data = { + anyMatch: false, + filters: filters, + // regex filter type cache + filter_regexCache : [] + }, + vars = { + // anyMatch really screws up with these types of filters + noAnyMatch: [ 'range', 'notMatch', 'operators' ], + // cache filter variables that use ts.getColumnData in the main loop + functions : [], + excludeFilter : [], + defaultColFilter : [], + defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' + }; + + // parse columns after formatter, in case the class is added at that point + data.parsed = c.$headers.map( function( columnIndex ) { + return c.parsers && c.parsers[ columnIndex ] && + // force parsing if parser type is numeric + c.parsers[ columnIndex ].parsed || + // getData won't return 'parsed' if other 'filter-' class names exist + // ( e.g. ) + ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], + ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || + $( this ).hasClass( 'filter-parsed' ); + }).get(); + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + vars.functions[ columnIndex ] = + ts.getColumnData( table, wo.filter_functions, columnIndex ); + vars.defaultColFilter[ columnIndex ] = + ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; + vars.excludeFilter[ columnIndex ] = + ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); + } + + if ( c.debug ) { + ts.log( 'Filter: Starting filter widget search', filters ); + time = new Date(); + } + // filtered rows count + c.filteredRows = 0; + c.totalRows = 0; + // combindedFilters are undefined on init + combinedFilters = ( storedFilters || [] ).join( '' ); + + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); + // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! + // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); + columnIndex = c.columns; + // convert stored rows into a jQuery object + norm_rows = c.cache[ tbodyIndex ].normalized; + $rows = $( $.map( norm_rows, function( el ) { + return el[ columnIndex ].$row.get(); + }) ); + + if ( combinedFilters === '' || wo.filter_serversideFiltering ) { + $rows + .removeClass( wo.filter_filteredRow ) + .not( '.' + c.cssChildRow ) + .css( 'display', '' ); + } else { + // filter out child rows + $rows = $rows.not( '.' + c.cssChildRow ); + len = $rows.length; + + if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || + typeof filters[c.columns] !== 'undefined' ) { + data.anyMatchFlag = true; + data.anyMatchFilter = '' + ( + filters[ c.columns ] || + wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || + '' + ); + if ( wo.filter_columnAnyMatch ) { + // specific columns search + query = data.anyMatchFilter.split( regex.andSplit ); + injected = false; + for ( indx = 0; indx < query.length; indx++ ) { + res = query[ indx ].split( ':' ); + if ( res.length > 1 ) { + // make the column a one-based index ( non-developers start counting from one :P ) + id = parseInt( res[0], 10 ) - 1; + if ( id >= 0 && id < c.columns ) { // if id is an integer + filters[ id ] = res[1]; + query.splice( indx, 1 ); + indx--; + injected = true; + } + } + } + if ( injected ) { + data.anyMatchFilter = query.join( ' && ' ); + } + } + } + + // optimize searching only through already filtered rows - see #313 + searchFiltered = wo.filter_searchFiltered; + lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; + if ( searchFiltered ) { + // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 + for ( indx = 0; indx < columnIndex + 1; indx++ ) { + val = filters[indx] || ''; + // break out of loop if we've already determined not to search filtered rows + if ( !searchFiltered ) { indx = columnIndex; } + // search already filtered rows if... + searchFiltered = searchFiltered && lastSearch.length && + // there are no changes from beginning of filter + val.indexOf( lastSearch[indx] || '' ) === 0 && + // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string + !regex.alreadyFiltered.test( val ) && + // if we are not doing exact matches, using '|' ( logical or ) or not '!' + !/[=\"\|!]/.test( val ) && + // don't search only filtered if the value is negative + // ( '> -10' => '> -100' will ignore hidden rows ) + !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && + // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 + !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && + !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); + } + } + notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; + // can't search when all rows are hidden - this happens when looking for exact matches + if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } + if ( c.debug ) { + ts.log( 'Filter: Searching through ' + + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); + } + if ( data.anyMatchFlag ) { + if ( c.sortLocaleCompare ) { + // replace accents + data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); + } + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { + data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); + // clear search filtered flag because default filters are not saved to the last search + searchFiltered = false; + } + // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true + // when c.ignoreCase is true, the cache contains all lower case data + data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? + data.anyMatchFilter : + data.anyMatchFilter.toLowerCase(); + } + + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + + txt = $rows[ rowIndex ].className; + // the first row can never be a child row + isChild = rowIndex && regex.child.test( txt ); + // skip child rows & already filtered rows + if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { + continue; + } + + data.$row = $rows.eq( rowIndex ); + data.cacheArray = norm_rows[ rowIndex ]; + rowData = data.cacheArray[ c.columns ]; + data.rawArray = rowData.raw; + data.childRowText = ''; + + if ( !wo.filter_childByColumn ) { + txt = ''; + // child row cached text + childRow = rowData.child; + // so, if 'table.config.widgetOptions.filter_childRows' is true and there is + // a match anywhere in the child row, then it will make the row visible + // checked here so the option can be changed dynamically + for ( indx = 0; indx < childRow.length; indx++ ) { + txt += ' ' + childRow[indx].join( '' ) || ''; + } + data.childRowText = wo.filter_childRows ? + ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : + ''; + } + + showRow = ts.filter.processRow( c, data, vars ); + childRow = rowData.$row.filter( ':gt( 0 )' ); + + if ( wo.filter_childRows && childRow.length ) { + if ( wo.filter_childByColumn ) { + // cycle through each child row + for ( indx = 0; indx < childRow.length; indx++ ) { + data.$row = childRow.eq( indx ); + data.cacheArray = rowData.child[ indx ]; + data.rawArray = data.cacheArray; + // use OR comparison on child rows + showRow = showRow || ts.filter.processRow( c, data, vars ); + } + } + childRow.toggleClass( wo.filter_filteredRow, !showRow ); + } + + rowData.$row + .toggleClass( wo.filter_filteredRow, !showRow )[0] + .display = showRow ? '' : 'none'; + } + } + c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; + c.totalRows += $rows.length; + ts.processTbody( table, $tbody, false ); + } + c.lastCombinedFilter = combinedFilters; // save last search + // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) + c.lastSearch = storedFilters; + c.$table.data( 'lastSearch', storedFilters ); + if ( wo.filter_saveFilters && ts.storage ) { + ts.storage( table, 'tablesorter-filters', storedFilters ); + } + if ( c.debug ) { + ts.benchmark( 'Completed filter widget search', time ); + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterEnd', c ); + } + setTimeout( function() { + c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied + }, 0 ); + }, + getOptionSource: function( table, column, onlyAvail ) { + table = $( table )[0]; + var cts, txt, indx, len, + c = table.config, + wo = c.widgetOptions, + parsed = [], + arry = false, + source = wo.filter_selectSource, + last = c.$table.data( 'lastSearch' ) || [], + fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); + + if ( onlyAvail && last[column] !== '' ) { + onlyAvail = false; + } + + // filter select source option + if ( fxn === true ) { + // OVERALL source + arry = source( table, column, onlyAvail ); + } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { + // selectSource is a jQuery object or string of options + return fxn; + } else if ( $.isArray( fxn ) ) { + arry = fxn; + } else if ( $.type( source ) === 'object' && fxn ) { + // custom select source function for a SPECIFIC COLUMN + arry = fxn( table, column, onlyAvail ); + } + if ( arry === false ) { + // fall back to original method + arry = ts.filter.getOptions( table, column, onlyAvail ); + } + + // get unique elements and sort the list + // if $.tablesorter.sortText exists ( not in the original tablesorter ), + // then natural sort the list otherwise use a basic sort + arry = $.grep( arry, function( value, indx ) { + return $.inArray( value, arry ) === indx; + }); + + if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { + // unsorted select options + return arry; + } else { + len = arry.length; + // parse select option values + for ( indx = 0; indx < len; indx++ ) { + txt = arry[ indx ]; + // parse array data using set column parser; this DOES NOT pass the original + // table cell to the parser format function + parsed.push({ + t : txt, + // check parser length - fixes #934 + p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + }); + } + + // sort parsed select options + cts = c.textSorter || ''; + parsed.sort( function( a, b ) { + // sortNatural breaks if you don't pass it strings + var x = a.p.toString(), + y = b.p.toString(); + if ( $.isFunction( cts ) ) { + // custom OVERALL text sorter + return cts( x, y, true, column, table ); + } else if ( typeof cts === 'object' && cts.hasOwnProperty( column ) ) { + // custom text sorter for a SPECIFIC COLUMN + return cts[column]( x, y, true, column, table ); + } else if ( ts.sortNatural ) { + // fall back to natural sort + return ts.sortNatural( x, y ); + } + // using an older version! do a basic sort + return true; + }); + // rebuild arry from sorted parsed data + arry = []; + len = parsed.length; + for ( indx = 0; indx < len; indx++ ) { + arry.push( parsed[indx].t ); + } + return arry; + } + }, + getOptions: function( table, column, onlyAvail ) { + table = $( table )[0]; + var rowIndex, tbodyIndex, len, row, cache, + c = table.config, + wo = c.widgetOptions, + arry = []; + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + cache = c.cache[tbodyIndex]; + len = c.cache[tbodyIndex].normalized.length; + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + // get cached row from cache.row ( old ) or row data object + // ( new; last item in normalized array ) + row = cache.row ? + cache.row[ rowIndex ] : + cache.normalized[ rowIndex ][ c.columns ].$row[0]; + // check if has class filtered + if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { + continue; + } + // get non-normalized cell content + if ( wo.filter_useParsedData || + c.parsers[column].parsed || + c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { + arry.push( '' + cache.normalized[ rowIndex ][ column ] ); + } else { + // get raw cached data instead of content directly from the cells + arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + } + } } return arry; - } - }, - getOptions: function( table, column, onlyAvail ) { - table = $( table )[0]; - var rowIndex, tbodyIndex, len, row, cache, - c = table.config, - wo = c.widgetOptions, - arry = []; - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - cache = c.cache[tbodyIndex]; - len = c.cache[tbodyIndex].normalized.length; - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - // get cached row from cache.row ( old ) or row data object - // ( new; last item in normalized array ) - row = cache.row ? - cache.row[ rowIndex ] : - cache.normalized[ rowIndex ][ c.columns ].$row[0]; - // check if has class filtered - if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { - continue; + }, + buildSelect: function( table, column, arry, updating, onlyAvail ) { + table = $( table )[0]; + column = parseInt( column, 10 ); + if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { + return; + } + var indx, val, txt, t, $filters, $filter, + c = table.config, + wo = c.widgetOptions, + node = c.$headerIndexed[ column ], + // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 + options = '', + // Get curent filter value + currentValue = c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .val(); + // nothing included in arry ( external source ), so get the options from + // filter_selectSource or column data + if ( typeof arry === 'undefined' || arry === '' ) { + arry = ts.filter.getOptionSource( table, column, onlyAvail ); + } + + if ( $.isArray( arry ) ) { + // build option list + for ( indx = 0; indx < arry.length; indx++ ) { + txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); + val = txt; + // allow including a symbol in the selectSource array + // 'a-z|A through Z' so that 'a-z' becomes the option value + // and 'A through Z' becomes the option text + if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + t = txt.split( wo.filter_selectSourceSeparator ); + val = t[0]; + txt = t[1]; + } + // replace quotes - fixes #242 & ignore empty strings + // see http://stackoverflow.com/q/14990971/145346 + options += arry[indx] !== '' ? + '' : ''; } - // get non-normalized cell content - if ( wo.filter_useParsedData || - c.parsers[column].parsed || - c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { - arry.push( '' + cache.normalized[ rowIndex ][ column ] ); - } else { - // get raw cached data instead of content directly from the cells - arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + // clear arry so it doesn't get appended twice + arry = []; + } + + // update all selects in the same column ( clone thead in sticky headers & + // any external selects ) - fixes 473 + $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) + .find( '.' + tscss.filter ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; + } + $filter = $filters.filter( 'select[data-column="' + column + '"]' ); + + // make sure there is a select there! + if ( $filter.length ) { + $filter[ updating ? 'html' : 'append' ]( options ); + if ( !$.isArray( arry ) ) { + // append options if arry is provided externally as a string or jQuery object + // options ( default value ) was already added + $filter.append( arry ).val( currentValue ); + } + $filter.val( currentValue ); + } + }, + buildDefault: function( table, updating ) { + var columnIndex, $header, noSelect, + c = table.config, + wo = c.widgetOptions, + columns = c.columns; + // build default select dropdown + for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { + $header = c.$headerIndexed[columnIndex]; + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + // look for the filter-select class; build/update it if found + if ( ( $header.hasClass( 'filter-select' ) || + ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { + ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); } } } - return arry; - }, - buildSelect: function( table, column, arry, updating, onlyAvail ) { - table = $( table )[0]; - column = parseInt( column, 10 ); - if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { - return; - } - var indx, val, txt, t, $filters, $filter, - c = table.config, - wo = c.widgetOptions, - node = c.$headerIndexed[ column ], - // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 - options = '', - // Get curent filter value - currentValue = c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .val(); - // nothing included in arry ( external source ), so get the options from - // filter_selectSource or column data - if ( typeof arry === 'undefined' || arry === '' ) { - arry = ts.filter.getOptionSource( table, column, onlyAvail ); - } + }; - if ( $.isArray( arry ) ) { - // build option list - for ( indx = 0; indx < arry.length; indx++ ) { - txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); - val = txt; - // allow including a symbol in the selectSource array - // 'a-z|A through Z' so that 'a-z' becomes the option value - // and 'A through Z' becomes the option text - if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - t = txt.split( wo.filter_selectSourceSeparator ); - val = t[0]; - txt = t[1]; - } - // replace quotes - fixes #242 & ignore empty strings - // see http://stackoverflow.com/q/14990971/145346 - options += arry[indx] !== '' ? - '' : ''; + ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { + var i, $filters, $column, cols, + filters = false, + c = table ? $( table )[0].config : '', + wo = c ? c.widgetOptions : ''; + if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || + // setFilters called, but last search is exactly the same as the current + // fixes issue #733 & #903 where calling update causes the input values to reset + ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { + return $( table ).data( 'lastSearch' ); + } + if ( c ) { + if ( c.$filters ) { + $filters = c.$filters.find( '.' + tscss.filter ); } - // clear arry so it doesn't get appended twice - arry = []; - } - - // update all selects in the same column ( clone thead in sticky headers & - // any external selects ) - fixes 473 - $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) - .find( '.' + tscss.filter ); - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - $filter = $filters.filter( 'select[data-column="' + column + '"]' ); - - // make sure there is a select there! - if ( $filter.length ) { - $filter[ updating ? 'html' : 'append' ]( options ); - if ( !$.isArray( arry ) ) { - // append options if arry is provided externally as a string or jQuery object - // options ( default value ) was already added - $filter.append( arry ).val( currentValue ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; } - $filter.val( currentValue ); - } - }, - buildDefault: function( table, updating ) { - var columnIndex, $header, noSelect, - c = table.config, - wo = c.widgetOptions, - columns = c.columns; - // build default select dropdown - for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { - $header = c.$headerIndexed[columnIndex]; - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - // look for the filter-select class; build/update it if found - if ( ( $header.hasClass( 'filter-select' ) || - ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { - ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } -}; - -ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { - var i, $filters, $column, cols, - filters = false, - c = table ? $( table )[0].config : '', - wo = c ? c.widgetOptions : ''; - if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || - // setFilters called, but last search is exactly the same as the current - // fixes issue #733 & #903 where calling update causes the input values to reset - ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { - return $( table ).data( 'lastSearch' ); - } - if ( c ) { - if ( c.$filters ) { - $filters = c.$filters.find( '.' + tscss.filter ); - } - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - if ( $filters && $filters.length ) { - filters = setFilters || []; - for ( i = 0; i < c.columns + 1; i++ ) { - cols = ( i === c.columns ? - // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) - wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : - '[data-column="' + i + '"]' ); - $column = $filters.filter( cols ); - if ( $column.length ) { - // move the latest search to the first slot in the array - $column = ts.filter.getLatestSearch( $column ); - if ( $.isArray( setFilters ) ) { - // skip first ( latest input ) to maintain cursor position while typing - if ( skipFirst && $column.length > 1 ) { - $column = $column.slice( 1 ); - } - if ( i === c.columns ) { - // prevent data-column='all' from filling data-column='0,1' ( etc ) - cols = $column.filter( wo.filter_anyColumnSelector ); - $column = cols.length ? cols : $column; - } - $column - .val( setFilters[ i ] ) - .trigger( 'change.tsfilter' ); - } else { - filters[i] = $column.val() || ''; - // don't change the first... it will move the cursor - if ( i === c.columns ) { - // don't update range columns from 'all' setting + if ( $filters && $filters.length ) { + filters = setFilters || []; + for ( i = 0; i < c.columns + 1; i++ ) { + cols = ( i === c.columns ? + // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) + wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : + '[data-column="' + i + '"]' ); + $column = $filters.filter( cols ); + if ( $column.length ) { + // move the latest search to the first slot in the array + $column = ts.filter.getLatestSearch( $column ); + if ( $.isArray( setFilters ) ) { + // skip first ( latest input ) to maintain cursor position while typing + if ( skipFirst && $column.length > 1 ) { + $column = $column.slice( 1 ); + } + if ( i === c.columns ) { + // prevent data-column='all' from filling data-column='0,1' ( etc ) + cols = $column.filter( wo.filter_anyColumnSelector ); + $column = cols.length ? cols : $column; + } $column - .slice( 1 ) - .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) - .val( filters[ i ] ); + .val( setFilters[ i ] ) + .trigger( 'change.tsfilter' ); } else { - $column - .slice( 1 ) - .val( filters[ i ] ); + filters[i] = $column.val() || ''; + // don't change the first... it will move the cursor + if ( i === c.columns ) { + // don't update range columns from 'all' setting + $column + .slice( 1 ) + .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) + .val( filters[ i ] ); + } else { + $column + .slice( 1 ) + .val( filters[ i ] ); + } + } + // save any match input dynamically + if ( i === c.columns && $column.length ) { + wo.filter_$anyMatch = $column; } - } - // save any match input dynamically - if ( i === c.columns && $column.length ) { - wo.filter_$anyMatch = $column; } } } } - } - if ( filters.length === 0 ) { - filters = false; - } - return filters; -}; + if ( filters.length === 0 ) { + filters = false; + } + return filters; + }; -ts.setFilters = function( table, filter, apply, skipFirst ) { - var c = table ? $( table )[0].config : '', - valid = ts.getFilters( table, true, filter, skipFirst ); - if ( c && apply ) { - // ensure new set filters are applied, even if the search is the same - c.lastCombinedFilter = null; - c.lastSearch = []; - ts.filter.searching( c.table, filter, skipFirst ); - c.$table.trigger( 'filterFomatterUpdate' ); - } - return !!valid; -}; + ts.setFilters = function( table, filter, apply, skipFirst ) { + var c = table ? $( table )[0].config : '', + valid = ts.getFilters( table, true, filter, skipFirst ); + if ( c && apply ) { + // ensure new set filters are applied, even if the search is the same + c.lastCombinedFilter = null; + c.lastSearch = []; + ts.filter.searching( c.table, filter, skipFirst ); + c.$table.trigger( 'filterFomatterUpdate' ); + } + return !!valid; + }; })( jQuery ); @@ -1985,750 +1985,751 @@ ts.setFilters = function( table, filter, apply, skipFirst ) { * by Rob Garrison */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - sticky : 'tablesorter-stickyHeader', // stickyHeader - stickyVis : 'tablesorter-sticky-visible', - stickyHide: 'tablesorter-sticky-hidden', - stickyWrap: 'tablesorter-sticky-wrapper' -}); + $.extend(ts.css, { + sticky : 'tablesorter-stickyHeader', // stickyHeader + stickyVis : 'tablesorter-sticky-visible', + stickyHide: 'tablesorter-sticky-hidden', + stickyWrap: 'tablesorter-sticky-wrapper' + }); -// Add a resize event to table headers -ts.addHeaderResizeEvent = function(table, disable, settings) { - table = $(table)[0]; // make sure we're using a dom element - if ( !table.config ) { return; } - var defaults = { - timer : 250 + // Add a resize event to table headers + ts.addHeaderResizeEvent = function(table, disable, settings) { + table = $(table)[0]; // make sure we're using a dom element + if ( !table.config ) { return; } + var defaults = { + timer : 250 + }, + options = $.extend({}, defaults, settings), + c = table.config, + wo = c.widgetOptions, + checkSizes = function( triggerEvent ) { + var index, headers, $header, sizes, width, height, + len = c.$headers.length; + wo.resize_flag = true; + headers = []; + for ( index = 0; index < len; index++ ) { + $header = c.$headers.eq( index ); + sizes = $header.data( 'savedSizes' ) || [ 0, 0 ]; // fixes #394 + width = $header[0].offsetWidth; + height = $header[0].offsetHeight; + if ( width !== sizes[0] || height !== sizes[1] ) { + $header.data( 'savedSizes', [ width, height ] ); + headers.push( $header[0] ); + } + } + if ( headers.length && triggerEvent !== false ) { + c.$table.trigger( 'resize', [ headers ] ); + } + wo.resize_flag = false; + }; + checkSizes( false ); + clearInterval(wo.resize_timer); + if (disable) { + wo.resize_flag = false; + return false; + } + wo.resize_timer = setInterval(function() { + if (wo.resize_flag) { return; } + checkSizes(); + }, options.timer); + }; + + // Sticky headers based on this awesome article: + // http://css-tricks.com/13465-persistent-headers/ + // and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech + // ************************** + ts.addWidget({ + id: 'stickyHeaders', + priority: 60, // sticky widget must be initialized after the filter widget! + options: { + stickyHeaders : '', // extra class name added to the sticky header row + stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to + stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) + stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) + stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element + stickyHeaders_filteredToTop: true, // scroll table top into view after filtering + stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists + stickyHeaders_addResizeEvent : true, // trigger 'resize' event on headers + stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header + stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs }, - options = $.extend({}, defaults, settings), - c = table.config, - wo = c.widgetOptions, - checkSizes = function( triggerEvent ) { - var index, headers, $header, sizes, width, height, - len = c.$headers.length; - wo.resize_flag = true; - headers = []; - for ( index = 0; index < len; index++ ) { - $header = c.$headers.eq( index ); - sizes = $header.data( 'savedSizes' ) || [ 0,0 ]; // fixes #394 - width = $header[0].offsetWidth; - height = $header[0].offsetHeight; - if ( width !== sizes[0] || height !== sizes[1] ) { - $header.data( 'savedSizes', [ width, height ] ); - headers.push( $header[0] ); + format: function(table, c, wo) { + // filter widget doesn't initialize on an empty table. Fixes #449 + if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { + return; + } + var index, len, $t, + $table = c.$table, + // add position: relative to attach element, hopefully it won't cause trouble. + $attach = $(wo.stickyHeaders_attachTo), + namespace = c.namespace + 'stickyheaders ', + // element to watch for the scroll event + $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), + $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), + $thead = $table.children('thead:first'), + $header = $thead.children('tr').not('.sticky-false').children(), + $tfoot = $table.children('tfoot'), + $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, + // is this table nested? If so, find parent sticky header wrapper (div, not table) + $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? + $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], + nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, + // clone table, then wrap to make sticky header + $stickyTable = wo.$sticky = $table.clone() + .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) + .wrap('
'), + $stickyWrap = $stickyTable.parent() + .addClass(ts.css.stickyHide) + .css({ + position : $attach.length ? 'absolute' : 'fixed', + padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), + top : stickyOffset + nestedStickyTop, + left : 0, + visibility : 'hidden', + zIndex : wo.stickyHeaders_zIndex || 2 + }), + $stickyThead = $stickyTable.children('thead:first'), + $stickyCells, + laststate = '', + spacing = 0, + setWidth = function($orig, $clone){ + var index, width, border, $cell, $this, + $cells = $orig.filter(':visible'), + len = $cells.length; + for ( index = 0; index < len; index++ ) { + $cell = $clone.filter(':visible').eq(index); + $this = $cells.eq(index); + // code from https://github.com/jmosbech/StickyTableHeaders + if ($this.css('box-sizing') === 'border-box') { + width = $this.outerWidth(); + } else { + if ($cell.css('border-collapse') === 'collapse') { + if (window.getComputedStyle) { + width = parseFloat( window.getComputedStyle($this[0], null).width ); + } else { + // ie8 only + border = parseFloat( $this.css('border-width') ); + width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; + } + } else { + width = $this.width(); + } + } + $cell.css({ + 'width': width, + 'min-width': width, + 'max-width': width + }); + } + }, + resizeHeader = function() { + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; + spacing = 0; + $stickyWrap.css({ + left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : + $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, + width: $table.outerWidth() + }); + setWidth( $table, $stickyTable ); + setWidth( $header, $stickyCells ); + }, + scrollSticky = function( resizing ) { + if (!$table.is(':visible')) { return; } // fixes #278 + // Detect nested tables - fixes #724 + nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; + var offset = $table.offset(), + yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 + xWindow = $.isWindow( $xScroll[0] ), + // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), + isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', + cssSettings = { visibility : isVisible }; + + if ($attach.length) { + cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + } + if (xWindow) { + // adjust when scrolling horizontally - fixes issue #143 + cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; + } + if ($nestedSticky.length) { + cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; + } + $stickyWrap + .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) + .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) + .css(cssSettings); + if (isVisible !== laststate || resizing) { + // make sure the column widths match + resizeHeader(); + laststate = isVisible; + } + }; + // only add a position relative if a position isn't already defined + if ($attach.length && !$attach.css('position')) { + $attach.css('position', 'relative'); + } + // fix clone ID, if it exists - fixes #271 + if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } + // clear out cloned table, except for sticky header + // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing + $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); + $stickyTable.find('tbody, tfoot').remove(); + $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); + // issue #172 - find td/th in sticky header + $stickyCells = $stickyThead.children().children(); + $stickyTable.css({ height:0, width:0, margin: 0 }); + // remove resizable block + $stickyCells.find('.' + ts.css.resizer).remove(); + // update sticky header class names to match real header after sorting + $table + .addClass('hasStickyHeaders') + .bind('pagerComplete' + namespace, function() { + resizeHeader(); + }); + + ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); + + // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. + $table.after( $stickyWrap ); + + // onRenderHeader is defined, we need to do something about it (fixes #641) + if (c.onRenderHeader) { + $t = $stickyThead.children('tr').children(); + len = $t.length; + for ( index = 0; index < len; index++ ) { + // send second parameter + c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } } - if ( headers.length && triggerEvent !== false ) { - c.$table.trigger( 'resize', [ headers ] ); - } - wo.resize_flag = false; - }; - checkSizes( false ); - clearInterval(wo.resize_timer); - if (disable) { - wo.resize_flag = false; - return false; - } - wo.resize_timer = setInterval(function() { - if (wo.resize_flag) { return; } - checkSizes(); - }, options.timer); -}; -// Sticky headers based on this awesome article: -// http://css-tricks.com/13465-persistent-headers/ -// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech -// ************************** -ts.addWidget({ - id: "stickyHeaders", - priority: 60, // sticky widget must be initialized after the filter widget! - options: { - stickyHeaders : '', // extra class name added to the sticky header row - stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to - stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) - stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) - stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element - stickyHeaders_filteredToTop: true, // scroll table top into view after filtering - stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists - stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers - stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header - stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs - }, - format: function(table, c, wo) { - // filter widget doesn't initialize on an empty table. Fixes #449 - if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { - return; - } - var index, len, $t, - $table = c.$table, - // add position: relative to attach element, hopefully it won't cause trouble. - $attach = $(wo.stickyHeaders_attachTo), - namespace = c.namespace + 'stickyheaders ', - // element to watch for the scroll event - $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), - $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), - $thead = $table.children('thead:first'), - $header = $thead.children('tr').not('.sticky-false').children(), - $tfoot = $table.children('tfoot'), - $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, - // is this table nested? If so, find parent sticky header wrapper (div, not table) - $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? - $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], - nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, - // clone table, then wrap to make sticky header - $stickyTable = wo.$sticky = $table.clone() - .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) - .wrap('
'), - $stickyWrap = $stickyTable.parent() - .addClass(ts.css.stickyHide) - .css({ - position : $attach.length ? 'absolute' : 'fixed', - padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), - top : stickyOffset + nestedStickyTop, - left : 0, - visibility : 'hidden', - zIndex : wo.stickyHeaders_zIndex || 2 - }), - $stickyThead = $stickyTable.children('thead:first'), - $stickyCells, - laststate = '', - spacing = 0, - setWidth = function($orig, $clone){ - var index, width, border, $cell, $this, - $cells = $orig.filter(':visible'), - len = $cells.length; - for ( index = 0; index < len; index++ ) { - $cell = $clone.filter(':visible').eq(index); - $this = $cells.eq(index); - // code from https://github.com/jmosbech/StickyTableHeaders - if ($this.css('box-sizing') === 'border-box') { - width = $this.outerWidth(); - } else { - if ($cell.css('border-collapse') === 'collapse') { - if (window.getComputedStyle) { - width = parseFloat( window.getComputedStyle($this[0], null).width ); - } else { - // ie8 only - border = parseFloat( $this.css('border-width') ); - width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; - } - } else { - width = $this.width(); + // make it sticky! + $xScroll.add($yScroll) + .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) + .bind('scroll resize '.split(' ').join( namespace ), function( event ) { + scrollSticky( event.type === 'resize' ); + }); + c.$table + .unbind('stickyHeadersUpdate' + namespace) + .bind('stickyHeadersUpdate' + namespace, function(){ + scrollSticky( true ); + }); + + if (wo.stickyHeaders_addResizeEvent) { + ts.addHeaderResizeEvent(table); + } + + // look for filter widget + if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { + // scroll table into view after filtering, if sticky header is active - #482 + $table.bind('filterEnd' + namespace, function() { + // $(':focus') needs jQuery 1.6+ + var $td = $(document.activeElement).closest('td'), + column = $td.parent().children().index($td); + // only scroll if sticky header is active + if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { + // scroll to original table (not sticky clone) + window.scrollTo(0, $table.position().top); + // give same input/select focus; check if c.$filters exists; fixes #594 + if (column >= 0 && c.$filters) { + c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); } } - $cell.css({ - 'width': width, - 'min-width': width, - 'max-width': width - }); - } - }, - resizeHeader = function() { - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; - spacing = 0; - $stickyWrap.css({ - left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : - $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, - width: $table.outerWidth() }); - setWidth( $table, $stickyTable ); - setWidth( $header, $stickyCells ); - }, - scrollSticky = function( resizing ) { - if (!$table.is(':visible')) { return; } // fixes #278 - // Detect nested tables - fixes #724 - nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; - var offset = $table.offset(), - yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 - xWindow = $.isWindow( $xScroll[0] ), - // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), - isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', - cssSettings = { visibility : isVisible }; - - if ($attach.length) { - cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); + // support hideFilters + if (wo.filter_hideFilters) { + ts.filter.hideFilters($stickyTable, c); } - if (xWindow) { - // adjust when scrolling horizontally - fixes issue #143 - cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; - } - if ($nestedSticky.length) { - cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; - } - $stickyWrap - .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) - .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) - .css(cssSettings); - if (isVisible !== laststate || resizing) { - // make sure the column widths match - resizeHeader(); - laststate = isVisible; - } - }; - // only add a position relative if a position isn't already defined - if ($attach.length && !$attach.css('position')) { - $attach.css('position', 'relative'); - } - // fix clone ID, if it exists - fixes #271 - if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } - // clear out cloned table, except for sticky header - // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing - $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); - $stickyTable.find('tbody, tfoot').remove(); - $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); - // issue #172 - find td/th in sticky header - $stickyCells = $stickyThead.children().children(); - $stickyTable.css({ height:0, width:0, margin: 0 }); - // remove resizable block - $stickyCells.find('.' + ts.css.resizer).remove(); - // update sticky header class names to match real header after sorting - $table - .addClass('hasStickyHeaders') - .bind('pagerComplete' + namespace, function() { - resizeHeader(); - }); - - ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); - - // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. - $table.after( $stickyWrap ); - - // onRenderHeader is defined, we need to do something about it (fixes #641) - if (c.onRenderHeader) { - $t = $stickyThead.children('tr').children(); - len = $t.length; - for ( index = 0; index < len; index++ ) { - // send second parameter - c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } + + $table.trigger('stickyHeadersInit'); + + }, + remove: function(table, c, wo) { + var namespace = c.namespace + 'stickyheaders '; + c.$table + .removeClass('hasStickyHeaders') + .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) + .next('.' + ts.css.stickyWrap).remove(); + if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table + $(window) + .add(wo.stickyHeaders_xScroll) + .add(wo.stickyHeaders_yScroll) + .add(wo.stickyHeaders_attachTo) + .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); + ts.addHeaderResizeEvent(table, false); } - - // make it sticky! - $xScroll.add($yScroll) - .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) - .bind('scroll resize '.split(' ').join( namespace ), function( event ) { - scrollSticky( event.type === 'resize' ); - }); - c.$table - .unbind('stickyHeadersUpdate' + namespace) - .bind('stickyHeadersUpdate' + namespace, function(){ - scrollSticky( true ); - }); - - if (wo.stickyHeaders_addResizeEvent) { - ts.addHeaderResizeEvent(table); - } - - // look for filter widget - if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { - // scroll table into view after filtering, if sticky header is active - #482 - $table.bind('filterEnd' + namespace, function() { - // $(':focus') needs jQuery 1.6+ - var $td = $(document.activeElement).closest('td'), - column = $td.parent().children().index($td); - // only scroll if sticky header is active - if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { - // scroll to original table (not sticky clone) - window.scrollTo(0, $table.position().top); - // give same input/select focus; check if c.$filters exists; fixes #594 - if (column >= 0 && c.$filters) { - c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); - } - } - }); - ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); - // support hideFilters - if (wo.filter_hideFilters) { - ts.filter.hideFilters($stickyTable, c); - } - } - - $table.trigger('stickyHeadersInit'); - - }, - remove: function(table, c, wo) { - var namespace = c.namespace + 'stickyheaders '; - c.$table - .removeClass('hasStickyHeaders') - .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) - .next('.' + ts.css.stickyWrap).remove(); - if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table - $(window) - .add(wo.stickyHeaders_xScroll) - .add(wo.stickyHeaders_yScroll) - .add(wo.stickyHeaders_attachTo) - .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); - ts.addHeaderResizeEvent(table, false); - } -}); + }); })(jQuery, window); /*! Widget: resizable - updated 6/26/2015 (v2.22.2) */ /*jshint browser:true, jquery:true, unused:false */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - resizableContainer : 'tablesorter-resizable-container', - resizableHandle : 'tablesorter-resizable-handle', - resizableNoSelect : 'tablesorter-disableSelection', - resizableStorage : 'tablesorter-resizable' -}); + $.extend(ts.css, { + resizableContainer : 'tablesorter-resizable-container', + resizableHandle : 'tablesorter-resizable-handle', + resizableNoSelect : 'tablesorter-disableSelection', + resizableStorage : 'tablesorter-resizable' + }); -// Add extra scroller css -$(function(){ - var s = ''; - $(s).appendTo('body'); -}); + // Add extra scroller css + $(function(){ + var s = ''; + $(s).appendTo('body'); + }); -ts.resizable = { - init : function( c, wo ) { - if ( c.$table.hasClass( 'hasResizable' ) ) { return; } - c.$table.addClass( 'hasResizable' ); + ts.resizable = { + init : function( c, wo ) { + if ( c.$table.hasClass( 'hasResizable' ) ) { return; } + c.$table.addClass( 'hasResizable' ); - var noResize, $header, column, storedSizes, tmp, - $table = c.$table, - $parent = $table.parent(), - marginTop = parseInt( $table.css( 'margin-top' ), 10 ), + var noResize, $header, column, storedSizes, tmp, + $table = c.$table, + $parent = $table.parent(), + marginTop = parseInt( $table.css( 'margin-top' ), 10 ), - // internal variables - vars = wo.resizable_ = { - useStorage : ts.storage && wo.resizable !== false, - $wrap : $parent, - mouseXPosition : 0, - $target : null, - $next : null, - overflow : $parent.css('overflow') === 'auto' || - $parent.css('overflow') === 'scroll' || - $parent.css('overflow-x') === 'auto' || - $parent.css('overflow-x') === 'scroll', - storedSizes : [] - }; + // internal variables + vars = wo.resizable_vars = { + useStorage : ts.storage && wo.resizable !== false, + $wrap : $parent, + mouseXPosition : 0, + $target : null, + $next : null, + overflow : $parent.css('overflow') === 'auto' || + $parent.css('overflow') === 'scroll' || + $parent.css('overflow-x') === 'auto' || + $parent.css('overflow-x') === 'scroll', + storedSizes : [] + }; - // set default widths - ts.resizableReset( c.table, true ); + // set default widths + ts.resizableReset( c.table, true ); - // now get measurements! - vars.tableWidth = $table.width(); - // attempt to autodetect - vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; + // now get measurements! + vars.tableWidth = $table.width(); + // attempt to autodetect + vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; - /* - // Hacky method to determine if table width is set to "auto" - // http://stackoverflow.com/a/20892048/145346 - if ( !vars.fullWidth ) { - tmp = $table.width(); - $header = $table.wrap('').parent(); // temp variable - storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; - $table.css( 'margin-left', storedSizes + 50 ); - vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; - $table.css( 'margin-left', storedSizes ? storedSizes : '' ); - $header = null; - $table.unwrap(''); - } - */ - - if ( vars.useStorage && vars.overflow ) { - // save table width - ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); - tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; - ts.resizable.setWidth( $table, tmp, true ); - } - wo.resizable_.storedSizes = storedSizes = ( vars.useStorage ? - ts.storage( c.table, ts.css.resizableStorage ) : - [] ) || []; - ts.resizable.setWidths( c, wo, storedSizes ); - ts.resizable.updateStoredSizes( c, wo ); - - wo.$resizable_container = $( '
' ) - .css({ top : marginTop }) - .insertBefore( $table ); - // add container - for ( column = 0; column < c.columns; column++ ) { - $header = c.$headerIndexed[ column ]; - tmp = ts.getColumnData( c.table, c.headers, column ); - noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; - if ( !noResize ) { - $( '
' ) - .appendTo( wo.$resizable_container ) - .attr({ - 'data-column' : column, - 'unselectable' : 'on' - }) - .data( 'header', $header ) - .bind( 'selectstart', false ); + /* + // Hacky method to determine if table width is set to 'auto' + // http://stackoverflow.com/a/20892048/145346 + if ( !vars.fullWidth ) { + tmp = $table.width(); + $header = $table.wrap('').parent(); // temp variable + storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; + $table.css( 'margin-left', storedSizes + 50 ); + vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; + $table.css( 'margin-left', storedSizes ? storedSizes : '' ); + $header = null; + $table.unwrap(''); } - } - $table.one('tablesorter-initialized', function() { - ts.resizable.setHandlePosition( c, wo ); - ts.resizable.bindings( this.config, this.config.widgetOptions ); - }); - }, + */ - updateStoredSizes : function( c, wo ) { - var column, $header, - len = c.columns, - vars = wo.resizable_; - vars.storedSizes = []; - for ( column = 0; column < len; column++ ) { - $header = c.$headerIndexed[ column ]; - vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; - } - }, + if ( vars.useStorage && vars.overflow ) { + // save table width + ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); + tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; + ts.resizable.setWidth( $table, tmp, true ); + } + wo.resizable_vars.storedSizes = storedSizes = ( vars.useStorage ? + ts.storage( c.table, ts.css.resizableStorage ) : + [] ) || []; + ts.resizable.setWidths( c, wo, storedSizes ); + ts.resizable.updateStoredSizes( c, wo ); - setWidth : function( $el, width, overflow ) { - // overflow tables need min & max width set as well - $el.css({ - 'width' : width, - 'min-width' : overflow ? width : '', - 'max-width' : overflow ? width : '' - }); - }, - - setWidths : function( c, wo, storedSizes ) { - var column, $temp, - vars = wo.resizable_, - $extra = $( c.namespace + '_extra_headers' ), - $col = c.$table.children( 'colgroup' ).children( 'col' ); - storedSizes = storedSizes || vars.storedSizes || []; - // process only if table ID or url match - if ( storedSizes.length ) { + wo.$resizable_container = $( '
' ) + .css({ top : marginTop }) + .insertBefore( $table ); + // add container for ( column = 0; column < c.columns; column++ ) { - // set saved resizable widths - ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); - if ( $extra.length ) { - // stickyHeaders needs to modify min & max width as well - $temp = $extra.eq( column ).add( $col.eq( column ) ); - ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + $header = c.$headerIndexed[ column ]; + tmp = ts.getColumnData( c.table, c.headers, column ); + noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; + if ( !noResize ) { + $( '
' ) + .appendTo( wo.$resizable_container ) + .attr({ + 'data-column' : column, + 'unselectable' : 'on' + }) + .data( 'header', $header ) + .bind( 'selectstart', false ); } } - $temp = $( c.namespace + '_extra_table' ); - if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { - ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); - } - } - }, - - setHandlePosition : function( c, wo ) { - var startPosition, - hasScroller = ts.hasWidget( c.table, 'scroller' ), - tableHeight = c.$table.height(), - $handles = wo.$resizable_container.children(), - handleCenter = Math.floor( $handles.width() / 2 ); - - if ( hasScroller ) { - tableHeight = 0; - c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ - var $this = $(this); - // center table has a max-height set - tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); + $table.one('tablesorter-initialized', function() { + ts.resizable.setHandlePosition( c, wo ); + ts.resizable.bindings( this.config, this.config.widgetOptions ); }); - } - // subtract out table left position from resizable handles. Fixes #864 - startPosition = c.$table.position().left; - $handles.each( function() { - var $this = $(this), - column = parseInt( $this.attr( 'data-column' ), 10 ), - columns = c.columns - 1, - $header = $this.data( 'header' ); - if ( !$header ) { return; } // see #859 - if ( !$header.is(':visible') ) { - $this.hide(); - } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { - $this.css({ - display: 'inline-block', - height : tableHeight, - left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }, + + updateStoredSizes : function( c, wo ) { + var column, $header, + len = c.columns, + vars = wo.resizable_vars; + vars.storedSizes = []; + for ( column = 0; column < len; column++ ) { + $header = c.$headerIndexed[ column ]; + vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; + } + }, + + setWidth : function( $el, width, overflow ) { + // overflow tables need min & max width set as well + $el.css({ + 'width' : width, + 'min-width' : overflow ? width : '', + 'max-width' : overflow ? width : '' + }); + }, + + setWidths : function( c, wo, storedSizes ) { + var column, $temp, + vars = wo.resizable_vars, + $extra = $( c.namespace + '_extra_headers' ), + $col = c.$table.children( 'colgroup' ).children( 'col' ); + storedSizes = storedSizes || vars.storedSizes || []; + // process only if table ID or url match + if ( storedSizes.length ) { + for ( column = 0; column < c.columns; column++ ) { + // set saved resizable widths + ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); + if ( $extra.length ) { + // stickyHeaders needs to modify min & max width as well + $temp = $extra.eq( column ).add( $col.eq( column ) ); + ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + } + } + $temp = $( c.namespace + '_extra_table' ); + if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { + ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); + } + } + }, + + setHandlePosition : function( c, wo ) { + var startPosition, + hasScroller = ts.hasWidget( c.table, 'scroller' ), + tableHeight = c.$table.height(), + $handles = wo.$resizable_container.children(), + handleCenter = Math.floor( $handles.width() / 2 ); + + if ( hasScroller ) { + tableHeight = 0; + c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ + var $this = $(this); + // center table has a max-height set + tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); }); } - }); - }, - - // prevent text selection while dragging resize bar - toggleTextSelection : function( c, toggle ) { - var namespace = c.namespace + 'tsresize'; - c.widgetOptions.resizable_.disabled = toggle; - $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); - if ( toggle ) { - $( 'body' ) - .attr( 'unselectable', 'on' ) - .bind( 'selectstart' + namespace, false ); - } else { - $( 'body' ) - .removeAttr( 'unselectable' ) - .unbind( 'selectstart' + namespace ); - } - }, - - bindings : function( c, wo ) { - var namespace = c.namespace + 'tsresize'; - wo.$resizable_container.children().bind( 'mousedown', function( event ) { - // save header cell and mouse position - var column, - vars = wo.resizable_, - $extras = $( c.namespace + '_extra_headers' ), - $header = $( event.target ).data( 'header' ); - - column = parseInt( $header.attr( 'data-column' ), 10 ); - vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); - vars.target = column; - - // if table is not as wide as it's parent, then resize the table - vars.$next = event.shiftKey || wo.resizable_targetLast ? - $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : - $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); - - column = parseInt( vars.$next.attr( 'data-column' ), 10 ); - vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); - vars.next = column; - - vars.mouseXPosition = event.pageX; - ts.resizable.updateStoredSizes( c, wo ); - ts.resizable.toggleTextSelection( c, true ); - }); - - $( document ) - .bind( 'mousemove' + namespace, function( event ) { - var vars = wo.resizable_; - // ignore mousemove if no mousedown - if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } - if ( wo.resizable_throttle ) { - clearTimeout( vars.timer ); - vars.timer = setTimeout( function() { - ts.resizable.mouseMove( c, wo, event ); - }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); - } else { - ts.resizable.mouseMove( c, wo, event ); + // subtract out table left position from resizable handles. Fixes #864 + startPosition = c.$table.position().left; + $handles.each( function() { + var $this = $(this), + column = parseInt( $this.attr( 'data-column' ), 10 ), + columns = c.columns - 1, + $header = $this.data( 'header' ); + if ( !$header ) { return; } // see #859 + if ( !$header.is(':visible') ) { + $this.hide(); + } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { + $this.css({ + display: 'inline-block', + height : tableHeight, + left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }); } - }) - .bind( 'mouseup' + namespace, function() { - if (!wo.resizable_.disabled) { return; } - ts.resizable.toggleTextSelection( c, false ); - ts.resizable.stopResize( c, wo ); - ts.resizable.setHandlePosition( c, wo ); }); + }, - // resizeEnd event triggered by scroller widget - $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }); - - // right click to reset columns to default widths - c.$table - .bind( 'columnUpdate' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }) - .find( 'thead:first' ) - .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) - .bind( 'contextmenu' + namespace, function() { - // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset - var allowClick = wo.resizable_.storedSizes.length === 0; - ts.resizableReset( c.table ); - ts.resizable.setHandlePosition( c, wo ); - wo.resizable_.storedSizes = []; - return allowClick; - }); - - }, - - mouseMove : function( c, wo, event ) { - if ( wo.resizable_.mouseXPosition === 0 || !wo.resizable_.$target ) { return; } - // resize columns - var column, - total = 0, - vars = wo.resizable_, - $next = vars.$next, - tar = vars.storedSizes[ vars.target ], - leftEdge = event.pageX - vars.mouseXPosition; - if ( vars.overflow ) { - if ( tar + leftEdge > 0 ) { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); - // update the entire table width - for ( column = 0; column < c.columns; column++ ) { - total += vars.storedSizes[ column ]; - } - ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); - } - if ( !$next.length ) { - // if expanding right-most column, scroll the wrapper - vars.$wrap[0].scrollLeft = c.$table.width(); - } - } else if ( vars.fullWidth ) { - vars.storedSizes[ vars.target ] += leftEdge; - vars.storedSizes[ vars.next ] -= leftEdge; - ts.resizable.setWidths( c, wo ); - } else { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidths( c, wo ); - } - vars.mouseXPosition = event.pageX; - // dynamically update sticky header widths - c.$table.trigger('stickyHeadersUpdate'); - }, - - stopResize : function( c, wo ) { - var vars = wo.resizable_; - ts.resizable.updateStoredSizes( c, wo ); - if ( vars.useStorage ) { - // save all column widths - ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); - ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); - } - vars.mouseXPosition = 0; - vars.$target = vars.$next = null; - // will update stickyHeaders, just in case, see #912 - c.$table.trigger('stickyHeadersUpdate'); - } -}; - -// this widget saves the column widths if -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: "resizable", - priority: 40, - options: { - resizable : true, // save column widths to storage - resizable_addLastColumn : false, - resizable_widths : [], - resizable_throttle : false, // set to true (5ms) or any number 0-10 range - resizable_targetLast : false, - resizable_fullWidth : null - }, - init: function(table, thisWidget, c, wo) { - ts.resizable.init( c, wo ); - }, - remove: function( table, c, wo, refreshing ) { - if (wo.$resizable_container) { + // prevent text selection while dragging resize bar + toggleTextSelection : function( c, toggle ) { var namespace = c.namespace + 'tsresize'; - c.$table.add( $( c.namespace + '_extra_table' ) ) - .removeClass('hasResizable') - .children( 'thead' ).unbind( 'contextmenu' + namespace ); + c.widgetOptions.resizable_vars.disabled = toggle; + $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); + if ( toggle ) { + $( 'body' ) + .attr( 'unselectable', 'on' ) + .bind( 'selectstart' + namespace, false ); + } else { + $( 'body' ) + .removeAttr( 'unselectable' ) + .unbind( 'selectstart' + namespace ); + } + }, + + bindings : function( c, wo ) { + var namespace = c.namespace + 'tsresize'; + wo.$resizable_container.children().bind( 'mousedown', function( event ) { + // save header cell and mouse position + var column, + vars = wo.resizable_vars, + $extras = $( c.namespace + '_extra_headers' ), + $header = $( event.target ).data( 'header' ); + + column = parseInt( $header.attr( 'data-column' ), 10 ); + vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); + vars.target = column; + + // if table is not as wide as it's parent, then resize the table + vars.$next = event.shiftKey || wo.resizable_targetLast ? + $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : + $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); + + column = parseInt( vars.$next.attr( 'data-column' ), 10 ); + vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); + vars.next = column; + + vars.mouseXPosition = event.pageX; + ts.resizable.updateStoredSizes( c, wo ); + ts.resizable.toggleTextSelection( c, true ); + }); + + $( document ) + .bind( 'mousemove' + namespace, function( event ) { + var vars = wo.resizable_vars; + // ignore mousemove if no mousedown + if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } + if ( wo.resizable_throttle ) { + clearTimeout( vars.timer ); + vars.timer = setTimeout( function() { + ts.resizable.mouseMove( c, wo, event ); + }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); + } else { + ts.resizable.mouseMove( c, wo, event ); + } + }) + .bind( 'mouseup' + namespace, function() { + if (!wo.resizable_vars.disabled) { return; } + ts.resizable.toggleTextSelection( c, false ); + ts.resizable.stopResize( c, wo ); + ts.resizable.setHandlePosition( c, wo ); + }); + + // resizeEnd event triggered by scroller widget + $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }); + + // right click to reset columns to default widths + c.$table + .bind( 'columnUpdate' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }) + .find( 'thead:first' ) + .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) + .bind( 'contextmenu' + namespace, function() { + // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset + var allowClick = wo.resizable_vars.storedSizes.length === 0; + ts.resizableReset( c.table ); + ts.resizable.setHandlePosition( c, wo ); + wo.resizable_vars.storedSizes = []; + return allowClick; + }); + + }, + + mouseMove : function( c, wo, event ) { + if ( wo.resizable_vars.mouseXPosition === 0 || !wo.resizable_vars.$target ) { return; } + // resize columns + var column, + total = 0, + vars = wo.resizable_vars, + $next = vars.$next, + tar = vars.storedSizes[ vars.target ], + leftEdge = event.pageX - vars.mouseXPosition; + if ( vars.overflow ) { + if ( tar + leftEdge > 0 ) { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); + // update the entire table width + for ( column = 0; column < c.columns; column++ ) { + total += vars.storedSizes[ column ]; + } + ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); + } + if ( !$next.length ) { + // if expanding right-most column, scroll the wrapper + vars.$wrap[0].scrollLeft = c.$table.width(); + } + } else if ( vars.fullWidth ) { + vars.storedSizes[ vars.target ] += leftEdge; + vars.storedSizes[ vars.next ] -= leftEdge; + ts.resizable.setWidths( c, wo ); + } else { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidths( c, wo ); + } + vars.mouseXPosition = event.pageX; + // dynamically update sticky header widths + c.$table.trigger('stickyHeadersUpdate'); + }, + + stopResize : function( c, wo ) { + var vars = wo.resizable_vars; + ts.resizable.updateStoredSizes( c, wo ); + if ( vars.useStorage ) { + // save all column widths + ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); + ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); + } + vars.mouseXPosition = 0; + vars.$target = vars.$next = null; + // will update stickyHeaders, just in case, see #912 + c.$table.trigger('stickyHeadersUpdate'); + } + }; + + // this widget saves the column widths if + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'resizable', + priority: 40, + options: { + resizable : true, // save column widths to storage + resizable_addLastColumn : false, + resizable_widths : [], + resizable_throttle : false, // set to true (5ms) or any number 0-10 range + resizable_targetLast : false, + resizable_fullWidth : null + }, + init: function(table, thisWidget, c, wo) { + ts.resizable.init( c, wo ); + }, + remove: function( table, c, wo, refreshing ) { + if (wo.$resizable_container) { + var namespace = c.namespace + 'tsresize'; + c.$table.add( $( c.namespace + '_extra_table' ) ) + .removeClass('hasResizable') + .children( 'thead' ) + .unbind( 'contextmenu' + namespace ); wo.$resizable_container.remove(); - ts.resizable.toggleTextSelection( c, false ); - ts.resizableReset( table, refreshing ); - $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); - } - } -}); - -ts.resizableReset = function( table, refreshing ) { - $( table ).each(function(){ - var index, $t, - c = this.config, - wo = c && c.widgetOptions, - vars = wo.resizable_; - if ( table && c && c.$headerIndexed.length ) { - // restore the initial table width - if ( vars.overflow && vars.tableWidth ) { - ts.resizable.setWidth( c.$table, vars.tableWidth, true ); - if ( vars.useStorage ) { - ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); - } - } - for ( index = 0; index < c.columns; index++ ) { - $t = c.$headerIndexed[ index ]; - if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { - ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); - } else if ( !$t.hasClass( 'resizable-false' ) ) { - // don't clear the width of any column that is not resizable - ts.resizable.setWidth( $t, '', vars.overflow ); - } - } - - // reset stickyHeader widths - c.$table.trigger( 'stickyHeadersUpdate' ); - if ( ts.storage && !refreshing ) { - ts.storage( this, ts.css.resizableStorage, {} ); + ts.resizable.toggleTextSelection( c, false ); + ts.resizableReset( table, refreshing ); + $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); } } }); -}; + + ts.resizableReset = function( table, refreshing ) { + $( table ).each(function(){ + var index, $t, + c = this.config, + wo = c && c.widgetOptions, + vars = wo.resizable_vars; + if ( table && c && c.$headerIndexed.length ) { + // restore the initial table width + if ( vars.overflow && vars.tableWidth ) { + ts.resizable.setWidth( c.$table, vars.tableWidth, true ); + if ( vars.useStorage ) { + ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); + } + } + for ( index = 0; index < c.columns; index++ ) { + $t = c.$headerIndexed[ index ]; + if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { + ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); + } else if ( !$t.hasClass( 'resizable-false' ) ) { + // don't clear the width of any column that is not resizable + ts.resizable.setWidth( $t, '', vars.overflow ); + } + } + + // reset stickyHeader widths + c.$table.trigger( 'stickyHeadersUpdate' ); + if ( ts.storage && !refreshing ) { + ts.storage( this, ts.css.resizableStorage, {} ); + } + } + }); + }; })( jQuery, window ); /*! Widget: saveSort */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -// this widget saves the last sort only if the -// saveSort widget option is true AND the -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: 'saveSort', - priority: 20, - options: { - saveSort : true - }, - init: function(table, thisWidget, c, wo) { - // run widget format before all other widgets are applied to the table - thisWidget.format(table, c, wo, true); - }, - format: function(table, c, wo, init) { - var stored, time, - $table = c.$table, - saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true - sortList = { "sortList" : c.sortList }; - if (c.debug) { - time = new Date(); - } - if ($table.hasClass('hasSaveSort')) { - if (saveSort && table.hasInitialized && ts.storage) { - ts.storage( table, 'tablesorter-savesort', sortList ); - if (c.debug) { - ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + // this widget saves the last sort only if the + // saveSort widget option is true AND the + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'saveSort', + priority: 20, + options: { + saveSort : true + }, + init: function(table, thisWidget, c, wo) { + // run widget format before all other widgets are applied to the table + thisWidget.format(table, c, wo, true); + }, + format: function(table, c, wo, init) { + var stored, time, + $table = c.$table, + saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true + sortList = { 'sortList' : c.sortList }; + if (c.debug) { + time = new Date(); + } + if ($table.hasClass('hasSaveSort')) { + if (saveSort && table.hasInitialized && ts.storage) { + ts.storage( table, 'tablesorter-savesort', sortList ); + if (c.debug) { + ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + } + } + } else { + // set table sort on initial run of the widget + $table.addClass('hasSaveSort'); + sortList = ''; + // get data + if (ts.storage) { + stored = ts.storage( table, 'tablesorter-savesort' ); + sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; + if (c.debug) { + ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); + } + $table.bind('saveSortReset', function(event) { + event.stopPropagation(); + ts.storage( table, 'tablesorter-savesort', '' ); + }); + } + // init is true when widget init is run, this will run this widget before all other widgets have initialized + // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. + if (init && sortList && sortList.length > 0) { + c.sortList = sortList; + } else if (table.hasInitialized && sortList && sortList.length > 0) { + // update sort change + $table.trigger('sorton', [ sortList ]); } } - } else { - // set table sort on initial run of the widget - $table.addClass('hasSaveSort'); - sortList = ''; - // get data - if (ts.storage) { - stored = ts.storage( table, 'tablesorter-savesort' ); - sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; - if (c.debug) { - ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); - } - $table.bind('saveSortReset', function(event) { - event.stopPropagation(); - ts.storage( table, 'tablesorter-savesort', '' ); - }); - } - // init is true when widget init is run, this will run this widget before all other widgets have initialized - // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. - if (init && sortList && sortList.length > 0) { - c.sortList = sortList; - } else if (table.hasInitialized && sortList && sortList.length > 0) { - // update sort change - $table.trigger('sorton', [sortList]); - } + }, + remove: function(table, c) { + c.$table.removeClass('hasSaveSort'); + // clear storage + if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } } - }, - remove: function(table, c) { - c.$table.removeClass('hasSaveSort'); - // clear storage - if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } - } -}); + }); })(jQuery); diff --git a/js/parsers/parser-date-extract.js b/js/parsers/parser-date-extract.js index 8e4a49f5..7af95851 100644 --- a/js/parsers/parser-date-extract.js +++ b/js/parsers/parser-date-extract.js @@ -1,7 +1,7 @@ /*! Parser: Extract out date - updated 10/26/2014 (v2.18.0) */ /*jshint jquery:true */ ;(function($){ -"use strict"; + 'use strict'; var regex = { usLong : /[A-Z]{3,10}\.?\s+\d{1,2},?\s+(?:\d{4})(?:\s+\d{1,2}:\d{2}(?::\d{2})?(?:\s+[AP]M)?)?/i, @@ -15,10 +15,10 @@ }; /*! extract US Long Date *//* (ignore any other text) - * e.g. "Sue's Birthday! Jun 26, 2004 7:22 AM (8# 2oz)" + * e.g. 'Sue's Birthday! Jun 26, 2004 7:22 AM (8# 2oz)' * demo: http://jsfiddle.net/Mottie/abkNM/4165/ */ $.tablesorter.addParser({ - id: "extractUSLongDate", + id: 'extractUSLongDate', is: function () { // don't auto detect this parser return false; @@ -32,67 +32,67 @@ } return s; }, - type: "numeric" + type: 'numeric' }); /*! extract MMDDYYYY *//* (ignore any other text) * demo: http://jsfiddle.net/Mottie/abkNM/4166/ */ $.tablesorter.addParser({ - id: "extractMMDDYYYY", + id: 'extractMMDDYYYY', is: function () { // don't auto detect this parser return false; }, format: function (s) { var date, - str = s ? s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(regex.mdy) : s; + str = s ? s.replace(/\s+/g, ' ').replace(/[\-.,]/g, '/').match(regex.mdy) : s; if (str) { date = new Date( str[0] ); return date instanceof Date && isFinite(date) ? date.getTime() : s; } return s; }, - type: "numeric" + type: 'numeric' }); /*! extract DDMMYYYY *//* (ignore any other text) * demo: http://jsfiddle.net/Mottie/abkNM/4167/ */ $.tablesorter.addParser({ - id: "extractDDMMYYYY", + id: 'extractDDMMYYYY', is: function () { // don't auto detect this parser return false; }, format: function (s) { var date, - str = s ? s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(regex.dmy) : s; + str = s ? s.replace(/\s+/g, ' ').replace(/[\-.,]/g, '/').match(regex.dmy) : s; if (str) { - date = new Date( str[0].replace(regex.dmyreplace, "$2/$1/$3") ); + date = new Date( str[0].replace(regex.dmyreplace, '$2/$1/$3') ); return date instanceof Date && isFinite(date) ? date.getTime() : s; } return s; }, - type: "numeric" + type: 'numeric' }); /*! extract YYYYMMDD *//* (ignore any other text) * demo: http://jsfiddle.net/Mottie/abkNM/4168/ */ $.tablesorter.addParser({ - id: "extractYYYYMMDD", + id: 'extractYYYYMMDD', is: function () { // don't auto detect this parser return false; }, format: function (s) { var date, - str = s ? s.replace(/\s+/g," ").replace(/[\-.,]/g, "/").match(regex.ymd) : s; + str = s ? s.replace(/\s+/g, ' ').replace(/[\-.,]/g, '/').match(regex.ymd) : s; if (str) { - date = new Date( str[0].replace(regex.ymdreplace, "$2/$3/$1") ); + date = new Date( str[0].replace(regex.ymdreplace, '$2/$3/$1') ); return date instanceof Date && isFinite(date) ? date.getTime() : s; } return s; }, - type: "numeric" + type: 'numeric' }); })(jQuery); diff --git a/js/parsers/parser-date-iso8601.js b/js/parsers/parser-date-iso8601.js index 3483d354..5a1e3e6e 100644 --- a/js/parsers/parser-date-iso8601.js +++ b/js/parsers/parser-date-iso8601.js @@ -6,7 +6,7 @@ */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; var iso8601date = /^([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/; diff --git a/js/parsers/parser-date-month.js b/js/parsers/parser-date-month.js index 7caaae0d..6eb86798 100644 --- a/js/parsers/parser-date-month.js +++ b/js/parsers/parser-date-month.js @@ -2,7 +2,7 @@ /* Demo: http://jsfiddle.net/Mottie/abkNM/4169/ */ /*jshint jquery:true */ ;(function($){ -"use strict"; + 'use strict'; var ts = $.tablesorter; ts.dates = $.extend({}, ts.dates, { @@ -12,7 +12,7 @@ ts.dates.monthLower = ts.dates.monthCased.join(',').toLocaleLowerCase().split(','); ts.addParser({ - id: "month", + id: 'month', is: function(){ return false; }, @@ -20,7 +20,7 @@ if (s) { var j = -1, c = table.config, n = c.ignoreCase ? s.toLocaleLowerCase() : s; - $.each(ts.dates[ 'month' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){ + $.each(ts.dates[ 'month' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i, v){ if (j < 0 && n.match(v)) { j = i; return false; @@ -32,7 +32,7 @@ } return s; }, - type: "numeric" + type: 'numeric' }); })(jQuery); diff --git a/js/parsers/parser-date-range.js b/js/parsers/parser-date-range.js index eab7440d..dd178be5 100644 --- a/js/parsers/parser-date-range.js +++ b/js/parsers/parser-date-range.js @@ -2,7 +2,7 @@ /* Include the 'widget-filter-type-insideRange.js' to filter ranges */ /*jshint jquery:true */ ;(function($){ -'use strict'; + 'use strict'; var regex = { mdy : /(\d{1,2}[-\s]\d{1,2}[-\s]\d{4}(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?)/gi, diff --git a/js/parsers/parser-date-two-digit-year.js b/js/parsers/parser-date-two-digit-year.js index 43571bd2..ab08afa0 100644 --- a/js/parsers/parser-date-two-digit-year.js +++ b/js/parsers/parser-date-two-digit-year.js @@ -2,7 +2,7 @@ /* Demo: http://mottie.github.io/tablesorter/docs/example-parsers-dates.html */ /*jshint jquery:true */ ;(function($){ -"use strict"; + 'use strict'; // Make the date be within +/- range of the 2 digit year // so if the current year is 2020, and the 2 digit year is 80 (2080 - 2020 > 50), it becomes 1980 @@ -23,7 +23,7 @@ var y, rng, n = s // replace separators - .replace(/\s+/g," ").replace(/[-.,]/g, "/") + .replace(/\s+/g, ' ').replace(/[-.,]/g, '/') // reformat xx/xx/xx to mm/dd/19yy; .replace(regex, format), d = new Date(n); @@ -31,7 +31,7 @@ y = d.getFullYear(); rng = table && table.config.dateRange || range; // if date > 50 years old (set range), add 100 years - // this will work when people start using "50" and mean "2050" + // this will work when people start using '50' and mean '2050' while (now - y > rng) { y += 100; } @@ -42,39 +42,39 @@ }; $.tablesorter.addParser({ - id: "ddmmyy", + id: 'ddmmyy', is: function() { return false; }, format: function(s, table) { // reformat dd/mm/yy to mm/dd/19yy; - return ts.formatDate(s, ts.dates.regxxxxyy, "$2/$1/19$3", table); + return ts.formatDate(s, ts.dates.regxxxxyy, '$2/$1/19$3', table); }, - type: "numeric" + type: 'numeric' }); $.tablesorter.addParser({ - id: "mmddyy", + id: 'mmddyy', is: function() { return false; }, format: function(s, table) { // reformat mm/dd/yy to mm/dd/19yy - return ts.formatDate(s, ts.dates.regxxxxyy, "$1/$2/19$3", table); + return ts.formatDate(s, ts.dates.regxxxxyy, '$1/$2/19$3', table); }, - type: "numeric" + type: 'numeric' }); $.tablesorter.addParser({ - id: "yymmdd", + id: 'yymmdd', is: function() { return false; }, format: function(s, table) { // reformat yy/mm/dd to mm/dd/19yy - return ts.formatDate(s, ts.dates.regyyxxxx, "$2/$3/19$1", table); + return ts.formatDate(s, ts.dates.regyyxxxx, '$2/$3/19$1', table); }, - type: "numeric" + type: 'numeric' }); })(jQuery); diff --git a/js/parsers/parser-date-weekday.js b/js/parsers/parser-date-weekday.js index 7a1b3fe8..0a00cda4 100644 --- a/js/parsers/parser-date-weekday.js +++ b/js/parsers/parser-date-weekday.js @@ -2,7 +2,7 @@ /* Demo: http://jsfiddle.net/Mottie/abkNM/4169/ */ /*jshint jquery:true */ ;(function($){ -"use strict"; + 'use strict'; var ts = $.tablesorter; ts.dates = $.extend({}, ts.dates, { @@ -12,7 +12,7 @@ ts.dates.weekdayLower = ts.dates.weekdayCased.join(',').toLocaleLowerCase().split(','); ts.addParser({ - id: "weekday", + id: 'weekday', is: function(){ return false; }, @@ -20,7 +20,7 @@ if (s) { var j = -1, c = table.config; s = c.ignoreCase ? s.toLocaleLowerCase() : s; - $.each(ts.dates[ 'weekday' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i,v){ + $.each(ts.dates[ 'weekday' + (c.ignoreCase ? 'Lower' : 'Cased') ], function(i, v){ if (j < 0 && s.match(v)) { j = i; return false; @@ -32,7 +32,7 @@ } return s; }, - type: "numeric" + type: 'numeric' }); })(jQuery); diff --git a/js/parsers/parser-date.js b/js/parsers/parser-date.js index dbc30ad7..a2acef5f 100644 --- a/js/parsers/parser-date.js +++ b/js/parsers/parser-date.js @@ -2,7 +2,7 @@ /* Extract dates using popular natural language date parsers */ /*jshint jquery:true */ ;(function($){ -'use strict'; + 'use strict'; /*! Sugar (http://sugarjs.com/dates#comparing_dates) */ /* demo: http://jsfiddle.net/Mottie/abkNM/4163/ */ diff --git a/js/parsers/parser-duration.js b/js/parsers/parser-duration.js index da9d4bac..2ad1fd0a 100644 --- a/js/parsers/parser-duration.js +++ b/js/parsers/parser-duration.js @@ -1,7 +1,7 @@ /*! Parser: duration & countdown - updated 2/7/2015 (v2.19.0) */ /*jshint jquery:true, unused:false */ ;(function($){ -'use strict'; + 'use strict'; // If any number > 9999, then set table.config.durationLength = 5 // The below regex matches this duration example: 1y 23d 12h 44m 9s diff --git a/js/parsers/parser-feet-inch-fraction.js b/js/parsers/parser-feet-inch-fraction.js index eb7f2591..3db96cbe 100644 --- a/js/parsers/parser-feet-inch-fraction.js +++ b/js/parsers/parser-feet-inch-fraction.js @@ -5,21 +5,21 @@ */ /*global jQuery: false */ ;(function($){ - "use strict"; + 'use strict'; var ts = $.tablesorter; ts.symbolRegex = /[\u215b\u215c\u215d\u215e\u00bc\u00bd\u00be]/g; ts.processFractions = function(n, table) { if (n) { var t, p = 0; - n = $.trim(n.replace(/\"/,'')); - // look for a space in the first part of the number: "10 3/4" and save the "10" + n = $.trim(n.replace(/\"/, '')); + // look for a space in the first part of the number: '10 3/4' and save the '10' if (/\s/.test(n)) { p = ts.formatFloat(n.split(' ')[0], table); // remove stuff to the left of the space n = $.trim(n.substring(n.indexOf(' '), n.length)); } - // look for a "/" to calculate fractions + // look for a '/' to calculate fractions if (/\//g.test(n)) { t = n.split('/'); // turn 3/4 into .75; make sure we don't divide by zero @@ -52,10 +52,10 @@ if (s === '') { return ''; } // look for feet symbol = ' // very generic test to catch 1.1', 1 1/2' and 1½' - var d = (/^\s*\S*(\s+\S+)?\s*\'/.test(s)) ? s.split("'") : [0,s], + var d = (/^\s*\S*(\s+\S+)?\s*\'/.test(s)) ? s.split(/\'/) : [ 0, s ], f = ts.processFractions(d[0], table), // feet i = ts.processFractions(d[1], table); // inches - return (/[\'\"]/).test(s) ? parseFloat(f) + (parseFloat(i)/12 || 0) : parseFloat(f) + parseFloat(i); + return (/[\'\"]/).test(s) ? parseFloat(f) + (parseFloat(i) / 12 || 0) : parseFloat(f) + parseFloat(i); }, type: 'numeric' }); diff --git a/js/parsers/parser-file-type.js b/js/parsers/parser-file-type.js index e60abcec..4180e65b 100644 --- a/js/parsers/parser-file-type.js +++ b/js/parsers/parser-file-type.js @@ -4,33 +4,33 @@ */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; // basic list from http://en.wikipedia.org/wiki/List_of_file_formats // To add a custom equivalent, define: - // $.tablesorter.fileTypes.equivalents['xx'] = "A|B|C"; + // $.tablesorter.fileTypes.equivalents['xx'] = 'A|B|C'; $.tablesorter.fileTypes = { // divides filetype extensions in the equivalent list below separator : '|', equivalents : { - "3D Image" : "3dm|3ds|dwg|max|obj", - "Audio" : "aif|aac|ape|flac|la|m4a|mid|midi|mp2|mp3|ogg|ra|raw|rm|wav|wma", - "Compressed" : "7z|bin|cab|cbr|gz|gzip|iso|lha|lz|rar|tar|tgz|zip|zipx|zoo", - "Database" : "csv|dat|db|dbf|json|ldb|mdb|myd|pdb|sql|tsv|wdb|wmdb|xlr|xls|xlsx|xml", - "Development" : "asm|c|class|cls|cpp|cc|cs|cxx|cbp|cs|dba|fla|h|java|lua|pl|py|pyc|pyo|sh|sln|r|rb|vb", - "Document" : "doc|docx|odt|ott|pages|pdf|rtf|tex|wpd|wps|wrd|wri", - "Executable" : "apk|app|com|exe|gadget|lnk|msi", - "Fonts" : "eot|fnt|fon|otf|ttf|woff", - "Icons" : "ani|cur|icns|ico", - "Images" : "bmp|gif|jpg|jpeg|jpe|jp2|pic|png|psd|tga|tif|tiff|wmf|webp", - "Presentation" : "pps|ppt", - "Published" : "chp|epub|lit|pub|ppp|fm|mobi", - "Script" : "as|bat|cgi|cmd|jar|js|lua|scpt|scptd|sh|vbs|vb|wsf", - "Styles" : "css|less|sass", - "Text" : "info|log|md|markdown|nfo|tex|text|txt", - "Vectors" : "awg|ai|eps|cdr|ps|svg", - "Video" : "asf|avi|flv|m4v|mkv|mov|mp4|mpe|mpeg|mpg|ogg|rm|rv|swf|vob|wmv", - "Web" : "asp|aspx|cer|cfm|htm|html|php|url|xhtml" + '3D Image' : '3dm|3ds|dwg|max|obj', + 'Audio' : 'aif|aac|ape|flac|la|m4a|mid|midi|mp2|mp3|ogg|ra|raw|rm|wav|wma', + 'Compressed' : '7z|bin|cab|cbr|gz|gzip|iso|lha|lz|rar|tar|tgz|zip|zipx|zoo', + 'Database' : 'csv|dat|db|dbf|json|ldb|mdb|myd|pdb|sql|tsv|wdb|wmdb|xlr|xls|xlsx|xml', + 'Development' : 'asm|c|class|cls|cpp|cc|cs|cxx|cbp|cs|dba|fla|h|java|lua|pl|py|pyc|pyo|sh|sln|r|rb|vb', + 'Document' : 'doc|docx|odt|ott|pages|pdf|rtf|tex|wpd|wps|wrd|wri', + 'Executable' : 'apk|app|com|exe|gadget|lnk|msi', + 'Fonts' : 'eot|fnt|fon|otf|ttf|woff', + 'Icons' : 'ani|cur|icns|ico', + 'Images' : 'bmp|gif|jpg|jpeg|jpe|jp2|pic|png|psd|tga|tif|tiff|wmf|webp', + 'Presentation' : 'pps|ppt', + 'Published' : 'chp|epub|lit|pub|ppp|fm|mobi', + 'Script' : 'as|bat|cgi|cmd|jar|js|lua|scpt|scptd|sh|vbs|vb|wsf', + 'Styles' : 'css|less|sass', + 'Text' : 'info|log|md|markdown|nfo|tex|text|txt', + 'Vectors' : 'awg|ai|eps|cdr|ps|svg', + 'Video' : 'asf|avi|flv|m4v|mkv|mov|mp4|mpe|mpeg|mpg|ogg|rm|rv|swf|vob|wmv', + 'Web' : 'asp|aspx|cer|cfm|htm|html|php|url|xhtml' } }; @@ -48,9 +48,9 @@ m = $.tablesorter.fileTypes.matching, types = $.tablesorter.fileTypes.equivalents; if (!m) { - // make a string to "quick" match the existing equivalents + // make a string to 'quick' match the existing equivalents t = []; - $.each(types, function(i,v){ + $.each(types, function(i, v){ t.push(v); }); m = $.tablesorter.fileTypes.matching = sep + t.join(sep) + sep; diff --git a/js/parsers/parser-globalize.js b/js/parsers/parser-globalize.js index af69a1fc..24a08bda 100644 --- a/js/parsers/parser-globalize.js +++ b/js/parsers/parser-globalize.js @@ -3,7 +3,7 @@ Globalize.locale( 'xx' ) prior to initializing tablesorter! */ /*jshint jquery:true */ ;( function( $ ) { -'use strict'; + 'use strict'; /*! jQuery Globalize date parser (https://github.com/jquery/globalize#date-module) */ /* demo: http://jsfiddle.net/Mottie/0j18Lw8r/ */ diff --git a/js/parsers/parser-ignore-articles.js b/js/parsers/parser-ignore-articles.js index 3424f981..4b6795a5 100644 --- a/js/parsers/parser-ignore-articles.js +++ b/js/parsers/parser-ignore-articles.js @@ -1,28 +1,28 @@ /*! Parser: ignoreArticles - updated 9/15/2014 (v2.17.8) *//* - * This parser will remove "The", "A" and "An" from the beginning of a book + * This parser will remove 'The', 'A' and 'An' from the beginning of a book * or movie title, so it sorts by the second word or number * Demo: http://jsfiddle.net/Mottie/abkNM/5/ */ /*jshint browser: true, jquery:true, unused:false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter; + var ts = $.tablesorter; // basic list from http://en.wikipedia.org/wiki/Article_%28grammar%29 ts.ignoreArticles = { - "en" : "the, a, an", - "de" : "der, die, das, des, dem, den, ein, eine, einer, eines, einem, einen", - "nl" : "de, het, de, een", - "es" : "el, la, lo, los, las, un, una, unos, unas", - "pt" : "o, a, os, as, um, uma, uns, umas", - "fr" : "le, la, l'_, les, un, une, des", - "it" : "il, lo, la, l'_, i, gli, le, un', uno, una, un", - "hu" : "a, az, egy" + 'en' : 'the, a, an', + 'de' : 'der, die, das, des, dem, den, ein, eine, einer, eines, einem, einen', + 'nl' : 'de, het, de, een', + 'es' : 'el, la, lo, los, las, un, una, unos, unas', + 'pt' : 'o, a, os, as, um, uma, uns, umas', + 'fr' : 'le, la, l\'_, les, un, une, des', + 'it' : 'il, lo, la, l\'_, i, gli, le, un\', uno, una, un', + 'hu' : 'a, az, egy' }; // To add a custom parser, define: - // $.tablesorter.ignoreArticles['xx'] = "A, B, C"; + // $.tablesorter.ignoreArticles['xx'] = 'A, B, C'; // and then set the language id 'xx' in the headers option // ignoreArticles : 'xx' @@ -40,11 +40,11 @@ var ts = $.tablesorter; if (!c.headers) { c.headers = {}; } if (!c.headers[cellIndex]) { c.headers[cellIndex] = {}; } lang = ts.getData( c.$headers.eq(cellIndex), ts.getColumnData( table, c.headers, cellIndex ), 'ignoreArticles' ); - art = (ts.ignoreArticles[lang] || "the, a, an" ) + ""; - c.headers[cellIndex].ignoreArticlesRegex = new RegExp('^(' + $.trim( art.split(/\s*\,\s*/).join('\\s|') + "\\s" ).replace("_\\s","") + ')', 'i'); + art = (ts.ignoreArticles[lang] || 'the, a, an' ) + ''; + c.headers[cellIndex].ignoreArticlesRegex = new RegExp('^(' + $.trim( art.split(/\s*\,\s*/).join('\\s|') + '\\s' ).replace('_\\s', '') + ')', 'i'); // exception regex stored in c.headers[cellIndex].ignoreArticlesRegex2 ignore = ts.getData( c.$headers.eq(cellIndex), ts.getColumnData( table, c.headers, cellIndex ), 'ignoreArticlesExcept' ); - c.headers[cellIndex].ignoreArticlesRegex2 = ignore !== '' ? new RegExp('^(' + ignore.replace(/\s/g, "\\s") + ')', 'i') : ''; + c.headers[cellIndex].ignoreArticlesRegex2 = ignore !== '' ? new RegExp('^(' + ignore.replace(/\s/g, '\\s') + ')', 'i') : ''; } art = c.headers[cellIndex].ignoreArticlesRegex; if (art.test(str)) { diff --git a/js/parsers/parser-image.js b/js/parsers/parser-image.js index a8b87238..9157eb0f 100644 --- a/js/parsers/parser-image.js +++ b/js/parsers/parser-image.js @@ -2,10 +2,10 @@ /* alt attribute parser for jQuery 1.7+ & tablesorter 2.7.11+ */ /*jshint jquery:true, unused:false */ ;(function($){ -"use strict"; + 'use strict'; $.tablesorter.addParser({ - id: "image", + id: 'image', is: function(){ return false; }, @@ -13,7 +13,7 @@ return $(cell).find('img').attr(table.config.imgAttr || 'alt') || s; }, parsed : true, // filter widget flag - type: "text" + type: 'text' }); })(jQuery); diff --git a/js/parsers/parser-input-select.js b/js/parsers/parser-input-select.js index e4016ec0..11c10e07 100644 --- a/js/parsers/parser-input-select.js +++ b/js/parsers/parser-input-select.js @@ -4,7 +4,7 @@ */ /*jshint browser: true, jquery:true, unused:false */ ;( function( $ ) { -'use strict'; + 'use strict'; var updateServer = function( event, $table, $input ) { // do something here to update your server, if needed diff --git a/js/parsers/parser-metric.js b/js/parsers/parser-metric.js index c216bf7e..2fff9d9a 100644 --- a/js/parsers/parser-metric.js +++ b/js/parsers/parser-metric.js @@ -6,7 +6,7 @@ */ /*jshint jquery:true */ ;( function( $ ) { -'use strict'; + 'use strict'; var prefixes = { // 'prefix' : [ base 10, base 2 ] @@ -23,7 +23,7 @@ 'h|hecto' : [ 1e2, 1e2 ], 'da|deka' : [ 1e1, 1e1 ], 'd|deci' : [ 1e-1, 1e-1 ], - 'c|centi' : [ 1e-2, 1e-2], + 'c|centi' : [ 1e-2, 1e-2 ], 'm|milli' : [ 1e-3, 1e-3 ], 'µ|micro' : [ 1e-6, 1e-6 ], 'n|nano' : [ 1e-9, 1e-9 ], diff --git a/js/parsers/parser-named-numbers.js b/js/parsers/parser-named-numbers.js index 53bfd8f0..28d2dc84 100644 --- a/js/parsers/parser-named-numbers.js +++ b/js/parsers/parser-named-numbers.js @@ -3,7 +3,7 @@ */ /*jshint jquery:true */ ;(function($){ -"use strict"; + 'use strict'; // Change language of the named numbers as needed var named = { @@ -99,7 +99,7 @@ }; $.tablesorter.addParser({ - id: "namedNumbers", + id: 'namedNumbers', is: function () { return false; }, @@ -116,7 +116,7 @@ // make sure to let zero get parsed, so check hasOwnProperty return result || named.numbers.hasOwnProperty( str ) ? result : $.tablesorter.formatFloat( str || '', table ); }, - type: "numeric" + type: 'numeric' }); })( jQuery ); diff --git a/js/parsers/parser-network.js b/js/parsers/parser-network.js index 71755788..53927e2e 100644 --- a/js/parsers/parser-network.js +++ b/js/parsers/parser-network.js @@ -69,7 +69,7 @@ // it's fastest & easiest for tablesorter to sort decimal values (vs hex) groups[i] = hex ? ('0000' + groups[i]).slice(-4) : ('00000' + (parseInt(groups[i], 16) || 0)).slice(-5); - expandedAddress += ( i != validGroupCount-1) ? groups[i] + ':' : groups[i]; + expandedAddress += ( i != validGroupCount - 1) ? groups[i] + ':' : groups[i]; } return hex ? expandedAddress : expandedAddress.replace(/:/g, ''); }, diff --git a/js/parsers/parser-roman.js b/js/parsers/parser-roman.js index 0032a744..0386bde8 100644 --- a/js/parsers/parser-roman.js +++ b/js/parsers/parser-roman.js @@ -5,7 +5,7 @@ */ /*jshint jquery:true, unused:false */ ;(function($){ -"use strict"; + 'use strict'; // allow lower case roman numerals, since lists use i, ii, iii, etc. var validator = /^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/i, @@ -34,7 +34,7 @@ return num; }, - type: "numeric" + type: 'numeric' }); $.tablesorter.addParser({ @@ -75,7 +75,7 @@ return num ? s.replace(orig, num) : s; }, - type: "text" + type: 'text' }); $.tablesorter.addParser({ @@ -111,7 +111,7 @@ return num ? num : s; }, - type: "numeric" + type: 'numeric' }); })(jQuery); diff --git a/js/widgets/widget-alignChar.js b/js/widgets/widget-alignChar.js index fddd0395..1894481a 100644 --- a/js/widgets/widget-alignChar.js +++ b/js/widgets/widget-alignChar.js @@ -5,142 +5,143 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; -var ts = $.tablesorter; + 'use strict'; + var ts = $.tablesorter; -ts.alignChar = { + ts.alignChar = { - init : function(table, c, wo) { - c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){ - var $this = $(this), - vars = { - column : this.column, - align : $this.attr(wo.alignChar_charAttrib), - alignIndex : parseInt( $this.attr(wo.alignChar_indexAttrib) || 0, 10), - adjust : parseFloat($this.attr(wo.alignChar_adjustAttrib)) || 0 - }; - vars.regex = new RegExp('\\' + vars.align, 'g'); - if (typeof vars.align !== 'undefined') { - wo.alignChar_savedVars[this.column] = vars; - ts.alignChar.setup(table, c, wo, vars); - } - }); - }, + init : function(table, c, wo) { + c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){ + var $this = $(this), + vars = { + column : this.column, + align : $this.attr(wo.alignChar_charAttrib), + alignIndex : parseInt( $this.attr(wo.alignChar_indexAttrib) || 0, 10), + adjust : parseFloat($this.attr(wo.alignChar_adjustAttrib)) || 0 + }; + vars.regex = new RegExp('\\' + vars.align, 'g'); + if (typeof vars.align !== 'undefined') { + wo.alignChar_savedVars[this.column] = vars; + ts.alignChar.setup(table, c, wo, vars); + } + }); + }, - setup: function(table, c, wo, v){ - // do nothing for empty tables - if ($.isEmptyObject(c.cache)) { return; } - var tbodyIndex, rowIndex, start, end, last, index, rows, val, count, - len, wLeft, wRight, alignChar, $row, - left = [], - right = []; - for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){ - rows = c.cache[tbodyIndex]; - len = rows.normalized.length; - for (rowIndex = 0; rowIndex < len; rowIndex++) { - // set up to work with modified cache v2.16.0+ - $row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row; - val = $row.find('td').eq(v.column).text().replace(/[ ]/g, "\u00a0"); - // count how many "align" characters are in the string - count = (val.match( v.regex ) || []).length; - // set alignment @ alignIndex (one-based index) - if (count > 0 && v.alignIndex > 0) { - end = Math.min(v.alignIndex, count); - start = 0; - index = 0; - last = 0; - // find index of nth align character based on alignIndex (data-align-index) - while (start++ < end) { - last = val.indexOf(v.align, last + 1); - index = last < 0 ? index : last; + setup: function(table, c, wo, v){ + // do nothing for empty tables + if ($.isEmptyObject(c.cache)) { return; } + var tbodyIndex, rowIndex, start, end, last, index, rows, val, count, + len, wLeft, wRight, alignChar, $row, + left = [], + right = []; + for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){ + rows = c.cache[tbodyIndex]; + len = rows.normalized.length; + for (rowIndex = 0; rowIndex < len; rowIndex++) { + // set up to work with modified cache v2.16.0+ + $row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row; + val = $row.find('td').eq(v.column).text().replace(/[ ]/g, '\u00a0'); + // count how many 'align' characters are in the string + count = (val.match( v.regex ) || []).length; + // set alignment @ alignIndex (one-based index) + if (count > 0 && v.alignIndex > 0) { + end = Math.min(v.alignIndex, count); + start = 0; + index = 0; + last = 0; + // find index of nth align character based on alignIndex (data-align-index) + while (start++ < end) { + last = val.indexOf(v.align, last + 1); + index = last < 0 ? index : last; + } + } else { + index = val.indexOf(v.align); + } + if ( index >= 0 ) { + left.push( val.substring(0, index) || '' ); + right.push( val.substring(index, val.length) || '' ); + } else { + // no align character found! + // put val in right or left based on the align index + left.push( (count >= 1 && v.alignIndex >= count) ? '' : val || '' ); + right.push( (count >= 1 && v.alignIndex >= count) ? val || '' : '' ); } - } else { - index = val.indexOf(v.align); } - if ( index >= 0 ) { - left.push( val.substring(0, index) || '' ); - right.push( val.substring(index, val.length) || '' ); - } else { - // no align character found! - // put val in right or left based on the align index - left.push( (count >= 1 && v.alignIndex >= count) ? '' : val || '' ); - right.push( (count >= 1 && v.alignIndex >= count) ? val || '' : '' ); + } + + // find widest segments + wLeft = ($.extend([], left)).sort(function(a, b){ return b.length - a.length; })[0]; + wRight = ($.extend([], right)).sort(function(a, b){ return b.length - a.length; })[0]; + // calculate percentage widths + v.width = v.width || ( Math.floor(wLeft.length / (wLeft.length + wRight.length) * 100) + v.adjust ); + wLeft = 'min-width:' + v.width + '%'; + wRight = 'min-width:' + (100 - v.width) + '%'; + + for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){ + rows = c.cache[tbodyIndex]; + len = rows.normalized.length; + for (rowIndex = 0; rowIndex < len; rowIndex++) { + alignChar = $(wo.alignChar_wrap).length ? $(wo.alignChar_wrap).html(v.align)[0].outerHTML : v.align; + $row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row; + last = right[rowIndex].slice(v.align.length); + $row.find('td').eq(v.column).html( + '' + left[rowIndex] + '' + + '' + ( last.length ? alignChar + last : '' ) + '' + ); + } + } + wo.alignChar_initialized = true; + + }, + + remove: function(table, c, column){ + if ($.isEmptyObject(c.cache)) { return; } + var tbodyIndex, rowIndex, len, rows, $row, $cell; + for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){ + rows = c.cache[tbodyIndex]; + len = rows.normalized.length; + for (rowIndex = 0; rowIndex < len; rowIndex++) { + $row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row; + $cell = $row.find('td').eq(column); + $cell.html( $cell.text().replace(/\s/g, ' ') ); } } } + }; - // find widest segments - wLeft = ($.extend([], left)).sort(function(a,b){ return b.length - a.length; })[0]; - wRight = ($.extend([], right)).sort(function(a,b){ return b.length - a.length; })[0]; - // calculate percentage widths - v.width = v.width || ( Math.floor(wLeft.length / (wLeft.length + wRight.length) * 100) + v.adjust ); - wLeft = 'min-width:' + v.width + '%'; - wRight = 'min-width:' + (100 - v.width) + '%'; - - for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){ - rows = c.cache[tbodyIndex]; - len = rows.normalized.length; - for (rowIndex = 0; rowIndex < len; rowIndex++) { - alignChar = $(wo.alignChar_wrap).length ? $(wo.alignChar_wrap).html(v.align)[0].outerHTML : v.align; - $row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row; - last = right[rowIndex].slice(v.align.length); - $row.find('td').eq(v.column).html( - '' + left[rowIndex] + '' + - '' + ( last.length ? alignChar + last : '' ) + '' - ); + ts.addWidget({ + id: 'alignChar', + priority: 100, + options: { + alignChar_wrap : '', + alignChar_charAttrib : 'data-align-char', + alignChar_indexAttrib : 'data-align-index', + alignChar_adjustAttrib : 'data-align-adjust' // percentage width adjustments + }, + init: function(table, thisWidget, c, wo){ + wo.alignChar_initialized = false; + wo.alignChar_savedVars = []; + ts.alignChar.init(table, c, wo); + c.$table.on('pagerEnd refreshAlign', function(){ + c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){ + ts.alignChar.remove(table, c, this.column); + }); + ts.alignChar.init(table, c, wo); + }); + }, + format : function(table, c, wo){ + // reinitialize in case table is empty when first initialized + if (!wo.alignChar_initialized) { + c.$table.trigger('refreshAlign'); } - } - wo.alignChar_initialized = true; - - }, - remove: function(table, c, column){ - if ($.isEmptyObject(c.cache)) { return; } - var tbodyIndex, rowIndex, len, rows, $row, $cell; - for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++){ - rows = c.cache[tbodyIndex]; - len = rows.normalized.length; - for (rowIndex = 0; rowIndex < len; rowIndex++) { - $row = rows.row ? rows.row[rowIndex] : rows.normalized[rowIndex][c.columns].$row; - $cell = $row.find('td').eq(column); - $cell.html( $cell.text().replace(/\s/g, ' ') ); - } - } - } -}; - -ts.addWidget({ - id: 'alignChar', - priority: 100, - options: { - alignChar_wrap : '', - alignChar_charAttrib : 'data-align-char', - alignChar_indexAttrib : 'data-align-index', - alignChar_adjustAttrib : 'data-align-adjust' // percentage width adjustments - }, - init: function(table, thisWidget, c, wo){ - wo.alignChar_initialized = false; - wo.alignChar_savedVars = []; - ts.alignChar.init(table, c, wo); - c.$table.on('pagerEnd refreshAlign', function(){ + }, + remove : function(table, c, wo, refreshing){ + if (refreshing) { return; } c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){ ts.alignChar.remove(table, c, this.column); }); - ts.alignChar.init(table, c, wo); - }); - }, - format : function(table, c, wo){ - // reinitialize in case table is empty when first initialized - if (!wo.alignChar_initialized) { - c.$table.trigger('refreshAlign'); + wo.alignChar_initialized = false; } - }, - remove : function(table, c, wo, refreshing){ - if (refreshing) { return; } - c.$headers.filter('[' + wo.alignChar_charAttrib + ']').each(function(){ - ts.alignChar.remove(table, c, this.column); - }); - wo.alignChar_initialized = false; - } -}); + }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/js/widgets/widget-build-table.js b/js/widgets/widget-build-table.js index 04fa1cfb..923eda48 100644 --- a/js/widgets/widget-build-table.js +++ b/js/widgets/widget-build-table.js @@ -5,8 +5,8 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; -var ts = $.tablesorter = $.tablesorter || {}, + 'use strict'; + var ts = $.tablesorter = $.tablesorter || {}, // build a table from data (requires existing tag) // data.header contains an array of header titles @@ -40,7 +40,7 @@ var ts = $.tablesorter = $.tablesorter || {}, // valid JSON! return bt.object( table, d, wo ); } - } catch(ignore) {} + } catch (ignore) {} // fall through in case it's a csv string } // Array @@ -105,7 +105,7 @@ var ts = $.tablesorter = $.tablesorter || {}, // *** CSV only options *** build_csvStartLine : 0, // line within the csv to start adding to table - build_csvSeparator : ",", // csv separator + build_csvSeparator : ',', // csv separator // *** build object options *** build_objectRowKey : 'rows', // object key containing table rows @@ -225,7 +225,7 @@ var ts = $.tablesorter = $.tablesorter || {}, var c, h, csv = wo.build_type === 'csv' || typeof data === 'string', $t = $(table), - lines = csv ? data.replace('\r','').split('\n') : data, + lines = csv ? data.replace('\r', '').split('\n') : data, len = lines.length, printedLines = 0, infooter = false, @@ -276,13 +276,13 @@ var ts = $.tablesorter = $.tablesorter || {}, // CSV Parser by Brian Huisman (http://www.greywyvern.com/?post=258) bt.splitCSV = function(str, sep) { var x, tl, - thisCSV = $.trim(str).split(sep = sep || ","); + thisCSV = $.trim(str).split(sep = sep || ','); for ( x = thisCSV.length - 1; x >= 0; x-- ) { if ( thisCSV[x].replace(/\"\s+$/, '"').charAt(thisCSV[x].length - 1) === '"' ) { if ( (tl = thisCSV[x].replace(/^\s+\"/, '"')).length > 1 && tl.charAt(0) === '"' ) { thisCSV[x] = thisCSV[x].replace(/^\s*"|"\s*$/g, '').replace(/""/g, '"'); } else if (x) { - thisCSV.splice(x - 1, 2, [thisCSV[x - 1], thisCSV[x]].join(sep)); + thisCSV.splice(x - 1, 2, [ thisCSV[x - 1], thisCSV[x] ].join(sep)); } else { thisCSV = thisCSV.shift().split(sep).concat(thisCSV); } @@ -304,7 +304,7 @@ var ts = $.tablesorter = $.tablesorter || {}, bt.buildComplete(table, wo); }; -/* ==== Object example ==== + /* ==== Object example ==== data : { headers : [ [ @@ -345,9 +345,9 @@ var ts = $.tablesorter = $.tablesorter || {}, } ] } -*/ + */ bt.object = function(table, data, wo) { - // "rows" + // 'rows' var j, l, t, $c, $t, $tb, $tr, c = table.config, kh = wo.build_objectHeaderKey, diff --git a/js/widgets/widget-chart.js b/js/widgets/widget-chart.js index 6589fb5e..b85fa96f 100644 --- a/js/widgets/widget-chart.js +++ b/js/widgets/widget-chart.js @@ -32,7 +32,7 @@ .off(wo.chart_event) .on(wo.chart_event, function() { if (this.hasInitialized) { - // refresh "c" variable in case options are updated dynamically + // refresh 'c' variable in case options are updated dynamically var c = this.config; chart.getCols(c, c.widgetOptions); chart.getData(c, c.widgetOptions); @@ -258,7 +258,7 @@ // Set the label column chart_labelCol: 0, // data sort, should always be first row, might want [[0,1]] - chart_sort: [[0,0]], + chart_sort: [ [ 0, 0 ] ], // event to trigger get updated data chart_event: 'chartData' }, diff --git a/js/widgets/widget-columnSelector.js b/js/widgets/widget-columnSelector.js index 2c519fcf..c0dbc9b5 100644 --- a/js/widgets/widget-columnSelector.js +++ b/js/widgets/widget-columnSelector.js @@ -5,362 +5,362 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter, -namespace = '.tscolsel', -tsColSel = ts.columnSelector = { + var ts = $.tablesorter, + namespace = '.tscolsel', + tsColSel = ts.columnSelector = { - queryAll : '@media only all { [columns] { display: none; } } ', - queryBreak : '@media all and (min-width: [size]) { [columns] { display: table-cell; } } ', + queryAll : '@media only all { [columns] { display: none; } } ', + queryBreak : '@media all and (min-width: [size]) { [columns] { display: table-cell; } } ', - init: function(table, c, wo) { - var $t, colSel; + init: function(table, c, wo) { + var $t, colSel; - // abort if no input is contained within the layout - $t = $(wo.columnSelector_layout); - if (!$t.find('input').add( $t.filter('input') ).length) { - if (c.debug) { - ts.log('ColumnSelector: >> ERROR: Column Selector aborting, no input found in the layout! ***'); + // abort if no input is contained within the layout + $t = $(wo.columnSelector_layout); + if (!$t.find('input').add( $t.filter('input') ).length) { + if (c.debug) { + ts.log('ColumnSelector: >> ERROR: Column Selector aborting, no input found in the layout! ***'); + } + return; } - return; - } - // unique table class name - c.$table.addClass( c.namespace.slice(1) + 'columnselector' ); + // unique table class name + c.$table.addClass( c.namespace.slice(1) + 'columnselector' ); - // build column selector/state array - colSel = c.selector = { $container : $(wo.columnSelector_container || '
') }; - colSel.$style = $('').prop('disabled', true).appendTo('head'); - colSel.$breakpoints = $('').prop('disabled', true).appendTo('head'); + // build column selector/state array + colSel = c.selector = { $container : $(wo.columnSelector_container || '
') }; + colSel.$style = $('').prop('disabled', true).appendTo('head'); + colSel.$breakpoints = $('').prop('disabled', true).appendTo('head'); - colSel.isInitializing = true; - tsColSel.setupSelector(table, c, wo); + colSel.isInitializing = true; + tsColSel.setupSelector(table, c, wo); - if (wo.columnSelector_mediaquery) { - tsColSel.setupBreakpoints(c, wo); - } + if (wo.columnSelector_mediaquery) { + tsColSel.setupBreakpoints(c, wo); + } - colSel.isInitializing = false; - if (colSel.$container.length) { - tsColSel.updateCols(c, wo); - } else if (c.debug) { - ts.log('ColumnSelector: >> container not found'); - } + colSel.isInitializing = false; + if (colSel.$container.length) { + tsColSel.updateCols(c, wo); + } else if (c.debug) { + ts.log('ColumnSelector: >> container not found'); + } - c.$table - .off('refreshColumnSelector' + namespace) - .on('refreshColumnSelector' + namespace, function(e, opt){ - // make sure we're using current config settings - var i, - isArry = $.isArray(opt), - c = this.config, - wo = c.widgetOptions; - // see #798 - if (opt && c.selector.$container.length) { - if (isArry) { - // make sure array contains numbers - $.each(opt, function(i,v){ - opt[i] = parseInt(v, 10); - }); - for (i = 0; i < c.columns; i++) { - c.selector.$container - .find('input[data-column=' + i + ']') - .prop('checked', $.inArray( i, opt ) >= 0 ); + c.$table + .off('refreshColumnSelector' + namespace) + .on('refreshColumnSelector' + namespace, function(e, opt){ + // make sure we're using current config settings + var i, + isArry = $.isArray(opt), + c = this.config, + wo = c.widgetOptions; + // see #798 + if (opt && c.selector.$container.length) { + if (isArry) { + // make sure array contains numbers + $.each(opt, function(i, v){ + opt[i] = parseInt(v, 10); + }); + for (i = 0; i < c.columns; i++) { + c.selector.$container + .find('input[data-column=' + i + ']') + .prop('checked', $.inArray( i, opt ) >= 0 ); + } } + // if passing an array, set auto to false to allow manual column selection & update columns + tsColSel.updateAuto( c, wo, colSel.$container.find('input[data-column="auto"]').prop('checked', !isArry) ); + } else { + tsColSel.updateBreakpoints(c, wo); + tsColSel.updateCols(c, wo); } - // if passing an array, set auto to false to allow manual column selection & update columns - tsColSel.updateAuto( c, wo, colSel.$container.find('input[data-column="auto"]').prop('checked', !isArry) ); - } else { - tsColSel.updateBreakpoints(c, wo); - tsColSel.updateCols(c, wo); + }); + + }, + + setupSelector: function(table, c, wo) { + var name, + colSel = c.selector, + $container = colSel.$container, + useStorage = wo.columnSelector_saveColumns && ts.storage, + // get stored column states + saved = useStorage ? ts.storage( table, 'tablesorter-columnSelector' ) : [], + state = useStorage ? ts.storage( table, 'tablesorter-columnSelector-auto') : {}; + + // initial states + colSel.auto = $.isEmptyObject(state) || $.type(state.auto) !== 'boolean' ? wo.columnSelector_mediaqueryState : state.auto; + colSel.states = []; + colSel.$column = []; + colSel.$wrapper = []; + colSel.$checkbox = []; + // populate the selector container + c.$table.children('thead').find('tr:first th', table).each(function() { + var $this = $(this), + // if no data-priority is assigned, default to 1, but don't remove it from the selector list + priority = $this.attr(wo.columnSelector_priority) || 1, + colId = $this.attr('data-column'), + state = ts.getData(this, c.headers[colId], 'columnSelector'); + + // if this column not hidable at all + // include getData check (includes 'columnSelector-false' class, data attribute, etc) + if ( isNaN(priority) && priority.length > 0 || state === 'disable' || + ( wo.columnSelector_columns[colId] && wo.columnSelector_columns[colId] === 'disable') ) { + return true; // goto next + } + + // set default state; storage takes priority + colSel.states[colId] = saved && typeof saved[colId] !== 'undefined' ? + saved[colId] : typeof wo.columnSelector_columns[colId] !== 'undefined' ? + wo.columnSelector_columns[colId] : (state === 'true' || state !== 'false'); + colSel.$column[colId] = $(this); + + // set default col title + name = $this.attr(wo.columnSelector_name) || $this.text(); + + if ($container.length) { + colSel.$wrapper[colId] = $(wo.columnSelector_layout.replace(/\{name\}/g, name)).appendTo($container); + colSel.$checkbox[colId] = colSel.$wrapper[colId] + // input may not be wrapped within the layout template + .find('input').add( colSel.$wrapper[colId].filter('input') ) + .attr('data-column', colId) + .toggleClass( wo.columnSelector_cssChecked, colSel.states[colId] ) + .prop('checked', colSel.states[colId]) + .on('change', function(){ + colSel.states[colId] = this.checked; + tsColSel.updateCols(c, wo); + }).change(); } }); - }, + }, - setupSelector: function(table, c, wo) { - var name, - colSel = c.selector, - $container = colSel.$container, - useStorage = wo.columnSelector_saveColumns && ts.storage, - // get stored column states - saved = useStorage ? ts.storage( table, 'tablesorter-columnSelector' ) : [], - state = useStorage ? ts.storage( table, 'tablesorter-columnSelector-auto') : {}; + setupBreakpoints: function(c, wo){ + var colSel = c.selector; - // initial states - colSel.auto = $.isEmptyObject(state) || $.type(state.auto) !== "boolean" ? wo.columnSelector_mediaqueryState : state.auto; - colSel.states = []; - colSel.$column = []; - colSel.$wrapper = []; - colSel.$checkbox = []; - // populate the selector container - c.$table.children('thead').find('tr:first th', table).each(function() { - var $this = $(this), - // if no data-priority is assigned, default to 1, but don't remove it from the selector list - priority = $this.attr(wo.columnSelector_priority) || 1, - colId = $this.attr('data-column'), - state = ts.getData(this, c.headers[colId], 'columnSelector'); - - // if this column not hidable at all - // include getData check (includes "columnSelector-false" class, data attribute, etc) - if ( isNaN(priority) && priority.length > 0 || state === 'disable' || - ( wo.columnSelector_columns[colId] && wo.columnSelector_columns[colId] === 'disable') ) { - return true; // goto next - } - - // set default state; storage takes priority - colSel.states[colId] = saved && typeof(saved[colId]) !== 'undefined' ? - saved[colId] : typeof(wo.columnSelector_columns[colId]) !== 'undefined' ? - wo.columnSelector_columns[colId] : (state === 'true' || state !== 'false'); - colSel.$column[colId] = $(this); - - // set default col title - name = $this.attr(wo.columnSelector_name) || $this.text(); - - if ($container.length) { - colSel.$wrapper[colId] = $(wo.columnSelector_layout.replace(/\{name\}/g, name)).appendTo($container); - colSel.$checkbox[colId] = colSel.$wrapper[colId] - // input may not be wrapped within the layout template - .find('input').add( colSel.$wrapper[colId].filter('input') ) - .attr('data-column', colId) - .toggleClass( wo.columnSelector_cssChecked, colSel.states[colId] ) - .prop('checked', colSel.states[colId]) - .on('change', function(){ - colSel.states[colId] = this.checked; + // add responsive breakpoints + if (wo.columnSelector_mediaquery) { + // used by window resize function + colSel.lastIndex = -1; + tsColSel.updateBreakpoints(c, wo); + c.$table + .off('updateAll' + namespace) + .on('updateAll' + namespace, function(){ + tsColSel.updateBreakpoints(c, wo); tsColSel.updateCols(c, wo); - }).change(); + }); } - }); - }, - - setupBreakpoints: function(c, wo){ - var colSel = c.selector; - - // add responsive breakpoints - if (wo.columnSelector_mediaquery) { - // used by window resize function - colSel.lastIndex = -1; - tsColSel.updateBreakpoints(c, wo); - c.$table - .off('updateAll' + namespace) - .on('updateAll' + namespace, function(){ - tsColSel.updateBreakpoints(c, wo); + if (colSel.$container.length) { + // Add media queries toggle + if (wo.columnSelector_mediaquery) { + colSel.$auto = $( wo.columnSelector_layout.replace(/\{name\}/g, wo.columnSelector_mediaqueryName) ).prependTo(colSel.$container); + colSel.$auto + // needed in case the input in the layout is not wrapped + .find('input').add( colSel.$auto.filter('input') ) + .attr('data-column', 'auto') + .prop('checked', colSel.auto) + .toggleClass( wo.columnSelector_cssChecked, colSel.auto ) + .on('change', function(){ + tsColSel.updateAuto(c, wo, $(this)); + }).change(); + } + // Add a bind on update to re-run col setup + c.$table.off('update' + namespace).on('update' + namespace, function() { tsColSel.updateCols(c, wo); }); - } + } + }, - if (colSel.$container.length) { - // Add media queries toggle + updateAuto: function(c, wo, $el) { + var colSel = c.selector; + colSel.auto = $el.prop('checked') || false; + $.each( colSel.$checkbox, function(i, $cb){ + if ($cb) { + $cb[0].disabled = colSel.auto; + colSel.$wrapper[i].toggleClass('disabled', colSel.auto); + } + }); if (wo.columnSelector_mediaquery) { - colSel.$auto = $( wo.columnSelector_layout.replace(/\{name\}/g, wo.columnSelector_mediaqueryName) ).prependTo(colSel.$container); - colSel.$auto - // needed in case the input in the layout is not wrapped - .find('input').add( colSel.$auto.filter('input') ) - .attr('data-column', 'auto') - .prop('checked', colSel.auto) - .toggleClass( wo.columnSelector_cssChecked, colSel.auto ) - .on('change', function(){ - tsColSel.updateAuto(c, wo, $(this)); - }).change(); + tsColSel.updateBreakpoints(c, wo); } - // Add a bind on update to re-run col setup - c.$table.off('update' + namespace).on('update' + namespace, function() { - tsColSel.updateCols(c, wo); - }); - } - }, + tsColSel.updateCols(c, wo); + // copy the column selector to a popup/tooltip + if (c.selector.$popup) { + c.selector.$popup.find('.tablesorter-column-selector') + .html( colSel.$container.html() ) + .find('input').each(function(){ + var indx = $(this).attr('data-column'); + $(this).prop( 'checked', indx === 'auto' ? colSel.auto : colSel.states[indx] ); + }); + } + if (wo.columnSelector_saveColumns && ts.storage) { + ts.storage( c.$table[0], 'tablesorter-columnSelector-auto', { auto : colSel.auto } ); + } + // trigger columnUpdate if auto is true (it gets skipped in updateCols() + if (colSel.auto) { + c.$table.trigger('columnUpdate'); + } + }, - updateAuto: function(c, wo, $el) { - var colSel = c.selector; - colSel.auto = $el.prop('checked') || false; - $.each( colSel.$checkbox, function(i, $cb){ - if ($cb) { - $cb[0].disabled = colSel.auto; - colSel.$wrapper[i].toggleClass('disabled', colSel.auto); + updateBreakpoints: function(c, wo) { + var priority, column, breaks, temp, + colSel = c.selector, + prefix = c.namespace + 'columnselector', + mediaAll = [], + breakpts = ''; + if (wo.columnSelector_mediaquery && !colSel.auto) { + colSel.$breakpoints.prop('disabled', true); + colSel.$style.prop('disabled', false); + return; } - }); - if (wo.columnSelector_mediaquery) { - tsColSel.updateBreakpoints(c, wo); - } - tsColSel.updateCols(c, wo); - // copy the column selector to a popup/tooltip - if (c.selector.$popup) { - c.selector.$popup.find('.tablesorter-column-selector') - .html( colSel.$container.html() ) - .find('input').each(function(){ - var indx = $(this).attr('data-column'); - $(this).prop( 'checked', indx === 'auto' ? colSel.auto : colSel.states[indx] ); + + // only 6 breakpoints (same as jQuery Mobile) + for (priority = 0; priority < 6; priority++){ + /*jshint loopfunc:true */ + breaks = []; + c.$headers.filter('[' + wo.columnSelector_priority + '=' + (priority + 1) + ']').each(function(){ + column = parseInt($(this).attr('data-column'), 10) + 1; + temp = ' col:nth-child(' + column + ')'; + breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp); + temp = ' tr th:nth-child(' + column + ')'; + breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp); + temp = ' tr td:nth-child(' + column + ')'; + breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp); }); - } - if (wo.columnSelector_saveColumns && ts.storage) { - ts.storage( c.$table[0], 'tablesorter-columnSelector-auto', { auto : colSel.auto } ); - } - // trigger columnUpdate if auto is true (it gets skipped in updateCols() - if (colSel.auto) { + if (breaks.length) { + mediaAll = mediaAll.concat( breaks ); + breakpts += tsColSel.queryBreak + .replace(/\[size\]/g, wo.columnSelector_breakpoints[priority]) + .replace(/\[columns\]/g, breaks.join(',')); + } + } + if (colSel.$style) { + colSel.$style.prop('disabled', true); + } + if (mediaAll.length) { + colSel.$breakpoints + .prop('disabled', false) + .html( tsColSel.queryAll.replace(/\[columns\]/g, mediaAll.join(',')) + breakpts ); + } + }, + + updateCols: function(c, wo) { + if (wo.columnSelector_mediaquery && c.selector.auto || c.selector.isInitializing) { + return; + } + var column, temp, + colSel = c.selector, + styles = [], + prefix = c.namespace + 'columnselector'; + colSel.$container.find('input[data-column]').filter('[data-column!="auto"]').each(function(){ + if (!this.checked) { + column = parseInt( $(this).attr('data-column'), 10 ) + 1; + temp = ' col:nth-child(' + column + ')'; + styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp); + temp = ' tr th:nth-child(' + column + ')'; + styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp); + temp = ' tr td:nth-child(' + column + ')'; + styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp); + } + $(this).toggleClass( wo.columnSelector_cssChecked, this.checked ); + }); + if (wo.columnSelector_mediaquery){ + colSel.$breakpoints.prop('disabled', true); + } + if (colSel.$style) { + colSel.$style.prop('disabled', false).html( styles.length ? styles.join(',') + ' { display: none; }' : '' ); + } + if (wo.columnSelector_saveColumns && ts.storage) { + ts.storage( c.$table[0], 'tablesorter-columnSelector', colSel.states ); + } c.$table.trigger('columnUpdate'); - } - }, + }, - updateBreakpoints: function(c, wo) { - var priority, column, breaks, temp, - colSel = c.selector, - prefix = c.namespace + 'columnselector', - mediaAll = [], - breakpts = ''; - if (wo.columnSelector_mediaquery && !colSel.auto) { - colSel.$breakpoints.prop('disabled', true); - colSel.$style.prop('disabled', false); - return; - } - - // only 6 breakpoints (same as jQuery Mobile) - for (priority = 0; priority < 6; priority++){ - /*jshint loopfunc:true */ - breaks = []; - c.$headers.filter('[' + wo.columnSelector_priority + '=' + (priority + 1) + ']').each(function(){ - column = parseInt($(this).attr('data-column'), 10) + 1; - temp = ' col:nth-child(' + column + ')'; - breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp); - temp = ' tr th:nth-child(' + column + ')'; - breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp); - temp = ' tr td:nth-child(' + column + ')'; - breaks.push(prefix + temp + ',' + prefix + '_extra_table' + temp); - }); - if (breaks.length) { - mediaAll = mediaAll.concat( breaks ); - breakpts += tsColSel.queryBreak - .replace(/\[size\]/g, wo.columnSelector_breakpoints[priority]) - .replace(/\[columns\]/g, breaks.join(',')); - } - } - if (colSel.$style) { - colSel.$style.prop('disabled', true); - } - if (mediaAll.length) { - colSel.$breakpoints - .prop('disabled', false) - .html( tsColSel.queryAll.replace(/\[columns\]/g, mediaAll.join(',')) + breakpts ); - } - }, - - updateCols: function(c, wo) { - if (wo.columnSelector_mediaquery && c.selector.auto || c.selector.isInitializing) { - return; - } - var column, temp, - colSel = c.selector, - styles = [], - prefix = c.namespace + 'columnselector'; - colSel.$container.find('input[data-column]').filter('[data-column!="auto"]').each(function(){ - if (!this.checked) { - column = parseInt( $(this).attr('data-column'), 10 ) + 1; - temp = ' col:nth-child(' + column + ')'; - styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp); - temp = ' tr th:nth-child(' + column + ')'; - styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp); - temp = ' tr td:nth-child(' + column + ')'; - styles.push(prefix + temp + ',' + prefix + '_extra_table' + temp); - } - $(this).toggleClass( wo.columnSelector_cssChecked, this.checked ); - }); - if (wo.columnSelector_mediaquery){ - colSel.$breakpoints.prop('disabled', true); - } - if (colSel.$style) { - colSel.$style.prop('disabled', false).html( styles.length ? styles.join(',') + ' { display: none; }' : '' ); - } - if (wo.columnSelector_saveColumns && ts.storage) { - ts.storage( c.$table[0], 'tablesorter-columnSelector', colSel.states ); - } - c.$table.trigger('columnUpdate'); - }, - - attachTo : function(table, elm) { - table = $(table)[0]; - var colSel, wo, indx, - c = table.config, - $popup = $(elm); - if ($popup.length && c) { - if (!$popup.find('.tablesorter-column-selector').length) { - // add a wrapper to add the selector into, in case the popup has other content - $popup.append(''); - } - colSel = c.selector; - wo = c.widgetOptions; - $popup.find('.tablesorter-column-selector') - .html( colSel.$container.html() ) - .find('input').each(function(){ - var indx = $(this).attr('data-column'), - isChecked = indx === 'auto' ? colSel.auto : colSel.states[indx]; - $(this) - .toggleClass( wo.columnSelector_cssChecked, isChecked ) - .prop( 'checked', isChecked ); + attachTo : function(table, elm) { + table = $(table)[0]; + var colSel, wo, indx, + c = table.config, + $popup = $(elm); + if ($popup.length && c) { + if (!$popup.find('.tablesorter-column-selector').length) { + // add a wrapper to add the selector into, in case the popup has other content + $popup.append(''); + } + colSel = c.selector; + wo = c.widgetOptions; + $popup.find('.tablesorter-column-selector') + .html( colSel.$container.html() ) + .find('input').each(function(){ + var indx = $(this).attr('data-column'), + isChecked = indx === 'auto' ? colSel.auto : colSel.states[indx]; + $(this) + .toggleClass( wo.columnSelector_cssChecked, isChecked ) + .prop( 'checked', isChecked ); + }); + colSel.$popup = $popup.on('change', 'input', function(){ + // data input + indx = $(this).toggleClass( wo.columnSelector_cssChecked, this.checked ).attr('data-column'); + // update original popup + colSel.$container.find('input[data-column="' + indx + '"]') + .prop('checked', this.checked) + .trigger('change'); }); - colSel.$popup = $popup.on('change', 'input', function(){ - // data input - indx = $(this).toggleClass( wo.columnSelector_cssChecked, this.checked ).attr('data-column'); - // update original popup - colSel.$container.find('input[data-column="' + indx + '"]') - .prop('checked', this.checked) - .trigger('change'); - }); + } } - } -}; + }; -ts.addWidget({ - id: "columnSelector", - priority: 10, - options: { - // target the column selector markup - columnSelector_container : null, - // column status, true = display, false = hide - // disable = do not display on list - columnSelector_columns : {}, - // remember selected columns - columnSelector_saveColumns: true, + ts.addWidget({ + id: 'columnSelector', + priority: 10, + options: { + // target the column selector markup + columnSelector_container : null, + // column status, true = display, false = hide + // disable = do not display on list + columnSelector_columns : {}, + // remember selected columns + columnSelector_saveColumns: true, - // container layout - columnSelector_layout : '', - // data attribute containing column name to use in the selector container - columnSelector_name : 'data-selector-name', + // container layout + columnSelector_layout : '', + // data attribute containing column name to use in the selector container + columnSelector_name : 'data-selector-name', - /* Responsive Media Query settings */ - // enable/disable mediaquery breakpoints - columnSelector_mediaquery: true, - // toggle checkbox name - columnSelector_mediaqueryName: 'Auto: ', - // breakpoints checkbox initial setting - columnSelector_mediaqueryState: true, - // responsive table hides columns with priority 1-6 at these breakpoints - // see http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/#Applyingapresetbreakpoint - // *** set to false to disable *** - columnSelector_breakpoints : [ '20em', '30em', '40em', '50em', '60em', '70em' ], - // data attribute containing column priority - // duplicates how jQuery mobile uses priorities: - // http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/ - columnSelector_priority : 'data-priority', - // class name added to checked checkboxes - this fixes an issue with Chrome not updating FontAwesome - // applied icons; use this class name (input.checked) instead of input:checked - columnSelector_cssChecked : 'checked' + /* Responsive Media Query settings */ + // enable/disable mediaquery breakpoints + columnSelector_mediaquery: true, + // toggle checkbox name + columnSelector_mediaqueryName: 'Auto: ', + // breakpoints checkbox initial setting + columnSelector_mediaqueryState: true, + // responsive table hides columns with priority 1-6 at these breakpoints + // see http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/#Applyingapresetbreakpoint + // *** set to false to disable *** + columnSelector_breakpoints : [ '20em', '30em', '40em', '50em', '60em', '70em' ], + // data attribute containing column priority + // duplicates how jQuery mobile uses priorities: + // http://view.jquerymobile.com/1.3.2/dist/demos/widgets/table-column-toggle/ + columnSelector_priority : 'data-priority', + // class name added to checked checkboxes - this fixes an issue with Chrome not updating FontAwesome + // applied icons; use this class name (input.checked) instead of input:checked + columnSelector_cssChecked : 'checked' - }, - init: function(table, thisWidget, c, wo) { - tsColSel.init(table, c, wo); - }, - remove: function(table, c, wo, refreshing) { - if (refreshing) { return; } - var csel = c.selector; - csel.$container.empty(); - if (csel.$popup) { csel.$popup.empty(); } - csel.$style.remove(); - csel.$breakpoints.remove(); - c.$table.off('updateAll' + namespace + ' update' + namespace); - } + }, + init: function(table, thisWidget, c, wo) { + tsColSel.init(table, c, wo); + }, + remove: function(table, c, wo, refreshing) { + if (refreshing) { return; } + var csel = c.selector; + csel.$container.empty(); + if (csel.$popup) { csel.$popup.empty(); } + csel.$style.remove(); + csel.$breakpoints.remove(); + c.$table.off('updateAll' + namespace + ' update' + namespace); + } -}); + }); })(jQuery); diff --git a/js/widgets/widget-columns.js b/js/widgets/widget-columns.js index 30a2b1f9..774443da 100644 --- a/js/widgets/widget-columns.js +++ b/js/widgets/widget-columns.js @@ -1,78 +1,78 @@ /*! Widget: columns */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.addWidget({ - id: "columns", - priority: 30, - options : { - columns : [ "primary", "secondary", "tertiary" ] - }, - format: function(table, c, wo) { - var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, + ts.addWidget({ + id: 'columns', + priority: 30, + options : { + columns : [ 'primary', 'secondary', 'tertiary' ] + }, + format: function(table, c, wo) { + var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, $table = c.$table, $tbodies = c.$tbodies, sortList = c.sortList, len = sortList.length, // removed c.widgetColumns support - css = wo && wo.columns || [ "primary", "secondary", "tertiary" ], + css = wo && wo.columns || [ 'primary', 'secondary', 'tertiary' ], last = css.length - 1; remove = css.join(' '); - // check if there is a sort (on initialization there may not be one) - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody - $rows = $tbody.children('tr'); - // loop through the visible rows - $rows.each(function() { - $row = $(this); - if (this.style.display !== 'none') { - // remove all columns class names - $cells = $row.children().removeClass(remove); - // add appropriate column class names - if (sortList && sortList[0]) { - // primary sort column class - $cells.eq(sortList[0][0]).addClass(css[0]); - if (len > 1) { - for (indx = 1; indx < len; indx++) { - // secondary, tertiary, etc sort column classes - $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + // check if there is a sort (on initialization there may not be one) + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody + $rows = $tbody.children('tr'); + // loop through the visible rows + $rows.each(function() { + $row = $(this); + if (this.style.display !== 'none') { + // remove all columns class names + $cells = $row.children().removeClass(remove); + // add appropriate column class names + if (sortList && sortList[0]) { + // primary sort column class + $cells.eq(sortList[0][0]).addClass(css[0]); + if (len > 1) { + for (indx = 1; indx < len; indx++) { + // secondary, tertiary, etc sort column classes + $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + } } } } - } - }); - ts.processTbody(table, $tbody, false); - } - // add classes to thead and tfoot - rows = wo.columns_thead !== false ? ['thead tr'] : []; - if (wo.columns_tfoot !== false) { - rows.push('tfoot tr'); - } - if (rows.length) { - $rows = $table.find( rows.join(',') ).children().removeClass(remove); - if (len) { - for (indx = 0; indx < len; indx++) { - // add primary. secondary, tertiary, etc sort column classes - $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + }); + ts.processTbody(table, $tbody, false); + } + // add classes to thead and tfoot + rows = wo.columns_thead !== false ? [ 'thead tr' ] : []; + if (wo.columns_tfoot !== false) { + rows.push('tfoot tr'); + } + if (rows.length) { + $rows = $table.find( rows.join(',') ).children().removeClass(remove); + if (len) { + for (indx = 0; indx < len; indx++) { + // add primary. secondary, tertiary, etc sort column classes + $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + } } } + }, + remove: function(table, c, wo) { + var tbodyIndex, $tbody, + $tbodies = c.$tbodies, + remove = (wo.columns || [ 'primary', 'secondary', 'tertiary' ]).join(' '); + c.$headers.removeClass(remove); + c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody + $tbody.children('tr').each(function() { + $(this).children().removeClass(remove); + }); + ts.processTbody(table, $tbody, false); // restore tbody + } } - }, - remove: function(table, c, wo) { - var tbodyIndex, $tbody, - $tbodies = c.$tbodies, - remove = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' '); - c.$headers.removeClass(remove); - c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); - for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody - $tbody.children('tr').each(function() { - $(this).children().removeClass(remove); - }); - ts.processTbody(table, $tbody, false); // restore tbody - } - } -}); + }); })(jQuery); diff --git a/js/widgets/widget-editable.js b/js/widgets/widget-editable.js index a493c447..596177aa 100644 --- a/js/widgets/widget-editable.js +++ b/js/widgets/widget-editable.js @@ -7,254 +7,254 @@ ;( function( $ ){ 'use strict'; -var tse = $.tablesorter.editable = { - namespace : '.tseditable', - // last edited class name - lastEdited: 'tseditable-last-edited-cell', + var tse = $.tablesorter.editable = { + namespace : '.tseditable', + // last edited class name + lastEdited: 'tseditable-last-edited-cell', - editComplete: function( c, wo, $cell, refocus ) { - c.$table - .find( '.' + tse.lastEdited ) - .removeClass( tse.lastEdited ) - .trigger( wo.editable_editComplete, [ c ] ); - // restore focus last cell after updating - if ( refocus ) { + editComplete: function( c, wo, $cell, refocus ) { + c.$table + .find( '.' + tse.lastEdited ) + .removeClass( tse.lastEdited ) + .trigger( wo.editable_editComplete, [ c ] ); + // restore focus last cell after updating + if ( refocus ) { + setTimeout( function() { + $cell.focus(); + }, 50 ); + } + }, + + selectAll: function( cell ) { setTimeout( function() { - $cell.focus(); - }, 50 ); - } - }, - - selectAll: function( cell ) { - setTimeout( function() { - // select all text in contenteditable - // see http://stackoverflow.com/a/6150060/145346 - var range, selection; - if ( document.body.createTextRange ) { - range = document.body.createTextRange(); - range.moveToElementText( cell ); - range.select(); - } else if ( window.getSelection ) { - selection = window.getSelection(); - range = document.createRange(); - range.selectNodeContents( cell ); - selection.removeAllRanges(); - selection.addRange( range ); - } - }, 100 ); - }, - - getColumns : function( c, wo ) { - var indx, tmp, - colIndex = [], - cols = []; - if ( !wo.editable_columnsArray && $.type( wo.editable_columns ) === 'string' && wo.editable_columns.indexOf( '-' ) >= 0 ) { - // editable_columns can contain a range string ( i.e. '2-4' ) - tmp = wo.editable_columns.split( /\s*-\s*/ ); - indx = parseInt( tmp[ 0 ], 10 ) || 0; - tmp = parseInt( tmp[ 1 ], 10 ) || ( c.columns - 1 ); - if ( tmp > c.columns ) { - tmp = c.columns - 1; - } - for ( ; indx <= tmp; indx++ ) { - colIndex.push( indx ); - cols.push( 'td:nth-child(' + ( indx + 1 ) + ')' ); - } - } else if ( $.isArray( wo.editable_columns ) ) { - $.each( wo.editable_columnsArray || wo.editable_columns, function( i, col ) { - if ( col < c.columns ) { - colIndex.push( col ); - cols.push( 'td:nth-child(' + ( col + 1 ) + ')' ); + // select all text in contenteditable + // see http://stackoverflow.com/a/6150060/145346 + var range, selection; + if ( document.body.createTextRange ) { + range = document.body.createTextRange(); + range.moveToElementText( cell ); + range.select(); + } else if ( window.getSelection ) { + selection = window.getSelection(); + range = document.createRange(); + range.selectNodeContents( cell ); + selection.removeAllRanges(); + selection.addRange( range ); } - }); - } - if ( !wo.editable_columnsArray ) { - wo.editable_columnsArray = colIndex; - wo.editable_columnsArray.sort(function(a,b){ return a - b; }); - } - return cols; - }, + }, 100 ); + }, - update: function( c, wo ) { - var $t, - tmp = $( '
' ).wrapInner( wo.editable_wrapContent ).children().length || $.isFunction( wo.editable_wrapContent ), - cols = tse.getColumns( c, wo ).join( ',' ); - - // turn off contenteditable to allow dynamically setting the wo.editable_noEdit - // class on table cells - see issue #900 - c.$tbodies.find( cols ).find( '[contenteditable]' ).prop( 'contenteditable', false ); - - // IE does not allow making TR/TH/TD cells directly editable ( issue #404 ) - // so add a div or span inside ( it's faster than using wrapInner() ) - c.$tbodies.find( cols ).not( '.' + wo.editable_noEdit ).each( function() { - // test for children, if they exist, then make the children editable - $t = $( this ); - - if ( tmp && $t.children( 'div, span' ).length === 0 ) { - $t.wrapInner( wo.editable_wrapContent ); + getColumns : function( c, wo ) { + var indx, tmp, + colIndex = [], + cols = []; + if ( !wo.editable_columnsArray && $.type( wo.editable_columns ) === 'string' && wo.editable_columns.indexOf( '-' ) >= 0 ) { + // editable_columns can contain a range string ( i.e. '2-4' ) + tmp = wo.editable_columns.split( /\s*-\s*/ ); + indx = parseInt( tmp[ 0 ], 10 ) || 0; + tmp = parseInt( tmp[ 1 ], 10 ) || ( c.columns - 1 ); + if ( tmp > c.columns ) { + tmp = c.columns - 1; + } + for ( ; indx <= tmp; indx++ ) { + colIndex.push( indx ); + cols.push( 'td:nth-child(' + ( indx + 1 ) + ')' ); + } + } else if ( $.isArray( wo.editable_columns ) ) { + $.each( wo.editable_columnsArray || wo.editable_columns, function( i, col ) { + if ( col < c.columns ) { + colIndex.push( col ); + cols.push( 'td:nth-child(' + ( col + 1 ) + ')' ); + } + }); } - if ( $t.children( 'div, span' ).length ) { - // make div/span children content editable - $t.children( 'div, span' ).not( '.' + wo.editable_noEdit ).each( function() { - var $this = $( this ); + if ( !wo.editable_columnsArray ) { + wo.editable_columnsArray = colIndex; + wo.editable_columnsArray.sort(function(a, b){ return a - b; }); + } + return cols; + }, + + update: function( c, wo ) { + var $t, + tmp = $( '
' ).wrapInner( wo.editable_wrapContent ).children().length || $.isFunction( wo.editable_wrapContent ), + cols = tse.getColumns( c, wo ).join( ',' ); + + // turn off contenteditable to allow dynamically setting the wo.editable_noEdit + // class on table cells - see issue #900 + c.$tbodies.find( cols ).find( '[contenteditable]' ).prop( 'contenteditable', false ); + + // IE does not allow making TR/TH/TD cells directly editable ( issue #404 ) + // so add a div or span inside ( it's faster than using wrapInner() ) + c.$tbodies.find( cols ).not( '.' + wo.editable_noEdit ).each( function() { + // test for children, if they exist, then make the children editable + $t = $( this ); + + if ( tmp && $t.children( 'div, span' ).length === 0 ) { + $t.wrapInner( wo.editable_wrapContent ); + } + if ( $t.children( 'div, span' ).length ) { + // make div/span children content editable + $t.children( 'div, span' ).not( '.' + wo.editable_noEdit ).each( function() { + var $this = $( this ); + if ( wo.editable_trimContent ) { + $this.html( function( i, txt ) { + return $.trim( txt ); + }); + } + $this.prop( 'contenteditable', true ); + }); + } else { if ( wo.editable_trimContent ) { - $this.html( function( i, txt ) { + $t.html( function( i, txt ) { return $.trim( txt ); }); } - $this.prop( 'contenteditable', true ); - }); - } else { - if ( wo.editable_trimContent ) { - $t.html( function( i, txt ) { - return $.trim( txt ); - }); - } - $t.prop( 'contenteditable', true ); - } - }); - }, - - bindEvents: function( c, wo ) { - var namespace = tse.namespace; - c.$table - .off( ( 'updateComplete pagerComplete '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ) ) - .on( 'updateComplete pagerComplete '.split( ' ' ).join( namespace + ' ' ), function() { - tse.update( c, c.widgetOptions ); - }) - // prevent sort initialized by user click on the header from changing the row indexing before - // updateCell can finish processing the change - .children( 'thead' ) - .add( $( c.namespace + '_extra_table' ).children( 'thead' ) ) - .off( 'mouseenter' + namespace ) - .on( 'mouseenter' + namespace, function() { - if ( c.$table.data( 'contentFocused' ) ) { - // change to 'true' instead of element to allow focusout to process - c.$table.data( 'contentFocused', true ); - $( ':focus' ).trigger( 'focusout' ); + $t.prop( 'contenteditable', true ); } }); + }, - c.$tbodies - .off( ( 'focus blur focusout keydown '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ) ) - .on( 'focus' + namespace, '[contenteditable]', function( e ) { - clearTimeout( $( this ).data( 'timer' ) ); - c.$table.data( 'contentFocused', e.target ); - c.table.isUpdating = true; // prevent sorting while editing - var $this = $( this ), - selAll = wo.editable_selectAll, - column = $this.closest( 'td' ).index(), - txt = $this.html(); - if ( wo.editable_trimContent ) { - txt = $.trim( txt ); - } - // prevent enter from adding into the content - $this - .off( 'keydown' + namespace ) - .on( 'keydown' + namespace, function( e ){ - if ( wo.editable_enterToAccept && e.which === 13 && !e.shiftKey ) { - e.preventDefault(); - } - }); - $this.data({ before : txt, original: txt }); + bindEvents: function( c, wo ) { + var namespace = tse.namespace; + c.$table + .off( ( 'updateComplete pagerComplete '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ) ) + .on( 'updateComplete pagerComplete '.split( ' ' ).join( namespace + ' ' ), function() { + tse.update( c, c.widgetOptions ); + }) + // prevent sort initialized by user click on the header from changing the row indexing before + // updateCell can finish processing the change + .children( 'thead' ) + .add( $( c.namespace + '_extra_table' ).children( 'thead' ) ) + .off( 'mouseenter' + namespace ) + .on( 'mouseenter' + namespace, function() { + if ( c.$table.data( 'contentFocused' ) ) { + // change to 'true' instead of element to allow focusout to process + c.$table.data( 'contentFocused', true ); + $( ':focus' ).trigger( 'focusout' ); + } + }); - if ( typeof wo.editable_focused === 'function' ) { - wo.editable_focused( txt, column, $this ); - } + c.$tbodies + .off( ( 'focus blur focusout keydown '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ) ) + .on( 'focus' + namespace, '[contenteditable]', function( e ) { + clearTimeout( $( this ).data( 'timer' ) ); + c.$table.data( 'contentFocused', e.target ); + c.table.isUpdating = true; // prevent sorting while editing + var $this = $( this ), + selAll = wo.editable_selectAll, + column = $this.closest( 'td' ).index(), + txt = $this.html(); + if ( wo.editable_trimContent ) { + txt = $.trim( txt ); + } + // prevent enter from adding into the content + $this + .off( 'keydown' + namespace ) + .on( 'keydown' + namespace, function( e ){ + if ( wo.editable_enterToAccept && e.which === 13 && !e.shiftKey ) { + e.preventDefault(); + } + }); + $this.data({ before : txt, original: txt }); - if ( selAll ) { - if ( typeof selAll === 'function' ) { - if ( selAll( txt, column, $this ) ) { + if ( typeof wo.editable_focused === 'function' ) { + wo.editable_focused( txt, column, $this ); + } + + if ( selAll ) { + if ( typeof selAll === 'function' ) { + if ( selAll( txt, column, $this ) ) { + tse.selectAll( $this[0] ); + } + } else { tse.selectAll( $this[0] ); } - } else { - tse.selectAll( $this[0] ); } - } - }) - .on( 'blur focusout keydown '.split( ' ' ).join( namespace + ' ' ), '[contenteditable]', function( e ) { - if ( !c.$table.data( 'contentFocused' ) ) { return; } - var t, validate, - valid = false, - $this = $( e.target ), - txt = $this.html(), - column = $this.closest( 'td' ).index(); - if ( wo.editable_trimContent ) { - txt = $.trim( txt ); - } - if ( e.which === 27 ) { - // user cancelled - $this.html( $this.data( 'original' ) ).trigger( 'blur' + namespace ); - c.$table.data( 'contentFocused', false ); - c.table.isUpdating = false; - return false; - } - // accept on enter ( if set ), alt-enter ( always ) or if autoAccept is set and element is blurred or unfocused - t = e.which === 13 && !e.shiftKey && ( wo.editable_enterToAccept || e.altKey ) || wo.editable_autoAccept && e.type !== 'keydown'; - // change if new or user hits enter ( if option set ) - if ( t && $this.data( 'before' ) !== txt ) { - - validate = wo.editable_validate; - valid = txt; - - if ( typeof( validate ) === 'function' ) { - valid = validate( txt, $this.data( 'original' ), column, $this ); - } else if ( typeof ( validate = $.tablesorter.getColumnData( c.table, validate, column ) ) === 'function' ) { - valid = validate( txt, $this.data( 'original' ), column, $this ); + }) + .on( 'blur focusout keydown '.split( ' ' ).join( namespace + ' ' ), '[contenteditable]', function( e ) { + if ( !c.$table.data( 'contentFocused' ) ) { return; } + var t, validate, + valid = false, + $this = $( e.target ), + txt = $this.html(), + column = $this.closest( 'td' ).index(); + if ( wo.editable_trimContent ) { + txt = $.trim( txt ); } - - if ( t && valid !== false ) { - c.$table.find( '.' + tse.lastEdited ).removeClass( tse.lastEdited ); - $this - .addClass( tse.lastEdited ) - .html( valid ) - .data( 'before', valid ) - .data( 'original', valid ) - .trigger( 'change' ); - c.$table.trigger( 'updateCell', [ $this.closest( 'td' ), false, function() { - if ( wo.editable_autoResort ) { - setTimeout( function() { - c.$table.trigger( 'sorton', [ c.sortList, function() { - tse.editComplete( c, wo, c.$table.data( 'contentFocused' ), true ); - }, true ] ); - }, 10 ); - } else { - tse.editComplete( c, wo, c.$table.data( 'contentFocused' ) ); - } - } ] ); + if ( e.which === 27 ) { + // user cancelled + $this.html( $this.data( 'original' ) ).trigger( 'blur' + namespace ); + c.$table.data( 'contentFocused', false ); + c.table.isUpdating = false; return false; } - } else if ( !valid && e.type !== 'keydown' ) { - clearTimeout( $this.data( 'timer' ) ); - $this.data( 'timer', setTimeout( function() { - c.table.isUpdating = false; // clear flag or sorting will be disabled + // accept on enter ( if set ), alt-enter ( always ) or if autoAccept is set and element is blurred or unfocused + t = e.which === 13 && !e.shiftKey && ( wo.editable_enterToAccept || e.altKey ) || wo.editable_autoAccept && e.type !== 'keydown'; + // change if new or user hits enter ( if option set ) + if ( t && $this.data( 'before' ) !== txt ) { - if ( $.isFunction( wo.editable_blur ) ) { - txt = $this.html(); - wo.editable_blur( wo.editable_trimContent ? $.trim( txt ) : txt, column, $this ); + validate = wo.editable_validate; + valid = txt; + + if ( typeof validate === 'function' ) { + valid = validate( txt, $this.data( 'original' ), column, $this ); + } else if ( typeof ( validate = $.tablesorter.getColumnData( c.table, validate, column ) ) === 'function' ) { + valid = validate( txt, $this.data( 'original' ), column, $this ); } - }, 100 ) ); - // restore original content on blur - $this.html( $this.data( 'original' ) ); - } - }); - }, - destroy : function( c, wo ) { - var namespace = tse.namespace, - cols = tse.getColumns( c, wo ), - tmp = ( 'updateComplete pagerComplete '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ); - c.$table.off( tmp ); + if ( t && valid !== false ) { + c.$table.find( '.' + tse.lastEdited ).removeClass( tse.lastEdited ); + $this + .addClass( tse.lastEdited ) + .html( valid ) + .data( 'before', valid ) + .data( 'original', valid ) + .trigger( 'change' ); + c.$table.trigger( 'updateCell', [ $this.closest( 'td' ), false, function() { + if ( wo.editable_autoResort ) { + setTimeout( function() { + c.$table.trigger( 'sorton', [ c.sortList, function() { + tse.editComplete( c, wo, c.$table.data( 'contentFocused' ), true ); + }, true ] ); + }, 10 ); + } else { + tse.editComplete( c, wo, c.$table.data( 'contentFocused' ) ); + } + } ] ); + return false; + } + } else if ( !valid && e.type !== 'keydown' ) { + clearTimeout( $this.data( 'timer' ) ); + $this.data( 'timer', setTimeout( function() { + c.table.isUpdating = false; // clear flag or sorting will be disabled - tmp = ( 'focus blur focusout keydown '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ); - c.$tbodies - .off( tmp ) - .find( cols.join( ',' ) ) - .find( '[contenteditable]' ) - .prop( 'contenteditable', false ); - } + if ( $.isFunction( wo.editable_blur ) ) { + txt = $this.html(); + wo.editable_blur( wo.editable_trimContent ? $.trim( txt ) : txt, column, $this ); + } + }, 100 ) ); + // restore original content on blur + $this.html( $this.data( 'original' ) ); + } + }); + }, + destroy : function( c, wo ) { + var namespace = tse.namespace, + cols = tse.getColumns( c, wo ), -}; + tmp = ( 'updateComplete pagerComplete '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ); + c.$table.off( tmp ); + + tmp = ( 'focus blur focusout keydown '.split( ' ' ).join( namespace + ' ' ) ).replace( /\s+/g, ' ' ); + c.$tbodies + .off( tmp ) + .find( cols.join( ',' ) ) + .find( '[contenteditable]' ) + .prop( 'contenteditable', false ); + } + + }; $.tablesorter.addWidget({ id: 'editable', diff --git a/js/widgets/widget-filter-formatter-html5.js b/js/widgets/widget-filter-formatter-html5.js index 1c5e303d..67e67353 100644 --- a/js/widgets/widget-filter-formatter-html5.js +++ b/js/widgets/widget-filter-formatter-html5.js @@ -8,422 +8,421 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter || {}, + var ts = $.tablesorter || {}, -// compare option selector class name (jQuery selector) -compareSelect = '.compare-select', + // compare option selector class name (jQuery selector) + compareSelect = '.compare-select', + tsff = ts.filterFormatter = $.extend( {}, ts.filterFormatter, { -tsff = ts.filterFormatter = $.extend( {}, ts.filterFormatter, { - - addCompare: function($cell, indx, options){ - if (options.compare && $.isArray(options.compare) && options.compare.length > 1) { - var opt = '', - compareSelectClass = [ compareSelect.slice(1), ' ' + compareSelect.slice(1), '' ], - txt = options.cellText ? '' : ''; - $.each(options.compare, function(i, c){ - opt += ''; - }); - $cell - .wrapInner('
') - .prepend( txt + '' ) + .find('select') + .append(opt); } - $cell.find(compareSelect).val( compare ); - } - return [ val, num ]; - }, + }, - /**********************\ - HTML5 Number (spinner) - \**********************/ - html5Number : function($cell, indx, def5Num) { - var t, o = $.extend({ - value : 0, - min : 0, - max : 100, - step : 1, - delayed : true, - disabled : false, - addToggle : false, - exactMatch : false, - cellText : '', - compare : '', - skipTest: false - }, def5Num), - - $input, - // test browser for HTML5 range support - $number = $('').appendTo($cell), - // test if HTML5 number is supported - from Modernizr - numberSupported = o.skipTest || $number.attr('type') === 'number' && $number.val() !== 'test', - $shcell = [], - c = $cell.closest('table')[0].config, - - updateNumber = function(delayed, notrigger){ - var chkd = o.addToggle ? $cell.find('.toggle').is(':checked') : true, - v = $cell.find('.number').val(), - compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', - searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) || '' : true; - $input - // add equal to the beginning, so we filter exact numbers - .val( !o.addToggle || chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' ) - .trigger( notrigger ? '' : 'search', searchType ).end() - .find('.number').val(v); - if ($cell.find('.number').length) { - $cell.find('.number')[0].disabled = (o.disabled || !chkd); - } - // update sticky header cell - if ($shcell.length) { - $shcell.find('.number').val(v)[0].disabled = (o.disabled || !chkd); - $shcell.find(compareSelect).val(compare); - if (o.addToggle) { - $shcell.find('.toggle')[0].checked = chkd; + updateCompare : function($cell, $input, o) { + var val = $input.val() || '', + num = val.replace(/\s*?[><=]\s*?/g, ''), + compare = val.match(/[><=]/g) || ''; + if (o.compare) { + if ($.isArray(o.compare)){ + compare = (compare || []).join('') || o.compare[o.selected || 0]; } + $cell.find(compareSelect).val( compare ); } - }; - $number.remove(); + return [ val, num ]; + }, - if (numberSupported) { - t = o.addToggle ? '
' + - '
' : ''; - t += ''; - // add HTML5 number (spinner) - $cell - .append(t + '') - .find('.toggle, .number').bind('change', function(){ - updateNumber(); - }) - .closest('thead').find('th[data-column=' + indx + ']') - .addClass('filter-parsed') // get exact numbers from column - // on reset - .closest('table').bind('filterReset', function(){ - if ($.isArray(o.compare)) { - $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); - } - // turn off the toggle checkbox + /**********************\ + HTML5 Number (spinner) + \**********************/ + html5Number : function($cell, indx, def5Num) { + var t, o = $.extend({ + value : 0, + min : 0, + max : 100, + step : 1, + delayed : true, + disabled : false, + addToggle : false, + exactMatch : false, + cellText : '', + compare : '', + skipTest: false + }, def5Num), + + $input, + // test browser for HTML5 range support + $number = $('').appendTo($cell), + // test if HTML5 number is supported - from Modernizr + numberSupported = o.skipTest || $number.attr('type') === 'number' && $number.val() !== 'test', + $shcell = [], + c = $cell.closest('table')[0].config, + + updateNumber = function(delayed, notrigger){ + var chkd = o.addToggle ? $cell.find('.toggle').is(':checked') : true, + v = $cell.find('.number').val(), + compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', + searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) || '' : true; + $input + // add equal to the beginning, so we filter exact numbers + .val( !o.addToggle || chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' ) + .trigger( notrigger ? '' : 'search', searchType ).end() + .find('.number').val(v); + if ($cell.find('.number').length) { + $cell.find('.number')[0].disabled = (o.disabled || !chkd); + } + // update sticky header cell + if ($shcell.length) { + $shcell.find('.number').val(v)[0].disabled = (o.disabled || !chkd); + $shcell.find(compareSelect).val(compare); if (o.addToggle) { - $cell.find('.toggle')[0].checked = false; - if ($shcell.length) { - $shcell.find('.toggle')[0].checked = false; - } + $shcell.find('.toggle')[0].checked = chkd; } - $cell.find('.number').val( o.value ); - setTimeout(function(){ + } + }; + $number.remove(); + + if (numberSupported) { + t = o.addToggle ? '
' + + '
' : ''; + t += ''; + // add HTML5 number (spinner) + $cell + .append(t + '') + .find('.toggle, .number').bind('change', function(){ updateNumber(); - }, 0); - }); - $input = $cell.find('input[type=hidden]').bind('change', function(){ - $cell.find('.number').val( this.value ); - updateNumber(); - }); - - // update slider from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - var val = tsff.updateCompare($cell, $input, o)[0] || o.value; - $cell.find('.number').val( ((val || '') + '').replace(/[><=]/g,'') ); - updateNumber(false, true); - ts.filter.formatterUpdated($cell, indx); - }); - - if (o.compare) { - // add compare select - tsff.addCompare($cell, indx, o); - $cell.find(compareSelect).bind('change', function(){ + }) + .closest('thead').find('th[data-column=' + indx + ']') + .addClass('filter-parsed') // get exact numbers from column + // on reset + .closest('table').bind('filterReset', function(){ + if ($.isArray(o.compare)) { + $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); + } + // turn off the toggle checkbox + if (o.addToggle) { + $cell.find('.toggle')[0].checked = false; + if ($shcell.length) { + $shcell.find('.toggle')[0].checked = false; + } + } + $cell.find('.number').val( o.value ); + setTimeout(function(){ + updateNumber(); + }, 0); + }); + $input = $cell.find('input[type=hidden]').bind('change', function(){ + $cell.find('.number').val( this.value ); updateNumber(); }); - } - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - $shcell - .append(t) - .find('.toggle, .number').bind('change', function(){ - $cell.find('.number').val( $(this).val() ); - updateNumber(); - }); + // update slider from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + var val = tsff.updateCompare($cell, $input, o)[0] || o.value; + $cell.find('.number').val( ((val || '') + '').replace(/[><=]/g, '') ); + updateNumber(false, true); + ts.filter.formatterUpdated($cell, indx); + }); if (o.compare) { // add compare select - tsff.addCompare($shcell, indx, o); - $shcell.find(compareSelect).bind('change', function(){ - $cell.find(compareSelect).val( $(this).val() ); + tsff.addCompare($cell, indx, o); + $cell.find(compareSelect).bind('change', function(){ updateNumber(); }); } + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); + $shcell + .append(t) + .find('.toggle, .number').bind('change', function(){ + $cell.find('.number').val( $(this).val() ); + updateNumber(); + }); + + if (o.compare) { + // add compare select + tsff.addCompare($shcell, indx, o); + $shcell.find(compareSelect).bind('change', function(){ + $cell.find(compareSelect).val( $(this).val() ); + updateNumber(); + }); + } + + updateNumber(); + }); + updateNumber(); - }); - updateNumber(); - - } - - return numberSupported ? $cell.find('input[type="hidden"]') : $(''); - }, - - /**********************\ - HTML5 range slider - \**********************/ - html5Range : function($cell, indx, def5Range) { - var o = $.extend({ - value : 0, - min : 0, - max : 100, - step : 1, - delayed : true, - valueToHeader : true, - exactMatch : true, - cellText : '', - compare : '', - allText : 'all', - skipTest : false - }, def5Range), - - $input, - // test browser for HTML5 range support - $range = $('').appendTo($cell), - // test if HTML5 range is supported - from Modernizr (but I left out the method to detect in Safari 2-4) - // see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/inputtypes.js - rangeSupported = o.skipTest || $range.attr('type') === 'range' && $range.val() !== 'test', - $shcell = [], - c = $cell.closest('table')[0].config, - - updateRange = function(v, delayed, notrigger){ - /*jshint eqeqeq:false */ - // hidden input changes may include compare symbols - v = ( typeof v === "undefined" ? $input.val() : v ).toString().replace(/[<>=]/g,'') || o.value; - var compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', - t = ' (' + (compare ? compare + v : v == o.min ? o.allText : v) + ')', - searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) || '' : true; - $cell.find('input[type=hidden]') - // add equal to the beginning, so we filter exact numbers - .val( ( compare ? compare + v : ( v == o.min ? '' : ( o.exactMatch ? '=' : '' ) + v ) ) ) - //( val == o.min ? '' : val + (o.exactMatch ? '=' : '')) - .trigger( notrigger ? '' : 'search', searchType ).end() - .find('.range').val(v); - // or add current value to the header cell, if desired - $cell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(t); - // update sticky header cell - if ($shcell.length) { - $shcell - .find('.range').val(v).end() - .find(compareSelect).val( compare ); - $shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(t); } - }; - $range.remove(); - if (rangeSupported) { - // add HTML5 range - $cell - .html('') - .closest('thead').find('th[data-column=' + indx + ']') - .addClass('filter-parsed') // get exact numbers from column - // add span to header for the current slider value - .find('.tablesorter-header-inner').append(''); - // hidden filter update namespace trigger by filter widget - $input = $cell.find('input[type=hidden]').bind('change' + c.namespace + 'filter', function(){ + return numberSupported ? $cell.find('input[type="hidden"]') : $(''); + }, + + /**********************\ + HTML5 range slider + \**********************/ + html5Range : function($cell, indx, def5Range) { + var o = $.extend({ + value : 0, + min : 0, + max : 100, + step : 1, + delayed : true, + valueToHeader : true, + exactMatch : true, + cellText : '', + compare : '', + allText : 'all', + skipTest : false + }, def5Range), + + $input, + // test browser for HTML5 range support + $range = $('').appendTo($cell), + // test if HTML5 range is supported - from Modernizr (but I left out the method to detect in Safari 2-4) + // see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/inputtypes.js + rangeSupported = o.skipTest || $range.attr('type') === 'range' && $range.val() !== 'test', + $shcell = [], + c = $cell.closest('table')[0].config, + + updateRange = function(v, delayed, notrigger){ /*jshint eqeqeq:false */ - var v = this.value, - compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || ''; - if (v !== this.lastValue) { - this.lastValue = ( compare ? compare + v : ( v == o.min ? '' : ( o.exactMatch ? '=' : '' ) + v ) ); - this.value = this.lastValue; - updateRange( v ); + // hidden input changes may include compare symbols + v = ( typeof v === 'undefined' ? $input.val() : v ).toString().replace(/[<>=]/g, '') || o.value; + var compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', + t = ' (' + (compare ? compare + v : v == o.min ? o.allText : v) + ')', + searchType = c.$table[0].hasInitialized ? (delayed ? delayed : o.delayed) || '' : true; + $cell.find('input[type=hidden]') + // add equal to the beginning, so we filter exact numbers + .val( ( compare ? compare + v : ( v == o.min ? '' : ( o.exactMatch ? '=' : '' ) + v ) ) ) + // ( val == o.min ? '' : val + (o.exactMatch ? '=' : '')) + .trigger( notrigger ? '' : 'search', searchType ).end() + .find('.range').val(v); + // or add current value to the header cell, if desired + $cell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(t); + // update sticky header cell + if ($shcell.length) { + $shcell + .find('.range').val(v).end() + .find(compareSelect).val( compare ); + $shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(t); } - }); + }; + $range.remove(); - $cell.find('.range').bind('change', function(){ - updateRange( this.value ); - }); - - // update spinner from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - var val = tsff.updateCompare($cell, $input, o)[0]; - $cell.find('.range').val( val ); - updateRange(val, false, true); - ts.filter.formatterUpdated($cell, indx); - }); - - if (o.compare) { - // add compare select - tsff.addCompare($cell, indx, o); - $cell.find(compareSelect).bind('change', function(){ - updateRange(); + if (rangeSupported) { + // add HTML5 range + $cell + .html('') + .closest('thead').find('th[data-column=' + indx + ']') + .addClass('filter-parsed') // get exact numbers from column + // add span to header for the current slider value + .find('.tablesorter-header-inner').append(''); + // hidden filter update namespace trigger by filter widget + $input = $cell.find('input[type=hidden]').bind('change' + c.namespace + 'filter', function(){ + /*jshint eqeqeq:false */ + var v = this.value, + compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || ''; + if (v !== this.lastValue) { + this.lastValue = ( compare ? compare + v : ( v == o.min ? '' : ( o.exactMatch ? '=' : '' ) + v ) ); + this.value = this.lastValue; + updateRange( v ); + } }); - } - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - $shcell - .html('') - .find('.range').bind('change', function(){ - updateRange( $shcell.find('.range').val() ); - }); - updateRange(); + $cell.find('.range').bind('change', function(){ + updateRange( this.value ); + }); + + // update spinner from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + var val = tsff.updateCompare($cell, $input, o)[0]; + $cell.find('.range').val( val ); + updateRange(val, false, true); + ts.filter.formatterUpdated($cell, indx); + }); if (o.compare) { // add compare select - tsff.addCompare($shcell, indx, o); - $shcell.find(compareSelect).bind('change', function(){ - $cell.find(compareSelect).val( $(this).val() ); + tsff.addCompare($cell, indx, o); + $cell.find(compareSelect).bind('change', function(){ updateRange(); }); } - }); + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); + $shcell + .html('') + .find('.range').bind('change', function(){ + updateRange( $shcell.find('.range').val() ); + }); + updateRange(); - // on reset - $cell.closest('table').bind('filterReset', function(){ - if ($.isArray(o.compare)) { - $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); - } - setTimeout(function(){ - updateRange(o.value, false, true); - }, 0); - }); - updateRange(); + if (o.compare) { + // add compare select + tsff.addCompare($shcell, indx, o); + $shcell.find(compareSelect).bind('change', function(){ + $cell.find(compareSelect).val( $(this).val() ); + updateRange(); + }); + } - } + }); - return rangeSupported ? $cell.find('input[type="hidden"]') : $(''); - }, + // on reset + $cell.closest('table').bind('filterReset', function(){ + if ($.isArray(o.compare)) { + $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); + } + setTimeout(function(){ + updateRange(o.value, false, true); + }, 0); + }); + updateRange(); - /**********************\ - HTML5 Color picker - \**********************/ - html5Color: function($cell, indx, defColor) { - var t, o = $.extend({ - value : '#000000', - disabled : false, - addToggle : true, - exactMatch : true, - valueToHeader : false, - skipTest : false - }, defColor), - $input, - // Add a hidden input to hold the range values - $color = $('').appendTo($cell), - // test if HTML5 color is supported - from Modernizr - colorSupported = o.skipTest || $color.attr('type') === 'color' && $color.val() !== 'test', - $shcell = [], - c = $cell.closest('table')[0].config, - - updateColor = function(v, notrigger){ - v = ( typeof v === "undefined" ? $input.val() : v ).toString().replace('=','') || o.value; - var chkd = true, - t = ' (' + v + ')'; - if (o.addToggle) { - chkd = $cell.find('.toggle').is(':checked'); - } - if ($cell.find('.colorpicker').length) { - $cell.find('.colorpicker').val(v)[0].disabled = (o.disabled || !chkd); } - $input - .val( chkd ? v + (o.exactMatch ? '=' : '') : '' ) - .trigger( !c.$table[0].hasInitialized || notrigger ? '' : 'search' ); - if (o.valueToHeader) { - // add current color to the header cell - $cell.closest('thead').find('th[data-column=' + indx + ']').find('.curcolor').html(t); - } else { - // current color to span in cell - $cell.find('.currentColor').html(t); - } + return rangeSupported ? $cell.find('input[type="hidden"]') : $(''); + }, - // update sticky header cell - if ($shcell.length) { - $shcell.find('.colorpicker').val(v)[0].disabled = (o.disabled || !chkd); + /**********************\ + HTML5 Color picker + \**********************/ + html5Color: function($cell, indx, defColor) { + var t, o = $.extend({ + value : '#000000', + disabled : false, + addToggle : true, + exactMatch : true, + valueToHeader : false, + skipTest : false + }, defColor), + $input, + // Add a hidden input to hold the range values + $color = $('').appendTo($cell), + // test if HTML5 color is supported - from Modernizr + colorSupported = o.skipTest || $color.attr('type') === 'color' && $color.val() !== 'test', + $shcell = [], + c = $cell.closest('table')[0].config, + + updateColor = function(v, notrigger) { + v = ( typeof v === 'undefined' ? $input.val() : v ).toString().replace('=', '') || o.value; + var chkd = true, + t = ' (' + v + ')'; if (o.addToggle) { - $shcell.find('.toggle')[0].checked = chkd; + chkd = $cell.find('.toggle').is(':checked'); } + if ($cell.find('.colorpicker').length) { + $cell.find('.colorpicker').val(v)[0].disabled = (o.disabled || !chkd); + } + + $input + .val( chkd ? v + (o.exactMatch ? '=' : '') : '' ) + .trigger( !c.$table[0].hasInitialized || notrigger ? '' : 'search' ); if (o.valueToHeader) { // add current color to the header cell - $shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curcolor').html(t); + $cell.closest('thead').find('th[data-column=' + indx + ']').find('.curcolor').html(t); } else { // current color to span in cell - $shcell.find('.currentColor').html(t); + $cell.find('.currentColor').html(t); } - } - }; - $color.remove(); - if (colorSupported) { - t = '' + indx + Math.round(Math.random() * 100); - // add HTML5 color picker - t = '
' + - (o.addToggle ? '
' : '') + - '' + - (o.valueToHeader ? '' : '(#000000)') + '
'; - $cell.html(t); - // add span to header for the current color value - only works if the line in the updateColor() function is also un-commented out - if (o.valueToHeader) { - $cell.closest('thead').find('th[data-column=' + indx + ']').find('.tablesorter-header-inner').append(''); - } - - $cell.find('.toggle, .colorpicker').bind('change', function(){ - updateColor( $cell.find('.colorpicker').val() ); - }); - - // hidden filter update namespace trigger by filter widget - $input = $cell.find('input[type=hidden]').bind('change' + c.namespace + 'filter', function(){ - updateColor( this.value ); - }); - - // update slider from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - updateColor( $input.val(), true ); - ts.filter.formatterUpdated($cell, indx); - }); - - // on reset - $cell.closest('table').bind('filterReset', function(){ - // just turn off the colorpicker - if (o.addToggle) { - $cell.find('.toggle')[0].checked = false; + // update sticky header cell + if ($shcell.length) { + $shcell.find('.colorpicker').val(v)[0].disabled = (o.disabled || !chkd); + if (o.addToggle) { + $shcell.find('.toggle')[0].checked = chkd; + } + if (o.valueToHeader) { + // add current color to the header cell + $shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curcolor').html(t); + } else { + // current color to span in cell + $shcell.find('.currentColor').html(t); + } } - // delay needed because default color needs to be set in the filter - // there is no compare option here, so if addToggle = false, - // default color is #000000 (even with no value set) - setTimeout(function(){ - updateColor(); - }, 0); - }); + }; + $color.remove(); - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx); - $shcell - .html(t) - .find('.toggle, .colorpicker').bind('change', function(){ - updateColor( $shcell.find('.colorpicker').val() ); - }); - updateColor( $shcell.find('.colorpicker').val() ); - }); + if (colorSupported) { + t = '' + indx + Math.round(Math.random() * 100); + // add HTML5 color picker + t = '
' + + (o.addToggle ? '
' : '') + + '' + + (o.valueToHeader ? '' : '(#000000)') + '
'; + $cell.html(t); + // add span to header for the current color value - only works if the line in the updateColor() function is also un-commented out + if (o.valueToHeader) { + $cell.closest('thead').find('th[data-column=' + indx + ']').find('.tablesorter-header-inner').append(''); + } - updateColor( o.value ); + $cell.find('.toggle, .colorpicker').bind('change', function(){ + updateColor( $cell.find('.colorpicker').val() ); + }); + + // hidden filter update namespace trigger by filter widget + $input = $cell.find('input[type=hidden]').bind('change' + c.namespace + 'filter', function(){ + updateColor( this.value ); + }); + + // update slider from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + updateColor( $input.val(), true ); + ts.filter.formatterUpdated($cell, indx); + }); + + // on reset + $cell.closest('table').bind('filterReset', function(){ + // just turn off the colorpicker + if (o.addToggle) { + $cell.find('.toggle')[0].checked = false; + } + // delay needed because default color needs to be set in the filter + // there is no compare option here, so if addToggle = false, + // default color is #000000 (even with no value set) + setTimeout(function(){ + updateColor(); + }, 0); + }); + + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx); + $shcell + .html(t) + .find('.toggle, .colorpicker').bind('change', function(){ + updateColor( $shcell.find('.colorpicker').val() ); + }); + updateColor( $shcell.find('.colorpicker').val() ); + }); + + updateColor( o.value ); + } + return colorSupported ? $cell.find('input[type="hidden"]') : $(''); } - return colorSupported ? $cell.find('input[type="hidden"]') : $(''); - } -}); + }); })(jQuery); diff --git a/js/widgets/widget-filter-formatter-jui.js b/js/widgets/widget-filter-formatter-jui.js index 00ee4612..6ba2a65c 100644 --- a/js/widgets/widget-filter-formatter-jui.js +++ b/js/widgets/widget-filter-formatter-jui.js @@ -10,755 +10,755 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter || {}, + var ts = $.tablesorter || {}, -// compare option selector class name (jQuery selector) -compareSelect = '.compare-select', + // compare option selector class name (jQuery selector) + compareSelect = '.compare-select', -tsff = ts.filterFormatter = $.extend( {}, ts.filterFormatter, { + tsff = ts.filterFormatter = $.extend( {}, ts.filterFormatter, { - addCompare: function($cell, indx, options){ - if (options.compare && $.isArray(options.compare) && options.compare.length > 1) { - var opt = '', - compareSelectClass = [ compareSelect.slice(1), ' ' + compareSelect.slice(1), '' ], - txt = options.cellText ? '' : ''; - $.each(options.compare, function(i, c){ - opt += ''; - }); - $cell - .wrapInner('
') - .prepend( txt + '') - .appendTo($cell) - // hidden filter update namespace trigger by filter widget - .bind('change' + c.namespace + 'filter', function(){ - updateSpinner({ value: this.value, delayed: false }); - }), - $shcell = [], - - // this function updates the hidden input and adds the current values to the header cell text - updateSpinner = function(ui, notrigger) { - var chkd = true, state, - // ui is not undefined on create - v = ui && ui.value && ts.formatFloat((ui.value + '').replace(/[><=]/g,'')) || - $cell.find('.spinner').val() || o.value, - compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', - searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '' : true; - if (o.addToggle) { - chkd = $cell.find('.toggle').is(':checked'); - } - state = o.disabled || !chkd ? 'disable' : 'enable'; - $cell.find('.filter') - // add equal to the beginning, so we filter exact numbers - .val( chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' ) - .trigger( notrigger ? '' : 'search', searchType ).end() - .find('.spinner').spinner(state).val(v); - // update sticky header cell - if ($shcell.length) { - $shcell - .find('.spinner').spinner(state).val(v).end() - .find(compareSelect).val( compare ); - if (o.addToggle) { - $shcell.find('.toggle')[0].checked = chkd; - } - } - }; - - // add callbacks; preserve added callbacks - o.oldcreate = o.create; - o.oldspin = o.spin; - o.create = function(event, ui) { - updateSpinner(); // ui is an empty object on create - if (typeof o.oldcreate === 'function') { o.oldcreate(event, ui); } - }; - o.spin = function(event, ui) { - updateSpinner(ui); - if (typeof o.oldspin === 'function') { o.oldspin(event, ui); } - }; - if (o.addToggle) { - $('
' + - '
') - .appendTo($cell) - .find('.toggle') - .bind('change', function(){ - updateSpinner(); + addCompare: function($cell, indx, options){ + if (options.compare && $.isArray(options.compare) && options.compare.length > 1) { + var opt = '', + compareSelectClass = [ compareSelect.slice(1), ' ' + compareSelect.slice(1), '' ], + txt = options.cellText ? '' : ''; + $.each(options.compare, function(i, c){ + opt += ''; }); - } - // make sure we use parsed data - $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'); - // add a jQuery UI spinner! - $('') - .val(o.value) - .appendTo($cell) - .spinner(o) - .bind('change keyup', function(){ - updateSpinner(); - }); - - // update spinner from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - var val = tsff.updateCompare($cell, $input, o)[0]; - $cell.find('.spinner').val( val ); - updateSpinner({ value: val }, true); - ts.filter.formatterUpdated($cell, indx); - }); - - if (o.compare) { - // add compare select - tsff.addCompare($cell, indx, o); - $cell.find(compareSelect).bind('change', function(){ - updateSpinner(); - }); - } - - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - if (o.addToggle) { - $('
' + - '
') - .appendTo($shcell) - .find('.toggle') - .bind('change', function(){ - $cell.find('.toggle')[0].checked = this.checked; - updateSpinner(); - }); - } - // add a jQuery UI spinner! - $('') - .val(o.value) - .appendTo($shcell) - .spinner(o) - .bind('change keyup', function(){ - $cell.find('.spinner').val( this.value ); - updateSpinner(); - }); - - if (o.compare) { - // add compare select - tsff.addCompare($shcell, indx, o); - $shcell.find(compareSelect).bind('change', function(){ - $cell.find(compareSelect).val( $(this).val() ); - updateSpinner(); - }); - } - - }); - - // on reset - c.$table.bind('filterReset', function(){ - if ($.isArray(o.compare)) { - $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); - } - // turn off the toggle checkbox - if (o.addToggle) { - $cell.find('.toggle')[0].checked = false; - } - $cell.find('.spinner').spinner('value', o.value); - setTimeout(function(){ - updateSpinner(); - }, 0); - }); - - updateSpinner(); - return $input; - }, - - /**********************\ - jQuery UI Slider - \**********************/ - uiSlider: function($cell, indx, sliderDef) { - var o = $.extend({ - // filter formatter options - delayed : true, - valueToHeader : false, - exactMatch : true, - cellText : '', - compare : '', - allText : 'all', - // include ANY jQuery UI spinner options below - // except values, since this is a non-range setup - value : 0, - min : 0, - max : 100, - step : 1, - range : "min" - }, sliderDef ), - c = $cell.closest('table')[0].config, - // Add a hidden input to hold the range values - $input = $('') - .appendTo($cell) - // hidden filter update namespace trigger by filter widget - .bind('change' + c.namespace + 'filter', function(){ - updateSlider({ value: this.value }); - }), - $shcell = [], - - // this function updates the hidden input and adds the current values to the header cell text - updateSlider = function(ui, notrigger) { - // ui is not undefined on create - var v = typeof ui !== "undefined" ? ts.formatFloat((ui.value + '').replace(/[><=]/g,'')) || o.value : o.value, - val = o.compare ? v : v === o.min ? o.allText : v, - compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', - result = compare + val, - searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '' : true; - if (o.valueToHeader) { - // add range indication to the header cell above! - $cell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(' (' + result + ')'); - } else { - // add values to the handle data-value attribute so the css tooltip will work properly - $cell.find('.ui-slider-handle').addClass('value-popup').attr('data-value', result); - } - // update the hidden input; - // ****** ADD AN EQUAL SIGN TO THE BEGINNING! <- this makes the slide exactly match the number ****** - // when the value is at the minimum, clear the hidden input so all rows will be seen - - $cell.find('.filter') - .val( ( compare ? compare + v : v === o.min ? '' : (o.exactMatch ? '=' : '') + v ) ) - .trigger( notrigger ? '' : 'search', searchType ).end() - .find('.slider').slider('value', v); - - // update sticky header cell - if ($shcell.length) { - $shcell - .find(compareSelect).val( compare ).end() - .find('.slider').slider('value', v); - if (o.valueToHeader) { - $shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(' (' + result + ')'); - } else { - $shcell.find('.ui-slider-handle').addClass('value-popup').attr('data-value', result); - } - } - - }; - $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'); - - // add span to header for value - only works if the line in the updateSlider() function is also un-commented out - if (o.valueToHeader) { - $cell.closest('thead').find('th[data-column=' + indx + ']').find('.tablesorter-header-inner').append(''); - } - - // add callbacks; preserve added callbacks - o.oldcreate = o.create; - o.oldslide = o.slide; - o.create = function(event, ui) { - updateSlider(); // ui is an empty object on create - if (typeof o.oldcreate === 'function') { o.oldcreate(event, ui); } - }; - o.slide = function(event, ui) { - updateSlider(ui); - if (typeof o.oldslide === 'function') { o.oldslide(event, ui); } - }; - // add a jQuery UI slider! - $('
') - .appendTo($cell) - .slider(o); - - // update slider from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - var val = tsff.updateCompare($cell, $input, o)[0]; - $cell.find('.slider').slider('value', val ); - updateSlider({ value: val }, false); - ts.filter.formatterUpdated($cell, indx); - }); - - if (o.compare) { - // add compare select - tsff.addCompare($cell, indx, o); - $cell.find(compareSelect).bind('change', function(){ - updateSlider({ value: $cell.find('.slider').slider('value') }); - }); - } - - // on reset - c.$table.bind('filterReset', function(){ - if ($.isArray(o.compare)) { - $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); - } - setTimeout(function(){ - updateSlider({ value: o.value }); - }, 0); - }); - - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - - // add a jQuery UI slider! - $('
') - .val(o.value) - .appendTo($shcell) - .slider(o) - .bind('change keyup', function(){ - $cell.find('.slider').slider('value', this.value ); - updateSlider(); - }); - - if (o.compare) { - // add compare select - tsff.addCompare($shcell, indx, o); - $shcell.find(compareSelect).bind('change', function(){ - $cell.find(compareSelect).val( $(this).val() ); - updateSlider(); - }); - } - - }); - - return $input; - }, - - /*************************\ - jQuery UI Range Slider (2 handles) - \*************************/ - uiRange: function($cell, indx, rangeDef) { - var o = $.extend({ - // filter formatter options - delayed : true, - valueToHeader : false, - // include ANY jQuery UI spinner options below - // except value, since this one is range specific) - values : [0, 100], - min : 0, - max : 100, - range : true - }, rangeDef ), - c = $cell.closest('table')[0].config, - // Add a hidden input to hold the range values - $input = $('') - .appendTo($cell) - // hidden filter update namespace trigger by filter widget - .bind('change' + c.namespace + 'filter', function(){ - getRange(); - }), - $shcell = [], - - getRange = function(){ - var val = $input.val(), - v = val.split(' - '); - if (val === '') { v = [ o.min, o.max ]; } - if (v && v[1]) { - updateUiRange({ values: v, delay: false }, true); + $cell + .wrapInner('
') + .prepend( txt + '') + .appendTo($cell) + // hidden filter update namespace trigger by filter widget + .bind('change' + c.namespace + 'filter', function(){ + updateSpinner({ value: this.value, delayed: false }); + }), + $shcell = [], - // add callbacks; preserve added callbacks - o.oldcreate = o.create; - o.oldslide = o.slide; - // add a jQuery UI range slider! - o.create = function(event, ui) { - updateUiRange(); // ui is an empty object on create - if (typeof o.oldcreate === 'function') { o.oldcreate(event, ui); } - }; - o.slide = function(event, ui) { - updateUiRange(ui); - if (typeof o.oldslide === 'function') { o.oldslide(event, ui); } - }; - $('
') - .appendTo($cell) - .slider(o); - - // update slider from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - getRange(); - ts.filter.formatterUpdated($cell, indx); - }); - - // on reset - c.$table.bind('filterReset', function(){ - $cell.find('.range').slider('values', o.values); - setTimeout(function(){ - updateUiRange(); - }, 0); - }); - - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - - // add a jQuery UI slider! - $('
') - .val(o.value) - .appendTo($shcell) - .slider(o) - .bind('change keyup', function(){ - $cell.find('.range').val( this.value ); - updateUiRange(); - }); - - }); - - // return the hidden input so the filter widget has a reference to it - return $input; - }, - - /*************************\ - jQuery UI Datepicker compare (1 input) - \*************************/ - uiDateCompare: function($cell, indx, defDate) { - var o = $.extend({ - // filter formatter options - cellText : '', - compare : '', - endOfDay : true, - // include ANY jQuery UI spinner options below - - defaultDate : '', - - changeMonth : true, - changeYear : true, - numberOfMonths : 1 - }, defDate), - - $date, - c = $cell.closest('table')[0].config, - // make sure we're using parsed dates in the search - $hdr = $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'), - // Add a hidden input to hold the range values - $input = $('') - .appendTo($cell) - // hidden filter update namespace trigger by filter widget - .bind('change' + c.namespace + 'filter', function(){ - var v = this.value; - if (v) { - o.onClose(v); + // this function updates the hidden input and adds the current values to the header cell text + updateSpinner = function(ui, notrigger) { + var chkd = true, state, + // ui is not undefined on create + v = ui && ui.value && ts.formatFloat((ui.value + '').replace(/[><=]/g, '')) || + $cell.find('.spinner').val() || o.value, + compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', + searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '' : true; + if (o.addToggle) { + chkd = $cell.find('.toggle').is(':checked'); } - }), - t, $shcell = [], + state = o.disabled || !chkd ? 'disable' : 'enable'; + $cell.find('.filter') + // add equal to the beginning, so we filter exact numbers + .val( chkd ? (compare ? compare : o.exactMatch ? '=' : '') + v : '' ) + .trigger( notrigger ? '' : 'search', searchType ).end() + .find('.spinner').spinner(state).val(v); + // update sticky header cell + if ($shcell.length) { + $shcell + .find('.spinner').spinner(state).val(v).end() + .find(compareSelect).val( compare ); + if (o.addToggle) { + $shcell.find('.toggle')[0].checked = chkd; + } + } + }; - // this function updates the hidden input - date1Compare = function(notrigger) { - var date, query, - getdate = $date.datepicker('getDate') || '', - compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', - searchType = c.$table[0].hasInitialized ? o.delayed || '': true; - $date.datepicker('setDate', (getdate === '' ? '' : getdate) || null); - if (getdate === '') { notrigger = false; } - date = $date.datepicker('getDate'); - query = date ? ( o.endOfDay && /<=/.test(compare) ? date.setHours(23, 59, 59) : date.getTime() ) || '' : ''; - if (date && o.endOfDay && compare === '=') { - compare = ''; - query += ' - ' + date.setHours(23, 59, 59); - notrigger = false; + // add callbacks; preserve added callbacks + o.oldcreate = o.create; + o.oldspin = o.spin; + o.create = function(event, ui) { + updateSpinner(); // ui is an empty object on create + if (typeof o.oldcreate === 'function') { o.oldcreate(event, ui); } + }; + o.spin = function(event, ui) { + updateSpinner(ui); + if (typeof o.oldspin === 'function') { o.oldspin(event, ui); } + }; + if (o.addToggle) { + $('
' + + '
') + .appendTo($cell) + .find('.toggle') + .bind('change', function(){ + updateSpinner(); + }); } - $cell.find('.dateCompare') - // add equal to the beginning, so we filter exact numbers - .val(compare + query) - .trigger( notrigger ? '' : 'search', searchType ).end(); - // update sticky header cell - if ($shcell.length) { - $shcell - .find('.dateCompare').val(compare + query).end() - .find(compareSelect).val(compare); - } - }; + // make sure we use parsed data + $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'); + // add a jQuery UI spinner! + $('') + .val(o.value) + .appendTo($cell) + .spinner(o) + .bind('change keyup', function(){ + updateSpinner(); + }); - // Add date range picker - t = ''; - $date = $(t).appendTo($cell); - - // add callbacks; preserve added callbacks - o.oldonClose = o.onClose; - - o.onClose = function( selectedDate, ui ) { - date1Compare(); - if (typeof o.oldonClose === 'function') { o.oldonClose(selectedDate, ui); } - }; - $date.datepicker(o); - - // on reset - c.$table.bind('filterReset', function(){ - if ($.isArray(o.compare)) { - $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); - } - $cell.add($shcell).find('.date').val(o.defaultDate).datepicker('setDate', o.defaultDate || null); - setTimeout(function(){ - date1Compare(); - }, 0); - }); - - // update date compare from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - var num, v = $input.val(); - if (/\s+-\s+/.test(v)) { - // date range found; assume an exact match on one day - $cell.find(compareSelect).val('='); - num = v.split(/\s+-\s+/)[0]; - $date.datepicker( 'setDate', num || null ); - } else { - num = (tsff.updateCompare($cell, $input, o)[1]).toString() || ''; - // differeniate 1388556000000 from 1/1/2014 using \d{5} regex - num = num !== '' ? /\d{5}/g.test(num) ? new Date(Number(num)) : num || '' : ''; - } - $cell.add($shcell).find('.date').datepicker( 'setDate', num || null ); - setTimeout(function(){ - date1Compare(true); + // update spinner from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + var val = tsff.updateCompare($cell, $input, o)[0]; + $cell.find('.spinner').val( val ); + updateSpinner({ value: val }, true); ts.filter.formatterUpdated($cell, indx); - }, 0); - }); - - if (o.compare) { - // add compare select - tsff.addCompare($cell, indx, o); - $cell.find(compareSelect).bind('change', function(){ - date1Compare(); }); - } - - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - - // add a jQuery datepicker! - $shcell - .append(t) - .find('.date') - .datepicker(o); if (o.compare) { // add compare select - tsff.addCompare($shcell, indx, o); - $shcell.find(compareSelect).bind('change', function(){ - $cell.find(compareSelect).val( $(this).val() ); + tsff.addCompare($cell, indx, o); + $cell.find(compareSelect).bind('change', function(){ + updateSpinner(); + }); + } + + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); + if (o.addToggle) { + $('
' + + '
') + .appendTo($shcell) + .find('.toggle') + .bind('change', function(){ + $cell.find('.toggle')[0].checked = this.checked; + updateSpinner(); + }); + } + // add a jQuery UI spinner! + $('') + .val(o.value) + .appendTo($shcell) + .spinner(o) + .bind('change keyup', function(){ + $cell.find('.spinner').val( this.value ); + updateSpinner(); + }); + + if (o.compare) { + // add compare select + tsff.addCompare($shcell, indx, o); + $shcell.find(compareSelect).bind('change', function(){ + $cell.find(compareSelect).val( $(this).val() ); + updateSpinner(); + }); + } + + }); + + // on reset + c.$table.bind('filterReset', function(){ + if ($.isArray(o.compare)) { + $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); + } + // turn off the toggle checkbox + if (o.addToggle) { + $cell.find('.toggle')[0].checked = false; + } + $cell.find('.spinner').spinner('value', o.value); + setTimeout(function(){ + updateSpinner(); + }, 0); + }); + + updateSpinner(); + return $input; + }, + + /**********************\ + jQuery UI Slider + \**********************/ + uiSlider: function($cell, indx, sliderDef) { + var o = $.extend({ + // filter formatter options + delayed : true, + valueToHeader : false, + exactMatch : true, + cellText : '', + compare : '', + allText : 'all', + // include ANY jQuery UI spinner options below + // except values, since this is a non-range setup + value : 0, + min : 0, + max : 100, + step : 1, + range : 'min' + }, sliderDef ), + c = $cell.closest('table')[0].config, + // Add a hidden input to hold the range values + $input = $('') + .appendTo($cell) + // hidden filter update namespace trigger by filter widget + .bind('change' + c.namespace + 'filter', function(){ + updateSlider({ value: this.value }); + }), + $shcell = [], + + // this function updates the hidden input and adds the current values to the header cell text + updateSlider = function(ui, notrigger) { + // ui is not undefined on create + var v = typeof ui !== 'undefined' ? ts.formatFloat((ui.value + '').replace(/[><=]/g, '')) || o.value : o.value, + val = o.compare ? v : v === o.min ? o.allText : v, + compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', + result = compare + val, + searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '' : true; + if (o.valueToHeader) { + // add range indication to the header cell above! + $cell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(' (' + result + ')'); + } else { + // add values to the handle data-value attribute so the css tooltip will work properly + $cell.find('.ui-slider-handle').addClass('value-popup').attr('data-value', result); + } + // update the hidden input; + // ****** ADD AN EQUAL SIGN TO THE BEGINNING! <- this makes the slide exactly match the number ****** + // when the value is at the minimum, clear the hidden input so all rows will be seen + + $cell.find('.filter') + .val( ( compare ? compare + v : v === o.min ? '' : (o.exactMatch ? '=' : '') + v ) ) + .trigger( notrigger ? '' : 'search', searchType ).end() + .find('.slider').slider('value', v); + + // update sticky header cell + if ($shcell.length) { + $shcell + .find(compareSelect).val( compare ).end() + .find('.slider').slider('value', v); + if (o.valueToHeader) { + $shcell.closest('thead').find('th[data-column=' + indx + ']').find('.curvalue').html(' (' + result + ')'); + } else { + $shcell.find('.ui-slider-handle').addClass('value-popup').attr('data-value', result); + } + } + + }; + $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'); + + // add span to header for value - only works if the line in the updateSlider() function is also un-commented out + if (o.valueToHeader) { + $cell.closest('thead').find('th[data-column=' + indx + ']').find('.tablesorter-header-inner').append(''); + } + + // add callbacks; preserve added callbacks + o.oldcreate = o.create; + o.oldslide = o.slide; + o.create = function(event, ui) { + updateSlider(); // ui is an empty object on create + if (typeof o.oldcreate === 'function') { o.oldcreate(event, ui); } + }; + o.slide = function(event, ui) { + updateSlider(ui); + if (typeof o.oldslide === 'function') { o.oldslide(event, ui); } + }; + // add a jQuery UI slider! + $('
') + .appendTo($cell) + .slider(o); + + // update slider from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + var val = tsff.updateCompare($cell, $input, o)[0]; + $cell.find('.slider').slider('value', val ); + updateSlider({ value: val }, false); + ts.filter.formatterUpdated($cell, indx); + }); + + if (o.compare) { + // add compare select + tsff.addCompare($cell, indx, o); + $cell.find(compareSelect).bind('change', function(){ + updateSlider({ value: $cell.find('.slider').slider('value') }); + }); + } + + // on reset + c.$table.bind('filterReset', function(){ + if ($.isArray(o.compare)) { + $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); + } + setTimeout(function(){ + updateSlider({ value: o.value }); + }, 0); + }); + + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); + + // add a jQuery UI slider! + $('
') + .val(o.value) + .appendTo($shcell) + .slider(o) + .bind('change keyup', function(){ + $cell.find('.slider').slider('value', this.value ); + updateSlider(); + }); + + if (o.compare) { + // add compare select + tsff.addCompare($shcell, indx, o); + $shcell.find(compareSelect).bind('change', function(){ + $cell.find(compareSelect).val( $(this).val() ); + updateSlider(); + }); + } + + }); + + return $input; + }, + + /*************************\ + jQuery UI Range Slider (2 handles) + \*************************/ + uiRange: function($cell, indx, rangeDef) { + var o = $.extend({ + // filter formatter options + delayed : true, + valueToHeader : false, + // include ANY jQuery UI spinner options below + // except value, since this one is range specific) + values : [ 0, 100 ], + min : 0, + max : 100, + range : true + }, rangeDef ), + c = $cell.closest('table')[0].config, + // Add a hidden input to hold the range values + $input = $('') + .appendTo($cell) + // hidden filter update namespace trigger by filter widget + .bind('change' + c.namespace + 'filter', function(){ + getRange(); + }), + $shcell = [], + + getRange = function(){ + var val = $input.val(), + v = val.split(' - '); + if (val === '') { v = [ o.min, o.max ]; } + if (v && v[1]) { + updateUiRange({ values: v, delay: false }, true); + } + }, + + // this function updates the hidden input and adds the current values to the header cell text + updateUiRange = function(ui, notrigger) { + // ui.values are undefined for some reason on create + var val = ui && ui.values || o.values, + result = val[0] + ' - ' + val[1], + // make range an empty string if entire range is covered so the filter row will hide (if set) + range = val[0] === o.min && val[1] === o.max ? '' : result, + searchType = ui && typeof ui.delayed === 'boolean' ? ui.delayed : c.$table[0].hasInitialized ? o.delayed || '' : true; + if (o.valueToHeader) { + // add range indication to the header cell above (if not using the css method)! + $cell.closest('thead').find('th[data-column=' + indx + ']').find('.currange').html(' (' + result + ')'); + } else { + // add values to the handle data-value attribute so the css tooltip will work properly + $cell.find('.ui-slider-handle') + .addClass('value-popup') + .eq(0).attr('data-value', val[0]).end() // adding value to data attribute + .eq(1).attr('data-value', val[1]); // value popup shown via css + } + // update the hidden input + $cell.find('.filter').val(range) + .trigger(notrigger ? '' : 'search', searchType).end() + .find('.range').slider('values', val); + // update sticky header cell + if ($shcell.length) { + $shcell.find('.range').slider('values', val); + if (o.valueToHeader) { + $shcell.closest('thead').find('th[data-column=' + indx + ']').find('.currange').html(' (' + result + ')'); + } else { + $shcell.find('.ui-slider-handle') + .addClass('value-popup') + .eq(0).attr('data-value', val[0]).end() // adding value to data attribute + .eq(1).attr('data-value', val[1]); // value popup shown via css + } + } + + }; + $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'); + + // add span to header for value - only works if the line in the updateUiRange() function is also un-commented out + if (o.valueToHeader) { + $cell.closest('thead').find('th[data-column=' + indx + ']').find('.tablesorter-header-inner').append(''); + } + + // add callbacks; preserve added callbacks + o.oldcreate = o.create; + o.oldslide = o.slide; + // add a jQuery UI range slider! + o.create = function(event, ui) { + updateUiRange(); // ui is an empty object on create + if (typeof o.oldcreate === 'function') { o.oldcreate(event, ui); } + }; + o.slide = function(event, ui) { + updateUiRange(ui); + if (typeof o.oldslide === 'function') { o.oldslide(event, ui); } + }; + $('
') + .appendTo($cell) + .slider(o); + + // update slider from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + getRange(); + ts.filter.formatterUpdated($cell, indx); + }); + + // on reset + c.$table.bind('filterReset', function(){ + $cell.find('.range').slider('values', o.values); + setTimeout(function(){ + updateUiRange(); + }, 0); + }); + + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); + + // add a jQuery UI slider! + $('
') + .val(o.value) + .appendTo($shcell) + .slider(o) + .bind('change keyup', function(){ + $cell.find('.range').val( this.value ); + updateUiRange(); + }); + + }); + + // return the hidden input so the filter widget has a reference to it + return $input; + }, + + /*************************\ + jQuery UI Datepicker compare (1 input) + \*************************/ + uiDateCompare: function($cell, indx, defDate) { + var o = $.extend({ + // filter formatter options + cellText : '', + compare : '', + endOfDay : true, + // include ANY jQuery UI spinner options below + + defaultDate : '', + + changeMonth : true, + changeYear : true, + numberOfMonths : 1 + }, defDate), + + $date, + c = $cell.closest('table')[0].config, + // make sure we're using parsed dates in the search + $hdr = $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'), + // Add a hidden input to hold the range values + $input = $('') + .appendTo($cell) + // hidden filter update namespace trigger by filter widget + .bind('change' + c.namespace + 'filter', function(){ + var v = this.value; + if (v) { + o.onClose(v); + } + }), + t, $shcell = [], + + // this function updates the hidden input + date1Compare = function(notrigger) { + var date, query, + getdate = $date.datepicker('getDate') || '', + compare = ($.isArray(o.compare) ? $cell.find(compareSelect).val() || o.compare[ o.selected || 0] : o.compare) || '', + searchType = c.$table[0].hasInitialized ? o.delayed || '' : true; + $date.datepicker('setDate', (getdate === '' ? '' : getdate) || null); + if (getdate === '') { notrigger = false; } + date = $date.datepicker('getDate'); + query = date ? ( o.endOfDay && /<=/.test(compare) ? date.setHours(23, 59, 59) : date.getTime() ) || '' : ''; + if (date && o.endOfDay && compare === '=') { + compare = ''; + query += ' - ' + date.setHours(23, 59, 59); + notrigger = false; + } + $cell.find('.dateCompare') + // add equal to the beginning, so we filter exact numbers + .val(compare + query) + .trigger( notrigger ? '' : 'search', searchType ).end(); + // update sticky header cell + if ($shcell.length) { + $shcell + .find('.dateCompare').val(compare + query).end() + .find(compareSelect).val(compare); + } + }; + + // Add date range picker + t = ''; + $date = $(t).appendTo($cell); + + // add callbacks; preserve added callbacks + o.oldonClose = o.onClose; + + o.onClose = function( selectedDate, ui ) { + date1Compare(); + if (typeof o.oldonClose === 'function') { o.oldonClose(selectedDate, ui); } + }; + $date.datepicker(o); + + // on reset + c.$table.bind('filterReset', function(){ + if ($.isArray(o.compare)) { + $cell.add($shcell).find(compareSelect).val( o.compare[ o.selected || 0 ] ); + } + $cell.add($shcell).find('.date').val(o.defaultDate).datepicker('setDate', o.defaultDate || null); + setTimeout(function(){ + date1Compare(); + }, 0); + }); + + // update date compare from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + var num, v = $input.val(); + if (/\s+-\s+/.test(v)) { + // date range found; assume an exact match on one day + $cell.find(compareSelect).val('='); + num = v.split(/\s+-\s+/)[0]; + $date.datepicker( 'setDate', num || null ); + } else { + num = (tsff.updateCompare($cell, $input, o)[1]).toString() || ''; + // differeniate 1388556000000 from 1/1/2014 using \d{5} regex + num = num !== '' ? /\d{5}/g.test(num) ? new Date(Number(num)) : num || '' : ''; + } + $cell.add($shcell).find('.date').datepicker( 'setDate', num || null ); + setTimeout(function(){ + date1Compare(true); + ts.filter.formatterUpdated($cell, indx); + }, 0); + }); + + if (o.compare) { + // add compare select + tsff.addCompare($cell, indx, o); + $cell.find(compareSelect).bind('change', function(){ date1Compare(); }); } - }); + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - // return the hidden input so the filter widget has a reference to it - return $input.val( o.defaultDate ? o.defaultDate : '' ); - }, + // add a jQuery datepicker! + $shcell + .append(t) + .find('.date') + .datepicker(o); - /*************************\ - jQuery UI Datepicker (2 inputs) - \*************************/ - uiDatepicker: function($cell, indx, defDate) { - var o = $.extend({ - // filter formatter options - endOfDay : true, - textFrom : 'from', - textTo : 'to', - from : '', // defaultDate "from" input - to : '', // defaultDate "to" input - // include ANY jQuery UI spinner options below - changeMonth : true, - changeYear : true, - numberOfMonths : 1 - }, defDate), - t, closeDate, $shcell = [], - c = $cell.closest('table')[0].config, - validDate = function(d){ - return d instanceof Date && isFinite(d); - }, - // Add a hidden input to hold the range values - $input = $('') - .appendTo($cell) - // hidden filter update namespace trigger by filter widget - .bind('change' + c.namespace + 'filter', function(){ - var v = this.value; - if (v.match(' - ')) { - v = v.split(' - '); - $cell.find('.dateTo').val(v[1]); - closeDate(v[0]); - } else if (v.match('>=')) { - closeDate( v.replace('>=', '') ); - } else if (v.match('<=')) { - closeDate( v.replace('<=', '') ); + if (o.compare) { + // add compare select + tsff.addCompare($shcell, indx, o); + $shcell.find(compareSelect).bind('change', function(){ + $cell.find(compareSelect).val( $(this).val() ); + date1Compare(); + }); } - }), - // make sure we're using parsed dates in the search - $hdr = $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'); - // Add date range picker - t = '' + - ''; - $(t).appendTo($cell); + }); - // add callbacks; preserve added callbacks - o.oldonClose = o.onClose; + // return the hidden input so the filter widget has a reference to it + return $input.val( o.defaultDate ? o.defaultDate : '' ); + }, - closeDate = o.onClose = function( selectedDate, ui ) { - var range, - from = $cell.find('.dateFrom').datepicker('getDate'), - to = $cell.find('.dateTo').datepicker('getDate'); - from = validDate(from) ? from.getTime() : ''; - to = validDate(to) ? ( o.endOfDay ? to.setHours(23, 59, 59) : to.getTime() ) || '' : ''; - range = from ? ( to ? from + ' - ' + to : '>=' + from ) : (to ? '<=' + to : ''); - $cell.add( $shcell ) - .find('.dateRange').val(range) - .trigger('search'); - // date picker needs date objects - from = from ? new Date(from) : ''; - to = to ? new Date(to) : ''; + /*************************\ + jQuery UI Datepicker (2 inputs) + \*************************/ + uiDatepicker: function($cell, indx, defDate) { + var o = $.extend({ + // filter formatter options + endOfDay : true, + textFrom : 'from', + textTo : 'to', + from : '', // defaultDate 'from' input + to : '', // defaultDate 'to' input + // include ANY jQuery UI spinner options below + changeMonth : true, + changeYear : true, + numberOfMonths : 1 + }, defDate), + t, closeDate, $shcell = [], + c = $cell.closest('table')[0].config, + validDate = function(d){ + return d instanceof Date && isFinite(d); + }, + // Add a hidden input to hold the range values + $input = $('') + .appendTo($cell) + // hidden filter update namespace trigger by filter widget + .bind('change' + c.namespace + 'filter', function(){ + var v = this.value; + if (v.match(' - ')) { + v = v.split(' - '); + $cell.find('.dateTo').val(v[1]); + closeDate(v[0]); + } else if (v.match('>=')) { + closeDate( v.replace('>=', '') ); + } else if (v.match('<=')) { + closeDate( v.replace('<=', '') ); + } + }), - if (/<=/.test(range)) { + // make sure we're using parsed dates in the search + $hdr = $cell.closest('thead').find('th[data-column=' + indx + ']').addClass('filter-parsed'); + // Add date range picker + t = '' + + ''; + $(t).appendTo($cell); + + // add callbacks; preserve added callbacks + o.oldonClose = o.onClose; + + closeDate = o.onClose = function( selectedDate, ui ) { + var range, + from = $cell.find('.dateFrom').datepicker('getDate'), + to = $cell.find('.dateTo').datepicker('getDate'); + from = validDate(from) ? from.getTime() : ''; + to = validDate(to) ? ( o.endOfDay ? to.setHours(23, 59, 59) : to.getTime() ) || '' : ''; + range = from ? ( to ? from + ' - ' + to : '>=' + from ) : (to ? '<=' + to : ''); $cell.add( $shcell ) - .find('.dateFrom').datepicker('option', 'maxDate', to || null ).end() - .find('.dateTo').datepicker('option', 'minDate', null).datepicker('setDate', to || null); - } else if (/>=/.test(range)) { - $cell.add( $shcell ) - .find('.dateFrom').datepicker('option', 'maxDate', null).datepicker('setDate', from || null).end() - .find('.dateTo').datepicker('option', 'minDate', from || null ); - } else { - $cell.add( $shcell ) - .find('.dateFrom').datepicker('option', 'maxDate', null).datepicker('setDate', from || null ).end() - .find('.dateTo').datepicker('option', 'minDate', null).datepicker('setDate', to || null); - } + .find('.dateRange').val(range) + .trigger('search'); + // date picker needs date objects + from = from ? new Date(from) : ''; + to = to ? new Date(to) : ''; - if (typeof o.oldonClose === 'function') { o.oldonClose(selectedDate, ui); } - }; + if (/<=/.test(range)) { + $cell.add( $shcell ) + .find('.dateFrom').datepicker('option', 'maxDate', to || null ).end() + .find('.dateTo').datepicker('option', 'minDate', null).datepicker('setDate', to || null); + } else if (/>=/.test(range)) { + $cell.add( $shcell ) + .find('.dateFrom').datepicker('option', 'maxDate', null).datepicker('setDate', from || null).end() + .find('.dateTo').datepicker('option', 'minDate', from || null ); + } else { + $cell.add( $shcell ) + .find('.dateFrom').datepicker('option', 'maxDate', null).datepicker('setDate', from || null ).end() + .find('.dateTo').datepicker('option', 'minDate', null).datepicker('setDate', to || null); + } - o.defaultDate = o.from || ''; - $cell.find('.dateFrom').datepicker(o); - o.defaultDate = o.to || '+7d'; // set to date +7 days from today (if not defined) - $cell.find('.dateTo').datepicker(o); + if (typeof o.oldonClose === 'function') { o.oldonClose(selectedDate, ui); } + }; - // update date compare from hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - var val = $input.val() || '', - from = '', - to = ''; - // date range - if (/\s+-\s+/.test(val)){ - val = val.split(/\s+-\s+/) || []; - from = val[0] || ''; - to = val[1] || ''; - } else if (/>=/.test(val)) { - // greater than date (to date empty) - from = val.replace(/>=/, '') || ''; - } else if (/<=/.test(val)) { - // less than date (from date empty) - to = val.replace(/<=/, '') || ''; - } - - // differeniate 1388556000000 from 1/1/2014 using \d{5} regex - from = from !== '' ? /\d{5}/g.test(from) ? new Date(Number(from)) : from || '' : ''; - to = to !== '' ? /\d{5}/g.test(to) ? new Date(Number(to)) : to || '' : ''; - - $cell.add($shcell).find('.dateFrom').datepicker('setDate', from || null); - $cell.add($shcell).find('.dateTo').datepicker('setDate', to || null); - // give datepicker time to process - setTimeout(function(){ - closeDate(); - ts.filter.formatterUpdated($cell, indx); - }, 0); - }); - - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - $shcell.append(t); - - // add a jQuery datepicker! o.defaultDate = o.from || ''; - $shcell.find('.dateFrom').datepicker(o); + $cell.find('.dateFrom').datepicker(o); + o.defaultDate = o.to || '+7d'; // set to date +7 days from today (if not defined) + $cell.find('.dateTo').datepicker(o); - o.defaultDate = o.to || '+7d'; - $shcell.find('.dateTo').datepicker(o); + // update date compare from hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + var val = $input.val() || '', + from = '', + to = ''; + // date range + if (/\s+-\s+/.test(val)){ + val = val.split(/\s+-\s+/) || []; + from = val[0] || ''; + to = val[1] || ''; + } else if (/>=/.test(val)) { + // greater than date (to date empty) + from = val.replace(/>=/, '') || ''; + } else if (/<=/.test(val)) { + // less than date (from date empty) + to = val.replace(/<=/, '') || ''; + } - }); + // differeniate 1388556000000 from 1/1/2014 using \d{5} regex + from = from !== '' ? /\d{5}/g.test(from) ? new Date(Number(from)) : from || '' : ''; + to = to !== '' ? /\d{5}/g.test(to) ? new Date(Number(to)) : to || '' : ''; - // on reset - $cell.closest('table').bind('filterReset', function(){ - $cell.add($shcell).find('.dateFrom').val('').datepicker('setDate', o.from || null ); - $cell.add($shcell).find('.dateTo').val('').datepicker('setDate', o.to || null ); - setTimeout(function(){ - closeDate(); - }, 0); - }); + $cell.add($shcell).find('.dateFrom').datepicker('setDate', from || null); + $cell.add($shcell).find('.dateTo').datepicker('setDate', to || null); + // give datepicker time to process + setTimeout(function(){ + closeDate(); + ts.filter.formatterUpdated($cell, indx); + }, 0); + }); - // return the hidden input so the filter widget has a reference to it - return $input.val( o.from ? ( o.to ? o.from + ' - ' + o.to : '>=' + o.from ) : (o.to ? '<=' + o.to : '') ); - } + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); + $shcell.append(t); -}); + // add a jQuery datepicker! + o.defaultDate = o.from || ''; + $shcell.find('.dateFrom').datepicker(o); + + o.defaultDate = o.to || '+7d'; + $shcell.find('.dateTo').datepicker(o); + + }); + + // on reset + $cell.closest('table').bind('filterReset', function(){ + $cell.add($shcell).find('.dateFrom').val('').datepicker('setDate', o.from || null ); + $cell.add($shcell).find('.dateTo').val('').datepicker('setDate', o.to || null ); + setTimeout(function(){ + closeDate(); + }, 0); + }); + + // return the hidden input so the filter widget has a reference to it + return $input.val( o.from ? ( o.to ? o.from + ' - ' + o.to : '>=' + o.from ) : (o.to ? '<=' + o.to : '') ); + } + + }); })(jQuery); diff --git a/js/widgets/widget-filter-formatter-select2.js b/js/widgets/widget-filter-formatter-select2.js index ec5d0d31..8139a58c 100644 --- a/js/widgets/widget-filter-formatter-select2.js +++ b/js/widgets/widget-filter-formatter-select2.js @@ -4,146 +4,146 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter || {}; -ts.filterFormatter = ts.filterFormatter || {}; + var ts = $.tablesorter || {}; + ts.filterFormatter = ts.filterFormatter || {}; -/************************\ - Select2 Filter Formatter -\************************/ -ts.filterFormatter.select2 = function($cell, indx, select2Def) { - var o = $.extend({ - // select2 filter formatter options - cellText : '', // Text (wrapped in a label element) - match : true, // adds "filter-match" to header - value : '', - // include ANY select2 options below - multiple : true, - width : '100%' + /************************\ + Select2 Filter Formatter + \************************/ + ts.filterFormatter.select2 = function($cell, indx, select2Def) { + var o = $.extend({ + // select2 filter formatter options + cellText : '', // Text (wrapped in a label element) + match : true, // adds 'filter-match' to header + value : '', + // include ANY select2 options below + multiple : true, + width : '100%' - }, select2Def ), - arry, data, - c = $cell.closest('table')[0].config, - wo = c.widgetOptions, - // Add a hidden input to hold the range values - $input = $('') - .appendTo($cell) - // hidden filter update namespace trigger by filter widget - .bind('change' + c.namespace + 'filter', function(){ - var val = this.value; - val = val.replace(/[/()$^]/g, '').split('|'); - $cell.find('.select2').select2('val', val); - updateSelect2(); - }), - $header = c.$headerIndexed[indx], - onlyAvail = $header.hasClass(wo.filter_onlyAvail), - $shcell = [], - matchPrefix = o.match ? '' : '^', - matchSuffix = o.match ? '' : '$', + }, select2Def ), + arry, data, + c = $cell.closest('table')[0].config, + wo = c.widgetOptions, + // Add a hidden input to hold the range values + $input = $('') + .appendTo($cell) + // hidden filter update namespace trigger by filter widget + .bind('change' + c.namespace + 'filter', function(){ + var val = this.value; + val = val.replace(/[/()$^]/g, '').split('|'); + $cell.find('.select2').select2('val', val); + updateSelect2(); + }), + $header = c.$headerIndexed[indx], + onlyAvail = $header.hasClass(wo.filter_onlyAvail), + $shcell = [], + matchPrefix = o.match ? '' : '^', + matchSuffix = o.match ? '' : '$', - // this function updates the hidden input and adds the current values to the header cell text - updateSelect2 = function() { - var arry = false, - v = $cell.find('.select2').select2('val') || o.value || ''; - // convert array to string - if ($.isArray(v)) { - arry = true; - v = v.join('\u0000'); - } - // escape special regex characters (http://stackoverflow.com/a/9310752/145346) - v = v.replace(/[-[\]{}()*+?.,/\\^$|#\s]/g, '\\$&'); - // convert string back into an array - if (arry) { - v = v.split('\u0000'); - } - $input - // add regex, so we filter exact numbers - .val( $.isArray(v) && v.length && v.join('') !== '' ? '/(' + matchPrefix + (v || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' : '' ) - .trigger('search').end() - .find('.select2').select2('val', v); - // update sticky header cell - if ($shcell.length) { - $shcell.find('.select2').select2('val', v); - } - }, + // this function updates the hidden input and adds the current values to the header cell text + updateSelect2 = function() { + var arry = false, + v = $cell.find('.select2').select2('val') || o.value || ''; + // convert array to string + if ($.isArray(v)) { + arry = true; + v = v.join('\u0000'); + } + // escape special regex characters (http://stackoverflow.com/a/9310752/145346) + v = v.replace(/[-[\]{}()*+?.,/\\^$|#\s]/g, '\\$&'); + // convert string back into an array + if (arry) { + v = v.split('\u0000'); + } + $input + // add regex, so we filter exact numbers + .val( $.isArray(v) && v.length && v.join('') !== '' ? '/(' + matchPrefix + (v || []).join(matchSuffix + '|' + matchPrefix) + matchSuffix + ')/' : '' ) + .trigger('search').end() + .find('.select2').select2('val', v); + // update sticky header cell + if ($shcell.length) { + $shcell.find('.select2').select2('val', v); + } + }, - // get options from table cell content or filter_selectSource (v2.16) - updateOptions = function(){ - data = []; - arry = ts.filter.getOptionSource(c.$table[0], indx, onlyAvail) || []; - // build select2 data option - $.each(arry, function(i,v){ - data.push({id: v, text: v}); - }); - o.data = data; - }; - - // get filter-match class from option - $header.toggleClass('filter-match', o.match); - if (o.cellText) { - $cell.prepend(''); - } - - // don't add default in table options if either ajax or - // data options are already defined - if (!(o.ajax && !$.isEmptyObject(o.ajax)) && !o.data) { - updateOptions(); - if (onlyAvail) { - c.$table.bind('filterEnd', function(){ - updateOptions(); - $cell.add($shcell).find('.select2').select2(o); + // get options from table cell content or filter_selectSource (v2.16) + updateOptions = function(){ + data = []; + arry = ts.filter.getOptionSource(c.$table[0], indx, onlyAvail) || []; + // build select2 data option + $.each(arry, function(i, v){ + data.push({id: v, text: v}); }); + o.data = data; + }; + + // get filter-match class from option + $header.toggleClass('filter-match', o.match); + if (o.cellText) { + $cell.prepend(''); } - } - // add a select2 hidden input! - $('') - .val(o.value) - .appendTo($cell) - .select2(o) - .bind('change', function(){ - updateSelect2(); - }); + // don't add default in table options if either ajax or + // data options are already defined + if (!(o.ajax && !$.isEmptyObject(o.ajax)) && !o.data) { + updateOptions(); + if (onlyAvail) { + c.$table.bind('filterEnd', function(){ + updateOptions(); + $cell.add($shcell).find('.select2').select2(o); + }); + } + } - // update select2 from filter hidden input, in case of saved filters - c.$table.bind('filterFomatterUpdate', function(){ - // value = '/(^x$|^y$)/' => 'x,y' - var val = c.$table.data('lastSearch')[indx] || ''; - val = val.replace(/^\/\(\^?/,'').replace(/\$\|\^/g, '|').replace(/\$?\)\/$/g,'').split('|'); - $cell.find('.select2').select2('val', val); - updateSelect2(); - ts.filter.formatterUpdated($cell, indx); - }); - - // has sticky headers? - c.$table.bind('stickyHeadersInit', function(){ - $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); - // add a select2! - $('') + // add a select2 hidden input! + $('') .val(o.value) - .appendTo($shcell) + .appendTo($cell) .select2(o) .bind('change', function(){ - $cell.find('.select2').select2('val', $shcell.find('.select2').select2('val') ); updateSelect2(); }); - if (o.cellText) { - $shcell.prepend(''); - } - }); - - // on reset - c.$table.bind('filterReset', function(){ - $cell.find('.select2').select2('val', o.value || ''); - setTimeout(function(){ + // update select2 from filter hidden input, in case of saved filters + c.$table.bind('filterFomatterUpdate', function(){ + // value = '/(^x$|^y$)/' => 'x,y' + var val = c.$table.data('lastSearch')[indx] || ''; + val = val.replace(/^\/\(\^?/, '').replace(/\$\|\^/g, '|').replace(/\$?\)\/$/g, '').split('|'); + $cell.find('.select2').select2('val', val); updateSelect2(); - }, 0); - }); + ts.filter.formatterUpdated($cell, indx); + }); - updateSelect2(); - return $input; -}; + // has sticky headers? + c.$table.bind('stickyHeadersInit', function(){ + $shcell = c.widgetOptions.$sticky.find('.tablesorter-filter-row').children().eq(indx).empty(); + // add a select2! + $('') + .val(o.value) + .appendTo($shcell) + .select2(o) + .bind('change', function(){ + $cell.find('.select2').select2('val', $shcell.find('.select2').select2('val') ); + updateSelect2(); + }); + if (o.cellText) { + $shcell.prepend(''); + } + + }); + + // on reset + c.$table.bind('filterReset', function(){ + $cell.find('.select2').select2('val', o.value || ''); + setTimeout(function(){ + updateSelect2(); + }, 0); + }); + + updateSelect2(); + return $input; + }; })(jQuery); diff --git a/js/widgets/widget-filter-type-insideRange.js b/js/widgets/widget-filter-type-insideRange.js index 0bdd37b5..bb39f92f 100644 --- a/js/widgets/widget-filter-type-insideRange.js +++ b/js/widgets/widget-filter-type-insideRange.js @@ -1,6 +1,6 @@ /*! Widget: filter, insideRange filter type - updated 2/23/2015 (v2.21.0) */ ;(function($){ -'use strict'; + 'use strict'; // Add insideRange filter type // ============================ diff --git a/js/widgets/widget-filter.js b/js/widgets/widget-filter.js index 3436e4c7..ced1de3a 100644 --- a/js/widgets/widget-filter.js +++ b/js/widgets/widget-filter.js @@ -3,92 +3,92 @@ * by Rob Garrison */ ;( function ( $ ) { -'use strict'; -var ts = $.tablesorter || {}, + 'use strict'; + var ts = $.tablesorter || {}, tscss = ts.css; -$.extend( tscss, { - filterRow : 'tablesorter-filter-row', - filter : 'tablesorter-filter', - filterDisabled : 'disabled', - filterRowHide : 'hideme' -}); + $.extend( tscss, { + filterRow : 'tablesorter-filter-row', + filter : 'tablesorter-filter', + filterDisabled : 'disabled', + filterRowHide : 'hideme' + }); -ts.addWidget({ - id: 'filter', - priority: 50, - options : { - filter_childRows : false, // if true, filter includes child row content in the search - filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped - filter_columnFilters : true, // if true, a filter will be added to the top of each table column - filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) - filter_cellFilter : '', // css class name added to the filter cell ( string or array ) - filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) - filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. - filter_excludeFilter : {}, // filters to exclude, per column - filter_external : '', // jQuery selector string ( or jQuery object ) of external filters - filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin - filter_formatter : null, // add custom filter elements to the filter row - filter_functions : null, // add custom filter functions using this option - filter_hideEmpty : true, // hide filter row when table is empty - filter_hideFilters : false, // collapse filter row when mouse leaves the area - filter_ignoreCase : true, // if true, make all searches case-insensitive - filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) - filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down - filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) - filter_reset : null, // jQuery selector string of an element used to reset the filters - filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters - filter_searchDelay : 300, // typing delay in milliseconds before starting a search - filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true - filter_selectSource : null, // include a function to return an array of values to be added to the column filter select - filter_startsWith : false, // if true, filter start from the beginning of the cell contents - filter_useParsedData : false, // filter all data using parsed content - filter_serversideFiltering : false, // if true, server-side filtering should be performed because client-side filtering will be disabled, but the ui and events will still be used. - filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value - filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text - }, - format: function( table, c, wo ) { - if ( !c.$table.hasClass( 'hasFilters' ) ) { - ts.filter.init( table, c, wo ); + ts.addWidget({ + id: 'filter', + priority: 50, + options : { + filter_childRows : false, // if true, filter includes child row content in the search + filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped + filter_columnFilters : true, // if true, a filter will be added to the top of each table column + filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) + filter_cellFilter : '', // css class name added to the filter cell ( string or array ) + filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) + filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. + filter_excludeFilter : {}, // filters to exclude, per column + filter_external : '', // jQuery selector string ( or jQuery object ) of external filters + filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin + filter_formatter : null, // add custom filter elements to the filter row + filter_functions : null, // add custom filter functions using this option + filter_hideEmpty : true, // hide filter row when table is empty + filter_hideFilters : false, // collapse filter row when mouse leaves the area + filter_ignoreCase : true, // if true, make all searches case-insensitive + filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) + filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down + filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) + filter_reset : null, // jQuery selector string of an element used to reset the filters + filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters + filter_searchDelay : 300, // typing delay in milliseconds before starting a search + filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true + filter_selectSource : null, // include a function to return an array of values to be added to the column filter select + filter_startsWith : false, // if true, filter start from the beginning of the cell contents + filter_useParsedData : false, // filter all data using parsed content + filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used. + filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value + filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text + }, + format: function( table, c, wo ) { + if ( !c.$table.hasClass( 'hasFilters' ) ) { + ts.filter.init( table, c, wo ); + } + }, + remove: function( table, c, wo, refreshing ) { + var tbodyIndex, $tbody, + $table = c.$table, + $tbodies = c.$tbodies, + events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + $table + .removeClass( 'hasFilters' ) + // add .tsfilter namespace to all BUT search + .unbind( events.replace( /\s+/g, ' ' ) ) + // remove the filter row even if refreshing, because the column might have been moved + .find( '.' + tscss.filterRow ).remove(); + if ( refreshing ) { return; } + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody + $tbody.children().removeClass( wo.filter_filteredRow ).show(); + ts.processTbody( table, $tbody, false ); // restore tbody + } + if ( wo.filter_reset ) { + $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); + } } - }, - remove: function( table, c, wo, refreshing ) { - var tbodyIndex, $tbody, - $table = c.$table, - $tbodies = c.$tbodies, - events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - $table - .removeClass( 'hasFilters' ) - // add .tsfilter namespace to all BUT search - .unbind( events.replace( /\s+/g, ' ' ) ) - // remove the filter row even if refreshing, because the column might have been moved - .find( '.' + tscss.filterRow ).remove(); - if ( refreshing ) { return; } - for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody - $tbody.children().removeClass( wo.filter_filteredRow ).show(); - ts.processTbody( table, $tbody, false ); // restore tbody - } - if ( wo.filter_reset ) { - $( document ).undelegate( wo.filter_reset, 'click.tsfilter' ); - } - } -}); + }); -ts.filter = { + ts.filter = { - // regex used in filter 'check' functions - not for general use and not documented - regex: { - regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex - child : /tablesorter-childRow/, // child row class name; this gets updated in the script - filtered : /filtered/, // filtered (hidden) row class name; updated in the script - type : /undefined|number/, // check type - exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') - nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) - operators : /[<>=]/g, // replace operators - query : '(q|query)' // replace filter queries - }, + // regex used in filter 'check' functions - not for general use and not documented + regex: { + regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex + child : /tablesorter-childRow/, // child row class name; this gets updated in the script + filtered : /filtered/, // filtered (hidden) row class name; updated in the script + type : /undefined|number/, // check type + exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') + nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) + operators : /[<>=]/g, // replace operators + query : '(q|query)' // replace filter queries + }, // function( c, data ) { } // c = table.config // data.$row = jQuery object of the row currently being processed @@ -102,1507 +102,1507 @@ ts.filter = { // data.cacheArray = An array of parsed content from each table cell in the row being processed // data.index = column index; table = table element ( DOM ) // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class ) - types: { - or : function( c, data, vars ) { - if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { - var indx, filterMatched, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.orSplit ), - iFilter = data.iFilter.split( ts.filter.regex.orSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // filterMatched = data2.filter === '' && indx > 0 ? true - // look for an exact match with the 'or' unless the 'filter-match' class is found - filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); - if ( filterMatched ) { - return filterMatched; + types: { + or : function( c, data, vars ) { + if ( /\|/.test( data.iFilter ) || ts.filter.regex.orSplit.test( data.filter ) ) { + var indx, filterMatched, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.orSplit ), + iFilter = data.iFilter.split( ts.filter.regex.orSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')'; + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // filterMatched = data2.filter === '' && indx > 0 ? true + // look for an exact match with the 'or' unless the 'filter-match' class is found + filterMatched = regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ); + if ( filterMatched ) { + return filterMatched; + } } + // may be null from processing types + return filterMatched || false; } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for an AND or && operator ( logical and ) - and : function( c, data, vars ) { - if ( ts.filter.regex.andTest.test( data.filter ) ) { - var indx, filterMatched, result, txt, query, regex, - // duplicate data but split filter - data2 = $.extend( {}, data ), - index = data.index, - parsed = data.parsed[ index ], - filter = data.filter.split( ts.filter.regex.andSplit ), - iFilter = data.iFilter.split( ts.filter.regex.andSplit ), - len = filter.length; - for ( indx = 0; indx < len; indx++ ) { - data2.nestedFilters = true; - data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); - data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); - query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) - // replace wild cards since /(a*)/i will match anything - .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); - regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); - // look for an exact match with the 'and' unless the 'filter-match' class is found - result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); - if ( indx === 0 ) { - filterMatched = result; + return null; + }, + // Look for an AND or && operator ( logical and ) + and : function( c, data, vars ) { + if ( ts.filter.regex.andTest.test( data.filter ) ) { + var indx, filterMatched, result, txt, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + index = data.index, + parsed = data.parsed[ index ], + filter = data.filter.split( ts.filter.regex.andSplit ), + iFilter = data.iFilter.split( ts.filter.regex.andSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( ts.filter.parseFilter( c, filter[ indx ], index, parsed ) || '' ); + data2.iFilter = '' + ( ts.filter.parseFilter( c, iFilter[ indx ], index, parsed ) || '' ); + query = ( '(' + ( ts.filter.parseFilter( c, data2.filter, index, parsed ) || '' ) + ')' ) + // replace wild cards since /(a*)/i will match anything + .replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ); + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // look for an exact match with the 'and' unless the 'filter-match' class is found + result = ( regex.test( data2.exact ) || ts.filter.processTypes( c, data2, vars ) ); + if ( indx === 0 ) { + filterMatched = result; + } else { + filterMatched = filterMatched && result; + } + } + // may be null from processing types + return filterMatched || false; + } + return null; + }, + // Look for regex + regex: function( c, data ) { + if ( ts.filter.regex.regex.test( data.filter ) ) { + var matches, + // cache regex per column for optimal speed + regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), + isRegex = regex instanceof RegExp; + try { + if ( !isRegex ) { + // force case insensitive search if ignoreCase option set? + // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } + data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); + } + matches = regex.test( data.exact ); + } catch ( error ) { + matches = false; + } + return matches; + } + return null; + }, + // Look for operators >, >=, < or <= + operators: function( c, data ) { + // ignore empty strings... because '' < 10 is true + if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { + var cachedValue, result, txt, + table = c.table, + index = data.index, + parsed = data.parsed[index], + query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), + parser = c.parsers[index], + savedSearch = query; + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || parser.type === 'numeric' ) { + txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); + result = ts.filter.parseFilter( c, txt, index, true ); + query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; + } + // iExact may be numeric - see issue #149; + // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) + if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && + typeof data.cache !== 'undefined' ) { + cachedValue = data.cache; } else { - filterMatched = filterMatched && result; + txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + cachedValue = ts.formatFloat( txt, table ); + } + if ( />/.test( data.iFilter ) ) { + result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; + } else if ( /= 0 ); } } - // may be null from processing types - return filterMatched || false; - } - return null; - }, - // Look for regex - regex: function( c, data ) { - if ( ts.filter.regex.regex.test( data.filter ) ) { - var matches, - // cache regex per column for optimal speed - regex = data.filter_regexCache[ data.index ] || ts.filter.regex.regex.exec( data.filter ), - isRegex = regex instanceof RegExp; - try { - if ( !isRegex ) { - // force case insensitive search if ignoreCase option set? - // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } - data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); - } - matches = regex.test( data.exact ); - } catch ( error ) { - matches = false; + return null; + }, + // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric + exact: function( c, data ) { + /*jshint eqeqeq:false */ + if ( ts.filter.regex.exact.test( data.iFilter ) ) { + var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), + filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; } - return matches; - } - return null; - }, - // Look for operators >, >=, < or <= - operators: function( c, data ) { - // ignore empty strings... because '' < 10 is true - if ( /^[<>]=?/.test( data.iFilter ) && data.iExact !== '' ) { - var cachedValue, result, txt, - table = c.table, - index = data.index, - parsed = data.parsed[index], - query = ts.formatFloat( data.iFilter.replace( ts.filter.regex.operators, '' ), table ), - parser = c.parsers[index], - savedSearch = query; - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || parser.type === 'numeric' ) { - txt = $.trim( '' + data.iFilter.replace( ts.filter.regex.operators, '' ) ); - result = ts.filter.parseFilter( c, txt, index, true ); - query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; - } - // iExact may be numeric - see issue #149; - // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) - if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && - typeof data.cache !== 'undefined' ) { - cachedValue = data.cache; - } else { - txt = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - cachedValue = ts.formatFloat( txt, table ); - } - if ( />/.test( data.iFilter ) ) { - result = />=/.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; - } else if ( /= 0 ); - } - } - return null; - }, - // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric - exact: function( c, data ) { - /*jshint eqeqeq:false */ - if ( ts.filter.regex.exact.test( data.iFilter ) ) { - var txt = data.iFilter.replace( ts.filter.regex.exact, '' ), - filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; - } - return null; - }, - // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! - range : function( c, data ) { - if ( ts.filter.regex.toTest.test( data.iFilter ) ) { - var result, tmp, range1, range2, - table = c.table, - index = data.index, - parsed = data.parsed[index], - // make sure the dash is for a range and not indicating a negative number - query = data.iFilter.split( ts.filter.regex.toSplit ); + return null; + }, + // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! + range : function( c, data ) { + if ( ts.filter.regex.toTest.test( data.iFilter ) ) { + var result, tmp, range1, range2, + table = c.table, + index = data.index, + parsed = data.parsed[index], + // make sure the dash is for a range and not indicating a negative number + query = data.iFilter.split( ts.filter.regex.toSplit ); - tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; - range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; - range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); - // parse filter value in case we're comparing numbers ( dates ) - if ( parsed || c.parsers[index].type === 'numeric' ) { - result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); - range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; - result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); - range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; - } - if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { - result = data.cache; - } else { - tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; - result = ts.formatFloat( tmp, table ); - } - if ( range1 > range2 ) { - tmp = range1; range1 = range2; range2 = tmp; // swap - } - return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); - } - return null; - }, - // Look for wild card: ? = single, * = multiple, or | = logical OR - wild : function( c, data ) { - if ( /[\?\*\|]/.test( data.iFilter ) ) { - var index = data.index, - parsed = data.parsed[ index ], - query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); - // look for an exact match with the 'or' unless the 'filter-match' class is found - if ( !/\?\*/.test( query ) && data.nestedFilters ) { - query = data.isMatch ? query : '^(' + query + ')$'; - } - // parsing the filter may not work properly when using wildcards =/ - return new RegExp( - query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), - c.widgetOptions.filter_ignoreCase ? 'i' : '' - ) - .test( data.exact ); - } - return null; - }, - // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) - fuzzy: function( c, data ) { - if ( /^~/.test( data.iFilter ) ) { - var indx, - patternIndx = 0, - len = data.iExact.length, - txt = data.iFilter.slice( 1 ), - pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - for ( indx = 0; indx < len; indx++ ) { - if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { - patternIndx += 1; + tmp = query[0].replace( ts.filter.regex.nondigit, '' ) || ''; + range1 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + tmp = query[1].replace( ts.filter.regex.nondigit, '' ) || ''; + range2 = ts.formatFloat( ts.filter.parseFilter( c, tmp, index, parsed ), table ); + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || c.parsers[index].type === 'numeric' ) { + result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); + range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; + result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); + range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; } + if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { + result = data.cache; + } else { + tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.filter.regex.nondigit, '' ) : data.iExact; + result = ts.formatFloat( tmp, table ); + } + if ( range1 > range2 ) { + tmp = range1; range1 = range2; range2 = tmp; // swap + } + return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); } - if ( patternIndx === pattern.length ) { - return true; + return null; + }, + // Look for wild card: ? = single, * = multiple, or | = logical OR + wild : function( c, data ) { + if ( /[\?\*\|]/.test( data.iFilter ) ) { + var index = data.index, + parsed = data.parsed[ index ], + query = '' + ( ts.filter.parseFilter( c, data.iFilter, index, parsed ) || '' ); + // look for an exact match with the 'or' unless the 'filter-match' class is found + if ( !/\?\*/.test( query ) && data.nestedFilters ) { + query = data.isMatch ? query : '^(' + query + ')$'; + } + // parsing the filter may not work properly when using wildcards =/ + return new RegExp( + query.replace( /\?/g, '\\S{1}' ).replace( /\*/g, '\\S*' ), + c.widgetOptions.filter_ignoreCase ? 'i' : '' + ) + .test( data.exact ); + } + return null; + }, + // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) + fuzzy: function( c, data ) { + if ( /^~/.test( data.iFilter ) ) { + var indx, + patternIndx = 0, + len = data.iExact.length, + txt = data.iFilter.slice( 1 ), + pattern = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; + for ( indx = 0; indx < len; indx++ ) { + if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { + patternIndx += 1; + } + } + if ( patternIndx === pattern.length ) { + return true; + } + return false; + } + return null; + } + }, + init: function( table, c, wo ) { + // filter language options + ts.language = $.extend( true, {}, { + to : 'to', + or : 'or', + and : 'and' + }, ts.language ); + + var options, string, txt, $header, column, filters, val, fxn, noSelect, + regex = ts.filter.regex; + c.$table.addClass( 'hasFilters' ); + + // define timers so using clearTimeout won't cause an undefined error + wo.searchTimer = null; + wo.filter_initTimer = null; + wo.filter_formatterCount = 0; + wo.filter_formatterInit = []; + wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; + wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; + + val = '\\{' + ts.filter.regex.query + '\\}'; + $.extend( regex, { + child : new RegExp( c.cssChildRow ), + filtered : new RegExp( wo.filter_filteredRow ), + alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), + toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), + toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ), + andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), + andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), + orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), + iQuery : new RegExp( val, 'i' ), + igQuery : new RegExp( val, 'ig' ) + }); + + // don't build filter row if columnFilters is false or all columns are set to 'filter-false' + // see issue #156 + val = c.$headers.filter( '.filter-false, .parser-false' ).length; + if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { + // build filter row + ts.filter.buildRow( table, c, wo ); + } + + txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + c.$table.bind( txt, function( event, filter ) { + val = wo.filter_hideEmpty && + $.isEmptyObject( c.cache ) && + !( c.delayInit && event.type === 'appendCache' ); + // hide filter row using the 'filtered' class name + c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 + if ( !/(search|filter)/.test( event.type ) ) { + event.stopPropagation(); + ts.filter.buildDefault( table, true ); + } + if ( event.type === 'filterReset' ) { + c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); + ts.filter.searching( table, [] ); + } else if ( event.type === 'filterEnd' ) { + ts.filter.buildDefault( table, true ); + } else { + // send false argument to force a new search; otherwise if the filter hasn't changed, + // it will return + filter = event.type === 'search' ? filter : + event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; + if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { + // force a new search since content has changed + c.lastCombinedFilter = null; + c.lastSearch = []; + } + // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first + // input ensures all inputs are updated when a search is triggered on the table + // $( 'table' ).trigger( 'search', [...] ); + ts.filter.searching( table, filter, true ); } return false; - } - return null; - } - }, - init: function( table, c, wo ) { - // filter language options - ts.language = $.extend( true, {}, { - to : 'to', - or : 'or', - and : 'and' - }, ts.language ); + }); - var options, string, txt, $header, column, filters, val, fxn, noSelect, - regex = ts.filter.regex; - c.$table.addClass( 'hasFilters' ); - - // define timers so using clearTimeout won't cause an undefined error - wo.searchTimer = null; - wo.filter_initTimer = null; - wo.filter_formatterCount = 0; - wo.filter_formatterInit = []; - wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; - wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; - - val = '\\{' + ts.filter.regex.query + '\\}'; - $.extend( regex, { - child : new RegExp( c.cssChildRow ), - filtered : new RegExp( wo.filter_filteredRow ), - alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), - toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), - toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)' ,'gi' ), - andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), - andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), - orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), - iQuery : new RegExp( val, 'i' ), - igQuery : new RegExp( val, 'ig' ) - }); - - // don't build filter row if columnFilters is false or all columns are set to 'filter-false' - // see issue #156 - val = c.$headers.filter( '.filter-false, .parser-false' ).length; - if ( wo.filter_columnFilters !== false && val !== c.$headers.length ) { - // build filter row - ts.filter.buildRow( table, c, wo ); - } - - txt = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' - .split( ' ' ).join( c.namespace + 'filter ' ); - c.$table.bind( txt, function( event, filter ) { - val = wo.filter_hideEmpty && - $.isEmptyObject( c.cache ) && - !( c.delayInit && event.type === 'appendCache' ); - // hide filter row using the 'filtered' class name - c.$table.find( '.' + tscss.filterRow ).toggleClass( wo.filter_filteredRow, val ); // fixes #450 - if ( !/(search|filter)/.test( event.type ) ) { - event.stopPropagation(); - ts.filter.buildDefault( table, true ); - } - if ( event.type === 'filterReset' ) { - c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); - ts.filter.searching( table, [] ); - } else if ( event.type === 'filterEnd' ) { - ts.filter.buildDefault( table, true ); - } else { - // send false argument to force a new search; otherwise if the filter hasn't changed, - // it will return - filter = event.type === 'search' ? filter : - event.type === 'updateComplete' ? c.$table.data( 'lastSearch' ) : ''; - if ( /(update|add)/.test( event.type ) && event.type !== 'updateComplete' ) { - // force a new search since content has changed - c.lastCombinedFilter = null; - c.lastSearch = []; - } - // pass true ( skipFirst ) to prevent the tablesorter.setFilters function from skipping the first - // input ensures all inputs are updated when a search is triggered on the table - // $( 'table' ).trigger( 'search', [...] ); - ts.filter.searching( table, filter, true ); - } - return false; - }); - - // reset button/link - if ( wo.filter_reset ) { - if ( wo.filter_reset instanceof $ ) { - // reset contains a jQuery object, bind to it - wo.filter_reset.click( function() { - c.$table.trigger( 'filterReset' ); - }); - } else if ( $( wo.filter_reset ).length ) { - // reset is a jQuery selector, use event delegation - $( document ) - .undelegate( wo.filter_reset, 'click.tsfilter' ) - .delegate( wo.filter_reset, 'click.tsfilter', function() { - // trigger a reset event, so other functions ( filter_formatter ) know when to reset + // reset button/link + if ( wo.filter_reset ) { + if ( wo.filter_reset instanceof $ ) { + // reset contains a jQuery object, bind to it + wo.filter_reset.click( function() { c.$table.trigger( 'filterReset' ); }); + } else if ( $( wo.filter_reset ).length ) { + // reset is a jQuery selector, use event delegation + $( document ) + .undelegate( wo.filter_reset, 'click.tsfilter' ) + .delegate( wo.filter_reset, 'click.tsfilter', function() { + // trigger a reset event, so other functions ( filter_formatter ) know when to reset + c.$table.trigger( 'filterReset' ); + }); + } } - } - if ( wo.filter_functions ) { - for ( column = 0; column < c.columns; column++ ) { - fxn = ts.getColumnData( table, wo.filter_functions, column ); - if ( fxn ) { - // remove 'filter-select' from header otherwise the options added here are replaced with - // all options - $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); - // don't build select if 'filter-false' or 'parser-false' set - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - options = ''; - if ( fxn === true && noSelect ) { - ts.filter.buildSelect( table, column ); - } else if ( typeof fxn === 'object' && noSelect ) { - // add custom drop down list - for ( string in fxn ) { - if ( typeof string === 'string' ) { - options += options === '' ? - '' : ''; - val = string; - txt = string; - if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - val = string.split( wo.filter_selectSourceSeparator ); - txt = val[1]; - val = val[0]; + if ( wo.filter_functions ) { + for ( column = 0; column < c.columns; column++ ) { + fxn = ts.getColumnData( table, wo.filter_functions, column ); + if ( fxn ) { + // remove 'filter-select' from header otherwise the options added here are replaced with + // all options + $header = c.$headerIndexed[ column ].removeClass( 'filter-select' ); + // don't build select if 'filter-false' or 'parser-false' set + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + options = ''; + if ( fxn === true && noSelect ) { + ts.filter.buildSelect( table, column ); + } else if ( typeof fxn === 'object' && noSelect ) { + // add custom drop down list + for ( string in fxn ) { + if ( typeof string === 'string' ) { + options += options === '' ? + '' : ''; + val = string; + txt = string; + if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + val = string.split( wo.filter_selectSourceSeparator ); + txt = val[1]; + val = val[0]; + } + options += ''; } - options += ''; + } + c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .append( options ); + txt = wo.filter_selectSource; + fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); + if ( fxn ) { + // updating so the extra options are appended + ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); } } + } + } + } + // not really updating, but if the column has both the 'filter-select' class & + // filter_functions set to true, it would append the same options twice. + ts.filter.buildDefault( table, true ); + + ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); + if ( wo.filter_external ) { + ts.filter.bindSearch( table, wo.filter_external ); + } + + if ( wo.filter_hideFilters ) { + ts.filter.hideFilters( table, c ); + } + + // show processing icon + if ( c.showProcessing ) { + txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function( event, columns ) { + // only add processing to certain columns to all columns + $header = ( columns ) ? c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .append( options ); - txt = wo.filter_selectSource; - fxn = $.isFunction( txt ) ? true : ts.getColumnData( table, txt, column ); - if ( fxn ) { - // updating so the extra options are appended - ts.filter.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } - } - } - // not really updating, but if the column has both the 'filter-select' class & - // filter_functions set to true, it would append the same options twice. - ts.filter.buildDefault( table, true ); - - ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); - if ( wo.filter_external ) { - ts.filter.bindSearch( table, wo.filter_external ); - } - - if ( wo.filter_hideFilters ) { - ts.filter.hideFilters( table, c ); - } - - // show processing icon - if ( c.showProcessing ) { - txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function( event, columns ) { - // only add processing to certain columns to all columns - $header = ( columns ) ? - c.$table - .find( '.' + tscss.header ) - .filter( '[data-column]' ) - .filter( function() { - return columns[ $( this ).data( 'column' ) ] !== ''; - }) : ''; - ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); - }); - } - - // set filtered rows count ( intially unfiltered ) - c.filteredRows = c.totalRows; - - // add default values - txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); - c.$table - .unbind( txt.replace( /\s+/g, ' ' ) ) - .bind( txt, function() { - // redefine 'wo' as it does not update properly inside this callback - var wo = this.config.widgetOptions; - filters = ts.filter.setDefaults( table, c, wo ) || []; - if ( filters.length ) { - // prevent delayInit from triggering a cache build if filters are empty - if ( !( c.delayInit && filters.join( '' ) === '' ) ) { - ts.setFilters( table, filters, true ); - } - } - c.$table.trigger( 'filterFomatterUpdate' ); - // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers - setTimeout( function() { - if ( !wo.filter_initialized ) { - ts.filter.filterInitComplete( c ); - } - }, 100 ); - }); - // if filter widget is added after pager has initialized; then set filter init flag - if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { - c.$table.trigger( 'filterFomatterUpdate' ); - setTimeout( function() { - ts.filter.filterInitComplete( c ); - }, 100 ); - } - }, - // $cell parameter, but not the config, is passed to the filter_formatters, - // so we have to work with it instead - formatterUpdated: function( $cell, column ) { - var wo = $cell.closest( 'table' )[0].config.widgetOptions; - if ( !wo.filter_initialized ) { - // add updates by column since this function - // may be called numerous times before initialization - wo.filter_formatterInit[ column ] = 1; - } - }, - filterInitComplete: function( c ) { - var indx, len, - wo = c.widgetOptions, - count = 0, - completed = function() { - wo.filter_initialized = true; - c.$table.trigger( 'filterInit', c ); - ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); - }; - if ( $.isEmptyObject( wo.filter_formatter ) ) { - completed(); - } else { - len = wo.filter_formatterInit.length; - for ( indx = 0; indx < len; indx++ ) { - if ( wo.filter_formatterInit[ indx ] === 1 ) { - count++; - } - } - clearTimeout( wo.filter_initTimer ); - if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { - // filter widget initialized - completed(); - } else if ( !wo.filter_initialized ) { - // fall back in case a filter_formatter doesn't call - // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off - wo.filter_initTimer = setTimeout( function() { - completed(); - }, 500 ); - } - } - }, - setDefaults: function( table, c, wo ) { - var isArray, saved, indx, col, $filters, - // get current ( default ) filters - filters = ts.getFilters( table ) || []; - if ( wo.filter_saveFilters && ts.storage ) { - saved = ts.storage( table, 'tablesorter-filters' ) || []; - isArray = $.isArray( saved ); - // make sure we're not just getting an empty array - if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { - filters = saved; - } - } - // if no filters saved, then check default settings - if ( filters.join( '' ) === '' ) { - // allow adding default setting to external filters - $filters = c.$headers.add( wo.filter_$externalFilters ) - .filter( '[' + wo.filter_defaultAttrib + ']' ); - for ( indx = 0; indx <= c.columns; indx++ ) { - // include data-column='all' external filters - col = indx === c.columns ? 'all' : indx; - filters[indx] = $filters - .filter( '[data-column="' + col + '"]' ) - .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; - } - } - c.$table.data( 'lastSearch', filters ); - return filters; - }, - parseFilter: function( c, filter, column, parsed ) { - return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; - }, - buildRow: function( table, c, wo ) { - var col, column, $header, buildSelect, disabled, name, ffxn, tmp, - // c.columns defined in computeThIndexes() - cellFilter = wo.filter_cellFilter, - columns = c.columns, - arry = $.isArray( cellFilter ), - buildFilter = '
'; - for ( column = 0; column < columns; column++ ) { - buildFilter += '' ).appendTo( c.$filters.eq( column ) ); - } else { - ffxn = ts.getColumnData( table, wo.filter_formatter, column ); - if ( ffxn ) { - wo.filter_formatterCount++; - buildFilter = ffxn( c.$filters.eq( column ), column ); - // no element returned, so lets go find it - if ( buildFilter && buildFilter.length === 0 ) { - buildFilter = c.$filters.eq( column ).children( 'input' ); - } - // element not in DOM, so lets attach it - if ( buildFilter && ( buildFilter.parent().length === 0 || - ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { - c.$filters.eq( column ).append( buildFilter ); - } - } else { - buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); - } - if ( buildFilter ) { - tmp = $header.data( 'placeholder' ) || - $header.attr( 'data-placeholder' ) || - wo.filter_placeholder.search || ''; - buildFilter.attr( 'placeholder', tmp ); - } - } - if ( buildFilter ) { - // add filter class name - name = ( $.isArray( wo.filter_cssFilter ) ? - ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : - wo.filter_cssFilter ) || ''; - buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); - if ( disabled ) { - buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; - } - } - } - }, - bindSearch: function( table, $el, internal ) { - table = $( table )[0]; - $el = $( $el ); // allow passing a selector string - if ( !$el.length ) { return; } - var tmp, - c = table.config, - wo = c.widgetOptions, - namespace = c.namespace + 'filter', - $ext = wo.filter_$externalFilters; - if ( internal !== true ) { - // save anyMatch element - tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; - wo.filter_$anyMatch = $el.filter( tmp ); - if ( $ext && $ext.length ) { - wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); - } else { - wo.filter_$externalFilters = $el; - } - // update values ( external filters added after table initialization ) - ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); - } - // unbind events - tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); - $el - // use data attribute instead of jQuery data since the head is cloned without including - // the data/binding - .attr( 'data-lastSearchTime', new Date().getTime() ) - .unbind( tmp.replace( /\s+/g, ' ' ) ) - // include change for select - fixes #473 - .bind( 'keyup' + namespace, function( event ) { - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - // emulate what webkit does.... escape clears the filter - if ( event.which === 27 ) { - this.value = ''; - // live search - } else if ( wo.filter_liveSearch === false ) { - return; - // don't return if the search value is empty ( all rows need to be revealed ) - } else if ( this.value !== '' && ( - // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace - ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || - // let return & backspace continue on, but ignore arrows & non-valid characters - ( event.which !== 13 && event.which !== 8 && - ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { - return; - } - // change event = no delay; last true flag tells getFilters to skip newest timed input - ts.filter.searching( table, true, true ); - }) - .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { - var column = $( this ).data( 'column' ); - // don't allow 'change' event to process if the input value is the same - fixes #685 - if ( event.which === 13 || event.type === 'search' || - event.type === 'change' && this.value !== c.lastSearch[column] ) { - event.preventDefault(); - // init search with no delay - $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); - ts.filter.searching( table, false, true ); - } - }); - }, - searching: function( table, filter, skipFirst ) { - var wo = table.config.widgetOptions; - clearTimeout( wo.searchTimer ); - if ( typeof filter === 'undefined' || filter === true ) { - // delay filtering - wo.searchTimer = setTimeout( function() { - ts.filter.checkFilters( table, filter, skipFirst ); - }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); - } else { - // skip delay - ts.filter.checkFilters( table, filter, skipFirst ); - } - }, - checkFilters: function( table, filter, skipFirst ) { - var c = table.config, - wo = c.widgetOptions, - filterArray = $.isArray( filter ), - filters = ( filterArray ) ? filter : ts.getFilters( table, true ), - combinedFilters = ( filters || [] ).join( '' ); // combined filter values - // prevent errors if delay init is set - if ( $.isEmptyObject( c.cache ) ) { - // update cache if delayInit set & pager has initialized ( after user initiates a search ) - if ( c.delayInit && c.pager && c.pager.initialized ) { - c.$table.trigger( 'updateCache', [ function() { - ts.filter.checkFilters( table, false, skipFirst ); - } ] ); - } - return; - } - // add filter array back into inputs - if ( filterArray ) { - ts.setFilters( table, filters, false, skipFirst !== true ); - if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } - } - if ( wo.filter_hideFilters ) { - // show/hide filter row as needed - c.$table - .find( '.' + tscss.filterRow ) - .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); - } - // return if the last search is the same; but filter === false when updating the search - // see example-widget-filter.html filter toggle buttons - if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { - return; - } else if ( filter === false ) { - // force filter refresh - c.lastCombinedFilter = null; - c.lastSearch = []; - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterStart', [filters] ); - } - if ( c.showProcessing ) { - // give it time for the processing icon to kick in - setTimeout( function() { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - }, 30 ); - } else { - ts.filter.findRows( table, filters, combinedFilters ); - return false; - } - }, - hideFilters: function( table, c ) { - var timer; - c.$table - .find( '.' + tscss.filterRow ) - .bind( 'mouseenter mouseleave', function( e ) { - // save event object - http://bugs.jquery.com/ticket/12140 - var event = e, - $filterRow = $( this ); - clearTimeout( timer ); - timer = setTimeout( function() { - if ( /enter|over/.test( event.type ) ) { - $filterRow.removeClass( tscss.filterRowHide ); - } else { - // don't hide if input has focus - // $( ':focus' ) needs jQuery 1.6+ - if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { - // don't hide row if any filter has a value - if ( c.lastCombinedFilter === '' ) { - $filterRow.addClass( tscss.filterRowHide ); - } - } - } - }, 200 ); - }) - .find( 'input, select' ).bind( 'focus blur', function( e ) { - var event = e, - $row = $( this ).closest( 'tr' ); - clearTimeout( timer ); - timer = setTimeout( function() { - clearTimeout( timer ); - // don't hide row if any filter has a value - if ( ts.getFilters( c.$table ).join( '' ) === '' ) { - $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); - } - }, 200 ); - }); - }, - defaultFilter: function( filter, mask ) { - if ( filter === '' ) { return filter; } - var regex = ts.filter.regex.iQuery, - maskLen = mask.match( ts.filter.regex.igQuery ).length, - query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], - len = query.length - 1, - indx = 0, - val = mask; - if ( len < 1 && maskLen > 1 ) { - // only one 'word' in query but mask has >1 slots - query[1] = query[0]; - } - // replace all {query} with query words... - // if query = 'Bob', then convert mask from '!{query}' to '!Bob' - // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' - while ( regex.test( val ) ) { - val = val.replace( regex, query[indx++] || '' ); - if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { - val = mask.replace( regex, val ); - } - } - return val; - }, - getLatestSearch: function( $input ) { - if ( $input ) { - return $input.sort( function( a, b ) { - return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); - }); - } - return $input || $(); - }, - multipleColumns: function( c, $input ) { - // look for multiple columns '1-3,4-6,8' in data-column - var temp, ranges, range, start, end, singles, i, indx, len, - wo = c.widgetOptions, - // only target 'all' column inputs on initialization - // & don't target 'all' column inputs if they don't exist - targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, - columns = [], - val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); - // process column range - if ( targets && /-/.test( val ) ) { - ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); - len = ranges.length; - for ( indx = 0; indx < len; indx++ ) { - range = ranges[indx].split( /\s*-\s*/ ); - start = parseInt( range[0], 10 ) || 0; - end = parseInt( range[1], 10 ) || ( c.columns - 1 ); - if ( start > end ) { - temp = start; start = end; end = temp; // swap - } - if ( end >= c.columns ) { - end = c.columns - 1; - } - for ( ; start <= end; start++ ) { - columns.push( start ); - } - // remove processed range from val - val = val.replace( ranges[ indx ], '' ); - } - } - // process single columns - if ( targets && /,/.test( val ) ) { - singles = val.split( /\s*,\s*/ ); - len = singles.length; - for ( i = 0; i < len; i++ ) { - if ( singles[ i ] !== '' ) { - indx = parseInt( singles[ i ], 10 ); - if ( indx < c.columns ) { - columns.push( indx ); - } - } - } - } - // return all columns - if ( !columns.length ) { - for ( indx = 0; indx < c.columns; indx++ ) { - columns.push( indx ); - } - } - return columns; - }, - processTypes: function( c, data, vars ) { - var ffxn, - filterMatched = null, - matches = null; - for ( ffxn in ts.filter.types ) { - if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { - matches = ts.filter.types[ffxn]( c, data, vars ); - if ( matches !== null ) { - filterMatched = matches; - } - } - } - return filterMatched; - }, - processRow: function( c, data, vars ) { - var columnIndex, hasSelect, result, val, filterMatched, - fxn, ffxn, txt, - regex = ts.filter.regex, - wo = c.widgetOptions, - showRow = true; - data.$cells = data.$row.children(); - - if ( data.anyMatchFlag ) { - // look for multiple columns '1-3,4-6,8' - columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); - data.anyMatch = true; - data.isMatch = true; - data.rowArray = data.$cells.map( function( i ) { - if ( $.inArray( i, columnIndex ) > -1 ) { - if ( data.parsed[ i ] ) { - txt = data.cacheArray[ i ]; - } else { - txt = data.rawArray[ i ]; - txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); - if ( c.sortLocaleCompare ) { - txt = ts.replaceAccents( txt ); - } - } - return txt; - } - }).get(); - data.filter = data.anyMatchFilter; - data.iFilter = data.iAnyMatchFilter; - data.exact = data.rowArray.join( ' ' ); - data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; - data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); - - vars.excludeMatch = vars.noAnyMatch; - filterMatched = ts.filter.processTypes( c, data, vars ); - - if ( filterMatched !== null ) { - showRow = filterMatched; - } else { - if ( wo.filter_startsWith ) { - showRow = false; - columnIndex = c.columns; - while ( !showRow && columnIndex > 0 ) { - columnIndex--; - showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; - } - } else { - showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; - } - } - data.anyMatch = false; - // no other filters to process - if ( data.filters.join( '' ) === data.filter ) { - return showRow; - } - } - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - data.filter = data.filters[ columnIndex ]; - data.index = columnIndex; - - // filter types to exclude, per column - vars.excludeMatch = vars.excludeFilter[ columnIndex ]; - - // ignore if filter is empty or disabled - if ( data.filter ) { - data.cache = data.cacheArray[ columnIndex ]; - // check if column data should be from the cell or from parsed data - if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { - data.exact = data.cache; - } else { - result = data.rawArray[ columnIndex ] || ''; - data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 - } - data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? - data.exact.toLowerCase() : data.exact; - - data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); - - result = showRow; // if showRow is true, show that row - - // in case select filter option has a different value vs text 'a - z|A through Z' - ffxn = wo.filter_columnFilters ? - c.$filters.add( c.$externalFilters ) - .filter( '[data-column="'+ columnIndex + '"]' ) - .find( 'select option:selected' ) - .attr( 'data-function-name' ) || '' : ''; - // replace accents - see #357 - if ( c.sortLocaleCompare ) { - data.filter = ts.replaceAccents( data.filter ); - } - - val = true; - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { - data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); - // val is used to indicate that a filter select is using a default filter; - // so we override the exact & partial matches - val = false; - } - // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), - // data.filter = case sensitive - data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; - fxn = vars.functions[ columnIndex ]; - hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); - filterMatched = null; - if ( fxn || ( hasSelect && val ) ) { - if ( fxn === true || hasSelect ) { - // default selector uses exact match unless 'filter-match' class is found - filterMatched = data.isMatch ? - data.iExact.search( data.iFilter ) >= 0 : - data.filter === data.exact; - } else if ( typeof fxn === 'function' ) { - // filter callback( exact cell content, parser normalized content, - // filter input value, column index, jQuery row object ) - filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { - // selector option function - txt = ffxn || data.filter; - filterMatched = - fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); - } - } - if ( filterMatched === null ) { - // cycle through the different filters - // filters return a boolean or null if nothing matches - filterMatched = ts.filter.processTypes( c, data, vars ); - if ( filterMatched !== null ) { - result = filterMatched; - // Look for match, and add child row data for matching - } else { - txt = ( data.iExact + data.childRowText ) - .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); - result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); - } - } else { - result = filterMatched; - } - showRow = ( result ) ? showRow : false; - } - } - return showRow; - }, - findRows: function( table, filters, combinedFilters ) { - if ( table.config.lastCombinedFilter === combinedFilters || - !table.config.widgetOptions.filter_initialized ) { - return; - } - var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, - isChild, childRow, lastSearch, showRow, time, val, indx, - notFiltered, searchFiltered, query, injected, res, id, txt, - storedFilters = $.extend( [], filters ), - regex = ts.filter.regex, - c = table.config, - wo = c.widgetOptions, - // data object passed to filters; anyMatch is a flag for the filters - data = { - anyMatch: false, - filters: filters, - // regex filter type cache - filter_regexCache : [] - }, - vars = { - // anyMatch really screws up with these types of filters - noAnyMatch: [ 'range', 'notMatch', 'operators' ], - // cache filter variables that use ts.getColumnData in the main loop - functions : [], - excludeFilter : [], - defaultColFilter : [], - defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' - }; - - // parse columns after formatter, in case the class is added at that point - data.parsed = c.$headers.map( function( columnIndex ) { - return c.parsers && c.parsers[ columnIndex ] && - // force parsing if parser type is numeric - c.parsers[ columnIndex ].parsed || - // getData won't return 'parsed' if other 'filter-' class names exist - // ( e.g. '; + for ( column = 0; column < columns; column++ ) { + buildFilter += '' ).appendTo( c.$filters.eq( column ) ); + } else { + ffxn = ts.getColumnData( table, wo.filter_formatter, column ); + if ( ffxn ) { + wo.filter_formatterCount++; + buildFilter = ffxn( c.$filters.eq( column ), column ); + // no element returned, so lets go find it + if ( buildFilter && buildFilter.length === 0 ) { + buildFilter = c.$filters.eq( column ).children( 'input' ); + } + // element not in DOM, so lets attach it + if ( buildFilter && ( buildFilter.parent().length === 0 || + ( buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column] ) ) ) { + c.$filters.eq( column ).append( buildFilter ); + } + } else { + buildFilter = $( '' ).appendTo( c.$filters.eq( column ) ); + } + if ( buildFilter ) { + tmp = $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.search || ''; + buildFilter.attr( 'placeholder', tmp ); + } + } + if ( buildFilter ) { + // add filter class name + name = ( $.isArray( wo.filter_cssFilter ) ? + ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : + wo.filter_cssFilter ) || ''; + buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', column ); + if ( disabled ) { + buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; + } + } + } + }, + bindSearch: function( table, $el, internal ) { + table = $( table )[0]; + $el = $( $el ); // allow passing a selector string + if ( !$el.length ) { return; } + var tmp, + c = table.config, + wo = c.widgetOptions, + namespace = c.namespace + 'filter', + $ext = wo.filter_$externalFilters; + if ( internal !== true ) { + // save anyMatch element + tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; + wo.filter_$anyMatch = $el.filter( tmp ); + if ( $ext && $ext.length ) { + wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); + } else { + wo.filter_$externalFilters = $el; + } + // update values ( external filters added after table initialization ) + ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); + } + // unbind events + tmp = ( 'keypress keyup search change '.split( ' ' ).join( namespace + ' ' ) ); + $el + // use data attribute instead of jQuery data since the head is cloned without including + // the data/binding + .attr( 'data-lastSearchTime', new Date().getTime() ) + .unbind( tmp.replace( /\s+/g, ' ' ) ) + // include change for select - fixes #473 + .bind( 'keyup' + namespace, function( event ) { + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + // emulate what webkit does.... escape clears the filter + if ( event.which === 27 ) { + this.value = ''; + // live search + } else if ( wo.filter_liveSearch === false ) { + return; + // don't return if the search value is empty ( all rows need to be revealed ) + } else if ( this.value !== '' && ( + // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace + ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || + // let return & backspace continue on, but ignore arrows & non-valid characters + ( event.which !== 13 && event.which !== 8 && + ( event.which < 32 || ( event.which >= 37 && event.which <= 40 ) ) ) ) ) { + return; + } + // change event = no delay; last true flag tells getFilters to skip newest timed input + ts.filter.searching( table, true, true ); + }) + .bind( 'search change keypress '.split( ' ' ).join( namespace + ' ' ), function( event ) { + var column = $( this ).data( 'column' ); + // don't allow 'change' event to process if the input value is the same - fixes #685 + if ( event.which === 13 || event.type === 'search' || + event.type === 'change' && this.value !== c.lastSearch[column] ) { + event.preventDefault(); + // init search with no delay + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + ts.filter.searching( table, false, true ); + } + }); + }, + searching: function( table, filter, skipFirst ) { + var wo = table.config.widgetOptions; + clearTimeout( wo.searchTimer ); + if ( typeof filter === 'undefined' || filter === true ) { + // delay filtering + wo.searchTimer = setTimeout( function() { + ts.filter.checkFilters( table, filter, skipFirst ); + }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); + } else { + // skip delay + ts.filter.checkFilters( table, filter, skipFirst ); + } + }, + checkFilters: function( table, filter, skipFirst ) { + var c = table.config, + wo = c.widgetOptions, + filterArray = $.isArray( filter ), + filters = ( filterArray ) ? filter : ts.getFilters( table, true ), + combinedFilters = ( filters || [] ).join( '' ); // combined filter values + // prevent errors if delay init is set + if ( $.isEmptyObject( c.cache ) ) { + // update cache if delayInit set & pager has initialized ( after user initiates a search ) + if ( c.delayInit && c.pager && c.pager.initialized ) { + c.$table.trigger( 'updateCache', [ function() { + ts.filter.checkFilters( table, false, skipFirst ); + } ] ); + } + return; + } + // add filter array back into inputs + if ( filterArray ) { + ts.setFilters( table, filters, false, skipFirst !== true ); + if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } + } + if ( wo.filter_hideFilters ) { + // show/hide filter row as needed + c.$table + .find( '.' + tscss.filterRow ) + .trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); + } + // return if the last search is the same; but filter === false when updating the search + // see example-widget-filter.html filter toggle buttons + if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { + return; + } else if ( filter === false ) { + // force filter refresh + c.lastCombinedFilter = null; + c.lastSearch = []; + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterStart', [ filters ] ); + } + if ( c.showProcessing ) { + // give it time for the processing icon to kick in + setTimeout( function() { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + }, 30 ); + } else { + ts.filter.findRows( table, filters, combinedFilters ); + return false; + } + }, + hideFilters: function( table, c ) { + var timer; + c.$table + .find( '.' + tscss.filterRow ) + .bind( 'mouseenter mouseleave', function( e ) { + // save event object - http://bugs.jquery.com/ticket/12140 + var event = e, + $filterRow = $( this ); + clearTimeout( timer ); + timer = setTimeout( function() { + if ( /enter|over/.test( event.type ) ) { + $filterRow.removeClass( tscss.filterRowHide ); + } else { + // don't hide if input has focus + // $( ':focus' ) needs jQuery 1.6+ + if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { + // don't hide row if any filter has a value + if ( c.lastCombinedFilter === '' ) { + $filterRow.addClass( tscss.filterRowHide ); + } + } + } + }, 200 ); + }) + .find( 'input, select' ).bind( 'focus blur', function( e ) { + var event = e, + $row = $( this ).closest( 'tr' ); + clearTimeout( timer ); + timer = setTimeout( function() { + clearTimeout( timer ); + // don't hide row if any filter has a value + if ( ts.getFilters( c.$table ).join( '' ) === '' ) { + $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); + } + }, 200 ); + }); + }, + defaultFilter: function( filter, mask ) { + if ( filter === '' ) { return filter; } + var regex = ts.filter.regex.iQuery, + maskLen = mask.match( ts.filter.regex.igQuery ).length, + query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], + len = query.length - 1, + indx = 0, + val = mask; + if ( len < 1 && maskLen > 1 ) { + // only one 'word' in query but mask has >1 slots + query[1] = query[0]; + } + // replace all {query} with query words... + // if query = 'Bob', then convert mask from '!{query}' to '!Bob' + // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' + while ( regex.test( val ) ) { + val = val.replace( regex, query[indx++] || '' ); + if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { + val = mask.replace( regex, val ); + } + } + return val; + }, + getLatestSearch: function( $input ) { + if ( $input ) { + return $input.sort( function( a, b ) { + return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); + }); + } + return $input || $(); + }, + multipleColumns: function( c, $input ) { + // look for multiple columns '1-3,4-6,8' in data-column + var temp, ranges, range, start, end, singles, i, indx, len, + wo = c.widgetOptions, + // only target 'all' column inputs on initialization + // & don't target 'all' column inputs if they don't exist + targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, + columns = [], + val = $.trim( ts.filter.getLatestSearch( $input ).attr( 'data-column' ) || '' ); + // process column range + if ( targets && /-/.test( val ) ) { + ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); + len = ranges.length; + for ( indx = 0; indx < len; indx++ ) { + range = ranges[indx].split( /\s*-\s*/ ); + start = parseInt( range[0], 10 ) || 0; + end = parseInt( range[1], 10 ) || ( c.columns - 1 ); + if ( start > end ) { + temp = start; start = end; end = temp; // swap + } + if ( end >= c.columns ) { + end = c.columns - 1; + } + for ( ; start <= end; start++ ) { + columns.push( start ); + } + // remove processed range from val + val = val.replace( ranges[ indx ], '' ); + } + } + // process single columns + if ( targets && /,/.test( val ) ) { + singles = val.split( /\s*,\s*/ ); + len = singles.length; + for ( i = 0; i < len; i++ ) { + if ( singles[ i ] !== '' ) { + indx = parseInt( singles[ i ], 10 ); + if ( indx < c.columns ) { + columns.push( indx ); + } + } + } + } + // return all columns + if ( !columns.length ) { + for ( indx = 0; indx < c.columns; indx++ ) { + columns.push( indx ); + } + } + return columns; + }, + processTypes: function( c, data, vars ) { + var ffxn, + filterMatched = null, + matches = null; + for ( ffxn in ts.filter.types ) { + if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { + matches = ts.filter.types[ffxn]( c, data, vars ); + if ( matches !== null ) { + filterMatched = matches; + } + } + } + return filterMatched; + }, + processRow: function( c, data, vars ) { + var columnIndex, hasSelect, result, val, filterMatched, + fxn, ffxn, txt, + regex = ts.filter.regex, + wo = c.widgetOptions, + showRow = true; + data.$cells = data.$row.children(); + + if ( data.anyMatchFlag ) { + // look for multiple columns '1-3,4-6,8' + columnIndex = ts.filter.multipleColumns( c, wo.filter_$anyMatch ); + data.anyMatch = true; + data.isMatch = true; + data.rowArray = data.$cells.map( function( i ) { + if ( $.inArray( i, columnIndex ) > -1 ) { + if ( data.parsed[ i ] ) { + txt = data.cacheArray[ i ]; + } else { + txt = data.rawArray[ i ]; + txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); + if ( c.sortLocaleCompare ) { + txt = ts.replaceAccents( txt ); + } + } + return txt; + } + }).get(); + data.filter = data.anyMatchFilter; + data.iFilter = data.iAnyMatchFilter; + data.exact = data.rowArray.join( ' ' ); + data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; + data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); + + vars.excludeMatch = vars.noAnyMatch; + filterMatched = ts.filter.processTypes( c, data, vars ); + + if ( filterMatched !== null ) { + showRow = filterMatched; + } else { + if ( wo.filter_startsWith ) { + showRow = false; + columnIndex = c.columns; + while ( !showRow && columnIndex > 0 ) { + columnIndex--; + showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; + } + } else { + showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; + } + } + data.anyMatch = false; + // no other filters to process + if ( data.filters.join( '' ) === data.filter ) { + return showRow; + } + } + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.filter = data.filters[ columnIndex ]; + data.index = columnIndex; + + // filter types to exclude, per column + vars.excludeMatch = vars.excludeFilter[ columnIndex ]; + + // ignore if filter is empty or disabled + if ( data.filter ) { + data.cache = data.cacheArray[ columnIndex ]; + // check if column data should be from the cell or from parsed data + if ( wo.filter_useParsedData || data.parsed[ columnIndex ] ) { + data.exact = data.cache; + } else { + result = data.rawArray[ columnIndex ] || ''; + data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 + } + data.iExact = !regex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? + data.exact.toLowerCase() : data.exact; + + data.isMatch = c.$headerIndexed[ data.index ].hasClass( 'filter-match' ); + + result = showRow; // if showRow is true, show that row + + // in case select filter option has a different value vs text 'a - z|A through Z' + ffxn = wo.filter_columnFilters ? + c.$filters.add( c.$externalFilters ) + .filter( '[data-column="' + columnIndex + '"]' ) + .find( 'select option:selected' ) + .attr( 'data-function-name' ) || '' : ''; + // replace accents - see #357 + if ( c.sortLocaleCompare ) { + data.filter = ts.replaceAccents( data.filter ); + } + + val = true; + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { + data.filter = ts.filter.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); + // val is used to indicate that a filter select is using a default filter; + // so we override the exact & partial matches + val = false; + } + // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), + // data.filter = case sensitive + data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; + fxn = vars.functions[ columnIndex ]; + hasSelect = c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); + filterMatched = null; + if ( fxn || ( hasSelect && val ) ) { + if ( fxn === true || hasSelect ) { + // default selector uses exact match unless 'filter-match' class is found + filterMatched = data.isMatch ? + data.iExact.search( data.iFilter ) >= 0 : + data.filter === data.exact; + } else if ( typeof fxn === 'function' ) { + // filter callback( exact cell content, parser normalized content, + // filter input value, column index, jQuery row object ) + filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { + // selector option function + txt = ffxn || data.filter; + filterMatched = + fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } + } + if ( filterMatched === null ) { + // cycle through the different filters + // filters return a boolean or null if nothing matches + filterMatched = ts.filter.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + result = filterMatched; + // Look for match, and add child row data for matching + } else { + txt = ( data.iExact + data.childRowText ) + .indexOf( ts.filter.parseFilter( c, data.iFilter, columnIndex, data.parsed[ columnIndex ] ) ); + result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); + } + } else { + result = filterMatched; + } + showRow = ( result ) ? showRow : false; + } + } + return showRow; + }, + findRows: function( table, filters, combinedFilters ) { + if ( table.config.lastCombinedFilter === combinedFilters || + !table.config.widgetOptions.filter_initialized ) { + return; + } + var len, norm_rows, rowData, $rows, rowIndex, tbodyIndex, $tbody, columnIndex, + isChild, childRow, lastSearch, showRow, time, val, indx, + notFiltered, searchFiltered, query, injected, res, id, txt, + storedFilters = $.extend( [], filters ), + regex = ts.filter.regex, + c = table.config, + wo = c.widgetOptions, + // data object passed to filters; anyMatch is a flag for the filters + data = { + anyMatch: false, + filters: filters, + // regex filter type cache + filter_regexCache : [] + }, + vars = { + // anyMatch really screws up with these types of filters + noAnyMatch: [ 'range', 'notMatch', 'operators' ], + // cache filter variables that use ts.getColumnData in the main loop + functions : [], + excludeFilter : [], + defaultColFilter : [], + defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' + }; + + // parse columns after formatter, in case the class is added at that point + data.parsed = c.$headers.map( function( columnIndex ) { + return c.parsers && c.parsers[ columnIndex ] && + // force parsing if parser type is numeric + c.parsers[ columnIndex ].parsed || + // getData won't return 'parsed' if other 'filter-' class names exist + // ( e.g. '); - if (wo.group_saveGroups && !savedGroup && wo.group_collapsed && wo.group_collapsible) { - // all groups start collapsed - wo.group_currentGroups[wo.group_currentGroup].push(currentGroup); + for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) { + norm_rows = c.cache[tbodyIndex].normalized; + group = ''; // clear grouping across tbodies + $rows = c.$tbodies.eq(tbodyIndex).children('tr').not('.' + c.cssChildRow); + for (rowIndex = 0; rowIndex < $rows.length; rowIndex++) { + if ( $rows.eq(rowIndex).is(':visible') ) { + // fixes #438 + if (ts.grouping.types[grouping[1]]) { + currentGroup = norm_rows[rowIndex] ? + ts.grouping.types[grouping[1]]( c, c.$headerIndexed[column], norm_rows[rowIndex][column], /date/.test(groupClass) ? + grouping[2] : parseInt(grouping[2] || 1, 10) || 1, group, lang ) : currentGroup; + if (group !== currentGroup) { + group = currentGroup; + // show range if number > 1 + if (grouping[1] === 'number' && grouping[2] > 1 && currentGroup !== '') { + currentGroup += ' - ' + (parseInt(currentGroup, 10) + + ((parseInt(grouping[2], 10) - 1) * (c.$headerIndexed[column].hasClass(ts.css.sortAsc) ? 1 : -1))); + } + if ($.isFunction(wo.group_formatter)) { + currentGroup = wo.group_formatter((currentGroup || '').toString(), column, table, c, wo) || currentGroup; + } + $rows.eq(rowIndex).before(''); + if (wo.group_saveGroups && !savedGroup && wo.group_collapsed && wo.group_collapsible) { + // all groups start collapsed + wo.group_currentGroups[wo.group_currentGroup].push(currentGroup); + } } } } } } + c.$table.find('tr.group-header') + .bind('selectstart', false) + .each(function(){ + var isHidden, $label, name, + $row = $(this), + $rows = $row.nextUntil('tr.group-header').filter(':visible'); + if (wo.group_count || $.isFunction(wo.group_callback)) { + $label = $row.find('.group-count'); + if ($label.length) { + if (wo.group_count) { + $label.html( wo.group_count.replace(/\{num\}/g, $rows.length) ); + } + if ($.isFunction(wo.group_callback)) { + wo.group_callback($row.find('td'), $rows, column, table); + } + } + } + if (wo.group_saveGroups && wo.group_currentGroups.length && wo.group_currentGroups[wo.group_currentGroup].length) { + name = $row.find('.group-name').text().toLowerCase(); + isHidden = $.inArray( name, wo.group_currentGroups[wo.group_currentGroup] ) > -1; + $row.toggleClass('collapsed', isHidden); + $rows.toggleClass('group-hidden', isHidden); + } else if (wo.group_collapsed && wo.group_collapsible) { + $row.addClass('collapsed'); + $rows.addClass('group-hidden'); + } + }); + c.$table.trigger(wo.group_complete); } - c.$table.find('tr.group-header') - .bind('selectstart', false) - .each(function(){ - var isHidden, $label, name, - $row = $(this), - $rows = $row.nextUntil('tr.group-header').filter(':visible'); - if (wo.group_count || $.isFunction(wo.group_callback)) { - $label = $row.find('.group-count'); - if ($label.length) { - if (wo.group_count) { - $label.html( wo.group_count.replace(/\{num\}/g, $rows.length) ); - } - if ($.isFunction(wo.group_callback)) { - wo.group_callback($row.find('td'), $rows, column, table); - } - } - } - if (wo.group_saveGroups && wo.group_currentGroups.length && wo.group_currentGroups[wo.group_currentGroup].length) { - name = $row.find('.group-name').text().toLowerCase(); - isHidden = $.inArray( name, wo.group_currentGroups[wo.group_currentGroup] ) > -1; - $row.toggleClass('collapsed', isHidden); - $rows.toggleClass('group-hidden', isHidden); - } else if (wo.group_collapsed && wo.group_collapsible) { - $row.addClass('collapsed'); - $rows.addClass('group-hidden'); - } - }); - c.$table.trigger(wo.group_complete); - } - }, + }, - bindEvents : function(table, c, wo){ - if (wo.group_collapsible) { - wo.group_currentGroups = []; - // .on() requires jQuery 1.7+ - c.$table.on('click toggleGroup keyup', 'tr.group-header', function(event){ - event.stopPropagation(); - // pressing enter will toggle the group - if (event.type === 'keyup' && event.which !== 13) { return; } - var isCollapsed, $groups, indx, - $this = $(this), - name = $this.find('.group-name').text().toLowerCase(); - // use shift-click to toggle ALL groups - if (event.shiftKey && (event.type === 'click' || event.type ==='keyup')) { - $this.siblings('.group-header').trigger('toggleGroup'); - } - $this.toggleClass('collapsed'); - // nextUntil requires jQuery 1.4+ - $this.nextUntil('tr.group-header').toggleClass('group-hidden', $this.hasClass('collapsed') ); - // save collapsed groups - if (wo.group_saveGroups && ts.storage) { - $groups = c.$table.find('.group-header'); - isCollapsed = $this.hasClass('collapsed'); - if (!wo.group_currentGroups[wo.group_currentGroup]) { - wo.group_currentGroups[wo.group_currentGroup] = []; + bindEvents : function(table, c, wo){ + if (wo.group_collapsible) { + wo.group_currentGroups = []; + // .on() requires jQuery 1.7+ + c.$table.on('click toggleGroup keyup', 'tr.group-header', function(event){ + event.stopPropagation(); + // pressing enter will toggle the group + if (event.type === 'keyup' && event.which !== 13) { return; } + var isCollapsed, $groups, indx, + $this = $(this), + name = $this.find('.group-name').text().toLowerCase(); + // use shift-click to toggle ALL groups + if (event.shiftKey && (event.type === 'click' || event.type === 'keyup')) { + $this.siblings('.group-header').trigger('toggleGroup'); } - if (isCollapsed && wo.group_currentGroup) { - wo.group_currentGroups[wo.group_currentGroup].push( name ); - } else if (wo.group_currentGroup) { - indx = $.inArray( name, wo.group_currentGroups[wo.group_currentGroup] ); - if (indx > -1) { - wo.group_currentGroups[wo.group_currentGroup].splice( indx, 1 ); + $this.toggleClass('collapsed'); + // nextUntil requires jQuery 1.4+ + $this.nextUntil('tr.group-header').toggleClass('group-hidden', $this.hasClass('collapsed') ); + // save collapsed groups + if (wo.group_saveGroups && ts.storage) { + $groups = c.$table.find('.group-header'); + isCollapsed = $this.hasClass('collapsed'); + if (!wo.group_currentGroups[wo.group_currentGroup]) { + wo.group_currentGroups[wo.group_currentGroup] = []; } + if (isCollapsed && wo.group_currentGroup) { + wo.group_currentGroups[wo.group_currentGroup].push( name ); + } else if (wo.group_currentGroup) { + indx = $.inArray( name, wo.group_currentGroups[wo.group_currentGroup] ); + if (indx > -1) { + wo.group_currentGroups[wo.group_currentGroup].splice( indx, 1 ); + } + } + ts.storage( table, 'tablesorter-groups', wo.group_currentGroups ); } - ts.storage( table, 'tablesorter-groups', wo.group_currentGroups ); - } + }); + } + $(wo.group_saveReset).on('click', function(){ + ts.grouping.clearSavedGroups(table); }); + c.$table.on('pagerChange.tsgrouping', function(){ + ts.grouping.update(table, c, wo); + }); + }, + + clearSavedGroups: function(table){ + if (table && ts.storage) { + ts.storage(table, 'tablesorter-groups', ''); + ts.grouping.update(table, table.config, table.config.widgetOptions); + } } - $(wo.group_saveReset).on('click', function(){ - ts.grouping.clearSavedGroups(table); - }); - c.$table.on('pagerChange.tsgrouping', function(){ + + }; + + ts.addWidget({ + id: 'group', + priority: 100, + options: { + group_collapsible : true, // make the group header clickable and collapse the rows below it. + group_collapsed : false, // start with all groups collapsed + group_saveGroups : true, // remember collapsed groups + group_saveReset : null, // element to clear saved collapsed groups + group_count : ' ({num})', // if not false, the '{num}' string is replaced with the number of rows in the group + group_separator : '-', // group name separator; used when group-separator-# class is used. + group_formatter : null, // function(txt, column, table, c, wo) { return txt; } + group_callback : null, // function($cell, $rows, column, table){}, callback allowing modification of the group header labels + group_complete : 'groupingComplete', // event triggered on the table when the grouping widget has finished work + + // checkbox parser text used for checked/unchecked values + group_checkbox : [ 'checked', 'unchecked' ], + // change these default date names based on your language preferences + group_months : [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], + group_week : [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], + group_time : [ 'AM', 'PM' ], + // this function is used when 'group-date' is set to create the date string + // you can just return date, date.toLocaleString(), date.toLocaleDateString() or d.toLocaleTimeString() + // reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Conversion_getter + group_dateString : function(date) { return date.toLocaleString(); } + }, + init: function(table, thisWidget, c, wo){ + ts.grouping.bindEvents(table, c, wo); + }, + format: function(table, c, wo) { ts.grouping.update(table, c, wo); - }); - }, - - clearSavedGroups: function(table){ - if (table && ts.storage) { - ts.storage(table, 'tablesorter-groups', ''); - ts.grouping.update(table, table.config, table.config.widgetOptions); + }, + remove : function(table, c, wo){ + c.$table + .off('click', 'tr.group-header') + .off('pagerChange.tsgrouping') + .find('.group-hidden').removeClass('group-hidden').end() + .find('tr.group-header').remove(); } - } - -}; - -ts.addWidget({ - id: 'group', - priority: 100, - options: { - group_collapsible : true, // make the group header clickable and collapse the rows below it. - group_collapsed : false, // start with all groups collapsed - group_saveGroups : true, // remember collapsed groups - group_saveReset : null, // element to clear saved collapsed groups - group_count : ' ({num})', // if not false, the "{num}" string is replaced with the number of rows in the group - group_separator : '-', // group name separator; used when group-separator-# class is used. - group_formatter : null, // function(txt, column, table, c, wo) { return txt; } - group_callback : null, // function($cell, $rows, column, table){}, callback allowing modification of the group header labels - group_complete : 'groupingComplete', // event triggered on the table when the grouping widget has finished work - - // checkbox parser text used for checked/unchecked values - group_checkbox : [ 'checked', 'unchecked' ], - // change these default date names based on your language preferences - group_months : [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], - group_week : [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], - group_time : [ 'AM', 'PM' ], - // this function is used when "group-date" is set to create the date string - // you can just return date, date.toLocaleString(), date.toLocaleDateString() or d.toLocaleTimeString() - // reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Conversion_getter - group_dateString : function(date) { return date.toLocaleString(); } - }, - init: function(table, thisWidget, c, wo){ - ts.grouping.bindEvents(table, c, wo); - }, - format: function(table, c, wo) { - ts.grouping.update(table, c, wo); - }, - remove : function(table, c, wo){ - c.$table - .off('click', 'tr.group-header') - .off('pagerChange.tsgrouping') - .find('.group-hidden').removeClass('group-hidden').end() - .find('tr.group-header').remove(); - } -}); + }); })(jQuery); diff --git a/js/widgets/widget-headerTitles.js b/js/widgets/widget-headerTitles.js index 8f4f5917..dfa738bb 100644 --- a/js/widgets/widget-headerTitles.js +++ b/js/widgets/widget-headerTitles.js @@ -5,14 +5,14 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; -var ts = $.tablesorter; + 'use strict'; + var ts = $.tablesorter; ts.addWidget({ id: 'headerTitles', options: { // use aria-label text - // e.g. "First Name: Ascending sort applied, activate to apply a descending sort" + // e.g. 'First Name: Ascending sort applied, activate to apply a descending sort' headerTitle_useAria : false, // add tooltip class headerTitle_tooltip : '', diff --git a/js/widgets/widget-math.js b/js/widgets/widget-math.js index 8ac8b496..dd946198 100644 --- a/js/widgets/widget-math.js +++ b/js/widgets/widget-math.js @@ -5,7 +5,7 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($) { - "use strict"; + 'use strict'; var ts = $.tablesorter, @@ -28,10 +28,10 @@ arry = $cells.not($el).map(function(){ $t = $(this); txt = $t.attr(c.textAttribute); - if (typeof txt === "undefined") { + if (typeof txt === 'undefined') { txt = this.textContent || $t.text(); } - txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table) || 0; + txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ''), table) || 0; return isNaN(txt) ? 0 : txt; }).get(); } @@ -57,15 +57,15 @@ mathAbove = $t.filter('[' + dataAttrib + '^=above]').length; // ignore filtered rows & rows with data-math="ignore" (and starting row) if ( ( !$rows.eq(i).hasClass(filtered) && $rows.eq(i).not('[' + dataAttrib + '=ignore]').length && i !== len ) || mathAbove && i !== len ) { - // stop calculating "above", when encountering another "above" + // stop calculating 'above', when encountering another 'above' if (mathAbove) { i = 0; } else if ($t.length) { txt = $t.attr(c.textAttribute); - if (typeof txt === "undefined") { + if (typeof txt === 'undefined') { txt = $t[0].textContent || $t.text(); } - txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table) || 0; + txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ''), table) || 0; arry.push(isNaN(txt) ? 0 : txt); } } @@ -76,11 +76,11 @@ $t = $(this).children().filter('[data-column=' + cIndex + ']'); if (!$(this).hasClass(filtered) && $t.not('[' + dataAttrib + '^=above],[' + dataAttrib + '^=col]').length && !$t.is($el)) { txt = $t.attr(c.textAttribute); - if (typeof txt === "undefined") { + if (typeof txt === 'undefined') { txt = ($t[0] ? $t[0].textContent : '') || $t.text(); } // isNaN('') => false - txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table) || 0; + txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ''), table) || 0; arry.push(isNaN(txt) ? 0 : txt); } }); @@ -102,10 +102,10 @@ col = parseInt( $t.attr('data-column'), 10); if (!$t.filter('[' + dataAttrib + ']').length && $.inArray(col, wo.math_ignore) < 0) { txt = $t.attr(c.textAttribute); - if (typeof txt === "undefined") { + if (typeof txt === 'undefined') { txt = ($t[0] ? $t[0].textContent : '') || $t.text(); } - txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ""), table) || 0; + txt = ts.formatFloat(txt.replace(/[^\w,. \-()]/g, ''), table) || 0; arry.push(isNaN(txt) ? 0 : txt); } }); @@ -133,8 +133,8 @@ $mathCells = c.$table.find('.' + c.cssInfoBlock + ', tfoot').find('[' + dataAttrib + ']'); math.mathType( table, wo, $mathCells, wo.math_priority, dataAttrib ); - // find the "all" total - math.mathType( table, wo, c.$table.find('[' + dataAttrib + '^=all]'), ['all'], dataAttrib ); + // find the 'all' total + math.mathType( table, wo, c.$table.find('[' + dataAttrib + '^=all]'), [ 'all' ], dataAttrib ); wo.math_isUpdating = true; c.$table.trigger('update'); @@ -153,8 +153,8 @@ $cells.filter('[' + dataAttrib + '^=' + type + ']').each(function(){ $t = $(this); formula = ($t.attr(dataAttrib) || '').replace(type + '-', ''); - arry = (type === "row") ? math.getRow(table, wo, $t, dataAttrib) : - (type === "all") ? getAll : math.getColumn(table, wo, $t, type, dataAttrib); + arry = (type === 'row') ? math.getRow(table, wo, $t, dataAttrib) : + (type === 'all') ? getAll : math.getColumn(table, wo, $t, type, dataAttrib); if (eq[formula]) { t = eq[formula](arry); if (table.config.debug && console && console.log) { @@ -232,7 +232,7 @@ // search for separator for grp & decimal, anything not digit, not +/- sign, not #. result = m.match( /[^\d\-\+#]/g ); - decimal = ( result && result[result.length-1] ) || '.'; // treat the right most symbol as decimal + decimal = ( result && result[result.length - 1] ) || '.'; // treat the right most symbol as decimal group = ( result && result[1] && result[0] ) || ','; // treat the left most symbol as group separator // split the decimal for the format string if any. @@ -282,7 +282,7 @@ v[0] = str; } - v[1] = ( m[1] && v[1] ) ? decimal + v[1] : ""; + v[1] = ( m[1] && v[1] ) ? decimal + v[1] : ''; // put back any negation, combine integer and fraction, and add back prefix & suffix return prefix + ( ( isNegative ? '-' : '' ) + v[0] + v[1] ) + suffix; }; @@ -304,7 +304,7 @@ }, median : function(arry) { // https://gist.github.com/caseyjustus/1166258 - arry.sort( function(a,b){ return a - b; } ); + arry.sort( function(a, b){ return a - b; } ); var half = Math.floor( arry.length / 2 ); return (arry.length % 2) ? arry[half] : ( arry[half - 1] + arry[half] ) / 2.0; }, @@ -314,12 +314,12 @@ var i, el, modeMap = {}, maxCount = 1, - modes = [arry[0]]; + modes = [ arry[0] ]; for (i = 0; i < arry.length; i++) { el = arry[i]; modeMap[el] = modeMap[el] ? modeMap[el] + 1 : 1; if ( modeMap[el] > maxCount ) { - modes = [el]; + modes = [ el ]; maxCount = modeMap[el]; } else if (modeMap[el] === maxCount) { modes.push(el); @@ -327,7 +327,7 @@ } } // returns arry of modes if there is a tie - return modes.sort( function(a,b){ return a - b; } ); + return modes.sort( function(a, b){ return a - b; } ); }, max : function(arry) { return Math.max.apply( Math, arry ); @@ -336,7 +336,7 @@ return Math.min.apply( Math, arry ); }, range: function(arry) { - var v = arry.sort(function(a,b){ return a - b; }); + var v = arry.sort(function(a, b){ return a - b; }); return v[ arry.length - 1 ] - v[0]; }, // common variance equation @@ -374,7 +374,7 @@ // add new widget called repeatHeaders // ************************************ ts.addWidget({ - id: "math", + id: 'math', priority: 100, options: { math_data : 'math', @@ -384,7 +384,7 @@ math_mask : '#,##0.00', // complete executed after each fucntion math_complete : null, // function($cell, wo, result, value, arry){ return result; }, - // order of calculation; "all" is last + // order of calculation; 'all' is last math_priority : [ 'row', 'above', 'col' ], // template for or just prepend the mask prefix & suffix with this HTML // e.g. '{content}' diff --git a/js/widgets/widget-output.js b/js/widgets/widget-output.js index 864518ed..0639eeab 100644 --- a/js/widgets/widget-output.js +++ b/js/widgets/widget-output.js @@ -7,365 +7,365 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter, + var ts = $.tablesorter, -output = ts.output = { + output = ts.output = { - event : 'outputTable', + event : 'outputTable', - // wrap line breaks & tabs in quotes - regexQuote : /([\n\t\x09\x0d\x0a]|<[^<]+>)/, // test if cell needs wrapping quotes - regexBR : /(|\n)/g, // replace - regexIMG : /]+alt\s*=\s*['"]([^'"]+)['"][^>]*>/i, // match - regexHTML : /<[^<]+>/g, // replace + // wrap line breaks & tabs in quotes + regexQuote : /([\n\t\x09\x0d\x0a]|<[^<]+>)/, // test if cell needs wrapping quotes + regexBR : /(|\n)/g, // replace + regexIMG : /]+alt\s*=\s*['"]([^'"]+)['"][^>]*>/i, // match + regexHTML : /<[^<]+>/g, // replace - replaceCR : '\x0d\x0a', - replaceTab : '\x09', + replaceCR : '\x0d\x0a', + replaceTab : '\x09', - popupTitle : 'Output', - popupStyle : 'width:100%;height:100%;', // for textarea - message : 'Your device does not support downloading. Please try again in desktop browser.', + popupTitle : 'Output', + popupStyle : 'width:100%;height:100%;', // for textarea + message : 'Your device does not support downloading. Please try again in desktop browser.', - init : function(c) { - c.$table - .off(output.event) - .on(output.event, function( e ) { - e.stopPropagation(); - // explicitly use table.config.widgetOptions because we want - // the most up-to-date values; not the "wo" from initialization - output.process(c, c.widgetOptions); - }); - }, + init : function(c) { + c.$table + .off(output.event) + .on(output.event, function( e ) { + e.stopPropagation(); + // explicitly use table.config.widgetOptions because we want + // the most up-to-date values; not the 'wo' from initialization + output.process(c, c.widgetOptions); + }); + }, - processRow: function(c, $rows, isHeader, isJSON) { - var $cell, $cells, cellsLen, rowIndex, row, col, indx, rowspanLen, colspanLen, txt, - wo = c.widgetOptions, - tmpRow = [], - dupe = wo.output_duplicateSpans, - addSpanIndex = isHeader && isJSON && wo.output_headerRows && $.isFunction(wo.output_callbackJSON), - cellIndex = 0, - rowsLength = $rows.length; + processRow: function(c, $rows, isHeader, isJSON) { + var $cell, $cells, cellsLen, rowIndex, row, col, indx, rowspanLen, colspanLen, txt, + wo = c.widgetOptions, + tmpRow = [], + dupe = wo.output_duplicateSpans, + addSpanIndex = isHeader && isJSON && wo.output_headerRows && $.isFunction(wo.output_callbackJSON), + cellIndex = 0, + rowsLength = $rows.length; - for ( rowIndex = 0; rowIndex < rowsLength; rowIndex++ ) { - if (!tmpRow[rowIndex]) { tmpRow[rowIndex] = []; } - cellIndex = 0; - $cells = $rows.eq( rowIndex ).children(); - cellsLen = $cells.length; - for ( indx = 0; indx < cellsLen; indx++ ) { - $cell = $cells.eq( indx ); - // process rowspans - if ($cell.filter('[rowspan]').length) { - rowspanLen = parseInt( $cell.attr('rowspan'), 10) - 1; - txt = output.formatData( c, wo, $cell, isHeader ); - for (row = 1; row <= rowspanLen; row++) { - if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; } - tmpRow[rowIndex + row][cellIndex] = isHeader ? txt : dupe ? txt : ''; + for ( rowIndex = 0; rowIndex < rowsLength; rowIndex++ ) { + if (!tmpRow[rowIndex]) { tmpRow[rowIndex] = []; } + cellIndex = 0; + $cells = $rows.eq( rowIndex ).children(); + cellsLen = $cells.length; + for ( indx = 0; indx < cellsLen; indx++ ) { + $cell = $cells.eq( indx ); + // process rowspans + if ($cell.filter('[rowspan]').length) { + rowspanLen = parseInt( $cell.attr('rowspan'), 10) - 1; + txt = output.formatData( c, wo, $cell, isHeader ); + for (row = 1; row <= rowspanLen; row++) { + if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; } + tmpRow[rowIndex + row][cellIndex] = isHeader ? txt : dupe ? txt : ''; + } } - } - // process colspans - if ($cell.filter('[colspan]').length) { - colspanLen = parseInt( $cell.attr('colspan'), 10) - 1; - // allow data-attribute to be an empty string - txt = output.formatData( c, wo, $cell, isHeader ); - for (col = 1; col <= colspanLen; col++) { - // if we're processing the header & making JSON, the header names need to be unique - if ($cell.filter('[rowspan]').length) { - rowspanLen = parseInt( $cell.attr('rowspan'), 10); - for (row = 0; row < rowspanLen; row++) { - if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; } - tmpRow[rowIndex + row][cellIndex + col] = addSpanIndex ? + // process colspans + if ($cell.filter('[colspan]').length) { + colspanLen = parseInt( $cell.attr('colspan'), 10) - 1; + // allow data-attribute to be an empty string + txt = output.formatData( c, wo, $cell, isHeader ); + for (col = 1; col <= colspanLen; col++) { + // if we're processing the header & making JSON, the header names need to be unique + if ($cell.filter('[rowspan]').length) { + rowspanLen = parseInt( $cell.attr('rowspan'), 10); + for (row = 0; row < rowspanLen; row++) { + if (!tmpRow[rowIndex + row]) { tmpRow[rowIndex + row] = []; } + tmpRow[rowIndex + row][cellIndex + col] = addSpanIndex ? + wo.output_callbackJSON($cell, txt, cellIndex + col) || + txt + '(' + (cellIndex + col) + ')' : isHeader ? txt : dupe ? txt : ''; + } + } else { + tmpRow[rowIndex][cellIndex + col] = addSpanIndex ? wo.output_callbackJSON($cell, txt, cellIndex + col) || txt + '(' + (cellIndex + col) + ')' : isHeader ? txt : dupe ? txt : ''; } - } else { - tmpRow[rowIndex][cellIndex + col] = addSpanIndex ? - wo.output_callbackJSON($cell, txt, cellIndex + col) || - txt + '(' + (cellIndex + col) + ')' : isHeader ? txt : dupe ? txt : ''; } } - } - // skip column if already defined - while (typeof tmpRow[rowIndex][cellIndex] !== 'undefined') { cellIndex++; } + // skip column if already defined + while (typeof tmpRow[rowIndex][cellIndex] !== 'undefined') { cellIndex++; } - tmpRow[rowIndex][cellIndex] = tmpRow[rowIndex][cellIndex] || - output.formatData( c, wo, $cell, isHeader ); - cellIndex++; - } - } - return ts.output.removeColumns( c, wo, tmpRow ); - }, - - // remove hidden/ignored columns - removeColumns : function( c, wo, arry ) { - var rowIndex, row, colIndex, - data = [], - len = arry.length; - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - row = arry[ rowIndex ]; - data[ rowIndex ] = []; - for ( colIndex = 0; colIndex < c.columns; colIndex++ ) { - if ( !wo.output_hiddenColumnArray[ colIndex ] ) { - data[ rowIndex ].push( row[ colIndex ] ); + tmpRow[rowIndex][cellIndex] = tmpRow[rowIndex][cellIndex] || + output.formatData( c, wo, $cell, isHeader ); + cellIndex++; } } - } - return data; - }, + return ts.output.removeColumns( c, wo, tmpRow ); + }, - process : function(c, wo) { - var mydata, $this, $rows, headers, csvData, len, rowsLen, tmp, - hasStringify = window.JSON && JSON.hasOwnProperty('stringify'), - indx = 0, - tmpData = (wo.output_separator || ',').toLowerCase(), - outputJSON = tmpData === 'json', - outputArray = tmpData === 'array', - separator = outputJSON || outputArray ? ',' : wo.output_separator, - saveRows = wo.output_saveRows, - $el = c.$table; - // regex to look for the set separator or HTML - wo.output_regex = new RegExp('(' + (/\\/.test(separator) ? '\\' : '' ) + separator + ')' ); + // remove hidden/ignored columns + removeColumns : function( c, wo, arry ) { + var rowIndex, row, colIndex, + data = [], + len = arry.length; + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + row = arry[ rowIndex ]; + data[ rowIndex ] = []; + for ( colIndex = 0; colIndex < c.columns; colIndex++ ) { + if ( !wo.output_hiddenColumnArray[ colIndex ] ) { + data[ rowIndex ].push( row[ colIndex ] ); + } + } + } + return data; + }, - // make a list of hidden columns - wo.output_hiddenColumnArray = []; - for ( indx = 0; indx < c.columns; indx++ ) { - wo.output_hiddenColumnArray[ indx ] = $.inArray( indx, wo.output_ignoreColumns ) > -1 || - c.$headerIndexed[ indx ].css( 'display' ) === 'none'; - } + process : function(c, wo) { + var mydata, $this, $rows, headers, csvData, len, rowsLen, tmp, + hasStringify = window.JSON && JSON.hasOwnProperty('stringify'), + indx = 0, + tmpData = (wo.output_separator || ',').toLowerCase(), + outputJSON = tmpData === 'json', + outputArray = tmpData === 'array', + separator = outputJSON || outputArray ? ',' : wo.output_separator, + saveRows = wo.output_saveRows, + $el = c.$table; + // regex to look for the set separator or HTML + wo.output_regex = new RegExp('(' + (/\\/.test(separator) ? '\\' : '' ) + separator + ')' ); - // get header cells - $this = $el.find('thead tr:visible').not('.' + (ts.css.filterRow || 'tablesorter-filter-row') ); - headers = output.processRow(c, $this, true, outputJSON); - - // all tbody rows - $rows = $el.children('tbody').children('tr'); - - // get (f)iltered, (v)isible, all rows (look for the first letter only), or jQuery filter selector - $rows = /^f/.test(saveRows) ? $rows.not('.' + (wo.filter_filteredRow || 'filtered') ) : - /^v/.test(saveRows) ? $rows.filter(':visible') : - // look for '.' (class selector), '#' (id selector), - // ':' (basic filters, e.g. ':not()') or '[' (attribute selector start) - /^[.#:\[]/.test(saveRows) ? $rows.filter(saveRows) : - // default to all rows - $rows; - - // process to array of arrays - csvData = output.processRow(c, $rows); - - if (wo.output_includeFooter) { - // clone, to force the tfoot rows to the end of this selection of rows - // otherwise they appear after the thead (the order in the HTML) - csvData = csvData.concat( output.processRow( c, $el.children('tfoot').children('tr:visible') ) ); - } - - len = headers.length; - - if (outputJSON) { - tmpData = []; - rowsLen = csvData.length; - for ( indx = 0; indx < rowsLen; indx++ ) { - // multiple header rows & output_headerRows = true, pick the last row... - tmp = headers[ ( len > 1 && wo.output_headerRows ) ? indx % len : len - 1 ]; - tmpData.push( output.row2Hash( tmp, csvData[ indx ] ) ); + // make a list of hidden columns + wo.output_hiddenColumnArray = []; + for ( indx = 0; indx < c.columns; indx++ ) { + wo.output_hiddenColumnArray[ indx ] = $.inArray( indx, wo.output_ignoreColumns ) > -1 || + c.$headerIndexed[ indx ].css( 'display' ) === 'none'; } - // requires JSON stringify; if it doesn't exist, the output will show [object Object],... in the output window - mydata = hasStringify ? JSON.stringify(tmpData) : tmpData; - } else { - tmp = [ headers[ ( len > 1 && wo.output_headerRows ) ? indx % len : len - 1 ] ]; - tmpData = output.row2CSV(wo, wo.output_headerRows ? headers : tmp, outputArray) - .concat( output.row2CSV(wo, csvData, outputArray) ); + // get header cells + $this = $el.find('thead tr:visible').not('.' + (ts.css.filterRow || 'tablesorter-filter-row') ); + headers = output.processRow(c, $this, true, outputJSON); - // stringify the array; if stringify doesn't exist the array will be flattened - mydata = outputArray && hasStringify ? JSON.stringify(tmpData) : tmpData.join('\n'); - } + // all tbody rows + $rows = $el.children('tbody').children('tr'); - // callback; if true returned, continue processing - if ($.isFunction(wo.output_callback) && !wo.output_callback(c, mydata)) { return; } + // get (f)iltered, (v)isible, all rows (look for the first letter only), or jQuery filter selector + $rows = /^f/.test(saveRows) ? $rows.not('.' + (wo.filter_filteredRow || 'filtered') ) : + /^v/.test(saveRows) ? $rows.filter(':visible') : + // look for '.' (class selector), '#' (id selector), + // ':' (basic filters, e.g. ':not()') or '[' (attribute selector start) + /^[.#:\[]/.test(saveRows) ? $rows.filter(saveRows) : + // default to all rows + $rows; - if ( /p/i.test( wo.output_delivery || '' ) ) { - output.popup(mydata, wo.output_popupStyle, outputJSON || outputArray); - } else { - output.download(wo, mydata); - } + // process to array of arrays + csvData = output.processRow(c, $rows); - }, // end process - - row2CSV : function(wo, tmpRow, outputArray) { - var tmp, rowIndex, - csvData = [], - rowLen = tmpRow.length; - for (rowIndex = 0; rowIndex < rowLen; rowIndex++) { - // remove any blank rows - tmp = ( tmpRow[rowIndex] || [] ).join('').replace(/\"/g,''); - if ( ( tmpRow[rowIndex] || [] ).length > 0 && tmp !== '' ) { - csvData[csvData.length] = outputArray ? tmpRow[rowIndex] : tmpRow[rowIndex].join(wo.output_separator); + if (wo.output_includeFooter) { + // clone, to force the tfoot rows to the end of this selection of rows + // otherwise they appear after the thead (the order in the HTML) + csvData = csvData.concat( output.processRow( c, $el.children('tfoot').children('tr:visible') ) ); } - } - return csvData; - }, - row2Hash : function( keys, values ) { - var indx, - json = {}, - len = values.length; - for ( indx = 0; indx < len; indx++ ) { - if ( indx < keys.length ) { - json[ keys[ indx ] ] = values[ indx ]; - } - } - return json; - }, + len = headers.length; - formatData : function(c, wo, $el, isHeader) { - var attr = $el.attr(wo.output_dataAttrib), - txt = typeof attr !== 'undefined' ? attr : $el.html(), - quotes = (wo.output_separator || ',').toLowerCase(), - separator = quotes === 'json' || quotes === 'array', - // replace " with “ if undefined - result = txt.replace(/\"/g, wo.output_replaceQuote || '\u201c'); - // replace line breaks with \\n & tabs with \\t - if (!wo.output_trimSpaces) { - result = result.replace(output.regexBR, output.replaceCR).replace(/\t/g, output.replaceTab); - } else { - result = result.replace(output.regexBR, ''); - } - // extract img alt text - txt = result.match(output.regexIMG); - if (!wo.output_includeHTML && txt !== null) { - result = txt[1]; - } - // replace/remove html - result = wo.output_includeHTML && !isHeader ? result : result.replace(output.regexHTML, ''); - result = wo.output_trimSpaces || isHeader ? $.trim(result) : result; - // JSON & array outputs don't need quotes - quotes = separator ? false : wo.output_wrapQuotes || wo.output_regex.test(result) || output.regexQuote.test(result); - result = quotes ? '"' + result + '"' : result; + if (outputJSON) { + tmpData = []; + rowsLen = csvData.length; + for ( indx = 0; indx < rowsLen; indx++ ) { + // multiple header rows & output_headerRows = true, pick the last row... + tmp = headers[ ( len > 1 && wo.output_headerRows ) ? indx % len : len - 1 ]; + tmpData.push( output.row2Hash( tmp, csvData[ indx ] ) ); + } - // formatting callback - added v2.22.4 - if ( typeof wo.output_formatContent === 'function' ) { - return wo.output_formatContent( c, wo, { - isHeader : isHeader, - $cell : $el, - content : result - }); - } - - return result; - }, - - popup : function(data, style, wrap) { - var generator = window.open('', output.popupTitle, style); - generator.document.write( - '' + output.popupTitle + '' + - '' + - '' - ); - generator.document.close(); - generator.focus(); - // select all text and focus within the textarea in the popup - // $(generator.document).find('textarea').select().focus(); - return true; - }, - - // modified from https://github.com/PixelsCommander/Download-File-JS - // & http://html5-demos.appspot.com/static/a.download.html - download : function (wo, data){ - - var e, blob, gotBlob, - nav = window.navigator, - link = document.createElement('a'); - - // iOS devices do not support downloading. We have to inform user about this. - if (/(iP)/g.test(nav.userAgent)) { - alert(output.message); - return false; - } - - // test for blob support - try { - gotBlob = !!new Blob(); - } catch (err) { - gotBlob = false; - } - - // Use HTML5 Blob if browser supports it - if ( gotBlob ) { - - window.URL = window.URL || window.webkitURL; - // prepend BOM for utf-8 encoding - see https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js#L140 - blob = new Blob( [ '\ufeff', data ], { type: wo.output_encoding } ); - - if (nav.msSaveBlob) { - // IE 10+ - nav.msSaveBlob(blob, wo.output_saveFileName); + // requires JSON stringify; if it doesn't exist, the output will show [object Object],... in the output window + mydata = hasStringify ? JSON.stringify(tmpData) : tmpData; } else { - // all other browsers - link.href = window.URL.createObjectURL(blob); - link.download = wo.output_saveFileName; - // Dispatching click event; using $(link).trigger() won't work - if (document.createEvent) { - e = document.createEvent('MouseEvents'); - // event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, - // ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget); - e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - link.dispatchEvent(e); + tmp = [ headers[ ( len > 1 && wo.output_headerRows ) ? indx % len : len - 1 ] ]; + tmpData = output.row2CSV(wo, wo.output_headerRows ? headers : tmp, outputArray) + .concat( output.row2CSV(wo, csvData, outputArray) ); + + // stringify the array; if stringify doesn't exist the array will be flattened + mydata = outputArray && hasStringify ? JSON.stringify(tmpData) : tmpData.join('\n'); + } + + // callback; if true returned, continue processing + if ($.isFunction(wo.output_callback) && !wo.output_callback(c, mydata)) { return; } + + if ( /p/i.test( wo.output_delivery || '' ) ) { + output.popup(mydata, wo.output_popupStyle, outputJSON || outputArray); + } else { + output.download(wo, mydata); + } + + }, // end process + + row2CSV : function(wo, tmpRow, outputArray) { + var tmp, rowIndex, + csvData = [], + rowLen = tmpRow.length; + for (rowIndex = 0; rowIndex < rowLen; rowIndex++) { + // remove any blank rows + tmp = ( tmpRow[rowIndex] || [] ).join('').replace(/\"/g, ''); + if ( ( tmpRow[rowIndex] || [] ).length > 0 && tmp !== '' ) { + csvData[csvData.length] = outputArray ? tmpRow[rowIndex] : tmpRow[rowIndex].join(wo.output_separator); } } - return false; + return csvData; + }, + + row2Hash : function( keys, values ) { + var indx, + json = {}, + len = values.length; + for ( indx = 0; indx < len; indx++ ) { + if ( indx < keys.length ) { + json[ keys[ indx ] ] = values[ indx ]; + } + } + return json; + }, + + formatData : function(c, wo, $el, isHeader) { + var attr = $el.attr(wo.output_dataAttrib), + txt = typeof attr !== 'undefined' ? attr : $el.html(), + quotes = (wo.output_separator || ',').toLowerCase(), + separator = quotes === 'json' || quotes === 'array', + // replace " with “ if undefined + result = txt.replace(/\"/g, wo.output_replaceQuote || '\u201c'); + // replace line breaks with \\n & tabs with \\t + if (!wo.output_trimSpaces) { + result = result.replace(output.regexBR, output.replaceCR).replace(/\t/g, output.replaceTab); + } else { + result = result.replace(output.regexBR, ''); + } + // extract img alt text + txt = result.match(output.regexIMG); + if (!wo.output_includeHTML && txt !== null) { + result = txt[1]; + } + // replace/remove html + result = wo.output_includeHTML && !isHeader ? result : result.replace(output.regexHTML, ''); + result = wo.output_trimSpaces || isHeader ? $.trim(result) : result; + // JSON & array outputs don't need quotes + quotes = separator ? false : wo.output_wrapQuotes || wo.output_regex.test(result) || output.regexQuote.test(result); + result = quotes ? '"' + result + '"' : result; + + // formatting callback - added v2.22.4 + if ( typeof wo.output_formatContent === 'function' ) { + return wo.output_formatContent( c, wo, { + isHeader : isHeader, + $cell : $el, + content : result + }); + } + + return result; + }, + + popup : function(data, style, wrap) { + var generator = window.open('', output.popupTitle, style); + generator.document.write( + '' + output.popupTitle + '' + + '' + + '' + ); + generator.document.close(); + generator.focus(); + // select all text and focus within the textarea in the popup + // $(generator.document).find('textarea').select().focus(); + return true; + }, + + // modified from https://github.com/PixelsCommander/Download-File-JS + // & http://html5-demos.appspot.com/static/a.download.html + download : function (wo, data){ + + var e, blob, gotBlob, + nav = window.navigator, + link = document.createElement('a'); + + // iOS devices do not support downloading. We have to inform user about this. + if (/(iP)/g.test(nav.userAgent)) { + alert(output.message); + return false; + } + + // test for blob support + try { + gotBlob = !!new Blob(); + } catch (err) { + gotBlob = false; + } + + // Use HTML5 Blob if browser supports it + if ( gotBlob ) { + + window.URL = window.URL || window.webkitURL; + // prepend BOM for utf-8 encoding - see https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js#L140 + blob = new Blob( [ '\ufeff', data ], { type: wo.output_encoding } ); + + if (nav.msSaveBlob) { + // IE 10+ + nav.msSaveBlob(blob, wo.output_saveFileName); + } else { + // all other browsers + link.href = window.URL.createObjectURL(blob); + link.download = wo.output_saveFileName; + // Dispatching click event; using $(link).trigger() won't work + if (document.createEvent) { + e = document.createEvent('MouseEvents'); + // event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, + // ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget); + e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(e); + } + } + return false; + } + + // fallback to force file download (whether supported by server). + // not sure if this actually works in IE9 and older... + window.open( wo.output_encoding + encodeURIComponent(data) + '?download', '_self'); + return true; + + }, + + remove : function(c) { + c.$table.off(output.event); } - // fallback to force file download (whether supported by server). - // not sure if this actually works in IE9 and older... - window.open( wo.output_encoding + encodeURIComponent(data) + '?download' , '_self'); - return true; + }; - }, + ts.addWidget({ + id: 'output', + options: { + output_separator : ',', // set to 'json', 'array' or any separator + output_ignoreColumns : [], // columns to ignore [0, 1,... ] (zero-based index) + output_hiddenColumns : false, // include hidden columns in the output + output_includeFooter : false, // include footer rows in the output + output_dataAttrib : 'data-name', // header attrib containing modified header name + output_headerRows : false, // if true, include multiple header rows (JSON only) + output_delivery : 'popup', // popup, download + output_saveRows : 'filtered', // (a)ll, (v)isible, (f)iltered or jQuery filter selector + output_duplicateSpans: true, // duplicate output data in tbody colspan/rowspan + output_replaceQuote : '\u201c;', // left double quote + output_includeHTML : false, + output_trimSpaces : true, + output_wrapQuotes : false, + output_popupStyle : 'width=500,height=300', + output_saveFileName : 'mytable.csv', + // format $cell content callback + output_formatContent : null, // function(config, data){ return data.content; } + // callback executed when processing completes + // return true to continue download/output + // return false to stop delivery & do something else with the data + output_callback : function(config, data){ return true; }, + // JSON callback executed when a colspan is encountered in the header + output_callbackJSON : function($cell, txt, cellIndex) { return txt + '(' + (cellIndex) + ')'; }, + // the need to modify this for Excel no longer exists + output_encoding : 'data:application/octet-stream;charset=utf8,' + }, + init: function(table, thisWidget, c) { + output.init(c); + }, + remove: function(table, c){ + output.remove(c); + } - remove : function(c) { - c.$table.off(output.event); - } - -}; - -ts.addWidget({ - id: "output", - options: { - output_separator : ',', // set to "json", "array" or any separator - output_ignoreColumns : [], // columns to ignore [0, 1,... ] (zero-based index) - output_hiddenColumns : false, // include hidden columns in the output - output_includeFooter : false, // include footer rows in the output - output_dataAttrib : 'data-name', // header attrib containing modified header name - output_headerRows : false, // if true, include multiple header rows (JSON only) - output_delivery : 'popup', // popup, download - output_saveRows : 'filtered', // (a)ll, (v)isible, (f)iltered or jQuery filter selector - output_duplicateSpans: true, // duplicate output data in tbody colspan/rowspan - output_replaceQuote : '\u201c;', // left double quote - output_includeHTML : false, - output_trimSpaces : true, - output_wrapQuotes : false, - output_popupStyle : 'width=500,height=300', - output_saveFileName : 'mytable.csv', - // format $cell content callback - output_formatContent : null, // function(config, data){ return data.content; } - // callback executed when processing completes - // return true to continue download/output - // return false to stop delivery & do something else with the data - output_callback : function(config, data){ return true; }, - // JSON callback executed when a colspan is encountered in the header - output_callbackJSON : function($cell, txt, cellIndex) { return txt + '(' + (cellIndex) + ')'; }, - // the need to modify this for Excel no longer exists - output_encoding : 'data:application/octet-stream;charset=utf8,' - }, - init: function(table, thisWidget, c) { - output.init(c); - }, - remove: function(table, c){ - output.remove(c); - } - -}); + }); })(jQuery); diff --git a/js/widgets/widget-pager.js b/js/widgets/widget-pager.js index 3e755a18..26d62f30 100644 --- a/js/widgets/widget-pager.js +++ b/js/widgets/widget-pager.js @@ -4,1159 +4,1159 @@ */ /*jshint browser:true, jquery:true, unused:false */ ;(function($){ -"use strict"; -var tsp, + 'use strict'; + var tsp, ts = $.tablesorter; -ts.addWidget({ - id: "pager", - priority: 55, // load pager after filter widget - options : { - // output default: '{page}/{totalPages}' - // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows} - pager_output: '{startRow} to {endRow} of {totalRows} rows', // '{page}/{totalPages}' + ts.addWidget({ + id: 'pager', + priority: 55, // load pager after filter widget + options : { + // output default: '{page}/{totalPages}' + // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows} + pager_output: '{startRow} to {endRow} of {totalRows} rows', // '{page}/{totalPages}' - // apply disabled classname to the pager arrows when the rows at either extreme is visible - pager_updateArrows: true, + // apply disabled classname to the pager arrows when the rows at either extreme is visible + pager_updateArrows: true, - // starting page of the pager (zero based index) - pager_startPage: 0, + // starting page of the pager (zero based index) + pager_startPage: 0, - // reset pager after filtering; set to desired page # - // set to false to not change page at filter start - pager_pageReset: 0, + // reset pager after filtering; set to desired page # + // set to false to not change page at filter start + pager_pageReset: 0, - // Number of visible rows - pager_size: 10, + // Number of visible rows + pager_size: 10, - // Number of options to include in the pager number selector - pager_maxOptionSize: 20, + // Number of options to include in the pager number selector + pager_maxOptionSize: 20, - // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js) - pager_savePages: true, + // Save pager page & size if the storage script is loaded (requires $.tablesorter.storage in jquery.tablesorter.widgets.js) + pager_savePages: true, - //defines custom storage key - pager_storageKey: 'tablesorter-pager', + // defines custom storage key + pager_storageKey: 'tablesorter-pager', - // if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty - // table row set to a height to compensate; default is false - pager_fixedHeight: false, + // if true, the table will remain the same height no matter how many records are displayed. The space is made up by an empty + // table row set to a height to compensate; default is false + pager_fixedHeight: false, - // count child rows towards the set page size? (set true if it is a visible table row within the pager) - // if true, child row(s) may not appear to be attached to its parent row, may be split across pages or - // may distort the table if rowspan or cellspans are included. - pager_countChildRows: false, + // count child rows towards the set page size? (set true if it is a visible table row within the pager) + // if true, child row(s) may not appear to be attached to its parent row, may be split across pages or + // may distort the table if rowspan or cellspans are included. + pager_countChildRows: false, - // remove rows from the table to speed up the sort of large tables. - // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled. - pager_removeRows: false, // removing rows in larger tables speeds up the sort + // remove rows from the table to speed up the sort of large tables. + // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled. + pager_removeRows: false, // removing rows in larger tables speeds up the sort - // use this format: "http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}" - // where {page} is replaced by the page number, {size} is replaced by the number of records to show, - // {sortList:col} adds the sortList to the url into a "col" array, and {filterList:fcol} adds - // the filterList to the url into an "fcol" array. - // So a sortList = [[2,0],[3,0]] becomes "&col[2]=0&col[3]=0" in the url - // and a filterList = [[2,Blue],[3,13]] becomes "&fcol[2]=Blue&fcol[3]=13" in the url - pager_ajaxUrl: null, + // use this format: 'http://mydatabase.com?page={page}&size={size}&{sortList:col}&{filterList:fcol}' + // where {page} is replaced by the page number, {size} is replaced by the number of records to show, + // {sortList:col} adds the sortList to the url into a 'col' array, and {filterList:fcol} adds + // the filterList to the url into an 'fcol' array. + // So a sortList = [[2,0],[3,0]] becomes '&col[2]=0&col[3]=0' in the url + // and a filterList = [[2,Blue],[3,13]] becomes '&fcol[2]=Blue&fcol[3]=13' in the url + pager_ajaxUrl: null, - // modify the url after all processing has been applied - pager_customAjaxUrl: function(table, url) { return url; }, + // modify the url after all processing has been applied + pager_customAjaxUrl: function(table, url) { return url; }, - // modify the $.ajax object to allow complete control over your ajax requests - pager_ajaxObject: { - dataType: 'json' + // modify the $.ajax object to allow complete control over your ajax requests + pager_ajaxObject: { + dataType: 'json' + }, + + // set this to false if you want to block ajax loading on init + pager_processAjaxOnInit: true, + + // process ajax so that the following information is returned: + // [ total_rows (number), rows (array of arrays), headers (array; optional) ] + // example: + // [ + // 100, // total rows + // [ + // [ "row1cell1", "row1cell2", ... "row1cellN" ], + // [ "row2cell1", "row2cell2", ... "row2cellN" ], + // ... + // [ "rowNcell1", "rowNcell2", ... "rowNcellN" ] + // ], + // [ "header1", "header2", ... "headerN" ] // optional + // ] + pager_ajaxProcessing: function(ajax){ return [ 0, [], null ]; }, + + // css class names of pager arrows + pager_css: { + container : 'tablesorter-pager', + errorRow : 'tablesorter-errorRow', // error information row (don't include period at beginning) + disabled : 'disabled' // class added to arrows @ extremes (i.e. prev/first arrows 'disabled' on first page) + }, + + // jQuery selectors + pager_selectors: { + container : '.pager', // target the pager markup + first : '.first', // go to first page arrow + prev : '.prev', // previous page arrow + next : '.next', // next page arrow + last : '.last', // go to last page arrow + gotoPage : '.gotoPage', // go to page selector - select dropdown that sets the current page + pageDisplay : '.pagedisplay', // location of where the 'output' is displayed + pageSize : '.pagesize' // page size selector - select dropdown that sets the 'size' option + } }, - - // set this to false if you want to block ajax loading on init - pager_processAjaxOnInit: true, - - // process ajax so that the following information is returned: - // [ total_rows (number), rows (array of arrays), headers (array; optional) ] - // example: - // [ - // 100, // total rows - // [ - // [ "row1cell1", "row1cell2", ... "row1cellN" ], - // [ "row2cell1", "row2cell2", ... "row2cellN" ], - // ... - // [ "rowNcell1", "rowNcell2", ... "rowNcellN" ] - // ], - // [ "header1", "header2", ... "headerN" ] // optional - // ] - pager_ajaxProcessing: function(ajax){ return [ 0, [], null ]; }, - - // css class names of pager arrows - pager_css: { - container : 'tablesorter-pager', - errorRow : 'tablesorter-errorRow', // error information row (don't include period at beginning) - disabled : 'disabled' // class added to arrows @ extremes (i.e. prev/first arrows "disabled" on first page) + init: function(table){ + tsp.init(table); }, - - // jQuery selectors - pager_selectors: { - container : '.pager', // target the pager markup - first : '.first', // go to first page arrow - prev : '.prev', // previous page arrow - next : '.next', // next page arrow - last : '.last', // go to last page arrow - gotoPage : '.gotoPage', // go to page selector - select dropdown that sets the current page - pageDisplay : '.pagedisplay', // location of where the "output" is displayed - pageSize : '.pagesize' // page size selector - select dropdown that sets the "size" option - } - }, - init: function(table){ - tsp.init(table); - }, - // only update to complete sorter initialization - format: function(table, c){ - if (!(c.pager && c.pager.initialized)){ - return tsp.initComplete(table, c); - } - tsp.moveToPage(table, c.pager, false); - }, - remove: function(table, c, wo, refreshing){ - tsp.destroyPager(table, c, refreshing); - } -}); - -/* pager widget functions */ -tsp = ts.pager = { - - init: function(table) { - // check if tablesorter has initialized - if (table.hasInitialized && table.config.pager.initialized) { return; } - var t, - c = table.config, - wo = c.widgetOptions, - s = wo.pager_selectors, - - // save pager variables - p = c.pager = $.extend({ - totalPages: 0, - filteredRows: 0, - filteredPages: 0, - currentFilters: [], - page: wo.pager_startPage, - startRow: 0, - endRow: 0, - ajaxCounter: 0, - $size: null, - last: {}, - // save original pager size - setSize: wo.pager_size, - setPage: wo.pager_startPage, - events: 'filterInit filterStart filterEnd sortEnd disable enable destroy updateComplete ' + - 'pageSize pageSet pageAndSize pagerUpdate refreshComplete ' - }, c.pager); - - // pager initializes multiple times before table has completed initialization - if (p.isInitializing) { return; } - - p.isInitializing = true; - if (c.debug) { - ts.log('Pager: Initializing'); - } - - p.size = $.data(table, 'pagerLastSize') || wo.pager_size; - // added in case the pager is reinitialized after being destroyed. - p.$container = $(s.container).addClass(wo.pager_css.container).show(); - // goto selector - p.$goto = p.$container.find(s.gotoPage); // goto is a reserved word #657 - // page size selector - p.$size = p.$container.find(s.pageSize); - p.totalRows = c.$tbodies.eq(0).children('tr').not( wo.pager_countChildRows ? '' : '.' + c.cssChildRow ).length; - p.oldAjaxSuccess = p.oldAjaxSuccess || wo.pager_ajaxObject.success; - c.appender = tsp.appender; - p.initializing = true; - if (wo.pager_savePages && ts.storage) { - t = ts.storage(table, wo.pager_storageKey) || {}; // fixes #387 - p.page = ( isNaN(t.page) ? p.page : t.page ) || p.setPage || 0; - p.size = ( isNaN(t.size) ? p.size : t.size ) || p.setSize || 10; - $.data(table, 'pagerLastSize', p.size); - } - - // skipped rows - p.regexRows = new RegExp('(' + (wo.filter_filteredRow || 'filtered') + '|' + c.selectorRemove.slice(1) + '|' + c.cssChildRow + ')'); - - // clear initialized flag - p.initialized = false; - // before initialization event - c.$table.trigger('pagerBeforeInitialized', c); - - tsp.enablePager(table, c, false); - - // p must have ajaxObject - p.ajaxObject = wo.pager_ajaxObject; // $.extend({}, wo.pager_ajaxObject ); - p.ajaxObject.url = wo.pager_ajaxUrl; - - if ( typeof(wo.pager_ajaxUrl) === 'string' ) { - // ajax pager; interact with database - p.ajax = true; - // When filtering with ajax, allow only custom filtering function, disable default filtering since it will be done server side. - wo.filter_serversideFiltering = true; - c.serverSideSorting = true; - tsp.moveToPage(table, p); - } else { - p.ajax = false; - // Regular pager; all rows stored in memory - c.$table.trigger('appendCache', [{}, true]); - } - - }, - - initComplete: function(table, c){ - var p = c.pager; - tsp.bindEvents(table, c); - tsp.setPageSize(table, 0, c); // page size 0 is ignored - - if (!p.ajax) { - tsp.hideRowsSetup(table, c); - } - - // pager initialized - p.initialized = true; - p.initializing = false; - p.isInitializing = false; - if (c.debug) { - ts.log('Pager: Triggering pagerInitialized'); - } - c.$table.trigger('pagerInitialized', c); - // filter widget not initialized; it will update the output display & fire off the pagerComplete event - if ( !( c.widgetOptions.filter_initialized && ts.hasWidget(table, 'filter') ) ) { - // if ajax, then don't fire off pagerComplete - tsp.updatePageDisplay(table, c, !p.ajax); - } - }, - - bindEvents: function(table, c){ - var ctrls, fxn, - p = c.pager, - wo = c.widgetOptions, - namespace = c.namespace + 'pager', - s = wo.pager_selectors; - - c.$table - .off( $.trim(p.events.split(' ').join(namespace + ' ')) ) - .on('filterInit filterStart '.split(' ').join(namespace + ' '), function(e, filters) { - p.currentFilters = $.isArray(filters) ? filters : c.$table.data('lastSearch'); - // don't change page if filters are the same (pager updating, etc) - if (e.type === 'filterStart' && wo.pager_pageReset !== false && (c.lastCombinedFilter || '') !== (p.currentFilters || []).join('')) { - p.page = wo.pager_pageReset; // fixes #456 & #565 - } - }) - // update pager after filter widget completes - .on('filterEnd sortEnd '.split(' ').join(namespace + ' '), function() { - p.currentFilters = c.$table.data('lastSearch'); - if (p.initialized || p.initializing) { - if (c.delayInit && c.rowsCopy && c.rowsCopy.length === 0) { - // make sure we have a copy of all table rows once the cache has been built - tsp.updateCache(table); - } - tsp.updatePageDisplay(table, c, false); - // tsp.moveToPage(table, p, false); <-- called when applyWidgets is triggered - c.$table.trigger('applyWidgets'); - } - }) - .on('disable' + namespace, function(e){ - e.stopPropagation(); - tsp.showAllRows(table, c); - }) - .on('enable' + namespace, function(e){ - e.stopPropagation(); - tsp.enablePager(table, c, true); - }) - .on('destroy' + namespace, function(e, refreshing){ - e.stopPropagation(); - tsp.destroyPager(table, c, refreshing); - }) - .on('updateComplete' + namespace, function(e, table, triggered){ - e.stopPropagation(); - // table can be unintentionally undefined in tablesorter v2.17.7 and earlier - // don't recalculate total rows/pages if using ajax - if (!table || triggered || p.ajax) { return; } - var $rows = c.$tbodies.eq(0).children('tr').not(c.selectorRemove); - p.totalRows = $rows.length - ( wo.pager_countChildRows ? 0 : $rows.filter('.' + c.cssChildRow).length ); - p.totalPages = Math.ceil( p.totalRows / p.size ); - if ($rows.length && c.rowsCopy && c.rowsCopy.length === 0) { - // make a copy of all table rows once the cache has been built - tsp.updateCache(table); - } - if ( p.page >= p.totalPages ) { - tsp.moveToLastPage(table, p); - } - tsp.hideRows(table, c); - tsp.changeHeight(table, c); - // update without triggering pagerComplete - tsp.updatePageDisplay(table, c, false); - // make sure widgets are applied - fixes #450 - c.$table.trigger('applyWidgets'); - tsp.updatePageDisplay(table, c); - }) - .on('pageSize refreshComplete '.split(' ').join(namespace + ' '), function(e,v){ - e.stopPropagation(); - tsp.setPageSize(table, parseInt(v, 10) || p.setSize || 10, c); - tsp.hideRows(table, c); - tsp.updatePageDisplay(table, c, false); - }) - .on('pageSet pagerUpdate '.split(' ').join(namespace + ' '), function(e,v){ - e.stopPropagation(); - // force pager refresh - if (e.type === 'pagerUpdate') { - v = typeof v === 'undefined' ? p.page + 1 : v; - p.last.page = true; - } - p.page = (parseInt(v, 10) || 1) - 1; - tsp.moveToPage(table, p, true); - tsp.updatePageDisplay(table, c, false); - }) - .on('pageAndSize' + namespace, function(e, page, size){ - e.stopPropagation(); - p.page = (parseInt(page, 10) || 1) - 1; - tsp.setPageSize(table, parseInt(size, 10) || p.setSize || 10, c); - tsp.moveToPage(table, p, true); - tsp.hideRows(table, c); - tsp.updatePageDisplay(table, c, false); - }); - - // clicked controls - ctrls = [ s.first, s.prev, s.next, s.last ]; - fxn = [ 'moveToFirstPage', 'moveToPrevPage', 'moveToNextPage', 'moveToLastPage' ]; - if (c.debug && !p.$container.length) { - ts.log('Pager: >> Container not found'); - } - p.$container.find(ctrls.join(',')) - .attr("tabindex", 0) - .off('click' + namespace) - .on('click' + namespace, function(e){ - e.stopPropagation(); - var i, - $c = $(this), - l = ctrls.length; - if ( !$c.hasClass(wo.pager_css.disabled) ) { - for (i = 0; i < l; i++) { - if ($c.is(ctrls[i])) { - tsp[fxn[i]](table, p); - break; - } - } - } - }); - - if ( p.$goto.length ) { - p.$goto - .off('change' + namespace) - .on('change' + namespace, function(){ - p.page = $(this).val() - 1; - tsp.moveToPage(table, p, true); - tsp.updatePageDisplay(table, c, false); - }); - } else if (c.debug) { - ts.log('Pager: >> Goto selector not found'); - } - - if ( p.$size.length ) { - // setting an option as selected appears to cause issues with initial page size - p.$size.find('option').removeAttr('selected'); - p.$size - .off('change' + namespace) - .on('change' + namespace, function() { - p.$size.val( $(this).val() ); // in case there are more than one pagers - if ( !$(this).hasClass(wo.pager_css.disabled) ) { - tsp.setPageSize(table, parseInt( $(this).val(), 10 ), c); - tsp.changeHeight(table, c); - } - return false; - }); - } else if (c.debug) { - ts.log('Pager: >> Size selector not found'); - } - - }, - - // hide arrows at extremes - pagerArrows: function(c, disable) { - var p = c.pager, - dis = !!disable, - first = dis || p.page === 0, - tp = Math.min( p.totalPages, p.filteredPages ), - last = dis || p.page === tp - 1 || tp === 0, - wo = c.widgetOptions, - s = wo.pager_selectors; - if ( wo.pager_updateArrows ) { - p.$container.find(s.first + ',' + s.prev).toggleClass(wo.pager_css.disabled, first).attr('aria-disabled', first); - p.$container.find(s.next + ',' + s.last).toggleClass(wo.pager_css.disabled, last).attr('aria-disabled', last); - } - }, - - calcFilters: function(table, c) { - var normalized, indx, len, - wo = c.widgetOptions, - p = c.pager, - hasFilters = c.$table.hasClass('hasFilters'); - if (hasFilters && !wo.pager_ajaxUrl) { - if ($.isEmptyObject(c.cache)) { - // delayInit: true so nothing is in the cache - p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not( wo.pager_countChildRows ? '' : '.' + c.cssChildRow ).length; - } else { - p.filteredRows = 0; - normalized = c.cache[0].normalized; - len = normalized.length; - for (indx = 0; indx < len; indx++) { - p.filteredRows += p.regexRows.test(normalized[indx][c.columns].$row[0].className) ? 0 : 1; - } + // only update to complete sorter initialization + format: function(table, c){ + if (!(c.pager && c.pager.initialized)){ + return tsp.initComplete(table, c); } - } else if (!hasFilters) { - p.filteredRows = p.totalRows; + tsp.moveToPage(table, c.pager, false); + }, + remove: function(table, c, wo, refreshing){ + tsp.destroyPager(table, c, refreshing); } - }, + }); - updatePageDisplay: function(table, c, completed) { - if ( c.pager.initializing ) { return; } - var s, t, $out, options, indx, len, - wo = c.widgetOptions, - p = c.pager, - namespace = c.namespace + 'pager', - sz = p.size || p.setSize || 10; // don't allow dividing by zero - if (wo.pager_countChildRows) { t.push(c.cssChildRow); } - p.$size.add(p.$goto).removeClass(wo.pager_css.disabled).removeAttr('disabled').attr('aria-disabled', 'false'); - p.totalPages = Math.ceil( p.totalRows / sz ); // needed for "pageSize" method - c.totalRows = p.totalRows; - tsp.calcFilters(table, c); - c.filteredRows = p.filteredRows; - p.filteredPages = Math.ceil( p.filteredRows / sz ) || 0; - if ( Math.min( p.totalPages, p.filteredPages ) >= 0 ) { - t = (p.size * p.page > p.filteredRows) && completed; - p.page = (t) ? wo.pager_pageReset || 0 : p.page; - p.startRow = (t) ? p.size * p.page + 1 : (p.filteredRows === 0 ? 0 : p.size * p.page + 1); - p.endRow = Math.min( p.filteredRows, p.totalRows, p.size * ( p.page + 1 ) ); - $out = p.$container.find(wo.pager_selectors.pageDisplay); - // form the output string (can now get a new output string from the server) - s = ( p.ajaxData && p.ajaxData.output ? p.ajaxData.output || wo.pager_output : wo.pager_output ) - // {page} = one-based index; {page+#} = zero based index +/- value - .replace(/\{page([\-+]\d+)?\}/gi, function(m,n){ - return p.totalPages ? p.page + (n ? parseInt(n, 10) : 1) : 0; - }) - // {totalPages}, {extra}, {extra:0} (array) or {extra : key} (object) - .replace(/\{\w+(\s*:\s*\w+)?\}/gi, function(m){ - var len, indx, - str = m.replace(/[{}\s]/g,''), - extra = str.split(':'), - data = p.ajaxData, - // return zero for default page/row numbers - deflt = /(rows?|pages?)$/i.test(str) ? 0 : ''; - if (/(startRow|page)/.test(extra[0]) && extra[1] === 'input') { - len = ('' + (extra[0] === 'page' ? p.totalPages : p.totalRows)).length; - indx = extra[0] === 'page' ? p.page + 1 : p.startRow; - return ''; - } - return extra.length > 1 && data && data[extra[0]] ? data[extra[0]][extra[1]] : p[str] || (data ? data[str] : deflt) || deflt; - }); - if ( p.$goto.length ) { - t = ''; - options = tsp.buildPageSelect(p, c); - len = options.length; - for (indx = 0; indx < len; indx++) { - t += ''; - } - // innerHTML doesn't work in IE9 - http://support2.microsoft.com/kb/276228 - p.$goto.html(t).val( p.page + 1 ); - } - if ($out.length) { - $out[ ($out[0].nodeName === 'INPUT') ? 'val' : 'html' ](s); - // rebind startRow/page inputs - $out.find('.ts-startRow, .ts-page').off('change' + namespace).on('change' + namespace, function(){ - var v = $(this).val(), - pg = $(this).hasClass('ts-startRow') ? Math.floor( v/p.size ) + 1 : v; - c.$table.trigger('pageSet' + namespace, [ pg ]); - }); - } - } - tsp.pagerArrows(c); - tsp.fixHeight(table, c); - if (p.initialized && completed !== false) { + /* pager widget functions */ + tsp = ts.pager = { + + init: function(table) { + // check if tablesorter has initialized + if (table.hasInitialized && table.config.pager.initialized) { return; } + var t, + c = table.config, + wo = c.widgetOptions, + s = wo.pager_selectors, + + // save pager variables + p = c.pager = $.extend({ + totalPages: 0, + filteredRows: 0, + filteredPages: 0, + currentFilters: [], + page: wo.pager_startPage, + startRow: 0, + endRow: 0, + ajaxCounter: 0, + $size: null, + last: {}, + // save original pager size + setSize: wo.pager_size, + setPage: wo.pager_startPage, + events: 'filterInit filterStart filterEnd sortEnd disable enable destroy updateComplete ' + + 'pageSize pageSet pageAndSize pagerUpdate refreshComplete ' + }, c.pager); + + // pager initializes multiple times before table has completed initialization + if (p.isInitializing) { return; } + + p.isInitializing = true; if (c.debug) { - ts.log('Pager: Triggering pagerComplete'); + ts.log('Pager: Initializing'); } - c.$table.trigger('pagerComplete', c); - // save pager info to storage + + p.size = $.data(table, 'pagerLastSize') || wo.pager_size; + // added in case the pager is reinitialized after being destroyed. + p.$container = $(s.container).addClass(wo.pager_css.container).show(); + // goto selector + p.$goto = p.$container.find(s.gotoPage); // goto is a reserved word #657 + // page size selector + p.$size = p.$container.find(s.pageSize); + p.totalRows = c.$tbodies.eq(0).children('tr').not( wo.pager_countChildRows ? '' : '.' + c.cssChildRow ).length; + p.oldAjaxSuccess = p.oldAjaxSuccess || wo.pager_ajaxObject.success; + c.appender = tsp.appender; + p.initializing = true; if (wo.pager_savePages && ts.storage) { - ts.storage(table, wo.pager_storageKey, { - page : p.page, - size : p.size - }); - } - } - }, - - buildPageSelect: function(p, c) { - // Filter the options page number link array if it's larger than 'pager_maxOptionSize' - // as large page set links will slow the browser on large dom inserts - var i, central_focus_size, focus_option_pages, insert_index, option_length, focus_length, - wo = c.widgetOptions, - pg = Math.min( p.totalPages, p.filteredPages ) || 1, - // make skip set size multiples of 5 - skip_set_size = Math.ceil( ( pg / wo.pager_maxOptionSize ) / 5 ) * 5, - large_collection = pg > wo.pager_maxOptionSize, - current_page = p.page + 1, - start_page = skip_set_size, - end_page = pg - skip_set_size, - option_pages = [1], - // construct default options pages array - option_pages_start_page = (large_collection) ? skip_set_size : 1; - - for ( i = option_pages_start_page; i <= pg; ) { - option_pages.push(i); - i = i + ( large_collection ? skip_set_size : 1 ); - } - option_pages.push(pg); - - if (large_collection) { - focus_option_pages = []; - // don't allow central focus size to be > 5 on either side of current page - central_focus_size = Math.max( Math.floor( wo.pager_maxOptionSize / skip_set_size ) - 1, 5 ); - - start_page = current_page - central_focus_size; - if (start_page < 1) { start_page = 1; } - end_page = current_page + central_focus_size; - if (end_page > pg) { end_page = pg; } - // construct an array to get a focus set around the current page - for (i = start_page; i <= end_page ; i++) { - focus_option_pages.push(i); + t = ts.storage(table, wo.pager_storageKey) || {}; // fixes #387 + p.page = ( isNaN(t.page) ? p.page : t.page ) || p.setPage || 0; + p.size = ( isNaN(t.size) ? p.size : t.size ) || p.setSize || 10; + $.data(table, 'pagerLastSize', p.size); } - // keep unique values - option_pages = $.grep(option_pages, function(value, indx) { - return $.inArray(value, option_pages) === indx; - }); + // skipped rows + p.regexRows = new RegExp('(' + (wo.filter_filteredRow || 'filtered') + '|' + c.selectorRemove.slice(1) + '|' + c.cssChildRow + ')'); - option_length = option_pages.length; - focus_length = focus_option_pages.length; + // clear initialized flag + p.initialized = false; + // before initialization event + c.$table.trigger('pagerBeforeInitialized', c); - // make sure at all option_pages aren't replaced - if (option_length - focus_length > skip_set_size / 2 && option_length + focus_length > wo.pager_maxOptionSize ) { - insert_index = Math.floor(option_length / 2) - Math.floor(focus_length / 2); - Array.prototype.splice.apply(option_pages, [ insert_index, focus_length ]); + tsp.enablePager(table, c, false); + + // p must have ajaxObject + p.ajaxObject = wo.pager_ajaxObject; // $.extend({}, wo.pager_ajaxObject ); + p.ajaxObject.url = wo.pager_ajaxUrl; + + if ( typeof wo.pager_ajaxUrl === 'string' ) { + // ajax pager; interact with database + p.ajax = true; + // When filtering with ajax, allow only custom filtering function, disable default filtering since it will be done server side. + wo.filter_serversideFiltering = true; + c.serverSideSorting = true; + tsp.moveToPage(table, p); + } else { + p.ajax = false; + // Regular pager; all rows stored in memory + c.$table.trigger('appendCache', [ {}, true ]); } - option_pages = option_pages.concat(focus_option_pages); - } + }, - // keep unique values again - option_pages = $.grep(option_pages, function(value, indx) { - return $.inArray(value, option_pages) === indx; - }) - .sort(function(a,b) { return a - b; }); + initComplete: function(table, c){ + var p = c.pager; + tsp.bindEvents(table, c); + tsp.setPageSize(table, 0, c); // page size 0 is ignored - return option_pages; - }, - - fixHeight: function(table, c) { - var d, h, - p = c.pager, - wo = c.widgetOptions, - $b = c.$tbodies.eq(0); - $b.find('tr.pagerSavedHeightSpacer').remove(); - if (wo.pager_fixedHeight && !p.isDisabled) { - h = $.data(table, 'pagerSavedHeight'); - if (h) { - d = h - $b.height(); - if ( d > 5 && $.data(table, 'pagerLastSize') === p.size && $b.children('tr:visible').length < p.size ) { - $b.append(''); - } + if (!p.ajax) { + tsp.hideRowsSetup(table, c); } - } - }, - changeHeight: function(table, c) { - var h, $b = c.$tbodies.eq(0); - $b.find('tr.pagerSavedHeightSpacer').remove(); - if (!$b.children('tr:visible').length) { - $b.append(''); - } - h = $b.children('tr').eq(0).height() * c.pager.size; - $.data(table, 'pagerSavedHeight', h); - tsp.fixHeight(table, c); - $.data(table, 'pagerLastSize', c.pager.size); - }, + // pager initialized + p.initialized = true; + p.initializing = false; + p.isInitializing = false; + if (c.debug) { + ts.log('Pager: Triggering pagerInitialized'); + } + c.$table.trigger('pagerInitialized', c); + // filter widget not initialized; it will update the output display & fire off the pagerComplete event + if ( !( c.widgetOptions.filter_initialized && ts.hasWidget(table, 'filter') ) ) { + // if ajax, then don't fire off pagerComplete + tsp.updatePageDisplay(table, c, !p.ajax); + } + }, - hideRows: function(table, c){ - if ( !c.widgetOptions.pager_ajaxUrl ) { - var tbodyIndex, rowIndex, $rows, len, lastIndex, + bindEvents: function(table, c){ + var ctrls, fxn, p = c.pager, wo = c.widgetOptions, - tbodyLen = c.$tbodies.length, - start = ( p.page * p.size ), - end = start + p.size, - filtr = wo && wo.filter_filteredRow || 'filtered', - last = 0, // for cache indexing - size = 0; // size counter - p.cacheIndex = []; - for ( tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++ ) { - $rows = c.$tbodies.eq( tbodyIndex ).children( 'tr' ); - len = $rows.length; - lastIndex = 0; - last = 0; // for cache indexing - size = 0; // size counter - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - if ( !$rows[ rowIndex ].className.match( filtr ) ) { - if ( size === start && $rows[ rowIndex ].className.match( c.cssChildRow ) ) { - // hide child rows @ start of pager (if already visible) - $rows[ rowIndex ].style.display = 'none'; - } else { - $rows[ rowIndex ].style.display = ( size >= start && size < end ) ? '' : 'none'; - if ( last !== size && size >= start && size < end ) { - p.cacheIndex.push( rowIndex ); - last = size; - } - // don't count child rows - size += $rows[ rowIndex ].className - .match( c.cssChildRow + '|' + c.selectorRemove.slice( 1 ) ) && !wo.pager_countChildRows ? 0 : 1; - if ( size === end && $rows[ rowIndex ].style.display !== 'none' && - $rows[ rowIndex ].className.match( ts.css.cssHasChild ) ) { - lastIndex = rowIndex; + namespace = c.namespace + 'pager', + s = wo.pager_selectors; + + c.$table + .off( $.trim(p.events.split(' ').join(namespace + ' ')) ) + .on('filterInit filterStart '.split(' ').join(namespace + ' '), function(e, filters) { + p.currentFilters = $.isArray(filters) ? filters : c.$table.data('lastSearch'); + // don't change page if filters are the same (pager updating, etc) + if (e.type === 'filterStart' && wo.pager_pageReset !== false && (c.lastCombinedFilter || '') !== (p.currentFilters || []).join('')) { + p.page = wo.pager_pageReset; // fixes #456 & #565 + } + }) + // update pager after filter widget completes + .on('filterEnd sortEnd '.split(' ').join(namespace + ' '), function() { + p.currentFilters = c.$table.data('lastSearch'); + if (p.initialized || p.initializing) { + if (c.delayInit && c.rowsCopy && c.rowsCopy.length === 0) { + // make sure we have a copy of all table rows once the cache has been built + tsp.updateCache(table); + } + tsp.updatePageDisplay(table, c, false); + // tsp.moveToPage(table, p, false); <-- called when applyWidgets is triggered + c.$table.trigger('applyWidgets'); + } + }) + .on('disable' + namespace, function(e){ + e.stopPropagation(); + tsp.showAllRows(table, c); + }) + .on('enable' + namespace, function(e){ + e.stopPropagation(); + tsp.enablePager(table, c, true); + }) + .on('destroy' + namespace, function(e, refreshing){ + e.stopPropagation(); + tsp.destroyPager(table, c, refreshing); + }) + .on('updateComplete' + namespace, function(e, table, triggered){ + e.stopPropagation(); + // table can be unintentionally undefined in tablesorter v2.17.7 and earlier + // don't recalculate total rows/pages if using ajax + if (!table || triggered || p.ajax) { return; } + var $rows = c.$tbodies.eq(0).children('tr').not(c.selectorRemove); + p.totalRows = $rows.length - ( wo.pager_countChildRows ? 0 : $rows.filter('.' + c.cssChildRow).length ); + p.totalPages = Math.ceil( p.totalRows / p.size ); + if ($rows.length && c.rowsCopy && c.rowsCopy.length === 0) { + // make a copy of all table rows once the cache has been built + tsp.updateCache(table); + } + if ( p.page >= p.totalPages ) { + tsp.moveToLastPage(table, p); + } + tsp.hideRows(table, c); + tsp.changeHeight(table, c); + // update without triggering pagerComplete + tsp.updatePageDisplay(table, c, false); + // make sure widgets are applied - fixes #450 + c.$table.trigger('applyWidgets'); + tsp.updatePageDisplay(table, c); + }) + .on('pageSize refreshComplete '.split(' ').join(namespace + ' '), function(e, v){ + e.stopPropagation(); + tsp.setPageSize(table, parseInt(v, 10) || p.setSize || 10, c); + tsp.hideRows(table, c); + tsp.updatePageDisplay(table, c, false); + }) + .on('pageSet pagerUpdate '.split(' ').join(namespace + ' '), function(e, v){ + e.stopPropagation(); + // force pager refresh + if (e.type === 'pagerUpdate') { + v = typeof v === 'undefined' ? p.page + 1 : v; + p.last.page = true; + } + p.page = (parseInt(v, 10) || 1) - 1; + tsp.moveToPage(table, p, true); + tsp.updatePageDisplay(table, c, false); + }) + .on('pageAndSize' + namespace, function(e, page, size){ + e.stopPropagation(); + p.page = (parseInt(page, 10) || 1) - 1; + tsp.setPageSize(table, parseInt(size, 10) || p.setSize || 10, c); + tsp.moveToPage(table, p, true); + tsp.hideRows(table, c); + tsp.updatePageDisplay(table, c, false); + }); + + // clicked controls + ctrls = [ s.first, s.prev, s.next, s.last ]; + fxn = [ 'moveToFirstPage', 'moveToPrevPage', 'moveToNextPage', 'moveToLastPage' ]; + if (c.debug && !p.$container.length) { + ts.log('Pager: >> Container not found'); + } + p.$container.find(ctrls.join(',')) + .attr('tabindex', 0) + .off('click' + namespace) + .on('click' + namespace, function(e){ + e.stopPropagation(); + var i, + $c = $(this), + l = ctrls.length; + if ( !$c.hasClass(wo.pager_css.disabled) ) { + for (i = 0; i < l; i++) { + if ($c.is(ctrls[i])) { + tsp[fxn[i]](table, p); + break; } } } - } - // add any attached child rows to last row of pager. Fixes part of issue #396 - if ( lastIndex > 0 && $rows[ lastIndex ].className.match( ts.css.cssHasChild ) ) { - while ( ++lastIndex < len && $rows[ lastIndex ].className.match( c.cssChildRow ) ) { - $rows[ lastIndex ].style.display = ''; - } - } + }); + + if ( p.$goto.length ) { + p.$goto + .off('change' + namespace) + .on('change' + namespace, function(){ + p.page = $(this).val() - 1; + tsp.moveToPage(table, p, true); + tsp.updatePageDisplay(table, c, false); + }); + } else if (c.debug) { + ts.log('Pager: >> Goto selector not found'); } - } - }, - hideRowsSetup: function(table, c){ - var p = c.pager, - namespace = c.namespace + 'pager'; - p.size = parseInt( p.$size.val(), 10 ) || p.size || p.setSize || 10; - $.data(table, 'pagerLastSize', p.size); - tsp.pagerArrows(c); - if ( !c.widgetOptions.pager_removeRows ) { - tsp.hideRows(table, c); - c.$table.on('sortEnd filterEnd '.split(' ').join(namespace + ' '), function(){ - tsp.hideRows(table, c); - }); - } - }, + if ( p.$size.length ) { + // setting an option as selected appears to cause issues with initial page size + p.$size.find('option').removeAttr('selected'); + p.$size + .off('change' + namespace) + .on('change' + namespace, function() { + p.$size.val( $(this).val() ); // in case there are more than one pagers + if ( !$(this).hasClass(wo.pager_css.disabled) ) { + tsp.setPageSize(table, parseInt( $(this).val(), 10 ), c); + tsp.changeHeight(table, c); + } + return false; + }); + } else if (c.debug) { + ts.log('Pager: >> Size selector not found'); + } - renderAjax: function(data, table, c, xhr, exception){ - var p = c.pager, - wo = c.widgetOptions; - // process data - if ( $.isFunction(wo.pager_ajaxProcessing) ) { - // ajaxProcessing result: [ total, rows, headers ] - var i, j, t, hsh, $f, $sh, $headers, $h, icon, th, d, l, rr_count, len, - $table = c.$table, - tds = '', - result = wo.pager_ajaxProcessing(data, table, xhr) || [ 0, [] ], - hl = $table.find('thead th').length; + }, - // Clean up any previous error. - ts.showError(table); + // hide arrows at extremes + pagerArrows: function(c, disable) { + var p = c.pager, + dis = !!disable, + first = dis || p.page === 0, + tp = Math.min( p.totalPages, p.filteredPages ), + last = dis || p.page === tp - 1 || tp === 0, + wo = c.widgetOptions, + s = wo.pager_selectors; + if ( wo.pager_updateArrows ) { + p.$container.find(s.first + ',' + s.prev).toggleClass(wo.pager_css.disabled, first).attr('aria-disabled', first); + p.$container.find(s.next + ',' + s.last).toggleClass(wo.pager_css.disabled, last).attr('aria-disabled', last); + } + }, - if ( exception ) { - if (c.debug) { - ts.log('Pager: >> Ajax Error', xhr, exception); - } - ts.showError(table, exception.message + ' (' + xhr.status + ')'); - c.$tbodies.eq(0).children('tr').detach(); - p.totalRows = 0; - } else { - // process ajax object - if (!$.isArray(result)) { - p.ajaxData = result; - c.totalRows = p.totalRows = result.total; - c.filteredRows = p.filteredRows = typeof result.filteredRows !== 'undefined' ? result.filteredRows : result.total; - th = result.headers; - d = result.rows || []; + calcFilters: function(table, c) { + var normalized, indx, len, + wo = c.widgetOptions, + p = c.pager, + hasFilters = c.$table.hasClass('hasFilters'); + if (hasFilters && !wo.pager_ajaxUrl) { + if ($.isEmptyObject(c.cache)) { + // delayInit: true so nothing is in the cache + p.filteredRows = p.totalRows = c.$tbodies.eq(0).children('tr').not( wo.pager_countChildRows ? '' : '.' + c.cssChildRow ).length; } else { - // allow [ total, rows, headers ] or [ rows, total, headers ] - t = isNaN(result[0]) && !isNaN(result[1]); - // ensure a zero returned row count doesn't fail the logical || - rr_count = result[t ? 1 : 0]; - p.totalRows = isNaN(rr_count) ? p.totalRows || 0 : rr_count; - // can't set filtered rows when returning an array - c.totalRows = c.filteredRows = p.filteredRows = p.totalRows; - // set row data to empty array if nothing found - see http://stackoverflow.com/q/30875583/145346 - d = p.totalRows === 0 ? [] : result[t ? 0 : 1] || []; // row data - th = result[2]; // headers - } - l = d && d.length; - if (d instanceof jQuery) { - if (wo.pager_processAjaxOnInit) { - // append jQuery object - c.$tbodies.eq(0).children('tr').detach(); - c.$tbodies.eq(0).append(d); - } - } else if (l) { - // build table from array - for ( i = 0; i < l; i++ ) { - tds += ''; - for ( j = 0; j < d[i].length; j++ ) { - // build tbody cells; watch for data containing HTML markup - see #434 - tds += /^\s*'; - } - tds += ''; - } - // add rows to first tbody - if (wo.pager_processAjaxOnInit) { - c.$tbodies.eq(0).html( tds ); - } - } - wo.pager_processAjaxOnInit = true; - // only add new header text if the length matches - if ( th && th.length === hl ) { - hsh = $table.hasClass('hasStickyHeaders'); - $sh = hsh ? wo.$sticky.children('thead:first').children('tr').children() : ''; - $f = $table.find('tfoot tr:first').children(); - // don't change td headers (may contain pager) - $headers = c.$headers.filter( 'th '); - len = $headers.length; - for ( j = 0; j < len; j++ ) { - $h = $headers.eq( j ); - // add new test within the first span it finds, or just in the header - if ( $h.find('.' + ts.css.icon).length ) { - icon = $h.find('.' + ts.css.icon).clone(true); - $h.find('.tablesorter-header-inner').html( th[j] ).append(icon); - if ( hsh && $sh.length ) { - icon = $sh.eq(j).find('.' + ts.css.icon).clone(true); - $sh.eq(j).find('.tablesorter-header-inner').html( th[j] ).append(icon); - } - } else { - $h.find('.tablesorter-header-inner').html( th[j] ); - if (hsh && $sh.length) { - $sh.eq(j).find('.tablesorter-header-inner').html( th[j] ); - } - } - $f.eq(j).html( th[j] ); + p.filteredRows = 0; + normalized = c.cache[0].normalized; + len = normalized.length; + for (indx = 0; indx < len; indx++) { + p.filteredRows += p.regexRows.test(normalized[indx][c.columns].$row[0].className) ? 0 : 1; } } + } else if (!hasFilters) { + p.filteredRows = p.totalRows; } - if (c.showProcessing) { - ts.isProcessing(table); // remove loading icon - } - // make sure last pager settings are saved, prevents multiple server side calls with - // the same parameters - p.totalPages = Math.ceil( p.totalRows / ( p.size || p.setSize || 10 ) ); - p.last.totalRows = p.totalRows; - p.last.currentFilters = p.currentFilters; - p.last.sortList = (c.sortList || []).join(','); - p.initializing = false; - // update display without triggering pager complete... before updating cache - tsp.updatePageDisplay(table, c, false); - $table.trigger('updateCache', [function(){ - if (p.initialized) { - // apply widgets after table has rendered & after a delay to prevent - // multiple applyWidget blocking code from blocking this trigger - setTimeout(function(){ - if (c.debug) { - ts.log('Pager: Triggering pagerChange'); - } - $table - .trigger('applyWidgets') - .trigger('pagerChange', p); - tsp.updatePageDisplay(table, c); - }, 0); - } - }]); - } - if (!p.initialized) { - c.$table.trigger('applyWidgets'); - } - }, + }, - getAjax: function(table, c){ - var counter, - url = tsp.getAjaxUrl(table, c), - $doc = $(document), - namespace = c.namespace + 'pager', - p = c.pager; - if ( url !== '' ) { - if (c.showProcessing) { - ts.isProcessing(table, true); // show loading icon - } - $doc.on('ajaxError' + namespace, function(e, xhr, settings, exception) { - tsp.renderAjax(null, table, c, xhr, exception); - $doc.off('ajaxError' + namespace); - }); - counter = ++p.ajaxCounter; - p.last.ajaxUrl = url; // remember processed url - p.ajaxObject.url = url; // from the ajaxUrl option and modified by customAjaxUrl - p.ajaxObject.success = function(data, status, jqxhr) { - // Refuse to process old ajax commands that were overwritten by new ones - see #443 - if (counter < p.ajaxCounter){ - return; + updatePageDisplay: function(table, c, completed) { + if ( c.pager.initializing ) { return; } + var s, t, $out, options, indx, len, + wo = c.widgetOptions, + p = c.pager, + namespace = c.namespace + 'pager', + sz = p.size || p.setSize || 10; // don't allow dividing by zero + if (wo.pager_countChildRows) { t.push(c.cssChildRow); } + p.$size.add(p.$goto).removeClass(wo.pager_css.disabled).removeAttr('disabled').attr('aria-disabled', 'false'); + p.totalPages = Math.ceil( p.totalRows / sz ); // needed for 'pageSize' method + c.totalRows = p.totalRows; + tsp.calcFilters(table, c); + c.filteredRows = p.filteredRows; + p.filteredPages = Math.ceil( p.filteredRows / sz ) || 0; + if ( Math.min( p.totalPages, p.filteredPages ) >= 0 ) { + t = (p.size * p.page > p.filteredRows) && completed; + p.page = (t) ? wo.pager_pageReset || 0 : p.page; + p.startRow = (t) ? p.size * p.page + 1 : (p.filteredRows === 0 ? 0 : p.size * p.page + 1); + p.endRow = Math.min( p.filteredRows, p.totalRows, p.size * ( p.page + 1 ) ); + $out = p.$container.find(wo.pager_selectors.pageDisplay); + // form the output string (can now get a new output string from the server) + s = ( p.ajaxData && p.ajaxData.output ? p.ajaxData.output || wo.pager_output : wo.pager_output ) + // {page} = one-based index; {page+#} = zero based index +/- value + .replace(/\{page([\-+]\d+)?\}/gi, function(m, n){ + return p.totalPages ? p.page + (n ? parseInt(n, 10) : 1) : 0; + }) + // {totalPages}, {extra}, {extra:0} (array) or {extra : key} (object) + .replace(/\{\w+(\s*:\s*\w+)?\}/gi, function(m){ + var len, indx, + str = m.replace(/[{}\s]/g, ''), + extra = str.split(':'), + data = p.ajaxData, + // return zero for default page/row numbers + deflt = /(rows?|pages?)$/i.test(str) ? 0 : ''; + if (/(startRow|page)/.test(extra[0]) && extra[1] === 'input') { + len = ('' + (extra[0] === 'page' ? p.totalPages : p.totalRows)).length; + indx = extra[0] === 'page' ? p.page + 1 : p.startRow; + return ''; + } + return extra.length > 1 && data && data[extra[0]] ? data[extra[0]][extra[1]] : p[str] || (data ? data[str] : deflt) || deflt; + }); + if ( p.$goto.length ) { + t = ''; + options = tsp.buildPageSelect(p, c); + len = options.length; + for (indx = 0; indx < len; indx++) { + t += ''; + } + // innerHTML doesn't work in IE9 - http://support2.microsoft.com/kb/276228 + p.$goto.html(t).val( p.page + 1 ); } - tsp.renderAjax(data, table, c, jqxhr); - $doc.off('ajaxError' + namespace); + if ($out.length) { + $out[ ($out[0].nodeName === 'INPUT') ? 'val' : 'html' ](s); + // rebind startRow/page inputs + $out.find('.ts-startRow, .ts-page').off('change' + namespace).on('change' + namespace, function(){ + var v = $(this).val(), + pg = $(this).hasClass('ts-startRow') ? Math.floor( v / p.size ) + 1 : v; + c.$table.trigger('pageSet' + namespace, [ pg ]); + }); + } + } + tsp.pagerArrows(c); + tsp.fixHeight(table, c); + if (p.initialized && completed !== false) { + if (c.debug) { + ts.log('Pager: Triggering pagerComplete'); + } + c.$table.trigger('pagerComplete', c); + // save pager info to storage + if (wo.pager_savePages && ts.storage) { + ts.storage(table, wo.pager_storageKey, { + page : p.page, + size : p.size + }); + } + } + }, + + buildPageSelect: function(p, c) { + // Filter the options page number link array if it's larger than 'pager_maxOptionSize' + // as large page set links will slow the browser on large dom inserts + var i, central_focus_size, focus_option_pages, insert_index, option_length, focus_length, + wo = c.widgetOptions, + pg = Math.min( p.totalPages, p.filteredPages ) || 1, + // make skip set size multiples of 5 + skip_set_size = Math.ceil( ( pg / wo.pager_maxOptionSize ) / 5 ) * 5, + large_collection = pg > wo.pager_maxOptionSize, + current_page = p.page + 1, + start_page = skip_set_size, + end_page = pg - skip_set_size, + option_pages = [ 1 ], + // construct default options pages array + option_pages_start_page = (large_collection) ? skip_set_size : 1; + + for ( i = option_pages_start_page; i <= pg; ) { + option_pages.push(i); + i = i + ( large_collection ? skip_set_size : 1 ); + } + option_pages.push(pg); + + if (large_collection) { + focus_option_pages = []; + // don't allow central focus size to be > 5 on either side of current page + central_focus_size = Math.max( Math.floor( wo.pager_maxOptionSize / skip_set_size ) - 1, 5 ); + + start_page = current_page - central_focus_size; + if (start_page < 1) { start_page = 1; } + end_page = current_page + central_focus_size; + if (end_page > pg) { end_page = pg; } + // construct an array to get a focus set around the current page + for (i = start_page; i <= end_page ; i++) { + focus_option_pages.push(i); + } + + // keep unique values + option_pages = $.grep(option_pages, function(value, indx) { + return $.inArray(value, option_pages) === indx; + }); + + option_length = option_pages.length; + focus_length = focus_option_pages.length; + + // make sure at all option_pages aren't replaced + if (option_length - focus_length > skip_set_size / 2 && option_length + focus_length > wo.pager_maxOptionSize ) { + insert_index = Math.floor(option_length / 2) - Math.floor(focus_length / 2); + Array.prototype.splice.apply(option_pages, [ insert_index, focus_length ]); + } + option_pages = option_pages.concat(focus_option_pages); + + } + + // keep unique values again + option_pages = $.grep(option_pages, function(value, indx) { + return $.inArray(value, option_pages) === indx; + }) + .sort(function(a, b) { return a - b; }); + + return option_pages; + }, + + fixHeight: function(table, c) { + var d, h, + p = c.pager, + wo = c.widgetOptions, + $b = c.$tbodies.eq(0); + $b.find('tr.pagerSavedHeightSpacer').remove(); + if (wo.pager_fixedHeight && !p.isDisabled) { + h = $.data(table, 'pagerSavedHeight'); + if (h) { + d = h - $b.height(); + if ( d > 5 && $.data(table, 'pagerLastSize') === p.size && $b.children('tr:visible').length < p.size ) { + $b.append(''); + } + } + } + }, + + changeHeight: function(table, c) { + var h, $b = c.$tbodies.eq(0); + $b.find('tr.pagerSavedHeightSpacer').remove(); + if (!$b.children('tr:visible').length) { + $b.append(''); + } + h = $b.children('tr').eq(0).height() * c.pager.size; + $.data(table, 'pagerSavedHeight', h); + tsp.fixHeight(table, c); + $.data(table, 'pagerLastSize', c.pager.size); + }, + + hideRows: function(table, c){ + if ( !c.widgetOptions.pager_ajaxUrl ) { + var tbodyIndex, rowIndex, $rows, len, lastIndex, + p = c.pager, + wo = c.widgetOptions, + tbodyLen = c.$tbodies.length, + start = ( p.page * p.size ), + end = start + p.size, + filtr = wo && wo.filter_filteredRow || 'filtered', + last = 0, // for cache indexing + size = 0; // size counter + p.cacheIndex = []; + for ( tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++ ) { + $rows = c.$tbodies.eq( tbodyIndex ).children( 'tr' ); + len = $rows.length; + lastIndex = 0; + last = 0; // for cache indexing + size = 0; // size counter + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + if ( !$rows[ rowIndex ].className.match( filtr ) ) { + if ( size === start && $rows[ rowIndex ].className.match( c.cssChildRow ) ) { + // hide child rows @ start of pager (if already visible) + $rows[ rowIndex ].style.display = 'none'; + } else { + $rows[ rowIndex ].style.display = ( size >= start && size < end ) ? '' : 'none'; + if ( last !== size && size >= start && size < end ) { + p.cacheIndex.push( rowIndex ); + last = size; + } + // don't count child rows + size += $rows[ rowIndex ].className + .match( c.cssChildRow + '|' + c.selectorRemove.slice( 1 ) ) && !wo.pager_countChildRows ? 0 : 1; + if ( size === end && $rows[ rowIndex ].style.display !== 'none' && + $rows[ rowIndex ].className.match( ts.css.cssHasChild ) ) { + lastIndex = rowIndex; + } + } + } + } + // add any attached child rows to last row of pager. Fixes part of issue #396 + if ( lastIndex > 0 && $rows[ lastIndex ].className.match( ts.css.cssHasChild ) ) { + while ( ++lastIndex < len && $rows[ lastIndex ].className.match( c.cssChildRow ) ) { + $rows[ lastIndex ].style.display = ''; + } + } + } + } + }, + + hideRowsSetup: function(table, c){ + var p = c.pager, + namespace = c.namespace + 'pager'; + p.size = parseInt( p.$size.val(), 10 ) || p.size || p.setSize || 10; + $.data(table, 'pagerLastSize', p.size); + tsp.pagerArrows(c); + if ( !c.widgetOptions.pager_removeRows ) { + tsp.hideRows(table, c); + c.$table.on('sortEnd filterEnd '.split(' ').join(namespace + ' '), function(){ + tsp.hideRows(table, c); + }); + } + }, + + renderAjax: function(data, table, c, xhr, exception){ + var p = c.pager, + wo = c.widgetOptions; + // process data + if ( $.isFunction(wo.pager_ajaxProcessing) ) { + // ajaxProcessing result: [ total, rows, headers ] + var i, j, t, hsh, $f, $sh, $headers, $h, icon, th, d, l, rr_count, len, + $table = c.$table, + tds = '', + result = wo.pager_ajaxProcessing(data, table, xhr) || [ 0, [] ], + hl = $table.find('thead th').length; + + // Clean up any previous error. + ts.showError(table); + + if ( exception ) { + if (c.debug) { + ts.log('Pager: >> Ajax Error', xhr, exception); + } + ts.showError(table, exception.message + ' (' + xhr.status + ')'); + c.$tbodies.eq(0).children('tr').detach(); + p.totalRows = 0; + } else { + // process ajax object + if (!$.isArray(result)) { + p.ajaxData = result; + c.totalRows = p.totalRows = result.total; + c.filteredRows = p.filteredRows = typeof result.filteredRows !== 'undefined' ? result.filteredRows : result.total; + th = result.headers; + d = result.rows || []; + } else { + // allow [ total, rows, headers ] or [ rows, total, headers ] + t = isNaN(result[0]) && !isNaN(result[1]); + // ensure a zero returned row count doesn't fail the logical || + rr_count = result[t ? 1 : 0]; + p.totalRows = isNaN(rr_count) ? p.totalRows || 0 : rr_count; + // can't set filtered rows when returning an array + c.totalRows = c.filteredRows = p.filteredRows = p.totalRows; + // set row data to empty array if nothing found - see http://stackoverflow.com/q/30875583/145346 + d = p.totalRows === 0 ? [] : result[t ? 0 : 1] || []; // row data + th = result[2]; // headers + } + l = d && d.length; + if (d instanceof jQuery) { + if (wo.pager_processAjaxOnInit) { + // append jQuery object + c.$tbodies.eq(0).children('tr').detach(); + c.$tbodies.eq(0).append(d); + } + } else if (l) { + // build table from array + for ( i = 0; i < l; i++ ) { + tds += ''; + for ( j = 0; j < d[i].length; j++ ) { + // build tbody cells; watch for data containing HTML markup - see #434 + tds += /^\s*'; + } + tds += ''; + } + // add rows to first tbody + if (wo.pager_processAjaxOnInit) { + c.$tbodies.eq(0).html( tds ); + } + } + wo.pager_processAjaxOnInit = true; + // only add new header text if the length matches + if ( th && th.length === hl ) { + hsh = $table.hasClass('hasStickyHeaders'); + $sh = hsh ? wo.$sticky.children('thead:first').children('tr').children() : ''; + $f = $table.find('tfoot tr:first').children(); + // don't change td headers (may contain pager) + $headers = c.$headers.filter( 'th '); + len = $headers.length; + for ( j = 0; j < len; j++ ) { + $h = $headers.eq( j ); + // add new test within the first span it finds, or just in the header + if ( $h.find('.' + ts.css.icon).length ) { + icon = $h.find('.' + ts.css.icon).clone(true); + $h.find('.tablesorter-header-inner').html( th[j] ).append(icon); + if ( hsh && $sh.length ) { + icon = $sh.eq(j).find('.' + ts.css.icon).clone(true); + $sh.eq(j).find('.tablesorter-header-inner').html( th[j] ).append(icon); + } + } else { + $h.find('.tablesorter-header-inner').html( th[j] ); + if (hsh && $sh.length) { + $sh.eq(j).find('.tablesorter-header-inner').html( th[j] ); + } + } + $f.eq(j).html( th[j] ); + } + } + } + if (c.showProcessing) { + ts.isProcessing(table); // remove loading icon + } + // make sure last pager settings are saved, prevents multiple server side calls with + // the same parameters + p.totalPages = Math.ceil( p.totalRows / ( p.size || p.setSize || 10 ) ); + p.last.totalRows = p.totalRows; + p.last.currentFilters = p.currentFilters; + p.last.sortList = (c.sortList || []).join(','); + p.initializing = false; + // update display without triggering pager complete... before updating cache + tsp.updatePageDisplay(table, c, false); + $table.trigger('updateCache', [ function(){ + if (p.initialized) { + // apply widgets after table has rendered & after a delay to prevent + // multiple applyWidget blocking code from blocking this trigger + setTimeout(function(){ + if (c.debug) { + ts.log('Pager: Triggering pagerChange'); + } + $table + .trigger('applyWidgets') + .trigger('pagerChange', p); + tsp.updatePageDisplay(table, c); + }, 0); + } + } ]); + } + if (!p.initialized) { + c.$table.trigger('applyWidgets'); + } + }, + + getAjax: function(table, c){ + var counter, + url = tsp.getAjaxUrl(table, c), + $doc = $(document), + namespace = c.namespace + 'pager', + p = c.pager; + if ( url !== '' ) { + if (c.showProcessing) { + ts.isProcessing(table, true); // show loading icon + } + $doc.on('ajaxError' + namespace, function(e, xhr, settings, exception) { + tsp.renderAjax(null, table, c, xhr, exception); + $doc.off('ajaxError' + namespace); + }); + counter = ++p.ajaxCounter; + p.last.ajaxUrl = url; // remember processed url + p.ajaxObject.url = url; // from the ajaxUrl option and modified by customAjaxUrl + p.ajaxObject.success = function(data, status, jqxhr) { + // Refuse to process old ajax commands that were overwritten by new ones - see #443 + if (counter < p.ajaxCounter){ + return; + } + tsp.renderAjax(data, table, c, jqxhr); + $doc.off('ajaxError' + namespace); if (typeof p.oldAjaxSuccess === 'function') { p.oldAjaxSuccess(data); } - }; - if (c.debug) { - ts.log('Pager: Ajax initialized', p.ajaxObject); - } - $.ajax(p.ajaxObject); - } - }, - - getAjaxUrl: function(table, c) { - var indx, len, - p = c.pager, - wo = c.widgetOptions, - url = (wo.pager_ajaxUrl) ? wo.pager_ajaxUrl - // allow using "{page+1}" in the url string to switch to a non-zero based index - .replace(/\{page([\-+]\d+)?\}/, function(s,n){ return p.page + (n ? parseInt(n, 10) : 0); }) - .replace(/\{size\}/g, p.size) : '', - sortList = c.sortList, - filterList = p.currentFilters || $(table).data('lastSearch') || [], - sortCol = url.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/), - filterCol = url.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/), - arry = []; - if (sortCol) { - sortCol = sortCol[1]; - len = sortList.length; - for (indx = 0; indx < len; indx++) { - arry.push(sortCol + '[' + sortList[indx][0] + ']=' + sortList[indx][1]); - } - // if the arry is empty, just add the col parameter... "&{sortList:col}" becomes "&col" - url = url.replace(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : sortCol ); - arry = []; - } - if (filterCol) { - filterCol = filterCol[1]; - len = filterList.length; - for (indx = 0; indx < len; indx++) { - if (filterList[indx]) { - arry.push(filterCol + '[' + indx + ']=' + encodeURIComponent(filterList[indx])); + }; + if (c.debug) { + ts.log('Pager: Ajax initialized', p.ajaxObject); } + $.ajax(p.ajaxObject); } - // if the arry is empty, just add the fcol parameter... "&{filterList:fcol}" becomes "&fcol" - url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol ); - p.currentFilters = filterList; - } - if ( $.isFunction(wo.pager_customAjaxUrl) ) { - url = wo.pager_customAjaxUrl(table, url); - } - if (c.debug) { - ts.log('Pager: Ajax url = ' + url); - } - return url; - }, + }, - renderTable: function(table, rows) { - var $tb, index, count, added, - c = table.config, - p = c.pager, - wo = c.widgetOptions, - f = c.$table.hasClass('hasFilters'), - l = rows && rows.length || 0, // rows may be undefined - s = ( p.page * p.size ), - e = p.size; - if ( l < 1 ) { - if (c.debug) { - ts.log('Pager: >> No rows for pager to render'); + getAjaxUrl: function(table, c) { + var indx, len, + p = c.pager, + wo = c.widgetOptions, + url = (wo.pager_ajaxUrl) ? wo.pager_ajaxUrl + // allow using '{page+1}' in the url string to switch to a non-zero based index + .replace(/\{page([\-+]\d+)?\}/, function(s, n){ return p.page + (n ? parseInt(n, 10) : 0); }) + .replace(/\{size\}/g, p.size) : '', + sortList = c.sortList, + filterList = p.currentFilters || $(table).data('lastSearch') || [], + sortCol = url.match(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/), + filterCol = url.match(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/), + arry = []; + if (sortCol) { + sortCol = sortCol[1]; + len = sortList.length; + for (indx = 0; indx < len; indx++) { + arry.push(sortCol + '[' + sortList[indx][0] + ']=' + sortList[indx][1]); + } + // if the arry is empty, just add the col parameter... '&{sortList:col}' becomes '&col' + url = url.replace(/\{\s*sort(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : sortCol ); + arry = []; } - // empty table, abort! - return; - } - if ( p.page >= p.totalPages ) { - // lets not render the table more than once - return tsp.moveToLastPage(table, p); - } - p.cacheIndex = []; - p.isDisabled = false; // needed because sorting will change the page and re-enable the pager - if (p.initialized) { - if (c.debug) { - ts.log('Pager: Triggering pagerChange'); - } - c.$table.trigger('pagerChange', c); - } - if ( !wo.pager_removeRows ) { - tsp.hideRows(table, c); - } else { - ts.clearTableBody(table); - $tb = ts.processTbody(table, c.$tbodies.eq(0), true); - // not filtered, start from the calculated starting point (s) - // if filtered, start from zero - index = f ? 0 : s; - count = f ? 0 : s; - added = 0; - while (added < e && index < rows.length) { - if (!f || !/filtered/.test(rows[index][0].className)){ - count++; - if (count > s && added <= e) { - added++; - p.cacheIndex.push(index); - $tb.append(rows[index]); + if (filterCol) { + filterCol = filterCol[1]; + len = filterList.length; + for (indx = 0; indx < len; indx++) { + if (filterList[indx]) { + arry.push(filterCol + '[' + indx + ']=' + encodeURIComponent(filterList[indx])); } } - index++; + // if the arry is empty, just add the fcol parameter... '&{filterList:fcol}' becomes '&fcol' + url = url.replace(/\{\s*filter(?:List)?\s*:\s*(\w*)\s*\}/g, arry.length ? arry.join('&') : filterCol ); + p.currentFilters = filterList; + } + if ( $.isFunction(wo.pager_customAjaxUrl) ) { + url = wo.pager_customAjaxUrl(table, url); } - ts.processTbody(table, $tb, false); - } - tsp.updatePageDisplay(table, c); - - wo.pager_startPage = p.page; - wo.pager_size = p.size; - if (table.isUpdating) { if (c.debug) { - ts.log('Pager: Triggering updateComplete'); + ts.log('Pager: Ajax url = ' + url); } - c.$table.trigger('updateComplete', [ table, true ]); - } + return url; + }, - }, - - showAllRows: function(table, c){ - var index, $controls, len, - p = c.pager, - wo = c.widgetOptions; - if ( p.ajax ) { - tsp.pagerArrows(c, true); - } else { - p.isDisabled = true; - $.data(table, 'pagerLastPage', p.page); - $.data(table, 'pagerLastSize', p.size); - p.page = 0; - p.size = p.totalRows; - p.totalPages = 1; - c.$table - .addClass('pagerDisabled') - .removeAttr('aria-describedby') - .find('tr.pagerSavedHeightSpacer').remove(); - tsp.renderTable(table, c.rowsCopy); - c.$table.trigger('applyWidgets'); - if (c.debug) { - ts.log('Pager: Disabled'); - } - } - // disable size selector - $controls = p.$size - .add( p.$goto ) - .add( p.$container.find( '.ts-startRow, .ts-page ' ) ); - len = $controls.length; - for ( index = 0; index < len; index++ ) { - $controls.eq( index ) - .attr( 'aria-disabled', 'true' ) - .addClass( wo.pager_css.disabled )[0].disabled = true; - } - }, - - // updateCache if delayInit: true - // this is normally done by "appendToTable" function in the tablesorter core AFTER a sort - updateCache: function(table) { - var c = table.config, - p = c.pager; - c.$table.trigger('updateCache', [ function(){ - if ( !$.isEmptyObject(table.config.cache) ) { - var i, - rows = [], - n = table.config.cache[0].normalized; - p.totalRows = n.length; - for (i = 0; i < p.totalRows; i++) { - rows.push(n[i][c.columns].$row); + renderTable: function(table, rows) { + var $tb, index, count, added, + c = table.config, + p = c.pager, + wo = c.widgetOptions, + f = c.$table.hasClass('hasFilters'), + l = rows && rows.length || 0, // rows may be undefined + s = ( p.page * p.size ), + e = p.size; + if ( l < 1 ) { + if (c.debug) { + ts.log('Pager: >> No rows for pager to render'); } - c.rowsCopy = rows; - tsp.moveToPage(table, p, true); - // clear out last search to force an update - p.last.currentFilters = [' ']; - } - } ]); - }, - - moveToPage: function(table, p, pageMoved) { - if ( p.isDisabled ) { return; } - if ( pageMoved !== false && p.initialized && $.isEmptyObject(table.config.cache)) { - return tsp.updateCache(table); - } - var pg, c = table.config, - wo = c.widgetOptions, - l = p.last; - - // abort page move if the table has filters and has not been initialized - if (p.ajax && !wo.filter_initialized && ts.hasWidget(table, 'filter')) { return; } - - tsp.calcFilters(table, c); - pg = Math.min( p.totalPages, p.filteredPages ); - if ( p.page < 0 ) { p.page = 0; } - if ( p.page > ( pg - 1 ) && pg !== 0 ) { p.page = pg - 1; } - - // fixes issue where one current filter is [] and the other is ['','',''], - // making the next if comparison think the filters as different. Fixes #202. - l.currentFilters = (l.currentFilters || []).join('') === '' ? [] : l.currentFilters; - p.currentFilters = (p.currentFilters || []).join('') === '' ? [] : p.currentFilters; - // don't allow rendering multiple times on the same page/size/totalRows/filters/sorts - if ( l.page === p.page && l.size === p.size && l.totalRows === p.totalRows && - (l.currentFilters || []).join(',') === (p.currentFilters || []).join(',') && - // check for ajax url changes see #730 - (l.ajaxUrl || '') === (p.ajaxObject.url || '') && - // & ajax url option changes (dynamically add/remove/rename sort & filter parameters) - (l.optAjaxUrl || '') === (wo.pager_ajaxUrl || '') && - l.sortList === (c.sortList || []).join(',') ) { + // empty table, abort! return; } - if (c.debug) { - ts.log('Pager: Changing to page ' + p.page); - } - p.last = { - page : p.page, - size : p.size, - // fixes #408; modify sortList otherwise it auto-updates - sortList : (c.sortList || []).join(','), - totalRows : p.totalRows, - currentFilters : p.currentFilters || [], - ajaxUrl : p.ajaxObject.url || '', - optAjaxUrl : wo.pager_ajaxUrl - }; - if (p.ajax) { - tsp.getAjax(table, c); - } else if (!p.ajax) { - tsp.renderTable(table, c.rowsCopy); - } - $.data(table, 'pagerLastPage', p.page); - if (p.initialized && pageMoved !== false) { - if (c.debug) { - ts.log('Pager: Triggering pageMoved'); + if ( p.page >= p.totalPages ) { + // lets not render the table more than once + return tsp.moveToLastPage(table, p); } - c.$table - .trigger('pageMoved', c) - .trigger('applyWidgets'); - if (!p.ajax && table.isUpdating) { + p.cacheIndex = []; + p.isDisabled = false; // needed because sorting will change the page and re-enable the pager + if (p.initialized) { + if (c.debug) { + ts.log('Pager: Triggering pagerChange'); + } + c.$table.trigger('pagerChange', c); + } + if ( !wo.pager_removeRows ) { + tsp.hideRows(table, c); + } else { + ts.clearTableBody(table); + $tb = ts.processTbody(table, c.$tbodies.eq(0), true); + // not filtered, start from the calculated starting point (s) + // if filtered, start from zero + index = f ? 0 : s; + count = f ? 0 : s; + added = 0; + while (added < e && index < rows.length) { + if (!f || !/filtered/.test(rows[index][0].className)){ + count++; + if (count > s && added <= e) { + added++; + p.cacheIndex.push(index); + $tb.append(rows[index]); + } + } + index++; + } + ts.processTbody(table, $tb, false); + } + tsp.updatePageDisplay(table, c); + + wo.pager_startPage = p.page; + wo.pager_size = p.size; + if (table.isUpdating) { if (c.debug) { ts.log('Pager: Triggering updateComplete'); } c.$table.trigger('updateComplete', [ table, true ]); } - } - }, - setPageSize: function(table, size, c) { - var p = c.pager; - p.size = size || p.size || p.setSize || 10; - p.$size.val(p.size); - $.data(table, 'pagerLastPage', p.page); - $.data(table, 'pagerLastSize', p.size); - p.totalPages = Math.ceil( p.totalRows / p.size ); - p.filteredPages = Math.ceil( p.filteredRows / p.size ); - tsp.moveToPage(table, p, true); - }, + }, - moveToFirstPage: function(table, p) { - p.page = 0; - tsp.moveToPage(table, p, true); - }, - - moveToLastPage: function(table, p) { - p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 ); - tsp.moveToPage(table, p, true); - }, - - moveToNextPage: function(table, p) { - p.page++; - if ( p.page >= ( Math.min( p.totalPages, p.filteredPages ) - 1 ) ) { - p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 ); - } - tsp.moveToPage(table, p, true); - }, - - moveToPrevPage: function(table, p) { - p.page--; - if ( p.page <= 0 ) { - p.page = 0; - } - tsp.moveToPage(table, p, true); - }, - - destroyPager: function(table, c, refreshing){ - var p = c.pager, - namespace = c.namespace + 'pager'; - p.initialized = false; - c.$table.off( $.trim(p.events.split(' ').join(namespace + ' ')) ); - if (refreshing) { return; } - tsp.showAllRows(table, c); - p.$container.hide(); // hide pager - c.appender = null; // remove pager appender function - delete table.config.rowsCopy; - if (ts.storage) { - ts.storage(table, c.widgetOptions.pager_storageKey, ''); - } - }, - - enablePager: function(table, c, triggered){ - var info, p = c.pager; - p.isDisabled = false; - p.page = $.data(table, 'pagerLastPage') || p.page || 0; - p.size = $.data(table, 'pagerLastSize') || parseInt(p.$size.find('option[selected]').val(), 10) || p.size || p.setSize || 10; - p.$size.val(p.size); // set page size - p.totalPages = Math.ceil( Math.min( p.totalRows, p.filteredRows ) / p.size ); - c.$table.removeClass('pagerDisabled'); - // if table id exists, include page display with aria info - if ( table.id ) { - info = table.id + '_pager_info'; - p.$container.find(c.widgetOptions.pager_selectors.pageDisplay).attr('id', info); - c.$table.attr('aria-describedby', info); - } - tsp.changeHeight(table, c); - if ( triggered ) { - c.$table.trigger('updateRows'); - tsp.setPageSize(table, p.size, c); - tsp.hideRowsSetup(table, c); - if (c.debug) { - ts.log('Pager: Enabled'); - } - } - }, - - appender: function(table, rows) { - var c = table.config, - wo = c.widgetOptions, - p = c.pager; - if ( !p.ajax ) { - c.rowsCopy = rows; - p.totalRows = wo.pager_countChildRows ? c.$tbodies.eq(0).children('tr').length : rows.length; - p.size = $.data(table, 'pagerLastSize') || p.size || wo.pager_size || p.setSize || 10; - p.totalPages = Math.ceil( p.totalRows / p.size ); - tsp.moveToPage(table, p); - // update display here in case all rows are removed - tsp.updatePageDisplay(table, c, false); - } else { - tsp.moveToPage(table, p, true); - } - } - -}; - -// see #486 -ts.showError = function( table, message ) { - var index, $row, c, wo, errorRow, - $table = $( table ), - len = $table.length; - for ( index = 0; index < len; index++ ) { - c = $table[ index ].config; - if ( c ) { - wo = c.widgetOptions; - errorRow = c.pager && c.pager.cssErrorRow || wo.pager_css && wo.pager_css.errorRow || 'tablesorter-errorRow'; - if ( typeof message === 'undefined' ) { - c.$table.find('thead').find(c.selectorRemove).remove(); + showAllRows: function(table, c){ + var index, $controls, len, + p = c.pager, + wo = c.widgetOptions; + if ( p.ajax ) { + tsp.pagerArrows(c, true); } else { - $row = ( /tr\>/.test(message) ? $(message) : $('') ) - .click(function(){ - $(this).remove(); - }) - // add error row to thead instead of tbody, or clicking on the header will result in a parser error - .appendTo( c.$table.find('thead:first') ) - .addClass( errorRow + ' ' + c.selectorRemove.slice(1) ) - .attr({ - role : 'alert', - 'aria-live' : 'assertive' - }); + p.isDisabled = true; + $.data(table, 'pagerLastPage', p.page); + $.data(table, 'pagerLastSize', p.size); + p.page = 0; + p.size = p.totalRows; + p.totalPages = 1; + c.$table + .addClass('pagerDisabled') + .removeAttr('aria-describedby') + .find('tr.pagerSavedHeightSpacer').remove(); + tsp.renderTable(table, c.rowsCopy); + c.$table.trigger('applyWidgets'); + if (c.debug) { + ts.log('Pager: Disabled'); + } + } + // disable size selector + $controls = p.$size + .add( p.$goto ) + .add( p.$container.find( '.ts-startRow, .ts-page ' ) ); + len = $controls.length; + for ( index = 0; index < len; index++ ) { + $controls.eq( index ) + .attr( 'aria-disabled', 'true' ) + .addClass( wo.pager_css.disabled )[0].disabled = true; + } + }, + + // updateCache if delayInit: true + // this is normally done by 'appendToTable' function in the tablesorter core AFTER a sort + updateCache: function(table) { + var c = table.config, + p = c.pager; + c.$table.trigger('updateCache', [ function(){ + if ( !$.isEmptyObject(table.config.cache) ) { + var i, + rows = [], + n = table.config.cache[0].normalized; + p.totalRows = n.length; + for (i = 0; i < p.totalRows; i++) { + rows.push(n[i][c.columns].$row); + } + c.rowsCopy = rows; + tsp.moveToPage(table, p, true); + // clear out last search to force an update + p.last.currentFilters = [ ' ' ]; + } + } ]); + }, + + moveToPage: function(table, p, pageMoved) { + if ( p.isDisabled ) { return; } + if ( pageMoved !== false && p.initialized && $.isEmptyObject(table.config.cache)) { + return tsp.updateCache(table); + } + var pg, c = table.config, + wo = c.widgetOptions, + l = p.last; + + // abort page move if the table has filters and has not been initialized + if (p.ajax && !wo.filter_initialized && ts.hasWidget(table, 'filter')) { return; } + + tsp.calcFilters(table, c); + pg = Math.min( p.totalPages, p.filteredPages ); + if ( p.page < 0 ) { p.page = 0; } + if ( p.page > ( pg - 1 ) && pg !== 0 ) { p.page = pg - 1; } + + // fixes issue where one current filter is [] and the other is [ '', '', '' ], + // making the next if comparison think the filters as different. Fixes #202. + l.currentFilters = (l.currentFilters || []).join('') === '' ? [] : l.currentFilters; + p.currentFilters = (p.currentFilters || []).join('') === '' ? [] : p.currentFilters; + // don't allow rendering multiple times on the same page/size/totalRows/filters/sorts + if ( l.page === p.page && l.size === p.size && l.totalRows === p.totalRows && + (l.currentFilters || []).join(',') === (p.currentFilters || []).join(',') && + // check for ajax url changes see #730 + (l.ajaxUrl || '') === (p.ajaxObject.url || '') && + // & ajax url option changes (dynamically add/remove/rename sort & filter parameters) + (l.optAjaxUrl || '') === (wo.pager_ajaxUrl || '') && + l.sortList === (c.sortList || []).join(',') ) { + return; + } + if (c.debug) { + ts.log('Pager: Changing to page ' + p.page); + } + p.last = { + page : p.page, + size : p.size, + // fixes #408; modify sortList otherwise it auto-updates + sortList : (c.sortList || []).join(','), + totalRows : p.totalRows, + currentFilters : p.currentFilters || [], + ajaxUrl : p.ajaxObject.url || '', + optAjaxUrl : wo.pager_ajaxUrl + }; + if (p.ajax) { + tsp.getAjax(table, c); + } else if (!p.ajax) { + tsp.renderTable(table, c.rowsCopy); + } + $.data(table, 'pagerLastPage', p.page); + if (p.initialized && pageMoved !== false) { + if (c.debug) { + ts.log('Pager: Triggering pageMoved'); + } + c.$table + .trigger('pageMoved', c) + .trigger('applyWidgets'); + if (!p.ajax && table.isUpdating) { + if (c.debug) { + ts.log('Pager: Triggering updateComplete'); + } + c.$table.trigger('updateComplete', [ table, true ]); + } + } + }, + + setPageSize: function(table, size, c) { + var p = c.pager; + p.size = size || p.size || p.setSize || 10; + p.$size.val(p.size); + $.data(table, 'pagerLastPage', p.page); + $.data(table, 'pagerLastSize', p.size); + p.totalPages = Math.ceil( p.totalRows / p.size ); + p.filteredPages = Math.ceil( p.filteredRows / p.size ); + tsp.moveToPage(table, p, true); + }, + + moveToFirstPage: function(table, p) { + p.page = 0; + tsp.moveToPage(table, p, true); + }, + + moveToLastPage: function(table, p) { + p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 ); + tsp.moveToPage(table, p, true); + }, + + moveToNextPage: function(table, p) { + p.page++; + if ( p.page >= ( Math.min( p.totalPages, p.filteredPages ) - 1 ) ) { + p.page = ( Math.min( p.totalPages, p.filteredPages ) - 1 ); + } + tsp.moveToPage(table, p, true); + }, + + moveToPrevPage: function(table, p) { + p.page--; + if ( p.page <= 0 ) { + p.page = 0; + } + tsp.moveToPage(table, p, true); + }, + + destroyPager: function(table, c, refreshing){ + var p = c.pager, + namespace = c.namespace + 'pager'; + p.initialized = false; + c.$table.off( $.trim(p.events.split(' ').join(namespace + ' ')) ); + if (refreshing) { return; } + tsp.showAllRows(table, c); + p.$container.hide(); // hide pager + c.appender = null; // remove pager appender function + delete table.config.rowsCopy; + if (ts.storage) { + ts.storage(table, c.widgetOptions.pager_storageKey, ''); + } + }, + + enablePager: function(table, c, triggered){ + var info, p = c.pager; + p.isDisabled = false; + p.page = $.data(table, 'pagerLastPage') || p.page || 0; + p.size = $.data(table, 'pagerLastSize') || parseInt(p.$size.find('option[selected]').val(), 10) || p.size || p.setSize || 10; + p.$size.val(p.size); // set page size + p.totalPages = Math.ceil( Math.min( p.totalRows, p.filteredRows ) / p.size ); + c.$table.removeClass('pagerDisabled'); + // if table id exists, include page display with aria info + if ( table.id ) { + info = table.id + '_pager_info'; + p.$container.find(c.widgetOptions.pager_selectors.pageDisplay).attr('id', info); + c.$table.attr('aria-describedby', info); + } + tsp.changeHeight(table, c); + if ( triggered ) { + c.$table.trigger('updateRows'); + tsp.setPageSize(table, p.size, c); + tsp.hideRowsSetup(table, c); + if (c.debug) { + ts.log('Pager: Enabled'); + } + } + }, + + appender: function(table, rows) { + var c = table.config, + wo = c.widgetOptions, + p = c.pager; + if ( !p.ajax ) { + c.rowsCopy = rows; + p.totalRows = wo.pager_countChildRows ? c.$tbodies.eq(0).children('tr').length : rows.length; + p.size = $.data(table, 'pagerLastSize') || p.size || wo.pager_size || p.setSize || 10; + p.totalPages = Math.ceil( p.totalRows / p.size ); + tsp.moveToPage(table, p); + // update display here in case all rows are removed + tsp.updatePageDisplay(table, c, false); + } else { + tsp.moveToPage(table, p, true); } } - } -}; + + }; + + // see #486 + ts.showError = function( table, message ) { + var index, $row, c, wo, errorRow, + $table = $( table ), + len = $table.length; + for ( index = 0; index < len; index++ ) { + c = $table[ index ].config; + if ( c ) { + wo = c.widgetOptions; + errorRow = c.pager && c.pager.cssErrorRow || wo.pager_css && wo.pager_css.errorRow || 'tablesorter-errorRow'; + if ( typeof message === 'undefined' ) { + c.$table.find('thead').find(c.selectorRemove).remove(); + } else { + $row = ( /tr\>/.test(message) ? $(message) : $('') ) + .click(function(){ + $(this).remove(); + }) + // add error row to thead instead of tbody, or clicking on the header will result in a parser error + .appendTo( c.$table.find('thead:first') ) + .addClass( errorRow + ' ' + c.selectorRemove.slice(1) ) + .attr({ + role : 'alert', + 'aria-live' : 'assertive' + }); + } + } + } + }; })(jQuery); diff --git a/js/widgets/widget-print.js b/js/widgets/widget-print.js index e7a53bbf..eca57fac 100644 --- a/js/widgets/widget-print.js +++ b/js/widgets/widget-print.js @@ -4,120 +4,120 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter, + var ts = $.tablesorter, -printTable = ts.printTable = { + printTable = ts.printTable = { - event : 'printTable', - basicStyle : 'table, tr, td, th { border : solid 1px black; border-collapse : collapse; } td, th { padding: 2px; }', + event : 'printTable', + basicStyle : 'table, tr, td, th { border : solid 1px black; border-collapse : collapse; } td, th { padding: 2px; }', - init : function(c) { - c.$table - .unbind(printTable.event) - .bind(printTable.event, function(){ - // explicitly use table.config.widgetOptions because we want - // the most up-to-date values; not the "wo" from initialization - printTable.process(c, c.widgetOptions); + init : function(c) { + c.$table + .unbind(printTable.event) + .bind(printTable.event, function(){ + // explicitly use table.config.widgetOptions because we want + // the most up-to-date values; not the 'wo' from initialization + printTable.process(c, c.widgetOptions); + }); + }, + + process : function(c, wo) { + var $this, + $table = $('
').append(c.$table.clone()), + printStyle = printTable.basicStyle + 'table { width: 100% }' + + // hide filter row + '.tablesorter-filter-row { display: none }' + + // hide sort arrows + '.tablesorter-header { background-image: none !important; }'; + + // replace content with data-attribute content + $table.find('[' + wo.print_dataAttrib + ']').each(function(){ + $this = $(this); + $this.text( $this.attr(wo.print_dataAttrib) ); }); - }, - process : function(c, wo) { - var $this, - $table = $('
').append(c.$table.clone()), - printStyle = printTable.basicStyle + 'table { width: 100% }' + - // hide filter row - '.tablesorter-filter-row { display: none }' + - // hide sort arrows - '.tablesorter-header { background-image: none !important; }'; + // === rows === + // Assume 'visible' means rows hidden by the pager (rows set to 'display:none') + // or hidden by a class name which is added to the wo.print_extraCSS definition + if (/a/i.test(wo.print_rows)) { + // force show of all rows + printStyle += 'tbody tr { display: table-row !important; }'; + } else if (/f/i.test(wo.print_rows)) { + // add definition to show all non-filtered rows (cells hidden by the pager) + printStyle += 'tbody tr:not(.' + (wo.filter_filteredRow || 'filtered') + ') { display: table-row !important; }'; + } - // replace content with data-attribute content - $table.find('[' + wo.print_dataAttrib + ']').each(function(){ - $this = $(this); - $this.text( $this.attr(wo.print_dataAttrib) ); - }); + // === columns === + // columnSelector -> c.selector.$style + // Assume 'visible' means hidden columns have a 'display:none' style, or a class name + // add the definition to the wo.print_extraCSS option + if (/s/i.test(wo.print_columns) && c.selector && c.widgets.indexOf('columnSelector') >= 0) { + // show selected (visible) columns; make a copy of the columnSelector widget css (not media queries) + printStyle += wo.columnSelector_mediaquery && c.selector.auto ? '' : c.selector.$style.text(); + } else if (/a/i.test(wo.print_columns)) { + // force show all cells + printStyle += 'td, th { display: table-cell !important; }'; + } - // === rows === - // Assume "visible" means rows hidden by the pager (rows set to "display:none") - // or hidden by a class name which is added to the wo.print_extraCSS definition - if (/a/i.test(wo.print_rows)) { - // force show of all rows - printStyle += 'tbody tr { display: table-row !important; }'; - } else if (/f/i.test(wo.print_rows)) { - // add definition to show all non-filtered rows (cells hidden by the pager) - printStyle += 'tbody tr:not(.' + (wo.filter_filteredRow || 'filtered') + ') { display: table-row !important; }'; + printStyle += wo.print_extraCSS; + + // callback function + if ( $.isFunction(wo.print_callback) ) { + wo.print_callback( c, $table, printStyle ); + } else { + printTable.printOutput(c, $table.html(), printStyle); + } + + }, // end process + + printOutput : function(c, data, style) { + var wo = c.widgetOptions, + generator = window.open('', wo.print_title, 'width=500,height=300'), + t = wo.print_title || c.$table.find('caption').text() || c.$table[0].id || document.title || 'table'; + generator.document.write( + '' + t + '' + + ( wo.print_styleSheet ? '' : '' ) + + '' + + '' + data + '' + ); + generator.document.close(); + generator.print(); + generator.close(); + return true; + }, + + remove : function(c) { + c.$table.off(printTable.event); } - // === columns === - // columnSelector -> c.selector.$style - // Assume "visible" means hidden columns have a "display:none" style, or a class name - // add the definition to the wo.print_extraCSS option - if (/s/i.test(wo.print_columns) && c.selector && c.widgets.indexOf('columnSelector') >= 0) { - // show selected (visible) columns; make a copy of the columnSelector widget css (not media queries) - printStyle += wo.columnSelector_mediaquery && c.selector.auto ? '' : c.selector.$style.text(); - } else if (/a/i.test(wo.print_columns)) { - // force show all cells - printStyle += 'td, th { display: table-cell !important; }'; + }; + + ts.addWidget({ + id: 'print', + options: { + print_title : '', // this option > caption > table id > 'table' + print_dataAttrib : 'data-name', // header attrib containing modified header name + print_rows : 'filtered', // (a)ll, (v)isible or (f)iltered + print_columns : 'selected', // (a)ll or (s)elected (if columnSelector widget is added) + print_extraCSS : '', // add any extra css definitions for the popup window here + print_styleSheet : '', // add the url of your print stylesheet + // callback executed when processing completes + // to continue printing, use the following function: + // function( config, $table, printStyle ) { + // // do something to the table or printStyle string + // $.tablesorter.printTable.printOutput( config, $table.html(), printStyle ); + // } + print_callback : null + }, + init: function(table, thisWidget, c) { + printTable.init(c); + }, + remove: function(table, c){ + printTable.remove(c); } - printStyle += wo.print_extraCSS; - - // callback function - if ( $.isFunction(wo.print_callback) ) { - wo.print_callback( c, $table, printStyle ); - } else { - printTable.printOutput(c, $table.html(), printStyle); - } - - }, // end process - - printOutput : function(c, data, style) { - var wo = c.widgetOptions, - generator = window.open('', wo.print_title, 'width=500,height=300'), - t = wo.print_title || c.$table.find('caption').text() || c.$table[0].id || document.title || 'table'; - generator.document.write( - '' + t + '' + - ( wo.print_styleSheet ? '' : '' ) + - '' + - '' + data + '' - ); - generator.document.close(); - generator.print(); - generator.close(); - return true; - }, - - remove : function(c) { - c.$table.off(printTable.event); - } - -}; - -ts.addWidget({ - id: 'print', - options: { - print_title : '', // this option > caption > table id > "table" - print_dataAttrib : 'data-name', // header attrib containing modified header name - print_rows : 'filtered', // (a)ll, (v)isible or (f)iltered - print_columns : 'selected', // (a)ll or (s)elected (if columnSelector widget is added) - print_extraCSS : '', // add any extra css definitions for the popup window here - print_styleSheet : '', // add the url of your print stylesheet - // callback executed when processing completes - // to continue printing, use the following function: - // function( config, $table, printStyle ) { - // // do something to the table or printStyle string - // $.tablesorter.printTable.printOutput( config, $table.html(), printStyle ); - // } - print_callback : null - }, - init: function(table, thisWidget, c) { - printTable.init(c); - }, - remove: function(table, c){ - printTable.remove(c); - } - -}); + }); })(jQuery); diff --git a/js/widgets/widget-reflow.js b/js/widgets/widget-reflow.js index 063ebeb7..781e20ae 100644 --- a/js/widgets/widget-reflow.js +++ b/js/widgets/widget-reflow.js @@ -50,131 +50,130 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; + 'use strict'; -var ts = $.tablesorter, + var ts = $.tablesorter, -tablereflow = { - // simple reflow - // add data-attribute to each cell which shows when media query is active - // this widget DOES NOT WORK on a table with multiple thead rows - init : function(table, c, wo) { - var $this, - title = wo.reflow_dataAttrib, - header = wo.reflow_headerAttrib, - headers = []; - c.$table - .addClass(wo.reflow_className) - .off('refresh.tsreflow updateComplete.tsreflow2') - // emulate jQuery Mobile refresh - // https://api.jquerymobile.com/table-reflow/#method-refresh - .on('refresh.tsreflow updateComplete.tsreflow2', function(){ - tablereflow.init(table, c, wo); + tablereflow = { + // simple reflow + // add data-attribute to each cell which shows when media query is active + // this widget DOES NOT WORK on a table with multiple thead rows + init : function(table, c, wo) { + var $this, + title = wo.reflow_dataAttrib, + header = wo.reflow_headerAttrib, + headers = []; + c.$table + .addClass(wo.reflow_className) + .off('refresh.tsreflow updateComplete.tsreflow2') + // emulate jQuery Mobile refresh + // https://api.jquerymobile.com/table-reflow/#method-refresh + .on('refresh.tsreflow updateComplete.tsreflow2', function(){ + tablereflow.init(table, c, wo); + }); + c.$headers.each(function(){ + $this = $(this); + headers.push( $.trim( $this.attr(header) || $this.text() ) ); }); - c.$headers.each(function(){ - $this = $(this); - headers.push( $.trim( $this.attr(header) || $this.text() ) ); - }); - c.$tbodies.children().each(function(){ - $(this).children().each(function(i){ - $(this).attr(title, headers[i]); - }); - }); - }, - init2: function(table, c, wo) { - var $this, $tbody, i, $hdr, txt, len, - cols = c.columns, - header = wo.reflow2_headerAttrib, - headers = []; - c.$table - .addClass(wo.reflow2_className) - .off('refresh.tsreflow2 updateComplete.tsreflow2') - // emulate jQuery Mobile refresh - // https://api.jquerymobile.com/table-reflow/#method-refresh - .on('refresh.tsreflow2 updateComplete.tsreflow2', function(){ - tablereflow.init2(table, c, wo); + c.$tbodies.children().each(function(){ + $(this).children().each(function(i){ + $(this).attr(title, headers[i]); + }); }); + }, + init2: function(table, c, wo) { + var $this, $tbody, i, $hdr, txt, len, + cols = c.columns, + header = wo.reflow2_headerAttrib, + headers = []; + c.$table + .addClass(wo.reflow2_className) + .off('refresh.tsreflow2 updateComplete.tsreflow2') + // emulate jQuery Mobile refresh + // https://api.jquerymobile.com/table-reflow/#method-refresh + .on('refresh.tsreflow2 updateComplete.tsreflow2', function(){ + tablereflow.init2(table, c, wo); + }); - // add to every table cell with thead cell contents - for (i = 0; i < cols; i++) { - $hdr = c.$headers.filter('[data-column="' + i + '"]'); - if ($hdr.length > 1) { - txt = []; - /*jshint loopfunc:true */ - $hdr.each(function(){ + // add to every table cell with thead cell contents + for (i = 0; i < cols; i++) { + $hdr = c.$headers.filter('[data-column="' + i + '"]'); + if ($hdr.length > 1) { + txt = []; + /*jshint loopfunc:true */ + $hdr.each(function(){ + $this = $(this); + if (!$this.hasClass(wo.reflow2_classIgnore)) { + txt.push( $this.attr(header) || $this.text() ); + } + }); + } else { + txt = [ $hdr.attr(header) || $hdr.text() ]; + } + headers.push( txt ); + } + // include 'remove-me' class so these additional elements are removed before updating + txt = '' + headers[j][i] + ''); + i--; } }); - } else { - txt = [ $hdr.attr(header) || $hdr.text() ]; - } - headers.push( txt ); - } - // include "remove-me" class so these additional elements are removed before updating - txt = '' + headers[j][i] + ''); - i--; - } + ts.processTbody(table, $tbody, false); }); - ts.processTbody(table, $tbody, false); - }); - }, - remove : function(table, c, wo) { - c.$table.removeClass(wo.reflow_className); - }, - remove2 : function(table, c, wo) { - c.$table.removeClass(wo.reflow2_className); - } -}; + }, + remove : function(table, c, wo) { + c.$table.removeClass(wo.reflow_className); + }, + remove2 : function(table, c, wo) { + c.$table.removeClass(wo.reflow2_className); + } + }; -ts.addWidget({ - id: "reflow", - options: { - // class name added to make it responsive (class name within media query) - reflow_className : 'ui-table-reflow', - // header attribute containing modified header name - reflow_headerAttrib : 'data-name', - // data attribute added to each tbody cell - reflow_dataAttrib : 'data-title' - }, - init: function(table, thisWidget, c, wo) { - tablereflow.init(table, c, wo); - }, - remove: function(table, c, wo){ - tablereflow.remove(table, c, wo); - } -}); - -ts.addWidget({ - id: "reflow2", - options: { - // class name added to make it responsive (class name within media query) - reflow2_className : 'ui-table-reflow', - // ignore header cell content with this class name - reflow2_classIgnore : 'ui-table-reflow-ignore', - // header attribute containing modified header name - reflow2_headerAttrib : 'data-name', - // class name applied to thead labels - reflow2_labelClass : 'ui-table-cell-label', - // class name applied to first row thead label - reflow2_labelTop : 'ui-table-cell-label-top' - }, - init: function(table, thisWidget, c, wo) { - tablereflow.init2(table, c, wo); - }, - remove: function(table, c, wo){ - tablereflow.remove2(table, c, wo); - } -}); + ts.addWidget({ + id: 'reflow', + options: { + // class name added to make it responsive (class name within media query) + reflow_className : 'ui-table-reflow', + // header attribute containing modified header name + reflow_headerAttrib : 'data-name', + // data attribute added to each tbody cell + reflow_dataAttrib : 'data-title' + }, + init: function(table, thisWidget, c, wo) { + tablereflow.init(table, c, wo); + }, + remove: function(table, c, wo){ + tablereflow.remove(table, c, wo); + } + }); + ts.addWidget({ + id: 'reflow2', + options: { + // class name added to make it responsive (class name within media query) + reflow2_className : 'ui-table-reflow', + // ignore header cell content with this class name + reflow2_classIgnore : 'ui-table-reflow-ignore', + // header attribute containing modified header name + reflow2_headerAttrib : 'data-name', + // class name applied to thead labels + reflow2_labelClass : 'ui-table-cell-label', + // class name applied to first row thead label + reflow2_labelTop : 'ui-table-cell-label-top' + }, + init: function(table, thisWidget, c, wo) { + tablereflow.init2(table, c, wo); + }, + remove: function(table, c, wo){ + tablereflow.remove2(table, c, wo); + } + }); })(jQuery); diff --git a/js/widgets/widget-resizable.js b/js/widgets/widget-resizable.js index ac911687..5a07ea4b 100644 --- a/js/widgets/widget-resizable.js +++ b/js/widgets/widget-resizable.js @@ -1,393 +1,394 @@ /*! Widget: resizable - updated 6/26/2015 (v2.22.2) */ /*jshint browser:true, jquery:true, unused:false */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - resizableContainer : 'tablesorter-resizable-container', - resizableHandle : 'tablesorter-resizable-handle', - resizableNoSelect : 'tablesorter-disableSelection', - resizableStorage : 'tablesorter-resizable' -}); + $.extend(ts.css, { + resizableContainer : 'tablesorter-resizable-container', + resizableHandle : 'tablesorter-resizable-handle', + resizableNoSelect : 'tablesorter-disableSelection', + resizableStorage : 'tablesorter-resizable' + }); -// Add extra scroller css -$(function(){ - var s = ''; - $(s).appendTo('body'); -}); + // Add extra scroller css + $(function(){ + var s = ''; + $(s).appendTo('body'); + }); -ts.resizable = { - init : function( c, wo ) { - if ( c.$table.hasClass( 'hasResizable' ) ) { return; } - c.$table.addClass( 'hasResizable' ); + ts.resizable = { + init : function( c, wo ) { + if ( c.$table.hasClass( 'hasResizable' ) ) { return; } + c.$table.addClass( 'hasResizable' ); - var noResize, $header, column, storedSizes, tmp, - $table = c.$table, - $parent = $table.parent(), - marginTop = parseInt( $table.css( 'margin-top' ), 10 ), + var noResize, $header, column, storedSizes, tmp, + $table = c.$table, + $parent = $table.parent(), + marginTop = parseInt( $table.css( 'margin-top' ), 10 ), - // internal variables - vars = wo.resizable_ = { - useStorage : ts.storage && wo.resizable !== false, - $wrap : $parent, - mouseXPosition : 0, - $target : null, - $next : null, - overflow : $parent.css('overflow') === 'auto' || - $parent.css('overflow') === 'scroll' || - $parent.css('overflow-x') === 'auto' || - $parent.css('overflow-x') === 'scroll', - storedSizes : [] - }; + // internal variables + vars = wo.resizable_vars = { + useStorage : ts.storage && wo.resizable !== false, + $wrap : $parent, + mouseXPosition : 0, + $target : null, + $next : null, + overflow : $parent.css('overflow') === 'auto' || + $parent.css('overflow') === 'scroll' || + $parent.css('overflow-x') === 'auto' || + $parent.css('overflow-x') === 'scroll', + storedSizes : [] + }; - // set default widths - ts.resizableReset( c.table, true ); + // set default widths + ts.resizableReset( c.table, true ); - // now get measurements! - vars.tableWidth = $table.width(); - // attempt to autodetect - vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; + // now get measurements! + vars.tableWidth = $table.width(); + // attempt to autodetect + vars.fullWidth = Math.abs( $parent.width() - vars.tableWidth ) < 20; - /* - // Hacky method to determine if table width is set to "auto" - // http://stackoverflow.com/a/20892048/145346 - if ( !vars.fullWidth ) { - tmp = $table.width(); - $header = $table.wrap('').parent(); // temp variable - storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; - $table.css( 'margin-left', storedSizes + 50 ); - vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; - $table.css( 'margin-left', storedSizes ? storedSizes : '' ); - $header = null; - $table.unwrap(''); - } - */ - - if ( vars.useStorage && vars.overflow ) { - // save table width - ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); - tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; - ts.resizable.setWidth( $table, tmp, true ); - } - wo.resizable_.storedSizes = storedSizes = ( vars.useStorage ? - ts.storage( c.table, ts.css.resizableStorage ) : - [] ) || []; - ts.resizable.setWidths( c, wo, storedSizes ); - ts.resizable.updateStoredSizes( c, wo ); - - wo.$resizable_container = $( '
' ) - .css({ top : marginTop }) - .insertBefore( $table ); - // add container - for ( column = 0; column < c.columns; column++ ) { - $header = c.$headerIndexed[ column ]; - tmp = ts.getColumnData( c.table, c.headers, column ); - noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; - if ( !noResize ) { - $( '
' ) - .appendTo( wo.$resizable_container ) - .attr({ - 'data-column' : column, - 'unselectable' : 'on' - }) - .data( 'header', $header ) - .bind( 'selectstart', false ); + /* + // Hacky method to determine if table width is set to 'auto' + // http://stackoverflow.com/a/20892048/145346 + if ( !vars.fullWidth ) { + tmp = $table.width(); + $header = $table.wrap('').parent(); // temp variable + storedSizes = parseInt( $table.css( 'margin-left' ), 10 ) || 0; + $table.css( 'margin-left', storedSizes + 50 ); + vars.tableWidth = $header.width() > tmp ? 'auto' : tmp; + $table.css( 'margin-left', storedSizes ? storedSizes : '' ); + $header = null; + $table.unwrap(''); } - } - $table.one('tablesorter-initialized', function() { - ts.resizable.setHandlePosition( c, wo ); - ts.resizable.bindings( this.config, this.config.widgetOptions ); - }); - }, + */ - updateStoredSizes : function( c, wo ) { - var column, $header, - len = c.columns, - vars = wo.resizable_; - vars.storedSizes = []; - for ( column = 0; column < len; column++ ) { - $header = c.$headerIndexed[ column ]; - vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; - } - }, + if ( vars.useStorage && vars.overflow ) { + // save table width + ts.storage( c.table, 'tablesorter-table-original-css-width', vars.tableWidth ); + tmp = ts.storage( c.table, 'tablesorter-table-resized-width' ) || 'auto'; + ts.resizable.setWidth( $table, tmp, true ); + } + wo.resizable_vars.storedSizes = storedSizes = ( vars.useStorage ? + ts.storage( c.table, ts.css.resizableStorage ) : + [] ) || []; + ts.resizable.setWidths( c, wo, storedSizes ); + ts.resizable.updateStoredSizes( c, wo ); - setWidth : function( $el, width, overflow ) { - // overflow tables need min & max width set as well - $el.css({ - 'width' : width, - 'min-width' : overflow ? width : '', - 'max-width' : overflow ? width : '' - }); - }, - - setWidths : function( c, wo, storedSizes ) { - var column, $temp, - vars = wo.resizable_, - $extra = $( c.namespace + '_extra_headers' ), - $col = c.$table.children( 'colgroup' ).children( 'col' ); - storedSizes = storedSizes || vars.storedSizes || []; - // process only if table ID or url match - if ( storedSizes.length ) { + wo.$resizable_container = $( '
' ) + .css({ top : marginTop }) + .insertBefore( $table ); + // add container for ( column = 0; column < c.columns; column++ ) { - // set saved resizable widths - ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); - if ( $extra.length ) { - // stickyHeaders needs to modify min & max width as well - $temp = $extra.eq( column ).add( $col.eq( column ) ); - ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + $header = c.$headerIndexed[ column ]; + tmp = ts.getColumnData( c.table, c.headers, column ); + noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; + if ( !noResize ) { + $( '
' ) + .appendTo( wo.$resizable_container ) + .attr({ + 'data-column' : column, + 'unselectable' : 'on' + }) + .data( 'header', $header ) + .bind( 'selectstart', false ); } } - $temp = $( c.namespace + '_extra_table' ); - if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { - ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); - } - } - }, - - setHandlePosition : function( c, wo ) { - var startPosition, - hasScroller = ts.hasWidget( c.table, 'scroller' ), - tableHeight = c.$table.height(), - $handles = wo.$resizable_container.children(), - handleCenter = Math.floor( $handles.width() / 2 ); - - if ( hasScroller ) { - tableHeight = 0; - c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ - var $this = $(this); - // center table has a max-height set - tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); + $table.one('tablesorter-initialized', function() { + ts.resizable.setHandlePosition( c, wo ); + ts.resizable.bindings( this.config, this.config.widgetOptions ); }); - } - // subtract out table left position from resizable handles. Fixes #864 - startPosition = c.$table.position().left; - $handles.each( function() { - var $this = $(this), - column = parseInt( $this.attr( 'data-column' ), 10 ), - columns = c.columns - 1, - $header = $this.data( 'header' ); - if ( !$header ) { return; } // see #859 - if ( !$header.is(':visible') ) { - $this.hide(); - } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { - $this.css({ - display: 'inline-block', - height : tableHeight, - left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }, + + updateStoredSizes : function( c, wo ) { + var column, $header, + len = c.columns, + vars = wo.resizable_vars; + vars.storedSizes = []; + for ( column = 0; column < len; column++ ) { + $header = c.$headerIndexed[ column ]; + vars.storedSizes[ column ] = $header.is(':visible') ? $header.width() : 0; + } + }, + + setWidth : function( $el, width, overflow ) { + // overflow tables need min & max width set as well + $el.css({ + 'width' : width, + 'min-width' : overflow ? width : '', + 'max-width' : overflow ? width : '' + }); + }, + + setWidths : function( c, wo, storedSizes ) { + var column, $temp, + vars = wo.resizable_vars, + $extra = $( c.namespace + '_extra_headers' ), + $col = c.$table.children( 'colgroup' ).children( 'col' ); + storedSizes = storedSizes || vars.storedSizes || []; + // process only if table ID or url match + if ( storedSizes.length ) { + for ( column = 0; column < c.columns; column++ ) { + // set saved resizable widths + ts.resizable.setWidth( c.$headerIndexed[ column ], storedSizes[ column ], vars.overflow ); + if ( $extra.length ) { + // stickyHeaders needs to modify min & max width as well + $temp = $extra.eq( column ).add( $col.eq( column ) ); + ts.resizable.setWidth( $temp, storedSizes[ column ], vars.overflow ); + } + } + $temp = $( c.namespace + '_extra_table' ); + if ( $temp.length && !ts.hasWidget( c.table, 'scroller' ) ) { + ts.resizable.setWidth( $temp, c.$table.outerWidth(), vars.overflow ); + } + } + }, + + setHandlePosition : function( c, wo ) { + var startPosition, + hasScroller = ts.hasWidget( c.table, 'scroller' ), + tableHeight = c.$table.height(), + $handles = wo.$resizable_container.children(), + handleCenter = Math.floor( $handles.width() / 2 ); + + if ( hasScroller ) { + tableHeight = 0; + c.$table.closest( '.' + ts.css.scrollerWrap ).children().each(function(){ + var $this = $(this); + // center table has a max-height set + tableHeight += $this.filter('[style*="height"]').length ? $this.height() : $this.children('table').height(); }); } - }); - }, - - // prevent text selection while dragging resize bar - toggleTextSelection : function( c, toggle ) { - var namespace = c.namespace + 'tsresize'; - c.widgetOptions.resizable_.disabled = toggle; - $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); - if ( toggle ) { - $( 'body' ) - .attr( 'unselectable', 'on' ) - .bind( 'selectstart' + namespace, false ); - } else { - $( 'body' ) - .removeAttr( 'unselectable' ) - .unbind( 'selectstart' + namespace ); - } - }, - - bindings : function( c, wo ) { - var namespace = c.namespace + 'tsresize'; - wo.$resizable_container.children().bind( 'mousedown', function( event ) { - // save header cell and mouse position - var column, - vars = wo.resizable_, - $extras = $( c.namespace + '_extra_headers' ), - $header = $( event.target ).data( 'header' ); - - column = parseInt( $header.attr( 'data-column' ), 10 ); - vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); - vars.target = column; - - // if table is not as wide as it's parent, then resize the table - vars.$next = event.shiftKey || wo.resizable_targetLast ? - $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : - $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); - - column = parseInt( vars.$next.attr( 'data-column' ), 10 ); - vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); - vars.next = column; - - vars.mouseXPosition = event.pageX; - ts.resizable.updateStoredSizes( c, wo ); - ts.resizable.toggleTextSelection( c, true ); - }); - - $( document ) - .bind( 'mousemove' + namespace, function( event ) { - var vars = wo.resizable_; - // ignore mousemove if no mousedown - if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } - if ( wo.resizable_throttle ) { - clearTimeout( vars.timer ); - vars.timer = setTimeout( function() { - ts.resizable.mouseMove( c, wo, event ); - }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); - } else { - ts.resizable.mouseMove( c, wo, event ); + // subtract out table left position from resizable handles. Fixes #864 + startPosition = c.$table.position().left; + $handles.each( function() { + var $this = $(this), + column = parseInt( $this.attr( 'data-column' ), 10 ), + columns = c.columns - 1, + $header = $this.data( 'header' ); + if ( !$header ) { return; } // see #859 + if ( !$header.is(':visible') ) { + $this.hide(); + } else if ( column < columns || column === columns && wo.resizable_addLastColumn ) { + $this.css({ + display: 'inline-block', + height : tableHeight, + left : $header.position().left - startPosition + $header.outerWidth() - handleCenter + }); } - }) - .bind( 'mouseup' + namespace, function() { - if (!wo.resizable_.disabled) { return; } - ts.resizable.toggleTextSelection( c, false ); - ts.resizable.stopResize( c, wo ); - ts.resizable.setHandlePosition( c, wo ); }); + }, - // resizeEnd event triggered by scroller widget - $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }); - - // right click to reset columns to default widths - c.$table - .bind( 'columnUpdate' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }) - .find( 'thead:first' ) - .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) - .bind( 'contextmenu' + namespace, function() { - // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset - var allowClick = wo.resizable_.storedSizes.length === 0; - ts.resizableReset( c.table ); - ts.resizable.setHandlePosition( c, wo ); - wo.resizable_.storedSizes = []; - return allowClick; - }); - - }, - - mouseMove : function( c, wo, event ) { - if ( wo.resizable_.mouseXPosition === 0 || !wo.resizable_.$target ) { return; } - // resize columns - var column, - total = 0, - vars = wo.resizable_, - $next = vars.$next, - tar = vars.storedSizes[ vars.target ], - leftEdge = event.pageX - vars.mouseXPosition; - if ( vars.overflow ) { - if ( tar + leftEdge > 0 ) { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); - // update the entire table width - for ( column = 0; column < c.columns; column++ ) { - total += vars.storedSizes[ column ]; - } - ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); - } - if ( !$next.length ) { - // if expanding right-most column, scroll the wrapper - vars.$wrap[0].scrollLeft = c.$table.width(); - } - } else if ( vars.fullWidth ) { - vars.storedSizes[ vars.target ] += leftEdge; - vars.storedSizes[ vars.next ] -= leftEdge; - ts.resizable.setWidths( c, wo ); - } else { - vars.storedSizes[ vars.target ] += leftEdge; - ts.resizable.setWidths( c, wo ); - } - vars.mouseXPosition = event.pageX; - // dynamically update sticky header widths - c.$table.trigger('stickyHeadersUpdate'); - }, - - stopResize : function( c, wo ) { - var vars = wo.resizable_; - ts.resizable.updateStoredSizes( c, wo ); - if ( vars.useStorage ) { - // save all column widths - ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); - ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); - } - vars.mouseXPosition = 0; - vars.$target = vars.$next = null; - // will update stickyHeaders, just in case, see #912 - c.$table.trigger('stickyHeadersUpdate'); - } -}; - -// this widget saves the column widths if -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: "resizable", - priority: 40, - options: { - resizable : true, // save column widths to storage - resizable_addLastColumn : false, - resizable_widths : [], - resizable_throttle : false, // set to true (5ms) or any number 0-10 range - resizable_targetLast : false, - resizable_fullWidth : null - }, - init: function(table, thisWidget, c, wo) { - ts.resizable.init( c, wo ); - }, - remove: function( table, c, wo, refreshing ) { - if (wo.$resizable_container) { + // prevent text selection while dragging resize bar + toggleTextSelection : function( c, toggle ) { var namespace = c.namespace + 'tsresize'; - c.$table.add( $( c.namespace + '_extra_table' ) ) - .removeClass('hasResizable') - .children( 'thead' ).unbind( 'contextmenu' + namespace ); + c.widgetOptions.resizable_vars.disabled = toggle; + $( 'body' ).toggleClass( ts.css.resizableNoSelect, toggle ); + if ( toggle ) { + $( 'body' ) + .attr( 'unselectable', 'on' ) + .bind( 'selectstart' + namespace, false ); + } else { + $( 'body' ) + .removeAttr( 'unselectable' ) + .unbind( 'selectstart' + namespace ); + } + }, + + bindings : function( c, wo ) { + var namespace = c.namespace + 'tsresize'; + wo.$resizable_container.children().bind( 'mousedown', function( event ) { + // save header cell and mouse position + var column, + vars = wo.resizable_vars, + $extras = $( c.namespace + '_extra_headers' ), + $header = $( event.target ).data( 'header' ); + + column = parseInt( $header.attr( 'data-column' ), 10 ); + vars.$target = $header = $header.add( $extras.filter('[data-column="' + column + '"]') ); + vars.target = column; + + // if table is not as wide as it's parent, then resize the table + vars.$next = event.shiftKey || wo.resizable_targetLast ? + $header.parent().children().not( '.resizable-false' ).filter( ':last' ) : + $header.nextAll( ':not(.resizable-false)' ).eq( 0 ); + + column = parseInt( vars.$next.attr( 'data-column' ), 10 ); + vars.$next = vars.$next.add( $extras.filter('[data-column="' + column + '"]') ); + vars.next = column; + + vars.mouseXPosition = event.pageX; + ts.resizable.updateStoredSizes( c, wo ); + ts.resizable.toggleTextSelection( c, true ); + }); + + $( document ) + .bind( 'mousemove' + namespace, function( event ) { + var vars = wo.resizable_vars; + // ignore mousemove if no mousedown + if ( !vars.disabled || vars.mouseXPosition === 0 || !vars.$target ) { return; } + if ( wo.resizable_throttle ) { + clearTimeout( vars.timer ); + vars.timer = setTimeout( function() { + ts.resizable.mouseMove( c, wo, event ); + }, isNaN( wo.resizable_throttle ) ? 5 : wo.resizable_throttle ); + } else { + ts.resizable.mouseMove( c, wo, event ); + } + }) + .bind( 'mouseup' + namespace, function() { + if (!wo.resizable_vars.disabled) { return; } + ts.resizable.toggleTextSelection( c, false ); + ts.resizable.stopResize( c, wo ); + ts.resizable.setHandlePosition( c, wo ); + }); + + // resizeEnd event triggered by scroller widget + $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }); + + // right click to reset columns to default widths + c.$table + .bind( 'columnUpdate' + namespace, function() { + ts.resizable.setHandlePosition( c, wo ); + }) + .find( 'thead:first' ) + .add( $( c.namespace + '_extra_table' ).find( 'thead:first' ) ) + .bind( 'contextmenu' + namespace, function() { + // $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset + var allowClick = wo.resizable_vars.storedSizes.length === 0; + ts.resizableReset( c.table ); + ts.resizable.setHandlePosition( c, wo ); + wo.resizable_vars.storedSizes = []; + return allowClick; + }); + + }, + + mouseMove : function( c, wo, event ) { + if ( wo.resizable_vars.mouseXPosition === 0 || !wo.resizable_vars.$target ) { return; } + // resize columns + var column, + total = 0, + vars = wo.resizable_vars, + $next = vars.$next, + tar = vars.storedSizes[ vars.target ], + leftEdge = event.pageX - vars.mouseXPosition; + if ( vars.overflow ) { + if ( tar + leftEdge > 0 ) { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidth( vars.$target, vars.storedSizes[ vars.target ], true ); + // update the entire table width + for ( column = 0; column < c.columns; column++ ) { + total += vars.storedSizes[ column ]; + } + ts.resizable.setWidth( c.$table.add( $( c.namespace + '_extra_table' ) ), total ); + } + if ( !$next.length ) { + // if expanding right-most column, scroll the wrapper + vars.$wrap[0].scrollLeft = c.$table.width(); + } + } else if ( vars.fullWidth ) { + vars.storedSizes[ vars.target ] += leftEdge; + vars.storedSizes[ vars.next ] -= leftEdge; + ts.resizable.setWidths( c, wo ); + } else { + vars.storedSizes[ vars.target ] += leftEdge; + ts.resizable.setWidths( c, wo ); + } + vars.mouseXPosition = event.pageX; + // dynamically update sticky header widths + c.$table.trigger('stickyHeadersUpdate'); + }, + + stopResize : function( c, wo ) { + var vars = wo.resizable_vars; + ts.resizable.updateStoredSizes( c, wo ); + if ( vars.useStorage ) { + // save all column widths + ts.storage( c.table, ts.css.resizableStorage, vars.storedSizes ); + ts.storage( c.table, 'tablesorter-table-resized-width', c.$table.width() ); + } + vars.mouseXPosition = 0; + vars.$target = vars.$next = null; + // will update stickyHeaders, just in case, see #912 + c.$table.trigger('stickyHeadersUpdate'); + } + }; + + // this widget saves the column widths if + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'resizable', + priority: 40, + options: { + resizable : true, // save column widths to storage + resizable_addLastColumn : false, + resizable_widths : [], + resizable_throttle : false, // set to true (5ms) or any number 0-10 range + resizable_targetLast : false, + resizable_fullWidth : null + }, + init: function(table, thisWidget, c, wo) { + ts.resizable.init( c, wo ); + }, + remove: function( table, c, wo, refreshing ) { + if (wo.$resizable_container) { + var namespace = c.namespace + 'tsresize'; + c.$table.add( $( c.namespace + '_extra_table' ) ) + .removeClass('hasResizable') + .children( 'thead' ) + .unbind( 'contextmenu' + namespace ); wo.$resizable_container.remove(); - ts.resizable.toggleTextSelection( c, false ); - ts.resizableReset( table, refreshing ); - $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); - } - } -}); - -ts.resizableReset = function( table, refreshing ) { - $( table ).each(function(){ - var index, $t, - c = this.config, - wo = c && c.widgetOptions, - vars = wo.resizable_; - if ( table && c && c.$headerIndexed.length ) { - // restore the initial table width - if ( vars.overflow && vars.tableWidth ) { - ts.resizable.setWidth( c.$table, vars.tableWidth, true ); - if ( vars.useStorage ) { - ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); - } - } - for ( index = 0; index < c.columns; index++ ) { - $t = c.$headerIndexed[ index ]; - if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { - ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); - } else if ( !$t.hasClass( 'resizable-false' ) ) { - // don't clear the width of any column that is not resizable - ts.resizable.setWidth( $t, '', vars.overflow ); - } - } - - // reset stickyHeader widths - c.$table.trigger( 'stickyHeadersUpdate' ); - if ( ts.storage && !refreshing ) { - ts.storage( this, ts.css.resizableStorage, {} ); + ts.resizable.toggleTextSelection( c, false ); + ts.resizableReset( table, refreshing ); + $( document ).unbind( 'mousemove' + namespace + ' mouseup' + namespace ); } } }); -}; + + ts.resizableReset = function( table, refreshing ) { + $( table ).each(function(){ + var index, $t, + c = this.config, + wo = c && c.widgetOptions, + vars = wo.resizable_vars; + if ( table && c && c.$headerIndexed.length ) { + // restore the initial table width + if ( vars.overflow && vars.tableWidth ) { + ts.resizable.setWidth( c.$table, vars.tableWidth, true ); + if ( vars.useStorage ) { + ts.storage( table, 'tablesorter-table-resized-width', 'auto' ); + } + } + for ( index = 0; index < c.columns; index++ ) { + $t = c.$headerIndexed[ index ]; + if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { + ts.resizable.setWidth( $t, wo.resizable_widths[ index ], vars.overflow ); + } else if ( !$t.hasClass( 'resizable-false' ) ) { + // don't clear the width of any column that is not resizable + ts.resizable.setWidth( $t, '', vars.overflow ); + } + } + + // reset stickyHeader widths + c.$table.trigger( 'stickyHeadersUpdate' ); + if ( ts.storage && !refreshing ) { + ts.storage( this, ts.css.resizableStorage, {} ); + } + } + }); + }; })( jQuery, window ); diff --git a/js/widgets/widget-saveSort.js b/js/widgets/widget-saveSort.js index d34e2ad5..b6b751a0 100644 --- a/js/widgets/widget-saveSort.js +++ b/js/widgets/widget-saveSort.js @@ -1,68 +1,68 @@ /*! Widget: saveSort */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -// this widget saves the last sort only if the -// saveSort widget option is true AND the -// $.tablesorter.storage function is included -// ************************** -ts.addWidget({ - id: 'saveSort', - priority: 20, - options: { - saveSort : true - }, - init: function(table, thisWidget, c, wo) { - // run widget format before all other widgets are applied to the table - thisWidget.format(table, c, wo, true); - }, - format: function(table, c, wo, init) { - var stored, time, - $table = c.$table, - saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true - sortList = { "sortList" : c.sortList }; - if (c.debug) { - time = new Date(); - } - if ($table.hasClass('hasSaveSort')) { - if (saveSort && table.hasInitialized && ts.storage) { - ts.storage( table, 'tablesorter-savesort', sortList ); - if (c.debug) { - ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + // this widget saves the last sort only if the + // saveSort widget option is true AND the + // $.tablesorter.storage function is included + // ************************** + ts.addWidget({ + id: 'saveSort', + priority: 20, + options: { + saveSort : true + }, + init: function(table, thisWidget, c, wo) { + // run widget format before all other widgets are applied to the table + thisWidget.format(table, c, wo, true); + }, + format: function(table, c, wo, init) { + var stored, time, + $table = c.$table, + saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true + sortList = { 'sortList' : c.sortList }; + if (c.debug) { + time = new Date(); + } + if ($table.hasClass('hasSaveSort')) { + if (saveSort && table.hasInitialized && ts.storage) { + ts.storage( table, 'tablesorter-savesort', sortList ); + if (c.debug) { + ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + } + } + } else { + // set table sort on initial run of the widget + $table.addClass('hasSaveSort'); + sortList = ''; + // get data + if (ts.storage) { + stored = ts.storage( table, 'tablesorter-savesort' ); + sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; + if (c.debug) { + ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); + } + $table.bind('saveSortReset', function(event) { + event.stopPropagation(); + ts.storage( table, 'tablesorter-savesort', '' ); + }); + } + // init is true when widget init is run, this will run this widget before all other widgets have initialized + // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. + if (init && sortList && sortList.length > 0) { + c.sortList = sortList; + } else if (table.hasInitialized && sortList && sortList.length > 0) { + // update sort change + $table.trigger('sorton', [ sortList ]); } } - } else { - // set table sort on initial run of the widget - $table.addClass('hasSaveSort'); - sortList = ''; - // get data - if (ts.storage) { - stored = ts.storage( table, 'tablesorter-savesort' ); - sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : ''; - if (c.debug) { - ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); - } - $table.bind('saveSortReset', function(event) { - event.stopPropagation(); - ts.storage( table, 'tablesorter-savesort', '' ); - }); - } - // init is true when widget init is run, this will run this widget before all other widgets have initialized - // this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice. - if (init && sortList && sortList.length > 0) { - c.sortList = sortList; - } else if (table.hasInitialized && sortList && sortList.length > 0) { - // update sort change - $table.trigger('sorton', [sortList]); - } + }, + remove: function(table, c) { + c.$table.removeClass('hasSaveSort'); + // clear storage + if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } } - }, - remove: function(table, c) { - c.$table.removeClass('hasSaveSort'); - // clear storage - if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); } - } -}); + }); })(jQuery); diff --git a/js/widgets/widget-scroller.js b/js/widgets/widget-scroller.js index 555d4730..f43ea8d3 100644 --- a/js/widgets/widget-scroller.js +++ b/js/widgets/widget-scroller.js @@ -33,884 +33,884 @@ */ /*jshint browser:true, jquery:true, unused:false */ ;( function( $, window ) { -'use strict'; + 'use strict'; -var ts = $.tablesorter, - tscss = ts.css; + var ts = $.tablesorter, + tscss = ts.css; -$.extend( ts.css, { - scrollerWrap : 'tablesorter-scroller', - scrollerHeader : 'tablesorter-scroller-header', - scrollerTable : 'tablesorter-scroller-table', - scrollerFooter : 'tablesorter-scroller-footer', - scrollerFixed : 'tablesorter-scroller-fixed', - scrollerFixedPanel : 'tablesorter-scroller-fixed-panel', - scrollerHasFix : 'tablesorter-scroller-has-fixed-columns', - scrollerHideColumn : 'tablesorter-scroller-hidden-column', - scrollerHideElement : 'tablesorter-scroller-hidden', - scrollerSpacerRow : 'tablesorter-scroller-spacer', - scrollerBarSpacer : 'tablesorter-scroller-bar-spacer', - scrollerAddedHeight : 'tablesorter-scroller-added-height', - scrollerHack : 'tablesorter-scroller-scrollbar-hack', - // class name on table cannot start with 'tablesorter-' or the - // suffix "scroller-rtl" will match as a theme name - scrollerRtl : 'ts-scroller-rtl' -}); + $.extend( ts.css, { + scrollerWrap : 'tablesorter-scroller', + scrollerHeader : 'tablesorter-scroller-header', + scrollerTable : 'tablesorter-scroller-table', + scrollerFooter : 'tablesorter-scroller-footer', + scrollerFixed : 'tablesorter-scroller-fixed', + scrollerFixedPanel : 'tablesorter-scroller-fixed-panel', + scrollerHasFix : 'tablesorter-scroller-has-fixed-columns', + scrollerHideColumn : 'tablesorter-scroller-hidden-column', + scrollerHideElement : 'tablesorter-scroller-hidden', + scrollerSpacerRow : 'tablesorter-scroller-spacer', + scrollerBarSpacer : 'tablesorter-scroller-bar-spacer', + scrollerAddedHeight : 'tablesorter-scroller-added-height', + scrollerHack : 'tablesorter-scroller-scrollbar-hack', + // class name on table cannot start with 'tablesorter-' or the + // suffix 'scroller-rtl' will match as a theme name + scrollerRtl : 'ts-scroller-rtl' + }); -ts.addWidget({ - id : 'scroller', - priority : 60, // run after the filter widget - options : { - scroller_height : 300, - // pop table header into view while scrolling up the page - scroller_jumpToHeader : true, - // scroll tbody to top after sorting - scroller_upAfterSort : true, - // set number of fixed columns - scroller_fixedColumns : 0, - // add hover highlighting to the fixed column (disable if it causes slowing) - scroller_rowHighlight : 'hover', - // add a fixed column overlay for styling - scroller_addFixedOverlay : false, - // In tablesorter v2.19.0 the scroll bar width is auto-detected - // add a value here to override the auto-detected setting - scroller_barWidth : null - }, - format : function( table, c, wo ) { - if ( !c.isScrolling ) { - // initialize here instead of in widget init to give the - // filter widget time to finish building the filter row - ts.scroller.setup( c, wo ); + ts.addWidget({ + id : 'scroller', + priority : 60, // run after the filter widget + options : { + scroller_height : 300, + // pop table header into view while scrolling up the page + scroller_jumpToHeader : true, + // scroll tbody to top after sorting + scroller_upAfterSort : true, + // set number of fixed columns + scroller_fixedColumns : 0, + // add hover highlighting to the fixed column (disable if it causes slowing) + scroller_rowHighlight : 'hover', + // add a fixed column overlay for styling + scroller_addFixedOverlay : false, + // In tablesorter v2.19.0 the scroll bar width is auto-detected + // add a value here to override the auto-detected setting + scroller_barWidth : null + }, + format : function( table, c, wo ) { + if ( !c.isScrolling ) { + // initialize here instead of in widget init to give the + // filter widget time to finish building the filter row + ts.scroller.setup( c, wo ); + } + }, + remove : function( table, c, wo ) { + ts.scroller.remove( c, wo ); } - }, - remove : function( table, c, wo ) { - ts.scroller.remove( c, wo ); - } -}); + }); -/* Add window resizeEnd event */ -ts.window_resize = function() { - if ( ts.timer_resize ) { - clearTimeout( ts.timer_resize ); - } - ts.timer_resize = setTimeout( function() { - $( window ).trigger( 'resizeEnd' ); - }, 250 ); -}; - -// Add extra scroller css -$( function() { - var style = ''; - $( style ).appendTo( 'body' ); -}); - -ts.scroller = { - - // Ugh.. Firefox misbehaves, so it needs to be detected - isFirefox : navigator.userAgent.toLowerCase().indexOf( 'firefox' ) > -1, - // old IE needs a wrap to hide the fixed column scrollbar; http://stackoverflow.com/a/24408672/145346 - isOldIE : document.all && !window.atob, - isIE : ( document.all && !window.atob ) || navigator.appVersion.indexOf( 'Trident/' ) > 0, - // http://stackoverflow.com/questions/7944460/detect-safari-browser - needed to position scrolling body - // when the table is set up in RTL direction - isSafari : navigator.userAgent.toLowerCase().indexOf( 'safari' ) > -1 && - navigator.userAgent.toLowerCase().indexOf( 'chrome' ) === -1, - - hasScrollBar : function( $target, checkWidth ) { - if ( checkWidth ) { - return $target.get(0).scrollWidth > $target.width(); - } else { - return $target.get(0).scrollHeight > $target.height(); + /* Add window resizeEnd event */ + ts.window_resize = function() { + if ( ts.timer_resize ) { + clearTimeout( ts.timer_resize ); } - }, + ts.timer_resize = setTimeout( function() { + $( window ).trigger( 'resizeEnd' ); + }, 250 ); + }; - setWidth : function( $el, width ) { - $el.css({ - 'width' : width, - 'min-width' : width, - 'max-width' : width - }); - }, + // Add extra scroller css + $( function() { + var style = ''; + $( style ).appendTo( 'body' ); + }); - setup : function( c, wo ) { - var maxHt, tbHt, $hdr, $t, $hCells, $fCells, $tableWrap, events, tmp, - $win = $( window ), - tsScroller = ts.scroller, - namespace = c.namespace + 'tsscroller', - $foot = $(), - // c.namespace contains a unique tablesorter ID, per table - id = c.namespace.slice( 1 ) + 'tsscroller', - $table = c.$table; + ts.scroller = { - // force config.widthFixed option - this helps maintain proper alignment across cloned tables - c.widthFixed = true; + // Ugh.. Firefox misbehaves, so it needs to be detected + isFirefox : navigator.userAgent.toLowerCase().indexOf( 'firefox' ) > -1, + // old IE needs a wrap to hide the fixed column scrollbar; http://stackoverflow.com/a/24408672/145346 + isOldIE : document.all && !window.atob, + isIE : ( document.all && !window.atob ) || navigator.appVersion.indexOf( 'Trident/' ) > 0, + // http://stackoverflow.com/questions/7944460/detect-safari-browser - needed to position scrolling body + // when the table is set up in RTL direction + isSafari : navigator.userAgent.toLowerCase().indexOf( 'safari' ) > -1 && + navigator.userAgent.toLowerCase().indexOf( 'chrome' ) === -1, - wo.scroller_calcWidths = []; - wo.scroller_saved = [ 0, 0 ]; - wo.scroller_isBusy = true; + hasScrollBar : function( $target, checkWidth ) { + if ( checkWidth ) { + return $target.get(0).scrollWidth > $target.width(); + } else { + return $target.get(0).scrollHeight > $target.height(); + } + }, - // set scrollbar width & allow setting width to zero - wo.scroller_barSetWidth = wo.scroller_barWidth !== null ? - wo.scroller_barWidth : - ( tsScroller.getBarWidth() || 15 ); + setWidth : function( $el, width ) { + $el.css({ + 'width' : width, + 'min-width' : width, + 'max-width' : width + }); + }, - maxHt = wo.scroller_height || 300; + // modified from http://davidwalsh.name/detect-scrollbar-width + getBarWidth : function() { + var $div = $( '
' ).css({ + 'position' : 'absolute', + 'top' : '-9999px', + 'left' : 0, + 'width' : '100px', + 'height' : '100px', + 'overflow' : 'scroll', + 'visibility' : 'hidden' + }).appendTo( 'body' ), + div = $div[0], + barWidth = div.offsetWidth - div.clientWidth; + $div.remove(); + return barWidth; + }, - $hdr = $( '
) - ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], - ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || - $( this ).hasClass( 'filter-parsed' ); - }).get(); - - for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { - vars.functions[ columnIndex ] = - ts.getColumnData( table, wo.filter_functions, columnIndex ); - vars.defaultColFilter[ columnIndex ] = - ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; - vars.excludeFilter[ columnIndex ] = - ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); - } - - if ( c.debug ) { - ts.log( 'Filter: Starting filter widget search', filters ); - time = new Date(); - } - // filtered rows count - c.filteredRows = 0; - c.totalRows = 0; - // combindedFilters are undefined on init - combinedFilters = ( storedFilters || [] ).join( '' ); - - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); - // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! - // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); - columnIndex = c.columns; - // convert stored rows into a jQuery object - norm_rows = c.cache[ tbodyIndex ].normalized; - $rows = $( $.map( norm_rows, function( el ) { - return el[ columnIndex ].$row.get(); - }) ); - - if ( combinedFilters === '' || wo.filter_serversideFiltering ) { - $rows - .removeClass( wo.filter_filteredRow ) - .not( '.' + c.cssChildRow ) - .css( 'display', '' ); - } else { - // filter out child rows - $rows = $rows.not( '.' + c.cssChildRow ); - len = $rows.length; - - if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || - typeof filters[c.columns] !== 'undefined' ) { - data.anyMatchFlag = true; - data.anyMatchFilter = '' + ( - filters[ c.columns ] || - wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || - '' - ); - if ( wo.filter_columnAnyMatch ) { - // specific columns search - query = data.anyMatchFilter.split( regex.andSplit ); - injected = false; - for ( indx = 0; indx < query.length; indx++ ) { - res = query[ indx ].split( ':' ); - if ( res.length > 1 ) { - // make the column a one-based index ( non-developers start counting from one :P ) - id = parseInt( res[0], 10 ) - 1; - if ( id >= 0 && id < c.columns ) { // if id is an integer - filters[ id ] = res[1]; - query.splice( indx, 1 ); - indx--; - injected = true; - } - } - } - if ( injected ) { - data.anyMatchFilter = query.join( ' && ' ); - } - } - } - - // optimize searching only through already filtered rows - see #313 - searchFiltered = wo.filter_searchFiltered; - lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; - if ( searchFiltered ) { - // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 - for ( indx = 0; indx < columnIndex + 1; indx++ ) { - val = filters[indx] || ''; - // break out of loop if we've already determined not to search filtered rows - if ( !searchFiltered ) { indx = columnIndex; } - // search already filtered rows if... - searchFiltered = searchFiltered && lastSearch.length && - // there are no changes from beginning of filter - val.indexOf( lastSearch[indx] || '' ) === 0 && - // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string - !regex.alreadyFiltered.test( val ) && - // if we are not doing exact matches, using '|' ( logical or ) or not '!' - !/[=\"\|!]/.test( val ) && - // don't search only filtered if the value is negative - // ( '> -10' => '> -100' will ignore hidden rows ) - !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && - // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 - !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && - !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); - } - } - notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; - // can't search when all rows are hidden - this happens when looking for exact matches - if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } - if ( c.debug ) { - ts.log( 'Filter: Searching through ' + - ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); - } - if ( data.anyMatchFlag ) { - if ( c.sortLocaleCompare ) { - // replace accents - data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); - } - if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { - data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); - // clear search filtered flag because default filters are not saved to the last search - searchFiltered = false; - } - // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true - // when c.ignoreCase is true, the cache contains all lower case data - data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? - data.anyMatchFilter : - data.anyMatchFilter.toLowerCase(); - } - - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - - txt = $rows[ rowIndex ].className; - // the first row can never be a child row - isChild = rowIndex && regex.child.test( txt ); - // skip child rows & already filtered rows - if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { - continue; - } - - data.$row = $rows.eq( rowIndex ); - data.cacheArray = norm_rows[ rowIndex ]; - rowData = data.cacheArray[ c.columns ]; - data.rawArray = rowData.raw; - data.childRowText = ''; - - if ( !wo.filter_childByColumn ) { - txt = ''; - // child row cached text - childRow = rowData.child; - // so, if 'table.config.widgetOptions.filter_childRows' is true and there is - // a match anywhere in the child row, then it will make the row visible - // checked here so the option can be changed dynamically - for ( indx = 0; indx < childRow.length; indx++ ) { - txt += ' ' + childRow[indx].join( '' ) || ''; - } - data.childRowText = wo.filter_childRows ? - ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : - ''; - } - - showRow = ts.filter.processRow( c, data, vars ); - childRow = rowData.$row.filter( ':gt( 0 )' ); - - if ( wo.filter_childRows && childRow.length ) { - if ( wo.filter_childByColumn ) { - // cycle through each child row - for ( indx = 0; indx < childRow.length; indx++ ) { - data.$row = childRow.eq( indx ); - data.cacheArray = rowData.child[ indx ]; - data.rawArray = data.cacheArray; - // use OR comparison on child rows - showRow = showRow || ts.filter.processRow( c, data, vars ); - } - } - childRow.toggleClass( wo.filter_filteredRow, !showRow ); - } - - rowData.$row - .toggleClass( wo.filter_filteredRow, !showRow )[0] - .display = showRow ? '' : 'none'; - } - } - c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; - c.totalRows += $rows.length; - ts.processTbody( table, $tbody, false ); - } - c.lastCombinedFilter = combinedFilters; // save last search - // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) - c.lastSearch = storedFilters; - c.$table.data( 'lastSearch', storedFilters ); - if ( wo.filter_saveFilters && ts.storage ) { - ts.storage( table, 'tablesorter-filters', storedFilters ); - } - if ( c.debug ) { - ts.benchmark( 'Completed filter widget search', time ); - } - if ( wo.filter_initialized ) { - c.$table.trigger( 'filterEnd', c ); - } - setTimeout( function() { - c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied - }, 0 ); - }, - getOptionSource: function( table, column, onlyAvail ) { - table = $( table )[0]; - var cts, txt, indx, len, - c = table.config, - wo = c.widgetOptions, - parsed = [], - arry = false, - source = wo.filter_selectSource, - last = c.$table.data( 'lastSearch' ) || [], - fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); - - if ( onlyAvail && last[column] !== '' ) { - onlyAvail = false; - } - - // filter select source option - if ( fxn === true ) { - // OVERALL source - arry = source( table, column, onlyAvail ); - } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { - // selectSource is a jQuery object or string of options - return fxn; - } else if ( $.isArray( fxn ) ) { - arry = fxn; - } else if ( $.type( source ) === 'object' && fxn ) { - // custom select source function for a SPECIFIC COLUMN - arry = fxn( table, column, onlyAvail ); - } - if ( arry === false ) { - // fall back to original method - arry = ts.filter.getOptions( table, column, onlyAvail ); - } - - // get unique elements and sort the list - // if $.tablesorter.sortText exists ( not in the original tablesorter ), - // then natural sort the list otherwise use a basic sort - arry = $.grep( arry, function( value, indx ) { - return $.inArray( value, arry ) === indx; - }); - - if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { - // unsorted select options - return arry; - } else { - len = arry.length; - // parse select option values - for ( indx = 0; indx < len; indx++ ) { - txt = arry[ indx ]; - // parse array data using set column parser; this DOES NOT pass the original - // table cell to the parser format function - parsed.push({ - t : txt, - // check parser length - fixes #934 - p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + .find( '.' + tscss.header ) + .filter( '[data-column]' ) + .filter( function() { + return columns[ $( this ).data( 'column' ) ] !== ''; + }) : ''; + ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); }); } - // sort parsed select options - cts = c.textSorter || ''; - parsed.sort( function( a, b ) { - // sortNatural breaks if you don't pass it strings - var x = a.p.toString(), - y = b.p.toString(); - if ( $.isFunction( cts ) ) { - // custom OVERALL text sorter - return cts( x, y, true, column, table ); - } else if ( typeof( cts ) === 'object' && cts.hasOwnProperty( column ) ) { - // custom text sorter for a SPECIFIC COLUMN - return cts[column]( x, y, true, column, table ); - } else if ( ts.sortNatural ) { - // fall back to natural sort - return ts.sortNatural( x, y ); + // set filtered rows count ( intially unfiltered ) + c.filteredRows = c.totalRows; + + // add default values + txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( /\s+/g, ' ' ) ) + .bind( txt, function() { + // redefine 'wo' as it does not update properly inside this callback + var wo = this.config.widgetOptions; + filters = ts.filter.setDefaults( table, c, wo ) || []; + if ( filters.length ) { + // prevent delayInit from triggering a cache build if filters are empty + if ( !( c.delayInit && filters.join( '' ) === '' ) ) { + ts.setFilters( table, filters, true ); + } } - // using an older version! do a basic sort - return true; + c.$table.trigger( 'filterFomatterUpdate' ); + // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers + setTimeout( function() { + if ( !wo.filter_initialized ) { + ts.filter.filterInitComplete( c ); + } + }, 100 ); }); - // rebuild arry from sorted parsed data - arry = []; - len = parsed.length; - for ( indx = 0; indx < len; indx++ ) { - arry.push( parsed[indx].t ); + // if filter widget is added after pager has initialized; then set filter init flag + if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { + c.$table.trigger( 'filterFomatterUpdate' ); + setTimeout( function() { + ts.filter.filterInitComplete( c ); + }, 100 ); + } + }, + // $cell parameter, but not the config, is passed to the filter_formatters, + // so we have to work with it instead + formatterUpdated: function( $cell, column ) { + var wo = $cell.closest( 'table' )[0].config.widgetOptions; + if ( !wo.filter_initialized ) { + // add updates by column since this function + // may be called numerous times before initialization + wo.filter_formatterInit[ column ] = 1; + } + }, + filterInitComplete: function( c ) { + var indx, len, + wo = c.widgetOptions, + count = 0, + completed = function() { + wo.filter_initialized = true; + c.$table.trigger( 'filterInit', c ); + ts.filter.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); + }; + if ( $.isEmptyObject( wo.filter_formatter ) ) { + completed(); + } else { + len = wo.filter_formatterInit.length; + for ( indx = 0; indx < len; indx++ ) { + if ( wo.filter_formatterInit[ indx ] === 1 ) { + count++; + } + } + clearTimeout( wo.filter_initTimer ); + if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { + // filter widget initialized + completed(); + } else if ( !wo.filter_initialized ) { + // fall back in case a filter_formatter doesn't call + // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off + wo.filter_initTimer = setTimeout( function() { + completed(); + }, 500 ); + } + } + }, + setDefaults: function( table, c, wo ) { + var isArray, saved, indx, col, $filters, + // get current ( default ) filters + filters = ts.getFilters( table ) || []; + if ( wo.filter_saveFilters && ts.storage ) { + saved = ts.storage( table, 'tablesorter-filters' ) || []; + isArray = $.isArray( saved ); + // make sure we're not just getting an empty array + if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { + filters = saved; + } + } + // if no filters saved, then check default settings + if ( filters.join( '' ) === '' ) { + // allow adding default setting to external filters + $filters = c.$headers.add( wo.filter_$externalFilters ) + .filter( '[' + wo.filter_defaultAttrib + ']' ); + for ( indx = 0; indx <= c.columns; indx++ ) { + // include data-column='all' external filters + col = indx === c.columns ? 'all' : indx; + filters[indx] = $filters + .filter( '[data-column="' + col + '"]' ) + .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; + } + } + c.$table.data( 'lastSearch', filters ); + return filters; + }, + parseFilter: function( c, filter, column, parsed ) { + return parsed ? c.parsers[column].format( filter, c.table, [], column ) : filter; + }, + buildRow: function( table, c, wo ) { + var col, column, $header, buildSelect, disabled, name, ffxn, tmp, + // c.columns defined in computeThIndexes() + cellFilter = wo.filter_cellFilter, + columns = c.columns, + arry = $.isArray( cellFilter ), + buildFilter = '
) + ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], + ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || + $( this ).hasClass( 'filter-parsed' ); + }).get(); + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + vars.functions[ columnIndex ] = + ts.getColumnData( table, wo.filter_functions, columnIndex ); + vars.defaultColFilter[ columnIndex ] = + ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; + vars.excludeFilter[ columnIndex ] = + ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); + } + + if ( c.debug ) { + ts.log( 'Filter: Starting filter widget search', filters ); + time = new Date(); + } + // filtered rows count + c.filteredRows = 0; + c.totalRows = 0; + // combindedFilters are undefined on init + combinedFilters = ( storedFilters || [] ).join( '' ); + + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); + // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! + // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); + columnIndex = c.columns; + // convert stored rows into a jQuery object + norm_rows = c.cache[ tbodyIndex ].normalized; + $rows = $( $.map( norm_rows, function( el ) { + return el[ columnIndex ].$row.get(); + }) ); + + if ( combinedFilters === '' || wo.filter_serversideFiltering ) { + $rows + .removeClass( wo.filter_filteredRow ) + .not( '.' + c.cssChildRow ) + .css( 'display', '' ); + } else { + // filter out child rows + $rows = $rows.not( '.' + c.cssChildRow ); + len = $rows.length; + + if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || + typeof filters[c.columns] !== 'undefined' ) { + data.anyMatchFlag = true; + data.anyMatchFilter = '' + ( + filters[ c.columns ] || + wo.filter_$anyMatch && ts.filter.getLatestSearch( wo.filter_$anyMatch ).val() || + '' + ); + if ( wo.filter_columnAnyMatch ) { + // specific columns search + query = data.anyMatchFilter.split( regex.andSplit ); + injected = false; + for ( indx = 0; indx < query.length; indx++ ) { + res = query[ indx ].split( ':' ); + if ( res.length > 1 ) { + // make the column a one-based index ( non-developers start counting from one :P ) + id = parseInt( res[0], 10 ) - 1; + if ( id >= 0 && id < c.columns ) { // if id is an integer + filters[ id ] = res[1]; + query.splice( indx, 1 ); + indx--; + injected = true; + } + } + } + if ( injected ) { + data.anyMatchFilter = query.join( ' && ' ); + } + } + } + + // optimize searching only through already filtered rows - see #313 + searchFiltered = wo.filter_searchFiltered; + lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; + if ( searchFiltered ) { + // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 + for ( indx = 0; indx < columnIndex + 1; indx++ ) { + val = filters[indx] || ''; + // break out of loop if we've already determined not to search filtered rows + if ( !searchFiltered ) { indx = columnIndex; } + // search already filtered rows if... + searchFiltered = searchFiltered && lastSearch.length && + // there are no changes from beginning of filter + val.indexOf( lastSearch[indx] || '' ) === 0 && + // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string + !regex.alreadyFiltered.test( val ) && + // if we are not doing exact matches, using '|' ( logical or ) or not '!' + !/[=\"\|!]/.test( val ) && + // don't search only filtered if the value is negative + // ( '> -10' => '> -100' will ignore hidden rows ) + !( /(>=?\s*-\d)/.test( val ) || /(<=?\s*\d)/.test( val ) ) && + // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 + !( val !== '' && c.$filters && c.$filters.eq( indx ).find( 'select' ).length && + !c.$headerIndexed[indx].hasClass( 'filter-match' ) ); + } + } + notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; + // can't search when all rows are hidden - this happens when looking for exact matches + if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } + if ( c.debug ) { + ts.log( 'Filter: Searching through ' + + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); + } + if ( data.anyMatchFlag ) { + if ( c.sortLocaleCompare ) { + // replace accents + data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); + } + if ( wo.filter_defaultFilter && regex.iQuery.test( vars.defaultAnyFilter ) ) { + data.anyMatchFilter = ts.filter.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); + // clear search filtered flag because default filters are not saved to the last search + searchFiltered = false; + } + // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true + // when c.ignoreCase is true, the cache contains all lower case data + data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? + data.anyMatchFilter : + data.anyMatchFilter.toLowerCase(); + } + + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + + txt = $rows[ rowIndex ].className; + // the first row can never be a child row + isChild = rowIndex && regex.child.test( txt ); + // skip child rows & already filtered rows + if ( isChild || ( searchFiltered && regex.filtered.test( txt ) ) ) { + continue; + } + + data.$row = $rows.eq( rowIndex ); + data.cacheArray = norm_rows[ rowIndex ]; + rowData = data.cacheArray[ c.columns ]; + data.rawArray = rowData.raw; + data.childRowText = ''; + + if ( !wo.filter_childByColumn ) { + txt = ''; + // child row cached text + childRow = rowData.child; + // so, if 'table.config.widgetOptions.filter_childRows' is true and there is + // a match anywhere in the child row, then it will make the row visible + // checked here so the option can be changed dynamically + for ( indx = 0; indx < childRow.length; indx++ ) { + txt += ' ' + childRow[indx].join( '' ) || ''; + } + data.childRowText = wo.filter_childRows ? + ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : + ''; + } + + showRow = ts.filter.processRow( c, data, vars ); + childRow = rowData.$row.filter( ':gt( 0 )' ); + + if ( wo.filter_childRows && childRow.length ) { + if ( wo.filter_childByColumn ) { + // cycle through each child row + for ( indx = 0; indx < childRow.length; indx++ ) { + data.$row = childRow.eq( indx ); + data.cacheArray = rowData.child[ indx ]; + data.rawArray = data.cacheArray; + // use OR comparison on child rows + showRow = showRow || ts.filter.processRow( c, data, vars ); + } + } + childRow.toggleClass( wo.filter_filteredRow, !showRow ); + } + + rowData.$row + .toggleClass( wo.filter_filteredRow, !showRow )[0] + .display = showRow ? '' : 'none'; + } + } + c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; + c.totalRows += $rows.length; + ts.processTbody( table, $tbody, false ); + } + c.lastCombinedFilter = combinedFilters; // save last search + // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) + c.lastSearch = storedFilters; + c.$table.data( 'lastSearch', storedFilters ); + if ( wo.filter_saveFilters && ts.storage ) { + ts.storage( table, 'tablesorter-filters', storedFilters ); + } + if ( c.debug ) { + ts.benchmark( 'Completed filter widget search', time ); + } + if ( wo.filter_initialized ) { + c.$table.trigger( 'filterEnd', c ); + } + setTimeout( function() { + c.$table.trigger( 'applyWidgets' ); // make sure zebra widget is applied + }, 0 ); + }, + getOptionSource: function( table, column, onlyAvail ) { + table = $( table )[0]; + var cts, txt, indx, len, + c = table.config, + wo = c.widgetOptions, + parsed = [], + arry = false, + source = wo.filter_selectSource, + last = c.$table.data( 'lastSearch' ) || [], + fxn = $.isFunction( source ) ? true : ts.getColumnData( table, source, column ); + + if ( onlyAvail && last[column] !== '' ) { + onlyAvail = false; + } + + // filter select source option + if ( fxn === true ) { + // OVERALL source + arry = source( table, column, onlyAvail ); + } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { + // selectSource is a jQuery object or string of options + return fxn; + } else if ( $.isArray( fxn ) ) { + arry = fxn; + } else if ( $.type( source ) === 'object' && fxn ) { + // custom select source function for a SPECIFIC COLUMN + arry = fxn( table, column, onlyAvail ); + } + if ( arry === false ) { + // fall back to original method + arry = ts.filter.getOptions( table, column, onlyAvail ); + } + + // get unique elements and sort the list + // if $.tablesorter.sortText exists ( not in the original tablesorter ), + // then natural sort the list otherwise use a basic sort + arry = $.grep( arry, function( value, indx ) { + return $.inArray( value, arry ) === indx; + }); + + if ( c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { + // unsorted select options + return arry; + } else { + len = arry.length; + // parse select option values + for ( indx = 0; indx < len; indx++ ) { + txt = arry[ indx ]; + // parse array data using set column parser; this DOES NOT pass the original + // table cell to the parser format function + parsed.push({ + t : txt, + // check parser length - fixes #934 + p : c.parsers && c.parsers.length && c.parsers[ column ].format( txt, table, [], column ) || txt + }); + } + + // sort parsed select options + cts = c.textSorter || ''; + parsed.sort( function( a, b ) { + // sortNatural breaks if you don't pass it strings + var x = a.p.toString(), + y = b.p.toString(); + if ( $.isFunction( cts ) ) { + // custom OVERALL text sorter + return cts( x, y, true, column, table ); + } else if ( typeof cts === 'object' && cts.hasOwnProperty( column ) ) { + // custom text sorter for a SPECIFIC COLUMN + return cts[column]( x, y, true, column, table ); + } else if ( ts.sortNatural ) { + // fall back to natural sort + return ts.sortNatural( x, y ); + } + // using an older version! do a basic sort + return true; + }); + // rebuild arry from sorted parsed data + arry = []; + len = parsed.length; + for ( indx = 0; indx < len; indx++ ) { + arry.push( parsed[indx].t ); + } + return arry; + } + }, + getOptions: function( table, column, onlyAvail ) { + table = $( table )[0]; + var rowIndex, tbodyIndex, len, row, cache, + c = table.config, + wo = c.widgetOptions, + arry = []; + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + cache = c.cache[tbodyIndex]; + len = c.cache[tbodyIndex].normalized.length; + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + // get cached row from cache.row ( old ) or row data object + // ( new; last item in normalized array ) + row = cache.row ? + cache.row[ rowIndex ] : + cache.normalized[ rowIndex ][ c.columns ].$row[0]; + // check if has class filtered + if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { + continue; + } + // get non-normalized cell content + if ( wo.filter_useParsedData || + c.parsers[column].parsed || + c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { + arry.push( '' + cache.normalized[ rowIndex ][ column ] ); + } else { + // get raw cached data instead of content directly from the cells + arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + } + } } return arry; - } - }, - getOptions: function( table, column, onlyAvail ) { - table = $( table )[0]; - var rowIndex, tbodyIndex, len, row, cache, - c = table.config, - wo = c.widgetOptions, - arry = []; - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - cache = c.cache[tbodyIndex]; - len = c.cache[tbodyIndex].normalized.length; - // loop through the rows - for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { - // get cached row from cache.row ( old ) or row data object - // ( new; last item in normalized array ) - row = cache.row ? - cache.row[ rowIndex ] : - cache.normalized[ rowIndex ][ c.columns ].$row[0]; - // check if has class filtered - if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { - continue; + }, + buildSelect: function( table, column, arry, updating, onlyAvail ) { + table = $( table )[0]; + column = parseInt( column, 10 ); + if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { + return; + } + var indx, val, txt, t, $filters, $filter, + c = table.config, + wo = c.widgetOptions, + node = c.$headerIndexed[ column ], + // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 + options = '', + // Get curent filter value + currentValue = c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .val(); + // nothing included in arry ( external source ), so get the options from + // filter_selectSource or column data + if ( typeof arry === 'undefined' || arry === '' ) { + arry = ts.filter.getOptionSource( table, column, onlyAvail ); + } + + if ( $.isArray( arry ) ) { + // build option list + for ( indx = 0; indx < arry.length; indx++ ) { + txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); + val = txt; + // allow including a symbol in the selectSource array + // 'a-z|A through Z' so that 'a-z' becomes the option value + // and 'A through Z' becomes the option text + if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + t = txt.split( wo.filter_selectSourceSeparator ); + val = t[0]; + txt = t[1]; + } + // replace quotes - fixes #242 & ignore empty strings + // see http://stackoverflow.com/q/14990971/145346 + options += arry[indx] !== '' ? + '' : ''; } - // get non-normalized cell content - if ( wo.filter_useParsedData || - c.parsers[column].parsed || - c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { - arry.push( '' + cache.normalized[ rowIndex ][ column ] ); - } else { - // get raw cached data instead of content directly from the cells - arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + // clear arry so it doesn't get appended twice + arry = []; + } + + // update all selects in the same column ( clone thead in sticky headers & + // any external selects ) - fixes 473 + $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) + .find( '.' + tscss.filter ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; + } + $filter = $filters.filter( 'select[data-column="' + column + '"]' ); + + // make sure there is a select there! + if ( $filter.length ) { + $filter[ updating ? 'html' : 'append' ]( options ); + if ( !$.isArray( arry ) ) { + // append options if arry is provided externally as a string or jQuery object + // options ( default value ) was already added + $filter.append( arry ).val( currentValue ); + } + $filter.val( currentValue ); + } + }, + buildDefault: function( table, updating ) { + var columnIndex, $header, noSelect, + c = table.config, + wo = c.widgetOptions, + columns = c.columns; + // build default select dropdown + for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { + $header = c.$headerIndexed[columnIndex]; + noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); + // look for the filter-select class; build/update it if found + if ( ( $header.hasClass( 'filter-select' ) || + ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { + ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); } } } - return arry; - }, - buildSelect: function( table, column, arry, updating, onlyAvail ) { - table = $( table )[0]; - column = parseInt( column, 10 ); - if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { - return; - } - var indx, val, txt, t, $filters, $filter, - c = table.config, - wo = c.widgetOptions, - node = c.$headerIndexed[ column ], - // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 - options = '', - // Get curent filter value - currentValue = c.$table - .find( 'thead' ) - .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) - .val(); - // nothing included in arry ( external source ), so get the options from - // filter_selectSource or column data - if ( typeof arry === 'undefined' || arry === '' ) { - arry = ts.filter.getOptionSource( table, column, onlyAvail ); - } + }; - if ( $.isArray( arry ) ) { - // build option list - for ( indx = 0; indx < arry.length; indx++ ) { - txt = arry[indx] = ( '' + arry[indx] ).replace( /\"/g, '"' ); - val = txt; - // allow including a symbol in the selectSource array - // 'a-z|A through Z' so that 'a-z' becomes the option value - // and 'A through Z' becomes the option text - if ( txt.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { - t = txt.split( wo.filter_selectSourceSeparator ); - val = t[0]; - txt = t[1]; - } - // replace quotes - fixes #242 & ignore empty strings - // see http://stackoverflow.com/q/14990971/145346 - options += arry[indx] !== '' ? - '' : ''; + ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { + var i, $filters, $column, cols, + filters = false, + c = table ? $( table )[0].config : '', + wo = c ? c.widgetOptions : ''; + if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || + // setFilters called, but last search is exactly the same as the current + // fixes issue #733 & #903 where calling update causes the input values to reset + ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { + return $( table ).data( 'lastSearch' ); + } + if ( c ) { + if ( c.$filters ) { + $filters = c.$filters.find( '.' + tscss.filter ); } - // clear arry so it doesn't get appended twice - arry = []; - } - - // update all selects in the same column ( clone thead in sticky headers & - // any external selects ) - fixes 473 - $filters = ( c.$filters ? c.$filters : c.$table.children( 'thead' ) ) - .find( '.' + tscss.filter ); - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - $filter = $filters.filter( 'select[data-column="' + column + '"]' ); - - // make sure there is a select there! - if ( $filter.length ) { - $filter[ updating ? 'html' : 'append' ]( options ); - if ( !$.isArray( arry ) ) { - // append options if arry is provided externally as a string or jQuery object - // options ( default value ) was already added - $filter.append( arry ).val( currentValue ); + if ( wo.filter_$externalFilters ) { + $filters = $filters && $filters.length ? + $filters.add( wo.filter_$externalFilters ) : + wo.filter_$externalFilters; } - $filter.val( currentValue ); - } - }, - buildDefault: function( table, updating ) { - var columnIndex, $header, noSelect, - c = table.config, - wo = c.widgetOptions, - columns = c.columns; - // build default select dropdown - for ( columnIndex = 0; columnIndex < columns; columnIndex++ ) { - $header = c.$headerIndexed[columnIndex]; - noSelect = !( $header.hasClass( 'filter-false' ) || $header.hasClass( 'parser-false' ) ); - // look for the filter-select class; build/update it if found - if ( ( $header.hasClass( 'filter-select' ) || - ts.getColumnData( table, wo.filter_functions, columnIndex ) === true ) && noSelect ) { - ts.filter.buildSelect( table, columnIndex, '', updating, $header.hasClass( wo.filter_onlyAvail ) ); - } - } - } -}; - -ts.getFilters = function( table, getRaw, setFilters, skipFirst ) { - var i, $filters, $column, cols, - filters = false, - c = table ? $( table )[0].config : '', - wo = c ? c.widgetOptions : ''; - if ( ( getRaw !== true && wo && !wo.filter_columnFilters ) || - // setFilters called, but last search is exactly the same as the current - // fixes issue #733 & #903 where calling update causes the input values to reset - ( $.isArray(setFilters) && setFilters.join('') === c.lastCombinedFilter ) ) { - return $( table ).data( 'lastSearch' ); - } - if ( c ) { - if ( c.$filters ) { - $filters = c.$filters.find( '.' + tscss.filter ); - } - if ( wo.filter_$externalFilters ) { - $filters = $filters && $filters.length ? - $filters.add( wo.filter_$externalFilters ) : - wo.filter_$externalFilters; - } - if ( $filters && $filters.length ) { - filters = setFilters || []; - for ( i = 0; i < c.columns + 1; i++ ) { - cols = ( i === c.columns ? - // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) - wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : - '[data-column="' + i + '"]' ); - $column = $filters.filter( cols ); - if ( $column.length ) { - // move the latest search to the first slot in the array - $column = ts.filter.getLatestSearch( $column ); - if ( $.isArray( setFilters ) ) { - // skip first ( latest input ) to maintain cursor position while typing - if ( skipFirst && $column.length > 1 ) { - $column = $column.slice( 1 ); - } - if ( i === c.columns ) { - // prevent data-column='all' from filling data-column='0,1' ( etc ) - cols = $column.filter( wo.filter_anyColumnSelector ); - $column = cols.length ? cols : $column; - } - $column - .val( setFilters[ i ] ) - .trigger( 'change.tsfilter' ); - } else { - filters[i] = $column.val() || ''; - // don't change the first... it will move the cursor - if ( i === c.columns ) { - // don't update range columns from 'all' setting + if ( $filters && $filters.length ) { + filters = setFilters || []; + for ( i = 0; i < c.columns + 1; i++ ) { + cols = ( i === c.columns ? + // 'all' columns can now include a range or set of columms ( data-column='0-2,4,6-7' ) + wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector : + '[data-column="' + i + '"]' ); + $column = $filters.filter( cols ); + if ( $column.length ) { + // move the latest search to the first slot in the array + $column = ts.filter.getLatestSearch( $column ); + if ( $.isArray( setFilters ) ) { + // skip first ( latest input ) to maintain cursor position while typing + if ( skipFirst && $column.length > 1 ) { + $column = $column.slice( 1 ); + } + if ( i === c.columns ) { + // prevent data-column='all' from filling data-column='0,1' ( etc ) + cols = $column.filter( wo.filter_anyColumnSelector ); + $column = cols.length ? cols : $column; + } $column - .slice( 1 ) - .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) - .val( filters[ i ] ); + .val( setFilters[ i ] ) + .trigger( 'change.tsfilter' ); } else { - $column - .slice( 1 ) - .val( filters[ i ] ); + filters[i] = $column.val() || ''; + // don't change the first... it will move the cursor + if ( i === c.columns ) { + // don't update range columns from 'all' setting + $column + .slice( 1 ) + .filter( '[data-column*="' + $column.attr( 'data-column' ) + '"]' ) + .val( filters[ i ] ); + } else { + $column + .slice( 1 ) + .val( filters[ i ] ); + } + } + // save any match input dynamically + if ( i === c.columns && $column.length ) { + wo.filter_$anyMatch = $column; } - } - // save any match input dynamically - if ( i === c.columns && $column.length ) { - wo.filter_$anyMatch = $column; } } } } - } - if ( filters.length === 0 ) { - filters = false; - } - return filters; -}; + if ( filters.length === 0 ) { + filters = false; + } + return filters; + }; -ts.setFilters = function( table, filter, apply, skipFirst ) { - var c = table ? $( table )[0].config : '', - valid = ts.getFilters( table, true, filter, skipFirst ); - if ( c && apply ) { - // ensure new set filters are applied, even if the search is the same - c.lastCombinedFilter = null; - c.lastSearch = []; - ts.filter.searching( c.table, filter, skipFirst ); - c.$table.trigger( 'filterFomatterUpdate' ); - } - return !!valid; -}; + ts.setFilters = function( table, filter, apply, skipFirst ) { + var c = table ? $( table )[0].config : '', + valid = ts.getFilters( table, true, filter, skipFirst ); + if ( c && apply ) { + // ensure new set filters are applied, even if the search is the same + c.lastCombinedFilter = null; + c.lastSearch = []; + ts.filter.searching( c.table, filter, skipFirst ); + c.$table.trigger( 'filterFomatterUpdate' ); + } + return !!valid; + }; })( jQuery ); diff --git a/js/widgets/widget-formatter.js b/js/widgets/widget-formatter.js index 61e1a03d..c3608a50 100644 --- a/js/widgets/widget-formatter.js +++ b/js/widgets/widget-formatter.js @@ -5,7 +5,7 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -'use strict'; + 'use strict'; var ts = $.tablesorter; ts.formatter = { diff --git a/js/widgets/widget-grouping.js b/js/widgets/widget-grouping.js index 178d8cc1..d0133dac 100644 --- a/js/widgets/widget-grouping.js +++ b/js/widgets/widget-grouping.js @@ -5,246 +5,246 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; -var ts = $.tablesorter; + 'use strict'; + var ts = $.tablesorter; -ts.grouping = { + ts.grouping = { - types : { - number : function(c, $column, txt, num, group){ - var value, word; - if (num > 1 && txt !== '') { - if ($column.hasClass(ts.css.sortAsc)) { - value = Math.floor(parseFloat(txt)/num) * num; - return value > parseFloat(group || 0) ? value : parseFloat(group || 0); + types : { + number : function(c, $column, txt, num, group){ + var value, word; + if (num > 1 && txt !== '') { + if ($column.hasClass(ts.css.sortAsc)) { + value = Math.floor(parseFloat(txt) / num) * num; + return value > parseFloat(group || 0) ? value : parseFloat(group || 0); + } else { + value = Math.ceil(parseFloat(txt) / num) * num; + return value < parseFloat(group || num) - value ? parseFloat(group || num) - value : value; + } } else { - value = Math.ceil(parseFloat(txt)/num) * num; - return value < parseFloat(group || num) - value ? parseFloat(group || num) - value : value; + word = (txt + '').match(/\d+/g); + return word && word.length >= num ? word[num - 1] : txt || ''; } - } else { - word = (txt + '').match(/\d+/g); + }, + separator : function(c, $column, txt, num){ + var word = (txt + '').split(c.widgetOptions.group_separator); + return $.trim(word && num > 0 && word.length >= num ? word[(num || 1) - 1] : ''); + }, + word : function(c, $column, txt, num){ + var word = (txt + ' ').match(/\w+/g); return word && word.length >= num ? word[num - 1] : txt || ''; + }, + letter : function(c, $column, txt, num){ + return txt ? (txt + ' ').substring(0, num) : ''; + }, + date : function(c, $column, txt, part, group){ + var wo = c.widgetOptions, + time = new Date(txt || ''), + hours = time.getHours(); + return part === 'year' ? time.getFullYear() : + part === 'month' ? wo.group_months[time.getMonth()] : + part === 'monthyear' ? wo.group_months[time.getMonth()] + ' ' + time.getFullYear() : + part === 'day' ? wo.group_months[time.getMonth()] + ' ' + time.getDate() : + part === 'week' ? wo.group_week[time.getDay()] : + part === 'time' ? ('00' + (hours > 12 ? hours - 12 : hours === 0 ? hours + 12 : hours)).slice(-2) + ':' + + ('00' + time.getMinutes()).slice(-2) + ' ' + ('00' + wo.group_time[hours >= 12 ? 1 : 0]).slice(-2) : + wo.group_dateString(time); } }, - separator : function(c, $column, txt, num){ - var word = (txt + '').split(c.widgetOptions.group_separator); - return $.trim(word && num > 0 && word.length >= num ? word[(num || 1) - 1] : ''); - }, - word : function(c, $column, txt, num){ - var word = (txt + ' ').match(/\w+/g); - return word && word.length >= num ? word[num - 1] : txt || ''; - }, - letter : function(c, $column, txt, num){ - return txt ? (txt + ' ').substring(0, num) : ''; - }, - date : function(c, $column, txt, part, group){ - var wo = c.widgetOptions, - time = new Date(txt || ''), - hours = time.getHours(); - return part === 'year' ? time.getFullYear() : - part === 'month' ? wo.group_months[time.getMonth()] : - part === 'monthyear' ? wo.group_months[time.getMonth()] + ' ' + time.getFullYear() : - part === 'day' ? wo.group_months[time.getMonth()] + ' ' + time.getDate() : - part === 'week' ? wo.group_week[time.getDay()] : - part === 'time' ? ('00' + (hours > 12 ? hours - 12 : hours === 0 ? hours + 12 : hours)).slice(-2) + ':' + - ('00' + time.getMinutes()).slice(-2) + ' ' + ('00' + wo.group_time[hours >= 12 ? 1 : 0]).slice(-2) : - wo.group_dateString(time); - } - }, - update : function(table, c, wo){ - if ($.isEmptyObject(c.cache)) { return; } - var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, norm_rows, saveName, direction, - lang = wo.grouping_language, - group = '', - savedGroup = false, - column = c.sortList[0] ? c.sortList[0][0] : -1; - c.$table - .find('tr.group-hidden').removeClass('group-hidden').end() - .find('tr.group-header').remove(); - if (wo.group_collapsible) { - // clear pager saved spacer height (in case the rows are collapsed) - c.$table.data('pagerSavedHeight', 0); - } - if (column >= 0 && !c.$headerIndexed[column].hasClass('group-false')) { - wo.group_currentGroup = ''; // save current groups - wo.group_currentGroups = {}; + update : function(table, c, wo){ + if ($.isEmptyObject(c.cache)) { return; } + var rowIndex, tbodyIndex, currentGroup, $rows, groupClass, grouping, norm_rows, saveName, direction, + lang = wo.grouping_language, + group = '', + savedGroup = false, + column = c.sortList[0] ? c.sortList[0][0] : -1; + c.$table + .find('tr.group-hidden').removeClass('group-hidden').end() + .find('tr.group-header').remove(); + if (wo.group_collapsible) { + // clear pager saved spacer height (in case the rows are collapsed) + c.$table.data('pagerSavedHeight', 0); + } + if (column >= 0 && !c.$headerIndexed[column].hasClass('group-false')) { + wo.group_currentGroup = ''; // save current groups + wo.group_currentGroups = {}; - // group class finds "group-{word/separator/letter/number/date/false}-{optional:#/year/month/day/week/time}" - groupClass = (c.$headerIndexed[column].attr('class') || '').match(/(group-\w+(-\w+)?)/g); - // grouping = [ 'group', '{word/separator/letter/number/date/false}', '{#/year/month/day/week/time}' ] - grouping = groupClass ? groupClass[0].split('-') : ['group','letter',1]; // default to letter 1 + // group class finds 'group-{word/separator/letter/number/date/false}-{optional:#/year/month/day/week/time}' + groupClass = (c.$headerIndexed[column].attr('class') || '').match(/(group-\w+(-\w+)?)/g); + // grouping = [ 'group', '{word/separator/letter/number/date/false}', '{#/year/month/day/week/time}' ] + grouping = groupClass ? groupClass[0].split('-') : [ 'group', 'letter', 1 ]; // default to letter 1 - // save current grouping - if (wo.group_collapsible && wo.group_saveGroups && ts.storage) { - wo.group_currentGroups = ts.storage( table, 'tablesorter-groups' ) || {}; - // include direction when grouping numbers > 1 (reversed direction shows different range values) - direction = (grouping[1] === 'number' && grouping[2] > 1) ? 'dir' + c.sortList[0][1] : ''; - // combine column, sort direction & grouping as save key - saveName = wo.group_currentGroup = '' + column + direction + grouping.join(''); - if (!wo.group_currentGroups[saveName]) { - wo.group_currentGroups[saveName] = []; - } else { - savedGroup = true; + // save current grouping + if (wo.group_collapsible && wo.group_saveGroups && ts.storage) { + wo.group_currentGroups = ts.storage( table, 'tablesorter-groups' ) || {}; + // include direction when grouping numbers > 1 (reversed direction shows different range values) + direction = (grouping[1] === 'number' && grouping[2] > 1) ? 'dir' + c.sortList[0][1] : ''; + // combine column, sort direction & grouping as save key + saveName = wo.group_currentGroup = '' + column + direction + grouping.join(''); + if (!wo.group_currentGroups[saveName]) { + wo.group_currentGroups[saveName] = []; + } else { + savedGroup = true; + } } - } - for (tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++) { - norm_rows = c.cache[tbodyIndex].normalized; - group = ''; // clear grouping across tbodies - $rows = c.$tbodies.eq(tbodyIndex).children('tr').not('.' + c.cssChildRow); - for (rowIndex = 0; rowIndex < $rows.length; rowIndex++) { - if ( $rows.eq(rowIndex).is(':visible') ) { - // fixes #438 - if (ts.grouping.types[grouping[1]]) { - currentGroup = norm_rows[rowIndex] ? - ts.grouping.types[grouping[1]]( c, c.$headerIndexed[column], norm_rows[rowIndex][column], /date/.test(groupClass) ? - grouping[2] : parseInt(grouping[2] || 1, 10) || 1, group, lang ) : currentGroup; - if (group !== currentGroup) { - group = currentGroup; - // show range if number > 1 - if (grouping[1] === 'number' && grouping[2] > 1 && currentGroup !== '') { - currentGroup += ' - ' + (parseInt(currentGroup, 10) + - ((parseInt(grouping[2],10) - 1) * (c.$headerIndexed[column].hasClass(ts.css.sortAsc) ? 1 : -1))); - } - if ($.isFunction(wo.group_formatter)) { - currentGroup = wo.group_formatter((currentGroup || '').toString(), column, table, c, wo) || currentGroup; - } - $rows.eq(rowIndex).before('
' + (wo.group_collapsible ? '' : '') + '' + - currentGroup + '
' + (wo.group_collapsible ? '' : '') + '' + + currentGroup + '
 
' + d[i][j] + '
 
' + d[i][j] + '
' + message + '
' + message + '
' + - $table.children( 'thead' )[ 0 ].outerHTML + '
' ); - wo.scroller_$header = $hdr.addClass( c.namespace.slice( 1 ) + '_extra_table' ); + setup : function( c, wo ) { + var maxHt, tbHt, $hdr, $t, $hCells, $fCells, $tableWrap, events, tmp, + $win = $( window ), + tsScroller = ts.scroller, + namespace = c.namespace + 'tsscroller', + $foot = $(), + // c.namespace contains a unique tablesorter ID, per table + id = c.namespace.slice( 1 ) + 'tsscroller', + $table = c.$table; - $t = $table.children( 'tfoot' ); - if ( $t.length ) { - $foot = $( '
' ) - .addClass( c.namespace.slice( 1 ) + '_extra_table' ) - // maintain any bindings on the tfoot cells - .append( $t.clone( true ) ) - .wrap( '
' ); - $fCells = $foot.children( 'tfoot' ).eq( 0 ).children( 'tr' ).children(); - } - wo.scroller_$footer = $foot; + // force config.widthFixed option - this helps maintain proper alignment across cloned tables + c.widthFixed = true; - $table - .wrap( '
' ) - .before( $hdr ) - // shrink filter row but don't completely hide it because the inputs/selectors may distort the columns - .find( '.' + tscss.filterRow ) - .addClass( tscss.filterRowHide ); + wo.scroller_calcWidths = []; + wo.scroller_saved = [ 0, 0 ]; + wo.scroller_isBusy = true; - wo.scroller_$container = $table.parent(); + // set scrollbar width & allow setting width to zero + wo.scroller_barSetWidth = wo.scroller_barWidth !== null ? + wo.scroller_barWidth : + ( tsScroller.getBarWidth() || 15 ); - if ( $foot.length ) { - // $foot.parent() to include
wrapper - $table.after( $foot.parent() ); - } + maxHt = wo.scroller_height || 300; - $hCells = $hdr - .wrap( '
' ) - .find( '.' + tscss.header ); + $hdr = $( '' + + $table.children( 'thead' )[ 0 ].outerHTML + '
' ); + wo.scroller_$header = $hdr.addClass( c.namespace.slice( 1 ) + '_extra_table' ); - // use max-height, so the height resizes dynamically while filtering - $table.wrap( '
' ); - $tableWrap = $table.parent(); + $t = $table.children( 'tfoot' ); + if ( $t.length ) { + $foot = $( '
' ) + .addClass( c.namespace.slice( 1 ) + '_extra_table' ) + // maintain any bindings on the tfoot cells + .append( $t.clone( true ) ) + .wrap( '
' ); + $fCells = $foot.children( 'tfoot' ).eq( 0 ).children( 'tr' ).children(); + } + wo.scroller_$footer = $foot; - // make scroller header sortable - ts.bindEvents( c.table, $hCells ); + $table + .wrap( '
' ) + .before( $hdr ) + // shrink filter row but don't completely hide it because the inputs/selectors may distort the columns + .find( '.' + tscss.filterRow ) + .addClass( tscss.filterRowHide ); - // look for filter widget - if ( $table.hasClass( 'hasFilters' ) ) { - ts.filter.bindSearch( $table, $hdr.find( '.' + tscss.filter ) ); - } + wo.scroller_$container = $table.parent(); - $table - .find( 'thead' ) - .addClass( tscss.scrollerHideElement ); + if ( $foot.length ) { + // $foot.parent() to include
wrapper + $table.after( $foot.parent() ); + } - tbHt = $tableWrap.parent().height(); + $hCells = $hdr + .wrap( '
' ) + .find( '.' + tscss.header ); - // The header will always jump into view if scrolling the table body - $tableWrap - .off( 'scroll' + namespace ) - .on( 'scroll' + namespace, function() { - if ( wo.scroller_jumpToHeader ) { - var pos = $win.scrollTop() - $hdr.offset().top; - if ( $( this ).scrollTop() !== 0 && pos < tbHt && pos > 0 ) { - $win.scrollTop( $hdr.offset().top ); + // use max-height, so the height resizes dynamically while filtering + $table.wrap( '
' ); + $tableWrap = $table.parent(); + + // make scroller header sortable + ts.bindEvents( c.table, $hCells ); + + // look for filter widget + if ( $table.hasClass( 'hasFilters' ) ) { + ts.filter.bindSearch( $table, $hdr.find( '.' + tscss.filter ) ); + } + + $table + .find( 'thead' ) + .addClass( tscss.scrollerHideElement ); + + tbHt = $tableWrap.parent().height(); + + // The header will always jump into view if scrolling the table body + $tableWrap + .off( 'scroll' + namespace ) + .on( 'scroll' + namespace, function() { + if ( wo.scroller_jumpToHeader ) { + var pos = $win.scrollTop() - $hdr.offset().top; + if ( $( this ).scrollTop() !== 0 && pos < tbHt && pos > 0 ) { + $win.scrollTop( $hdr.offset().top ); + } + } + $hdr + .parent() + .add( $foot.parent() ) + .scrollLeft( $( this ).scrollLeft() ); + }); + + // resize/update events + events = ( ( ts.hasWidget( c.table, 'filter' ) ? 'filterEnd' : 'tablesorter-initialized' ) + + ' updateComplete pagerComplete columnUpdate ' ).split( ' ' ).join( namespace + ' ' ); + + $table + .off( namespace ) + .on( 'sortEnd filterEnd'.split( ' ' ).join( namespace + ' ' ), function( event ) { + // Sorting, so scroll to top + if ( event.type === 'sortEnd' && wo.scroller_upAfterSort ) { + $tableWrap.animate({ + scrollTop : 0 + }, 'fast' ); + } else if ( wo.scroller_fixedColumns ) { + setTimeout( function() { + // restore previous scroll position + $tableWrap + .scrollTop( wo.scroller_saved[1] ) + .scrollLeft( wo.scroller_saved[0] ); + tsScroller.updateFixed( c, wo, false ); + }, 0 ); + } + }) + .on( 'setFixedColumnSize' + namespace, function( event, size ) { + var $wrap = wo.scroller_$container; + if ( typeof size !== 'undefined' && !isNaN( size ) ) { + wo.scroller_fixedColumns = parseInt( size, 10 ); + } + // remove fixed columns + tsScroller.removeFixed( c, wo ); + size = wo.scroller_fixedColumns; + if ( size > 0 && size < c.columns - 1 ) { + tsScroller.updateFixed( c, wo ); + } else if ( $wrap.hasClass( tscss.scrollerHasFix ) ) { + $wrap.removeClass( tscss.scrollerHasFix ); + // resize needed to make tables full width + tsScroller.resize( c, wo ); + } + }) + .on( events, function( event ) { + // Stop from running twice with pager + if ( ts.hasWidget( 'pager' ) && event.type === 'updateComplete' ) { + return; + } + if ( wo.scroller_fixedColumns > 0 ) { + tsScroller.updateFixed( c, wo, false ); + } + // adjust column sizes after an update + tsScroller.resize( c, wo ); + }); + + // Setup window.resizeEnd event + $win + .off( 'resize resizeEnd '.split( ' ' ).join( namespace + ' ' ) ) + .on( 'resize' + namespace, ts.window_resize ) + .on( 'resizeEnd' + namespace, function() { + // IE calls resize when you modify content, so we have to unbind the resize event + // so we don't end up with an infinite loop. we can rebind after we're done. + $win.off( 'resize' + namespace, ts.window_resize ); + tsScroller.resize( c, wo ); + $win.on( 'resize' + namespace, ts.window_resize ); + $tableWrap.trigger( 'scroll' + namespace ); + }); + + // initialization flag + c.isScrolling = true; + + tsScroller.updateFixed( c, wo ); + + // updateAll called - need to give the browser time to adjust the layout + // before calculating fix column widths + if ( c.table.hasInitialized && c.isScrolling ) { + setTimeout(function(){ + ts.scroller.resize( c, wo ); + }, 50); + } + + }, + + resize : function( c, wo ) { + if ( wo.scroller_isBusy ) { return; } + var index, borderWidth, setWidth, $hCells, $bCells, $fCells, $headers, $this, temp, + tsScroller = ts.scroller, + $container = wo.scroller_$container, + $table = c.$table, + $tableWrap = $table.parent(), + $hdr = wo.scroller_$header, + $foot = wo.scroller_$footer, + id = c.namespace.slice( 1 ) + 'tsscroller', + // Hide other scrollers so we can resize + $div = $( 'div.' + tscss.scrollerWrap + '[id!="' + id + '"]' ) + .addClass( tscss.scrollerHideElement ), + row = ''; + + wo.scroller_calcWidths = []; + + // Remove fixed so we get proper widths and heights + tsScroller.removeFixed( c, wo ); + $container.find( '.' + tscss.scrollerSpacerRow ).remove(); + // remove ts added colgroups + $container.find( '.' + ts.css.colgroup ).remove(); + + // show original table elements to get proper alignment + $table + .find( '.' + tscss.scrollerHideElement ) + .removeClass( tscss.scrollerHideElement ); + + // include left & right border widths + borderWidth = parseInt( $table.css( 'border-left-width' ), 10 ); + + $headers = c.$headerIndexed; + + for ( index = 0; index < c.columns; index++ ) { + $this = $headers[ index ]; + // code from https://github.com/jmosbech/StickyTableHeaders + if ( $this.css( 'box-sizing' ) === 'border-box' ) { + setWidth = $this.outerWidth(); + } else { + if ( $this.css( 'border-collapse' ) === 'collapse' ) { + if ( $this.length && window.getComputedStyle ) { + setWidth = parseFloat( window.getComputedStyle( $this[ 0 ], null ).width ); + } else { + // ie8 only + setWidth = $this.outerWidth() - parseFloat( $this.css( 'padding-left' ) ) - + parseFloat( $this.css( 'padding-right' ) ) - + ( parseFloat( $this.css( 'border-width' ) ) || 0 ); + } + } else { + setWidth = $this.width(); } } + row += ''; + + // save current widths + wo.scroller_calcWidths[ index ] = setWidth; + } + row += ''; + c.$tbodies.eq(0).prepend( row ); // tbody + $hdr.children( 'thead' ).append( row ); + $foot.children( 'tfoot' ).append( row ); + + // include colgroup or alignment is off + ts.fixColumnWidth( c.table ); + row = c.$table.children( 'colgroup' )[0].outerHTML; + $hdr.prepend( row ); + $foot.prepend( row ); + + temp = $tableWrap.parent().innerWidth() - + ( tsScroller.hasScrollBar( $tableWrap ) ? wo.scroller_barSetWidth : 0 ); + $tableWrap.width( temp ); + + temp = ( tsScroller.hasScrollBar( $tableWrap ) ? wo.scroller_barSetWidth : 0 ) + borderWidth; + setWidth = $tableWrap.innerWidth() - temp; + + $hdr + .parent() + .add( $foot.parent() ) + .width( setWidth ); + + $tableWrap + .width( setWidth + temp ); + + // hide original table thead + $table.children( 'thead' ).addClass( tscss.scrollerHideElement ); + + // update fixed column sizes + tsScroller.updateFixed( c, wo ); + + $div.removeClass( tscss.scrollerHideElement ); + + // restore scrollTop - fixes #926 + $tableWrap.scrollTop( wo.scroller_saved[1] ); + wo.scroller_$container + .find( '.' + tscss.scrollerFixed ) + .find( '.' + tscss.scrollerTable ) + .scrollTop( wo.scroller_saved[1] ); + + // update resizable widget handles + setTimeout( function() { + c.$table.trigger( 'resizableUpdate' ); + }, 100 ); + + }, + + // Add fixed (frozen) columns (Do not call directly, use updateFixed) + setupFixed : function( c, wo ) { + var index, index2, $el, len, temp, $fixedColumn, $fixedTbody, + $table = c.$table, + $wrapper = wo.scroller_$container, + fixedColumns = wo.scroller_fixedColumns; + + $fixedColumn = $wrapper + .addClass( tscss.scrollerHasFix ) + .clone() + .addClass( tscss.scrollerFixed ) + .removeClass( tscss.scrollerWrap ) + .attr( 'id', '' ); + + if ( wo.scroller_addFixedOverlay ) { + $fixedColumn.append( '
' ); + } + + $fixedTbody = $fixedColumn.find( '.' + tscss.scrollerTable ); + $fixedTbody + .children( 'table' ) + .addClass( c.namespace.slice( 1 ) + '_extra_table' ) + .attr( 'id', '' ) + .children( 'thead, tfoot' ) + .remove(); + + wo.scroller_$fixedColumns = $fixedColumn; + + // RTL support (fixes column on right) + if ( $table.hasClass( tscss.scrollerRtl ) ) { + $fixedColumn.addClass( tscss.scrollerRtl ); + } + + $el = $fixedColumn.find( 'tr' ); + len = $el.length; + for ( index = 0; index < len; index++ ) { + $el.eq( index ).children( ':gt(' + ( fixedColumns - 1 ) + ')' ).remove(); + } + $fixedColumn + .addClass( tscss.scrollerHideElement ) + .prependTo( $wrapper ); + + // look for filter widget + if ( c.$table.hasClass( 'hasFilters' ) ) { + // make sure fixed column filters aren't disabled + $el = $fixedColumn + .find( '.' + tscss.filter ) + .not( '.' + tscss.filterDisabled ) + .prop( 'disabled', false ); + ts.filter.bindSearch( $table, $fixedColumn.find( '.' + tscss.filter ) ); + // disable/enable filters behind fixed column + $el = $wrapper + .children( '.' + tscss.scrollerHeader ) + .find( '.' + tscss.filter ); + len = $el.length; + for ( index = 0; index < len; index++ ) { + // previously disabled filter; don't mess with it! filterDisabled class added by filter widget + if ( !$el.eq( index ).hasClass( tscss.filterDisabled || 'disabled' ) ) { + // disable filters behind fixed column; don't disable visible filters + $el.eq( index ).prop( 'disabled', index < fixedColumns ); + } + } + } + + // disable/enable tab indexes behind fixed column + c.$table + .add( '.' + tscss.scrollerFooter + ' table' ) + .children( 'thead' ) + .children( 'tr.' + tscss.headerRow ) + .children() + .attr( 'tabindex', -1 ); + + $el = wo.scroller_$header + .add( $fixedColumn.find( '.' + tscss.scrollerTable + ' table' ) ) + .children( 'thead' ) + .children( 'tr.' + tscss.headerRow ); + len = $el.length; + for ( index = 0; index < len; index++ ) { + temp = $el.eq( index ).children(); + for ( index2 = 0; index2 < temp.length; index2++ ) { + temp.eq( index2 ).attr( 'tabindex', index2 < fixedColumns ? -1 : 0 ); + } + } + + ts.bindEvents( c.table, $fixedColumn.find( '.' + tscss.header ) ); + ts.scroller.bindFixedColumnEvents( c, wo ); + + /*** Scrollbar hack! Since we can't hide the scrollbar with css ***/ + if ( ts.scroller.isFirefox || ts.scroller.isOldIE ) { + $fixedTbody.wrap( '
' ); + } + + }, + + bindFixedColumnEvents : function( c, wo ) { + // update thead & tbody in fixed column + var tsScroller = ts.scroller, + namespace = c.namespace + 'tsscrollerFixed', + events = 'scroll' + namespace, + $fixedTbody = wo.scroller_$fixedColumns.find( '.' + tscss.scrollerTable ), + fixedScroll = true, + tableScroll = true; + + c.$table + .parent() + // *** SCROLL *** scroll fixed column along with main + .off( events ) + .on( events, function() { + if ( wo.scroller_isBusy ) { return; } + // using flags to prevent firing the scroll event excessively leading to slow scrolling in Firefox + if ( !wo.scroller_isBusy && ( fixedScroll || !( tsScroller.isFirefox || tsScroller.isIE ) ) ) { + tableScroll = false; + var $this = $( this ); + $fixedTbody[0].scrollTop = wo.scroller_saved[1] = $this.scrollTop(); + wo.scroller_saved[0] = $this.scrollLeft(); + setTimeout( function() { + tableScroll = true; + }, 20 ); + } + }); + // scroll main along with fixed column + $fixedTbody + .off( events ) + .on( events, function() { + // using flags to prevent firing the scroll event excessively leading to slow scrolling in Firefox + if ( !wo.scroller_isBusy && ( tableScroll || !( tsScroller.isFirefox || tsScroller.isIE ) ) ) { + fixedScroll = false; + var $this = $( this ); + c.$table.parent()[0].scrollTop = wo.scroller_saved[1] = $this.scrollTop(); + setTimeout( function() { + fixedScroll = true; + }, 20 ); + } + }) + .scroll(); + + // *** ROW HIGHLIGHT *** + if ( wo.scroller_rowHighlight !== '' ) { + events = 'mouseover mouseleave '.split( ' ' ).join( namespace + ' ' ); + // can't use c.$tbodies because it doesn't include info-only tbodies + c.$table + .off( events, 'tbody > tr' ) + .on( events, 'tbody > tr', function( event ) { + var indx = c.$table.children( 'tbody' ).children( 'tr' ).index( this ); + $fixedTbody + .children( 'table' ) + .children( 'tbody' ) + .children( 'tr' ) + .eq( indx ) + .add( this ) + .toggleClass( wo.scroller_rowHighlight, event.type === 'mouseover' ); + }); + $fixedTbody + .find( 'table' ) + .off( events, 'tbody > tr' ) + .on( events, 'tbody > tr', function( event ) { + var $fixed = $fixedTbody.children( 'table' ).children( 'tbody' ).children( 'tr' ), + indx = $fixed.index( this ); + c.$table + .children( 'tbody' ) + .children( 'tr' ) + .eq( indx ) + .add( this ) + .toggleClass( wo.scroller_rowHighlight, event.type === 'mouseover' ); + }); + } + }, + + adjustWidth : function( c, wo, totalWidth, adj, dir ) { + var $wrapper = wo.scroller_$container; + + // RTL support (fixes column on right) + $wrapper + .children( '.' + tscss.scrollerTable ) + .css( dir ? 'right' : 'left', totalWidth ); + $wrapper + .children( '.' + tscss.scrollerHeader + ', .' + tscss.scrollerFooter ) + // Safari needs a scrollbar width of extra adjusment to align the fixed & scrolling columns + .css( dir ? 'right' : 'left', totalWidth + ( dir && ts.scroller.isSafari ? adj : 0 ) ); + }, + + updateFixed : function( c, wo ) { + var temp, adj, + $wrapper = wo.scroller_$container, + $hdr = wo.scroller_$header, + $foot = wo.scroller_$footer, + $table = c.$table, + $tableWrap = $table.parent(), + scrollBarWidth = wo.scroller_barSetWidth, + dir = $table.hasClass( tscss.scrollerRtl ); + + if ( wo.scroller_fixedColumns === 0 ) { + wo.scroller_isBusy = false; + ts.scroller.removeFixed( c, wo ); + temp = $wrapper.width(); + $tableWrap.width( temp ); + adj = ts.scroller.hasScrollBar( $tableWrap ) ? scrollBarWidth : 0; $hdr .parent() .add( $foot.parent() ) - .scrollLeft( $( this ).scrollLeft() ); - }); + .width( temp - adj ); + return; + } - // resize/update events - events = ( ( ts.hasWidget( c.table, 'filter' ) ? 'filterEnd' : 'tablesorter-initialized' ) + - ' updateComplete pagerComplete columnUpdate ' ).split( ' ' ).join( namespace + ' ' ); + if ( !c.isScrolling ) { + return; + } - $table - .off( namespace ) - .on( 'sortEnd filterEnd'.split( ' ' ).join( namespace + ' ' ), function( event ) { - // Sorting, so scroll to top - if ( event.type === 'sortEnd' && wo.scroller_upAfterSort ) { - $tableWrap.animate({ - scrollTop : 0 - }, 'fast' ); - } else if ( wo.scroller_fixedColumns ) { - setTimeout( function() { - // restore previous scroll position - $tableWrap - .scrollTop( wo.scroller_saved[1] ) - .scrollLeft( wo.scroller_saved[0] ); - tsScroller.updateFixed( c, wo, false ); - }, 0 ); - } - }) - .on( 'setFixedColumnSize' + namespace, function( event, size ) { - var $wrap = wo.scroller_$container; - if ( typeof size !== 'undefined' && !isNaN( size ) ) { - wo.scroller_fixedColumns = parseInt( size, 10 ); - } - // remove fixed columns - tsScroller.removeFixed( c, wo ); - size = wo.scroller_fixedColumns; - if ( size > 0 && size < c.columns - 1 ) { - tsScroller.updateFixed( c, wo ); - } else if ( $wrap.hasClass( tscss.scrollerHasFix ) ) { - $wrap.removeClass( tscss.scrollerHasFix ); - // resize needed to make tables full width - tsScroller.resize( c, wo ); - } - }) - .on( events, function( event ) { - // Stop from running twice with pager - if ( ts.hasWidget( 'pager' ) && event.type === 'updateComplete' ) { - return; - } - if ( wo.scroller_fixedColumns > 0 ) { - tsScroller.updateFixed( c, wo, false ); - } - // adjust column sizes after an update - tsScroller.resize( c, wo ); - }); + wo.scroller_isBusy = true; - // Setup window.resizeEnd event - $win - .off( 'resize resizeEnd '.split( ' ' ).join( namespace + ' ' ) ) - .on( 'resize' + namespace, ts.window_resize ) - .on( 'resizeEnd' + namespace, function() { - // IE calls resize when you modify content, so we have to unbind the resize event - // so we don't end up with an infinite loop. we can rebind after we're done. - $win.off( 'resize' + namespace, ts.window_resize ); - tsScroller.resize( c, wo ); - $win.on( 'resize' + namespace, ts.window_resize ); - $tableWrap.trigger( 'scroll' + namespace ); - }); + // Make sure the wo.scroller_$fixedColumns container exists, if not build it + if ( !$wrapper.find( '.' + tscss.scrollerFixed ).length ) { + ts.scroller.setupFixed( c, wo ); + } - // initialization flag - c.isScrolling = true; + // scroller_fixedColumns + var index, tbodyIndex, rowIndex, $tbody, $adjCol, $fb, $fixHead, $fixBody, $fixFoot, + totalRows, row, - tsScroller.updateFixed( c, wo ); + // source cells for measurement + $mainTbodies = wo.scroller_$container + .children( '.' + tscss.scrollerTable ) + .children( 'table' ) + .children( 'tbody' ), + // variable gets redefined + $rows = wo.scroller_$header + .children( 'thead' ) + .children( '.' + tscss.headerRow ), - // updateAll called - need to give the browser time to adjust the layout - // before calculating fix column widths - if ( c.table.hasInitialized && c.isScrolling ) { - setTimeout(function(){ - ts.scroller.resize( c, wo ); - }, 50); - } + // hide fixed column during resize, or we get a FOUC + $fixedColumn = wo.scroller_$fixedColumns + .addClass( tscss.scrollerHideElement ), - }, + // target cells + $fixedTbodiesTable = $fixedColumn + .find( '.' + tscss.scrollerTable ) + .children( 'table' ), + $fixedTbodies = $fixedTbodiesTable + .children( 'tbody' ), + // variables + tsScroller = ts.scroller, + fixedColumns = wo.scroller_fixedColumns, + // get dimensions + $temp = $table.find( 'tbody td' ), + borderRightWidth = parseInt( $temp.css( 'border-right-width' ), 10 ) || 1, + borderSpacing = parseInt( ( $temp.css( 'border-spacing' ) || '' ).split( /\s/ )[ 0 ], 10 ) / 2 || 0, + totalWidth = parseInt( $table.css( 'padding-left' ), 10 ) + + parseInt( $table.css( 'padding-right' ), 10 ) - + borderRightWidth, + widths = wo.scroller_calcWidths; - resize : function( c, wo ) { - if ( wo.scroller_isBusy ) { return; } - var index, borderWidth, setWidth, $hCells, $bCells, $fCells, $headers, $this, temp, - tsScroller = ts.scroller, - $container = wo.scroller_$container, - $table = c.$table, - $tableWrap = $table.parent(), - $hdr = wo.scroller_$header, - $foot = wo.scroller_$footer, - id = c.namespace.slice( 1 ) + 'tsscroller', - // Hide other scrollers so we can resize - $div = $( 'div.' + tscss.scrollerWrap + '[id!="' + id + '"]' ) - .addClass( tscss.scrollerHideElement ), - row = ''; + ts.scroller.removeFixed( c, wo, false ); - wo.scroller_calcWidths = []; + // calculate fixed column width + for ( index = 0; index < fixedColumns; index++ ) { + totalWidth += widths[ index ] + borderSpacing; + } - // Remove fixed so we get proper widths and heights - tsScroller.removeFixed( c, wo ); - $container.find( '.' + tscss.scrollerSpacerRow ).remove(); - // remove ts added colgroups - $container.find( '.' + ts.css.colgroup ).remove(); + // set fixed column width + totalWidth = totalWidth + borderRightWidth * 2 - borderSpacing; + tsScroller.setWidth( $fixedColumn.add( $fixedColumn.children() ), totalWidth ); + tsScroller.setWidth( $fixedColumn.children().children( 'table' ), totalWidth ); - // show original table elements to get proper alignment - $table - .find( '.' + tscss.scrollerHideElement ) - .removeClass( tscss.scrollerHideElement ); - - // include left & right border widths - borderWidth = parseInt( $table.css( 'border-left-width' ), 10 ); - - $headers = c.$headerIndexed; - - for ( index = 0; index < c.columns; index++ ) { - $this = $headers[ index ]; - // code from https://github.com/jmosbech/StickyTableHeaders - if ( $this.css( 'box-sizing' ) === 'border-box' ) { - setWidth = $this.outerWidth(); - } else { - if ( $this.css( 'border-collapse' ) === 'collapse' ) { - if ( $this.length && window.getComputedStyle ) { - setWidth = parseFloat( window.getComputedStyle( $this[ 0 ], null ).width ); - } else { - // ie8 only - setWidth = $this.outerWidth() - parseFloat( $this.css( 'padding-left' ) ) - - parseFloat( $this.css( 'padding-right' ) ) - - ( parseFloat( $this.css( 'border-width' ) ) || 0 ); + // update fixed column tbody content, set cell widths on hidden row + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + $tbody = $mainTbodies.eq( tbodyIndex ); + if ( $tbody.length ) { + // get tbody + $rows = $tbody.children(); + totalRows = $rows.length; + $fb = ts.processTbody( $fixedTbodiesTable, $fixedTbodies.eq( tbodyIndex ), true ); + $fb.empty(); + // update tbody cells after sort/filtering + for ( rowIndex = 0; rowIndex < totalRows; rowIndex++ ) { + $adjCol = $( $rows[ rowIndex ].outerHTML ); + $adjCol + .children( 'td, th' ) + .slice( fixedColumns ) + .remove(); + $fb.append( $adjCol ); } - } else { - setWidth = $this.width(); + + // restore tbody + ts.processTbody( $fixedTbodiesTable, $fb, false ); } } - row += ''; - // save current widths - wo.scroller_calcWidths[ index ] = setWidth; - } - row += ''; - c.$tbodies.eq(0).prepend( row ); // tbody - $hdr.children( 'thead' ).append( row ); - $foot.children( 'tfoot' ).append( row ); - - // include colgroup or alignment is off - ts.fixColumnWidth( c.table ); - row = c.$table.children( 'colgroup' )[0].outerHTML; - $hdr.prepend( row ); - $foot.prepend( row ); - - temp = $tableWrap.parent().innerWidth() - - ( tsScroller.hasScrollBar( $tableWrap ) ? wo.scroller_barSetWidth : 0 ); - $tableWrap.width( temp ); - - temp = ( tsScroller.hasScrollBar( $tableWrap ) ? wo.scroller_barSetWidth : 0 ) + borderWidth; - setWidth = $tableWrap.innerWidth() - temp; - - $hdr - .parent() - .add( $foot.parent() ) - .width( setWidth ); - - $tableWrap - .width( setWidth + temp ); - - // hide original table thead - $table.children( 'thead' ).addClass( tscss.scrollerHideElement ); - - // update fixed column sizes - tsScroller.updateFixed( c, wo ); - - $div.removeClass( tscss.scrollerHideElement ); - - // restore scrollTop - fixes #926 - $tableWrap.scrollTop( wo.scroller_saved[1] ); - wo.scroller_$container - .find( '.' + tscss.scrollerFixed ) - .find( '.' + tscss.scrollerTable ) - .scrollTop( wo.scroller_saved[1] ); - - // update resizable widget handles - setTimeout( function() { - c.$table.trigger( 'resizableUpdate' ); - }, 100 ); - - }, - - // Add fixed (frozen) columns (Do not call directly, use updateFixed) - setupFixed : function( c, wo ) { - var index, index2, $el, len, temp, $fixedColumn, $fixedTbody, - $table = c.$table, - $wrapper = wo.scroller_$container, - fixedColumns = wo.scroller_fixedColumns; - - $fixedColumn = $wrapper - .addClass( tscss.scrollerHasFix ) - .clone() - .addClass( tscss.scrollerFixed ) - .removeClass( tscss.scrollerWrap ) - .attr( 'id', '' ); - - if ( wo.scroller_addFixedOverlay ) { - $fixedColumn.append( '
' ); - } - - $fixedTbody = $fixedColumn.find( '.' + tscss.scrollerTable ); - $fixedTbody - .children( 'table' ) - .addClass( c.namespace.slice( 1 ) + '_extra_table' ) - .attr( 'id', '' ) - .children( 'thead, tfoot' ) - .remove(); - - wo.scroller_$fixedColumns = $fixedColumn; - - // RTL support (fixes column on right) - if ( $table.hasClass( tscss.scrollerRtl ) ) { - $fixedColumn.addClass( tscss.scrollerRtl ); - } - - $el = $fixedColumn.find( 'tr' ); - len = $el.length; - for ( index = 0; index < len; index++ ) { - $el.eq( index ).children( ':gt(' + ( fixedColumns - 1 ) + ')' ).remove(); - } - $fixedColumn - .addClass( tscss.scrollerHideElement ) - .prependTo( $wrapper ); - - // look for filter widget - if ( c.$table.hasClass( 'hasFilters' ) ) { - // make sure fixed column filters aren't disabled - $el = $fixedColumn - .find( '.' + tscss.filter ) - .not( '.' + tscss.filterDisabled ) - .prop( 'disabled', false ); - ts.filter.bindSearch( $table, $fixedColumn.find( '.' + tscss.filter ) ); - // disable/enable filters behind fixed column - $el = $wrapper - .children( '.' + tscss.scrollerHeader ) - .find( '.' + tscss.filter ); - len = $el.length; - for ( index = 0; index < len; index++ ) { - // previously disabled filter; don't mess with it! filterDisabled class added by filter widget - if ( !$el.eq( index ).hasClass( tscss.filterDisabled || 'disabled' ) ) { - // disable filters behind fixed column; don't disable visible filters - $el.eq( index ).prop( 'disabled', index < fixedColumns ); - } - } - } - - // disable/enable tab indexes behind fixed column - c.$table - .add( '.' + tscss.scrollerFooter + ' table' ) - .children( 'thead' ) - .children( 'tr.' + tscss.headerRow ) - .children() - .attr( 'tabindex', -1 ); - - $el = wo.scroller_$header - .add( $fixedColumn.find( '.' + tscss.scrollerTable + ' table' ) ) - .children( 'thead' ) - .children( 'tr.' + tscss.headerRow ); - len = $el.length; - for ( index = 0; index < len; index++ ) { - temp = $el.eq( index ).children(); - for ( index2 = 0; index2 < temp.length; index2++ ) { - temp.eq( index2 ).attr( 'tabindex', index2 < fixedColumns ? -1 : 0 ); - } - } - - ts.bindEvents( c.table, $fixedColumn.find( '.' + tscss.header ) ); - ts.scroller.bindFixedColumnEvents( c, wo ); - - /*** Scrollbar hack! Since we can't hide the scrollbar with css ***/ - if ( ts.scroller.isFirefox || ts.scroller.isOldIE ) { - $fixedTbody.wrap( '
' ); - } - - }, - - bindFixedColumnEvents : function( c, wo ) { - // update thead & tbody in fixed column - var tsScroller = ts.scroller, - namespace = c.namespace + 'tsscrollerFixed', - events = 'scroll' + namespace, - $fixedTbody = wo.scroller_$fixedColumns.find( '.' + tscss.scrollerTable ), - fixedScroll = true, - tableScroll = true; - - c.$table - .parent() - // *** SCROLL *** scroll fixed column along with main - .off( events ) - .on( events, function() { - if ( wo.scroller_isBusy ) { return; } - // using flags to prevent firing the scroll event excessively leading to slow scrolling in Firefox - if ( !wo.scroller_isBusy && ( fixedScroll || !( tsScroller.isFirefox || tsScroller.isIE ) ) ) { - tableScroll = false; - var $this = $( this ); - $fixedTbody[0].scrollTop = wo.scroller_saved[1] = $this.scrollTop(); - wo.scroller_saved[0] = $this.scrollLeft(); - setTimeout( function() { - tableScroll = true; - }, 20 ); - } - }); - // scroll main along with fixed column - $fixedTbody - .off( events ) - .on( events, function() { - // using flags to prevent firing the scroll event excessively leading to slow scrolling in Firefox - if ( !wo.scroller_isBusy && ( tableScroll || !( tsScroller.isFirefox || tsScroller.isIE ) ) ) { - fixedScroll = false; - var $this = $( this ); - c.$table.parent()[0].scrollTop = wo.scroller_saved[1] = $this.scrollTop(); - setTimeout( function() { - fixedScroll = true; - }, 20 ); - } - }) - .scroll(); - - // *** ROW HIGHLIGHT *** - if ( wo.scroller_rowHighlight !== '' ) { - events = 'mouseover mouseleave '.split( ' ' ).join( namespace + ' ' ); - // can't use c.$tbodies because it doesn't include info-only tbodies - c.$table - .off( events, 'tbody > tr' ) - .on( events, 'tbody > tr', function( event ) { - var indx = c.$table.children( 'tbody' ).children( 'tr' ).index( this ); - $fixedTbody - .children( 'table' ) - .children( 'tbody' ) - .children( 'tr' ) - .eq( indx ) - .add( this ) - .toggleClass( wo.scroller_rowHighlight, event.type === 'mouseover' ); - }); - $fixedTbody - .find( 'table' ) - .off( events, 'tbody > tr' ) - .on( events, 'tbody > tr', function( event ) { - var $fixed = $fixedTbody.children( 'table' ).children( 'tbody' ).children( 'tr' ), - indx = $fixed.index( this ); - c.$table - .children( 'tbody' ) - .children( 'tr' ) - .eq( indx ) - .add( this ) - .toggleClass( wo.scroller_rowHighlight, event.type === 'mouseover' ); - }); - } - }, - - adjustWidth : function( c, wo, totalWidth, adj, dir ) { - var $wrapper = wo.scroller_$container; - - // RTL support (fixes column on right) - $wrapper - .children( '.' + tscss.scrollerTable ) - .css( dir ? 'right' : 'left', totalWidth ); - $wrapper - .children( '.' + tscss.scrollerHeader + ', .' + tscss.scrollerFooter ) - // Safari needs a scrollbar width of extra adjusment to align the fixed & scrolling columns - .css( dir ? 'right' : 'left', totalWidth + ( dir && ts.scroller.isSafari ? adj : 0 ) ); - }, - - updateFixed : function( c, wo ) { - var temp, adj, - $wrapper = wo.scroller_$container, - $hdr = wo.scroller_$header, - $foot = wo.scroller_$footer, - $table = c.$table, - $tableWrap = $table.parent(), - scrollBarWidth = wo.scroller_barSetWidth, - dir = $table.hasClass( tscss.scrollerRtl ); - - if ( wo.scroller_fixedColumns === 0 ) { - wo.scroller_isBusy = false; - ts.scroller.removeFixed( c, wo ); - temp = $wrapper.width(); - $tableWrap.width( temp ); adj = ts.scroller.hasScrollBar( $tableWrap ) ? scrollBarWidth : 0; + + /*** scrollbar HACK! Since we can't hide the scrollbar with css ***/ + if ( tsScroller.isFirefox || tsScroller.isOldIE ) { + $fixedTbodiesTable + .css( 'width', totalWidth ) + .parent() + .css( 'width', totalWidth + adj ); + } + + $fixedColumn.removeClass( tscss.scrollerHideElement ); + for ( index = 0; index < fixedColumns; index++ ) { + $wrapper + .children( 'div' ) + .children( 'table' ) + .find( 'th:nth-child(' + ( index + 1 ) + '), td:nth-child(' + ( index + 1 ) + ')' ) + .addClass( tscss.scrollerHideColumn ); + } + + totalWidth = totalWidth - borderRightWidth; + temp = $tableWrap.parent().innerWidth() - totalWidth; + $tableWrap.width( temp ); + // RTL support (fixes column on right) + $wrapper + .children( '.' + tscss.scrollerTable ) + .css( dir ? 'right' : 'left', totalWidth ); + $wrapper + .children( '.' + tscss.scrollerHeader + ', .' + tscss.scrollerFooter ) + // Safari needs a scrollbar width of extra adjusment to align the fixed & scrolling columns + .css( dir ? 'right' : 'left', totalWidth + ( dir && ts.scroller.isSafari ? adj : 0 ) ); + $hdr .parent() .add( $foot.parent() ) .width( temp - adj ); - return; - } - if ( !c.isScrolling ) { - return; - } + // fix gap under the tbody for the horizontal scrollbar + temp = ts.scroller.hasScrollBar( $tableWrap, true ); + adj = temp ? scrollBarWidth : 0; + if ( !$fixedColumn.find( '.' + tscss.scrollerBarSpacer ).length && temp ) { + $temp = $( '
' ) + .css( 'height', adj + 'px' ); + $fixedColumn.find( '.' + tscss.scrollerTable ).append( $temp ); + } else if ( !temp ) { + $fixedColumn.find( '.' + tscss.scrollerBarSpacer ).remove(); + } - wo.scroller_isBusy = true; + ts.scroller.updateRowHeight( c, wo ); + // set fixed column height (changes with filtering) + $fixedColumn.height( $wrapper.height() ); - // Make sure the wo.scroller_$fixedColumns container exists, if not build it - if ( !$wrapper.find( '.' + tscss.scrollerFixed ).length ) { - ts.scroller.setupFixed( c, wo ); - } + $fixedColumn.removeClass( tscss.scrollerHideElement ); - // scroller_fixedColumns - var index, tbodyIndex, rowIndex, $tbody, $adjCol, $fb, $fixHead, $fixBody, $fixFoot, - totalRows, row, + wo.scroller_isBusy = false; + + }, + + fixHeight : function( $rows, $fixedRows ) { + var index, heightRow, heightFixed, $r, $f, + len = $rows.length; + for ( index = 0; index < len; index++ ) { + $r = $rows.eq( index ); + $f = $fixedRows.eq( index ); + heightRow = $r.height(); + heightFixed = $f.height(); + if ( heightRow > heightFixed ) { + $f.addClass( tscss.scrollerAddedHeight ).height( heightRow ); + } else if ( heightRow < heightFixed ) { + $r.addClass( tscss.scrollerAddedHeight ).height( heightFixed ); + } + } + }, + + updateRowHeight : function( c, wo ) { + var $rows, $fixed, + $fixedColumns = wo.scroller_$fixedColumns; + + wo.scroller_$container + .find( '.' + tscss.scrollerAddedHeight ) + .removeClass( tscss.scrollerAddedHeight ) + .height( '' ); - // source cells for measurement - $mainTbodies = wo.scroller_$container - .children( '.' + tscss.scrollerTable ) - .children( 'table' ) - .children( 'tbody' ), - // variable gets redefined $rows = wo.scroller_$header .children( 'thead' ) - .children( '.' + tscss.headerRow ), - - // hide fixed column during resize, or we get a FOUC - $fixedColumn = wo.scroller_$fixedColumns - .addClass( tscss.scrollerHideElement ), - - // target cells - $fixedTbodiesTable = $fixedColumn - .find( '.' + tscss.scrollerTable ) - .children( 'table' ), - $fixedTbodies = $fixedTbodiesTable - .children( 'tbody' ), - // variables - tsScroller = ts.scroller, - fixedColumns = wo.scroller_fixedColumns, - // get dimensions - $temp = $table.find( 'tbody td' ), - borderRightWidth = parseInt( $temp.css( 'border-right-width' ), 10 ) || 1, - borderSpacing = parseInt( ( $temp.css( 'border-spacing' ) || '' ).split( /\s/ )[ 0 ], 10 ) / 2 || 0, - totalWidth = parseInt( $table.css( 'padding-left' ), 10 ) + - parseInt( $table.css( 'padding-right' ), 10 ) - - borderRightWidth, - widths = wo.scroller_calcWidths; - - ts.scroller.removeFixed( c, wo, false ); - - // calculate fixed column width - for ( index = 0; index < fixedColumns; index++ ) { - totalWidth += widths[ index ] + borderSpacing; - } - - // set fixed column width - totalWidth = totalWidth + borderRightWidth * 2 - borderSpacing; - tsScroller.setWidth( $fixedColumn.add( $fixedColumn.children() ), totalWidth ); - tsScroller.setWidth( $fixedColumn.children().children( 'table' ), totalWidth ); - - // update fixed column tbody content, set cell widths on hidden row - for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { - $tbody = $mainTbodies.eq( tbodyIndex ); - if ( $tbody.length ) { - // get tbody - $rows = $tbody.children(); - totalRows = $rows.length; - $fb = ts.processTbody( $fixedTbodiesTable, $fixedTbodies.eq( tbodyIndex ), true ); - $fb.empty(); - // update tbody cells after sort/filtering - for ( rowIndex = 0; rowIndex < totalRows; rowIndex++ ) { - $adjCol = $( $rows[ rowIndex ].outerHTML ); - $adjCol - .children( 'td, th' ) - .slice( fixedColumns ) - .remove(); - $fb.append( $adjCol ); - } - - // restore tbody - ts.processTbody( $fixedTbodiesTable, $fb, false ); - } - } - - adj = ts.scroller.hasScrollBar( $tableWrap ) ? scrollBarWidth : 0; - - /*** scrollbar HACK! Since we can't hide the scrollbar with css ***/ - if ( tsScroller.isFirefox || tsScroller.isOldIE ) { - $fixedTbodiesTable - .css( 'width', totalWidth ) - .parent() - .css( 'width', totalWidth + adj ); - } - - $fixedColumn.removeClass( tscss.scrollerHideElement ); - for ( index = 0; index < fixedColumns; index++ ) { - $wrapper - .children( 'div' ) + .children( 'tr' ); + $fixed = $fixedColumns + .children( '.' + tscss.scrollerHeader ) .children( 'table' ) - .find( 'th:nth-child(' + ( index + 1 ) + '), td:nth-child(' + ( index + 1 ) + ')' ) - .addClass( tscss.scrollerHideColumn ); - } + .children( 'thead' ) + .children( 'tr' ); + ts.scroller.fixHeight( $rows, $fixed ); - totalWidth = totalWidth - borderRightWidth; - temp = $tableWrap.parent().innerWidth() - totalWidth; - $tableWrap.width( temp ); - // RTL support (fixes column on right) - $wrapper - .children( '.' + tscss.scrollerTable ) - .css( dir ? 'right' : 'left', totalWidth ); - $wrapper - .children( '.' + tscss.scrollerHeader + ', .' + tscss.scrollerFooter ) - // Safari needs a scrollbar width of extra adjusment to align the fixed & scrolling columns - .css( dir ? 'right' : 'left', totalWidth + ( dir && ts.scroller.isSafari ? adj : 0 ) ); + $rows = wo.scroller_$footer + .children( 'tfoot' ) + .children( 'tr' ); + $fixed = $fixedColumns + .children( '.' + tscss.scrollerFooter ) + .children( 'table' ) + .children( 'tfoot' ) + .children( 'tr' ); + ts.scroller.fixHeight( $rows, $fixed ); - $hdr - .parent() - .add( $foot.parent() ) - .width( temp - adj ); + if ( ts.scroller.isFirefox || ts.scroller.isOldIE ) { + // Firefox/Old IE scrollbar hack (wraps table to hide the scrollbar) + $fixedColumns = $fixedColumns.find( '.' + tscss.scrollerHack ); + } + $rows = c.$table + .children( 'tbody' ) + .children( 'tr' ); + $fixed = $fixedColumns + .children( '.' + tscss.scrollerTable ) + .children( 'table' ) + .children( 'tbody' ) + .children( 'tr' ); + ts.scroller.fixHeight( $rows, $fixed ); - // fix gap under the tbody for the horizontal scrollbar - temp = ts.scroller.hasScrollBar( $tableWrap, true ); - adj = temp ? scrollBarWidth : 0; - if ( !$fixedColumn.find( '.' + tscss.scrollerBarSpacer ).length && temp ) { - $temp = $( '
' ) - .css( 'height', adj + 'px' ); - $fixedColumn.find( '.' + tscss.scrollerTable ).append( $temp ); - } else if ( !temp ) { - $fixedColumn.find( '.' + tscss.scrollerBarSpacer ).remove(); - } + }, - ts.scroller.updateRowHeight( c, wo ); - // set fixed column height (changes with filtering) - $fixedColumn.height( $wrapper.height() ); + removeFixed : function( c, wo, removeIt ) { + var $table = c.$table, + $wrapper = wo.scroller_$container, + dir = $table.hasClass( tscss.scrollerRtl ); - $fixedColumn.removeClass( tscss.scrollerHideElement ); + // remove fixed columns + if ( removeIt || typeof removeIt === 'undefined' ) { + $wrapper.find( '.' + tscss.scrollerFixed ).remove(); + } - wo.scroller_isBusy = false; + $wrapper + .find( '.' + tscss.scrollerHideColumn ) + .removeClass( tscss.scrollerHideColumn ); - }, + // RTL support ( fixes column on right ) + $wrapper + .children( ':not(.' + tscss.scrollerFixed + ')' ) + .css( dir ? 'right' : 'left', 0 ); + }, - fixHeight : function( $rows, $fixedRows ) { - var index, heightRow, heightFixed, $r, $f, - len = $rows.length; - for ( index = 0; index < len; index++ ) { - $r = $rows.eq( index ); - $f = $fixedRows.eq( index ); - heightRow = $r.height(); - heightFixed = $f.height(); - if ( heightRow > heightFixed ) { - $f.addClass( tscss.scrollerAddedHeight ).height( heightRow ); - } else if ( heightRow < heightFixed ) { - $r.addClass( tscss.scrollerAddedHeight ).height( heightFixed ); + remove : function( c, wo ) { + var $wrap = wo.scroller_$container, + namespace = c.namespace + 'tsscroller'; + c.$table.off( namespace ); + $( window ).off( namespace ); + if ( $wrap ) { + c.$table + .insertBefore( $wrap ) + .find( 'thead' ) + .removeClass( tscss.scrollerHideElement ) + .children( 'tr.' + tscss.headerRow ) + .children() + .attr( 'tabindex', 0 ) + .end() + .find( '.' + tscss.filterRow ) + .removeClass( tscss.scrollerHideElement + ' ' + tscss.filterRowHide ); + c.$table + .find( '.' + tscss.filter ) + .not( '.' + tscss.filterDisabled ) + .prop( 'disabled', false ); + $wrap.remove(); + c.isScrolling = false; } } - }, - updateRowHeight : function( c, wo ) { - var $rows, $fixed, - $fixedColumns = wo.scroller_$fixedColumns; - - wo.scroller_$container - .find( '.' + tscss.scrollerAddedHeight ) - .removeClass( tscss.scrollerAddedHeight ) - .height( '' ); - - $rows = wo.scroller_$header - .children( 'thead' ) - .children( 'tr' ); - $fixed = $fixedColumns - .children( '.' + tscss.scrollerHeader ) - .children( 'table' ) - .children( 'thead' ) - .children( 'tr' ); - ts.scroller.fixHeight( $rows, $fixed ); - - $rows = wo.scroller_$footer - .children( 'tfoot' ) - .children( 'tr' ); - $fixed = $fixedColumns - .children( '.' + tscss.scrollerFooter ) - .children( 'table' ) - .children( 'tfoot' ) - .children( 'tr' ); - ts.scroller.fixHeight( $rows, $fixed ); - - if ( ts.scroller.isFirefox || ts.scroller.isOldIE ) { - // Firefox/Old IE scrollbar hack (wraps table to hide the scrollbar) - $fixedColumns = $fixedColumns.find( '.' + tscss.scrollerHack ); - } - $rows = c.$table - .children( 'tbody' ) - .children( 'tr' ); - $fixed = $fixedColumns - .children( '.' + tscss.scrollerTable ) - .children( 'table' ) - .children( 'tbody' ) - .children( 'tr' ); - ts.scroller.fixHeight( $rows, $fixed ); - - }, - - removeFixed : function( c, wo, removeIt ) { - var $table = c.$table, - $wrapper = wo.scroller_$container, - dir = $table.hasClass( tscss.scrollerRtl ); - - // remove fixed columns - if ( removeIt || typeof removeIt === 'undefined' ) { - $wrapper.find( '.' + tscss.scrollerFixed ).remove(); - } - - $wrapper - .find( '.' + tscss.scrollerHideColumn ) - .removeClass( tscss.scrollerHideColumn ); - - // RTL support ( fixes column on right ) - $wrapper - .children( ':not(.' + tscss.scrollerFixed + ')' ) - .css( dir ? 'right' : 'left', 0 ); - }, - - remove : function( c, wo ) { - var $wrap = wo.scroller_$container, - namespace = c.namespace + 'tsscroller'; - c.$table.off( namespace ); - $( window ).off( namespace ); - if ( $wrap ) { - c.$table - .insertBefore( $wrap ) - .find( 'thead' ) - .removeClass( tscss.scrollerHideElement ) - .children( 'tr.' + tscss.headerRow ) - .children() - .attr( 'tabindex', 0 ) - .end() - .find( '.' + tscss.filterRow ) - .removeClass( tscss.scrollerHideElement + ' ' + tscss.filterRowHide ); - c.$table - .find( '.' + tscss.filter ) - .not( '.' + tscss.filterDisabled ) - .prop( 'disabled', false ); - $wrap.remove(); - c.isScrolling = false; - } - } - -}; + }; })( jQuery, window ); diff --git a/js/widgets/widget-sort2Hash.js b/js/widgets/widget-sort2Hash.js index 209495e3..2c210809 100644 --- a/js/widgets/widget-sort2Hash.js +++ b/js/widgets/widget-sort2Hash.js @@ -1,128 +1,128 @@ /*! Widget: sort2Hash - updated 7/21/2015 (v2.22.4) */ ;( function( $ ) { -'use strict'; -var ts = $.tablesorter || {}, -s2h = { - init : function( c, wo ) { - var hasSaveSort = ts.hasWidget( c.table, 'saveSort' ), - sort = s2h.getSort( c, wo ); - if ( ( sort && !hasSaveSort ) || ( sort && hasSaveSort && wo.sort2Hash_overrideSaveSort ) ) { - s2h.processHash( c, wo, sort ); - } - c.$table.on( 'sortEnd.sort2hash', function() { - s2h.setHash( c, wo ); - }); - }, - getTableId : function( c, wo ) { - // option > table id > table index on page - return wo.sort2Hash_tableId || - c.table.id || - 'table' + $( 'table' ).index( c.$table ); - }, - getSort : function( c, wo, clean ) { - // modified original code from http://www.netlobo.com/url_query_string_javascript.html - var value, - name = s2h.getTableId( c, wo ).replace( /[\[]/, '\\[' ).replace( /[\]]/, '\\]' ), - sort = ( new RegExp( '[\\#&]' + name + '=([^&]*)' ) ).exec( window.location.hash ); - if ( sort === null ) { - return ''; - } else { - value = s2h.processSort( c, wo, sort[ 1 ] ); - if ( clean ) { - window.location.hash = window.location.hash.replace( '&' + name + '=' + sort[ 1 ], '' ); - return value; + 'use strict'; + var ts = $.tablesorter || {}, + s2h = { + init : function( c, wo ) { + var hasSaveSort = ts.hasWidget( c.table, 'saveSort' ), + sort = s2h.getSort( c, wo ); + if ( ( sort && !hasSaveSort ) || ( sort && hasSaveSort && wo.sort2Hash_overrideSaveSort ) ) { + s2h.processHash( c, wo, sort ); } - return sort[ 1 ]; - } - }, - // convert 'first%20name,asc,last%20name,desc' into [[0,0], [1,1]] - processHash : function( c, wo, sortHash ) { - var regex, column, direction, temp, - arry = decodeURI( sortHash || '' ).split( wo.sort2Hash_separator ), - indx = 0, - len = arry.length, - sort = []; - while ( indx < len ) { - // column index or text - column = arry[ indx++ ]; - temp = parseInt( column, 10 ); - // ignore wo.sort2Hash_useHeaderText setting & - // just see if column contains a number - if ( isNaN( temp ) || temp > c.columns ) { - regex = new RegExp( '(' + column + ')', 'i' ); - column = c.$headers.filter( function( index, cell ) { - return regex.test( c.$headers[ index ].textContent || '' ); - }).attr( 'data-column' ); - } - direction = arry[ indx++ ]; - // ignore unpaired values - if ( typeof direction !== 'undefined' ) { - // convert text to 0, 1 - if ( isNaN( direction ) ) { - // default to ascending sort - direction = direction.indexOf( wo.sort2Hash_directionText[ 1 ] ) > -1 ? 1 : 0; + c.$table.on( 'sortEnd.sort2hash', function() { + s2h.setHash( c, wo ); + }); + }, + getTableId : function( c, wo ) { + // option > table id > table index on page + return wo.sort2Hash_tableId || + c.table.id || + 'table' + $( 'table' ).index( c.$table ); + }, + getSort : function( c, wo, clean ) { + // modified original code from http://www.netlobo.com/url_query_string_javascript.html + var value, + name = s2h.getTableId( c, wo ).replace( /[\[]/, '\\[' ).replace( /[\]]/, '\\]' ), + sort = ( new RegExp( '[\\#&]' + name + '=([^&]*)' ) ).exec( window.location.hash ); + if ( sort === null ) { + return ''; + } else { + value = s2h.processSort( c, wo, sort[ 1 ] ); + if ( clean ) { + window.location.hash = window.location.hash.replace( '&' + name + '=' + sort[ 1 ], '' ); + return value; } - sort.push( [ column, direction ] ); + return sort[ 1 ]; } - } - if ( sort.length ) { - c.sortList = sort; - } - }, - - // convert [[0,0],[1,1]] to 'first%20name,asc,last%20name,desc' - processSort : function( c, wo ) { - var index, txt, column, direction, - sort = [], - arry = c.sortList || [], - len = arry.length; - for ( index = 0; index < len; index++ ) { - column = arry[ index ][ 0 ]; - if ( wo.sort2Hash_useHeaderText ) { - txt = $.trim( c.$headerIndexed[ column ].text() ); - if ( typeof wo.sort2Hash_processHeaderText === 'function' ) { - txt = wo.sort2Hash_processHeaderText( txt, c, column ); + }, + // convert 'first%20name,asc,last%20name,desc' into [[0,0], [1,1]] + processHash : function( c, wo, sortHash ) { + var regex, column, direction, temp, + arry = decodeURI( sortHash || '' ).split( wo.sort2Hash_separator ), + indx = 0, + len = arry.length, + sort = []; + while ( indx < len ) { + // column index or text + column = arry[ indx++ ]; + temp = parseInt( column, 10 ); + // ignore wo.sort2Hash_useHeaderText setting & + // just see if column contains a number + if ( isNaN( temp ) || temp > c.columns ) { + regex = new RegExp( '(' + column + ')', 'i' ); + column = c.$headers.filter( function( index, cell ) { + return regex.test( c.$headers[ index ].textContent || '' ); + }).attr( 'data-column' ); + } + direction = arry[ indx++ ]; + // ignore unpaired values + if ( typeof direction !== 'undefined' ) { + // convert text to 0, 1 + if ( isNaN( direction ) ) { + // default to ascending sort + direction = direction.indexOf( wo.sort2Hash_directionText[ 1 ] ) > -1 ? 1 : 0; + } + sort.push( [ column, direction ] ); } - column = txt; } - sort.push( column ); - direction = wo.sort2Hash_directionText[ arry[ index ][ 1 ] ]; - sort.push( direction ); + if ( sort.length ) { + c.sortList = sort; + } + }, - } - // join with separator - return sort.join( wo.sort2Hash_separator ); - }, - setHash : function( c, wo ) { - var arry = [], - sort = s2h.processSort( c, wo ); - if ( sort.length ) { - // remove old hash - s2h.getSort( c, wo, true ); - window.location.hash += ( window.location.hash.length ? '' : wo.sort2Hash_hash ) + - '&' + s2h.getTableId( c, wo ) + '=' + encodeURI( sort ); - } - } -}; + // convert [[0,0],[1,1]] to 'first%20name,asc,last%20name,desc' + processSort : function( c, wo ) { + var index, txt, column, direction, + sort = [], + arry = c.sortList || [], + len = arry.length; + for ( index = 0; index < len; index++ ) { + column = arry[ index ][ 0 ]; + if ( wo.sort2Hash_useHeaderText ) { + txt = $.trim( c.$headerIndexed[ column ].text() ); + if ( typeof wo.sort2Hash_processHeaderText === 'function' ) { + txt = wo.sort2Hash_processHeaderText( txt, c, column ); + } + column = txt; + } + sort.push( column ); + direction = wo.sort2Hash_directionText[ arry[ index ][ 1 ] ]; + sort.push( direction ); -ts.addWidget({ - id: 'sort2Hash', - priority: 30, // after saveSort - options: { - sort2Hash_hash : '#', // hash prefix - sort2Hash_separator : '-', // don't '#' or '=' here - sort2Hash_tableId : null, // this option > table ID > table index on page, - sort2Hash_useHeaderText : false, // use column header text (true) or zero-based column index - sort2Hash_processHeaderText : null, // function( text, config, columnIndex ) {}, - sort2Hash_directionText : [ 0, 1 ], // [ 'asc', 'desc' ], - sort2Hash_overrideSaveSort : false // if true, override saveSort widget if saved sort available - }, - init: function(table, thisWidget, c, wo) { - s2h.init( c, wo ); - }, - remove: function(table, c) { - c.$table.off( 'sortEnd.sort2hash' ); - } -}); + } + // join with separator + return sort.join( wo.sort2Hash_separator ); + }, + setHash : function( c, wo ) { + var arry = [], + sort = s2h.processSort( c, wo ); + if ( sort.length ) { + // remove old hash + s2h.getSort( c, wo, true ); + window.location.hash += ( window.location.hash.length ? '' : wo.sort2Hash_hash ) + + '&' + s2h.getTableId( c, wo ) + '=' + encodeURI( sort ); + } + } + }; + + ts.addWidget({ + id: 'sort2Hash', + priority: 30, // after saveSort + options: { + sort2Hash_hash : '#', // hash prefix + sort2Hash_separator : '-', // don't '#' or '=' here + sort2Hash_tableId : null, // this option > table ID > table index on page, + sort2Hash_useHeaderText : false, // use column header text (true) or zero-based column index + sort2Hash_processHeaderText : null, // function( text, config, columnIndex ) {}, + sort2Hash_directionText : [ 0, 1 ], // [ 'asc', 'desc' ], + sort2Hash_overrideSaveSort : false // if true, override saveSort widget if saved sort available + }, + init: function(table, thisWidget, c, wo) { + s2h.init( c, wo ); + }, + remove: function(table, c) { + c.$table.off( 'sortEnd.sort2hash' ); + } + }); })(jQuery); diff --git a/js/widgets/widget-sortTbodies.js b/js/widgets/widget-sortTbodies.js index 9d497400..73af3e2f 100644 --- a/js/widgets/widget-sortTbodies.js +++ b/js/widgets/widget-sortTbodies.js @@ -5,224 +5,224 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;( function( $ ) { -'use strict'; -var ts = $.tablesorter; + 'use strict'; + var ts = $.tablesorter; -ts.sortTbodies = { - init: function( c, wo ) { + ts.sortTbodies = { + init: function( c, wo ) { - var index, rows, txt, max, $rows, - namespace = c.namespace + 'sortTbody', - $tbodies = c.$table.children( 'tbody' ), - len = $tbodies.length; + var index, rows, txt, max, $rows, + namespace = c.namespace + 'sortTbody', + $tbodies = c.$table.children( 'tbody' ), + len = $tbodies.length; - // save serverSideSorting value; use to toggle internal row sorting - wo.sortTbody_original_serverSideSorting = c.serverSideSorting; + // save serverSideSorting value; use to toggle internal row sorting + wo.sortTbody_original_serverSideSorting = c.serverSideSorting; - // include info-only tbodies - we need parsed data from *all* tbodies - wo.sortTbody_original_cssInfoBlock = c.cssInfoBlock; - c.cssInfoBlock = wo.sortTbody_noSort; - ts.sortTbodies.setTbodies( c, wo ); - - // add original order index for stable sort - for ( index = 0; index < len; index++ ) { - $tbodies.eq( index ).attr( 'data-ts-original-order', index ); - } - - c.$table - .unbind( 'sortBegin updateComplete '.split( ' ' ).join( namespace + ' ' ) ) - .bind( 'sortBegin' + namespace, function() { - ts.sortTbodies.sorter( c ); - }) - .bind( 'updateComplete' + namespace, function() { - // find parsers for each column - ts.sortTbodies.setTbodies( c, wo ); - c.$table.trigger( 'updateCache', [ null, c.$tbodies ] ); - }); - - // detect parsers - in case the table contains only info-only tbodies - if ( $.isEmptyObject( c.parsers ) || c.$tbodies.length !== $tbodies.length ) { + // include info-only tbodies - we need parsed data from *all* tbodies + wo.sortTbody_original_cssInfoBlock = c.cssInfoBlock; + c.cssInfoBlock = wo.sortTbody_noSort; ts.sortTbodies.setTbodies( c, wo ); - c.$table.trigger( 'updateCache', [ null, c.$tbodies ] ); - } - // find colMax; this only matter for numeric columns - $rows = $tbodies.children( 'tr' ); - len = $rows.length; - for ( index = 0; index < c.columns; index++ ) { - max = 0; - if ( c.parsers[ index ].type === 'numeric' ) { - for ( rows = 0; rows < len; rows++ ) { - // update column max value (ignore sign) - txt = ts.getParsedText( c, $rows.eq( rows ).children()[ index ], index ); - max = Math.max( Math.abs( txt ) || 0, max ); - } + // add original order index for stable sort + for ( index = 0; index < len; index++ ) { + $tbodies.eq( index ).attr( 'data-ts-original-order', index ); } - c.$headerIndexed[ index ].attr( 'data-ts-col-max-value', max ); - } - }, - - // make sure c.$tbodies is up-to-date (init & after updates) - setTbodies: function( c, wo ) { - c.$tbodies = c.$table.children( 'tbody' ).not( '.' + wo.sortTbody_noSort ); - }, - - sorter: function( c ) { - var $table = c.$table, - wo = c.widgetOptions; - - // prevent multiple calls while processing - if ( wo.sortTbody_busy !== true ) { - wo.sortTbody_busy = true; - var $tbodies = $table.children( 'tbody' ).not( '.' + wo.sortTbody_noSort ), - primary = wo.sortTbody_primaryRow || 'tr:eq(0)', - sortList = c.sortList || [], - len = sortList.length; - - if ( len ) { - - // toggle internal row sorting - c.serverSideSorting = !wo.sortTbody_sortRows; - - $tbodies.sort( function( a, b ) { - var sortListIndex, txt, dir, num, colMax, sort, col, order, colA, colB, x, y, - table = c.table, - parsers = c.parsers, - cts = c.textSorter || '', - $tbodyA = $( a ), - $tbodyB = $( b ), - $a = $tbodyA.find( primary ).children( 'td, th' ), - $b = $tbodyB.find( primary ).children( 'td, th' ); - for ( sortListIndex = 0; sortListIndex < len; sortListIndex++ ) { - col = sortList[ sortListIndex ][0]; - order = sortList[ sortListIndex ][1]; - // sort direction, true = asc, false = desc - dir = order === 0; - // column txt - tbody A - txt = ts.getElementText( c, $a.eq( col ), col ); - colA = parsers[ col ].format( txt, table, $a[ col ], col ); - // column txt - tbody B - txt = ts.getElementText( c, $b.eq( col ), col ); - colB = parsers[ col ].format( txt, table, $b[ col ], col ); - - if (c.sortStable && colA === colB && len === 1) { - return $tbodyA.attr( 'data-ts-original-order' ) - $tbodyB.attr( 'data-ts-original-order' ); - } - - // fallback to natural sort since it is more robust - num = /n/i.test( parsers && parsers[ col ] ? parsers[ col ].type || '' : '' ); - if ( num && c.strings[ col ] ) { - colMax = c.$headerIndexed[ col ].attr( 'data-ts-col-max-value' ) || - 1.79E+308; // close to Number.MAX_VALUE - // sort strings in numerical columns - if ( typeof ( c.string[ c.strings[ col ] ] ) === 'boolean' ) { - num = ( dir ? 1 : -1 ) * ( c.string[ c.strings[ col ] ] ? -1 : 1 ); - } else { - num = ( c.strings[ col ] ) ? c.string[ c.strings[ col ] ] || 0 : 0; - } - // fall back to built-in numeric sort - // var sort = $.tablesorter['sort' + s](a, b, dir, colMax, table); - sort = c.numberSorter ? c.numberSorter( colA, colB, dir, colMax, table ) : - ts[ 'sortNumeric' + ( dir ? 'Asc' : 'Desc' ) ]( colA, colB, num, colMax, col, table ); - } else { - // set a & b depending on sort direction - x = dir ? colA : colB; - y = dir ? colB : colA; - // text sort function - if ( typeof ( cts ) === 'function' ) { - // custom OVERALL text sorter - sort = cts( x, y, dir, col, table ); - } else if ( typeof ( cts ) === 'object' && cts.hasOwnProperty( col ) ) { - // custom text sorter for a SPECIFIC COLUMN - sort = cts[ col ]( x, y, dir, col, table ); - } else { - // fall back to natural sort - sort = ts[ 'sortNatural' + ( dir ? 'Asc' : 'Desc' ) ]( colA, colB, col, table, c ); - } - } - if ( sort ) { return sort; } - } - return $tbodyA.attr( 'data-ts-original-order' ) - $tbodyB.attr( 'data-ts-original-order' ); + c.$table + .unbind( 'sortBegin updateComplete '.split( ' ' ).join( namespace + ' ' ) ) + .bind( 'sortBegin' + namespace, function() { + ts.sortTbodies.sorter( c ); + }) + .bind( 'updateComplete' + namespace, function() { + // find parsers for each column + ts.sortTbodies.setTbodies( c, wo ); + c.$table.trigger( 'updateCache', [ null, c.$tbodies ] ); }); - ts.sortTbodies.restoreTbodies ( c, wo, $tbodies ); - wo.sortTbody_busy = false; + // detect parsers - in case the table contains only info-only tbodies + if ( $.isEmptyObject( c.parsers ) || c.$tbodies.length !== $tbodies.length ) { + ts.sortTbodies.setTbodies( c, wo ); + c.$table.trigger( 'updateCache', [ null, c.$tbodies ] ); } - } - }, - - restoreTbodies : function ( c, wo, $sortedTbodies ) { - var $nosort, $tbodies, $thisTbody, tbLen, nsLen, index, targetIndex, - $table = c.$table, - hasShuffled = true, - indx = 0; - - // hide entire table to improve sort performance - $table.hide(); - $sortedTbodies.appendTo( $table ); - - // reposition no-sort tbodies - $tbodies = $table.children( 'tbody' ); - tbLen = $tbodies.length; - $nosort = $tbodies.filter( '.' + wo.sortTbody_noSort ).appendTo( $table ); - nsLen = $nosort.length; - - if ( nsLen ) { - // don't allow the while loop to cycle more times than the set number of no-sort tbodies - while ( hasShuffled && indx < nsLen ) { - hasShuffled = false; - for ( index = 0; index < nsLen; index++ ) { - targetIndex = parseInt( $nosort.eq( index ).attr( 'data-ts-original-order' ), 10 ); - // if target index > number of tbodies, make it last - targetIndex = targetIndex >= tbLen ? tbLen : targetIndex < 0 ? 0 : targetIndex; - - if ( targetIndex !== $nosort.eq( index ).index() ) { - hasShuffled = true; - $thisTbody = $nosort.eq( index ).detach(); - - if ( targetIndex >= tbLen ) { - // Are we trying to be the last tbody? - $thisTbody.appendTo( $table ); - } else if ( targetIndex === 0 ) { - // Are we trying to be the first tbody? - $thisTbody.prependTo( $table ); - } else { - // No, we want to be somewhere in the middle! - $thisTbody.insertBefore( $table.children( 'tbody:eq(' + targetIndex + ')' ) ); - } + // find colMax; this only matter for numeric columns + $rows = $tbodies.children( 'tr' ); + len = $rows.length; + for ( index = 0; index < c.columns; index++ ) { + max = 0; + if ( c.parsers[ index ].type === 'numeric' ) { + for ( rows = 0; rows < len; rows++ ) { + // update column max value (ignore sign) + txt = ts.getParsedText( c, $rows.eq( rows ).children()[ index ], index ); + max = Math.max( Math.abs( txt ) || 0, max ); } } - indx++; + c.$headerIndexed[ index ].attr( 'data-ts-col-max-value', max ); } + + }, + + // make sure c.$tbodies is up-to-date (init & after updates) + setTbodies: function( c, wo ) { + c.$tbodies = c.$table.children( 'tbody' ).not( '.' + wo.sortTbody_noSort ); + }, + + sorter: function( c ) { + var $table = c.$table, + wo = c.widgetOptions; + + // prevent multiple calls while processing + if ( wo.sortTbody_busy !== true ) { + wo.sortTbody_busy = true; + var $tbodies = $table.children( 'tbody' ).not( '.' + wo.sortTbody_noSort ), + primary = wo.sortTbody_primaryRow || 'tr:eq(0)', + sortList = c.sortList || [], + len = sortList.length; + + if ( len ) { + + // toggle internal row sorting + c.serverSideSorting = !wo.sortTbody_sortRows; + + $tbodies.sort( function( a, b ) { + var sortListIndex, txt, dir, num, colMax, sort, col, order, colA, colB, x, y, + table = c.table, + parsers = c.parsers, + cts = c.textSorter || '', + $tbodyA = $( a ), + $tbodyB = $( b ), + $a = $tbodyA.find( primary ).children( 'td, th' ), + $b = $tbodyB.find( primary ).children( 'td, th' ); + for ( sortListIndex = 0; sortListIndex < len; sortListIndex++ ) { + col = sortList[ sortListIndex ][0]; + order = sortList[ sortListIndex ][1]; + // sort direction, true = asc, false = desc + dir = order === 0; + // column txt - tbody A + txt = ts.getElementText( c, $a.eq( col ), col ); + colA = parsers[ col ].format( txt, table, $a[ col ], col ); + // column txt - tbody B + txt = ts.getElementText( c, $b.eq( col ), col ); + colB = parsers[ col ].format( txt, table, $b[ col ], col ); + + if (c.sortStable && colA === colB && len === 1) { + return $tbodyA.attr( 'data-ts-original-order' ) - $tbodyB.attr( 'data-ts-original-order' ); + } + + // fallback to natural sort since it is more robust + num = /n/i.test( parsers && parsers[ col ] ? parsers[ col ].type || '' : '' ); + if ( num && c.strings[ col ] ) { + colMax = c.$headerIndexed[ col ].attr( 'data-ts-col-max-value' ) || + 1.79E+308; // close to Number.MAX_VALUE + // sort strings in numerical columns + if ( typeof ( c.string[ c.strings[ col ] ] ) === 'boolean' ) { + num = ( dir ? 1 : -1 ) * ( c.string[ c.strings[ col ] ] ? -1 : 1 ); + } else { + num = ( c.strings[ col ] ) ? c.string[ c.strings[ col ] ] || 0 : 0; + } + // fall back to built-in numeric sort + // var sort = $.tablesorter['sort' + s](a, b, dir, colMax, table); + sort = c.numberSorter ? c.numberSorter( colA, colB, dir, colMax, table ) : + ts[ 'sortNumeric' + ( dir ? 'Asc' : 'Desc' ) ]( colA, colB, num, colMax, col, table ); + } else { + // set a & b depending on sort direction + x = dir ? colA : colB; + y = dir ? colB : colA; + // text sort function + if ( typeof ( cts ) === 'function' ) { + // custom OVERALL text sorter + sort = cts( x, y, dir, col, table ); + } else if ( typeof ( cts ) === 'object' && cts.hasOwnProperty( col ) ) { + // custom text sorter for a SPECIFIC COLUMN + sort = cts[ col ]( x, y, dir, col, table ); + } else { + // fall back to natural sort + sort = ts[ 'sortNatural' + ( dir ? 'Asc' : 'Desc' ) ]( colA, colB, col, table, c ); + } + } + if ( sort ) { return sort; } + } + return $tbodyA.attr( 'data-ts-original-order' ) - $tbodyB.attr( 'data-ts-original-order' ); + }); + + ts.sortTbodies.restoreTbodies( c, wo, $tbodies ); + wo.sortTbody_busy = false; + } + } + }, + + restoreTbodies : function ( c, wo, $sortedTbodies ) { + var $nosort, $tbodies, $thisTbody, tbLen, nsLen, index, targetIndex, + $table = c.$table, + hasShuffled = true, + indx = 0; + + // hide entire table to improve sort performance + $table.hide(); + $sortedTbodies.appendTo( $table ); + + // reposition no-sort tbodies + $tbodies = $table.children( 'tbody' ); + tbLen = $tbodies.length; + $nosort = $tbodies.filter( '.' + wo.sortTbody_noSort ).appendTo( $table ); + nsLen = $nosort.length; + + if ( nsLen ) { + // don't allow the while loop to cycle more times than the set number of no-sort tbodies + while ( hasShuffled && indx < nsLen ) { + hasShuffled = false; + for ( index = 0; index < nsLen; index++ ) { + targetIndex = parseInt( $nosort.eq( index ).attr( 'data-ts-original-order' ), 10 ); + // if target index > number of tbodies, make it last + targetIndex = targetIndex >= tbLen ? tbLen : targetIndex < 0 ? 0 : targetIndex; + + if ( targetIndex !== $nosort.eq( index ).index() ) { + hasShuffled = true; + $thisTbody = $nosort.eq( index ).detach(); + + if ( targetIndex >= tbLen ) { + // Are we trying to be the last tbody? + $thisTbody.appendTo( $table ); + } else if ( targetIndex === 0 ) { + // Are we trying to be the first tbody? + $thisTbody.prependTo( $table ); + } else { + // No, we want to be somewhere in the middle! + $thisTbody.insertBefore( $table.children( 'tbody:eq(' + targetIndex + ')' ) ); + } + + } + } + indx++; + } + } + + $table.show(); } - $table.show(); - } + }; -}; - -ts.addWidget({ - id: 'sortTbody', - // priority < 50 (filter widget), so c.$tbodies has the correct elements - priority: 40, - options: { - // point to a row within the tbody that matches the number of header columns - sortTbody_primaryRow : null, - // sort tbody internal rows - sortTbody_sortRows : false, - // static tbodies (like static rows) - sortTbody_noSort : 'tablesorter-no-sort-tbody' - }, - init : function( table, thisWidget, c, wo ) { - ts.sortTbodies.init( c, wo ); - }, - remove : function( table, c, wo, refreshing ) { - c.$table.unbind( 'sortBegin updateComplete '.split( ' ' ).join( c.namespace + 'sortTbody ' ) ); - c.serverSideSorting = wo.sortTbody_original_serverSideSorting; - c.cssInfoBlock = wo.sortTbody_original_cssInfoBlock; - } -}); + ts.addWidget({ + id: 'sortTbody', + // priority < 50 (filter widget), so c.$tbodies has the correct elements + priority: 40, + options: { + // point to a row within the tbody that matches the number of header columns + sortTbody_primaryRow : null, + // sort tbody internal rows + sortTbody_sortRows : false, + // static tbodies (like static rows) + sortTbody_noSort : 'tablesorter-no-sort-tbody' + }, + init : function( table, thisWidget, c, wo ) { + ts.sortTbodies.init( c, wo ); + }, + remove : function( table, c, wo, refreshing ) { + c.$table.unbind( 'sortBegin updateComplete '.split( ' ' ).join( c.namespace + 'sortTbody ' ) ); + c.serverSideSorting = wo.sortTbody_original_serverSideSorting; + c.cssInfoBlock = wo.sortTbody_original_cssInfoBlock; + } + }); })( jQuery ); diff --git a/js/widgets/widget-staticRow.js b/js/widgets/widget-staticRow.js index ea04b85d..7c34b808 100644 --- a/js/widgets/widget-staticRow.js +++ b/js/widgets/widget-staticRow.js @@ -12,110 +12,110 @@ /*jshint browser:true, jquery:true, unused:false */ /*global jQuery: false */ ;(function($){ -"use strict"; -var ts = $.tablesorter, + 'use strict'; + var ts = $.tablesorter, -// add/refresh row indexes -addIndexes = function(table){ - var $tr, wo, v, indx, rows, - c = table.config; - // "Index" the static rows, saving their current (starting) position in the - // table inside a data() param on the element itself for later use. - if (c) { - wo = c.widgetOptions; - c.$tbodies.each(function(){ - $tr = $(this).children(); - rows = $tr.length; - $tr.filter(wo.staticRow_class).each(function() { - $tr = $(this); - indx = $tr.data(wo.staticRow_index); - if (typeof indx !== "undefined") { - v = parseFloat(indx); - // percentage of total rows - indx = (/%/.test(indx)) ? Math.round(v/100 * rows) : v; - } else { - indx = $tr.index(); - } - // row indexing starts over within each tbody - $tr.data( wo.staticRow_data, indx ); + // add/refresh row indexes + addIndexes = function(table){ + var $tr, wo, v, indx, rows, + c = table.config; + // 'Index' the static rows, saving their current (starting) position in the + // table inside a data() param on the element itself for later use. + if (c) { + wo = c.widgetOptions; + c.$tbodies.each(function(){ + $tr = $(this).children(); + rows = $tr.length; + $tr.filter(wo.staticRow_class).each(function() { + $tr = $(this); + indx = $tr.data(wo.staticRow_index); + if (typeof indx !== 'undefined') { + v = parseFloat(indx); + // percentage of total rows + indx = (/%/.test(indx)) ? Math.round(v / 100 * rows) : v; + } else { + indx = $tr.index(); + } + // row indexing starts over within each tbody + $tr.data( wo.staticRow_data, indx ); + }); }); - }); - } -}; + } + }; -ts.addWidget({ - // Give the new Widget an ID to be used in the tablesorter() call, as follows: - // $('#myElement').tablesorter({ widgets: ['zebra', 'staticRow'] }); - id: 'staticRow', + ts.addWidget({ + // Give the new Widget an ID to be used in the tablesorter() call, as follows: + // $('#myElement').tablesorter({ widgets: ['zebra', 'staticRow'] }); + id: 'staticRow', - options: { - staticRow_class : '.static', - staticRow_data : 'static-index', - staticRow_index : 'row-index', - staticRow_event : 'staticRowsRefresh' - }, + options: { + staticRow_class : '.static', + staticRow_data : 'static-index', + staticRow_index : 'row-index', + staticRow_event : 'staticRowsRefresh' + }, - init: function(table, thisWidget, c, wo){ - addIndexes(table); - // refresh static rows after updates - c.$table - .unbind( ('updateComplete.tsstaticrows ' + wo.staticRow_event).replace(/\s+/g, ' ') ) - .bind('updateComplete.tsstaticrows ' + wo.staticRow_event, function(){ - addIndexes(table); - c.$table.trigger('applyWidgets'); - }); - }, + init: function(table, thisWidget, c, wo){ + addIndexes(table); + // refresh static rows after updates + c.$table + .unbind( ('updateComplete.tsstaticrows ' + wo.staticRow_event).replace(/\s+/g, ' ') ) + .bind('updateComplete.tsstaticrows ' + wo.staticRow_event, function(){ + addIndexes(table); + c.$table.trigger('applyWidgets'); + }); + }, - format: function(table, c, wo) { - // Loop thru static rows, moving them to their original "indexed" position, - // & repeat until no more re-shuffling is needed - var targetIndex, $thisRow, indx, numRows, $tbody, hasShuffled, $rows, max; + format: function(table, c, wo) { + // Loop thru static rows, moving them to their original 'indexed' position, + // & repeat until no more re-shuffling is needed + var targetIndex, $thisRow, indx, numRows, $tbody, hasShuffled, $rows, max; - c.$tbodies.each(function(){ - $tbody = $.tablesorter.processTbody(table, $(this), true); // remove tbody - hasShuffled = true; - indx = 0; - $rows = $tbody.children(wo.staticRow_class); - numRows = $tbody.children('tr').length - 1; - max = $rows.length; + c.$tbodies.each(function(){ + $tbody = $.tablesorter.processTbody(table, $(this), true); // remove tbody + hasShuffled = true; + indx = 0; + $rows = $tbody.children(wo.staticRow_class); + numRows = $tbody.children('tr').length - 1; + max = $rows.length; - // don't allow the while loop to cycle more times than the set number of static rows - while (hasShuffled && indx < max) { - hasShuffled = false; - /*jshint loopfunc:true */ - $rows.each(function() { - targetIndex = $(this).data(wo.staticRow_data); - // allow setting target index >> num rows to always make a row last - targetIndex = targetIndex >= numRows ? numRows : targetIndex < 0 ? 0 : targetIndex; - if (targetIndex !== $(this).index()) { - hasShuffled = true; - $thisRow = $(this).detach(); + // don't allow the while loop to cycle more times than the set number of static rows + while (hasShuffled && indx < max) { + hasShuffled = false; + /*jshint loopfunc:true */ + $rows.each(function() { + targetIndex = $(this).data(wo.staticRow_data); + // allow setting target index >> num rows to always make a row last + targetIndex = targetIndex >= numRows ? numRows : targetIndex < 0 ? 0 : targetIndex; + if (targetIndex !== $(this).index()) { + hasShuffled = true; + $thisRow = $(this).detach(); - if (targetIndex >= numRows) { - // Are we trying to be the last row? - $thisRow.appendTo( $tbody ); - } else if (targetIndex === 0) { + if (targetIndex >= numRows) { + // Are we trying to be the last row? + $thisRow.appendTo( $tbody ); + } else if (targetIndex === 0) { // Are we trying to be the first row? $thisRow.prependTo( $tbody ); - } else { - // No, we want to be somewhere in the middle! - $thisRow.insertBefore( $tbody.find('tr:eq(' + targetIndex + ')') ); + } else { + // No, we want to be somewhere in the middle! + $thisRow.insertBefore( $tbody.find('tr:eq(' + targetIndex + ')') ); + } } - } - }); - indx++; - } + }); + indx++; + } - $.tablesorter.processTbody(table, $tbody, false); // restore tbody - }); + $.tablesorter.processTbody(table, $tbody, false); // restore tbody + }); - c.$table.trigger('staticRowsComplete', table); - }, + c.$table.trigger('staticRowsComplete', table); + }, - remove : function(table, c, wo){ - c.$table.unbind( ('updateComplete.tsstaticrows ' + wo.staticRow_event).replace(/\s+/g, ' ') ); - } + remove : function(table, c, wo){ + c.$table.unbind( ('updateComplete.tsstaticrows ' + wo.staticRow_event).replace(/\s+/g, ' ') ); + } -}); + }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/js/widgets/widget-stickyHeaders.js b/js/widgets/widget-stickyHeaders.js index f0fa8fcd..e545dc60 100644 --- a/js/widgets/widget-stickyHeaders.js +++ b/js/widgets/widget-stickyHeaders.js @@ -3,286 +3,286 @@ * by Rob Garrison */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -$.extend(ts.css, { - sticky : 'tablesorter-stickyHeader', // stickyHeader - stickyVis : 'tablesorter-sticky-visible', - stickyHide: 'tablesorter-sticky-hidden', - stickyWrap: 'tablesorter-sticky-wrapper' -}); + $.extend(ts.css, { + sticky : 'tablesorter-stickyHeader', // stickyHeader + stickyVis : 'tablesorter-sticky-visible', + stickyHide: 'tablesorter-sticky-hidden', + stickyWrap: 'tablesorter-sticky-wrapper' + }); -// Add a resize event to table headers -ts.addHeaderResizeEvent = function(table, disable, settings) { - table = $(table)[0]; // make sure we're using a dom element - if ( !table.config ) { return; } - var defaults = { - timer : 250 + // Add a resize event to table headers + ts.addHeaderResizeEvent = function(table, disable, settings) { + table = $(table)[0]; // make sure we're using a dom element + if ( !table.config ) { return; } + var defaults = { + timer : 250 + }, + options = $.extend({}, defaults, settings), + c = table.config, + wo = c.widgetOptions, + checkSizes = function( triggerEvent ) { + var index, headers, $header, sizes, width, height, + len = c.$headers.length; + wo.resize_flag = true; + headers = []; + for ( index = 0; index < len; index++ ) { + $header = c.$headers.eq( index ); + sizes = $header.data( 'savedSizes' ) || [ 0, 0 ]; // fixes #394 + width = $header[0].offsetWidth; + height = $header[0].offsetHeight; + if ( width !== sizes[0] || height !== sizes[1] ) { + $header.data( 'savedSizes', [ width, height ] ); + headers.push( $header[0] ); + } + } + if ( headers.length && triggerEvent !== false ) { + c.$table.trigger( 'resize', [ headers ] ); + } + wo.resize_flag = false; + }; + checkSizes( false ); + clearInterval(wo.resize_timer); + if (disable) { + wo.resize_flag = false; + return false; + } + wo.resize_timer = setInterval(function() { + if (wo.resize_flag) { return; } + checkSizes(); + }, options.timer); + }; + + // Sticky headers based on this awesome article: + // http://css-tricks.com/13465-persistent-headers/ + // and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech + // ************************** + ts.addWidget({ + id: 'stickyHeaders', + priority: 60, // sticky widget must be initialized after the filter widget! + options: { + stickyHeaders : '', // extra class name added to the sticky header row + stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to + stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) + stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) + stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element + stickyHeaders_filteredToTop: true, // scroll table top into view after filtering + stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists + stickyHeaders_addResizeEvent : true, // trigger 'resize' event on headers + stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header + stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs }, - options = $.extend({}, defaults, settings), - c = table.config, - wo = c.widgetOptions, - checkSizes = function( triggerEvent ) { - var index, headers, $header, sizes, width, height, - len = c.$headers.length; - wo.resize_flag = true; - headers = []; - for ( index = 0; index < len; index++ ) { - $header = c.$headers.eq( index ); - sizes = $header.data( 'savedSizes' ) || [ 0,0 ]; // fixes #394 - width = $header[0].offsetWidth; - height = $header[0].offsetHeight; - if ( width !== sizes[0] || height !== sizes[1] ) { - $header.data( 'savedSizes', [ width, height ] ); - headers.push( $header[0] ); + format: function(table, c, wo) { + // filter widget doesn't initialize on an empty table. Fixes #449 + if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { + return; + } + var index, len, $t, + $table = c.$table, + // add position: relative to attach element, hopefully it won't cause trouble. + $attach = $(wo.stickyHeaders_attachTo), + namespace = c.namespace + 'stickyheaders ', + // element to watch for the scroll event + $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), + $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), + $thead = $table.children('thead:first'), + $header = $thead.children('tr').not('.sticky-false').children(), + $tfoot = $table.children('tfoot'), + $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, + // is this table nested? If so, find parent sticky header wrapper (div, not table) + $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? + $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], + nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, + // clone table, then wrap to make sticky header + $stickyTable = wo.$sticky = $table.clone() + .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) + .wrap('
'), + $stickyWrap = $stickyTable.parent() + .addClass(ts.css.stickyHide) + .css({ + position : $attach.length ? 'absolute' : 'fixed', + padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), + top : stickyOffset + nestedStickyTop, + left : 0, + visibility : 'hidden', + zIndex : wo.stickyHeaders_zIndex || 2 + }), + $stickyThead = $stickyTable.children('thead:first'), + $stickyCells, + laststate = '', + spacing = 0, + setWidth = function($orig, $clone){ + var index, width, border, $cell, $this, + $cells = $orig.filter(':visible'), + len = $cells.length; + for ( index = 0; index < len; index++ ) { + $cell = $clone.filter(':visible').eq(index); + $this = $cells.eq(index); + // code from https://github.com/jmosbech/StickyTableHeaders + if ($this.css('box-sizing') === 'border-box') { + width = $this.outerWidth(); + } else { + if ($cell.css('border-collapse') === 'collapse') { + if (window.getComputedStyle) { + width = parseFloat( window.getComputedStyle($this[0], null).width ); + } else { + // ie8 only + border = parseFloat( $this.css('border-width') ); + width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; + } + } else { + width = $this.width(); + } + } + $cell.css({ + 'width': width, + 'min-width': width, + 'max-width': width + }); + } + }, + resizeHeader = function() { + stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; + spacing = 0; + $stickyWrap.css({ + left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : + $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, + width: $table.outerWidth() + }); + setWidth( $table, $stickyTable ); + setWidth( $header, $stickyCells ); + }, + scrollSticky = function( resizing ) { + if (!$table.is(':visible')) { return; } // fixes #278 + // Detect nested tables - fixes #724 + nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; + var offset = $table.offset(), + yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 + xWindow = $.isWindow( $xScroll[0] ), + // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, + tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), + isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', + cssSettings = { visibility : isVisible }; + + if ($attach.length) { + cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + } + if (xWindow) { + // adjust when scrolling horizontally - fixes issue #143 + cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; + } + if ($nestedSticky.length) { + cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; + } + $stickyWrap + .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) + .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) + .css(cssSettings); + if (isVisible !== laststate || resizing) { + // make sure the column widths match + resizeHeader(); + laststate = isVisible; + } + }; + // only add a position relative if a position isn't already defined + if ($attach.length && !$attach.css('position')) { + $attach.css('position', 'relative'); + } + // fix clone ID, if it exists - fixes #271 + if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } + // clear out cloned table, except for sticky header + // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing + $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); + $stickyTable.find('tbody, tfoot').remove(); + $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); + // issue #172 - find td/th in sticky header + $stickyCells = $stickyThead.children().children(); + $stickyTable.css({ height:0, width:0, margin: 0 }); + // remove resizable block + $stickyCells.find('.' + ts.css.resizer).remove(); + // update sticky header class names to match real header after sorting + $table + .addClass('hasStickyHeaders') + .bind('pagerComplete' + namespace, function() { + resizeHeader(); + }); + + ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); + + // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. + $table.after( $stickyWrap ); + + // onRenderHeader is defined, we need to do something about it (fixes #641) + if (c.onRenderHeader) { + $t = $stickyThead.children('tr').children(); + len = $t.length; + for ( index = 0; index < len; index++ ) { + // send second parameter + c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } } - if ( headers.length && triggerEvent !== false ) { - c.$table.trigger( 'resize', [ headers ] ); - } - wo.resize_flag = false; - }; - checkSizes( false ); - clearInterval(wo.resize_timer); - if (disable) { - wo.resize_flag = false; - return false; - } - wo.resize_timer = setInterval(function() { - if (wo.resize_flag) { return; } - checkSizes(); - }, options.timer); -}; -// Sticky headers based on this awesome article: -// http://css-tricks.com/13465-persistent-headers/ -// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech -// ************************** -ts.addWidget({ - id: "stickyHeaders", - priority: 60, // sticky widget must be initialized after the filter widget! - options: { - stickyHeaders : '', // extra class name added to the sticky header row - stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to - stickyHeaders_xScroll : null, // jQuery selector or object to monitor horizontal scroll position (defaults: xScroll > attachTo > window) - stickyHeaders_yScroll : null, // jQuery selector or object to monitor vertical scroll position (defaults: yScroll > attachTo > window) - stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element - stickyHeaders_filteredToTop: true, // scroll table top into view after filtering - stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists - stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers - stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header - stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs - }, - format: function(table, c, wo) { - // filter widget doesn't initialize on an empty table. Fixes #449 - if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) { - return; - } - var index, len, $t, - $table = c.$table, - // add position: relative to attach element, hopefully it won't cause trouble. - $attach = $(wo.stickyHeaders_attachTo), - namespace = c.namespace + 'stickyheaders ', - // element to watch for the scroll event - $yScroll = $(wo.stickyHeaders_yScroll || wo.stickyHeaders_attachTo || window), - $xScroll = $(wo.stickyHeaders_xScroll || wo.stickyHeaders_attachTo || window), - $thead = $table.children('thead:first'), - $header = $thead.children('tr').not('.sticky-false').children(), - $tfoot = $table.children('tfoot'), - $stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '', - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0, - // is this table nested? If so, find parent sticky header wrapper (div, not table) - $nestedSticky = $table.parent().closest('.' + ts.css.table).hasClass('hasStickyHeaders') ? - $table.parent().closest('table.tablesorter')[0].config.widgetOptions.$sticky.parent() : [], - nestedStickyTop = $nestedSticky.length ? $nestedSticky.height() : 0, - // clone table, then wrap to make sticky header - $stickyTable = wo.$sticky = $table.clone() - .addClass('containsStickyHeaders ' + ts.css.sticky + ' ' + wo.stickyHeaders + ' ' + c.namespace.slice(1) + '_extra_table' ) - .wrap('
'), - $stickyWrap = $stickyTable.parent() - .addClass(ts.css.stickyHide) - .css({ - position : $attach.length ? 'absolute' : 'fixed', - padding : parseInt( $stickyTable.parent().parent().css('padding-left'), 10 ), - top : stickyOffset + nestedStickyTop, - left : 0, - visibility : 'hidden', - zIndex : wo.stickyHeaders_zIndex || 2 - }), - $stickyThead = $stickyTable.children('thead:first'), - $stickyCells, - laststate = '', - spacing = 0, - setWidth = function($orig, $clone){ - var index, width, border, $cell, $this, - $cells = $orig.filter(':visible'), - len = $cells.length; - for ( index = 0; index < len; index++ ) { - $cell = $clone.filter(':visible').eq(index); - $this = $cells.eq(index); - // code from https://github.com/jmosbech/StickyTableHeaders - if ($this.css('box-sizing') === 'border-box') { - width = $this.outerWidth(); - } else { - if ($cell.css('border-collapse') === 'collapse') { - if (window.getComputedStyle) { - width = parseFloat( window.getComputedStyle($this[0], null).width ); - } else { - // ie8 only - border = parseFloat( $this.css('border-width') ); - width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; - } - } else { - width = $this.width(); + // make it sticky! + $xScroll.add($yScroll) + .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) + .bind('scroll resize '.split(' ').join( namespace ), function( event ) { + scrollSticky( event.type === 'resize' ); + }); + c.$table + .unbind('stickyHeadersUpdate' + namespace) + .bind('stickyHeadersUpdate' + namespace, function(){ + scrollSticky( true ); + }); + + if (wo.stickyHeaders_addResizeEvent) { + ts.addHeaderResizeEvent(table); + } + + // look for filter widget + if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { + // scroll table into view after filtering, if sticky header is active - #482 + $table.bind('filterEnd' + namespace, function() { + // $(':focus') needs jQuery 1.6+ + var $td = $(document.activeElement).closest('td'), + column = $td.parent().children().index($td); + // only scroll if sticky header is active + if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { + // scroll to original table (not sticky clone) + window.scrollTo(0, $table.position().top); + // give same input/select focus; check if c.$filters exists; fixes #594 + if (column >= 0 && c.$filters) { + c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); } } - $cell.css({ - 'width': width, - 'min-width': width, - 'max-width': width - }); - } - }, - resizeHeader = function() { - stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0; - spacing = 0; - $stickyWrap.css({ - left : $attach.length ? parseInt($attach.css('padding-left'), 10) || 0 : - $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing, - width: $table.outerWidth() }); - setWidth( $table, $stickyTable ); - setWidth( $header, $stickyCells ); - }, - scrollSticky = function( resizing ) { - if (!$table.is(':visible')) { return; } // fixes #278 - // Detect nested tables - fixes #724 - nestedStickyTop = $nestedSticky.length ? $nestedSticky.offset().top - $yScroll.scrollTop() + $nestedSticky.height() : 0; - var offset = $table.offset(), - yWindow = $.isWindow( $yScroll[0] ), // $.isWindow needs jQuery 1.4.3 - xWindow = $.isWindow( $xScroll[0] ), - // scrollTop = ( $attach.length ? $attach.offset().top : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - scrollTop = ( $attach.length ? ( yWindow ? $yScroll.scrollTop() : $yScroll.offset().top ) : $yScroll.scrollTop() ) + stickyOffset + nestedStickyTop, - tableHeight = $table.height() - ($stickyWrap.height() + ($tfoot.height() || 0)), - isVisible = ( scrollTop > offset.top ) && ( scrollTop < offset.top + tableHeight ) ? 'visible' : 'hidden', - cssSettings = { visibility : isVisible }; - - if ($attach.length) { - cssSettings.top = yWindow ? scrollTop - $attach.offset().top : $attach.scrollTop(); + ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); + // support hideFilters + if (wo.filter_hideFilters) { + ts.filter.hideFilters($stickyTable, c); } - if (xWindow) { - // adjust when scrolling horizontally - fixes issue #143 - cssSettings.left = $table.offset().left - parseInt($table.css('margin-left'), 10) - $xScroll.scrollLeft() - spacing; - } - if ($nestedSticky.length) { - cssSettings.top = ( cssSettings.top || 0 ) + stickyOffset + nestedStickyTop; - } - $stickyWrap - .removeClass( ts.css.stickyVis + ' ' + ts.css.stickyHide ) - .addClass( isVisible === 'visible' ? ts.css.stickyVis : ts.css.stickyHide ) - .css(cssSettings); - if (isVisible !== laststate || resizing) { - // make sure the column widths match - resizeHeader(); - laststate = isVisible; - } - }; - // only add a position relative if a position isn't already defined - if ($attach.length && !$attach.css('position')) { - $attach.css('position', 'relative'); - } - // fix clone ID, if it exists - fixes #271 - if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; } - // clear out cloned table, except for sticky header - // include caption & filter row (fixes #126 & #249) - don't remove cells to get correct cell indexing - $stickyTable.find('thead:gt(0), tr.sticky-false').hide(); - $stickyTable.find('tbody, tfoot').remove(); - $stickyTable.find('caption').toggle(wo.stickyHeaders_includeCaption); - // issue #172 - find td/th in sticky header - $stickyCells = $stickyThead.children().children(); - $stickyTable.css({ height:0, width:0, margin: 0 }); - // remove resizable block - $stickyCells.find('.' + ts.css.resizer).remove(); - // update sticky header class names to match real header after sorting - $table - .addClass('hasStickyHeaders') - .bind('pagerComplete' + namespace, function() { - resizeHeader(); - }); - - ts.bindEvents(table, $stickyThead.children().children('.' + ts.css.header)); - - // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. - $table.after( $stickyWrap ); - - // onRenderHeader is defined, we need to do something about it (fixes #641) - if (c.onRenderHeader) { - $t = $stickyThead.children('tr').children(); - len = $t.length; - for ( index = 0; index < len; index++ ) { - // send second parameter - c.onRenderHeader.apply( $t.eq( index ), [ index, c, $stickyTable ] ); } + + $table.trigger('stickyHeadersInit'); + + }, + remove: function(table, c, wo) { + var namespace = c.namespace + 'stickyheaders '; + c.$table + .removeClass('hasStickyHeaders') + .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) + .next('.' + ts.css.stickyWrap).remove(); + if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table + $(window) + .add(wo.stickyHeaders_xScroll) + .add(wo.stickyHeaders_yScroll) + .add(wo.stickyHeaders_attachTo) + .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); + ts.addHeaderResizeEvent(table, false); } - - // make it sticky! - $xScroll.add($yScroll) - .unbind( ('scroll resize '.split(' ').join( namespace )).replace(/\s+/g, ' ') ) - .bind('scroll resize '.split(' ').join( namespace ), function( event ) { - scrollSticky( event.type === 'resize' ); - }); - c.$table - .unbind('stickyHeadersUpdate' + namespace) - .bind('stickyHeadersUpdate' + namespace, function(){ - scrollSticky( true ); - }); - - if (wo.stickyHeaders_addResizeEvent) { - ts.addHeaderResizeEvent(table); - } - - // look for filter widget - if ($table.hasClass('hasFilters') && wo.filter_columnFilters) { - // scroll table into view after filtering, if sticky header is active - #482 - $table.bind('filterEnd' + namespace, function() { - // $(':focus') needs jQuery 1.6+ - var $td = $(document.activeElement).closest('td'), - column = $td.parent().children().index($td); - // only scroll if sticky header is active - if ($stickyWrap.hasClass(ts.css.stickyVis) && wo.stickyHeaders_filteredToTop) { - // scroll to original table (not sticky clone) - window.scrollTo(0, $table.position().top); - // give same input/select focus; check if c.$filters exists; fixes #594 - if (column >= 0 && c.$filters) { - c.$filters.eq(column).find('a, select, input').filter(':visible').focus(); - } - } - }); - ts.filter.bindSearch( $table, $stickyCells.find('.' + ts.css.filter) ); - // support hideFilters - if (wo.filter_hideFilters) { - ts.filter.hideFilters($stickyTable, c); - } - } - - $table.trigger('stickyHeadersInit'); - - }, - remove: function(table, c, wo) { - var namespace = c.namespace + 'stickyheaders '; - c.$table - .removeClass('hasStickyHeaders') - .unbind( ('pagerComplete filterEnd stickyHeadersUpdate '.split(' ').join(namespace)).replace(/\s+/g, ' ') ) - .next('.' + ts.css.stickyWrap).remove(); - if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table - $(window) - .add(wo.stickyHeaders_xScroll) - .add(wo.stickyHeaders_yScroll) - .add(wo.stickyHeaders_attachTo) - .unbind( ('scroll resize '.split(' ').join(namespace)).replace(/\s+/g, ' ') ); - ts.addHeaderResizeEvent(table, false); - } -}); + }); })(jQuery, window); diff --git a/js/widgets/widget-storage.js b/js/widgets/widget-storage.js index 472d34c3..34a8e99d 100644 --- a/js/widgets/widget-storage.js +++ b/js/widgets/widget-storage.js @@ -1,89 +1,89 @@ /*! Widget: storage - updated 3/26/2015 (v2.21.3) */ ;(function ($, window, document) { -'use strict'; + 'use strict'; -var ts = $.tablesorter || {}; -// *** Store data in local storage, with a cookie fallback *** -/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) - if you need it, then include https://github.com/douglascrockford/JSON-js + var ts = $.tablesorter || {}; + // *** Store data in local storage, with a cookie fallback *** + /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) + if you need it, then include https://github.com/douglascrockford/JSON-js - $.parseJSON is not available is jQuery versions older than 1.4.1, using older - versions will only allow storing information for one page at a time + $.parseJSON is not available is jQuery versions older than 1.4.1, using older + versions will only allow storing information for one page at a time - // *** Save data (JSON format only) *** - // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid - var val = { "mywidget" : "data1" }; // valid JSON uses double quotes - // $.tablesorter.storage(table, key, val); - $.tablesorter.storage(table, 'tablesorter-mywidget', val); + // *** Save data (JSON format only) *** + // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid + var val = { "mywidget" : "data1" }; // valid JSON uses double quotes + // $.tablesorter.storage(table, key, val); + $.tablesorter.storage(table, 'tablesorter-mywidget', val); - // *** Get data: $.tablesorter.storage(table, key); *** - v = $.tablesorter.storage(table, 'tablesorter-mywidget'); - // val may be empty, so also check for your data - val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; - alert(val); // "data1" if saved, or "" if not -*/ -ts.storage = function(table, key, value, options) { - table = $(table)[0]; - var cookieIndex, cookies, date, - hasStorage = false, - values = {}, - c = table.config, - wo = c && c.widgetOptions, - storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? - 'sessionStorage' : 'localStorage', - $table = $(table), - // id from (1) options ID, (2) table "data-table-group" attribute, (3) widgetOptions.storage_tableId, - // (4) table ID, then (5) table index - id = options && options.id || - $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || - wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), - // url from (1) options url, (2) table "data-table-page" attribute, (3) widgetOptions.storage_fixedUrl, - // (4) table.config.fixedUrl (deprecated), then (5) window location path - url = options && options.url || - $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || - wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; - // https://gist.github.com/paulirish/5558557 - if (storageType in window) { - try { - window[storageType].setItem('_tmptest', 'temp'); - hasStorage = true; - window[storageType].removeItem('_tmptest'); - } catch(error) { - if (c && c.debug) { - ts.log( storageType + ' is not supported in this browser' ); + // *** Get data: $.tablesorter.storage(table, key); *** + v = $.tablesorter.storage(table, 'tablesorter-mywidget'); + // val may be empty, so also check for your data + val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; + alert(val); // 'data1' if saved, or '' if not + */ + ts.storage = function(table, key, value, options) { + table = $(table)[0]; + var cookieIndex, cookies, date, + hasStorage = false, + values = {}, + c = table.config, + wo = c && c.widgetOptions, + storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? + 'sessionStorage' : 'localStorage', + $table = $(table), + // id from (1) options ID, (2) table 'data-table-group' attribute, (3) widgetOptions.storage_tableId, + // (4) table ID, then (5) table index + id = options && options.id || + $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || + wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), + // url from (1) options url, (2) table 'data-table-page' attribute, (3) widgetOptions.storage_fixedUrl, + // (4) table.config.fixedUrl (deprecated), then (5) window location path + url = options && options.url || + $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || + wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; + // https://gist.github.com/paulirish/5558557 + if (storageType in window) { + try { + window[storageType].setItem('_tmptest', 'temp'); + hasStorage = true; + window[storageType].removeItem('_tmptest'); + } catch (error) { + if (c && c.debug) { + ts.log( storageType + ' is not supported in this browser' ); + } } } - } - // *** get value *** - if ($.parseJSON) { - if (hasStorage) { - values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + // *** get value *** + if ($.parseJSON) { + if (hasStorage) { + values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + } else { + // old browser, using cookies + cookies = document.cookie.split(/[;\s|=]/); + // add one to get from the key to the value + cookieIndex = $.inArray(key, cookies) + 1; + values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + } + } + // allow value to be an empty string too + if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { + // add unique identifiers = url pathname > table ID/index on page > data + if (!values[url]) { + values[url] = {}; + } + values[url][id] = value; + // *** set value *** + if (hasStorage) { + window[storageType][key] = JSON.stringify(values); + } else { + date = new Date(); + date.setTime(date.getTime() + (31536e+6)); // 365 days + document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/'; + } } else { - // old browser, using cookies - cookies = document.cookie.split(/[;\s|=]/); - // add one to get from the key to the value - cookieIndex = $.inArray(key, cookies) + 1; - values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + return values && values[url] ? values[url][id] : ''; } - } - // allow value to be an empty string too - if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) { - // add unique identifiers = url pathname > table ID/index on page > data - if (!values[url]) { - values[url] = {}; - } - values[url][id] = value; - // *** set value *** - if (hasStorage) { - window[storageType][key] = JSON.stringify(values); - } else { - date = new Date(); - date.setTime(date.getTime() + (31536e+6)); // 365 days - document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/'; - } - } else { - return values && values[url] ? values[url][id] : ''; - } -}; + }; })(jQuery, window, document); diff --git a/js/widgets/widget-uitheme.js b/js/widgets/widget-uitheme.js index 9d6a095e..988dbe22 100644 --- a/js/widgets/widget-uitheme.js +++ b/js/widgets/widget-uitheme.js @@ -1,185 +1,185 @@ /*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */ ;(function ($) { -'use strict'; -var ts = $.tablesorter || {}; + 'use strict'; + var ts = $.tablesorter || {}; -ts.themes = { - 'bootstrap' : { - table : 'table table-bordered table-striped', - caption : 'caption', - // header class names - header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) - sortNone : '', - sortAsc : '', - sortDesc : '', - active : '', // applied when column is sorted - hover : '', // custom css required - a defined bootstrap style may not override other classes - // icon class names - icons : '', // add "icon-white" to make them white; this icon class is added to the in the header - iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted - iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort - iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort - filterRow : '', // filter row class - footerRow : '', - footerCells : '', - even : '', // even row zebra striping - odd : '' // odd row zebra striping - }, - 'jui' : { - table : 'ui-widget ui-widget-content ui-corner-all', // table classes - caption : 'ui-widget-content', - // header class names - header : 'ui-widget-header ui-corner-all ui-state-default', // header classes - sortNone : '', - sortAsc : '', - sortDesc : '', - active : 'ui-state-active', // applied when column is sorted - hover : 'ui-state-hover', // hover class - // icon class names - icons : 'ui-icon', // icon class added to the in the header - iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted - iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort - iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort - filterRow : '', - footerRow : '', - footerCells : '', - even : 'ui-widget-content', // even row zebra striping - odd : 'ui-state-default' // odd row zebra striping - } -}; + ts.themes = { + 'bootstrap' : { + table : 'table table-bordered table-striped', + caption : 'caption', + // header class names + header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) + sortNone : '', + sortAsc : '', + sortDesc : '', + active : '', // applied when column is sorted + hover : '', // custom css required - a defined bootstrap style may not override other classes + // icon class names + icons : '', // add 'icon-white' to make them white; this icon class is added to the in the header + iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted + iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort + iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort + filterRow : '', // filter row class + footerRow : '', + footerCells : '', + even : '', // even row zebra striping + odd : '' // odd row zebra striping + }, + 'jui' : { + table : 'ui-widget ui-widget-content ui-corner-all', // table classes + caption : 'ui-widget-content', + // header class names + header : 'ui-widget-header ui-corner-all ui-state-default', // header classes + sortNone : '', + sortAsc : '', + sortDesc : '', + active : 'ui-state-active', // applied when column is sorted + hover : 'ui-state-hover', // hover class + // icon class names + icons : 'ui-icon', // icon class added to the in the header + iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted + iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort + iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort + filterRow : '', + footerRow : '', + footerCells : '', + even : 'ui-widget-content', // even row zebra striping + odd : 'ui-state-default' // odd row zebra striping + } + }; -$.extend(ts.css, { - wrapper : 'tablesorter-wrapper' // ui theme & resizable -}); + $.extend(ts.css, { + wrapper : 'tablesorter-wrapper' // ui theme & resizable + }); -ts.addWidget({ - id: "uitheme", - priority: 10, - format: function(table, c, wo) { - var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, - themesAll = ts.themes, - $table = c.$table.add( $( c.namespace + '_extra_table' ) ), - $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), - theme = c.theme || 'jui', - themes = themesAll[theme] || {}, - remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), - iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); - if (c.debug) { time = new Date(); } - // initialization code - run once - if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { - wo.uitheme_applied = true; - oldtheme = themesAll[c.appliedTheme] || {}; - hasOldTheme = !$.isEmptyObject(oldtheme); - oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; - oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; - if (hasOldTheme) { - wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); - wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); - c.$tbodies.children().removeClass( [oldtheme.even, oldtheme.odd].join(' ') ); - } - // update zebra stripes - if (themes.even) { wo.zebra[0] += ' ' + themes.even; } - if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } - // add caption style - $table.children('caption') - .removeClass(oldtheme.caption || '') - .addClass(themes.caption); - // add table/footer class names - $tfoot = $table - // remove other selected themes - .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) - .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name - .children('tfoot'); - c.appliedTheme = c.theme; - - if ($tfoot.length) { - $tfoot - // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed - .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) - .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); - } - // update header classes - $headers - .removeClass( (hasOldTheme ? [oldtheme.header, oldtheme.hover, oldremove].join(' ') : '') || '' ) - .addClass(themes.header) - .not('.sorter-false') - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') - .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { - // toggleClass with switch added in jQuery 1.3 - $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); - }); - - $headers.each(function(){ - var $this = $(this); - if (!$this.find('.' + ts.css.wrapper).length) { - // Firefox needs this inner div to position the icon & resizer correctly - $this.wrapInner('
'); + ts.addWidget({ + id: 'uitheme', + priority: 10, + format: function(table, c, wo) { + var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, + themesAll = ts.themes, + $table = c.$table.add( $( c.namespace + '_extra_table' ) ), + $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), + theme = c.theme || 'jui', + themes = themesAll[theme] || {}, + remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), + iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); + if (c.debug) { time = new Date(); } + // initialization code - run once + if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { + wo.uitheme_applied = true; + oldtheme = themesAll[c.appliedTheme] || {}; + hasOldTheme = !$.isEmptyObject(oldtheme); + oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; + oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; + if (hasOldTheme) { + wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); + wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); + c.$tbodies.children().removeClass( [ oldtheme.even, oldtheme.odd ].join(' ') ); } - }); - if (c.cssIcon) { - // if c.cssIcon is '', then no is added to the header + // update zebra stripes + if (themes.even) { wo.zebra[0] += ' ' + themes.even; } + if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } + // add caption style + $table.children('caption') + .removeClass(oldtheme.caption || '') + .addClass(themes.caption); + // add table/footer class names + $tfoot = $table + // remove other selected themes + .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) + .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name + .children('tfoot'); + c.appliedTheme = c.theme; + + if ($tfoot.length) { + $tfoot + // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed + .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) + .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); + } + // update header classes $headers - .find('.' + ts.css.icon) - .removeClass(hasOldTheme ? [oldtheme.icons, oldIconRmv].join(' ') : '') - .addClass(themes.icons || ''); - } - if ($table.hasClass('hasFilters')) { - $table.children('thead').children('.' + ts.css.filterRow) - .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') - .addClass(themes.filterRow || ''); - } - } - for (i = 0; i < c.columns; i++) { - $header = c.$headers - .add($(c.namespace + '_extra_headers')) - .not('.sorter-false') - .filter('[data-column="' + i + '"]'); - $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); - $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); - if ($h.length) { - $header.removeClass(remove); - $icon.removeClass(iconRmv); - if ($h[0].sortDisabled) { - // no sort arrows for disabled columns! - $icon.removeClass(themes.icons || ''); - } else { - hdr = themes.sortNone; - icon = themes.iconSortNone; - if ($h.hasClass(ts.css.sortAsc)) { - hdr = [themes.sortAsc, themes.active].join(' '); - icon = themes.iconSortAsc; - } else if ($h.hasClass(ts.css.sortDesc)) { - hdr = [themes.sortDesc, themes.active].join(' '); - icon = themes.iconSortDesc; + .removeClass( (hasOldTheme ? [ oldtheme.header, oldtheme.hover, oldremove ].join(' ') : '') || '' ) + .addClass(themes.header) + .not('.sorter-false') + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') + .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { + // toggleClass with switch added in jQuery 1.3 + $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); + }); + + $headers.each(function(){ + var $this = $(this); + if (!$this.find('.' + ts.css.wrapper).length) { + // Firefox needs this inner div to position the icon & resizer correctly + $this.wrapInner('
'); } - $header.addClass(hdr); - $icon.addClass(icon || ''); + }); + if (c.cssIcon) { + // if c.cssIcon is '', then no is added to the header + $headers + .find('.' + ts.css.icon) + .removeClass(hasOldTheme ? [ oldtheme.icons, oldIconRmv ].join(' ') : '') + .addClass(themes.icons || ''); + } + if ($table.hasClass('hasFilters')) { + $table.children('thead').children('.' + ts.css.filterRow) + .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') + .addClass(themes.filterRow || ''); } } + for (i = 0; i < c.columns; i++) { + $header = c.$headers + .add($(c.namespace + '_extra_headers')) + .not('.sorter-false') + .filter('[data-column="' + i + '"]'); + $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); + $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); + if ($h.length) { + $header.removeClass(remove); + $icon.removeClass(iconRmv); + if ($h[0].sortDisabled) { + // no sort arrows for disabled columns! + $icon.removeClass(themes.icons || ''); + } else { + hdr = themes.sortNone; + icon = themes.iconSortNone; + if ($h.hasClass(ts.css.sortAsc)) { + hdr = [ themes.sortAsc, themes.active ].join(' '); + icon = themes.iconSortAsc; + } else if ($h.hasClass(ts.css.sortDesc)) { + hdr = [ themes.sortDesc, themes.active ].join(' '); + icon = themes.iconSortDesc; + } + $header.addClass(hdr); + $icon.addClass(icon || ''); + } + } + } + if (c.debug) { + ts.benchmark('Applying ' + theme + ' theme', time); + } + }, + remove: function(table, c, wo, refreshing) { + if (!wo.uitheme_applied) { return; } + var $table = c.$table, + theme = c.appliedTheme || 'jui', + themes = ts.themes[ theme ] || ts.themes.jui, + $headers = $table.children('thead').children(), + remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, + iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; + $table.removeClass('tablesorter-' + theme + ' ' + themes.table); + wo.uitheme_applied = false; + if (refreshing) { return; } + $table.find(ts.css.header).removeClass(themes.header); + $headers + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover + .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) + .filter('.' + ts.css.filterRow) + .removeClass(themes.filterRow); + $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); } - if (c.debug) { - ts.benchmark("Applying " + theme + " theme", time); - } - }, - remove: function(table, c, wo, refreshing) { - if (!wo.uitheme_applied) { return; } - var $table = c.$table, - theme = c.appliedTheme || 'jui', - themes = ts.themes[ theme ] || ts.themes.jui, - $headers = $table.children('thead').children(), - remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, - iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; - $table.removeClass('tablesorter-' + theme + ' ' + themes.table); - wo.uitheme_applied = false; - if (refreshing) { return; } - $table.find(ts.css.header).removeClass(themes.header); - $headers - .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover - .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) - .filter('.' + ts.css.filterRow) - .removeClass(themes.filterRow); - $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); - } -}); + }); })(jQuery); diff --git a/jscs.errors.txt b/jscs.errors.txt new file mode 100644 index 00000000..e69de29b diff --git a/package.json b/package.json index 442e2611..e2fdcafb 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "grunt-contrib-qunit": "^0.5.2", "grunt-contrib-uglify": "^0.7.0", "grunt-contrib-watch": "^0.6.1", + "grunt-jscs": "^1.8.0", "grunt-string-replace": "^1.0.0" } }