-1

Create a settings form for a module in Drupal 8 - when a user selects a View from a list, the drop down under it should populate with the displays in that view.

While the form updates the option list for the user, it also throws an error message on the screen "Illegal choice has been detected".

Here is the code (it doesn't paste very well):

http://cgit.drupalcode.org/tvi/tree/src/Form/SettingsForm.php?h=8.x-1.x

getViewDisplays is the ajax callback that is populating the options.

Some people who have posted about this around the internet say to set #validated on the select field to TRUE, though, that doesn't necessarily feel right. I still want to validate the submission to do some checks.

1
6

You can't populate the choices in the ajax handler. The ajax handler is purely for returning the re-rendered form. If you want to do more advanced stuff you can use ajax response but that's it.

What you need to do is to use form state storage to store the selected view name and submit handler that will set this value(ie copy the value form submitted values into storage) and rebuild the form.

In the form you have to check if the view is in the storage and if not set the default one and use it and if it is use that one. Load the displays in the form based on this view.

This is a portion of one of my forms:

<?php

/**
 * Defines an e-mail settings form.
 */
class EmailSettingsForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $system_config = $this->config('system.mail');
    $plugin_id = $system_config->get('interface.default');
    $definitions = $this->mail->getDefinitions();

    if ($form_state->has('mail_plugin_id')) {
      $plugin_id = $form_state->get('mail_plugin_id');
    } else {
      $form_state->set('mail_plugin_id', $plugin_id);
    }

    // Make sure the plugin exists.
    $plugin_id = $this->mail->hasDefinition($plugin_id) ? $plugin_id : 'php_mail';

    $ajax = [
      'wrapper' => 'email-settings-form-ajax-wrapper',
      'callback' => '::ajaxRebuildForm'
    ];

    $form['#prefix'] = '<div id="' . $ajax['wrapper'] . '">';
    $form['#suffix'] = '</div>';

    $form['email_plugin'] = [
      '#type' => 'select',
      '#title' => $this->t('E-mail provider'),
      '#default_value' => $plugin_id,
      '#required' => TRUE,
      '#ajax' => $ajax + ['trigger_as' => ['name' => 'email_plugin_submit']],
      '#options' => array_map(function($definition) {
        return $definition['label'];
      }, $definitions)
    ];

    $form['email_plugin_submit'] = [
      '#type' => 'submit',
      '#name' => 'email_plugin_submit',
      '#value' => $this->t('Submit'),
      '#submit' => ['::changePluginSubmit'],
      '#limit_validation_errors' => [],
      '#ajax' => $ajax,
      '#attributes' => [
        'class' => ['js-hide']
      ]
    ];

    return $form;
  }

  /**
   * Submit handler that will change the mail plugin.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   */
  public function changePluginSubmit(array &$form, FormStateInterface $form_state) {
    $plugin_id = $form_state->getValue('email_plugin');
    $form_state->set('mail_plugin_id', $plugin_id);
    $form_state->setRebuild();

    // Set the e-mail plugin.
    $this->configFactory()->getEditable('system.mail')
      ->set('interface.default', $plugin_id)
      ->save();
  }

  /**
   * Ajax submit handler that will return the whole form structure.
   *
   * @param array $form
   *   An associative array containing the structure of the form.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return array
   *   The form structure.
   */
  public function ajaxRebuildForm(array &$form, FormStateInterface $form_state) {
    return $form;
  }
}

It's not a perfect example since there are other things going on that I have removed for simplicity but you should get the logic.

5
  • Where is a concrete example of this? – Kevin Jan 27 '16 at 18:36
  • A relatively complex example would be WidgetBase::addMoreSubmit(). The idea is that you have a submit callback, there you can update form_state, or you can just look at the form_state values directly in the form code, then call setRebuild(). Then the ajax callback just returns the part of the form that was updated. – Berdir Jan 27 '16 at 18:43
  • I don't get what $form_state storage has to do with the options list - all I want to do is when one option is selected, it powers the options for the next select field under it. – Kevin Jan 27 '16 at 18:51
  • Second to that, I just ran into the "cant set default value" after the form is built in AJAX callbacks. So while it now works, saves and validates, I can't reset the default value when the option list shifts. I have to set #value to blank in the AJAX callback before returning it... that is the only thing that works short of writing raw JS to manipulate the UI. – Kevin Jan 27 '16 at 20:57
  • Thanks for that - I wound up close to what you have above. For some reason I was under the impression that you could change and rebuild the form options. I still have trouble resetting the default option to NULL when the option list changes, without explicitly setting the value to blank in the AJAX handler. – Kevin Jan 28 '16 at 15:05

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.