123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- /**
- * @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|bool}
- */
- 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) {
- const $tables = $(e.data.context)
- .find('table.sticky-enabled')
- .once('tableheader');
- const il = $tables.length;
- for (let i = 0; i < il; i++) {
- TableHeader.tables.push(new TableHeader($tables[i]));
- }
- 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 {bool}
- */
- stickyVisible: false,
- /**
- * Create the duplicate header.
- */
- createSticky() {
- // 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"></table>')
- .css({
- visibility: 'hidden',
- position: 'fixed',
- top: '0px',
- })
- .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`;
- }
- return this.$stickyTable.css(css);
- },
- /**
- * Returns true if sticky is currently visible.
- *
- * @return {bool}
- * 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.css(
- '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 = $that.css('display');
- if (display !== 'none') {
- $stickyCell.css({ width: $that.css('width'), display });
- } else {
- $stickyCell.css('display', 'none');
- }
- }
- this.$stickyTable.css('width', this.$originalTable.outerWidth());
- },
- },
- );
- // Expose constructor in the public space.
- Drupal.TableHeader = TableHeader;
- })(jQuery, Drupal, window.Drupal.displace);
|