Ajax in Drupal

The other day needed to use AJAX to implement a feature in Drupal. The main reason to use AJAX was to be able to use caching and still have some content vary slightly on the same page. I could not find a clear answer straight away, I found lots of good documentation about AJAX in the context of Forms in Drupal. Not plain AJAX. Hence this tutorial.

The goal of this tutorial is to implement AJAX in its simplest form. As an example we will create a block and replace it's content with the server timestamp, using AJAX.

You can find the code on Github.

Create a new module and create an info yaml file:

hello_ajax.info.yml

name: 'Hello AJAX' 
type: module 
description: 'Simple module to show base AJAX functionality.' 
core: 8.x 
core_version_requirement: ^8 || ^9 
package: Custom 

To render some code on the front end we will implement a block:

src/Plugin/Block/HelloAjaxBlock.php

<?php

declare(strict_types = 1);

namespace Drupal\hello_ajax\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\BlockPluginInterface;

/**
 * Provides the 'Hello Ajax' Block.
 *
 * @Block(
 *   id = "hello_ajax_block",
 *   admin_label = @Translation("Hello AJAX Block"),
 *   category= @Translation("Hello AJAX"),
 * )
 *
 * @package Drupal\hello_ajax\Plugin\Block
 */
class HelloAjaxBlock extends BlockBase implements BlockPluginInterface {

  /**
   * {@inheritDoc}
   */
  public function build(): array {
    return [
      '#markup' = $this->t("Hello AJAX!"),
      '#attached' => [
        'library' => [
          'hello_ajax/hello-ajax',
        ],
      ],
    ];
  }

}

The previous code creates a basic block rendering the text "Hello AJAX!" via a Render Array. We also attach a library which we will create later. This library will add the Javascript part of the AJAX functionality.

At this point, you can enable the module and place the block in the sidebar or some other location where it is visible for all users.

Next, we will create the Javascript to do the AJAX request:

js/hello_ajax.js

/**
 * @file
 * Getting the server time via AJAX.
 */

(function ($, Drupal) {

  'use strict';

  /**
   * Replaces content of hello Ajax block with response from Ajax call.
   */
  Drupal.behaviors.helloAjaxGetTime = {
    attach: function () {

      $.ajax({
        url: Drupal.url('hello-ajax-response'),
        type: 'POST',
        dataType: 'json',
        success: function (response) {
          if (response.hasOwnProperty('time')) {
            $(".block-hello-ajax .content").text(response.time);
          }
        }
      });
    }
  };

})(jQuery, Drupal);

In the previous file, we create a Drupal behaviour that will perform the AJAX call on page load. We make use of jQuery here to make life easier and to make the example more clear, we will require it when we define the Library in a later step.

The important part here is that we make a call to a route we will be creating later. The controller at the end of this route will create a timestamp and return it as a JSON Object. If the Controller is responding well the success response will be called and we simply replace the content of <div class="content"> with the time stamp, we received from the endpoint.

Let's define the library:

hello_ajax.libraries.yml

hello-ajax:
  version: VERSION
  js:
    js/hello-ajax.js: {}
  dependencies:
    - core/drupal.ajax

Here we register our javascript file and require core/drupal.ajax, this will include many things we need(and do not need) including jQuery, which enables us to use the jQuery ajax() function. This library is still pretty convenient and makes clear what we are trying to achieve here.

Let's create our controller:

src/Controller/HelloAjaxController.php

<?php

declare(strict_types=1);

namespace Drupal\hello_ajax\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;

/**
 * Returns Ajax response for Hello Ajax route.
 */
class HelloAjaxController extends ControllerBase {

  /**
   * Returns Ajax Response containing the current time.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   Ajax response containing html to render time.
   */
  public function createHelloAjaxResponse(): JsonResponse {
    $currentTime = \Drupal::service('date.formatter')--->format(time());
    $time = [
      '#markup' => $this->t('Current time: @time', ['@time' => $currentTime]),
    ];

    $response['time'] = \Drupal::service('renderer')->render($time);
    return new JsonResponse($response);
  }

}

This controller is pretty straight forward, we implement _ControllerBase_ and only implement the method returning our JSON object as an Instance of Symfony\Component\HttpFoundation\JsonResponse, we will be linking to our route later. in this case, I choose the JsonResponse Object because I wanted to use our own JS implementation. When using AJAX in a form context you can make use of AJAX commands, which are PHP classes linked to specific Javascript to perform common tasks in JS. This is out of the scope of this tutorial. Read more about these commands in the AJAX API docs on Drupal.org.

hello_ajax.routing.yml

hello_ajax.hello_ajax_response:
  path: '/hello-ajax-response'
  defaults:
    _controller: '\Drupal\hello_ajax\Controller\HelloAjaxController::createHelloAjaxResponse'
  requirements:
    _permission: 'access content'

We create the route which will link the path we used in the Ajax call in the Javascript earlier, which we link to our controller. Important to note is that you have to set the permissions for the route here. In this example I choose to use very wide permissions, to view published content, as all anonymous users may see the date in the block. Without defining the permissions this route will not work and will return an HTTP 403 to our Ajax method.

I hope this tutorial makes it a bit clear ho to start working with AJAX in Drupal 8 and higher.