I tried many different complicated approaches, before I found a much easier solution that suits my needs so I am posting this as a solution.
I setup my prints
View this way:
- Category is added as an exposed filter
- No contextual filter, no relationship
- Set the view path to
prints/%
- Use AJAX: Yes
I created a module and used hook_views_pre_build()
to get the category from the url parts, ie. get landscapes
from the path /prints/landscapes
then load the vocabulary term by name and if the term exists, set the exposed filter to the term id.
/**
* Implements hook_views_pre_build().
*/
function MYMODULE_views_pre_build(\Drupal\views\ViewExecutable $view) {
$view_ids = ['prints'];
if (isset($view) && in_array($view->id(), $view_ids)) {
// Get exposed input
$exposedInput = $view->getExposedInput();
// If exposed input is not set
if (empty($exposedInput['category'])) {
// Get current path
$current_path = \Drupal::service('path.current')->getPath();
$uri_segments = explode('/', $current_path);
// 2nd uri segment is the category (term name)
$current_category = isset($uri_segments[2]) ? $uri_segments[2] : NULL;
if ($current_category !== NULL) {
// Set vocabulary id
$vid = 'photo_category';
// Load term by name from vocabulary
$term = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadByProperties(['vid' => $vid, 'name' => $current_category]);
// If term was found
if ($term) {
// Get first array element
$term = reset($term);
// Filter view by setting exposed input to term id
$view->setExposedInput(['category' => $term->id()]);
}
}
}
}
}
This fixes filtering the view on page load when the category is in the URL.
As I also wanted to update the page title based on the selected category, I created an EventSubscriber for the AJAX response. All I do here is load the Category term name based on the exposed filter value, then use an HtmlCommand
to update the page title.
<?php
namespace Drupal\MYMODULE\EventSubscriber;
use Drupal\views\Ajax\ViewAjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Drupal\taxonomy\Entity\Term;
/**
* Response subscriber to handle View AJAX responses.
*/
class ViewAjaxResponseSubscriber implements EventSubscriberInterface {
/**
* Adds an AJAX command to ViewAjaxResponse.
*/
public function onResponse(FilterResponseEvent $event) {
$response = $event->getResponse();
if ($response instanceof ViewAjaxResponse) {
$view_ids = ['prints'];
$view = $response->getView();
if (isset($view) && in_array($view->id(), $view_ids)) {
$titleSelector = '.page__header h1';
if ($view->exposed_data['category'] !== 'All') {
$term = Term::load($view->exposed_data['category']);
$name = $term->getName();
$title = 'Prints in category <em class="text-grey">' . $name . '</em>';
$response->addCommand(new HtmlCommand($titleSelector, $title));
} else {
$response->addCommand(new HtmlCommand($titleSelector, 'Prints - All categories'));
}
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[KernelEvents::RESPONSE][] = ['onResponse'];
return $events;
}
}
I'll do the same for when I load the view with the category in the URL, using hook_views_pre_render()
:
/**
* Implements hook_views_pre_render().
*/
function MYMODULE_views_pre_render(\Drupal\views\ViewExecutable $view) {
$view_ids = ['prints'];
if (isset($view) && in_array($view->id(), $view_ids)) {
if ($view->exposed_data['category'] !== 'All') {
// Load category (term name) from View exposed filter data
$term = Term::load($view->exposed_data['category']);
$name = $term->getName();
// Update View title with category (term name)
$view->setTitle('Prints in category <em class="text-grey">' . $name . '</em>');
}
}
}
My last requirement was to update the URL when the view would be filtered with AJAX so in my module Javascript file:
Drupal.behaviors.viewsAjax = {
attach: function (context, settings) {
// Attach action to Drupal Ajax Views event
$(document).once('#views-exposed-form-prints-page-1', context).ajaxSuccess(function (event, data) {
if ($('#views-exposed-form-prints-page-1').length) {
let stateUri = '/prints/' + $('#views-exposed-form-prints-page-1 select[name="category"] > option:selected').text().toLowerCase();
window.history.replaceState('', '', stateUri);
}
});
}
};