5a920362 |
1 | (function ($) { |
2 | |
3 | /** |
4 | * Attaches sticky table headers. |
5 | */ |
6 | Drupal.behaviors.tableHeader = { |
7 | attach: function (context, settings) { |
8 | if (!$.support.positionFixed) { |
9 | return; |
10 | } |
11 | |
12 | $('table.sticky-enabled', context).once('tableheader', function () { |
13 | $(this).data("drupal-tableheader", new Drupal.tableHeader(this)); |
14 | }); |
15 | } |
16 | }; |
17 | |
18 | /** |
19 | * Constructor for the tableHeader object. Provides sticky table headers. |
20 | * |
21 | * @param table |
22 | * DOM object for the table to add a sticky header to. |
23 | */ |
24 | Drupal.tableHeader = function (table) { |
25 | var self = this; |
26 | |
27 | this.originalTable = $(table); |
28 | this.originalHeader = $(table).children('thead'); |
29 | this.originalHeaderCells = this.originalHeader.find('> tr > th'); |
30 | this.displayWeight = null; |
31 | |
32 | // React to columns change to avoid making checks in the scroll callback. |
33 | this.originalTable.bind('columnschange', function (e, display) { |
34 | // This will force header size to be calculated on scroll. |
35 | self.widthCalculated = (self.displayWeight !== null && self.displayWeight === display); |
36 | self.displayWeight = display; |
37 | }); |
38 | |
39 | // Clone the table header so it inherits original jQuery properties. Hide |
40 | // the table to avoid a flash of the header clone upon page load. |
41 | this.stickyTable = $('<table class="sticky-header"/>') |
42 | .insertBefore(this.originalTable) |
43 | .css({ position: 'fixed', top: '0px' }); |
44 | this.stickyHeader = this.originalHeader.clone(true) |
45 | .hide() |
46 | .appendTo(this.stickyTable); |
47 | this.stickyHeaderCells = this.stickyHeader.find('> tr > th'); |
48 | |
49 | this.originalTable.addClass('sticky-table'); |
50 | $(window) |
51 | .bind('scroll.drupal-tableheader', $.proxy(this, 'eventhandlerRecalculateStickyHeader')) |
52 | .bind('resize.drupal-tableheader', { calculateWidth: true }, $.proxy(this, 'eventhandlerRecalculateStickyHeader')) |
53 | // Make sure the anchor being scrolled into view is not hidden beneath the |
54 | // sticky table header. Adjust the scrollTop if it does. |
55 | .bind('drupalDisplaceAnchor.drupal-tableheader', function () { |
56 | window.scrollBy(0, -self.stickyTable.outerHeight()); |
57 | }) |
58 | // Make sure the element being focused is not hidden beneath the sticky |
59 | // table header. Adjust the scrollTop if it does. |
60 | .bind('drupalDisplaceFocus.drupal-tableheader', function (event) { |
61 | if (self.stickyVisible && event.clientY < (self.stickyOffsetTop + self.stickyTable.outerHeight()) && event.$target.closest('sticky-header').length === 0) { |
62 | window.scrollBy(0, -self.stickyTable.outerHeight()); |
63 | } |
64 | }) |
65 | .triggerHandler('resize.drupal-tableheader'); |
66 | |
67 | // We hid the header to avoid it showing up erroneously on page load; |
68 | // we need to unhide it now so that it will show up when expected. |
69 | this.stickyHeader.show(); |
70 | }; |
71 | |
72 | /** |
73 | * Event handler: recalculates position of the sticky table header. |
74 | * |
75 | * @param event |
76 | * Event being triggered. |
77 | */ |
78 | Drupal.tableHeader.prototype.eventhandlerRecalculateStickyHeader = function (event) { |
79 | var self = this; |
80 | var calculateWidth = event.data && event.data.calculateWidth; |
81 | |
82 | // Reset top position of sticky table headers to the current top offset. |
83 | this.stickyOffsetTop = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0; |
84 | this.stickyTable.css('top', this.stickyOffsetTop + 'px'); |
85 | |
86 | // Save positioning data. |
87 | var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; |
88 | if (calculateWidth || this.viewHeight !== viewHeight) { |
89 | this.viewHeight = viewHeight; |
90 | this.vPosition = this.originalTable.offset().top - 4 - this.stickyOffsetTop; |
91 | this.hPosition = this.originalTable.offset().left; |
92 | this.vLength = this.originalTable[0].clientHeight - 100; |
93 | calculateWidth = true; |
94 | } |
95 | |
96 | // Track horizontal positioning relative to the viewport and set visibility. |
97 | var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft; |
98 | var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - this.vPosition; |
99 | this.stickyVisible = vOffset > 0 && vOffset < this.vLength; |
100 | this.stickyTable.css({ left: (-hScroll + this.hPosition) + 'px', visibility: this.stickyVisible ? 'visible' : 'hidden' }); |
101 | |
102 | // Only perform expensive calculations if the sticky header is actually |
103 | // visible or when forced. |
104 | if (this.stickyVisible && (calculateWidth || !this.widthCalculated)) { |
105 | this.widthCalculated = true; |
106 | var $that = null; |
107 | var $stickyCell = null; |
108 | var display = null; |
109 | var cellWidth = null; |
110 | // Resize header and its cell widths. |
111 | // Only apply width to visible table cells. This prevents the header from |
112 | // displaying incorrectly when the sticky header is no longer visible. |
113 | for (var i = 0, il = this.originalHeaderCells.length; i < il; i += 1) { |
114 | $that = $(this.originalHeaderCells[i]); |
115 | $stickyCell = this.stickyHeaderCells.eq($that.index()); |
116 | display = $that.css('display'); |
117 | if (display !== 'none') { |
118 | cellWidth = $that.css('width'); |
119 | // Exception for IE7. |
120 | if (cellWidth === 'auto') { |
121 | cellWidth = $that[0].clientWidth + 'px'; |
122 | } |
123 | $stickyCell.css({'width': cellWidth, 'display': display}); |
124 | } |
125 | else { |
126 | $stickyCell.css('display', 'none'); |
127 | } |
128 | } |
129 | this.stickyTable.css('width', this.originalTable.outerWidth()); |
130 | } |
131 | }; |
132 | |
133 | })(jQuery); |