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.

Notice the class name and the read only attribute added programmatically

--

--