Drupal 8 API: Ajax

Pre-requisites

Interacting with the server, without reloading the entire page

Back when it was first proposed that browsers could contact servers to load data, or small fragments of HTML, without reloading the whole page, people referred to it using the name "Ajax". This originally stood for Asynchronous Javascript And XML, but nowadays is a catch-all term for communicating between the browser and server "in the background", while the site visitor is still presented with a page.

These days, Drupal uses JSON rather than XML to communicate, but otherwise the principle is the same:

  1. Detect some user input or interaction in the browser.
  2. Make a background HTTP call to the server, while letting the site visitor know something is happening.
  3. Act on the result.

To accomplish this, we need (in theory at least) the following pieces:

  • A route—a path with a handler method or class—for Drupal to manage incoming Ajax requests.
  • A form (or maybe some links or other buttons) for the site visitor to click on.
  • Some Javascript in the browser to make the initial call, and do something with the result.

We've discussed routing and forms previously, so here we extend those examples to perform an Ajax call. Our example will do the following:

  1. When a user fills in the existing form at /tutorial/form, send the data to the server for "validation".
  2. Show a spinner while waiting for the server to respond.
  3. When the response returns, alert the user, then submit the form anyway.

As we'll see, Drupal gives us a comprehensive toolkit with which to accomplish this.

Configure an Ajax action to use a class and method

An Ajax request, made as part of some existing form, uses the same path as the form but with some query parameters. If you use some kind of browser debugger tool, you might see the example below making requests to a URL like /tutorial/form?ajax_form=1&_wrapper_format=drupal_ajax. This tells Drupal to invoke the form to handle the request, but then switch to a different configured class and method to specifically handle it as Ajax.

To configure this, we modify the existing buildForm() method on our form, to return an extra #ajax key in its structured data:

<?php
/* ... (unchanged) ... */
class TutorialForm extends FormBase {
  /* ... (unchanged) ... */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['phone'] = [
      '#type' => 'tel',
      '#title' => $this->t('Your telephone number'),
    ];
    $form['go'] = [
      '#type' => 'submit',
      '#value' => $this->t('Go'),
 
      // Modifications below.
      '#ajax' => [
        'callback' => 'Drupal\d8api\Form\TutorialForm::respondToAjax',
        'event' => 'click',
        'progress' => ['type' => 'throbber', 'message' => NULL],
      ],
    ];
 
    return $form;
  }
 
  /* ... (unchanged) ... */
}

In the example above, we give the #ajax configuration sub-array three keys:

callback
Which class and method (yet to be written) will respond to the Ajax call. This could involve any class, but for simplicity's sake we're going to add a new method to the existing TutorialForm class.
event
What event in the browser should trigger the Ajax call. This is a click event on the "Go" button. Note that, for accessibility reasons, Drupal's Javascript will also deal with keypress events e.g. submitting the form by tabbing to the button and pressing "enter".
progress
What hint should be given to the site visitor that an Ajax call is in progress. Here we specify the throbber (we'll see it in action below) but no other message.

This configuration is all that's required for the Javascript behaviours in the browser: Drupal will build the rest.

Add a class method to respond to the browser's Ajax call

To match our configuration above, we need to add a respondToAjax() method to the TutorialForm class.

Ajax-responding methods need to return a special AjaxResponse object. This object can chain any arbitrary Ajax commands: Drupal core comes with a number of PHP objects that correspond to Javascript and jQuery methods in the browser; you can also create your own classes, and instantiate them as objects to chain them; alternatively, existing objects like InvokeCommand let you

Below we implement a method which, when the response reaches the browser, causes the following three Javascript actions to occur in order:

  1. Pop up a native browser alert box with the submitted user input in it.
  2. Change the internal build ID of the form. Forms are given a unique build ID by Drupal, but if your Ajax call might be modifying the form, it's good practice to change this build ID.
  3. Submit the form, referencing the new build ID in order to find the form in the browser.

Here's the new method:

<?php
 
namespace Drupal\d8api\Form;
 
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\UpdateBuildIdCommand;
/* ... (unchanged) ... */
class TutorialForm extends FormBase {
  /* ... (with previous changes) ... */
  /**
   * AJAX response: alert contents of input field, update form and save it.
   *
   * @param array $form
   *   Form API array structure.
   * @param array $form_state
   *   Form state information.
   *
   * @return AjaxResponse
   *   Response object.
   */
  public static function respondToAjax(array $form, FormStateInterface $form_state) {
    $response = new AjaxResponse();
    $message = 'Your phone number is ' . $form_state->getValue('phone');
    $submit_selector = 'form:has(input[name=form_build_id][value='
      . $form['#build_id'] . '])';
 
    $response->addCommand(new AlertCommand($message));
    $response->addCommand(new UpdateBuildIdCommand($form['#build_id_old'], $form['#build_id']));
    $response->addCommand(new InvokeCommand($submit_selector, 'submit'));
 
    return $response;
  }
}

Arguably, you might not want to submit forms on the site visitor's behalf—it might not be best practice in some situations—but this is only meant to serve as an example.

What you should see

If you navigate to /tutorial/form, you should see the form as before:

However, when you now press the "Go" button, a blue spinner appears beside the button, and shortly afterwards an alert box appears:

If you then dismiss the alert box, the form "submits itself" and you should see the resulting page with a message, in the same way as before we added Ajax to the form:

If you can see all this, then congratulations! you have successfully added Ajax to a form in Drupal.

Further reading etc.

Comments

hi,

I need to know how to add a new field in that form via Drupal 8 Ajax submission.

Please help me.

 

Thank You,

Hi Hari,

Form API (and by extension Ajax API) is designed explicitly to work first and foremost without Javascript enabled: this is a key principle. For that, and for several other reasons (accessibility, progressive enhancement, robustness) you should also get whatever-you-want-to-do working without Javascript first, and then see how you can use Ajax to enhance it. Work out what you want to do, get the form working, and only then add Javascript to improve the experience.

Adding the markup for a new input field in Ajax is fairly trivial (as you can see, the callback can make use of arbitrary jQuery methods). But your server-side form API array must also react to the new field, so that when you submit it, Drupal will be able to make sense of the new input field (it will normally just disregard it.) You could also encounter problems with form cache, because sometimes Drupal gets form API arrays from its cache (which won't include your new element) rather than modified for Ajax. How to work around this means adding hack upon hack to get it right!

Instead of worrying about dynamically adding the field, the simplest thing would be to make the input field already present on the form, but hide it using form states until some other input field changes (e.g. a visible checkbox) and then it can appear without an Ajax call. That way people without Javascript will be able to see the field.

The official documentation for form states is pretty hard to navigate, but there's a lot of other resources for it:

I don't think it's changed much since Drupal 8 (confusingly, Drupal 8 does have a State API, which is something different!) so you should be OK with those resources.

Hope this helps, and apologies for not quite answering your original question.

 

Dear JP:

I believe your example needs to include the following "use" statements:

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\UpdateBuildIdCommand;
use Drupal\Core\Ajax\InvokeCommand;

Keep up the great work.  I am learning bunch.  Thanks!

Hey Fred,

thanks for this and your other comment. I really appreciate the feedback. You're right that these statements are missing.

To give you the background, I did a lot of coding work up front, as a kind of expedition, to make sure I really could make enough progress to warrant the blogposts. Because so much was then tied together in the same codebase, it ended up tough to split it up so that: the forms blogpost had what was relevant there; and the Ajax blogpost has what's relevant here. Clearly there's been a slip 'twixt cup and lip! Future posts should be a bit easier to isolate and hence less likely to lose details.

Thanks for the compliment too. I'm glad you're getting something out of these. I also really appreciate people trying out the worked examples, taking them seriously enough to be able to spot bugs like this. The worked examples have to work or there's no point!

Cheers,
J-P

 

Hii,

i need to know how to do core php jquery ajax in drupal 8 ?

Thnx.

Add new comment