diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 9b89250..cfa4062 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -827,6 +827,8 @@ function template_preprocess_image(&$variables) { * narrow viewports to save horizontal space. * - Any HTML attributes, such as "colspan", to apply to the column header * cell. + * - header_multilevel: A boolean flag, FALSE by default. If set to TRUE, the + * header key array should have one more depth level. * - rows: An array of table rows. Every row is an array of cells, or an * associative array with the following keys: * - data: An array of cells. @@ -926,57 +928,77 @@ function template_preprocess_table(&$variables) { if (!empty($variables['header'])) { $ts = tablesort_init($variables['header']); - // Use a separate index with responsive classes as headers - // may be associative. - $responsive_index = -1; - foreach ($variables['header'] as $col_key => $cell) { - // Increase the responsive index. - $responsive_index++; - - if (!is_array($cell)) { - $header_columns++; - $cell_content = $cell; - $cell_attributes = new Attribute(); - $is_header = TRUE; - } - else { - if (isset($cell['colspan'])) { - $header_columns += $cell['colspan']; + // Build multirows header from simple row. + if (empty($variables['header_multilevel'])) { + $variables['header'] = [ + 'single_row' => $variables['header'], + ]; + } + + foreach ($variables['header'] as $row_key => $header_row) { + $header_columns_in_row = 0; + + // Use a separate index with responsive classes as headers + // may be associative. + $responsive_index = -1; + foreach ($header_row as $col_key => $cell) { + // Increase the responsive index. + $responsive_index++; + + if (!is_array($cell)) { + $header_columns_in_row++; + $cell_content = $cell; + $cell_attributes = new Attribute(); + $is_header = TRUE; } else { - $header_columns++; - } - $cell_content = ''; - if (isset($cell['data'])) { - $cell_content = $cell['data']; - unset($cell['data']); - } - // Flag the cell as a header or not and remove the flag. - $is_header = isset($cell['header']) ? $cell['header'] : TRUE; - unset($cell['header']); - - // Track responsive classes for each column as needed. Only the header - // cells for a column are marked up with the responsive classes by a - // module developer or themer. The responsive classes on the header cells - // must be transferred to the content cells. - if (!empty($cell['class']) && is_array($cell['class'])) { - if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { - $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; + if (isset($cell['colspan'])) { + $header_columns_in_row += $cell['colspan']; } - elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { - $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; + else { + $header_columns_in_row++; + } + $cell_content = ''; + if (isset($cell['data'])) { + $cell_content = $cell['data']; + unset($cell['data']); + } + // Flag the cell as a header or not and remove the flag. + $is_header = isset($cell['header']) ? $cell['header'] : TRUE; + unset($cell['header']); + + // Track responsive classes for each column as needed. Only the header + // cells for a column are marked up with the responsive classes by a + // module developer or themer. The responsive classes on the header cells + // must be transferred to the content cells. + if (!empty($cell['class']) && is_array($cell['class'])) { + if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { + $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; + } + elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { + $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; + } } - } - tablesort_header($cell_content, $cell, $variables['header'], $ts); + tablesort_header($cell_content, $cell, $variables['header'], $ts); - // tablesort_header() removes the 'sort' and 'field' keys. - $cell_attributes = new Attribute($cell); + // tablesort_header() removes the 'sort' and 'field' keys. + $cell_attributes = new Attribute($cell); + } + $variables['header'][$row_key][$col_key] = array(); + $variables['header'][$row_key][$col_key]['tag'] = $is_header ? 'th' : 'td'; + $variables['header'][$row_key][$col_key]['attributes'] = $cell_attributes; + $variables['header'][$row_key][$col_key]['content'] = $cell_content; } - $variables['header'][$col_key] = array(); - $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td'; - $variables['header'][$col_key]['attributes'] = $cell_attributes; - $variables['header'][$col_key]['content'] = $cell_content; + $header_columns = max($header_columns_in_row, $header_columns); + } + + // Get single row header back to normal for BC. + //// @todo: remove this in D9. + if (empty($variables['header_multilevel'])) { + $variables['header'] = $variables['header']['single_row']; + } else { + $variables['header_multilevel'] = $variables['header']; } } $variables['header_columns'] = $header_columns; @@ -1765,7 +1787,7 @@ function drupal_common_theme() { 'variables' => array('links' => array()), ), 'table' => array( - 'variables' => array('header' => NULL, 'rows' => NULL, 'footer' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''), + 'variables' => array('header' => NULL, 'header_multilevel' => NULL, 'rows' => NULL, 'footer' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''), ), 'tablesort_indicator' => array( 'variables' => array('style' => NULL), diff --git a/core/lib/Drupal/Core/Render/Element/Table.php b/core/lib/Drupal/Core/Render/Element/Table.php index 2da6ef9..e5d5f7b 100644 --- a/core/lib/Drupal/Core/Render/Element/Table.php +++ b/core/lib/Drupal/Core/Render/Element/Table.php @@ -60,6 +60,7 @@ public function getInfo() { $class = get_class($this); return array( '#header' => array(), + '#header_multilevel' => FALSE, '#rows' => array(), '#empty' => '', // Properties for tableselect support. diff --git a/core/modules/system/templates/table.html.twig b/core/modules/system/templates/table.html.twig index 4436570..82ae0a0 100644 --- a/core/modules/system/templates/table.html.twig +++ b/core/modules/system/templates/table.html.twig @@ -8,10 +8,8 @@ * - caption: A localized string for the tag. * - colgroups: Column groups. Each group contains the following properties: * - attributes: HTML attributes to apply to the tag. - * Note: Drupal currently supports only one table header row, see - * https://www.drupal.org/node/893530 and - * http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109. - * - header: Table header cells. Each cell contains the following properties: + * - header: Table header rows. Each row contains and array of cell with the + * following properties: * - tag: The HTML tag name to use; either 'th' or 'td'. * - attributes: HTML attributes to apply to the tag. * - content: A localized string for the title of the column. @@ -60,13 +58,24 @@ {% if header %} - - {% for cell in header %} - <{{ cell.tag }}{{ cell.attributes }}> - {{- cell.content -}} - - {% endfor %} - + {% for row in header if header_multilevel %} + + {% for cell in row %} + <{{ cell.tag }}{{ cell.attributes }}> + {{- cell.content -}} + + {% endfor %} + + {% endfor %} + {% if header_multilevel == FALSE %} + + {% for cell in header %} + <{{ cell.tag }}{{ cell.attributes }}> + {{- cell.content -}} + + {% endfor %} + + {% endif %} {% endif %} diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php index 34cbfc0..d056c7d 100644 --- a/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php +++ b/core/tests/Drupal/KernelTests/Core/Render/Element/TableTest.php @@ -308,4 +308,33 @@ public function testThemeTableTitle() { $this->assertRaw('Update my favourite fruit is bananas'); } + /** + * Tests multi rows header table. + */ + public function testThemeTableHeaderMultiRows() { + $table = [ + '#type' => 'table', + '#header' => [ + 'row1' => [ + 'bigcell' => [ + 'data' => 'Big header', + 'rowspan' => 2 + ], + 'longcell' => [ + 'data' => 'Long header', + 'colspan' => 2 + ], + 'Small header' + ], + 'row2' => ['cell1', 'cell2', 'cell3'] + ], + '#header_multilevel' => TRUE, + '#empty' => 'There are no lines here.', + ]; + $this->render($table); + $this->removeWhiteSpace(); + $this->assertRaw('Big headerLong headerSmall headercell1cell2cell3', 'Table header found.'); + $this->assertRaw('There are no lines here.', 'Table empty message found.'); + } + } diff --git a/core/themes/classy/templates/dataset/table.html.twig b/core/themes/classy/templates/dataset/table.html.twig index 2afa9c1..e36f955 100644 --- a/core/themes/classy/templates/dataset/table.html.twig +++ b/core/themes/classy/templates/dataset/table.html.twig @@ -8,10 +8,8 @@ * - caption: A localized string for the tag. * - colgroups: Column groups. Each group contains the following properties: * - attributes: HTML attributes to apply to the tag. - * Note: Drupal currently supports only one table header row, see - * https://www.drupal.org/node/893530 and - * http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109. - * - header: Table header cells. Each cell contains the following properties: + * - header: Table header rows. Each row contains and array of cell with the + * following properties: * - tag: The HTML tag name to use; either 'th' or 'td'. * - attributes: HTML attributes to apply to the tag. * - content: A localized string for the title of the column. @@ -58,18 +56,34 @@ {% if header %} + {% for row in header if header_multilevel %} - {% for cell in header %} + {% for cell in row %} {% set cell_classes = [ - cell.active_table_sort ? 'is-active', - ] + cell.active_table_sort ? 'is-active', + ] %} <{{ cell.tag }}{{ cell.attributes.addClass(cell_classes) }}> {{- cell.content -}} {% endfor %} + {% endfor %} + {% if header_multilevel == FALSE %} + + {% for cell in header %} + {% + set cell_classes = [ + cell.active_table_sort ? 'is-active', + ] + %} + <{{ cell.tag }}{{ cell.attributes.addClass(cell_classes) }}> + {{- cell.content -}} + + {% endfor %} + + {% endif %} {% endif %} diff --git a/core/themes/stable/templates/dataset/table.html.twig b/core/themes/stable/templates/dataset/table.html.twig index 231ada2..9665bd8 100644 --- a/core/themes/stable/templates/dataset/table.html.twig +++ b/core/themes/stable/templates/dataset/table.html.twig @@ -8,10 +8,8 @@ * - caption: A localized string for the tag. * - colgroups: Column groups. Each group contains the following properties: * - attributes: HTML attributes to apply to the tag. - * Note: Drupal currently supports only one table header row, see - * https://www.drupal.org/node/893530 and - * http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109. - * - header: Table header cells. Each cell contains the following properties: + * - header: Table header rows. Each row contains and array of cell with the + * following properties: * - tag: The HTML tag name to use; either 'th' or 'td'. * - attributes: HTML attributes to apply to the tag. * - content: A localized string for the title of the column. @@ -58,13 +56,24 @@ {% if header %} - - {% for cell in header %} - <{{ cell.tag }}{{ cell.attributes }}> - {{- cell.content -}} - - {% endfor %} - + {% for row in header if header_multilevel %} + + {% for cell in row %} + <{{ cell.tag }}{{ cell.attributes }}> + {{- cell.content -}} + + {% endfor %} + + {% endfor %} + {%if header_multilevel == FALSE %} + + {% for cell in header %} + <{{ cell.tag }}{{ cell.attributes }}> + {{- cell.content -}} + + {% endfor %} + + {% endfor %} {% endif %}