diff --git misc/instasearch.css misc/instasearch.css new file mode 100644 index 0000000..24facfb --- /dev/null +++ misc/instasearch.css @@ -0,0 +1,10 @@ +/* $Id$ */ + +.instasearch-irrelevant, +.instasearch-no-contents { + display:none; +} + +.instasearch-no-results { + padding:0 0 16px 0; +} \ No newline at end of file diff --git misc/instasearch.js misc/instasearch.js new file mode 100644 index 0000000..346ab5f --- /dev/null +++ misc/instasearch.js @@ -0,0 +1,114 @@ +// $Id$ + +(function ($) { + +Drupal.instasearch = function (container) { + var data = { items: undefined, index: undefined }; + + var textfield = container.find('.instasearch-textfield'); + + var text = function (elem) { + var ret = ''; + if (!$(elem).is('.instasearch-ignore')) + for (var i = 0; i < elem.childNodes.length; i++) + if (elem.childNodes[i].nodeType != 8) + ret += elem.childNodes[i].nodeType != 1 ? + elem.childNodes[i].nodeValue : + text(elem.childNodes[i]); + return ret; + }; + + var rebuildIndex = function () { + data.items = container.find('.instasearch-item'); + data.index = $.map(data.items, function (elem) { + return text(elem).toLowerCase(); + }); + container.find('.instasearch-group').data('instasearch:group', undefined); + data.groups = $.map(data.items, function (elem) { + var group = $(elem).closest('.instasearch-group'); + if (group.length) { + var x = group.data('instasearch:group'); + if (x === undefined) { + group.data('instasearch:group', x = { group: group, total: 0 }); + } + x.total++; + return x; + } + }); + }; + + var addPlaceholder = function() { + if (!container.find('.instasearch-no-results').length) + textfield.closest('.form-item').after($('

').text(Drupal.t('There were no results.'))); + }; + + var removePlaceholder = function () { + container.find('.instasearch-no-results').remove(); + }; + + var filter = function () { + if (data.index === undefined) { + rebuildIndex(); + } + + var value = $.trim(textfield.val().toLowerCase()); + if (value !== '') { + var results = 0; + $.each($.unique(data.groups), function () { + this.current = 0; + }); + $.each(data.index, function (i, text) { + var match = text.indexOf(value) >= 0; + results += match; + data.groups[i].current += match; + data.items.eq(i) + [match ? 'addClass' : 'removeClass']('instasearch-match') + [match ? 'removeClass' : 'addClass']('instasearch-irrelevant'); + }); + $.each($.unique(data.groups), function () { + this.group + [this.current ? 'addClass' : 'removeClass']('instasearch-has-contents') + [this.current ? 'removeClass' : 'addClass']('instasearch-no-contents'); + }); + if (results) { + removePlaceholder(); + } + else { + addPlaceholder(); + } + } + else { + data.items.removeClass('instasearch-match').removeClass('instasearch-irrelevant'); + removePlaceholder(); + } + + container.trigger('instasearch'); + }; + + textfield.bind('click keyup', filter); + + if (textfield[0]) { + textfield[0].type = 'search'; + textfield.attr('results', 5); + } + + return data; +}; + +Drupal.behaviors.instasearch = { + attach: function (context) { + $('.instasearch-container', context).once('instasearch', function () { + var container = $(this); + container.data('instasearch', Drupal.instasearch(container)); + }); + + $(context).closest('.instasearch-container').each(function () { + var data = $(this).data('instasearch'); + if (data) { + data.index = undefined; + } + }); + } +}; + +})(jQuery); \ No newline at end of file diff --git modules/system/system.admin.inc modules/system/system.admin.inc index e9dd5f1..b284ed0 100644 --- modules/system/system.admin.inc +++ modules/system/system.admin.inc @@ -727,6 +727,7 @@ function system_modules($form, $form_state = array()) { '#title' => t($package), '#collapsible' => TRUE, '#theme' => 'system_modules_fieldset', + '#attributes' => array('class' => array('instasearch-group')), '#header' => array( array('data' => t('Enabled'), 'class' => array('checkbox')), t('Name'), @@ -742,6 +743,24 @@ function system_modules($form, $form_state = array()) { ); $form['#action'] = url('admin/config/modules/list/confirm'); + $form['#attributes']['class'] = 'instasearch-container'; + $form['#attached_js']['misc/instasearch.js'] = array(); + $form['instasearch'] = array( + '#type' => 'textfield', + '#weight' => -5, + '#input' => FALSE, + '#title' => t('Filter modules'), + '#name' => 'instasearch', + '#size' => 30, + '#value' => '', + '#description' => t('Enter the name of a module to filter the list.'), + '#attributes' => array('class' => array('instasearch-textfield')), + '#attached' => array( + 'js' => array('misc/instasearch.js'), + 'css' => array('misc/instasearch.css'), + ), + ); + return $form; } @@ -2198,7 +2217,7 @@ function theme_system_modules_fieldset($variables) { // If we have help, it becomes the first part // of the description - with CSS, it is float: right'd. if (isset($module['help'])) { - $description = '
' . drupal_render($module['help']) . '
'; + $description = '
' . drupal_render($module['help']) . '
'; } // Add the description, along with any modules it requires. $description .= drupal_render($module['description']); @@ -2209,7 +2228,7 @@ function theme_system_modules_fieldset($variables) { $description .= '
' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '
'; } $row[] = array('data' => $description, 'class' => array('description')); - $rows[] = $row; + $rows[] = array('data' => $row, 'class' => array('instasearch-item')); } return theme('table', array('header' => $form['#header'], 'rows' => $rows));