Loading facet blocks via AJAX in a Dialog results in incorrect link generation

Created on 13 January 2023, over 2 years ago
Updated 24 January 2023, about 2 years ago

Problem/Motivation

I'm currently loading facet blocks via AJAX into an off canvas dialog provided by the core Dialog API. To achieve this, I have a controller set up that accepts a facet ID as a parameter, and then loads the block (I've attached the code at the end of this issue/support request as an example).

This works perfectly on the face of it (all of my facets are listed, the dialog works and shows the facet widgets for each facet). However, what I've noticed is that if I have a facet enabled, this isn't taken into account when generating facet links. For example; if I have a facet applied for language, and then look at the link generated for my collection facets, the facets link to my search page but without the existing language facet applied.

The search view itself is not an AJAX view, it's just the loading of the facet blocks. Unfortunately, this is a requirement as we have a large amount of facets that kill the performance if loaded as part of the main request.

Here are some things I've tried:

  • Adding the main page parameters to my dialog links
  • Using these query parameters to configure, load, and execute my search view during the AJAX request before the facet block is generated (see following example)

These facet blocks are only ever rendered on the same page as the facet source View, however as it's not the same _request_ it may as well be on a different page.

I guess my question is one of multiple parts:

  1. Am I missing something obvious in my approach
  2. Is it possible to do what I'm trying to achieve
  3. If it is and I'm going the wrong way about it, what's the right way to go about it

As an aside, I'm rendering the blocks isn't a requirement if it's easier to render the facets themselves - it just felt like it would be easier.

As promised, here's my controller code. As mentioned, the facet blocks display, but already-applied facets aren't taken into account during link generation.

MY_MODULE.routing.yml

my_facets.ajax_facet_block:
  path: '/ajax/my-facets/{facet_id}'
  defaults:
    _title: 'Filter by'
    _controller: '\Drupal\my_facets\Controller\AjaxFacetBlockController::renderFacetBlockAction'
  requirements:
    _permission: 'access content'

Controller

<?php

namespace Drupal\my_facets\Controller;

use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Render\RendererInterface;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Returns responses for Facets routes.
 */
class AjaxFacetBlockController extends ControllerBase {

  public function __construct(
    protected BlockManagerInterface $blockManager,
    protected RendererInterface $renderer,
    protected Request $request
  ) {
  }

  public static function create(ContainerInterface $container): static {
    return new static(
      $container->get('plugin.manager.block'),
      $container->get('renderer'),
      $container->get('request_stack')?->getCurrentRequest()
    );
  }

  /**
   * Builds the response.
   */
  public function renderFacetBlockAction(string $facet_id) {
    // We need to execute the search view to correctly generate links.
    // @ToDo: Once this is working inject the entity type manager rather than calling `Views::getView`
    $search_view = Views::getView('search');

    if (!$search_view) {
      throw new NotFoundHttpException();
    }

    $search_view->setDisplay('search_page');

    $search_view->setExposedInput(array_filter(
      $this->request->query->all(),
      static fn ($k) => !str_starts_with('_', $k),
      ARRAY_FILTER_USE_KEY
    ));

    $non_facet_params = array_filter(
      $this->request->query->all(),
      static fn ($key) => $key !== 'f' && !str_starts_with($key, '_'),
      ARRAY_FILTER_USE_KEY
    );

    $search_view->exposed_raw_input = array_merge(
      $search_view->exposed_raw_input,
      $non_facet_params
    );

    $search_view->exposed_data = array_merge(
      $search_view->exposed_data,
      $non_facet_params
    );

    $search_view->preExecute();
    $search_view->execute();

    try {
      /** @var \Drupal\facets\Plugin\Block\FacetBlock $block */
      $block = $this->blockManager->createInstance(
        'facet_block' . PluginBase::DERIVATIVE_SEPARATOR . $facet_id, []
      );
    }
    catch (\Exception) {
      throw new NotFoundHttpException();
    }

    if (!$block || !$block->access($this->currentUser())) {
      throw new NotFoundHttpException();
    }

    return $block->build();
  }

}
💬 Support request
Status

Fixed

Version

3.0

Component

Code

Created by

🇬🇧United Kingdom chapabu

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇬🇧United Kingdom chapabu

    I managed to work this one out. The QueryString URL processor has a check for whether or not the request is an XMLHttpRequest (I assume for AJAX based Views) where there is a subrequest. In my case, this subrequest doesn't exist because I'm doing something a little different.

    The fix for my use-case was to strip the 'X-Requested-With' header out of the request for my specific path in an event listener listening on the KernelEvents::REQUEST event. Possibly not the best solution, but it worked for me without risking breaking anything else I may want to do with Facets.

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024