I created a form with ajax buttons to add / remove a person.

namespace Drupal\general\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;



class CreditPersonsForm extends ConfigFormBase
{
  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames()
  {
    return array('general.credit_person_form');
  }

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

  /**
   * {@inheritdoc}
   */
  public function buildForm($form, FormStateInterface $form_state)
  {
    $configs = $this->config('general.credit_persons_form');
    $num_person = $form_state->get('num_person');

    if ($num_person === null)
    {
      $persons = $form_state->set('num_person', 1);
      $num_person = 1;
    }
    
    $form['#tree'] = true;
    $form['site_credits'] = array(
      '#type' => 'fieldset',
      '#title' => $this->t('Site managers'),
      '#prefix' => '<div id="siteCreditsWrapper">',
      '#suffix' => '</div>',
    );

    

    for($i = 0; $i < $num_person; $i++) {
      $num = $i + 1;
      
      $form['site_credits']["persons{$num}"] = array(
        '#type' => 'fieldset',
        '#title' => $this->t("Person #{$num}"),
        '#prefix' => "<div id=\"person-{$num}\">",
        '#suffix' => '</div>',
      );

      $form['site_credits']["persons{$num}"]['manager_picture'] = array(
        '#type' => 'managed_file',
        '#title' => $this->t('Manager picture'),
        '#description'=> $this->t("Upload file. <br />2MB limit. <br/>Allowed types: png gif jpg jpeg."),
      );
  
      $form['site_credits']["persons{$num}"]['first_name'] = array(
        '#type' => 'textfield',
        '#title' => $this->t('First name'),
      );
  
      $form['site_credits']["persons{$num}"]['last_name'] = array(
        '#type' => 'textfield',
        '#title' => $this->t('Last name'),
      );
  
      $form['site_credits']["persons{$num}"]['position'] = array(
        '#type' => 'textfield',
        '#title' => $this->t('Position'),
      );
  
      $form['site_credits']["persons{$num}"]['profile_url'] = array(
        '#type' => 'textfield',
        '#title' => $this->t('Link to Linkedin profile'),
      );

      $form['site_credits']["persons{$num}"]['actions'] = array(
        '#type' => 'actions',
      );

      if ($num_person > 1)
      {
        $form['site_credits']["persons{$num}"]['actions']['remove_person'] = array(
          '#type' => 'submit',
          '#value' => $this->t("Remove person"),
          '#submit' => ['::removeCallback'],
          '#ajax' => array(
            'callback' => '::addmoreCallback',
            'wrapper' => 'siteCreditsWrapper',
          ),
        );
      }
    }

    $form['site_credits']['actions'] = array(
      '#type' => 'actions',
    );

    $form['site_credits']['actions']['add_person'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Add one more'),
      '#submit' => ['::addOne'],
      '#ajax' => array(
        'callback' => '::addmoreCallback',
        'wrapper' => 'siteCreditsWrapper',
      ),
      '#button_type' => 'default'
    );

    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Save'),
      '#button_type' => 'primary',
    );

    return $form;
  }

  /**
   * 
   */
  public function addmoreCallback(array &$form, FormStateInterface $form_state) {
    return $form['site_credits'];
  }

  /**
   * {@inheritdoc}
   */
  public function addOne(array &$form, FormStateInterface $form_state)
  {
    $persons = $form_state->get('num_person');
    $add_person = $persons + 1;
    $form_state->set('num_person', $add_person);
    
    $form_state->setRebuild();
  }


  /**
   * {@inheritdoc}
   */
  public function removeCallback(array &$form, FormStateInterface $form_state)
  {
    $persons = $form_state->get('num_person');
    if ( $persons > 1 )
    {
      $remove_person = $persons - 1;
      $form_state->set('num_person', $remove_person);
    }
    
    $form_state->setRebuild();
  }

But the "remove" button only reduces the value and starts the rebuild process of the form, respectively, the last element of the form is permanently deleted.
Tell me, please, how to implement the "remove" button that will delete the necessary block of fields, and not reduce the number of blocks for their rebuilding? That is, if it is necessary to remove the second block of the three, it is not the third block that will be removed, but the second one.

Comments

wombatbuddy’s picture

You need to know "persons{$name}" key of a item you want to delete.
You can do this with calling:
$form_state->getTriggeringElement()
After that, you be able to remove this item from the array:
unset($form['site_credits']['KEY_TO_REMOVE']).
Take a look this example:
https://gist.github.com/alexdesignworks/94bac012e7d679051bc0#gistcomment...

santosh.tiwari2422’s picture

Please help

ivel’s picture

I have found a solution. I don't know if it's the best but it will do it for me. I modified the example code from the example module. I use form state vars for each new row (1 for active row, 0 for removed row).

I use  $form_state->getTriggeringElement()['#name'] to know which remove buttons is clicked. The name of the button is equal to the row count. So after the button is clicked the corresponding var is set to 0 and when the form is rebuilt it will not display the removed text field.

See the attached code.

 public function buildForm(array $form, FormStateInterface $form_state) {
        $form['description'] = [
            '#type' => 'item',
            '#markup' => $this->t('This example shows an add-more and a remove button.'),
        ];

        // Gather the number of rows in the form already.
        $row_count = $form_state->get('row_count');
        // We have to ensure that there is at least one row field.
        if ($row_count === NULL) {
            $form_state->set('row_count', 1);
            $form_state->set("row_0_active", 1);
            $row_count = 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 ($row_no = 0; $row_no < $row_count; $row_no++) {

            $is_active_row = $form_state->get("row_" . $row_no . "_active");

            //We check if the row is active
            if ($is_active_row) {
                $form['names_fieldset'][$row_no]['name'] = [
                    '#type' => 'textfield',
                    '#title' => $this->t('Name'),
                ];

                $form['names_fieldset'][$row_no]['remove_name'] = [
                    '#type' => 'submit',
                    '#name' => $row_no,
                    '#value' => $this->t('Remove one'),
                    '#submit' => ['::removeCallback'],
                    '#ajax' => [
                        'callback' => '::addmoreCallback',
                        'wrapper' => 'names-fieldset-wrapper',
                    ],
                ];
            }
        }

        $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',
            ],
        ];

        $form['actions']['submit'] = [
            '#type' => 'submit',
            '#value' => $this->t('Submit'),
        ];

        return $form;
    }

    /**
     * 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) {
        $cur_rows = $form_state->get('row_count');
        $rows = $cur_rows + 1;
        $form_state->set('row_count', $rows);
        $this->messenger()->addMessage($rows);
        $form_state->set("row_" . $cur_rows . "_active", 1);
        // Since our buildForm() method relies on the value of 'row_count' 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) {
        $button_clicked = $form_state->getTriggeringElement()['#name'];
        $form_state->set("row_" . $button_clicked . "_active", 0);
        $this->messenger()->addMessage($button_clicked);
        // Since our buildForm() method relies on the value of 'row_count' 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) {
        $row_count = $form_state->get('row_count');

        for ($row_no = 0; $row_no < $row_count; $row_no++) {
            $is_active_row = $form_state->get("row_" . $row_no . "_active");
            if ($is_active_row) {

                $values .= $form_state->getValue(['names_fieldset', $row_no, 'name']).", ";

                }
        }
        $this->messenger()->addMessage($values);
    }

Hope that this helps someone.

tomy mohan’s picture

Hi Ivel,

This solution helped me a lot. I applied similar conditions as you used. Thanks for the idea......