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