0

The code is taken from the "Form API examples" and supplemented. There must be a pair of first and last name fields. A button “Add more” should add a new pair of fields “name and surname” below the existing one. This works (a little later using css so that they are on the same line), but they are grouped incorrectly. They are displayed following this order.

  • Name
  • Name
  • Name
  • Surname
  • Surname
  • Surname

I need to get them in the following order. (Using CSS, I will inline them.)

  • Name
  • Surname
  • Name
  • Surname
  • Name
  • Surname

What am I doing wrong?

The code I am using is the following one.

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']['surname'][$i] = [
        '#type' => 'textfield',
        '#title' => $this->t('Surname'),
      ];
    }

    $form['names_fieldset']['actions'] = [
      '#type' => 'actions',
    ];
    $form['names_fieldset']['actions']['add_name'] = [
      '#type' => 'submit',
      '#value' => $this->t('Add one'),
      '#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;

The form is rendered as in this screenshot.

screenshot

The second part of the code where the code is processed

  public function submitForm(array &$form, FormStateInterface $form_state) {
    $values_1 = $form_state->getValue(['names_fieldset', 'name']);
    $values_2 = $form_state->getValue(['names_fieldset', 'surname']);

    for ($i = 0; $i < count($values_1); $i++) {
      $arr[] = $values_1[$i] . ' ' . $values_2[$i];
    }

    $output = $this->t('These people are coming to the picnic: @names', [
      '@names' => implode(', ', $arr),
    ]
    );
    $this->messenger()->addMessage($output);
  }
4
  • 1
    Isn't this just a logic breakdown? $form['names_fieldset']['name'][$i] should surely be $form['names_fieldset'][$i]['name'], etc, to get the output you want?
    – Clive
    Jan 28 '20 at 12:57
  • You'd also need to make the ['name'] unique to each row otherwise the final value for ['name'] in the submit handler would just be the final items value entered I believe
    – Leigh
    Jan 28 '20 at 13:05
  • 1
    @Leigh $form['#tree'] = TRUE; will take care of that
    – Clive
    Jan 28 '20 at 13:32
  • @Clive its works! Jan 29 '20 at 13:27
0

You could add a fieldset for each person, something like this should work:

for ($i = 0; $i < $num_names; $i++) {
  $form['names_fieldset']['person_' . $i] = [
    '#type' => 'fieldset',
    '#title' => $this->t('Person ' . $i),
  ];

  $form['names_fieldset']['person_' . $i]['name'] = [
    '#type' => 'textfield',
    '#title' => $this->t('Name'),
  ];
  $form['names_fieldset']['person_' . $i]['surname'] = [
    '#type' => 'textfield',
    '#title' => $this->t('Surname'),
  ];
}

And then alter your remove callback and a few other bits to match

3
  • and count persons - count($form['names_fieldset'])? Jan 29 '20 at 5:27
  • 1
    You already set "num_names" in $form_state so it should still be in $form_state in your submit handler
    – Leigh
    Jan 29 '20 at 9:06
  • It works! But the indexing of form elements took the one suggested by a mr.Clive. Jan 29 '20 at 13:16

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.