diff --git a/facets.routing.yml b/facets.routing.yml index 9c08b77..9f26911 100644 --- a/facets.routing.yml +++ b/facets.routing.yml @@ -48,8 +48,9 @@ entity.facets_facet_source.edit_form: _title: 'Edit facet source configuration' requirements: _entity_create_access: 'facets_facet' + facets.block.ajax: - path: '/facets-block/ajax' + path: '/facets-block-ajax' defaults: _controller: '\Drupal\facets\Controller\FacetBlockAjaxController::ajaxFacetBlockView' requirements: diff --git a/js/dropdown-widget.js b/js/dropdown-widget.js index 50771d8..1e790c5 100644 --- a/js/dropdown-widget.js +++ b/js/dropdown-widget.js @@ -37,6 +37,7 @@ // Remove the class which we are using for .once(). $dropdown.removeClass('js-facets-dropdown-links'); + $dropdown.addClass('facets-dropdown'); $dropdown.addClass('js-facets-dropdown'); var id = $(this).data('drupal-facet-id'); diff --git a/js/facets-views-ajax.js b/js/facets-views-ajax.js index 3a12506..3f2fac5 100644 --- a/js/facets-views-ajax.js +++ b/js/facets-views-ajax.js @@ -1,17 +1,6 @@ /** * @file - * Facets Views AJAX handling. - */ - -/** - * @name FacetsViewsAjaxSettings - * @property {String} view_id - * @property {String} current_display_id - * @property {String} view_base_path - */ - -/** - * @property {FacetsViewsAjaxSettings[]} drupalSettings.facets_views_ajax + * Facets views AJAX handling. */ @@ -120,8 +109,8 @@ var facets_blocks = facetsBlocks(); // Update facet blocks. - var facet_settings = { - url: Drupal.url('facets-block/ajax'), + let facet_settings = { + url: Drupal.url('facets-block-ajax'), submit: { facet_link: href, facets_blocks: facets_blocks @@ -157,7 +146,7 @@ } return update_summary; - } + }; // Helper function, return facet blocks. var facetsBlocks = function () { @@ -176,7 +165,7 @@ }); return facets_blocks; - } + }; /** * Overrides beforeSend to trigger facetblocks update on exposed filter change. diff --git a/src/Controller/FacetBlockAjaxController.php b/src/Controller/FacetBlockAjaxController.php index e1c148c..72f1478 100644 --- a/src/Controller/FacetBlockAjaxController.php +++ b/src/Controller/FacetBlockAjaxController.php @@ -10,6 +10,7 @@ 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; use Symfony\Component\HttpFoundation\RequestStack; @@ -17,7 +18,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\RouterInterface; /** - * Defines a controller to load a view via AJAX. + * Defines a controller to load a facet via AJAX. */ class FacetBlockAjaxController extends ControllerBase { @@ -56,6 +57,13 @@ class FacetBlockAjaxController extends ControllerBase { */ protected $pathProcessor; + /** + * The current route match service. + * + * @var \Drupal\Core\Routing\CurrentRouteMatch + */ + protected $currentRouteMatch; + /** * Constructs a FacetBlockAjaxController object. * @@ -68,12 +76,13 @@ class FacetBlockAjaxController extends ControllerBase { * @param \Drupal\Core\PathProcessor\PathProcessorManager $pathProcessor * The path processor manager. */ - public function __construct(RendererInterface $renderer, CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor) { + public function __construct(RendererInterface $renderer, CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor, CurrentRouteMatch $currentRouteMatch) { $this->storage = $this->entityTypeManager()->getStorage('block'); $this->renderer = $renderer; $this->currentPath = $currentPath; $this->router = $router; $this->pathProcessor = $pathProcessor; + $this->currentRouteMatch = $currentRouteMatch; } /** @@ -84,7 +93,8 @@ class FacetBlockAjaxController extends ControllerBase { $container->get('renderer'), $container->get('path.current'), $container->get('router'), - $container->get('path_processor_manager') + $container->get('path_processor_manager'), + $container->get('current_route_match') ); } @@ -114,23 +124,18 @@ class FacetBlockAjaxController extends ControllerBase { throw new NotFoundHttpException('No facet link or facet blocks found.'); } - \Drupal::service('current_route_match')->resetRouteMatch(); + $this->currentRouteMatch->resetRouteMatch(); $new_request = Request::create($path); - $requestStack = new RequestStack(); + $request_stack = new RequestStack(); $processed = $this->pathProcessor->processInbound($path, $new_request); - // @todo When does this happen? - if (empty($processed)) { - throw new NotFoundHttpException(); - } - $this->currentPath->setPath($processed, $new_request); $request->attributes->add($this->router->matchRequest($new_request)); - $requestStack->push($new_request); + $request_stack->push($new_request); $container = \Drupal::getContainer(); - $container->set('request_stack', $requestStack); - $activeFacet = $request->request->get('active_facet'); + $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) { @@ -142,21 +147,15 @@ class FacetBlockAjaxController extends ControllerBase { ->getViewBuilder('block') ->view($block_entity); - /** @var \Drupal\Core\Render\Markup $block_view */ $block_view = (string) $this->renderer->renderPlain($block_view); // Make sure we retain the empty wrapper in case of empty facets to fill // them up in next request if they have data. - if ($block_view) { - $response->addCommand(new ReplaceCommand($block_selector, $block_view)); - } - else { - $response->addCommand(new HtmlCommand($block_selector, $block_view)); - } + $response->addCommand(new ReplaceCommand($block_selector, $block_view)); } } - $response->addCommand(new InvokeCommand('[data-block-plugin-id="' . $activeFacet . '"]', 'addClass', ['facet-active'])); + $response->addCommand(new InvokeCommand('[data-block-plugin-id="' . $active_facet . '"]', 'addClass', ['facet-active'])); $update_summary_block = $request->request->get('update_summary_block'); diff --git a/src/FacetManager/DefaultFacetManager.php b/src/FacetManager/DefaultFacetManager.php index 7e33a30..4ccacce 100644 --- a/src/FacetManager/DefaultFacetManager.php +++ b/src/FacetManager/DefaultFacetManager.php @@ -334,7 +334,19 @@ class DefaultFacetManager { ]; } else { - return []; + // 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 [ + [ + '#type' => 'container', + '#attributes' => [ + 'data-drupal-facet-id' => $facet->id(), + 'class' => 'facet-empty', + ], + ], + ]; } } diff --git a/src/Plugin/Block/FacetBlock.php b/src/Plugin/Block/FacetBlock.php index bb23da2..ac224cc 100644 --- a/src/Plugin/Block/FacetBlock.php +++ b/src/Plugin/Block/FacetBlock.php @@ -94,7 +94,7 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface { 'route_parameters' => ['facets_facet' => $facet->id()], ]; - if (!empty($build['#use_ajax']) && isset($facet_id)) { + if (!empty($build['#use_ajax'])) { $build['#attributes']['class'][] = 'block-facets-ajax'; $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->configuration['block_id']; } diff --git a/tests/src/FunctionalJavascript/AjaxBehaviorTest.php b/tests/src/FunctionalJavascript/AjaxBehaviorTest.php new file mode 100644 index 0000000..a4c8d71 --- /dev/null +++ b/tests/src/FunctionalJavascript/AjaxBehaviorTest.php @@ -0,0 +1,74 @@ +createFacet('owl'); + $this->createFacet('duck', 'keywords'); + + // Go to the views page. + $this->drupalGet('search-api-test-fulltext'); + + // Make sure the block is shown on the page. + $page = $this->getSession()->getPage(); + $block_owl = $page->findById('block-owl-block'); + $block_owl->isVisible(); + $block_duck = $page->findById('block-duck-block'); + $block_duck->isVisible(); + } + + /** + * Create a facet. + * + * @param string $id + * The id of the facet. + * @param string $field + * The field name. + */ + protected function createFacet($id, $field = 'type') { + $facet_storage = \Drupal::entityTypeManager()->getStorage('facets_facet'); + // Create and save a facet with a checkbox widget. + $facet_storage->create([ + 'id' => $id, + 'name' => strtoupper($id), + 'url_alias' => $id, + 'facet_source_id' => 'search_api:views_page__search_api_test_view__page_1', + 'field_identifier' => $field, + 'empty_behavior' => ['behavior' => 'none'], + 'weight' => 1, + 'widget' => [ + 'type' => 'links', + 'config' => [ + 'show_numbers' => TRUE, + 'soft_limit' => 0, + ], + ], + 'processor_configs' => [ + 'url_processor_handler' => [ + 'processor_id' => 'url_processor_handler', + 'weights' => ['pre_query' => -10, 'build' => -10], + 'settings' => [], + ], + ], + ])->save(); + $this->createBlock($id); + } + +} diff --git a/tests/src/FunctionalJavascript/JsBase.php b/tests/src/FunctionalJavascript/JsBase.php new file mode 100644 index 0000000..28b9448 --- /dev/null +++ b/tests/src/FunctionalJavascript/JsBase.php @@ -0,0 +1,130 @@ +drupalCreateUser([ + 'administer search_api', + 'administer facets', + 'access administration pages', + 'administer blocks', + ]); + $this->drupalLogin($admin_user); + + $this->insertExampleContent(); + } + + /** + * Setup and insert test content. + */ + protected function insertExampleContent() { + entity_test_create_bundle('item', NULL, 'entity_test_mulrev_changed'); + entity_test_create_bundle('article', NULL, 'entity_test_mulrev_changed'); + + $entity_test_storage = \Drupal::entityTypeManager() + ->getStorage('entity_test_mulrev_changed'); + $entity_1 = $entity_test_storage->create([ + 'name' => 'foo bar baz', + 'body' => 'test test', + 'type' => 'item', + 'keywords' => ['orange'], + 'category' => 'item_category', + ]); + $entity_1->save(); + $entity_2 = $entity_test_storage->create([ + 'name' => 'foo test', + 'body' => 'bar test', + 'type' => 'item', + 'keywords' => ['orange', 'apple', 'grape'], + 'category' => 'item_category', + ]); + $entity_2->save(); + $entity_3 = $entity_test_storage->create([ + 'name' => 'bar', + 'body' => 'test foobar', + 'type' => 'item', + ]); + $entity_3->save(); + $entity_4 = $entity_test_storage->create([ + 'name' => 'foo baz', + 'body' => 'test test test', + 'type' => 'article', + 'keywords' => ['apple', 'strawberry', 'grape'], + 'category' => 'article_category', + ]); + $entity_4->save(); + $entity_5 = $entity_test_storage->create([ + 'name' => 'bar baz', + 'body' => 'foo', + 'type' => 'article', + 'keywords' => ['orange', 'strawberry', 'grape', 'banana'], + 'category' => 'article_category', + ]); + $entity_5->save(); + + $inserted_entities = \Drupal::entityQuery('entity_test_mulrev_changed') + ->count() + ->execute(); + $this->assertEquals(5, $inserted_entities, "5 items inserted."); + + /** @var \Drupal\search_api\IndexInterface $index */ + $index = Index::load('database_search_index'); + $indexed_items = $index->indexItems(); + $this->assertEquals(5, $indexed_items, '5 items indexed.'); + } + + /** + * Create and place a facet block in the first sidebar. + * + * @param string $id + * Create a block for a facet. + */ + protected function createBlock($id) { + $config = \Drupal::configFactory(); + $settings = [ + 'plugin' => 'facet_block:' . $id, + 'region' => 'sidebar_first', + 'id' => $id . '_block', + 'theme' => $config->get('system.theme')->get('default'), + 'label' => ucfirst($id) . ' block', + 'visibility' => [], + 'weight' => 0, + ]; + + foreach (['region', 'id', 'theme', 'plugin', 'weight', 'visibility'] as $key) { + $values[$key] = $settings[$key]; + // Remove extra values that do not belong in the settings array. + unset($settings[$key]); + } + $values['settings'] = $settings; + $block = Block::create($values); + $block->save(); + } + +} diff --git a/tests/src/FunctionalJavascript/WidgetJSTest.php b/tests/src/FunctionalJavascript/WidgetJSTest.php index 2b606a2..0d67d97 100644 --- a/tests/src/FunctionalJavascript/WidgetJSTest.php +++ b/tests/src/FunctionalJavascript/WidgetJSTest.php @@ -2,46 +2,14 @@ namespace Drupal\Tests\facets\FunctionalJavascript; -use Drupal\block\Entity\Block; use Drupal\facets\Entity\Facet; -use Drupal\FunctionalJavascriptTests\JavascriptTestBase; -use Drupal\search_api\Entity\Index; /** * Tests for the JS that transforms widgets into form elements. * * @group facets */ -class WidgetJSTest extends JavascriptTestBase { - - /** - * {@inheritdoc} - */ - public static $modules = [ - 'views', - 'search_api', - 'facets', - 'facets_search_api_dependency', - 'block', - ]; - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - // Create the users used for the tests. - $admin_user = $this->drupalCreateUser([ - 'administer search_api', - 'administer facets', - 'access administration pages', - 'administer blocks', - ]); - $this->drupalLogin($admin_user); - - $this->insertExampleContent(); - } +class WidgetJSTest extends JsBase { /** * Tests JS interactions in the admin UI. @@ -251,91 +219,4 @@ class WidgetJSTest extends JavascriptTestBase { $this->assertTrue(strpos($current_url, 'search-api-test-fulltext?f%5B0%5D=llama%253Aitem') !== FALSE); } - /** - * Setup and insert test content. - */ - protected function insertExampleContent() { - entity_test_create_bundle('item', NULL, 'entity_test_mulrev_changed'); - entity_test_create_bundle('article', NULL, 'entity_test_mulrev_changed'); - - $entity_test_storage = \Drupal::entityTypeManager() - ->getStorage('entity_test_mulrev_changed'); - $entity_1 = $entity_test_storage->create([ - 'name' => 'foo bar baz', - 'body' => 'test test', - 'type' => 'item', - 'keywords' => ['orange'], - 'category' => 'item_category', - ]); - $entity_1->save(); - $entity_2 = $entity_test_storage->create([ - 'name' => 'foo test', - 'body' => 'bar test', - 'type' => 'item', - 'keywords' => ['orange', 'apple', 'grape'], - 'category' => 'item_category', - ]); - $entity_2->save(); - $entity_3 = $entity_test_storage->create([ - 'name' => 'bar', - 'body' => 'test foobar', - 'type' => 'item', - ]); - $entity_3->save(); - $entity_4 = $entity_test_storage->create([ - 'name' => 'foo baz', - 'body' => 'test test test', - 'type' => 'article', - 'keywords' => ['apple', 'strawberry', 'grape'], - 'category' => 'article_category', - ]); - $entity_4->save(); - $entity_5 = $entity_test_storage->create([ - 'name' => 'bar baz', - 'body' => 'foo', - 'type' => 'article', - 'keywords' => ['orange', 'strawberry', 'grape', 'banana'], - 'category' => 'article_category', - ]); - $entity_5->save(); - - $inserted_entities = \Drupal::entityQuery('entity_test_mulrev_changed') - ->count() - ->execute(); - $this->assertEquals(5, $inserted_entities, "5 items inserted."); - - /** @var \Drupal\search_api\IndexInterface $index */ - $index = Index::load('database_search_index'); - $indexed_items = $index->indexItems(); - $this->assertEquals(5, $indexed_items, '5 items indexed.'); - } - - /** - * Create and place a facet block in the first sidebar. - * - * @param string $id - * Create a block for a facet. - */ - protected function createBlock($id) { - $config = \Drupal::configFactory(); - $settings = [ - 'plugin' => 'facet_block:' . $id, - 'region' => 'sidebar_first', - 'id' => $id . '_block', - 'theme' => $config->get('system.theme')->get('default'), - 'label' => ucfirst($id) . ' block', - 'visibility' => [], - 'weight' => 0, - ]; - - foreach (['region', 'id', 'theme', 'plugin', 'weight', 'visibility'] as $key) { - $values[$key] = $settings[$key]; - // Remove extra values that do not belong in the settings array. - unset($settings[$key]); - } - $values['settings'] = $settings; - $block = Block::create($values); - $block->save(); - } - }