annotate core/misc/tableheader.es6.js @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children a9cd425dd02b
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * Sticky table headers.
Chris@0 4 */
Chris@0 5
Chris@0 6 (function ($, Drupal, displace) {
Chris@0 7 /**
Chris@0 8 * Attaches sticky table headers.
Chris@0 9 *
Chris@0 10 * @type {Drupal~behavior}
Chris@0 11 *
Chris@0 12 * @prop {Drupal~behaviorAttach} attach
Chris@0 13 * Attaches the sticky table header behavior.
Chris@0 14 */
Chris@0 15 Drupal.behaviors.tableHeader = {
Chris@0 16 attach(context) {
Chris@0 17 $(window).one('scroll.TableHeaderInit', { context }, tableHeaderInitHandler);
Chris@0 18 },
Chris@0 19 };
Chris@0 20
Chris@0 21 function scrollValue(position) {
Chris@0 22 return document.documentElement[position] || document.body[position];
Chris@0 23 }
Chris@0 24
Chris@0 25 // Select and initialize sticky table headers.
Chris@0 26 function tableHeaderInitHandler(e) {
Chris@0 27 const $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');
Chris@0 28 const il = $tables.length;
Chris@0 29 for (let i = 0; i < il; i++) {
Chris@0 30 TableHeader.tables.push(new TableHeader($tables[i]));
Chris@0 31 }
Chris@0 32 forTables('onScroll');
Chris@0 33 }
Chris@0 34
Chris@0 35 // Helper method to loop through tables and execute a method.
Chris@0 36 function forTables(method, arg) {
Chris@0 37 const tables = TableHeader.tables;
Chris@0 38 const il = tables.length;
Chris@0 39 for (let i = 0; i < il; i++) {
Chris@0 40 tables[i][method](arg);
Chris@0 41 }
Chris@0 42 }
Chris@0 43
Chris@0 44 function tableHeaderResizeHandler(e) {
Chris@0 45 forTables('recalculateSticky');
Chris@0 46 }
Chris@0 47
Chris@0 48 function tableHeaderOnScrollHandler(e) {
Chris@0 49 forTables('onScroll');
Chris@0 50 }
Chris@0 51
Chris@0 52 function tableHeaderOffsetChangeHandler(e, offsets) {
Chris@0 53 forTables('stickyPosition', offsets.top);
Chris@0 54 }
Chris@0 55
Chris@0 56 // Bind event that need to change all tables.
Chris@0 57 $(window).on({
Chris@0 58
Chris@0 59 /**
Chris@0 60 * When resizing table width can change, recalculate everything.
Chris@0 61 *
Chris@0 62 * @ignore
Chris@0 63 */
Chris@0 64 'resize.TableHeader': tableHeaderResizeHandler,
Chris@0 65
Chris@0 66 /**
Chris@0 67 * Bind only one event to take care of calling all scroll callbacks.
Chris@0 68 *
Chris@0 69 * @ignore
Chris@0 70 */
Chris@0 71 'scroll.TableHeader': tableHeaderOnScrollHandler,
Chris@0 72 });
Chris@0 73 // Bind to custom Drupal events.
Chris@0 74 $(document).on({
Chris@0 75
Chris@0 76 /**
Chris@0 77 * Recalculate columns width when window is resized and when show/hide
Chris@0 78 * weight is triggered.
Chris@0 79 *
Chris@0 80 * @ignore
Chris@0 81 */
Chris@0 82 'columnschange.TableHeader': tableHeaderResizeHandler,
Chris@0 83
Chris@0 84 /**
Chris@0 85 * Recalculate TableHeader.topOffset when viewport is resized.
Chris@0 86 *
Chris@0 87 * @ignore
Chris@0 88 */
Chris@0 89 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler,
Chris@0 90 });
Chris@0 91
Chris@0 92 /**
Chris@0 93 * Constructor for the tableHeader object. Provides sticky table headers.
Chris@0 94 *
Chris@0 95 * TableHeader will make the current table header stick to the top of the page
Chris@0 96 * if the table is very long.
Chris@0 97 *
Chris@0 98 * @constructor Drupal.TableHeader
Chris@0 99 *
Chris@0 100 * @param {HTMLElement} table
Chris@0 101 * DOM object for the table to add a sticky header to.
Chris@0 102 *
Chris@0 103 * @listens event:columnschange
Chris@0 104 */
Chris@0 105 function TableHeader(table) {
Chris@0 106 const $table = $(table);
Chris@0 107
Chris@0 108 /**
Chris@0 109 * @name Drupal.TableHeader#$originalTable
Chris@0 110 *
Chris@0 111 * @type {HTMLElement}
Chris@0 112 */
Chris@0 113 this.$originalTable = $table;
Chris@0 114
Chris@0 115 /**
Chris@0 116 * @type {jQuery}
Chris@0 117 */
Chris@0 118 this.$originalHeader = $table.children('thead');
Chris@0 119
Chris@0 120 /**
Chris@0 121 * @type {jQuery}
Chris@0 122 */
Chris@0 123 this.$originalHeaderCells = this.$originalHeader.find('> tr > th');
Chris@0 124
Chris@0 125 /**
Chris@0 126 * @type {null|bool}
Chris@0 127 */
Chris@0 128 this.displayWeight = null;
Chris@0 129 this.$originalTable.addClass('sticky-table');
Chris@0 130 this.tableHeight = $table[0].clientHeight;
Chris@0 131 this.tableOffset = this.$originalTable.offset();
Chris@0 132
Chris@0 133 // React to columns change to avoid making checks in the scroll callback.
Chris@0 134 this.$originalTable.on('columnschange', { tableHeader: this }, (e, display) => {
Chris@0 135 const tableHeader = e.data.tableHeader;
Chris@0 136 if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) {
Chris@0 137 tableHeader.recalculateSticky();
Chris@0 138 }
Chris@0 139 tableHeader.displayWeight = display;
Chris@0 140 });
Chris@0 141
Chris@0 142 // Create and display sticky header.
Chris@0 143 this.createSticky();
Chris@0 144 }
Chris@0 145
Chris@0 146 /**
Chris@0 147 * Store the state of TableHeader.
Chris@0 148 */
Chris@0 149 $.extend(TableHeader, /** @lends Drupal.TableHeader */{
Chris@0 150
Chris@0 151 /**
Chris@0 152 * This will store the state of all processed tables.
Chris@0 153 *
Chris@0 154 * @type {Array.<Drupal.TableHeader>}
Chris@0 155 */
Chris@0 156 tables: [],
Chris@0 157 });
Chris@0 158
Chris@0 159 /**
Chris@0 160 * Extend TableHeader prototype.
Chris@0 161 */
Chris@0 162 $.extend(TableHeader.prototype, /** @lends Drupal.TableHeader# */{
Chris@0 163
Chris@0 164 /**
Chris@0 165 * Minimum height in pixels for the table to have a sticky header.
Chris@0 166 *
Chris@0 167 * @type {number}
Chris@0 168 */
Chris@0 169 minHeight: 100,
Chris@0 170
Chris@0 171 /**
Chris@0 172 * Absolute position of the table on the page.
Chris@0 173 *
Chris@0 174 * @type {?Drupal~displaceOffset}
Chris@0 175 */
Chris@0 176 tableOffset: null,
Chris@0 177
Chris@0 178 /**
Chris@0 179 * Absolute position of the table on the page.
Chris@0 180 *
Chris@0 181 * @type {?number}
Chris@0 182 */
Chris@0 183 tableHeight: null,
Chris@0 184
Chris@0 185 /**
Chris@0 186 * Boolean storing the sticky header visibility state.
Chris@0 187 *
Chris@0 188 * @type {bool}
Chris@0 189 */
Chris@0 190 stickyVisible: false,
Chris@0 191
Chris@0 192 /**
Chris@0 193 * Create the duplicate header.
Chris@0 194 */
Chris@0 195 createSticky() {
Chris@0 196 // Clone the table header so it inherits original jQuery properties.
Chris@0 197 const $stickyHeader = this.$originalHeader.clone(true);
Chris@0 198 // Hide the table to avoid a flash of the header clone upon page load.
Chris@0 199 this.$stickyTable = $('<table class="sticky-header"/>')
Chris@0 200 .css({
Chris@0 201 visibility: 'hidden',
Chris@0 202 position: 'fixed',
Chris@0 203 top: '0px',
Chris@0 204 })
Chris@0 205 .append($stickyHeader)
Chris@0 206 .insertBefore(this.$originalTable);
Chris@0 207
Chris@0 208 this.$stickyHeaderCells = $stickyHeader.find('> tr > th');
Chris@0 209
Chris@0 210 // Initialize all computations.
Chris@0 211 this.recalculateSticky();
Chris@0 212 },
Chris@0 213
Chris@0 214 /**
Chris@0 215 * Set absolute position of sticky.
Chris@0 216 *
Chris@0 217 * @param {number} offsetTop
Chris@0 218 * The top offset for the sticky header.
Chris@0 219 * @param {number} offsetLeft
Chris@0 220 * The left offset for the sticky header.
Chris@0 221 *
Chris@0 222 * @return {jQuery}
Chris@0 223 * The sticky table as a jQuery collection.
Chris@0 224 */
Chris@0 225 stickyPosition(offsetTop, offsetLeft) {
Chris@0 226 const css = {};
Chris@0 227 if (typeof offsetTop === 'number') {
Chris@0 228 css.top = `${offsetTop}px`;
Chris@0 229 }
Chris@0 230 if (typeof offsetLeft === 'number') {
Chris@0 231 css.left = `${this.tableOffset.left - offsetLeft}px`;
Chris@0 232 }
Chris@0 233 return this.$stickyTable.css(css);
Chris@0 234 },
Chris@0 235
Chris@0 236 /**
Chris@0 237 * Returns true if sticky is currently visible.
Chris@0 238 *
Chris@0 239 * @return {bool}
Chris@0 240 * The visibility status.
Chris@0 241 */
Chris@0 242 checkStickyVisible() {
Chris@0 243 const scrollTop = scrollValue('scrollTop');
Chris@0 244 const tableTop = this.tableOffset.top - displace.offsets.top;
Chris@0 245 const tableBottom = tableTop + this.tableHeight;
Chris@0 246 let visible = false;
Chris@0 247
Chris@0 248 if (tableTop < scrollTop && scrollTop < (tableBottom - this.minHeight)) {
Chris@0 249 visible = true;
Chris@0 250 }
Chris@0 251
Chris@0 252 this.stickyVisible = visible;
Chris@0 253 return visible;
Chris@0 254 },
Chris@0 255
Chris@0 256 /**
Chris@0 257 * Check if sticky header should be displayed.
Chris@0 258 *
Chris@0 259 * This function is throttled to once every 250ms to avoid unnecessary
Chris@0 260 * calls.
Chris@0 261 *
Chris@0 262 * @param {jQuery.Event} e
Chris@0 263 * The scroll event.
Chris@0 264 */
Chris@0 265 onScroll(e) {
Chris@0 266 this.checkStickyVisible();
Chris@0 267 // Track horizontal positioning relative to the viewport.
Chris@0 268 this.stickyPosition(null, scrollValue('scrollLeft'));
Chris@0 269 this.$stickyTable.css('visibility', this.stickyVisible ? 'visible' : 'hidden');
Chris@0 270 },
Chris@0 271
Chris@0 272 /**
Chris@0 273 * Event handler: recalculates position of the sticky table header.
Chris@0 274 *
Chris@0 275 * @param {jQuery.Event} event
Chris@0 276 * Event being triggered.
Chris@0 277 */
Chris@0 278 recalculateSticky(event) {
Chris@0 279 // Update table size.
Chris@0 280 this.tableHeight = this.$originalTable[0].clientHeight;
Chris@0 281
Chris@0 282 // Update offset top.
Chris@0 283 displace.offsets.top = displace.calculateOffset('top');
Chris@0 284 this.tableOffset = this.$originalTable.offset();
Chris@0 285 this.stickyPosition(displace.offsets.top, scrollValue('scrollLeft'));
Chris@0 286
Chris@0 287 // Update columns width.
Chris@0 288 let $that = null;
Chris@0 289 let $stickyCell = null;
Chris@0 290 let display = null;
Chris@0 291 // Resize header and its cell widths.
Chris@0 292 // Only apply width to visible table cells. This prevents the header from
Chris@0 293 // displaying incorrectly when the sticky header is no longer visible.
Chris@0 294 const il = this.$originalHeaderCells.length;
Chris@0 295 for (let i = 0; i < il; i++) {
Chris@0 296 $that = $(this.$originalHeaderCells[i]);
Chris@0 297 $stickyCell = this.$stickyHeaderCells.eq($that.index());
Chris@0 298 display = $that.css('display');
Chris@0 299 if (display !== 'none') {
Chris@0 300 $stickyCell.css({ width: $that.css('width'), display });
Chris@0 301 }
Chris@0 302 else {
Chris@0 303 $stickyCell.css('display', 'none');
Chris@0 304 }
Chris@0 305 }
Chris@0 306 this.$stickyTable.css('width', this.$originalTable.outerWidth());
Chris@0 307 },
Chris@0 308 });
Chris@0 309
Chris@0 310 // Expose constructor in the public space.
Chris@0 311 Drupal.TableHeader = TableHeader;
Chris@0 312 }(jQuery, Drupal, window.parent.Drupal.displace));