1/*! Copyright (c) Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders 2 MIT license info: https://github.com/jmosbech/StickyTableHeaders/blob/master/license.txt */ 3 4;(function ($, window, undefined) { 5 'use strict'; 6 7 var name = 'stickyTableHeaders', 8 id = 0, 9 defaults = { 10 fixedOffset: 0, 11 leftOffset: 0, 12 marginTop: 0, 13 objDocument: document, 14 objHead: 'head', 15 objWindow: window, 16 scrollableArea: window, 17 cacheHeaderHeight: false, 18 zIndex: 3, 19 addfixedColumn: false 20 }; 21 22 function Plugin (el, options) { 23 // To avoid scope issues, use 'base' instead of 'this' 24 // to reference this class from internal events and functions. 25 var base = this; 26 27 // Access to jQuery and DOM versions of element 28 base.$el = $(el); 29 base.el = el; 30 base.id = id++; 31 32 // Listen for destroyed, call teardown 33 base.$el.bind('destroyed', 34 $.proxy(base.teardown, base)); 35 36 // Cache DOM refs for performance reasons 37 base.$clonedHeader = null; 38 base.$originalHeader = null; 39 40 // Cache header height for performance reasons 41 base.cachedHeaderHeight = null; 42 43 // Keep track of state 44 base.isSticky = false; 45 base.hasBeenSticky = false; 46 base.leftOffset = null; 47 base.topOffset = null; 48 49 base.init = function () { 50 base.setOptions(options); 51 52 base.$el.each(function () { 53 var $this = $(this); 54 55 // remove padding on <table> to fix issue #7 56 $this.css('padding', 0); 57 58 base.$originalHeader = $('thead:first', this); 59 base.$clonedHeader = base.$originalHeader.clone(); 60 $this.trigger('clonedHeader.' + name, [base.$clonedHeader]); 61 62 base.$clonedHeader.addClass('tableFloatingHeader'); 63 if (base.options.addfixedColumn) base.$clonedHeader.addClass('fixed-column'); 64 base.$clonedHeader.css({display: 'none', opacity: 0.0}); 65 66 base.$originalHeader.addClass('tableFloatingHeaderOriginal'); 67 if (base.options.addfixedColumn) base.$originalHeader.addClass('fixed-column'); 68 69 base.$originalHeader.after(base.$clonedHeader); 70 71 base.$printStyle = $('<style type="text/css" media="print">' + 72 '.tableFloatingHeader{display:none !important;}' + 73 '.tableFloatingHeaderOriginal{position:static !important;}' + 74 '</style>'); 75 base.$head.append(base.$printStyle); 76 }); 77 78 base.$clonedHeader.find("input, select").attr("disabled", true); 79 80 base.updateWidth(); 81 base.toggleHeaders(); 82 base.bind(); 83 }; 84 85 base.destroy = function (){ 86 base.$el.unbind('destroyed', base.teardown); 87 base.teardown(); 88 }; 89 90 base.teardown = function(){ 91 if (base.isSticky) { 92 base.$originalHeader.css('position', 'static'); 93 } 94 $.removeData(base.el, 'plugin_' + name); 95 base.unbind(); 96 97 base.$clonedHeader.remove(); 98 base.$originalHeader.removeClass('tableFloatingHeaderOriginal'); 99 base.$originalHeader.css('visibility', 'visible'); 100 base.$printStyle.remove(); 101 102 base.el = null; 103 base.$el = null; 104 }; 105 106 base.bind = function(){ 107 base.$scrollableArea.on('scroll.' + name, base.toggleHeaders); 108 if (!base.isWindowScrolling) { 109 base.$window.on('scroll.' + name + base.id, base.setPositionValues); 110 base.$window.on('resize.' + name + base.id, base.toggleHeaders); 111 } 112 base.$scrollableArea.on('resize.' + name, base.toggleHeaders);
113 base.$scrollableArea.on('resize.' + name, base.updateWidth); 114 }; 115 116 base.unbind = function(){ 117 // unbind window events by specifying handle so we don't remove too much 118 base.$scrollableArea.off('.' + name, base.toggleHeaders); 119 if (!base.isWindowScrolling) { 120 base.$window.off('.' + name + base.id, base.setPositionValues); 121 base.$window.off('.' + name + base.id, base.toggleHeaders); 122 } 123 base.$scrollableArea.off('.' + name, base.updateWidth); 124 }; 125 126 // We debounce the functions bound to the scroll and resize events 127 base.debounce = function (fn, delay) { 128 var timer = null; 129 return function () { 130 var context = this, args = arguments; 131 clearTimeout(timer); 132 timer = setTimeout(function () { 133 fn.apply(context, args); 134 }, delay); 135 }; 136 }; 137 138 let lastscrollTop = null; 139 let lastscrollLeft = null; 140 141 base.toggleHeaders = base.debounce(function () { 142 if (base.$el) { 143 base.$el.each(function (i,e) { 144 145 var $this = $(this), 146 newLeft, 147 newTopOffset = base.isWindowScrolling ? ( 148 isNaN(base.options.fixedOffset) ? 149 base.options.fixedOffset.outerHeight() : 150 base.options.fixedOffset 151 ) : 152 base.$scrollableArea.offset().top + (!isNaN(base.options.fixedOffset) ? base.options.fixedOffset : 0), 153 offset = $this.offset(), 154 155 scrollTop = base.$scrollableArea.scrollTop() + newTopOffset, 156 scrollLeft = base.$scrollableArea.scrollLeft(), 157 158 headerHeight, 159 160 scrolledPastTop = base.isWindowScrolling ? 161 scrollTop > offset.top : 162 newTopOffset > offset.top, 163 notScrolledPastBottom; 164 165 newLeft = offset.left - scrollLeft + base.options.leftOffset; 166 if (scrollTop === lastscrollTop && newLeft === lastscrollLeft) 167 return; 168 169 let $fixedColumn = $('.fixed-column'); 170 171 if (offset.top === 0) scrolledPastTop = false; 172 173 if (scrolledPastTop) { 174 175 $fixedColumn.css("margin-top","1px"); 176 177 headerHeight = base.options.cacheHeaderHeight ? base.cachedHeaderHeight : base.$clonedHeader.height(); 178 notScrolledPastBottom = (base.isWindowScrolling ? scrollTop : 0) < 179 (offset.top + $this.height() - headerHeight - (base.isWindowScrolling ? 0 : newTopOffset)); 180 181 182 } 183 if (scrolledPastTop && notScrolledPastBottom) { 184 fixedTableHeader = true; 185 186 $fixedColumn.css("margin-top","1px").css("box-shadow","3px -8px 6px rgba(0, 0, 0, 0.1)"); 187 $fixedColumn.find('th:nth-child(n+1)').css("border-top",""); 188 $fixedColumn.find('th:nth-child(n+1)').css("border-bottom","none"); 189 190 newLeft = offset.left - scrollLeft + base.options.leftOffset; 191 base.$originalHeader.css({ 192 'position': 'fixed', 193 'margin-top': base.options.marginTop, 194 //'top': 0, 195 'left': newLeft, 196 'z-index': base.options.zIndex 197 }); 198 base.leftOffset = newLeft; 199 base.topOffset = newTopOffset; 200 base.$clonedHeader.css('display', ''); 201 202 203 if (!base.isSticky) { 204 base.isSticky = true; 205 // make sure the width is correct: the user might have resized the browser while in static mode 206 base.updateWidth(); 207 $this.trigger('enabledStickiness.' + name); 208 } 209 base.setPositionValues();
210 } else if (base.isSticky) { 211 fixedTableHeader = false; 212 213 $fixedColumn.css("margin-top","1px").css("box-shadow","none"); 214 $fixedColumn.find('th:nth-child(n+1)').css("border-top","none"); 215 $fixedColumn.find('th:nth-child(n+1)').css("border-bottom",""); 216 base.$originalHeader.css('position', 'static'); 217 base.$clonedHeader.css('display', 'none'); 218 base.isSticky = false; 219 220 221 base.resetWidth($('td,th', base.$clonedHeader), $('td,th', base.$originalHeader)); 222 $this.trigger('disabledStickiness.' + name); 223 } 224 lastscrollTop = scrollTop; 225 lastscrollLeft = newLeft; 226 227 }); 228 } 229 }, 0); 230 231 base.setPositionValues = base.debounce(function () { 232 233 var winScrollTop = base.$window.scrollTop(), 234 winScrollLeft = base.$window.scrollLeft(); 235 if (!base.isSticky || 236 winScrollTop < 0 || winScrollTop + base.$window.height() > base.$document.height() || 237 winScrollLeft < 0 || winScrollLeft + base.$window.width() > base.$document.width()) { 238 return; 239 } 240 base.$originalHeader.css({ 241 'top': base.topOffset - (base.isWindowScrolling ? 0 : winScrollTop), 242 'left': base.leftOffset - (base.isWindowScrolling ? 0 : winScrollLeft) 243 }); 244 }, 0); 245 246 base.updateWidth = base.debounce(function () { 247 if (!base.isSticky) { 248 return; 249 } 250 // Copy cell widths from clone 251 if (!base.$originalHeaderCells) { 252 base.$originalHeaderCells = $('th,td', base.$originalHeader); 253 } 254 if (!base.$clonedHeaderCells) { 255 base.$clonedHeaderCells = $('th,td', base.$clonedHeader); 256 } 257 var cellWidths = base.getWidth(base.$clonedHeaderCells); 258 base.setWidth(cellWidths, base.$clonedHeaderCells, base.$originalHeaderCells); 259 260 // Copy row width from whole table 261 base.$originalHeader.css('width', base.$clonedHeader.width()); 262 263 // If we're caching the height, we need to update the cached value when the width changes 264 if (base.options.cacheHeaderHeight) { 265 base.cachedHeaderHeight = base.$clonedHeader.height(); 266 } 267 let val = Number(base.$originalHeader.css("left").slice(0, -2) ) 268 let $fixedColumn = $('.fixed-column'); 269 if (val !== 10 && val < 10){ 270 $fixedColumn.css("margin-top","1px").css("box-shadow","3px -8px 6px rgba(0, 0, 0, 0.2)"); 271 }else if (val !== 10 && val >= 9){ 272 $fixedColumn.css("margin-top","1px").css("box-shadow","1px -8px 6px rgba(0, 0, 0, 0.1)"); 273 } 274 275 276 }, 0); 277 278 base.getWidth = function ($clonedHeaders) { 279 var widths = []; 280 $clonedHeaders.each(function (index) { 281 var width, $this = $(this); 282 283 if ($this.css('box-sizing') === 'border-box') { 284 var boundingClientRect = $this[0].getBoundingClientRect(); 285 if(boundingClientRect.width) { 286 width = boundingClientRect.width; // #39: border-box bug 287 } else { 288 width = boundingClientRect.right - boundingClientRect.left; // ie8 bug: getBoundingClientRect() does not have a width property 289 } 290 } else { 291 var $origTh = $('th', base.$originalHeader); 292 if ($origTh.css('border-collapse') === 'collapse') { 293 if (window.getComputedStyle) { 294 width = parseFloat(window.getComputedStyle(this, null).width); 295 } else { 296 // ie8 only 297 var leftPadding = parseFloat($this.css('padding-left')); 298 var rightPadding = parseFloat($this.css('padding-right')); 299 // Needs more investigation - this is assuming constant border around this cell and it's neighbours. 300 var border = parseFloat($this.css('border-width')); 301 width = $this.outerWidth() - leftPadding - rightPadding - border; 302 } 303 } else { 304 width = $this.width(); 305 } 306 } 307 308 widths[index] = width; 309 }); 310 return widths; 311 }; 312 313 base.setWidth = function (widths, $clonedHeaders, $origHeaders) { 314 $clonedHeaders.each(function (index) { 315 var width = widths[index]; 316 $origHeaders.eq(index).css({ 317 'min-width': width, 318 'max-width': width 319 }); 320 }); 321 }; 322 323 base.resetWidth = function ($clonedHeaders, $origHeaders) { 324 $clonedHeaders.each(function (index) { 325 var $this = $(this); 326 $origHeaders.eq(index).css({ 327 'min-width': $this.css('min-width'), 328 'max-width': $this.css('max-width') 329 }); 330 }); 331 }; 332 333 base.setOptions = function (options) { 334 base.options = $.extend({}, defaults, options); 335 base.$window = $(base.options.objWindow); 336 base.$head = $(base.options.objHead); 337 base.$document = $(base.options.objDocument); 338 base.$scrollableArea = $(base.options.scrollableArea); 339 base.isWindowScrolling = base.$scrollableArea[0] === base.$window[0]; 340 }; 341 342 base.updateOptions = function (options) { 343 base.setOptions(options); 344 // scrollableArea might have changed 345 base.unbind(); 346 base.bind(); 347 base.updateWidth(); 348 base.toggleHeaders(); 349 }; 350 351 // Run initializer 352 base.init(); 353 } 354 355 // A plugin wrapper around the constructor, 356 // preventing against multiple instantiations 357 $.fn[name] = function ( options ) { 358 return this.each(function () {
359 var instance = $.data(this, 'plugin_' + name); 360 if (instance) { 361 if (typeof options === 'string') { 362 instance[options].apply(instance); 363 } else { 364 instance.updateOptions(options); 365 } 366 } else if(options !== 'destroy') { 367 $.data(this, 'plugin_' + name, new Plugin( this, options )); 368 } 369 }); 370 }; 371 372})(jQuery, window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.