Updating form field values with Ajax callbacks in Drupal 10
Let’s look at how to use Ajax property in Drupal 10 form API.
I have covered some of the form altering functionality in my previous posts and if you are interested please take a look;
Let’s begin. Below are two examples on how we can utilise Ajax callbacks for better editor experience.
Before we begin, please take a look at the User form fields that I’ve added in the Admin UI. For this exercise, I’m only altering existing fields created from Account settings managed fields.
Example 1
Displaying the available character count below a text box.
In your custom module or in the form or form build function, use the below code.
// Set a maximum character limit.
$form['field_about_me']['widget'][0]['value']['#maxlength'] = 500;
// Add an Ajax callback to count the input from the user and update
// the value below.
$form['field_about_me']['widget'][0]['value']['#ajax'] = [
'callback' => [$this, 'countCharactersAjaxCallback'],
'disable-refocus' => TRUE,
'event' => 'change',
'progress' => [
'type' => 'throbber',
'message' => $this->t('Calculating entry...'),
],
];
callback
The function where the logic of the calculation will take place. This can be in the same class file or outside. If it is outside of the class file, that has to be specified. ‘callback’ => ‘Drupal\js_webassert_test\Form\JsWebAssertTestForm::addButton’,
When we get to example 2, you will notice we can simply add the function name because we will be using the .module
file instead of a class file.
disable-refocus
After the Ajax callback returns data, the field will not be focused to the user
event
Any jQuery event type can be used here. click
keyup
focusout
are named a few.
progress
Specifies the visuals of the Ajax call should look like when making the request.
You can also use the
wrapper
property to provide the wrapper element ID where the data needs to be altered, however I had issues where Drupal dynamically appends a wildcard to the ID making it unusable for this situation.
wrapper
property only accepts the element ID attribute.
The countCharactersAjaxCallback
code consists of the following.
/**
* Calculates the number of characters of field_about_me field.
*
* @param array $form
* @param \Drupal\Core\Form\FormStateInterface $form_state
*
* @return \Drupal\Core\Ajax\AjaxResponse
*/
public function countCharactersAjaxCallback(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$size = strlen($form_state->getValue('field_about_me')[0]['value']);
$max_size = $form['field_about_me']['widget'][0]['value']['#maxlength'];
$text = '%d out of %d characters used.';
$response->addCommand(new AppendCommand('.form-item-field-about-me-0-value ', '<label>' . sprintf($text, $size, $max_size) . '</label>'));
return $response;
}
The addCommand
function lets us choose between a variety of AJAX commands to response
and depending on the situation we can pick a suitable one.
In this case, we need to append a label element to the About me field.
Example 2
Let’s try to create a field that will automatically calculate the Age given the Date of Birth.
For us to explore another way to approach this, I’m using a hook_form_FORM_ID_alter
hook inside a custom module’s .module
file .
/**
* Implements hook_form_FORM_ID_alter().
*/
function playground_menu_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Make Age field read only for users.
$form['field_age']['widget'][0]['value']['#attributes']['readonly'] = 'readonly';
$form['field_age']['widget'][0]['value']['#attributes']['class'][] = 'readonly';
// Add Ajax callback for DOB field to calculate Age.
$form['field_date_of_birth']['widget'][0]['value'] += [
'#ajax' => [
'callback' => 'calculate_age',
'event' => 'focusout',
'disable-refocus' => TRUE,
'progress' => [
'type' => 'throbber',
'message' => 'Calculating age',
],
],
];
}
And calculate_age function will be;
/**
* Get age when DOB provided.
*
* @param $form
* @param \Drupal\Core\Form\FormStateInterface $form_state
*
* @return \Drupal\Core\Ajax\AjaxResponse
*/
function calculate_age($form, FormStateInterface $form_state): AjaxResponse {
$response = new AjaxResponse();
$date = $form_state->getValue('field_date_of_birth')[0]['value']['date'];
$age = date_diff(date_create('today'), date_create($date), TRUE);
return $response->addCommand(new InvokeCommand('#edit-field-age-0-value', 'val', [$age->y]));
}
You may notice we are using InvokeCommand
instead of AppendCommand
here.
The FE form for this looks as below.
Please check out my Drupal Playground repository for the full code.
Gotchas
- If a form field is disabled, its values updated via Ajax will not be captured on form submit. A workaround for this is to set the field as read-only and use a style to make it look like disabled.
- Some interesting information around hook_form_alter and form submit.
Resources
https://www.drupal.org/docs/drupal-apis/javascript-api/ajax-forms