? cck-196421-84.patch
? content_multigroup-119102-320.patch
? images
? out
? theme/content-edit.js
Index: content.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cck/Attic/content.module,v
retrieving revision 1.301.2.99
diff -u -p -r1.301.2.99 content.module
--- content.module	5 Mar 2009 22:58:57 -0000	1.301.2.99
+++ content.module	10 Mar 2009 22:55:14 -0000
@@ -490,8 +490,11 @@ function theme_content_multiple_values($
         'data' => t('!title: !required', array('!title' => $element['#title'], '!required' => $required)),
         'colspan' => 2
       ),
-      t('Order'),
+      array('data' => t('Order'), 'class' => 'content-multiple-weight-header'),
     );
+    if ($field['multiple'] == 1) {
+      $header[] = array('data' => t('Remove'), 'class' => 'content-multiple-remove-header');
+    }
     $rows = array();
 
     // Sort items according to '_weight' (needed when the form comes back after
@@ -499,24 +502,31 @@ function theme_content_multiple_values($
     $items = array();
     foreach (element_children($element) as $key) {
       if ($key !== $element['#field_name'] .'_add_more') {
-        $items[] = &$element[$key];
+        $items[$element[$key]['#delta']] = &$element[$key];
       }
     }
-    usort($items, '_content_sort_items_value_helper');
+    uasort($items, '_content_sort_items_value_helper');
 
     // Add the items as table rows.
-    foreach ($items as $key => $item) {
+    foreach ($items as $delta => $item) {
       $item['_weight']['#attributes']['class'] = $order_class;
       $delta_element = drupal_render($item['_weight']);
+      if ($field['multiple'] == 1) {
+        $remove_element = drupal_render($item['_remove']);
+      }
       $cells = array(
         array('data' => '', 'class' => 'content-multiple-drag'),
         drupal_render($item),
         array('data' => $delta_element, 'class' => 'delta-order'),
       );
-      $rows[] = array(
-        'data' => $cells,
-        'class' => 'draggable',
-      );
+      $row_class = 'draggable';
+      if ($field['multiple'] == 1) {
+        if (!empty($item['_remove']['#default_value'])) {
+          $row_class .= ' content-multiple-removed-row';
+        }
+        $cells[] = array('data' => $remove_element, 'class' => 'content-multiple-remove-cell');
+      }
+      $rows[] = array('data' => $cells, 'class' => $row_class);
     }
 
     $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table'));
@@ -524,6 +534,7 @@ function theme_content_multiple_values($
     $output .= drupal_render($element[$element['#field_name'] .'_add_more']);
 
     drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
+    drupal_add_js(drupal_get_path('module', 'content') .'/theme/content-edit.js');
   }
   else {
     foreach (element_children($element) as $key) {
@@ -724,8 +735,8 @@ function content_field($op, &$node, $fie
     case 'view':
       $addition = array();
       
-      // Previewed nodes bypass the 'presave' op, so we need to some massaging.
-      if ($node->build_mode == NODE_BUILD_PREVIEW && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
+      // Previewed nodes bypass the 'presave' op, so we need to do some massaging.
+      if ($node->build_mode == NODE_BUILD_PREVIEW) {
         if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
           // Reorder items to account for drag-n-drop reordering.
           $items = _content_sort_items($field, $items);
@@ -770,10 +781,10 @@ function content_field($op, &$node, $fie
         );
 
         // Fill-in items.
-        foreach ($items as $delta => $item) {
+        foreach (array_keys($items) as $weight => $delta) {
           $element['items'][$delta] = array(
-            '#item' => $item,
-            '#weight' => $delta,
+            '#item' => $items[$delta],
+            '#weight' => $weight,
           );
         }
 
@@ -893,20 +904,22 @@ function content_field($op, &$node, $fie
  *   returns filtered and adjusted item array
  */
 function content_set_empty($field, $items) {
-  // Filter out empty values.
+  // Prepare an empty item.
+  $empty = array();
+  foreach (array_keys($field['columns']) as $column) {
+    $empty[$column] = NULL;
+  }
+
+  // Filter out items flagged for removal.
   $filtered = array();
   $function = $field['module'] .'_content_is_empty';
   foreach ((array) $items as $delta => $item) {
-    if (!$function($item, $field)) {
-      $filtered[] = $item;
+    if (empty($item['_remove'])) {
+      $filtered[] = ($function($item, $field) ? $empty : $item);
     }
   }
 
   // Make sure we store the right number of 'empty' values.
-  $empty = array();
-  foreach (array_keys($field['columns']) as $column) {
-    $empty[$column] = NULL;
-  }
   $pad = $field['multiple'] > 1 ? $field['multiple'] : 1;
   $filtered = array_pad($filtered, $pad, $empty);
 
@@ -921,7 +934,7 @@ function _content_sort_items($field, $it
   if ($field['multiple'] >= 1 && isset($items[0]['_weight'])) {
     usort($items, '_content_sort_items_helper');
     foreach ($items as $delta => $item) {
-      if (is_array($items[$delta])) {
+      if (is_array($item) && isset($item['_weight'])) {
         unset($items[$delta]['_weight']);
       }
     }
@@ -1002,7 +1015,18 @@ function content_storage($op, $node) {
             if (!isset($additions[$field_name])) {
               $additions[$field_name] = array();
             }
-            $additions[$field_name][] = $item;
+
+            // When the node is loaded and saved programatically, we need to
+            // ensure all items are preserved during content_set_empty().
+            $item['_remove'] = 0;
+
+            // Preserve deltas when loading items from database.
+            if (isset($row['delta'])) {
+              $additions[$field_name][$row['delta']] = $item;
+            }
+            else {
+              $additions[$field_name][] = $item;
+            }
           }
         }
       }
Index: includes/content.node_form.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cck/includes/Attic/content.node_form.inc,v
retrieving revision 1.7.2.18
diff -u -p -r1.7.2.18 content.node_form.inc
--- includes/content.node_form.inc	10 Feb 2009 22:53:04 -0000	1.7.2.18
+++ includes/content.node_form.inc	10 Mar 2009 22:55:14 -0000
@@ -151,21 +151,23 @@ function content_multiple_value_form(&$f
 
   switch ($field['multiple']) {
     case 0:
+      $deltas = array(0);
       $max = 0;
       break;
-    case 1:
-      $filled_items = content_set_empty($field, $items);
-      $current_item_count = isset($form_state['item_count'][$field_name])
-                            ? $form_state['item_count'][$field_name]
-                            : count($items);
-      // We always want at least one empty icon for the user to fill in.
-      $max = ($current_item_count > count($filled_items))
-              ? $current_item_count - 1
-              : $current_item_count;
 
+    case 1:
+      $deltas = array_keys($items);
+      $current_item_count = isset($form_state['item_count'][$field_name]) ? $form_state['item_count'][$field_name] : max(1, count($deltas));
+      $max = (!empty($deltas) ? max($deltas) : -1);
+      while (count($deltas) < $current_item_count) {
+        $max++;
+        $deltas[] = $max;
+      }
       break;
+
     default:
       $max = $field['multiple'] - 1;
+      $deltas = array_keys(array_fill(0, $field['multiple'], 0));
       break;
   }
 
@@ -180,7 +182,7 @@ function content_multiple_value_form(&$f
   );
   $function = $field['widget']['module'] .'_widget';
 
-  for ($delta = 0; $delta <= $max; $delta++) {
+  foreach ($deltas as $delta) {
     if ($element = $function($form, $form_state, $field, $items, $delta)) {
       $defaults = array(
         '#title' => ($field['multiple'] >= 1) ? '' : $title,
@@ -206,6 +208,21 @@ function content_multiple_value_form(&$f
         );
       }
 
+      // Add a checkbox to allow users remove a single delta item.
+      // See content_set_empty() and theme_content_multiple_values().
+      if ($field['multiple'] == 1) {
+        // We name the element '_remove' to avoid clashing with column names
+        // defined by field modules.
+        $element['_remove'] = array(
+          '#type' => 'checkbox',
+          '#attributes' => array(
+            'class' => 'content-multiple-remove-checkbox',
+            'alt' => 'type:'. $field['widget']['type'] .',name:'. $field_name .',delta:'. $delta,
+          ),
+          '#default_value' => isset($items[$delta]['_remove']) ? $items[$delta]['_remove'] : 0,
+        );
+      }
+
       $form_element[$delta] = array_merge($element, $defaults);
     }
   }
@@ -313,11 +330,13 @@ function content_add_more_js($type_name_
   unset($form_state['values'][$field_name][$field['field_name'] .'_add_more']);
   foreach ($_POST[$field_name] as $delta => $item) {
     $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
+    $form_state['values'][$field_name][$delta]['_remove'] = isset($item['_remove']) ? $item['_remove'] : 0;
   }
   $form_state['values'][$field_name] = _content_sort_items($field, $form_state['values'][$field_name]);
   $_POST[$field_name] = _content_sort_items($field, $_POST[$field_name]);
 
   // Build our new form element for the whole field, asking for one more element.
+  $delta = max(array_keys($_POST[$field_name])) + 1;
   $form_state['item_count'] = array($field_name => count($_POST[$field_name]) + 1);
   $form_element = content_field_form($form, $form_state, $field);
   // Let other modules alter it.
@@ -337,7 +356,6 @@ function content_add_more_js($type_name_
 
   // Build the new form against the incoming $_POST values so that we can
   // render the new element.
-  $delta = max(array_keys($_POST[$field_name])) + 1;
   $_POST[$field_name][$delta]['_weight'] = $delta;
   $form_state = array('submitted' => FALSE);
   $form += array(
Index: theme/content-module.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cck/theme/Attic/content-module.css,v
retrieving revision 1.1.2.5
diff -u -p -r1.1.2.5 content-module.css
--- theme/content-module.css	27 Oct 2008 16:58:38 -0000	1.1.2.5
+++ theme/content-module.css	10 Mar 2009 22:55:14 -0000
@@ -27,6 +27,40 @@
   margin:0;
 }
 
+.content-multiple-remove-button {
+  display: block;
+  float: right;
+  height: 12px;
+  width: 16px;
+  margin: 2px 0 1px 0;
+  padding: 0;
+  background:transparent url(../images/button-remove.png) no-repeat 0 0;
+  border-bottom: #C2C9CE 1px solid;
+  border-right: #C2C9CE 1px solid;
+}
+.content-multiple-remove-button:hover {
+  background-position: 0 -12px;
+}
+.content-multiple-throbber,
+.content-multiple-throbber:hover {
+  background-image: url(../images/button-throbber.gif);
+  background-position: 0 0;
+  border: none;
+}
+.content-multiple-weight-header,
+.content-multiple-remove-header,
+.content-multiple-remove-cell,
+.content-multiple-table td.delta-order {
+  text-align: center;
+}
+html.js .content-multiple-weight-header,
+html.js .content-multiple-remove-header,
+html.js .content-multiple-removed-row,
+html.js .content-multiple-table td.delta-order,
+html.js .content-multiple-remove-checkbox {
+  display: none;
+}
+
 .node-form .number {
   display:inline;
   width:auto;
