6

I'm working on creating an unlimited value field in a custom form, and getting hung up on the remove button. I've been able to create a form that will pull the saved values for the fieldset, then allow the user to click an "Add" button to get more date field values in the fieldset. The problem is the individual remove button paired with each date. BTW, the context for the form is that a user needs to be able to add an indeterminate number of dates, then remove or update dates as needed.

I've tried adding the remove buttons with name values that correspond to the date's index in the $form and $form_state array values, then accessing that value through $form_state['clicked_button'], removing the date, then re-rendering the fieldset. I've also tried simply returning nothing from the ajax call in order to just remove that date from the form. I run into different issues with each approach.

If I try to re-render the entire fieldset in the callback, the remove button only removes the last date in the fieldset. I've dpm-ed all the values that I'm looking at, and I can't figure out where that data is being rendered from.

If I try to return an empty string from the callback and only update the single removed date field, it seems to work initially. After I hit save and the page refreshes though, I'm left with what seems to be a weird re-indexing of my values. For example, if I have consecutive days 1-4 in October and try to remove the 1st, after hitting save, I'm left with 2, 2, 3.

The code I'm working with (including some comments and troubleshooting) is below:

function daily_hours_form($form, &$form_state) {

  // Fieldset container where we can add unlimited values
  $form['closed_mon_exceptions'] = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#prefix' => '<div id="closed_mon">',
    '#suffix' => '</div>',
    '#description' => t('Exceptions to our typical closed Mondays.'),
  );
  // Add a flag to add on additional Mondays if the user clicks "Add Open Monday"
  $form_state['storage']['add_monday'] = isset($form_state['storage']['add_monday']) ? $form_state['storage']['add_monday'] : 0;
  if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#value'] == 'Add Open Monday') {
    $form_state['storage']['add_monday']++;
  }

  // If the user clicked to remove a day, need to regenerate the dates with that value removed
  if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#value'] == 'Remove Monday') {
    // get current values from form_state
    $closed_mon_dates_array = $form_state['values']['closed_mon_exceptions'];
    // remove the Add Open Monday button from this array
    array_pop($closed_mon_dates_array);
    // retrieve the index of the one we need to remove
    $remove_mon = $form_state['clicked_button']['#name'];
    // Try to remove that value from any array it's being used in
    //unset($form['closed_mon_exceptions'][$remove_mon]);
    unset($form_state['values']['closed_mon_exceptions'][$remove_mon]);
    unset($form_state['complete form']['closed_mon_exceptions'][$remove_mon]);
    unset($closed_mon_dates_array[$remove_mon]);
    // reorder the array, so we have expected 0 through (count-1) indices
    $closed_mon_dates_array = array_values($closed_mon_dates_array);
  } else {
    // if we're not trying to remove a monday, just get the saved values
    $closed_mon_dates_array = variable_get('closed_mon_exceptions');
  }
  dpm($closed_mon_dates_array);
  dpm($form_state);
  $num_closed_mondays = count($closed_mon_dates_array);

  // Add all the current closed Monday exceptions
  for ($i = 0; $i < $num_closed_mondays; $i++) {
    if (isset($closed_mon_dates_array[$i])) {
      $current_date_val = $closed_mon_dates_array[$i];
      // Remove the value for the button, if this data was pulled from
      // $form_state above
      if (count($current_date_val) == 4) {
        array_pop($current_date_val);
      }
    }
    dpm($current_date_val); // Just making sure it's formatted as expected...
    $form['closed_mon_exceptions'][$i] = array(
      '#type' => 'date',
      '#default_value' => $current_date_val,
      '#title' => t('Closed Monday Exception'),
      '#prefix' => '<div id="monday-' . $i . '">',
      '#suffix' => '</div>',
    );
    // Add a remove button for each one
    $form['closed_mon_exceptions'][$i]['remove_day'] = array(
      '#type' => 'button',
      '#value' => t('Remove Monday'),
      '#name' => "$i",
      '#ajax' => array(
        'callback' => 'monday_remove',
        'wrapper' => 'closed_mon' // Update the whole fieldset
        //'wrapper' => 'monday-' . $i, // Alternately, try to update just this value
      ),
    );
  }

  // Check if user clicked "Add Open Monday"
  if ($form_state['storage']['add_monday'] > 0) {
    // Starting index for adding open Mondays
    $i = count($closed_mon_dates_array);
    $number_to_add = $form_state['storage']['add_monday'];
    $end_index = $i + $number_to_add;
    for ($index = $i; $index < $end_index; $index++) {
      $form['closed_mon_exceptions'][$index] = array(
        '#type' => 'date',
        '#default_value' => array(),
        '#title' => t('Closed Monday Exception'),
        '#tree' => TRUE,
        '#prefix' => '<div id="monday-' . $index . '">',
        '#suffix' => '</div>',
      );
      $form['closed_mon_exceptions'][$index]['remove_day'] = array(
        '#type' => 'button',
        '#value' => t('Remove Monday'),
        '#name' => "$index",
        '#ajax' => array(
          'callback' => 'monday_remove',
          'wrapper' => 'closed_mon',
          //'wrapper' => 'monday-' . $index,
        ),
      );
    }
  }
  $form['closed_mon_exceptions']['add_open_monday'] = array(
    '#type' => 'button',
    '#value' => t('Add Open Monday'),
    '#ajax' => array(
      'callback' => 'multi_date_ajax_callback',
      'wrapper' => 'closed_mon'
    ),
  );

  return system_settings_form($form);
}

function monday_remove($form, &$form_state) {
  //return ''; // Trying to update just the date + remove button field
  $form_state['rebuild'] = TRUE; // Tried adding this for troubleshooting; not seeing any difference
  return $form['closed_mon_exceptions'];
}

function multi_date_ajax_callback($form, &$form_state) {
  return $form['closed_mon_exceptions'];
}

1 Answer 1

3
+50

I believe I've got this figured out, and the code for two separate unlimited value fieldsets is included below.

The basic idea is on the initial form rendering, pull the stored values from the database. On subsequent renderings, pull the values from the $form_state['values'] array. If the user has clicked the 'Add item' button, one additional item is added to the fieldset. If the user clicks the 'Remove item' button, the index is pulled from the button name and the item is removed from anywhere it's set in $form_state. I had to remove it from multiple arrays in $form_state because there are different arrays used for the saving the data and rendering the form.

I also had to add some unique button names and preg_match to pull the index of the removed item properly. (There are some bugs that I believe may be related to an issue I was seeing: FAPI identifies wrong button and Wrong detection of triggering element)

function daily_hours_form($form, &$form_state) {

  // CLOSED MONDAY EXCEPTIONS
  // Fieldset container where we can add unlimited values
  $form['closed_mon_exceptions'] = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#prefix' => '<div id="closed_mon">',
    '#suffix' => '</div>',
    '#description' => t('Exceptions to our typical closed Mondays.'),
  );

  // Add a flag to add on additional Mondays if the user clicks "Add Open Monday"
  $form_state['storage']['add_monday'] = FALSE;
  if (isset($form_state['triggering_element']) && $form_state['triggering_element']['#value'] == 'Add Open Monday') {
    $form_state['storage']['add_monday'] = TRUE;
  }

  $form_state['storage']['monday_init'] = isset($form_state['storage']['monday_init']) ? $form_state['storage']['monday_init'] : TRUE;

  // First time through, grab the values from the DB
  if ($form_state['storage']['monday_init']) {
    $closed_mon_dates_array = variable_get('closed_mon_exceptions');
    $form_state['storage']['monday_init'] = FALSE;
  // On subsequent renderings, grab the values from the values array
  } else {
    // get current values from form_state
    $closed_mon_dates_array = $form_state['values']['closed_mon_exceptions'];
    // remove the Add Open Monday button from this array
    array_pop($closed_mon_dates_array);
    $num_closed_mondays = count($closed_mon_dates_array);
  }

  // Get all the stored closed Mondays exceptions and remove the user-selected index
  if (isset($form_state['triggering_element']) && $form_state['triggering_element']['#value'] == 'Remove Monday') {
    // retrieve the index of the one we need to remove
    // preg_match required to get around Drupal bug related to multiple
    // submit buttons with the same name
    preg_match('/\d+/', $form_state['triggering_element']['#name'], $remove_mon);
    $remove_mon = $remove_mon[0];
    unset($closed_mon_dates_array[$remove_mon]);
    // reorder the array, so we have expected 0 through (count-1) indices
    $closed_mon_dates_array = array_values($closed_mon_dates_array);

    // Update the form values that will be submitted
    unset($form_state['input']['closed_mon_exceptions'][$remove_mon]);
    $form_state['input']['closed_mon_exceptions'] = array_values($form_state['input']['closed_mon_exceptions']);
    // or re-rendered to the form
    unset($form_state['complete form']['closed_mon_exceptions'][$remove_mon]);
    unset($form_state['values']['closed_mon_exceptions'][$remove_mon]);
  }

  $num_closed_mondays = count($closed_mon_dates_array);

  // Add all the current closed Monday exceptions
  for ($i = 0; $i < $num_closed_mondays; $i++) {
    if (isset($closed_mon_dates_array[$i])) {
      $current_date_val = $closed_mon_dates_array[$i];
      if (count($current_date_val) == 4) {
        array_pop($current_date_val);
      }
    }
    $form['closed_mon_exceptions'][$i] = array(
      '#type' => 'date',
      '#default_value' => $current_date_val,
      '#title' => t('Closed Monday Exception'),
      '#prefix' => '<div id="monday-' . $i . '">',
      '#suffix' => '</div>',
    );
    // Add a remove button for each one
    $form['closed_mon_exceptions'][$i]['remove_day'] = array(
      '#type' => 'button',
      '#value' => t('Remove Monday'),
      '#name' => "removemon-$i", // need to create unique names, regardless of tree structure
      '#ajax' => array(
        'callback' => 'monday_ajax_callback',
        'wrapper' => 'closed_mon'
      ),
    );
  }

  // Check if user clicked "Add Open Monday"
  if ($form_state['storage']['add_monday']) {
    // index for this one is at the end of the stored values
    $index = count($closed_mon_dates_array);
    $form['closed_mon_exceptions'][$index] = array(
      '#type' => 'date',
      '#default_value' => array(),
      '#title' => t('Closed Monday Exception'),
      '#prefix' => '<div id="monday-' . $index . '">',
      '#suffix' => '</div>',
    );
    $form['closed_mon_exceptions'][$index]['remove_day'] = array(
      '#type' => 'button',
      '#value' => t('Remove Monday'),
      '#name' => "removemon-$index", // need to create unique names, regardless of tree structure
      '#ajax' => array(
        'callback' => 'monday_ajax_callback',
        'wrapper' => 'closed_mon',
      ),
    );
  }

  $form['closed_mon_exceptions']['add_open_monday'] = array(
    '#type' => 'button',
    '#value' => t('Add Open Monday'),
    '#name' => t('add-monday'),
    '#ajax' => array(
      'callback' => 'monday_ajax_callback',
      'wrapper' => 'closed_mon'
    ),
  );


  // HOLIDAYS
  // Fieldset container where we can add unlimited values
  $form['holidays'] = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#prefix' => '<div id="holidays">',
    '#suffix' => '</div>',
    '#description' => t('Additional closed days.'),
  );

  // Add a flag to add on additional Holidays if the user clicks "Add Holiday"
  $form_state['storage']['add_holiday'] = FALSE;
  if (isset($form_state['triggering_element']) && $form_state['triggering_element']['#value'] == 'Add Holiday') {
    $form_state['storage']['add_holiday'] = TRUE;
  }

  $form_state['storage']['holiday_init'] = isset($form_state['storage']['holiday_init']) ? $form_state['storage']['holiday_init'] : TRUE;

  // First time through, grab the values from the DB
  if ($form_state['storage']['holiday_init']) {
    $holidays_array = variable_get('holidays');
    $form_state['storage']['holiday_init'] = FALSE;
  // On subsequent renderings, grab the values from the values array
  } else {
    // get current values from form_state
    $holidays_array = $form_state['values']['holidays'];
    // remove the Add Holiday button from this array
    array_pop($holidays_array);
    $num_holidays = count($holidays_array);
  }

  // Get all the stored closed Holidays and remove the user-selected index
  if (isset($form_state['triggering_element']) && $form_state['triggering_element']['#value'] == 'Remove Holiday') {
    // retrieve the index of the one we need to remove
    // preg_match required to get around Drupal bug related to multiple
    // submit buttons with the same name
    preg_match('/\d+/', $form_state['triggering_element']['#name'], $remove_holiday);
    $remove_holiday = $remove_holiday[0];
    unset($holidays_array[$remove_holiday]);
    // reorder the array, so we have expected 0 through (count-1) indices
    $holidays_array = array_values($holidays_array);

    // Update the form values that will be submitted
    unset($form_state['input']['holidays'][$remove_holiday]);
    $form_state['input']['holidays'] = array_values($form_state['input']['holidays']);
    // or re-rendered to the form
    unset($form_state['complete form']['holidays'][$remove_holiday]);
    unset($form_state['values']['holidays'][$remove_holiday]);
  }

  $num_holidays = count($holidays_array);

  // Add all the current Holidays
  for ($i = 0; $i < $num_holidays; $i++) {
    if (isset($holidays_array[$i])) {
      $current_date_val = $holidays_array[$i];
      if (count($current_date_val) == 4) {
        array_pop($current_date_val);
      }
    }
    $form['holidays'][$i] = array(
      '#type' => 'date',
      '#default_value' => $current_date_val,
      '#title' => t('Closed Holiday'),
      '#prefix' => '<div id="holiday-' . $i . '">',
      '#suffix' => '</div>',
    );
    // Add a remove button for each one
    $form['holidays'][$i]['remove_day'] = array(
      '#type' => 'button',
      '#value' => t('Remove Holiday'),
      '#name' => "removehol-$i", // need to create unique names, regardless of tree structure
      '#ajax' => array(
        'callback' => 'holiday_ajax_callback',
        'wrapper' => 'holidays'
      ),
    );
  }


  // Check if user clicked "Add Holiday"
  if ($form_state['storage']['add_holiday']) {
    // index for this one is at the end of the stored values
    $index = count($holidays_array);
    $form['holidays'][$index] = array(
      '#type' => 'date',
      '#default_value' => array(),
      '#title' => t('Closed Holiday'),
      '#prefix' => '<div id="holiday-' . $index . '">',
      '#suffix' => '</div>',
    );
    $form['holidays'][$index]['remove_day'] = array(
      '#type' => 'button',
      '#value' => t('Remove Holiday'),
      '#name' => "removehol-$index", // need to create unique names, regardless of tree structure
      '#ajax' => array(
        'callback' => 'holiday_ajax_callback',
        'wrapper' => 'holidays',
      ),
    );
  }

  $form['holidays']['add_holiday'] = array(
    '#type' => 'button',
    '#value' => t('Add Holiday'),
    '#name' => t('add-holiday'),
    '#ajax' => array(
      'callback' => 'holiday_ajax_callback',
      'wrapper' => 'holidays'
    ),
  );

  return system_settings_form($form);
}

function monday_ajax_callback($form, &$form_state) {
  return $form['closed_mon_exceptions'];
}

function holiday_ajax_callback($form, &$form_state) {
  return $form['holidays'];
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.