I have a module in drupal 9 with a configuration form in the back office, I saw multiple answers on the web but none of them was optimized and compatible with drupal 9. In this form, the admin has to choose between two radio button options "Import" and "Export", and based on this choice, a select list called import_country_select or another completely different one called export_country_select, will be displayed. I will only put the code for the import_country_select case. Once import_country_select changes, the admin will choose two preferred cities which are two distinct select lists preferred_city_number_1 and preferred_city_number_2 listing different cities belonging to the previously selected country (I will only put the code for the "preferred_city_number_1").
1- Is my code correct or can it be optimized? I saw some posts stating that it's not recommended to return a form element in the ajax callback, it's better to return a response, but I didn't know what to write in my case.
2- My second concern is that I saw some examples where they do $form_state->setRebuild(TRUE); in the callback, why is that being used?
3- '#validated' => 'true', Is it correct to use to solve the "Illegal choice detected" error ?
4- In my method, I create the preferred_city_number_1 with an empty options array. Is there a way to add this form element on ajaxCallback instead of creating it first then only populating its options on callback?
5- And lastly, after some research I didn't quite understand whether or not we should use form_state inside the scope of buildForm.
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$config = $this->config('mymodule.settings');
$form['first_section'] = [
'#type' => 'details',
'#title' => $this->t('Export/Import Settings'),
'#weight' => 1,
'#collapsible' => TRUE,
];
$form['first_section']['export_or_import'] = [
'#type' => 'radios',
'#title' => $this->t('Do you want to use this module for export or import?'),
'#options' => [
'option_import' => $this->t('Import'),
'option_export' => $this->t('Export'),
],
];
$form['first_section']['import_country_select'] = [
'#type' => 'select',
'#title' => $this->t('Choose a country:'),
'#empty_option'=> $this->t('-Select-'),
'#options' => [
'country_1' => $this->t('France'),
'country_2' => $this->t('Belgium'),
'country_3' => $this->t('Canada'),
],
'#states' => [
'visible' => [
':input[name="export_or_import"]' => ['value' => 'option_import'],
],
],
'#ajax' => [
'callback' => '::selectUpdateAjaxCallback',
'event' => 'change',
'wrapper' => 'replace-select-div',
'progress' => [
'type' => 'throbber',
'message' => $this->t('Loading...'),
],
]
];
$form['first_section']['import_preferred_cities'] = [
'#type' => 'details',
'#title' => $this->t('Preferred cities (Import)'),
'#weight' => 1,
'#collapsible' => TRUE,
'#states' => [
'visible' => [
':input[name="export_or_import"]' => ['value' => 'option_import'],
],
],
];
$form['first_section']['import_preferred_cities']['preferred_city_number_1'] = [
'#type' => 'select',
'#title' => $this->t('Select your preferred city:'),
'#empty_option'=> $this->t('-Select-'),
'#options' => [],
'#states' => [
'visible' => [
':input[name="export_or_import"]' => ['value' => 'option_import'],
],
],
'#prefix' => '<div id="replace-select-div">',
'#suffix' => '</div>',
'#validated' => 'true',
];
return parent::buildForm($form, $form_state);
}
public function selectUpdateAjaxCallback(array &$form, FormStateInterface $form_state) {
$citiesByCountry =
[
'country_1' => ['Paris', 'Lyon'],
'country_2' => ['Brussels', 'Leuven'],
'country_3' => ['Toronto', 'Ottawa'],
];
$selectedOption = $form_state->getValue("import_country_select");
$form['first_section']['import_preferred_cities']['preferred_city_number_1']['#options'] = $citiesByCountry[$selectedOption];
return $form['first_section']['import_preferred_cities']['preferred_city_number_1'];
}