diff --git a/includes/theme.inc b/includes/theme.inc index a829b2cca8f789a6e806aa2f261fff2f819aabdb..de1be95dd5fb693922450a1c6705efc1f2f35c77 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 0000000000000000000000000000000000000000..97b57ec602d87faac7dcece350bf1c9e2e498e0e --- /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 = ' '; + } + 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 7676e594886dc93d0e51e2b6f4ad3e0341c8cc7a..f69583db7830aa3a72d77fd9ae994add735a56c8 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 94776035bc38d5ebd9ac4de77a12b9e1a4926a77..e4e73e059b6161fa0792793241a0d9227fc5bfb6 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; }