The simplest way to interact with Drupal's Ajax API is to add the use-ajax class to any link. This will cause the link to make an Ajax request to the path of the link rather than moving the browser to it. A similar thing can be done with the submit button of a form using the use-ajax-submit class. This makes the form submit via Ajax to the path defined in the form's action.
The most important thing, however, is what we do on the other end of the process. Clicking a link that triggers an Ajax request won't do anything if we don't handle that request accordingly. What we have to do is return an AjaxResponse object with some jQuery commands that instruct the browser on the changes it needs to make to the DOM. So, let's see an example.
Remember in Chapter 2, Creating Your First Module, when we created our first block which simply rendered the salutation message from the service? It didn't use the theme hook we created in Chapter 4, Theming, but simply delegated to the getSalutation() method of the HelloWorldSalutation service. Let's say we want to add a link after the message that we can click on, and that hides the block entirely. There are a few easy steps we need to take to achieve this.
First, we need to alter the build() method of the block to get something like this:
/** * {@inheritdoc} */ public function build() { $build = []; $build[] = [ '#theme' => 'container', '#children' => [ '#markup' => $this->salutation->getSalutation(), ] ]; $url = Url::fromRoute('hello_world.hide_block'); $url->setOption('attributes', ['class' => 'use-ajax']); $build[] = [ '#type' => 'link', '#url' => $url, '#title' => $this->t('Remove'), ]; return $build; }
And the new use statement:
use Drupal\Core\Url;
The first thing we do is wrap our original simple #markup-based array into a Drupal core container theme hook, just so it wraps it with some divs and we don't have to create our own theme hook. After all, we are doing proof-of-concept work here. Next, below the message, we print a link to a new route we have to define. And as we talked about, to that link we add the use-ajax class. You'll notice that we can add attributes (refer back to Chapter 4, Theming, for more info on those) straight to the Url object, and they will be added to the rendered link element.
Second, we need to define this new route. Nothing could be simpler:
hello_world.hide_block: path: '/hide-block' defaults: _controller: '\Drupal\hello_world\Controller\HelloWorldController::hideBlock' requirements: _permission: 'access content'
We map it to a new method on the same Controller class we've been using and allow all users access to it.
Third (and last), we need to define the Controller method:
/** * Route callback for hiding the Salutation block. * Only works for Ajax calls. * * @param \Symfony\Component\HttpFoundation\Request $request * * @return \Drupal\Core\Ajax\AjaxResponse */ public function hideBlock(Request $request) { if (!$request->isXmlHttpRequest()) { throw new NotFoundHttpException(); } $response = new AjaxResponse(); $command = new RemoveCommand('.block-hello-world'); $response->addCommand($command); return $response; }
And the new use statements at the top:
use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\RemoveCommand; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
The first thing you'll notice is the $request parameter of this method, and you may be wondering where it's coming from. Drupal passes the current request object to any Controller method which simply type hints a parameter with that class. So, we don't have to inject it into our Controller. The reason we need it is so that we can check whether the request to this route was made via Ajax. Because if not, we don't want to handle it. That is, we throw a NotFoundHttpException which results in a regular 404.
Then comes the fun stuff relating to the Ajax API, namely, the building of an AjaxResponse full of commands back to the browser. In our example, there is only one command which instructs it to run the jQuery remove() method on the elements that match the selector that is passed to it. In our case, this is the class of the block wrapper. And with this, our functionality is in place. We can clear our cache and the block should now print a link that removes the block via Ajax.
You may be thinking: why do we need a trip back to the server for a job that can be done on the client-side alone? And the answer is—we actually don't. However, it serves as a good example of how Ajax responses work. And I encourage you to check out the documentation page (https://api.drupal.org/api/drupal/core!core.api.php/group/ajax/8.6.x ) for the Ajax API, where you can find a list of all the available commands. For example, we could have used the ReplaceCommand to replace the block with something else that comes back from the server, or the HtmlCommand to insert some data into an element on the page, or even an AlertCommand to trigger a JavaScript alert with some data coming from the server. The cool thing is that the response can process multiple commands so we are not restricted to only using one.