1

I have a simple form where I am trying to add a couple of fields to it when the user clicks a button using an ajax callback. Here is what I have:

public function buildForm($form, FormStateInterface $form_state)
  {

    $form['add_fields'] = [
      '#type' => 'button',
      '#value' => 'Add new fields',
      '#ajax' => [
        'callback' => [$this, 'ajax_callback'],
        'disable-refocus' => FALSE, 
        'event' => 'click',
        'wrapper' => 'new-fields-wrapper',
        'progress' => [
          'type' => 'throbber',
          'message' => $this->t('Verifying entry...'),
        ],
      ],
    ];

    // Wrapper for new fields
    $form['new_fields_wrapper'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'new-fields-wrapper'],
    ];

    return $form;
  }

As you see, it's a very simple form and I am defining a wrapper element inside where I would like to add the new fields.

Here is my ajax callback function where I am basically adding 3 new fields:

 public function ajax_callback($form, FormStateInterface $form_state)
  {

    $form['new_fields_wrapper']['new_field_1'] = [
      '#type' => 'textfield',
      '#title' => 'New Field 1',
    ];
    $form['new_fields_wrapper']['new_field_2'] = [
      '#type' => 'select',    '
      #title' => 'New Field 2',
      '#options' => [1 => 'Option 1', 2 => 'Option 2'],
    ];
    $form['new_fields_wrapper']['new_field_3'] = [
      '#type' => 'checkbox',
      '#title' => 'New Field 3',
    ];

    return $form['new_fields_wrapper'];
  }

And finally I have a submit form function where I'd like to get the values entered for the new fields:

public function blockSubmit($form, FormStateInterface $form_state)
  {
    $values = $form_state->getValues();
  }

I have two problems here:

  1. When I click on the button to add the new fields through the ajax callback, it does not add the fields. I made sure that the callback function is actually called by adding some logging. However, it doesn't add the new fields. As I explored with the devtools, I realized that when the form is displayed, the wrapper element does not exist in the DOM. I added a hidden field to it like this: $form['new_fields_wrapper']['hidden'] = [ '#type' => 'hidden', '#value' => 0, ];

This made the wrapper appear in the DOM and now the button click works and the fields are added. Is this how I am supposed to handle it? In all my research, there was no mention of having to include a field in the wrapper in the original form.

  1. With the new fields added, when I enter values in them and hit the form submit button, the new field values are not included in the $form_state->getValues(). How can I get the values entered in the new fields with the form submit?

Thanks a lot in advance for any help.

1 Answer 1

1

You cannot add form elements in ajax callbacks. Drupal caches the form before the callback, so if you add elements in the callback, they are not part of the cache, and any values submitted by those elements is disregarded as a security measure.

The solution is to put these fields in your form definition:

public function buildForm($form, FormStateInterface $form_state)
  ...
  if (!empty($form_state->getValues()) {
    $form['new_fields_wrapper']['new_field_1'] = [
      '#type' => 'textfield',
      '#title' => 'New Field 1',
    ];
    $form['new_fields_wrapper']['new_field_2'] = [
      '#type' => 'select',    '
      #title' => 'New Field 2',
      '#options' => [1 => 'Option 1', 2 => 'Option 2'],
    ];
    $form['new_fields_wrapper']['new_field_3'] = [
      '#type' => 'checkbox',
      '#title' => 'New Field 3',
    ];
  }
  ...
}
6
  • Thanks Jaypan for the reply. So, the reason for me to do it this way was that I don't know if the user wants the field or if they do, how many of it they want. The example I provided here is just a Minimal code. In real life, I want to let the users click on the button if they need to add a url in a textfield. Some users may need only 2 urls, but others may want 8 or so on. I am guessing it is not feasible currently based on your answer? Commented Jan 12, 2023 at 15:56
  • 1
    It's feasible, but you have to put all the logic in the form definition, not the ajax callback. The reason I put if (!empty($form_state->getValues()) is because the value will be empty when the form is first built, and will have values when the ajax is submitted, so those fields will be added when the form is built with #ajax. You can add additional logic as necessary.
    – Jaypan
    Commented Jan 12, 2023 at 16:31
  • @Jaypan can we use if(!$form_state->isRebuilding() instead of the condition on getValues , to check if the page is loading for first time and form is being build first time or via ajax ? Commented Apr 20 at 18:40
  • 1
    I never have - try it out and let us know if it works.
    – Jaypan
    Commented Apr 20 at 22:50
  • It works! I have a question related to your method, suppose we have two dependent dropdowns and for both of them the empty option is selected, so $form_state->getValues() will still be non empty and we'll be able to detect if this is ajax rebuild even if all form fields input values are empty? Commented Apr 21 at 5:54

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.