| 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 | |
| 31 | // Clone the table header so it inherits original jQuery properties. Hide |
| 32 | // the table to avoid a flash of the header clone upon page load. |
| 33 | this.stickyTable = $('<table class="sticky-header"/>') |
| 34 | .insertBefore(this.originalTable) |
| 35 | .css({ position: 'fixed', top: '0px' }); |
| 36 | this.stickyHeader = this.originalHeader.clone(true) |
| 37 | .hide() |
| 38 | .appendTo(this.stickyTable); |
| 39 | this.stickyHeaderCells = this.stickyHeader.find('> tr > th'); |
| 40 | |
| 41 | this.originalTable.addClass('sticky-table'); |
| 42 | $(window) |
| 43 | .bind('scroll.drupal-tableheader', $.proxy(this, 'eventhandlerRecalculateStickyHeader')) |
| 44 | .bind('resize.drupal-tableheader', { calculateWidth: true }, $.proxy(this, 'eventhandlerRecalculateStickyHeader')) |
| 45 | // Make sure the anchor being scrolled into view is not hidden beneath the |
| 46 | // sticky table header. Adjust the scrollTop if it does. |
| 47 | .bind('drupalDisplaceAnchor.drupal-tableheader', function () { |
| 48 | window.scrollBy(0, -self.stickyTable.outerHeight()); |
| 49 | }) |
| 50 | // Make sure the element being focused is not hidden beneath the sticky |
| 51 | // table header. Adjust the scrollTop if it does. |
| 52 | .bind('drupalDisplaceFocus.drupal-tableheader', function (event) { |
| 53 | if (self.stickyVisible && event.clientY < (self.stickyOffsetTop + self.stickyTable.outerHeight()) && event.$target.closest('sticky-header').length === 0) { |
| 54 | window.scrollBy(0, -self.stickyTable.outerHeight()); |
| 55 | } |
| 56 | }) |
| 57 | .triggerHandler('resize.drupal-tableheader'); |
| 58 | |
| 59 | // We hid the header to avoid it showing up erroneously on page load; |
| 60 | // we need to unhide it now so that it will show up when expected. |
| 61 | this.stickyHeader.show(); |
| 62 | }; |
| 63 | |
| 64 | /** |
| 65 | * Event handler: recalculates position of the sticky table header. |
| 66 | * |
| 67 | * @param event |
| 68 | * Event being triggered. |
| 69 | */ |
| 70 | Drupal.tableHeader.prototype.eventhandlerRecalculateStickyHeader = function (event) { |
| 71 | var self = this; |
| 72 | var calculateWidth = event.data && event.data.calculateWidth; |
| 73 | |
| 74 | // Reset top position of sticky table headers to the current top offset. |
| 75 | this.stickyOffsetTop = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0; |
| 76 | this.stickyTable.css('top', this.stickyOffsetTop + 'px'); |
| 77 | |
| 78 | // Save positioning data. |
| 79 | var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; |
| 80 | if (calculateWidth || this.viewHeight !== viewHeight) { |
| 81 | this.viewHeight = viewHeight; |
| 82 | this.vPosition = this.originalTable.offset().top - 4 - this.stickyOffsetTop; |
| 83 | this.hPosition = this.originalTable.offset().left; |
| 84 | this.vLength = this.originalTable[0].clientHeight - 100; |
| 85 | calculateWidth = true; |
| 86 | } |
| 87 | |
| 88 | // Track horizontal positioning relative to the viewport and set visibility. |
| 89 | var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft; |
| 90 | var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - this.vPosition; |
| 91 | this.stickyVisible = vOffset > 0 && vOffset < this.vLength; |
| 92 | this.stickyTable.css({ left: (-hScroll + this.hPosition) + 'px', visibility: this.stickyVisible ? 'visible' : 'hidden' }); |
| 93 | |
| 94 | // Only perform expensive calculations if the sticky header is actually |
| 95 | // visible or when forced. |
| 96 | if (this.stickyVisible && (calculateWidth || !this.widthCalculated)) { |
| 97 | this.widthCalculated = true; |
| 98 | // Resize header and its cell widths. |
| 99 | this.stickyHeaderCells.each(function (index) { |
| 100 | var cellWidth = self.originalHeaderCells.eq(index).css('width'); |
| 101 | // Exception for IE7. |
| 102 | if (cellWidth == 'auto') { |
| 103 | cellWidth = self.originalHeaderCells.get(index).clientWidth + 'px'; |
| 104 | } |
| 105 | $(this).css('width', cellWidth); |
| 106 | }); |
| 107 | this.stickyTable.css('width', this.originalTable.css('width')); |
| 108 | } |
| 109 | }; |
| 110 | |
| 111 | })(jQuery); |