From 2f438310aa0796811fbf13b7f411ca19a6537537 Mon Sep 17 00:00:00 2001 From: dabbor <dabbor@2214664.no-reply.drupal.org> Date: Wed, 25 Aug 2021 13:39:08 +0200 Subject: [PATCH] Issue #3052574 by dabbor, Mschudders, czigor, ctrlADel, matiasmiranda, raman.b, MPetkovFFW, dealancer, allaprishchepa, lukasss, Oleksiy, amoebanath, KarlShea, balis_m, silajit, yuraosn, p-neyens: Facets with AJAX not working in most of situations --- facets-ajax-3052574-117.patch | 698 ++++++++++++++++++ facets.module | 19 +- js/facets-views-ajax.js | 105 ++- modules/facets_summary/facets_summary.module | 23 + .../facets_summary.services.yml | 2 + .../DefaultFacetsSummaryManager.php | 66 +- .../src/Plugin/Block/FacetsSummaryBlock.php | 46 +- src/Controller/FacetBlockAjaxController.php | 103 +-- src/Plugin/Block/FacetBlock.php | 64 +- 9 files changed, 1012 insertions(+), 114 deletions(-) create mode 100644 facets-ajax-3052574-117.patch diff --git a/facets-ajax-3052574-117.patch b/facets-ajax-3052574-117.patch new file mode 100644 index 00000000..55e47777 --- /dev/null +++ b/facets-ajax-3052574-117.patch @@ -0,0 +1,698 @@ +diff --git a/facets.module b/facets.module +index 6c04f21..7b43909 100644 +--- a/facets.module ++++ b/facets.module +@@ -124,20 +124,27 @@ function facets_entity_presave(EntityInterface $entity) { + /** + * Implements hook_preprocess_block(). + * +- * Adds a class for the widget to the facet block to allow for more specific +- * styling. ++ * Adds some classes for hiding empty blocks, working ajax, and more. + */ + function facets_preprocess_block(&$variables) { + if ($variables['configuration']['provider'] == 'facets') { + // Hide the block if it's empty. +- if (!empty($variables['elements']['content'][0]['#attributes']['class']) && in_array('facet-hidden', $variables['elements']['content'][0]['#attributes']['class'])) { +- // Add the Drupal class for hiding this for everyone, including screen +- // readers. See hidden.module.css in the core system module. +- $variables['attributes']['class'][] = 'hidden'; ++ if (!empty($variables['content']['facet_block']['#attributes'])) { ++ $attributes = $variables['content']['facet_block']['#attributes']; ++ if (isset($attributes['class'])) { ++ if (in_array('hidden', $attributes['class'])) { ++ // Add the Drupal class for hiding this for everyone, including screen ++ // readers. See hidden.module.css in the core system module. ++ $variables['attributes']['class'][] = 'hidden'; ++ } ++ } + } ++ + if (!empty($variables['derivative_plugin_id'])) { + $facet = Facet::load($variables['derivative_plugin_id']); + $variables['attributes']['class'][] = 'block-facet--' . Html::cleanCssIdentifier($facet->getWidget()['type']); ++ // For use ajax. ++ $variables['attributes']['data-drupal-block-facet-id'] = $facet->id(); + } + } + } +diff --git a/js/facets-views-ajax.js b/js/facets-views-ajax.js +index e0e4f2c..0af8c8e 100644 +--- a/js/facets-views-ajax.js ++++ b/js/facets-views-ajax.js +@@ -37,17 +37,72 @@ + return; + } + +- // Update view on summary block click. +- if (updateFacetsSummaryBlock() && (facetId === 'facets_summary_ajax')) { +- $('[data-drupal-facets-summary-id=' + facetSettings.facets_summary_id + ']').children('ul').children('li').once().click(function (e) { +- e.preventDefault(); +- var facetLink = $(this).find('a'); +- updateFacetsView(facetLink.attr('href'), current_dom_id, view_path); +- }); ++ // Update view on range slider stop event ++ if (typeof settings.facets !== "undefined" && settings.facets.sliders && settings.facets.sliders[facetId]) { ++ settings.facets.sliders[facetId].stop = function (e, ui) { ++ var href = settings.facets.sliders[facetId].url.replace('__range_slider_min__', ui.values[0]).replace('__range_slider_max__', ui.values[1]); ++ ++ // Update facet query params on the request. ++ var currentHref = window.location.href; ++ var currentQueryParams = Drupal.Views.parseQueryString(currentHref); ++ var newQueryParams = Drupal.Views.parseQueryString(href); ++ ++ var queryParams = {}; ++ var facetPositions = []; ++ var fCount = 0; ++ var value = ''; ++ var facetKey = ''; ++ for (var paramName in currentQueryParams) { ++ if (paramName.substr(0, 1) === 'f') { ++ value = currentQueryParams[paramName]; ++ // Store the facet position so we can override it later. ++ facetKey = value.substr(0, value.indexOf(':')); ++ facetPositions[facetKey] = fCount; ++ queryParams['f[' + fCount + ']'] = value; ++ fCount++; ++ } ++ else { ++ queryParams[paramName] = currentQueryParams[paramName]; ++ } ++ } ++ ++ var paramKey = ''; ++ for (let paramName in newQueryParams) { ++ if (paramName.substr(0, 1) === 'f') { ++ value = newQueryParams[paramName]; ++ // replace ++ facetKey = value.substr(0, value.indexOf(':')); ++ if (typeof facetPositions[facetKey] !== 'undefined') { ++ paramKey = 'f[' + facetPositions[facetKey] + ']'; ++ } ++ else { ++ paramKey = 'f[' + fCount + ']'; ++ fCount++; ++ } ++ queryParams[paramKey] = newQueryParams[paramName]; ++ } ++ else { ++ queryParams[paramName] = newQueryParams[paramName]; ++ } ++ } ++ ++ href = '/' + Drupal.Views.getPath(href) + '?' + $.param(queryParams); ++ ++ updateFacetsView(href, current_dom_id, view_path); ++ }; ++ } ++ else if (facetId == 'facets_summary_ajax_summary' || facetId == 'facets_summary_ajax_summary_count') { ++ if (updateFacetsSummaryBlock()) { ++ $('[data-drupal-facets-summary-id=' + facetSettings.facets_summary_id + ']').children('ul').children('li').once().click(function (e) { ++ e.preventDefault(); ++ var facetLink = $(this).find('a'); ++ updateFacetsView(facetLink.attr('href'), current_dom_id, view_path); ++ }); ++ } + } + // Update view on facet item click. + else { +- $('[data-drupal-facet-id=' + facetId + ']').each(function (index, facet_item) { ++ $('[data-drupal-facet-id |= ' + facetId + ']').each(function (index, facet_item) { + if ($(facet_item).hasClass('js-facets-widget')) { + $(facet_item).unbind('facets_filter.facets'); + $(facet_item).on('facets_filter.facets', function (event, url) { +@@ -104,8 +159,8 @@ + var facets_blocks = facetsBlocks(); + + // Remove All Range Input Form Facet Blocks from being updated. +- if(settings.facets && settings.facets.rangeInput) { +- $.each(settings.facets.rangeInput, function (index, value){ ++ if (settings.facets && settings.facets.rangeInput) { ++ $.each(settings.facets.rangeInput, function (index, value) { + delete facets_blocks[value.facetId]; + }); + } +@@ -121,17 +176,17 @@ + + // Update facets summary block. + if (updateFacetsSummaryBlock()) { +- var facet_summary_wrapper_id = $('[data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax.facets_summary_id + ']').attr('id'); +- var facet_summary_block_id = ''; +- if (facet_summary_wrapper_id.indexOf('--') !== -1) { +- facet_summary_block_id = facet_summary_wrapper_id.substring(0, facet_summary_wrapper_id.indexOf('--')).replace('block-', ''); +- } +- else { +- facet_summary_block_id = facet_summary_wrapper_id.replace('block-', ''); +- } + facet_settings.submit.update_summary_block = true; +- facet_settings.submit.facet_summary_block_id = facet_summary_block_id; +- facet_settings.submit.facet_summary_wrapper_id = settings.facets_views_ajax.facets_summary_ajax.facets_summary_id; ++ facet_settings.submit.facet_summary_plugin_ids = {}; ++ let summary_selector = '[data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax_summary.facets_summary_id + ']'; ++ if (settings.facets_views_ajax.facets_summary_ajax_summary_count !== undefined) { ++ summary_selector += ', [data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax_summary_count.facets_summary_id + ']'; ++ } ++ $(summary_selector).each(function (index, summaryWrapper) { ++ let summaryPluginId = $(summaryWrapper).attr('data-drupal-facets-summary-plugin-id'); ++ let summaryPluginIdWrapper = $(summaryWrapper).attr('id'); ++ facet_settings.submit.facet_summary_plugin_ids[summaryPluginIdWrapper] = summaryPluginId; ++ }); + } + + Drupal.ajax(facet_settings).execute(); +@@ -143,7 +198,7 @@ + var settings = drupalSettings; + var update_summary = false; + +- if (settings.facets_views_ajax.facets_summary_ajax) { ++ if (settings.facets_views_ajax.facets_summary_ajax_summary || settings.facets_views_ajax.facets_summary_ajax_summary_count) { + update_summary = true; + } + +@@ -162,8 +217,8 @@ + return v.slice(block_id_start.length, v.length); + } + }).join(); +- var block_selector = '#' + $(this).attr('id'); +- facets_blocks[block_id] = block_selector; ++ var block_selector = $(this).attr('id'); ++ facets_blocks[block_selector] = block_id; + }); + + return facets_blocks; +@@ -204,12 +259,12 @@ + } + + // Helper function to add exposed form data to facets url +- var addExposedFiltersToFacetsUrl = function(href, view_name, view_display_id) { ++ var addExposedFiltersToFacetsUrl = function (href, view_name, view_display_id) { + var $exposed_form = $('form#views-exposed-form-' + view_name.replace(/_/g, '-') + '-' + view_display_id.replace(/_/g, '-')); + + var params = Drupal.Views.parseQueryString(href); + +- $.each($exposed_form.serializeArray(), function() { ++ $.each($exposed_form.serializeArray(), function () { + params[this.name] = this.value; + }); + +diff --git a/modules/facets_summary/facets_summary.module b/modules/facets_summary/facets_summary.module +index 6720862..af2dc3f 100644 +--- a/modules/facets_summary/facets_summary.module ++++ b/modules/facets_summary/facets_summary.module +@@ -98,3 +98,26 @@ function facets_summary_theme_suggestions_facets_summary_item_list(array $variab + function facets_summary_preprocess_facets_summary_item_list(array &$variables) { + template_preprocess_item_list($variables); + } ++ ++/** ++ * Implements hook_preprocess_block(). ++ * ++ * Adds some classes for hiding empty blocks, working ajax, and more. ++ */ ++function facets_summary_preprocess_block(&$variables) { ++ if ($variables['configuration']['provider'] == 'facets_summary') { ++ // Hide the block if it's empty. ++ if (!empty($variables['content']['facets_summary']['#attributes'])) { ++ $attributes = $variables['content']['facets_summary']['#attributes']; ++ if (isset($attributes['class'])) { ++ if (in_array('hidden', $attributes['class'])) { ++ // Add the Drupal class for hiding this for everyone, including screen ++ // readers. See hidden.module.css in the core system module. ++ $variables['attributes']['class'][] = 'hidden'; ++ } ++ } ++ } ++ // For use ajax. ++ $variables['attributes']['data-drupal-block-facet-summary-id'] = $variables['configuration']['id']; ++ } ++} +diff --git a/modules/facets_summary/facets_summary.services.yml b/modules/facets_summary/facets_summary.services.yml +index ba1a6f5..bd8f14e 100644 +--- a/modules/facets_summary/facets_summary.services.yml ++++ b/modules/facets_summary/facets_summary.services.yml +@@ -8,6 +8,8 @@ services: + - '@plugin.manager.facets.facet_source' + - '@plugin.manager.facets_summary.processor' + - '@facets.manager' ++ - '@plugin.manager.facets.url_processor' ++ - '@facets.utility.url_generator' + facets_summary.search_api_subscriber: + class: Drupal\facets_summary\EventSubscriber\SearchApiSubscriber + arguments: ['@entity_type.manager'] +diff --git a/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php b/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php +index 105c438..0adafc0 100644 +--- a/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php ++++ b/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php +@@ -4,13 +4,17 @@ namespace Drupal\facets_summary\FacetsSummaryManager; + + use Drupal\Core\Link; + use Drupal\Core\StringTranslation\StringTranslationTrait; ++use Drupal\Core\Url; + use Drupal\facets\Exception\InvalidProcessorException; + use Drupal\facets\FacetManager\DefaultFacetManager; + use Drupal\facets\FacetSource\FacetSourcePluginManager; ++use Drupal\facets\Result\Result; + use Drupal\facets_summary\Processor\BuildProcessorInterface; + use Drupal\facets_summary\Processor\ProcessorInterface; + use Drupal\facets_summary\Processor\ProcessorPluginManager; + use Drupal\facets_summary\FacetsSummaryInterface; ++use Drupal\facets\UrlProcessor\UrlProcessorPluginManager; ++use Drupal\facets\Utility\FacetsUrlGenerator; + + /** + * The facet summary manager. +@@ -45,6 +49,20 @@ class DefaultFacetsSummaryManager { + */ + protected $facetManager; + ++ /** ++ * The url processor plugin manager. ++ * ++ * @var \Drupal\facets\UrlProcessor\UrlProcessorPluginManager ++ */ ++ protected $urlProcessorPluginManager; ++ ++ /** ++ * The facets url generator. ++ * ++ * @var \Drupal\facets\Utility\FacetsUrlGenerator ++ */ ++ protected $facetsUrlGenerator; ++ + /** + * Constructs a new instance of the DefaultFacetManager. + * +@@ -54,11 +72,17 @@ class DefaultFacetsSummaryManager { + * The facets summary processor plugin manager. + * @param \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager + * The facet manager service. ++ * @param \Drupal\facets\UrlProcessor\UrlProcessorPluginManager $url_processor_plugin_manager ++ * The url processor plugin manager. ++ * @param \Drupal\facets\Utility\FacetsUrlGenerator $facets_url_generator ++ * The facets url generator. + */ +- public function __construct(FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, DefaultFacetManager $facet_manager) { ++ public function __construct(FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, DefaultFacetManager $facet_manager, UrlProcessorPluginManager $url_processor_plugin_manager, FacetsUrlGenerator $facets_url_generator) { + $this->facetSourcePluginManager = $facet_source_manager; + $this->processorPluginManager = $processor_plugin_manager; + $this->facetManager = $facet_manager; ++ $this->urlProcessorPluginManager = $url_processor_plugin_manager; ++ $this->facetsUrlGenerator = $facets_url_generator; + } + + /** +@@ -117,16 +141,48 @@ class DefaultFacetsSummaryManager { + '#theme' => 'facets_summary_item_list', + '#facet_summary_id' => $facets_summary->id(), + '#attributes' => [ +- 'data-drupal-facets-summary-id' => $facets_summary->id(), ++ 'class' => ['facet-summary__items'], + ], + ]; + + $results = []; + foreach ($facets as $facet) { +- $show_count = $facets_config[$facet->id()]['show_count']; +- $results = array_merge($results, $this->buildResultTree($show_count, $facet->getResults())); ++ $enabled_processors = $facet->getProcessors(TRUE); ++ if (isset($enabled_processors["range_slider"])) { ++ $active_values = $facet->getActiveItems(); ++ if ($active_values) { ++ $min = $active_values[0][0]; ++ $max = $active_values[0][1]; ++ $url_processor = $this->urlProcessorPluginManager->createInstance($facet->getFacetSourceConfig()->getUrlProcessorName(), ['facet' => $facet]); ++ $active_filters = $url_processor->getActiveFilters(); ++ if (isset($active_filters[''])) { ++ unset($active_filters['']); ++ } ++ unset($active_filters[$facet->id()]); ++ // Only if there are still active filters, use url generator. ++ if ($active_filters) { ++ $url = $this->facetsUrlGenerator->getUrl($active_filters, FALSE); ++ } ++ else { ++ // @todo Keep non-facet related get params. ++ $url = Url::fromUserInput($facets_summary->getFacetSource()->getPath()); ++ } ++ $result_without_current_rangeslider = new Result($facet, "(min:$min,max:$max)", " $min - $max", 1); ++ $result_without_current_rangeslider->setActiveState(TRUE); ++ $result_without_current_rangeslider->setUrl($url); ++ $results = array_merge($results, $this->buildResultTree(FALSE, [$result_without_current_rangeslider])); ++ } ++ } ++ else { ++ $show_count = $facets_config[$facet->id()]['show_count']; ++ $results = array_merge($results, $this->buildResultTree($show_count, $facet->getResults())); ++ } ++ ++ } ++ ++ if ($results) { ++ $build['#items'] = $results; + } +- $build['#items'] = $results; + + // Allow our Facets Summary processors to alter the build array in a + // configured order. +diff --git a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php +index 5b9255a..4ace992 100644 +--- a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php ++++ b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php +@@ -6,6 +6,7 @@ use Drupal\Core\Block\BlockBase; + use Drupal\Core\Cache\UncacheableDependencyTrait; + use Drupal\Core\Plugin\ContainerFactoryPluginInterface; + use Drupal\Core\Url; ++use Drupal\Component\Utility\Html; + use Drupal\facets_summary\Entity\FacetsSummary; + use Drupal\facets_summary\FacetsSummaryBlockInterface; + use Drupal\facets_summary\FacetsSummaryManager\DefaultFacetsSummaryManager; +@@ -88,25 +89,46 @@ class FacetsSummaryBlock extends BlockBase implements FacetsSummaryBlockInterfac + $facets_summary = $this->getEntity(); + + // Let the facet_manager build the facets. +- $build = $this->facetsSummaryManager->build($facets_summary); ++ $build = []; + +- // Add contextual links only when we have results. +- if (!empty($build)) { +- $build['#contextual_links']['facets_summary'] = [ +- 'route_parameters' => ['facets_summary' => $facets_summary->id()], ++ // Let the facet_manager build the facets. ++ $summary_build = $this->facetsSummaryManager->build($facets_summary); ++ ++ if ($summary_build) { ++ $build = [ ++ 'facets_summary' => [ ++ '#type' => 'container', ++ '#contextual_links' => [ ++ 'facets_summary' => [ ++ 'route_parameters' => ['facets_summary' => $facets_summary->id()], ++ ], ++ ], ++ '#attributes' => [ ++ 'data-drupal-facets-summary-id' => $facets_summary->id(), ++ 'data-drupal-facets-summary-plugin-id' => $this->getPluginId(), ++ 'id' => Html::getUniqueId(str_replace(':', '-', $this->getPluginId())), ++ 'class' => [ ++ 'facets-summary-block__wrapper', ++ ], ++ ], ++ 'summary_build' => $summary_build, ++ ], + ]; +- } + +- /** @var \Drupal\views\ViewExecutable $view */ +- if ($view = $facets_summary->getFacetSource()->getViewsDisplay()) { +- $build['#attached']['drupalSettings']['facets_views_ajax'] = [ +- 'facets_summary_ajax' => [ ++ // Hidden empty result. ++ if (!isset($summary_build['#items']) && !isset($summary_build['#message'])) { ++ $build['facets_summary']['#attributes']['class'][] = 'hidden'; ++ } ++ ++ /** @var \Drupal\views\ViewExecutable $view */ ++ if ($view = $facets_summary->getFacetSource()->getViewsDisplay()) { ++ $build['#attached']['drupalSettings']['facets_views_ajax']['facets_summary_ajax_' . $facets_summary->id()] = [ + 'facets_summary_id' => $facets_summary->id(), + 'view_id' => $view->id(), + 'current_display_id' => $view->current_display, + 'ajax_path' => Url::fromRoute('views.ajax')->toString(), +- ], +- ]; ++ ]; ++ } + } + + return $build; +diff --git a/src/Controller/FacetBlockAjaxController.php b/src/Controller/FacetBlockAjaxController.php +index d1d446d..9b2cf1a 100644 +--- a/src/Controller/FacetBlockAjaxController.php ++++ b/src/Controller/FacetBlockAjaxController.php +@@ -5,10 +5,10 @@ namespace Drupal\facets\Controller; + use Drupal\Core\Ajax\AjaxResponse; + use Drupal\Core\Ajax\InvokeCommand; + use Drupal\Core\Ajax\ReplaceCommand; ++use Drupal\Core\Block\BlockManager; + use Drupal\Core\Controller\ControllerBase; + use Drupal\Core\Path\CurrentPathStack; + use Drupal\Core\PathProcessor\PathProcessorManager; +-use Drupal\Core\Render\RendererInterface; + use Drupal\Core\Routing\CurrentRouteMatch; + use Symfony\Component\DependencyInjection\ContainerInterface; + use Symfony\Component\HttpFoundation\Request; +@@ -28,13 +28,6 @@ class FacetBlockAjaxController extends ControllerBase { + */ + protected $storage; + +- /** +- * The renderer. +- * +- * @var \Drupal\Core\Render\RendererInterface +- */ +- protected $renderer; +- + /** + * The current path. + * +@@ -63,11 +56,16 @@ class FacetBlockAjaxController extends ControllerBase { + */ + protected $currentRouteMatch; + ++ /** ++ * The block manager service. ++ * ++ * @var \Drupal\Core\Block\BlockManager ++ */ ++ protected $blockManager; ++ + /** + * Constructs a FacetBlockAjaxController object. + * +- * @param \Drupal\Core\Render\RendererInterface $renderer +- * The renderer service. + * @param \Drupal\Core\Path\CurrentPathStack $currentPath + * The current path service. + * @param \Symfony\Component\Routing\RouterInterface $router +@@ -76,14 +74,16 @@ class FacetBlockAjaxController extends ControllerBase { + * The path processor manager. + * @param \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatch + * The current route match service. ++ * @param \Drupal\Core\Block\BlockManager $blockManager ++ * The block manager service. + */ +- public function __construct(RendererInterface $renderer, CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor, CurrentRouteMatch $currentRouteMatch) { ++ public function __construct(CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor, CurrentRouteMatch $currentRouteMatch, BlockManager $blockManager) { + $this->storage = $this->entityTypeManager()->getStorage('block'); +- $this->renderer = $renderer; + $this->currentPath = $currentPath; + $this->router = $router; + $this->pathProcessor = $pathProcessor; + $this->currentRouteMatch = $currentRouteMatch; ++ $this->blockManager = $blockManager; + } + + /** +@@ -91,11 +91,11 @@ class FacetBlockAjaxController extends ControllerBase { + */ + public static function create(ContainerInterface $container) { + return new static( +- $container->get('renderer'), + $container->get('path.current'), + $container->get('router'), + $container->get('path_processor_manager'), +- $container->get('current_route_match') ++ $container->get('current_route_match'), ++ $container->get('plugin.manager.block') + ); + } + +@@ -122,9 +122,6 @@ class FacetBlockAjaxController extends ControllerBase { + throw new NotFoundHttpException('No facet link or facet blocks found.'); + } + +- // Make sure we are not updating blocks multiple times. +- $facets_blocks = array_unique($facets_blocks); +- + $new_request = Request::create($path); + $request_stack = new RequestStack(); + $processed = $this->pathProcessor->processInbound($path, $new_request); +@@ -136,40 +133,56 @@ class FacetBlockAjaxController extends ControllerBase { + + $container = \Drupal::getContainer(); + $container->set('request_stack', $request_stack); +- $active_facet = $request->request->get('active_facet'); +- +- // Build the facets blocks found for the current request and update. +- foreach ($facets_blocks as $block_id => $block_selector) { +- $block_entity = $this->storage->load($block_id); +- +- if ($block_entity) { +- // Render a block, then add it to the response as a replace command. +- $block_view = $this->entityTypeManager +- ->getViewBuilder('block') +- ->view($block_entity); +- +- $block_view = (string) $this->renderer->renderPlain($block_view); +- $response->addCommand(new ReplaceCommand($block_selector, $block_view)); ++ foreach ($facets_blocks as $block_selector => $block_id) { ++ // Facet block render array. ++ $block_view = NULL; ++ // Re prepare from css standarts. ++ $block_id = str_replace(['--', '-'], [':', '_'], $block_id); ++ ++ // @todo We should not create an instance if we have already created one. ++ $block_instance = $this->blockManager->createInstance($block_id); ++ if ($block_instance) { ++ $block_view = $block_instance->build(); ++ if ($block_view) { ++ // Replace content current ID selector. ++ $response->addCommand(new ReplaceCommand('#' . $block_selector, $block_view)); ++ // Hide or show block. ++ $facet_id = explode(':', $block_id)[1]; ++ $hide_show_selector = '[data-drupal-block-facet-id = "' . $facet_id . '"]'; ++ if (!empty($block_view['facet_block']['#attributes']['class']) && in_array('hidden', $block_view['facet_block']['#attributes']['class'])) { ++ $response->addCommand(new InvokeCommand($hide_show_selector, 'addClass', ['hidden'])); ++ } ++ else { ++ $response->addCommand(new InvokeCommand($hide_show_selector, 'removeClass', ['hidden'])); ++ } ++ } + } + } + +- $response->addCommand(new InvokeCommand('[data-block-plugin-id="' . $active_facet . '"]', 'addClass', ['facet-active'])); +- + // Update filter summary block. + $update_summary_block = $request->request->get('update_summary_block'); + if ($update_summary_block) { +- $facet_summary_block_id = $request->request->get('facet_summary_block_id'); +- $facet_summary_wrapper_id = $request->request->get('facet_summary_wrapper_id'); +- $facet_summary_block_id = str_replace('-', '_', $facet_summary_block_id); +- +- if ($facet_summary_block_id) { +- $block_entity = $this->storage->load($facet_summary_block_id); +- $block_view = $this->entityTypeManager +- ->getViewBuilder('block') +- ->view($block_entity); +- $block_view = (string) $this->renderer->renderPlain($block_view); +- +- $response->addCommand(new ReplaceCommand('[data-drupal-facets-summary-id=' . $facet_summary_wrapper_id . ']', $block_view)); ++ $facet_summary_plugin_ids = $request->request->get('facet_summary_plugin_ids'); ++ foreach ($facet_summary_plugin_ids as $block_selector => $summary_plugin_id) { ++ // Facet summary block render array. ++ $block_view = NULL; ++ // @todo We should not create an instance if we have already created one. ++ $block_instance = $this->blockManager->createInstance($summary_plugin_id); ++ if ($block_instance) { ++ $block_view = $block_instance->build(); ++ if ($block_view) { ++ // Replace content facets summary plugin ID selector. ++ $response->addCommand(new ReplaceCommand('[data-drupal-facets-summary-plugin-id = "' . $summary_plugin_id . '"]', $block_view)); ++ // Hide or show block. ++ $hide_show_selector = '[data-drupal-block-facet-summary-id = "' . $summary_plugin_id . '"]'; ++ if (!empty($block_view['facets_summary']['#attributes']['class']) && in_array('hidden', $block_view['facets_summary']['#attributes']['class'])) { ++ $response->addCommand(new InvokeCommand($hide_show_selector, 'addClass', ['hidden'])); ++ } ++ else { ++ $response->addCommand(new InvokeCommand($hide_show_selector, 'removeClass', ['hidden'])); ++ } ++ } ++ } + } + } + +diff --git a/src/Plugin/Block/FacetBlock.php b/src/Plugin/Block/FacetBlock.php +index 676c573..509552f 100644 +--- a/src/Plugin/Block/FacetBlock.php ++++ b/src/Plugin/Block/FacetBlock.php +@@ -3,6 +3,7 @@ + namespace Drupal\facets\Plugin\Block; + + use Drupal\Core\Block\BlockBase; ++use Drupal\Component\Utility\Html; + use Drupal\Core\Entity\EntityStorageInterface; + use Drupal\Core\Plugin\ContainerFactoryPluginInterface; + use Drupal\Core\Form\FormStateInterface; +@@ -79,39 +80,60 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface { + return []; + } + ++ $build = []; ++ + // Let the facet_manager build the facets. +- $build = $this->facetManager->build($facet); ++ $facet_build = $this->facetManager->build($facet); + +- if (!empty($build)) { ++ if ($facet_build) { + // Add extra elements from facet source, for example, ajax scripts. + // @see Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay +- /* @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */ ++ /** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */ + $facet_source = $facet->getFacetSource(); +- $build += $facet_source->buildFacet(); +- +- // Add contextual links only when we have results. +- $build['#contextual_links']['facets_facet'] = [ +- 'route_parameters' => ['facets_facet' => $facet->id()], ++ $facet_build += $facet_source->buildFacet(); ++ ++ $build = [ ++ '#type' => 'container', ++ '#contextual_links' => [ ++ 'facets_facet' => [ ++ 'route_parameters' => ['facets_facet' => $facet->id()], ++ ], ++ ], ++ '#attributes' => [ ++ 'class' => ['block-facet__wrapper'], ++ ], ++ $facet_build, + ]; + +- if (!empty($build[0]['#attributes']['class']) && in_array('facet-active', $build[0]['#attributes']['class'], TRUE)) { +- $build['#attributes']['class'][] = 'facet-active'; +- } +- else { +- $build['#attributes']['class'][] = 'facet-inactive'; ++ // Add css classes. ++ if (!empty($facet_build[0]['#attributes']['class'])) { ++ $css_classes = $facet_build[0]['#attributes']['class']; ++ // Active/inactive css classes. ++ if (in_array('facet-active', $css_classes)) { ++ $build['#attributes']['class'][] = 'facet-active'; ++ } ++ else { ++ $build['#attributes']['class'][] = 'facet-inactive'; ++ } ++ // Whether it is necessary to add hide css class. ++ if (in_array('facet-hidden', $css_classes)) { ++ $build['#attributes']['class'][] = 'hidden'; ++ } + } + + // Add classes needed for ajax. +- if (!empty($build['#use_ajax'])) { ++ if (!empty($facet_build['#use_ajax'])) { + $build['#attributes']['class'][] = 'block-facets-ajax'; +- // The configuration block id isn't always set in the configuration. +- if (isset($this->configuration['block_id'])) { +- $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->configuration['block_id']; +- } +- else { +- $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->pluginId; +- } ++ $block_id = str_replace(':', '--', $this->pluginId); ++ $block_id = Html::cleanCssIdentifier($block_id); ++ $build['#attributes']['class'][] = 'js-facet-block-id-' . $block_id; ++ $build['#attributes']['id'] = Html::getUniqueId($block_id); + } ++ ++ // To render correctly in different situations. ++ $build = [ ++ 'facet_block' => $build, ++ ]; + } + + return $build; diff --git a/facets.module b/facets.module index 6c04f21a..7b43909e 100644 --- a/facets.module +++ b/facets.module @@ -124,20 +124,27 @@ function facets_entity_presave(EntityInterface $entity) { /** * Implements hook_preprocess_block(). * - * Adds a class for the widget to the facet block to allow for more specific - * styling. + * Adds some classes for hiding empty blocks, working ajax, and more. */ function facets_preprocess_block(&$variables) { if ($variables['configuration']['provider'] == 'facets') { // Hide the block if it's empty. - if (!empty($variables['elements']['content'][0]['#attributes']['class']) && in_array('facet-hidden', $variables['elements']['content'][0]['#attributes']['class'])) { - // Add the Drupal class for hiding this for everyone, including screen - // readers. See hidden.module.css in the core system module. - $variables['attributes']['class'][] = 'hidden'; + if (!empty($variables['content']['facet_block']['#attributes'])) { + $attributes = $variables['content']['facet_block']['#attributes']; + if (isset($attributes['class'])) { + if (in_array('hidden', $attributes['class'])) { + // Add the Drupal class for hiding this for everyone, including screen + // readers. See hidden.module.css in the core system module. + $variables['attributes']['class'][] = 'hidden'; + } + } } + if (!empty($variables['derivative_plugin_id'])) { $facet = Facet::load($variables['derivative_plugin_id']); $variables['attributes']['class'][] = 'block-facet--' . Html::cleanCssIdentifier($facet->getWidget()['type']); + // For use ajax. + $variables['attributes']['data-drupal-block-facet-id'] = $facet->id(); } } } diff --git a/js/facets-views-ajax.js b/js/facets-views-ajax.js index e0e4f2ce..0af8c8e9 100644 --- a/js/facets-views-ajax.js +++ b/js/facets-views-ajax.js @@ -37,17 +37,72 @@ return; } - // Update view on summary block click. - if (updateFacetsSummaryBlock() && (facetId === 'facets_summary_ajax')) { - $('[data-drupal-facets-summary-id=' + facetSettings.facets_summary_id + ']').children('ul').children('li').once().click(function (e) { - e.preventDefault(); - var facetLink = $(this).find('a'); - updateFacetsView(facetLink.attr('href'), current_dom_id, view_path); - }); + // Update view on range slider stop event + if (typeof settings.facets !== "undefined" && settings.facets.sliders && settings.facets.sliders[facetId]) { + settings.facets.sliders[facetId].stop = function (e, ui) { + var href = settings.facets.sliders[facetId].url.replace('__range_slider_min__', ui.values[0]).replace('__range_slider_max__', ui.values[1]); + + // Update facet query params on the request. + var currentHref = window.location.href; + var currentQueryParams = Drupal.Views.parseQueryString(currentHref); + var newQueryParams = Drupal.Views.parseQueryString(href); + + var queryParams = {}; + var facetPositions = []; + var fCount = 0; + var value = ''; + var facetKey = ''; + for (var paramName in currentQueryParams) { + if (paramName.substr(0, 1) === 'f') { + value = currentQueryParams[paramName]; + // Store the facet position so we can override it later. + facetKey = value.substr(0, value.indexOf(':')); + facetPositions[facetKey] = fCount; + queryParams['f[' + fCount + ']'] = value; + fCount++; + } + else { + queryParams[paramName] = currentQueryParams[paramName]; + } + } + + var paramKey = ''; + for (let paramName in newQueryParams) { + if (paramName.substr(0, 1) === 'f') { + value = newQueryParams[paramName]; + // replace + facetKey = value.substr(0, value.indexOf(':')); + if (typeof facetPositions[facetKey] !== 'undefined') { + paramKey = 'f[' + facetPositions[facetKey] + ']'; + } + else { + paramKey = 'f[' + fCount + ']'; + fCount++; + } + queryParams[paramKey] = newQueryParams[paramName]; + } + else { + queryParams[paramName] = newQueryParams[paramName]; + } + } + + href = '/' + Drupal.Views.getPath(href) + '?' + $.param(queryParams); + + updateFacetsView(href, current_dom_id, view_path); + }; + } + else if (facetId == 'facets_summary_ajax_summary' || facetId == 'facets_summary_ajax_summary_count') { + if (updateFacetsSummaryBlock()) { + $('[data-drupal-facets-summary-id=' + facetSettings.facets_summary_id + ']').children('ul').children('li').once().click(function (e) { + e.preventDefault(); + var facetLink = $(this).find('a'); + updateFacetsView(facetLink.attr('href'), current_dom_id, view_path); + }); + } } // Update view on facet item click. else { - $('[data-drupal-facet-id=' + facetId + ']').each(function (index, facet_item) { + $('[data-drupal-facet-id |= ' + facetId + ']').each(function (index, facet_item) { if ($(facet_item).hasClass('js-facets-widget')) { $(facet_item).unbind('facets_filter.facets'); $(facet_item).on('facets_filter.facets', function (event, url) { @@ -104,8 +159,8 @@ var facets_blocks = facetsBlocks(); // Remove All Range Input Form Facet Blocks from being updated. - if(settings.facets && settings.facets.rangeInput) { - $.each(settings.facets.rangeInput, function (index, value){ + if (settings.facets && settings.facets.rangeInput) { + $.each(settings.facets.rangeInput, function (index, value) { delete facets_blocks[value.facetId]; }); } @@ -121,17 +176,17 @@ // Update facets summary block. if (updateFacetsSummaryBlock()) { - var facet_summary_wrapper_id = $('[data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax.facets_summary_id + ']').attr('id'); - var facet_summary_block_id = ''; - if (facet_summary_wrapper_id.indexOf('--') !== -1) { - facet_summary_block_id = facet_summary_wrapper_id.substring(0, facet_summary_wrapper_id.indexOf('--')).replace('block-', ''); - } - else { - facet_summary_block_id = facet_summary_wrapper_id.replace('block-', ''); - } facet_settings.submit.update_summary_block = true; - facet_settings.submit.facet_summary_block_id = facet_summary_block_id; - facet_settings.submit.facet_summary_wrapper_id = settings.facets_views_ajax.facets_summary_ajax.facets_summary_id; + facet_settings.submit.facet_summary_plugin_ids = {}; + let summary_selector = '[data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax_summary.facets_summary_id + ']'; + if (settings.facets_views_ajax.facets_summary_ajax_summary_count !== undefined) { + summary_selector += ', [data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax_summary_count.facets_summary_id + ']'; + } + $(summary_selector).each(function (index, summaryWrapper) { + let summaryPluginId = $(summaryWrapper).attr('data-drupal-facets-summary-plugin-id'); + let summaryPluginIdWrapper = $(summaryWrapper).attr('id'); + facet_settings.submit.facet_summary_plugin_ids[summaryPluginIdWrapper] = summaryPluginId; + }); } Drupal.ajax(facet_settings).execute(); @@ -143,7 +198,7 @@ var settings = drupalSettings; var update_summary = false; - if (settings.facets_views_ajax.facets_summary_ajax) { + if (settings.facets_views_ajax.facets_summary_ajax_summary || settings.facets_views_ajax.facets_summary_ajax_summary_count) { update_summary = true; } @@ -162,8 +217,8 @@ return v.slice(block_id_start.length, v.length); } }).join(); - var block_selector = '#' + $(this).attr('id'); - facets_blocks[block_id] = block_selector; + var block_selector = $(this).attr('id'); + facets_blocks[block_selector] = block_id; }); return facets_blocks; @@ -204,12 +259,12 @@ } // Helper function to add exposed form data to facets url - var addExposedFiltersToFacetsUrl = function(href, view_name, view_display_id) { + var addExposedFiltersToFacetsUrl = function (href, view_name, view_display_id) { var $exposed_form = $('form#views-exposed-form-' + view_name.replace(/_/g, '-') + '-' + view_display_id.replace(/_/g, '-')); var params = Drupal.Views.parseQueryString(href); - $.each($exposed_form.serializeArray(), function() { + $.each($exposed_form.serializeArray(), function () { params[this.name] = this.value; }); diff --git a/modules/facets_summary/facets_summary.module b/modules/facets_summary/facets_summary.module index 6720862c..af2dc3f6 100644 --- a/modules/facets_summary/facets_summary.module +++ b/modules/facets_summary/facets_summary.module @@ -98,3 +98,26 @@ function facets_summary_theme_suggestions_facets_summary_item_list(array $variab function facets_summary_preprocess_facets_summary_item_list(array &$variables) { template_preprocess_item_list($variables); } + +/** + * Implements hook_preprocess_block(). + * + * Adds some classes for hiding empty blocks, working ajax, and more. + */ +function facets_summary_preprocess_block(&$variables) { + if ($variables['configuration']['provider'] == 'facets_summary') { + // Hide the block if it's empty. + if (!empty($variables['content']['facets_summary']['#attributes'])) { + $attributes = $variables['content']['facets_summary']['#attributes']; + if (isset($attributes['class'])) { + if (in_array('hidden', $attributes['class'])) { + // Add the Drupal class for hiding this for everyone, including screen + // readers. See hidden.module.css in the core system module. + $variables['attributes']['class'][] = 'hidden'; + } + } + } + // For use ajax. + $variables['attributes']['data-drupal-block-facet-summary-id'] = $variables['configuration']['id']; + } +} diff --git a/modules/facets_summary/facets_summary.services.yml b/modules/facets_summary/facets_summary.services.yml index ba1a6f5f..bd8f14ed 100644 --- a/modules/facets_summary/facets_summary.services.yml +++ b/modules/facets_summary/facets_summary.services.yml @@ -8,6 +8,8 @@ services: - '@plugin.manager.facets.facet_source' - '@plugin.manager.facets_summary.processor' - '@facets.manager' + - '@plugin.manager.facets.url_processor' + - '@facets.utility.url_generator' facets_summary.search_api_subscriber: class: Drupal\facets_summary\EventSubscriber\SearchApiSubscriber arguments: ['@entity_type.manager'] diff --git a/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php b/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php index 105c4380..0adafc09 100644 --- a/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php +++ b/modules/facets_summary/src/FacetsSummaryManager/DefaultFacetsSummaryManager.php @@ -4,13 +4,17 @@ namespace Drupal\facets_summary\FacetsSummaryManager; use Drupal\Core\Link; use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Url; use Drupal\facets\Exception\InvalidProcessorException; use Drupal\facets\FacetManager\DefaultFacetManager; use Drupal\facets\FacetSource\FacetSourcePluginManager; +use Drupal\facets\Result\Result; use Drupal\facets_summary\Processor\BuildProcessorInterface; use Drupal\facets_summary\Processor\ProcessorInterface; use Drupal\facets_summary\Processor\ProcessorPluginManager; use Drupal\facets_summary\FacetsSummaryInterface; +use Drupal\facets\UrlProcessor\UrlProcessorPluginManager; +use Drupal\facets\Utility\FacetsUrlGenerator; /** * The facet summary manager. @@ -45,6 +49,20 @@ class DefaultFacetsSummaryManager { */ protected $facetManager; + /** + * The url processor plugin manager. + * + * @var \Drupal\facets\UrlProcessor\UrlProcessorPluginManager + */ + protected $urlProcessorPluginManager; + + /** + * The facets url generator. + * + * @var \Drupal\facets\Utility\FacetsUrlGenerator + */ + protected $facetsUrlGenerator; + /** * Constructs a new instance of the DefaultFacetManager. * @@ -54,11 +72,17 @@ class DefaultFacetsSummaryManager { * The facets summary processor plugin manager. * @param \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager * The facet manager service. + * @param \Drupal\facets\UrlProcessor\UrlProcessorPluginManager $url_processor_plugin_manager + * The url processor plugin manager. + * @param \Drupal\facets\Utility\FacetsUrlGenerator $facets_url_generator + * The facets url generator. */ - public function __construct(FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, DefaultFacetManager $facet_manager) { + public function __construct(FacetSourcePluginManager $facet_source_manager, ProcessorPluginManager $processor_plugin_manager, DefaultFacetManager $facet_manager, UrlProcessorPluginManager $url_processor_plugin_manager, FacetsUrlGenerator $facets_url_generator) { $this->facetSourcePluginManager = $facet_source_manager; $this->processorPluginManager = $processor_plugin_manager; $this->facetManager = $facet_manager; + $this->urlProcessorPluginManager = $url_processor_plugin_manager; + $this->facetsUrlGenerator = $facets_url_generator; } /** @@ -117,16 +141,48 @@ class DefaultFacetsSummaryManager { '#theme' => 'facets_summary_item_list', '#facet_summary_id' => $facets_summary->id(), '#attributes' => [ - 'data-drupal-facets-summary-id' => $facets_summary->id(), + 'class' => ['facet-summary__items'], ], ]; $results = []; foreach ($facets as $facet) { - $show_count = $facets_config[$facet->id()]['show_count']; - $results = array_merge($results, $this->buildResultTree($show_count, $facet->getResults())); + $enabled_processors = $facet->getProcessors(TRUE); + if (isset($enabled_processors["range_slider"])) { + $active_values = $facet->getActiveItems(); + if ($active_values) { + $min = $active_values[0][0]; + $max = $active_values[0][1]; + $url_processor = $this->urlProcessorPluginManager->createInstance($facet->getFacetSourceConfig()->getUrlProcessorName(), ['facet' => $facet]); + $active_filters = $url_processor->getActiveFilters(); + if (isset($active_filters[''])) { + unset($active_filters['']); + } + unset($active_filters[$facet->id()]); + // Only if there are still active filters, use url generator. + if ($active_filters) { + $url = $this->facetsUrlGenerator->getUrl($active_filters, FALSE); + } + else { + // @todo Keep non-facet related get params. + $url = Url::fromUserInput($facets_summary->getFacetSource()->getPath()); + } + $result_without_current_rangeslider = new Result($facet, "(min:$min,max:$max)", " $min - $max", 1); + $result_without_current_rangeslider->setActiveState(TRUE); + $result_without_current_rangeslider->setUrl($url); + $results = array_merge($results, $this->buildResultTree(FALSE, [$result_without_current_rangeslider])); + } + } + else { + $show_count = $facets_config[$facet->id()]['show_count']; + $results = array_merge($results, $this->buildResultTree($show_count, $facet->getResults())); + } + + } + + if ($results) { + $build['#items'] = $results; } - $build['#items'] = $results; // Allow our Facets Summary processors to alter the build array in a // configured order. diff --git a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php index 5b9255af..4ace9922 100644 --- a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php +++ b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php @@ -6,6 +6,7 @@ use Drupal\Core\Block\BlockBase; use Drupal\Core\Cache\UncacheableDependencyTrait; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Url; +use Drupal\Component\Utility\Html; use Drupal\facets_summary\Entity\FacetsSummary; use Drupal\facets_summary\FacetsSummaryBlockInterface; use Drupal\facets_summary\FacetsSummaryManager\DefaultFacetsSummaryManager; @@ -88,25 +89,46 @@ class FacetsSummaryBlock extends BlockBase implements FacetsSummaryBlockInterfac $facets_summary = $this->getEntity(); // Let the facet_manager build the facets. - $build = $this->facetsSummaryManager->build($facets_summary); + $build = []; - // Add contextual links only when we have results. - if (!empty($build)) { - $build['#contextual_links']['facets_summary'] = [ - 'route_parameters' => ['facets_summary' => $facets_summary->id()], + // Let the facet_manager build the facets. + $summary_build = $this->facetsSummaryManager->build($facets_summary); + + if ($summary_build) { + $build = [ + 'facets_summary' => [ + '#type' => 'container', + '#contextual_links' => [ + 'facets_summary' => [ + 'route_parameters' => ['facets_summary' => $facets_summary->id()], + ], + ], + '#attributes' => [ + 'data-drupal-facets-summary-id' => $facets_summary->id(), + 'data-drupal-facets-summary-plugin-id' => $this->getPluginId(), + 'id' => Html::getUniqueId(str_replace(':', '-', $this->getPluginId())), + 'class' => [ + 'facets-summary-block__wrapper', + ], + ], + 'summary_build' => $summary_build, + ], ]; - } - /** @var \Drupal\views\ViewExecutable $view */ - if ($view = $facets_summary->getFacetSource()->getViewsDisplay()) { - $build['#attached']['drupalSettings']['facets_views_ajax'] = [ - 'facets_summary_ajax' => [ + // Hidden empty result. + if (!isset($summary_build['#items']) && !isset($summary_build['#message'])) { + $build['facets_summary']['#attributes']['class'][] = 'hidden'; + } + + /** @var \Drupal\views\ViewExecutable $view */ + if ($view = $facets_summary->getFacetSource()->getViewsDisplay()) { + $build['#attached']['drupalSettings']['facets_views_ajax']['facets_summary_ajax_' . $facets_summary->id()] = [ 'facets_summary_id' => $facets_summary->id(), 'view_id' => $view->id(), 'current_display_id' => $view->current_display, 'ajax_path' => Url::fromRoute('views.ajax')->toString(), - ], - ]; + ]; + } } return $build; diff --git a/src/Controller/FacetBlockAjaxController.php b/src/Controller/FacetBlockAjaxController.php index d1d446d1..9b2cf1ad 100644 --- a/src/Controller/FacetBlockAjaxController.php +++ b/src/Controller/FacetBlockAjaxController.php @@ -5,10 +5,10 @@ namespace Drupal\facets\Controller; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\InvokeCommand; use Drupal\Core\Ajax\ReplaceCommand; +use Drupal\Core\Block\BlockManager; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\PathProcessor\PathProcessorManager; -use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\CurrentRouteMatch; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -28,13 +28,6 @@ class FacetBlockAjaxController extends ControllerBase { */ protected $storage; - /** - * The renderer. - * - * @var \Drupal\Core\Render\RendererInterface - */ - protected $renderer; - /** * The current path. * @@ -63,11 +56,16 @@ class FacetBlockAjaxController extends ControllerBase { */ protected $currentRouteMatch; + /** + * The block manager service. + * + * @var \Drupal\Core\Block\BlockManager + */ + protected $blockManager; + /** * Constructs a FacetBlockAjaxController object. * - * @param \Drupal\Core\Render\RendererInterface $renderer - * The renderer service. * @param \Drupal\Core\Path\CurrentPathStack $currentPath * The current path service. * @param \Symfony\Component\Routing\RouterInterface $router @@ -76,14 +74,16 @@ class FacetBlockAjaxController extends ControllerBase { * The path processor manager. * @param \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatch * The current route match service. + * @param \Drupal\Core\Block\BlockManager $blockManager + * The block manager service. */ - public function __construct(RendererInterface $renderer, CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor, CurrentRouteMatch $currentRouteMatch) { + public function __construct(CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor, CurrentRouteMatch $currentRouteMatch, BlockManager $blockManager) { $this->storage = $this->entityTypeManager()->getStorage('block'); - $this->renderer = $renderer; $this->currentPath = $currentPath; $this->router = $router; $this->pathProcessor = $pathProcessor; $this->currentRouteMatch = $currentRouteMatch; + $this->blockManager = $blockManager; } /** @@ -91,11 +91,11 @@ class FacetBlockAjaxController extends ControllerBase { */ public static function create(ContainerInterface $container) { return new static( - $container->get('renderer'), $container->get('path.current'), $container->get('router'), $container->get('path_processor_manager'), - $container->get('current_route_match') + $container->get('current_route_match'), + $container->get('plugin.manager.block') ); } @@ -122,9 +122,6 @@ class FacetBlockAjaxController extends ControllerBase { throw new NotFoundHttpException('No facet link or facet blocks found.'); } - // Make sure we are not updating blocks multiple times. - $facets_blocks = array_unique($facets_blocks); - $new_request = Request::create($path); $request_stack = new RequestStack(); $processed = $this->pathProcessor->processInbound($path, $new_request); @@ -136,40 +133,56 @@ class FacetBlockAjaxController extends ControllerBase { $container = \Drupal::getContainer(); $container->set('request_stack', $request_stack); - $active_facet = $request->request->get('active_facet'); - - // Build the facets blocks found for the current request and update. - foreach ($facets_blocks as $block_id => $block_selector) { - $block_entity = $this->storage->load($block_id); - - if ($block_entity) { - // Render a block, then add it to the response as a replace command. - $block_view = $this->entityTypeManager - ->getViewBuilder('block') - ->view($block_entity); - - $block_view = (string) $this->renderer->renderPlain($block_view); - $response->addCommand(new ReplaceCommand($block_selector, $block_view)); + foreach ($facets_blocks as $block_selector => $block_id) { + // Facet block render array. + $block_view = NULL; + // Re prepare from css standarts. + $block_id = str_replace(['--', '-'], [':', '_'], $block_id); + + // @todo We should not create an instance if we have already created one. + $block_instance = $this->blockManager->createInstance($block_id); + if ($block_instance) { + $block_view = $block_instance->build(); + if ($block_view) { + // Replace content current ID selector. + $response->addCommand(new ReplaceCommand('#' . $block_selector, $block_view)); + // Hide or show block. + $facet_id = explode(':', $block_id)[1]; + $hide_show_selector = '[data-drupal-block-facet-id = "' . $facet_id . '"]'; + if (!empty($block_view['facet_block']['#attributes']['class']) && in_array('hidden', $block_view['facet_block']['#attributes']['class'])) { + $response->addCommand(new InvokeCommand($hide_show_selector, 'addClass', ['hidden'])); + } + else { + $response->addCommand(new InvokeCommand($hide_show_selector, 'removeClass', ['hidden'])); + } + } } } - $response->addCommand(new InvokeCommand('[data-block-plugin-id="' . $active_facet . '"]', 'addClass', ['facet-active'])); - // Update filter summary block. $update_summary_block = $request->request->get('update_summary_block'); if ($update_summary_block) { - $facet_summary_block_id = $request->request->get('facet_summary_block_id'); - $facet_summary_wrapper_id = $request->request->get('facet_summary_wrapper_id'); - $facet_summary_block_id = str_replace('-', '_', $facet_summary_block_id); - - if ($facet_summary_block_id) { - $block_entity = $this->storage->load($facet_summary_block_id); - $block_view = $this->entityTypeManager - ->getViewBuilder('block') - ->view($block_entity); - $block_view = (string) $this->renderer->renderPlain($block_view); - - $response->addCommand(new ReplaceCommand('[data-drupal-facets-summary-id=' . $facet_summary_wrapper_id . ']', $block_view)); + $facet_summary_plugin_ids = $request->request->get('facet_summary_plugin_ids'); + foreach ($facet_summary_plugin_ids as $block_selector => $summary_plugin_id) { + // Facet summary block render array. + $block_view = NULL; + // @todo We should not create an instance if we have already created one. + $block_instance = $this->blockManager->createInstance($summary_plugin_id); + if ($block_instance) { + $block_view = $block_instance->build(); + if ($block_view) { + // Replace content facets summary plugin ID selector. + $response->addCommand(new ReplaceCommand('[data-drupal-facets-summary-plugin-id = "' . $summary_plugin_id . '"]', $block_view)); + // Hide or show block. + $hide_show_selector = '[data-drupal-block-facet-summary-id = "' . $summary_plugin_id . '"]'; + if (!empty($block_view['facets_summary']['#attributes']['class']) && in_array('hidden', $block_view['facets_summary']['#attributes']['class'])) { + $response->addCommand(new InvokeCommand($hide_show_selector, 'addClass', ['hidden'])); + } + else { + $response->addCommand(new InvokeCommand($hide_show_selector, 'removeClass', ['hidden'])); + } + } + } } } diff --git a/src/Plugin/Block/FacetBlock.php b/src/Plugin/Block/FacetBlock.php index 676c5731..509552fe 100644 --- a/src/Plugin/Block/FacetBlock.php +++ b/src/Plugin/Block/FacetBlock.php @@ -3,6 +3,7 @@ namespace Drupal\facets\Plugin\Block; use Drupal\Core\Block\BlockBase; +use Drupal\Component\Utility\Html; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Form\FormStateInterface; @@ -79,39 +80,60 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface { return []; } + $build = []; + // Let the facet_manager build the facets. - $build = $this->facetManager->build($facet); + $facet_build = $this->facetManager->build($facet); - if (!empty($build)) { + if ($facet_build) { // Add extra elements from facet source, for example, ajax scripts. // @see Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay - /* @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */ + /** @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */ $facet_source = $facet->getFacetSource(); - $build += $facet_source->buildFacet(); - - // Add contextual links only when we have results. - $build['#contextual_links']['facets_facet'] = [ - 'route_parameters' => ['facets_facet' => $facet->id()], + $facet_build += $facet_source->buildFacet(); + + $build = [ + '#type' => 'container', + '#contextual_links' => [ + 'facets_facet' => [ + 'route_parameters' => ['facets_facet' => $facet->id()], + ], + ], + '#attributes' => [ + 'class' => ['block-facet__wrapper'], + ], + $facet_build, ]; - if (!empty($build[0]['#attributes']['class']) && in_array('facet-active', $build[0]['#attributes']['class'], TRUE)) { - $build['#attributes']['class'][] = 'facet-active'; - } - else { - $build['#attributes']['class'][] = 'facet-inactive'; + // Add css classes. + if (!empty($facet_build[0]['#attributes']['class'])) { + $css_classes = $facet_build[0]['#attributes']['class']; + // Active/inactive css classes. + if (in_array('facet-active', $css_classes)) { + $build['#attributes']['class'][] = 'facet-active'; + } + else { + $build['#attributes']['class'][] = 'facet-inactive'; + } + // Whether it is necessary to add hide css class. + if (in_array('facet-hidden', $css_classes)) { + $build['#attributes']['class'][] = 'hidden'; + } } // Add classes needed for ajax. - if (!empty($build['#use_ajax'])) { + if (!empty($facet_build['#use_ajax'])) { $build['#attributes']['class'][] = 'block-facets-ajax'; - // The configuration block id isn't always set in the configuration. - if (isset($this->configuration['block_id'])) { - $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->configuration['block_id']; - } - else { - $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->pluginId; - } + $block_id = str_replace(':', '--', $this->pluginId); + $block_id = Html::cleanCssIdentifier($block_id); + $build['#attributes']['class'][] = 'js-facet-block-id-' . $block_id; + $build['#attributes']['id'] = Html::getUniqueId($block_id); } + + // To render correctly in different situations. + $build = [ + 'facet_block' => $build, + ]; } return $build; -- GitLab