0

I have a form with 3 dropdowns. When changing the first one, it updates the 2 other by an ajax call. Once the 2 selects are re-rendered, when the user submits the form, it no longer pass in the submitForm() method. I have tried many combinations to try to fix this but found no way. Here is the last state of my attempts.

In a module for geo location, I load a form in a block:

in src/Plugin/Block/GeoPopupBlock.php:

class GeoPopUpBlock extends BlockBase {

    public function build() {

    if(!$cookie = Util::getCookie()){
      $message = 'Cookie was not set.';
      \Drupal::logger($message);
      die($message);
    }


      $buildForm = \Drupal::formBuilder()->getForm('\Drupal\my_module\Form\GeoForm');
//      $buildForm = \Drupal::formBuilder()->getForm(new GeoForm());
      $build['#form'] = $buildForm;

[...]
    return $build;

In src/Form/GeoForm.php

<?php

[...]

class GeoForm extends FormBase{

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

  public function buildForm(array $form, FormStateInterface $form_state){
    if (!$cookie = Util::getCookie()){
      $message = 'Cookie was not set.';
      \Drupal::logger(__CLASS__)->critical($message);
      die($message);
    }

    $form['country'] = array(
      '#type' => 'select',
      '#options' => $this->getCountries(),
      '#default_value' => $cookie['countryId'],
      '#attributes' => array('class' => array('edit-country')),
      '#ajax' => array(
        'callback' => '::ajaxFillProvider',
        'wrapper' => 'provider-language',
        'method' => 'replaceWith',
        'progress' => array(
          'type' => 'throbber',
          'message' => NULL,
        ),
      ),
    );
    $form['provider'] = array(
      '#prefix' => '<div id="provider-language">',
      '#type' => 'select',
      '#options' => $this->getProviders($cookie['countryId']),
      '#default_value' => $cookie['providerId'],
      '#attributes' => array('class' => array('edit-provider')),
    );
    $form['language'] = array(
      '#type' => 'select',
      '#options' => $this->getLanguages($cookie['countryId']),
      '#default_value' => $cookie['languageId'],
      '#suffix' => '</div>',
      '#attributes' => array('class' => array('edit-language')),
    );
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = array(
      '#prefix' => '<div class="flex"><div class="submit-button">',
      '#type' => 'submit',
      '#value' => $this->t('Access my content'),
      '#suffix' => '</div>',
    );
    $form['actions']['decline'] = array(
      '#prefix' => '<div class="decline-button" id="decline-button">',
      '#markup' => t('Decline'),
      '#suffix' => '</div></div>',
    );
    return $form;
  }

  public function ajaxFillProvider(array &$form, FormStateInterface $form_state){

    $ajaxForm['provider'] = array(
      '#prefix' => '<div id="provider-language">',
      '#type' => 'select',
      '#name' => 'provider', // have to add this otherwise the select would have no name!
      '#options' => $this->getProviders($form_state->getValue('country')),
      '#attributes' => array('class' => array('edit-provider')),
    );
    $ajaxForm['language'] = array(
      '#type' => 'select',
      '#name' => 'language', // have to add this otherwise the select would have no name!
      '#options' => $this->getLanguages($form_state->getValue('country')),
      '#suffix' => '</div>',
      '#attributes' => array('class' => array('edit-language')),
    );
    return $ajaxForm;

  }

  /**
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   */
  public function submitForm(array &$form, FormStateInterface $form_state){

    if (!$cookie = Util::getCookie()){
      $message = 'Cookie was not set.';
      \Drupal::logger(__CLASS__)->critical($message);
      die($message);
    }

    $commConfig = \Drupal::config('communication.settings');

    $cookie['countryId'] = $form_state->getValue('country');
    $cookie['providerId'] = $form_state->getValue('provider');
    $cookie['languageId'] = $form_state->getValue('language');

    Util::setCookie($cookie);
  }

[... more private methods]

}

At this point the ajax is working and filling the 2 selects with new values. Submitting the form will send all values to the server. But for an unknown reason, it will not pass in the submitForm().

Note that if I remove the extra '#name' => 'provider', and '#name' => 'language', it will pass in the submitForm() but since the new select tags have no name attribute, the form in unable the use those form elements.

1

1 Answer 1

3

Pretty sure I answered this before but can't find a matching answer right now.

This is the golden rule when doing ajax with Drupal. Ajax callbacks must not alter $form, they only "logic" they should contain is what to return and possible additional ajax commands.

Instead, buildForm() must contain the logic to update the form according to what's in form user input or e.g. by setting something with $form_state->set('foo', 'bar') in a submit callback (if you want to have a submit callback on a select element, you need #execute_submit_callback and possibly #limit_validation_errors so that the element can be changed when other fields are not filled out). Then you just return the part of the form you want to return in your ajax callback.

This also helps to ensure that the form works without JS (with a select that's obviously a bit tricky, but you can for example also add a submit button that you hide by adding a js-hide class).

2
  • Your answer was quite cryptic but I was able to use it to come up with the other half of the solution. The thing you made me understand is that the BE version of the $form must be identical of the FE version of the form. If you only update the FE version of the form, then the BE will consider that it is a new form and thus actively "submit" into the void the previous form. So the other half of the solution is to update the FE version of the form with the callback function using ajaxResponse object. Commented May 16, 2017 at 12:35
  • Unfortunately this works only partially. After experimenting more, I get An illegal choice has been detected. Please contact the site administrator. message. I don't know why. I am tired of loosing so much time on something that should be easy, but with Drupal nothing is easy. So I decided to simply hide the damn message. Commented May 23, 2017 at 14:32

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.