From 001d54f663136894ce0dce0a09693cc107cfb788 Mon Sep 17 00:00:00 2001
From: Steven Wittens <steven@10.no-reply.drupal.org>
Date: Tue, 21 Nov 2006 08:16:39 +0000
Subject: [PATCH] #84961: Add 'select all' and range select feature to admin
 tables.

---
 CHANGELOG.txt                  |  1 +
 includes/theme.inc             | 10 +++++
 misc/tableselect.js            | 75 ++++++++++++++++++++++++++++++++++
 modules/comment/comment.module |  2 +-
 modules/node/node.module       |  2 +-
 modules/system/system.css      |  7 ++++
 modules/user/user.module       |  2 +-
 themes/garland/style.css       |  9 ++++
 8 files changed, 105 insertions(+), 3 deletions(-)
 create mode 100644 misc/tableselect.js

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index ba54a811953e..f16782bb2668 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -30,6 +30,7 @@ Drupal 5.0, xxxx-xx-xx (development version)
     * improved configurability of the contact forms.
     * reorganized the settings pages.
     * made it easy to investigate popular search terms
+    * added a 'select all' checkbox and a range select feature to administration tables.
 - integrated urlfilter.module in filter.module
 - block system:
     * extended the block visibility settings with a role specific settings.
diff --git a/includes/theme.inc b/includes/theme.inc
index e90fb36f18d1..2dcbc021f24b 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -778,6 +778,16 @@ function theme_table($header, $rows, $attributes = array(), $caption = NULL) {
   return $output;
 }
 
+/**
+ * Returns a header cell for tables that have a select all functionality.
+ */
+function theme_table_select_header_cell() {
+  drupal_add_js(array('tableSelect' => array('selectAll' => t('Select all rows in this table'), 'selectNone' => t('Deselect all rows in this table'))), 'setting');
+  drupal_add_js('misc/tableselect.js');
+
+  return array('class' => 'select-all');
+}
+
 /**
  * Return a themed sort icon.
  *
diff --git a/misc/tableselect.js b/misc/tableselect.js
new file mode 100644
index 000000000000..052af02dd6b5
--- /dev/null
+++ b/misc/tableselect.js
@@ -0,0 +1,75 @@
+// $Id$
+
+Drupal.tableSelect = function() {
+  // Keep track of the table, which checkbox is checked and alias the settings.
+  var table = this, selectAll, checkboxes, lastChecked, settings = Drupal.settings.tableSelect;
+
+  // Store the select all checkbox in a variable as we need it quite often.
+  selectAll = $('<input type="checkbox" class="form-checkbox" />').attr('title', settings.selectAll).click(function() {
+    // Loop through all checkboxes and set their state to the select all checkbox' state.
+    checkboxes.each(function() {
+      this.checked = selectAll[0].checked;
+      // Either add or remove the selected class based on the state of the check all checkbox.
+      $(this).parents('tr:first')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
+    });
+    // Update the title and the state of the check all box.
+    selectAll.attr('title', selectAll[0].checked ? settings.selectNone : settings.selectAll);
+  });
+
+  // Find all <th> with class select-all, and insert the check all checkbox.
+  $('th.select-all', table).prepend(selectAll);
+
+  // For each of the checkboxes within the table.
+  checkboxes = $('td input:checkbox', table).click(function(e) {
+    // Either add or remove the selected class based on the state of the check all checkbox.
+    $(this).parents('tr:first')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
+
+    // If this is a shift click, we need to highlight everything in the range.
+    // Also make sure that we are actually checking checkboxes over a range and
+    // that a checkbox has been checked or unchecked before.
+    if (e.shiftKey && lastChecked && lastChecked != e.target) {
+      // We use the checkbox's parent TR to do our range searching.
+      Drupal.tableSelectRange($(e.target).parents('tr')[0], $(lastChecked).parents('tr')[0], e.target.checked);
+    }
+
+    // If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
+    selectAll[0].checked = (checkboxes.length == $(checkboxes).filter(':checked').length);
+    // Set the title to the current action.
+    selectAll.attr('title', selectAll[0].checked ? settings.selectNone : settings.selectAll);
+
+    // Keep track of the last checked checkbox.
+    lastChecked = e.target;
+  });
+}
+
+Drupal.tableSelectRange = function(from, to, state) {
+  // We determine the looping mode based on the the order of from and to.
+  var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
+
+  // Traverse through the sibling nodes.
+  for (var i = from[mode]; i; i = i[mode]) {
+    // Make sure that we're only dealing with elements.
+    if (i.nodeType != 1) continue;
+
+    // Either add or remove the selected class based on the state of the target checkbox.
+    $(i)[ state ? 'addClass' : 'removeClass' ]('selected');
+    $('input:checkbox', i).each(function() {
+      this.checked = state;
+    });
+
+    if (to.nodeType) {
+      // If we are at the end of the range, stop.
+      if (i == to) break;
+    }
+    // A faster alternative to doing $(i).filter(to).length.
+    else if (jQuery.filter(to, [i]).r.length) break;
+
+  }
+}
+
+// Global Killswitch
+if (Drupal.jsEnabled) {
+  $(document).ready(function() {
+    $('form table[th.select-all]').each(Drupal.tableSelect);
+  });
+}
diff --git a/modules/comment/comment.module b/modules/comment/comment.module
index 1873c1b84c48..8ba74e689713 100644
--- a/modules/comment/comment.module
+++ b/modules/comment/comment.module
@@ -1044,7 +1044,7 @@ function comment_admin_overview($type = 'new', $arg) {
   // load the comments that we want to display
   $status = ($type == 'approval') ? COMMENT_NOT_PUBLISHED : COMMENT_PUBLISHED;
   $form['header'] = array('#type' => 'value', '#value' => array(
-    NULL,
+    theme('table_select_header_cell'),
     array('data' => t('Subject'), 'field' => 'subject'),
     array('data' => t('Author'), 'field' => 'name'),
     array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
diff --git a/modules/node/node.module b/modules/node/node.module
index ef0b949d213f..2ce5863dba73 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -1533,7 +1533,7 @@ function node_admin_nodes() {
  */
 function theme_node_admin_nodes($form) {
   // Overview table:
-  $header = array(NULL, t('Title'), t('Type'), t('Author'), t('Status'), t('Operations'));
+  $header = array(theme('table_select_header_cell'), t('Title'), t('Type'), t('Author'), t('Status'), t('Operations'));
 
   $output .= drupal_render($form['options']);
   if (isset($form['title']) && is_array($form['title'])) {
diff --git a/modules/system/system.css b/modules/system/system.css
index c801759d646d..5a92c5a8979f 100644
--- a/modules/system/system.css
+++ b/modules/system/system.css
@@ -325,3 +325,10 @@ html.js .resizable-textarea textarea {
   display: block;
   padding: 1.5em 0 .5em;
 }
+
+/*
+** To be used with tableselect.js
+*/
+tr.selected td {
+  background: #ffc;
+}
diff --git a/modules/user/user.module b/modules/user/user.module
index d9e7fcb6625e..d35f36ecdca3 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -2043,7 +2043,7 @@ function user_admin_account() {
 function theme_user_admin_account($form) {
   // Overview table:
   $header = array(
-    array(),
+    theme('table_select_header_cell'),
     array('data' => t('Username'), 'field' => 'u.name'),
     array('data' => t('Status'), 'field' => 'u.status'),
     t('Roles'),
diff --git a/themes/garland/style.css b/themes/garland/style.css
index b870d74849bb..195d2f6460c3 100644
--- a/themes/garland/style.css
+++ b/themes/garland/style.css
@@ -840,6 +840,15 @@ table.system-status-report th {
   border-color: #d3e7f4;
 }
 
+#autocomplete li.selected, tr.selected td, tr.selected td.active {
+  background: #027ac6;
+  color: #fff;
+}
+
+tr.selected td a:link, tr.selected td a:visited, tr.selected td a:active {
+  color: #d3e7f4;
+}
+
 /**
  * CSS support
  */
-- 
GitLab