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