5

I want to show a Add more button that adds an extra text field and the corresponding Remove button.

I followed ajax_example_add_more() to achieve this in Drupal 7. For Drupal 8, I tried with the following code.

class XmlfeedsSettingsForm extends FormBase {
  public function getFormId() {
    return 'formid';
  }


  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
   // Because we have many fields with the same values, we have to set
  // #tree to be able to access them.
  $form['#tree'] = TRUE;
  $form['names_fieldset'] = array(
    '#type' => 'fieldset',
    '#title' => t('People coming to the picnic'),
    // Set up the wrapper so that AJAX will be able to replace the fieldset.
    '#prefix' => '<div id="names-fieldset-wrapper">',
    '#suffix' => '</div>',
  );

  // Build the fieldset with the proper number of names. We'll use
  // $form_state['num_names'] to determine the number of textfields to build.
  $form_state->getValue['num_names_parent'] = '';
  if (empty($form_state->getValue['num_names_parent'])) {
    $form_state->getValue['num_names_parent'] = 1;
  }
  for ($i = 0; $i < $form_state->getValue['num_names_parent']; $i++) {
    $form['names_fieldset']['name'][$i] = array(
      '#type' => 'textfield',
      '#title' => t('Name'),
    );
  }
  $form['names_fieldset']['add_name'] = array(
    '#type' => 'submit',
    '#value' => t('Add one more'),
    '#submit' => array('::ajax_example_add_more_add_one'),
    // See the examples in ajax_example.module for more details on the
    // properties of #ajax.
    '#ajax' => array(
      'callback' => '::ajax_example_add_more_callback',
      'wrapper' => 'names-fieldset-wrapper',
    ),
  );
  if ($form_state->getValue['num_names_parent'] > 1) {
    $form['names_fieldset']['remove_name'] = array(
      '#type' => 'submit',
      '#value' => t('Remove one'),
      '#submit' => array('::ajax_example_add_more_remove_one'),
      '#ajax' => array(
        'callback' => '::ajax_example_add_more_callback',
        'wrapper' => 'names-fieldset-wrapper',
      ),
    );
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  // This simply allows us to demonstrate no-javascript use without
  // actually turning off javascript in the browser. Removing the #ajax
  // element turns off AJAX behaviors on that element and as a result
  // ajax.js doesn't get loaded.
  // For demonstration only! You don't need this.
 // if ($no_js_use) {
    // Remove the #ajax from the above, so ajax.js won't be loaded.
//    if (!empty($form['names_fieldset']['remove_name']['#ajax'])) {
//      unset($form['names_fieldset']['remove_name']['#ajax']);
//    }
//    unset($form['names_fieldset']['add_name']['#ajax']);
 // }

  return $form;
  }
  public function ajax_example_add_more_add_one(array &$form, FormStateInterface $form_state) {
      print_R($form_state);die;
  //$form_state->getValue['num_names_parent']++;
  //$form_state->setRebuild(TRUE);


}
public function ajax_example_add_more_callback(array &$form, FormStateInterface $form_state) {
  return $form['names_fieldset'];

}
public function ajax_example_add_more_remove_one(array &$form, FormStateInterface $form_state) {
  if ($form_state->getValue['num_names_parent'] > 1) {
    $form_state->getValue['num_names_parent']--;
  }
  $form_state['rebuild'] = TRUE;
}

print_r($form_state) in buildForm() outputs the following.

[getValue] => Array
        (
            [num_names_parent] => 1
        )

In ajax_example_add_more_add_one(), the num_names_parent index is missing.

How can I correctly add the Add more button?

  • Ok, which form you want this 'add_more' behavior? If you talking about Fields, just change storage setting to anything higher than 1. – Vagner May 3 '16 at 18:47
  • I have edit in the questions, It is basically any custom form, so field storage is not required here I think. Any possible way to achieve it? – Sugandh Khanna May 4 '16 at 7:10
  • Looking at the core, I found how Drupal implements this behavior, take a look in WidgetBase.php. If you need more help just ask. (there is no remove button, empty fields will be ignored). – Vagner May 4 '16 at 12:31
  • @see api.drupal.org/api/examples/… – leymannx Mar 11 at 22:01
9

It works !! Try this

namespace Drupal\fapi_example\Form;

use Drupal\Core\Form\FormStateInterface;

class AjaxAddMore extends DemoBase {

  public function buildForm(array $form, FormStateInterface $form_state) {
        $form['description'] = array(
    '#markup' => '<div>'. t('This example shows an add-more and a remove-last button.').'</div>',
  );
    $i = 0;
    $name_field = $form_state->get('num_names');
    $form['#tree'] = TRUE;
    $form['names_fieldset'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('People coming to picnic'),
      '#prefix' => '<div id="names-fieldset-wrapper">',
      '#suffix' => '</div>',
    ];
    if (empty($name_field)) {
      $name_field = $form_state->set('num_names', 1);
    }
    for ($i = 0; $i < $name_field; $i++) {
      $form['names_fieldset']['name'][$i] = [
        '#type' => 'textfield',
        '#title' => t('Name'),
      ];
    }
    $form['actions'] = [
      '#type' => 'actions',
    ];
    $form['names_fieldset']['actions']['add_name'] = [
      '#type' => 'submit',
      '#value' => t('Add one more'),
      '#submit' => array('::addOne'),
      '#ajax' => [
        'callback' => '::addmoreCallback',
        'wrapper' => 'names-fieldset-wrapper',
      ],
    ];
    if ($name_field > 1) {
      $form['names_fieldset']['actions']['remove_name'] = [
        '#type' => 'submit',
        '#value' => t('Remove one'),
        '#submit' => array('::removeCallback'),
       '#ajax' => [
          'callback' => '::addmoreCallback',
          'wrapper' => 'names-fieldset-wrapper',
        ]
      ];
    }
    $form_state->setCached(FALSE);
    $form['actions']['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];

    return $form;
  }

  public function getFormId() {
    return 'fapi_example_ajax_addmore';
  }

  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);
    $form_state->setRebuild();
  }

  public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    return $form['names_fieldset'];
  }

  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);
    }
   $form_state->setRebuild();
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue(array('names_fieldset', 'name'));

    $output = t('These people are coming to the picnic: @names', array(
      '@names' => implode(', ', $values),
        )
    );
    drupal_set_message($output);
  }

}
  • What's the usage of $form_state->setCached(FALSE); this line? What if I don't write this line? Also, we generally write form like $form['foo'] = array(); but you didn't use this format, instead you simply used $form['foo'] = []; What's teh difference b/w these two formats? – Sugandh Khanna May 16 '16 at 6:18
  • stackoverflow.com/questions/17772534/… and if you have any other question please post it as a question rather than discussing it in this comment thread . – Shreya Shetty May 16 '16 at 6:48
2

If reporting error is turned on then the code Shreya Shetty returns error - might be related to changes in Drupal 8 ... but it is related to objects not being able to be used as ints... the amended version would be:

namespace Drupal\dynamic_forms\Form;


 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;

  class test3 extends FormBase {

public function buildForm(array $form, FormStateInterface $form_state) {
    $form['description'] = array(
        '#markup' => '<div>'. t('This example shows an add-more and a remove-last button.').'</div>',
    );
    $i = 0;
    $name_field = $form_state->get('num_names');
    $form['#tree'] = TRUE;
    $form['names_fieldset'] = [
        '#type' => 'fieldset',
        '#title' => $this->t('People coming to picnic'),
        '#prefix' => '<div id="names-fieldset-wrapper">',
        '#suffix' => '</div>',
    ];
    if (empty($name_field)) {
        $name_field = $form_state->set('num_names', 1);
    }

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

    $form['actions'] = [
        '#type' => 'actions',
    ];
    $form['names_fieldset']['actions']['add_name'] = [
        '#type' => 'submit',
        '#value' => t('Add one more'),
        '#submit' => array('::addOne'),
        '#ajax' => [
            'callback' => '::addmoreCallback',
            'wrapper' => 'names-fieldset-wrapper',
        ],
    ];
    //        if ($name_field > 1) {
    if ($value > 1) {
        $form['names_fieldset']['actions']['remove_name'] = [
            '#type' => 'submit',
            '#value' => t('Remove one'),
            '#submit' => array('::removeCallback'),
            '#ajax' => [
                'callback' => '::addmoreCallback',
                'wrapper' => 'names-fieldset-wrapper',
            ]
        ];
    }
    $form_state->setCached(FALSE);
    $form['actions']['submit'] = [
        '#type' => 'submit',
        '#value' => $this->t('Submit'),
    ];

    return $form;
}

public function getFormId() {
    return 'fapi_example_ajax_addmore';
}

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);
    $form_state->setRebuild();
}

public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    $name_field = $form_state->get('num_names');
    return $form['names_fieldset'];
}

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);
    }
    $form_state->setRebuild();
}

public function submitForm(array &$form, FormStateInterface $form_state) {
    $values = $form_state->getValue(array('names_fieldset', 'name'));

    $output = t('These people are coming to the picnic: @names', array(
            '@names' => implode(', ', $values),
        )
    );
    drupal_set_message($output);
}

}

Thought the amended version might help someone, especially newcomers... don't forget to say thanks to Shreya for original solution.

0

Another solution (use the get method instead of relying on the return value of the set method for $form_state):

if (empty($name_field)) {
  $form_state->set('num_names', 1);
}
$name_field = $form_state->get('num_names');
for ($i = 0; $i < $name_field; $i++) {
  $form['names_fieldset']['name'][$i] = [
    '#type' => 'textfield',
    '#title' => t('Name'),
  ];
}
-1

For Drupal 8:

namespace Drupal\form_api_example\Form;

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 {

  /**
   * 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);
  }
}

Source: https://api.drupal.org/api/examples/form_api_example%21src%21Form%21AjaxAddMore.php/class/AjaxAddMore/8.x-1.x

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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