diff --git a/web/yaamp/modules/coin/index.php b/web/yaamp/modules/coin/index.php index a9582a5..bf72c13 100644 --- a/web/yaamp/modules/coin/index.php +++ b/web/yaamp/modules/coin/index.php @@ -1,6 +1,7 @@ Add a coin"; +JavascriptFile("/yaamp/ui/js/jquery.metadata.js"); +JavascriptFile("/yaamp/ui/js/jquery.tablesorter.widgets.js"); echo << @@ -13,7 +14,18 @@ end; showTableSorter('maintable', "{ tableClass: 'dataGrid', - headers: { 0: { sorter: false} }, + headers: { + 0:{sorter:false}, + 1:{sorter:'text'}, + 2:{sorter:'text'}, + 3:{sorter:'text'}, + 4:{sorter:'text'}, + 5:{sorter:'text'}, + 6:{sorter:'metadata'}, + 7:{sorter:'numeric'}, + 8:{sorter:'text'}, + 9:{sorter: false } + }, widgets: ['zebra','filter'], widgetOptions: { filter_external: '.search', @@ -52,7 +64,7 @@ foreach($coins as $coin) $coin->errors = substr($coin->errors, 0, 30); $coin->version = substr($coin->version, 0, 20); $difficulty = Itoa2($coin->difficulty, 3); - $d = datetoa2($coin->created); + $created = datetoa2($coin->created); echo ""; echo ""; @@ -76,10 +88,10 @@ foreach($coins as $coin) echo ""; echo "$coin->version"; - echo "$d ago"; + echo ''.$created.''; // echo "$difficulty"; - echo "$coin->block_height"; + echo ''.$coin->block_height.''; echo "$coin->errors"; echo ""; @@ -116,12 +128,18 @@ $total = count($coins); echo ""; echo ""; -echo "$total coins, $total_installed installed, $total_active running"; +echo ''; +echo "$total coins, $total_installed installed, $total_active running"; +echo '

Add a coin'; +echo ''; +echo ''; +echo ''; +echo ''; +echo ''; +echo ''; echo ""; echo ""; echo "




"; echo "




"; - - diff --git a/web/yaamp/modules/site/admin.php b/web/yaamp/modules/site/admin.php index 22b833f..e10542c 100644 --- a/web/yaamp/modules/site/admin.php +++ b/web/yaamp/modules/site/admin.php @@ -1,5 +1,8 @@ Empty Markets '; @@ -40,28 +43,29 @@ Select Server: $('#server_select').change(function(event) { var server = $('#server_select').val(); + clearTimeout(main_timeout); window.location.href = '/site/admin?server='+server; }); -//var current_hash; - $(function() { -// current_hash = window.location.hash; -// window.location.hash = ''; - main_refresh(); }); var main_delay=30000; var main_timeout; +var lastSearch = false; function main_ready(data) { $('#main_results').html(data); $('#server_select').val('{$server}'); -// window.location.hash = current_hash; + if (lastSearch !== false) { + $('input.search').val(lastSearch); + $('table.dataGrid').trigger('search'); + } + main_timeout = setTimeout(main_refresh, main_delay); } @@ -75,6 +79,7 @@ function main_refresh() var url = "/site/admin_results?server=$server"; clearTimeout(main_timeout); + lastSearch = $('input.search').val(); $.get(url, '', main_ready).error(main_error); } diff --git a/web/yaamp/modules/site/admin_results.php b/web/yaamp/modules/site/admin_results.php index b5efd5f..b5d6e8d 100644 --- a/web/yaamp/modules/site/admin_results.php +++ b/web/yaamp/modules/site/admin_results.php @@ -11,8 +11,8 @@ end; showTableSorter('maintable', '{ tableClass: "dataGrid", headers: { - 0:{sorter:false}, - 1:{sorter:false}, + 0:{sorter:"metadata"}, /* reset filter */ + 1:{sorter:"metadata"}, 2:{sorter:"text"}, 3:{sorter:"text"}, 4:{sorter:"currency"}, @@ -21,10 +21,13 @@ headers: { 7:{sorter:"currency"}, 8:{sorter:"currency"}, 9:{sorter:"currency"}, - 10:{sorter:"currency"} + 10:{sorter:"currency"}, + 11:{sorter:"currency"} }, -widgets: ["zebra","filter"], +widgets: ["zebra","filter","Storage","saveSort"], widgetOptions: { + saveSort: true, + filter_saveFilters: true, filter_external: ".search", filter_columnFilters: false, filter_childRows : true, @@ -42,17 +45,16 @@ echo <<Diff/Height Profit Owed/BTC -Balance/BTC -Mint/BTC +Balance/Mint Price +BTC +USD Win/Market end; -$current_algo = ''; - $server = getparam('server'); if(!empty($server)) { @@ -62,21 +64,17 @@ if(!empty($server)) else $coins = getdbolist('db_coins', "installed or enable order by algo, index_avg desc"); +$mining = getdbosql('db_mining'); + foreach($coins as $coin) { - if($coin->algo != $current_algo) - { - $current_algo = $coin->algo; - echo ""; - } - else - echo ""; + echo ''; $lowsymbol = strtolower($coin->symbol); echo ""; $algo_color = getAlgoColors($coin->algo); - echo ""; + echo ''; if($coin->enable) { @@ -109,6 +107,7 @@ foreach($coins as $coin) echo "
$coin->rpcencoding   ($coin->algo)  "; $difficulty = Itoa2($coin->difficulty, 3); + if ($difficulty > 1e20) $difficulty = ' '; if(!empty($coin->errors)) echo "$difficulty
$coin->block_height"; @@ -147,25 +146,29 @@ foreach($coins as $coin) else echo "$owed
$owed_btc"; - $btc = bitcoinvaluetoa($coin->balance*$coin->price); - echo "$coin->balance
$btc"; - - $btc = bitcoinvaluetoa($coin->mint*$coin->price); - echo "$coin->mint
$btc"; + echo ''.$coin->balance.'
'.$coin->mint.''; $price = bitcoinvaluetoa($coin->price); $price2 = bitcoinvaluetoa($coin->price2); // $marketcount = getdbocount('db_markets', "coinid=$coin->id"); - $marketname = ''; - $bestmarket = getBestMarket($coin); - if($bestmarket) $marketname = $bestmarket->name; - if($coin->dontsell) echo "$price
$price2"; else echo "$price
$price2"; + $btc = bitcoinvaluetoa($coin->balance * $coin->price); + $mint = bitcoinvaluetoa($coin->mint * $coin->price); + echo ''.$btc.'
'.$mint.''; + + $fiat = round($coin->balance * $coin->price * $mining->usdbtc, 2). ' $'; + $mint = round($coin->mint * $coin->price * $mining->usdbtc, 2). ' $'; + echo ''.$fiat.'
'.$mint.''; + + $marketname = ''; + $bestmarket = getBestMarket($coin); + if($bestmarket) $marketname = $bestmarket->name; + echo "$coin->reward
$marketname"; echo ""; diff --git a/web/yaamp/modules/site/earning_results.php b/web/yaamp/modules/site/earning_results.php index 60550a7..115b71b 100644 --- a/web/yaamp/modules/site/earning_results.php +++ b/web/yaamp/modules/site/earning_results.php @@ -1,5 +1,8 @@ @@ -11,7 +14,18 @@ end; showTableSorter('maintable', "{ tableClass: 'dataGrid', - headers: { 0: { sorter: false} }, + headers: { + 0:{sorter:false}, + 1:{sorter:'text'}, + 2:{sorter:'text'}, + 3:{sorter:'currency'}, + 4:{sorter:'numeric'}, + 5:{sorter:false}, + 6:{sorter:'metadata'}, + 7:{sorter:false}, + 8:{sorter:false}, + 9:{sorter:false} + }, widgets: ['zebra','filter'], widgetOptions: { filter_external: '.search', @@ -51,7 +65,8 @@ foreach($earnings as $earning) if(!$block) continue; $t1 = datetoa2($earning->create_time). ' ago'; - $t2 = datetoa2($earning->mature_time). ' ago'; + $t2 = datetoa2($earning->mature_time); + if ($t2) $t2 = '+'.$t2; $coinimg = CHtml::image($coin->image, $coin->symbol, array('width'=>'16')); $coinlink = CHtml::link($coin->name, '/site/coin?id='.$coin->id); @@ -63,7 +78,7 @@ foreach($earnings as $earning) echo ''.bitcoinvaluetoa($earning->amount).''; echo "$block->height"; echo "$block->category ($block->confirmations)"; - echo "$t1 $t2"; + echo ''."$t1 $t2"; echo " [clear] diff --git a/web/yaamp/ui/js/jquery.metadata.js b/web/yaamp/ui/js/jquery.metadata.js new file mode 100644 index 0000000..8410463 --- /dev/null +++ b/web/yaamp/ui/js/jquery.metadata.js @@ -0,0 +1,122 @@ +/* + * Metadata - jQuery plugin for parsing metadata from elements + * + * Copyright (c) 2006 John Resig, Yehuda Katz, J�örn Zaefferer, Paul McLanahan + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id: jquery.metadata.js 4187 2007-12-16 17:15:27Z joern.zaefferer $ + * + */ + +/** + * Sets the type of metadata to use. Metadata is encoded in JSON, and each property + * in the JSON will become a property of the element itself. + * + * There are three supported types of metadata storage: + * + * attr: Inside an attribute. The name parameter indicates *which* attribute. + * + * class: Inside the class attribute, wrapped in curly braces: { } + * + * elem: Inside a child element (e.g. a script tag). The + * name parameter indicates *which* element. + * + * The metadata for an element is loaded the first time the element is accessed via jQuery. + * + * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements + * matched by expr, then redefine the metadata type and run another $(expr) for other elements. + * + * @name $.metadata.setType + * + * @example

This is a p

+ * @before $.metadata.setType("class") + * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" + * @desc Reads metadata from the class attribute + * + * @example

This is a p

+ * @before $.metadata.setType("attr", "data") + * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" + * @desc Reads metadata from a "data" attribute + * + * @example

This is a p

+ * @before $.metadata.setType("elem", "script") + * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" + * @desc Reads metadata from a nested script element + * + * @param String type The encoding type + * @param String name The name of the attribute to be used to get metadata (optional) + * @cat Plugins/Metadata + * @descr Sets the type of encoding to be used when loading metadata for the first time + * @type undefined + * @see metadata() + */ + +(function($) { + +$.extend({ + metadata : { + defaults : { + type: 'class', + name: 'metadata', + cre: /({.*})/, + single: 'metadata' + }, + setType: function( type, name ){ + this.defaults.type = type; + this.defaults.name = name; + }, + get: function( elem, opts ){ + var settings = $.extend({},this.defaults,opts); + // check for empty string in single property + if ( !settings.single.length ) settings.single = 'metadata'; + + var data = $.data(elem, settings.single); + // returned cached data if it already exists + if ( data ) return data; + + data = "{}"; + + if ( settings.type == "class" ) { + var m = settings.cre.exec( elem.className ); + if ( m ) + data = m[1]; + } else if ( settings.type == "elem" ) { + if( !elem.getElementsByTagName ) + return undefined; + var e = elem.getElementsByTagName(settings.name); + if ( e.length ) + data = $.trim(e[0].innerHTML); + } else if ( elem.getAttribute != undefined ) { + var attr = elem.getAttribute( settings.name ); + if ( attr ) + data = attr; + } + + if ( data.indexOf( '{' ) <0 ) + data = "{" + data + "}"; + + data = eval("(" + data + ")"); + + $.data( elem, settings.single, data ); + return data; + } + } +}); + +/** + * Returns the metadata object for the first member of the jQuery object. + * + * @name metadata + * @descr Returns element's metadata object + * @param Object opts An object contianing settings to override the defaults + * @type jQuery + * @cat Plugins/Metadata + */ +$.fn.metadata = function( opts ){ + return $.metadata.get( this[0], opts ); +}; + +})(jQuery); \ No newline at end of file diff --git a/web/yaamp/ui/js/jquery.tablesorter.widgets.js b/web/yaamp/ui/js/jquery.tablesorter.widgets.js new file mode 100644 index 0000000..528307c --- /dev/null +++ b/web/yaamp/ui/js/jquery.tablesorter.widgets.js @@ -0,0 +1,1037 @@ +/*! tableSorter 2.4+ widgets - updated 3/27/2013 + * + * Column Styles + * Column Filters + * Column Resizing + * Sticky Header + * UI Theme (generalized) + * Save Sort + * ["zebra", "uitheme", "stickyHeaders", "filter", "columns"] + */ +/*jshint browser:true, jquery:true, unused:false, loopfunc:true */ +/*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 + footerRow : '', + footerCells: '', + 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 : '', // even row zebra striping + odd : '' // odd 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 + footerRow : '', + footerCells: '', + 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', // even row zebra striping + odd : 'ui-state-default' // odd row zebra striping + } +}; + +// *** 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 + + // *** 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 +*/ +$.tablesorter.storage = function(table, key, val){ + var d, k, ls = false, v = {}, + id = table.id || $('.tablesorter').index( $(table) ), + url = window.location.pathname; + try { ls = !!(localStorage.getItem); } catch(e) {} + // *** get val *** + if ($.parseJSON){ + if (ls){ + v = $.parseJSON(localStorage[key] || '{}'); + } else { + k = document.cookie.split(/[;\s|=]/); // cookie + d = $.inArray(key, k) + 1; // add one to get from the key to the value + v = (d !== 0) ? $.parseJSON(k[d] || '{}') : {}; + } + } + // allow val to be an empty string to + if ((val || val === '') && window.JSON && JSON.hasOwnProperty('stringify')){ + // add unique identifiers = url pathname > table ID/index on page > data + if (!v[url]) { + v[url] = {}; + } + v[url][id] = val; + // *** set val *** + if (ls){ + localStorage[key] = JSON.stringify(v); + } else { + d = new Date(); + d.setTime(d.getTime() + (31536e+6)); // 365 days + document.cookie = key + '=' + (JSON.stringify(v)).replace(/\"/g,'\"') + '; expires=' + d.toGMTString() + '; path=/'; + } + } else { + return v && v[url] ? v[url][id] : {}; + } +}; + +// Widget: General UI theme +// "uitheme" option in "widgetOptions" +// ************************** +$.tablesorter.addWidget({ + id: "uitheme", + format: function(table){ + var time, klass, $el, $tar, + t = $.tablesorter.themes, + $t = $(table), + c = table.config, + wo = c.widgetOptions, + theme = c.theme !== 'default' ? c.theme : wo.uitheme || 'jui', // default uitheme is 'jui' + o = t[ t[theme] ? theme : t[wo.uitheme] ? wo.uitheme : '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 = $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'); + if (t.length) { + t + .find('tr').addClass(o.footerRow) + .children('th, td').addClass(o.footerCells); + } + // update header classes + $h + .addClass(o.header) + .filter(':not(.sorter-false)') + .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(e){ + // toggleClass with switch added in jQuery 1.3 + $(this)[ e.type === 'mouseenter' ? 'addClass' : 'removeClass' ](o.hover); + }); + if (!$h.find('.tablesorter-wrapper').length) { + // Firefox needs this inner div to position the resizer correctly + $h.wrapInner('
'); + } + 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($h, function(i){ + $el = $(this); + $tar = (c.cssIcon) ? $el.find('.' + c.cssIcon) : $el; + if (this.sortDisabled){ + // no sort arrows for disabled columns! + $el.removeClass(rmv); + $tar.removeClass(rmv + ' tablesorter-icon ' + o.icons); + } else { + t = ($t.hasClass('hasStickyHeaders')) ? $t.find(sh).find('th').eq(i).add($el) : $el; + klass = ($el.hasClass(c.cssAsc)) ? o.sortAsc : ($el.hasClass(c.cssDesc)) ? o.sortDesc : $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 " + theme + " theme", time); + } + }, + remove: function(table, c, wo){ + var $t = $(table), + theme = typeof wo.uitheme === 'object' ? 'jui' : 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.tsuitheme mouseleave.tsuitheme') // remove hover + .removeClass(o.hover + ' ' + rmv + ' ' + o.active) + .find('.tablesorter-filter-row').removeClass(o.filterRow); + $h.find('.tablesorter-icon').removeClass(o.icons); + } +}); + +// Widget: Column styles +// "columns", "columns_thead" (true) and +// "columns_tfoot" (true) options in "widgetOptions" +// ************************** +$.tablesorter.addWidget({ + id: "columns", + format: function(table){ + var $tb, $tr, $td, $t, time, last, rmv, i, k, l, + $tbl = $(table), + c = table.config, + wo = c.widgetOptions, + b = c.$tbodies, + 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 : + (wo && wo.hasOwnProperty('columns')) ? wo.columns || css : css; + last = css.length-1; + rmv = css.join(' '); + 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 = $.tablesorter.processTbody(table, b.eq(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'){ + // remove all columns class names + $td = $t.children().removeClass(rmv); + // add appropriate column class names + if (list && list[0]){ + // primary sort column class + $td.eq(list[0][0]).addClass(css[0]); + 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] ); + } + } + } + } + }); + $.tablesorter.processTbody(table, $tb, false); + } + // add classes to thead and tfoot + $tr = wo.columns_thead !== false ? 'thead tr' : ''; + if (wo.columns_tfoot !== false) { + $tr += ($tr === '' ? '' : ',') + 'tfoot tr'; + } + if ($tr.length) { + $t = $tbl.find($tr).children().removeClass(rmv); + if (list && list[0]){ + // primary sort column class + $t.filter('[data-column="' + list[0][0] + '"]').addClass(css[0]); + if (len > 1){ + for (i = 1; i < len; i++){ + // secondary, tertiary, etc sort column classes + $t.filter('[data-column="' + list[i][0] + '"]').addClass(css[i] || css[last]); + } + } + } + } + if (c.debug){ + $.tablesorter.benchmark("Applying Columns widget", time); + } + }, + remove: function(table, c, wo){ + var k, $tb, + b = c.$tbodies, + rmv = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' '); + c.$headers.removeClass(rmv); + $(table).children('tfoot').children('tr').children('th, td').removeClass(rmv); + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody + $tb.children('tr').each(function(){ + $(this).children().removeClass(rmv); + }); + $.tablesorter.processTbody(table, $tb, false); // restore tbody + } + } +}); + +/* 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_formatter : null // add custom filter elements to the filter 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 + 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. + **************************/ +$.tablesorter.addWidget({ + id: "filter", + 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, $fr, rg, s, t, dis, col, + fmt = $.tablesorter.formatFloat, + last = '', // save last filter search + ts = $.tablesorter, + c = table.config, + $ths = $(c.headerList), + wo = c.widgetOptions, + css = wo.filter_cssFilter || 'tablesorter-filter', + $t = $(table).addClass('hasFilters'), + b = $t.find('tbody'), + cols = c.parsers.length, + reg = { // regex used in filter "check" functions + "regex" : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex + "child" : new RegExp(c.cssChildRow), // child row + "type" : /undefined|number/, // check type + "exact" : /(^[\"|\'|=])|([\"|\'|=]$)/g, // exact match + "nondigit" : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser) + "operators" : /[<>=]/g // replace operators + }, + parsed, time, timer, + + // dig fer gold + checkFilters = function(filter){ + var arry = $.isArray(filter), + $inpts = $t.find('thead').eq(0).find('.tablesorter-filter-row').children(), + v = (arry) ? filter : $inpts.map(function(t){ + // make sure input arry index matches header indexes. + t = $(this).find('select.' + css + ', input.' + css); + return t.length ? t.val() || '' : ''; + }).get(), + cv = (v || []).join(''); // combined filter values + // add filter array back into inputs + if (arry) { + $inpts.each(function(i,el){ + $(el).val(filter[i] || ''); + }); + } + 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]); + if (c.showProcessing) { + // give it time for the processing icon to kick in + setTimeout(function(){ + findRows(filter, v, cv); + return false; + }, 30); + } else { + findRows(filter, v, cv); + return false; + } + }, + findRows = function(filter, v, cv){ + var $tb, $tr, $td, cr, r, l, ff, time, r1, r2; + if (c.debug) { time = new Date(); } + + for (k = 0; k < b.length; k++ ){ + if (b.eq(k).hasClass(c.cssInfoBlock)) { continue; } // ignore info blocks, issue #264 + $tb = $.tablesorter.processTbody(table, b.eq(k), true); + $tr = $tb.children('tr'); + l = $tr.length; + if (cv === '' || wo.filter_serversideFiltering){ + $tr.show().removeClass('filtered'); + } else { + // loop through the rows + for (j = 0; j < l; j++){ + // skip child rows + if (reg.child.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 + // a match anywhere in the child row, then it will make the row visible + // checked here so the option can be changed dynamically + t = (cr.length && (wo && wo.hasOwnProperty('filter_childRows') && + 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++){ + // 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.type.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){ + // default selector; no "filter-select" class + 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'){ + // selector option function + ff = wo.filter_functions[i][v[i]](x, c.cache[k].normalized[j][i], v[i], i); + } + // Look for regex + } else if (reg.regex.test(val)){ + rg = reg.regex.exec(val); + try { + ff = new RegExp(rg[1], rg[2]).test(xi); + } catch (err){ + ff = false; + } + // Look for quotes or equals to get an exact match; ignore type since xi could be numeric + /*jshint eqeqeq:false */ + } else if (val.replace(reg.exact, '') == xi){ + 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)){ + // xi may be numeric - see issue #149 + rg = isNaN(xi) ? fmt(xi.replace(reg.nondigit, ''), table) : fmt(xi, table); + s = fmt(val.replace(reg.nondigit, '').replace(reg.operators,''), table); + if (/>/.test(val)) { ff = />=/.test(val) ? rg >= s : rg > s; } + if (/= 0; + r1 = s.length - 1; + while (ff && r1) { + ff = ff && xi.search($.trim(s[r1])) >= 0; + r1--; + } + // Look for a range (using " to " or " - ") - see issue #166; thanks matzhu! + } else if (/\s+(-|to)\s+/.test(val)){ + rg = isNaN(xi) ? fmt(xi.replace(reg.nondigit, ''), table) : fmt(xi, table); + s = val.split(/(?: - | to )/); // make sure the dash is for a range and not indicating a negative number + r1 = fmt(s[0].replace(reg.nondigit, ''), table); + r2 = fmt(s[1].replace(reg.nondigit, ''), table); + if (r1 > r2) { ff = r1; r1 = r2; r2 = ff; } // swap + ff = (rg >= r1 && rg <= r2) || (r1 === '' || r2 === '') ? true : false; + // Look for wild card: ? = single, * = multiple, or | = logical OR + } else if ( /[\?|\*]/.test(val) || /\s+OR\s+/.test(v[i]) ){ + ff = new RegExp( val.replace(/\s+or\s+/gi,"|").replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(xi); + // Look for match, and add child row data for matching + } else { + x = (xi + t).indexOf(val); + ff = ( (!wo.filter_startsWith && x >= 0) || (wo.filter_startsWith && x === 0) ); + } + r = (ff) ? (r ? true : false) : false; + } + } + $tr[j].style.display = (r ? '' : 'none'); + $tr.eq(j)[r ? 'removeClass' : 'addClass']('filtered'); + if (cr.length) { cr[r ? 'show' : 'hide'](); } + } + } + $.tablesorter.processTbody(table, $tb, false); + } + + last = cv; // save last search + $t.data('lastSearch', last); + if (c.debug){ + ts.benchmark("Completed filter widget search", time); + } + $t.trigger('applyWidgets'); // make sure zebra widget is applied + $t.trigger('filterEnd'); + }, + buildSelect = function(i, updating){ + var o, arry = []; + i = parseInt(i, 10); + o = ''; + for (k = 0; k < b.length; k++ ){ + l = c.cache[k].row.length; + // loop through the rows + for (j = 0; j < l; j++){ + // get non-normalized cell content + 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( $.trim(c.supportsTextContent ? t.textContent : $(t).text()) ); + } + } + } + } + + // 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(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++){ + // replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346 + o += arry[k] !== '' ? '' : ''; + } + $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++){ + t = $ths.filter('[data-column="' + i + '"]:last'); + // look for the filter-select class; build/update it if found + if ((t.hasClass('filter-select') || wo.filter_functions && wo.filter_functions[i] === true) && !t.hasClass('filter-false')){ + 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){ + time = new Date(); + } + wo.filter_ignoreCase = wo.filter_ignoreCase !== false; // set default filter_ignoreCase to true + wo.filter_useParsedData = wo.filter_useParsedData === true; // default is false + // don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156 + if (wo.filter_columnFilters !== false && $ths.filter('.filter-false').length !== $ths.length){ + // build filter row + t = ''; + for (i = 0; i < cols; i++){ + t += ''; + } + $fr = $t.find('thead').eq(0).append(t += '').find('td'); + // build each filter input + for (i = 0; i < cols; i++){ + dis = false; + $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'); + // use header option - headers: { 1: { filter: false } } OR add class="filter-false" + if (ts.getData){ + // get data from jQuery data, metadata, headers option or header class name + dis = ts.getData($th[0], c.headers[i], 'filter') === 'false'; + } else { + // only class names and header options - keep this for compatibility with tablesorter v2.0.5 + dis = (c.headers[i] && c.headers[i].hasOwnProperty('filter') && c.headers[i].filter === false) || $th.hasClass('filter-false'); + } + + if (sel){ + t = $('').appendTo( $fr.eq(i) ); + } + if (t) { + t.attr('placeholder', $th.attr('data-placeholder') || ''); + } + } + if (t) { + t.addClass(css).attr('data-column', i); + if (dis) { + t.addClass('disabled')[0].disabled = true; // disabled! + } + } + } + } + $t + .bind('addRows updateCell update updateRows updateComplete appendCache filterReset search '.split(' ').join('.tsfilter '), function(e, filter){ + if (!/(search|filterReset)/.test(e.type)){ + e.stopPropagation(); + buildDefault(true); + } + if (e.type === 'filterReset') { + $t.find('.' + css).val(''); + } + // send false argument to force a new search; otherwise if the filter hasn't changed, it will return + filter = e.type === 'search' ? filter : e.type === 'updateComplete' ? $t.data('lastSearch') : false; + checkFilters(filter); + return false; + }) + .find('input.' + css).bind('keyup search', function(e, filter){ + // ignore arrow and meta keys; allow backspace + if (e.type === 'keyup' && ((e.which < 32 && e.which !== 8) || (e.which >= 37 && e.which <=40))) { return; } + // skip delay + if (typeof filter !== 'undefined' && filter !== true){ + checkFilters(filter); + } + // delay filtering + clearTimeout(timer); + timer = setTimeout(function(){ + checkFilters(false); + }, wo.filter_searchDelay || 300); + return false; + }); + + // parse columns after formatter, in case the class is added at that point + 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(); + + // reset button/link + if (wo.filter_reset && $(wo.filter_reset).length){ + $(wo.filter_reset).bind('click.tsfilter', function(){ + $t.trigger('filterReset'); + }); + } + if (wo.filter_functions){ + // i = column # (string) + 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); + } + } + } + } + // 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. + buildDefault(true); + + $t.find('select.' + css).bind('change search', function(){ + checkFilters(); + }); + + 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 processing icon + if (c.showProcessing) { + $t.bind('filterStart.tsfilter filterEnd.tsfilter', 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); + } + // filter widget initialized + $t.trigger('filterInit'); + checkFilters(); + } + }, + remove: function(table, c, wo){ + var k, $tb, + $t = $(table), + b = c.$tbodies; + $t + .removeClass('hasFilters') + // add .tsfilter namespace to all BUT search + .unbind('addRows updateCell update updateComplete appendCache search filterStart filterEnd '.split(' ').join('.tsfilter ')) + .find('.tablesorter-filter-row').remove(); + for (k = 0; k < b.length; k++ ){ + $tb = $.tablesorter.processTbody(table, b.eq(k), true); // remove tbody + $tb.children().removeClass('filtered').show(); + $.tablesorter.processTbody(table, $tb, false); // restore tbody + } + if (wo.filterreset) { $(wo.filter_reset).unbind('click.tsfilter'); } + } +}); + +// Widget: Sticky headers +// based on this awesome article: +// http://css-tricks.com/13465-persistent-headers/ +// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech +// ************************** +$.tablesorter.addWidget({ + id: "stickyHeaders", + format: function(table){ + if ($(table).hasClass('hasStickyHeaders')) { return; } + var $table = $(table).addClass('hasStickyHeaders'), + c = table.config, + wo = c.widgetOptions, + win = $(window), + header = $(table).children('thead:first'), //.add( $(table).find('caption') ), + hdrCells = header.children('tr:not(.sticky-false)').children(), + css = wo.stickyHeaders || 'tablesorter-stickyHeader', + innr = '.tablesorter-header-inner', + firstRow = hdrCells.eq(0).parent(), + tfoot = $table.find('tfoot'), + t2 = wo.$sticky = $table.clone(), // clone table, but don't remove id... the table might be styled by css + // clone the entire thead - seems to work in IE8+ + stkyHdr = t2.children('thead:first') + .addClass(css) + .css({ + width : header.outerWidth(true), + position : 'fixed', + margin : 0, + top : 0, + visibility : 'hidden', + zIndex : 1 + }), + stkyCells = stkyHdr.children('tr:not(.sticky-false)').children(), // issue #172 + laststate = '', + spacing = 0, + 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|msie)/i.test(bwsr)) { + // Firefox & Opera use the border-spacing + // update border-spacing here because of demos that switch themes + spacing = parseInt(hdrCells.eq(0).css('border-left-width'), 10) * 2; + } + 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() - spacing, + 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); + }); + }; + // clear out cloned table, except for sticky header + t2.find('thead:gt(0),tr.sticky-false,tbody,tfoot,caption').remove(); + t2.css({ height:0, width:0, padding:0, margin:0, border:0 }); + // 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.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.tsSticky', function(){ + resizeHdr(); + }); + // set sticky header cell width and link clicks to real header; andSelf() deprecated in jQuery 1.8 + hdrCells.find('*')[ $.fn.addBack ? 'addBack': 'andSelf' ]().filter(c.selectorSort).each(function(i){ + var t = $(this); + stkyCells.eq(i) + // clicking on sticky will trigger sort + .bind('mouseup', function(e){ + t.trigger(e, true); // external mouseup flag (click timer is ignored) + }) + // prevent sticky header text selection + .bind('mousedown', function(){ + this.onselectstart = function(){ return false; }; + return false; + }); + }); + // add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned. + $table.after( t2 ); + // make it sticky! + win + .bind('scroll.tsSticky', function(){ + var offset = firstRow.offset(), + sTop = win.scrollTop(), + tableHt = $table.height() - (stkyHdr.height() + (tfoot.height() || 0)), + vis = (sTop > offset.top) && (sTop < offset.top + tableHt) ? 'visible' : 'hidden'; + stkyHdr + .css({ + // adjust when scrolling horizontally - fixes issue #143 + left : header.offset().left - win.scrollLeft() - spacing, + 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(); + if (wo.$sticky) { wo.$sticky.remove(); } // remove cloned thead + $(window).unbind('scroll.tsSticky resize.tsSticky'); + } +}); + +// Add Column resizing widget +// this widget saves the column widths if +// $.tablesorter.storage function is included +// ************************** +$.tablesorter.addWidget({ + id: "resizable", + format: function(table){ + if ($(table).hasClass('hasResizable')) { return; } + $(table).addClass('hasResizable'); + var $t, t, i, j, s, $c, $cols, w, tw, + $tbl = $(table), + c = table.config, + wo = c.widgetOptions, + position = 0, + $target = null, + $next = null, + fullWidth = Math.abs($tbl.parent().width() - $tbl.width()) < 20, + stopResize = function(){ + if ($.tablesorter.storage && $target){ + s[$target.index()] = $target.width(); + s[$next.index()] = $next.width(); + $target.width( s[$target.index()] ); + $next.width( s[$next.index()] ); + if (wo.resizable !== false){ + $.tablesorter.storage(table, 'tablesorter-resizable', s); + } + } + position = 0; + $target = $next = null; + $(window).trigger('resize'); // will update stickyHeaders, just in case + }; + s = ($.tablesorter.storage && wo.resizable !== 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){ + $(c.headerList[j]).width(s[j]); // set saved resizable widths + } + } + } + $t = $tbl.children('thead:first').children('tr'); + // add resizable-false class name to headers (across rows as needed) + $t.children().each(function(){ + t = $(this); + i = t.attr('data-column'); + j = $.tablesorter.getData( t, c.headers[i], 'resizable') === "false"; + $t.children().filter('[data-column="' + i + '"]').toggleClass('resizable-false', j); + }); + // add wrapper inside each cell to allow for positioning of the resizable target block + $t.each(function(){ + $c = $(this).children(':not(.resizable-false)'); + if (!$(this).find('.tablesorter-wrapper').length) { + // Firefox needs this inner div to position the resizer correctly + $c.wrapInner('
'); + } + $c = $c.slice(0,-1); // don't include the last column of the row + $cols = $cols ? $cols.add($c) : $c; + }); + $cols + .each(function(){ + $t = $(this); + j = parseInt($t.css('padding-right'), 10) + 10; // 8 is 1/2 of the 16px wide resizer grip + t = '
'; + $t + .find('.tablesorter-wrapper') + .append(t); + }) + .bind('mousemove.tsresize', function(e){ + // ignore mousemove if no mousedown + if (position === 0 || !$target) { return; } + // resize columns + w = e.pageX - position; + tw = $target.width(); + $target.width( tw + w ); + if ($target.width() !== tw && fullWidth){ + $next.width( $next.width() - w ); + } + position = e.pageX; + }) + .bind('mouseup.tsresize', function(){ + stopResize(); + }) + .find('.tablesorter-resizer,.tablesorter-resizer-grip') + .bind('mousedown', function(e){ + // save header cell and mouse position; closest() not supported by jQuery v1.2.6 + $target = $(e.target).closest('th'); + t = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]'); + if (t.length > 1) { $target = $target.add(t); } + // if table is not as wide as it's parent, then resize the table + $next = e.shiftKey ? $target.parent().find('th:not(.resizable-false)').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0); + position = e.pageX; + }); + $tbl.find('thead:first') + .bind('mouseup.tsresize mouseleave.tsresize', function(){ + stopResize(); + }) + // right click to reset columns to default widths + .bind('contextmenu.tsresize', function(){ + $.tablesorter.resizableReset(table); + // $.isEmptyObject() needs jQuery 1.4+ + var rtn = $.isEmptyObject ? $.isEmptyObject(s) : s === {}; // allow right click if already reset + s = {}; + return rtn; + }); + }, + remove: function(table){ + $(table) + .removeClass('hasResizable') + .find('thead') + .unbind('mouseup.tsresize mouseleave.tsresize contextmenu.tsresize') + .find('tr').children() + .unbind('mousemove.tsresize mouseup.tsresize') + // don't remove "tablesorter-wrapper" as uitheme uses it too + .find('.tablesorter-resizer,.tablesorter-resizer-grip').remove(); + $.tablesorter.resizableReset(table); + } +}); +$.tablesorter.resizableReset = function(table){ + $(table.config.headerList).filter(':not(.resizable-false)').css('width',''); + if ($.tablesorter.storage) { $.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, 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){ + // redefining c & wo for backwards compatibility + c = table.config; + wo = c.widgetOptions; + var sl, time, $t = $(table), + ss = wo.saveSort !== false, // make saveSort active/inactive; default to true + sortList = { "sortList" : c.sortList }; + if (c.debug){ + time = new Date(); + } + if ($t.hasClass('hasSaveSort')){ + if (ss && table.hasInitialized && $.tablesorter.storage){ + $.tablesorter.storage( table, 'tablesorter-savesort', sortList ); + if (c.debug){ + $.tablesorter.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time); + } + } + } else { + // set table sort on initial run of the widget + $t.addClass('hasSaveSort'); + sortList = ''; + // get data + 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); + } + $t.bind('saveSortReset', function(e){ + e.stopPropagation(); + $.tablesorter.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 + $t.trigger('sorton', [sortList]); + } + } + }, + remove: function(table){ + // clear storage + if ($.tablesorter.storage) { $.tablesorter.storage( table, 'tablesorter-savesort', '' ); } + } +}); + +})(jQuery); \ No newline at end of file