Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.337
diff -u -F^f -r1.337 theme.inc
--- includes/theme.inc	11 Jan 2007 03:36:06 -0000	1.337
+++ includes/theme.inc	6 Feb 2007 06:13:21 -0000
@@ -761,6 +761,9 @@ function theme_table($header, $rows, $at
 
   // 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) {
Index: misc/tableheader.js
===================================================================
RCS file: misc/tableheader.js
diff -N misc/tableheader.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ misc/tableheader.js	6 Feb 2007 06:13:21 -0000
@@ -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);
+}
Index: modules/system/system.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.css,v
retrieving revision 1.21
diff -u -F^f -r1.21 system.css
--- modules/system/system.css	21 Dec 2006 16:13:06 -0000	1.21
+++ modules/system/system.css	6 Feb 2007 06:13:22 -0000
@@ -390,3 +390,10 @@
 tr.selected td {
   background: #ffc;
 }
+
+/*
+** Floating header for tableheader.js
+*/
+thead div.sticky-header {
+  background: #fff;
+}
Index: themes/garland/style.css
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/style.css,v
retrieving revision 1.14
diff -u -F^f -r1.14 style.css
--- themes/garland/style.css	10 Jan 2007 08:24:02 -0000	1.14
+++ themes/garland/style.css	6 Feb 2007 06:13:28 -0000
@@ -170,6 +170,10 @@ fieldset ul.clear-block li {
   font-weight: bold;
 }
 
+thead div.sticky-header {
+  border-bottom: 2px solid #d3e7f4;
+}
+
 th a:link, th a:visited {
   color: #6f9dbd;
 }
