Index: modules/system/system.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v retrieving revision 1.259 diff -u -r1.259 system.admin.inc --- modules/system/system.admin.inc 15 Feb 2010 15:07:57 -0000 1.259 +++ modules/system/system.admin.inc 22 Feb 2010 14:27:49 -0000 @@ -50,7 +50,7 @@ // Only show blocks for items which are not containers, or those which // are containers and do have items we can show. $block['show'] = TRUE; - $block['title'] = l($item['title'], $item['href'], $item['localized_options']); + $block['title'] = l($item['title'], $item['href'], $item['localized_options']); if (!empty($content)) { $block['content'] .= theme('admin_block_content', array('content' => $content)); } @@ -848,6 +848,30 @@ return system_modules_confirm_form($files, $form_state['storage']); } + $form['modulesfilter'] = array( + '#type' => 'textfield', + '#title' => t('Filter modules'), + '#description' => t('Enter keywords to filter the list of modules.'), + ); + + $form['modulesfilter']['#attached']['library'][] = array('system', 'instantfilter'); + $form['modulesfilter']['#attached']['js'][] = array('type' => 'setting', 'data' => array( + 'instantfilter' => array( + 'edit-modulesfilter' => array( + 'container' => '#system-modules', + 'groups' => array( + 'fieldset' => array(), + 'fieldset table > tbody' => array('items' => 'tr', 'zebra' => TRUE) + ), + 'items' => array( + 'table > tbody > tr' => array( + 'ignore' => 'td.version, .admin-requirements, td.help, td.permissions, td.configure', + ) + ) + ) + ), + )); + $modules = array(); $form['modules'] = array('#tree' => TRUE); @@ -2512,7 +2536,7 @@ $label .= ' for="' . $module['enable']['#id'] . '"'; } $row[] = $label . '>' . drupal_render($module['name']) . ''; - $row[] = drupal_render($module['version']); + $row[] = array('data' => drupal_render($module['version']), 'class' => 'version'); // Add the description, along with any modules it requires. $description = drupal_render($module['description']); if ($module['#requires']) { Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.890 diff -u -r1.890 system.module --- modules/system/system.module 17 Feb 2010 09:09:30 -0000 1.890 +++ modules/system/system.module 22 Feb 2010 14:27:49 -0000 @@ -1107,6 +1107,16 @@ ), ); + // Instant Filter. + $libraries['instantfilter'] = array( + 'title' => 'Instant Filter', + 'website' => 'http://drupal.org/node/396478', + 'version' => '1.0', + 'js' => array( + 'misc/instantfilter.js' => 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,217 @@ +// $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) { + $('#' + base + ':input', context).once('instantfilter', function () { + $(this).data('instantfilter', new Drupal.instantFilter(this, settings.instantfilter[base])); + }); + } + + // Bind instantfilter behaviors to all elements showing the class. + $('.instantfilter-filter:input', context).once('instantfilter', function () { + $(this).data('instantfilter', new Drupal.instantFilter(this)); + }); + + // If context has an parent with the instantfilter class, then context is + // added dynamicly (e.g. using AJAX). If so, the index needs to be rebuild. + $(context).closest('.instantfilter-container').each(function () { + $(this).trigger('drupalInstantFilterIndexInvalidated'); + }); + } +}; + +/** + * The instantFilter object. + * + * @constructor + * @param element + * DOM input element to attach the instantfilter to. + * @param settings + * Settings for the instantfilter. + */ +Drupal.instantFilter = function (element, settings) { + var self = this; + this.instanceID = Drupal.instantFilter.instanceCounter++; + + this.settings = $.extend({ + container: null, + groups: { + '.instantfilter-group': { zebra: false } + }, + items: { + '.instantfilter-item': { ignore: null } + }, + empty: Drupal.t('There were no results.') + }, settings); + this.index = false; + + this.element = $(element); + if (this.settings.container) { + this.container = $(this.settings.container); + } + else { + this.container = $(document.body); + } + + var events = (this.element.is('[type=text]')) ? 'keyup' : 'change'; + this.element.bind(events, function () { + self.applyFilter($.trim($(this).val().toLowerCase())); + }); + + this.container + .addClass('instantfilter-container') + .bind('drupalInstantFilterIndexInvalidated', function () { + self.index = false; + }); + + // Apply filter once if the element is not empty. + if (this.element.val()) { + self.applyFilter($.trim(this.element.val().toLowerCase())); + } +}; + +Drupal.instantFilter.instanceCounter = 0; + +/** + * Get text value of an item. + */ +Drupal.instantFilter.prototype.getValue = function (element, settings) { + var text = ''; + if (!settings.ignore || !$(element).is(settings.ignore)) { + for (var i = 0; i < element.childNodes.length; i++) { + if (element.childNodes[i].nodeType == 1) { // ELEMENT_NODE + text += this.getValue(element.childNodes[i], settings); + } + else if (element.childNodes[i].nodeType == 3) { // TEXT_NODE + text += element.childNodes[i].nodeValue; + } + } + } + return text.toLowerCase(); +}; + +/** + * Rebuild index of the filter. + */ +Drupal.instantFilter.prototype.rebuildIndex = function () { + var self = this; + var allitems = ''; + + this.items = []; + this.groups = []; + + var i = 0; + for (var selector in this.settings.items) { + allitems += ',' + selector; + + this.container.find(selector).each(function () { + var item = $.extend({}, self.settings.items[selector], { + element: $(this), + value: self.getValue(this, self.settings.items[selector]), + groups: [] + }); + + self.items[i] = item; + item.element.data('instantfilter:' + self.instanceID + ':item', i); + i++; + }); + } + + allitems = allitems.substring(1); + + var i = 0; + for (var selector in this.settings.groups) { + this.container.find(selector).each(function () { + var group = $.extend({}, self.settings.groups[selector], { + element: $(this), + total: 0, + results: 0 + }); + + // Link group to items. + group.element.find(group.items || allitems).each(function () { + group.total++; + + var item = $(this).data('instantfilter:' + self.instanceID + ':item'); + if (item !== undefined && self.items[item]) { + self.items[item].groups.push(group); + } + }); + + self.groups[i] = group; + group.element.data('instantfilter:' + self.instanceID + ':group', i); + i++; + }); + } +}; + +/** + * Filters all 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. + * + * @param search + * The string to filter items on. + */ +Drupal.instantFilter.prototype.applyFilter = function (search) { + if (!this.index) { + this.rebuildIndex(); + this.index = true; + } + + this.search = search; + // Reset the total and group result counters. + this.results = 0; + for (var i in this.groups) { + this.groups[i].results = 0; + } + + for (var i in this.items) { + var item = this.items[i]; + + var match = item.value.indexOf(this.search) >= 0; + + if (match) { + // Increment the total and item's groups result counters. + this.results++; + for (var i in item.groups) { + item.groups[i].results++; + } + } + + item.element[match ? 'show' : 'hide'](); + } + + for (var i in this.groups) { + var group = this.groups[i]; + + group.element[group.results ? 'show' : 'hide'](); + + if (group.zebra) { + group.element.children(':visible') + .removeClass('odd even') + .filter(':odd').addClass('even').end() + .filter(':even').addClass('odd'); + } + } + + // If any results are found, remove the 'no results' message. + // Otherwise display the 'no results message. + if (this.results) { + this.element.closest('.form-item').find('.instantfilter-no-results').remove(); + } + else { + if (!this.element.closest('.form-item').find('.instantfilter-no-results').length) { + this.element.closest('.form-item').append($('

').text(this.settings.empty)); + }; + }; +}; + +})(jQuery);