From f996fd9568dd2dd09c01740885fea85f77a9ce8b Mon Sep 17 00:00:00 2001
From: Dries Buytaert <dries@buytaert.net>
Date: Tue, 6 Feb 2007 08:35:13 +0000
Subject: [PATCH] - Patch #112605 by Steven: sticky table headers.

---
 includes/theme.inc        |   3 ++
 misc/tableheader.js       | 109 ++++++++++++++++++++++++++++++++++++++
 modules/system/system.css |   7 +++
 themes/garland/style.css  |   4 ++
 4 files changed, 123 insertions(+)
 create mode 100644 misc/tableheader.js

diff --git a/includes/theme.inc b/includes/theme.inc
index a829b2cca8f7..de1be95dd5fb 100644
--- a/includes/theme.inc
+++ b/includes/theme.inc
@@ -761,6 +761,9 @@ function theme_table($header, $rows, $attributes = array(), $caption = NULL) {
 
   // Format the table header:
   if (count($header)) {
+    // Include JS for sticky headers.
+    drupal_add_js('misc/tableheader.js');
+
     $ts = tablesort_init($header);
     $output .= ' <thead><tr>';
     foreach ($header as $cell) {
diff --git a/misc/tableheader.js b/misc/tableheader.js
new file mode 100644
index 000000000000..97b57ec602d8
--- /dev/null
+++ b/misc/tableheader.js
@@ -0,0 +1,109 @@
+// $Id$
+
+// Global Killswitch
+if (Drupal.jsEnabled) {
+  // Keep track of all header cells.
+  var cells = [];
+
+  // Attach to all headers.
+  $(document).ready(function() {
+    var z = 0;
+    $('table thead').each(function () {
+      // Find table height.
+      var table = $(this).parent('table')[0];
+      var height = $(table).addClass('sticky-table').height();
+      var i = 0;
+
+      // Find all header cells.
+      $('th', this).each(function () {
+
+        // Ensure each cell has an element in it.
+        var html = $(this).html();
+        if (html == ' ') {
+          html = '&nbsp;';
+        }
+        if ($(this).children().size() == 0) {
+          html = '<span>'+ html +'</span>';
+        }
+
+        // Clone and wrap cell contents in sticky wrapper that overlaps the cell's padding.
+        $('<div class="sticky-header" style="position: fixed; visibility: hidden; top: 0px;">'+ html +'</div>').prependTo(this);
+        var div = $('div.sticky-header', this).css({
+          'marginLeft': '-'+ $(this).css('paddingLeft'),
+          'marginRight': '-'+ $(this).css('paddingRight'),
+          'paddingLeft': $(this).css('paddingLeft'),
+          'paddingTop': $(this).css('paddingTop'),
+          'paddingBottom': $(this).css('paddingBottom'),
+          'z-index': ++z
+        })[0];
+        cells.push(div);
+
+        // Adjust width to fit cell/table.
+        var ref = this;
+        if (!i++) {
+          // The first cell is as wide as the table to prevent gaps.
+          ref = table;
+          div.wide = true;
+        }
+        $(div).css('width', parseInt($(ref).width())
+                          - parseInt($(div).css('paddingLeft')) +'px');
+
+        // Get position and store.
+        div.cell = this;
+        div.table = table;
+        div.stickyMax = height;
+        div.stickyPosition = Drupal.absolutePosition(this).y;
+      });
+    });
+  });
+
+  // Track scrolling.
+  var scroll = function() {
+    $(cells).each(function () {
+      // Fetch scrolling position.
+      var scroll = document.documentElement.scrollTop || document.body.scrollTop;
+      var offset = scroll - this.stickyPosition - 4;
+      if (offset > 0 && offset < this.stickyMax - 100) {
+        $(this).css('visibility', 'visible');
+      }
+      else {
+        $(this).css('visibility', 'hidden');
+      }
+    });
+  };
+  $(window).scroll(scroll);
+  $(document.documentElement).scroll(scroll);
+
+  // Track resizing.
+  var time = null;
+  var resize = function () {
+    // Ensure minimum time between adjustments.
+    if (time) {
+      clearTimeout(time);
+      time = null;
+    }
+    time = setTimeout(function () {
+
+      // Precalculate table heights
+      $('table.sticky-table').each(function () {
+        this.height = $(this).height();
+      })
+
+      $(cells).each(function () {
+        // Get position.
+        this.stickyPosition = Drupal.absolutePosition(this.cell).y;
+        this.stickyMax = this.table.height;
+
+        // Reflow the cell.
+        var ref = this.cell;
+        if (this.wide) {
+          // Resize the first cell to fit the table.
+          ref = this.table;
+        }
+        $(this).css('width', parseInt($(ref).width())
+                           - parseInt($(this).css('paddingLeft')) +'px');
+      });
+    }, 250);
+  };
+  $(window).resize(resize);
+}
diff --git a/modules/system/system.css b/modules/system/system.css
index 7676e594886d..f69583db7830 100644
--- a/modules/system/system.css
+++ b/modules/system/system.css
@@ -390,3 +390,10 @@ html.js .resizable-textarea textarea {
 tr.selected td {
   background: #ffc;
 }
+
+/*
+** Floating header for tableheader.js
+*/
+thead div.sticky-header {
+  background: #fff;
+}
diff --git a/themes/garland/style.css b/themes/garland/style.css
index 94776035bc38..e4e73e059b61 100644
--- a/themes/garland/style.css
+++ b/themes/garland/style.css
@@ -170,6 +170,10 @@ thead th {
   font-weight: bold;
 }
 
+thead div.sticky-header {
+  border-bottom: 2px solid #d3e7f4;
+}
+
 th a:link, th a:visited {
   color: #6f9dbd;
 }
-- 
GitLab