You are here

multiple_fields_remove_button.module in Multiple Fields Remove Button 7

Same filename and directory in other branches
  1. 8 multiple_fields_remove_button.module

In this file we use widget hooks to extend their functionality.

We use drupal hooks and add remove button for fields remove.

File

multiple_fields_remove_button.module
View source
<?php

/**
 * @file
 * In this file we use widget hooks to extend their functionality.
 *
 * We use drupal hooks and add remove button for fields remove.
 */

/**
 * Implements hook_menu().
 */
function multiple_fields_remove_button_menu() {
  $items = array();
  $items['multiple_fields_remove_button/ajax'] = array(
    'title' => 'Remove item callback',
    'page callback' => 'multiple_fields_remove_button_js',
    'delivery callback' => 'ajax_deliver',
    'access callback' => TRUE,
    'theme callback' => 'ajax_base_page_theme',
    'type' => MENU_CALLBACK,
    'file path' => 'includes',
    'file' => 'form.inc',
  );
  return $items;
}

/**
 * Ajax callback remove field when remove click is trigger.
 *
 * In this callback we will replace field items. Main job
 * to delete field item we will done into submit handler.
 *
 * @see multiple_fields_remove_button_field_widget_form_alter()
 */
function multiple_fields_remove_button_js() {
  if (isset($_POST['ajax_html_ids'])) {
    unset($_POST['ajax_html_ids']);
  }
  list($form, $form_state) = ajax_get_form();
  drupal_process_form($form['#form_id'], $form, $form_state);

  // Get the information on what we're removing.
  $button = $form_state['triggering_element'];

  // Go two levels up in the form, to the whole widget.
  $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -3));

  // Now send back the proper AJAX command to replace it.
  $return = array(
    '#type' => 'ajax',
    '#commands' => array(
      ajax_command_replace('#' . $element['#id'], drupal_render($element)),
    ),
  );

  // Because we're doing this ourselves, messages aren't automatic. We have
  // to add them.
  $messages = theme('status_messages');
  if ($messages) {
    $return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages);
  }
  return $return;
}

/**
 * Implements hook_form_alter().
 */
function multiple_fields_remove_button_form_alter(&$form, &$form_state) {
  if (isset($_POST['ajax_html_ids'])) {
    $name = '';
    if (isset($form_state['clicked_button']['#parents'][0]) && !empty($form_state['clicked_button']['#parents'][0])) {
      $name = $form_state['clicked_button']['#parents'][0];
    }
    $language = !empty($form['language']['#value']) ? $form['language']['#value'] : LANGUAGE_NONE;
    if (!empty($name) && isset($form_state['input'][$name][$language]) && !empty($form_state['input'][$name][$language])) {
      $count = count($form_state['input'][$name][$language]) - 1;
      if (isset($form[$name][$language]) && !empty($form[$name][$language])) {
        foreach ($form[$name][$language] as $key => &$value) {
          if (isset($key) && is_numeric($key) && !empty($value['target_id'])) {
            if ($key > $count) {
              $value['target_id']['#default_value'] = '';
            }
          }
        }
      }
    }
  }
}

/**
 * Implements hook_field_widget_form_alter().
 */
function multiple_fields_remove_button_field_widget_form_alter(&$element, &$form_state, $context) {

  // Remove button for the following field type widgets.
  $fieldwidgets = array(
    'multiple_selects',
    'text_textfield',
    'date_popup',
    'date_text',
    'link_field',
    'entityreference_autocomplete',
    'number',
    'text_textarea',
    'text_textarea_with_summary',
    'addressfield_standard',
    'location',
  );

  // Add extra widgets with the help of others modules.
  drupal_alter('multiple_field_remove_button_field_widgets', $fieldwidgets);
  if (isset($element['#entity']) && $element['#entity'] && $context['field']['cardinality'] == FIELD_CARDINALITY_UNLIMITED && in_array($context['instance']['widget']['type'], $fieldwidgets)) {
    $original_element = $element;
    if ($context['field']['type'] == 'entityreference') {
      $original_element = $element['target_id'];
    }
    $field_parents = isset($original_element['#field_parents']) ? $original_element['#field_parents'] : array();
    $field_name = isset($original_element['#field_name']) ? $original_element['#field_name'] : NULL;
    $language = isset($original_element['#language']) ? $original_element['#language'] : LANGUAGE_NONE;
    $delta = isset($original_element['#delta']) ? $original_element['#delta'] : 0;

    // Get parent which will we use into Remove Button Element.
    $parents = array_merge($field_parents, array(
      $field_name,
      $language,
      $delta,
    ));
    $element['remove_button'] = array(
      '#delta' => $delta,
      '#name' => implode('_', $parents) . '_remove_button',
      '#type' => 'submit',
      '#value' => t('Remove'),
      '#validate' => array(),
      '#attributes' => array(
        'class' => array(
          'multiple-fields-remove-button',
        ),
      ),
      '#submit' => array(
        'multiple_fields_remove_button_submit_handler',
      ),
      '#limit_validation_errors' => array(),
      '#ajax' => array(
        'path' => 'multiple_fields_remove_button/ajax',
        'effect' => 'fade',
      ),
      '#weight' => 1000,
    );
  }
}

/**
 * Submit callback to remove an item from the field UI multiple wrapper.
 *
 * When a remove button is submitted, we need to find the item that it
 * referenced and delete it. Since field UI has the deltas as a straight
 * unbroken array key, we have to renumber everything down. Since we do this
 * we *also* need to move all the deltas around in the $form_state['values']
 * and $form_state['input'] so that user changed values follow. This is a bit
 * of a complicated process.
 */
function multiple_fields_remove_button_submit_handler($form, &$form_state) {
  $button = $form_state['triggering_element'];
  $delta = $button['#delta'];
  $inpt = 'input';

  // Where in the form we'll find the parent element.
  $address = array_slice($button['#array_parents'], 0, -2);

  // Go one level up in the form, to the widgets container.
  $parent_element = drupal_array_get_nested_value($form, $address);
  $field_name = $parent_element['#field_name'];
  $langcode = $parent_element['#language'];
  $parents = $parent_element['#field_parents'];

  // We should update also entity (if set) in form state
  // to correctly add new items.
  if (isset($form['#entity_type']) && isset($form_state[$form['#entity_type']]->{$field_name})) {
    $entity_field =& $form_state[$form['#entity_type']]->{$field_name};
  }
  else {
    $entity_field = array();
  }
  $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state);

  // Go ahead and renumber everything from our delta to the last
  // item down one. This will overwrite the item being removed.
  for ($i = $delta; $i <= $field_state['items_count']; $i++) {
    $old_element_address = array_merge($address, array(
      $i + 1,
    ));
    $new_element_address = array_merge($address, array(
      $i,
    ));

    // Address of field elements oin the entity field.
    $old_entity_element_address = $old_element_address;
    array_shift($old_entity_element_address);
    $new_entity_element_address = $new_element_address;
    array_shift($new_entity_element_address);
    $moving_element = drupal_array_get_nested_value($form, $old_element_address);
    $moving_element_value = drupal_array_get_nested_value($form_state['values'], $old_element_address);
    $moving_element_input = drupal_array_get_nested_value($form_state[$inpt], $old_element_address);
    $moving_element_item = drupal_array_get_nested_value($entity_field, $old_entity_element_address);

    // Tell the element where it's being moved to.
    $moving_element['#parents'] = $new_element_address;

    // Move the element around.
    form_set_value($moving_element, $moving_element_value, $form_state);
    drupal_array_set_nested_value($form_state[$inpt], $moving_element['#parents'], $moving_element_input);
    drupal_array_set_nested_value($entity_field, $new_entity_element_address, $moving_element_item);

    // Move the entity in our saved state.
    if (isset($field_state['entity'][$i + 1])) {
      $field_state['entity'][$i] = $field_state['entity'][$i + 1];
    }
    else {
      unset($field_state['entity'][$i]);
    }
  }

  // Replace the deleted entity with an empty one. This helps to ensure that
  // trying to add a new entity won't resurrect a deleted entity
  // from thev trash bin.
  // $count = count($field_state['entity']);
  // Then remove the last item. But we must not go negative.
  if ($field_state['items_count'] > 0) {
    $field_state['items_count']--;
  }

  // Fix the weights. Field UI lets the weights be in a range of
  // (-1 * item_count) to (item_count). This means that when we remove one,
  // the range shrinks; weights outside of that range then get set to
  // the first item in the select by the browser, floating them to the top.
  // We use a brute force method because we lost weights on both ends
  // and if the user has moved things around, we have to cascade because
  // if I have items weight weights 3 and 4, and I change 4 to 3 but leave
  // the 3, the order of the two 3s now is undefined and may not match what
  // the user had selected.
  $input = drupal_array_get_nested_value($form_state[$inpt], $address);

  // Sort by weight.
  uasort($input, '_field_sort_items_helper');

  // Reweight everything in the correct order.
  $weight = -1 * $field_state['items_count'];
  foreach ($input as $key => $item) {
    if ($item) {
      $input[$key]['_weight'] = $weight++;
    }
  }
  drupal_array_set_nested_value($form_state[$inpt], $address, $input);
  field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state);
  $form_state['rebuild'] = TRUE;
}

Functions

Namesort descending Description
multiple_fields_remove_button_field_widget_form_alter Implements hook_field_widget_form_alter().
multiple_fields_remove_button_form_alter Implements hook_form_alter().
multiple_fields_remove_button_js Ajax callback remove field when remove click is trigger.
multiple_fields_remove_button_menu Implements hook_menu().
multiple_fields_remove_button_submit_handler Submit callback to remove an item from the field UI multiple wrapper.