Add support for tables with two headers in views table display

Created on 21 July 2016, over 8 years ago
Updated 31 May 2024, 6 months ago

Problem/Motivation

For accessibility, some tables call for having both column headers and row headers. The WAI table concepts tutorial describes a number of table models, with corresponding example use cases.

The tables currently generated by views are all tables with one header. These are quite acceptable for many cases, including many of the administrative views in Drupal core.

This feature request aims to support simple tables with two headers, with accessible markup. Such tables have <th scope="col"> in the <thead>, and a <th scope="row"> as a header inside each <tr> row, as appropriate. This is primarily intended as a feature for site builders, though future administration tables provided by by core or contrib modules may also benefit from it.

Recently #1831162: Tables generated by Views need better semantics. added the scope="col" for column header elements, but did not add any support for row headers.

Proposed resolution

A configuration option that allows the site builder to set any column in a table to be treated as a table header for that row. The site builder will also have the option not to have any row header, producing only column headers. Note that this approach is flexible enough to support tables with an offset column of header cells.

Note: supporting tables with irregular headers or tables with multi-level headers is NOT in scope for this issue. If Multi level (multirow) header for table FAPI element Needs work gets into core, we can create separate feature request issues for these.

Table format settings showing new Row Header column. Title field has been selected:

Content list view output with title field selected as the row header (Seven Theme):



Note: the column header styles apply to the row header field on hover (see comment #21)

Content list view output with title field selected as the row header (Bartik Theme):

Selecting a field as the row header causes that field to be rendered as a <th> tag inside the table row:

Remaining tasks

  • DONE. Usability review: do we use a column of radio buttons or a drop-down select? See comment #33, @yoroy has a preference for a column of radio buttons, but it doesn't seem like a strong preference either way.
  • DONE. Add a setting to the Views table display plugin config form.
  • DONE. Update markup in output.
  • DONE. Review design impact for Bartik, styling of row-headers. See comments #22 and #33, @yoroy is happy.
  • DONE. Review design impact for Seven, styling of row-headers. See comments #21 and #33, @yoroy's comment could do with clarifying. This change also impacts other themes that ship with core; Bartik, Seven, Classy, Olivero, Claro
  • DONE. Write tests. Rough plan outlined in comment #65. @lendude added tests in #65 and subsequent rerolls adopted this test.
  • TODO: Update existing views which use the table display plugin, to include the new config option (set to none). See comment #67.
  • DONE: A change record with advice for themers:
    • If they already have a custom views.view-table.html.twig template, it will need updated to benefit from this feature.
    • Turning on the new row-level headers option may have an unexpected effect on how tables look, if their theme doesn't already anticipate row-level table headers.

User interface changes

  • An additional configuration in the views style format. Either of these:
    • An extra column of radio buttons in the settings table, similar to the default-sort column (screenshot in comment #16)
    • A drop-down select, which has a description (screenshot in comment #30)
  • (Possibly) a different appearance for table headers on each row (if design review permits it).

We do not propose to change the configuration of any existing admin views tables in core as yet.

API changes

None.

Data model changes

Additional "row header" option in Views table display config, similar to the existing default-sort option.

Original report by @gettysburger

Accessible tables have "scope=col" in the headers and "scope=row" in the first field in a row. The first field in the row should also have a <th> instead of a <td> tag. Tables generated by Views have scope=col" but not scope ="row". A recent issue ( https://www.drupal.org/node/1831162 ) added the "scope=col" but did not add "scope=row".

I proposed that "scope=row" be added to the first field in each table row and that first field in the row should also have a <th> instead of a <td> tag.

Feature request
Status

Needs work

Version

11.0 🔥

Component
Views 

Last updated about 2 hours ago

Created by

🇺🇸United States gettysburger

Live updates comments and jobs are added and updated live.
  • Usability

    Makes Drupal easier to use. Preferred over UX, D7UX, etc.

  • Needs accessibility review

    Used to alert the accessibility topic maintainer(s) that an issue significantly affects (or has the potential to affect) the accessibility of Drupal, and their signoff is needed (see the governance policy draft for more information). Useful links: Drupal's accessibility standards, the Drupal Core accessibility gate.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • 🇮🇳India pooja saraah Chennai

    Fixed failed commands on #120
    Attached patch against Drupal 10.1.x

  • Status changed to Needs review almost 2 years ago
  • Status changed to Needs work over 1 year ago
  • 🇳🇱Netherlands Lendude Amsterdam

    Still needs test coverage for the Views UI working.

    1. +++ b/core/modules/views/tests/src/Functional/Plugin/StyleTableTest.php
      @@ -242,6 +242,24 @@ public function testGrouping() {
      +    $this->assertNotEmpty($this->cssSelect('tbody th[scope="row"]'));
      

      This is still too minimal, we should actually check what is in it

    2. +++ b/core/themes/olivero/templates/views/views-view-table.html.twig
      index a26eaeba85..a212ef8dcc 100644
      --- a/core/themes/stable9/templates/views/views-view-table.html.twig
      
      --- a/core/themes/stable9/templates/views/views-view-table.html.twig
      +++ b/core/themes/stable9/templates/views/views-view-table.html.twig
      

      I don't think we can change these here templates since they are 'stable'

    And looking at this again, we should add an upgrade path to set the default value for existing Views so people don't get unrelated changes later when they change a View

  • 🇺🇸United States theMusician

    Thank you for the feedback Lendude. Items 1 and 2 I believe I have knowledge of how to address moving forward.

    Regarding an upgrade path for existing views, in the proposed patch this is set in Views/Style/Table.php,

    $options['row_header'] = ['default' => ' '];

    For existing views, where would I look to provide an upgrade path? In local testing, existing views seem to show the new row header option but not have it selected. I am guessing I am missing something obvious.

    I appreciate any guidance you can offer.

    Thank you

  • 🇺🇸United States theMusician

    A new patch that does a more robust check for scope=row on output.

    Removal of changes to stable9.

    Thank you @CarlyGerard and @packern for team programming on this patch.

  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & MySQL 5.7
    last update over 1 year ago
    Custom Commands Failed
  • 🇺🇸United States theMusician

    Missed the code formatting fixes. Trying again.

  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & MySQL 5.7
    last update over 1 year ago
    29,379 pass
  • Status changed to Needs review over 1 year ago
  • 🇺🇸United States theMusician

    I believe the new patch satisfies the concerns raised earlier in the discussion.

  • Status changed to Needs work over 1 year ago
  • 🇺🇸United States smustgrave

    Cleaned up the tags some.

    Seems #124 still needs to be addressed.

    For the upgrade path it will, I believe,
    1. need to load all views
    2. Check if they are using table
    3. Add this setting even if it's unchecked

    Idea being if this patch is applied and I open a view with a table. If I make 0 changes and click save and configuration is shown as changed because of this new setting then that would need upgrade path.

    Upgrade test can be simple
    Check a view table that this setting doesn't exist
    Run updates
    Check setting now exists.

  • 🇺🇸United States CarlyGerard

    Adds upgrade path test to views where if not set, set header to "none." The interdiff showed a template also removed a classy template--I think in 11.x-dev the classy files are removed, unless someone can confirm otherwise?

  • last update about 1 year ago
    Patch Failed to Apply
  • 🇺🇸United States CarlyGerard

    Re-rolled patch from comment #131 Add support for tables with two headers in views table display Needs work since it couldn't apply.

  • last update about 1 year ago
    Custom Commands Failed
  • 🇺🇸United States CarlyGerard

    Fixing custom commands error from ViewsConfigUpdater.php in #132 Add support for tables with two headers in views table display Needs work , where the bool value wasn't returned properly given the function parameter.

  • last update about 1 year ago
    30,324 pass, 62 fail
  • 🇺🇸United States CarlyGerard

    Fixes undefined array key issue flagged from ViewsConfigUpdater.php in last CI testing.

  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 5.3 & MySQL 5.5
    last update about 1 year ago
    Composer error. Unable to continue.
  • last update about 1 year ago
    30,487 pass
  • Status changed to Needs review about 1 year ago
  • 🇺🇸United States theMusician

    Hooray. Passing tests again and they are more comprehensive. I believe this checks all the tests described in comment #129.

  • 🇺🇸United States theMusician

    The change record has been updated to reflect the current passing patch.
    https://www.drupal.org/node/3263530

  • Status changed to Needs work 12 months ago
  • 🇳🇱Netherlands Lendude Amsterdam
    +++ b/core/modules/views/views.post_update.php
    @@ -137,3 +137,14 @@ function views_post_update_taxonomy_filter_user_context(?array &$sandbox = NULL)
    +    return $view_config_updater->setRowHeaderOption($view);
    

    This should follow the pattern of 'needsABC' and further changes to ConfigEntityUpdater so it will also update newly imported config when installing a module like we do in other Views updates.

    Also we need a test for the upgrade path to make sure it actually works

  • 🇺🇸United States theMusician

    As always, thank you for the feedback and guidance.

    On my dev instance, there is an upgrade test in theory that operates but I can't get views actually to run an update hook.

    A new file in tests/fixtures/update exists called set_row_header_option.php

    It looks like

    
    /**
     * @file
     * Test fixture.
     */
    
    use Drupal\Core\Database\Database;
    use Drupal\Core\Serialization\Yaml;
    
    $connection = Database::getConnection();
    
    $connection->insert('config')
      ->fields([
        'collection' => '',
        'name' => 'views.view.test_set_row_header_option',
        'data' => serialize(Yaml::decode(file_get_contents('core/modules/views/tests/fixtures/update/views.view.test_set_row_header_option.yml'))),
      ])
      ->execute();
    

    and then in tests/src/Functional/Update/ViewsSetRowHeaderUpdateTest.php I try to run the test which includes firing the updates using runUpdates(); which as I understand it runs update hooks. It is modeled after ViewsFixRevisionUpdateTest.php in the same directory.

    
        /**
       * Tests the upgrade path for adding row header option.
       */
      public function testViewsPostUpdateSetRowHeaderOption() {
        $view = View::load('test_set_row_header_option');
        $data = $view->toArray();
    
        // Assert no row header exists
        $this->assertArrayNotHasKey('row_header', $data['display']['default']['display_options']['style']['options']);
    
        $this->runUpdates();
    
        $view = View::load('test_set_row_header_option');
        $data = $view->toArray();
    
        // Assert row header option exists, return values
        $this->assertArrayHasKey('row_header', $data['display']['default']['display_options']['style']['options']);
      }
    
    

    which I think means the update hook needs to be in views.install. I have tried various iterations but when runUpdates() executes it doesn't apply any changes. Is there a different spot that updates go to when running tests? I can't find any views updates that would correspond to the other tests in /Tests/Src/Functional/Update.

    I don't get any errors in the tests except that after running the updates, the new key cannot be found which tells me the hook_update I added into views.install is not running properly. If I introduce a syntax error into the hook_update, the test will fail and point out the syntax error, so it does seem like it wants the code to be in that spot.

    Thank you for any hints.

  • 🇺🇸United States theMusician

    I believe the code for ViewsConfigUpdater needs to be called https://git.drupalcode.org/project/drupal/-/blob/96d21a4603df23080a34051... in updateAll of viewsConfigUpdater for the test runUpdates() function to work.

Production build 0.71.5 2024