Problem/Motivation

Drupal's theme system does not support tables with more than one row of table headers. Browsers support rendering multiple table headers and it can be useful for grouping/hierarchies of table header cells.

The idea of this feature request is to let users build table via the FAPI just as it was until then, but also to allow them to add one more depth level and let them build complex table header.

Further info:
http://jsfiddle.net/7pDqb/
http://stackoverflow.com/questions/18680044/how-can-i-construct-a-table-...

Proposed resolution

Update template_preprocess_table() method to let analyse headers with one more depth level, in a backward compatible way.
Update all table.html.twig files to reflect this change and support multiple rows of table headers.
Build a test to show the new feature and keep existing table test to show it's backward compatibility.

This would result in the ability to create this kind of headers via FAPI:
Complex header table

Remaining tasks

Design a solution and implement it.

User interface changes

none.

API changes

'#type' => 'table' FAPI element will support multiple row headers, that means the 'header' key would allow a one more depth level.
It may request another speical key to be achieved, but the exact API changes have to be discussed during implementation.

Comments

LEternity’s picture

Title: theme_table » theme_table and multiple header rows
Status: Active » Needs review

Since I couldn't find a viable solution for tables with more than one table headers, I wrote the following snippet myself. This solution has worked well for me! Please feel free to improve the code.

Paste the below code into your template.php file. Replace THEME with your theme prefix. Don't forget to flush your cache after saving changes in template.php!

Whenever you'd like to include additional rows, add a value for $rows_multiple other than NULL to your theme() function. (e.g. theme('table', $header, $row, $attributes, $caption, 'multiple')).

Your $header variable will now be able to take multiple arrays (see example below).

This would be part of your module.

$header[] = array('Hello','World');
$header[] = array('Bye', 'World');

Paste the following into your theme's template.php.

/**
* Modification of theme_table
*/
function THEME_table($header, $rows, $attributes = array(), $caption = NULL, $rows_multiple = NULL) {
 
  // Add sticky headers, if applicable.
  if (count($header)) {
    drupal_add_js('misc/tableheader.js');
    // Add 'sticky-enabled' class to the table to identify it for JS.
    // This is needed to target tables constructed by this function.
    $attributes['class'] = empty($attributes['class']) ? 'sticky-enabled' : ($attributes['class'] .' sticky-enabled');
  }

  $output = '\n";

  if (isset($caption)) {
    $output .= '\n";
  }
   
    // Multiple header rows
    if(!$rows_multiple == NULL){
      $thead_set = '';
      // Format the table header:
      if (count($header)) {
          foreach($header as $number => $head){
            $ts = tablesort_init($head);
            // HTML requires that the thead tag has tr tags in it followed by tbody
            // tags. Using if clause to check and see if we have any rows and whether
            // the thead tag is already open
            if(count($rows) && $thead_set != 1){
                $output .= ' ';
                $thead_set = 1;
            }else{
                $output .= ' ';
            }
            //$output .= (count($rows) ? ' ' : ' ');
            foreach ($head as $cell) {
              $cell = tablesort_header($cell, $head, $ts);
              $output .= _theme_table_cell($cell, TRUE);
            }
        }
            // Using ternary operator to close the tags based on whether or not there are rows
            $output .= (count($rows) ? " \n" : "\n");
      }
      else {
        $ts = array();
      }
    // One header row
    }else{
         // Format the table header:
      if (count($header)) {
        $ts = tablesort_init($header);
        // HTML requires that the thead tag has tr tags in it followed by tbody
        // tags. Using ternary operator to check and see if we have any rows.
        $output .= (count($rows) ? ' ' : ' ');
        foreach ($header as $cell) {
          $cell = tablesort_header($cell, $header, $ts);
          $output .= _theme_table_cell($cell, TRUE);
        }
        // Using ternary operator to close the tags based on whether or not there are rows
        $output .= (count($rows) ? " \n" : "\n");
      }
      else {
        $ts = array();
      }
    }
   
  // Format the table rows:
  if (count($rows)) {
    $output .= "\n";
    $flip = array('even' => 'odd', 'odd' => 'even');
    $class = 'even';
    foreach ($rows as $number => $row) {
      $attributes = array();

      // Check if we're dealing with a simple or complex row
      if (isset($row['data'])) {
        foreach ($row as $key => $value) {
          if ($key == 'data') {
            $cells = $value;
          }
          else {
            $attributes[$key] = $value;
          }
        }
      }
      else {
        $cells = $row;
      }
      if (count($cells)) {
        // Add odd/even class
        $class = $flip[$class];
        if (isset($attributes['class'])) {
          $attributes['class'] .= ' '. $class;
        }
        else {
          $attributes['class'] = $class;
        }

        // Build row
        $output .= ' ';
        $i = 0;
        foreach ($cells as $cell) {
          $cell = tablesort_cell($cell, $header, $ts, $i++);
          $output .= _theme_table_cell($cell);
        }
        $output .= " \n";
      }
    }
    $output .= "\n";
  }

  $output .= "
'. $caption ."
\n"; return $output; }
thedavidmeister’s picture

Version: 6.x-dev » 8.0.x-dev
Issue summary: View changes
Status: Needs review » Needs work

This is an issue in d8 still, and has no patch so must be set to needs work.

Some extra reading on what this might look like:

http://stackoverflow.com/questions/18680044/how-can-i-construct-a-table-...

http://jsfiddle.net/7pDqb/

For reference, current table.html.twig looks like:

  {% if header %}
    <thead>
      <tr>
        {% for cell in header %}
          <{{ cell.tag }}{{ cell.attributes }}>
            {{- cell.content -}}
          </{{ cell.tag }}>
        {% endfor %}
      </tr>
    </thead>
  {% endif %}
Mirakolous’s picture

Assigned: Unassigned » Mirakolous
Mirakolous’s picture

Assigned: Mirakolous » Unassigned
joelpittet’s picture

Title: theme_table and multiple header rows » table template with multiple header rows
Version: 8.0.x-dev » 8.1.x-dev
Issue tags: +Twig

We need this to be backwards compatible way. And we are going to bump this to 8.1.x

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Dom.’s picture

Issue summary: View changes
Dom.’s picture

Title: table template with multiple header rows » Multi level (multirow) header for table FAPI element
Status: Needs work » Needs review
FileSize
2.86 KB
12.09 KB

In this patch, I make a suggestion that would let usual table work in a backward compatible way, as long as the overriden table.html.twig files are updated to add the new level loop.

The patch let's you use a new key called 'header_multirows' (boolean). By default, this key is FALSE and the former tables don't change.
If you set this flag to TRUE, you are indeed using multi row level headers and you must then give one more level to your 'header' key.
Using the patch attached, you can define a table as such:

$form[table'] = [
  '#type' => 'table',
  '#header' => [
    'row1' => [
      'bigcell' => [
        'data' => 'Big header',
        'rowspan' => 3
      ],
      'longcell' => [
        'data' => 'Long header',
        'colspan' => 2
      ],
      'Small header'
    ],
    'row2' => ['cell1', 'cell2', 'cell3']
  ],
  '#header_multirows' => TRUE,
  '#empty' => 'There are no lines here.',
];

And it would lead you to this ridiculously overrcomplicated table header ! :
Headerwith complex multilevel header

The patch attached includes:
- the functionnal changes to let this happen
- a test for this the functionnality

Dom.’s picture

Simply correct rowspan since 2 is enough.
Also get rid of special NOTE about only one row support for header element.

Status: Needs review » Needs work

The last submitted patch, 10: multilevel_headers--893530-10.patch, failed testing.

Dom.’s picture

Status: Needs work » Needs review
FileSize
12.67 KB

what's wrong with you patch ? ;)
Same patch again for reroll.

dawehner’s picture

+++ b/core/modules/system/templates/table.html.twig
@@ -60,13 +58,15 @@
   {% if header %}
     <thead>
-      <tr>
-        {% for cell in header %}
-          <{{ cell.tag }}{{ cell.attributes }}>
-            {{- cell.content -}}
-          </{{ cell.tag }}>
-        {% endfor %}
-      </tr>
+      {% for row in header %}
+        <tr>
+          {% for cell in row %}
+            <{{ cell.tag }}{{ cell.attributes }}>
+              {{- cell.content -}}
+            </{{ cell.tag }}>
+          {% endfor %}
+        </tr>
+      {% endfor %}

We should try to not break existing templates, so we should absolute not change the data passed along to a specific variable, but rather introduce a new one

Dom.’s picture

@dawehner: I don't really understand... if the 'header' variable can store multiple rows, it has one more level. Therefore, if I should keep that 'header' variable as is and add a new one, let say 'header_multirow' that would contain the new variable. Then... the template still will need an update to use that new variable. Or did I miss something ?

dawehner’s picture

@dawehner: I don't really understand... if the 'header' variable can store multiple rows, it has one more level. Therefore, if I should keep that 'header' variable as is and add a new one, let say 'header_multirow' that would contain the new variable. Then... the template still will need an update to use that new variable. Or did I miss something ?

You are right, it needs an update, when you use multiple rows, but it at least doesn't break for tables with just one header row.

Dom.’s picture

Hopefully this new patch is made here in a backward compatible way.

Yes table.html.twig are updated here too, but in a way that, if not (for instance a custom table.html.twig in a theme), the new feature won't work but the old feature with the simple header would still work without BC break.

To test it manually, you can apply the patch and run it's test: the new feature works. Then visit /admin/structure/block: it still works.
Now manually revert all table.html.twig template file. The new feature won't work, but /admin/structure/block still works => BC !

Status: Needs review » Needs work

The last submitted patch, 16: multilevel_headers--893530-14.patch, failed testing.

The last submitted patch, 16: multilevel_headers--893530-14.patch, failed testing.

Dom.’s picture

Status: Needs work » Needs review
Dom.’s picture

Change endfor to endif as appropriate.

dawehner’s picture

It would be great now if someone from the theme system team could have a look at.
@joelpittet, @laurii @cottser or someone else.

Cottser’s picture

I've only had a chance to quickly review but what would happen with an overridden table.html.twig when header_multilevel is TRUE (for example set by a contrib module)? Would it break or still show the old/standard header behaviour?

Dom.’s picture

It should be tested for double check, but I guess it would break.
Since you are using a new functionnality from 8.y you have to update your template to 8.y, otherwise, without using the new functionnality, your 8.x template still work with you 8.x table.
Threfore is it also considered as a BC break ?

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Dom.’s picture

Issue summary: View changes
FileSize
2.86 KB
Dom.’s picture

Issue tags: +DevDaysSeville

Adding issue tag, hoping for review !