sbpanel 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +3 -0
- data/Rakefile +2 -0
- data/bin/sbpanel +43 -0
- data/lib/sbpanel.rb +2 -0
- data/lib/sbpanel/game.rb +197 -0
- data/lib/sbpanel/version.rb +3 -0
- data/public/css/bootstrap.min.css +7 -0
- data/public/css/tablesorter.bootstrap.css +132 -0
- data/public/fonts/glyphicons-halflings-regular.eot +0 -0
- data/public/fonts/glyphicons-halflings-regular.svg +229 -0
- data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/public/fonts/glyphicons-halflings-regular.woff +0 -0
- data/public/js/jquery-1.10.2.min.js +6 -0
- data/public/js/jquery.tablesorter.js +1591 -0
- data/public/js/jquery.tablesorter.widgets.js +1435 -0
- data/sbpanel.gemspec +27 -0
- data/views/index.erb +195 -0
- metadata +119 -0
@@ -0,0 +1,1435 @@
|
|
1
|
+
/*! tableSorter 2.8+ widgets - updated 12/16/2013 (v2.14.5)
|
2
|
+
*
|
3
|
+
* Column Styles
|
4
|
+
* Column Filters
|
5
|
+
* Column Resizing
|
6
|
+
* Sticky Header
|
7
|
+
* UI Theme (generalized)
|
8
|
+
* Save Sort
|
9
|
+
* [ "columns", "filter", "resizable", "stickyHeaders", "uitheme", "saveSort" ]
|
10
|
+
*/
|
11
|
+
/*jshint browser:true, jquery:true, unused:false, loopfunc:true */
|
12
|
+
/*global jQuery: false, localStorage: false, navigator: false */
|
13
|
+
;(function($) {
|
14
|
+
"use strict";
|
15
|
+
var ts = $.tablesorter = $.tablesorter || {};
|
16
|
+
|
17
|
+
ts.themes = {
|
18
|
+
"bootstrap" : {
|
19
|
+
table : 'table table-bordered table-striped',
|
20
|
+
caption : 'caption',
|
21
|
+
header : 'bootstrap-header', // give the header a gradient background
|
22
|
+
footerRow : '',
|
23
|
+
footerCells: '',
|
24
|
+
icons : '', // add "icon-white" to make them white; this icon class is added to the <i> in the header
|
25
|
+
sortNone : 'bootstrap-icon-unsorted',
|
26
|
+
sortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up',
|
27
|
+
sortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down',
|
28
|
+
active : '', // applied when column is sorted
|
29
|
+
hover : '', // use custom css here - bootstrap class may not override it
|
30
|
+
filterRow : '', // filter row class
|
31
|
+
even : '', // even row zebra striping
|
32
|
+
odd : '' // odd row zebra striping
|
33
|
+
},
|
34
|
+
"jui" : {
|
35
|
+
table : 'ui-widget ui-widget-content ui-corner-all', // table classes
|
36
|
+
caption : 'ui-widget-content ui-corner-all',
|
37
|
+
header : 'ui-widget-header ui-corner-all ui-state-default', // header classes
|
38
|
+
footerRow : '',
|
39
|
+
footerCells: '',
|
40
|
+
icons : 'ui-icon', // icon class added to the <i> in the header
|
41
|
+
sortNone : 'ui-icon-carat-2-n-s',
|
42
|
+
sortAsc : 'ui-icon-carat-1-n',
|
43
|
+
sortDesc : 'ui-icon-carat-1-s',
|
44
|
+
active : 'ui-state-active', // applied when column is sorted
|
45
|
+
hover : 'ui-state-hover', // hover class
|
46
|
+
filterRow : '',
|
47
|
+
even : 'ui-widget-content', // even row zebra striping
|
48
|
+
odd : 'ui-state-default' // odd row zebra striping
|
49
|
+
}
|
50
|
+
};
|
51
|
+
|
52
|
+
// *** Store data in local storage, with a cookie fallback ***
|
53
|
+
/* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json)
|
54
|
+
if you need it, then include https://github.com/douglascrockford/JSON-js
|
55
|
+
|
56
|
+
$.parseJSON is not available is jQuery versions older than 1.4.1, using older
|
57
|
+
versions will only allow storing information for one page at a time
|
58
|
+
|
59
|
+
// *** Save data (JSON format only) ***
|
60
|
+
// val must be valid JSON... use http://jsonlint.com/ to ensure it is valid
|
61
|
+
var val = { "mywidget" : "data1" }; // valid JSON uses double quotes
|
62
|
+
// $.tablesorter.storage(table, key, val);
|
63
|
+
$.tablesorter.storage(table, 'tablesorter-mywidget', val);
|
64
|
+
|
65
|
+
// *** Get data: $.tablesorter.storage(table, key); ***
|
66
|
+
v = $.tablesorter.storage(table, 'tablesorter-mywidget');
|
67
|
+
// val may be empty, so also check for your data
|
68
|
+
val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : '';
|
69
|
+
alert(val); // "data1" if saved, or "" if not
|
70
|
+
*/
|
71
|
+
ts.storage = function(table, key, value, options) {
|
72
|
+
table = $(table)[0];
|
73
|
+
var cookieIndex, cookies, date,
|
74
|
+
hasLocalStorage = false,
|
75
|
+
values = {},
|
76
|
+
c = table.config,
|
77
|
+
$table = $(table),
|
78
|
+
id = options && options.id || $table.attr(options && options.group ||
|
79
|
+
'data-table-group') || table.id || $('.tablesorter').index( $table ),
|
80
|
+
url = options && options.url || $table.attr(options && options.page ||
|
81
|
+
'data-table-page') || c && c.fixedUrl || window.location.pathname;
|
82
|
+
// https://gist.github.com/paulirish/5558557
|
83
|
+
if ("localStorage" in window) {
|
84
|
+
try {
|
85
|
+
window.localStorage.setItem('_tmptest', 'temp');
|
86
|
+
hasLocalStorage = true;
|
87
|
+
window.localStorage.removeItem('_tmptest');
|
88
|
+
} catch(error) {}
|
89
|
+
}
|
90
|
+
// *** get value ***
|
91
|
+
if ($.parseJSON) {
|
92
|
+
if (hasLocalStorage) {
|
93
|
+
values = $.parseJSON(localStorage[key] || '{}');
|
94
|
+
} else {
|
95
|
+
// old browser, using cookies
|
96
|
+
cookies = document.cookie.split(/[;\s|=]/);
|
97
|
+
// add one to get from the key to the value
|
98
|
+
cookieIndex = $.inArray(key, cookies) + 1;
|
99
|
+
values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || '{}') : {};
|
100
|
+
}
|
101
|
+
}
|
102
|
+
// allow value to be an empty string too
|
103
|
+
if ((value || value === '') && window.JSON && JSON.hasOwnProperty('stringify')) {
|
104
|
+
// add unique identifiers = url pathname > table ID/index on page > data
|
105
|
+
if (!values[url]) {
|
106
|
+
values[url] = {};
|
107
|
+
}
|
108
|
+
values[url][id] = value;
|
109
|
+
// *** set value ***
|
110
|
+
if (hasLocalStorage) {
|
111
|
+
localStorage[key] = JSON.stringify(values);
|
112
|
+
} else {
|
113
|
+
date = new Date();
|
114
|
+
date.setTime(date.getTime() + (31536e+6)); // 365 days
|
115
|
+
document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g,'\"') + '; expires=' + date.toGMTString() + '; path=/';
|
116
|
+
}
|
117
|
+
} else {
|
118
|
+
return values && values[url] ? values[url][id] : {};
|
119
|
+
}
|
120
|
+
};
|
121
|
+
|
122
|
+
// Add a resize event to table headers
|
123
|
+
// **************************
|
124
|
+
ts.addHeaderResizeEvent = function(table, disable, settings) {
|
125
|
+
var headers,
|
126
|
+
defaults = {
|
127
|
+
timer : 250
|
128
|
+
},
|
129
|
+
options = $.extend({}, defaults, settings),
|
130
|
+
c = table.config,
|
131
|
+
wo = c.widgetOptions,
|
132
|
+
checkSizes = function(triggerEvent) {
|
133
|
+
wo.resize_flag = true;
|
134
|
+
headers = [];
|
135
|
+
c.$headers.each(function() {
|
136
|
+
var $header = $(this),
|
137
|
+
sizes = $header.data('savedSizes') || [0,0], // fixes #394
|
138
|
+
width = this.offsetWidth,
|
139
|
+
height = this.offsetHeight;
|
140
|
+
if (width !== sizes[0] || height !== sizes[1]) {
|
141
|
+
$header.data('savedSizes', [ width, height ]);
|
142
|
+
headers.push(this);
|
143
|
+
}
|
144
|
+
});
|
145
|
+
if (headers.length && triggerEvent !== false) {
|
146
|
+
c.$table.trigger('resize', [ headers ]);
|
147
|
+
}
|
148
|
+
wo.resize_flag = false;
|
149
|
+
};
|
150
|
+
checkSizes(false);
|
151
|
+
clearInterval(wo.resize_timer);
|
152
|
+
if (disable) {
|
153
|
+
wo.resize_flag = false;
|
154
|
+
return false;
|
155
|
+
}
|
156
|
+
wo.resize_timer = setInterval(function() {
|
157
|
+
if (wo.resize_flag) { return; }
|
158
|
+
checkSizes();
|
159
|
+
}, options.timer);
|
160
|
+
};
|
161
|
+
|
162
|
+
// Widget: General UI theme
|
163
|
+
// "uitheme" option in "widgetOptions"
|
164
|
+
// **************************
|
165
|
+
ts.addWidget({
|
166
|
+
id: "uitheme",
|
167
|
+
priority: 10,
|
168
|
+
format: function(table, c, wo) {
|
169
|
+
var time, classes, $header, $icon, $tfoot,
|
170
|
+
themesAll = ts.themes,
|
171
|
+
$table = c.$table,
|
172
|
+
$headers = c.$headers,
|
173
|
+
theme = c.theme || 'jui',
|
174
|
+
themes = themesAll[theme] || themesAll.jui,
|
175
|
+
remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc;
|
176
|
+
if (c.debug) { time = new Date(); }
|
177
|
+
// initialization code - run once
|
178
|
+
if (!$table.hasClass('tablesorter-' + theme) || c.theme === theme || !table.hasInitialized) {
|
179
|
+
// update zebra stripes
|
180
|
+
if (themes.even !== '') { wo.zebra[0] += ' ' + themes.even; }
|
181
|
+
if (themes.odd !== '') { wo.zebra[1] += ' ' + themes.odd; }
|
182
|
+
// add caption style
|
183
|
+
$table.find('caption').addClass(themes.caption);
|
184
|
+
// add table/footer class names
|
185
|
+
$tfoot = $table
|
186
|
+
// remove other selected themes
|
187
|
+
.removeClass( c.theme === '' ? '' : 'tablesorter-' + c.theme )
|
188
|
+
.addClass('tablesorter-' + theme + ' ' + themes.table) // add theme widget class name
|
189
|
+
.find('tfoot');
|
190
|
+
if ($tfoot.length) {
|
191
|
+
$tfoot
|
192
|
+
.find('tr').addClass(themes.footerRow)
|
193
|
+
.children('th, td').addClass(themes.footerCells);
|
194
|
+
}
|
195
|
+
// update header classes
|
196
|
+
$headers
|
197
|
+
.addClass(themes.header)
|
198
|
+
.not('.sorter-false')
|
199
|
+
.bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) {
|
200
|
+
// toggleClass with switch added in jQuery 1.3
|
201
|
+
$(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover);
|
202
|
+
});
|
203
|
+
if (!$headers.find('.tablesorter-wrapper').length) {
|
204
|
+
// Firefox needs this inner div to position the resizer correctly
|
205
|
+
$headers.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
|
206
|
+
}
|
207
|
+
if (c.cssIcon) {
|
208
|
+
// if c.cssIcon is '', then no <i> is added to the header
|
209
|
+
$headers.find('.' + ts.css.icon).addClass(themes.icons);
|
210
|
+
}
|
211
|
+
if ($table.hasClass('hasFilters')) {
|
212
|
+
$headers.find('.tablesorter-filter-row').addClass(themes.filterRow);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
$.each($headers, function() {
|
216
|
+
$header = $(this);
|
217
|
+
$icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $header;
|
218
|
+
if (this.sortDisabled) {
|
219
|
+
// no sort arrows for disabled columns!
|
220
|
+
$header.removeClass(remove);
|
221
|
+
$icon.removeClass(remove + ' tablesorter-icon ' + themes.icons);
|
222
|
+
} else {
|
223
|
+
classes = ($header.hasClass(ts.css.sortAsc)) ?
|
224
|
+
themes.sortAsc :
|
225
|
+
($header.hasClass(ts.css.sortDesc)) ? themes.sortDesc :
|
226
|
+
$header.hasClass(ts.css.header) ? themes.sortNone : '';
|
227
|
+
$header[classes === themes.sortNone ? 'removeClass' : 'addClass'](themes.active);
|
228
|
+
$icon.removeClass(remove).addClass(classes);
|
229
|
+
}
|
230
|
+
});
|
231
|
+
if (c.debug) {
|
232
|
+
ts.benchmark("Applying " + theme + " theme", time);
|
233
|
+
}
|
234
|
+
},
|
235
|
+
remove: function(table, c, wo) {
|
236
|
+
var $table = c.$table,
|
237
|
+
theme = c.theme || 'jui',
|
238
|
+
themes = ts.themes[ theme ] || ts.themes.jui,
|
239
|
+
$headers = $table.children('thead').children(),
|
240
|
+
remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc;
|
241
|
+
$table
|
242
|
+
.removeClass('tablesorter-' + theme + ' ' + themes.table)
|
243
|
+
.find(ts.css.header).removeClass(themes.header);
|
244
|
+
$headers
|
245
|
+
.unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover
|
246
|
+
.removeClass(themes.hover + ' ' + remove + ' ' + themes.active)
|
247
|
+
.find('.tablesorter-filter-row')
|
248
|
+
.removeClass(themes.filterRow);
|
249
|
+
$headers.find('.tablesorter-icon').removeClass(themes.icons);
|
250
|
+
}
|
251
|
+
});
|
252
|
+
|
253
|
+
// Widget: Column styles
|
254
|
+
// "columns", "columns_thead" (true) and
|
255
|
+
// "columns_tfoot" (true) options in "widgetOptions"
|
256
|
+
// **************************
|
257
|
+
ts.addWidget({
|
258
|
+
id: "columns",
|
259
|
+
priority: 30,
|
260
|
+
options : {
|
261
|
+
columns : [ "primary", "secondary", "tertiary" ]
|
262
|
+
},
|
263
|
+
format: function(table, c, wo) {
|
264
|
+
var time, $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx,
|
265
|
+
$table = c.$table,
|
266
|
+
$tbodies = c.$tbodies,
|
267
|
+
sortList = c.sortList,
|
268
|
+
len = sortList.length,
|
269
|
+
// removed c.widgetColumns support
|
270
|
+
css = wo && wo.columns || [ "primary", "secondary", "tertiary" ],
|
271
|
+
last = css.length - 1;
|
272
|
+
remove = css.join(' ');
|
273
|
+
if (c.debug) {
|
274
|
+
time = new Date();
|
275
|
+
}
|
276
|
+
// check if there is a sort (on initialization there may not be one)
|
277
|
+
for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
|
278
|
+
$tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody
|
279
|
+
$rows = $tbody.children('tr');
|
280
|
+
// loop through the visible rows
|
281
|
+
$rows.each(function() {
|
282
|
+
$row = $(this);
|
283
|
+
if (this.style.display !== 'none') {
|
284
|
+
// remove all columns class names
|
285
|
+
$cells = $row.children().removeClass(remove);
|
286
|
+
// add appropriate column class names
|
287
|
+
if (sortList && sortList[0]) {
|
288
|
+
// primary sort column class
|
289
|
+
$cells.eq(sortList[0][0]).addClass(css[0]);
|
290
|
+
if (len > 1) {
|
291
|
+
for (indx = 1; indx < len; indx++) {
|
292
|
+
// secondary, tertiary, etc sort column classes
|
293
|
+
$cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] );
|
294
|
+
}
|
295
|
+
}
|
296
|
+
}
|
297
|
+
}
|
298
|
+
});
|
299
|
+
ts.processTbody(table, $tbody, false);
|
300
|
+
}
|
301
|
+
// add classes to thead and tfoot
|
302
|
+
rows = wo.columns_thead !== false ? ['thead tr'] : [];
|
303
|
+
if (wo.columns_tfoot !== false) {
|
304
|
+
rows.push('tfoot tr');
|
305
|
+
}
|
306
|
+
if (rows.length) {
|
307
|
+
$rows = $table.find( rows.join(',') ).children().removeClass(remove);
|
308
|
+
if (len) {
|
309
|
+
for (indx = 0; indx < len; indx++) {
|
310
|
+
// add primary. secondary, tertiary, etc sort column classes
|
311
|
+
$rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]);
|
312
|
+
}
|
313
|
+
}
|
314
|
+
}
|
315
|
+
if (c.debug) {
|
316
|
+
ts.benchmark("Applying Columns widget", time);
|
317
|
+
}
|
318
|
+
},
|
319
|
+
remove: function(table, c, wo) {
|
320
|
+
var tbodyIndex, $tbody,
|
321
|
+
$tbodies = c.$tbodies,
|
322
|
+
remove = (wo.columns || [ "primary", "secondary", "tertiary" ]).join(' ');
|
323
|
+
c.$headers.removeClass(remove);
|
324
|
+
c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove);
|
325
|
+
for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
|
326
|
+
$tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
|
327
|
+
$tbody.children('tr').each(function() {
|
328
|
+
$(this).children().removeClass(remove);
|
329
|
+
});
|
330
|
+
ts.processTbody(table, $tbody, false); // restore tbody
|
331
|
+
}
|
332
|
+
}
|
333
|
+
});
|
334
|
+
|
335
|
+
// Widget: filter
|
336
|
+
// **************************
|
337
|
+
ts.addWidget({
|
338
|
+
id: "filter",
|
339
|
+
priority: 50,
|
340
|
+
options : {
|
341
|
+
filter_anyMatch : false, // if true overrides default find rows behaviours and if any column matches query it returns that row
|
342
|
+
filter_childRows : false, // if true, filter includes child row content in the search
|
343
|
+
filter_columnFilters : true, // if true, a filter will be added to the top of each table column
|
344
|
+
filter_cssFilter : '', // css class name added to the filter row & each input in the row (tablesorter-filter is ALWAYS added)
|
345
|
+
filter_filteredRow : 'filtered', // class added to filtered rows; needed by pager plugin
|
346
|
+
filter_formatter : null, // add custom filter elements to the filter row
|
347
|
+
filter_functions : null, // add custom filter functions using this option
|
348
|
+
filter_hideFilters : false, // collapse filter row when mouse leaves the area
|
349
|
+
filter_ignoreCase : true, // if true, make all searches case-insensitive
|
350
|
+
filter_liveSearch : true, // if true, search column content while the user types (with a delay)
|
351
|
+
filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available (visible) options within the drop down
|
352
|
+
filter_reset : null, // jQuery selector string of an element used to reset the filters
|
353
|
+
filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters
|
354
|
+
filter_searchDelay : 300, // typing delay in milliseconds before starting a search
|
355
|
+
filter_startsWith : false, // if true, filter start from the beginning of the cell contents
|
356
|
+
filter_useParsedData : false, // filter all data using parsed content
|
357
|
+
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.
|
358
|
+
filter_defaultAttrib : 'data-value' // data attribute in the header cell that contains the default filter value
|
359
|
+
},
|
360
|
+
format: function(table, c, wo) {
|
361
|
+
if (!c.$table.hasClass('hasFilters')) {
|
362
|
+
if (c.parsers || !c.parsers && wo.filter_serversideFiltering) {
|
363
|
+
ts.filter.init(table, c, wo);
|
364
|
+
}
|
365
|
+
}
|
366
|
+
},
|
367
|
+
remove: function(table, c, wo) {
|
368
|
+
var tbodyIndex, $tbody,
|
369
|
+
$table = c.$table,
|
370
|
+
$tbodies = c.$tbodies;
|
371
|
+
$table
|
372
|
+
.removeClass('hasFilters')
|
373
|
+
// add .tsfilter namespace to all BUT search
|
374
|
+
.unbind('addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join('.tsfilter '))
|
375
|
+
.find('.tablesorter-filter-row').remove();
|
376
|
+
for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
|
377
|
+
$tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
|
378
|
+
$tbody.children().removeClass(wo.filter_filteredRow).show();
|
379
|
+
ts.processTbody(table, $tbody, false); // restore tbody
|
380
|
+
}
|
381
|
+
if (wo.filter_reset) {
|
382
|
+
$(document).undelegate(wo.filter_reset, 'click.tsfilter');
|
383
|
+
}
|
384
|
+
}
|
385
|
+
});
|
386
|
+
|
387
|
+
ts.filter = {
|
388
|
+
|
389
|
+
// regex used in filter "check" functions - not for general use and not documented
|
390
|
+
regex: {
|
391
|
+
regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex
|
392
|
+
child : /tablesorter-childRow/, // child row class name; this gets updated in the script
|
393
|
+
filtered : /filtered/, // filtered (hidden) row class name; updated in the script
|
394
|
+
type : /undefined|number/, // check type
|
395
|
+
exact : /(^[\"|\'|=]+)|([\"|\'|=]+$)/g, // exact match (allow '==')
|
396
|
+
nondigit : /[^\w,. \-()]/g, // replace non-digits (from digit & currency parser)
|
397
|
+
operators : /[<>=]/g // replace operators
|
398
|
+
},
|
399
|
+
// function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed )
|
400
|
+
// filter = array of filter input values; iFilter = same array, except lowercase
|
401
|
+
// exact = table cell text (or parsed data if column parser enabled)
|
402
|
+
// iExact = same as exact, except lowercase
|
403
|
+
// cached = table cell text from cache, so it has been parsed
|
404
|
+
// index = column index; table = table element (DOM)
|
405
|
+
// wo = widget options (table.config.widgetOptions)
|
406
|
+
// parsed = array (by column) of boolean values (from filter_useParsedData or "filter-parsed" class)
|
407
|
+
types: {
|
408
|
+
// Look for regex
|
409
|
+
regex: function( filter, iFilter, exact, iExact ) {
|
410
|
+
if ( ts.filter.regex.regex.test(iFilter) ) {
|
411
|
+
var matches,
|
412
|
+
regex = ts.filter.regex.regex.exec(iFilter);
|
413
|
+
try {
|
414
|
+
matches = new RegExp(regex[1], regex[2]).test( iExact );
|
415
|
+
} catch (error) {
|
416
|
+
matches = false;
|
417
|
+
}
|
418
|
+
return matches;
|
419
|
+
}
|
420
|
+
return null;
|
421
|
+
},
|
422
|
+
// Look for quotes or equals to get an exact match; ignore type since iExact could be numeric
|
423
|
+
exact: function( filter, iFilter, exact, iExact ) {
|
424
|
+
/*jshint eqeqeq:false */
|
425
|
+
if (ts.filter.regex.exact.test(iFilter)) {
|
426
|
+
return iFilter.replace(ts.filter.regex.exact, '') == iExact;
|
427
|
+
}
|
428
|
+
return null;
|
429
|
+
},
|
430
|
+
// Look for a not match
|
431
|
+
notMatch: function( filter, iFilter, exact, iExact, cached, index, table, wo ) {
|
432
|
+
if ( /^\!/.test(iFilter) ) {
|
433
|
+
iFilter = iFilter.replace('!', '');
|
434
|
+
var indx = iExact.search( $.trim(iFilter) );
|
435
|
+
return iFilter === '' ? true : !(wo.filter_startsWith ? indx === 0 : indx >= 0);
|
436
|
+
}
|
437
|
+
return null;
|
438
|
+
},
|
439
|
+
// Look for operators >, >=, < or <=
|
440
|
+
operators: function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
|
441
|
+
if ( /^[<>]=?/.test(iFilter) ) {
|
442
|
+
var cachedValue, result,
|
443
|
+
c = table.config,
|
444
|
+
query = ts.formatFloat( iFilter.replace(ts.filter.regex.operators, ''), table ),
|
445
|
+
parser = c.parsers[index],
|
446
|
+
savedSearch = query;
|
447
|
+
// parse filter value in case we're comparing numbers (dates)
|
448
|
+
if (parsed[index] || parser.type === 'numeric') {
|
449
|
+
cachedValue = parser.format( '' + iFilter.replace(ts.filter.regex.operators, ''), table, c.$headers.eq(index), index );
|
450
|
+
query = ( typeof query === "number" && cachedValue !== '' && !isNaN(cachedValue) ) ? cachedValue : query;
|
451
|
+
}
|
452
|
+
// iExact may be numeric - see issue #149;
|
453
|
+
// check if cached is defined, because sometimes j goes out of range? (numeric columns)
|
454
|
+
cachedValue = ( parsed[index] || parser.type === 'numeric' ) && !isNaN(query) && cached ? cached :
|
455
|
+
isNaN(iExact) ? ts.formatFloat( iExact.replace(ts.filter.regex.nondigit, ''), table) :
|
456
|
+
ts.formatFloat( iExact, table );
|
457
|
+
if ( />/.test(iFilter) ) { result = />=/.test(iFilter) ? cachedValue >= query : cachedValue > query; }
|
458
|
+
if ( /</.test(iFilter) ) { result = /<=/.test(iFilter) ? cachedValue <= query : cachedValue < query; }
|
459
|
+
// keep showing all rows if nothing follows the operator
|
460
|
+
if ( !result && savedSearch === '' ) { result = true; }
|
461
|
+
return result;
|
462
|
+
}
|
463
|
+
return null;
|
464
|
+
},
|
465
|
+
// Look for an AND or && operator (logical and)
|
466
|
+
and : function( filter, iFilter, exact, iExact ) {
|
467
|
+
if ( /\s+(AND|&&)\s+/g.test(filter) ) {
|
468
|
+
var query = iFilter.split( /(?:\s+(?:and|&&)\s+)/g ),
|
469
|
+
result = iExact.search( $.trim(query[0]) ) >= 0,
|
470
|
+
indx = query.length - 1;
|
471
|
+
while (result && indx) {
|
472
|
+
result = result && iExact.search( $.trim(query[indx]) ) >= 0;
|
473
|
+
indx--;
|
474
|
+
}
|
475
|
+
return result;
|
476
|
+
}
|
477
|
+
return null;
|
478
|
+
},
|
479
|
+
// Look for a range (using " to " or " - ") - see issue #166; thanks matzhu!
|
480
|
+
range : function( filter, iFilter, exact, iExact, cached, index, table, wo, parsed ) {
|
481
|
+
if ( /\s+(-|to)\s+/.test(iFilter) ) {
|
482
|
+
var result, tmp,
|
483
|
+
c = table.config,
|
484
|
+
query = iFilter.split(/(?: - | to )/), // make sure the dash is for a range and not indicating a negative number
|
485
|
+
range1 = ts.formatFloat(query[0].replace(ts.filter.regex.nondigit, ''), table),
|
486
|
+
range2 = ts.formatFloat(query[1].replace(ts.filter.regex.nondigit, ''), table);
|
487
|
+
// parse filter value in case we're comparing numbers (dates)
|
488
|
+
if (parsed[index] || c.parsers[index].type === 'numeric') {
|
489
|
+
result = c.parsers[index].format('' + query[0], table, c.$headers.eq(index), index);
|
490
|
+
range1 = (result !== '' && !isNaN(result)) ? result : range1;
|
491
|
+
result = c.parsers[index].format('' + query[1], table, c.$headers.eq(index), index);
|
492
|
+
range2 = (result !== '' && !isNaN(result)) ? result : range2;
|
493
|
+
}
|
494
|
+
result = ( parsed[index] || c.parsers[index].type === 'numeric' ) && !isNaN(range1) && !isNaN(range2) ? cached :
|
495
|
+
isNaN(iExact) ? ts.formatFloat( iExact.replace(ts.filter.regex.nondigit, ''), table) :
|
496
|
+
ts.formatFloat( iExact, table );
|
497
|
+
if (range1 > range2) { tmp = range1; range1 = range2; range2 = tmp; } // swap
|
498
|
+
return (result >= range1 && result <= range2) || (range1 === '' || range2 === '');
|
499
|
+
}
|
500
|
+
return null;
|
501
|
+
},
|
502
|
+
// Look for wild card: ? = single, * = multiple, or | = logical OR
|
503
|
+
wild : function( filter, iFilter, exact, iExact, cached, index, table ) {
|
504
|
+
if ( /[\?|\*]/.test(iFilter) || /\s+OR\s+/.test(filter) ) {
|
505
|
+
var c = table.config,
|
506
|
+
query = iFilter.replace(/\s+OR\s+/gi,"|");
|
507
|
+
// look for an exact match with the "or" unless the "filter-match" class is found
|
508
|
+
if (!c.$headers.filter('[data-column="' + index + '"]:last').hasClass('filter-match') && /\|/.test(query)) {
|
509
|
+
query = '^(' + query + ')$';
|
510
|
+
}
|
511
|
+
return new RegExp( query.replace(/\?/g, '\\S{1}').replace(/\*/g, '\\S*') ).test(iExact);
|
512
|
+
}
|
513
|
+
return null;
|
514
|
+
},
|
515
|
+
// fuzzy text search; modified from https://github.com/mattyork/fuzzy (MIT license)
|
516
|
+
fuzzy: function( filter, iFilter, exact, iExact ) {
|
517
|
+
if ( /^~/.test(iFilter) ) {
|
518
|
+
var indx,
|
519
|
+
patternIndx = 0,
|
520
|
+
len = iExact.length,
|
521
|
+
pattern = iFilter.slice(1);
|
522
|
+
for (indx = 0; indx < len; indx++) {
|
523
|
+
if (iExact[indx] === pattern[patternIndx]) {
|
524
|
+
patternIndx += 1;
|
525
|
+
}
|
526
|
+
}
|
527
|
+
if (patternIndx === pattern.length) {
|
528
|
+
return true;
|
529
|
+
}
|
530
|
+
return false;
|
531
|
+
}
|
532
|
+
return null;
|
533
|
+
}
|
534
|
+
},
|
535
|
+
init: function(table, c, wo) {
|
536
|
+
var options, string, $header, column, filters, time;
|
537
|
+
if (c.debug) {
|
538
|
+
time = new Date();
|
539
|
+
}
|
540
|
+
c.$table.addClass('hasFilters');
|
541
|
+
|
542
|
+
ts.filter.regex.child = new RegExp(c.cssChildRow);
|
543
|
+
ts.filter.regex.filtered = new RegExp(wo.filter_filteredRow);
|
544
|
+
|
545
|
+
// don't build filter row if columnFilters is false or all columns are set to "filter-false" - issue #156
|
546
|
+
if (wo.filter_columnFilters !== false && c.$headers.filter('.filter-false').length !== c.$headers.length) {
|
547
|
+
// build filter row
|
548
|
+
ts.filter.buildRow(table, c, wo);
|
549
|
+
}
|
550
|
+
|
551
|
+
c.$table.bind('addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search '.split(' ').join('.tsfilter '), function(event, filter) {
|
552
|
+
if ( !/(search|filterReset|filterEnd)/.test(event.type) ) {
|
553
|
+
event.stopPropagation();
|
554
|
+
ts.filter.buildDefault(table, true);
|
555
|
+
}
|
556
|
+
if (event.type === 'filterReset') {
|
557
|
+
ts.filter.searching(table, []);
|
558
|
+
} else if (event.type === 'filterEnd') {
|
559
|
+
ts.filter.buildDefault(table, true);
|
560
|
+
} else {
|
561
|
+
// send false argument to force a new search; otherwise if the filter hasn't changed, it will return
|
562
|
+
filter = event.type === 'search' ? filter : event.type === 'updateComplete' ? c.$table.data('lastSearch') : '';
|
563
|
+
ts.filter.searching(table, filter);
|
564
|
+
}
|
565
|
+
return false;
|
566
|
+
});
|
567
|
+
ts.filter.bindSearch( table, c.$table.find('input.tablesorter-filter') );
|
568
|
+
|
569
|
+
// reset button/link
|
570
|
+
if (wo.filter_reset) {
|
571
|
+
$(document).delegate(wo.filter_reset, 'click.tsfilter', function() {
|
572
|
+
// trigger a reset event, so other functions (filterFormatter) know when to reset
|
573
|
+
c.$table.trigger('filterReset');
|
574
|
+
});
|
575
|
+
}
|
576
|
+
if (wo.filter_functions) {
|
577
|
+
// column = column # (string)
|
578
|
+
for (column in wo.filter_functions) {
|
579
|
+
if (wo.filter_functions.hasOwnProperty(column) && typeof column === 'string') {
|
580
|
+
$header = c.$headers.filter('[data-column="' + column + '"]:last');
|
581
|
+
options = '';
|
582
|
+
if (wo.filter_functions[column] === true && !$header.hasClass('filter-false')) {
|
583
|
+
ts.filter.buildSelect(table, column);
|
584
|
+
} else if (typeof column === 'string' && !$header.hasClass('filter-false')) {
|
585
|
+
// add custom drop down list
|
586
|
+
for (string in wo.filter_functions[column]) {
|
587
|
+
if (typeof string === 'string') {
|
588
|
+
options += options === '' ?
|
589
|
+
'<option value="">' + ($header.data('placeholder') || $header.attr('data-placeholder') || '') + '</option>' : '';
|
590
|
+
options += '<option value="' + string + '">' + string + '</option>';
|
591
|
+
}
|
592
|
+
}
|
593
|
+
c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]').append(options);
|
594
|
+
}
|
595
|
+
}
|
596
|
+
}
|
597
|
+
}
|
598
|
+
// not really updating, but if the column has both the "filter-select" class & filter_functions set to true,
|
599
|
+
// it would append the same options twice.
|
600
|
+
ts.filter.buildDefault(table, true);
|
601
|
+
|
602
|
+
c.$table.find('select.tablesorter-filter').bind('change search', function(event, filter) {
|
603
|
+
ts.filter.checkFilters(table, filter);
|
604
|
+
});
|
605
|
+
|
606
|
+
if (wo.filter_hideFilters) {
|
607
|
+
ts.filter.hideFilters(table, c);
|
608
|
+
}
|
609
|
+
|
610
|
+
// show processing icon
|
611
|
+
if (c.showProcessing) {
|
612
|
+
c.$table.bind('filterStart.tsfilter filterEnd.tsfilter', function(event, columns) {
|
613
|
+
// only add processing to certain columns to all columns
|
614
|
+
$header = (columns) ? c.$table.find('.' + ts.css.header).filter('[data-column]').filter(function() {
|
615
|
+
return columns[$(this).data('column')] !== '';
|
616
|
+
}) : '';
|
617
|
+
ts.isProcessing(table, event.type === 'filterStart', columns ? $header : '');
|
618
|
+
});
|
619
|
+
}
|
620
|
+
|
621
|
+
if (c.debug) {
|
622
|
+
ts.benchmark("Applying Filter widget", time);
|
623
|
+
}
|
624
|
+
// add default values
|
625
|
+
c.$table.bind('tablesorter-initialized pagerInitialized', function() {
|
626
|
+
filters = ts.filter.setDefaults(table, c, wo) || [];
|
627
|
+
if (filters.length) {
|
628
|
+
ts.setFilters(table, filters, true);
|
629
|
+
}
|
630
|
+
ts.filter.checkFilters(table, filters);
|
631
|
+
});
|
632
|
+
// filter widget initialized
|
633
|
+
wo.filter_Initialized = true;
|
634
|
+
c.$table.trigger('filterInit');
|
635
|
+
},
|
636
|
+
setDefaults: function(table, c, wo) {
|
637
|
+
var indx, isArray,
|
638
|
+
filters = [],
|
639
|
+
columns = c.columns;
|
640
|
+
if (wo.filter_saveFilters && ts.storage) {
|
641
|
+
filters = ts.storage( table, 'tablesorter-filters' ) || [];
|
642
|
+
isArray = $.isArray(filters);
|
643
|
+
// make sure we're not just saving an empty array
|
644
|
+
if (isArray && filters.join('') === '' || !isArray ) { filters = []; }
|
645
|
+
}
|
646
|
+
// if not filters saved, then check default settings
|
647
|
+
if (!filters.length) {
|
648
|
+
for (indx = 0; indx < columns; indx++) {
|
649
|
+
filters[indx] = c.$headers.filter('[data-column="' + indx + '"]:last').attr(wo.filter_defaultAttrib) || filters[indx];
|
650
|
+
}
|
651
|
+
}
|
652
|
+
$(table).data('lastSearch', filters);
|
653
|
+
return filters;
|
654
|
+
},
|
655
|
+
buildRow: function(table, c, wo) {
|
656
|
+
var column, $header, buildSelect, disabled,
|
657
|
+
// c.columns defined in computeThIndexes()
|
658
|
+
columns = c.columns,
|
659
|
+
buildFilter = '<tr class="tablesorter-filter-row">';
|
660
|
+
for (column = 0; column < columns; column++) {
|
661
|
+
buildFilter += '<td></td>';
|
662
|
+
}
|
663
|
+
c.$filters = $(buildFilter += '</tr>').appendTo( c.$table.find('thead').eq(0) ).find('td');
|
664
|
+
// build each filter input
|
665
|
+
for (column = 0; column < columns; column++) {
|
666
|
+
disabled = false;
|
667
|
+
// assuming last cell of a column is the main column
|
668
|
+
$header = c.$headers.filter('[data-column="' + column + '"]:last');
|
669
|
+
buildSelect = (wo.filter_functions && wo.filter_functions[column] && typeof wo.filter_functions[column] !== 'function') ||
|
670
|
+
$header.hasClass('filter-select');
|
671
|
+
// get data from jQuery data, metadata, headers option or header class name
|
672
|
+
if (ts.getData) {
|
673
|
+
// get data from jQuery data, metadata, headers option or header class name
|
674
|
+
disabled = ts.getData($header[0], c.headers[column], 'filter') === 'false';
|
675
|
+
} else {
|
676
|
+
// only class names and header options - keep this for compatibility with tablesorter v2.0.5
|
677
|
+
disabled = (c.headers[column] && c.headers[column].hasOwnProperty('filter') && c.headers[column].filter === false) ||
|
678
|
+
$header.hasClass('filter-false');
|
679
|
+
}
|
680
|
+
if (buildSelect) {
|
681
|
+
buildFilter = $('<select>').appendTo( c.$filters.eq(column) );
|
682
|
+
} else {
|
683
|
+
if (wo.filter_formatter && $.isFunction(wo.filter_formatter[column])) {
|
684
|
+
buildFilter = wo.filter_formatter[column]( c.$filters.eq(column), column );
|
685
|
+
// no element returned, so lets go find it
|
686
|
+
if (buildFilter && buildFilter.length === 0) {
|
687
|
+
buildFilter = c.$filters.eq(column).children('input');
|
688
|
+
}
|
689
|
+
// element not in DOM, so lets attach it
|
690
|
+
if ( buildFilter && (buildFilter.parent().length === 0 ||
|
691
|
+
(buildFilter.parent().length && buildFilter.parent()[0] !== c.$filters[column])) ) {
|
692
|
+
c.$filters.eq(column).append(buildFilter);
|
693
|
+
}
|
694
|
+
} else {
|
695
|
+
buildFilter = $('<input type="search">').appendTo( c.$filters.eq(column) );
|
696
|
+
}
|
697
|
+
if (buildFilter) {
|
698
|
+
buildFilter.attr('placeholder', $header.data('placeholder') || $header.attr('data-placeholder') || '');
|
699
|
+
}
|
700
|
+
}
|
701
|
+
if (buildFilter) {
|
702
|
+
buildFilter.addClass('tablesorter-filter ' + wo.filter_cssFilter).attr('data-column', column);
|
703
|
+
if (disabled) {
|
704
|
+
buildFilter.addClass('disabled')[0].disabled = true; // disabled!
|
705
|
+
}
|
706
|
+
}
|
707
|
+
}
|
708
|
+
},
|
709
|
+
bindSearch: function(table, $el) {
|
710
|
+
table = $(table)[0];
|
711
|
+
var external, wo = table.config.widgetOptions;
|
712
|
+
$el.unbind('keyup search filterReset')
|
713
|
+
.bind('keyup search', function(event, filter) {
|
714
|
+
var $this = $(this);
|
715
|
+
// emulate what webkit does.... escape clears the filter
|
716
|
+
if (event.which === 27) {
|
717
|
+
this.value = '';
|
718
|
+
// liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace
|
719
|
+
} else if ( (typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch && this.value !== '') ||
|
720
|
+
( event.type === 'keyup' && ( (event.which < 32 && event.which !== 8 && wo.filter_liveSearch === true && event.which !== 13) ||
|
721
|
+
( event.which >= 37 && event.which <= 40 ) || (event.which !== 13 && wo.filter_liveSearch === false) ) ) ) {
|
722
|
+
return;
|
723
|
+
}
|
724
|
+
// external searches won't have a filter parameter, so grab the value
|
725
|
+
if ($this.hasClass('tablesorter-filter') && !$this.hasClass('tablesorter-external-filter')) {
|
726
|
+
external = filter;
|
727
|
+
} else {
|
728
|
+
external = [];
|
729
|
+
$el.each(function(){
|
730
|
+
// target the appropriate column if the external input has a data-column attribute
|
731
|
+
external[ $(this).data('column') || 0 ] = $(this).val();
|
732
|
+
});
|
733
|
+
}
|
734
|
+
ts.filter.searching(table, filter, external);
|
735
|
+
})
|
736
|
+
.bind('filterReset', function(){
|
737
|
+
$el.val('');
|
738
|
+
});
|
739
|
+
},
|
740
|
+
checkFilters: function(table, filter) {
|
741
|
+
var c = table.config,
|
742
|
+
wo = c.widgetOptions,
|
743
|
+
filterArray = $.isArray(filter),
|
744
|
+
filters = (filterArray) ? filter : ts.getFilters(table),
|
745
|
+
combinedFilters = (filters || []).join(''); // combined filter values
|
746
|
+
// add filter array back into inputs
|
747
|
+
if (filterArray) {
|
748
|
+
ts.setFilters( table, filters );
|
749
|
+
}
|
750
|
+
if (wo.filter_hideFilters) {
|
751
|
+
// show/hide filter row as needed
|
752
|
+
c.$table.find('.tablesorter-filter-row').trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
|
753
|
+
}
|
754
|
+
// return if the last search is the same; but filter === false when updating the search
|
755
|
+
// see example-widget-filter.html filter toggle buttons
|
756
|
+
if (c.lastCombinedFilter === combinedFilters && filter !== false) {
|
757
|
+
return;
|
758
|
+
} else if (filter === false) {
|
759
|
+
// force filter refresh
|
760
|
+
c.lastCombinedFilter = null;
|
761
|
+
}
|
762
|
+
c.$table.trigger('filterStart', [filters]);
|
763
|
+
if (c.showProcessing) {
|
764
|
+
// give it time for the processing icon to kick in
|
765
|
+
setTimeout(function() {
|
766
|
+
ts.filter.findRows(table, filters, combinedFilters);
|
767
|
+
return false;
|
768
|
+
}, 30);
|
769
|
+
} else {
|
770
|
+
ts.filter.findRows(table, filters, combinedFilters);
|
771
|
+
return false;
|
772
|
+
}
|
773
|
+
},
|
774
|
+
hideFilters: function(table, c) {
|
775
|
+
var $filterRow, $filterRow2, timer;
|
776
|
+
c.$table
|
777
|
+
.find('.tablesorter-filter-row')
|
778
|
+
.addClass('hideme')
|
779
|
+
.bind('mouseenter mouseleave', function(e) {
|
780
|
+
// save event object - http://bugs.jquery.com/ticket/12140
|
781
|
+
var event = e;
|
782
|
+
$filterRow = $(this);
|
783
|
+
clearTimeout(timer);
|
784
|
+
timer = setTimeout(function() {
|
785
|
+
if ( /enter|over/.test(event.type) ) {
|
786
|
+
$filterRow.removeClass('hideme');
|
787
|
+
} else {
|
788
|
+
// don't hide if input has focus
|
789
|
+
// $(':focus') needs jQuery 1.6+
|
790
|
+
if ( $(document.activeElement).closest('tr')[0] !== $filterRow[0] ) {
|
791
|
+
// don't hide row if any filter has a value
|
792
|
+
if (ts.getFilters(table).join('') === '') {
|
793
|
+
$filterRow.addClass('hideme');
|
794
|
+
}
|
795
|
+
}
|
796
|
+
}
|
797
|
+
}, 200);
|
798
|
+
})
|
799
|
+
.find('input, select').bind('focus blur', function(e) {
|
800
|
+
$filterRow2 = $(this).closest('tr');
|
801
|
+
clearTimeout(timer);
|
802
|
+
var event = e;
|
803
|
+
timer = setTimeout(function() {
|
804
|
+
// don't hide row if any filter has a value
|
805
|
+
if (ts.getFilters(table).join('') === '') {
|
806
|
+
$filterRow2[ event.type === 'focus' ? 'removeClass' : 'addClass']('hideme');
|
807
|
+
}
|
808
|
+
}, 200);
|
809
|
+
});
|
810
|
+
},
|
811
|
+
findRows: function(table, filters, combinedFilters) {
|
812
|
+
if (table.config.lastCombinedFilter === combinedFilters) { return; }
|
813
|
+
var cached, len, $rows, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
|
814
|
+
childRow, childRowText, exact, iExact, iFilter, lastSearch, matches, result,
|
815
|
+
searchFiltered, filterMatched, showRow, time,
|
816
|
+
c = table.config,
|
817
|
+
wo = c.widgetOptions,
|
818
|
+
columns = c.columns,
|
819
|
+
$tbodies = c.$tbodies,
|
820
|
+
// anyMatch really screws up with these types of filters
|
821
|
+
anyMatchNotAllowedTypes = [ 'range', 'notMatch', 'operators' ],
|
822
|
+
// parse columns after formatter, in case the class is added at that point
|
823
|
+
parsed = c.$headers.map(function(columnIndex) {
|
824
|
+
return (ts.getData) ?
|
825
|
+
ts.getData(c.$headers.filter('[data-column="' + columnIndex + '"]:last'), c.headers[columnIndex], 'filter') === 'parsed' :
|
826
|
+
$(this).hasClass('filter-parsed');
|
827
|
+
}).get();
|
828
|
+
if (c.debug) { time = new Date(); }
|
829
|
+
for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
|
830
|
+
if ($tbodies.eq(tbodyIndex).hasClass(ts.css.info)) { continue; } // ignore info blocks, issue #264
|
831
|
+
$tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true);
|
832
|
+
// skip child rows & widget added (removable) rows - fixes #448 thanks to @hempel!
|
833
|
+
$rows = $tbody.children('tr').not('.' + c.cssChildRow).not(c.selectorRemove);
|
834
|
+
len = $rows.length;
|
835
|
+
if (combinedFilters === '' || wo.filter_serversideFiltering) {
|
836
|
+
$tbody.children().show().removeClass(wo.filter_filteredRow);
|
837
|
+
} else {
|
838
|
+
// optimize searching only through already filtered rows - see #313
|
839
|
+
searchFiltered = true;
|
840
|
+
lastSearch = c.lastSearch || c.$table.data('lastSearch') || [];
|
841
|
+
$.each(filters, function(indx, val) {
|
842
|
+
// check for changes from beginning of filter; but ignore if there is a logical "or" in the string
|
843
|
+
searchFiltered = (val || '').indexOf(lastSearch[indx] || '') === 0 && searchFiltered && !/(\s+or\s+|\|)/g.test(val || '');
|
844
|
+
});
|
845
|
+
// can't search when all rows are hidden - this happens when looking for exact matches
|
846
|
+
if (searchFiltered && $rows.filter(':visible').length === 0) { searchFiltered = false; }
|
847
|
+
// loop through the rows
|
848
|
+
for (rowIndex = 0; rowIndex < len; rowIndex++) {
|
849
|
+
childRow = $rows[rowIndex].className;
|
850
|
+
// skip child rows & already filtered rows
|
851
|
+
if ( ts.filter.regex.child.test(childRow) || (searchFiltered && ts.filter.regex.filtered.test(childRow)) ) { continue; }
|
852
|
+
showRow = true;
|
853
|
+
// *** nextAll/nextUntil not supported by Zepto! ***
|
854
|
+
childRow = $rows.eq(rowIndex).nextUntil('tr:not(.' + c.cssChildRow + ')');
|
855
|
+
// so, if "table.config.widgetOptions.filter_childRows" is true and there is
|
856
|
+
// a match anywhere in the child row, then it will make the row visible
|
857
|
+
// checked here so the option can be changed dynamically
|
858
|
+
childRowText = (childRow.length && wo.filter_childRows) ? childRow.text() : '';
|
859
|
+
childRowText = wo.filter_ignoreCase ? childRowText.toLocaleLowerCase() : childRowText;
|
860
|
+
$cells = $rows.eq(rowIndex).children('td');
|
861
|
+
for (columnIndex = 0; columnIndex < columns; columnIndex++) {
|
862
|
+
// ignore if filter is empty or disabled
|
863
|
+
if (filters[columnIndex] || wo.filter_anyMatch) {
|
864
|
+
cached = c.cache[tbodyIndex].normalized[rowIndex][columnIndex];
|
865
|
+
// check if column data should be from the cell or from parsed data
|
866
|
+
if (wo.filter_useParsedData || parsed[columnIndex]) {
|
867
|
+
exact = cached;
|
868
|
+
} else {
|
869
|
+
// using older or original tablesorter
|
870
|
+
exact = $.trim($cells.eq(columnIndex).text());
|
871
|
+
exact = c.sortLocaleCompare ? ts.replaceAccents(exact) : exact; // issue #405
|
872
|
+
}
|
873
|
+
iExact = !ts.filter.regex.type.test(typeof exact) && wo.filter_ignoreCase ? exact.toLocaleLowerCase() : exact;
|
874
|
+
result = showRow; // if showRow is true, show that row
|
875
|
+
|
876
|
+
if (typeof filters[columnIndex] === "undefined" || filters[columnIndex] === null) {
|
877
|
+
filters[columnIndex] = wo.filter_anyMatch ? combinedFilters : filters[columnIndex];
|
878
|
+
}
|
879
|
+
|
880
|
+
// replace accents - see #357
|
881
|
+
filters[columnIndex] = c.sortLocaleCompare ? ts.replaceAccents(filters[columnIndex]) : filters[columnIndex];
|
882
|
+
// val = case insensitive, filters[columnIndex] = case sensitive
|
883
|
+
iFilter = wo.filter_ignoreCase ? filters[columnIndex].toLocaleLowerCase() : filters[columnIndex];
|
884
|
+
if (wo.filter_functions && wo.filter_functions[columnIndex]) {
|
885
|
+
if (wo.filter_functions[columnIndex] === true) {
|
886
|
+
// default selector; no "filter-select" class
|
887
|
+
result = (c.$headers.filter('[data-column="' + columnIndex + '"]:last').hasClass('filter-match')) ?
|
888
|
+
iExact.search(iFilter) >= 0 : filters[columnIndex] === exact;
|
889
|
+
} else if (typeof wo.filter_functions[columnIndex] === 'function') {
|
890
|
+
// filter callback( exact cell content, parser normalized content, filter input value, column index, jQuery row object )
|
891
|
+
result = wo.filter_functions[columnIndex](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
|
892
|
+
} else if (typeof wo.filter_functions[columnIndex][filters[columnIndex]] === 'function') {
|
893
|
+
// selector option function
|
894
|
+
result = wo.filter_functions[columnIndex][filters[columnIndex]](exact, cached, filters[columnIndex], columnIndex, $rows.eq(rowIndex));
|
895
|
+
}
|
896
|
+
} else {
|
897
|
+
filterMatched = null;
|
898
|
+
// cycle through the different filters
|
899
|
+
// filters return a boolean or null if nothing matches
|
900
|
+
$.each(ts.filter.types, function(type, typeFunction) {
|
901
|
+
if (!wo.filter_anyMatch || (wo.filter_anyMatch && $.inArray(type, anyMatchNotAllowedTypes) < 0)) {
|
902
|
+
matches = typeFunction( filters[columnIndex], iFilter, exact, iExact, cached, columnIndex, table, wo, parsed );
|
903
|
+
if (matches !== null) {
|
904
|
+
filterMatched = matches;
|
905
|
+
return false;
|
906
|
+
}
|
907
|
+
}
|
908
|
+
});
|
909
|
+
if (filterMatched !== null) {
|
910
|
+
result = filterMatched;
|
911
|
+
// Look for match, and add child row data for matching
|
912
|
+
} else {
|
913
|
+
exact = (iExact + childRowText).indexOf(iFilter);
|
914
|
+
result = ( (!wo.filter_startsWith && exact >= 0) || (wo.filter_startsWith && exact === 0) );
|
915
|
+
}
|
916
|
+
}
|
917
|
+
if (wo.filter_anyMatch) {
|
918
|
+
showRow = result;
|
919
|
+
if (showRow){
|
920
|
+
break;
|
921
|
+
}
|
922
|
+
} else {
|
923
|
+
showRow = (result) ? showRow : false;
|
924
|
+
}
|
925
|
+
}
|
926
|
+
}
|
927
|
+
$rows[rowIndex].style.display = (showRow ? '' : 'none');
|
928
|
+
$rows.eq(rowIndex)[showRow ? 'removeClass' : 'addClass'](wo.filter_filteredRow);
|
929
|
+
if (childRow.length) {
|
930
|
+
if (c.pager && c.pager.countChildRows || wo.pager_countChildRows || wo.filter_childRows) {
|
931
|
+
childRow[showRow ? 'removeClass' : 'addClass'](wo.filter_filteredRow); // see issue #396
|
932
|
+
}
|
933
|
+
childRow.toggle(showRow);
|
934
|
+
}
|
935
|
+
}
|
936
|
+
}
|
937
|
+
ts.processTbody(table, $tbody, false);
|
938
|
+
}
|
939
|
+
c.lastCombinedFilter = combinedFilters; // save last search
|
940
|
+
c.lastSearch = filters;
|
941
|
+
c.$table.data('lastSearch', filters);
|
942
|
+
if (wo.filter_saveFilters && ts.storage) {
|
943
|
+
ts.storage( table, 'tablesorter-filters', filters );
|
944
|
+
}
|
945
|
+
if (c.debug) {
|
946
|
+
ts.benchmark("Completed filter widget search", time);
|
947
|
+
}
|
948
|
+
c.$table.trigger('applyWidgets'); // make sure zebra widget is applied
|
949
|
+
c.$table.trigger('filterEnd');
|
950
|
+
},
|
951
|
+
buildSelect: function(table, column, updating, onlyavail) {
|
952
|
+
column = parseInt(column, 10);
|
953
|
+
var indx, rowIndex, tbodyIndex, len, currentValue, txt,
|
954
|
+
c = table.config,
|
955
|
+
wo = c.widgetOptions,
|
956
|
+
$tbodies = c.$tbodies,
|
957
|
+
arry = [],
|
958
|
+
node = c.$headers.filter('[data-column="' + column + '"]:last'),
|
959
|
+
// t.data('placeholder') won't work in jQuery older than 1.4.3
|
960
|
+
options = '<option value="">' + ( node.data('placeholder') || node.attr('data-placeholder') || '' ) + '</option>';
|
961
|
+
for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) {
|
962
|
+
len = c.cache[tbodyIndex].row.length;
|
963
|
+
// loop through the rows
|
964
|
+
for (rowIndex = 0; rowIndex < len; rowIndex++) {
|
965
|
+
// check if has class filtered
|
966
|
+
if (onlyavail && c.cache[tbodyIndex].row[rowIndex][0].className.match(wo.filter_filteredRow)) { continue; }
|
967
|
+
// get non-normalized cell content
|
968
|
+
if (wo.filter_useParsedData) {
|
969
|
+
arry.push( '' + c.cache[tbodyIndex].normalized[rowIndex][column] );
|
970
|
+
} else {
|
971
|
+
node = c.cache[tbodyIndex].row[rowIndex][0].cells[column];
|
972
|
+
if (node) {
|
973
|
+
arry.push( $.trim( node.textContent || node.innerText || $(node).text() ) );
|
974
|
+
}
|
975
|
+
}
|
976
|
+
}
|
977
|
+
}
|
978
|
+
// get unique elements and sort the list
|
979
|
+
// if $.tablesorter.sortText exists (not in the original tablesorter),
|
980
|
+
// then natural sort the list otherwise use a basic sort
|
981
|
+
arry = $.grep(arry, function(value, indx) {
|
982
|
+
return $.inArray(value, arry) === indx;
|
983
|
+
});
|
984
|
+
arry = (ts.sortNatural) ? arry.sort(function(a, b) { return ts.sortNatural(a, b); }) : arry.sort(true);
|
985
|
+
|
986
|
+
// Get curent filter value
|
987
|
+
currentValue = c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]').val();
|
988
|
+
|
989
|
+
// build option list
|
990
|
+
for (indx = 0; indx < arry.length; indx++) {
|
991
|
+
txt = arry[indx].replace(/\"/g, """);
|
992
|
+
// replace quotes - fixes #242 & ignore empty strings - see http://stackoverflow.com/q/14990971/145346
|
993
|
+
options += arry[indx] !== '' ? '<option value="' + txt + '"' + (currentValue === txt ? ' selected="selected"' : '') +
|
994
|
+
'>' + arry[indx] + '</option>' : '';
|
995
|
+
}
|
996
|
+
c.$table.find('thead').find('select.tablesorter-filter[data-column="' + column + '"]')[ updating ? 'html' : 'append' ](options);
|
997
|
+
},
|
998
|
+
buildDefault: function(table, updating) {
|
999
|
+
var columnIndex, $header,
|
1000
|
+
c = table.config,
|
1001
|
+
wo = c.widgetOptions,
|
1002
|
+
columns = c.columns;
|
1003
|
+
// build default select dropdown
|
1004
|
+
for (columnIndex = 0; columnIndex < columns; columnIndex++) {
|
1005
|
+
$header = c.$headers.filter('[data-column="' + columnIndex + '"]:last');
|
1006
|
+
// look for the filter-select class; build/update it if found
|
1007
|
+
if (($header.hasClass('filter-select') || wo.filter_functions && wo.filter_functions[columnIndex] === true) &&
|
1008
|
+
!$header.hasClass('filter-false')) {
|
1009
|
+
if (!wo.filter_functions) { wo.filter_functions = {}; }
|
1010
|
+
wo.filter_functions[columnIndex] = true; // make sure this select gets processed by filter_functions
|
1011
|
+
ts.filter.buildSelect(table, columnIndex, updating, $header.hasClass(wo.filter_onlyAvail));
|
1012
|
+
}
|
1013
|
+
}
|
1014
|
+
},
|
1015
|
+
searching: function(table, filter, external) {
|
1016
|
+
if (typeof filter === 'undefined' || filter === true || external) {
|
1017
|
+
var wo = table.config.widgetOptions;
|
1018
|
+
// delay filtering
|
1019
|
+
clearTimeout(wo.searchTimer);
|
1020
|
+
wo.searchTimer = setTimeout(function() {
|
1021
|
+
ts.filter.checkFilters(table, external || filter);
|
1022
|
+
}, wo.filter_liveSearch ? wo.filter_searchDelay : 10);
|
1023
|
+
} else {
|
1024
|
+
// skip delay
|
1025
|
+
ts.filter.checkFilters(table, filter);
|
1026
|
+
}
|
1027
|
+
}
|
1028
|
+
};
|
1029
|
+
|
1030
|
+
ts.getFilters = function(table) {
|
1031
|
+
var c = table ? $(table)[0].config : {};
|
1032
|
+
if (c && c.widgetOptions && !c.widgetOptions.filter_columnFilters) {
|
1033
|
+
// no filter row
|
1034
|
+
return $(table).data('lastSearch');
|
1035
|
+
}
|
1036
|
+
return c && c.$filters ? c.$filters.map(function(indx, el) {
|
1037
|
+
return $(el).find('.tablesorter-filter').val() || '';
|
1038
|
+
}).get() || [] : false;
|
1039
|
+
};
|
1040
|
+
|
1041
|
+
ts.setFilters = function(table, filter, apply) {
|
1042
|
+
var $table = $(table),
|
1043
|
+
c = $table.length ? $table[0].config : {},
|
1044
|
+
valid = c && c.$filters ? c.$filters.each(function(indx, el) {
|
1045
|
+
$(el).find('.tablesorter-filter').val(filter[indx] || '');
|
1046
|
+
}).trigger('change.tsfilter') || false : false;
|
1047
|
+
if (apply) { $table.trigger('search', [filter, false]); }
|
1048
|
+
return !!valid;
|
1049
|
+
};
|
1050
|
+
|
1051
|
+
// Widget: Sticky headers
|
1052
|
+
// based on this awesome article:
|
1053
|
+
// http://css-tricks.com/13465-persistent-headers/
|
1054
|
+
// and https://github.com/jmosbech/StickyTableHeaders by Jonas Mosbech
|
1055
|
+
// **************************
|
1056
|
+
ts.addWidget({
|
1057
|
+
id: "stickyHeaders",
|
1058
|
+
priority: 60, // sticky widget must be initialized after the filter widget!
|
1059
|
+
options: {
|
1060
|
+
stickyHeaders : '', // extra class name added to the sticky header row
|
1061
|
+
stickyHeaders_attachTo : null, // jQuery selector or object to attach sticky header to
|
1062
|
+
stickyHeaders_offset : 0, // number or jquery selector targeting the position:fixed element
|
1063
|
+
stickyHeaders_cloneId : '-sticky', // added to table ID, if it exists
|
1064
|
+
stickyHeaders_addResizeEvent : true, // trigger "resize" event on headers
|
1065
|
+
stickyHeaders_includeCaption : true, // if false and a caption exist, it won't be included in the sticky header
|
1066
|
+
stickyHeaders_zIndex : 2 // The zIndex of the stickyHeaders, allows the user to adjust this to their needs
|
1067
|
+
},
|
1068
|
+
format: function(table, c, wo) {
|
1069
|
+
// filter widget doesn't initialize on an empty table. Fixes #449
|
1070
|
+
if ( c.$table.hasClass('hasStickyHeaders') || ($.inArray('filter', c.widgets) >= 0 && !c.$table.hasClass('hasFilters')) ) {
|
1071
|
+
return;
|
1072
|
+
}
|
1073
|
+
var $cell,
|
1074
|
+
$table = c.$table,
|
1075
|
+
$attach = $(wo.stickyHeaders_attachTo),
|
1076
|
+
$thead = $table.children('thead:first'),
|
1077
|
+
$win = $attach.length ? $attach : $(window),
|
1078
|
+
$header = $thead.children('tr').not('.sticky-false').children(),
|
1079
|
+
innerHeader = '.tablesorter-header-inner',
|
1080
|
+
$tfoot = $table.find('tfoot'),
|
1081
|
+
filterInputs = '.tablesorter-filter',
|
1082
|
+
$stickyOffset = isNaN(wo.stickyHeaders_offset) ? $(wo.stickyHeaders_offset) : '',
|
1083
|
+
stickyOffset = $attach.length ? 0 : $stickyOffset.length ?
|
1084
|
+
$stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0,
|
1085
|
+
$stickyTable = wo.$sticky = $table.clone()
|
1086
|
+
.addClass('containsStickyHeaders')
|
1087
|
+
.css({
|
1088
|
+
position : $attach.length ? 'absolute' : 'fixed',
|
1089
|
+
margin : 0,
|
1090
|
+
top : stickyOffset,
|
1091
|
+
left : 0,
|
1092
|
+
visibility : 'hidden',
|
1093
|
+
zIndex : wo.stickyHeaders_zIndex ? wo.stickyHeaders_zIndex : 2
|
1094
|
+
}),
|
1095
|
+
$stickyThead = $stickyTable.children('thead:first').addClass('tablesorter-stickyHeader ' + wo.stickyHeaders),
|
1096
|
+
$stickyCells,
|
1097
|
+
laststate = '',
|
1098
|
+
spacing = 0,
|
1099
|
+
nonwkie = $table.css('border-collapse') !== 'collapse' && !/(webkit|msie)/i.test(navigator.userAgent),
|
1100
|
+
resizeHeader = function() {
|
1101
|
+
stickyOffset = $stickyOffset.length ? $stickyOffset.height() || 0 : parseInt(wo.stickyHeaders_offset, 10) || 0;
|
1102
|
+
spacing = 0;
|
1103
|
+
// yes, I dislike browser sniffing, but it really is needed here :(
|
1104
|
+
// webkit automatically compensates for border spacing
|
1105
|
+
if (nonwkie) {
|
1106
|
+
// Firefox & Opera use the border-spacing
|
1107
|
+
// update border-spacing here because of demos that switch themes
|
1108
|
+
spacing = parseInt($header.eq(0).css('border-left-width'), 10) * 2;
|
1109
|
+
}
|
1110
|
+
$stickyTable.css({
|
1111
|
+
left : $attach.length ? parseInt($attach.css('padding-left'), 10) +
|
1112
|
+
parseInt($attach.css('margin-left'), 10) + parseInt($table.css('border-left-width'), 10) :
|
1113
|
+
$thead.offset().left - $win.scrollLeft() - spacing,
|
1114
|
+
width: $table.width()
|
1115
|
+
});
|
1116
|
+
$stickyCells.filter(':visible').each(function(i) {
|
1117
|
+
var $cell = $header.filter(':visible').eq(i),
|
1118
|
+
// some wibbly-wobbly... timey-wimey... stuff, to make columns line up in Firefox
|
1119
|
+
offset = nonwkie && $(this).attr('data-column') === ( '' + parseInt(c.columns/2, 10) ) ? 1 : 0;
|
1120
|
+
$(this)
|
1121
|
+
.css({
|
1122
|
+
width: $cell.width() - spacing,
|
1123
|
+
height: $cell.height()
|
1124
|
+
})
|
1125
|
+
.find(innerHeader).width( $cell.find(innerHeader).width() - offset );
|
1126
|
+
});
|
1127
|
+
};
|
1128
|
+
// fix clone ID, if it exists - fixes #271
|
1129
|
+
if ($stickyTable.attr('id')) { $stickyTable[0].id += wo.stickyHeaders_cloneId; }
|
1130
|
+
// clear out cloned table, except for sticky header
|
1131
|
+
// include caption & filter row (fixes #126 & #249)
|
1132
|
+
$stickyTable.find('thead:gt(0), tr.sticky-false, tbody, tfoot').remove();
|
1133
|
+
if (!wo.stickyHeaders_includeCaption) {
|
1134
|
+
$stickyTable.find('caption').remove();
|
1135
|
+
} else {
|
1136
|
+
$stickyTable.find('caption').css( 'margin-left', '-1px' );
|
1137
|
+
}
|
1138
|
+
// issue #172 - find td/th in sticky header
|
1139
|
+
$stickyCells = $stickyThead.children().children();
|
1140
|
+
$stickyTable.css({ height:0, width:0, padding:0, margin:0, border:0 });
|
1141
|
+
// remove resizable block
|
1142
|
+
$stickyCells.find('.tablesorter-resizer').remove();
|
1143
|
+
// update sticky header class names to match real header after sorting
|
1144
|
+
$table
|
1145
|
+
.addClass('hasStickyHeaders')
|
1146
|
+
.bind('sortEnd.tsSticky', function() {
|
1147
|
+
$header.filter(':visible').each(function(indx) {
|
1148
|
+
$cell = $stickyCells.filter(':visible').eq(indx)
|
1149
|
+
.attr('class', $(this).attr('class'))
|
1150
|
+
// remove processing icon
|
1151
|
+
.removeClass(ts.css.processing + ' ' + c.cssProcessing);
|
1152
|
+
if (c.cssIcon) {
|
1153
|
+
$cell
|
1154
|
+
.find('.' + ts.css.icon)
|
1155
|
+
.attr('class', $(this).find('.' + ts.css.icon).attr('class'));
|
1156
|
+
}
|
1157
|
+
});
|
1158
|
+
})
|
1159
|
+
.bind('pagerComplete.tsSticky', function() {
|
1160
|
+
resizeHeader();
|
1161
|
+
});
|
1162
|
+
// http://stackoverflow.com/questions/5312849/jquery-find-self;
|
1163
|
+
$header.find(c.selectorSort).add( c.$headers.filter(c.selectorSort) ).each(function(indx) {
|
1164
|
+
var $header = $(this),
|
1165
|
+
// clicking on sticky will trigger sort
|
1166
|
+
$cell = $stickyThead.children('tr.tablesorter-headerRow').children().eq(indx).bind('mouseup', function(event) {
|
1167
|
+
$header.trigger(event, true); // external mouseup flag (click timer is ignored)
|
1168
|
+
});
|
1169
|
+
// prevent sticky header text selection
|
1170
|
+
if (c.cancelSelection) {
|
1171
|
+
$cell
|
1172
|
+
.attr('unselectable', 'on')
|
1173
|
+
.bind('selectstart', false)
|
1174
|
+
.css({
|
1175
|
+
'user-select': 'none',
|
1176
|
+
'MozUserSelect': 'none'
|
1177
|
+
});
|
1178
|
+
}
|
1179
|
+
});
|
1180
|
+
// add stickyheaders AFTER the table. If the table is selected by ID, the original one (first) will be returned.
|
1181
|
+
$table.after( $stickyTable );
|
1182
|
+
|
1183
|
+
// make it sticky!
|
1184
|
+
$win.bind('scroll.tsSticky resize.tsSticky', function(event) {
|
1185
|
+
if (!$table.is(':visible')) { return; } // fixes #278
|
1186
|
+
var prefix = 'tablesorter-sticky-',
|
1187
|
+
offset = $table.offset(),
|
1188
|
+
captionHeight = (wo.stickyHeaders_includeCaption ? 0 : $table.find('caption').outerHeight(true)),
|
1189
|
+
scrollTop = ($attach.length ? $attach.offset().top : $win.scrollTop()) + stickyOffset - captionHeight,
|
1190
|
+
tableHeight = $table.height() - ($stickyTable.height() + ($tfoot.height() || 0)),
|
1191
|
+
isVisible = (scrollTop > offset.top) && (scrollTop < offset.top + tableHeight) ? 'visible' : 'hidden',
|
1192
|
+
cssSettings = { visibility : isVisible };
|
1193
|
+
if ($attach.length) {
|
1194
|
+
cssSettings.top = $attach.scrollTop();
|
1195
|
+
} else {
|
1196
|
+
// adjust when scrolling horizontally - fixes issue #143
|
1197
|
+
cssSettings.left = $thead.offset().left - $win.scrollLeft() - spacing;
|
1198
|
+
}
|
1199
|
+
$stickyTable
|
1200
|
+
.removeClass(prefix + 'visible ' + prefix + 'hidden')
|
1201
|
+
.addClass(prefix + isVisible)
|
1202
|
+
.css(cssSettings);
|
1203
|
+
if (isVisible !== laststate || event.type === 'resize') {
|
1204
|
+
// make sure the column widths match
|
1205
|
+
resizeHeader();
|
1206
|
+
laststate = isVisible;
|
1207
|
+
}
|
1208
|
+
});
|
1209
|
+
if (wo.stickyHeaders_addResizeEvent) {
|
1210
|
+
ts.addHeaderResizeEvent(table);
|
1211
|
+
}
|
1212
|
+
|
1213
|
+
// look for filter widget
|
1214
|
+
if ($table.hasClass('hasFilters')) {
|
1215
|
+
$table.bind('filterEnd', function() {
|
1216
|
+
// $(':focus') needs jQuery 1.6+
|
1217
|
+
if ( $(document.activeElement).closest('thead')[0] !== $stickyThead[0] ) {
|
1218
|
+
// don't update the stickyheader filter row if it already has focus
|
1219
|
+
$stickyThead.find('.tablesorter-filter-row').children().each(function(indx) {
|
1220
|
+
$(this).find(filterInputs).val( c.$filters.find(filterInputs).eq(indx).val() );
|
1221
|
+
});
|
1222
|
+
}
|
1223
|
+
});
|
1224
|
+
|
1225
|
+
ts.filter.bindSearch( $table, $stickyCells.find('.tablesorter-filter').addClass('tablesorter-external-filter') );
|
1226
|
+
}
|
1227
|
+
|
1228
|
+
$table.trigger('stickyHeadersInit');
|
1229
|
+
|
1230
|
+
},
|
1231
|
+
remove: function(table, c, wo) {
|
1232
|
+
c.$table
|
1233
|
+
.removeClass('hasStickyHeaders')
|
1234
|
+
.unbind('sortEnd.tsSticky pagerComplete.tsSticky')
|
1235
|
+
.find('.tablesorter-stickyHeader').remove();
|
1236
|
+
if (wo.$sticky && wo.$sticky.length) { wo.$sticky.remove(); } // remove cloned table
|
1237
|
+
// don't unbind if any table on the page still has stickyheaders applied
|
1238
|
+
if (!$('.hasStickyHeaders').length) {
|
1239
|
+
$(window).unbind('scroll.tsSticky resize.tsSticky');
|
1240
|
+
}
|
1241
|
+
ts.addHeaderResizeEvent(table, false);
|
1242
|
+
}
|
1243
|
+
});
|
1244
|
+
|
1245
|
+
// Add Column resizing widget
|
1246
|
+
// this widget saves the column widths if
|
1247
|
+
// $.tablesorter.storage function is included
|
1248
|
+
// **************************
|
1249
|
+
ts.addWidget({
|
1250
|
+
id: "resizable",
|
1251
|
+
priority: 40,
|
1252
|
+
options: {
|
1253
|
+
resizable : true,
|
1254
|
+
resizable_addLastColumn : false
|
1255
|
+
},
|
1256
|
+
format: function(table, c, wo) {
|
1257
|
+
if (c.$table.hasClass('hasResizable')) { return; }
|
1258
|
+
c.$table.addClass('hasResizable');
|
1259
|
+
var $rows, $columns, $column, column,
|
1260
|
+
storedSizes = {},
|
1261
|
+
$table = c.$table,
|
1262
|
+
mouseXPosition = 0,
|
1263
|
+
$target = null,
|
1264
|
+
$next = null,
|
1265
|
+
fullWidth = Math.abs($table.parent().width() - $table.width()) < 20,
|
1266
|
+
stopResize = function() {
|
1267
|
+
if (ts.storage && $target) {
|
1268
|
+
storedSizes[$target.index()] = $target.width();
|
1269
|
+
storedSizes[$next.index()] = $next.width();
|
1270
|
+
$target.width( storedSizes[$target.index()] );
|
1271
|
+
$next.width( storedSizes[$next.index()] );
|
1272
|
+
if (wo.resizable !== false) {
|
1273
|
+
ts.storage(table, 'tablesorter-resizable', storedSizes);
|
1274
|
+
}
|
1275
|
+
}
|
1276
|
+
mouseXPosition = 0;
|
1277
|
+
$target = $next = null;
|
1278
|
+
$(window).trigger('resize'); // will update stickyHeaders, just in case
|
1279
|
+
};
|
1280
|
+
storedSizes = (ts.storage && wo.resizable !== false) ? ts.storage(table, 'tablesorter-resizable') : {};
|
1281
|
+
// process only if table ID or url match
|
1282
|
+
if (storedSizes) {
|
1283
|
+
for (column in storedSizes) {
|
1284
|
+
if (!isNaN(column) && column < c.$headers.length) {
|
1285
|
+
c.$headers.eq(column).width(storedSizes[column]); // set saved resizable widths
|
1286
|
+
}
|
1287
|
+
}
|
1288
|
+
}
|
1289
|
+
$rows = $table.children('thead:first').children('tr');
|
1290
|
+
// add resizable-false class name to headers (across rows as needed)
|
1291
|
+
$rows.children().each(function() {
|
1292
|
+
var canResize,
|
1293
|
+
$column = $(this);
|
1294
|
+
column = $column.attr('data-column');
|
1295
|
+
canResize = ts.getData( $column, c.headers[column], 'resizable') === "false";
|
1296
|
+
$rows.children().filter('[data-column="' + column + '"]')[canResize ? 'addClass' : 'removeClass']('resizable-false');
|
1297
|
+
});
|
1298
|
+
// add wrapper inside each cell to allow for positioning of the resizable target block
|
1299
|
+
$rows.each(function() {
|
1300
|
+
$column = $(this).children().not('.resizable-false');
|
1301
|
+
if (!$(this).find('.tablesorter-wrapper').length) {
|
1302
|
+
// Firefox needs this inner div to position the resizer correctly
|
1303
|
+
$column.wrapInner('<div class="tablesorter-wrapper" style="position:relative;height:100%;width:100%"></div>');
|
1304
|
+
}
|
1305
|
+
// don't include the last column of the row
|
1306
|
+
if (!wo.resizable_addLastColumn) { $column = $column.slice(0,-1); }
|
1307
|
+
$columns = $columns ? $columns.add($column) : $column;
|
1308
|
+
});
|
1309
|
+
$columns
|
1310
|
+
.each(function() {
|
1311
|
+
var $column = $(this),
|
1312
|
+
padding = parseInt($column.css('padding-right'), 10) + 10; // 10 is 1/2 of the 20px wide resizer grip
|
1313
|
+
$column
|
1314
|
+
.find('.tablesorter-wrapper')
|
1315
|
+
.append('<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;z-index:1;right:-' +
|
1316
|
+
padding + 'px;top:0;height:100%;width:20px;"></div>');
|
1317
|
+
})
|
1318
|
+
.bind('mousemove.tsresize', function(event) {
|
1319
|
+
// ignore mousemove if no mousedown
|
1320
|
+
if (mouseXPosition === 0 || !$target) { return; }
|
1321
|
+
// resize columns
|
1322
|
+
var leftEdge = event.pageX - mouseXPosition,
|
1323
|
+
targetWidth = $target.width();
|
1324
|
+
$target.width( targetWidth + leftEdge );
|
1325
|
+
if ($target.width() !== targetWidth && fullWidth) {
|
1326
|
+
$next.width( $next.width() - leftEdge );
|
1327
|
+
}
|
1328
|
+
mouseXPosition = event.pageX;
|
1329
|
+
})
|
1330
|
+
.bind('mouseup.tsresize', function() {
|
1331
|
+
stopResize();
|
1332
|
+
})
|
1333
|
+
.find('.tablesorter-resizer,.tablesorter-resizer-grip')
|
1334
|
+
.bind('mousedown', function(event) {
|
1335
|
+
// save header cell and mouse position; closest() not supported by jQuery v1.2.6
|
1336
|
+
$target = $(event.target).closest('th');
|
1337
|
+
var $header = c.$headers.filter('[data-column="' + $target.attr('data-column') + '"]');
|
1338
|
+
if ($header.length > 1) { $target = $target.add($header); }
|
1339
|
+
// if table is not as wide as it's parent, then resize the table
|
1340
|
+
$next = event.shiftKey ? $target.parent().find('th').not('.resizable-false').filter(':last') : $target.nextAll(':not(.resizable-false)').eq(0);
|
1341
|
+
mouseXPosition = event.pageX;
|
1342
|
+
});
|
1343
|
+
$table.find('thead:first')
|
1344
|
+
.bind('mouseup.tsresize mouseleave.tsresize', function() {
|
1345
|
+
stopResize();
|
1346
|
+
})
|
1347
|
+
// right click to reset columns to default widths
|
1348
|
+
.bind('contextmenu.tsresize', function() {
|
1349
|
+
ts.resizableReset(table);
|
1350
|
+
// $.isEmptyObject() needs jQuery 1.4+; allow right click if already reset
|
1351
|
+
var allowClick = $.isEmptyObject ? $.isEmptyObject(storedSizes) : true;
|
1352
|
+
storedSizes = {};
|
1353
|
+
return allowClick;
|
1354
|
+
});
|
1355
|
+
},
|
1356
|
+
remove: function(table, c) {
|
1357
|
+
c.$table
|
1358
|
+
.removeClass('hasResizable')
|
1359
|
+
.children('thead')
|
1360
|
+
.unbind('mouseup.tsresize mouseleave.tsresize contextmenu.tsresize')
|
1361
|
+
.children('tr').children()
|
1362
|
+
.unbind('mousemove.tsresize mouseup.tsresize')
|
1363
|
+
// don't remove "tablesorter-wrapper" as uitheme uses it too
|
1364
|
+
.find('.tablesorter-resizer,.tablesorter-resizer-grip').remove();
|
1365
|
+
ts.resizableReset(table);
|
1366
|
+
}
|
1367
|
+
});
|
1368
|
+
ts.resizableReset = function(table) {
|
1369
|
+
table.config.$headers.not('.resizable-false').css('width','');
|
1370
|
+
if (ts.storage) { ts.storage(table, 'tablesorter-resizable', {}); }
|
1371
|
+
};
|
1372
|
+
|
1373
|
+
// Save table sort widget
|
1374
|
+
// this widget saves the last sort only if the
|
1375
|
+
// saveSort widget option is true AND the
|
1376
|
+
// $.tablesorter.storage function is included
|
1377
|
+
// **************************
|
1378
|
+
ts.addWidget({
|
1379
|
+
id: 'saveSort',
|
1380
|
+
priority: 20,
|
1381
|
+
options: {
|
1382
|
+
saveSort : true
|
1383
|
+
},
|
1384
|
+
init: function(table, thisWidget, c, wo) {
|
1385
|
+
// run widget format before all other widgets are applied to the table
|
1386
|
+
thisWidget.format(table, c, wo, true);
|
1387
|
+
},
|
1388
|
+
format: function(table, c, wo, init) {
|
1389
|
+
var stored, time,
|
1390
|
+
$table = c.$table,
|
1391
|
+
saveSort = wo.saveSort !== false, // make saveSort active/inactive; default to true
|
1392
|
+
sortList = { "sortList" : c.sortList };
|
1393
|
+
if (c.debug) {
|
1394
|
+
time = new Date();
|
1395
|
+
}
|
1396
|
+
if ($table.hasClass('hasSaveSort')) {
|
1397
|
+
if (saveSort && table.hasInitialized && ts.storage) {
|
1398
|
+
ts.storage( table, 'tablesorter-savesort', sortList );
|
1399
|
+
if (c.debug) {
|
1400
|
+
ts.benchmark('saveSort widget: Saving last sort: ' + c.sortList, time);
|
1401
|
+
}
|
1402
|
+
}
|
1403
|
+
} else {
|
1404
|
+
// set table sort on initial run of the widget
|
1405
|
+
$table.addClass('hasSaveSort');
|
1406
|
+
sortList = '';
|
1407
|
+
// get data
|
1408
|
+
if (ts.storage) {
|
1409
|
+
stored = ts.storage( table, 'tablesorter-savesort' );
|
1410
|
+
sortList = (stored && stored.hasOwnProperty('sortList') && $.isArray(stored.sortList)) ? stored.sortList : '';
|
1411
|
+
if (c.debug) {
|
1412
|
+
ts.benchmark('saveSort: Last sort loaded: "' + sortList + '"', time);
|
1413
|
+
}
|
1414
|
+
$table.bind('saveSortReset', function(event) {
|
1415
|
+
event.stopPropagation();
|
1416
|
+
ts.storage( table, 'tablesorter-savesort', '' );
|
1417
|
+
});
|
1418
|
+
}
|
1419
|
+
// init is true when widget init is run, this will run this widget before all other widgets have initialized
|
1420
|
+
// this method allows using this widget in the original tablesorter plugin; but then it will run all widgets twice.
|
1421
|
+
if (init && sortList && sortList.length > 0) {
|
1422
|
+
c.sortList = sortList;
|
1423
|
+
} else if (table.hasInitialized && sortList && sortList.length > 0) {
|
1424
|
+
// update sort change
|
1425
|
+
$table.trigger('sorton', [sortList]);
|
1426
|
+
}
|
1427
|
+
}
|
1428
|
+
},
|
1429
|
+
remove: function(table) {
|
1430
|
+
// clear storage
|
1431
|
+
if (ts.storage) { ts.storage( table, 'tablesorter-savesort', '' ); }
|
1432
|
+
}
|
1433
|
+
});
|
1434
|
+
|
1435
|
+
})(jQuery);
|