6

I'm creating a Block Plugin with a form. I'm able to create the standard form with various input field types. Like so.

    $form['text_field_1'] = array(
        '#type'          => 'textfield',
        '#title'         => $this->t('Entry 1'),
        '#default_value' => $this->configuration['text_field_1_submit'],
        '#size'          => 60,
        '#maxlength'     => 10,
    );

    $form['text_field_2'] = array(
        '#type'          => 'textfield',
        '#title'         => $this->t('Entry 2'),
        '#default_value' => $this->configuration['text_field_2_submit'],
        '#size'          => 60,
        '#maxlength'     => 10,
    );

This works but I can't anticipate how many entries the user may need, so I'd like users to be able to add multiple field entries in the same way that you can in a content type field.

enter image description here

Is this possible?

4 Answers 4

5

This is not a form API level feature, it's field/widget API.

For a custom form, you have to build it yourself with an add more button that uses #ajax to add more elements. A full code example would be too complicated for an answer, but you have to keep track of the count in $form_state, on pressing of the button increaes it, rebuild the form and then return the new elements.

See https://api.drupal.org/api/drupal/core%21core.api.php/group/ajax/8.2.x for more documentation, a quick tutorial that I found: https://www.sitepoint.com/using-ajax-forms-drupal-8/

3
  • 1
    Just checked, the drupal 8 example module has now an ajax add more example (commited on July 9).
    – 4uk4
    Commented Jul 22, 2016 at 19:31
  • are you talking about this module? drupal.org/project/examples because I'm having trouble finding the ajax add more example.
    – Amy
    Commented Aug 3, 2016 at 14:50
  • The ajax add more example in the examples module was put in the fapi_example module. If you enable it, you should see it at: /examples/fapi-example/ajax-addmore Commented Mar 28, 2017 at 20:53
3

Here's the example from the examples module found here

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements the ajax demo form controller.
 *
 * This example demonstrates using ajax callbacks to add people's names to a
 * list of picnic attendees.
 *
 * @see \Drupal\Core\Form\FormBase
 * @see \Drupal\Core\Form\ConfigFormBase
 */
class AjaxAddMore extends DemoBase {

  /**
   * Form with 'add more' and 'remove' buttons.
   *
   * This example shows a button to "add more" - add another textfield, and
   * the corresponding "remove" button.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['description'] = [
      '#type' => 'item',
      '#markup' => $this->t('This example shows an add-more and a remove-last button.'),
    ];

    // Gather the number of names in the form already.
    $num_names = $form_state->get('num_names');
    // We have to ensure that there is at least one name field.
    if ($num_names === NULL) {
      $name_field = $form_state->set('num_names', 1);
      $num_names = 1;
    }

    $form['#tree'] = TRUE;
    $form['names_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('People coming to picnic'),
      '#prefix' => '<div id="names-fieldset-wrapper">',
      '#suffix' => '</div>',
    ];

    for ($i = 0; $i < $num_names; $i++) {
      $form['names_fieldset']['name'][$i] = [
        '#type' => 'textfield',
        '#title' => $this->t('Name'),
      ];
    }

    $form['names_fieldset']['actions'] = [
      '#type' => 'actions',
    ];
    $form['names_fieldset']['actions']['add_name'] = [
      '#type' => 'submit',
      '#value' => $this->t('Add one more'),
      '#submit' => ['::addOne'],
      '#ajax' => [
        'callback' => '::addmoreCallback',
        'wrapper' => 'names-fieldset-wrapper',
      ],
    ];
    // If there is more than one name, add the remove button.
    if ($num_names > 1) {
      $form['names_fieldset']['actions']['remove_name'] = [
        '#type' => 'submit',
        '#value' => $this->t('Remove one'),
        '#submit' => ['::removeCallback'],
        '#ajax' => [
          'callback' => '::addmoreCallback',
          'wrapper' => 'names-fieldset-wrapper',
        ],
      ];
    }
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'form_api_example_ajax_addmore';
  }

  /**
   * Callback for both ajax-enabled buttons.
   *
   * Selects and returns the fieldset with the names in it.
   */
  public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    return $form['names_fieldset'];
  }

  /**
   * Submit handler for the "add-one-more" button.
   *
   * Increments the max counter and causes a rebuild.
   */
  public function addOne(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    $add_button = $name_field + 1;
    $form_state->set('num_names', $add_button);
    // Since our buildForm() method relies on the value of 'num_names' to
    // generate 'name' form elements, we have to tell the form to rebuild. If we
    // don't do this, the form builder will not call buildForm().
    $form_state->setRebuild();
  }

  /**
   * Submit handler for the "remove one" button.
   *
   * Decrements the max counter and causes a form rebuild.
   */
  public function removeCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    if ($name_field > 1) {
      $remove_button = $name_field - 1;
      $form_state->set('num_names', $remove_button);
    }
    // Since our buildForm() method relies on the value of 'num_names' to
    // generate 'name' form elements, we have to tell the form to rebuild. If we
    // don't do this, the form builder will not call buildForm().
    $form_state->setRebuild();
  }

  /**
   * Final submit handler.
   *
   * Reports what values were finally set.
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue(['names_fieldset', 'name']);

    $output = $this->t('These people are coming to the picnic: @names', [
      '@names' => implode(', ', $values),
    ]
    );
    $this->messenger()->addMessage($output);
  }

}
1

I found this tutorial for Drupal 7 useful from Rapid Development Group. Their code below.

function custom_registration_form($form, &$form_state) {
  $form_state['storage']['participants'] =
    isset($form_state['storage']['participants']) ? $form_state['storage']['participants'] : 0;

  $form['name'] = array(
    '#title' => t('Name'),
    '#type' => 'textfield',
  );

  $form['participants'] = array(
    '#type' => 'container',
    '#tree' => TRUE,
    '#prefix' => '<div id="participants">',
    '#suffix' => '</div>',
  );

  if ($form_state['storage']['participants']) {
    for ($i = 1; $i <= $form_state['storage']['participants']; $i++) {
      $form['participants'][$i] = array(
        '#type' => 'fieldset',
        '#tree' => TRUE,
      );

      $form['participants'][$i]['name'] = array(
        '#title' => t('Name'),
        '#type' => 'textfield',
      );
    }
  }

  $form['add_participant'] = array(
    '#type' => 'button',
    '#value' => t('Add A Participant'),
    '#href' => '',
    '#ajax' => array(
    'callback' => 'custom_registration_ajax_add_participant',
    'wrapper' => 'participants',
   ),
  );

  $form['submit'] = array(
    '#value' => t('Register'),
    '#type' => 'submit', 
  );

  $form_state['storage']['participants']++;

  return $form;
}



function custom_registration_ajax_add_participant($form, $form_state) {
  return $form['participants'];
}
1

For those wanting to add multiple text field values in Drupal 8/9 theme settings (via - hook_form_system_theme_settings_alter() here is some code.

Based off a combination of:

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_system_theme_settings_alter().
 */
function mytheme_form_system_theme_settings_alter(&$form, FormStateInterface $form_state) {
  // Some existing field set.
  $form['my_fieldset'] = [
    '#type' => 'details',
    '#title' => t('My existing fieldset'),
    '#description' => t('My existing description'),
  ];
  // My item settings.
  $form['my_fieldset']['myitem_settings'] = [
    '#type' => 'details',
    '#title' => t('My item settings'),
    '#description' => t('My description'),
  ];
  // Gather the items list in the form state already.
  // Key 'myitems' must be same as array key.
  $form_state_values = $form_state->getValues();
  $myitems = isset($form_state_values['myitems']) ? $form_state_values['myitems'] : NULL;
  // If NULL, try theme settings.
  if ($myitems === NULL) {
    $myitems = theme_get_setting('myitems');
  }
  // We have to ensure that there is at least one item.
  if ($myitems === NULL) {
    $myitems = [''];
  }
  $form['my_fieldset']['myitem_settings']['myitems_fieldset'] = [
    '#type' => 'fieldset',
    '#title' => t('My item list'),
    '#prefix' => '<div id="myitems-fieldset-wrapper">',
    '#suffix' => '</div>',
  ];
  $form['my_fieldset']['myitem_settings']['myitems_fieldset']['myitems'] = [
    // Required for correct inputs to be generated.
    '#tree' => TRUE,
  ];
  foreach ($myitems as $key => $html_cache_headers_url) {
    $form['my_fieldset']['myitem_settings']['myitems_fieldset']['myitems'][$key] = [
      '#type' => 'textfield',
      '#title' => t('A single item'),
      '#description' => t('My single item description.'),
      // Key 'myitems' must be same as parent key.
      '#default_value' => theme_get_setting('myitems.' . $key),
    ];
  }
  $form['my_fieldset']['myitem_settings']['myitems_fieldset']['actions'] = [
    '#type' => 'actions',
  ];
  $form['my_fieldset']['myitem_settings']['myitems_fieldset']['actions']['add_item'] = [
    '#type' => 'submit',
    '#value' => t('Add one more'),
    '#submit' => ['__mytheme_form_system_theme_settings_alter__add_one_item'],
    '#ajax' => [
      'callback' => '__mytheme_form_system_theme_settings_alter__add_more_callback',
      'wrapper' => 'myitems-fieldset-wrapper',
    ],
  ];
  // If there is more than one item, add the remove button.
  if (count($myitems) > 1) {
    $form['my_fieldset']['myitem_settings']['myitems_fieldset']['actions']['remove_item'] = [
      '#type' => 'submit',
      '#value' => t('Remove one'),
      '#submit' => ['__mytheme_form_system_theme_settings_alter__remove_callback'],
      '#ajax' => [
        'callback' => '__mytheme_form_system_theme_settings_alter__add_more_callback',
        'wrapper' => 'myitems-fieldset-wrapper',
      ],
    ];
  }
}

/**
 * Submit handler for the "add-one-more" button.
 *
 * Increments the amount of items and causes a rebuild.
 */
function __mytheme_form_system_theme_settings_alter__add_one_item(&$form, FormStateInterface $form_state) {
  $form_state_values = &$form_state->getValues();
  $paths_field = isset($form_state_values['myitems']) ? $form_state_values['myitems'] : [];
  $paths_field[] = '';
  $form_state_values['myitems'] = $paths_field;
  // Since our form() method relies on the value of 'myitems'
  // to generate 'item' form elements, we have to tell the form to rebuild.
  // If we don't do this, the form builder will not call buildForm().
  $form_state->setRebuild();
}

/**
 * Callback for both ajax-enabled buttons.
 *
 * Selects and returns the fieldset with the paths in it.
 */
function __mytheme_form_system_theme_settings_alter__add_more_callback(&$form, FormStateInterface $form_state) {
  return $form['my_fieldset']['myitem_settings']['myitems_fieldset'];
}

/**
 * Submit handler for the "remove one" button.
 *
 * Decrements the amount of items from the bottom and causes a form rebuild.
 */
function __mytheme_form_system_theme_settings_alter__remove_callback(&$form, FormStateInterface $form_state) {
  $form_state_values = &$form_state->getValues();
  $paths_field = isset($form_state_values['myitems']) ? $form_state_values['myitems'] : [];
  if ($paths_field > 1) {
    array_pop($paths_field);
    $form_state_values['myitems'] = $paths_field;
  }
  // Since our buildForm() method relies on the value of
  // 'myitems' to generate 'item' form elements, we have to
  // tell the form to rebuild. If we don't do this, the form builder will
  // not call buildForm().
  $form_state->setRebuild();
}

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.