Skip to content
Snippets Groups Projects
Verified Commit 4609482e authored by Théodore Biadala's avatar Théodore Biadala
Browse files

Issue #3439580 by dipakmdhrm, balagan, Shriaas, catch, BramDriesen, nicxvan:...

Issue #3439580 by dipakmdhrm, balagan, Shriaas, catch, BramDriesen, nicxvan: Make drupal.tableheader only use CSS for sticky table headers

(cherry picked from commit 5b5c4ec0d749c9fa9818e760cdccbf2207dcbb2c)
parent 48c9bf36
Branches
Tags
16 merge requests!10602Issue #3438769 by vinmayiswamy, antonnavi, michelle, amateescu: Sub workspace does not clear,!10301Issue #3469309 by mstrelan, smustgrave, moshe weitzman: Use one-time login...,!10187Issue #3487488 by dakwamine: ExtensionMimeTypeGuesser::guessMimeType must support file names with "0" (zero) like foo.0.zip,!9929Issue #3445469 by pooja_sharma, smustgrave: Add additional test coverage for...,!9787Resolve issue 3479427 - bootstrap barrio issue under Windows,!9742Issue #3463908 by catch, quietone: Split OptionsFieldUiTest into two,!9526Issue #3458177 by mondrake, catch, quietone, godotislate, longwave, larowlan,...,!8949Backport .gitlabci.yml changes.,!8738Issue #3424162 by camilledavis, dineshkumarbollu, smustgrave: Claro...,!8704Make greek characters available in ckeditor5,!8533Issue #3446962 by kim.pepper: Remove incorrectly added...,!8517Issue #3443748 by NexusNovaz, smustgrave: Testcase creates false positive,!7445Issue #3440169: When using drupalGet(), provide an associative array for $headers,!6502Draft: Resolve #2938524 "Plach testing issue",!38582585169-10.1.x,!3226Issue #2987537: Custom menu link entity type should not declare "bundle" entity key
Pipeline #180912 passed with warnings
Pipeline: drupal

#180940

    Pipeline: drupal

    #180933

      Pipeline: drupal

      #180925

        +1
        Showing
        with 41 additions and 496 deletions
        ......@@ -760,15 +760,20 @@ drupal.tabledrag.ajax:
        drupal.tableheader:
        version: VERSION
        js:
        misc/tableheader.js: {}
        dependencies:
        - core/jquery
        - core/drupal
        - core/drupalSettings
        - core/once
        - core/drupal.displace
        css:
        component:
        misc/components/sticky-header.module.css: { weight: -10 }
        moved_files:
        system/base:
        deprecation_version: 10.3.0
        removed_version: 11.0.0
        deprecation_link: https://www.drupal.org/node/3440477
        css:
        component:
        css/components/sticky-header.module.css: misc/components/sticky-header.module.css
        js:
        misc/tableheader.js: false
        drupal.tableresponsive:
        version: VERSION
        js:
        ......
        ......@@ -23,8 +23,8 @@
        * - #empty: Text to display when no rows are present.
        * - #responsive: Indicates whether to add the drupal.tableresponsive library
        * providing responsive tables. Defaults to TRUE.
        * - #sticky: Indicates whether to add the drupal.tableheader library that makes
        * table headers always visible at the top of the page. Defaults to FALSE.
        * - #sticky: Indicates whether to make the table headers sticky at
        * the top of the page. Defaults to FALSE.
        * - #footer: Table footer rows, in the same format as the #rows property.
        * - #caption: A localized string for the <caption> tag.
        *
        ......@@ -420,9 +420,7 @@ public static function preRenderTable($element) {
        // Add sticky headers, if applicable.
        if (count($element['#header']) && $element['#sticky']) {
        $element['#attached']['library'][] = 'core/drupal.tableheader';
        // Add 'sticky-enabled' class to the table to identify it for JS.
        // This is needed to target tables constructed by this function.
        $element['#attributes']['class'][] = 'sticky-enabled';
        $element['#attributes']['class'][] = 'sticky-header';
        }
        // If the table has headers and it should react responsively to columns hidden
        // with the classes represented by the constants RESPONSIVE_PRIORITY_MEDIUM
        ......
        table.sticky-header thead {
        position: sticky;
        z-index: 500;
        top: var(--drupal-displace-offset-top, 0);
        }
        /**
        * @file
        * Sticky table headers.
        */
        (function ($, Drupal, displace) {
        /**
        * Constructor for the tableHeader object. Provides sticky table headers.
        *
        * TableHeader will make the current table header stick to the top of the page
        * if the table is very long.
        *
        * @constructor Drupal.TableHeader
        *
        * @param {HTMLElement} table
        * DOM object for the table to add a sticky header to.
        *
        * @listens event:columnschange
        */
        function TableHeader(table) {
        const $table = $(table);
        /**
        * @name Drupal.TableHeader#$originalTable
        *
        * @type {HTMLElement}
        */
        this.$originalTable = $table;
        /**
        * @type {jQuery}
        */
        this.$originalHeader = $table.children('thead');
        /**
        * @type {jQuery}
        */
        this.$originalHeaderCells = this.$originalHeader.find('> tr > th');
        /**
        * @type {null|boolean}
        */
        this.displayWeight = null;
        this.$originalTable.addClass('sticky-table');
        this.tableHeight = $table[0].clientHeight;
        this.tableOffset = this.$originalTable.offset();
        // React to columns change to avoid making checks in the scroll callback.
        this.$originalTable.on(
        'columnschange',
        { tableHeader: this },
        (e, display) => {
        const tableHeader = e.data.tableHeader;
        if (
        tableHeader.displayWeight === null ||
        tableHeader.displayWeight !== display
        ) {
        tableHeader.recalculateSticky();
        }
        tableHeader.displayWeight = display;
        },
        );
        // Create and display sticky header.
        this.createSticky();
        }
        // Helper method to loop through tables and execute a method.
        function forTables(method, arg) {
        const tables = TableHeader.tables;
        const il = tables.length;
        for (let i = 0; i < il; i++) {
        tables[i][method](arg);
        }
        }
        // Select and initialize sticky table headers.
        function tableHeaderInitHandler(e) {
        once('tableheader', $(e.data.context).find('table.sticky-enabled')).forEach(
        (table) => {
        TableHeader.tables.push(new TableHeader(table));
        },
        );
        forTables('onScroll');
        }
        /**
        * Attaches sticky table headers.
        *
        * @type {Drupal~behavior}
        *
        * @prop {Drupal~behaviorAttach} attach
        * Attaches the sticky table header behavior.
        */
        Drupal.behaviors.tableHeader = {
        attach(context) {
        $(window).one(
        'scroll.TableHeaderInit',
        { context },
        tableHeaderInitHandler,
        );
        },
        };
        function scrollValue(position) {
        return document.documentElement[position] || document.body[position];
        }
        function tableHeaderResizeHandler(e) {
        forTables('recalculateSticky');
        }
        function tableHeaderOnScrollHandler(e) {
        forTables('onScroll');
        }
        function tableHeaderOffsetChangeHandler(e, offsets) {
        forTables('stickyPosition', offsets.top);
        }
        // Bind event that need to change all tables.
        $(window).on({
        /**
        * When resizing table width can change, recalculate everything.
        *
        * @ignore
        */
        'resize.TableHeader': tableHeaderResizeHandler,
        /**
        * Bind only one event to take care of calling all scroll callbacks.
        *
        * @ignore
        */
        'scroll.TableHeader': tableHeaderOnScrollHandler,
        });
        // Bind to custom Drupal events.
        $(document).on({
        /**
        * Recalculate columns width when window is resized, when show/hide weight
        * is triggered, or when toolbar tray is toggled.
        *
        * @ignore
        */
        'columnschange.TableHeader drupalToolbarTrayChange':
        tableHeaderResizeHandler,
        /**
        * Recalculate TableHeader.topOffset when viewport is resized.
        *
        * @ignore
        */
        'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler,
        });
        /**
        * Store the state of TableHeader.
        */
        $.extend(
        TableHeader,
        /** @lends Drupal.TableHeader */ {
        /**
        * This will store the state of all processed tables.
        *
        * @type {Array.<Drupal.TableHeader>}
        */
        tables: [],
        },
        );
        /**
        * Extend TableHeader prototype.
        */
        $.extend(
        TableHeader.prototype,
        /** @lends Drupal.TableHeader# */ {
        /**
        * Minimum height in pixels for the table to have a sticky header.
        *
        * @type {number}
        */
        minHeight: 100,
        /**
        * Absolute position of the table on the page.
        *
        * @type {?Drupal~displaceOffset}
        */
        tableOffset: null,
        /**
        * Absolute position of the table on the page.
        *
        * @type {?number}
        */
        tableHeight: null,
        /**
        * Boolean storing the sticky header visibility state.
        *
        * @type {boolean}
        */
        stickyVisible: false,
        /**
        * Create the duplicate header.
        */
        createSticky() {
        // For caching purposes.
        this.$html = $('html');
        // Clone the table header so it inherits original jQuery properties.
        const $stickyHeader = this.$originalHeader.clone(true);
        // Hide the table to avoid a flash of the header clone upon page load.
        this.$stickyTable = $(
        '<table class="sticky-header" style="visibility: hidden; position: fixed; top: 0;"></table>',
        )
        .append($stickyHeader)
        .insertBefore(this.$originalTable);
        this.$stickyHeaderCells = $stickyHeader.find('> tr > th');
        // Initialize all computations.
        this.recalculateSticky();
        },
        /**
        * Set absolute position of sticky.
        *
        * @param {number} offsetTop
        * The top offset for the sticky header.
        * @param {number} offsetLeft
        * The left offset for the sticky header.
        *
        * @return {jQuery}
        * The sticky table as a jQuery collection.
        */
        stickyPosition(offsetTop, offsetLeft) {
        const css = {};
        if (typeof offsetTop === 'number') {
        css.top = `${offsetTop}px`;
        }
        if (typeof offsetLeft === 'number') {
        css.left = `${this.tableOffset.left - offsetLeft}px`;
        }
        this.$html[0].style.scrollPaddingTop =
        displace.offsets.top +
        (this.stickyVisible ? this.$stickyTable.height() : 0);
        Object.assign(this.$stickyTable[0].style, css);
        return this.$stickyTable;
        },
        /**
        * Returns true if sticky is currently visible.
        *
        * @return {boolean}
        * The visibility status.
        */
        checkStickyVisible() {
        const scrollTop = scrollValue('scrollTop');
        const tableTop = this.tableOffset.top - displace.offsets.top;
        const tableBottom = tableTop + this.tableHeight;
        let visible = false;
        if (tableTop < scrollTop && scrollTop < tableBottom - this.minHeight) {
        visible = true;
        }
        this.stickyVisible = visible;
        return visible;
        },
        /**
        * Check if sticky header should be displayed.
        *
        * This function is throttled to once every 250ms to avoid unnecessary
        * calls.
        *
        * @param {jQuery.Event} e
        * The scroll event.
        */
        onScroll(e) {
        this.checkStickyVisible();
        // Track horizontal positioning relative to the viewport.
        this.stickyPosition(null, scrollValue('scrollLeft'));
        this.$stickyTable[0].style.visibility = this.stickyVisible
        ? 'visible'
        : 'hidden';
        },
        /**
        * Event handler: recalculates position of the sticky table header.
        *
        * @param {jQuery.Event} event
        * Event being triggered.
        */
        recalculateSticky(event) {
        // Update table size.
        this.tableHeight = this.$originalTable[0].clientHeight;
        // Update offset top.
        displace.offsets.top = displace.calculateOffset('top');
        this.tableOffset = this.$originalTable.offset();
        this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
        // Update columns width.
        let $that = null;
        let $stickyCell = null;
        let display = null;
        // Resize header and its cell widths.
        // Only apply width to visible table cells. This prevents the header from
        // displaying incorrectly when the sticky header is no longer visible.
        const il = this.$originalHeaderCells.length;
        for (let i = 0; i < il; i++) {
        $that = $(this.$originalHeaderCells[i]);
        $stickyCell = this.$stickyHeaderCells.eq($that.index());
        display = window.getComputedStyle($that[0]).display;
        if (display !== 'none') {
        Object.assign($stickyCell[0].style, {
        width: window.getComputedStyle($that[0]).width,
        display,
        });
        } else {
        $stickyCell[0].style.display = 'none';
        }
        }
        this.$stickyTable[0].style.width = `${this.$originalTable.outerWidth()}px`;
        },
        },
        );
        // Expose constructor in the public space.
        Drupal.TableHeader = TableHeader;
        })(jQuery, Drupal, window.Drupal.displace);
        /**
        * @file
        * Table header behavior.
        *
        * @see tableheader.js
        */
        table.sticky-header {
        z-index: 500;
        top: 0;
        margin-top: 0;
        background-color: #fff;
        }
        ......@@ -15,7 +15,6 @@ base:
        css/components/position-container.module.css: { weight: -10 }
        css/components/reset-appearance.module.css: { weight: -10 }
        css/components/resize.module.css: { weight: -10 }
        css/components/sticky-header.module.css: { weight: -10 }
        css/components/system-status-counter.css: { weight: -10 }
        css/components/system-status-report-counters.css: { weight: -10 }
        css/components/system-status-report-general-info.css: { weight: -10 }
        ......
        ......@@ -37,7 +37,7 @@
        set classes = [
        'cols-' ~ header|length,
        responsive ? 'responsive-enabled',
        sticky ? 'sticky-enabled',
        sticky ? 'sticky-header',
        ]
        %}
        <table{{ attributes.addClass(classes) }}>
        ......
        ......@@ -37,7 +37,7 @@
        'views-view-table',
        'cols-' ~ header|length,
        responsive ? 'responsive-enabled',
        sticky ? 'sticky-enabled',
        sticky ? 'sticky-header',
        ]
        %}
        <table{{ attributes.addClass(classes) }}>
        ......
        ......@@ -21,7 +21,7 @@ class TableTest extends KernelTestBase {
        protected static $modules = ['system', 'form_test'];
        /**
        * Tableheader.js provides 'sticky' table headers, and is included by default.
        * If $sticky is TRUE, `sticky-header` class should be included.
        */
        public function testThemeTableStickyHeaders() {
        $header = ['one', 'two', 'three'];
        ......@@ -33,14 +33,11 @@ public function testThemeTableStickyHeaders() {
        '#sticky' => TRUE,
        ];
        $this->render($table);
        // Make sure tableheader.js was attached.
        $tableheader = $this->xpath("//script[contains(@src, 'tableheader.js')]");
        $this->assertCount(1, $tableheader);
        $this->assertRaw('sticky-enabled');
        $this->assertRaw('sticky-header');
        }
        /**
        * If $sticky is FALSE, no tableheader.js should be included.
        * If $sticky is FALSE, `sticky-header` class should not be included.
        */
        public function testThemeTableNoStickyHeaders() {
        $header = ['one', 'two', 'three'];
        ......@@ -58,10 +55,7 @@ public function testThemeTableNoStickyHeaders() {
        '#sticky' => FALSE,
        ];
        $this->render($table);
        // Make sure tableheader.js was not attached.
        $tableheader = $this->xpath("//script[contains(@src, 'tableheader.js')]");
        $this->assertCount(0, $tableheader);
        $this->assertNoRaw('sticky-enabled');
        $this->assertNoRaw('sticky-header');
        }
        /**
        ......
        <?php
        declare(strict_types=1);
        namespace Drupal\KernelTests\Core\Theme;
        use Drupal\claro\ClaroPreRender;
        use Drupal\KernelTests\KernelTestBase;
        /**
        * Tests Claro specific table functionality.
        *
        * @group Theme
        */
        class ClaroTableTest extends KernelTestBase {
        /**
        * {@inheritdoc}
        */
        protected static $modules = ['system'];
        /**
        * Confirm that Claro tables override use of the `sticky-enabled` class.
        */
        public function testThemeTableStickyHeaders() {
        // Enable the Claro theme.
        \Drupal::service('theme_installer')->install(['claro']);
        $this->config('system.theme')->set('default', 'claro')->save();
        $header = ['one', 'two', 'three'];
        $rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
        $table = [
        '#type' => 'table',
        '#header' => $header,
        '#rows' => $rows,
        '#sticky' => TRUE,
        ];
        $this->render($table);
        // Confirm that position-sticky is used instead of sticky-enabled.
        $this->assertNoRaw('sticky-enabled');
        $this->assertRaw('position-sticky');
        }
        /**
        * Confirm Claro prerender callback is not executed for non-array class.
        */
        public function testThemeTablePositionStickyPreRender(): void {
        // Enable the Claro theme.
        \Drupal::service('theme_installer')->install(['claro']);
        $this->config('system.theme')->set('default', 'claro')->save();
        $header = ['one', 'two', 'three'];
        $rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
        $table = [
        '#type' => 'table',
        '#header' => $header,
        '#rows' => $rows,
        '#sticky' => TRUE,
        '#attributes' => [
        'class' => 'class',
        ],
        '#pre_render' => [
        [
        ClaroPreRender::class,
        'tablePositionSticky',
        ],
        ],
        ];
        $renderedTable = (string) \Drupal::service('renderer')->renderRoot($table);
        // Confirm that table is rendered.
        $this->assertStringContainsString('class="class"', $renderedTable);
        }
        }
        ......@@ -295,11 +295,6 @@ function claro_element_info_alter(&$type) {
        if (isset($type['status_messages'])) {
        $type['status_messages']['#pre_render'][] = [ClaroPreRender::class, 'messagePlaceholder'];
        }
        // Add a pre-render to tables to use CSS for sticky positioning.
        if (isset($type['table'])) {
        $type['table']['#pre_render'][] = [ClaroPreRender::class, 'tablePositionSticky'];
        }
        }
        /**
        ......
        ......@@ -130,9 +130,3 @@ tr.selected td {
        width: auto;
        }
        }
        .position-sticky thead {
        position: sticky;
        z-index: 500;
        top: var(--drupal-displace-offset-top, 0);
        }
        ......@@ -116,9 +116,3 @@ tr.selected td {
        width: auto;
        }
        }
        .position-sticky thead {
        position: sticky;
        z-index: 500;
        top: var(--drupal-displace-offset-top, 0);
        }
        ......@@ -189,17 +189,6 @@ public static function messagePlaceholder(array $element) {
        return $element;
        }
        /**
        * Prerender callback for table elements.
        */
        public static function tablePositionSticky(array $element) {
        if (isset($element['#attributes']['class']) && is_array($element['#attributes']['class']) && in_array('sticky-enabled', $element['#attributes']['class'], TRUE)) {
        unset($element['#attributes']['class'][array_search('sticky-enabled', $element['#attributes']['class'])]);
        $element['#attributes']['class'][] = 'position-sticky';
        }
        return $element;
        }
        /**
        * {@inheritdoc}
        */
        ......@@ -211,7 +200,6 @@ public static function trustedCallbacks() {
        'container',
        'textFormat',
        'messagePlaceholder',
        'tablePositionSticky',
        ];
        }
        ......
        ......@@ -37,7 +37,7 @@
        'views-view-table',
        'cols-' ~ header|length,
        responsive ? 'responsive-enabled',
        sticky ? 'position-sticky',
        sticky ? 'position-sticky sticky-header',
        ]
        %}
        <table{{ attributes.addClass(classes) }}>
        ......
        ......@@ -38,7 +38,7 @@
        'views-table',
        'cols-' ~ header|length,
        responsive ? 'responsive-enabled',
        sticky ? 'sticky-enabled',
        sticky ? 'sticky-header',
        ]
        %}
        <table{{ attributes.addClass(classes) }}>
        ......
        table.sticky-header thead {
        position: sticky;
        z-index: 500;
        top: var(--drupal-displace-offset-top, 0);
        }
        /**
        * @file
        * Table header behavior.
        *
        * @see tableheader.js
        */
        table.sticky-header {
        z-index: 500;
        top: 0;
        margin-top: 0;
        background-color: #fff;
        }
        ......@@ -96,6 +96,11 @@ libraries-override:
        misc/components/tabledrag.module.css: css/core/components/tabledrag.module.css
        misc/components/tree-child.module.css: css/core/components/tree-child.module.css
        core/drupal.tableheader:
        css:
        component:
        misc/components/sticky-header.module.css: css/core/components/sticky-header.module.css
        core/drupal.vertical-tabs:
        css:
        component:
        ......@@ -243,7 +248,6 @@ libraries-override:
        css/components/position-container.module.css: css/system/components/position-container.module.css
        css/components/reset-appearance.module.css: css/system/components/reset-appearance.module.css
        css/components/resize.module.css: css/system/components/resize.module.css
        css/components/sticky-header.module.css: css/system/components/sticky-header.module.css
        css/components/system-status-counter.css: css/system/components/system-status-counter.css
        css/components/system-status-report-counters.css: css/system/components/system-status-report-counters.css
        css/components/system-status-report-general-info.css: css/system/components/system-status-report-general-info.css
        ......
        ......@@ -35,7 +35,7 @@
        set classes = [
        'cols-' ~ header|length,
        responsive ? 'responsive-enabled',
        sticky ? 'sticky-enabled',
        sticky ? 'sticky-enabled sticky-header',
        ]
        %}
        <table{{ attributes.addClass(classes) }}>
        ......
        0% Loading or .
        You are about to add 0 people to the discussion. Proceed with caution.
        Please register or to comment