Index: views.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/views.module,v
retrieving revision 1.270
diff -u -p -r1.270 views.module
--- views.module	8 May 2008 21:20:31 -0000	1.270
+++ views.module	13 May 2008 22:24:12 -0000
@@ -29,7 +29,7 @@ function views_theme() {
 
   $arguments = array(
     'display' => array('view' => NULL),
-    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL),
+    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
     'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL),
   );
 
Index: includes/field.handlers.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/field.handlers.inc,v
retrieving revision 1.10
diff -u -p -r1.10 field.handlers.inc
--- includes/field.handlers.inc	8 May 2008 21:20:31 -0000	1.10
+++ includes/field.handlers.inc	13 May 2008 22:24:12 -0000
@@ -117,6 +117,12 @@ class views_handler_field extends views_
       '#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
       '#description' => t('The label for this field that will be displayed to end users if the style requires it.'),
     );
+    $form['exclude'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Exclude from display'),
+      '#default_value' => $this->options['exclude'],
+      '#description' => t('Check this box to not display this field, but still load it in the view.  Use this option to not show a grouping field in each record, or when doing advanced theming.'),
+    );
   }
 
   /**
Index: includes/plugins.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/plugins.inc,v
retrieving revision 1.89
diff -u -p -r1.89 plugins.inc
--- includes/plugins.inc	13 May 2008 22:12:40 -0000	1.89
+++ includes/plugins.inc	13 May 2008 22:24:13 -0000
@@ -80,6 +80,7 @@ function views_views_plugins() {
         'handler' => 'views_plugin_style_default',
         'theme' => 'views_view_unformatted',
         'uses row plugin' => TRUE,
+        'uses options' => TRUE,
         'type' => 'normal',
       ),
       'list' => array(
@@ -2489,8 +2490,28 @@ class views_plugin_style extends views_p
   /**
    * Static member function to set default options.
    */
-  function options(&$options) { }
+  function options(&$options) { 
+    $options['grouping'] = '';
+  }
 
+  function options_form(&$form, &$form_state) {
+    // Only fields-based views can handle grouping.
+    if ($this->uses_fields()) {
+      $options = array('' => t('<None>'));
+      foreach ($this->display->handler->get_option('fields') as $field) {
+        $options[$field['id']] = $field['label'];
+      }
+      
+      $form['grouping'] = array(
+        '#type' => 'select',
+        '#title' => t('Grouping field'),
+        '#options' => $options,
+        '#default_value' => $this->options['grouping'],
+        '#description' => t('You may optionally specify a field by which to group the records.  Leave blank to not group.'),
+      );
+    }
+  }
+  
   /**
    * Called by the view builder to see if this style handler wants to
    * interfere with the sorts. If so it should build; if it returns
@@ -2509,11 +2530,37 @@ class views_plugin_style extends views_p
       vpr('views_plugin_style_default: Missing row plugin');
       return;
     }
-    $rows = array();
-    foreach ($this->view->result as $row) {
-      $rows[] = $this->row_plugin->render($row);
+    
+    // Group the rows according to the grouping field, if specified.
+    $sets = array();
+    if ($this->options['grouping']) {
+      foreach ($this->view->result as $row) {
+        // Group on the rendered version of the field, not the raw.  That way,
+        // we can control any special formatting of the grouping field through
+        // the admin or theme layer or anywhere else we'd like.
+        $grouping = $this->view->field[$this->options['grouping']]['handler']->theme($row);
+        if ($this->options['grouping_exclude']) {
+          //$this->view->field[$this->options['grouping']]['handler']->options['exclude'] = TRUE;
+        }
+        $sets[$grouping][] = $row;
+      }
     }
-    return theme($this->theme_functions(), $this->view, $this->options, $rows);
+    else {
+      // Create a single group with an empty grouping field.
+      $sets[''] = $this->view->result;
+    }
+    
+    // Render each group separately and concatenate.  Plugins may override this
+    // method if they wish some other way of handling grouping.
+    $output = '';
+    foreach ($sets as $title => $records) {
+      $rows = array();
+      foreach ($records as $label => $row) {
+        $rows[] = $this->row_plugin->render($row);
+      }
+      $output .= theme($this->theme_functions(), $this->view, $this->options, $rows, $title);
+    }
+    return $output;
   }
 
   function validate() {
@@ -2534,19 +2581,28 @@ class views_plugin_style extends views_p
  * decorations.
  */
 class views_plugin_style_default extends views_plugin_style {
+  /**
+   * Set default options
+   */
+  function options(&$options) {
+    parent::options($options);
+  }
+
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     // @todo -- separator option
   }
 }
 
 /**
- * Style plugin to render each item in an ordered or unordered list
+ * Style plugin to render each item in an ordered or unordered list.
  */
 class views_plugin_style_list extends views_plugin_style {
   /**
    * Set default options
    */
   function options(&$options) {
+    parent::options($options);
     $options['type'] = 'ul';
   }
 
@@ -2554,6 +2610,7 @@ class views_plugin_style_list extends vi
    * Render the given style.
    */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     $form['type'] = array(
       '#type' => 'radios',
       '#title' => t('List type'),
@@ -2571,6 +2628,7 @@ class views_plugin_style_grid extends vi
    * Set default options
    */
   function options(&$options) {
+    parent::options($options);
     $options['columns'] = 4;
     $options['alignment'] = 'horizontal';
   }
@@ -2579,6 +2637,7 @@ class views_plugin_style_grid extends vi
    * Render the given style.
    */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     $form['columns'] = array(
       '#type' => 'textfield',
       '#title' => t('Number of columns'),
@@ -2602,6 +2661,7 @@ class views_plugin_style_table extends v
    * Set default options
    */
   function options(&$options) {
+    parent::options($options);
     $options['columns'] = array();
     $options['default'] = '';
     $options['info'] = array();
@@ -2702,6 +2762,7 @@ class views_plugin_style_table extends v
    * Render the given style.
    */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     $fields = $this->display->handler->get_option('fields');
     if (empty($fields)) {
       $form['error_markup'] = array(
@@ -2822,7 +2883,26 @@ class views_plugin_style_table extends v
    * Render the table style.
    */
   function render() {
-    return theme($this->theme_functions(), $this->view, $this->options, array());
+    $sets = array();
+    if ($this->options['grouping']) {
+      foreach ($this->view->result as $row) {
+        // Group on the rendered version of the field, not the raw.  That way,
+        // we can control any special formatting of the grouping field through
+        // the admin or theme layer or anywhere else we'd like.
+        $grouping = $this->view->field[$this->options['grouping']]['handler']->theme($row);
+        $sets[$grouping][] = $row;
+      }
+    }
+    else {
+      // Create a single group with an empty grouping field.
+      $sets[''] = $this->view->result;
+    }
+
+    $output = '';
+    foreach ($sets as $title => $records) {
+      $output .= theme($this->theme_functions(), $this->view, $this->options, $records, $title);
+    }
+    return $output;
   }
 }
 
Index: theme/theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/theme.inc,v
retrieving revision 1.39
diff -u -p -r1.39 theme.inc
--- theme/theme.inc	5 May 2008 23:51:47 -0000	1.39
+++ theme/theme.inc	13 May 2008 22:24:13 -0000
@@ -141,7 +141,7 @@ function template_preprocess_views_view_
   // Loop through the fields for this view.
   $inline = FALSE;
   foreach ($view->field as $id => $field) {
-    if (!empty($field['handler']) && is_object($field['handler'])) {
+    if (!empty($field['handler']) && is_object($field['handler']) && empty($field['exclude'])) {
       $object = new stdClass();
 
       $object->content = $field['handler']->theme($vars['row']);
@@ -206,7 +206,10 @@ function template_preprocess_views_view_
  */
 function template_preprocess_views_view_table(&$vars) {
   $view     = $vars['view'];
-  $result   = $view->result;
+  $result   = $vars['rows'];
+  // We're going to rebuild this variable below, but need to empty it first.
+  $vars['rows'] = array();
+
   $options  = $view->style_handler->options;
   $handler  = $view->style_handler;
 
@@ -220,10 +223,10 @@ function template_preprocess_views_view_
   if ($query) {
     $query = '&' . $query;
   }
-
+  
   foreach ($columns as $field => $column) {
     // render the header labels
-    if ($field == $column) {
+    if ($field == $column && empty($fields[$field]['exclude'])) {
       $label = check_plain(!empty($fields[$field]['handler']) ? $fields[$field]['handler']->label() : '');
       if (empty($options['info'][$field]['sortable'])) {
         $vars['header'][$field] = $label;
@@ -256,7 +259,7 @@ function template_preprocess_views_view_
 
     // Render each field into its appropriate column.
     foreach ($result as $num => $row) {
-      if (!empty($fields[$field]['handler']) && is_object($fields[$field]['handler'])) {
+      if (!empty($fields[$field]['handler']) && is_object($fields[$field]['handler']) && empty($fields[$field]['exclude'])) {
         $handler = &$fields[$field]['handler'];
         $field_output = $handler->theme($row);
 
Index: theme/views-view-grid.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-grid.tpl.php,v
retrieving revision 1.1
diff -u -p -r1.1 views-view-grid.tpl.php
--- theme/views-view-grid.tpl.php	20 Apr 2008 23:41:06 -0000	1.1
+++ theme/views-view-grid.tpl.php	13 May 2008 22:24:13 -0000
@@ -10,6 +10,9 @@
  * @ingroup views_templates
  */
 ?>
+<?php if (!empty($title)) : ?>
+  <h3><?php print $title; ?></h3>
+<?php endif; ?>
 <table class="views-view-grid">
   <tbody>
     <?php foreach ($rows as $row_number => $columns): ?>
Index: theme/views-view-list.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-list.tpl.php,v
retrieving revision 1.1
diff -u -p -r1.1 views-view-list.tpl.php
--- theme/views-view-list.tpl.php	15 Feb 2008 04:11:48 -0000	1.1
+++ theme/views-view-list.tpl.php	13 May 2008 22:24:13 -0000
@@ -4,11 +4,15 @@
  * @file views-view-list.tpl.php
  * Default simple view template to display a list of rows.
  *
+ * - $title : The title of this group of rows.  May be empty.
  * - $options['type'] will either be ul or ol.
  * @ingroup views_templates
  */
 ?>
 <div class="item-list">
+  <?php if (!empty($title)) : ?>
+    <h3><?php print $title; ?></h3>
+  <?php endif; ?>
   <<?php print $options['type']; ?>>
     <?php foreach ($rows as $row): ?>
       <li><?php print $row ?></li>
Index: theme/views-view-table.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-table.tpl.php,v
retrieving revision 1.3
diff -u -p -r1.3 views-view-table.tpl.php
--- theme/views-view-table.tpl.php	28 Mar 2008 19:17:17 -0000	1.3
+++ theme/views-view-table.tpl.php	13 May 2008 22:24:13 -0000
@@ -4,6 +4,7 @@
  * @file views-view-table.tpl.php
  * Template to display a view as a table.
  *
+ * - $title : The title of this group of rows.  May be empty.
  * - $header: An array of header labels keyed by field id.
  * - $fields: An array of CSS IDs to use for each field id.
  * - $rows: An array of row items. Each row is an array of content
@@ -12,6 +13,9 @@
  */
 ?>
 <table>
+  <?php if (!empty($title)) : ?>
+    <caption><?php print $title; ?></caption>
+  <?php endif; ?>
   <thead>
     <tr>
       <?php foreach ($header as $field => $label): ?>
Index: theme/views-view-unformatted.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-unformatted.tpl.php,v
retrieving revision 1.2
diff -u -p -r1.2 views-view-unformatted.tpl.php
--- theme/views-view-unformatted.tpl.php	15 Feb 2008 04:11:48 -0000	1.2
+++ theme/views-view-unformatted.tpl.php	13 May 2008 22:24:13 -0000
@@ -7,6 +7,9 @@
  * @ingroup views_templates
  */
 ?>
+<?php if (!empty($title)) : ?>
+  <h3><?php print $title; ?></h3>
+<?php endif; ?>
 <?php foreach ($rows as $row): ?>
   <?php print $row ?>
 <?php endforeach; ?>
\ No newline at end of file
