Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Using Ajax Forms in Drupal 8

Parent Feed: 

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.

Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web