0

I have a view of Commerce products of type Print. The product type Print has a reference on a Taxonomy called Category. I setup the Category field as an exposed filter (dropdown) for my View page. The View uses AJAX and the URL is /prints.

If I want to load my View with a preselected category, I can go to http://d8.local/prints?category=1 which displays the View already filtered and with the corresponding category selected in the dropdown.

I could setup a contextual filter and a relationship on the View and set the path to prints/% so that I could filter and access it as http://d8.local/prints/landscapes where landscapes is one of the vocabulary terms, which feels better than ?category=1.

When I do so however, going to http://d8.local/prints/landscapes then using the exposed filter, it will filter the already filtered results. Also going to /landscapes doesn't select the option in the dropdown.

Is there a way to combine the 2 functionalities?

Edit:

To summarize the functionalities I am after:

  1. I should be able to filter the products by category by adding the vocabulary term name as a path component, ie. /prints/landscapes.

  2. When accessing the View with the category as the path component, the appropriate category exposed filter dropdown option should be selected, ie. Landscapes should be selected in the dropdown if accessing /prints/landscapes.

  3. I should be able to filter the View using AJAX when manually selecting another category from the exposed filter dropdown.

  4. The main page title must be updated based on the category whichever way the view was loaded (page load and AJAX reload)

  5. Ideally, I would update the URL when manually selecting a category from the dropdown, but I suppose that's a separate question and it shouldn't be too hard to handle.

  • Can you clarify what exactly is your goal? How important is the link? You can make the term name appear in the Exposed filter (use Relationship). You could use Summary option of the Contextual filter instead of the Exposed filter, this would provide an interface for user to choose an option, and then when click through they would see the "nice" link. What do you want to achieve WRT user filtering a term, having a term preselected, having the URL show something in particular? – prkos Aug 15 at 13:35
  • @prkos please see my edit in the question – MrUpsidown Aug 15 at 14:27
1

There are many ways to solve this, you can take different approaches to the filtering part, the URL part, and the contextual part, and how they play together to get you to your goal. Here's one of the possibilities, probably the easiest route:

  1. This is solved by adding a Relationship for the category field, and then adding a Term "Name" Contextual filter (it will automatically use that Relationship). Be mindful how you configure the spaces/dashes options. This will give you the term name in the URL instead of the term ID.
  2. To get the Exposed filter to match the Contextual you need to use the Views Filter Harmonizer module. There's no workable D8 version yet, you can try and help there, there is an issue open for it.
  3. If you've set up the Exposed filter properly, and turned Ajax on, it should work without a page reload. The "harmonizer" module also claims to play well with that.
  4. I might be missing something here, I wouldn't expect that the Addressbar updating without a page reload is easy, but again the "harnomizer" module claims to do just that, so it seems you're covered if you can get that module to D8.

Another module that might be relevant in another approach: (also no D8 yet) Query Parameters To URL , but similar outcome can be done through your server configuration.

  • Thanks for that. I'll investigate further then. I might try to achieve that without an additional module. I'll post my findings anyway. – MrUpsidown Aug 16 at 8:02
0

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);
            }
        });
    }
};

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.