2018-11-23 12:29:20 +00:00
/ * *
* @ file
* Sticky table headers .
* /
( function ( $ , Drupal , displace ) {
/ * *
* Constructor for the tableHeader object . Provides sticky table headers .
*
* TableHeader will make the current table header stick to the top of the page
* if the table is very long .
*
* @ constructor Drupal . TableHeader
*
* @ param { HTMLElement } table
* DOM object for the table to add a sticky header to .
*
* @ listens event : columnschange
* /
function TableHeader ( table ) {
const $table = $ ( table ) ;
/ * *
* @ name Drupal . TableHeader # $originalTable
*
* @ type { HTMLElement }
* /
this . $originalTable = $table ;
/ * *
* @ type { jQuery }
* /
this . $originalHeader = $table . children ( 'thead' ) ;
/ * *
* @ type { jQuery }
* /
this . $originalHeaderCells = this . $originalHeader . find ( '> tr > th' ) ;
/ * *
* @ type { null | bool }
* /
this . displayWeight = null ;
this . $originalTable . addClass ( 'sticky-table' ) ;
this . tableHeight = $table [ 0 ] . clientHeight ;
this . tableOffset = this . $originalTable . offset ( ) ;
// React to columns change to avoid making checks in the scroll callback.
this . $originalTable . on (
'columnschange' ,
{ tableHeader : this } ,
( e , display ) => {
const tableHeader = e . data . tableHeader ;
if (
tableHeader . displayWeight === null ||
tableHeader . displayWeight !== display
) {
tableHeader . recalculateSticky ( ) ;
}
tableHeader . displayWeight = display ;
} ,
) ;
// Create and display sticky header.
this . createSticky ( ) ;
}
// Helper method to loop through tables and execute a method.
function forTables ( method , arg ) {
const tables = TableHeader . tables ;
const il = tables . length ;
for ( let i = 0 ; i < il ; i ++ ) {
tables [ i ] [ method ] ( arg ) ;
}
}
// Select and initialize sticky table headers.
function tableHeaderInitHandler ( e ) {
const $tables = $ ( e . data . context )
. find ( 'table.sticky-enabled' )
. once ( 'tableheader' ) ;
const il = $tables . length ;
for ( let i = 0 ; i < il ; i ++ ) {
TableHeader . tables . push ( new TableHeader ( $tables [ i ] ) ) ;
}
forTables ( 'onScroll' ) ;
}
/ * *
* Attaches sticky table headers .
*
* @ type { Drupal ~ behavior }
*
* @ prop { Drupal ~ behaviorAttach } attach
* Attaches the sticky table header behavior .
* /
Drupal . behaviors . tableHeader = {
attach ( context ) {
$ ( window ) . one (
'scroll.TableHeaderInit' ,
{ context } ,
tableHeaderInitHandler ,
) ;
} ,
} ;
function scrollValue ( position ) {
return document . documentElement [ position ] || document . body [ position ] ;
}
function tableHeaderResizeHandler ( e ) {
forTables ( 'recalculateSticky' ) ;
}
function tableHeaderOnScrollHandler ( e ) {
forTables ( 'onScroll' ) ;
}
function tableHeaderOffsetChangeHandler ( e , offsets ) {
forTables ( 'stickyPosition' , offsets . top ) ;
}
// Bind event that need to change all tables.
$ ( window ) . on ( {
/ * *
* When resizing table width can change , recalculate everything .
*
* @ ignore
* /
'resize.TableHeader' : tableHeaderResizeHandler ,
/ * *
* Bind only one event to take care of calling all scroll callbacks .
*
* @ ignore
* /
'scroll.TableHeader' : tableHeaderOnScrollHandler ,
} ) ;
// Bind to custom Drupal events.
$ ( document ) . on ( {
/ * *
* Recalculate columns width when window is resized and when show / hide
* weight is triggered .
*
* @ ignore
* /
'columnschange.TableHeader' : tableHeaderResizeHandler ,
/ * *
* Recalculate TableHeader . topOffset when viewport is resized .
*
* @ ignore
* /
'drupalViewportOffsetChange.TableHeader' : tableHeaderOffsetChangeHandler ,
} ) ;
/ * *
* Store the state of TableHeader .
* /
$ . extend (
TableHeader ,
/** @lends Drupal.TableHeader */ {
/ * *
* This will store the state of all processed tables .
*
* @ type { Array . < Drupal . TableHeader > }
* /
tables : [ ] ,
} ,
) ;
/ * *
* Extend TableHeader prototype .
* /
$ . extend (
TableHeader . prototype ,
/** @lends Drupal.TableHeader# */ {
/ * *
* Minimum height in pixels for the table to have a sticky header .
*
* @ type { number }
* /
minHeight : 100 ,
/ * *
* Absolute position of the table on the page .
*
* @ type { ? Drupal ~ displaceOffset }
* /
tableOffset : null ,
/ * *
* Absolute position of the table on the page .
*
* @ type { ? number }
* /
tableHeight : null ,
/ * *
* Boolean storing the sticky header visibility state .
*
* @ type { bool }
* /
stickyVisible : false ,
/ * *
* Create the duplicate header .
* /
createSticky ( ) {
// Clone the table header so it inherits original jQuery properties.
const $stickyHeader = this . $originalHeader . clone ( true ) ;
// Hide the table to avoid a flash of the header clone upon page load.
this . $stickyTable = $ ( '<table class="sticky-header"/>' )
. css ( {
visibility : 'hidden' ,
position : 'fixed' ,
top : '0px' ,
} )
. append ( $stickyHeader )
. insertBefore ( this . $originalTable ) ;
this . $stickyHeaderCells = $stickyHeader . find ( '> tr > th' ) ;
// Initialize all computations.
this . recalculateSticky ( ) ;
} ,
/ * *
* Set absolute position of sticky .
*
* @ param { number } offsetTop
* The top offset for the sticky header .
* @ param { number } offsetLeft
* The left offset for the sticky header .
*
* @ return { jQuery }
* The sticky table as a jQuery collection .
* /
stickyPosition ( offsetTop , offsetLeft ) {
const css = { } ;
if ( typeof offsetTop === 'number' ) {
css . top = ` ${ offsetTop } px ` ;
}
if ( typeof offsetLeft === 'number' ) {
css . left = ` ${ this . tableOffset . left - offsetLeft } px ` ;
}
return this . $stickyTable . css ( css ) ;
} ,
/ * *
* Returns true if sticky is currently visible .
*
* @ return { bool }
* The visibility status .
* /
checkStickyVisible ( ) {
const scrollTop = scrollValue ( 'scrollTop' ) ;
const tableTop = this . tableOffset . top - displace . offsets . top ;
const tableBottom = tableTop + this . tableHeight ;
let visible = false ;
if ( tableTop < scrollTop && scrollTop < tableBottom - this . minHeight ) {
visible = true ;
}
this . stickyVisible = visible ;
return visible ;
} ,
/ * *
* Check if sticky header should be displayed .
*
* This function is throttled to once every 250 ms to avoid unnecessary
* calls .
*
* @ param { jQuery . Event } e
* The scroll event .
* /
onScroll ( e ) {
this . checkStickyVisible ( ) ;
// Track horizontal positioning relative to the viewport.
this . stickyPosition ( null , scrollValue ( 'scrollLeft' ) ) ;
this . $stickyTable . css (
'visibility' ,
this . stickyVisible ? 'visible' : 'hidden' ,
) ;
} ,
/ * *
* Event handler : recalculates position of the sticky table header .
*
* @ param { jQuery . Event } event
* Event being triggered .
* /
recalculateSticky ( event ) {
// Update table size.
this . tableHeight = this . $originalTable [ 0 ] . clientHeight ;
// Update offset top.
displace . offsets . top = displace . calculateOffset ( 'top' ) ;
this . tableOffset = this . $originalTable . offset ( ) ;
this . stickyPosition ( displace . offsets . top , scrollValue ( 'scrollLeft' ) ) ;
// Update columns width.
let $that = null ;
let $stickyCell = null ;
let display = null ;
// Resize header and its cell widths.
// Only apply width to visible table cells. This prevents the header from
// displaying incorrectly when the sticky header is no longer visible.
const il = this . $originalHeaderCells . length ;
for ( let i = 0 ; i < il ; i ++ ) {
$that = $ ( this . $originalHeaderCells [ i ] ) ;
$stickyCell = this . $stickyHeaderCells . eq ( $that . index ( ) ) ;
display = $that . css ( 'display' ) ;
if ( display !== 'none' ) {
$stickyCell . css ( { width : $that . css ( 'width' ) , display } ) ;
} else {
$stickyCell . css ( 'display' , 'none' ) ;
}
}
this . $stickyTable . css ( 'width' , this . $originalTable . outerWidth ( ) ) ;
} ,
} ,
) ;
// Expose constructor in the public space.
Drupal . TableHeader = TableHeader ;
2019-01-24 08:00:03 +00:00
} ) ( jQuery , Drupal , window . Drupal . displace ) ;