diff --git a/core/lib/Drupal/Core/Render/Element/Checkboxes.php b/core/lib/Drupal/Core/Render/Element/Checkboxes.php index 117f839..f61f794 100644 --- a/core/lib/Drupal/Core/Render/Element/Checkboxes.php +++ b/core/lib/Drupal/Core/Render/Element/Checkboxes.php @@ -127,4 +127,23 @@ public static function valueCallback(&$element, $input, FormStateInterface $form } } + /** + * Determines if the submitted checkbox values include a selected option. + * + * @param array $input + * Form values returned from a set of checkboxes. + * + * @return bool + * TRUE if all options are unchecked. FALSE otherwise. + */ + static public function detectEmptyCheckboxes(array $input) { + // Checkboxes show up as an array in the form of option_id => bool. If all + // the values are zero, then there is no input. + $checked = array_filter($input, function($value) { + return $value !== 0; + }); + + return empty($checked); + } + } diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php index c0cd5d0..6e93f34 100644 --- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php +++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php @@ -1320,15 +1320,20 @@ public function storeGroupInput($input, $status) { } /** - * Check to see if input from the exposed filters should change - * the behavior of this filter. + * Determines if the input from a filter should change the generated query. + * + * @param array $input + * The exposed data for this view. + * + * @return bool + * TRUE if the input for this filter should be included in the view query. + * FALSE otherwise. */ public function acceptExposedInput($input) { if (empty($this->options['exposed'])) { return TRUE; } - if (!empty($this->options['expose']['use_operator']) && !empty($this->options['expose']['operator_id']) && isset($input[$this->options['expose']['operator_id']])) { $this->operator = $input[$this->options['expose']['operator_id']]; } diff --git a/core/modules/views/src/Plugin/views/filter/InOperator.php b/core/modules/views/src/Plugin/views/filter/InOperator.php index 042dca5..fb08c5f 100644 --- a/core/modules/views/src/Plugin/views/filter/InOperator.php +++ b/core/modules/views/src/Plugin/views/filter/InOperator.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element\Checkboxes; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\ViewExecutable; use Drupal\Core\Form\OptGroup; @@ -287,15 +288,17 @@ public function reduceValueOptions($input = NULL) { return $options; } + /** + * @inheritdoc + */ public function acceptExposedInput($input) { - // A very special override because the All state for this type of - // filter could have a default: if (empty($this->options['exposed'])) { return TRUE; } - // If this is non-multiple and non-required, then this filter will - // participate, but using the default settings, *if* 'limit is true. + // The "All" state for this type of filter could have a default value. If + // this is a non-multiple and non-required option, then this filter will + // participate, but using the default settings *if* 'limit' is true. if (empty($this->options['expose']['multiple']) && empty($this->options['expose']['required']) && !empty($this->options['expose']['limit'])) { $identifier = $this->options['expose']['identifier']; if ($input[$identifier] == 'All') { @@ -303,6 +306,12 @@ public function acceptExposedInput($input) { } } + // If checkboxes are used to render this filter, we do not include the + // filter if all option are unchecked. + if (!empty($this->options['expose']['identifier']) && is_array($input[$this->options['expose']['identifier']]) && Checkboxes::detectEmptyCheckboxes($input[$this->options['expose']['identifier']])) { + return FALSE; + } + return parent::acceptExposedInput($input); } diff --git a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php index 1c520e0..fbd5b88 100644 --- a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php +++ b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php @@ -148,6 +148,38 @@ public function testExposedFormRender() { } /** + * Tests overriding the default render option with checkboxes. + */ + public function testExposedFormRenderCheckboxes() { + // Make sure we have at least two options for node type. + $this->drupalCreateContentType(['type' => 'page']); + $this->drupalCreateNode(['type' => 'page']); + + // Use a test theme to convert multi-select elements into checkboxes. + \Drupal::service('theme_handler')->install(array('views_test_checkboxes_theme')); + $this->config('system.theme') + ->set('default', 'views_test_checkboxes_theme') + ->save(); + + // Set the "type" filter to multi-select. + $view = Views::getView('test_exposed_form_buttons'); + $filter = $view->getHandler('page_1', 'filter', 'type'); + $filter['expose']['multiple'] = TRUE; + $view->setHandler('page_1', 'filter', 'type', $filter); + $view->save(); + $this->drupalGet('test_exposed_form_buttons'); + + $actual = $this->xpath('//form//input[@type="checkbox" and @name="type[article]"]'); + $this->assertEqual(count($actual), 1, 'Article option renders as a checkbox.'); + $actual = $this->xpath('//form//input[@type="checkbox" and @name="type[page]"]'); + $this->assertEqual(count($actual), 1, 'Page option renders as a checkbox'); + + // Ensure that all results are displayed. + $rows = $this->xpath("//div[contains(@class, 'views-row')]"); + $this->assertEqual(count($rows), 6, 'All rows are displayed by default when no options are checked.'); + } + + /** * Tests the exposed block functionality. */ public function testExposedBlock() { diff --git a/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.info.yml b/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.info.yml new file mode 100644 index 0000000..50bf73e --- /dev/null +++ b/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.info.yml @@ -0,0 +1,5 @@ +name: Views test checkboxes theme +type: theme +description: Theme for testing Views rendering of checkboxes. +version: VERSION +core: 8.x diff --git a/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.theme b/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.theme new file mode 100644 index 0000000..4a038ae --- /dev/null +++ b/core/modules/views/tests/themes/views_test_checkboxes_theme/views_test_checkboxes_theme.theme @@ -0,0 +1,7 @@ +