26
if ( typeof define === 'function' && define.amd ) {
28
define( ['jquery', 'datatables.net'], function ( $ ) {
29
return factory( $, window, document );
32
else if ( typeof exports === 'object' ) {
34
module.exports = function (root, $) {
39
if ( ! $ || ! $.fn.dataTable ) {
40
$ = require('datatables.net')(root, $).$;
43
return factory( $, root, root.document );
48
factory( jQuery, window, document );
50
}(function( $, window, document, undefined ) {
52
var DataTable = $.fn.dataTable;
57
var FixedHeader = function ( dt, config ) {
59
if ( ! (this instanceof FixedHeader) ) {
60
throw "FixedHeader must be initialised with the 'new' keyword.";
64
if ( config === true ) {
68
dt = new DataTable.Api( dt );
70
this.c = $.extend( true, {}, FixedHeader.defaults, config );
83
windowHeight: $(window).height(),
88
autoWidth: dt.settings()[0].oFeatures.bAutoWidth,
89
namespace: '.dtfc'+(_instCounter++),
99
thead: $(dt.table().header()),
100
tbody: $(dt.table().body()),
101
tfoot: $(dt.table().footer()),
105
floatingParent: $('<div class="dtfh-floatingparent">'),
111
floatingParent: $('<div class="dtfh-floatingparent">'),
116
this.dom.header.host = this.dom.thead.parent();
117
this.dom.footer.host = this.dom.tfoot.parent();
119
var dtSettings = dt.settings()[0];
120
if ( dtSettings._fixedHeader ) {
121
throw "FixedHeader already initialised on table "+dtSettings.nTable.id;
124
dtSettings._fixedHeader = this;
135
$.extend( FixedHeader.prototype, {
143
destroy: function () {
144
this.s.dt.off( '.dtfc' );
145
$(window).off( this.s.namespace );
147
if ( this.c.header ) {
148
this._modeChange( 'in-place', 'header', true );
151
if ( this.c.footer && this.dom.tfoot.length ) {
152
this._modeChange( 'in-place', 'footer', true );
161
enable: function ( enable, update )
163
this.s.enable = enable;
165
if ( update || update === undefined ) {
167
this._scroll( true );
176
return this.s.enable;
184
headerOffset: function ( offset )
186
if ( offset !== undefined ) {
187
this.c.headerOffset = offset;
191
return this.c.headerOffset;
199
footerOffset: function ( offset )
201
if ( offset !== undefined ) {
202
this.c.footerOffset = offset;
206
return this.c.footerOffset;
213
update: function (force)
215
var table = this.s.dt.table().node();
217
if ( $(table).is(':visible') ) {
218
this.enable( true, false );
221
this.enable( false, false );
226
if ($(table).children('thead').length === 0) {
231
this._scroll( force !== undefined ? force : true );
245
_constructor: function ()
251
.on( 'scroll'+this.s.namespace, function () {
254
.on( 'resize'+this.s.namespace, DataTable.util.throttle( function () {
255
that.s.position.windowHeight = $(window).height();
259
var autoHeader = $('.fh-fixedHeader');
260
if ( ! this.c.headerOffset && autoHeader.length ) {
261
this.c.headerOffset = autoHeader.outerHeight();
264
var autoFooter = $('.fh-fixedFooter');
265
if ( ! this.c.footerOffset && autoFooter.length ) {
266
this.c.footerOffset = autoFooter.outerHeight();
270
.on( 'column-reorder.dt.dtfc column-visibility.dt.dtfc column-sizing.dt.dtfc responsive-display.dt.dtfc', function (e, ctx) {
273
.on( 'draw.dt.dtfc', function (e, ctx) {
275
that.update(ctx === dt.settings()[0] ? false : true);
278
dt.on( 'destroy.dtfc', function () {
301
_clone: function ( item, force )
304
var itemDom = this.dom[ item ];
305
var itemElement = item === 'header' ?
311
if (item === 'footer' && this._scrollEnabled()) {
315
if ( ! force && itemDom.floating ) {
317
itemDom.floating.removeClass( 'fixedHeader-floating fixedHeader-locked' );
320
if ( itemDom.floating ) {
321
if(itemDom.placeholder !== null) {
322
itemDom.placeholder.remove();
324
this._unsize( item );
325
itemDom.floating.children().detach();
326
itemDom.floating.remove();
329
var tableNode = $(dt.table().node());
330
var scrollBody = $(tableNode.parent());
331
var scrollEnabled = this._scrollEnabled();
333
itemDom.floating = $( dt.table().node().cloneNode( false ) )
334
.attr( 'aria-hidden', 'true' )
336
'table-layout': 'fixed',
341
.append( itemElement );
343
itemDom.floatingParent
345
width: scrollBody.width(),
347
height: 'fit-content',
349
left: scrollEnabled ? tableNode.offset().left + scrollBody.scrollLeft() : 0
354
top: this.c.headerOffset,
359
bottom: this.c.footerOffset
362
.addClass(item === 'footer' ? 'dtfh-floatingparentfoot' : 'dtfh-floatingparenthead')
363
.append(itemDom.floating)
366
this._stickyPosition(itemDom.floating, '-');
368
var scrollLeftUpdate = () => {
369
var scrollLeft = scrollBody.scrollLeft()
370
this.s.scrollLeft = {footer: scrollLeft, header: scrollLeft};
371
itemDom.floatingParent.scrollLeft(this.s.scrollLeft.header);
375
scrollBody.scroll(scrollLeftUpdate)
378
itemDom.placeholder = itemElement.clone( false );
383
itemDom.host.prepend( itemDom.placeholder );
386
this._matchWidths( itemDom.placeholder, itemDom.floating );
395
_stickyPosition(el, sign) {
396
if (this._scrollEnabled()) {
398
var rtl = $(that.s.dt.table().node()).css('direction') === 'rtl';
400
el.find('th').each(function() {
402
if ($(this).css('position') === 'sticky') {
403
var right = $(this).css('right');
404
var left = $(this).css('left');
405
if (right !== 'auto' && !rtl) {
407
var potential = +right.replace(/px/g, '') + (sign === '-' ? -1 : 1) * that.s.dt.settings()[0].oBrowser.barWidth;
408
$(this).css('right', potential > 0 ? potential : 0);
410
else if(left !== 'auto' && rtl) {
411
var potential = +left.replace(/px/g, '') + (sign === '-' ? -1 : 1) * that.s.dt.settings()[0].oBrowser.barWidth;
412
$(this).css('left', potential > 0 ? potential : 0);
430
_matchWidths: function ( from, to ) {
431
var get = function ( name ) {
434
return $(this).css('width').replace(/[^\d\.]/g, '') * 1;
438
var set = function ( name, toWidths ) {
439
$(name, to).each( function ( i ) {
442
minWidth: toWidths[i]
447
var thWidths = get( 'th' );
448
var tdWidths = get( 'td' );
450
set( 'th', thWidths );
451
set( 'td', tdWidths );
463
_unsize: function ( item ) {
464
var el = this.dom[ item ].floating;
466
if ( el && (item === 'footer' || (item === 'header' && ! this.s.autoWidth)) ) {
467
$('th, td', el).css( {
472
else if ( el && item === 'header' ) {
473
$('th, td', el).css( 'min-width', '' );
485
_horizontal: function ( item, scrollLeft )
487
var itemDom = this.dom[ item ];
488
var position = this.s.position;
489
var lastScrollLeft = this.s.scrollLeft;
491
if ( itemDom.floating && lastScrollLeft[ item ] !== scrollLeft ) {
493
if (this._scrollEnabled()) {
494
var newScrollLeft = $($(this.s.dt.table().node()).parent()).scrollLeft()
495
itemDom.floating.scrollLeft(newScrollLeft);
496
itemDom.floatingParent.scrollLeft(newScrollLeft);
499
lastScrollLeft[ item ] = scrollLeft;
518
_modeChange: function ( mode, item, forceChange )
521
var itemDom = this.dom[ item ];
522
var position = this.s.position;
525
var scrollEnabled = this._scrollEnabled();
529
if (item === 'footer' && scrollEnabled) {
534
var importantWidth = function (w) {
535
itemDom.floating.attr('style', function(i,s) {
536
return (s || '') + 'width: '+w+'px !important;';
540
if (!scrollEnabled) {
541
itemDom.floatingParent.attr('style', function(i,s) {
542
return (s || '') + 'width: '+w+'px !important;';
549
var tablePart = this.dom[ item==='footer' ? 'tfoot' : 'thead' ];
550
var focus = $.contains( tablePart[0], document.activeElement ) ?
551
document.activeElement :
553
var scrollBody = $($(this.s.dt.table().node()).parent());
555
if ( mode === 'in-place' ) {
557
if ( itemDom.placeholder ) {
558
itemDom.placeholder.remove();
559
itemDom.placeholder = null;
562
this._unsize( item );
564
if ( item === 'header' ) {
565
itemDom.host.prepend( tablePart );
568
itemDom.host.append( tablePart );
571
if ( itemDom.floating ) {
572
itemDom.floating.remove();
573
itemDom.floating = null;
574
this._stickyPosition(itemDom.host, '+');
577
if ( itemDom.floatingParent ) {
578
itemDom.floatingParent.remove();
581
$($(itemDom.host.parent()).parent()).scrollLeft(scrollBody.scrollLeft())
583
else if ( mode === 'in' ) {
586
this._clone( item, forceChange );
589
var scrollOffset = scrollBody.offset();
590
var windowTop = $(document).scrollTop();
591
var windowHeight = $(window).height();
592
var windowBottom = windowTop + windowHeight;
593
var bodyTop = scrollEnabled ? scrollOffset.top : position.tbodyTop;
594
var bodyBottom = scrollEnabled ? scrollOffset.top + scrollBody.outerHeight() : position.tfootTop
597
var shuffle = item === 'footer' ?
599
bodyTop > windowBottom ?
601
position.tfootHeight :
603
bodyTop + position.tfootHeight - windowBottom :
606
windowTop + this.c.headerOffset + position.theadHeight - bodyBottom
609
var prop = item === 'header' ? 'top' : 'bottom';
610
var val = this.c[item+'Offset'] - (shuffle > 0 ? shuffle : 0);
612
itemDom.floating.addClass( 'fixedHeader-floating' );
613
itemDom.floatingParent
616
'left': position.left,
617
'height': item === 'header' ? position.theadHeight : position.tfootHeight,
620
.append(itemDom.floating);
622
importantWidth(position.width);
624
if ( item === 'footer' ) {
625
itemDom.floating.css( 'top', '' );
628
else if ( mode === 'below' ) {
630
this._clone( item, forceChange );
632
itemDom.floating.addClass( 'fixedHeader-locked' );
633
itemDom.floatingParent.css({
634
position: 'absolute',
635
top: position.tfootTop - position.theadHeight,
636
left: position.left+'px'
639
importantWidth(position.width);
641
else if ( mode === 'above' ) {
643
this._clone( item, forceChange );
645
itemDom.floating.addClass( 'fixedHeader-locked' );
646
itemDom.floatingParent.css({
647
position: 'absolute',
648
top: position.tbodyTop,
649
left: position.left+'px'
652
importantWidth(position.width);
656
if ( focus && focus !== document.activeElement ) {
657
setTimeout( function () {
662
this.s.scrollLeft.header = -1;
663
this.s.scrollLeft.footer = -1;
664
this.s[item+'Mode'] = mode;
673
_positions: function ()
676
var table = dt.table();
677
var position = this.s.position;
679
var tableNode = $(table.node());
680
var scrollEnabled = this._scrollEnabled();
685
var thead = $(dt.table().header());
686
var tfoot = $(dt.table().footer());
687
var tbody = dom.tbody;
688
var scrollBody = tableNode.parent();
690
position.visible = tableNode.is(':visible');
691
position.width = tableNode.outerWidth();
692
position.left = tableNode.offset().left;
693
position.theadTop = thead.offset().top;
694
position.tbodyTop = scrollEnabled ? scrollBody.offset().top : tbody.offset().top;
695
position.tbodyHeight = scrollEnabled ? scrollBody.outerHeight() : tbody.outerHeight();
696
position.theadHeight = thead.outerHeight();
697
position.theadBottom = position.theadTop + position.theadHeight;
699
if ( tfoot.length ) {
700
position.tfootTop = position.tbodyTop + position.tbodyHeight;
701
position.tfootBottom = position.tfootTop + tfoot.outerHeight();
702
position.tfootHeight = tfoot.outerHeight();
705
position.tfootTop = position.tbodyTop + tbody.outerHeight();
706
position.tfootBottom = position.tfootTop;
707
position.tfootHeight = position.tfootTop;
720
_scroll: function ( forceChange )
723
var scrollEnabled = this._scrollEnabled();
724
var scrollBody = $(this.s.dt.table().node()).parent();
725
var scrollOffset = scrollBody.offset();
726
var scrollHeight = scrollBody.outerHeight();
729
var windowLeft = $(document).scrollLeft();
730
var windowTop = $(document).scrollTop();
731
var windowHeight = $(window).height();
732
var windowBottom = windowHeight + windowTop
735
var position = this.s.position;
736
var headerMode, footerMode;
739
var bodyTop = (scrollEnabled ? scrollOffset.top : position.tbodyTop);
740
var bodyLeft = (scrollEnabled ? scrollOffset.left : position.left);
741
var bodyBottom = (scrollEnabled ? scrollOffset.top + scrollHeight : position.tfootTop);
742
var bodyWidth = (scrollEnabled ? scrollBody.outerWidth() : position.tbodyWidth);
744
var windowBottom = windowTop + windowHeight;
746
if ( this.c.header ) {
747
if ( ! this.s.enable ) {
748
headerMode = 'in-place';
752
else if ( ! position.visible || windowTop + this.c.headerOffset + position.theadHeight <= bodyTop) {
753
headerMode = 'in-place';
758
windowTop + this.c.headerOffset + position.theadHeight > bodyTop &&
760
windowTop + this.c.headerOffset < bodyBottom
763
var scrollBody = $($(this.s.dt.table().node()).parent());
767
if(windowTop + this.c.headerOffset + position.theadHeight > bodyBottom || this.dom.header.floatingParent === undefined){
771
this.dom.header.floatingParent
773
'top': this.c.headerOffset,
776
.append(this.dom.header.floating);
781
headerMode = 'below';
784
if ( forceChange || headerMode !== this.s.headerMode ) {
785
this._modeChange( headerMode, 'header', forceChange );
788
this._horizontal( 'header', windowLeft );
792
offset: {top: 0, left: 0},
796
offset: {top: 0, left: 0},
800
if ( this.c.footer && this.dom.tfoot.length ) {
801
if ( ! this.s.enable ) {
802
footerMode = 'in-place';
804
else if ( ! position.visible || position.tfootBottom + this.c.footerOffset <= windowBottom ) {
805
footerMode = 'in-place';
808
bodyBottom + position.tfootHeight + this.c.footerOffset > windowBottom &&
809
bodyTop + this.c.footerOffset < windowBottom
815
footerMode = 'above';
818
if ( forceChange || footerMode !== this.s.footerMode ) {
819
this._modeChange( footerMode, 'footer', forceChange );
822
this._horizontal( 'footer', windowLeft );
824
var getOffsetHeight = (el) => {
827
height: el.outerHeight()
831
header = this.dom.header.floating ? getOffsetHeight(this.dom.header.floating) : getOffsetHeight(this.dom.thead);
832
footer = this.dom.footer.floating ? getOffsetHeight(this.dom.footer.floating) : getOffsetHeight(this.dom.tfoot);
835
if (scrollEnabled && footer.offset.top > windowTop){
837
var overlap = windowTop - scrollOffset.top;
839
var newHeight = windowBottom +
843
(overlap > -header.height ? overlap : 0) -
849
(overlap < -header.height ? header.height : 0) +
861
scrollBody.outerHeight(newHeight);
865
if(Math.round(scrollBody.outerHeight()) >= Math.round(newHeight)) {
866
$(this.dom.tfoot.parent()).addClass("fixedHeader-floating");
870
$(this.dom.tfoot.parent()).removeClass("fixedHeader-floating");
875
if(this.dom.header.floating){
876
this.dom.header.floatingParent.css('left', bodyLeft-windowLeft);
878
if(this.dom.footer.floating){
879
this.dom.footer.floatingParent.css('left', bodyLeft-windowLeft);
885
if (this.s.dt.settings()[0]._fixedColumns !== undefined) {
886
var adjustBlocker = (side, end, el) => {
887
if (el === undefined) {
888
let blocker = $('div.dtfc-'+side+'-'+end+'-blocker');
889
el = blocker.length === 0 ?
891
blocker.clone().appendTo('body').css('z-index', 1);
895
top: end === 'top' ? header.offset.top : footer.offset.top,
896
left: side === 'right' ? bodyLeft + bodyWidth - el.width() : bodyLeft
904
this.dom.header.rightBlocker = adjustBlocker('right', 'top', this.dom.header.rightBlocker);
905
this.dom.header.leftBlocker = adjustBlocker('left', 'top', this.dom.header.leftBlocker);
906
this.dom.footer.rightBlocker = adjustBlocker('right', 'bottom', this.dom.footer.rightBlocker);
907
this.dom.footer.leftBlocker = adjustBlocker('left', 'bottom', this.dom.footer.leftBlocker);
915
_scrollEnabled: function() {
916
var oScroll = this.s.dt.settings()[0].oScroll;
917
if(oScroll.sY !== "" || oScroll.sX !== "") {
930
FixedHeader.version = "3.2.1";
937
FixedHeader.defaults = {
950
$.fn.dataTable.FixedHeader = FixedHeader;
951
$.fn.DataTable.FixedHeader = FixedHeader;
956
$(document).on( 'init.dt.dtfh', function (e, settings, json) {
957
if ( e.namespace !== 'dt' ) {
961
var init = settings.oInit.fixedHeader;
962
var defaults = DataTable.defaults.fixedHeader;
964
if ( (init || defaults) && ! settings._fixedHeader ) {
965
var opts = $.extend( {}, defaults, init );
967
if ( init !== false ) {
968
new FixedHeader( settings, opts );
974
DataTable.Api.register( 'fixedHeader()', function () {} );
976
DataTable.Api.register( 'fixedHeader.adjust()', function () {
977
return this.iterator( 'table', function ( ctx ) {
978
var fh = ctx._fixedHeader;
986
DataTable.Api.register( 'fixedHeader.enable()', function ( flag ) {
987
return this.iterator( 'table', function ( ctx ) {
988
var fh = ctx._fixedHeader;
990
flag = ( flag !== undefined ? flag : true );
991
if ( fh && flag !== fh.enabled() ) {
997
DataTable.Api.register( 'fixedHeader.enabled()', function () {
998
if ( this.context.length ) {
999
var fh = this.context[0]._fixedHeader;
1002
return fh.enabled();
1009
DataTable.Api.register( 'fixedHeader.disable()', function ( ) {
1010
return this.iterator( 'table', function ( ctx ) {
1011
var fh = ctx._fixedHeader;
1013
if ( fh && fh.enabled() ) {
1019
$.each( ['header', 'footer'], function ( i, el ) {
1020
DataTable.Api.register( 'fixedHeader.'+el+'Offset()', function ( offset ) {
1021
var ctx = this.context;
1023
if ( offset === undefined ) {
1024
return ctx.length && ctx[0]._fixedHeader ?
1025
ctx[0]._fixedHeader[el +'Offset']() :
1029
return this.iterator( 'table', function ( ctx ) {
1030
var fh = ctx._fixedHeader;
1033
fh[ el +'Offset' ]( offset );