diff --git a/js/jquery.tablesorter.js b/js/jquery.tablesorter.js index b3999aec..bf2f7fda 100644 --- a/js/jquery.tablesorter.js +++ b/js/jquery.tablesorter.js @@ -1307,6 +1307,16 @@ if (c.debug) { ts.benchmark("Applying Zebra widget", time); } + }, + remove: function(table, c, wo){ + var k, $tb, + b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'), + rmv = (c.widgetOptions.zebra || [ "even", "odd" ]).join(' '); + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, $(b[k]), true); // remove tbody + $tb.children().removeClass(rmv); + $.tablesorter.processTbody(table, $tb, false); // restore tbody + } } }); diff --git a/js/jquery.tablesorter.widgets.js b/js/jquery.tablesorter.widgets.js index 63d3e180..8f9bfe7e 100644 --- a/js/jquery.tablesorter.widgets.js +++ b/js/jquery.tablesorter.widgets.js @@ -1,14 +1,61 @@ -/*! tableSorter 2.3 widgets - updated 6/21/2012 +/*! tableSorter 2.4 widgets - updated 8/18/2012 * - * jQuery UI Theme * Column Styles * Column Filters - * Sticky Header * Column Resizing + * Sticky Header + * UI Theme (generalized) * Save Sort - * + * ["zebra", "uitheme", "stickyHeaders", "filter", "columns"] */ +/*jshint browser:true, jquery:true, unused:false */ +/*global jQuery: false, localStorage: false, navigator: false */ ;(function($){ +"use strict"; +$.tablesorter = $.tablesorter || {}; + +$.tablesorter.themes = { + "bootstrap" : { + table : 'table table-bordered table-striped', + header : 'bootstrap-header', // give the header a gradient background + icons : '', // add "icon-white" to make them white; this icon class is added to the in the header + sortNone : 'bootstrap-icon-unsorted', + sortAsc : 'icon-chevron-up', + sortDesc : 'icon-chevron-down', + active : '', // applied when column is sorted + hover : '', // use custom css here - bootstrap class may not override it + filterRow: '', // filter row class + even : '', // odd row zebra striping + odd : '' // even row zebra striping + }, + "jui" : { + table : 'ui-widget ui-widget-content ui-corner-all', // table classes + header : 'ui-widget-header ui-corner-all ui-state-default', // header classes + icons : 'ui-icon', // icon class added to the in the header + sortNone : 'ui-icon-carat-2-n-s', + sortAsc : 'ui-icon-carat-1-n', + sortDesc : 'ui-icon-carat-1-s', + active : 'ui-state-active', // applied when column is sorted + hover : 'ui-state-hover', // hover class + filterRow: '', + even : 'ui-widget-content', // odd row zebra striping + odd : 'ui-state-default' // even row zebra striping + } +}; + +// detach tbody but save the position +// added to v2.4 core, but added here to make widgets backwards compatible +$.tablesorter.processTbody = function(table, $tb, getIt){ + var t, holdr; + if (getIt) { + $tb.before(''); + holdr = ($.fn.detach) ? $tb.detach() : $tb.remove(); + return holdr; + } + holdr = $(table).find('span.tablesorter-savemyplace'); + $tb.insertAfter( holdr ); + holdr.remove(); +}; // *** Store data in local storage, with a cookie fallback *** /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) @@ -35,8 +82,8 @@ $.tablesorter.storage = function(table, key, val){ url = window.location.pathname; try { ls = !!(localStorage.getItem); } catch(e) {} // *** get val *** - if ($.parseJSON) { - if (ls) { + if ($.parseJSON){ + if (ls){ v = $.parseJSON(localStorage[key]) || {}; } else { k = document.cookie.split(/[;\s|=]/); // cookie @@ -44,12 +91,12 @@ $.tablesorter.storage = function(table, key, val){ v = (d !== 0) ? $.parseJSON(k[d]) || {} : {}; } } - if (val && JSON && JSON.hasOwnProperty('stringify')) { + if (val && JSON && JSON.hasOwnProperty('stringify')){ // add unique identifiers = url pathname > table ID/index on page > data - if (v[url] && v[url][id]) { + if (v[url] && v[url][id]){ v[url][id] = val; } else { - if (v[url]) { + if (v[url]){ v[url][id] = val; } else { v[url] = {}; @@ -57,11 +104,11 @@ $.tablesorter.storage = function(table, key, val){ } } // *** set val *** - if (ls) { + if (ls){ localStorage[key] = JSON.stringify(v); } else { d = new Date(); - d.setTime(d.getTime()+(31536e+6)); // 365 days + d.setTime(d.getTime() + (31536e+6)); // 365 days document.cookie = key + '=' + (JSON.stringify(v)).replace(/\"/g,'\"') + '; expires=' + d.toGMTString() + '; path=/'; } } else { @@ -69,53 +116,83 @@ $.tablesorter.storage = function(table, key, val){ } }; -// Widget: jQuery UI theme +// Widget: General UI theme // "uitheme" option in "widgetOptions" // ************************** $.tablesorter.addWidget({ id: "uitheme", - format: function(table) { - var time, klass, rmv, $t, t, $table = $(table), - c = table.config, wo = c.widgetOptions, - // ["up/down arrow (cssHeaders, unsorted)", "down arrow (cssDesc, descending)", "up arrow (cssAsc, ascending)" ] - icons = ["ui-icon-arrowthick-2-n-s", "ui-icon-arrowthick-1-s", "ui-icon-arrowthick-1-n"]; - // keep backwards compatibility, for now - icons = (c.widgetUitheme && c.widgetUitheme.hasOwnProperty('css')) ? c.widgetUitheme.css || icons : - (wo && wo.hasOwnProperty('uitheme')) ? wo.uitheme : icons; - rmv = icons.join(' '); - if (c.debug) { - time = new Date(); - } - if (!$table.hasClass('ui-theme')) { - $table.addClass('ui-widget ui-widget-content ui-corner-all ui-theme'); - $.each(c.headerList, function(){ - $(this) - // using "ui-theme" class in case the user adds their own ui-icon using onRenderHeader - .addClass('ui-widget-header ui-corner-all ui-state-default') - .append('') - .wrapInner('
') + format: function(table){ + var time, klass, $el, $tar, t, + $t = $(table), + c = table.config, + wo = c.widgetOptions, + theme = typeof wo.uitheme === 'object' ? wo.uitheme.name || 'jui' : wo.uitheme || 'jui', + o = (typeof wo.uitheme === 'object' && !$.isArray(wo.uitheme)) ? wo.uitheme : $.tablesorter.themes[ $.tablesorter.themes.hasOwnProperty(theme) ? theme : 'jui'], + $h = $(c.headerList), + sh = 'tr.' + (wo.stickyHeaders || 'tablesorter-stickyHeader'), + rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc; + if (c.debug) { time = new Date(); } + if (!$t.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized){ + // update zebra stripes + if (o.even !== '') { wo.zebra[0] += ' ' + o.even; } + if (o.odd !== '') { wo.zebra[1] += ' ' + o.odd; } + // add table/footer class names + $t + // remove other selected themes; use widgetOptions.theme_remove + .removeClass( c.theme === '' ? '' : 'tablesorter-' + c.theme ) + .addClass('tablesorter-' + theme + ' ' + o.table) // add theme widget class name + .find('tfoot tr') + .addClass(o.header); + c.theme = ''; // clear out theme option so it doesn't interfere + // update header classes + $h + .addClass(o.header) + .filter(':not(.sorter-false)') .hover(function(){ - $(this).addClass('ui-state-hover'); + $(this).addClass(o.hover); }, function(){ - $(this).removeClass('ui-state-hover'); + $(this).removeClass(o.hover); }); - }); + if (c.cssIcon){ + // if c.cssIcon is '', then no is added to the header + $h.find('.' + c.cssIcon).addClass(o.icons); + } + if ($t.hasClass('hasFilters')){ + $h.find('.tablesorter-filter-row').addClass(o.filterRow); + } } - $.each(c.headerList, function(i){ - $t = $(this); - if (this.sortDisabled) { + $.each($h, function(i){ + $el = $(this); + $tar = (c.cssIcon) ? $el.find('.' + c.cssIcon) : $el; + if (this.sortDisabled){ // no sort arrows for disabled columns! - $t.find('span.ui-icon').removeClass(rmv + ' ui-icon'); + $el.removeClass(rmv); + $tar.removeClass(rmv + ' tablesorter-icon ' + o.icons); } else { - klass = ($t.hasClass(c.cssAsc)) ? icons[1] : ($t.hasClass(c.cssDesc)) ? icons[2] : $t.hasClass(c.cssHeader) ? icons[0] : ''; - t = ($table.hasClass('hasStickyHeaders')) ? $table.find('tr.' + (wo.stickyHeaders || 'tablesorter-stickyHeader')).find('th').eq(i).add($t) : $t; - t[klass === icons[0] ? 'removeClass' : 'addClass']('ui-state-active') - .find('span.ui-icon').removeClass(rmv).addClass(klass); + t = ($t.hasClass('hasStickyHeaders')) ? $t.find(sh).find('th').eq(i).add($el) : $el; + klass = ($el.hasClass(c.cssAsc)) ? o.sortDesc : ($el.hasClass(c.cssDesc)) ? o.sortAsc : $el.hasClass(c.cssHeader) ? o.sortNone : ''; + $el[klass === o.sortNone ? 'removeClass' : 'addClass'](o.active); + $tar.removeClass(rmv).addClass(klass); } }); - if (c.debug) { - $.tablesorter.benchmark("Applying uitheme widget", time); + if (c.debug){ + $.tablesorter.benchmark("Applying " + theme + " theme", time); } + }, + remove: function(table, c, wo){ + var $t = $(table), + theme = typeof wo.uitheme === 'object' ? wo.uitheme.name || 'custom' : wo.uitheme || 'jui', + o = typeof wo.uitheme === 'object' ? wo.uitheme : $.tablesorter.themes[ $.tablesorter.themes.hasOwnProperty(theme) ? theme : 'jui'], + $h = $t.children('thead').children(), + rmv = o.sortNone + ' ' + o.sortDesc + ' ' + o.sortAsc; + $t + .removeClass('tablesorter-' + theme + ' ' + o.table) + .find(c.cssHeader).removeClass(o.header); + $h + .unbind('mouseenter mouseleave') // remove hover + .removeClass(o.hover + ' ' + rmv + ' ' + o.active) + .find('.tablesorter-filter-row').removeClass(o.filterRow); + $h.find('.tablesorter-icon').removeClass(o.icons); } }); @@ -124,37 +201,39 @@ $.tablesorter.addWidget({ // ************************** $.tablesorter.addWidget({ id: "columns", - format: function(table) { + format: function(table){ var $tb, $tr, $td, $t, time, last, rmv, i, k, l, + $tbl = $(table), c = table.config, - b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'), + wo = c.widgetOptions, + b = $tbl.children('tbody:not(.' + c.cssInfoBlock + ')'), list = c.sortList, len = list.length, css = [ "primary", "secondary", "tertiary" ]; // default options // keep backwards compatibility, for now css = (c.widgetColumns && c.widgetColumns.hasOwnProperty('css')) ? c.widgetColumns.css || css : - (c.widgetOptions && c.widgetOptions.hasOwnProperty('columns')) ? c.widgetOptions.columns || css : css; + (wo && wo.hasOwnProperty('columns')) ? wo.columns || css : css; last = css.length-1; rmv = css.join(' '); - if (c.debug) { + if (c.debug){ time = new Date(); } // check if there is a sort (on initialization there may not be one) - for (k = 0; k < b.length; k++ ) { - $tb = $(b[k]); - $tr = $tb.addClass('tablesorter-hidden').children('tr'); + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, $(b[k]), true); // detach tbody + $tr = $tb.children('tr'); l = $tr.length; // loop through the visible rows $tr.each(function(){ $t = $(this); - if (this.style.display !== 'none') { + if (this.style.display !== 'none'){ // remove all columns class names $td = $t.children().removeClass(rmv); // add appropriate column class names - if (list && list[0]) { + if (list && list[0]){ // primary sort column class $td.eq(list[0][0]).addClass(css[0]); - if (len > 1) { + if (len > 1){ for (i = 1; i < len; i++){ // secondary, tertiary, etc sort column classes $td.eq(list[i][0]).addClass( css[i] || css[last] ); @@ -163,24 +242,58 @@ $.tablesorter.addWidget({ } } }); - $tb.removeClass('tablesorter-hidden'); + $.tablesorter.processTbody(table, $tb, false); } - if (c.debug) { + // add tfoot classes + if ($tbl.find('tfoot').length) { + $t = $tbl.find('tfoot tr').children().removeClass('tablesorter-sorted'); + if (list) { + for (k = 0; k < list.length; k++) { + $t.eq(list[k][0]).addClass('tablesorter-sorted'); + } + } + } + if (c.debug){ $.tablesorter.benchmark("Applying Columns widget", time); } + }, + remove: function(table, c, wo){ + var k, $tb, + b = $(table).children('tbody:not(.' + c.cssInfoBlock + ')'), + rmv = (c.widgetOptions.columns || [ "primary", "secondary", "tertiary" ]).join(' '); + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, $(b[k]), true); // remove tbody + $tb.children('tr').each(function(){ + $(this).children().removeClass(rmv); + }); + $.tablesorter.processTbody(table, $tb, false); // restore tbody + } + // clear tfoot + $(table).find('.tablesorter-sorted').removeClass('tablesorter-sorted'); } }); -// Widget: Filter -// "filter_startsWith", "filter_childRows", "filter_ignoreCase", -// "filter_searchDelay" & "filter_functions" options in "widgetOptions" -// ************************** +/* Widget: filter + widgetOptions: + filter_childRows : false // if true, filter includes child row content in the search + filter_columnFilters : true // if true, a filter will be added to the top of each table column + filter_cssFilter : 'tablesorter-filter' // css class name added to the filter row & each input in the row + filter_functions : null // add custom filter functions using this option + filter_hideFilters : false // collapse filter row when mouse leaves the area + filter_ignoreCase : true // if true, make all searches case-insensitive + filter_reset : null // jQuery selector string of an element used to reset the filters + filter_searchDelay : 300 // typing delay in milliseconds before starting a search + filter_startsWith : false // if true, filter start from the beginning of the cell contents + filter_useParsedData : false // filter all data using parsed content + **************************/ $.tablesorter.addWidget({ id: "filter", - format: function(table) { - if (table.config.parsers && !$(table).hasClass('hasFilters')) { - var i, j, k, l, cv, v, val, r, ff, t, x, xi, cr, - sel, $tb, $th, $tr, $td, reg2, + format: function(table){ + if (table.config.parsers && !$(table).hasClass('hasFilters')){ + var i, j, k, l, val, ff, x, xi, st, sel, str, + ft, ft2, $th, rg, s, t, dis, col, + last = '', // save last filter search + ts = $.tablesorter, c = table.config, $ths = $(c.headerList), wo = c.widgetOptions, @@ -188,27 +301,56 @@ $.tablesorter.addWidget({ $t = $(table).addClass('hasFilters'), b = $t.children('tbody:not(.' + c.cssInfoBlock + ')'), cols = c.parsers.length, - fr = '', - regexp = /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, - reg1 = new RegExp(c.cssChildRow), + reg = [ // regex used in filter "check" functions + /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // 0 = regex to test for regex + new RegExp(c.cssChildRow), // 1 = child row + /undefined|number/, // 2 = check type + /(^[\"|\'|=])|([\"|\'|=]$)/, // 3 = exact match + /[\"\'=]/g, // 4 = replace exact match flags + /[^\w,. \-()]/g, // 5 = replace non-digits (from digit & currency parser) + /[<>=]/g // 6 = replace operators + ], + parsed = $ths.map(function(i){ + return (ts.getData) ? ts.getData($ths.filter('[data-column="' + i + '"]:last'), c.headers[i], 'filter') === 'parsed' : $(this).hasClass('filter-parsed'); + }).get(), time, timer, - findRows = function(){ + + // dig fer gold + checkFilters = function(filter){ + var arry = $.isArray(filter), + v = (arry) ? filter : $t.find('thead').eq(0).children('tr').find('select.' + css + ', input.' + css).map(function(){ + return $(this).val() || ''; + }).get(), + cv = (v || []).join(''); // combined filter values + if (wo.filter_hideFilters === true){ + // show/hide filter row as needed + $t.find('.tablesorter-filter-row').trigger( cv === '' ? '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 (last === cv && filter !== false) { return; } + $t.trigger('filterStart', [v]); + // give it time for the processing icon to kick in + setTimeout(function(){ + findRows(filter, v, cv); + return false; + }, 10); + }, + findRows = function(filter, v, cv){ + var $tb, $tr, $td, cr, r, v, cv, l, ff, time, arry; if (c.debug) { time = new Date(); } - v = $t.find('thead').eq(0).children('tr').find('select.' + css + ', input.' + css).map(function(){ - return $(this).val() || ''; - }).get(); - cv = v.join(''); - for (k = 0; k < b.length; k++ ) { - $tb = $(b[k]); - $tr = $tb.addClass('tablesorter-hidden').children('tr'); + + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, $(b[k]), true); + $tr = $tb.children('tr'); l = $tr.length; - // loop through the rows - for (j = 0; j < l; j++) { - if (cv === '') { - $tr[j].style.display = ''; - } else { + if (cv === ''){ + $tr.show().removeClass('filtered'); + } else { + // loop through the rows + for (j = 0; j < l; j++){ // skip child rows - if (reg1.test($tr[j].className)) { continue; } + if (reg[1].test($tr[j].className)) { continue; } r = true; cr = $tr.eq(j).nextUntil('tr:not(.' + c.cssChildRow + ')'); // so, if "table.config.widgetOptions.filter_childRows" is true and there is @@ -218,19 +360,25 @@ $.tablesorter.addWidget({ typeof wo.filter_childRows !== 'undefined' ? wo.filter_childRows : true)) ? cr.text() : ''; t = wo.filter_ignoreCase ? t.toLocaleLowerCase() : t; $td = $tr.eq(j).children('td'); - for (i = 0; i < cols; i++) { - x = $.trim($td.eq(i).text()); - xi = wo.filter_ignoreCase ? x.toLocaleLowerCase() : x; - // ignore if filter is empty - if (v[i] !== '') { + for (i = 0; i < cols; i++){ + // ignore if filter is empty or disabled + if (v[i]){ + // check if column data should be from the cell or from parsed data + if (wo.filter_useParsedData || parsed[i]){ + x = c.cache[k].normalized[j][i]; + } else { + // using older or original tablesorter + x = $.trim($td.eq(i).text()); + } + xi = !reg[2].test(typeof x) && wo.filter_ignoreCase ? x.toLocaleLowerCase() : x; ff = r; // if r is true, show that row // val = case insensitive, v[i] = case sensitive val = wo.filter_ignoreCase ? v[i].toLocaleLowerCase() : v[i]; - if (wo.filter_functions && wo.filter_functions[i]) { - if (wo.filter_functions[i] === true) { + if (wo.filter_functions && wo.filter_functions[i]){ + if (wo.filter_functions[i] === true){ // default selector; no "filter-select" class - ff = wo.filter_ignoreCase ? val === xi : v[i] === x; - } else if (typeof wo.filter_functions[i] === 'function') { + ff = ($ths.filter('[data-column="' + i + '"]:last').hasClass('filter-match')) ? xi.search(val) >= 0 : v[i] === x; + } else if (typeof wo.filter_functions[i] === 'function'){ // filter callback( exact cell content, parser normalized content, filter input value, column index ) ff = wo.filter_functions[i](x, c.cache[k].normalized[j][i], v[i], i); } else if (typeof wo.filter_functions[i][v[i]] === 'function'){ @@ -238,18 +386,30 @@ $.tablesorter.addWidget({ ff = wo.filter_functions[i][v[i]](x, c.cache[k].normalized[j][i], v[i], i); } // Look for regex - } else if (regexp.test(val)) { - reg2 = regexp.exec(val); + } else if (reg[0].test(val)){ + rg = reg[0].exec(val); try { - ff = new RegExp(reg2[1], reg2[2]).test(xi); - } catch (err) { + ff = new RegExp(rg[1], rg[2]).test(xi); + } catch (err){ ff = false; } - // Look for quotes to get an exact match - } else if (/[\"|\']$/.test(val) && xi === val.replace(/(\"|\')/g,'')) { + // Look for quotes or equals to get an exact match + } else if (reg[3].test(val) && xi === val.replace(reg[4], '')){ ff = true; + // Look for a not match + } else if (/^\!/.test(val)){ + val = val.replace('!',''); + s = xi.search($.trim(val)); + ff = val === '' ? true : !(wo.filter_startsWith ? s === 0 : s >= 0); + // Look for operators >, >=, < or <= + } else if (/^[<>]=?/.test(val)){ + rg = $.tablesorter.formatFloat(xi.replace(reg[5], ''), table); + if (isNaN(rg)) { rg = xi; } + s = $.tablesorter.formatFloat(val.replace(reg[5], '').replace(reg[6],''), table); + if (/>/.test(val)) { ff = />=/.test(val) ? rg >= s : rg > s; } + if (/' + ($ths.filter('[data-column="' + i + '"]:last').attr('data-placeholder') || '') + ''; - for (k = 0; k < b.length; k++ ) { + for (k = 0; k < b.length; k++ ){ l = c.cache[k].row.length; // loop through the rows - for (j = 0; j < l; j++) { + for (j = 0; j < l; j++){ // get non-normalized cell content - t = c.cache[k].row[j][0].cells[i]; - if (t) { - arry.push( c.supportsTextContent ? t.textContent : $(t).text() ); + if (wo.filter_useParsedData){ + arry.push( '' + c.cache[k].normalized[j][i] ); + } else { + t = c.cache[k].row[j][0].cells[i]; + if (t){ + arry.push( c.supportsTextContent ? t.textContent : $(t).text() ); + } } } } + // get unique elements and sort the list - arry = arry.getUnique(true); + // if $.tablesorter.sortText exists (not in the original tablesorter), + // then natural sort the list otherwise use a basic sort + arry = $.grep(arry, function(v, k){ + return $.inArray(v ,arry) === k; + }); + arry = (ts.sortText) ? arry.sort(function(a,b){ return ts.sortText(table, a, b, i); }) : arry.sort(true); + // build option list - for (k = 0; k < arry.length; k++) { + for (k = 0; k < arry.length; k++){ o += ''; } $t.find('thead').find('select.' + css + '[data-column="' + i + '"]')[ updating ? 'html' : 'append' ](o); }, buildDefault = function(updating){ // build default select dropdown - for (i = 0; i < cols; i++) { + for (i = 0; i < cols; i++){ t = $ths.filter('[data-column="' + i + '"]:last'); // look for the filter-select class, but don't build it twice. if (t.hasClass('filter-select') && !t.hasClass('filter-false') && !(wo.filter_functions && wo.filter_functions[i] === true)){ + if (!wo.filter_functions) { wo.filter_functions = {}; } + wo.filter_functions[i] = true; // make sure this select gets processed by filter_functions buildSelect(i, updating); } } }; - if (c.debug) { + + if (c.debug){ time = new Date(); } wo.filter_ignoreCase = wo.filter_ignoreCase !== false; // set default filter_ignoreCase to true - for (i=0; i < cols; i++){ - $th = $ths.filter('[data-column="' + i + '"]:last'); // assuming last cell of a column is the main column - sel = (wo.filter_functions && wo.filter_functions[i] && typeof wo.filter_functions[i] !== 'function') || $th.hasClass('filter-select'); - fr += ''; - if (sel){ - fr += '' : '>') + ''; } - // use header option - headers: { 1: { filter: false } } OR add class="filter-false" - if ($.tablesorter.getData) { - // get data from jQuery data, metadata, headers option or header class name - fr += $.tablesorter.getData($th[0], c.headers[i], 'filter') === 'false' ? ' disabled" disabled' : '"'; - } else { - // only class names and header options - keep this for compatibility with tablesorter v2.0.5 - fr += ((c.headers[i] && c.headers[i].hasOwnProperty('filter') && c.headers[i].filter === false) || $th.hasClass('filter-false') ) ? ' disabled" disabled' : '"'; - } - fr += (sel ? '>' : '>') + ''; + $t.find('thead').eq(0).append(t += ''); } $t - .bind('addRows updateCell update appendCache', function(){ - buildDefault(true); - findRows(); + // add .tsfilter namespace to all BUT search + .bind('addRows updateCell update appendCache search'.split(' ').join('.tsfilter '), function(e, filter){ + if (e.type !== 'search'){ + buildDefault(true); + } + checkFilters(e.type === 'search' ? filter : ''); + return false; }) - .find('thead').eq(0).append(fr += '') - .find('input.' + css).bind('keyup search', function(e, delay){ + .find('input.' + css).bind('keyup search', function(e, filter){ // ignore arrow and meta keys; allow backspace if ((e.which < 32 && e.which !== 8) || (e.which >= 37 && e.which <=40)) { return; } // skip delay - if (delay === false) { - findRows(); - return; + if (typeof filter !== 'undefined'){ + checkFilters(filter); + return false; } // delay filtering clearTimeout(timer); timer = setTimeout(function(){ - findRows(); + checkFilters(); }, wo.filter_searchDelay || 300); }); - if (wo.filter_functions) { + + // reset button/link + if (wo.filter_reset && $(wo.filter_reset).length){ + $(wo.filter_reset).bind('click', function(){ + $t.find('.' + css).val(''); + checkFilters(); + return false; + }); + } + if (wo.filter_functions){ // i = column # (string) - for (i in wo.filter_functions) { - t = $ths.filter('[data-column="' + i + '"]:last'); - fr = ''; - if (typeof i === 'string' && wo.filter_functions[i] === true && !t.hasClass('filter-false')) { - buildSelect(i); - } else if (typeof i === 'string' && !t.hasClass('filter-false')) { - // add custom drop down list - for (j in wo.filter_functions[i]) { - if (typeof j === 'string') { - fr += fr === '' ? '' : ''; - fr += ''; + for (col in wo.filter_functions){ + if (wo.filter_functions.hasOwnProperty(col) && typeof col === 'string'){ + t = $ths.filter('[data-column="' + col + '"]:last'); + ff = ''; + if (wo.filter_functions[col] === true && !t.hasClass('filter-false')){ + buildSelect(col); + } else if (typeof col === 'string' && !t.hasClass('filter-false')){ + // add custom drop down list + for (str in wo.filter_functions[col]){ + if (typeof str === 'string'){ + ff += ff === '' ? '' : ''; + ff += ''; + } } + $t.find('thead').find('select.' + css + '[data-column="' + col + '"]').append(ff); } - $t.find('thead').find('select.' + css + '[data-column="' + i + '"]').append(fr); } } } buildDefault(); $t.find('select.' + css).bind('change', function(){ - findRows(); + checkFilters(); }); - if (c.debug) { - $.tablesorter.benchmark("Applying Filter widget", time); + if (wo.filter_hideFilters === true){ + $t + .find('.tablesorter-filter-row') + .addClass('hideme') + .bind('mouseenter mouseleave', function(e){ + // save event object - http://bugs.jquery.com/ticket/12140 + var all, evt = e; + ft = $(this); + clearTimeout(st); + st = setTimeout(function(){ + if (/enter|over/.test(evt.type)){ + ft.removeClass('hideme'); + } else { + // don't hide if input has focus + // $(':focus') needs jQuery 1.6+ + if ($(document.activeElement).closest('tr')[0] !== ft[0]){ + // get all filter values + all = $t.find('.' + (wo.filter_cssFilter || 'tablesorter-filter')).map(function(){ + return $(this).val() || ''; + }).get().join(''); + // don't hide row if any filter has a value + if (all === ''){ + ft.addClass('hideme'); + } + } + } + }, 200); + }) + .find('input, select').bind('focus blur', function(e){ + ft2 = $(this).closest('tr'); + clearTimeout(st); + st = setTimeout(function(){ + // don't hide row if any filter has a value + if ($t.find('.' + (wo.filter_cssFilter || 'tablesorter-filter')).map(function(){ return $(this).val() || ''; }).get().join('') === ''){ + ft2[ e.type === 'focus' ? 'removeClass' : 'addClass']('hideme'); + } + }, 200); + }); } + + // show processesing icon + if (c.showProcessing) { + $t.bind('filterStart filterEnd', function(e, v) { + var fc = (v) ? $t.find('.' + c.cssHeader).filter('[data-column]').filter(function(){ + return v[$(this).data().column] !== ''; + }) : ''; + ts.isProcessing($t[0], e.type === 'filterStart', v ? fc : ''); + }); + } + + if (c.debug){ + ts.benchmark("Applying Filter widget", time); + } + } + }, + remove: function(table, c, wo){ + var k, $tb, + $t = $(table), + b = $t.children('tbody:not(.' + c.cssInfoBlock + ')'); + $t + .removeClass('hasFilters') + // add .tsfilter namespace to all BUT search + .unbind('addRows updateCell update appendCache search'.split(' ').join('.tsfilter')) + .find('.tablesorter-filter-row').remove(); + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, $(b[k]), true); // remove tbody + $tb.children().removeClass('filtered').show(); + $.tablesorter.processTbody(table, $tb, false); // restore tbody } } }); // Widget: Sticky headers // based on this awesome article: -// http://css-tricks.com/13465-persistent-headers/ +// http://css-tricks.com/13465-persistent-headers/ +// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech // ************************** $.tablesorter.addWidget({ id: "stickyHeaders", - format: function(table) { + format: function(table){ if ($(table).hasClass('hasStickyHeaders')) { return; } var $table = $(table).addClass('hasStickyHeaders'), - wo = table.config.widgetOptions, + c = table.config, + wo = c.widgetOptions, win = $(window), - header = $(table).children('thead'), + header = $(table).children('thead:first'), hdrCells = header.children('tr:not(.sticky-false)').children(), css = wo.stickyHeaders || 'tablesorter-stickyHeader', innr = '.tablesorter-header-inner', firstCell = hdrCells.eq(0), tfoot = $table.find('tfoot'), - sticky = header.find('tr.tablesorter-header:not(.sticky-false)').clone() - .removeClass('tablesorter-header') + // clone the entire thead - seems to work in IE8+ + stkyHdr = header.clone() .addClass(css) .css({ width : header.outerWidth(true), position : 'fixed', - left : firstCell.offset().left, margin : 0, top : 0, visibility : 'hidden', - zIndex : 10 + zIndex : 1 }), - stkyCells = sticky.children(), - laststate = ''; + stkyCells = stkyHdr.find('tr').children(), + laststate = '', + resizeHdr = function(){ + var bwsr = navigator.userAgent, + spacing = 0; + // yes, I dislike browser sniffing, but it really is needed here :( + // webkit automatically compensates for border spacing + if ($table.css('border-collapse') !== 'collapse' && !/webkit/i.test(bwsr)) { + // IE needs to be adjusted by the padding; Firefox & Opera use the border-spacing + // update border-spacing here because of demos that switch themes + spacing = /msie/i.test(bwsr) ? parseInt(header.css('padding-left'), 10) : parseInt($table.css('border-spacing'), 10); + } + stkyHdr.css({ + left : header.offset().left - win.scrollLeft() - spacing, + width: header.outerWidth() + }); + stkyCells + .each(function(i){ + var $h = hdrCells.eq(i); + $(this).css({ + width: $h.width(), + height: $h.height() + }); + }) + .find(innr).each(function(i){ + var hi = hdrCells.eq(i).find(innr), + w = hi.width(); // - ( parseInt(hi.css('padding-left'), 10) + parseInt(hi.css('padding-right'), 10) ); + $(this).width(w); + }); + }; + // remove rows you don't want to be sticky + stkyHdr.find('tr.sticky-false').remove(); + // remove resizable block + stkyCells.find('.tablesorter-resizer').remove(); // update sticky header class names to match real header after sorting - $table.bind('sortEnd', function(e,t){ - var th = $(t).find('thead tr'), - sh = th.filter('.' + css).children(); - th.filter(':not(.' + css + ')').children().each(function(i){ - sh.eq(i).attr('class', $(this).attr('class')); + $table + .bind('sortEnd.tsSticky', function(){ + hdrCells.each(function(i){ + var t = stkyCells.eq(i); + t.attr('class', $(this).attr('class')); + if (c.cssIcon){ + t + .find('.' + c.cssIcon) + .attr('class', $(this).find('.' + c.cssIcon).attr('class')); + } }); - }).bind('pagerComplete', function(){ - win.resize(); // trigger window resize to make sure column widths & position are correct + }) + .bind('pagerComplete.tsSticky', function(){ + resizeHdr(); }); // set sticky header cell width and link clicks to real header hdrCells.each(function(i){ @@ -431,41 +735,37 @@ $.tablesorter.addWidget({ .bind('mousedown', function(){ this.onselectstart = function(){ return false; }; return false; - }) - // set cell widths - .find(innr).width( t.find(innr).width() ); + }); }); - header.prepend( sticky ); + header.after( stkyHdr ); // make it sticky! win - .scroll(function(){ - var offset = firstCell.offset(), - sTop = win.scrollTop(), - tableHt = $table.height() - (firstCell.height() + (tfoot.height() || 0)), - vis = (sTop > offset.top) && (sTop < offset.top + tableHt) ? 'visible' : 'hidden'; - sticky.css({ - left : offset.left - win.scrollLeft(), - visibility : vis - }); - if (vis !== laststate) { - // trigger resize to make sure the column widths match - win.resize(); - laststate = vis; - } - }) - .resize(function(){ - var ht = 0; - sticky.css({ - left : firstCell.offset().left - win.scrollLeft(), - width: header.outerWidth() - }).each(function(i){ - $(this).css('top', ht); - ht += header.find('tr').eq(i).outerHeight(); - }); - stkyCells.find(innr).each(function(i){ - $(this).width( hdrCells.eq(i).find(innr).width() ); - }); + .bind('scroll.tsSticky', function(){ + var offset = firstCell.offset(), + sTop = win.scrollTop(), + tableHt = $table.height() - (firstCell.height() + (tfoot.height() || 0)), + vis = (sTop > offset.top) && (sTop < offset.top + tableHt) ? 'visible' : 'hidden'; + stkyHdr.css({ + visibility : vis }); + if (vis !== laststate){ + // make sure the column widths match + resizeHdr(); + laststate = vis; + } + }) + .bind('resize.tsSticky', function(){ + resizeHdr(); + }); + }, + remove: function(table, c, wo){ + var $t = $(table), + css = wo.stickyHeaders || 'tablesorter-stickyHeader'; + $t + .removeClass('hasStickyHeaders') + .unbind('sortEnd.tsSticky pagerComplete.tsSticky') + .find('.' + css).remove(); + $(window).unbind('scroll.tsSticky resize.tsSticky'); } }); @@ -475,85 +775,126 @@ $.tablesorter.addWidget({ // ************************** $.tablesorter.addWidget({ id: "resizable", - format: function(table) { + format: function(table){ if ($(table).hasClass('hasResizable')) { return; } $(table).addClass('hasResizable'); - var j, s, c = table.config, - $cols = $(c.headerList).filter(':gt(0)'), + var t, j, s, $c, $cols, + $tbl = $(table), + c = table.config, + wo = c.widgetOptions, position = 0, $target = null, - $prev = null, + $next = null, stopResize = function(){ position = 0; - $target = $prev = null; + $target = $next = null; $(window).trigger('resize'); // will update stickyHeaders, just in case }; - s = ($.tablesorter.storage) ? $.tablesorter.storage(table, 'tablesorter-resizable') : ''; + s = ($.tablesorter.storage && wo.resize !== false) ? $.tablesorter.storage(table, 'tablesorter-resizable') : {}; // process only if table ID or url match - if (s) { - for (j in s) { - if (!isNaN(j) && j < c.headerList.length) { + if (s){ + for (j in s){ + if (!isNaN(j) && j < c.headerList.length){ $(c.headerList[j]).width(s[j]); // set saved resizable widths } } } + $tbl.children('thead:first').find('tr').each(function(){ + $c = $(this).children().not(':last'); // don't include the first column of the row + $cols = $cols ? $cols.add($c) : $c; + }); $cols - .each(function(){ - $(this) - .append('
') - .wrapInner('
'); - }) - .bind('mousemove', function(e){ - // ignore mousemove if no mousedown - if (position === 0 || !$target) { return; } - var w = e.pageX - position; - // make sure - if ( $target.width() < -w || ( $prev && $prev.width() <= w )) { return; } - // resize current column - $prev.width( $prev.width() + w ); - position = e.pageX; - }) - .bind('mouseup', function(){ - if (s && $.tablesorter.storage && $target) { - s[$prev.index()] = $prev.width(); + .each(function(){ + t = $(this); + j = parseInt(t.css('padding-right'), 10) + 8; // 8 is 1/2 of the 16px wide resizer + t + .append('
') + // Firefox needs this inner div to position the resizer correctly + .wrapInner('
'); + }) + .bind('mousemove.tsresize', function(e){ + // ignore mousemove if no mousedown + if (position === 0 || !$target) { return; } + // resize columns + var w = e.pageX - position; + $target.width( $target.width() + w ); + $next.width( $next.width() - w ); + position = e.pageX; + }) + .bind('mouseup.tsresize', function(){ + if ($.tablesorter.storage && $target){ + s[$target.index()] = $target.width(); + s[$next.index()] = $next.width(); + if (wo.resize !== false){ $.tablesorter.storage(table, 'tablesorter-resizable', s); } - stopResize(); - return false; - }) - .find('.tablesorter-resizer') - .bind('mousedown', function(e){ - // save header cell and mouse position - $target = $(e.target).closest('th'); - $prev = $target.prev(); - position = e.pageX; - return false; - }); - $(table).find('thead').bind('mouseup mouseleave', function(){ + } stopResize(); + }) + .find('.tablesorter-resizer') + .bind('mousedown', function(e){ + // save header cell and mouse position + $target = $(e.target).closest('th'); + $next = $target.next(); + position = e.pageX; }); + $tbl.find('thead:first') + .bind('mouseup.tsresize mouseleave.tsresize', function(e){ + stopResize(); + }) + // right click to reset columns to default widths + .bind('contextmenu.tsresize', function(){ + $.tablesorter.resizableReset(table); + // $.isEmptyObject() needs jQuery 1.4+ + var rtn = $.isEmptyObject(s); // allow right click if already reset + s = {}; + return rtn; + }); + }, + remove: function(table, c, wo){ + $(table) + .removeClass('hasResizable') + .find('thead') + .unbind('mouseup.tsresize mouseleave.tsresize contextmenu.tsresize') + .find('tr').children() + .unbind('mousemove.tsresize mouseup.tsresize') + .find('.tablesorter-wrapper').each(function(){ + $(this).find('.tablesorter-resizer').remove(); + $(this).replaceWith( $(this).contents() ); + }); + $(c.headerList).width('auto'); + // clear storage? + // $.tablesorter.storage( table, 'tablesorter-resizable', '' ); } }); +$.tablesorter.resizableReset = function(table){ + $(table.config.headerList).width('auto'); + $.tablesorter.storage(table, 'tablesorter-resizable', {}); +}; // Save table sort widget // this widget saves the last sort only if the +// saveSort widget option is true AND the // $.tablesorter.storage function is included // ************************** $.tablesorter.addWidget({ id: 'saveSort', - init: function(table, allWidgets, thisWidget){ + init: function(table, thisWidget){ // run widget format before all other widgets are applied to the table thisWidget.format(table, true); }, - format: function(table, init) { - var sl, time, c = table.config, sortList = { "sortList" : c.sortList }; - if (c.debug) { + format: function(table, init){ + var sl, time, c = table.config, + wo = c.widgetOptions, + ss = 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 (table.hasInitialized && $.tablesorter.storage) { + if ($(table).hasClass('hasSaveSort')){ + if (ss && table.hasInitialized && $.tablesorter.storage){ $.tablesorter.storage( table, 'tablesorter-savesort', sortList ); - if (c.debug) { + if (c.debug){ $.tablesorter.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); } } @@ -562,36 +903,27 @@ $.tablesorter.addWidget({ $(table).addClass('hasSaveSort'); sortList = ''; // get data - if ($.tablesorter.storage) { + if ($.tablesorter.storage){ sl = $.tablesorter.storage( table, 'tablesorter-savesort' ); sortList = (sl && sl.hasOwnProperty('sortList') && $.isArray(sl.sortList)) ? sl.sortList : ''; - if (c.debug) { - $.tablesorter.benchmark('saveSort: Last sort loaded: ' + sortList, time); + if (c.debug){ + $.tablesorter.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time); } } // 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) { + if (init && sortList && sortList.length > 0){ c.sortList = sortList; - } else if (table.hasInitialized && sortList && sortList.length > 0) { + } else if (table.hasInitialized && sortList && sortList.length > 0){ // update sort change $(table).trigger('sorton', [sortList]); } } + }, + remove: function(table, c, wo){ + // clear storage + $.tablesorter.storage( table, 'tablesorter-savesort', '' ); } }); })(jQuery); - -// return an array with unique values https://gist.github.com/461516 -Array.prototype.getUnique = function(s){ - var c, a = [], o = {}, i, j = 0, l = this.length; - for(i=0; i < l; ++i) { - c = this[i]; - if (!o[c]) { - o[c] = {}; - a[j++] = c; - } - } - return (s) ? a.sort() : a; -};