NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name jquery.filtertable // @description Filtering tables // @version 1.0 // @author Sterling[1616063] // @license MIT // @copyright 2021, denyull (https://openuserjs.org/users/denyull) // ==/UserScript== (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);