diff --git a/facets.libraries.yml b/facets.libraries.yml index 84a7c46..62daaf3 100644 --- a/facets.libraries.yml +++ b/facets.libraries.yml @@ -14,10 +14,17 @@ drupal.facets.edit-facet: dependencies: - core/jquery - core/drupal - - core/jquery.once drupal.facets.admin_css: version: VERSION css: theme: css/facets.admin.css: {} + +drupal.facets.checkbox-widget: + version: VERSION + js: + js/checkbox-widget.js: {} + dependencies: + - core/jquery + - core/drupal diff --git a/js/checkbox-widget.js b/js/checkbox-widget.js new file mode 100644 index 0000000..997aaca --- /dev/null +++ b/js/checkbox-widget.js @@ -0,0 +1,69 @@ +/** + * @file + * Transforms links into checkboxes. + */ + +(function ($) { + + "use strict"; + + Drupal.facets = {}; + Drupal.behaviors.facetsCheckboxWidget = { + attach: function (context, settings) { + Drupal.facets.makeCheckboxes(); + } + }; + + /** + * Turns all facet links into checkboxes. + */ + Drupal.facets.makeCheckboxes = function () { + // Find all checkbox facet links and give them a checkbox. + var $links = $('.js-facets-checkbox-links .facet-item a'); + $links.once('facets-checkbox-transform').each(Drupal.facets.makeCheckbox); + }; + + /** + * Replace a link with a checked checkbox. + */ + Drupal.facets.makeCheckbox = function () { + var $link = $(this); + var active = $link.hasClass('is-active'); + var description = $link.html(); + var href = $link.attr('href'); + var id = $link.data('facet-id'); + + var checkbox = $(''); + var label = $(''); + + checkbox.change(function (e) { + Drupal.facets.disableFacet($link.parents('.js-facets-checkbox-links')); + window.location.href = $(this).data('facetsredir'); + }); + + if (active) { + checkbox.attr('checked', true); + label.find('.facet-deactivate').remove(); + } + + $link.before(checkbox).before(label).hide(); + + }; + + /** + * Disable all facet checkboxes in the facet and apply a 'disabled' class. + */ + Drupal.facets.disableFacet = function ($facet) { + $facet.addClass('facets-disabled'); + $('input.facets-checkbox').click(Drupal.facets.preventDefault); + $('input.facetapi-checkbox', $facet).attr('disabled', true); + }; + + /** + * Event listener for easy prevention of event propagation. + */ + Drupal.facets.preventDefault = function (e) { + e.preventDefault(); + } + +})(jQuery); diff --git a/src/Form/CheckboxWidgetForm.php b/src/Form/CheckboxWidgetForm.php deleted file mode 100644 index 33beff9..0000000 --- a/src/Form/CheckboxWidgetForm.php +++ /dev/null @@ -1,135 +0,0 @@ -facet = $facet; - } - - /** - * {@inheritdoc} - */ - public function getBaseFormId() { - return 'facets_checkbox_widget'; - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - return $this->getBaseFormId() . '__' . $this->facet->id(); - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state) { - $facet = $this->facet; - - /** @var \Drupal\facets\Result\Result[] $results */ - $results = $facet->getResults(); - - $configuration = $facet->getWidgetConfigs(); - $show_numbers = (bool) isset($configuration['show_numbers']) ? $configuration['show_numbers'] : FALSE; - $form[$facet->getFieldAlias()] = [ - '#type' => 'checkboxes', - '#title' => $facet->getName(), - ]; - - $options = []; - foreach ($results as $result) { - $text = $result->getDisplayValue(); - if ($show_numbers) { - $text .= ' (' . $result->getCount() . ')'; - } - - $options[$result->getRawValue()] = $text; - - if ($result->isActive()) { - $form[$facet->getFieldAlias()]['#default_value'][] = $result->getRawValue(); - } - } - - $form[$facet->getFieldAlias()]['#options'] = $options; - - $form[$facet->id() . '_submit'] = [ - '#type' => 'submit', - '#value' => 'submit', - ]; - - return $form; - } - - /** - * {@inheritdoc} - */ - public function validateForm(array &$form, FormStateInterface $form_state) {} - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $values = $form_state->getValues(); - $facet = $this->facet; - - $result_link = FALSE; - $active_items = []; - - foreach ($values[$facet->getFieldAlias()] as $key => $value) { - if ($value !== 0) { - $active_items[] = $value; - } - } - - foreach ($facet->getResults() as $result) { - if (in_array($result->getRawValue(), $active_items)) { - $result_link = $result->getUrl(); - } - } - - // We have an active item, so we redirect to the page that has that facet - // selected. This should be an absolute link because RedirectResponse is a - // symfony class that requires a full URL. - if ($result_link instanceof Url) { - $result_link->setAbsolute(); - $form_state->setResponse(new RedirectResponse($result_link->toString())); - return; - } - - // The form was submitted but nothing was active in the form, we should - // still redirect, but the url for the new page can't come from a result. - // So we're redirecting to the facet source's page. - $path = $facet->getFacetSource()->getPath(); - if (substr($path, 0, 1) !== '/') { - $path = '/' . $path; - } - $link = Url::fromUserInput($path); - $link->setAbsolute(); - $form_state->setResponse(new RedirectResponse($link->toString())); - } - -} diff --git a/src/Plugin/facets/widget/CheckboxWidget.php b/src/Plugin/facets/widget/CheckboxWidget.php index 48ffe93..b22b4aa 100644 --- a/src/Plugin/facets/widget/CheckboxWidget.php +++ b/src/Plugin/facets/widget/CheckboxWidget.php @@ -2,11 +2,8 @@ namespace Drupal\facets\Plugin\facets\widget; -use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\facets\FacetInterface; -use Drupal\facets\Form\CheckboxWidgetForm; -use Drupal\facets\Widget\WidgetInterface; +use Drupal\facets\Result\ResultInterface; /** * The checkbox / radios widget. @@ -17,44 +14,61 @@ use Drupal\facets\Widget\WidgetInterface; * description = @Translation("A configurable widget that shows a list of checkboxes"), * ) */ -class CheckboxWidget implements WidgetInterface { - - use StringTranslationTrait; +class CheckboxWidget extends LinksWidget { /** - * {@inheritdoc} + * The facet the widget is being built for. + * + * @var \Drupal\facets\FacetInterface */ - public function build(FacetInterface $facet) { - $form_builder = \Drupal::getContainer()->get('form_builder'); - $form_object = new CheckboxWidgetForm($facet); - return $form_builder->getForm($form_object); - } + protected $facet; /** * {@inheritdoc} */ - public function buildConfigurationForm(array $form, FormStateInterface $form_state, $config) { + public function build(FacetInterface $facet) { + $this->facet = $facet; - $form['show_numbers'] = [ - '#type' => 'checkbox', - '#title' => $this->t('Show the amount of results'), - ]; + /** @var \Drupal\facets\Result\Result[] $results */ + $results = $facet->getResults(); + $items = []; + + $configuration = $facet->getWidgetConfigs(); + $this->showNumbers = empty($configuration['show_numbers']) ? FALSE : (bool) $configuration['show_numbers']; - if (!is_null($config)) { - $widget_configs = $config->get('widget_configs'); - if (isset($widget_configs['show_numbers'])) { - $form['show_numbers']['#default_value'] = $widget_configs['show_numbers']; + foreach ($results as $result) { + if (is_null($result->getUrl())) { + $text = $this->extractText($result); + $items[] = ['#markup' => $text]; + } + else { + $items[] = $this->buildListItems($result); } } - return $form; + $build = [ + '#theme' => 'item_list', + '#items' => $items, + '#attributes' => ['class' => ['js-facets-checkbox-links']], + '#cache' => [ + 'contexts' => [ + 'url.path', + 'url.query_args', + ], + ], + ]; + $build['#attached']['library'][] = 'facets/drupal.facets.checkbox-widget'; + + return $build; } /** * {@inheritdoc} */ - public function getQueryType($query_types) { - return $query_types['string']; + protected function buildListItems(ResultInterface $result) { + $items = parent::buildListItems($result); + $items['#attributes']['data-facet-id'] = $this->facet->getUrlAlias() . '-' . $result->getRawValue(); + return $items; } } diff --git a/src/Tests/WidgetIntegrationTest.php b/src/Tests/WidgetIntegrationTest.php index 871bd3d..6d3abda 100644 --- a/src/Tests/WidgetIntegrationTest.php +++ b/src/Tests/WidgetIntegrationTest.php @@ -69,80 +69,8 @@ class WidgetIntegrationTest extends WebTestBase { $this->drupalPlaceBlock($block_values['plugin_id'], $block_values['settings']); $this->drupalGet('search-api-test-fulltext'); - $this->drupalPostForm(NULL, array('type[item]' => 'item'), $this->t('submit')); - $this->assertFieldChecked('edit-type-item'); - } - - /** - * Tests multiple checkbox widgets. - */ - public function testMultipleCheckboxWidget() { - $facet_add_page = 'admin/config/search/facets/add-facet'; - - $id = 'type'; - $name = 'Northern hawk-owl | type'; - $id_2 = 'keywords'; - $name_2 = 'Papuan hawk-owl | keywords'; - - // Add a new facet. - $form_values = [ - 'id' => $id, - 'status' => 1, - 'name' => $name, - 'facet_source_id' => 'search_api_views:search_api_test_view:page_1', - 'facet_source_configs[search_api_views:search_api_test_view:page_1][field_identifier]' => 'type', - ]; - $this->drupalGet($facet_add_page); - $this->drupalPostForm(NULL, ['facet_source_id' => 'search_api_views:search_api_test_view:page_1'], $this->t('Configure facet source')); - $this->drupalPostForm(NULL, $form_values, $this->t('Save')); - $this->drupalPostForm(NULL, ['widget' => 'checkbox'], $this->t('Save')); - - // Add a new facet. - $form_values = [ - 'id' => $id_2, - 'status' => 1, - 'name' => $name_2, - 'facet_source_id' => 'search_api_views:search_api_test_view:page_1', - 'facet_source_configs[search_api_views:search_api_test_view:page_1][field_identifier]' => 'keywords', - ]; - $this->drupalGet($facet_add_page); - $this->drupalPostForm(NULL, ['facet_source_id' => 'search_api_views:search_api_test_view:page_1'], $this->t('Configure facet source')); - $this->drupalPostForm(NULL, $form_values, $this->t('Save')); - $this->drupalPostForm(NULL, ['widget' => 'checkbox'], $this->t('Save')); - - // Place facets as blocks. - $block_values = ['region' => 'footer', 'id' => str_replace('_', '-', $id)]; - $this->drupalPlaceBlock('facet_block:' . $id, $block_values); - $block_values = ['region' => 'footer', 'id' => str_replace('_', '-', $id_2)]; - $this->drupalPlaceBlock('facet_block:' . $id_2, $block_values); - - // Go to the test view and test that both facets are shown on the page. - $this->drupalGet('search-api-test-fulltext'); - $this->assertText($name); - $this->assertText($name_2); - $this->assertText('item'); - $this->assertText('apple'); - - // Submit the facet form and check that the form is submitted and the - // checkbox is now checked. - $edit = array('type[item]' => 'item'); - $this->drupalPostForm(NULL, $edit, $this->t('submit')); - $this->assertText($name); - $this->assertText($name_2); - $this->assertText('item'); - $this->assertText('apple'); - $this->assertFieldChecked('edit-type-item'); - - // Submit the second facet form and check that the form is submitted and the - // checkbox is now checked. - $edit = array('keywords[apple]' => 'apple'); - $this->drupalPostForm(NULL, $edit, $this->t('submit')); - $this->assertText($name); - $this->assertText($name_2); - $this->assertText('item'); - $this->assertText('apple'); - $this->assertFieldChecked('edit-type-item'); - $this->assertFieldChecked('edit-keywords-apple'); + $this->assertLink('item'); + $this->assertLink('article'); } /** diff --git a/tests/src/Unit/Form/CheckboxWidgetFormTest.php b/tests/src/Unit/Form/CheckboxWidgetFormTest.php deleted file mode 100644 index aead740..0000000 --- a/tests/src/Unit/Form/CheckboxWidgetFormTest.php +++ /dev/null @@ -1,140 +0,0 @@ -setUrl(new Url('test')); - } - $original_results[1]->setActiveState(TRUE); - - $this->originalResults = $original_results; - } - - /** - * Tests widget form with default settings. - */ - public function testDefaultSettings() { - $facet = new Facet(['id' => 'zoo_animal'], 'facet'); - $facet->setResults($this->originalResults); - $facet->setFieldIdentifier('zoo_animal'); - - $form_state = new FormState(); - $form_state->addBuildInfo('args', [$facet]); - $form = []; - - $widget_form = new CheckboxWidgetForm($facet); - $built_form = $widget_form->buildForm($form, $form_state); - - $this->assertInternalType('array', $built_form); - $this->assertCount(4, $built_form['zoo_animal']['#options']); - $this->assertEquals('checkboxes', $built_form['zoo_animal']['#type']); - - $expected_links = [ - 'llama' => 'Llama', - 'badger' => 'Badger', - 'duck' => 'Duck', - 'alpaca' => 'Alpaca', - ]; - foreach ($expected_links as $index => $value) { - $this->assertEquals($value, $built_form['zoo_animal']['#options'][$index]); - } - $this->assertEquals(array('zoo_animal', 'zoo_animal_submit'), array_keys($built_form)); - } - - /** - * Tests widget form, make sure hiding and showing numbers works. - */ - public function testHideNumbers() { - $facet = new Facet([], 'facet'); - $facet->setResults($this->originalResults); - $facet->setFieldIdentifier('zoo__animal'); - $facet->setWidgetConfigs(['show_numbers' => 0]); - - $form_state = new FormState(); - $form_state->addBuildInfo('args', [$facet]); - $form = []; - - $widget_form = new CheckboxWidgetForm($facet); - $built_form = $widget_form->buildForm($form, $form_state); - - $this->assertInternalType('array', $built_form); - $this->assertCount(4, $built_form['zoo__animal']['#options']); - $expected_links = [ - 'llama' => 'Llama', - 'badger' => 'Badger', - 'duck' => 'Duck', - 'alpaca' => 'Alpaca', - ]; - foreach ($expected_links as $index => $value) { - $this->assertEquals($value, $built_form['zoo__animal']['#options'][$index]); - } - - // Enable the 'show_numbers' setting again to make sure that the switch - // between those settings works. - $facet->setWidgetConfigs(['show_numbers' => 1]); - - $built_form = $widget_form->buildForm($form, $form_state); - $this->assertInternalType('array', $built_form); - $this->assertCount(4, $built_form['zoo__animal']['#options']); - - $expected_links = [ - 'llama' => 'Llama (10)', - 'badger' => 'Badger (20)', - 'duck' => 'Duck (15)', - 'alpaca' => 'Alpaca (9)', - ]; - foreach ($expected_links as $index => $value) { - $this->assertEquals($value, $built_form['zoo__animal']['#options'][$index]); - } - } - - /** - * Tests form default methods. - */ - public function testForm() { - $facet = new Facet(['id' => 'donkey'], 'facet'); - $facet->setResults($this->originalResults); - $facet->setFieldIdentifier('donkey'); - - $form = new CheckboxWidgetForm($facet); - - $this->assertEquals('facets_checkbox_widget', $form->getBaseFormId()); - $this->assertEquals('facets_checkbox_widget__donkey', $form->getFormId()); - } - -} diff --git a/tests/src/Unit/Plugin/widget/CheckboxWidgetTest.php b/tests/src/Unit/Plugin/widget/CheckboxWidgetTest.php index c868d2d..8fc63da 100644 --- a/tests/src/Unit/Plugin/widget/CheckboxWidgetTest.php +++ b/tests/src/Unit/Plugin/widget/CheckboxWidgetTest.php @@ -2,12 +2,12 @@ namespace Drupal\Tests\facets\Unit\Plugin\widget; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Url; use Drupal\facets\Entity\Facet; use Drupal\facets\Plugin\facets\widget\CheckboxWidget; use Drupal\facets\Result\Result; use Drupal\Tests\UnitTestCase; -use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Unit test for widget. @@ -49,35 +49,63 @@ class CheckboxWidgetTest extends UnitTestCase { } $this->originalResults = $original_results; - $form_builder = $this->getMockBuilder('\Drupal\Core\Form\FormBuilder') - ->disableOriginalConstructor() - ->getMock(); - $form_builder->expects($this->once()) - ->method('getForm') - ->willReturn('build'); - - $string_translation = $this->getMockBuilder('\Drupal\Core\StringTranslation\TranslationManager') - ->disableOriginalConstructor() - ->getMock(); - - $container_builder = new ContainerBuilder(); - $container_builder->set('form_builder', $form_builder); - $container_builder->set('string_translation', $string_translation); - \Drupal::setContainer($container_builder); - $this->widget = new CheckboxWidget(); } /** - * Tests widget with default settings. + * Tests widget without filters. */ - public function testDefaultSettings() { + public function testNoFilterResults() { $facet = new Facet([], 'facet'); $facet->setResults($this->originalResults); - $facet->setFieldIdentifier('test_field'); + $facet->setWidgetConfigs(['show_numbers' => 1]); + + $output = $this->widget->build($facet); + + $this->assertInternalType('array', $output); + $this->assertCount(4, $output['#items']); + + $this->assertEquals(['js-facets-checkbox-links'], $output['#attributes']['class']); + + $expected_links = [ + $this->buildLinkAssertion('Llama', 10), + $this->buildLinkAssertion('Badger', 20), + $this->buildLinkAssertion('Duck', 15), + $this->buildLinkAssertion('Alpaca', 9), + ]; + foreach ($expected_links as $index => $value) { + $this->assertInternalType('array', $output['#items'][$index]); + $this->assertEquals($value, $output['#items'][$index]['#title']); + $this->assertInstanceOf(FormattableMarkup::class, $output['#items'][$index]['#title']); + $this->assertEquals('link', $output['#items'][$index]['#type']); + $this->assertEquals(['facet-item'], $output['#items'][$index]['#wrapper_attributes']['class']); + } + } - $built_form = $this->widget->build($facet); - $this->assertEquals('build', $built_form); + /** + * Build a formattable markup object to use in the other tests. + * + * @param string $text + * Text to display. + * @param int $count + * Number of results. + * @param bool $active + * Link is active. + * @param bool $show_numbers + * Numbers are displayed. + * + * @return \Drupal\Component\Render\FormattableMarkup + * Formattable markup object for link. + */ + private function buildLinkAssertion($text, $count = 0, $active = FALSE, $show_numbers = TRUE) { + $text = new FormattableMarkup('@text', ['@text' => $text, '@count' => $count]); + if ($show_numbers !== FALSE) { + $text->string .= ' (@count)'; + } + if ($active) { + $text->string = '(-) ' . $text->string; + } + return $text; } } diff --git a/tests/src/Unit/Plugin/widget/LinksWidgetTest.php b/tests/src/Unit/Plugin/widget/LinksWidgetTest.php index ae2b6f4..075bcb3 100644 --- a/tests/src/Unit/Plugin/widget/LinksWidgetTest.php +++ b/tests/src/Unit/Plugin/widget/LinksWidgetTest.php @@ -237,4 +237,5 @@ class LinksWidgetTest extends UnitTestCase { } return $text; } + }