diff options
author | gebele <gebele@in-silico.ch> | 2016-01-28 21:27:26 +0100 |
---|---|---|
committer | gebele <gebele@in-silico.ch> | 2016-01-28 21:27:26 +0100 |
commit | 2b6ae54466d9854bb8898e1136c125d40b2d3444 (patch) | |
tree | 8167e00632368ce4631d933ab5f92e087e1e57a3 | |
parent | b72906cf99507b1be887b7ecf98f596135099412 (diff) |
restyled prediction table
-rw-r--r-- | public/css/style.css | 9 | ||||
-rw-r--r-- | public/javascripts/jquery.tablesorter.min.js | 4 | ||||
-rw-r--r-- | public/javascripts/jquery.tablesorter.staticrow.min.js | 1 | ||||
-rw-r--r-- | public/javascripts/jquery.tablesorter.widgets.js | 5095 | ||||
-rw-r--r-- | views/layout.haml | 2 | ||||
-rw-r--r-- | views/predict.haml | 17 | ||||
-rw-r--r-- | views/prediction.haml | 269 |
7 files changed, 2821 insertions, 2576 deletions
diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..45b5c52 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,9 @@ +.tablesorter thead tr th { + vertical-align: middle; +} +.tablesorter tbody tr td { + white-space: nowrap; +} +.tablesorter tbody tr.static td { + background-color: #B0B0B0; +} diff --git a/public/javascripts/jquery.tablesorter.min.js b/public/javascripts/jquery.tablesorter.min.js index 263a5a6..2c1e468 100644 --- a/public/javascripts/jquery.tablesorter.min.js +++ b/public/javascripts/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;k<l.length;k++){for(s=[],f=t.cache[k]={normalized:[]},p=l[k]&&l[k].rows.length||0,i=0;p>i;++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;j<t.columns;j++)r.child[g][j]=u.getParsedText(t,h[j],j);else{for(q.$row=m,q.order=i,j=0;j<t.columns;++j)"undefined"!=typeof v[j]?(g=u.getElementText(t,m[0].cells[j],j),q.raw.push(g),h=u.getParsedText(t,m[0].cells[j],j,g),n.push(h),"numeric"===(v[j].type||"").toLowerCase()&&(s[j]=Math.max(Math.abs(h)||0,s[j]||0))):t.debug&&b("No parser found for cell:",m[0].cells[j],"does it have a header?");n[t.columns]=q,f.normalized.push(n)}f.colMax=s,t.totalRows+=f.normalized.length}t.showProcessing&&u.isProcessing(d),t.debug&&c("Building cache for "+p+" rows",o)}function h(a,b){var e,f,g,h,i,j,k,l=a.config,m=l.widgetOptions,n=l.$tbodies,o=[],p=l.cache;if(d(p))return l.appender?l.appender(a,o):a.isUpdating?l.$table.trigger("updateComplete",a):"";for(l.debug&&(k=new Date),j=0;j<n.length;j++)if(g=n.eq(j),g.length){for(h=u.processTbody(a,g,!0),e=p[j].normalized,f=e.length,i=0;f>i;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?'<i class="'+(o.cssIcon===u.css.icon?u.css.icon:o.cssIcon+" "+u.css.icon)+'"></i>':"",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('<div class="'+u.css.headerIn+'">'+j+"</div>")),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;n<o.columns;n++)f=o.$headers.filter('[data-column="'+n+'"]'),o.$headerIndexed[n]=f.not(".sorter-false").length?f.not(".sorter-false").filter(":last"):f.filter(":last");a(d).find(o.selectorHeaders).attr({scope:"col",role:"columnheader"}),l(d),o.debug&&(c("Built headers:",m),b(o.$headers))}function k(a,b,c){var d=a.config;d.$table.find(d.selectorRemove).remove(),f(d),g(a),s(d,b,c)}function l(a){var b,c,d,e,f=a.config,g=f.$headers.length;for(b=0;g>b;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;f<c.length;f++)c[f].sortDisabled||c.eq(f).removeClass(n).addClass(o[l[e][1]]).attr("aria-sort",q[l[e][1]]).find("."+u.css.icon).removeClass(p[2]).addClass(p[l[e][1]]);r.length&&r.filter('[data-column="'+l[e][0]+'"]').removeClass(n).addClass(o[l[e][1]])}for(m=k.$headers.length,g=k.$headers.not(".sorter-false"),e=0;m>e;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<l.columns&&l.$headerIndexed[d]){switch(g=l.$headerIndexed[d][0],e=(""+k[1]).match(/^(1|d|s|o|n)/),e=e?e[0]:""){case"1":case"d":e=1;break;case"s":e=i||0;break;case"o":j=g.order[(i||0)%(l.sortReset?3:2)],e=0===j?1:1===j?0:2;break;case"n":g.count=g.count+1,e=g.order[g.count%(l.sortReset?3:2)];break;default:e=0}i=0===h?e:i,f=[d,parseInt(e,10)||0],l.sortList.push(f),e=a.inArray(f[1],g.order),g.count=e>=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;i<e.length;i++)e[i][0]!==f&&n.sortList.push(e[i]);if(j=c.order[c.count],2>j&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;i<c.colSpan;i++)n.sortList.push([f+i,j])}else{if(n.sortAppend&&n.sortList.length>1)for(i=0;i<n.sortAppend.length;i++)k=u.isValueInArray(n.sortAppend[i][0],n.sortList),k>=0&&n.sortList.splice(k,1);if(u.isValueInArray(f,n.sortList)>=0)for(i=0;i<n.sortList.length;i++)k=n.sortList[i],j=n.$headerIndexed[k[0]][0],k[0]===f&&(k[1]=j.order[c.count],2===k[1]&&(n.sortList.splice(i,1),j.count=-1));else if(j=c.order[c.count],2>j&&(n.sortList.push([f,j]),c.colSpan>1))for(i=1;i<c.colSpan;i++)n.sortList.push([f+i,j])}if(null!==n.sortAppend)for(e=n.sortAppend,i=0;i<e.length;i++)e[i][0]!==f&&n.sortList.push(e[i]);r.trigger("sortBegin",b),setTimeout(function(){m(b),q(b),h(b),r.trigger("sortEnd",b)},1)}function q(a){var b,e,f,g,h,i,j,k,l,m,n,p=0,q=a.config,r=q.textSorter||"",s=q.sortList,t=s.length,v=q.$tbodies.length;if(!q.serverSideSorting&&!d(q.cache)){for(q.debug&&(h=new Date),e=0;v>e;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.1",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?a.trim(("basic"===f?g.attr(b.textAttribute)||c.textContent: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('<colgroup class="'+u.css.colgroup+'">'),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("<col>").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;c<b.length;c++)for(i=b[c].cells,d=0;d<i.length;d++){for(h=i[d],g=a(h),j=h.parentNode.rowIndex,k=j+"-"+g.index(),l=h.rowSpan||1,m=h.colSpan||1,"undefined"==typeof o[j]&&(o[j]=[]),e=0;e<o[j].length+1;e++)if("undefined"==typeof o[j][e]){n=e;break}for(q[k]=n,g.attr({"data-column":n}),e=j;j+l>e;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('<span class="tablesorter-savemyplace"/>'),e=a.fn.detach?c.detach():c.remove()):(e=a(b).find("span.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.priority<b.priority?-1:a.priority===b.priority?0:1}),g=p.length,f=0;g>f;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;j<n.length;j++)for(g=0,e=n.eq(j).children("tr:visible").not(c.selectorRemove),l=e.length,k=0;l>k;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<h.length;f++)g=b.processTbody(a,h.eq(f),!0),g.children().removeClass(i),b.processTbody(a,g,!1)}}})}(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";var b=a.tablesorter={version:"2.25.3",parsers:[],widgets:[],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",duplicateSpan:!0,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",cssInfoBlock:"tablesorter-infoOnly",cssNoSort:"tablesorter-noSort",cssIgnoreRow:"tablesorter-ignoreRow",cssIcon:"tablesorter-icon",cssIconNone:"",cssIconAsc:"",cssIconDesc:"",pointerClick:"click",pointerDown:"mousedown",pointerUp:"mouseup",selectorHeaders:"> thead th, > thead td",selectorSort:"th, td",selectorRemove:".remove-me",debug:!1,headerList:[],empties:{},strings:{},parsers:[]},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"},language:{sortAsc:"Ascending sort applied, ",sortDesc:"Descending sort applied, ",sortNone:"No sort applied, ",sortDisabled:"sorting is disabled",nextAsc:"activate to apply an ascending sort",nextDesc:"activate to apply a descending sort",nextNone:"activate to remove the sort"},regex:{templateContent:/\{content\}/g,templateIcon:/\{icon\}/g,templateName:/\{name\}/i,spaces:/\s+/g,nonWord:/\W/g,formElements:/(input|select|button|textarea)/i,chunk:/(^([+\-]?(?:\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,chunks:/(^\\0|\\0$)/,hex:/^0x[0-9a-f]+$/i,comma:/,/g,digitNonUS:/[\s|\.]/g,digitNegativeTest:/^\s*\([.\d]+\)/,digitNegativeReplace:/^\s*\(([.\d]+)\)/,digitTest:/^[\-+(]?\d+[)]?$/,digitReplace:/[,.'"\s]/g},string:{max:1,min:-1,emptymin:1,emptymax:-1,zero:0,none:0,"null":0,top:!0,bottom:!1},keyCodes:{enter:13},dates:{},instanceMethods:{},setup:function(c,d){if(!c||!c.tHead||0===c.tBodies.length||c.hasInitialized===!0)return void(d.debug&&(c.hasInitialized?console.warn("Stopping initialization. Tablesorter has already been initialized"):console.error("Stopping initialization! No table, thead or tbody",c)));var e="",f=a(c),g=a.metadata;c.hasInitialized=!1,c.isProcessing=!0,c.config=d,a.data(c,"tablesorter",d),d.debug&&(console[console.group?"group":"log"]("Initializing tablesorter"),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.emptyTo=d.emptyTo.toLowerCase(),d.stringTo=d.stringTo.toLowerCase(),d.last={sortList:[],clickedIndex:-1},/tablesorter\-/.test(f.attr("class"))||(e=""!==d.theme?" tablesorter-"+d.theme:""),d.table=c,d.$table=f.addClass(b.css.table+" "+d.tableClass+e).attr("role","grid"),d.$headers=f.find(d.selectorHeaders),d.namespace?d.namespace="."+d.namespace.replace(b.regex.nonWord,""):d.namespace=".tablesorter"+Math.random().toString(16).slice(2),d.$table.children().children("tr").attr("role","row"),d.$tbodies=f.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",b.buildHeaders(d),b.fixColumnWidth(c),b.addWidgetFromClass(c),b.applyWidgetOptions(c),b.setupParsers(d),d.totalRows=0,d.delayInit||b.buildCache(d),b.bindEvents(c,d.$headers,!0),b.bindMethods(d),d.supportsDataObject&&"undefined"!=typeof f.data().sortlist?d.sortList=f.data().sortlist:g&&f.metadata()&&f.metadata().sortlist&&(d.sortList=f.metadata().sortlist),b.applyWidget(c,!0),d.sortList.length>0?b.sortOn(d,d.sortList,{},!d.initWidgets):(b.setHeadersCss(d),d.initWidgets&&b.applyWidget(c,!1)),d.showProcessing&&f.unbind("sortBegin"+d.namespace+" sortEnd"+d.namespace).bind("sortBegin"+d.namespace+" sortEnd"+d.namespace,function(a){clearTimeout(d.timerProcessing),b.isProcessing(c),"sortBegin"===a.type&&(d.timerProcessing=setTimeout(function(){b.isProcessing(c,!0)},500))}),c.hasInitialized=!0,c.isProcessing=!1,d.debug&&(console.log("Overall initialization time: "+b.benchmark(a.data(c,"startoveralltimer"))),d.debug&&console.groupEnd&&console.groupEnd()),f.triggerHandler("tablesorter-initialized",c),"function"==typeof d.initialized&&d.initialized(c)},bindMethods:function(c){var d=c.$table,e=c.namespace,f="sortReset update updateRows updateAll updateHeaders addRows updateCell updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave ".split(" ").join(e+" ");d.unbind(f.replace(b.regex.spaces," ")).bind("sortReset"+e,function(a,c){a.stopPropagation(),b.sortReset(this.config,c)}).bind("updateAll"+e,function(a,c,d){a.stopPropagation(),b.updateAll(this.config,c,d)}).bind("update"+e+" updateRows"+e,function(a,c,d){a.stopPropagation(),b.update(this.config,c,d)}).bind("updateHeaders"+e,function(a,c){a.stopPropagation(),b.updateHeaders(this.config,c)}).bind("updateCell"+e,function(a,c,d,e){a.stopPropagation(),b.updateCell(this.config,c,d,e)}).bind("addRows"+e,function(a,c,d,e){a.stopPropagation(),b.addRows(this.config,c,d,e)}).bind("updateComplete"+e,function(){this.isUpdating=!1}).bind("sorton"+e,function(a,c,d,e){a.stopPropagation(),b.sortOn(this.config,c,d,e)}).bind("appendCache"+e,function(c,d,e){c.stopPropagation(),b.appendCache(this.config,e),a.isFunction(d)&&d(this)}).bind("updateCache"+e,function(a,c,d){a.stopPropagation(),b.updateCache(this.config,c,d)}).bind("applyWidgetId"+e,function(a,c){a.stopPropagation(),b.applyWidgetId(this,c)}).bind("applyWidgets"+e,function(a,c){a.stopPropagation(),b.applyWidget(this,c)}).bind("refreshWidgets"+e,function(a,c,d){a.stopPropagation(),b.refreshWidgets(this,c,d)}).bind("removeWidget"+e,function(a,c,d){a.stopPropagation(),b.removeWidget(this,c,d)}).bind("destroy"+e,function(a,c,d){a.stopPropagation(),b.destroy(this,c,d)}).bind("resetToLoadState"+e,function(d){d.stopPropagation(),b.removeWidget(this,!0,!1),c=a.extend(!0,b.defaults,c.originalSettings),this.hasInitialized=!1,b.setup(this,c)})},bindEvents:function(c,d,e){c=a(c)[0];var f,g=c.config,h=g.namespace,i=null;e!==!0&&(d.addClass(h.slice(1)+"_extra_headers"),f=a.fn.closest?d.closest("table")[0]:d.parents("table")[0],f&&"TABLE"===f.nodeName&&f!==c&&a(f).addClass(h.slice(1)+"_extra_table")),f=(g.pointerDown+" "+g.pointerUp+" "+g.pointerClick+" sort keyup ").replace(b.regex.spaces," ").split(" ").join(h+" "),d.find(g.selectorSort).add(d.filter(g.selectorSort)).unbind(f).bind(f,function(c,e){var f,h,j,k=a(c.target),l=" "+c.type+" ";if(!(1!==(c.which||c.button)&&!l.match(" "+g.pointerClick+" | sort | keyup ")||" keyup "===l&&c.which!==b.keyCodes.enter||l.match(" "+g.pointerClick+" ")&&"undefined"!=typeof c.which||l.match(" "+g.pointerUp+" ")&&i!==c.target&&e!==!0)){if(l.match(" "+g.pointerDown+" "))return i=c.target,j=k.jquery.split("."),void("1"===j[0]&&j[1]<4&&c.preventDefault());if(i=null,b.regex.formElements.test(c.target.nodeName)||k.hasClass(g.cssNoSort)||k.parents("."+g.cssNoSort).length>0||k.parents("button").length>0)return!g.cancelSelection;g.delayInit&&b.isEmptyObject(g.cache)&&b.buildCache(g),f=a.fn.closest?a(this).closest("th, td"):/TH|TD/.test(this.nodeName)?a(this):a(this).parents("th, td"),j=d.index(f),g.last.clickedIndex=0>j?f.attr("data-column"):j,h=g.$headers[g.last.clickedIndex],h&&!h.sortDisabled&&b.initSort(g,h,c)}}),g.cancelSelection&&d.attr("unselectable","on").bind("selectstart",!1).css({"user-select":"none",MozUserSelect:"none"})},buildHeaders:function(c){var d,e,f,g;for(c.headerList=[],c.headerContent=[],c.sortVars=[],c.debug&&(f=new Date),c.columns=b.computeColumnIndex(c.$table.children("thead, tfoot").children("tr")),e=c.cssIcon?'<i class="'+(c.cssIcon===b.css.icon?b.css.icon:c.cssIcon+" "+b.css.icon)+'"></i>':"",c.$headers=a(a.map(c.$table.find(c.selectorHeaders),function(d,f){var g,h,i,j,k,l=a(d);if(!l.parent().hasClass(c.cssIgnoreRow))return g=b.getColumnData(c.table,c.headers,f,!0),c.headerContent[f]=l.html(),""===c.headerTemplate||l.find("."+b.css.headerIn).length||(j=c.headerTemplate.replace(b.regex.templateContent,l.html()).replace(b.regex.templateIcon,l.find("."+b.css.icon).length?"":e),c.onRenderTemplate&&(h=c.onRenderTemplate.apply(l,[f,j]),h&&"string"==typeof h&&(j=h)),l.html('<div class="'+b.css.headerIn+'">'+j+"</div>")),c.onRenderHeader&&c.onRenderHeader.apply(l,[f,c,c.$table]),i=parseInt(l.attr("data-column"),10),d.column=i,k=b.getData(l,g,"sortInitialOrder")||c.sortInitialOrder,c.sortVars[i]={count:-1,order:b.getOrder(k)?[1,0,2]:[0,1,2],lockedOrder:!1},k=b.getData(l,g,"lockedOrder")||!1,"undefined"!=typeof k&&k!==!1&&(c.sortVars[i].lockedOrder=!0,c.sortVars[i].order=b.getOrder(k)?[1,1,1]:[0,0,0]),c.headerList[f]=d,l.addClass(b.css.header+" "+c.cssHeader).parent().addClass(b.css.headerRow+" "+c.cssHeaderRow).attr("role","row"),c.tabIndex&&l.attr("tabindex",0),d})),c.$headerIndexed=[],g=0;g<c.columns;g++)b.isEmptyObject(c.sortVars[g])&&(c.sortVars[g]={}),d=c.$headers.filter('[data-column="'+g+'"]'),c.$headerIndexed[g]=d.length?d.not(".sorter-false").length?d.not(".sorter-false").filter(":last"):d.filter(":last"):a();c.$table.find(c.selectorHeaders).attr({scope:"col",role:"columnheader"}),b.updateHeader(c),c.debug&&(console.log("Built headers:"+b.benchmark(f)),console.log(c.$headers))},addInstanceMethods:function(c){a.extend(b.instanceMethods,c)},setupParsers:function(a,c){var d,e,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 c?a.$tbodies:c,q=p.length,0===q)return a.debug?console.warn("Warning: *Empty table!* Not building a parser cache"):"";for(a.debug&&(o=new Date,console[console.group?"group":"log"]("Detecting parsers for each column")),e={extractors:[],parsers:[]};q>s;){if(d=p[s].rows,d.length)for(h=0,g=a.columns,i=0;g>i;i++){if(j=a.$headerIndexed[h],j&&j.length&&(k=b.getColumnData(r,a.headers,h),n=b.getParserById(b.getData(j,k,"extractor")),m=b.getParserById(b.getData(j,k,"sorter")),l="false"===b.getData(j,k,"parser"),a.empties[h]=(b.getData(j,k,"empty")||a.emptyTo||(a.emptyToBottom?"bottom":"top")).toLowerCase(),a.strings[h]=(b.getData(j,k,"string")||a.stringTo||"max").toLowerCase(),l&&(m=b.getParserById("no-parser")),n||(n=!1),m||(m=b.detectParserForColumn(a,d,-1,h)),a.debug&&(t["("+h+") "+j.text()]={parser:m.id,extractor:n?n.id:"none",string:a.strings[h],empty:a.empties[h]}),e.parsers[h]=m,e.extractors[h]=n,f=j[0].colSpan-1,f>0))for(h+=f,g+=f;f+1>0;)e.parsers[h-f]=m,e.extractors[h-f]=n,f--;h++}s+=e.parsers.length?q:1}a.debug&&(b.isEmptyObject(t)?console.warn(" No parsers detected!"):console[console.table?"table":"log"](t),console.log("Completed detecting parsers"+b.benchmark(o)),console.groupEnd&&console.groupEnd()),a.parsers=e.parsers,a.extractors=e.extractors},addParser:function(a){var c,d=b.parsers.length,e=!0;for(c=0;d>c;c++)b.parsers[c].id.toLowerCase()===a.id.toLowerCase()&&(e=!1);e&&b.parsers.push(a)},getParserById:function(a){if("false"==a)return!1;var c,d=b.parsers.length;for(c=0;d>c;c++)if(b.parsers[c].id.toLowerCase()===a.toString().toLowerCase())return b.parsers[c];return!1},detectParserForColumn:function(c,d,e,f){for(var g,h,i,j=b.parsers.length,k=!1,l="",m=!0;""===l&&m;)e++,i=d[e],i&&50>e?i.className.indexOf(b.cssIgnoreRow)<0&&(k=d[e].cells[f],l=b.getElementText(c,k,f),h=a(k),c.debug&&console.log("Checking if value was empty on row "+e+", column: "+f+': "'+l+'"')):m=!1;for(;--j>=0;)if(g=b.parsers[j],g&&"text"!==g.id&&g.is&&g.is(l,c.table,k,h))return g;return b.getParserById("text")},getElementText:function(c,d,e){if(!d)return"";var f,g=c.textExtraction||"",h=d.jquery?d:a(d);return"string"==typeof g?"basic"===g&&"undefined"!=typeof(f=h.attr(c.textAttribute))?a.trim(f):a.trim(d.textContent||h.text()):"function"==typeof g?a.trim(g(h[0],c.table,e)):"function"==typeof(f=b.getColumnData(c.table,g,e))?a.trim(f(h[0],c.table,e)):a.trim(h[0].textContent||h.text())},getParsedText:function(a,c,d,e){"undefined"==typeof e&&(e=b.getElementText(a,c,d));var f=""+e,g=a.parsers[d],h=a.extractors[d];return g&&(h&&"function"==typeof h.format&&(e=h.format(e,a.table,c,d)),f="no-parser"===g.id?"":g.format(""+e,a.table,c,d),a.ignoreCase&&"string"==typeof f&&(f=f.toLowerCase())),f},buildCache:function(c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B=c.table,C=c.parsers;if(c.$tbodies=c.$table.children("tbody:not(."+c.cssInfoBlock+")"),l="undefined"==typeof e?c.$tbodies:e,c.cache={},c.totalRows=0,!C)return c.debug?console.warn("Warning: *Empty table!* Not building a cache"):"";for(c.debug&&(q=new Date),c.showProcessing&&b.isProcessing(B,!0),k=0;k<l.length;k++){for(u=[],f=c.cache[k]={normalized:[]},r=l[k]&&l[k].rows.length||0,i=0;r>i;++i)if(s={child:[],raw:[]},m=a(l[k].rows[i]),n=[],m.hasClass(c.cssChildRow)&&0!==i)for(z=f.normalized.length-1,t=f.normalized[z][c.columns],t.$row=t.$row.add(m),m.prev().hasClass(c.cssChildRow)||m.prev().addClass(b.css.cssHasChild),o=m.children("th, td"),z=t.child.length,t.child[z]=[],w=0,y=c.columns,j=0;y>j;j++)p=o[j],p&&(t.child[z][j]=b.getParsedText(c,p,j),v=o[j].colSpan-1,v>0&&(w+=v,y+=v)),w++;else{for(s.$row=m,s.order=i,w=0,y=c.columns,j=0;y>j;++j){if(p=m[0].cells[j],p&&w<c.columns&&(x="undefined"!=typeof C[w],!x&&c.debug&&console.warn("No parser found for row: "+i+", column: "+j+'; cell containing: "'+a(p).text()+'"; does it have a header?'),g=b.getElementText(c,p,w),s.raw[w]=g,h=b.getParsedText(c,p,w,g),n[w]=h,x&&"numeric"===(C[w].type||"").toLowerCase()&&(u[w]=Math.max(Math.abs(h)||0,u[w]||0)),v=p.colSpan-1,v>0)){for(A=0;v>=A;)s.raw[w+A]=c.duplicateSpan||0===A?g:"",n[w+A]=c.duplicateSpan||0===A?g:"",A++;w+=v,y+=v}w++}n[c.columns]=s,f.normalized.push(n)}f.colMax=u,c.totalRows+=f.normalized.length}if(c.showProcessing&&b.isProcessing(B),c.debug){for(z=Math.min(5,c.cache[0].normalized.length),console[console.group?"group":"log"]("Building cache for "+c.totalRows+" rows (showing "+z+" rows in log)"+b.benchmark(q)),g={},j=0;j<c.columns;j++)for(w=0;z>w;w++)g["row: "+w]||(g["row: "+w]={}),g["row: "+w][c.$headerIndexed[j].text()]=c.cache[0].normalized[w][j];console[console.table?"table":"log"](g),console.groupEnd&&console.groupEnd()}a.isFunction(d)&&d(B)},getColumnText:function(c,d,e,f){c=a(c)[0];var g,h,i,j,k,l,m,n,o,p,q="function"==typeof e,r="all"===d,s={raw:[],parsed:[],$cell:[]},t=c.config;if(!b.isEmptyObject(t)){for(k=t.$tbodies.length,g=0;k>g;g++)for(i=t.cache[g].normalized,l=i.length,h=0;l>h;h++)j=i[h],(!f||j[t.columns].$row.is(f))&&(p=!0,n=r?j.slice(0,t.columns):j[d],j=j[t.columns],m=r?j.raw:j.raw[d],o=r?j.$row.children():j.$row.children().eq(d),q&&(p=e({tbodyIndex:g,rowIndex:h,parsed:n,raw:m,$row:j.$row,$cell:o})),p!==!1&&(s.parsed.push(n),s.raw.push(m),s.$cell.push(o)));return s}t.debug&&console.warn("No cache found - aborting getColumnText function!")},setHeadersCss:function(c){var d,e,f,g=c.sortList,h=g.length,i=b.css.sortNone+" "+c.cssNone,j=[b.css.sortAsc+" "+c.cssAsc,b.css.sortDesc+" "+c.cssDesc],k=[c.cssIconAsc,c.cssIconDesc,c.cssIconNone],l=["ascending","descending"],m=c.$table.find("tfoot tr").children("td, th").add(a(c.namespace+"_extra_headers")).removeClass(j.join(" "));for(c.$headers.removeClass(j.join(" ")).addClass(i).attr("aria-sort","none").find("."+b.css.icon).removeClass(k.join(" ")).addClass(k[2]),e=0;h>e;e++)if(2!==g[e][1]&&(d=c.$headers.filter(function(a){for(var d=!0,e=c.$headers.eq(a),f=parseInt(e.attr("data-column"),10),g=f+c.$headers[a].colSpan;g>f;f++)d=d?d||b.isValueInArray(f,c.sortList)>-1:!1;return d}),d=d.not(".sorter-false").filter('[data-column="'+g[e][0]+'"]'+(1===h?":last":"")),d.length)){for(f=0;f<d.length;f++)d[f].sortDisabled||d.eq(f).removeClass(i).addClass(j[g[e][1]]).attr("aria-sort",l[g[e][1]]).find("."+b.css.icon).removeClass(k[2]).addClass(k[g[e][1]]);m.length&&m.filter('[data-column="'+g[e][0]+'"]').removeClass(i).addClass(j[g[e][1]])}for(h=c.$headers.length,e=0;h>e;e++)b.setColumnAriaLabel(c,c.$headers.eq(e))},setColumnAriaLabel:function(c,d,e){if(d.length){var f=parseInt(d.attr("data-column"),10),g=d.hasClass(b.css.sortAsc)?"sortAsc":d.hasClass(b.css.sortDesc)?"sortDesc":"sortNone",h=a.trim(d.text())+": "+b.language[g];d.hasClass("sorter-false")||e===!1?h+=b.language.sortDisabled:(e=c.sortVars[f].order[(c.sortVars[f].count+1)%(c.sortReset?3:2)],h+=b.language[0===e?"nextAsc":1===e?"nextDesc":"nextNone"]),d.attr("aria-label",h)}},updateHeader:function(a){var c,d,e,f,g=a.table,h=a.$headers.length;for(c=0;h>c;c++)e=a.$headers.eq(c),f=b.getColumnData(g,a.headers,c,!0),d="false"===b.getData(e,f,"sorter")||"false"===b.getData(e,f,"parser"),b.setColumnSort(a,e,d)},setColumnSort:function(a,b,c){var d=a.table.id;b[0].sortDisabled=c,b[c?"addClass":"removeClass"]("sorter-false").attr("aria-disabled",""+c),a.tabIndex&&(c?b.removeAttr("tabindex"):b.attr("tabindex","0")),d&&(c?b.removeAttr("aria-controls"):b.attr("aria-controls",d))},updateHeaderSortCount:function(c,d){var e,f,g,h,i,j,k,l,m=d||c.sortList,n=m.length;for(c.sortList=[],h=0;n>h;h++)if(k=m[h],e=parseInt(k[0],10),e<c.columns){switch(c.sortVars[e].order||(l=c.sortVars[e].order=b.getOrder(c.sortInitialOrder)?[1,0,2]:[0,1,2],c.sortVars[e].count=0),l=c.sortVars[e].order,f=(""+k[1]).match(/^(1|d|s|o|n)/),f=f?f[0]:""){case"1":case"d":f=1;break;case"s":f=i||0;break;case"o":j=l[(i||0)%(c.sortReset?3:2)],f=0===j?1:1===j?0:2;break;case"n":f=l[++c.sortVars[e].count%(c.sortReset?3:2)];break;default:f=0}i=0===h?f:i,g=[e,parseInt(f,10)||0],c.sortList.push(g),f=a.inArray(g[1],l),c.sortVars[e].count=f>=0?f:g[1]%(c.sortReset?3:2)}},updateAll:function(a,c,d){var e=a.table;e.isUpdating=!0,b.refreshWidgets(e,!0,!0),b.buildHeaders(a),b.bindEvents(e,a.$headers,!0),b.bindMethods(a),b.commonUpdate(a,c,d)},update:function(a,c,d){var e=a.table;e.isUpdating=!0,b.updateHeader(a),b.commonUpdate(a,c,d)},updateHeaders:function(a,c){a.table.isUpdating=!0,b.buildHeaders(a),b.bindEvents(a.table,a.$headers,!0),b.resortComplete(a,c)},updateCell:function(c,d,e,f){if(b.isEmptyObject(c.cache))return b.updateHeader(c),void b.commonUpdate(c,e,f);c.table.isUpdating=!0,c.$table.find(c.selectorRemove).remove();var g,h,i,j,k,l,m=c.$tbodies,n=a(d),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");if(d=n[0],m.length&&o>=0){if(i=m.eq(o).find("tr").index(q),k=p.normalized[i],l=q[0].cells.length,l!==c.columns)for(j=0,g=!1,h=0;l>h;h++)g||q[0].cells[h]===d?g=!0:j+=q[0].cells[h].colSpan;else j=n.index();g=b.getElementText(c,d,j),k[c.columns].raw[j]=g,g=b.getParsedText(c,d,j,g),k[j]=g,k[c.columns].$row=q,"numeric"===(c.parsers[j].type||"").toLowerCase()&&(p.colMax[j]=Math.max(Math.abs(g)||0,p.colMax[j]||0)),g="undefined"!==e?e:c.resort,g!==!1?b.checkResort(c,g,f):b.resortComplete(c,f)}else c.debug&&console.error("updateCell aborted, tbody missing or not within the indicated table"),c.table.isUpdating=!1},addRows:function(c,d,e,f){var g,h,i,j,k,l,m,n,o,p,q,r,s="string"==typeof d&&1===c.$tbodies.length&&/<tr/.test(d||""),t=c.table;if(s)d=a(d),c.$tbodies.append(d);else if(!(d&&d instanceof jQuery&&(a.fn.closest?d.closest("table")[0]:d.parents("table")[0])===c.table))return c.debug&&console.error("addRows method requires (1) a jQuery selector reference to rows that have already been added to the table, or (2) row HTML string to be added to a table with only one tbody"),!1;if(t.isUpdating=!0,b.isEmptyObject(c.cache))b.updateHeader(c),b.commonUpdate(c,e,f);else{for(k=d.filter("tr").attr("role","row").length,i=c.$tbodies.index(d.parents("tbody").filter(":first")),c.parsers&&c.parsers.length||b.setupParsers(c),j=0;k>j;j++){for(n=0,m=d[j].cells.length,p=[],o={child:[],raw:[],$row:d.eq(j),order:c.cache[i].normalized.length},l=0;m>l;l++)q=d[j].cells[l],g=b.getElementText(c,q,n),o.raw[n]=g,h=b.getParsedText(c,q,n,g),p[n]=h,"numeric"===(c.parsers[n].type||"").toLowerCase()&&(c.cache[i].colMax[n]=Math.max(Math.abs(h)||0,c.cache[i].colMax[n]||0)),r=q.colSpan-1,r>0&&(n+=r),n++;p[c.columns]=o,c.cache[i].normalized.push(p)}b.checkResort(c,e,f)}},updateCache:function(a,c,d){a.parsers&&a.parsers.length||b.setupParsers(a,d),b.buildCache(a,c,d)},appendCache:function(a,c){var d,e,f,g,h,i,j,k=a.table,l=a.widgetOptions,m=a.$tbodies,n=[],o=a.cache;if(b.isEmptyObject(o))return a.appender?a.appender(k,n):k.isUpdating?a.$table.triggerHandler("updateComplete",k):"";for(a.debug&&(j=new Date),i=0;i<m.length;i++)if(f=m.eq(i),f.length){for(g=b.processTbody(k,f,!0),d=o[i].normalized,e=d.length,h=0;e>h;h++)n.push(d[h][a.columns].$row),a.appender&&(!a.pager||a.pager.removeRows&&l.pager_removeRows||a.pager.ajax)||g.append(d[h][a.columns].$row);b.processTbody(k,g,!1)}a.appender&&a.appender(k,n),a.debug&&console.log("Rebuilt table"+b.benchmark(j)),c||a.appender||b.applyWidget(k),k.isUpdating&&a.$table.triggerHandler("updateComplete",k)},commonUpdate:function(a,c,d){a.$table.find(a.selectorRemove).remove(),b.setupParsers(a),b.buildCache(a),b.checkResort(a,c,d)},initSort:function(c,d,e){if(c.table.isUpdating)return setTimeout(function(){b.initSort(c,d,e)},50);var f,g,h,i,j,k,l,m=!e[c.sortMultiSortKey],n=c.table,o=c.$headers.length,p=parseInt(a(d).attr("data-column"),10),q=c.sortVars[p].order;if(c.$table.triggerHandler("sortStart",n),c.sortVars[p].count=e[c.sortResetKey]?2:(c.sortVars[p].count+1)%(c.sortReset?3:2),c.sortRestart)for(h=0;o>h;h++)l=c.$headers.eq(h),k=parseInt(l.attr("data-column"),10),p!==k&&(m||l.hasClass(b.css.sortNone))&&(c.sortVars[k].count=-1);if(m){if(c.sortList=[],c.last.sortList=[],null!==c.sortForce)for(f=c.sortForce,g=0;g<f.length;g++)f[g][0]!==p&&c.sortList.push(f[g]);if(i=q[c.sortVars[p].count],2>i&&(c.sortList.push([p,i]),d.colSpan>1))for(g=1;g<d.colSpan;g++)c.sortList.push([p+g,i]),c.sortVars[p+g].count=a.inArray(i,q)}else if(c.sortList=a.extend([],c.last.sortList),b.isValueInArray(p,c.sortList)>=0)for(g=0;g<c.sortList.length;g++)k=c.sortList[g],k[0]===p&&(k[1]=q[c.sortVars[p].count],2===k[1]&&(c.sortList.splice(g,1),c.sortVars[p].count=-1));else if(i=q[c.sortVars[p].count],2>i&&(c.sortList.push([p,i]),d.colSpan>1))for(g=1;g<d.colSpan;g++)c.sortList.push([p+g,i]),c.sortVars[p+g].count=a.inArray(i,q);if(c.last.sortList=a.extend([],c.sortList),c.sortList.length&&c.sortAppend&&(f=a.isArray(c.sortAppend)?c.sortAppend:c.sortAppend[c.sortList[0][0]],!b.isEmptyObject(f)))for(g=0;g<f.length;g++)if(f[g][0]!==p&&b.isValueInArray(f[g][0],c.sortList)<0){if(i=f[g][1],j=(""+i).match(/^(a|d|s|o|n)/))switch(k=c.sortList[0][1],j[0]){case"d":i=1;break;case"s":i=k;break;case"o":i=0===k?1:0;break;case"n":i=(k+1)%(c.sortReset?3:2);break;default:i=0}c.sortList.push([f[g][0],i])}c.$table.triggerHandler("sortBegin",n),setTimeout(function(){b.setHeadersCss(c),b.multisort(c),b.appendCache(c),c.$table.triggerHandler("sortBeforeEnd",n),c.$table.triggerHandler("sortEnd",n)},1)},multisort:function(a){var c,d,e,f,g=a.table,h=0,i=a.textSorter||"",j=a.sortList,k=j.length,l=a.$tbodies.length;if(!a.serverSideSorting&&!b.isEmptyObject(a.cache)){for(a.debug&&(d=new Date),c=0;l>c;c++)e=a.cache[c].colMax,f=a.cache[c].normalized,f.sort(function(c,d){var f,l,m,n,o,p,q;for(f=0;k>f;f++){if(m=j[f][0],n=j[f][1],h=0===n,a.sortStable&&c[m]===d[m]&&1===k)return c[a.columns].order-d[a.columns].order;if(l=/n/i.test(b.getSortType(a.parsers,m)),l&&a.strings[m]?(l="boolean"==typeof b.string[a.strings[m]]?(h?1:-1)*(b.string[a.strings[m]]?-1:1):a.strings[m]?b.string[a.strings[m]]||0:0,o=a.numberSorter?a.numberSorter(c[m],d[m],h,e[m],g):b["sortNumeric"+(h?"Asc":"Desc")](c[m],d[m],l,e[m],m,a)):(p=h?c:d,q=h?d:c,o="function"==typeof i?i(p[m],q[m],h,m,g):"object"==typeof i&&i.hasOwnProperty(m)?i[m](p[m],q[m],h,m,g):b["sortNatural"+(h?"Asc":"Desc")](c[m],d[m],m,a)),o)return o}return c[a.columns].order-d[a.columns].order});a.debug&&console.log("Applying sort "+j.toString()+b.benchmark(d))}},resortComplete:function(b,c){b.table.isUpdating&&b.$table.triggerHandler("updateComplete",b.table),a.isFunction(c)&&c(b.table)},checkResort:function(c,d,e){var f=a.isArray(d)?d:c.sortList,g="undefined"==typeof d?c.resort:d;g===!1||c.serverSideSorting||c.table.isProcessing?(b.resortComplete(c,e),b.applyWidget(c.table,!1)):f.length?b.sortOn(c,f,function(){b.resortComplete(c,e)},!0):b.sortReset(c,function(){b.resortComplete(c,e),b.applyWidget(c.table,!1)})},sortOn:function(c,d,e,f){var g=c.table;c.$table.triggerHandler("sortStart",g),b.updateHeaderSortCount(c,d),b.setHeadersCss(c),c.delayInit&&b.isEmptyObject(c.cache)&&b.buildCache(c),c.$table.triggerHandler("sortBegin",g),b.multisort(c),b.appendCache(c,f),c.$table.triggerHandler("sortBeforeEnd",g),c.$table.triggerHandler("sortEnd",g),b.applyWidget(g),a.isFunction(e)&&e(g)},sortReset:function(c,d){c.sortList=[],b.setHeadersCss(c),b.multisort(c),b.appendCache(c),a.isFunction(d)&&d(c.table)},getSortType:function(a,b){return a&&a[b]?a[b].type||"":""},getOrder:function(a){return/^d/i.test(a)||1===a},sortNatural:function(a,c){if(a===c)return 0;var d,e,f,g,h,i,j=b.regex;if(j.hex.test(c)){if(d=parseInt(a.match(j.hex),16),e=parseInt(c.match(j.hex),16),e>d)return-1;if(d>e)return 1}for(d=a.replace(j.chunk,"\\0$1\\0").replace(j.chunks,"").split("\\0"),e=c.replace(j.chunk,"\\0$1\\0").replace(j.chunks,"").split("\\0"),i=Math.max(d.length,e.length),h=0;i>h;h++){if(f=isNaN(d[h])?d[h]||0:parseFloat(d[h])||0,g=isNaN(e[h])?e[h]||0:parseFloat(e[h])||0,isNaN(f)!==isNaN(g))return isNaN(f)?1:-1;if(typeof f!=typeof g&&(f+="",g+=""),g>f)return-1;if(f>g)return 1}return 0},sortNaturalAsc:function(a,c,d,e){if(a===c)return 0;var f=b.string[e.empties[d]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:-f||-1:""===c&&0!==f?"boolean"==typeof f?f?1:-1:f||1:b.sortNatural(a,c)},sortNaturalDesc:function(a,c,d,e){if(a===c)return 0;var f=b.string[e.empties[d]||e.emptyTo];return""===a&&0!==f?"boolean"==typeof f?f?-1:1:f||1:""===c&&0!==f?"boolean"==typeof f?f?1:-1:-f||-1:b.sortNatural(c,a)},sortText:function(a,b){return a>b?1:b>a?-1:0},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},sortNumericAsc:function(a,c,d,e,f,g){if(a===c)return 0;var h=b.string[g.empties[f]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:-h||-1:""===c&&0!==h?"boolean"==typeof h?h?1:-1:h||1:(isNaN(a)&&(a=b.getTextValue(a,d,e)),isNaN(c)&&(c=b.getTextValue(c,d,e)),a-c)},sortNumericDesc:function(a,c,d,e,f,g){if(a===c)return 0;var h=b.string[g.empties[f]||g.emptyTo];return""===a&&0!==h?"boolean"==typeof h?h?-1:1:h||1:""===c&&0!==h?"boolean"==typeof h?h?1:-1:-h||-1:(isNaN(a)&&(a=b.getTextValue(a,d,e)),isNaN(c)&&(c=b.getTextValue(c,d,e)),c-a)},sortNumeric:function(a,b){return a-b},addWidget:function(a){b.widgets.push(a)},hasWidget:function(b,c){return b=a(b),b.length&&b[0].config&&b[0].config.widgetInit[c]||!1},getWidgetById:function(a){var c,d,e=b.widgets.length;for(c=0;e>c;c++)if(d=b.widgets[c],d&&d.id&&d.id.toLowerCase()===a.toLowerCase())return d},applyWidgetOptions:function(c){var d,e,f=c.config,g=f.widgets.length;if(g)for(d=0;g>d;d++)e=b.getWidgetById(f.widgets[d]),e&&e.options&&(f.widgetOptions=a.extend(!0,{},e.options,f.widgetOptions))},addWidgetFromClass:function(a){var c,d,e=a.config,f="^"+e.widgetClass.replace(b.regex.templateName,"(\\S+)+")+"$",g=new RegExp(f,"g"),h=(a.className||"").split(b.regex.spaces);if(h.length)for(c=h.length,d=0;c>d;d++)h[d].match(g)&&e.widgets.push(h[d].replace(g,"$1"))},applyWidgetId:function(c,d,e){var f,g,h,i=c.config,j=i.widgetOptions,k=b.getWidgetById(d);k&&(h=k.id,f=!1,a.inArray(h,i.widgets)<0&&i.widgets.push(h),i.debug&&(g=new Date),(e||!i.widgetInit[h])&&(i.widgetInit[h]=!0,c.hasInitialized&&b.applyWidgetOptions(c),"function"==typeof k.init&&(f=!0,i.debug&&console[console.group?"group":"log"]("Initializing "+h+" widget"),k.init(c,k,i,j))),e||"function"!=typeof k.format||(f=!0,i.debug&&console[console.group?"group":"log"]("Updating "+h+" widget"),k.format(c,i,j,!1)),i.debug&&f&&(console.log("Completed "+(e?"initializing ":"applying ")+h+" widget"+b.benchmark(g)),console.groupEnd&&console.groupEnd()))},applyWidget:function(c,d,e){c=a(c)[0];var f,g,h,i,j,k=c.config,l=[];if(d===!1||!c.hasInitialized||!c.isApplyingWidgets&&!c.isUpdating){if(k.debug&&(j=new Date),b.addWidgetFromClass(c),clearTimeout(k.timerReady),k.widgets.length){for(c.isApplyingWidgets=!0,k.widgets=a.grep(k.widgets,function(b,c){return a.inArray(b,k.widgets)===c}),h=k.widgets||[],g=h.length,f=0;g>f;f++)i=b.getWidgetById(h[f]),i&&i.id&&(i.priority||(i.priority=10),l[f]=i);for(l.sort(function(a,b){return a.priority<b.priority?-1:a.priority===b.priority?0:1}),g=l.length,k.debug&&console[console.group?"group":"log"]("Start "+(d?"initializing":"applying")+" widgets"),f=0;g>f;f++)i=l[f],i&&i.id&&b.applyWidgetId(c,i.id,d);k.debug&&console.groupEnd&&console.groupEnd(),d||"function"!=typeof e||e(c)}k.timerReady=setTimeout(function(){c.isApplyingWidgets=!1,a.data(c,"lastWidgetApplication",new Date),k.$table.triggerHandler("tablesorter-ready")},10),k.debug&&(i=k.widgets.length,console.log("Completed "+(d===!0?"initializing ":"applying ")+i+" widget"+(1!==i?"s":"")+b.benchmark(j)))}},removeWidget:function(c,d,e){c=a(c)[0];var f,g,h,i,j=c.config;if(d===!0)for(d=[],i=b.widgets.length,h=0;i>h;h++)g=b.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=b.getWidgetById(d[f]),h=a.inArray(d[f],j.widgets),g&&g.remove&&(j.debug&&console.log((e?"Refreshing":"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)},refreshWidgets:function(c,d,e){c=a(c)[0];var f,g,h=c.config,i=h.widgets,j=b.widgets,k=j.length,l=[],m=function(b){a(b).triggerHandler("refreshComplete")};for(f=0;k>f;f++)g=j[f],g&&g.id&&(d||a.inArray(g.id,i)<0)&&l.push(g.id);b.removeWidget(c,l.join(","),!0),e!==!0?(b.applyWidget(c,d||!1,m),d&&b.applyWidget(c,!1,m)):m(c)},benchmark:function(a){return" ( "+((new Date).getTime()-a.getTime())+"ms )"},log:function(){console.log(arguments)},isEmptyObject:function(a){for(var b in a)return!1;return!0},isValueInArray:function(a,b){var c,d=b&&b.length||0;for(c=0;d>c;c++)if(b[c][0]===a)return c;return-1},formatFloat:function(c,d){if("string"!=typeof c||""===c)return c;var e,f=d&&d.config?d.config.usNumberFormat!==!1:"undefined"!=typeof d?d:!0;return c=f?c.replace(b.regex.comma,""):c.replace(b.regex.digitNonUS,"").replace(b.regex.comma,"."),b.regex.digitNegativeTest.test(c)&&(c=c.replace(b.regex.digitNegativeReplace,"-$1")),e=parseFloat(c),isNaN(e)?a.trim(c):e},isDigit:function(a){return isNaN(a)?b.regex.digitTest.test(a.toString().replace(b.regex.digitReplace,"")):""!==a},computeColumnIndex:function(b,c){var d,e,f,g,h,i,j,k,l,m,n=c&&c.columns||0,o=[],p=new Array(n);for(d=0;d<b.length;d++)for(i=b[d].cells,e=0;e<i.length;e++){for(h=i[e],j=h.parentNode.rowIndex,k=h.rowSpan||1,l=h.colSpan||1,"undefined"==typeof o[j]&&(o[j]=[]),f=0;f<o[j].length+1;f++)if("undefined"==typeof o[j][f]){m=f;break}for(n&&h.cellIndex===m||(h.setAttribute?h.setAttribute("data-column",m):a(h).attr("data-column",m)),f=j;j+k>f;f++)for("undefined"==typeof o[f]&&(o[f]=[]), +p=o[f],g=m;m+l>g;g++)p[g]="x"}return p.length},fixColumnWidth:function(c){c=a(c)[0];var d,e,f,g,h,i=c.config,j=i.$table.children("colgroup");if(j.length&&j.hasClass(b.css.colgroup)&&j.remove(),i.widthFixed&&0===i.$table.children("colgroup").length){for(j=a('<colgroup class="'+b.css.colgroup+'">'),d=i.$table.width(),f=i.$tbodies.find("tr:first").children(":visible"),g=f.length,h=0;g>h;h++)e=parseInt(f.eq(h).width()/d*1e3,10)/10+"%",j.append(a("<col>").css("width",e));i.$table.prepend(j)}},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)):""},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]}},isProcessing:function(c,d,e){c=a(c);var f=c[0].config,g=e||c.find("."+b.css.header);d?("undefined"!=typeof e&&f.sortList.length>0&&(g=g.filter(function(){return this.sortDisabled?!1:b.isValueInArray(parseFloat(a(this).attr("data-column")),f.sortList)>=0})),c.add(g).addClass(b.css.processing+" "+f.cssProcessing)):c.add(g).removeClass(b.css.processing+" "+f.cssProcessing)},processTbody:function(b,c,d){if(b=a(b)[0],d)return b.isProcessing=!0,c.before('<colgroup class="tablesorter-savemyplace"/>'),a.fn.detach?c.detach():c.remove();var e=a(b).find("colgroup.tablesorter-savemyplace");c.insertAfter(e),e.remove(),b.isProcessing=!1},clearTableBody:function(b){a(b)[0].config.$tbodies.children().detach()},characterEquivalents:{a:"áàâãäąå",A:"ÁÀÂÃÄĄÅ",c:"çćč",C:"ÇĆČ",e:"éèêëěę",E:"ÉÈÊËĚĘ",i:"íìİîïı",I:"ÍÌİÎÏ",o:"óòôõöō",O:"ÓÒÔÕÖŌ",ss:"ß",SS:"ẞ",u:"úùûüů",U:"ÚÙÛÜŮ"},replaceAccents:function(a){var c,d="[",e=b.characterEquivalents;if(!b.characterRegex){b.characterRegexArray={};for(c in e)"string"==typeof c&&(d+=e[c],b.characterRegexArray[c]=new RegExp("["+e[c]+"]","g"));b.characterRegex=new RegExp(d+"]")}if(b.characterRegex.test(a))for(c in e)"string"==typeof c&&(a=a.replace(b.characterRegexArray[c],c));return a},restoreHeaders:function(c){var d,e,f=a(c)[0].config,g=f.$table.find(f.selectorHeaders),h=g.length;for(d=0;h>d;d++)e=g.eq(d),e.find("."+b.css.headerIn).length&&e.html(f.headerContent[d])},destroy:function(c,d,e){if(c=a(c)[0],c.hasInitialized){b.removeWidget(c,!0,!1);var f,g=a(c),h=c.config,i=h.debug,j=g.find("thead:first"),k=j.find("tr."+b.css.headerRow).removeClass(b.css.headerRow+" "+h.cssHeaderRow),l=g.find("tfoot:first > tr").children("th, td");d===!1&&a.inArray("uitheme",h.widgets)>=0&&(g.triggerHandler("applyWidgetId",["uitheme"]),g.triggerHandler("applyWidgetId",["zebra"])),j.find("tr").not(k).remove(),f="sortReset update updateRows updateAll updateHeaders updateCell addRows updateComplete sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets removeWidget destroy mouseup mouseleave "+"keypress sortBegin sortEnd resetToLoadState ".split(" ").join(h.namespace+" "),g.removeData("tablesorter").unbind(f.replace(b.regex.spaces," ")),h.$headers.add(l).removeClass([b.css.header,h.cssHeader,h.cssAsc,h.cssDesc,b.css.sortAsc,b.css.sortDesc,b.css.sortNone].join(" ")).removeAttr("data-column").removeAttr("aria-label").attr("aria-disabled","true"),k.find(h.selectorSort).unbind("mousedown mouseup keypress ".split(" ").join(h.namespace+" ").replace(b.regex.spaces," ")),b.restoreHeaders(c),g.toggleClass(b.css.table+" "+h.tableClass+" tablesorter-"+h.theme,d===!1),c.hasInitialized=!1,delete c.config.cache,"function"==typeof e&&e(c),i&&console.log("tablesorter has been removed")}}};a.fn.tablesorter=function(c){return this.each(function(){var d=this,e=a.extend(!0,{},b.defaults,c,b.instanceMethods);e.originalSettings=c,!d.hasInitialized&&b.buildTable&&"TABLE"!==this.nodeName?b.buildTable(d,e):b.setup(d,e)})},window.console&&window.console.log||(b.logs=[],console={},console.log=console.warn=console.error=console.table=function(){var a=arguments.length>1?arguments:arguments[0];b.logs.push({date:Date.now(),log:a})}),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.regex.nondigit=/[^\w,. \-()]/g,b.addParser({id:"digit",is:function(a){return b.isDigit(a)},format:function(c,d){var e=b.formatFloat((c||"").replace(b.regex.nondigit,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.regex.currencyReplace=/[+\-,. ]/g,b.regex.currencyTest=/^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/,b.addParser({id:"currency",is:function(a){return a=(a||"").replace(b.regex.currencyReplace,""),b.regex.currencyTest.test(a)},format:function(c,d){var e=b.formatFloat((c||"").replace(b.regex.nondigit,""),d);return c&&"number"==typeof e?e:c?a.trim(c&&d.config.ignoreCase?c.toLocaleLowerCase():c):c},type:"numeric"}),b.regex.urlProtocolTest=/^(https?|ftp|file):\/\//,b.regex.urlProtocolReplace=/(https?|ftp|file):\/\//,b.addParser({id:"url",is:function(a){return b.regex.urlProtocolTest.test(a)},format:function(c){return c?a.trim(c.replace(b.regex.urlProtocolReplace,"")):c},parsed:!0,type:"text"}),b.regex.dash=/-/g,b.regex.isoDate=/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/,b.addParser({id:"isoDate",is:function(a){return b.regex.isoDate.test(a)},format:function(a,c){var d=a?new Date(a.replace(b.regex.dash,"/")):a;return d instanceof Date&&isFinite(d)?d.getTime():a},type:"numeric"}),b.regex.percent=/%/g,b.regex.percentTest=/(\d\s*?%|%\s*?\d)/,b.addParser({id:"percent",is:function(a){return b.regex.percentTest.test(a)&&a.length<15},format:function(a,c){return a?b.formatFloat(a.replace(b.regex.percent,""),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.regex.dateReplace=/(\S)([AP]M)$/i,b.regex.usLongDateTest1=/^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i,b.regex.usLongDateTest2=/^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i,b.addParser({id:"usLongDate",is:function(a){return b.regex.usLongDateTest1.test(a)||b.regex.usLongDateTest2.test(a)},format:function(a,c){var d=a?new Date(a.replace(b.regex.dateReplace,"$1 $2")):a;return d instanceof Date&&isFinite(d)?d.getTime():a},type:"numeric"}),b.regex.shortDateTest=/(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/,b.regex.shortDateReplace=/[\-.,]/g,b.regex.shortDateXXY=/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/,b.regex.shortDateYMD=/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/,b.convertFormat=function(a,c){a=(a||"").replace(b.regex.spaces," ").replace(b.regex.shortDateReplace,"/"),"mmddyyyy"===c?a=a.replace(b.regex.shortDateXXY,"$3/$1/$2"):"ddmmyyyy"===c?a=a.replace(b.regex.shortDateXXY,"$3/$2/$1"):"yyyymmdd"===c&&(a=a.replace(b.regex.shortDateYMD,"$1/$2/$3"));var d=new Date(a);return d instanceof Date&&isFinite(d)?d.getTime():""},b.addParser({id:"shortDate",is:function(a){return a=(a||"").replace(b.regex.spaces," ").replace(b.regex.shortDateReplace,"/"),b.regex.shortDateTest.test(a)},format:function(a,c,d,e){if(a){var f=c.config,g=f.$headerIndexed[e],h=g.length&&g.data("dateFormat")||b.getData(g,b.getColumnData(c,f.headers,e),"dateFormat")||f.dateFormat;return g.length&&g.data("dateFormat",h),b.convertFormat(a,h)||a}return a},type:"numeric"}),b.regex.timeTest=/^([1-9]|1[0-2]):([0-5]\d)(\s[AP]M)$|^((?:[01]\d|[2][0-4]):[0-5]\d)$/i,b.regex.timeMatch=/([1-9]|1[0-2]):([0-5]\d)(\s[AP]M)|((?:[01]\d|[2][0-4]):[0-5]\d)/i,b.addParser({id:"time",is:function(a){return b.regex.timeTest.test(a)},format:function(a,c){var d,e=(a||"").match(b.regex.timeMatch),f=new Date(a),g=a&&(null!==e?e[0]:"00:00 AM"),h=g?new Date("2000/01/01 "+g.replace(b.regex.dateReplace,"$1 $2")):g;return h instanceof Date&&isFinite(h)?(d=f instanceof Date&&isFinite(f)?f.getTime():0,d?parseFloat(h.getTime()+"."+f.getTime()):h.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=new RegExp(c.cssChildRow,"i"),m=c.$tbodies.add(a(c.namespace+"_extra_table").children("tbody:not(."+c.cssInfoBlock+")"));for(i=0;i<m.length;i++)for(g=0,e=m.eq(i).children("tr:visible").not(c.selectorRemove),k=e.length,j=0;k>j;j++)f=e.eq(j),l.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<h.length;f++)g=b.processTbody(a,h.eq(f),!0),g.children().removeClass(i),b.processTbody(a,g,!1)}}})}(jQuery),a.tablesorter});
\ No newline at end of file diff --git a/public/javascripts/jquery.tablesorter.staticrow.min.js b/public/javascripts/jquery.tablesorter.staticrow.min.js new file mode 100644 index 0000000..e3065ef --- /dev/null +++ b/public/javascripts/jquery.tablesorter.staticrow.min.js @@ -0,0 +1 @@ +$.tablesorter.addWidget({id:"staticRow",format:function(a){if(typeof $(a).data("hasSorted")=="undefined"){$(a).data("hasSorted",true);$("tbody .static",a).each(function(){$(this).data("tableindex",$(this).index())})}else{var b=true;while(b){b=false;$("tbody .static",a).each(function(){var c=$(this).data("tableindex");if(c!=$(this).index()){b=true;var d=$(this).detach();var e=$("tbody tr",a).length;if(c>=e){d.appendTo($("tbody",a))}else if(c==0){d.prependTo($("tbody",a))}else{d.insertBefore($("tbody tr:eq("+c+")",a))}}})}}}})
\ No newline at end of file diff --git a/public/javascripts/jquery.tablesorter.widgets.js b/public/javascripts/jquery.tablesorter.widgets.js index 12469a6..1c4ace8 100644 --- a/public/javascripts/jquery.tablesorter.widgets.js +++ b/public/javascripts/jquery.tablesorter.widgets.js @@ -1,10 +1,10 @@ /*** This file is dynamically generated *** █████▄ ▄████▄ █████▄ ▄████▄ ██████ ███████▄ ▄████▄ █████▄ ██ ██████ ██ ██ -██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▀▀ ▀▀▀▀██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄▄ ██▄▄██ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▀▀ ▀▀▀██ █████▀ ▀████▀ ██ ██ ▀████▀ ██ ██ ██ ██ ▀████▀ █████▀ ██ ██ █████▀ */ -/*! tablesorter (FORK) - updated 06-12-2015 (v2.22.1)*/ +/*! tablesorter (FORK) - updated 01-21-2016 (v2.25.3)*/ /* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */ (function(factory) { if (typeof define === 'function' && define.amd) { @@ -17,451 +17,468 @@ }(function($) { /*! Widget: storage - updated 3/26/2015 (v2.21.3) */ +/*global JSON:false */ ;(function ($, window, document) { -'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 - - $.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 -*/ -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' ); + '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 + + $.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 + */ + 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) { + console.warn( storageType + ' is not supported in this browser' ); + } } } - } - // *** 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); + // *** 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 { - date = new Date(); - date.setTime(date.getTime() + (31536e+6)); // 365 days - document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/'; + return values && values[url] ? values[url][id] : ''; } - } 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 || {}; - -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 <i> 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 <i> 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 -}); - -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 || ''); - }); + '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 <i> 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 <i> 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 + }); - $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('<div class="' + ts.css.wrapper + '" style="position:relative;height:100%;width:100%"></div>'); + 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 <i> 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 || ''); + .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('<div class="' + ts.css.wrapper + '" style="position:relative;height:100%;width:100%"></div>'); + } + }); + if (c.cssIcon) { + // if c.cssIcon is '', then no <i> 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; + 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 || ''); } - $header.addClass(hdr); - $icon.addClass(icon || ''); } } + if (c.debug) { + console.log('Applying ' + theme + ' theme' + ts.benchmark(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 || {}; - -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, + '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, $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); -/*! Widget: filter - updated 5/17/2015 (v2.22.1) *//* +/*! Widget: filter - updated 1/21/2016 (v2.25.3) *//* * Requires tablesorter v2.8+ and jQuery 1.7+ * by Rob Garrison */ ;( function ( $ ) { -'use strict'; -var ts = $.tablesorter || {}, - tscss = ts.css; - -$.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 ); - } - }, - 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' ); + 'use strict'; + var tsf, tsfRegex, + ts = $.tablesorter || {}, + tscss = ts.css, + tskeyCodes = ts.keyCodes; + + $.extend( tscss, { + filterRow : 'tablesorter-filter-row', + filter : 'tablesorter-filter', + filterDisabled : 'disabled', + filterRowHide : 'hideme' + }); + + $.extend( tskeyCodes, { + backSpace : 8, + escape : 27, + space : 32, + left : 37, + down : 40 + }); + + 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_childWithSibs : true, // if true, include matching child row siblings + 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_resetOnEsc : true, // Reset filter input when the user presses escape - normalized across browsers + 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' ) ) { + tsf.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 filter namespace to all BUT search + .unbind( events.replace( ts.regex.spaces, ' ' ) ) + // 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' + c.namespace + '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 - }, + }); + + tsf = 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 '==') + operators : /[<>=]/g, // replace operators + query : '(q|query)', // replace filter queries + wild01 : /\?/g, // wild card match 0 or 1 + wild0More : /\*/g, // wild care match 0 or more + quote : /\"/g, + isNeg1 : /(>=?\s*-\d)/, + isNeg2 : /(<=?\s*\d)/ + }, // function( c, data ) { } // c = table.config // data.$row = jQuery object of the row currently being processed @@ -469,2208 +486,2430 @@ ts.filter = { // data.filters = array of filters for all columns ( some may be undefined ) // data.filter = filter for the current column // data.iFilter = same as data.filter, except lowercase ( if wo.filter_ignoreCase is true ) - // data.exact = table cell text ( or parsed data if column parser enabled ) - // data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true ) + // data.exact = table cell text ( or parsed data if column parser enabled; may be a number & not a string ) + // data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true; may be a number & not a string ) // data.cache = table cell text from cache, so it has been parsed ( & in all lower case if c.ignoreCase is true ) // 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 ) { + // look for "|", but not if it is inside of a regular expression + if ( ( tsfRegex.orTest.test( data.iFilter ) || tsfRegex.orSplit.test( data.filter ) ) && + // this test for regex has potential to slow down the overall search + !tsfRegex.regex.test( data.filter ) ) { + var indx, filterMatched, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + filter = data.filter.split( tsfRegex.orSplit ), + iFilter = data.iFilter.split( tsfRegex.orSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' ); + data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' ); + query = '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')'; + try { + // use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search, + // e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group + 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 ) || tsf.processTypes( c, data2, vars ); + if ( filterMatched ) { + return filterMatched; + } + } catch ( error ) { + return null; + } } + // 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; - } else { - filterMatched = filterMatched && result; + return null; + }, + // Look for an AND or && operator ( logical and ) + and : function( c, data, vars ) { + if ( tsfRegex.andTest.test( data.filter ) ) { + var indx, filterMatched, result, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + filter = data.filter.split( tsfRegex.andSplit ), + iFilter = data.iFilter.split( tsfRegex.andSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' ); + data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' ); + query = ( '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')' ) + // replace wild cards since /(a*)/i will match anything + .replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' ); + try { + // use try/catch just in case RegExp is invalid + 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 ) || tsf.processTypes( c, data2, vars ) ); + if ( indx === 0 ) { + filterMatched = result; + } else { + filterMatched = filterMatched && result; + } + } catch ( error ) { + return null; + } } + // may be null from processing types + return filterMatched || false; } - // 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] ); + return null; + }, + // Look for regex + regex: function( c, data ) { + if ( tsfRegex.regex.test( data.filter ) ) { + var matches, + // cache regex per column for optimal speed + regex = data.filter_regexCache[ data.index ] || tsfRegex.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; } - 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 { - 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 ( /</.test( data.iFilter ) ) { - result = /<=/.test( data.iFilter ) ? cachedValue <= query : cachedValue < query; + return matches; } - // keep showing all rows if nothing follows the operator - if ( !result && savedSearch === '' ) { - result = true; + return null; + }, + // Look for operators >, >=, < or <= + operators: function( c, data ) { + // ignore empty strings... because '' < 10 is true + if ( tsfRegex.operTest.test( data.iFilter ) && data.iExact !== '' ) { + var cachedValue, result, txt, + table = c.table, + parsed = data.parsed[ data.index ], + query = ts.formatFloat( data.iFilter.replace( tsfRegex.operators, '' ), table ), + parser = c.parsers[ data.index ] || {}, + savedSearch = query; + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || parser.type === 'numeric' ) { + txt = $.trim( '' + data.iFilter.replace( tsfRegex.operators, '' ) ); + result = tsf.parseFilter( c, txt, data, 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.regex.nondigit, '' ) : data.iExact; + cachedValue = ts.formatFloat( txt, table ); + } + if ( tsfRegex.gtTest.test( data.iFilter ) ) { + result = tsfRegex.gteTest.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; + } else if ( tsfRegex.ltTest.test( data.iFilter ) ) { + result = tsfRegex.lteTest.test( data.iFilter ) ? cachedValue <= query : cachedValue < query; + } + // keep showing all rows if nothing follows the operator + if ( !result && savedSearch === '' ) { + result = true; + } + return result; } - return result; - } - return null; - }, - // Look for a not match - notMatch: function( c, data ) { - if ( /^\!/.test( data.iFilter ) ) { - var indx, - txt = data.iFilter.replace( '!', '' ), - filter = ts.filter.parseFilter( c, txt, data.index, data.parsed[data.index] ) || ''; - if ( ts.filter.regex.exact.test( filter ) ) { - // look for exact not matches - see #628 - filter = filter.replace( ts.filter.regex.exact, '' ); - return filter === '' ? true : $.trim( filter ) !== data.iExact; - } else { - indx = data.iExact.search( $.trim( filter ) ); - return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 ); + return null; + }, + // Look for a not match + notMatch: function( c, data ) { + if ( tsfRegex.notTest.test( data.iFilter ) ) { + var indx, + txt = data.iFilter.replace( '!', '' ), + filter = tsf.parseFilter( c, txt, data ) || ''; + if ( tsfRegex.exact.test( filter ) ) { + // look for exact not matches - see #628 + filter = filter.replace( tsfRegex.exact, '' ); + return filter === '' ? true : $.trim( filter ) !== data.iExact; + } else { + indx = data.iExact.search( $.trim( filter ) ); + return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 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 ); - - 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 ); + 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 ( tsfRegex.exact.test( data.iFilter ) ) { + var txt = data.iFilter.replace( tsfRegex.exact, '' ), + filter = tsf.parseFilter( c, txt, data ) || ''; + return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; } - if ( range1 > range2 ) { - tmp = range1; range1 = range2; range2 = tmp; // swap + return null; + }, + // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! + range : function( c, data ) { + if ( tsfRegex.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( tsfRegex.toSplit ); + + tmp = query[0].replace( ts.regex.nondigit, '' ) || ''; + range1 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), table ); + tmp = query[1].replace( ts.regex.nondigit, '' ) || ''; + range2 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), 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.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 ( 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; + return null; + }, + // Look for wild card: ? = single, * = multiple, or | = logical OR + wild : function( c, data ) { + if ( tsfRegex.wildOrTest.test( data.iFilter ) ) { + var query = '' + ( tsf.parseFilter( c, data.iFilter, data ) || '' ); + // look for an exact match with the 'or' unless the 'filter-match' class is found + if ( !tsfRegex.wildTest.test( query ) && data.nestedFilters ) { + query = data.isMatch ? query : '^(' + query + ')$'; + } + // parsing the filter may not work properly when using wildcards =/ + try { + return new RegExp( + query.replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' ), + c.widgetOptions.filter_ignoreCase ? 'i' : '' + ) + .test( data.exact ); + } catch ( error ) { + return null; } } - if ( patternIndx === pattern.length ) { - return true; + return null; + }, + // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) + fuzzy: function( c, data ) { + if ( tsfRegex.fuzzyTest.test( data.iFilter ) ) { + var indx, + patternIndx = 0, + len = data.iExact.length, + txt = data.iFilter.slice( 1 ), + pattern = tsf.parseFilter( c, txt, data ) || ''; + for ( indx = 0; indx < len; indx++ ) { + if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { + patternIndx += 1; + } + } + return patternIndx === pattern.length; } - return false; + return null; } - 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' ) - }); + }, + 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; + c.$table.addClass( 'hasFilters' ); + c.lastSearch = []; - // 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 ); - } + // define timers so using clearTimeout won't cause an undefined error + wo.filter_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 = '\\{' + tsfRegex.query + '\\}'; + $.extend( tsfRegex, { + 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' ), + orTest : new RegExp( '(\\||\\s+' + ts.language.or + '\\s+)', 'i' ), + orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), + iQuery : new RegExp( val, 'i' ), + igQuery : new RegExp( val, 'ig' ), + operTest : /^[<>]=?/, + gtTest : />/, + gteTest : />=/, + ltTest : /</, + lteTest : /<=/, + notTest : /^\!/, + wildOrTest : /[\?\*\|]/, + wildTest : /\?\*/, + fuzzyTest : /^~/, + exactTest : /[=\"\|!]/ + }); - 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 ); + // 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 + tsf.buildRow( table, c, wo ); } - 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 - c.$table.trigger( 'filterReset' ); + 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(); + tsf.buildDefault( table, true ); + } + if ( event.type === 'filterReset' ) { + c.$table.find( '.' + tscss.filter ).add( wo.filter_$externalFilters ).val( '' ); + tsf.searching( table, [] ); + } else if ( event.type === 'filterEnd' ) { + tsf.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', [...] ); + tsf.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.triggerHandler( 'filterReset' ); }); + } else if ( $( wo.filter_reset ).length ) { + // reset is a jQuery selector, use event delegation + $( document ) + .undelegate( wo.filter_reset, 'click' + c.namespace + 'filter' ) + .delegate( wo.filter_reset, 'click' + c.namespace + 'filter', function() { + // trigger a reset event, so other functions ( filter_formatter ) know when to reset + c.$table.triggerHandler( '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 === '' ? - '<option value="">' + - ( $header.data( 'placeholder' ) || - $header.attr( 'data-placeholder' ) || - wo.filter_placeholder.select || - '' - ) + - '</option>' : ''; - 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 ) { + tsf.buildSelect( table, column ); + } else if ( typeof fxn === 'object' && noSelect ) { + // add custom drop down list + for ( string in fxn ) { + if ( typeof string === 'string' ) { + options += options === '' ? + '<option value="">' + + ( $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.select || + '' + ) + + '</option>' : ''; + val = string; + txt = string; + if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + val = string.split( wo.filter_selectSourceSeparator ); + txt = val[1]; + val = val[0]; + } + options += '<option ' + + ( txt === val ? '' : 'data-function-name="' + string + '" ' ) + + 'value="' + val + '">' + txt + '</option>'; } - options += '<option ' + - ( txt === val ? '' : 'data-function-name="' + string + '" ' ) + - 'value="' + val + '">' + txt + '</option>'; } - } - 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 ) ); + c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .append( options ); + txt = wo.filter_selectSource; + fxn = typeof txt === 'function' ? true : ts.getColumnData( table, txt, column ); + if ( fxn ) { + // updating so the extra options are appended + tsf.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 ); + // 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. + tsf.buildDefault( table, true ); - ts.filter.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); - if ( wo.filter_external ) { - ts.filter.bindSearch( table, wo.filter_external ); - } + tsf.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); + if ( wo.filter_external ) { + tsf.bindSearch( table, wo.filter_external ); + } - if ( wo.filter_hideFilters ) { - ts.filter.hideFilters( table, c ); - } + if ( wo.filter_hideFilters ) { + tsf.hideFilters( 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 : '' ); - }); - } + // show processing icon + if ( c.showProcessing ) { + txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( ts.regex.spaces, ' ' ) ) + .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++; + // 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( ts.regex.spaces, ' ' ) ) + .bind( txt, function() { + // redefine 'wo' as it does not update properly inside this callback + var wo = this.config.widgetOptions; + filters = tsf.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.triggerHandler( 'filterFomatterUpdate' ); + // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers + setTimeout( function() { + if ( !wo.filter_initialized ) { + tsf.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.triggerHandler( 'filterFomatterUpdate' ); + setTimeout( function() { + tsf.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 ) { + // prevent error if $cell is undefined - see #1056 + var wo = $cell && $cell.closest( 'table' )[0].config.widgetOptions; + if ( wo && !wo.filter_initialized ) { + // add updates by column since this function + // may be called numerous times before initialization + wo.filter_formatterInit[ column ] = 1; } - clearTimeout( wo.filter_initTimer ); - if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { - // filter widget initialized + }, + filterInitComplete: function( c ) { + var indx, len, + wo = c.widgetOptions, + count = 0, + completed = function() { + wo.filter_initialized = true; + c.$table.triggerHandler( 'filterInit', c ); + tsf.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); + }; + if ( $.isEmptyObject( wo.filter_formatter ) ) { 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() { + } 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(); - }, 500 ); + } 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; + }, + // encode or decode filters for storage; see #1026 + processFilters: function( filters, encode ) { + var indx, + mode = encode ? encodeURIComponent : decodeURIComponent, + len = filters.length; + for ( indx = 0; indx < len; indx++ ) { + if ( filters[ indx ] ) { + filters[ indx ] = mode( filters[ indx ] ); + } } - } - // 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] || ''; + return filters; + }, + 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 = tsf.processFilters( saved ); + } } - } - 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 = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">'; - for ( column = 0; column < columns; column++ ) { - buildFilter += '<td'; - if ( arry ) { - buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' ); - } else { - buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' ); + // 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] || ''; + } } - buildFilter += '></td>'; - } - c.$filters = $( buildFilter += '</tr>' ) - .appendTo( c.$table.children( 'thead' ).eq( 0 ) ) - .find( 'td' ); - // build each filter input - for ( column = 0; column < columns; column++ ) { - disabled = false; - // assuming last cell of a column is the main column - $header = c.$headerIndexed[ column ]; - ffxn = ts.getColumnData( table, wo.filter_functions, column ); - buildSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) || - $header.hasClass( 'filter-select' ); - // get data from jQuery data, metadata, headers option or header class name - col = ts.getColumnData( table, c.headers, column ); - disabled = ts.getData( $header[0], col, 'filter' ) === 'false' || - ts.getData( $header[0], col, 'parser' ) === 'false'; - - if ( buildSelect ) { - buildFilter = $( '<select>' ).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' ); + c.$table.data( 'lastSearch', filters ); + return filters; + }, + parseFilter: function( c, filter, data, parsed ) { + return parsed || data.parsed[ data.index ] ? + c.parsers[ data.index ].format( filter, c.table, [], data.index ) : + filter; + }, + buildRow: function( table, c, wo ) { + var $filter, col, column, $header, makeSelect, disabled, name, ffxn, tmp, + // c.columns defined in computeThIndexes() + cellFilter = wo.filter_cellFilter, + columns = c.columns, + arry = $.isArray( cellFilter ), + buildFilter = '<tr role="row" class="' + tscss.filterRow + ' ' + c.cssIgnoreRow + '">'; + for ( column = 0; column < columns; column++ ) { + if ( c.$headerIndexed[ column ].length ) { + // account for entire column set with colspan. See #1047 + tmp = c.$headerIndexed[ column ] && c.$headerIndexed[ column ][0].colSpan || 0; + if ( tmp > 1 ) { + buildFilter += '<td data-column="' + column + '-' + ( column + tmp - 1 ) + '" colspan="' + tmp + '"'; + } else { + buildFilter += '<td data-column="' + column + '"'; } - // 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 ); + if ( arry ) { + buildFilter += ( cellFilter[ column ] ? ' class="' + cellFilter[ column ] + '"' : '' ); + } else { + buildFilter += ( cellFilter !== '' ? ' class="' + cellFilter + '"' : '' ); } - } else { - buildFilter = $( '<input type="search">' ).appendTo( c.$filters.eq( column ) ); - } - if ( buildFilter ) { - tmp = $header.data( 'placeholder' ) || - $header.attr( 'data-placeholder' ) || - wo.filter_placeholder.search || ''; - buildFilter.attr( 'placeholder', tmp ); + buildFilter += '></td>'; } } - 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; + c.$filters = $( buildFilter += '</tr>' ) + .appendTo( c.$table.children( 'thead' ).eq( 0 ) ) + .children( 'td' ); + // build each filter input + for ( column = 0; column < columns; column++ ) { + disabled = false; + // assuming last cell of a column is the main column + $header = c.$headerIndexed[ column ]; + if ( $header && $header.length ) { + // $filter = c.$filters.filter( '[data-column="' + column + '"]' ); + $filter = tsf.getColumnElm( c, c.$filters, column ); + ffxn = ts.getColumnData( table, wo.filter_functions, column ); + makeSelect = ( wo.filter_functions && ffxn && typeof ffxn !== 'function' ) || + $header.hasClass( 'filter-select' ); + // get data from jQuery data, metadata, headers option or header class name + col = ts.getColumnData( table, c.headers, column ); + disabled = ts.getData( $header[0], col, 'filter' ) === 'false' || + ts.getData( $header[0], col, 'parser' ) === 'false'; + + if ( makeSelect ) { + buildFilter = $( '<select>' ).appendTo( $filter ); + } else { + ffxn = ts.getColumnData( table, wo.filter_formatter, column ); + if ( ffxn ) { + wo.filter_formatterCount++; + buildFilter = ffxn( $filter, column ); + // no element returned, so lets go find it + if ( buildFilter && buildFilter.length === 0 ) { + buildFilter = $filter.children( 'input' ); + } + // element not in DOM, so lets attach it + if ( buildFilter && ( buildFilter.parent().length === 0 || + ( buildFilter.parent().length && buildFilter.parent()[0] !== $filter[0] ) ) ) { + $filter.append( buildFilter ); + } + } else { + buildFilter = $( '<input type="search">' ).appendTo( $filter ); + } + 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 ) || ''; + // copy data-column from table cell (it will include colspan) + buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', $filter.attr( 'data-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 ); + }, + 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 keydown search change input '.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( ts.regex.spaces, ' ' ) ) + .bind( 'keydown' + namespace, function( event ) { + if ( event.which === tskeyCodes.escape && !wo.filter_resetOnEsc ) { + // prevent keypress event + return false; + } + }) + .bind( 'keyup' + namespace, function( event ) { + var column = parseInt( $( this ).attr( 'data-column' ), 10 ); + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + // emulate what webkit does.... escape clears the filter + if ( event.which === tskeyCodes.escape ) { + // make sure to restore the last value on escape + this.value = wo.filter_resetOnEsc ? '' : c.lastSearch[column]; + // 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 !== tskeyCodes.enter && event.which !== tskeyCodes.backSpace && + ( event.which < tskeyCodes.space || ( event.which >= tskeyCodes.left && event.which <= tskeyCodes.down ) ) ) ) ) { + return; + } + // change event = no delay; last true flag tells getFilters to skip newest timed input + tsf.searching( table, true, true ); + }) + // include change for select - fixes #473 + .bind( 'search change keypress input '.split( ' ' ).join( namespace + ' ' ), function( event ) { + // don't get cached data, in case data-column changes dynamically + var column = parseInt( $( this ).attr( 'data-column' ), 10 ); + // don't allow 'change' event to process if the input value is the same - fixes #685 + if ( wo.filter_initialized && ( event.which === tskeyCodes.enter || event.type === 'search' || + // only "input" event fires in MS Edge when clicking the "x" to clear the search + ( event.type === 'change' || event.type === 'input' ) && this.value !== c.lastSearch[column] ) ) { + event.preventDefault(); + // init search with no delay + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + tsf.searching( table, event.type !== 'keypress', true ); + } + }); + }, + searching: function( table, filter, skipFirst ) { + var wo = table.config.widgetOptions; + clearTimeout( wo.filter_searchTimer ); + if ( typeof filter === 'undefined' || filter === true ) { + // delay filtering + wo.filter_searchTimer = setTimeout( function() { + tsf.checkFilters( table, filter, skipFirst ); + }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); } else { - wo.filter_$externalFilters = $el; + // skip delay + tsf.checkFilters( table, filter, skipFirst ); } - // 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 ) ) ) ) ) { + }, + 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 ) { + ts.updateCache( c, function() { + tsf.checkFilters( table, false, skipFirst ); + }); + } 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 ); + // add filter array back into inputs + if ( filterArray ) { + ts.setFilters( table, filters, false, skipFirst !== true ); + if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } } - }); - }, - 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 ); + if ( wo.filter_hideFilters ) { + // show/hide filter row as needed + c.$table + .find( '.' + tscss.filterRow ) + .triggerHandler( 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 = []; + } + // define filter inside it is false + filters = filters || []; + // convert filters to strings - see #1070 + filters = Array.prototype.map ? + filters.map( String ) : + // for IE8 & older browsers - maybe not the best method + filters.join( '\ufffd' ).split( '\ufffd' ); + + if ( wo.filter_initialized ) { + c.$table.triggerHandler( 'filterStart', [ filters ] ); + } + if ( c.showProcessing ) { + // give it time for the processing icon to kick in + setTimeout( function() { + tsf.findRows( table, filters, combinedFilters ); + return false; + }, 30 ); + } else { + tsf.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 ); + } + }, + hideFilters: function( c, $table ) { + var timer, + $row = ( $table || c.$table ).find( '.' + tscss.filterRow ).addClass( tscss.filterRowHide ); + $row + .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() { + }, 200 ); + }) + .find( 'input, select' ).bind( 'focus blur', function( e ) { + var event = e, + $row = $( this ).closest( 'tr' ); 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 ); + 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 = tsfRegex.iQuery, + maskLen = mask.match( tsfRegex.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 ); } - // 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 val; + }, + getLatestSearch: function( $input ) { + if ( $input ) { + return $input.sort( function( a, b ) { + return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); + }); + } + return $input || $(); + }, + findRange: function( c, val, ignoreRanges ) { + // look for multiple columns '1-3,4-6,8' in data-column + var temp, ranges, range, start, end, singles, i, indx, len, + columns = []; + if ( /^[0-9]+$/.test( val ) ) { + // always return an array + return [ parseInt( val, 10 ) ]; + } + // process column range + if ( !ignoreRanges && /-/.test( val ) ) { + ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); + len = ranges ? ranges.length : 0; + 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 ], '' ); } } - } - // return all columns - if ( !columns.length ) { - for ( indx = 0; indx < c.columns; indx++ ) { - columns.push( indx ); + // process single columns + if ( !ignoreRanges && /,/.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 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 all columns + if ( !columns.length ) { + for ( indx = 0; indx < c.columns; indx++ ) { + columns.push( indx ); } } - } - 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 columns; + }, + getColumnElm: function( c, $elements, column ) { + // data-column may contain multiple columns '1-3,5-6,8' + // replaces: c.$filters.filter( '[data-column="' + column + '"]' ); + return $elements.filter( function() { + var cols = tsf.findRange( c, $( this ).attr( 'data-column' ) ); + return $.inArray( column, cols ) > -1; + }); + }, + multipleColumns: function( c, $input ) { + // look for multiple columns '1-3,4-6,8' in data-column + var 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, + val = $.trim( tsf.getLatestSearch( $input ).attr( 'data-column' ) || '' ); + return tsf.findRange( c, val, !targets ); + }, + processTypes: function( c, data, vars ) { + var ffxn, + filterMatched = null, + matches = null; + for ( ffxn in tsf.types ) { + if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { + matches = tsf.types[ffxn]( c, data, vars ); + if ( matches !== null ) { + filterMatched = matches; } - 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; + } + return filterMatched; + }, + processRow: function( c, data, vars ) { + var result, filterMatched, + fxn, ffxn, txt, + wo = c.widgetOptions, + showRow = true, + + // if wo.filter_$anyMatch data-column attribute is changed dynamically + // we don't want to do an "anyMatch" search on one column using data + // for the entire row - see #998 + columnIndex = wo.filter_$anyMatch && wo.filter_$anyMatch.length ? + // look for multiple columns '1-3,4-6,8' + tsf.multipleColumns( c, wo.filter_$anyMatch ) : + []; + + data.$cells = data.$row.children(); + + if ( data.anyMatchFlag && columnIndex.length > 1 ) { + 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 = tsf.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + showRow = filterMatched; } else { - showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; + if ( wo.filter_startsWith ) { + showRow = false; + // data.rowArray may not contain all columns + columnIndex = Math.min( c.columns, data.rowArray.length ); + 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; } } - 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; + 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 ]; + // 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 { + // ignore if filter is empty or disabled + if ( data.filter ) { + data.cache = data.cacheArray[ columnIndex ]; 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 ); + data.iExact = !tsfRegex.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 ); } - } - 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 + + // replace column specific default filters - see #1088 + if ( wo.filter_defaultFilter && tsfRegex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { + data.filter = tsf.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); + } + + // 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 ]; + filterMatched = null; + if ( fxn ) { + if ( fxn === true ) { + // default selector uses exact match unless 'filter-match' class is found + filterMatched = data.isMatch ? + // data.iExact may be a number + ( '' + 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 = tsf.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( tsf.parseFilter( c, data.iFilter, data ) ); + result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); + } } 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 ) ); + result = filterMatched; } - } else { - result = filterMatched; + showRow = ( result ) ? showRow : false; } - 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. <th class="filter-select filter-parsed"> ) - 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; + return showRow; + }, + findRows: function( table, filters, combinedFilters ) { + if ( table.config.lastCombinedFilter === combinedFilters || + !table.config.widgetOptions.filter_initialized ) { + return; + } + var len, norm_rows, rowData, $rows, $row, rowIndex, tbodyIndex, $tbody, columnIndex, + isChild, childRow, lastSearch, showRow, showParent, time, val, indx, + notFiltered, searchFiltered, query, injected, res, id, txt, + storedFilters = $.extend( [], filters ), + 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 = []; + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.parsed[ columnIndex ] = wo.filter_useParsedData || + // parser has a "parsed" parameter + ( c.parsers && c.parsers[ columnIndex ] && c.parsers[ columnIndex ].parsed || + // getData may not return 'parsed' if other 'filter-' class names exist + // ( e.g. <th class="filter-select filter-parsed"> ) + ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], + ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || + c.$headerIndexed[ columnIndex ].hasClass( 'filter-parsed' ) ); + + 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 ) { + console.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 && tsf.getLatestSearch( wo.filter_$anyMatch ).val() || + '' + ); + if ( wo.filter_columnAnyMatch ) { + // specific columns search + query = data.anyMatchFilter.split( tsfRegex.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( ' && ' ); + } } - 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 + !tsfRegex.alreadyFiltered.test( val ) && + // if we are not doing exact matches, using '|' ( logical or ) or not '!' + !tsfRegex.exactTest.test( val ) && + // don't search only filtered if the value is negative + // ( '> -10' => '> -100' will ignore hidden rows ) + !( tsfRegex.isNeg1.test( val ) || tsfRegex.isNeg2.test( val ) ) && + // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 + !( val !== '' && c.$filters && c.$filters.filter( '[data-column="' + 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 ) { + console.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 && tsfRegex.iQuery.test( vars.defaultAnyFilter ) ) { + data.anyMatchFilter = tsf.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++ ) { - // 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' ) ); + txt = $rows[ rowIndex ].className; + // the first row can never be a child row + isChild = rowIndex && tsfRegex.child.test( txt ); + // skip child rows & already filtered rows + if ( isChild || ( searchFiltered && tsfRegex.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 = false; + showParent = tsf.processRow( c, data, vars ); + $row = rowData.$row; + + // don't pass reference to val + val = showParent ? true : false; + childRow = rowData.$row.filter( ':gt(0)' ); + if ( wo.filter_childRows && childRow.length ) { + if ( wo.filter_childByColumn ) { + if ( !wo.filter_childWithSibs ) { + // hide all child rows + childRow.addClass( wo.filter_filteredRow ); + // if only showing resulting child row, only include parent + $row = $row.eq( 0 ); + } + // 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; + val = tsf.processRow( c, data, vars ); + // use OR comparison on child rows + showRow = showRow || val; + if ( !wo.filter_childWithSibs && val ) { + childRow.eq( indx ).removeClass( wo.filter_filteredRow ); + } + } + } + // keep parent row match even if no child matches... see #1020 + showRow = showRow || showParent; + } else { + showRow = val; + } + $row + .toggleClass( wo.filter_filteredRow, !showRow )[0] + .display = showRow ? '' : 'none'; } } - 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' ); + 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', tsf.processFilters( storedFilters, true ) ); + } + if ( c.debug ) { + console.log( 'Completed filter widget search' + ts.benchmark(time) ); + } + if ( wo.filter_initialized ) { + c.$table.triggerHandler( 'filterBeforeEnd', c ); + c.$table.triggerHandler( 'filterEnd', c ); + } + setTimeout( function() { + ts.applyWidget( c.table ); // make sure zebra widget is applied + }, 0 ); + }, + getOptionSource: function( table, column, onlyAvail ) { + table = $( table )[0]; + var c = table.config, + wo = c.widgetOptions, + arry = false, + source = wo.filter_selectSource, + last = c.$table.data( 'lastSearch' ) || [], + fxn = typeof source === 'function' ? 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( '</option>' ) >= 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 = tsf.getOptions( table, column, onlyAvail ); + } + + return tsf.processOptions( table, column, arry ); + + }, + processOptions: function( table, column, arry ) { + if ( !$.isArray( arry ) ) { + return false; + } + table = $( table )[0]; + var cts, txt, indx, len, parsedTxt, str, + c = table.config, + validColumn = typeof column !== 'undefined' && column !== null && column >= 0 && column < c.columns, + parsed = []; + // 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 ) { + if ( value.text ) { + return true; } - if ( data.anyMatchFlag ) { - if ( c.sortLocaleCompare ) { - // replace accents - data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); + return $.inArray( value, arry ) === indx; + }); + if ( validColumn && 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 ]; + // check for object + str = txt.text ? txt.text : txt; + // sortNatural breaks if you don't pass it strings + parsedTxt = ( validColumn && c.parsers && c.parsers.length && + c.parsers[ column ].format( str, table, [], column ) || str ).toString(); + parsedTxt = c.widgetOptions.filter_ignoreCase ? parsedTxt.toLowerCase() : parsedTxt; + // parse array data using set column parser; this DOES NOT pass the original + // table cell to the parser format function + if ( txt.text ) { + txt.parsed = parsedTxt; + parsed.push( txt ); + } else { + parsed.push({ + text : txt, + // check parser length - fixes #934 + parsed : parsedTxt + }); } - 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; + } + // sort parsed select options + cts = c.textSorter || ''; + parsed.sort( function( a, b ) { + var x = a.parsed, + y = b.parsed; + if ( validColumn && typeof cts === 'function' ) { + // custom OVERALL text sorter + return cts( x, y, true, column, table ); + } else if ( validColumn && 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 ); } - // 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(); + // 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] ); } - + return arry; + } + }, + getOptions: function( table, column, onlyAvail ) { + table = $( table )[0]; + var rowIndex, tbodyIndex, len, row, cache, indx, child, childLen, + 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++ ) { - - 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 ) ) ) { + // 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; } - - 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( '' ) || ''; + // 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 ] ); + // child row parsed data + if ( wo.filter_childRows && wo.filter_childByColumn ) { + childLen = cache.normalized[ rowIndex ][ c.columns ].$row.length - 1; + for ( indx = 0; indx < childLen; indx++ ) { + arry.push( '' + cache.normalized[ rowIndex ][ c.columns ].child[ indx ][ column ] ); + } } - 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 ); + } else { + // get raw cached data instead of content directly from the cells + arry.push( cache.normalized[ rowIndex ][ c.columns ].raw[ column ] ); + // child row unparsed data + if ( wo.filter_childRows && wo.filter_childByColumn ) { + childLen = cache.normalized[ rowIndex ][ c.columns ].$row.length; + for ( indx = 1; indx < childLen; indx++ ) { + child = cache.normalized[ rowIndex ][ c.columns ].$row.eq( indx ).children().eq( column ); + arry.push( '' + ts.getElementText( c, child, column ) ); } } - 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( '</option>' ) >= 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 - }); + }, + buildSelect: function( table, column, arry, updating, onlyAvail ) { + table = $( table )[0]; + column = parseInt( column, 10 ); + if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { + return; } - // 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 ] ); + var indx, val, txt, t, $filters, $filter, option, + c = table.config, + wo = c.widgetOptions, + node = c.$headerIndexed[ column ], + // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 + options = '<option value="">' + + ( node.data( 'placeholder' ) || + node.attr( 'data-placeholder' ) || + wo.filter_placeholder.select || '' + ) + '</option>', + // 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 = tsf.getOptionSource( table, column, onlyAvail ); + } + + if ( $.isArray( arry ) ) { + // build option list + for ( indx = 0; indx < arry.length; indx++ ) { + option = arry[ indx ]; + if ( option.text ) { + // OBJECT!! add data-function-name in case the value is set in filter_functions + option['data-function-name'] = typeof option.value === 'undefined' ? option.text : option.value; + + // support jQuery < v1.8, otherwise the below code could be shortened to + // options += $( '<option>', option )[ 0 ].outerHTML; + options += '<option'; + for ( val in option ) { + if ( option.hasOwnProperty( val ) && val !== 'text' ) { + options += ' ' + val + '="' + option[ val ] + '"'; + } + } + if ( !option.value ) { + options += ' value="' + option.text + '"'; + } + options += '>' + option.text + '</option>'; + // above code is needed in jQuery < v1.8 + + // make sure we don't turn an object into a string (objects without a "text" property) + } else if ( '' + option !== '[object Object]' ) { + txt = option = ( '' + option ).replace( tsfRegex.quote, '"' ); + 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 += option !== '' ? + '<option ' + + ( val === txt ? '' : 'data-function-name="' + option + '" ' ) + + 'value="' + val + '">' + txt + + '</option>' : ''; + } } + // 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 ); } - } - 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 = '<option value="">' + - ( node.data( 'placeholder' ) || - node.attr( 'data-placeholder' ) || - wo.filter_placeholder.select || '' - ) + '</option>', - // 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] !== '' ? - '<option ' + - ( val === txt ? '' : 'data-function-name="' + arry[indx] + '" ' ) + - 'value="' + val + '">' + txt + - '</option>' : ''; - } - // 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 ) ); + }, + 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 ) { + tsf.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 + }; + + // filter regex variable + tsfRegex = tsf.regex; + + 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 = tsf.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 ] ) + // must include a namespace here; but not c.namespace + 'filter'? + .trigger( 'change' + c.namespace ); } 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; -}; - -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; -}; + 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 ); + // default apply to "true" + if ( typeof apply === 'undefined' ) { + apply = true; + } + if ( c && apply ) { + // ensure new set filters are applied, even if the search is the same + c.lastCombinedFilter = null; + c.lastSearch = []; + tsf.searching( c.table, filter, skipFirst ); + c.$table.triggerHandler( 'filterFomatterUpdate' ); + } + return !!valid; + }; })( jQuery ); -/*! Widget: stickyHeaders - updated 3/26/2015 (v2.21.3) *//* +/*! Widget: stickyHeaders - updated 10/31/2015 (v2.24.0) *//* * Requires tablesorter v2.8+ and jQuery 1.4.3+ * by Rob Garrison */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; - -$.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 - }, - 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 ] ); - } + 'use strict'; + var ts = $.tablesorter || {}; + + $.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 + }, + 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.triggerHandler( 'resize', [ headers ] ); + } + wo.resize_flag = false; + }; + checkSizes( false ); + clearInterval(wo.resize_timer); + if (disable) { 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; + return false; } - 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('<div class="' + ts.css.stickyWrap + '">'), - $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 ); + 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('<div class="' + ts.css.stickyWrap + '">'), + $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 { - // ie8 only - border = parseFloat( $this.css('border-width') ); - width = $this.outerWidth() - parseFloat( $this.css('padding-left') ) - parseFloat( $this.css('padding-right') ) - border; + width = $this.width(); } - } else { - width = $this.width(); } + $cell.css({ + 'width': width, + 'min-width': width, + 'max-width': 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() }); - } - }, - 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 + 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(); - 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)); + 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 ); + // 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 ] ); + // 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 ] ); + } } - } - - // 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); - } + // 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 ); + }); - // 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(); + 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(c, $stickyTable); } - }); - 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); - } -}); + $table.triggerHandler('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 5/17/2015 (v2.22.0) */ +/*! Widget: resizable - updated 11/4/2015 (v2.24.3) */ +/*jshint browser:true, jquery:true, unused:false */ ;(function ($, window) { -'use strict'; -var ts = $.tablesorter || {}; - -$.extend(ts.css, { - resizableContainer : 'tablesorter-resizable-container', - resizableHandle : 'tablesorter-resizable-handle', - resizableNoSelect : 'tablesorter-disableSelection', - resizableStorage : 'tablesorter-resizable' -}); - -// Add extra scroller css -$(function(){ - var s = '<style>' + - 'body.' + ts.css.resizableNoSelect + ' { -ms-user-select: none; -moz-user-select: -moz-none;' + - '-khtml-user-select: none; -webkit-user-select: none; user-select: none; }' + - '.' + ts.css.resizableContainer + ' { position: relative; height: 1px; }' + - // make handle z-index > than stickyHeader z-index, so the handle stays above sticky header - '.' + ts.css.resizableHandle + ' { position: absolute; display: inline-block; width: 8px; top: 1px;' + - 'cursor: ew-resize; z-index: 3; user-select: none; -moz-user-select: none; }' + - '</style>'; - $(s).appendTo('body'); -}); - -ts.resizable = { - init : function( c, wo ) { - if ( c.$table.hasClass( 'hasResizable' ) ) { return; } - c.$table.addClass( 'hasResizable' ); - ts.resizableReset( c.table, true ); // set default widths - - // internal variables - wo.resizable_ = { - $wrap : c.$table.parent(), - mouseXPosition : 0, - $target : null, - $next : null, - overflow : c.$table.parent().css('overflow') === 'auto', - fullWidth : Math.abs(c.$table.parent().width() - c.$table.width()) < 20, - storedSizes : [] - }; - - var noResize, $header, column, storedSizes, - marginTop = parseInt( c.$table.css( 'margin-top' ), 10 ); - - wo.resizable_.storedSizes = storedSizes = ( ( ts.storage && wo.resizable !== false ) ? - ts.storage( c.table, ts.css.resizableStorage ) : - [] ) || []; - ts.resizable.setWidths( c, wo, storedSizes ); - - wo.$resizable_container = $( '<div class="' + ts.css.resizableContainer + '">' ) - .css({ top : marginTop }) - .insertBefore( c.$table ); - // add container - for ( column = 0; column < c.columns; column++ ) { - $header = c.$headerIndexed[ column ]; - noResize = ts.getData( $header, ts.getColumnData( c.table, c.headers, column ), 'resizable' ) === 'false'; - if ( !noResize ) { - $( '<div class="' + ts.css.resizableHandle + '">' ) - .appendTo( wo.$resizable_container ) - .attr({ - 'data-column' : column, - 'unselectable' : 'on' - }) - .data( 'header', $header ) - .bind( 'selectstart', false ); - } - } - c.$table.one('tablesorter-initialized', function() { - ts.resizable.setHandlePosition( c, wo ); - ts.resizable.bindings( this.config, this.config.widgetOptions ); - }); - }, + 'use strict'; + var ts = $.tablesorter || {}; + + $.extend(ts.css, { + resizableContainer : 'tablesorter-resizable-container', + resizableHandle : 'tablesorter-resizable-handle', + resizableNoSelect : 'tablesorter-disableSelection', + resizableStorage : 'tablesorter-resizable' + }); - setWidth : function( $el, width ) { - $el.css({ - 'width' : width, - 'min-width' : '', - 'max-width' : '' - }); - }, - - setWidths : function( c, wo, storedSizes ) { - var column, - $extra = $( c.namespace + '_extra_headers' ), - $col = c.$table.children( 'colgroup' ).children( 'col' ); - storedSizes = storedSizes || wo.resizable_.storedSizes || []; - // process only if table ID or url match - if ( storedSizes.length ) { + // Add extra scroller css + $(function(){ + var s = '<style>' + + 'body.' + ts.css.resizableNoSelect + ' { -ms-user-select: none; -moz-user-select: -moz-none;' + + '-khtml-user-select: none; -webkit-user-select: none; user-select: none; }' + + '.' + ts.css.resizableContainer + ' { position: relative; height: 1px; }' + + // make handle z-index > than stickyHeader z-index, so the handle stays above sticky header + '.' + ts.css.resizableHandle + ' { position: absolute; display: inline-block; width: 8px;' + + 'top: 1px; cursor: ew-resize; z-index: 3; user-select: none; -moz-user-select: none; }' + + '</style>'; + $(s).appendTo('body'); + }); + + 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 ), + + // 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 ); + + // 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('<span>').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('<span>'); + } + */ + + 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 ); + + wo.$resizable_container = $( '<div class="' + ts.css.resizableContainer + '">' ) + .css({ top : marginTop }) + .insertBefore( $table ); + // add container for ( column = 0; column < c.columns; column++ ) { - // set saved resizable widths - c.$headerIndexed[ column ].width( storedSizes[ column ] ); - if ( $extra.length ) { - // stickyHeaders needs to modify min & max width as well - ts.resizable.setWidth( $extra.eq( column ).add( $col.eq( column ) ), storedSizes[ column ] ); + $header = c.$headerIndexed[ column ]; + tmp = ts.getColumnData( c.table, c.headers, column ); + noResize = ts.getData( $header, tmp, 'resizable' ) === 'false'; + if ( !noResize ) { + $( '<div class="' + ts.css.resizableHandle + '">' ) + .appendTo( wo.$resizable_container ) + .attr({ + 'data-column' : column, + 'unselectable' : 'on' + }) + .data( 'header', $header ) + .bind( 'selectstart', false ); } } - if ( $( c.namespace + '_extra_table' ).length && !ts.hasWidget( c.table, 'scroller' ) ) { - ts.resizable.setWidth( $( c.namespace + '_extra_table' ), c.$table.outerWidth() ); + ts.resizable.setHandlePosition( c, wo ); + ts.resizable.bindings( c, wo ); + }, + + 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; } - } - }, - - 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(); + }, + + 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 : '' }); - } - // 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 + }, + + 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, $this, - 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; + // 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 + }); + } + }); + }, - vars.mouseXPosition = event.pageX; - vars.storedSizes = []; - for ( column = 0; column < c.columns; column++ ) { - $this = c.$headerIndexed[ column ]; - vars.storedSizes[ column ] = $this.is(':visible') ? $this.width() : 0; + // prevent text selection while dragging resize bar + toggleTextSelection : function( c, wo, toggle ) { + var namespace = c.namespace + 'tsresize'; + wo.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 ); } - 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 ); - } - }) - .bind( 'mouseup' + namespace, function() { - if (!wo.resizable_.disabled) { return; } - ts.resizable.toggleTextSelection( c, false ); - ts.resizable.stopResize( c, wo ); - ts.resizable.setHandlePosition( c, wo ); + 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, wo, true ); }); - // resizeEnd event triggered by scroller widget - $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { - ts.resizable.setHandlePosition( c, wo ); - }); + $( 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, wo, false ); + ts.resizable.stopResize( c, wo ); + 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 ); + // resizeEnd event triggered by scroller widget + $( window ).bind( 'resize' + namespace + ' resizeEnd' + namespace, function() { 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 vars = wo.resizable_, - $next = vars.$next, - leftEdge = event.pageX - vars.mouseXPosition; - if ( vars.fullWidth ) { - vars.storedSizes[ vars.target ] += leftEdge; - vars.storedSizes[ vars.next ] -= leftEdge; - ts.resizable.setWidths( c, wo ); - - } else if ( vars.overflow ) { - c.$table.add( $( c.namespace + '_extra_table' ) ).width(function(i, w){ - return w + leftEdge; - }); - if ( !$next.length ) { - // if expanding right-most column, scroll the wrapper - vars.$wrap[0].scrollLeft = c.$table.width(); - } - } 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 $this, column, - vars = wo.resizable_; - vars.storedSizes = []; - if ( ts.storage ) { - vars.storedSizes = []; - for ( column = 0; column < c.columns; column++ ) { - $this = c.$headerIndexed[ column ]; - vars.storedSizes[ column ] = $this.is(':visible') ? $this.width() : 0; + // 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 ); } - if ( wo.resizable !== false ) { + vars.mouseXPosition = event.pageX; + // dynamically update sticky header widths + c.$table.triggerHandler('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 ); - } - } - 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, - resizable_addLastColumn : false, - resizable_widths : [], - resizable_throttle : false, // set to true (5ms) or any number 0-10 range - resizable_targetLast : false - }, - 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 ); + 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.triggerHandler('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; - if ( table && c && c.$headerIndexed.length ) { - for ( index = 0; index < c.columns; index++ ) { - $t = c.$headerIndexed[ index ]; - if ( wo.resizable_widths && wo.resizable_widths[ index ] ) { - $t.css( 'width', wo.resizable_widths[ index ] ); - } else if ( !$t.hasClass( 'resizable-false' ) ) { - // don't clear the width of any column that is not resizable - $t.css( 'width', '' ); - } - } - // reset stickyHeader widths - c.$table.trigger( 'stickyHeadersUpdate' ); - if ( ts.storage && !refreshing ) { - ts.storage( this, ts.css.resizableStorage, {} ); + ts.resizable.toggleTextSelection( c, wo, 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.triggerHandler( 'stickyHeadersUpdate' ); + if ( ts.storage && !refreshing ) { + ts.storage( this, ts.css.resizableStorage, {} ); + } + } + }); + }; })( jQuery, window ); -/*! Widget: saveSort */ +/*! Widget: saveSort - updated 10/31/2015 (v2.24.0) *//* +* Requires tablesorter v2.16+ +* by Rob Garrison +*/ ;(function ($) { -'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); + '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) { + console.log('saveSort widget: Saving last sort: ' + c.sortList + ts.benchmark(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) { + console.log('saveSort: Last sort loaded: "' + sortList + '"' + ts.benchmark(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 + ts.sortOn( c, 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/views/layout.haml b/views/layout.haml index b940d16..f3843cc 100644 --- a/views/layout.haml +++ b/views/layout.haml @@ -6,12 +6,14 @@ %meta{'name'=>"viewport", :content=>"width=device-width, initial-scale=1"} %title Nano Lazar Toxicity Predictions %link{:rel=>'icon', :type=>'image/x-icon', :href=>'/images/favicon.ico'} + %link{:rel=>'stylesheet', :href=>"#{'/css/style.css'}"} %link{:rel=>'stylesheet', :href=>"#{'/css/bootstrap.min.css'}"} %link{:rel=>'stylesheet', :href=>"#{'/css/theme.default.min.css'}"} %link{:rel=>'stylesheet', :href=>"#{'/css/theme.bootstrap.min.css'}"} %script{:src=>"/javascripts/jquery-1.11.2.min.js"} %script{:src=>"/javascripts/jquery.tablesorter.min.js"} %script{:src=>"/javascripts/jquery.tablesorter.widgets.js"} + %script{:src=>"/javascripts/jquery.tablesorter.staticrow.min.js"} %script{:src=>"/javascripts/bootstrap.min.js"} %body %noscript diff --git a/views/predict.haml b/views/predict.haml index 4584cbf..7fa52ed 100644 --- a/views/predict.haml +++ b/views/predict.haml @@ -1,18 +1,23 @@ %div.well %form{:role=>"form", :action=> to("/predict"), :method=>"post"} %h3.help-block - Please characterise a Gold nanoparticle + Please characterise a nanoparticle + %hr #input - size = @example.size + %div.form-group + %h5 Core + %input.input-sm.form-control{:id=>"Au",:type=>"text",:name=>"input_value_Au",:value=>"[Au]", :disabled=>"true"} - @example.each_with_index do |v,id| - id = id + 1 - key = v[0] - val = v[1] - %div.form-group - %h5= key - %input.form-control{:id=>id,:type=>"hidden",:name=>"input_key_#{id}",:value=>key} - %input.form-control{:id=>id,:type=>"text",:name=>"input_value_#{id}",:value=>val} + %h5= key.gsub(/\sMean/, '') + %input.form-control{:id=>id,:type=>"hidden",:name=>"input_key_#{id}",:value=>key} + %input.input-sm.form-control{:id=>id,:type=>"text",:name=>"input_value_#{id}",:value=>val} %input.form-control{:id=>"example",:type=>"hidden",:name=>"size",:value=>size} %hr #predict - %button.btn.btn-info{:type=>"submit"} predict + %button.btn.btn-success{:type=>"submit"} + Find similar nano particles + %span.glyphicon.glyphicon-chevron-right diff --git a/views/prediction.haml b/views/prediction.haml index 9647cea..029800a 100644 --- a/views/prediction.haml +++ b/views/prediction.haml @@ -1,153 +1,142 @@ %div.well - %a.btn.btn-xs.btn-warning{:href => to('/predict'), :style=>"margin-bottom:1em;"} - %i.glyphicon.glyphicon-menu-left + %a.btn.btn-xs.btn-default{:href => to('/predict'), :style=>"margin-bottom:1em;"} + %span.glyphicon.glyphicon-chevron-left New Prediction - %ul.nav.nav-tabs - %li - %a{:href=>"#query", :data=>{:toggle=>"tab"}} Query - %li - %a{:href=>"#match", :data=>{:toggle=>"tab"}} Match - %li.active - %a{:href=>"#prediction", :data=>{:toggle=>"tab"}} Prediction - %li - %a{:href=>"#neighbors", :data=>{:toggle=>"tab"}} Neighbors - %div.tab-content - #query.tab-pane.fade - %span.help-block - your query was - - @input.each_with_index do |v,id| - - key = v[0] - - val = v[1] - %h5= key - %input.form-control{:id=>id,:type=>"text",:value=>"#{val}", :disabled=>"true"} - #match.tab-pane.fade - - if @prediction[:match] - %table{:id=>"match", :style=>"border-style: 1px solid black;margin-top:10px;"} - %thead - %tr - %th - ID - %th - Composition - %th - Tox - %th - Physchem - %tbody + %h3.help-block + Similar particles + %hr + :javascript + $(document).ready(function(){ + $("table#neighbors").tablesorter({ + debug: false, + theme: "bootstrap", + headerTemplate: '{content} {icon}', + widgets: ['uitheme', 'stickyHeaders', 'staticRow'], + widgetOptions: { + stickyHeaders : '', + stickyHeaders_offset : 0, + stickyHeaders_cloneId : '-sticky', + stickyHeaders_addResizeEvent : true, + stickyHeaders_includeCaption : true, + stickyHeaders_zIndex : 2, + stickyHeaders_attachTo : null, + stickyHeaders_xScroll : null, + stickyHeaders_yScroll : null, + stickyHeaders_filteredToTop: true + }, + sortList: [[1,1]], + widthFixed: false + }); + $("table#physchem").tablesorter({ + debug: false, + theme: "bootstrap", + }); + }); + + %div.table-responsive + %table.tablesorter{:id=>"neighbors"} + %thead + %tr + %th + ID + %th + Similarity + %th + Composition + %th + Tox + %th + Physchem + %tbody + / query + %tr.static + / id + %td + %h5 Query + / similarity + %td + x + / composition + %td + x + / tox + %td + - if @prediction[:prediction] + - @prediction[:prediction].each do |k,v| + %h5 Prediction + %p= k + - if v == nil + %p 0 + - else + %p= v.round(5) + - else + x + / physchem + %td + - @input.each do |v| + %div{:style=>"display:inline-block;padding-right:20px;"} + %h5= v[0].gsub(/\sMean/, '') + %p= v[1].round(3) + / match + - if @prediction[:match] + %tr.static + / id + %td + %h5= @prediction[:match].keys[0] + / similarity + %td + %h5 match + / composition + %td + - @prediction[:match].values[0]["composition"].each do |k,v| + %h5= k + %p= v + / tox + %td + - @prediction[:match].values[0]["tox"].each do |k,v| + %h5 Measurement prediction + %p= k + %p= v.round(3) + - if @prediction[:prediction] + - @prediction[:prediction].each do |k,v| + %h5 Prediction + %p= k + - if v == nil + %p 0 + - else + %p= v.round(5) + / physchem + %td + - @prediction[:match].values[0]["physchem"].each do |k,v| + %div{:style=>"display:inline-block;padding-right:20px;"} + %h5= k.gsub(/\sMean/, '') + %p= v.round(3) + / neighbors + - if @prediction[:neighbors] + - @prediction[:neighbors].each_with_index do |neighbor,idx| %tr / ID %td - %h5= @prediction[:match].keys[0] - / composition + %h5= neighbor["id"] + / Similarity %td - - @prediction[:match].values[0]["composition"].each do |k,v| - %h5= k - %p= v - / tox + %h5= neighbor["similarity"].round(3) + / Composition %td - - @prediction[:match].values[0]["tox"].each do |k,v| + - neighbor["composition"].each do |k,v| %h5= k %p= v - / physchem + / Tox %td - - @prediction[:match].values[0]["physchem"].each do |k,v| - %h5= k - %p= v - - else - %h5 No match - #prediction.tab-pane.in.active - - if @prediction[:prediction] - - @prediction[:prediction].each do |k,v| - %h5= k - - if v == nil - %p no value - - else - %p= v.round(5) - #neighbors.tab-pane.fade - - if @prediction[:neighbors] - :javascript - $(document).ready(function(){ - $("table#match").tablesorter({ - debug: false, - theme: "bootstrap", - headerTemplate: '{content} {icon}', - widgets: ['uitheme', 'stickyHeaders'], - widgetOptions: { - stickyHeaders : '', - stickyHeaders_offset : 0, - stickyHeaders_cloneId : '-sticky', - stickyHeaders_addResizeEvent : true, - stickyHeaders_includeCaption : true, - stickyHeaders_zIndex : 2, - stickyHeaders_attachTo : null, - stickyHeaders_xScroll : null, - stickyHeaders_yScroll : null, - stickyHeaders_filteredToTop: true - }, - headers: {0: {sorter: false}, 1: {sorter: false}, 2: {sorter: false}, 3: {sorter: false}}, - sortList: [[1,1]], - widthFixed: false - }); - }); - $(document).ready(function(){ - $("table#neighbors").tablesorter({ - debug: false, - theme: "bootstrap", - headerTemplate: '{content} {icon}', - widgets: ['uitheme', 'stickyHeaders'], - widgetOptions: { - stickyHeaders : '', - stickyHeaders_offset : 0, - stickyHeaders_cloneId : '-sticky', - stickyHeaders_addResizeEvent : true, - stickyHeaders_includeCaption : true, - stickyHeaders_zIndex : 2, - stickyHeaders_attachTo : null, - stickyHeaders_xScroll : null, - stickyHeaders_yScroll : null, - stickyHeaders_filteredToTop: true - }, - sortList: [[1,1]], - widthFixed: false - }); - }); - %div.table-responsive - %table.tablesorter{:id=>"neighbors", :style=>"border-style: 1px solid black;margin-top:10px;"} - %thead - %tr - %th{:style =>"vertical-align:middle;"} - ID - %th{:style =>"vertical-align:middle;"} - Similarity - %th{:style =>"vertical-align:middle;"} - Composition - %th{:style =>"vertical-align:middle;"} - Tox - %th{:style =>"vertical-align:middle;"} - Physchem - %tbody - - @prediction[:neighbors].each do |neighbor| - %tr - / ID - %td - %h5= neighbor["id"] - / Similarity - %td - %h5= neighbor["similarity"].round(3) - / Composition - %td - - neighbor["composition"].each do |k,v| - %h5= k - %p= v - / Tox - %td - - neighbor["tox"].each do |k,v| - %h5= k + - neighbor["tox"].each do |k,v| + %h5 Measurement prediction + %p= k + %p= v.round(3) + / Physchem + %td + - neighbor["physchem"].each do |k,v| + %div{:style=>"display:inline-block;padding-right:20px;"} + %h5= k.gsub(/\sMean/, '') %p= v.round(3) - / Physchem - %td - - neighbor["physchem"].each do |k,v| - %h5= k - %p= v |