diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/style/Table.php b/core/modules/views/lib/Drupal/views/Plugin/views/style/Table.php index 3956202..b74f830 100644 --- a/core/modules/views/lib/Drupal/views/Plugin/views/style/Table.php +++ b/core/modules/views/lib/Drupal/views/Plugin/views/style/Table.php @@ -94,7 +94,9 @@ protected function defineOptions() { $options['override'] = array('default' => TRUE, 'bool' => TRUE); $options['sticky'] = array('default' => FALSE, 'bool' => TRUE); $options['order'] = array('default' => 'asc'); + $options['caption'] = array('default' => '', 'translatable' => TRUE); $options['summary'] = array('default' => '', 'translatable' => TRUE); + $options['description'] = array('default' => '', 'translatable' => TRUE); $options['empty_table'] = array('default' => FALSE, 'bool' => TRUE); return $options; @@ -242,12 +244,38 @@ public function buildOptionsForm(&$form, &$form_state) { '#description' => t('(Sticky header effects will not be active for preview below, only on live output.)'), ); + $form['caption'] = array( + '#type' => 'textfield', + '#title' => t('Caption for the table'), + '#description' => t('A title which is semantically associated to your table for increased accessibility.'), + '#default_value' => $this->options['caption'], + '#maxlength' => 255, + ); + + $form['accessibility_details'] = array( + '#type' => 'details', + '#title' => t('Table details'), + '#collapsed' => TRUE, + ); + $form['summary'] = array( + '#title' => t('Summary title'), '#type' => 'textfield', - '#title' => t('Table summary'), - '#description' => t('This value will be displayed as table-summary attribute in the html. Set this for better accessiblity of your site.'), '#default_value' => $this->options['summary'], - '#maxlength' => 255, + '#fieldset' => 'accessibility_details', + ); + + $form['description'] = array( + '#title' => t('Table description'), + '#type' => 'textarea', + '#description' => t('Provide additional details about the table to increase accessibility.'), + '#default_value' => $this->options['description'], + '#states' => array( + 'visible' => array( + 'input[name="style_options[summary]"]' => array('filled' => TRUE), + ), + ), + '#fieldset' => 'accessibility_details', ); // Note: views UI registers this theme handler on our behalf. Your module diff --git a/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleTableTest.php b/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleTableTest.php new file mode 100644 index 0000000..5bcca66 --- /dev/null +++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/StyleTableTest.php @@ -0,0 +1,83 @@ + 'Style: Table', + 'description' => 'Tests the table style plugin.', + 'group' => 'Views Plugins', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->enableViewsTestModule(); + } + + /** + * Test table caption/summary/description. + */ + public function testAccessibilitySettings() { + $this->drupalGet('test-table'); + + $result = $this->xpath('//caption'); + $this->assertTrue(count($result), 'The caption appears on the table.'); + $this->assertEqual(trim((string) $result[0]), 'caption-text'); + + $result = $this->xpath('//summary'); + $this->assertTrue(count($result), 'The summary appears on the table.'); + $this->assertEqual(trim((string) $result[0]), 'summary-text'); + + $result = $this->xpath('//caption/details'); + $this->assertTrue(count($result), 'The table description appears on the table.'); + $this->assertEqual(trim((string) $result[0]), 'description-text'); + + // Remove the caption and ensure the caption is not displayed anymore. + $view = entity_load('view', 'test_table'); + $display = &$view->getDisplay('default'); + $display['display_options']['style']['options']['caption'] = ''; + $view->save(); + + $this->drupalGet('test-table'); + $result = $this->xpath('//caption'); + $this->assertFalse(trim((string) $result[0]), 'Ensure that the caption disappears.'); + + // Remove the table summary. + $display = &$view->getDisplay('default'); + $display['display_options']['style']['options']['summary'] = ''; + $view->save(); + + $this->drupalGet('test-table'); + $result = $this->xpath('//summary'); + $this->assertFalse(count($result), 'Ensure that the summary disappears.'); + + // Remove the table description. + $display = &$view->getDisplay('default'); + $display['display_options']['style']['options']['description'] = ''; + $view->save(); + + $this->drupalGet('test-table'); + $result = $this->xpath('//caption/details'); + $this->assertFalse(count($result), 'Ensure that the description disappears.'); + } + +} diff --git a/core/modules/views/templates/views-view-table.html.twig b/core/modules/views/templates/views-view-table.html.twig index 50ae9d6..6778ee8 100644 --- a/core/modules/views/templates/views-view-table.html.twig +++ b/core/modules/views/templates/views-view-table.html.twig @@ -10,6 +10,10 @@ * - header: Header labels. * - header_classes: HTML classes to apply to each header cell, indexed by * the header's key. + * - caption_needed: Is the caption tag needed. + * - caption: The caption for this table. + * - accessibility_description: Extended description for the table details. + * - accessibility_summary: Summary for the table details. * - rows: Table row items. Rows are keyed by row number, fields within rows * are keyed by field ID. * - field: Table data field ID. @@ -29,6 +33,23 @@ {% if title is not empty %} {{ title }} {% endif %} + {% if caption_needed %} + {% if caption %} + {{ caption }} + {% else %} + {{ title }} + {% endif %} + {% if (summary is not empty) or (description is not empty) %} +
+ {% if summary is not empty %} + {{ summary }} + {% endif %} + {% if description is not empty %} + {{ description }} + {% endif %} +
+ {% endif %} + {% endif %} {% if header %} diff --git a/core/modules/views/tests/views_test_config/test_views/views.view.test_table.yml b/core/modules/views/tests/views_test_config/test_views/views.view.test_table.yml index 49ae0e9..991fbdf 100644 --- a/core/modules/views/tests/views_test_config/test_views/views.view.test_table.yml +++ b/core/modules/views/tests/views_test_config/test_views/views.view.test_table.yml @@ -80,6 +80,9 @@ display: responsive: '' default: 'id' empty_table: '1' + caption: caption-text + summary: summary-text + description: description-text row: type: fields empty: @@ -99,6 +102,13 @@ display: display_title: Master id: default position: '0' + page_1: + display_options: + path: 'test-table' + display_plugin: page + display_title: 'Page display' + id: page_1 + position: '1' label: '' id: test_table tag: '' diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index 42dd2f6..9980f6c 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -5,6 +5,7 @@ * Preprocessors and helper functions to make theming easier. */ +use Drupal\Component\Utility\Xss; use Drupal\Core\Language\Language; use Drupal\Core\Template\Attribute; use Drupal\views\ViewExecutable; @@ -736,13 +737,24 @@ function template_preprocess_views_view_table(&$vars) { } $vars['attributes']['class'][] = 'cols-' . count($vars['header']); - if (!empty($handler->options['summary'])) { - $vars['attributes']['summary'] = $handler->options['summary']; + // Add the caption to the list if set. + if (!empty($handler->options['caption'])) { + $vars['caption'] = Xss::filterAdmin($handler->options['caption']); + $vars['caption_needed'] = TRUE; + } + else { + $vars['caption'] = ''; + $vars['caption_needed'] = FALSE; } + + $vars['summary'] = $handler->options['summary']; + $vars['description'] = $handler->options['description']; + $vars['caption_needed'] |= !empty($vars['summary']) || !empty($vars['description']); + // If the table has headers and it should react responsively to columns hidden // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM // and RESPONSIVE_PRIORITY_LOW, add the tableresponsive behaviors. - if (count($vars['header']) && $responsive) { + if (isset($vars['header']) && $responsive) { $vars['view']->element['#attached']['library'][] = array('system', 'drupal.tableresponsive'); // Add 'responsive-enabled' class to the table to identify it for JS. // This is needed to target tables constructed by this function.