Mercurial > hg > cmmr2012-drupal-site
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)); |