Created
June 26, 2012 09:42
-
-
Save bor/2994710 to your computer and use it in GitHub Desktop.
jQuery plugin: Scrollable Table with Sticky Headers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
<html> | |
<head> | |
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |
<title>jQuery.plugin.convertTableToScrollable test</title> | |
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.js"></script> | |
<script type="text/javascript" src="jQuery.plugin.convertTableToScrollable.js"></script> | |
<style type="text/css"> | |
table { | |
border-collapse: collapse; | |
border-spacing: 0; | |
} | |
table td, | |
table th { | |
border: 1px dotted; | |
padding: 0 4px 0 8px; | |
text-align: left; | |
} | |
.clipped { | |
max-width: 100px; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
width: 100%; | |
} | |
.uncollapse { | |
border-collapse: separate; | |
} | |
.hidden { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div style="margin: auto; width: 50%;"> | |
<br> | |
<form action=""> | |
<input type="checkbox" name="switch_collapse" value="none"> switch collapse | |
<input type="checkbox" name="display_none" value="none"> display: none | |
</form> | |
<h5>with 'height'</h5> | |
<div id="wrapper" style="height: 200px;"> | |
<table id="table_id" style="width: 100%;"> | |
<thead> | |
<tr> | |
<th>title 1</th> | |
<th>title 22</th> | |
<th>title 333</th> | |
<th>title</th> | |
<th> <div class="clipped">long title 55555</div> </th> | |
<th>title</th> | |
<th>title</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>data 11</td> | |
<td>data 12</td> | |
<td>data 13</td> | |
<td>data</td> | |
<td> <div class="clipped">data</div> </td> | |
<td>data</td> | |
<td> <a id="row_add" href="" title="add row">+</a> <a id="row_del" href="" title="remove row">-</a> </td> | |
</tr> | |
<tr> | |
<td>data 21</td> | |
<td>data 22</td> | |
<td>data 23</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data 31</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data 81</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
<h5>with 'max-height'</h5> | |
<div id="wrapper_max_height" style="max-height: 200px;"> | |
<table id="table_id_max_height" style="width: 100%;"> | |
<thead> | |
<tr> | |
<th>title 1</th> | |
<th>title 22</th> | |
<th>title 333</th> | |
<th class="hidden">title</th> | |
<th>title 55555</th> | |
<th>title</th> | |
<th>title 7777777</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>data 1</td> | |
<td>data 2</td> | |
<td>data 333</td> | |
<td class="hidden">data 4444</td> | |
<td>data 55555</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data 1</td> | |
<td>data 2</td> | |
<td>data 3</td> | |
<td class="hidden">data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
$(document).ready( function() { | |
// make #table_id_max_height bigger to check 'IE9 big table insert' bug | |
var $row = $('#table_id_max_height>tbody>tr:last'); | |
for (var i = 0; i < 500 ; i++) { | |
$row.clone().appendTo('#table_id_max_height'); | |
} | |
$('input[name=switch_collapse]').on('click', function() { | |
$('#wrapper').find('table').toggleClass('uncollapse'); | |
$('#wrapper_max_height').find('table').toggleClass('uncollapse'); | |
}); | |
$('input[name=display_none]').on('click', function() { | |
$('#wrapper').toggle(); | |
$('#wrapper_max_height').toggle(); | |
//$('#wrapper').toggle( function() { if ( $('#table_id').is(':visible') ) $('#table_id').trigger('resize'); } ); | |
}); | |
$('#table_id_max_height').convertTableToScrollable({ extend: false }); | |
$('#table_id').convertTableToScrollable({ scrollbar_detect: true }); | |
$('#row_add').click( function() { | |
var $tbody = $(this).closest('table').children('tbody'); | |
$('tr:last', $tbody).clone().appendTo($tbody); | |
return false; | |
} ); | |
$('#row_del').click( function() { | |
var $tbody = $(this).closest('table').children('tbody'); | |
$('tr:last', $tbody).remove(); | |
return false; | |
} ); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// jQuery plugin | |
// Scrollable Table with Sticky Headers | |
// Required: jQuery 1.8+ (use outerWidth as setter) | |
// Usage: $('#table_to_scrollable').convertTableToScrollable(); | |
// Note: after show(if table was hidden) you may be want to do $('#table_to_scrollable').trigger('resize'); | |
'use strict'; | |
(function($) { | |
$.fn.convertTableToScrollable = function(options) { | |
options = $.extend({}, $.fn.convertTableToScrollable.defaults, options); | |
var get_max_height = function (el) { | |
var v = $(el).css('max-height'); | |
if ( v === undefined || v === 'none' || parseInt(v, 10) === -1 ) return null; // or Number.MAX_VALUE | |
return parseInt(v, 10) || 0; | |
}; | |
var get_width = function get_width(el) { | |
if (!$(el).is(':visible')) return 0; // handle hidden element | |
var width; | |
//if (navigator.userAgent.match(/webkit/i)) width = $(el).width() + ($(el).outerWidth() - $(el).innerWidth()); | |
//else if (navigator.userAgent.match(/msie/i) && parseInt(navigator.appVersion,10) < 9) width = $(el).outerWidth() - 2; | |
//else | |
width = $(el).width(); // at this moment work for most browsers | |
// respect to max-width | |
var max_width = parseInt($(el).css('max-width'), 10); | |
if ( max_width && width > max_width ) width = max_width; | |
return width; | |
}; | |
var table_convert = function table_convert(table) { | |
var $table = $(table); | |
// prevent double conversion | |
if ( $table.hasClass('_scrollable') || $table.parent().hasClass('_scrollable') || $table.parent().parent().hasClass('_scrollable') ) | |
return; | |
var table_id = $table.attr('id'); | |
$table.removeAttr('id'); | |
// reset dimensions & layout on wrappers, tables | |
var dimensions_reset = function dimensions_reset() { | |
// clean up old hardcoded widths | |
$head_table_th_first.each( function(i) { if ($(this).is(':visible')) $(this).css('width', 'auto'); } ); | |
$table_tb_first.each( function(i) { if ($(this).is(':visible')) $(this).css('width', 'auto'); } ); | |
$wrapper.css({ height: 'auto', width: '100%' }); | |
$table.css({ 'table-layout': 'auto', width: '100%' }); | |
$head_wrapper.css('width', '100%'); | |
$head_table.css({ 'table-layout': 'auto', 'width': '100%' }); | |
}; | |
// fix wrapper height if need | |
var heights_fix = function heights_fix() { | |
var height = Math.min( $wrapper.parent().height(), $wrapper.parent().parent().height() ); | |
var max_height = options.max_height; | |
if (!max_height) max_height = $wrapper.parent().parent().height(); | |
// fix height for body (considering head) | |
if ( $wrapper.height() > height ) $wrapper.css('height', height); | |
$wrapper.css({ 'max-height': Math.max( $wrapper.height(), max_height || 0 ) - $head_wrapper.height() + 'px' }); | |
}; | |
// fix wrapper & table widths | |
var widths_fix = function widths_fix() { | |
// setup wrapper/table widths directly | |
$wrapper.width($wrapper.width()); | |
$head_wrapper.width($head_wrapper.width()); | |
var head_table_width = $wrapper.width(); | |
var table_width = head_table_width; | |
// if we has scrollbar - compensate it | |
if ( $wrapper[0].scrollHeight > $wrapper.innerHeight() + 1 ) { // +1 = hack for IE | |
// calculate scrollbar width | |
var scrollbar_width = $wrapper.width() - $wrapper[0].clientWidth; | |
// add space for compensate scrollbar | |
if (!options.extend) { | |
$head_wrapper.css( 'padding-right', scrollbar_width + 'px' ); | |
head_table_width -= scrollbar_width; | |
} | |
table_width -= scrollbar_width; | |
} | |
else if (!options.extend) { | |
$head_wrapper.css( 'padding-right', 0 ); | |
} | |
$head_table.outerWidth(head_table_width); | |
$table.outerWidth(table_width); | |
return table_width; | |
}; | |
// resize table | |
var resize = function resize() { | |
var widths = []; | |
heights_fix(); | |
$table.children('thead').hide(); // hide, but do not remove - need for resize in future | |
var table_width = widths_fix(); | |
$table.children('thead').show(); // to be sure | |
// get table widths (in px) | |
$table_tb_first.each( function() { | |
widths.push(get_width(this)); | |
} ); | |
var n_last_col = widths.length - 1; | |
$table.children('thead').hide(); // hide, but do not remove - need for resize in future | |
// setup fixed layout | |
$head_table.css('table-layout', 'fixed'); | |
$table.css('table-layout', 'fixed'); | |
// fix widths for header columns & body columns | |
// left the last column's width as 'auto' to fix a couple of issues (especially IE bugs) | |
$head_table_th_first.each( function(i) { if ( i !== n_last_col ) $(this).width(widths[i]); } ); | |
$table_tb_first.each( function(i) { if ( i !== n_last_col ) $(this).width(widths[i]); } ); | |
// try to fix 'convert table to fixed' problem = +1px? | |
if ( $table.outerWidth() > table_width ) $table_tb_first.last().width('auto'); | |
}; | |
// reset & resize all again + process fixes | |
var resize_all = function resize_all() { | |
// dirty hack for sure table is visible | |
if ( $table.is(':hidden') ) return; | |
dimensions_reset(); | |
resize(); | |
if ( options.scrollbar_detect ) _scrollbar_detect(true); | |
}; | |
// calculate max height if not passed | |
if ( !options.max_height ) { | |
if (get_max_height($table.parent())) options.max_height = get_max_height($table.parent()); | |
if (get_max_height($table)) options.max_height = get_max_height($table); | |
} | |
// we collapse borders for better cross-browser support | |
//$table.css({ 'border-collapse': 'collapse', 'border-spacing': 0, 'empty-cells': 'show' }); | |
// set width to 0 for hidden elements | |
$('thead>tr:first>th', $table).each( function() { | |
if (!$(this).is(':visible')) $(this).css('width', 0); | |
} ); | |
// wrap the table (with just body at end) | |
var $wrapper = $table.wrap('<div></div>').parent().attr('id', table_id + '_body'); | |
$wrapper.css({ 'border-width': 0, margin: 0, overflow: 'auto', width: '100%' }); | |
// main table wrapper | |
var $main_wrapper = $wrapper.wrap('<div class="_scrollable"></div>').parent().attr('id', table_id); | |
$main_wrapper.css({ 'border-width': 0, margin: 0, overflow: 'hidden', width: '100%' }); | |
// header wrapper | |
var $head_wrapper = $('<div></div>').attr('id', table_id + '_header').prependTo($main_wrapper); | |
$head_wrapper.css({ 'border-width': 0, margin: 0, overflow: 'hidden' }); | |
// clone table to the header | |
var $head_table = $table.clone().appendTo($head_wrapper); | |
// remove the extra html | |
$head_table.children('tbody').empty().append('<tr><td/></tr>').hide(); | |
// detect border difference | |
var width_diff = $wrapper.outerWidth() - $table.outerWidth(); | |
// apply 'remove whitespace fix' for fix 'IE9 insert big table' bug | |
if ( navigator.userAgent.match(/msie/i) && parseInt(navigator.appVersion,10) === 9 && $table.find('tbody>tr').length > 200 ) { | |
var expr = new RegExp('>[ \t\r\n\v\f]*<', 'g'); | |
var table_html = $table.html(); | |
$table.html( table_html.replace(expr, '><') ); | |
} | |
// define some common selecters | |
var $head_table_th_first = $('thead>tr:first>th', $head_table); | |
var $table_tb_first = $('tbody>tr:first>td', $table); | |
if ( options.scrollbar_detect ) $wrapper.data('scrollbar_height_prev', $wrapper[0].scrollHeight); | |
resize(); | |
// detecting scrollbar appear | |
var _scrollbar_detect = function _scrollbar_detect(just_reset) { | |
if ( _resize_id !== false ) clearTimeout(_resize_id); | |
if ( $wrapper.data('scrollbar_height_prev') && $wrapper[0].scrollHeight != $wrapper.data('scrollbar_height_prev') ) { | |
$wrapper.data('scrollbar_height_prev', $wrapper[0].scrollHeight); | |
if ( $wrapper[0].scrollHeight > $wrapper.innerHeight()+1 && !$wrapper.data('scrollbar_exists') ) { // +1 = hack for IE | |
$wrapper.data('scrollbar_exists', true); | |
if (!just_reset) resize_all(); | |
} | |
else if ( $wrapper[0].scrollHeight <= $wrapper.innerHeight() && $wrapper.data('scrollbar_exists') ) { | |
$wrapper.data('scrollbar_exists', false); | |
if (!just_reset) resize_all(); | |
} | |
} | |
_resize_id = setTimeout(function() { _scrollbar_detect(false); }, 500); | |
}; | |
// respond to changing window's size | |
var _resize_id; | |
$(window).resize( function() { | |
if ( _resize_id !== false ) clearTimeout(_resize_id); | |
_resize_id = setTimeout( resize_all, 500 ); | |
}); | |
// respond to appear/lost scrollbar (on wrapper element) | |
if ( options.scrollbar_detect ) _scrollbar_detect(); | |
}; | |
return this.each(function() { | |
table_convert(this); | |
}); | |
}; | |
$.fn.convertTableToScrollable.defaults = { | |
//debug: false, // debug on/off flag | |
extend: true, // extend last thead column (above scrollbar) | |
max_height: null, // max height of the scrollable area | |
scrollbar_detect: false // detect if scrollbar appear, performance hit! | |
}; | |
})(jQuery); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
<html> | |
<head> | |
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> | |
<title>jQuery.plugin.convertTableToScrollable test</title> | |
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.js"></script> | |
<script type="text/javascript" src="jQuery.plugin.convertTableToScrollable.js"></script> | |
<style type="text/css"> | |
table { | |
border-collapse: collapse; | |
border-spacing: 0; | |
} | |
table td, | |
table th { | |
border: 1px dotted; | |
padding: 0 4px 0 8px; | |
text-align: left; | |
} | |
.clipped { | |
max-width: 100px; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
width: 100%; | |
} | |
.uncollapse { | |
border-collapse: separate; | |
} | |
.hidden { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div style="margin: auto; width: 50%;"> | |
<br> | |
<form action=""> | |
<input type="checkbox" name="switch_collapse" value="none"> switch collapse | |
<input type="checkbox" name="display_none" value="none"> display: none | |
</form> | |
<h5>with 'height'</h5> | |
<div id="wrapper" style="height: 200px;"> | |
<table id="table_id" style="width: 100%;"> | |
<thead> | |
<tr> | |
<th>title 1</th> | |
<th>title 22</th> | |
<th>title 333</th> | |
<th>title</th> | |
<th> <div class="clipped">long title 55555</div> </th> | |
<th>title</th> | |
<th>title</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>data 11</td> | |
<td>data 12</td> | |
<td>data 13</td> | |
<td>data</td> | |
<td> <div class="clipped">data</div> </td> | |
<td>data</td> | |
<td> <a id="row_add" href="" title="add row">+</a> <a id="row_del" href="" title="remove row">-</a> </td> | |
</tr> | |
<tr> | |
<td>data 21</td> | |
<td>data 22</td> | |
<td>data 23</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data 31</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data 81</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
<h5>with 'max-height'</h5> | |
<div id="wrapper_max_height" style="max-height: 200px;"> | |
<table id="table_id_max_height" style="width: 100%;"> | |
<thead> | |
<tr> | |
<th>title 1</th> | |
<th>title 22</th> | |
<th>title 333</th> | |
<th class="hidden">title</th> | |
<th>title 55555</th> | |
<th>title</th> | |
<th>title 7777777</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr> | |
<td>data 1</td> | |
<td>data 2</td> | |
<td>data 333</td> | |
<td class="hidden">data 4444</td> | |
<td>data 55555</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
<tr> | |
<td>data 1</td> | |
<td>data 2</td> | |
<td>data 3</td> | |
<td class="hidden">data</td> | |
<td>data</td> | |
<td>data</td> | |
<td>data</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
$(document).ready( function() { | |
// make #table_id_max_height bigger to check 'IE9 big table insert' bug | |
var $row = $('#table_id_max_height>tbody>tr:last'); | |
for (var i = 0; i < 500 ; i++) { | |
$row.clone().appendTo('#table_id_max_height'); | |
} | |
$('input[name=switch_collapse]').on('click', function() { | |
$('#wrapper').find('table').toggleClass('uncollapse'); | |
$('#wrapper_max_height').find('table').toggleClass('uncollapse'); | |
}); | |
$('input[name=display_none]').on('click', function() { | |
$('#wrapper').toggle(); | |
$('#wrapper_max_height').toggle(); | |
//$('#wrapper').toggle( function() { if ( $('#table_id').is(':visible') ) $('#table_id').trigger('resize'); } ); | |
}); | |
$('#table_id_max_height').convertTableToScrollable({ extend: false }); | |
$('#table_id').convertTableToScrollable({ scrollbar_detect: true }); | |
$('#row_add').click( function() { | |
var $tbody = $(this).closest('table').children('tbody'); | |
$('tr:last', $tbody).clone().appendTo($tbody); | |
return false; | |
} ); | |
$('#row_del').click( function() { | |
var $tbody = $(this).closest('table').children('tbody'); | |
$('tr:last', $tbody).remove(); | |
return false; | |
} ); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment