summaryrefslogtreecommitdiffstatshomepage
path: root/core/misc/tableheader.js
diff options
context:
space:
mode:
Diffstat (limited to 'core/misc/tableheader.js')
-rw-r--r--core/misc/tableheader.js366
1 files changed, 267 insertions, 99 deletions
diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js
index 35eef305737..1d4eca7e69e 100644
--- a/core/misc/tableheader.js
+++ b/core/misc/tableheader.js
@@ -1,57 +1,105 @@
/**
-* DO NOT EDIT THIS FILE.
-* See the following change record for more information,
-* https://www.drupal.org/node/2815083
-* @preserve
-**/
+ * @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();
- this.$originalTable.on('columnschange', {
- tableHeader: this
- }, (e, display) => {
- const tableHeader = e.data.tableHeader;
- if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) {
- tableHeader.recalculateSticky();
- }
+ // 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;
+ },
+ );
- 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));
- });
+ 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);
- }
-
+ $(window).one(
+ 'scroll.TableHeaderInit',
+ { context },
+ tableHeaderInitHandler,
+ );
+ },
};
function scrollValue(position) {
@@ -70,98 +118,218 @@
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,
- 'scroll.TableHeader': tableHeaderOnScrollHandler
+
+ /**
+ * Bind only one event to take care of calling all scroll callbacks.
+ *
+ * @ignore
+ */
+ 'scroll.TableHeader': tableHeaderOnScrollHandler,
});
+ // Bind to custom Drupal events.
$(document).on({
- 'columnschange.TableHeader drupalToolbarTrayChange': tableHeaderResizeHandler,
- 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler
- });
- $.extend(TableHeader, {
- tables: []
+ /**
+ * 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,
});
- $.extend(TableHeader.prototype, {
- minHeight: 100,
- tableOffset: null,
- tableHeight: null,
- stickyVisible: false,
-
- createSticky() {
- this.$html = $('html');
- const $stickyHeader = this.$originalHeader.clone(true);
- 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');
- this.recalculateSticky();
+
+ /**
+ * Store the state of TableHeader.
+ */
+ $.extend(
+ TableHeader,
+ /** @lends Drupal.TableHeader */ {
+ /**
+ * This will store the state of all processed tables.
+ *
+ * @type {Array.<Drupal.TableHeader>}
+ */
+ tables: [],
},
+ );
- stickyPosition(offsetTop, offsetLeft) {
- const css = {};
+ /**
+ * 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,
- if (typeof offsetTop === 'number') {
- css.top = `${offsetTop}px`;
- }
+ /**
+ * Absolute position of the table on the page.
+ *
+ * @type {?Drupal~displaceOffset}
+ */
+ tableOffset: null,
- if (typeof offsetLeft === 'number') {
- css.left = `${this.tableOffset.left - offsetLeft}px`;
- }
+ /**
+ * Absolute position of the table on the page.
+ *
+ * @type {?number}
+ */
+ tableHeight: null,
- this.$html.css('scroll-padding-top', displace.offsets.top + (this.stickyVisible ? this.$stickyTable.height() : 0));
- return this.$stickyTable.css(css);
- },
+ /**
+ * Boolean storing the sticky header visibility state.
+ *
+ * @type {bool}
+ */
+ stickyVisible: false,
- checkStickyVisible() {
- const scrollTop = scrollValue('scrollTop');
- const tableTop = this.tableOffset.top - displace.offsets.top;
- const tableBottom = tableTop + this.tableHeight;
- let visible = 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"></table>')
+ .css({
+ visibility: 'hidden',
+ position: 'fixed',
+ top: '0px',
+ })
+ .append($stickyHeader)
+ .insertBefore(this.$originalTable);
- if (tableTop < scrollTop && scrollTop < tableBottom - this.minHeight) {
- visible = true;
- }
+ this.$stickyHeaderCells = $stickyHeader.find('> tr > th');
- this.stickyVisible = visible;
- return visible;
- },
+ // Initialize all computations.
+ this.recalculateSticky();
+ },
- onScroll(e) {
- this.checkStickyVisible();
- this.stickyPosition(null, scrollValue('scrollLeft'));
- this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden');
- },
+ /**
+ * 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.css(
+ 'scroll-padding-top',
+ displace.offsets.top +
+ (this.stickyVisible ? this.$stickyTable.height() : 0),
+ );
+ return this.$stickyTable.css(css);
+ },
- recalculateSticky(event) {
- this.tableHeight = this.$originalTable[0].clientHeight;
- displace.offsets.top = displace.calculateOffset('top');
- this.tableOffset = this.$originalTable.offset();
- this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
- let $that = null;
- let $stickyCell = null;
- let display = null;
- 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');
+ /**
+ * 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.$stickyTable.css('width', this.$originalTable.outerWidth());
- }
+ 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); \ No newline at end of file
+})(jQuery, Drupal, window.Drupal.displace);