-
Notifications
You must be signed in to change notification settings - Fork 64
/
Copy pathjquery.filtertable.js
402 lines (369 loc) · 17.9 KB
/
jquery.filtertable.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/**
* jquery.filterTable
*
* This plugin will add a search filter to tables. When typing in the filter,
* any rows that do not contain the filter will be hidden.
*
* Utilizes bindWithDelay() if available. https://github.com/bgrins/bindWithDelay
*
* @version v1.5.7
* @author Sunny Walker, [email protected]
* @license MIT
*/
(function ($) {
var jversion = $.fn.jquery.split('.'),
jmajor = parseFloat(jversion[0]),
jminor = parseFloat(jversion[1]);
// build the pseudo selector for jQuery < 1.8
if (jmajor < 2 && jminor < 8) {
// build the case insensitive filtering functionality as a pseudo-selector expression
$.expr[':'].filterTableFind = function (a, i, m) {
return $(a).text().toUpperCase().indexOf(m[3].toUpperCase().replace(/"""/g, '"').replace(/"\\"/g, "\\")) >= 0;
};
// build the case insensitive all-words filtering functionality as a pseudo-selector expression
$.expr[':'].filterTableFindAny = function (a, i, m) {
// build an array of each non-falsey value passed
var raw_args = m[3].split(/[\s,]/),
args = [];
$.each(raw_args, function (j, v) {
var t = v.replace(/^\s+|\s$/g, '');
if (t) {
args.push(t);
}
});
// if there aren't any non-falsey values to search for, abort
if (!args.length) {
return false;
}
return function (a) {
var found = false;
$.each(args, function (j, v) {
if ($(a).text().toUpperCase().indexOf(v.toUpperCase().replace(/"""/g, '"').replace(/"\\"/g, "\\")) >= 0) {
found = true;
return false;
}
});
return found;
};
};
// build the case insensitive all-words filtering functionality as a pseudo-selector expression
$.expr[':'].filterTableFindAll = function (a, i, m) {
// build an array of each non-falsey value passed
var raw_args = m[3].split(/[\s,]/),
args = [];
$.each(raw_args, function (j, v) {
var t = v.replace(/^\s+|\s$/g, '');
if (t) {
args.push(t);
}
});
// if there aren't any non-falsey values to search for, abort
if (!args.length) {
return false;
}
return function (a) {
// how many terms were found?
var found = 0;
$.each(args, function (j, v) {
if ($(a).text().toUpperCase().indexOf(v.toUpperCase().replace(/"""/g, '"').replace(/"\\"/g, "\\")) >= 0) {
// found another term
found++;
}
});
return found === args.length; // did we find all of them in this cell?
};
};
} else {
// build the pseudo selector for jQuery >= 1.8
$.expr[':'].filterTableFind = jQuery.expr.createPseudo(function (arg) {
return function (el) {
return $(el).text().toUpperCase().indexOf(arg.toUpperCase().replace(/"""/g, '"').replace(/"\\"/g, "\\")) >= 0;
};
});
$.expr[':'].filterTableFindAny = jQuery.expr.createPseudo(function (arg) {
// build an array of each non-falsey value passed
var raw_args = arg.split(/[\s,]/),
args = [];
$.each(raw_args, function (i, v) {
// trim the string
var t = v.replace(/^\s+|\s$/g, '');
if (t) {
args.push(t);
}
});
// if there aren't any non-falsey values to search for, abort
if (!args.length) {
return false;
}
return function (el) {
var found = false;
$.each(args, function (i, v) {
if ($(el).text().toUpperCase().indexOf(v.toUpperCase().replace(/"""/g, '"').replace(/"\\"/g, "\\")) >= 0) {
found = true;
// short-circuit the searching since this cell has one of the terms
return false;
}
});
return found;
};
});
$.expr[':'].filterTableFindAll = jQuery.expr.createPseudo(function (arg) {
// build an array of each non-falsey value passed
var raw_args = arg.split(/[\s,]/),
args = [];
$.each(raw_args, function (i, v) {
// trim the string
var t = v.replace(/^\s+|\s$/g, '');
if (t) {
args.push(t);
}
});
// if there aren't any non-falsey values to search for, abort
if (!args.length) {
return false;
}
return function (el) {
// how many terms were found?
var found = 0;
$.each(args, function (i, v) {
if ($(el).text().toUpperCase().indexOf(v.toUpperCase().replace(/"""/g, '"').replace(/"\\"/g, "\\")) >= 0) {
// found another term
found++;
}
});
// did we find all of them in this cell?
return found === args.length;
};
});
}
// define the filterTable plugin
$.fn.filterTable = function (options) {
// start off with some default settings
var defaults = {
// make the filter input field autofocused (not recommended for accessibility)
autofocus: false,
// callback function: function (term, table){}
callback: null,
// class to apply to the container
containerClass: 'filter-table',
// tag name of the container
containerTag: 'p',
// jQuery expression method to use for filtering
filterExpression: 'filterTableFind',
// if true, the table's tfoot(s) will be hidden when the table is filtered
hideTFootOnFilter: false,
// class applied to cells containing the filter term
highlightClass: 'alt',
// don't filter the contents of cells with this class
ignoreClass: '',
// don't filter the contents of these columns
ignoreColumns: [],
// use the element with this selector for the filter input field instead of creating one
inputSelector: null,
// name of filter input field
inputName: '',
// tag name of the filter input tag
inputType: 'search',
// text to precede the filter input tag
label: 'Filter:',
// filter only when at least this number of characters are in the filter input field
minChars: 1,
// don't show the filter on tables with at least this number of rows
minRows: 8,
// HTML5 placeholder text for the filter field
placeholder: 'search this table',
// prevent the return key in the filter input field from trigger form submits
preventReturnKey: true,
// list of phrases to quick fill the search
quickList: [],
// class of each quick list item
quickListClass: 'quick',
// quick list item label to clear the filter (e.g., '× Clear filter')
quickListClear: '',
// tag surrounding quick list items (e.g., ul)
quickListGroupTag: '',
// tag type of each quick list item (e.g., a or li)
quickListTag: 'a',
// class applied to visible rows
visibleClass: 'visible'
},
// mimic PHP's htmlspecialchars() function
hsc = function (text) {
return text.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
},
// merge the user's settings into the defaults
settings = $.extend({}, defaults, options);
// handle the actual table filtering
var doFiltering = function (table, q) {
// cache the tbody element
var tbody = table.find('tbody');
// if the filtering query is blank or the number of chars is less than the minChars option
if (q === '' || q.length < settings.minChars) {
// show all rows
tbody.find('tr').show().addClass(settings.visibleClass);
// remove the row highlight from all cells
tbody.find('td').removeClass(settings.highlightClass);
// show footer if the setting was specified
if (settings.hideTFootOnFilter) {
table.find('tfoot').show();
}
} else {
// if the filter query is not blank
var all_tds = tbody.find('td');
// hide all rows, assuming none were found
tbody.find('tr').hide().removeClass(settings.visibleClass);
// remove previous highlights
all_tds.removeClass(settings.highlightClass);
// hide footer if the setting was specified
if (settings.hideTFootOnFilter) {
table.find('tfoot').hide();
}
if (settings.ignoreColumns.length) {
var tds = [];
if (settings.ignoreClass) {
all_tds = all_tds.not('.' + settings.ignoreClass);
}
tds = all_tds.filter(':' + settings.filterExpression + '("' + q + '")');
tds.each(function () {
var t = $(this),
col = t.parent().children().index(t);
if ($.inArray(col, settings.ignoreColumns) === -1) {
t.addClass(settings.highlightClass).closest('tr').show().addClass(settings.visibleClass);
}
});
} else {
if (settings.ignoreClass) {
all_tds = all_tds.not('.' + settings.ignoreClass);
}
// highlight (class=alt) only the cells that match the query and show their rows
all_tds.filter(':' + settings.filterExpression + '("' + q + '")').addClass(settings.highlightClass).closest('tr').show().addClass(settings.visibleClass);
}
}
// call the callback function
if (settings.callback) {
settings.callback(q, table);
}
}; // doFiltering()
return this.each(function () {
// cache the table
var t = $(this),
// cache the tbody
tbody = t.find('tbody'),
// placeholder for the filter field container DOM node
container = null,
// placeholder for the quick list items
quicks = null,
// placeholder for the field field DOM node
filter = null,
// was the filter created or chosen from an existing element?
created_filter = true;
// only if object is a table and there's a tbody and at least minRows trs and hasn't already had a filter added
if (t[0].nodeName === 'TABLE' && tbody.length > 0 && (settings.minRows === 0 || (settings.minRows > 0 && tbody.find('tr').length >= settings.minRows)) && !t.prev().hasClass(settings.containerClass)) {
// use a single existing field as the filter input field
if (settings.inputSelector && $(settings.inputSelector).length === 1) {
filter = $(settings.inputSelector);
// container to hold the quick list options
container = filter.parent();
created_filter = false;
} else {
// create the filter input field (and container)
// build the container tag for the filter field
container = $('<' + settings.containerTag + ' />');
// add any classes that need to be added
if (settings.containerClass !== '') {
container.addClass(settings.containerClass);
}
// add the label for the filter field
container.prepend(settings.label + ' ');
// build the filter field
filter = $('<input type="' + settings.inputType + '" placeholder="' + settings.placeholder + '" name="' + settings.inputName + '" />');
// prevent return in the filter field from submitting any forms
if (settings.preventReturnKey) {
filter.on('keydown', function (ev) {
if ((ev.keyCode || ev.which) === 13) {
ev.preventDefault();
return false;
}
});
}
}
// add the autofocus attribute if requested
if (settings.autofocus) {
filter.attr('autofocus', true);
}
// does bindWithDelay() exist?
if ($.fn.bindWithDelay) {
// bind doFiltering() to keyup (delayed)
filter.bindWithDelay('keyup', function () {
doFiltering(t, $(this).val());
}, 200);
} else {
// just bind to onKeyUp
// bind doFiltering() to keyup
filter.bind('keyup', function () {
doFiltering(t, $(this).val());
});
}
// bind doFiltering() to additional events
filter.bind('click search input paste blur', function () {
doFiltering(t, $(this).val());
});
// add the filter field to the container if it was created by the plugin
if (created_filter) {
container.append(filter);
}
// are there any quick list items to add?
if (settings.quickList.length > 0 || settings.quickListClear) {
quicks = settings.quickListGroupTag ? $('<' + settings.quickListGroupTag + ' />') : container;
// for each quick list item...
$.each(settings.quickList, function (index, value) {
// build the quick list item link
var q = $('<' + settings.quickListTag + ' class="' + settings.quickListClass + '" />');
// add the item's text
q.text(hsc(value));
if (q[0].nodeName === 'A') {
// add a (worthless) href to the item if it's an anchor tag so that it gets the browser's link treatment
q.attr('href', '#');
}
// bind the click event to it
q.bind('click', function (e) {
// stop the normal anchor tag behavior from happening
e.preventDefault();
// send the quick list value over to the filter field and trigger the event
filter.val(value).focus().trigger('click');
});
// add the quick list link to the quick list groups container
quicks.append(q);
});
// add the quick list clear item if a label has been specified
if (settings.quickListClear) {
// build the clear item
var q = $('<' + settings.quickListTag + ' class="' + settings.quickListClass + '" />');
// add the label text
q.html(settings.quickListClear);
if (q[0].nodeName === 'A') {
// add a (worthless) href to the item if it's an anchor tag so that it gets the browser's link treatment
q.attr('href', '#');
}
// bind the click event to it
q.bind('click', function (e) {
e.preventDefault();
// clear the quick list value and trigger the event
filter.val('').focus().trigger('click');
});
// add the clear item to the quick list groups container
quicks.append(q);
}
// add the quick list groups container to the DOM if it isn't already there
if (quicks !== container) {
container.append(quicks);
}
}
// add the filter field and quick list container to just before the table if it was created by the plugin
if (created_filter) {
t.before(container);
}
}
}); // return this.each
}; // $.fn.filterTable
})(jQuery);