Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.248 diff -u -r1.248 system.admin.inc --- modules/system/system.admin.inc 9 Jan 2010 23:03:21 -0000 1.248 +++ modules/system/system.admin.inc 11 Jan 2010 02:16:29 -0000 @@ -958,6 +958,24 @@ ); $form['#action'] = url('admin/modules/list/confirm'); + $js_settings = array( + 'instantfilter' => array( + 'system-modules' => array( + 'label' => t('Filter modules'), + 'description' => t('Enter the name of a module to filter the list.'), + 'itemSelector' => 'tbody > tr', + 'ignoreSelector' => '.admin-requirements', + 'groupSelector' => 'fieldset, table' + ), + ), + ); + + $form['#attached'] = array( + 'js' => array( + array('data' => $js_settings, 'type' => 'setting'), + ), + 'library' => array(array('system', 'instantfilter')), + ); return $form; } Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.873 diff -u -r1.873 system.module --- modules/system/system.module 10 Jan 2010 00:09:37 -0000 1.873 +++ modules/system/system.module 11 Jan 2010 02:16:29 -0000 @@ -1117,6 +1117,19 @@ ), ); + // Instant Filter. + $libraries['instantfilter'] = array( + 'title' => 'Instant Filter', + 'website' => 'http://drupal.org/node/396478', + 'version' => '1.0', + 'js' => array( + 'misc/instantfilter.js' => array(), + ), + 'css' => array( + 'misc/instantfilter.css' => array(), + ), + ); + // Farbtastic. $libraries['farbtastic'] = array( 'title' => 'Farbtastic', Index: misc/instantfilter.js =================================================================== RCS file: misc/instantfilter.js diff -N misc/instantfilter.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/instantfilter.js 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,239 @@ +// $Id$ +(function ($) { + +/** + * Attaches the instantfilter behavior. + */ +Drupal.behaviors.instantFilter = { + attach: function (context, settings) { + // Bind instantfilter behaviors specified in the settings. + for (var base in settings.instantfilter) { + var container = $('#' + base); + if (!container.hasClass('.instantfilter-processed')) { + container + .addClass('instantfilter-processed') + .data('instantfilter', new Drupal.instantFilter(container, settings.instantfilter[base])); + } + } + + // Bind instantfilter behaviors to all items showing the class. + $('.instantfilter-container', context).once('instantfilter', function () { + $.data(this, 'instantfilter', new Drupal.instantFilter($(this), {})); + }); + + // If context has an parent with the instantfilter class, then it's added + // dynamicly (e.g. using AJAX). If so, the index needs to be rebuild. + $(context).closest('.instantfilter-processed').each(function () { + $(this).data('instantfilter').rebuildIndex(); + }); + } +}; + +/** + * The tableFilter object. + */ +Drupal.instantFilter = function (container, settings) { + this.settings = $.extend(Drupal.instantFilter.defaultSettings, settings); + this.container = (container.is('form') ? container.children('div') : container); + + // Create the filter textfield. + this.createTextfield(); +}; + +/** + * TODO + */ +Drupal.instantFilter.defaultSettings = { + itemSelector : '.instantfilter-item', + ignoreSelector : '.instantfilter-ignore', + groupSelector : '.instantfilter-group', + updateItemZebra : true, + updateGroupZebra : false, + + label : Drupal.t('Filter'), + description : Drupal.t('Enter text to filter the table below.'), + empty : Drupal.t('There were no results.') +}; + +/** + * Creates an input textfield inside the container. + */ +Drupal.instantFilter.prototype.createTextfield = function () { + var self = this; + + this.container.prepend(Drupal.theme('textfield', this.settings)); + this.textfield = $('#edit-' + this.settings.name, this.container).bind('click.drupal-instantfilter keyup.drupal-instantfilter', function () { + self.filter($.trim($(this).val().toLowerCase())); + }); + + if (this.textfield[0]) { + this.textfield[0].type = 'search'; + this.textfield.attr('results', 5); + }; +}; + +/** + * Adds a DOM element after the textfield element and displays a 'no results' message. + */ +Drupal.instantFilter.prototype.addPlaceholder = function () { + if (!this.container.find('.instantfilter-no-results').length) { + this.textfield.closest('.form-item').after($('

').text(this.settings.empty)); + }; +}; + +/** + * Removes the 'no results' message. + */ +Drupal.instantFilter.prototype.removePlaceholder = function () { + this.container.find('.instantfilter-no-results').remove(); +}; + +/** + * Get text value of an item. + */ +Drupal.instantFilter.prototype.getText = function (element) { + var text = ''; + if (!$(element).is(this.settings.ignoreSelector)) { + for (var i = 0; i < element.childNodes.length; i++) { + if (element.childNodes[i].nodeType == 1) { + text += this.getText(element.childNodes[i]); + } + else if (element.childNodes[i].nodeType != 8) { + text += element.childNodes[i].nodeValue; + } + } + } + return text.toLowerCase(); +}; + +/** + * TODO comments. + */ +Drupal.instantFilter.prototype.rebuildIndex = function () { + var self = this; + + this.items = $.map(this.container.find(this.settings.itemSelector), function (element) { + var item = { + element: element, + text: self.getText(element), + groups: {} + }; + + return $.data(element, 'instantfilter', item); + }); + + this.groups = $.map(this.container.find(this.settings.groupSelector), function (element, groupIndex) { + var group = { + element: element, + total: 0, + results: 0 + }; + + // Link group to items. + $(element).find(self.settings.itemSelector).each(function () { + $.data(this, 'instantfilter').groups[groupIndex] = group; + + group.total++; + }); + + return $.data(this, 'instantfilter', group); + }); +}; + +/** + * Filters all instant searchable items for the given string. + * + * All items containing the string will stay visible, while other items are + * hidden. All groups that don't have any matching items will also be hidden. + */ +Drupal.instantFilter.prototype.filter = function (search) { + var self = this; + + if (this.items === undefined) { + this.rebuildIndex(); + } + + this.search = search; + + // Reset the total and per-group result counters. + this.results = 0; + $.each(this.groups, function () { + this.results = 0; + }); + + // Allow other scripts to interact with this event. + this.container.trigger('DrupalBeforeInstantFilter', this); + + var lastParent; + // Walk through items and hide items that don't match. + $.each(this.items, function (i, item) { + var match = this.text.indexOf(self.search) >= 0; + + if (match) { + // Increment the total results counter. + self.results++; + // Increment the per-group result counter of item's groups. + $.each(this.groups, function () { + this.results++; + }); + } + + $(this.element) + [match ? 'addClass' : 'removeClass']('instantfilter-match') + [match ? 'removeClass' : 'addClass']('instantfilter-irrelevant'); + + if (self.settings.updateItemZebra) { + var currentParent = $.data(this.element.parentNode); + if (currentParent != lastParent && self.items[i-1]) { + var $item = $(self.items[i-1].element); + + // Only update zebra if item actually has an odd or even class. + if ($item.hasClass('odd') || $item.hasClass('even')) { + $item.parent().children(':visible') + .removeClass('odd even') + .filter(':odd').addClass('even').end() + .filter(':even').addClass('odd'); + } + } + lastParent = currentParent; + } + }); + + // Hide groups that have no matching items. + $.each(this.groups, function () { + $(this.element) + [this.results ? 'addClass' : 'removeClass']('instantfilter-has-contents') + [this.results ? 'removeClass' : 'addClass']('instantfilter-no-contents'); + }); + + // If any results are found, remove the 'no results' message. + // Otherwise dipslay the 'no results message. + if (this.results) { + this.removePlaceholder(); + } + else { + this.addPlaceholder(); + }; + + // Allow other scripts to interact with this event. + this.container.trigger('DrupalInstantFilter', this); +}; + +/** + * Theme an input textfield + */ +Drupal.theme.prototype.textfield = function (element) { + var output = ''; + if (element.label) { + output += ''; + }; + output += ''; + + if (element.description) { + output += '
' + element.description + '
'; + }; + + return '
' + output + '
'; +}; + +})(jQuery); Index: misc/instantfilter.css =================================================================== RCS file: misc/instantfilter.css diff -N misc/instantfilter.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/instantfilter.css 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,10 @@ +/* $Id$ */ + +.instantfilter-irrelevant, +.instantfilter-no-contents { + display: none !important; +} + +.instantfilter-no-results { + padding: 0 0 16px 0; +}