Using Ajax Forms in Drupal 8

Daniel Sipos
Daniel Sipos
Share

In this article, I am going to show you a clean way of using the Drupal 8 Ajax API without writing one line of JavaScript code. To this end, we will go back to the first custom form we built for Drupal 8 in a previous article and Ajaxify some of its behaviour to make it more user friendly.

Drupal 8 logo

An updated version of this form can be found in this repository under the name DemoForm (the demo module). The code we write in this article can also be found there but in a separate branch called ajax. I recommend you clone the repo and install the module in your development environment if you want to follow along.

DemoForm

Although poorly named, the DemoForm was very helpful in illustrating the basics of writing a custom form in Drupal 8. It handles validation, configuration and exemplifies the use of the Form API in general. Of course, it focuses on the basics and has nothing spectacular going on.

If you remember, or check the code, you’ll see that the form presents a single textfield responsible for collecting an email address to be saved as configuration. The form validation is in charge of making sure that the submitted email has a .com ending (a poor attempt at that but enough to illustrate the principle of form validation). So when a user submits the form, they are saving a new email address to the configuration and get a confirmation message printed to the screen.

In this article, we will move the email validation logic to an Ajax callback so that after the user has finished typing the email address, the validation gets automagically triggered and a message printed without submitting the form. Again, there is nothing spectacular about this behaviour and you will see it quite often in forms in the wild (typically to validate usernames). But it’s a good exercise for looking at Ajax in Drupal 8.

Ajax form

The first thing we need to do is move the email validation logic from the general validateForm() to a method that handles only this aspect:

/**
 * Validates that the email field is correct.
 */
protected function validateEmail(array &$form, FormStateInterface $form_state) {
  if (substr($form_state->getValue('email'), -4) !== '.com') {
    return FALSE;
  }
  return TRUE;
}

As you can notice, we’ve also changed the logic a bit to make sure the email address ends with a .com.

Then, we can defer to this logic from the main validation method to make sure our existing behaviour still works:

/**
 * {@inheritdoc}
 */
public function validateForm(array &$form, FormStateInterface $form_state) {
  // Validate email.
  if (!$this->validateEmail($form, $form_state)) {
    $form_state->setErrorByName('email', $this->t('This is not a .com email address.'));
  }
}

This way even if our form gets somehow submitted (programatically or otherwise), the validation will still be run.

Next, we need to turn to our form definition, specifically the email field, and make it trigger ajax requests based on a user interaction. This will be the act of a user changing the value of the field and removing focus from it:

$form['email'] = array(
  '#type' => 'email',
  '#title' => $this->t('Your .com email address.'),
  '#default_value' => $config->get('demo.email_address'),
  '#ajax' => [
    'callback' => array($this, 'validateEmailAjax'),
    'event' => 'change',
    'progress' => array(
      'type' => 'throbber',
      'message' => t('Verifying email...'),
    ),
  ],
  '#suffix' => '<span class="email-valid-message"></span>'
);

What we did new here is add the #ajax key to the array with some of the relevant keys. Additionally, we added a little markup after the form element as a wrapper for a short message regarding the validity of the email.

The callback inside the #ajax array points to a method inside our form class (validateEmailAjax()) while the event adds a javascript binding to this form element for the jQuery change event. Alternatively, you can also specify a path key instead of a callback, but in our case it would mean having to also set up a route and a controller which seems redundant. And we don’t want the wrapper key because we do not intend to fill up an area with returned content but want to fine grain the actions that result from the callback. For that, we will use Ajax commands.

To learn more about all of this, I encourage you to consult the Ajax API page or the Form API entry for Ajax. There are a handful of other options you can use to further customize the Ajax behavior of your form elements.

Now it’s time to write the callback method inside of our form class. This receives the $form array and $form_state object as arguments coming from the form that triggered the Ajax request:

/**
 * Ajax callback to validate the email field.
 */
public function validateEmailAjax(array &$form, FormStateInterface $form_state) {
  $valid = $this->validateEmail($form, $form_state);
  $response = new AjaxResponse();
  if ($valid) {
    $css = ['border' => '1px solid green'];
    $message = $this->t('Email ok.');
  }
  else {
    $css = ['border' => '1px solid red'];
    $message = $this->t('Email not valid.');
  }
  $response->addCommand(new CssCommand('#edit-email', $css));
  $response->addCommand(new HtmlCommand('.email-valid-message', $message));
  return $response;
}

Simply put, in this method, we perform the validation and return an Ajax response with multiple commands that differ depending on the validation result. With the CssCommand we apply some css directly to the email form element while with the HtmlCommand we replace the contents of the specified selector (remember the suffix from our form element?).

These commands pretty much map to jQuery functions so they are quite easy to grasp. You can find a list of all available commands on this page. And since we are using three new classes inside this method, we must remember to also use them at the top:

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CssCommand;
use Drupal\Core\Ajax\HtmlCommand;

And that is pretty much it. If you clear the cache and reload your form, typing into the email field and removing focus will trigger the callback to validate the email address. You’ll notice the little throbber icon there (which can be changed in the definition) and the short message we defined as well. A correct email address should highlight the field in green and print the OK message while on the contrary the color red is used with an opposite message.

If we had specified a wrapper in the form element definition, we could have returned some content (or render array) which would have been placed inside that selector. So you have the option of choosing between returning content or Ajax commands but I recommend the latter for most cases because they offer a more flexible (and consistent) behavior.

Conclusion

In this article we’ve seen an example of using Ajax to improve our form and make it more friendly to end users. And we have written exactly zero lines of javascript to accomplish this.

In our case, it really is a matter of preference or fancification. But if you are dealing with a 20 field form which has validation on multiple fields similar to this, using Ajax really makes sense. It doesn’t annoy users with having to submit the form only to realize their input is invalid.

Although forms are the main area where you’ll see Ajax in Drupal 8, there are a couple of other ways you can leverage it without writing JavaScript.

Once nice way is to add the use-ajax class on any link. This will have Drupal make an Ajax request to the URL in the href attribute whenever the link is clicked. From the callback you can return Ajax commands and perform various actions as needed. But do keep in mind that jQuery and other core scripts are not loaded on all pages for anonymous users (hence Ajax will gracefully degrade to regular link behaviour). So make sure you include these scripts for anonymous users if you need this behavior.

Frequently Asked Questions on Using AJAX in Drupal 8 Forms

How can I implement AJAX in Drupal 8 forms?

Implementing AJAX in Drupal 8 forms involves a few steps. First, you need to define a form that includes an AJAX callback. This can be done in the form’s buildForm method. The ‘#ajax’ property should be added to the form element that will trigger the AJAX request. This property is an array that includes the ‘callback’ key, which specifies the method to be called when the form element is triggered. The callback method should return an AJAX response object that defines what should be updated on the page.

What are AJAX callback commands in Drupal 8?

AJAX callback commands in Drupal 8 are used to specify the actions to be performed on the client side when an AJAX request is processed. These commands are PHP classes that implement the CommandInterface. Drupal 8 includes several built-in AJAX commands, such as ‘alert’, ‘insert’, ‘remove’, and ‘replace’, among others. You can also create custom AJAX commands by creating a new class that implements the CommandInterface.

How can I create a custom AJAX command in Drupal 8?

To create a custom AJAX command in Drupal 8, you need to create a new class that implements the CommandInterface. This class should define a render method that returns an array with the following keys: ‘command’, which is the name of the command, and ‘method’, which is the method to be called on the client side. The array can also include additional data that will be passed to the client-side method.

How can I handle AJAX errors in Drupal 8?

Handling AJAX errors in Drupal 8 can be done by using the ‘error’ key in the ‘#ajax’ property of the form element. This key specifies a callback method that will be called if an error occurs during the AJAX request. The callback method should return an AJAX response object that defines how to handle the error.

How can I use AJAX to update multiple elements on a page in Drupal 8?

To use AJAX to update multiple elements on a page in Drupal 8, you can return multiple commands in the AJAX response object. Each command should specify the element to be updated and the action to be performed. The commands will be executed in the order they are added to the response object.

How can I use AJAX to replace an element on a page in Drupal 8?

To use AJAX to replace an element on a page in Drupal 8, you can use the ‘replace’ command. This command requires two arguments: the selector of the element to be replaced, and the new content. The selector can be any valid jQuery selector.

How can I use AJAX to remove an element from a page in Drupal 8?

To use AJAX to remove an element from a page in Drupal 8, you can use the ‘remove’ command. This command requires one argument: the selector of the element to be removed. The selector can be any valid jQuery selector.

How can I use AJAX to insert content into a page in Drupal 8?

To use AJAX to insert content into a page in Drupal 8, you can use the ‘insert’ command. This command requires two arguments: the selector of the element where the content will be inserted, and the new content. The content can be inserted before, after, or inside the selected element.

How can I use AJAX to display an alert message in Drupal 8?

To use AJAX to display an alert message in Drupal 8, you can use the ‘alert’ command. This command requires one argument: the message to be displayed. The message can be any valid string.

How can I use AJAX to redirect to a different page in Drupal 8?

To use AJAX to redirect to a different page in Drupal 8, you can use the ‘redirect’ command. This command requires one argument: the URL of the page to redirect to. The URL can be any valid URL.