(function ($) { var defaults = { rowsToDisplay: 10 }; var scrollBarWidth = 15, fixedTableWidth; $.fn.scrollTableBody = function(options) { options = $.extend(defaults, options); var table = this; wrapTable(table, options); alignColumns(table); var resizeAlignFunction = function () { alignOnResize(table); }; var canDebounce = typeof _ == 'function' && typeof _.debounce == 'function'; if (canDebounce) resizeAlignFunction = _.debounce(resizeAlignFunction, 150); $(window).resize(resizeAlignFunction); }; function wrapTable(table, options) { var existingClasses = table.attr('class'); var existingMarginBottom = table.css('margin-bottom'); table.css('margin-bottom', 0); var rowHeight = table.find('tbody tr:first').outerHeight(); var tableHeight = rowHeight * options.rowsToDisplay; var headerTable = $('
'), footerTable = $(''), scrollDiv = '
'; // Insert the table that will hold the fixed header and footer, and insert the div that will get scrolled table.before(headerTable).before(scrollDiv).after(footerTable); } function alignColumns(table) { table.each(function (index) { // To minimize "Flash of Unstyled Content" (FOUC), set the relevant variables before manipulating the DOM var $dataTable = $(this); var $headerTable = $('table.jqstb-header-table').eq(index); var $footerTable = $('table.jqstb-footer-table').eq(index); // Place main table data inside of relevant scrollable div (using jQuery eq() and index) var $scrollDiv = $('div.jqstb-scroll').eq(index); $scrollDiv.prepend($dataTable); var scrollEl = $scrollDiv[0]; var hasHorizontalScroll = scrollEl.clientWidth < scrollEl.scrollWidth; $scrollDiv.outerWidth(fixedTableWidth + scrollBarWidth + 2); if (hasHorizontalScroll) { var dataTableWidth = $dataTable.outerWidth(); $headerTable.width(dataTableWidth); $footerTable.width(dataTableWidth); $scrollDiv.outerWidth(scrollEl.clientWidth); var scrollDivWidth = $scrollDiv.outerWidth(); var width = scrollDivWidth - scrollBarWidth; var $headerScrollDiv = $('
'); $headerTable.wrap($headerScrollDiv); var $footerScrollDiv = $(''); $footerTable.wrap($footerScrollDiv); $scrollDiv.on('scroll', function() { $('div.jqstb-header-scroll').scrollLeft($(this).scrollLeft()); $('div.jqstb-footer-scroll').scrollLeft($(this).scrollLeft()); }); } // Force column widths to be set for each header column $dataTable.find('thead tr:first th, tbody tr:first td').each(function () { $(this).outerWidth($(this).outerWidth()); }); // Insert header data into fixed header table $headerTable.find('thead').replaceWith($dataTable.children('caption, thead').clone()); // Force column widths to be set for each footer column $dataTable.find('tfoot tr:first td').each(function () { $(this).outerWidth($(this).outerWidth()); }); // Insert footer data into fixed footer table $footerTable.find('tfoot').replaceWith($dataTable.children('tfoot').clone()); // Hide original caption, header, and footer $dataTable.children('caption, thead, tfoot').hide(); }); } function alignOnResize(table) { table.each(function (index) { var $dataTable = $(this); var $headerTable = $('table.jqstb-header-table').eq(index); var $footerTable = $('table.jqstb-footer-table').eq(index); // Temporarily show the inner table's header and footer since the dom calculates width based on them being visible $dataTable.children('thead, tfoot').show(); var scrollEl = $('div.jqstb-scroll')[0]; var hasHorizontalScroll = scrollEl.clientWidth < scrollEl.scrollWidth; if (hasHorizontalScroll) { var scrollDivWidth = $('div.jqstb-scroll').outerWidth(); $('div.jqstb-header-scroll').outerWidth(scrollDivWidth - scrollBarWidth); $('div.jqstb-footer-scroll').outerWidth(scrollDivWidth - scrollBarWidth); } // Force column widths to be set for each header column var $headerColumns = $headerTable.find('thead tr:first th'); $dataTable.find('thead tr:first th').each(function (i) { $headerColumns.eq(i).outerWidth($(this).outerWidth()); }); // Force column widths to be set for each footer column var $footerColumns = $footerTable.find('tfoot tr:first td'); $dataTable.find('tfoot tr:first td').each(function (i) { $footerColumns.eq(i).outerWidth($(this).outerWidth()); }); // Hide the inner table's header and footer when we're done $dataTable.children('thead, tfoot').hide(); }); } })(jQuery);