? misc/collapse.js
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.454
diff -u -r1.454 common.inc
--- includes/common.inc	5 Jun 2005 19:10:53 -0000	1.454
+++ includes/common.inc	8 Jun 2005 09:14:03 -0000
@@ -1110,6 +1110,33 @@
 }

 /**
+ * Format a group of form items.
+ *
+ * @param $legend
+ *   The label for the form item group.
+ * @param $group
+ *   The form items within the group, as an HTML string.
+ * @param $collapsed
+ *   A boolean value decided whether the group starts collapsed.
+ * @param $description
+ *   Explanatory text to display after the form item group.
+ * @param $attributes
+ *   An associative array of HTML attributes to add to the fieldset tag.
+ * @return
+ *   A themed HTML string representing the form item group.
+ */
+function form_group_collapsible($legend, $group, $collapsed = FALSE, $description = NULL, $attributes = NULL) {
+  drupal_add_js('misc/collapse.js');
+
+  $attributes['class'] .= ' collapsible';
+  if ($collapsed) {
+    $attributes['class'] .= ' collapsed';
+  }
+
+  return '<fieldset' . drupal_attributes($attributes) .'>' . ($legend ? '<legend>'. $legend .'</legend>' : '') . $group . ($description ? '<div class="description">'. $description .'</div>' : '') . "</fieldset>\n";
+}
+
+/**
  * Format a radio button.
  *
  * @param $title
Index: misc/drupal.css
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.css,v
retrieving revision 1.107
diff -u -r1.107 drupal.css
--- misc/drupal.css	31 May 2005 23:23:48 -0000	1.107
+++ misc/drupal.css	8 Jun 2005 09:23:12 -0000
@@ -586,3 +586,31 @@
 input.throbbing {
   background-position: 100% -18px;
 }
+
+/*
+** Collapsing fieldsets
+*/
+html.js fieldset.collapsed {
+  border-bottom-width: 0;
+  border-left-width: 0;
+  border-right-width: 0;
+  margin-bottom: 0;
+}
+
+html.js fieldset.collapsed * {
+   display: none;
+}
+
+html.js fieldset.collapsed legend,
+html.js fieldset.collapsed legend * {
+  display: inline;
+}
+
+html.js fieldset.collapsible legend a {
+  padding-left: 15px;
+  background: url('menu-expanded.png') 5px 50% no-repeat;
+}
+
+html.js fieldset.collapsed legend a {
+  background-image: url('menu-collapsed.png');
+}
Index: misc/drupal.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.js,v
retrieving revision 1.2
diff -u -r1.2 drupal.js
--- misc/drupal.js	1 Jun 2005 17:43:33 -0000	1.2
+++ misc/drupal.js	9 Jun 2005 08:29:25 -0000
@@ -8,6 +8,7 @@
      !document.getElementsByTagName ||
      !document.createElement        ||
      !document.createTextNode       ||
+     !document.documentElement      ||
      !document.getElementById);
   }
   return document.jsEnabled;
@@ -15,7 +16,7 @@

 // Global Killswitch
 if (isJsEnabled()) {
-
+  document.documentElement.className = 'js';
 }

 /**
Index: modules/comment.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/comment.module,v
retrieving revision 1.353
diff -u -r1.353 comment.module
--- modules/comment.module	22 May 2005 21:14:59 -0000	1.353
+++ modules/comment.module	8 Jun 2005 10:47:04 -0000
@@ -254,7 +254,7 @@
       if (user_access('administer comments')) {
         $selected = isset($node->comment) ? $node->comment : variable_get("comment_$node->type", 2);
         $output = form_radios('', 'comment', $selected, array(t('Disabled'), t('Read only'), t('Read/write')));
-        return form_group(t('User comments'), $output);
+        return form_group_collapsible(t('User comments'), $output, TRUE);
       }
       break;

Index: modules/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter.module,v
retrieving revision 1.64
diff -u -r1.64 filter.module
--- modules/filter.module	1 Jun 2005 03:21:44 -0000	1.64
+++ modules/filter.module	8 Jun 2005 09:54:27 -0000
@@ -725,7 +725,8 @@
       $output .= theme('filter_tips', $tips);
       $output .= '</div>';
     }
-    return theme('form_element', t('Input format'), $output, $extra, NULL, _form_get_error($name));
+    $group = theme('form_element', NULL, $output, $extra, NULL, _form_get_error($name));
+    return form_group_collapsible(t('Input format'), $group, TRUE);
   }
   else {
     // Only one format available: use a hidden form item and only show tips.
Index: modules/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node.module,v
retrieving revision 1.496
diff -u -r1.496 node.module
--- modules/node.module	6 Jun 2005 14:07:04 -0000	1.496
+++ modules/node.module	8 Jun 2005 11:17:49 -0000
@@ -1292,7 +1292,7 @@
     $author .= form_textfield(t('Authored on'), 'date', $edit->date, 20, 25, NULL, NULL, TRUE);

     $output .= '<div class="authored">';
-    $output .= form_group(t('Authoring information'), $author);
+    $output .= form_group_collapsible(t('Authoring information'), $author, TRUE);
     $output .= "</div>\n";

     $node_options = variable_get('node_options_'. $edit->type, array('status', 'promote'));
@@ -1303,7 +1303,7 @@
     $options .= form_checkbox(t('Create new revision'), 'revision', 1, isset($edit->revision) ? $edit->revision : in_array('revision', $node_options));

     $output .= '<div class="options">';
-    $output .= form_group(t('Options'), $options);
+    $output .= form_group_collapsible(t('Publishing Options'), $options, TRUE);
     $output .= "</div>\n";

     $extras .= implode('</div><div class="extra">', node_invoke_nodeapi($edit, 'form admin'));
Index: modules/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system.module,v
retrieving revision 1.214
diff -u -r1.214 system.module
--- modules/system.module	7 Jun 2005 18:39:35 -0000	1.214
+++ modules/system.module	8 Jun 2005 09:28:25 -0000
@@ -201,7 +201,7 @@
   // We will use a random URL so there is no way a proxy or a browser could cache the "no such image" answer.
   $group .= '<img style="position: relative; left: -1000em;" src="'. $base_url. '/system/test/'. user_password(20) .'.png" alt="" />';

-  $output = form_group(t('General settings'), $group);
+  $output = form_group_collapsible(t('General settings'), $group, TRUE);

   // Error handling:
   $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
@@ -211,12 +211,12 @@
   $group .= form_select(t('Error reporting'), 'error_level', variable_get('error_level', 1), array(t('Write errors to the log'), t('Write errors to the log and to the screen')), t('Where Drupal, PHP and SQL errors are logged. On a production server it is recommended that errors are only written to the error log. On a test server it can be helpful to write logs to the screen.'));
   $group .= form_select(t('Discard log entries older than'), 'watchdog_clear', variable_get('watchdog_clear', 604800), $period, t('The time log entries should be kept.  Older entries will be automatically discarded.  Requires crontab.'));

-  $output .= form_group(t('Error handling'), $group);
+  $output .= form_group_collapsible(t('Error handling'), $group, TRUE);

   // Caching:
   $group  = form_radios(t('Page cache'), 'cache', variable_get('cache', CACHE_DISABLED), array(CACHE_DISABLED => t('Disabled (low-traffic sites)'), CACHE_ENABLED_STRICT => t('Strict (medium-traffic sites)'), CACHE_ENABLED_LOOSE => t('Loose (high-traffic sites)')), t("Drupal has a caching mechanism which stores dynamically generated web pages in a database.  By caching a web page, Drupal does not have to create the page each time someone wants to view it, instead it takes only one SQL query to display it, reducing response time and the server's load.  Only pages requested by \"anonymous\" users are cached.  In order to reduce server load and save bandwidth, Drupal stores and sends cached pages compressed.  Drupal supports strict caching and loose caching.  Strict caching immediately deletes cached data as soon as it becomes invalid for any user.  Loose caching delays the deletion of cached data to provide better performance for high traffic sites."));

-  $output .= form_group(t('Cache settings'), $group);
+  $output .= form_group_collapsible(t('Cache settings'), $group, TRUE);

   // File system:
   $directory_path = variable_get('file_directory_path', 'files');
@@ -228,7 +228,7 @@
   $group = form_textfield(t('File system path'), 'file_directory_path', $directory_path, 70, 255, t('A file system path where the files will be stored. This directory has to exist and be writable by Drupal. If the download method is set to public this directory has to be relative to Drupal installation directory, and be accessible over the web. When download method is set to private this directory should not be accessible over the web. Changing this location after the site has been in use will cause problems so only change this setting on an existing site if you know what you are doing.'));
   $group .= form_textfield(t('Temporary directory'), 'file_directory_temp', $directory_temp, 70, 255, t('Location where uploaded files will be kept during previews. Relative paths will be resolved relative to the file system path.'));
   $group .= form_radios(t('Download method'), 'file_downloads', variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC), array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using http directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are transferred by Drupal.')), t('If you want any sort of access control on the downloading of files, this needs to be set to <em>private</em>. You can change this at any time, however all download URLs will change and there may be unexpected problems so it is not recommended.'));
-  $output .= form_group(t('File system settings'), $group);
+  $output .= form_group_collapsible(t('File system settings'), $group, TRUE);

   // Image handling:
   $group = '';
@@ -238,7 +238,7 @@
   }
   $group .= image_toolkit_invoke('settings');
   if ($group) {
-    $output .= form_group(t('Image handling'), $group);
+    $output .= form_group_collapsible(t('Image handling'), '<p>'.$group.'</p>', TRUE);
   }

   // Date settings:
@@ -274,7 +274,7 @@
   $group .= form_select(t('Long date format'), 'date_format_long', variable_get('date_format_long', $datelong[0]), $datelongchoices, t('Longer date format used for detailed display.'));
   $group .= form_select(t('First day of week'), 'date_first_day', variable_get('date_first_day', 0), array(0 => t('Sunday'), 1 => t('Monday'), 2 => t('Tuesday'), 3 => t('Wednesday'), 4 => t('Thursday'), 5 => t('Friday'), 6 => t('Saturday')), t('The first day of the week for calendar views.'));

-  $output .= form_group(t('Date settings'), $group);
+  $output .= form_group_collapsible(t('Date settings'), $group, TRUE);

   return $output;
 }
Index: modules/upload.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/upload.module,v
retrieving revision 1.40
diff -u -r1.40 upload.module
--- modules/upload.module	31 May 2005 04:10:21 -0000	1.40
+++ modules/upload.module	9 Jun 2005 08:28:16 -0000
@@ -383,7 +383,7 @@
     $output .= form_button(t('Attach'), 'fileop');
   }

-  return '<div class="attachments">'. form_group(t('Attachments'), $output, t('Changes made to the attachments are not permanent until you save this post.  The first "listed" file will be included in RSS feeds.')) .'</div>';
+  return '<div class="attachments">'. form_group_collapsible(t('File Attachments'), $output, TRUE, t('Changes made to the attachments are not permanent until you save this post.  The first "listed" file will be included in RSS feeds.')) .'</div>';
 }

 function upload_load($node) {
--- misc/collapse.js
+++ misc/collapse.js
@@ -0,0 +1,48 @@
+if (isJsEnabled()) {
+  addLoadEvent(collapseAutoAttach);
+}
+
+function collapseAutoAttach() {
+  var fieldsets = document.getElementsByTagName('fieldset');
+  var legend, fieldset;
+  for (var i = 0; fieldset = fieldsets[i]; i++) {
+    if (!hasClass(fieldset, 'collapsible')) {
+      continue;
+    }
+    legend = fieldset.getElementsByTagName('legend');
+    if (legend.length == 0) {
+      continue;
+    }
+    legend = legend[0];
+    var a = document.createElement('a');
+    a.href = '#';
+    a.onclick = function() {
+      toggleClass(this.parentNode.parentNode, 'collapsed');
+      this.blur();
+      return false;
+    };
+    a.innerHTML = legend.innerHTML;
+    while (legend.hasChildNodes()) {
+      removeNode(legend.childNodes[0]);
+    }
+    legend.appendChild(a);
+    collapseEnsureErrorsVisible(fieldset);
+  }
+}
+
+function collapseEnsureErrorsVisible(fieldset) {
+  if (!hasClass(fieldset, 'collapsed')) {
+    return;
+  }
+  var inputs = [];
+  inputs = inputs.concat(fieldset.getElementsByTagName('input'));
+  inputs = inputs.concat(fieldset.getElementsByTagName('textarea'));
+  inputs = inputs.concat(fieldset.getElementsByTagName('select'));
+  for (var j = 0; j<3; j++) {
+    for (var i = 0; i < inputs[j].length; i++) {
+      if (hasClass(inputs[j][i], 'error')) {
+        return removeClass(fieldset, 'collapsed');
+      }
+    }
+  }
+}
