In order to avoid frustrations, having a code snippet ready to be copy & pasted comes in handy in many situations when you are working with AJAX and Drupal 8 or 9. This is such an article, so don't expect too much theory -- just some code snippets.
By the way, I have a lot more Drupal 8 and 9 code snippets ready to be used as examples or reused in your own projects.
Let's see what are we trying to accomplish in this article.
We have a simple form with two select boxes, Country and City. When you change the Country select box the City field also has to change accordingly.
If a picture is worth a thousand words then the following gif animation is all you need to understand the problem we are trying to solve.
First, let's create the route for our Ajax form.
MY_MODULE.ajax_form:
path: '/ajax-form'
defaults:
_form: '\Drupal\MY_MODULE\Form\AjaxForm'
_title: 'Ajax form'
requirements:
_permission: 'access content'
Nothing fancy here just a simple route that points to the form.
Now let's create the Ajax form itself.
<?php
namespace Drupal\MY_MODULE\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class AjaxForm extends FormBase {
public function getFormId() {
return 'MY_MODULE_ajax_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
$form['country'] = [
'#type' => 'select',
'#title' => $this->t('Country'),
'#options' => [
'serbia' => $this->t('Serbia'),
'usa' => $this->t('USA'),
'italy' => $this->t('Italy'),
'france' => $this->t('France'),
'germany' => $this->t('Germany'),
],
'#ajax' => [
'callback' => [$this, 'reloadCity'],
'event' => 'change',
'wrapper' => 'city-field-wrapper',
],
];
$form['city'] = [
'#type' => 'select',
'#title' => $this->t('City'),
'#options' => [
'belgrade' => $this->t('Belgrade'),
'washington' => $this->t('Washington'),
'rome' => $this->t('Rome'),
'paris' => $this->t('Paris'),
'berlin' => $this->t('Berlin'),
],
'#prefix' => '<div id="city-field-wrapper">',
'#suffix' => '</div>',
'#value' => empty($triggering_element) ? 'belgrade' : $this->getCityForCountry($triggering_element['#value']),
];
return $form;
}
public function reloadCity(array $form, FormStateInterface $form_state) {
return $form['city'];
}
protected function getCityForCountry($country) {
$map = [
'serbia' => 'belgrade',
'usa' => 'washington',
'italy' => 'rome',
'france' => 'paris',
'germany' => 'berlin',
];
return $map[$country] ?? NULL;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
//
}
}
As you can see the Country select box is Ajaxified. On changing the value, the City select box is updated.
The most interesting line is this:
empty($triggering_element) ? 'belgrade' : $this->getCityForCountry($triggering_element['#value']),
If we don't have a form element that triggered submission then we set 'belgrade' as the default value for the City field. Otherwise, we are setting the appropriate City based on the selected Country value.