3

I've created a custom field as a module Plugin and have the following code in the formElement() method of the field's Widget.php. The intention is to populate options in the second select element based on the value chosen in the first element:

$element += array(
  'sheet_name' => [
    '#title' => 'Sheet',
    '#description' => 'Select the sheet',
    '#type' => 'select',
    '#options' => $options,
    '#required' => TRUE,
    '#element_validate' => [
      [$this, 'validate'],
    ],
    '#ajax' => [
      'event' => 'change',
      'callback' => [$this, 'getPositions'],
      'progress' => [
        'type' => 'throbber',
        'message' => 'Getting positions.',
        'method' => 'replace',
      ],
    ],
  ],
  'position' => [
    '#type' => 'select',
    '#options' =>['Select'],
    '#title' => 'Position',
    '#description' => 'Position',
    '#prefix' => '<div id="position-select">',
    '#suffix' => '</div>',
  ],
);
return array('value' => $element);

My callback is as follows:

$options = ['Select' => 'Select'];
foreach ($positions as $position) {
  $options[$position['position']] = ucfirst($position['position']);
}

$sheet_position = [
  '#type' => 'select',
  '#options' => $options,
  '#title' => 'Position',
  '#required' => TRUE,
  '#description' => 'Position',
];

$response = new AjaxResponse();
$response->addCommand(new HtmlCommand('#position-select', $sheet_position));
$form_state->setRebuild(TRUE);
return $response;

This puts the option values I want into the second select element, but the form isn't being rebuilt, even with the call to $form_state->setRebuild(), so my updated select element isn't being associated with a form field. I'd love to understand why, but I'll settle for any way to force the form to rebuild after the callback runs.

Update: I have tried $form['position'] instead of $sheet_position in my callback but no joy there, either.

  • I believe you can just set the rebuild to true, you don't need the response object. – Kevin Mar 11 '16 at 19:22
  • Alas, that didn't work. – Sunset Bill Mar 15 '16 at 13:01
1

Here is what I did in TVI which does something similar to what you are doing (changing out options):

    // form code ...

$form['tvi']['view'] = [
  '#type' => 'select',
  '#title' => 'Using the view',
  '#description' => $this->t('The default view that you want to use for all vocabularies and terms.'),
  '#default_value' => $config->get('view'),
  '#options' => $view_options,
  '#states' => [
    'visible' => [
      ':input[name="enable_override"]' => array('checked' => true),
    ]
  ],
  '#ajax' => [
    'callback' => '::loadDisplayOptions',
    'event' => 'change',
    'wrapper' => 'tvi-settings-wrapper',
    'progress' => array(
      'type' => 'throbber',
    ),
  ],
];

// form code ...

  /**
   * Ajax callback - null the value and return the form.
   * The value gets nulled because we cannot overwrite #default_value in an ajax callback.
   * @param array $form
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   * @return mixed
   * @see https://www.drupal.org/node/1446510
   * @see https://www.drupal.org/node/752056
   */
  public function loadDisplayOptions(array &$form, FormStateInterface $form_state) {
    $form['tvi']['view_display']['#value'] = '';
    $form_state->setRebuild();
    return $form;
  }

In my form code, I am reading the values from $form_state and checking them - which is passed to a function that populates the option list. Therefore, when the AJAX callback rebuilds the form, the new option list will be populated.

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $values = $form_state->getValues();
    $config = $this->config('tvi.global_settings');
    $views = Views::getEnabledViews();

$view_options = ['' => ' - Select a View -'];
$display_options = ['' => ' - Select a View Display -'];
$default_display = '';

foreach ($views as $view) {
  $view_options[$view->id()] = $view->label();
}

if (isset($values['view'])) {
  $display_options += $this->getViewDisplayOptions($values['view']);
}
elseif ($config !== NULL) {
  $view = $config->get('view');
  $view_display = $config->get('view_display');

  if (isset($view)) {
    $display_options += $this->getViewDisplayOptions($view);
  }

  if (isset($view_display)) {
    $default_display = $view_display;
  }
}

$form['#prefix'] = '<div id="tvi-settings-wrapper">';
$form['#suffix'] = '</div>';

// irrelevant fields redacted...

$form['tvi']['view'] = [
  '#type' => 'select',
  '#title' => 'Using the view',
  '#description' => $this->t('The default view that you want to use for all vocabularies and terms.'),
  '#default_value' => $config->get('view'),
  '#options' => $view_options,
  '#states' => [
    'visible' => [
      ':input[name="enable_override"]' => array('checked' => true),
    ]
  ],
  '#ajax' => [
    'callback' => '::loadDisplayOptions',
    'event' => 'change',
    'wrapper' => 'tvi-settings-wrapper',
    'progress' => array(
      'type' => 'throbber',
    ),
  ],
];

$form['tvi']['view_display'] = [
  '#type' => 'select',
  '#title' => 'With this view display',
  '#description' => $this->t('The view display that you want to use from the selected view.'),
  '#default_value' => $default_display,
  '#options' => $display_options,
  '#states' => [
    'visible' => [
      ':input[name="enable_override"]' => array('checked' => true),
    ]
  ],
  '#prefix' => '<div id="tvi-view-display">',
  '#suffix' => '</div>',
];

return parent::buildForm($form, $form_state);
}

(Sorry for the broken paste) So when the form rebuilds, the options are populated from another method, while the AJAX callback just triggers a rebuild.

As a side note, the reason I am setting #value to '' is to force the field to default to the placeholder 'Select a Value' on change. Otherwise, it will stick to the last selected position by the user.

  • I see you're doing this via buildForm(). My code is happening inside of a formElement() method, which doesn't trigger a form rebuild on an AJAX call, even with $form_state->setRebuild() – Sunset Bill Mar 15 '16 at 13: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.