update floatThead plugin to imporove performance for large tables
This commit is contained in:
		| @@ -22,6 +22,7 @@ | |||||||
|   * [#49] Allow device port searching with preference for port/name/vlan. |   * [#49] Allow device port searching with preference for port/name/vlan. | ||||||
|     This is to support some devices (HP?) which have plain numbers for port names |     This is to support some devices (HP?) which have plain numbers for port names | ||||||
|     and Netdisco defaults to assuming this is a VLAN number (R. Kerr) |     and Netdisco defaults to assuming this is a VLAN number (R. Kerr) | ||||||
|  |   * Upgrade floatThead JS plugin to improve performance for large tables | ||||||
|  |  | ||||||
|   [BUG FIXES] |   [BUG FIXES] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,612 +1,19 @@ | |||||||
| /*! | /* | ||||||
|  * jQuery.floatThead |  jQuery.floatThead 1.2.0 | ||||||
|  * Copyright (c) 2012 - 2013 Misha Koryak - https://github.com/mkoryak/floatThead |  Copyright (c) 2013 Misha Koryak - http://mkoryak.github.io/floatThead/ | ||||||
|  * Licensed under Creative Commons Attribution-NonCommercial 3.0 Unported - http://creativecommons.org/licenses/by-sa/3.0/ |  Licensed under http://creativecommons.org/licenses/by-sa/4.0/ | ||||||
|  * Date: 8/25/13 |  | ||||||
|  * |  | ||||||
|  * @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 |  | ||||||
|  * underscore.js 1.3.0 + [required] |  | ||||||
|  * |  | ||||||
|  * http://notetodogself.blogspot.com |  | ||||||
|  * http://programmingdrunk.com/floatThead/ |  | ||||||
|  * |  | ||||||
|  * Tested on FF13+, Chrome 21+, IE9, IE8 |  | ||||||
|  * |  | ||||||
|  * @author Misha Koryak |  | ||||||
|  * @version 1.0.0 |  | ||||||
|  */ |  */ | ||||||
| // ==ClosureCompiler== | (function(a){function ha(a,d){var b=e.width(),ia=_.debounce(function(){var a=e.width();b!=a&&(b=a,d())},a);e.bind("resize.floatTHead",ia)}function ja(){var g=a('<div style="width:50px;height:50px;overflow-y:scroll;position:absolute;top:-200px;left:-200px;"><div style="height:100px;width:100%"></div>');a("body").append(g);var d=g.innerWidth(),b=a("div",g).innerWidth();g.remove();return d-b}function ka(a){if(a.dataTableSettings)for(var d=0;d<a.dataTableSettings.length;d++)if(a[0]==a.dataTableSettings[d].nTable)return!0; | ||||||
| // @compilation_level SIMPLE_OPTIMIZATIONS |   return!1}a.floatThead={defaults:{cellTag:"th",zIndex:1001,debounceResizeMs:1,useAbsolutePositioning:!0,scrollingTop:0,scrollingBottom:0,scrollContainer:function(g){return a([])},getSizingRow:function(a,d,b){return a.find("tbody tr:visible:first>td")},floatTableClass:"floatThead-table",debug:!1}};var D=function(){for(var a=3,d=document.createElement("b"),b=d.all||[];d.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i><![endif]--\x3e",b[0];);return 4<a?a:document.documentMode}(),v=null,J=function(){if(D)return!1; | ||||||
| // @output_file_name jquery.floatThead.min.js |   var e=a("<table><colgroup><col></colgroup><tbody><tr><td style='width:10px'></td></tbody></table>");a("body").append(e);var d=e.find("col").width();e.remove();return 0==d},e=a(window),K=0;a.fn.floatThead=function(g){if(8>D)return this;null==v&&(v=J())&&(document.createElement("fthtr"),document.createElement("fthtd"),document.createElement("fthfoot"));if(_.isString(g)){var d=this;this.filter("table").each(function(){var b=a(this).data("floatThead-attached");b&&_.isFunction(b[g])&&(b=b[g](),"undefined"!== | ||||||
| // ==/ClosureCompiler== |   typeof b&&(d=b))});return d}var b=a.extend({},a.floatThead.defaults,g);_.each(g,function(e,d){if(!(d in a.floatThead.defaults)&&b.debug){var g="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(", ");window.console&&window.console&&window.console.log&&window.console.log(g)}});this.filter(":not(."+b.floatTableClass+")").each(function(){function d(){var a=n.outerHeight(!0);L.outerHeight(a);la.outerHeight(a)} | ||||||
| /** |   function g(){w=(_.isFunction(b.scrollingTop)?b.scrollingTop(c):b.scrollingTop)||0;X=(_.isFunction(b.scrollingBottom)?b.scrollingBottom(c):b.scrollingBottom)||0}function J(){var c=n.find("tr:first>"+b.cellTag),c=_.reduce(c,function(c,b){var d=parseInt(a(b).attr("colspan")||1,10);return c+d},0);if(c!=Y){Y=c;for(var d=[],s=[],f=[],e=0;e<c;e++)d.push("<"+b.cellTag+' class="floatThead-col-'+e+'"/>'),s.push("<col/>"),f.push("<fthtd style='display:table-cell;height:0;width:auto;'/>");s=s.join("");d=d.join(""); | ||||||
|  * @preserve jQuery.floatThead 1.0.0 |     v&&(f=f.join(""),M.html(f),N=M.find("fthtd"));L.html(d);E.html(s);O=E.find("col");P.html(s);Z=P.find("col")}return c}function F(){G||(G=!0,c.css($),h.css($),h.append(n),ma.before(z),d())}function Q(){G&&(G=!1,z.detach(),c.prepend(n),c.css(A),h.css(A))}function aa(a){p!=a&&(p=a,l.css({position:p?"absolute":"fixed"}))}function R(){var a,e=J();return function(){var s;s=O;s=v?N:D?b.getSizingRow(c,s,N):s;if(s.length==e&&0<e){Q();for(a=0;a<e;a++){var f=s.get(a).offsetWidth;Z.eq(a).width(f);O.eq(a).width(f)}F()}else h.append(n), | ||||||
|  * Copyright (c) 2013 Misha Koryak - https://github.com/mkoryak/floatThead |     c.css(A),h.css(A),d()}}function S(){var a=m.scrollTop(),b,d=0,f=H?I.outerHeight(!0):0,g=l.height(),k=c.offset();if(u){var h=m.offset(),d=k.top-h.top+a;H&&ba&&(d+=f)}else b=k.top-w-g+X+B.horizontal;var r=e.scrollTop(),n=e.scrollLeft(),t=m.scrollLeft(),a=m.scrollTop();return function(q){"windowScroll"==q?(r=e.scrollTop(),n=e.scrollLeft()):"containerScroll"==q?(a=m.scrollTop(),t=m.scrollLeft()):"init"!=q&&(r=e.scrollTop(),n=e.scrollLeft(),a=m.scrollTop(),t=m.scrollLeft());if(!v||!(0>r||0>n)){if(na)"windowScrollDone"== | ||||||
|  * Licensed under Creative Commons Attribution-NonCommercial 3.0 Unported - http://creativecommons.org/licenses/by-sa/3.0/ |     q?aa(!0):aa(!1);else if("windowScrollDone"==q)return null;k=c.offset();H&&ba&&(k.top+=f);var h,l;u&&p?(d>=a?(q=d-a,h=0<q?q:0):h=ca?0:a,l=0):!u&&p?(q=c.outerHeight(),r>b+q+f?h=q-g+f:k.top>r+w?(h=0,Q()):(h=w+r-k.top+d+f,F()),l=0):u&&!p?(d>a?(h=k.top-r,Q()):(h=k.top+a-r-d,F()),l=k.left+t-n):u||p||(q=c.outerHeight(),r>b+q+f?h=q+w-r+b+f:k.top>r+w?(h=k.top-r,F()):h=w,l=k.left-n);return{top:h,left:l}}}}function da(){var a=null,b=null,e=null;return function(f,g,k){null==f||a==f.top&&b==f.left||(l.css({top:f.top, | ||||||
|  */ |     left:f.left}),a=f.top,b=f.left);g&&(f=c.outerWidth(),g=m.width()||f,l.width(g-B.vertical),u?h.css("width",100*f/(g-B.vertical)+"%"):h.outerWidth(f));k&&d();k=m.scrollLeft();e!=k&&(l.scrollLeft(k),e=k)}}function T(){m.length&&(B.horizontal=m.width()<c.width()?ea:0,B.vertical=m.height()<c.height()?ea:0)}var c=a(this);if(c.data("floatThead-attached"))return!0;if(!c.is("table"))throw Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();');var n=c.find("thead:first"),ma= | ||||||
| (function( $ ) { |     c.find("tbody:first");if(0==n.length)throw Error("jQuery.floatThead must be run on a table that contains a <thead> element");var G=!0,w,X,B={vertical:0,horizontal:0},ea=ja(),Y=0,m=b.scrollContainer(c)||a([]),p=b.useAbsolutePositioning;null==p&&(p=b.scrollContainer(c).length);var I=c.find("caption"),H=1==I.length;if(H)var ba="top"===(I.css("caption-side")||I.attr("align")||"top");var U=a('<fthfoot style="display:table-footer-group;"/>'),u=0<m.length,ca=!1,na=9>=D&&!u&&p,h=a("<table/>"),P=a("<colgroup/>"), | ||||||
|  |     E=a("<colgroup/>"),M=a('<fthrow style="display:table-row;height:0;"/>'),l=a('<div style="overflow: hidden;"></div>'),z=a("<thead/>"),L=a('<tr class="size-row"/>'),la=a([]),O=a([]),Z=a([]),N=a([]);z.append(L);n.detach();c.prepend(z);c.prepend(E);v&&(U.append(M),c.append(U));h.append(P);l.append(h);h.attr("class",c.attr("class"));h.addClass(b.floatTableClass).css("margin",0);if(p){var fa=function(a,c){var b=a.css("position");if("relative"!=b&&"absolute"!=b||c)b={paddingLeft:a.css("paddingLeft"),paddingRight:a.css("paddingRight")}, | ||||||
| //browser stuff |     l.css(b),a=a.wrap("<div style='position: relative; clear:both;'></div>").parent(),ca=!0;return a};u?fa(m,!0).append(l):(fa(c),c.after(l))}else c.after(l);l.css({position:p?"absolute":"fixed",marginTop:0,top:p?0:"auto",zIndex:b.zIndex});g();var $={"table-layout":"fixed"},A={"table-layout":c.css("tableLayout")||"auto"};T();var C=R();C();var t=S(),x=da();x(t("init"),!0);var oa=_.debounce(function(){x(t("windowScrollDone"),!1)},300),V=function(){x(t("windowScroll"),!1);oa()},W=function(){x(t("containerScroll"), | ||||||
| var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];b.innerHTML="<!--[if gt IE "+ ++a+"]><i><![endif]-->",c[0];);return 4<a?a:document.documentMode}(); |     !1)},ga=function(){g();T();C=R();C();t=S();x=da();x(t("resize"),!0,!0)},y=_.debounce(function(){T();g();C=R();C();t=S();x(t("reflow"),!0)},1);u?p?m.bind("scroll.floatTHead",W):(m.bind("scroll.floatTHead",W),e.bind("scroll.floatTHead",V)):e.bind("scroll.floatTHead",V);e.bind("load.floatTHead",y);ha(b.debounceResizeMs,ga);c.bind("reflow",y);ka(c)&&c.bind("filter",y).bind("sort",y).bind("page",y);c.data("floatThead-attached",{destroy:function(){c.css(A);E.remove();v&&U.remove();z.parent().length&&z.replaceWith(n); | ||||||
| var ifChrome = function(){ |     c.unbind("reflow");y=ga=W=V=function(){};m.unbind("scroll.floatTHead");l.remove();c.data("floatThead-attached",!1);K--;0==K&&(e.unbind("scroll.floatTHead"),e.unbind("resize.floatTHead"),e.unbind("load.floatTHead"))},reflow:function(){y()},setHeaderHeight:function(){d()},getFloatContainer:function(){return l}});K++});return this}})(jQuery); | ||||||
|   var $table = $("<table><colgroup><col></colgroup><tbody><tr><td style='width:10px'></td></tbody></table>"); |  | ||||||
|   $('body').append($table); |  | ||||||
|   var width = $table.find('col').width(); |  | ||||||
|   $table.remove(); |  | ||||||
|   return width == 0; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * provides a default config object. You can modify this after including this script if you want to change the init defaults |  | ||||||
|  * @type {Object} |  | ||||||
|  */ |  | ||||||
| $.floatThead = { |  | ||||||
|     defaults: { |  | ||||||
|         cellTag: 'th', |  | ||||||
|         zIndex: 1001, //zindex of the floating thead (actually a container div) |  | ||||||
|         debounceResizeMs: 1, |  | ||||||
|         useAbsolutePositioning: true, //if set to NULL - defaults: has scrollContainer=true, doesnt have scrollContainer=false |  | ||||||
|         scrollingTop: 0, //String or function($table) - offset from top of window where the header should not pass above |  | ||||||
|         //TODO: this got lost somewhere - needs to be re-implemented |  | ||||||
|         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 |  | ||||||
|         }, |  | ||||||
|         floatTableClass: 'floatThead-table' |  | ||||||
|     }             |  | ||||||
| }; |  | ||||||
|    |  | ||||||
| var $window = $(window); |  | ||||||
| var floatTheadCreated = 0; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * debounce and fix window resize event for ie7. ie7 is evil and will fire window resize event when ANY dom element is resized. |  | ||||||
|  * @param debounceMs |  | ||||||
|  * @param cb |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| function windowResize(debounceMs, cb){ |  | ||||||
|     var winWidth = $window.width(); |  | ||||||
|     var debouncedCb = _.debounce(function(){ |  | ||||||
|         var winWidthNew = $window.width(); |  | ||||||
|         if(winWidth != winWidthNew){ |  | ||||||
|             winWidth = winWidthNew; |  | ||||||
|             cb(); |  | ||||||
|         } |  | ||||||
|     }, debounceMs); |  | ||||||
|     $window.bind('resize.floatTHead', debouncedCb); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * try to calculate the scrollbar width for your browser/os |  | ||||||
|  * @return {Number} |  | ||||||
|  */ |  | ||||||
| function scrollbarWidth() { |  | ||||||
|     var $div = $('<div/>') |  | ||||||
|     .css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 }) |  | ||||||
|     .prependTo('body').append('<div/>').find('div') |  | ||||||
|     .css({ width: '100%', height: 200 }); |  | ||||||
|     var scrollbarWidth = 100 - $div.width(); |  | ||||||
|     $div.parent().remove(); |  | ||||||
|     return scrollbarWidth; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * 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){ |  | ||||||
|     if(ieVersion < 8){ |  | ||||||
|         return this; //no more crappy browser support.  |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     isChrome = ifChrome(); //need to call this after dom ready, and now it is. |  | ||||||
|     if(isChrome){ |  | ||||||
|         //because chrome cant read <col> 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(_.isString(map)){ |  | ||||||
|         var command = map; |  | ||||||
|         var ret = this; |  | ||||||
|         this.filter('table').each(function(){ |  | ||||||
|             var obj = $(this).data('floatThead-attached'); |  | ||||||
|             if(obj && _.isFunction(obj[command])){ |  | ||||||
|                 r = obj[command](); |  | ||||||
|                 if(typeof r !== 'undefined'){ |  | ||||||
|                     ret = r; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|         return ret; |  | ||||||
|     } |  | ||||||
|     var opts = $.extend({}, $.floatThead.defaults, map); |  | ||||||
|      |  | ||||||
|  |  | ||||||
|     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 <thead> element'); |  | ||||||
|         } |  | ||||||
|         var headerFloated = true; |  | ||||||
|         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 $fthGrp = $('<fthfoot style="display:table-footer-group;"/>'); |  | ||||||
|         |  | ||||||
|         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 && !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 = $("<table/>"); |  | ||||||
|         var $floatColGroup = $("<colgroup/>"); |  | ||||||
|         var $tableColGroup = $("<colgroup/>"); |  | ||||||
|         var $fthRow = $('<fthrow style="display:table-row;height:0;"/>'); //created unstyled elements |  | ||||||
|         var $floatContainer = $('<div style="overflow: hidden;"></div>'); |  | ||||||
|         var $newHeader = $("<thead/>"); |  | ||||||
|         var $sizerRow = $('<tr class="size-row"/>'); |  | ||||||
|         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); |  | ||||||
|         $header.detach(); |  | ||||||
|  |  | ||||||
|         $table.prepend($newHeader);  |  | ||||||
|         $table.prepend($tableColGroup); |  | ||||||
|         if(isChrome){ |  | ||||||
|             $fthGrp.append($fthRow); |  | ||||||
|             $table.append($fthGrp); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         $floatTable.append($floatColGroup); |  | ||||||
|         $floatContainer.append($floatTable); |  | ||||||
|         $floatTable.attr('class', $table.attr('class')); |  | ||||||
|         $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("<div style='position: relative; clear:both;'></div>").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 |  | ||||||
|         }); |  | ||||||
|         updateScrollingOffsets(); |  | ||||||
|          |  | ||||||
|         var layoutFixed = {'table-layout': 'fixed'}; |  | ||||||
|         var layoutAuto = {'table-layout': $table.css('tableLayout') || 'auto'}; |  | ||||||
|  |  | ||||||
|         function setHeaderHeight(){ |  | ||||||
|             var headerHeight = $header.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 = (_.isFunction(opts.scrollingTop) ? opts.scrollingTop($table) : opts.scrollingTop) || 0; |  | ||||||
|             scrollingBottom = (_.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 =  _.reduce($headerColumns, function(sum, cell){ |  | ||||||
|                 var colspan = parseInt(($(cell).attr('colspan') || 1), 10); |  | ||||||
|                 return sum + colspan; |  | ||||||
|             }, 0); |  | ||||||
|             if(count != lastColumnCount){ |  | ||||||
|                 lastColumnCount = count; |  | ||||||
|                 var cells = [], cols = [], psuedo = []; |  | ||||||
|                 for(var x = 0; x < count; x++){ |  | ||||||
|                     cells.push('<'+opts.cellTag+' class="floatThead-col-'+x+'"/>'); |  | ||||||
|                     cols.push('<col/>'); |  | ||||||
|                     psuedo.push("<fthcell style='display:table-cell;height:0;width:auto;'/>"); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 cols = cols.join(''); |  | ||||||
|                 cells = cells.join(''); |  | ||||||
|  |  | ||||||
|                 if(isChrome){ |  | ||||||
|                     psuedo = psuedo.join(''); |  | ||||||
|                     $fthRow.html(psuedo); |  | ||||||
|                     $fthCells = $fthRow.find('fthcell') |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 $sizerRow.html(cells); |  | ||||||
|                 $tableColGroup.html(cols); |  | ||||||
|                 $tableCells = $tableColGroup.find('col'); |  | ||||||
|                 $floatColGroup.html(cols); |  | ||||||
|                 $headerCells = $floatColGroup.find("col"); |  | ||||||
|                  |  | ||||||
|             } |  | ||||||
|             return count; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         function refloat(){ |  | ||||||
|             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(){ |  | ||||||
|             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' |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /** |  | ||||||
|          * returns a function that updates the floating header's cell widths. |  | ||||||
|          * @return {Function} |  | ||||||
|          */ |  | ||||||
|         function reflow(){ |  | ||||||
|             var numCols = columnNum(); //if the tables columns change dynamically since last time (datatables) we need to rebuild the sizer rows and get new count |  | ||||||
|             var flow = function(){ |  | ||||||
|                 var badReflow = false; |  | ||||||
|                 var $rowCells = isChrome ? $fthCells : $tableCells; |  | ||||||
|                 if($rowCells.length == numCols && numCols > 0){ |  | ||||||
|                     unfloat(); |  | ||||||
|                     for(var i=0; i < numCols; i++){ |  | ||||||
|                         var $rowCell = $rowCells.eq(i); |  | ||||||
|                         var rowWidth = $rowCell.outerWidth(true); |  | ||||||
|                         $headerCells.eq(i).outerWidth(rowWidth); |  | ||||||
|                         $tableCells.eq(i).outerWidth(rowWidth); |  | ||||||
|                     } |  | ||||||
|                     refloat(); |  | ||||||
|                     for(var i=0; i < numCols; i++){ |  | ||||||
|                         var hw = $headerCells.eq(i).outerWidth(true); |  | ||||||
|                         var tw = $tableCells.eq(i).outerWidth(true); |  | ||||||
|                         if(hw != tw){ |  | ||||||
|                             badReflow = true; |  | ||||||
|                             break; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } else {  |  | ||||||
|                     $floatTable.append($header); |  | ||||||
|                     $table.css(layoutAuto); |  | ||||||
|                     $floatTable.css(layoutAuto); |  | ||||||
|                     setHeaderHeight(); |  | ||||||
|                 } |  | ||||||
|                 return badReflow; |  | ||||||
|             }; |  | ||||||
|             return flow; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         /** |  | ||||||
|          * 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 floatContainerHeight = $floatContainer.height(); |  | ||||||
|             var tableOffset = $table.offset(); |  | ||||||
|             if(locked){ |  | ||||||
|                 var containerOffset = $scrollContainer.offset(); |  | ||||||
|                 tableContainerGap = tableOffset.top - containerOffset.top + scrollingContainerTop; |  | ||||||
|             } else { |  | ||||||
|                 floatEnd = tableOffset.top - scrollingTop - floatContainerHeight + scrollingBottom + scrollbarOffset.horizontal; |  | ||||||
|             } |  | ||||||
|             var windowTop = $window.scrollTop(); |  | ||||||
|             var windowLeft = $window.scrollLeft(); |  | ||||||
|             var scrollContainerLeft =  $scrollContainer.scrollLeft(); |  | ||||||
|             scrollingContainerTop = $scrollContainer.scrollTop(); |  | ||||||
|  |  | ||||||
|     |  | ||||||
|  |  | ||||||
|             var positionFn =  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(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(); |  | ||||||
|                 var top, left, tableHeight; |  | ||||||
|  |  | ||||||
|                 //absolute positioning |  | ||||||
|                 if(locked && useAbsolutePositioning){ //inner scrolling |  | ||||||
|                     if (tableContainerGap >= scrollingContainerTop) { |  | ||||||
|                         var gap = tableContainerGap - scrollingContainerTop; |  | ||||||
|                         gap = gap > 0 ? gap : 0; |  | ||||||
|                         top = gap; |  | ||||||
|             //            unfloat(); //more trouble than its worth |  | ||||||
|                     } else { |  | ||||||
|                         top = wrappedContainer ? 0 : scrollingContainerTop; |  | ||||||
|             //            refloat(); //more trouble than its worth |  | ||||||
|                         //headers stop at the top of the viewport |  | ||||||
|                     } |  | ||||||
|                     left = 0; |  | ||||||
|                 } else if(!locked && useAbsolutePositioning) { //window scrolling |  | ||||||
|                     tableHeight = $table.outerHeight(); |  | ||||||
|                     if(windowTop > floatEnd + tableHeight){ |  | ||||||
|                         top = tableHeight - floatContainerHeight; //scrolled past table |  | ||||||
|                     } else if (tableOffset.top > windowTop + scrollingTop) { |  | ||||||
|                         top = 0; //scrolling to table |  | ||||||
|                         unfloat(); |  | ||||||
|                     } else { |  | ||||||
|                         top = scrollingTop + windowTop - tableOffset.top + tableContainerGap; |  | ||||||
|                         refloat(); //scrolling within table. header floated |  | ||||||
|                     } |  | ||||||
|                     left =  0; |  | ||||||
|  |  | ||||||
|                 //fixed positioning: |  | ||||||
|                 } else if(locked && !useAbsolutePositioning){ //inner scrolling |  | ||||||
|                     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 |  | ||||||
|                     tableHeight = $table.outerHeight(); |  | ||||||
|                     if(windowTop > floatEnd + tableHeight){ |  | ||||||
|                         top = tableHeight + scrollingTop - windowTop + floatEnd;  |  | ||||||
|                         unfloat(); |  | ||||||
|                     } else if (tableOffset.top > windowTop + scrollingTop) { |  | ||||||
|                         top = tableOffset.top - windowTop; |  | ||||||
|                         refloat(); |  | ||||||
|                     } else { |  | ||||||
|                         top = scrollingTop; |  | ||||||
|                     } |  | ||||||
|                     left = tableOffset.left - windowLeft; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 return {top: top, left: left}; |  | ||||||
|             }; |  | ||||||
|             return positionFn; |  | ||||||
|         } |  | ||||||
|         /** |  | ||||||
|          * 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 = reflow(); |  | ||||||
|         flow(); |  | ||||||
|         var calculateFloatContainerPos = calculateFloatContainerPosFn(); |  | ||||||
|         var repositionFloatContainer = repositionFloatContainerFn(); |  | ||||||
|          |  | ||||||
|         repositionFloatContainer(calculateFloatContainerPos('init'), true, true); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead |  | ||||||
|          |  | ||||||
|         var windowScrollDoneEvent = _.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(); |  | ||||||
|             flow = reflow(); |  | ||||||
|             var badReflow = flow(); |  | ||||||
|             if(badReflow){ |  | ||||||
|                 flow(); |  | ||||||
|             } |  | ||||||
|             calculateFloatContainerPos = calculateFloatContainerPosFn(); |  | ||||||
|             repositionFloatContainer = repositionFloatContainerFn(); |  | ||||||
|             repositionFloatContainer(calculateFloatContainerPos('resize'), true, true); |  | ||||||
|         }; |  | ||||||
|         var reflowEvent = _.debounce(function(){ |  | ||||||
|             calculateScrollBarSize(); |  | ||||||
|             updateScrollingOffsets(); |  | ||||||
|             flow = reflow(); |  | ||||||
|             var badReflow = flow(); |  | ||||||
|             if(badReflow){ |  | ||||||
|                 flow(); |  | ||||||
|             } |  | ||||||
|             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(); |  | ||||||
|                 $fthGrp.remove(); |  | ||||||
|                 $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; |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|         floatTheadCreated++; |  | ||||||
|     }); |  | ||||||
|     return this; |  | ||||||
| }; |  | ||||||
| })(jQuery); |  | ||||||
		Reference in New Issue
	
	Block a user