Problem/Motivation

Changes introduced in #2826449: Ajax Facets blocks for views mean that facet blocks are rendered even when they are empty. This creates display errors including:

  • If a block is configured to render a title but is empty, the title appears because Drupal's block processing considers the block non-empty.
  • Even without a title, the block may occupy screen space due to common Drupal theming approaches such as vertical margin between blocks.

Proposed resolution

In hook_preprocess_block(), detect empty blocks and assign them the 'hidden' class.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Nick_vh created an issue. See original summary.

Nick_vh’s picture

FileSize
2.48 KB
Nick_vh’s picture

FileSize
2.48 KB

Silly mistake with comments

Nick_vh’s picture

Status: Active » Needs review

Tested this locally with bigpipe and ajax enabled and it seems to work pretty fine. Looking for some reviews from frontend developers here in terms of jquery performance impact.

Nick_vh’s picture

It is scary that all tests pass though. We need to write a test to make sure that the behavior was "wrong" before, and is corrected with this patch.

borisson_’s picture

I agree, this needs a test.

borisson_’s picture

FileSize
2.25 KB
3.3 KB

While this needs a test, I think we should get this in and make sure we add new testcoverage in the future. I'll get to it eventually.

darvanen’s picture

Status: Needs review » Reviewed & tested by the community

Didn't apply very well with composer but I think that's cweagans/composer-patchers, not this patch.

JS works well as long as people use the default block classes. Not sure how you would get around that in a more generic fashion.

I think this approach is good, and as @borisson_ says tests can come later I call this RTBC on #7.

borisson_’s picture

Crediting Christian for their work in #2867000: Add "Render facet anway" emptyBehavior to the same effect as this issue.

seanB’s picture

FileSize
2.25 KB

Rerolled for latest module version.

seanB’s picture

+++ b/js/facets-hide-when-empty.js
@@ -0,0 +1,37 @@
+      $('body').on('DOMSubtreeModified', '.block-facets', function () {

Is there a better way to find facet blocks than this class? A lot of (custom) themes might not have this class. Just ran into this while testing this.

Added the class to my template for now, but this could be an issue that is hard to debug later if you don't know the module.

borisson_’s picture

Is there a better way to find facet blocks than this class? A lot of (custom) themes might not have this class. Just ran into this while testing this.

We should probably add a class that is prefixed with js- and use that to target the blocks? If we issue a change request - that shouldn't be a problem I think?

5n00py’s picture

https://developer.mozilla.org/en-US/docs/Web/Events/DOMSubtreeModified

It's deprecated.

$.hide();
$.show();

This who calls also can be avoided. We can hide elements using css, html attributes, etc.

$('body').on('DOMSubtreeModified', '.block-facets', function () {
In drupal behaviors he have context. It should be used to avoid adding multiple event subscribers.

5n00py’s picture

Status: Reviewed & tested by the community » Needs work

Facets ajax callbacks uses ReplaceCommand to update blocks.

So when block updated via ajax or bigpipe module Drupal anyway attach behavior's for new one.

Also we can add attribute/class to empty block and i will be updated after ajax. Hiding can be implemented via css.
If we solve this issue in right way, we don't need any JS.

UPD: look \Drupal\facets\Controller\FacetBlockAjaxController::ajaxFacetBlockView

Jon352’s picture

This seems related to an issue I'm running into regarding ajax facets. If you configure a facet to use a widget (dropdown for example) the library and settings are not attached if the facet is empty when it is first built. You may run into this for example if there is some filter condition from a view / contextual filter or whatever your use case is.

My use case is I'm setting some facets active by default depending on the values of some fields of a product content type. Pretend we are looking at a product that's tagged a certain way. On the product page I want to see related products by default. So I set some active facets. Unfortunately some facets may be empty.

The missing piece is getting the widget instance and building the facet. If I comment out the test for empty results everything works as expected. I assume there is a good reason for not building the entire facet if its empty.

** edit
Here's a bit of a work around for those facing a similar issue. It might also just be useful for anyone that might want to build some custom facet results.

function your_module_search_api_solr_search_results_alter(\Drupal\search_api\Query\ResultSetInterface $result_set, Drupal\search_api\Query\Query $query, Solarium\QueryType\Select\Result\Result $result) {
  $search_id = $query->getSearchId();
  if($search_id === 'views_block:search__block_1') { // change this to whatever your search id is 
    if (!$query->getIndex()->getServerInstance()->supportsFeature('search_api_facets')) {
      return;
    }
    $facet_manager = \Drupal::service('facets.manager');
    $facet_source = 'search_api:' . str_replace(':', '__', $query->getSearchId());
    $facets = $facet_manager->getFacetsByFacetSourceId($facet_source);

    foreach($facets as $facet) {
      $results = $facet->getResults();
      if(empty($results)) {
        $new_result = new \Drupal\facets\Result\Result($facet,'','Placeholder',0);
        $facet->setResults([$new_result]);
      }
    }
  }
}
andypost’s picture

  1. +++ b/js/facets-hide-when-empty.js
    @@ -0,0 +1,37 @@
    +        if ($(this).find('.facet-hide-when-empty').length) {
    +          $(this).hide();
    ...
    +          $(this).show();
    ...
    +        if ($(this).find('.facet-hide-when-empty').length) {
    +          $(this).hide();
    ...
    +          $(this).show();
    

    Could you extract $(this) into variable?

  2. +++ b/src/FacetManager/DefaultFacetManager.php
    @@ -343,7 +343,15 @@ class DefaultFacetManager {
    +              'class' => [
    +                'facet-empty',
    +                'facet-hide-when-empty',
    

    why this class added without condition?

akalam’s picture

FileSize
1.85 KB

The Drupal behaviours are invoked on ajaxRequest, so no need to do this twice. Also, don't need to show the block, just to hide it.
Taking the andypost recomendation and saving $(this) to a variable.

akalam’s picture

Status: Needs work » Needs review
FileSize
2.5 KB

Sorry, wrong patch.

akalam’s picture

FileSize
1.07 KB
geek-merlin’s picture

Status: Needs review » Needs work

IS:

Currently on drupalsear.ch you can search for "bread" and you will see facets that should be hidden as they do not have an empty text configured for their facet. This is because AJAX and Bigpipe need to be able to replace the content of these blocks after the initial pageload.

Either this is outdated or i do not get it, but current code in \Drupal\facets\FacetManager\DefaultFacetManager::build render empty divs:

        // If the facet has no results, but it is being rendered trough ajax we
        // should render a container (that is empty). This is because the
        // javascript needs to be able to find a div to replace with the new
        // content.
        return [
          [
            $build,
            '#type' => 'container',
            '#attributes' => [
              'data-drupal-facet-id' => $facet->id(),
              'class' => ['facet-empty'],
            ],
          ],
        ];

So either this is outdated or needs clarification.

geek-merlin’s picture

Crosslinking a similar issue that i am facing, with a fixing patch.

I checked that this patch does not fix the other issue, so not a dup.
You might want to check if the other patch fixes any of the problems faced here (which i tbh do not fully understand, see above).

In any case, both issues touch the same code.

RoSk0’s picture

Status: Needs work » Reviewed & tested by the community
Related issues: +#2826449: Ajax Facets blocks for views

Just faced this issue.

Facets "Empty facet behavior" configured to "Do not display facet" but issue #2826449: Ajax Facets blocks for views introduced this code, mentioned in #21, which makes facet block be rendered always.

Patch fixes issue, tested on 1.3.

lahoosascoots’s picture

I'm not sure we can call this fixed and would be a workaround at best. The facets are hidden with jQuery and are seen on the screen at page load before they are hidden. This is not a good user experience. Is there any other way we could do this? Maybe via using a hidden class attribute and css?

lahoosascoots’s picture

Not elegant but this works to kill the flickering for now if it bothers anyone else.

function mymodule_preprocess_block(&$variables) {
  if ($variables['base_plugin_id'] === 'facet_block') {
    if (in_array('facet-hide-when-empty', $variables['content'][0]['#attributes']['class'])) {
      $variables['attributes']['class'][] = 'hidden';
    }
  }
}
nedjo’s picture

Issue summary: View changes
Status: Reviewed & tested by the community » Needs review
FileSize
816 bytes

Attached is a patch that starts fresh, following #25. Rather than working with JavaScript, we process the block prior to rendering and conditionally hide it.

borisson_’s picture

Not doing this with javascript seems like a better way to do this, so #26 looks like a good start, since this does not add additional testcoverage, we should probably at least get more +1's, but I'd prefer an extra testcase.

FeyP’s picture

Status: Needs review » Needs work

I'm sorry, if I missed anything, but as far as I can see, Drupal\facets\FacetManager\DefaultFacetManager::build() adds the facet-empty class to all facets with an empty result set regardless of the configured empty behavior.

We want to hide the block, if the empty behavior is configured to none, but if the behavior is configured to text we want to show that text, right? I guess we shouldn't hide the block then. So the check whether facet-empty exists in patch #26 is probably not enough.

Nick_vh’s picture

What Feyp said is actually correct. I tried to reproduce this and indeed, the facet hides itself even though the empty facet behavior is not respected anymore. Let's try to fix this.

Nick_vh’s picture

Status: Needs work » Needs review
FileSize
1.74 KB
Nick_vh’s picture

estoyausente’s picture

Status: Needs review » Reviewed & tested by the community

Tested and seems ok!

  • borisson_ committed 8f0ea21 on 8.x-1.x authored by Nick_vh
    Issue #2984465 by Nick_vh, akalam, borisson_, seanB, nedjo,...
estoyausente’s picture

Issue tags: +drupaldevdays
borisson_’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed.

Status: Fixed » Closed (fixed)

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

joshua.boltz’s picture

This was exactly what I needed for a use case i was seeing where i placed a facet block via layout builder and even if there were no facets to use and drill down the results for, the title was still being displayed. This patch solved that!

bmcclure’s picture

Since this fix has been on the dev branch for a year already, it would be great to get a new release version of facets so that those not using the dev branch can benefit from this.

berliner’s picture