diff --git a/README.md b/README.md index 4bf1566..df5c77d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -jquery.floatThead v1.2.2 +jquery.floatThead v1.2.3 ================= Float the table header without special css. This plugin assumes nothing about your table markup and "just works" without losing your events or styles. Supports floating the header while scrolling within the window or while scrolling within a container with overflow. @@ -27,8 +27,14 @@ bower install floatThead or download: -- [development version](https://raw.github.com/mkoryak/floatThead/master/jquery.floatThead.js) -- [production version](https://raw.github.com/mkoryak/floatThead/master/jquery.floatThead.min.js) +- [development version](https://raw.github.com/mkoryak/floatThead/master/dist/jquery.floatThead.js) +- [production version](https://raw.github.com/mkoryak/floatThead/master/dist/jquery.floatThead.min.js) + +if your project includes [underscore](http://underscorejs.org) and you want to save a few bytes you can use the slim version: + +- [slim development version](https://raw.github.com/mkoryak/floatThead/master/dist/jquery.floatThead-slim.js) +- [slim production version](https://raw.github.com/mkoryak/floatThead/master/dist/jquery.floatThead-slim.min.js) + Features: --------- @@ -46,7 +52,6 @@ Requirements: ------------- - jQuery 1.8.x or better (1.9 compliant) (or jQuery 1.7.x and jQuery UI core) -- Underscore.js 1.3 or better - IE8, IE9, IE10, IE11, FF10+ or Chrome15+. - The following meta tag to placate IE: <meta http-equiv="X-UA-Compatible" content="IE=10; IE=9; IE=8; IE=7; IE=EDGE" /> @@ -68,6 +73,14 @@ Watch for it. Change Log ---------- +### 1.2.3 + +- removed underscore dependency, added a *slim* version which is very slightly smaller and requires underscore +- now supporting a few evil deprecated table attributes that people still use: `cellpadding` and `cellspacing` +- fixed https://github.com/mkoryak/floatThead/issues/52 +- fixed https://github.com/mkoryak/floatThead/issues/50 +- added floatWrapperClass option +- added copyTableClass option ### 1.2.2 diff --git a/bower.json b/bower.json index 99c9c7a..77b2f9c 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "jquery.floatThead", - "version": "1.2.2", + "version": "1.2.3", "homepage": "http://mkoryak.github.io/floatThead/", "authors": [ "Misha Koryak" diff --git a/dist/jquery.floatThead-slim.js b/dist/jquery.floatThead-slim.js new file mode 100644 index 0000000..25a3eac --- /dev/null +++ b/dist/jquery.floatThead-slim.js @@ -0,0 +1,647 @@ +// @preserve jQuery.floatThead 1.2.3 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak +// @license Licensed under http://creativecommons.org/licenses/by-sa/4.0/ + +/* @author Misha Koryak + * @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header + * + * Dependencies: + * jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core + * + * http://mkoryak.github.io/floatThead/ + * + * Tested on FF13+, Chrome 21+, IE8, IE9, IE10, IE11 + * + */ +(function( $ ) { + /** + * provides a default config object. You can modify this after including this script if you want to change the init defaults + * @type {Object} + */ + $.floatThead = $.floatThead || {}; + $.floatThead.defaults = { + cellTag: 'th:visible', //thead cells are this + zIndex: 1001, //zindex of the floating thead (actually a container div) + debounceResizeMs: 1, + useAbsolutePositioning: true, //if set to NULL - defaults: has scrollContainer=true, doesn't have scrollContainer=false + scrollingTop: 0, //String or function($table) - offset from top of window where the header should not pass above + scrollingBottom: 0, //String or function($table) - offset from the bottom of the table where the header should stop scrolling + scrollContainer: function($table){ + return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars + }, + getSizingRow: function($table, $cols, $fthCells){ // this is only called when using IE, + // override it if the first row of the table is going to contain colgroups (any cell spans greater then one col) + // it should return a jquery object containing a wrapped set of table cells comprising a row that contains no col spans and is visible + return $table.find('tbody tr:visible:first>td'); + }, + floatTableClass: 'floatThead-table', + floatWrapperClass: 'floatThead-wrapper', + floatContainerClass: 'floatThead-container', + copyTableClass: true, //copy 'class' attribute from table into the floated table so that the styles match. + debug: false //print possible issues (that don't prevent script loading) to console, if console exists. + }; + + var util = window._; + + + //browser stuff + var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];b.innerHTML="",c[0];);return 4"); + $('body').append($table); + var width = $table.find('col').width(); + $table.remove(); + return width == 0; + }; + + var $window = $(window); + var floatTheadCreated = 0; + + + /** + * @param debounceMs + * @param cb + */ + + function windowResize(debounceMs, cb){ + $window.bind('resize.floatTHead', util.debounce(cb, debounceMs)); //TODO: check if resize bug is gone in IE8 + + } + + + function debug(str){ + window.console && window.console && window.console.log && window.console.log(str); + } + + /** + * try to calculate the scrollbar width for your browser/os + * @return {Number} + */ + function scrollbarWidth() { + var $div = $( //borrowed from anti-scroll + '
' + + '
' + ); + $('body').append($div); + var w1 = $div.innerWidth(); + var w2 = $('div', $div).innerWidth(); + $div.remove(); + return w1 - w2; + } + /** + * Check if a given table has been datatableized (http://datatables.net) + * @param $table + * @return {Boolean} + */ + function isDatatable($table){ + if($table.dataTableSettings){ + for(var i = 0; i < $table.dataTableSettings.length; i++){ + var table = $table.dataTableSettings[i].nTable; + if($table[0] == table){ + return true; + } + } + } + return false; + } + $.fn.floatThead = function(map){ + map = map || {}; + if(!util){ //may have been included after the script? lets try to grab it again. + util = window._ || $.floatThead._; + if(!util){ + throw new Error("jquery.floatThead-slim.js requires underscore. You should use the non-lite version since you do not have underscore."); + } + } + + if(ieVersion < 8){ + return this; //no more crappy browser support. + } + + if(isChrome == null){ //make sure this is done only once no matter how many times you call the plugin fn + isChrome = isChromeCheck(); //need to call this after dom ready, and now it is. + if(isChrome){ + //because chrome cant read width, these elements are used for sizing the table. Need to create new elements because they must be unstyled by user's css. + document.createElement('fthtr'); //tr + document.createElement('fthtd'); //td + document.createElement('fthfoot'); //tfoot + } + } + if(util.isString(map)){ + var command = map; + var ret = this; + this.filter('table').each(function(){ + var obj = $(this).data('floatThead-attached'); + if(obj && util.isFunction(obj[command])){ + var r = obj[command](); + if(typeof r !== 'undefined'){ + ret = r; + } + } + }); + return ret; + } + var opts = $.extend({}, $.floatThead.defaults || {}, map); + + $.each(map, function(val, key){ + if((!(key in $.floatThead.defaults)) && opts.debug){ + debug("jQuery.floatThead: used ["+key+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+ (util.keys($.floatThead.defaults)).join(', ')); + } + }); + + this.filter(':not(.'+opts.floatTableClass+')').each(function(){ + var $table = $(this); + if($table.data('floatThead-attached')){ + return true; //continue the each loop + } + if(!$table.is('table')){ + throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();'); + } + var $header = $table.find('thead:first'); + var $tbody = $table.find('tbody:first'); + if($header.length == 0){ + throw new Error('jQuery.floatThead must be run on a table that contains a element'); + } + var headerFloated = false; + var scrollingTop, scrollingBottom; + var scrollbarOffset = {vertical: 0, horizontal: 0}; + var scWidth = scrollbarWidth(); + var lastColumnCount = 0; //used by columnNum() + var $scrollContainer = opts.scrollContainer($table) || $([]); //guard against returned nulls + + var useAbsolutePositioning = opts.useAbsolutePositioning; + if(useAbsolutePositioning == null){ //defaults: locked=true, !locked=false + useAbsolutePositioning = opts.scrollContainer($table).length; + } + var $caption = $table.find("caption"); + var haveCaption = $caption.length == 1; + if(haveCaption){ + var captionAlignTop = ($caption.css("caption-side") || $caption.attr("align") || "top") === "top"; + } + + var $fthGrp = $(''); + + var locked = $scrollContainer.length > 0; + var wrappedContainer = false; //used with absolute positioning enabled. did we need to wrap the scrollContainer/table with a relative div? + var absoluteToFixedOnScroll = ieVersion <= 9 && !locked && useAbsolutePositioning; //on ie using absolute positioning doesnt look good with window scrolling, so we change positon to fixed on scroll, and then change it back to absolute when done. + var $floatTable = $(""); + var $floatColGroup = $(""); + var $tableColGroup = $(""); + var $fthRow = $(''); //created unstyled elements + var $floatContainer = $('
'); + var $newHeader = $("
"); + var $sizerRow = $(''); + var $sizerCells = $([]); + var $tableCells = $([]); //used for sizing - either $sizerCells or $tableColGroup cols. $tableColGroup cols are only created in chrome for borderCollapse:collapse because of a chrome bug. + var $headerCells = $([]); + var $fthCells = $([]); //created elements + + $newHeader.append($sizerRow); + $table.prepend($tableColGroup); + if(isChrome){ + $fthGrp.append($fthRow); + $table.append($fthGrp); + } + + $floatTable.append($floatColGroup); + $floatContainer.append($floatTable); + if(opts.copyTableClass){ + $floatTable.attr('class', $table.attr('class')); + } + $floatTable.attr({ //copy over some deprecated table attributes that people still like to use. Good thing poeple dont use colgroups... + 'cellpadding': $table.attr('cellpadding'), + 'cellspacing': $table.attr('cellspacing'), + 'border': $table.attr('border') + }); + + $floatTable.addClass(opts.floatTableClass).css('margin', 0); //must have no margins or you wont be able to click on things under floating table + + if(useAbsolutePositioning){ + var makeRelative = function($container, alwaysWrap){ + var positionCss = $container.css('position'); + var relativeToScrollContainer = (positionCss == "relative" || positionCss == "absolute"); + if(!relativeToScrollContainer || alwaysWrap){ + var css = {"paddingLeft": $container.css('paddingLeft'), "paddingRight": $container.css('paddingRight')}; + $floatContainer.css(css); + $container = $container.wrap("
").parent(); + wrappedContainer = true; + } + return $container; + }; + if(locked){ + var $relative = makeRelative($scrollContainer, true); + $relative.append($floatContainer); + } else { + makeRelative($table); + $table.after($floatContainer); + } + } else { + $table.after($floatContainer); + } + + + $floatContainer.css({ + position: useAbsolutePositioning ? 'absolute' : 'fixed', + marginTop: 0, + top: useAbsolutePositioning ? 0 : 'auto', + zIndex: opts.zIndex + }); + $floatContainer.addClass(opts.floatContainerClass) + updateScrollingOffsets(); + + var layoutFixed = {'table-layout': 'fixed'}; + var layoutAuto = {'table-layout': $table.css('tableLayout') || 'auto'}; + + function setHeaderHeight(){ + var headerHeight = $header.find(opts.cellTag).outerHeight(true); + $sizerRow.outerHeight(headerHeight); + $sizerCells.outerHeight(headerHeight); + } + + + function setFloatWidth(){ + var tableWidth = $table.outerWidth(); + var width = $scrollContainer.width() || tableWidth; + $floatContainer.width(width - scrollbarOffset.vertical); + if(locked){ + var percent = 100 * tableWidth / (width - scrollbarOffset.vertical); + $floatTable.css('width', percent+'%'); + } else { + $floatTable.outerWidth(tableWidth); + } + } + + function updateScrollingOffsets(){ + scrollingTop = (util.isFunction(opts.scrollingTop) ? opts.scrollingTop($table) : opts.scrollingTop) || 0; + scrollingBottom = (util.isFunction(opts.scrollingBottom) ? opts.scrollingBottom($table) : opts.scrollingBottom) || 0; + } + + /** + * get the number of columns and also rebuild resizer rows if the count is different then the last count + */ + function columnNum(){ + var $headerColumns = $header.find('tr:first>'+opts.cellTag); + var count = 0; + $headerColumns.each(function(){ + count += parseInt(($(this).attr('colspan') || 1), 10); + }); + if(count != lastColumnCount){ + lastColumnCount = count; + var cells = [], cols = [], psuedo = []; + for(var x = 0; x < count; x++){ + cells.push(''); + psuedo.push(""); + } + + cols = cols.join(''); + cells = cells.join(''); + + if(isChrome){ + psuedo = psuedo.join(''); + $fthRow.html(psuedo); + $fthCells = $fthRow.find('fthtd'); + } + + $sizerRow.html(cells); + $sizerCells = $sizerRow.find("th"); + $tableColGroup.html(cols); + $tableCells = $tableColGroup.find('col'); + $floatColGroup.html(cols); + $headerCells = $floatColGroup.find("col"); + + } + return count; + } + + function refloat(){ //make the thing float + if(!headerFloated){ + headerFloated = true; + $table.css(layoutFixed); + $floatTable.css(layoutFixed); + $floatTable.append($header); //append because colgroup must go first in chrome + $tbody.before($newHeader); + setHeaderHeight(); + } + } + function unfloat(){ //put the header back into the table + if(headerFloated){ + headerFloated = false; + $newHeader.detach(); + $table.prepend($header); + $table.css(layoutAuto); + $floatTable.css(layoutAuto); + } + } + function changePositioning(isAbsolute){ + if(useAbsolutePositioning != isAbsolute){ + useAbsolutePositioning = isAbsolute; + $floatContainer.css({ + position: useAbsolutePositioning ? 'absolute' : 'fixed' + }); + } + } + function getSizingRow($table, $cols, $fthCells, ieVersion){ + if(isChrome){ + return $fthCells; + } else if(ieVersion) { + return opts.getSizingRow($table, $cols, $fthCells); + } else { + return $cols; + } + } + + /** + * returns a function that updates the floating header's cell widths. + * @return {Function} + */ + function reflow(){ + var i; + var numCols = columnNum(); //if the tables columns change dynamically since last time (datatables) we need to rebuild the sizer rows and get new count + return function(){ + var $rowCells = getSizingRow($table, $tableCells, $fthCells, ieVersion); + if($rowCells.length == numCols && numCols > 0){ + unfloat(); + for(i=0; i < numCols; i++){ + var _rowcell = $rowCells.get(i); + var rowWidth = _rowcell.offsetWidth; + $headerCells.eq(i).width(rowWidth); + $tableCells.eq(i).width(rowWidth); + } + refloat(); + } else { + $floatTable.append($header); + $table.css(layoutAuto); + $floatTable.css(layoutAuto); + setHeaderHeight(); + } + }; + } + + /** + * first performs initial calculations that we expect to not change when the table, window, or scrolling container are scrolled. + * returns a function that calculates the floating container's top and left coords. takes into account if we are using page scrolling or inner scrolling + * @return {Function} + */ + function calculateFloatContainerPosFn(){ + var scrollingContainerTop = $scrollContainer.scrollTop(); + + //this floatEnd calc was moved out of the returned function because we assume the table height doesnt change (otherwise we must reinit by calling calculateFloatContainerPosFn) + var floatEnd; + var tableContainerGap = 0; + var captionHeight = haveCaption ? $caption.outerHeight(true) : 0; + var captionScrollOffset = captionAlignTop ? captionHeight : -captionHeight; + + var floatContainerHeight = $floatContainer.height(); + var tableOffset = $table.offset(); + if(locked){ + var containerOffset = $scrollContainer.offset(); + tableContainerGap = tableOffset.top - containerOffset.top + scrollingContainerTop; + if(haveCaption && captionAlignTop){ + tableContainerGap += captionHeight; + } + } else { + floatEnd = tableOffset.top - scrollingTop - floatContainerHeight + scrollingBottom + scrollbarOffset.horizontal; + } + var windowTop = $window.scrollTop(); + var windowLeft = $window.scrollLeft(); + var scrollContainerLeft = $scrollContainer.scrollLeft(); + scrollingContainerTop = $scrollContainer.scrollTop(); + + + + return function(eventType){ + if(eventType == 'windowScroll'){ + windowTop = $window.scrollTop(); + windowLeft = $window.scrollLeft(); + } else if(eventType == 'containerScroll'){ + scrollingContainerTop = $scrollContainer.scrollTop(); + scrollContainerLeft = $scrollContainer.scrollLeft(); + } else if(eventType != 'init') { + windowTop = $window.scrollTop(); + windowLeft = $window.scrollLeft(); + scrollingContainerTop = $scrollContainer.scrollTop(); + scrollContainerLeft = $scrollContainer.scrollLeft(); + } + if(isChrome && (windowTop < 0 || windowLeft < 0)){ //chrome overscroll effect at the top of the page - breaks fixed positioned floated headers + return; + } + + if(absoluteToFixedOnScroll){ + if(eventType == 'windowScrollDone'){ + changePositioning(true); //change to absolute + } else { + changePositioning(false); //change to fixed + } + } else if(eventType == 'windowScrollDone'){ + return null; //event is fired when they stop scrolling. ignore it if not 'absoluteToFixedOnScroll' + } + + tableOffset = $table.offset(); + if(haveCaption && captionAlignTop){ + tableOffset.top += captionHeight; + } + var top, left, tableHeight; + + if(locked && useAbsolutePositioning){ //inner scrolling, absolute positioning + if (tableContainerGap >= scrollingContainerTop) { + var gap = tableContainerGap - scrollingContainerTop; + gap = gap > 0 ? gap : 0; + top = gap; + } else { + top = wrappedContainer ? 0 : scrollingContainerTop; + //headers stop at the top of the viewport + } + left = 0; + } else if(!locked && useAbsolutePositioning) { //window scrolling, absolute positioning + tableHeight = $table.outerHeight(); + if(windowTop > floatEnd + tableHeight + captionScrollOffset){ + top = tableHeight - floatContainerHeight + captionScrollOffset; //scrolled past table + } else if (tableOffset.top > windowTop + scrollingTop) { + top = 0; //scrolling to table + unfloat(); + } else { + top = scrollingTop + windowTop - tableOffset.top + tableContainerGap + (captionAlignTop ? captionHeight : 0); + refloat(); //scrolling within table. header floated + } + left = 0; + } else if(locked && !useAbsolutePositioning){ //inner scrolling, fixed positioning + if (tableContainerGap > scrollingContainerTop) { + top = tableOffset.top - windowTop; + unfloat(); + } else { + top = tableOffset.top + scrollingContainerTop - windowTop - tableContainerGap; + refloat(); + //headers stop at the top of the viewport + } + left = tableOffset.left + scrollContainerLeft - windowLeft; + } else if(!locked && !useAbsolutePositioning) { //window scrolling, fixed positioning + tableHeight = $table.outerHeight(); + if(windowTop > floatEnd + tableHeight + captionScrollOffset){ + top = tableHeight + scrollingTop - windowTop + floatEnd + captionScrollOffset; + //scrolled past the bottom of the table + } else if (tableOffset.top > windowTop + scrollingTop) { + top = tableOffset.top - windowTop; + refloat(); + //scrolled past the top of the table + } else { + //scrolling within the table + top = scrollingTop; + } + left = tableOffset.left - windowLeft; + } + return {top: top, left: left}; + }; + } + /** + * returns a function that caches old floating container position and only updates css when the position changes + * @return {Function} + */ + function repositionFloatContainerFn(){ + var oldTop = null; + var oldLeft = null; + var oldScrollLeft = null; + return function(pos, setWidth, setHeight){ + if(pos != null && (oldTop != pos.top || oldLeft != pos.left)){ + $floatContainer.css({ + top: pos.top, + left: pos.left + }); + oldTop = pos.top; + oldLeft = pos.left; + } + if(setWidth){ + setFloatWidth(); + } + if(setHeight){ + setHeaderHeight(); + } + var scrollLeft = $scrollContainer.scrollLeft(); + if(oldScrollLeft != scrollLeft){ + $floatContainer.scrollLeft(scrollLeft); + oldScrollLeft = scrollLeft; + } + } + } + + /** + * checks if THIS table has scrollbars, and finds their widths + */ + function calculateScrollBarSize(){ //this should happen after the floating table has been positioned + if($scrollContainer.length){ + scrollbarOffset.horizontal = $scrollContainer.width() < $table.width() ? scWidth : 0; + scrollbarOffset.vertical = $scrollContainer.height() < $table.height() ? scWidth: 0; + } + } + //finish up. create all calculation functions and bind them to events + calculateScrollBarSize(); + + var flow; + + var ensureReflow = function(){ + flow = reflow(); + flow(); + }; + + ensureReflow(); + + var calculateFloatContainerPos = calculateFloatContainerPosFn(); + var repositionFloatContainer = repositionFloatContainerFn(); + + repositionFloatContainer(calculateFloatContainerPos('init'), true); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead + + var windowScrollDoneEvent = util.debounce(function(){ + repositionFloatContainer(calculateFloatContainerPos('windowScrollDone'), false); + }, 300); + + var windowScrollEvent = function(){ + repositionFloatContainer(calculateFloatContainerPos('windowScroll'), false); + windowScrollDoneEvent(); + }; + var containerScrollEvent = function(){ + repositionFloatContainer(calculateFloatContainerPos('containerScroll'), false); + }; + + + var windowResizeEvent = function(){ + updateScrollingOffsets(); + calculateScrollBarSize(); + ensureReflow(); + calculateFloatContainerPos = calculateFloatContainerPosFn(); + repositionFloatContainer = repositionFloatContainerFn(); + repositionFloatContainer(calculateFloatContainerPos('resize'), true, true); + }; + var reflowEvent = util.debounce(function(){ + calculateScrollBarSize(); + updateScrollingOffsets(); + ensureReflow(); + calculateFloatContainerPos = calculateFloatContainerPosFn(); + repositionFloatContainer(calculateFloatContainerPos('reflow'), true); + }, 1); + if(locked){ //internal scrolling + if(useAbsolutePositioning){ + $scrollContainer.bind('scroll.floatTHead', containerScrollEvent); + } else { + $scrollContainer.bind('scroll.floatTHead', containerScrollEvent); + $window.bind('scroll.floatTHead', windowScrollEvent); + } + } else { //window scrolling + $window.bind('scroll.floatTHead', windowScrollEvent); + } + + $window.bind('load.floatTHead', reflowEvent); //for tables with images + + windowResize(opts.debounceResizeMs, windowResizeEvent); + $table.bind('reflow', reflowEvent); + if(isDatatable($table)){ + $table + .bind('filter', reflowEvent) + .bind('sort', reflowEvent) + .bind('page', reflowEvent); + } + + //attach some useful functions to the table. + $table.data('floatThead-attached', { + destroy: function(){ + $table.css(layoutAuto); + $tableColGroup.remove(); + isChrome && $fthGrp.remove(); + if($newHeader.parent().length){ //only if its in the dom + $newHeader.replaceWith($header); + } + $table.unbind('reflow'); + reflowEvent = windowResizeEvent = containerScrollEvent = windowScrollEvent = function() {}; + $scrollContainer.unbind('scroll.floatTHead'); + $floatContainer.remove(); + $table.data('floatThead-attached', false); + floatTheadCreated--; + if(floatTheadCreated == 0){ + $window.unbind('scroll.floatTHead'); + $window.unbind('resize.floatTHead'); + $window.unbind('load.floatTHead'); + } + }, + reflow: function(){ + reflowEvent(); + }, + setHeaderHeight: function(){ + setHeaderHeight(); + }, + getFloatContainer: function(){ + return $floatContainer; + }, + getRowGroups: function(){ + if(headerFloated){ + return $floatContainer.find("thead").add($table.find("tbody,tfoot")); + } else { + return $table.find("thead,tbody,tfoot"); + } + } + }); + floatTheadCreated++; + }); + return this; + }; +})(jQuery); diff --git a/dist/jquery.floatThead-slim.min.js b/dist/jquery.floatThead-slim.min.js new file mode 100644 index 0000000..97694bf --- /dev/null +++ b/dist/jquery.floatThead-slim.min.js @@ -0,0 +1,3 @@ +// @preserve jQuery.floatThead 1.2.3 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak +// @license Licensed under http://creativecommons.org/licenses/by-sa/4.0/ +!function(a){function b(a,b){j.bind("resize.floatTHead",f.debounce(b,a))}function c(a){window.console&&window.console&&window.console.log&&window.console.log(a)}function d(){var b=a('
');a("body").append(b);var c=b.innerWidth(),d=a("div",b).innerWidth();return b.remove(),c-d}function e(a){if(a.dataTableSettings)for(var b=0;btd")},floatTableClass:"floatThead-table",floatWrapperClass:"floatThead-wrapper",floatContainerClass:"floatThead-container",copyTableClass:!0,debug:!1};var f=window._,g=function(){for(var a=3,b=document.createElement("b"),c=b.all||[];b.innerHTML="",c[0];);return a>4?a:document.documentMode}(),h=null,i=function(){if(g)return!1;var b=a("
'); + cols.push('
");a("body").append(b);var c=b.find("col").width();return b.remove(),0==c},j=a(window),k=0;a.fn.floatThead=function(l){if(l=l||{},!f&&(f=window._||a.floatThead._,!f))throw new Error("jquery.floatThead-slim.js requires underscore. You should use the non-lite version since you do not have underscore.");if(8>g)return this;if(null==h&&(h=i(),h&&(document.createElement("fthtr"),document.createElement("fthtd"),document.createElement("fthfoot"))),f.isString(l)){var m=l,n=this;return this.filter("table").each(function(){var b=a(this).data("floatThead-attached");if(b&&f.isFunction(b[m])){var c=b[m]();"undefined"!=typeof c&&(n=c)}}),n}var o=a.extend({},a.floatThead.defaults||{},l);return a.each(l,function(b,d){d in a.floatThead.defaults||!o.debug||c("jQuery.floatThead: used ["+d+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+f.keys(a.floatThead.defaults).join(", "))}),this.filter(":not(."+o.floatTableClass+")").each(function(){function c(){var a=x.find(o.cellTag).outerHeight(!0);U.outerHeight(a),V.outerHeight(a)}function i(){var a=w.outerWidth(),b=F.width()||a;if(S.width(b-C.vertical),L){var c=100*a/(b-C.vertical);O.css("width",c+"%")}else O.outerWidth(a)}function l(){z=(f.isFunction(o.scrollingTop)?o.scrollingTop(w):o.scrollingTop)||0,A=(f.isFunction(o.scrollingBottom)?o.scrollingBottom(w):o.scrollingBottom)||0}function m(){var b=x.find("tr:first>"+o.cellTag),c=0;if(b.each(function(){c+=parseInt(a(this).attr("colspan")||1,10)}),c!=E){E=c;for(var d=[],e=[],f=[],g=0;c>g;g++)d.push(''),e.push(""),f.push("");e=e.join(""),d=d.join(""),h&&(f=f.join(""),R.html(f),Y=R.find("fthtd")),U.html(d),V=U.find("th"),Q.html(e),W=Q.find("col"),P.html(e),X=P.find("col")}return c}function n(){B||(B=!0,w.css(_),O.css(_),O.append(x),y.before(T),c())}function p(){B&&(B=!1,T.detach(),w.prepend(x),w.css(ab),O.css(ab))}function q(a){G!=a&&(G=a,S.css({position:G?"absolute":"fixed"}))}function r(a,b,c,d){return h?c:d?o.getSizingRow(a,b,c):b}function s(){var a,b=m();return function(){var d=r(w,W,Y,g);if(d.length==b&&b>0){for(p(),a=0;b>a;a++){var e=d.get(a),f=e.offsetWidth;X.eq(a).width(f),W.eq(a).width(f)}n()}else O.append(x),w.css(ab),O.css(ab),c()}}function t(){var a,b=F.scrollTop(),c=0,d=I?H.outerHeight(!0):0,e=J?d:-d,f=S.height(),g=w.offset();if(L){var i=F.offset();c=g.top-i.top+b,I&&J&&(c+=d)}else a=g.top-z-f+A+C.horizontal;var k=j.scrollTop(),l=j.scrollLeft(),m=F.scrollLeft();return b=F.scrollTop(),function(i){if("windowScroll"==i?(k=j.scrollTop(),l=j.scrollLeft()):"containerScroll"==i?(b=F.scrollTop(),m=F.scrollLeft()):"init"!=i&&(k=j.scrollTop(),l=j.scrollLeft(),b=F.scrollTop(),m=F.scrollLeft()),!h||!(0>k||0>l)){if(N)"windowScrollDone"==i?q(!0):q(!1);else if("windowScrollDone"==i)return null;g=w.offset(),I&&J&&(g.top+=d);var o,r,s;if(L&&G){if(c>=b){var t=c-b;t=t>0?t:0,o=t}else o=M?0:b;r=0}else!L&&G?(s=w.outerHeight(),k>a+s+e?o=s-f+e:g.top>k+z?(o=0,p()):(o=z+k-g.top+c+(J?d:0),n()),r=0):L&&!G?(c>b?(o=g.top-k,p()):(o=g.top+b-k-c,n()),r=g.left+m-l):L||G||(s=w.outerHeight(),k>a+s+e?o=s+z-k+a+e:g.top>k+z?(o=g.top-k,n()):o=z,r=g.left-l);return{top:o,left:r}}}}function u(){var a=null,b=null,d=null;return function(e,f,g){null==e||a==e.top&&b==e.left||(S.css({top:e.top,left:e.left}),a=e.top,b=e.left),f&&i(),g&&c();var h=F.scrollLeft();d!=h&&(S.scrollLeft(h),d=h)}}function v(){F.length&&(C.horizontal=F.width() element");var z,A,B=!1,C={vertical:0,horizontal:0},D=d(),E=0,F=o.scrollContainer(w)||a([]),G=o.useAbsolutePositioning;null==G&&(G=o.scrollContainer(w).length);var H=w.find("caption"),I=1==H.length;if(I)var J="top"===(H.css("caption-side")||H.attr("align")||"top");var K=a(''),L=F.length>0,M=!1,N=9>=g&&!L&&G,O=a(""),P=a(""),Q=a(""),R=a(''),S=a('
'),T=a("
"),U=a(''),V=a([]),W=a([]),X=a([]),Y=a([]);if(T.append(U),w.prepend(Q),h&&(K.append(R),w.append(K)),O.append(P),S.append(O),o.copyTableClass&&O.attr("class",w.attr("class")),O.attr({cellpadding:w.attr("cellpadding"),cellspacing:w.attr("cellspacing"),border:w.attr("border")}),O.addClass(o.floatTableClass).css("margin",0),G){var Z=function(a,b){var c=a.css("position"),d="relative"==c||"absolute"==c;if(!d||b){var e={paddingLeft:a.css("paddingLeft"),paddingRight:a.css("paddingRight")};S.css(e),a=a.wrap("
").parent(),M=!0}return a};if(L){var $=Z(F,!0);$.append(S)}else Z(w),w.after(S)}else w.after(S);S.css({position:G?"absolute":"fixed",marginTop:0,top:G?0:"auto",zIndex:o.zIndex}),S.addClass(o.floatContainerClass),l();var _={"table-layout":"fixed"},ab={"table-layout":w.css("tableLayout")||"auto"};v();var bb,cb=function(){(bb=s())()};cb();var db=t(),eb=u();eb(db("init"),!0);var fb=f.debounce(function(){eb(db("windowScrollDone"),!1)},300),gb=function(){eb(db("windowScroll"),!1),fb()},hb=function(){eb(db("containerScroll"),!1)},ib=function(){l(),v(),cb(),db=t(),(eb=u())(db("resize"),!0,!0)},jb=f.debounce(function(){v(),l(),cb(),db=t(),eb(db("reflow"),!0)},1);L?G?F.bind("scroll.floatTHead",hb):(F.bind("scroll.floatTHead",hb),j.bind("scroll.floatTHead",gb)):j.bind("scroll.floatTHead",gb),j.bind("load.floatTHead",jb),b(o.debounceResizeMs,ib),w.bind("reflow",jb),e(w)&&w.bind("filter",jb).bind("sort",jb).bind("page",jb),w.data("floatThead-attached",{destroy:function(){w.css(ab),Q.remove(),h&&K.remove(),T.parent().length&&T.replaceWith(x),w.unbind("reflow"),jb=ib=hb=gb=function(){},F.unbind("scroll.floatTHead"),S.remove(),w.data("floatThead-attached",!1),k--,0==k&&(j.unbind("scroll.floatTHead"),j.unbind("resize.floatTHead"),j.unbind("load.floatTHead"))},reflow:function(){jb()},setHeaderHeight:function(){c()},getFloatContainer:function(){return S},getRowGroups:function(){return B?S.find("thead").add(w.find("tbody,tfoot")):w.find("thead,tbody,tfoot")}}),k++}),this}}(jQuery); diff --git a/dist/jquery.floatThead.js b/dist/jquery.floatThead.js new file mode 100644 index 0000000..f426692 --- /dev/null +++ b/dist/jquery.floatThead.js @@ -0,0 +1,706 @@ +// @preserve jQuery.floatThead 1.2.3 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak +// @license Licensed under http://creativecommons.org/licenses/by-sa/4.0/ + +/* @author Misha Koryak + * @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header + * + * Dependencies: + * jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core + * + * http://mkoryak.github.io/floatThead/ + * + * Tested on FF13+, Chrome 21+, IE8, IE9, IE10, IE11 + * + */ +(function( $ ) { + /** + * provides a default config object. You can modify this after including this script if you want to change the init defaults + * @type {Object} + */ + $.floatThead = $.floatThead || {}; + $.floatThead.defaults = { + cellTag: 'th:visible', //thead cells are this + zIndex: 1001, //zindex of the floating thead (actually a container div) + debounceResizeMs: 1, + useAbsolutePositioning: true, //if set to NULL - defaults: has scrollContainer=true, doesn't have scrollContainer=false + scrollingTop: 0, //String or function($table) - offset from top of window where the header should not pass above + scrollingBottom: 0, //String or function($table) - offset from the bottom of the table where the header should stop scrolling + scrollContainer: function($table){ + return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars + }, + getSizingRow: function($table, $cols, $fthCells){ // this is only called when using IE, + // override it if the first row of the table is going to contain colgroups (any cell spans greater then one col) + // it should return a jquery object containing a wrapped set of table cells comprising a row that contains no col spans and is visible + return $table.find('tbody tr:visible:first>td'); + }, + floatTableClass: 'floatThead-table', + floatWrapperClass: 'floatThead-wrapper', + floatContainerClass: 'floatThead-container', + copyTableClass: true, //copy 'class' attribute from table into the floated table so that the styles match. + debug: false //print possible issues (that don't prevent script loading) to console, if console exists. + }; + + var util = window._; + + + //browser stuff + var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];b.innerHTML="",c[0];);return 4
"); + $('body').append($table); + var width = $table.find('col').width(); + $table.remove(); + return width == 0; + }; + + var $window = $(window); + var floatTheadCreated = 0; + + + /** + * @param debounceMs + * @param cb + */ + + function windowResize(debounceMs, cb){ + $window.bind('resize.floatTHead', util.debounce(cb, debounceMs)); //TODO: check if resize bug is gone in IE8 + + } + + + function debug(str){ + window.console && window.console && window.console.log && window.console.log(str); + } + + /** + * try to calculate the scrollbar width for your browser/os + * @return {Number} + */ + function scrollbarWidth() { + var $div = $( //borrowed from anti-scroll + '
' + + '
' + ); + $('body').append($div); + var w1 = $div.innerWidth(); + var w2 = $('div', $div).innerWidth(); + $div.remove(); + return w1 - w2; + } + /** + * Check if a given table has been datatableized (http://datatables.net) + * @param $table + * @return {Boolean} + */ + function isDatatable($table){ + if($table.dataTableSettings){ + for(var i = 0; i < $table.dataTableSettings.length; i++){ + var table = $table.dataTableSettings[i].nTable; + if($table[0] == table){ + return true; + } + } + } + return false; + } + $.fn.floatThead = function(map){ + map = map || {}; + if(!util){ //may have been included after the script? lets try to grab it again. + util = window._ || $.floatThead._; + if(!util){ + throw new Error("jquery.floatThead-slim.js requires underscore. You should use the non-lite version since you do not have underscore."); + } + } + + if(ieVersion < 8){ + return this; //no more crappy browser support. + } + + if(isChrome == null){ //make sure this is done only once no matter how many times you call the plugin fn + isChrome = isChromeCheck(); //need to call this after dom ready, and now it is. + if(isChrome){ + //because chrome cant read width, these elements are used for sizing the table. Need to create new elements because they must be unstyled by user's css. + document.createElement('fthtr'); //tr + document.createElement('fthtd'); //td + document.createElement('fthfoot'); //tfoot + } + } + if(util.isString(map)){ + var command = map; + var ret = this; + this.filter('table').each(function(){ + var obj = $(this).data('floatThead-attached'); + if(obj && util.isFunction(obj[command])){ + var r = obj[command](); + if(typeof r !== 'undefined'){ + ret = r; + } + } + }); + return ret; + } + var opts = $.extend({}, $.floatThead.defaults || {}, map); + + $.each(map, function(val, key){ + if((!(key in $.floatThead.defaults)) && opts.debug){ + debug("jQuery.floatThead: used ["+key+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+ (util.keys($.floatThead.defaults)).join(', ')); + } + }); + + this.filter(':not(.'+opts.floatTableClass+')').each(function(){ + var $table = $(this); + if($table.data('floatThead-attached')){ + return true; //continue the each loop + } + if(!$table.is('table')){ + throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();'); + } + var $header = $table.find('thead:first'); + var $tbody = $table.find('tbody:first'); + if($header.length == 0){ + throw new Error('jQuery.floatThead must be run on a table that contains a element'); + } + var headerFloated = false; + var scrollingTop, scrollingBottom; + var scrollbarOffset = {vertical: 0, horizontal: 0}; + var scWidth = scrollbarWidth(); + var lastColumnCount = 0; //used by columnNum() + var $scrollContainer = opts.scrollContainer($table) || $([]); //guard against returned nulls + + var useAbsolutePositioning = opts.useAbsolutePositioning; + if(useAbsolutePositioning == null){ //defaults: locked=true, !locked=false + useAbsolutePositioning = opts.scrollContainer($table).length; + } + var $caption = $table.find("caption"); + var haveCaption = $caption.length == 1; + if(haveCaption){ + var captionAlignTop = ($caption.css("caption-side") || $caption.attr("align") || "top") === "top"; + } + + var $fthGrp = $(''); + + var locked = $scrollContainer.length > 0; + var wrappedContainer = false; //used with absolute positioning enabled. did we need to wrap the scrollContainer/table with a relative div? + var absoluteToFixedOnScroll = ieVersion <= 9 && !locked && useAbsolutePositioning; //on ie using absolute positioning doesnt look good with window scrolling, so we change positon to fixed on scroll, and then change it back to absolute when done. + var $floatTable = $(""); + var $floatColGroup = $(""); + var $tableColGroup = $(""); + var $fthRow = $(''); //created unstyled elements + var $floatContainer = $('
'); + var $newHeader = $("
"); + var $sizerRow = $(''); + var $sizerCells = $([]); + var $tableCells = $([]); //used for sizing - either $sizerCells or $tableColGroup cols. $tableColGroup cols are only created in chrome for borderCollapse:collapse because of a chrome bug. + var $headerCells = $([]); + var $fthCells = $([]); //created elements + + $newHeader.append($sizerRow); + $table.prepend($tableColGroup); + if(isChrome){ + $fthGrp.append($fthRow); + $table.append($fthGrp); + } + + $floatTable.append($floatColGroup); + $floatContainer.append($floatTable); + if(opts.copyTableClass){ + $floatTable.attr('class', $table.attr('class')); + } + $floatTable.attr({ //copy over some deprecated table attributes that people still like to use. Good thing poeple dont use colgroups... + 'cellpadding': $table.attr('cellpadding'), + 'cellspacing': $table.attr('cellspacing'), + 'border': $table.attr('border') + }); + + $floatTable.addClass(opts.floatTableClass).css('margin', 0); //must have no margins or you wont be able to click on things under floating table + + if(useAbsolutePositioning){ + var makeRelative = function($container, alwaysWrap){ + var positionCss = $container.css('position'); + var relativeToScrollContainer = (positionCss == "relative" || positionCss == "absolute"); + if(!relativeToScrollContainer || alwaysWrap){ + var css = {"paddingLeft": $container.css('paddingLeft'), "paddingRight": $container.css('paddingRight')}; + $floatContainer.css(css); + $container = $container.wrap("
").parent(); + wrappedContainer = true; + } + return $container; + }; + if(locked){ + var $relative = makeRelative($scrollContainer, true); + $relative.append($floatContainer); + } else { + makeRelative($table); + $table.after($floatContainer); + } + } else { + $table.after($floatContainer); + } + + + $floatContainer.css({ + position: useAbsolutePositioning ? 'absolute' : 'fixed', + marginTop: 0, + top: useAbsolutePositioning ? 0 : 'auto', + zIndex: opts.zIndex + }); + $floatContainer.addClass(opts.floatContainerClass) + updateScrollingOffsets(); + + var layoutFixed = {'table-layout': 'fixed'}; + var layoutAuto = {'table-layout': $table.css('tableLayout') || 'auto'}; + + function setHeaderHeight(){ + var headerHeight = $header.find(opts.cellTag).outerHeight(true); + $sizerRow.outerHeight(headerHeight); + $sizerCells.outerHeight(headerHeight); + } + + + function setFloatWidth(){ + var tableWidth = $table.outerWidth(); + var width = $scrollContainer.width() || tableWidth; + $floatContainer.width(width - scrollbarOffset.vertical); + if(locked){ + var percent = 100 * tableWidth / (width - scrollbarOffset.vertical); + $floatTable.css('width', percent+'%'); + } else { + $floatTable.outerWidth(tableWidth); + } + } + + function updateScrollingOffsets(){ + scrollingTop = (util.isFunction(opts.scrollingTop) ? opts.scrollingTop($table) : opts.scrollingTop) || 0; + scrollingBottom = (util.isFunction(opts.scrollingBottom) ? opts.scrollingBottom($table) : opts.scrollingBottom) || 0; + } + + /** + * get the number of columns and also rebuild resizer rows if the count is different then the last count + */ + function columnNum(){ + var $headerColumns = $header.find('tr:first>'+opts.cellTag); + var count = 0; + $headerColumns.each(function(){ + count += parseInt(($(this).attr('colspan') || 1), 10); + }); + if(count != lastColumnCount){ + lastColumnCount = count; + var cells = [], cols = [], psuedo = []; + for(var x = 0; x < count; x++){ + cells.push(''); + psuedo.push(""); + } + + cols = cols.join(''); + cells = cells.join(''); + + if(isChrome){ + psuedo = psuedo.join(''); + $fthRow.html(psuedo); + $fthCells = $fthRow.find('fthtd'); + } + + $sizerRow.html(cells); + $sizerCells = $sizerRow.find("th"); + $tableColGroup.html(cols); + $tableCells = $tableColGroup.find('col'); + $floatColGroup.html(cols); + $headerCells = $floatColGroup.find("col"); + + } + return count; + } + + function refloat(){ //make the thing float + if(!headerFloated){ + headerFloated = true; + $table.css(layoutFixed); + $floatTable.css(layoutFixed); + $floatTable.append($header); //append because colgroup must go first in chrome + $tbody.before($newHeader); + setHeaderHeight(); + } + } + function unfloat(){ //put the header back into the table + if(headerFloated){ + headerFloated = false; + $newHeader.detach(); + $table.prepend($header); + $table.css(layoutAuto); + $floatTable.css(layoutAuto); + } + } + function changePositioning(isAbsolute){ + if(useAbsolutePositioning != isAbsolute){ + useAbsolutePositioning = isAbsolute; + $floatContainer.css({ + position: useAbsolutePositioning ? 'absolute' : 'fixed' + }); + } + } + function getSizingRow($table, $cols, $fthCells, ieVersion){ + if(isChrome){ + return $fthCells; + } else if(ieVersion) { + return opts.getSizingRow($table, $cols, $fthCells); + } else { + return $cols; + } + } + + /** + * returns a function that updates the floating header's cell widths. + * @return {Function} + */ + function reflow(){ + var i; + var numCols = columnNum(); //if the tables columns change dynamically since last time (datatables) we need to rebuild the sizer rows and get new count + return function(){ + var $rowCells = getSizingRow($table, $tableCells, $fthCells, ieVersion); + if($rowCells.length == numCols && numCols > 0){ + unfloat(); + for(i=0; i < numCols; i++){ + var _rowcell = $rowCells.get(i); + var rowWidth = _rowcell.offsetWidth; + $headerCells.eq(i).width(rowWidth); + $tableCells.eq(i).width(rowWidth); + } + refloat(); + } else { + $floatTable.append($header); + $table.css(layoutAuto); + $floatTable.css(layoutAuto); + setHeaderHeight(); + } + }; + } + + /** + * first performs initial calculations that we expect to not change when the table, window, or scrolling container are scrolled. + * returns a function that calculates the floating container's top and left coords. takes into account if we are using page scrolling or inner scrolling + * @return {Function} + */ + function calculateFloatContainerPosFn(){ + var scrollingContainerTop = $scrollContainer.scrollTop(); + + //this floatEnd calc was moved out of the returned function because we assume the table height doesnt change (otherwise we must reinit by calling calculateFloatContainerPosFn) + var floatEnd; + var tableContainerGap = 0; + var captionHeight = haveCaption ? $caption.outerHeight(true) : 0; + var captionScrollOffset = captionAlignTop ? captionHeight : -captionHeight; + + var floatContainerHeight = $floatContainer.height(); + var tableOffset = $table.offset(); + if(locked){ + var containerOffset = $scrollContainer.offset(); + tableContainerGap = tableOffset.top - containerOffset.top + scrollingContainerTop; + if(haveCaption && captionAlignTop){ + tableContainerGap += captionHeight; + } + } else { + floatEnd = tableOffset.top - scrollingTop - floatContainerHeight + scrollingBottom + scrollbarOffset.horizontal; + } + var windowTop = $window.scrollTop(); + var windowLeft = $window.scrollLeft(); + var scrollContainerLeft = $scrollContainer.scrollLeft(); + scrollingContainerTop = $scrollContainer.scrollTop(); + + + + return function(eventType){ + if(eventType == 'windowScroll'){ + windowTop = $window.scrollTop(); + windowLeft = $window.scrollLeft(); + } else if(eventType == 'containerScroll'){ + scrollingContainerTop = $scrollContainer.scrollTop(); + scrollContainerLeft = $scrollContainer.scrollLeft(); + } else if(eventType != 'init') { + windowTop = $window.scrollTop(); + windowLeft = $window.scrollLeft(); + scrollingContainerTop = $scrollContainer.scrollTop(); + scrollContainerLeft = $scrollContainer.scrollLeft(); + } + if(isChrome && (windowTop < 0 || windowLeft < 0)){ //chrome overscroll effect at the top of the page - breaks fixed positioned floated headers + return; + } + + if(absoluteToFixedOnScroll){ + if(eventType == 'windowScrollDone'){ + changePositioning(true); //change to absolute + } else { + changePositioning(false); //change to fixed + } + } else if(eventType == 'windowScrollDone'){ + return null; //event is fired when they stop scrolling. ignore it if not 'absoluteToFixedOnScroll' + } + + tableOffset = $table.offset(); + if(haveCaption && captionAlignTop){ + tableOffset.top += captionHeight; + } + var top, left, tableHeight; + + if(locked && useAbsolutePositioning){ //inner scrolling, absolute positioning + if (tableContainerGap >= scrollingContainerTop) { + var gap = tableContainerGap - scrollingContainerTop; + gap = gap > 0 ? gap : 0; + top = gap; + } else { + top = wrappedContainer ? 0 : scrollingContainerTop; + //headers stop at the top of the viewport + } + left = 0; + } else if(!locked && useAbsolutePositioning) { //window scrolling, absolute positioning + tableHeight = $table.outerHeight(); + if(windowTop > floatEnd + tableHeight + captionScrollOffset){ + top = tableHeight - floatContainerHeight + captionScrollOffset; //scrolled past table + } else if (tableOffset.top > windowTop + scrollingTop) { + top = 0; //scrolling to table + unfloat(); + } else { + top = scrollingTop + windowTop - tableOffset.top + tableContainerGap + (captionAlignTop ? captionHeight : 0); + refloat(); //scrolling within table. header floated + } + left = 0; + } else if(locked && !useAbsolutePositioning){ //inner scrolling, fixed positioning + if (tableContainerGap > scrollingContainerTop) { + top = tableOffset.top - windowTop; + unfloat(); + } else { + top = tableOffset.top + scrollingContainerTop - windowTop - tableContainerGap; + refloat(); + //headers stop at the top of the viewport + } + left = tableOffset.left + scrollContainerLeft - windowLeft; + } else if(!locked && !useAbsolutePositioning) { //window scrolling, fixed positioning + tableHeight = $table.outerHeight(); + if(windowTop > floatEnd + tableHeight + captionScrollOffset){ + top = tableHeight + scrollingTop - windowTop + floatEnd + captionScrollOffset; + //scrolled past the bottom of the table + } else if (tableOffset.top > windowTop + scrollingTop) { + top = tableOffset.top - windowTop; + refloat(); + //scrolled past the top of the table + } else { + //scrolling within the table + top = scrollingTop; + } + left = tableOffset.left - windowLeft; + } + return {top: top, left: left}; + }; + } + /** + * returns a function that caches old floating container position and only updates css when the position changes + * @return {Function} + */ + function repositionFloatContainerFn(){ + var oldTop = null; + var oldLeft = null; + var oldScrollLeft = null; + return function(pos, setWidth, setHeight){ + if(pos != null && (oldTop != pos.top || oldLeft != pos.left)){ + $floatContainer.css({ + top: pos.top, + left: pos.left + }); + oldTop = pos.top; + oldLeft = pos.left; + } + if(setWidth){ + setFloatWidth(); + } + if(setHeight){ + setHeaderHeight(); + } + var scrollLeft = $scrollContainer.scrollLeft(); + if(oldScrollLeft != scrollLeft){ + $floatContainer.scrollLeft(scrollLeft); + oldScrollLeft = scrollLeft; + } + } + } + + /** + * checks if THIS table has scrollbars, and finds their widths + */ + function calculateScrollBarSize(){ //this should happen after the floating table has been positioned + if($scrollContainer.length){ + scrollbarOffset.horizontal = $scrollContainer.width() < $table.width() ? scWidth : 0; + scrollbarOffset.vertical = $scrollContainer.height() < $table.height() ? scWidth: 0; + } + } + //finish up. create all calculation functions and bind them to events + calculateScrollBarSize(); + + var flow; + + var ensureReflow = function(){ + flow = reflow(); + flow(); + }; + + ensureReflow(); + + var calculateFloatContainerPos = calculateFloatContainerPosFn(); + var repositionFloatContainer = repositionFloatContainerFn(); + + repositionFloatContainer(calculateFloatContainerPos('init'), true); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead + + var windowScrollDoneEvent = util.debounce(function(){ + repositionFloatContainer(calculateFloatContainerPos('windowScrollDone'), false); + }, 300); + + var windowScrollEvent = function(){ + repositionFloatContainer(calculateFloatContainerPos('windowScroll'), false); + windowScrollDoneEvent(); + }; + var containerScrollEvent = function(){ + repositionFloatContainer(calculateFloatContainerPos('containerScroll'), false); + }; + + + var windowResizeEvent = function(){ + updateScrollingOffsets(); + calculateScrollBarSize(); + ensureReflow(); + calculateFloatContainerPos = calculateFloatContainerPosFn(); + repositionFloatContainer = repositionFloatContainerFn(); + repositionFloatContainer(calculateFloatContainerPos('resize'), true, true); + }; + var reflowEvent = util.debounce(function(){ + calculateScrollBarSize(); + updateScrollingOffsets(); + ensureReflow(); + calculateFloatContainerPos = calculateFloatContainerPosFn(); + repositionFloatContainer(calculateFloatContainerPos('reflow'), true); + }, 1); + if(locked){ //internal scrolling + if(useAbsolutePositioning){ + $scrollContainer.bind('scroll.floatTHead', containerScrollEvent); + } else { + $scrollContainer.bind('scroll.floatTHead', containerScrollEvent); + $window.bind('scroll.floatTHead', windowScrollEvent); + } + } else { //window scrolling + $window.bind('scroll.floatTHead', windowScrollEvent); + } + + $window.bind('load.floatTHead', reflowEvent); //for tables with images + + windowResize(opts.debounceResizeMs, windowResizeEvent); + $table.bind('reflow', reflowEvent); + if(isDatatable($table)){ + $table + .bind('filter', reflowEvent) + .bind('sort', reflowEvent) + .bind('page', reflowEvent); + } + + //attach some useful functions to the table. + $table.data('floatThead-attached', { + destroy: function(){ + $table.css(layoutAuto); + $tableColGroup.remove(); + isChrome && $fthGrp.remove(); + if($newHeader.parent().length){ //only if its in the dom + $newHeader.replaceWith($header); + } + $table.unbind('reflow'); + reflowEvent = windowResizeEvent = containerScrollEvent = windowScrollEvent = function() {}; + $scrollContainer.unbind('scroll.floatTHead'); + $floatContainer.remove(); + $table.data('floatThead-attached', false); + floatTheadCreated--; + if(floatTheadCreated == 0){ + $window.unbind('scroll.floatTHead'); + $window.unbind('resize.floatTHead'); + $window.unbind('load.floatTHead'); + } + }, + reflow: function(){ + reflowEvent(); + }, + setHeaderHeight: function(){ + setHeaderHeight(); + }, + getFloatContainer: function(){ + return $floatContainer; + }, + getRowGroups: function(){ + if(headerFloated){ + return $floatContainer.find("thead").add($table.find("tbody,tfoot")); + } else { + return $table.find("thead,tbody,tfoot"); + } + } + }); + floatTheadCreated++; + }); + return this; + }; +})(jQuery); +/* jQuery.floatThead.utils - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak + * Licensed under CC BY-SA 4.0 and MIT + * + * This file is required if you do not use underscore in your project and you want to use floatThead. + * It contains functions from underscore that the plugin uses. + * + * YOU DON'T NEED TO INCLUDE THIS IF YOU ALREADY INCLUDE UNDERSCORE! + * + */ + +(function(){ + + $.floatThead = $.floatThead || {}; + + $.floatThead._ = window._ || (function(){ + var that = {}; + var hasOwnProperty = Object.prototype.hasOwnProperty, isThings = ['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp']; + that.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + that.keys = function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (that.has(obj, key)) keys.push(key); + return keys; + }; + $.each(isThings, function(){ + var name = this; + that['is' + name] = function(obj) { + return Object.prototype.toString.call(obj) == '[object ' + name + ']'; + }; + }); + that.debounce = function(func, wait, immediate) { + var timeout, args, context, timestamp, result; + return function() { + context = this; + args = arguments; + timestamp = new Date(); + var later = function() { + var last = (new Date()) - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) result = func.apply(context, args); + } + }; + var callNow = immediate && !timeout; + if (!timeout) { + timeout = setTimeout(later, wait); + } + if (callNow) result = func.apply(context, args); + return result; + }; + }; + return that; + })(); +})(); + diff --git a/dist/jquery.floatThead.min.js b/dist/jquery.floatThead.min.js new file mode 100644 index 0000000..4ba2b91 --- /dev/null +++ b/dist/jquery.floatThead.min.js @@ -0,0 +1,3 @@ +// @preserve jQuery.floatThead 1.2.3 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak +// @license Licensed under http://creativecommons.org/licenses/by-sa/4.0/ +!function(a){function b(a,b){j.bind("resize.floatTHead",f.debounce(b,a))}function c(a){window.console&&window.console&&window.console.log&&window.console.log(a)}function d(){var b=a('
');a("body").append(b);var c=b.innerWidth(),d=a("div",b).innerWidth();return b.remove(),c-d}function e(a){if(a.dataTableSettings)for(var b=0;btd")},floatTableClass:"floatThead-table",floatWrapperClass:"floatThead-wrapper",floatContainerClass:"floatThead-container",copyTableClass:!0,debug:!1};var f=window._,g=function(){for(var a=3,b=document.createElement("b"),c=b.all||[];b.innerHTML="",c[0];);return a>4?a:document.documentMode}(),h=null,i=function(){if(g)return!1;var b=a("
'); + cols.push('
");a("body").append(b);var c=b.find("col").width();return b.remove(),0==c},j=a(window),k=0;a.fn.floatThead=function(l){if(l=l||{},!f&&(f=window._||a.floatThead._,!f))throw new Error("jquery.floatThead-slim.js requires underscore. You should use the non-lite version since you do not have underscore.");if(8>g)return this;if(null==h&&(h=i(),h&&(document.createElement("fthtr"),document.createElement("fthtd"),document.createElement("fthfoot"))),f.isString(l)){var m=l,n=this;return this.filter("table").each(function(){var b=a(this).data("floatThead-attached");if(b&&f.isFunction(b[m])){var c=b[m]();"undefined"!=typeof c&&(n=c)}}),n}var o=a.extend({},a.floatThead.defaults||{},l);return a.each(l,function(b,d){d in a.floatThead.defaults||!o.debug||c("jQuery.floatThead: used ["+d+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+f.keys(a.floatThead.defaults).join(", "))}),this.filter(":not(."+o.floatTableClass+")").each(function(){function c(){var a=x.find(o.cellTag).outerHeight(!0);U.outerHeight(a),V.outerHeight(a)}function i(){var a=w.outerWidth(),b=F.width()||a;if(S.width(b-C.vertical),L){var c=100*a/(b-C.vertical);O.css("width",c+"%")}else O.outerWidth(a)}function l(){z=(f.isFunction(o.scrollingTop)?o.scrollingTop(w):o.scrollingTop)||0,A=(f.isFunction(o.scrollingBottom)?o.scrollingBottom(w):o.scrollingBottom)||0}function m(){var b=x.find("tr:first>"+o.cellTag),c=0;if(b.each(function(){c+=parseInt(a(this).attr("colspan")||1,10)}),c!=E){E=c;for(var d=[],e=[],f=[],g=0;c>g;g++)d.push(''),e.push(""),f.push("");e=e.join(""),d=d.join(""),h&&(f=f.join(""),R.html(f),Y=R.find("fthtd")),U.html(d),V=U.find("th"),Q.html(e),W=Q.find("col"),P.html(e),X=P.find("col")}return c}function n(){B||(B=!0,w.css(_),O.css(_),O.append(x),y.before(T),c())}function p(){B&&(B=!1,T.detach(),w.prepend(x),w.css(ab),O.css(ab))}function q(a){G!=a&&(G=a,S.css({position:G?"absolute":"fixed"}))}function r(a,b,c,d){return h?c:d?o.getSizingRow(a,b,c):b}function s(){var a,b=m();return function(){var d=r(w,W,Y,g);if(d.length==b&&b>0){for(p(),a=0;b>a;a++){var e=d.get(a),f=e.offsetWidth;X.eq(a).width(f),W.eq(a).width(f)}n()}else O.append(x),w.css(ab),O.css(ab),c()}}function t(){var a,b=F.scrollTop(),c=0,d=I?H.outerHeight(!0):0,e=J?d:-d,f=S.height(),g=w.offset();if(L){var i=F.offset();c=g.top-i.top+b,I&&J&&(c+=d)}else a=g.top-z-f+A+C.horizontal;var k=j.scrollTop(),l=j.scrollLeft(),m=F.scrollLeft();return b=F.scrollTop(),function(i){if("windowScroll"==i?(k=j.scrollTop(),l=j.scrollLeft()):"containerScroll"==i?(b=F.scrollTop(),m=F.scrollLeft()):"init"!=i&&(k=j.scrollTop(),l=j.scrollLeft(),b=F.scrollTop(),m=F.scrollLeft()),!h||!(0>k||0>l)){if(N)"windowScrollDone"==i?q(!0):q(!1);else if("windowScrollDone"==i)return null;g=w.offset(),I&&J&&(g.top+=d);var o,r,s;if(L&&G){if(c>=b){var t=c-b;t=t>0?t:0,o=t}else o=M?0:b;r=0}else!L&&G?(s=w.outerHeight(),k>a+s+e?o=s-f+e:g.top>k+z?(o=0,p()):(o=z+k-g.top+c+(J?d:0),n()),r=0):L&&!G?(c>b?(o=g.top-k,p()):(o=g.top+b-k-c,n()),r=g.left+m-l):L||G||(s=w.outerHeight(),k>a+s+e?o=s+z-k+a+e:g.top>k+z?(o=g.top-k,n()):o=z,r=g.left-l);return{top:o,left:r}}}}function u(){var a=null,b=null,d=null;return function(e,f,g){null==e||a==e.top&&b==e.left||(S.css({top:e.top,left:e.left}),a=e.top,b=e.left),f&&i(),g&&c();var h=F.scrollLeft();d!=h&&(S.scrollLeft(h),d=h)}}function v(){F.length&&(C.horizontal=F.width() element");var z,A,B=!1,C={vertical:0,horizontal:0},D=d(),E=0,F=o.scrollContainer(w)||a([]),G=o.useAbsolutePositioning;null==G&&(G=o.scrollContainer(w).length);var H=w.find("caption"),I=1==H.length;if(I)var J="top"===(H.css("caption-side")||H.attr("align")||"top");var K=a(''),L=F.length>0,M=!1,N=9>=g&&!L&&G,O=a(""),P=a(""),Q=a(""),R=a(''),S=a('
'),T=a("
"),U=a(''),V=a([]),W=a([]),X=a([]),Y=a([]);if(T.append(U),w.prepend(Q),h&&(K.append(R),w.append(K)),O.append(P),S.append(O),o.copyTableClass&&O.attr("class",w.attr("class")),O.attr({cellpadding:w.attr("cellpadding"),cellspacing:w.attr("cellspacing"),border:w.attr("border")}),O.addClass(o.floatTableClass).css("margin",0),G){var Z=function(a,b){var c=a.css("position"),d="relative"==c||"absolute"==c;if(!d||b){var e={paddingLeft:a.css("paddingLeft"),paddingRight:a.css("paddingRight")};S.css(e),a=a.wrap("
").parent(),M=!0}return a};if(L){var $=Z(F,!0);$.append(S)}else Z(w),w.after(S)}else w.after(S);S.css({position:G?"absolute":"fixed",marginTop:0,top:G?0:"auto",zIndex:o.zIndex}),S.addClass(o.floatContainerClass),l();var _={"table-layout":"fixed"},ab={"table-layout":w.css("tableLayout")||"auto"};v();var bb,cb=function(){(bb=s())()};cb();var db=t(),eb=u();eb(db("init"),!0);var fb=f.debounce(function(){eb(db("windowScrollDone"),!1)},300),gb=function(){eb(db("windowScroll"),!1),fb()},hb=function(){eb(db("containerScroll"),!1)},ib=function(){l(),v(),cb(),db=t(),(eb=u())(db("resize"),!0,!0)},jb=f.debounce(function(){v(),l(),cb(),db=t(),eb(db("reflow"),!0)},1);L?G?F.bind("scroll.floatTHead",hb):(F.bind("scroll.floatTHead",hb),j.bind("scroll.floatTHead",gb)):j.bind("scroll.floatTHead",gb),j.bind("load.floatTHead",jb),b(o.debounceResizeMs,ib),w.bind("reflow",jb),e(w)&&w.bind("filter",jb).bind("sort",jb).bind("page",jb),w.data("floatThead-attached",{destroy:function(){w.css(ab),Q.remove(),h&&K.remove(),T.parent().length&&T.replaceWith(x),w.unbind("reflow"),jb=ib=hb=gb=function(){},F.unbind("scroll.floatTHead"),S.remove(),w.data("floatThead-attached",!1),k--,0==k&&(j.unbind("scroll.floatTHead"),j.unbind("resize.floatTHead"),j.unbind("load.floatTHead"))},reflow:function(){jb()},setHeaderHeight:function(){c()},getFloatContainer:function(){return S},getRowGroups:function(){return B?S.find("thead").add(w.find("tbody,tfoot")):w.find("thead,tbody,tfoot")}}),k++}),this}}(jQuery),function(){$.floatThead=$.floatThead||{},$.floatThead._=window._||function(){var a={},b=Object.prototype.hasOwnProperty,c=["Arguments","Function","String","Number","Date","RegExp"];return a.has=function(a,c){return b.call(a,c)},a.keys=function(b){if(b!==Object(b))throw new TypeError("Invalid object");var c=[];for(var d in b)a.has(b,d)&&c.push(d);return c},$.each(c,function(){var b=this;a["is"+b]=function(a){return Object.prototype.toString.call(a)=="[object "+b+"]"}}),a.debounce=function(a,b,c){var d,e,f,g,h;return function(){f=this,e=arguments,g=new Date;var i=function(){var j=new Date-g;b>j?d=setTimeout(i,b-j):(d=null,c||(h=a.apply(f,e)))},j=c&&!d;return d||(d=setTimeout(i,b)),j&&(h=a.apply(f,e)),h}},a}()}(); diff --git a/jquery.floatThead.js b/jquery.floatThead.js index 6754606..9ec98cc 100644 --- a/jquery.floatThead.js +++ b/jquery.floatThead.js @@ -42,7 +42,6 @@ var util = window._; - //browser stuff var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];a = 1+a,b.innerHTML="",c[0];);return 4'); + cells.push(''); psuedo.push(""); } @@ -305,6 +305,7 @@ } $sizerRow.html(cells); + $sizerCells = $sizerRow.find("th"); $tableColGroup.html(cols); $tableCells = $tableColGroup.find('col'); $floatColGroup.html(cols); diff --git a/jquery.floatThead.min.js b/jquery.floatThead.min.js deleted file mode 100644 index 46433d9..0000000 --- a/jquery.floatThead.min.js +++ /dev/null @@ -1,3 +0,0 @@ -// @preserve jQuery.floatThead 1.2.2 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak -// @license Licensed under http://creativecommons.org/licenses/by-sa/4.0/ -!function(a){function b(a,b){i.bind("resize.floatTHead",_.debounce(b,a))}function c(a){window.console&&window.console&&window.console.log&&window.console.log(a)}function d(){var b=a('
');a("body").append(b);var c=b.innerWidth(),d=a("div",b).innerWidth();return b.remove(),c-d}function e(a){if(a.dataTableSettings)for(var b=0;btd")},floatTableClass:"floatThead-table",floatContainerClass:"floatThead-container",debug:!1}};var f=function(){for(var a=3,b=document.createElement("b"),c=b.all||[];b.innerHTML="",c[0];);return a>4?a:document.documentMode}(),g=null,h=function(){if(f)return!1;var b=a("
'); cols.push('
");a("body").append(b);var c=b.find("col").width();return b.remove(),0==c},i=a(window),j=0;a.fn.floatThead=function(k){if(8>f)return this;if(null==g&&(g=h(),g&&(document.createElement("fthtr"),document.createElement("fthtd"),document.createElement("fthfoot"))),_.isString(k)){var l=k,m=this;return this.filter("table").each(function(){var b=a(this).data("floatThead-attached");if(b&&_.isFunction(b[l])){var c=b[l]();"undefined"!=typeof c&&(m=c)}}),m}var n=a.extend({},a.floatThead.defaults,k);return _.each(k,function(b,d){d in a.floatThead.defaults||!n.debug||c("jQuery.floatThead: used ["+d+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+_.keys(a.floatThead.defaults).join(", "))}),this.filter(":not(."+n.floatTableClass+")").each(function(){function c(){var a=w.outerHeight(!0);T.outerHeight(a),U.outerHeight(a)}function h(){var a=v.outerWidth(),b=E.width()||a;if(R.width(b-B.vertical),K){var c=100*a/(b-B.vertical);N.css("width",c+"%")}else N.outerWidth(a)}function k(){y=(_.isFunction(n.scrollingTop)?n.scrollingTop(v):n.scrollingTop)||0,z=(_.isFunction(n.scrollingBottom)?n.scrollingBottom(v):n.scrollingBottom)||0}function l(){var b=w.find("tr:first>"+n.cellTag),c=_.reduce(b,function(b,c){var d=parseInt(a(c).attr("colspan")||1,10);return b+d},0);if(c!=D){D=c;for(var d=[],e=[],f=[],h=0;c>h;h++)d.push(''),e.push(""),f.push("");e=e.join(""),d=d.join(""),g&&(f=f.join(""),Q.html(f),X=Q.find("fthtd")),T.html(d),P.html(e),V=P.find("col"),O.html(e),W=O.find("col")}return c}function m(){A||(A=!0,v.css($),N.css($),N.append(w),x.before(S),c())}function o(){A&&(A=!1,S.detach(),v.prepend(w),v.css(ab),N.css(ab))}function p(a){F!=a&&(F=a,R.css({position:F?"absolute":"fixed"}))}function q(a,b,c,d){return g?c:d?n.getSizingRow(a,b,c):b}function r(){var a,b=l();return function(){var d=q(v,V,X,f);if(d.length==b&&b>0){for(o(),a=0;b>a;a++){var e=d.get(a),g=e.offsetWidth;W.eq(a).width(g),V.eq(a).width(g)}m()}else N.append(w),v.css(ab),N.css(ab),c()}}function s(){var a,b=E.scrollTop(),c=0,d=H?G.outerHeight(!0):0,e=I?d:-d,f=R.height(),h=v.offset();if(K){var j=E.offset();c=h.top-j.top+b,H&&I&&(c+=d)}else a=h.top-y-f+z+B.horizontal;var k=i.scrollTop(),l=i.scrollLeft(),n=E.scrollLeft();return b=E.scrollTop(),function(j){if("windowScroll"==j?(k=i.scrollTop(),l=i.scrollLeft()):"containerScroll"==j?(b=E.scrollTop(),n=E.scrollLeft()):"init"!=j&&(k=i.scrollTop(),l=i.scrollLeft(),b=E.scrollTop(),n=E.scrollLeft()),!g||!(0>k||0>l)){if(M)"windowScrollDone"==j?p(!0):p(!1);else if("windowScrollDone"==j)return null;h=v.offset(),H&&I&&(h.top+=d);var q,r,s;if(K&&F){if(c>=b){var t=c-b;t=t>0?t:0,q=t}else q=L?0:b;r=0}else!K&&F?(s=v.outerHeight(),k>a+s+e?q=s-f+e:h.top>k+y?(q=0,o()):(q=y+k-h.top+c+(I?d:0),m()),r=0):K&&!F?(c>b?(q=h.top-k,o()):(q=h.top+b-k-c,m()),r=h.left+n-l):K||F||(s=v.outerHeight(),k>a+s+e?q=s+y-k+a+e:h.top>k+y?(q=h.top-k,m()):q=y,r=h.left-l);return{top:q,left:r}}}}function t(){var a=null,b=null,d=null;return function(e,f,g){null==e||a==e.top&&b==e.left||(R.css({top:e.top,left:e.left}),a=e.top,b=e.left),f&&h(),g&&c();var i=E.scrollLeft();d!=i&&(R.scrollLeft(i),d=i)}}function u(){E.length&&(B.horizontal=E.width() element");var y,z,A=!1,B={vertical:0,horizontal:0},C=d(),D=0,E=n.scrollContainer(v)||a([]),F=n.useAbsolutePositioning;null==F&&(F=n.scrollContainer(v).length);var G=v.find("caption"),H=1==G.length;if(H)var I="top"===(G.css("caption-side")||G.attr("align")||"top");var J=a(''),K=E.length>0,L=!1,M=9>=f&&!K&&F,N=a(""),O=a(""),P=a(""),Q=a(''),R=a('
'),S=a("
"),T=a(''),U=a([]),V=a([]),W=a([]),X=a([]);if(S.append(T),v.prepend(P),g&&(J.append(Q),v.append(J)),N.append(O),R.append(N),N.attr("class",v.attr("class")),N.addClass(n.floatTableClass).css("margin",0),F){var Y=function(a,b){var c=a.css("position"),d="relative"==c||"absolute"==c;if(!d||b){var e={paddingLeft:a.css("paddingLeft"),paddingRight:a.css("paddingRight")};R.css(e),a=a.wrap("
").parent(),L=!0}return a};if(K){var Z=Y(E,!0);Z.append(R)}else Y(v),v.after(R)}else v.after(R);R.css({position:F?"absolute":"fixed",marginTop:0,top:F?0:"auto",zIndex:n.zIndex}),R.addClass(n.floatContainerClass),k();var $={"table-layout":"fixed"},ab={"table-layout":v.css("tableLayout")||"auto"};u();var bb,cb=function(){(bb=r())()};cb();var db=s(),eb=t();eb(db("init"),!0);var fb=_.debounce(function(){eb(db("windowScrollDone"),!1)},300),gb=function(){eb(db("windowScroll"),!1),fb()},hb=function(){eb(db("containerScroll"),!1)},ib=function(){k(),u(),cb(),db=s(),(eb=t())(db("resize"),!0,!0)},jb=_.debounce(function(){u(),k(),cb(),db=s(),eb(db("reflow"),!0)},1);K?F?E.bind("scroll.floatTHead",hb):(E.bind("scroll.floatTHead",hb),i.bind("scroll.floatTHead",gb)):i.bind("scroll.floatTHead",gb),i.bind("load.floatTHead",jb),b(n.debounceResizeMs,ib),v.bind("reflow",jb),e(v)&&v.bind("filter",jb).bind("sort",jb).bind("page",jb),v.data("floatThead-attached",{destroy:function(){v.css(ab),P.remove(),g&&J.remove(),S.parent().length&&S.replaceWith(w),v.unbind("reflow"),jb=ib=hb=gb=function(){},E.unbind("scroll.floatTHead"),R.remove(),v.data("floatThead-attached",!1),j--,0==j&&(i.unbind("scroll.floatTHead"),i.unbind("resize.floatTHead"),i.unbind("load.floatTHead"))},reflow:function(){jb()},setHeaderHeight:function(){c()},getFloatContainer:function(){return R},getRowGroups:function(){return A?R.find("thead").add(v.find("tbody,tfoot")):v.find("thead,tbody,tfoot")}}),j++}),this}}(jQuery); \ No newline at end of file diff --git a/package.json b/package.json index 4978cd4..2a0d04f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "floatthead", - "version": "1.2.2", + "version": "1.2.3", "description": "locking table headers without special css", "main": "jquery.floatThead.js", "dependencies": {},