Source: admin/javascript/engine/extensions/datatable_fixed_header.js

/* --------------------------------------------------------------
 datatable_fixed_header.js 2016-07-13
 Gambio GmbH
 http://www.gambio.de
 Copyright (c) 2016 Gambio GmbH
 Released under the GNU General Public License (Version 2)
 [http://www.gnu.org/licenses/gpl-2.0.html]
 --------------------------------------------------------------
 */

/**
 * ## Enable Fixed DataTable Header
 *
 * The table header will remain in the viewport as the user scrolls down the page. The style change of this
 * module is a bit tricky because we need to remove the thead from the normal flow, something that breaks the
 * display of the table. Therefore a helper clone of the thead is used to maintain the table formatting.
 *
 * **Notice #1**: The .table-fixed-header class is styled by the _tables.scss and is part of this solution.
 *
 * **Notice #2**: This method will take into concern the .content-header element which shouldn't overlap the
 * table header.
 *
 * @module Admin/Extensions/datatable_fixed_header
 */
gx.extensions.module('datatable_fixed_header', [], function(data) {
	
	'use strict';
	
	// ------------------------------------------------------------------------
	// VARIABLES
	// ------------------------------------------------------------------------
	
	/**
	 * Module Selector
	 *
	 * @type {jQuery}
	 */
	const $this = $(this);
	
	/**
	 * Table Header Selector
	 *
	 * @type {jQuery}
	 */
	const $thead = $this.children('thead');
	
	/**
	 * Module Instance
	 *
	 * @type {Object}
	 */
	const module = {};
	
	/**
	 * Marks the end of the table.
	 *
	 * This value is used to stop the fixed header when the user reaches the end of the table.
	 *
	 * @type {Number}
	 */
	let tableOffsetBottom;
	
	// ------------------------------------------------------------------------
	// FUNCTIONS
	// ------------------------------------------------------------------------
	
	/**
	 * On DataTable Draw Event
	 *
	 * Re-calculate the table bottom offset value.
	 */
	function _onDataTableDraw() {
		tableOffsetBottom = $this.offset().top + $this.height() - $thead.height();
	}
	
	/**
	 * On DataTable Initialization
	 *
	 * Modify the table HTML and set the required event handling for the fixed header functionality.
	 */
	function _onDataTableInit() {
		const $mainHeader = $('#main-header');
		const $contentHeader = $('.content-header');
		const $clone = $thead.clone();
		const originalTop = $thead.offset().top;
		let isFixed = false;
		let rollingAnimationInterval = null;
		
		$clone
			.hide()
			.addClass('table-fixed-header-helper')
			.prependTo($this);
		
		$(window)
			.on('scroll', function() {
				const scrollTop = $(window).scrollTop();
				
				if (!isFixed && scrollTop + $mainHeader.outerHeight() > originalTop) {
					$this.addClass('table-fixed-header');
					$thead
						.outerWidth($this.outerWidth())
						.addClass('fixed');
					$clone.show();
					isFixed = true;
				} else if (isFixed && scrollTop + $mainHeader.outerHeight() < originalTop) {
					$this.removeClass('table-fixed-header');
					$thead
						.outerWidth('')
						.removeClass('fixed');
					$clone.hide();
					isFixed = false;
				}
				
				if (scrollTop >= tableOffsetBottom) {
					$thead.removeClass('fixed');
				} else if ($(window).scrollTop() < tableOffsetBottom && !$thead.hasClass('fixed')) {
					$thead.addClass('fixed');
				}
			})
			.on('content_header:roll_in', function() {
				rollingAnimationInterval = setInterval(() => {
					$thead.css('top', $contentHeader.position().top + $contentHeader.outerHeight());
					if ($contentHeader.hasClass('fixed')) {
						clearInterval(rollingAnimationInterval);
					}
				}, 1);
			})
			.on('content_header:roll_out', function() {
				clearInterval(rollingAnimationInterval);
				$thead.css('top', $mainHeader.outerHeight());
			});
	}
	
	
	// ------------------------------------------------------------------------
	// INITIALIZATION
	// ------------------------------------------------------------------------
	
	module.init = function(done) {
		$(window).on('JSENGINE_INIT_FINISHED', () => {
			$this
				.on('draw.dt', _onDataTableDraw)
				.on('init.dt', _onDataTableInit);
			
			// Setup fixed header functionality if the table is already initialized.
			if ($this.DataTable().ajax.json() !== undefined) {
				_onDataTableInit();
			}
		});
		
		done();
	};
	
	return module;
	
});