Hey, so this module's facet resource Drupal\facets\Plugin\facets\facet_source\SearchApiViews only supports one views with one display. Is it possible to support multiple displays for one view in the same time?

So I have a search page to search jobs using facets blocks search. For the searched job results, we need to display a block of featured jobs above the regular job results, just look like this page https://jobs.drupal.org/. The featured jobs block should share the same facets search filters as the regular jobs block.

I created a views, and a block display to be used the facets resource. And put the facets blocks, and this views' display block in the same page. Now the problem comes to how to construct the featured jobs block above the regular jobs block.

I once thought I can create an attachment block display for this views, but this attachment block won't be filtered when facets search has been triggered. The gets back to this class Drupal\facets\Plugin\facets\facet_source\SearchApiViews. It only support one display inside the views. I played with it a bit, and haven't been able to figure it out yet. Anyone can help? Thanks!

CommentFileSizeAuthor
#26 Selection_061.png101.42 KBletrollpoilu

Comments

smiletrl created an issue. See original summary.

borisson_’s picture

We're currently rewriting the facet sources in #2772745: Search API integration doesn't check/define feature support of backends, however even with that work - I don't think we actually tested a block and a page controlled by the same facet before. I don't think that would work out of the box anyway. I think you're going to need to do a search api query alter and apply the same query arguments to the query as that are applied to the other query

smiletrl’s picture

Status: Active » Fixed

Thanks borisson_. I was having issue to alter the query, and now this problem has been solved.

Status: Fixed » Closed (fixed)

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

ayalon’s picture

The main issue is, that views attachment are not supported at all eventhough they belong normally to a page view.

I did it like that:

/**
 * Implementation of hook_search_api_query_alter.
 *
 * @param \Drupal\search_api\Query\QueryInterface $query
 */
function mymodule_search_api_query_alter(QueryInterface &$query) {
  if ($query->getIndex()->getServerInstance()->supportsFeature('search_api_facets')) {
    /** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
    $facet_manager = \Drupal::service('facets.manager');

    $search_id = $query->getSearchId();

    // If we find an attachment view query, we use the same query alter as the page because they belong together
    if(strpos($search_id, 'views_attachment:facet_search_') === 0) {
      $search_id = 'views_page:facet_search__search_page';
    }

    // Add the active filters.
    $facet_manager->alterQuery($query, $search_id);
  }
}

I check for the name of the attachment view and then replace it with the related search_id of the views page.

valgibson’s picture

I stumbled upon the same problem and used the custom code in comment #5, to alter my attached view. But as I found out: in newer versions the $search_id string notation has changed. E.g. $search_id should be 'search_api:views_page__search__page'.


/**
 * Implements hook_search_api_query_alter.
 *
 * @param \Drupal\search_api\Query\QueryInterface $query
 */
function mymodule_search_api_query_alter(\Drupal\search_api\Query\QueryInterface &$query) {
  if ($query->getIndex()->getServerInstance()->supportsFeature('search_api_facets')) {
    /** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
    $facet_manager = \Drupal::service('facets.manager');

    $search_id = $query->getSearchId();

    // If we find an attachment view query, we use the same query alter as the page because they belong together
    if(strpos($search_id, 'views_attachment:search__') === 0) {
      $search_id = 'search_api:views_page__search__page';

      // Add the active filters.
      $facet_manager->alterQuery($query, $search_id);
    }
  }
}
friera’s picture

That works for me. It would be great to use the same facet for different displays.

borisson_’s picture

@friera: it was a design decision to not do that, we wanted to allow different configuration per display and that's needed as well (can't use dropdown widget on a rest view, can't use a search api adapter for core search facets, ...). So that won't change, certainly not in the 1.x branch. If we'd ever want to do that, we'd have to do a huge refactor of search api + facets and I don't see that happening in the near future.

Sorry, but the custom code pasted in #6 by @valgibson is the best solution for this problem.

jpascoal’s picture

Hello everyone! I have the same problem. I'm kind of a newbie in Drupal.

Where can I put this code posted in the comment #6? In which file?

Thanks in advance!

smiletrl’s picture

@jpascoal, you may take a look at https://www.drupal.org/docs/8/creating-custom-modules and https://www.drupal.org/docs/8/creating-custom-modules/a-practical-guide-....

So what you may do is

  • create a custom module, saying the name is 'my_module';
  • put the code into my_module.module file.

I didn't expect so many people have similar issues. Anyway, here's my code to fix this issue originally.

The search id might need to be changed to something like 'search_api:views_page_jobs__featured_jobs' according to #6 for newer version of search api module.

/**
 * Implements hook_jobs_search_api_query_alter().
 */
function mymodule_search_api_query_alter(QueryInterface &$query) {
  $search_id = $query->getSearchId();
  if ($search_id === 'views_page:jobs__featured_jobs') {
    $query->setSearchId('views_page:jobs__jobs_landing');
    $query->addTag('views_page:jobs__featured_jobs'); 
    facets_search_api_query_alter($query);
  }
}

The only difference I see from #5/#6 is above code adds tag to the original query. So if code wants to do something with above altered query, the tag can be used. See

/**
 * Implements hook_search_api_results_alter().
 */
function mymodule_search_api_results_alter(ResultSetInterface &$results) {
  $query = $results->getQuery();
  if ($query->hasTag('views_page:jobs__featured_jobs')) {
    $items = $results->getResultItems();
    // do something more with these results.
   }
}
jpascoal’s picture

Hi @smiletrl, I'm trying to create a view (machine name: homepage_view) with a page (machine name: page_1) and an attachment (machine name: attachment_1).

So, I implemented the following code in the Query.php file the in search_api module:

public function mymodule_search_api_query_alter(QueryInterface &$query) {
  $search_id = $query->getSearchId();
  if ($search_id === 'views_page:homepage_view__page_1') {
    $query->setSearchId('views_page:homepage_view__attachment_1');
    facets_search_api_query_alter($query);
  }
}

Also, I implemented the following code in the Queryinterface.php file, also in search_api module:

public function mymodule_search_api_query_alter(QueryInterface &$query);

It's not working. What I'm doing wrong? Can you guys help me?

Thanks!!!

borisson_’s picture

You shouldn't alter contrib modules like that, that's not a good idea.

It's also not supposed to happen in the search api module, but in a custom module. Create your own module (modules/custom/mymodule/ with an info file and a .module file (modules/custom/mymodule/mymodule.info.yml (with contents similar to http://cgit.drupalcode.org/sandbox-sidharrell-2466449/tree/hello_world.i...), and a modules/custom/mymodule/mymodule.module file with the alter hook in it).

Reading the documentation provided by @smiletrl will help you get started with this.

jpascoal’s picture

Thank you @borisson_, I'll try that!

jpascoal’s picture

Hello everyone! It works! Thanks @borisson_ and @smiletrl for the help. I've created a custom module and ran the code:

function worldstudy_module_search_api_query_alter(\Drupal\search_api\Query\QueryInterface &$query) {

    $facet_manager = \Drupal::service('facets.manager');
    $search_id = $query->getSearchId();

    if(strpos($search_id, 'views_attachment:homepage_view__attachment_1') === 0) {
      $search_id = 'search_api:views_page__homepage_view__page_1';
      $facet_manager->alterQuery($query, $search_id);
    }
}

After flushing all the cash, the code ran for both views (page_1 and attachment_1). However, after I changed the facet filter or changed to other page and returned to the homepage_view, the code ran only for page_1 (I need to flush all the cash again to make it runs to page_1 and attachment_1 again).

What should I do?

Thanks in advance!

jpascoal’s picture

Hello again!

I've solved the problem!

The problem was that the attachment_1 was with a tag-based cashing. After I changed to none, it worked!

vaccinemedia’s picture

@borisson_ is it possible to do this on block displays too? I currently have a search api view with a page display (displayed as a table of results) and also a block in the banner region of the theme (displayed as a map) and I have 4 facets with blocks in the "content top" region. So from a page perspective I have:

Search api view (map)
Facet 1 (block) Facet 2 (block) Facet 3 (block) Facet 4 (block)
Search api page view (table in the content region)

So I can't use an attachment as the facet blocks need to be sandwiched in-between the map in the banner region and the table in the content region. Here's my code:

/**
 * Implements hook_search_api_query_alter.
 *
 * @param \Drupal\search_api\Query\QueryInterface $query
 */
function talentmap_candidate_search_search_api_query_alter(\Drupal\search_api\Query\QueryInterface &$query) {
  if ($query->getIndex()->getServerInstance()->supportsFeature('search_api_facets')) {
    /** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
    $facet_manager = \Drupal::service('facets.manager');

    $search_id = $query->getSearchId();

    // If we find an attachment view query, we use the same query alter as the page because they belong together
    if(strpos($search_id, 'views_block:candidate_search__') === 0) {
      $search_id = 'views_page:candidate_search__page';

      // Add the active filters.
      $facet_manager->alterQuery($query, $search_id);
    }
  }
}

The block in the banner region however is not picking up the same filter as the page on which it is placed. Is there something I am missing in the config of the block or in the query alter?

vaccinemedia’s picture

I got it :) The code above works as it should (I just need to update the comments ;) )

I made it work by going to the facet config at:
admin/config/search/facets

Next to the 4 existing ones I selected clone and selected the block view as the destination on the next config screen and finally saved the generated clone.

Now I have 8 facets: 4 for the page and 4 identical ones associated with the block view and the ones for the page are the ones which have been used as the blocks. I made the mistake of trying to clone the existing ones as a test and had the same names and therefore got the error that the machine name already existed. I then made new ones with different names / machine names and these had different url aliases.

Cloning the existing facets from the page view made the machine names different but with the same url aliases which did the trick.

If this isn't in documentation I think we should get this in somewhere - maybe on Drupal.org so people can find the instructions using a search engine such as Google. I may write up a tutorial on this very subject ;)

vaccinemedia’s picture

UPDATE! ===========
It turned out I didn't even need the code example module to have multiple views displays repeating the search api query driven by the facets. All that I needed to do was make sure that each of the views which needed it had exact clones (with different machine names) of the main page view facets with the same url aliases.

I found this out by having a data export attached to the page view which did not respect the facet selections. So I added / cloned the page facets to the export view and it worked.

Un-installing the custom module had no effect on the map block view which also still worked.

So what's needed is simply to add facets to the main view and then clone them for each other the other displays which need to respect the same filter / query.

proweb.ua’s picture

#14 works for me

strykaizer’s picture

Status: Closed (fixed) » Needs work

regarding comment from #8

Attachments are a special case, and we probably do not want this behaviour.
Also, attachments are no separate facet source at this moment, so you can not even fix it at this moment.

We probably need to fix this specificly for attachments, based on code from #6.

The hard part is that attachments can be attached to multipe blocks/pages, so I'm not sure how to tackle this. Maybe we can just fire the alterQuery on every facetsource the attachment is attached to and it'll work, haven't tested it though

ariane’s picture

#6 worked for me, thanks!

valgibson’s picture

Ah, so nice to see my code suggestions still work after so many years! I'm probably going to need it too for a new project. Have a nice day everyone!

pieterjandp’s picture

In case someone wants to support multiple views displays with AJAX enabled: I managed to do this by performing an additional Drupal ajax request (in addition to the solution posted in #6), based on https://stackoverflow.com/a/3597640/528310 and https://git.drupalcode.org/project/facets/blob/8.x-1.x/js/facets-views-ajax.js#L80-83:

MYMODULE.libraries.yml:

search:
  version: 1.x
  js:
    js/search.js: {}
  dependencies:
    - core/jquery
    - core/drupal.ajax

js/search.js:

(function($, Drupal){
  // Add ajax request to refresh block view when refreshing the page view
  let s_ajaxListener = {
    tempOpen: XMLHttpRequest.prototype.open,
    tempSend: XMLHttpRequest.prototype.send,
    callback: function () {
      if (this.method === 'POST' && this.url.substring(0,ADAPTTOCORRECTLENGTH) === '/views/ajax?q=/VIEWID' && this.data.match(/&view_display_id=DISPLAYWITHFACETSID&/)) {
        const blockDomId = $('.view-display-id-DISPLAYTHATSHOULDBELINKEDWITHFACETSID').attr('class').split(' ').filter(part => part.substring(0, 15) === 'js-view-dom-id-')[0].substring(15);
        const blockData = this.data.replace(/(&view_display_id=)[^&]+/, '$1DISPLAYTHATSHOULDBELINKEDWITHFACETSID').replace(/(&view_dom_id=)[^&]+/, '$1' + blockDomId);
        Drupal.ajax({url: this.url, submit: blockData}).execute();
      }
    },
  };

  XMLHttpRequest.prototype.open = function(a = '', b = '') {
    s_ajaxListener.tempOpen.apply(this, arguments);
    s_ajaxListener.method = a;
    s_ajaxListener.url = b;
    if (a.toLowerCase() === 'get') {
      s_ajaxListener.data = b.split('?');
      s_ajaxListener.data = s_ajaxListener.data[1];
    }
  };

  XMLHttpRequest.prototype.send = function(a = '', b = '') {
    s_ajaxListener.tempSend.apply(this, arguments);
    if (s_ajaxListener.method.toLowerCase() === 'post') {
      s_ajaxListener.data = a;
    }
    s_ajaxListener.callback();
  };

})(jQuery, Drupal);
dmouse’s picture

To solve this I created an event subscriber to change the Search Id Source and I extended the checkbox widget to render the correct url for each facet.

BTW, I'm using a view with 3 displays and these are linked using the "Global: Link to display" in the header of my first display


namespace Drupal\my_module\EventSubscriber;

use Drupal\search_api\Event\QueryPreExecuteEvent;
use Drupal\search_api\Event\SearchApiEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ShareSearchSource implements EventSubscriberInterface {

  /**
   * @param \Drupal\search_api\Event\QueryPreExecuteEvent $event
   */
  public function alterSearchId(QueryPreExecuteEvent $event) {
    $query = $event->getQuery();
    $query->setSearchId('views_page:solr_search_content__page_1');
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      SearchApiEvents::QUERY_PRE_EXECUTE => 'alterSearchId',
    ];
  }

}

namespace Drupal\my_module\Plugin\facets\widget;

use Drupal\Core\Url;
use Drupal\facets\FacetInterface;
use Drupal\facets\Plugin\facets\widget\CheckboxWidget;

/**
 * The checkbox / radios widget.
 *
 * @FacetsWidget(
 *   id = "other_checkbox",
 *   label = @Translation("List of checkboxes"),
 *   description = @Translation("A configurable widget that shows a list of checkboxes shared into different view displays."),
 * )
 */
class Checkbox extends CheckboxWidget {

  /**
   * {@inheritdoc}
   */
  public function build(FacetInterface $facet) {
    $request = \Drupal::request();
    $route_name = $request->attributes->get('_route');

    $results = [];
    foreach ($facet->getResults() as $id => $result) {
      $url = $result->getUrl();
      $params = $url->getRouteParameters();
      $options = $url->getOptions();

      $url = new Url($route_name, $params, $options);
      $result->setUrl($url);

      $results[$id] = $result;
    }

    $facet->setResults($results);

    return parent::build($facet);
  }

}
erik_petra’s picture

#6 or #14 at first didn't work, I needed to disable caching in attachment to make it work as mentioned in #15.

letrollpoilu’s picture

StatusFileSize
new101.42 KB

I don't get it... I think I've done everything properly but it's still not working.

My goal is to do a global search on the website, for that I have an Index with news, media... When people are searching with a tag, I want them to see like max 3 news with this tag, then max 3 media with this tag. So I have my view with the facets and it works just fine. I just can't limit each content type to 3 items.

So I created attachments per content type, one for news, media... and set a pager of 3. The idea is to print on the same page:

- The main view (hidden, like not printed but generated)
- The attachments
- The facets

Now the facets should filter the attachments so that I have several content types groups filtered.

So I checked that there is no cache on my attachment, then I put this code on a module:

function maintheme_alter_frontend_search_api_query_alter(\Drupal\search_api\Query\QueryInterface &$query) {
    if ($query->getIndex()->getServerInstance()->supportsFeature('search_api_facets')) {
        /** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
        $facet_manager = \Drupal::service('facets.manager');

        $search_id = $query->getSearchId();

        dpm($search_id);
        
        // If we find an attachment view query, we use the same query alter
        // as the page because they belong together.
        if (strpos($search_id, 'views_attachment:content_index__attachment_1') === 0) {
            $search_id = 'views_block:content_index__2_search_index';

            // Add the active filters.
            $facet_manager->alterQuery($query, $search_id);
        }
    }
}

This is still not working. Am I doing something wrong? Attached are my views.

https://ibb.co/dJctfB0
https://ibb.co/sQbNvFL
https://ibb.co/7Jtr9Hc

Thanks for your help!

dimonuga’s picture

As per document, since 1.14 version API hooks depreciated.

Please use the "search_api.query_pre_execute" event instead.

I've tried to use solution in #24, but did not succeed. can anybody share examples to implement pre_execute in new approach?

drclaw’s picture

Just wanted to +1 Comment-#18 and re-iterate that if you just clone the facets from one display to another and keep the facet alias the same, the view will pick it up. In @vaccinemedia's words:

So what's needed is simply to add facets to the main view and then clone them for each other the other displays which need to respect the same filter / query

I don't think the preExecute or query_alter is necessary at all. At least not for the relatively simple example of a "featured" block inheriting the facets from the main listing.

v.kydyba’s picture

Solution #24 works for me, thanks.

mkalkbrenner’s picture

#24 basically works. But since Facets itself subcribes to the same event now you need to ensure that your event subscriber has the higher priority. So the adjusted code looks like this:


namespace Drupal\my_module\EventSubscriber;

use Drupal\search_api\Event\QueryPreExecuteEvent;
use Drupal\search_api\Event\SearchApiEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ShareSearchSource implements EventSubscriberInterface {

  /**
   * @param \Drupal\search_api\Event\QueryPreExecuteEvent $event
   */
  public function alterSearchId(QueryPreExecuteEvent $event) {
    $query = $event->getQuery();

    // Add some more logic here!

    $query->setSearchId('views_page:solr_search_content__page_1');
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    $events[SearchApiEvents::QUERY_PRE_EXECUTE][] = ['alterSearchId', 100];

    return $events;    
  }

}

vurt’s picture

Thank you, the solution with the eventsubscriber #30 is a good general workaround to use facets with attachment views. Worked for me.

tvalimaa’s picture

#30 It seems that code got it working. Thanks you.

mlncn’s picture

Anyone doing this (e.g. eight items from one display, 12 from the next, so as to have visually distinct as far as view mode and number per row but all filtered with the same facets) with Facets 3.0.x? Seems things have changed there, or i'm missing a trick.

ressa’s picture

Thanks @dmouse and @mkalkbrenner. I have attempted to use your suggestions, but it seems to not work any more, maybe due to changes in version 3, as @mlncn mentions?

I have added the first part with function alterSearchId (@mkalkbrenner's updated version) in my_module.module and the second part in Checkbox.php, like this:

$ tree -L 5 .
.
├── my_module.info.yml
├── my_module.module
├── README.md
└── src
    └── Plugin
        └── facets
            └── widget
                └── Checkbox.php

On /admin/config/search/facets my Views page is listed as "Facet source - search_api:views_page__map__map - View Map, display Map - Configure" which I use the facets on.

I assume I should replace $query->setSearchId('views_page:solr_search_content__page_1'); in the example with search_api:views_page__map__map, right?

After this, under each facet, I also assume I would get checkboxes, in stead of the single select current dropdown, but I still get the dropdown ...