[Drupal] Ajax callbacks on form elements

maandag 4 januari 2021

Building forms with Drupal 8 is cool and the form API is providing a lot of options for building different things.

Building forms with Drupal 8 is cool and the form API is providing a lot of options for building different things. For many form elements I’m using the AJAX option for several purposes:

  • client-side data validation
  • retrieving data from an API
  • DOM manipulations

When I wrote this post, I used Drupal version 8.9.10.

You can add an Ajax option to a form element in the following ways:

  1. buildForm function
  2. hook_form_alter function

Notice the differences when you determine the callback function in between those two. In a buildForm function you can use the following methods.
First method:
callback => [$this, ‘myCallbackFunction’]
This will call the function within the class.
Second method:
callback => ‘myControllerCallback::validate’
This will call the function in a controller class. For example located in src/Controller/myControllerCallback.php file.

In a hook_form_alter function the syntax will be:
callback => ‘myCallbackFunction’
This will call the function in your .module file.

Configuration for event property
The event to trigger on; any valid DOM event for the element can be used, simply omit the ‘on’ portion of the event. ‘onclick’ => ‘click’, ‘onkeyup’ => ‘keyup’, ‘onchange’ => ‘change’, etc. See all DOM events here which you can use eventually.

Configuration for wrapper property
This is optional. You won’t need this property when you’re using an AjaxResponse with commands to return data to an element in the DOM. 
If you wish to display the output of the callback function in your form, you need an HTML element with an ID which corresponds with the value you set in the wrapper property. This element will be updated with the result of your callback function.

The callback function
In this function you can use different responses: https://www.drupal.org/docs/drupal-apis/javascript-api/ajax-forms#s-implement-the-callback-function

  • Render array / form element
  • Custom HTML markup
  • AjaxResponse with commands

I prefer using the command InvokeCommand because you can use it like a Swiss knife in combination with most jQuery functions.

Catch the triggered element when your form is being rebuild
When you return a form element from your callback function, your form will be rebuilt. You can catch the triggered element with the following syntax:
Another code example using this technique:

Some other things I learned when using the Ajax option on form elements

  • Disable the progress throbber:
    Property: ‘progress’ => FALSE
  • Prevent returning the focus to the element (https://www.drupal.org/node/2627788):
    Property:'disable-refocus' => TRUE

When you’ve added an Ajax option to an element which is added by another Ajax event before, then this event will fire only once. Read this issue

Full example snippet
A postal code validation callback with an ajax form element added in the hook_form_alter function.
File: your_module/your_module.module


use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Form\FormStateInterface;

 * Implements hook_form_alter().
function your_module_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  $form[‘field_postalcode’][‘widget’][0][‘value’][#ajax’] = [
    ‘callback’ => ‘validate_postalcode’,
    ‘wrapper’ => ‘id-html-element’,
    ‘event’ => ‘change’,
  $form[‘field_postalcode’][#suffix’] = ‘<div id=”id-html-element”></div>’;

function validate_postalcode($form, FormStateInterface $form_state) {
  // Get value.
  $postalcode = $form_state->getValue(‘field_postalcode’)[0][‘value’];
  // Or you can use
  // $element = $form_state->getTriggeringElement();
  // $postcode = $element[‘#value’];
  if (preg_match('/^[0-9]{4}[a-zA-Z]{2}$/', $value) === 0) {
    // Return error
    $ajaxResponse = new ajaxResponse();
    $ajaxResponse->addCommand(new InvokeCommand(#id-html-element’, ‘text’, ‘The postal code is invalid. A valid pattern is 1234AB.’));
    return $ajaxResponse;

More resources:

Sebastian Hagens | creative developer