diff --git a/includes/common.inc b/includes/common.inc index 0c6c9eb..50ec2d1 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -6449,7 +6449,7 @@ function drupal_common_theme() { 'variables' => array(), ), 'table' => array( - 'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'empty' => ''), + 'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'rowgroup' => array(), 'rowgroups' => array(), 'sticky' => TRUE, 'empty' => ''), ), 'tablesort_indicator' => array( 'variables' => array('style' => NULL), diff --git a/includes/theme.inc b/includes/theme.inc index f592891..fc6a83e 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1579,6 +1579,8 @@ function theme_breadcrumb($variables) { * ) * ); * @endcode + * Note that when rowgroups are used rows need to grouped using an extra + * associative array. See colgroups variable for more info. * - attributes: An array of HTML attributes to apply to the table tag. * - caption: A localized string to use for the tag. * - colgroups: An array of column groups. Each element of the array can be @@ -1589,9 +1591,9 @@ function theme_breadcrumb($variables) { * include a "data" attribute. To add attributes to COL elements, set the * "data" attribute with an array of columns, each of which is an * associative array of HTML attributes. - * Here's an example for $colgroup: + * Here's an example for $colgroups: * @code - * $colgroup = array( + * $colgroups = array( * // COLGROUP with one COL element. * array( * array( @@ -1612,6 +1614,43 @@ function theme_breadcrumb($variables) { * These optional tags are used to group and set properties on columns * within a table. For example, one may easily group three columns and * apply same background style to all. + * - rowgroup: An array of attributes applied to the TBODY element(s). + * Rowgroup specific attributes set in rowgroups override these attributes. + * - rowgroups: An associative array of row groups. Each element represents a + * rowgroup identifiable by it's key. When this variable is set, rows are + * expected to be grouped by rowgroup using an associative array while their + * keys point to the keys of this variable. Elements of this variable are + * expected to be an array of attributes which will be applied to the TBODY + * element representing that rowgroup. + * Here's an example for $rows combined with $rowgroups: + * @code + * $rowgroups = array( + * 'funky' => array( // Attributes for the TBODY element. + * 'title' => 'Funky rows', + * 'class' => array('funky'), + * ), + * 'jazzy' => array( // Attributes for the TBODY element. + * 'title' => 'Jazzy rows', + * 'class' => array('jazzy'), + * ), + * ); + * $rows = array( + * // Rows of the funky rowgroup. + * 'funky' => array( + * // Simple row + * array( + * 'Cell 1', 'Cell 2', 'Cell 3' + * ), + * ), + * // Rows of the jazzy rowgroup. + * 'jazzy' => array( + * // Row with attributes on the row and some of its cells. + * array( + * 'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => array('funky') + * ), + * ), + * ); + * @endcode * - sticky: Use a "sticky" table header. * - empty: The message to display in an extra row if table does not have any * rows. @@ -1622,6 +1661,8 @@ function theme_table($variables) { $attributes = $variables['attributes']; $caption = $variables['caption']; $colgroups = $variables['colgroups']; + $rowgroup = $variables['rowgroup']; + $rowgroups = $variables['rowgroups']; $sticky = $variables['sticky']; $empty = $variables['empty']; @@ -1705,46 +1746,58 @@ function theme_table($variables) { $ts = array(); } - // Format the table rows: - if (count($rows)) { - $output .= "\n"; - $flip = array('even' => 'odd', 'odd' => 'even'); - $class = 'even'; - foreach ($rows as $number => $row) { - $attributes = array(); + if (!count($rowgroups)) { + $rowgroups = array(); + $rows_by_groups = array($rows); + } + else { + $rows_by_groups = $rows; + } - // Check if we're dealing with a simple or complex row - if (isset($row['data'])) { - foreach ($row as $key => $value) { - if ($key == 'data') { - $cells = $value; - } - else { - $attributes[$key] = $value; + // Format the table rows: + foreach ($rows_by_groups as $rowgroup_id => $rows) { + if (count($rows)) { + $attributes = isset($rowgroups[$rowgroup_id]) ? $rowgroup + $rowgroups[$rowgroup_id] : $rowgroup; + $output .= '\n"; + + $flip = array('even' => 'odd', 'odd' => 'even'); + $class = 'even'; + foreach ($rows as $number => $row) { + $attributes = array(); + + // Check if we're dealing with a simple or complex row + if (isset($row['data'])) { + foreach ($row as $key => $value) { + if ($key == 'data') { + $cells = $value; + } + else { + $attributes[$key] = $value; + } } } - } - else { - $cells = $row; - } - if (count($cells)) { - // Add odd/even class - if (empty($row['no_striping'])) { - $class = $flip[$class]; - $attributes['class'][] = $class; + else { + $cells = $row; } + if (count($cells)) { + // Add odd/even class + if (empty($row['no_striping'])) { + $class = $flip[$class]; + $attributes['class'][] = $class; + } - // Build row - $output .= ' '; - $i = 0; - foreach ($cells as $cell) { - $cell = tablesort_cell($cell, $header, $ts, $i++); - $output .= _theme_table_cell($cell); + // Build row + $output .= ' '; + $i = 0; + foreach ($cells as $cell) { + $cell = tablesort_cell($cell, $header, $ts, $i++); + $output .= _theme_table_cell($cell); + } + $output .= " \n"; } - $output .= " \n"; } + $output .= "\n"; } - $output .= "\n"; } $output .= "\n"; diff --git a/misc/instantfilter.js b/misc/instantfilter.js new file mode 100644 index 0000000..2fdf348 --- /dev/null +++ b/misc/instantfilter.js @@ -0,0 +1,216 @@ +(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); diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 6824f19..490e466 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -783,6 +783,30 @@ function system_modules($form, $form_state = array()) { return system_modules_confirm_form($visible_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.description, td.version, .admin-requirements, td.help, td.permissions, td.configure', + ) + ) + ) + ), + )); + $modules = array(); $form['modules'] = array('#tree' => TRUE); @@ -2581,7 +2605,7 @@ function theme_system_modules_fieldset($variables) { $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']) { diff --git a/modules/system/system.module b/modules/system/system.module index d7dc69c..d93ca89 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -1221,6 +1221,16 @@ function system_library() { ), ); + // 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', diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index 4789e7e..db1f4cc 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -655,6 +655,27 @@ function user_admin_settings() { */ function user_admin_permissions($form, $form_state, $rid = NULL) { + $form['permissionsfilter'] = array( + '#type' => 'textfield', + '#title' => t('Permissions modules'), + '#description' => t('Enter keywords to filter the list of permissions.'), + ); + + $form['permissionsfilter']['#attached']['library'][] = array('system', 'instantfilter'); + $form['permissionsfilter']['#attached']['js'][] = array('type' => 'setting', 'data' => array( + 'instantfilter' => array( + 'edit-permissionsfilter' => array( + 'container' => '#user-admin-permissions', + 'groups' => array( + 'table > tbody' => array('items' => 'tr:has(td.permission)', 'zebra' => TRUE) + ), + 'items' => array( + 'table > tbody > tr:has(td.permission)' => array() + ) + ) + ), + )); + // Retrieve role names for columns. $role_names = user_roles(); if (is_numeric($rid)) { @@ -683,10 +704,12 @@ function user_admin_permissions($form, $form_state, $rid = NULL) { foreach ($modules as $module => $display_name) { if ($permissions = module_invoke($module, 'permission')) { - $form['permission'][] = array( - '#markup' => $module_info[$module]['name'], + $form['permission'][$module] = array( '#id' => $module, ); + $form['permission'][$module][] = array( + '#markup' => $module_info[$module]['name'], + ); foreach ($permissions as $perm => $perm_item) { // Fill in default values for the permission. $perm_item += array( @@ -695,7 +718,7 @@ function user_admin_permissions($form, $form_state, $rid = NULL) { 'warning' => !empty($perm_item['restrict access']) ? t('Warning: Give to trusted roles only; this permission has security implications.') : '', ); $options[$perm] = ''; - $form['permission'][$perm] = array( + $form['permission'][$module][$perm] = array( '#type' => 'item', '#markup' => $perm_item['title'], '#description' => theme('user_permission_description', array('permission_item' => $perm_item, 'hide' => $hide_descriptions)), @@ -758,32 +781,36 @@ function theme_user_admin_permissions($variables) { $form = $variables['form']; $roles = user_roles(); - foreach (element_children($form['permission']) as $key) { - $row = array(); - // Module name - if (is_numeric($key)) { - $row[] = array('data' => drupal_render($form['permission'][$key]), 'class' => array('module'), 'id' => 'module-' . $form['permission'][$key]['#id'], 'colspan' => count($form['role_names']['#value']) + 1); - } - else { - // Permission row. - $row[] = array( - 'data' => drupal_render($form['permission'][$key]), - 'class' => array('permission'), - ); - foreach (element_children($form['checkboxes']) as $rid) { - $form['checkboxes'][$rid][$key]['#title'] = $roles[$rid] . ': ' . $form['permission'][$key]['#markup']; - $form['checkboxes'][$rid][$key]['#title_display'] = 'invisible'; - $row[] = array('data' => drupal_render($form['checkboxes'][$rid][$key]), 'class' => array('checkbox')); + foreach (element_children($form['permission']) as $module) { + $rowgroups[$module] = array('id' => 'module-' . $form['permission'][$module]['#id']); + foreach (element_children($form['permission'][$module]) as $key) { + $row = array(); + // Module name + if (is_numeric($key)) { + $row[] = array('data' => drupal_render($form['permission'][$module][$key]), 'class' => array('module'), 'id' => 'module-' . $form['permission'][$module][$key]['#id'], 'colspan' => count($form['role_names']['#value']) + 1); + } + else { + // Permission row. + $row[] = array( + 'data' => drupal_render($form['permission'][$module][$key]), + 'class' => array('permission'), + ); + foreach (element_children($form['checkboxes']) as $rid) { + $form['checkboxes'][$rid][$key]['#title'] = $roles[$rid] . ': ' . $form['permission'][$module][$key]['#markup']; + $form['checkboxes'][$rid][$key]['#title_display'] = 'invisible'; + $row[] = array('data' => drupal_render($form['checkboxes'][$rid][$key]), 'class' => array('checkbox')); + } } + $rows[$module][] = $row; } - $rows[] = $row; } $header[] = (t('Permission')); foreach (element_children($form['role_names']) as $rid) { $header[] = array('data' => drupal_render($form['role_names'][$rid]), 'class' => array('checkbox')); } $output = theme('system_compact_link'); - $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'permissions'))); + $output .= drupal_render($form['permissionsfilter']); + $output .= theme('table', array('header' => $header, 'rows' => $rows, 'rowgroups' => $rowgroups, 'attributes' => array('id' => 'permissions'))); $output .= drupal_render_children($form); return $output; }