Drupal 8/9: Facets AJAX commands

A while ago I was developing a website in Drupal that included a faceted search. The client had some very specific requirements which were not included in the facets module by default. One of those requirements was updating and adding several pieces of content on the page depending on which filters were activated. This might be an easy task if it weren’t for the AJAX functionality that’s embedded in the facets module…

Faceted search

Possible solutions

I came up with three possible solutions:

  • Disable AJAX on the faceted search 💩
  • Listen to the facets_filter JavaScript event 😐
  • Alter the response object of the facet AJAX callback 👍

The first option was never really an option because the client required the faceted search to work without reloading the page after every change. The only two options left, needed some more in-depth analysis.

The JavaScript event

Implementing this solution would be pretty straight forward:

  • Add a JavaScript file
  • Add an event listener
  • Execute (jQuery) AJAX call(s) to the Drupal back-end
  • Update the content with for example jQuery(‘.selector’).html(response);

This approach however required an extra (unnecessary) request to the back-end which would slow down the process. I would also have to create some sort of endpoint to allow the JavaScript AJAX request to fetch the data. Which in turn would mean that this solution would depend on functionality written in JavaScript (front-end) as well as PHP (back-end). I had a funny feeling this would not be an ideal approach.

Altering the response object

After some researching I figured out that all facet AJAX requests were routed through one specific path:

/facets-block-ajax

That route had a controller as a callback that returned a simple AjaxResponse object with multiple AJAX commands attached to it:

Knowing Drupal I knew I could override this callback and add extra AJAX commands to the response.

This would mean that:

  1. I didn’t need an extra (unnecessary) request to the back-end
  2. I only had to add some PHP code to make this work. Everything simple and centralized, just as I like my code to be.

The best way forward

To me, it was clear that the “Alter the response object of the facet AJAX callback” was by far the best approach. Now I just had to implement it… 💻

I divided the task at hand into four sub-tasks:

Overriding the facet route

I just registered a route subscriber in my services.yml file and added the subscriber:

At this point, the facet AJAX callback pointed to my custom controller.

Adding a custom controller

This controller extends the base FacetBlockAjaxController class which does all the heavy lifting. The only thing I added was an event dispatcher which dispatches a custom event to alter the response object. This allowed me to alter the AJAX commands attached to the response.

Defining a custom event

The next step was to define the custom FacetsAlterAjaxCommandsEvent event class. This class keeps track of the altered response object throughout the entire event subscriber process:

Important to keep in mind here is that in PHP, objects exhibit a “reference-like” behavior: While you can not assign a completely different value, you can still change the properties of the object.

This means we don’t need to set the altered response object for the changes to be picked up by the event dispatcher in the controller.

Adding event subscribers

At this point, my “mini framework” was ready to be put to work. I added several custom modules which all had an event subscriber:

Each of these event subscribers added an extra AJAX command which in turn were picked up by the dispatcher in my custom controller… ét voila, every facet filter change was accompanied by my custom HTML changes.

I feel confident this is a pretty good solution until the facets module provides us with some mechanism to achieve this more generically. Of course, this is not the only solution. Feel free to share your thoughts on this approach in the comment section!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store