import { Browser , EventHandler } from '@syncfusion/ej2-base' ; import { addClass , removeClass } from '@syncfusion/ej2-base' ; import { formatUnit , isNullOrUndefined } from '@syncfusion/ej2-base' ; import { IGrid , IAction , NotifyArgs } from '../base/interface' ; import { getScrollBarWidth , getUpdateUsingRaf } from '../base/util' ; import { scroll , contentReady , uiUpdate , onEmpty , headerRefreshed } from '../base/constant' ; import { ColumnWidthService } from '../services/width-controller' ; import { Grid } from '../base/grid' ; /**
 * The `Scroll` module is used to handle scrolling behaviour.
 */ export class Scroll implements IAction { private parent : IGrid ; private lastScrollTop : number = 0 ; private previousValues : { top : number ; left : number ; } = { top : 0 , left : 0 } ; private oneTimeReady : boolean = true ; private content : HTMLDivElement ; private header : HTMLDivElement ; private widthService : ColumnWidthService ; private pageXY : { x : number ; y : number ; } ; hidden constructor ( parent ? ) { this . parent = parent ; this . widthService = new ColumnWidthService ( parent ) ; this . addEventListener ( ) ; } private protected getModuleName ( ) { return 'scroll' ; } hidden public setWidth ( ) { this . parent . element . style . width = formatUnit ( this . parent . width ) ; if ( ( < Grid > this . parent ) . toolbarModule && ( < Grid > this . parent ) . toolbarModule . toolbar && ( < Grid > this . parent ) . toolbarModule . toolbar . element ) { ( < Grid > this . parent ) . toolbarModule . toolbar . refreshOverflow ( ) ; } } hidden public setHeight ( ) { let mHdrHeight = 0 ; let content = ( < HTMLElement > this . parent . getContent ( ) . firstChild ) ; if ( this . parent . frozenRows && this . parent . height !== 'auto' ) { mHdrHeight = ( this . parent . getHeaderContent ( ) . querySelector ( 'tbody' ) as HTMLElement ) . offsetHeight ; content . style . height = formatUnit ( ( this . parent . height as number ) - mHdrHeight ) ; } else { content . style . height = formatUnit ( this . parent . height ) ; } this . ensureOverflow ( content ) ; } hidden public setPadding ( ) { let content = < HTMLElement > this . parent . getHeaderContent ( ) ; let scrollWidth = Scroll . getScrollBarWidth ( ) - this . getThreshold ( ) ; let cssProps = this . getCssProperties ( ) ; ( < HTMLElement > content . firstChild ) . style [ cssProps . border ] = scrollWidth > 0 ? '1px' : '0px' ; content . style [ cssProps . padding ] = scrollWidth > 0 ? scrollWidth + 'px' : '0px' ; } hidden public removePadding ( rtl ? ) { let cssProps = this . getCssProperties ( rtl ) ; ( < HTMLDivElement > this . parent . getHeaderContent ( ) . firstChild ) . style [ cssProps . border ] = '' ; ( < HTMLDivElement > this . parent . getHeaderContent ( ) . firstChild ) . parentElement . style [ cssProps . padding ] = '' ; } return public refresh ( ) { if ( this . parent . height !== '100%' ) { return ; } let content = < HTMLElement > this . parent . getContent ( ) ; this . parent . element . style . height = '100%' ; let height = this . widthService . getSiblingsHeight ( content ) ; content . style . height = 'calc(100% - ' + height + 'px)' ; } private getThreshold ( ) { let appName = Browser . info . name ; if ( appName === 'mozilla' ) { return 0.5 ; } return 1 ; } hidden public addEventListener ( ) { if ( this . parent . isDestroyed ) { return ; } this . parent . on ( onEmpty , this . wireEvents , this ) ; this . parent . on ( contentReady , this . wireEvents , this ) ; this . parent . on ( uiUpdate , this . onPropertyChanged , this ) ; this . parent . on ( headerRefreshed , this . setScrollLeft , this ) ; } hidden public removeEventListener ( ) { if ( this . parent . isDestroyed ) { return ; } this . parent . off ( onEmpty , this . wireEvents ) ; this . parent . off ( contentReady , this . wireEvents ) ; this . parent . off ( uiUpdate , this . onPropertyChanged ) ; this . parent . off ( headerRefreshed , this . setScrollLeft ) ; } private setScrollLeft ( ) { if ( this . parent . frozenColumns ) { ( < HTMLElement > ( < Grid > this . parent ) . headerModule . getMovableHeader ( ) ) . scrollLeft = this . previousValues . left ; } } private onContentScroll ( scrollTarget ) { let element = scrollTarget ; let isHeader = element . classList . contains ( 'e-headercontent' ) ; return ( e ) => { if ( this . content . querySelector ( 'tbody' ) === null || this . parent . isPreventScrollEvent ) { return ; } let target = ( < HTMLElement > e . target ) ; let left = target . scrollLeft ; let sLimit = target . scrollWidth ; let isFooter = target . classList . contains ( 'e-summarycontent' ) ; if ( this . previousValues . left === left ) { this . previousValues . top = ! isHeader ? this . previousValues . top : target . scrollTop ; return ; } element . scrollLeft = left ; if ( isFooter ) { this . header . scrollLeft = left ; } this . previousValues . left = left ; this . parent . notify ( scroll , { left : left } ) ; } ; } private onFreezeContentScroll ( scrollTarget ) { let element = scrollTarget ; return ( e ) => { if ( this . content . querySelector ( 'tbody' ) === null ) { return ; } let target = < HTMLElement > e . target ; let top = target . scrollTop ; if ( this . previousValues . top === top ) { return ; } element . scrollTop = top ; this . previousValues . top = top ; if ( this . parent . isDestroyed ) { return ; } } ; } private onWheelScroll ( scrollTarget ) { let element = scrollTarget ; return ( e ) => { if ( this . content . querySelector ( 'tbody' ) === null ) { return ; } let top = element . scrollTop + ( e . deltaMode === 1 ? e . deltaY * 30 : e . deltaY ) ; if ( this . previousValues . top === top ) { return ; } e . preventDefault ( ) ; this . parent . getContent ( ) . querySelector ( '.e-frozencontent' ) . scrollTop = top ; element . scrollTop = top ; this . previousValues . top = top ; } ; } private onTouchScroll ( scrollTarget ) { let element = scrollTarget ; return ( e ) => { if ( ( e as PointerEvent ) . pointerType === 'mouse' ) { return ; } let cont ; let mHdr ; let pageXY = this . getPointXY ( e ) ; let top = element . scrollTop + ( this . pageXY . y - pageXY . y ) ; let left = element . scrollLeft + ( this . pageXY . x - pageXY . x ) ; if ( this . parent . getHeaderContent ( ) . contains ( e . target as Element ) ) { mHdr = this . parent . getFrozenColumns ( ) ? this . parent . getHeaderContent ( ) . querySelector ( '.e-movableheader' ) : this . parent . getHeaderContent ( ) . firstChild as Element ; if ( this . previousValues . left === left || ( left < 0 || ( mHdr . scrollWidth - mHdr . clientWidth ) < left ) ) { return ; } e . preventDefault ( ) ; mHdr . scrollLeft = left ; element . scrollLeft = left ; this . pageXY . x = pageXY . x ; this . previousValues . left = left ; } else { cont = this . parent . getContent ( ) . querySelector ( '.e-frozencontent' ) ; if ( this . previousValues . top === top || ( top < 0 || ( cont . scrollHeight - cont . clientHeight ) < top ) ) { return ; } e . preventDefault ( ) ; cont . scrollTop = top ; element . scrollTop = top ; this . pageXY . y = pageXY . y ; this . previousValues . top = top ; } } ; } private setPageXY ( ) { return ( e ) => { if ( ( e as PointerEvent ) . pointerType === 'mouse' ) { return ; } this . pageXY = this . getPointXY ( e ) ; } ; } private getPointXY ( e ) { let pageXY = { x : 0 , y : 0 } ; if ( ( e as TouchEvent ) . touches && ( e as TouchEvent ) . touches . length ) { pageXY . x = ( e as TouchEvent ) . touches [ 0 ] . pageX ; pageXY . y = ( e as TouchEvent ) . touches [ 0 ] . pageY ; } else { pageXY . x = ( e as PointerEvent ) . pageX ; pageXY . y = ( e as PointerEvent ) . pageY ; } return pageXY ; } private wireEvents ( ) { if ( this . oneTimeReady ) { let frzCols = this . parent . getFrozenColumns ( ) ; this . content = < HTMLDivElement > this . parent . getContent ( ) . firstChild ; this . header = < HTMLDivElement > this . parent . getHeaderContent ( ) . firstChild ; let mCont = this . content . querySelector ( '.e-movablecontent' ) as HTMLElement ; let fCont = this . content . querySelector ( '.e-frozencontent' ) as HTMLElement ; let mHdr = this . header . querySelector ( '.e-movableheader' ) as HTMLElement ; if ( this . parent . frozenRows ) { EventHandler . add ( frzCols ? mHdr : this . header , 'touchstart pointerdown' , this . setPageXY ( ) , this ) ; EventHandler . add ( frzCols ? mHdr : this . header , 'touchmove pointermove' , this . onTouchScroll ( frzCols ? mCont : this . content ) , this ) ; } if ( frzCols ) { EventHandler . add ( mCont , 'scroll' , this . onContentScroll ( mHdr ) , this ) ; EventHandler . add ( mCont , 'scroll' , this . onFreezeContentScroll ( fCont ) , this ) ; EventHandler . add ( fCont , 'scroll' , this . onFreezeContentScroll ( mCont ) , this ) ; EventHandler . add ( mHdr , 'scroll' , this . onContentScroll ( mCont ) , this ) ; EventHandler . add ( fCont , 'wheel' , this . onWheelScroll ( mCont ) , this ) ; EventHandler . add ( fCont , 'touchstart pointerdown' , this . setPageXY ( ) , this ) ; EventHandler . add ( fCont , 'touchmove pointermove' , this . onTouchScroll ( mCont ) , this ) ; } else { EventHandler . add ( this . content , 'scroll' , this . onContentScroll ( this . header ) , this ) ; EventHandler . add ( this . header , 'scroll' , this . onContentScroll ( this . content ) , this ) ; } if ( this . parent . aggregates . length ) { EventHandler . add ( < HTMLDivElement > this . parent . getFooterContent ( ) . firstChild , 'scroll' , this . onContentScroll ( this . content ) , this ) ; } this . refresh ( ) ; this . oneTimeReady = false ; } let table = this . parent . getContentTable ( ) ; let sLeft ; let sHeight ; let clientHeight ; getUpdateUsingRaf ( ( ) => { sLeft = this . header . scrollLeft ; sHeight = table . scrollHeight ; clientHeight = this . parent . getContent ( ) . clientHeight ; } , ( ) => { if ( ! this . parent . enableVirtualization ) { if ( sHeight < clientHeight ) { addClass ( table . querySelectorAll ( 'tr:last-child td' ) , 'e-lastrowcell' ) ; if ( this . parent . getFrozenColumns ( ) ) { addClass ( this . parent . getContent ( ) . querySelector ( '.e-movablecontent' ) . querySelectorAll ( 'tr:last-child td' ) , 'e-lastrowcell' ) ; } } if ( ( this . parent . frozenRows > 0 || this . parent . frozenColumns > 0 ) && this . header . querySelector ( '.e-movableheader' ) ) { this . header . querySelector ( '.e-movableheader' ) . scrollLeft = this . previousValues . left ; } else { this . header . scrollLeft = this . previousValues . left ; } this . content . scrollLeft = this . previousValues . left ; this . content . scrollTop = this . previousValues . top ; } if ( ! this . parent . enableColumnVirtualization ) { this . content . scrollLeft = sLeft ; } if ( this . parent . frozenColumns && this . header . querySelector ( '.e-movableheader' ) ) { ( this . header . querySelector ( '.e-movableheader' ) as HTMLElement ) . scrollLeft = ( this . content . querySelector ( '.e-movablecontent' ) as HTMLElement ) . scrollLeft ; } } ) ; this . parent . isPreventScrollEvent = false ; } hidden public getCssProperties ( rtl ? ) { let css = { } ; let enableRtl = isNullOrUndefined ( rtl ) ? this . parent . enableRtl : rtl ; css . border = enableRtl ? 'borderLeftWidth' : 'borderRightWidth' ; css . padding = enableRtl ? 'paddingLeft' : 'paddingRight' ; return css ; } private ensureOverflow ( content ) { if ( this . parent . getFrozenColumns ( ) ) { ( content . querySelector ( '.e-movablecontent' ) as HTMLElement ) . style . overflowY = this . parent . height === 'auto' ? 'auto' : 'scroll' ; } else { content . style . overflowY = this . parent . height === 'auto' ? 'auto' : 'scroll' ; } } private onPropertyChanged ( e ) { if ( e . module !== this . getModuleName ( ) ) { return ; } this . setPadding ( ) ; this . oneTimeReady = true ; if ( this . parent . height === 'auto' ) { this . removePadding ( ) ; } this . wireEvents ( ) ; this . setHeight ( ) ; this . setWidth ( ) ; } hidden public destroy ( ) { let gridElement = this . parent . element ; if ( ! gridElement || ( ! gridElement . querySelector ( '.e-gridheader' ) && ! gridElement . querySelector ( '.e-gridcontent' ) ) ) { return ; } this . removeEventListener ( ) ; this . removePadding ( ) ; removeClass ( [ < HTMLDivElement > this . parent . getHeaderContent ( ) . firstChild ] , 'e-headercontent' ) ; removeClass ( [ < HTMLDivElement > this . parent . getContent ( ) . firstChild ] , 'e-content' ) ; ( < HTMLDivElement > this . parent . getContent ( ) . firstChild ) . style . height = '' ; this . parent . element . style . width = '' ; EventHandler . remove ( < HTMLDivElement > this . parent . getContent ( ) . firstChild , 'scroll' , this . onContentScroll ) ; } return { number } hidden public static getScrollBarWidth ( ) { return getScrollBarWidth ( ) ; } } hidden export interface ScrollCss { padding ? : string ; border ? : string ; }