import { extend , isNullOrUndefined } from '@syncfusion/ej2-base' ; import { closest as closestElement , removeClass , classList , remove } from '@syncfusion/ej2-base' ; import { Column } from '../models/column' ; import { getElementIndex , inArray , parentsUntil , getPosition , isActionPrevent } from '../base/util' ; import { IGrid , IAction , NotifyArgs } from '../base/interface' ; import * as events from '../base/constant' ; /**
 *
 * The `Reorder` module is used for reordering columns.
 */ export class Reorder implements IAction { private element : HTMLElement ; private upArrow : HTMLElement ; private downArrow : HTMLElement ; private x : number ; private timer : number ; private parent : IGrid ; hidden constructor ( parent ? ) { this . parent = parent ; if ( this . parent . isDestroyed ) { return ; } this . parent . on ( events . headerDrop , this . headerDrop , this ) ; this . parent . on ( events . uiUpdate , this . enableAfterRender , this ) ; this . parent . on ( events . reorderComplete , this . onActionComplete , this ) ; this . parent . on ( events . columnDrag , this . drag , this ) ; this . parent . on ( events . columnDragStart , this . dragStart , this ) ; this . parent . on ( events . columnDragStop , this . dragStop , this ) ; this . parent . on ( events . headerDrop , this . headerDrop , this ) ; this . parent . on ( events . headerRefreshed , this . createReorderElement , this ) ; } private chkDropPosition ( srcElem , destElem ) { return ( srcElem . parentElement . isEqualNode ( destElem . parentElement ) || ( this . parent . getFrozenColumns ( ) && Array . prototype . indexOf . call ( closestElement ( srcElem , 'thead' ) . children , srcElem . parentElement ) === Array . prototype . indexOf . call ( closestElement ( destElem , 'thead' ) . children , destElem . parentElement ) ) ) && this . targetParentContainerIndex ( srcElem , destElem ) > - 1 ; } private chkDropAllCols ( srcElem , destElem ) { let isFound ; let headers = this . getHeaderCells ( ) ; let header ; while ( ! isFound && headers . length > 0 ) { header = headers . pop ( ) ; isFound = srcElem !== header && this . targetParentContainerIndex ( srcElem , destElem ) > - 1 ; } return isFound ; } private findColParent ( col , cols , parent ) { parent = parent ; for ( let i = 0 , len = cols . length ; i < len ; i ++ ) { if ( col === cols [ i ] ) { return true ; } else if ( cols [ i ] . columns ) { let cnt = parent . length ; parent . push ( cols [ i ] ) ; if ( ! this . findColParent ( col , cols [ i ] . columns as Column [ ] , parent ) ) { parent . splice ( cnt , parent . length - cnt ) ; } else { return true ; } } } return false ; } private getColumnsModel ( cols ) { let columnModel = [ ] ; let subCols = [ ] ; for ( let i = 0 , len = cols . length ; i < len ; i ++ ) { columnModel . push ( cols [ i ] ) ; if ( cols [ i ] . columns ) { subCols = subCols . concat ( cols [ i ] . columns as Column [ ] ) ; } } if ( subCols . length ) { columnModel = columnModel . concat ( this . getColumnsModel ( subCols as Column [ ] ) ) ; } return columnModel ; } private headerDrop ( e ) { let gObj = this . parent ; let dropElement = this . element . querySelector ( '.e-headercelldiv' ) || this . element . querySelector ( '.e-stackedheadercelldiv' ) ; let uId = dropElement . getAttribute ( 'e-mappinguid' ) ; let column = gObj . getColumnByUid ( uId ) ; if ( ! closestElement ( e . target , 'th' ) || ( ! isNullOrUndefined ( column ) && column . allowReordering === false ) ) { return ; } let destElem = closestElement ( e . target as Element , '.e-headercell' ) ; let destElemDiv = destElem . querySelector ( '.e-headercelldiv' ) || destElem . querySelector ( '.e-stackedheadercelldiv' ) ; let destElemUid = destElemDiv . getAttribute ( 'e-mappinguid' ) ; if ( ! isNullOrUndefined ( destElemUid ) ) { let destColumn = gObj . getColumnByUid ( destElemUid ) ; if ( isNullOrUndefined ( destColumn ) || destColumn . allowReordering === false ) { return ; } } if ( destElem && ! ( ! this . chkDropPosition ( this . element , destElem ) || ! this . chkDropAllCols ( this . element , destElem ) ) ) { if ( this . parent . enableColumnVirtualization ) { let columns = this . parent . columns as Column [ ] ; let sourceUid = this . element . querySelector ( '.e-headercelldiv' ) . getAttribute ( 'e-mappinguid' ) ; let col = this . parent . getColumns ( true ) . filter ( ( col ) => col . uid === sourceUid ) ; let colMatchIndex = null ; let column = col [ 0 ] ; let destUid = destElem . querySelector ( '.e-headercelldiv' ) . getAttribute ( 'e-mappinguid' ) ; let bool = columns . some ( ( col , index ) => { if ( col . uid === destUid ) { colMatchIndex = index ; return col . uid === destUid ; } return false ; } ) ; if ( ! isNullOrUndefined ( colMatchIndex ) ) { this . moveColumns ( colMatchIndex , column ) ; } } else { let headers = this . getHeaderCells ( ) ; let oldIdx = getElementIndex ( this . element , headers ) ; let columns = this . getColumnsModel ( this . parent . columns as Column [ ] ) ; let column = columns [ oldIdx ] ; let newIndex = this . targetParentContainerIndex ( this . element , destElem ) ; this . moveColumns ( newIndex , column ) ; } } } private isActionPrevent ( gObj ) { return isActionPrevent ( gObj ) ; } private moveColumns ( destIndex , column ) { let gObj = this . parent ; if ( this . isActionPrevent ( gObj ) ) { gObj . notify ( events . preventBatch , { instance : this , handler : this . moveColumns , arg1 : destIndex , arg2 : column } ) ; return ; } let parent = this . getColParent ( column , this . parent . columns as Column [ ] ) ; let cols = parent ? parent . columns as Column [ ] : this . parent . columns as Column [ ] ; let srcIdx = inArray ( column , cols ) ; if ( ! gObj . allowReordering || srcIdx === destIndex || srcIdx === - 1 || destIndex === - 1 ) { return ; } ( cols as Column [ ] ) . splice ( destIndex , 0 , ( cols as Column [ ] ) . splice ( srcIdx , 1 ) [ 0 ] as Column ) ; gObj . getColumns ( true ) ; gObj . notify ( events . columnPositionChanged , { fromIndex : destIndex , toIndex : srcIdx } ) ; gObj . notify ( events . modelChanged , { type : events . actionBegin , requestType : 'reorder' } ) ; } private targetParentContainerIndex ( srcElem , destElem ) { let headers = this . getHeaderCells ( ) ; let cols = this . parent . columns as Column [ ] ; let flatColumns = this . getColumnsModel ( cols ) ; let parent = this . getColParent ( flatColumns [ getElementIndex ( srcElem , headers ) ] , cols ) ; cols = parent ? parent . columns as Column [ ] : cols ; return inArray ( flatColumns [ getElementIndex ( destElem , headers ) ] , cols ) ; } private getHeaderCells ( ) { if ( this . parent . getFrozenColumns ( ) ) { let fTh ; let mTh ; let fHeaders = [ ] ; let fRows = [ ] . slice . call ( this . parent . getHeaderTable ( ) . querySelectorAll ( '.e-columnheader' ) ) ; let mRows = [ ] . slice . call ( this . parent . getHeaderContent ( ) . querySelector ( '.e-movableheader' ) . querySelectorAll ( '.e-columnheader' ) ) ; for ( let i = 0 ; i < fRows . length ; i ++ ) { fTh = [ ] . slice . call ( fRows [ i ] . getElementsByClassName ( 'e-headercell' ) ) ; mTh = [ ] . slice . call ( mRows [ i ] . getElementsByClassName ( 'e-headercell' ) ) ; fHeaders = fHeaders . concat ( fTh ) ; for ( let j = 0 ; j < mTh . length ; j ++ ) { if ( ! fTh . length || j !== 0 || fTh [ fTh . length - 1 ] . innerText !== mTh [ 0 ] . innerText ) { fHeaders . push ( mTh [ j ] ) ; } } } return fHeaders ; } else { return [ ] . slice . call ( this . parent . element . getElementsByClassName ( 'e-headercell' ) ) ; } } private getColParent ( column , columns ) { let parents = [ ] ; this . findColParent ( column , columns , parents ) ; return parents [ parents . length - 1 ] ; } param { string } fromFName param { string } toFName return { void } public reorderColumns ( fromFName , toFName ) { let fColumn = this . parent . getColumnByField ( fromFName ) ; let toColumn = this . parent . getColumnByField ( toFName ) ; if ( ( ! isNullOrUndefined ( fColumn ) && ! fColumn . allowReordering ) || ( ! isNullOrUndefined ( toColumn ) && ! toColumn . allowReordering ) ) { return ; } let column = this . parent . getColumnByField ( toFName ) ; let parent = this . getColParent ( column , this . parent . columns as Column [ ] ) ; let columns = parent ? parent . columns as Column [ ] : this . parent . columns as Column [ ] ; let destIndex = inArray ( column , columns ) ; if ( destIndex > - 1 ) { this . moveColumns ( destIndex , this . parent . getColumnByField ( fromFName ) ) ; } } private enableAfterRender ( e ) { if ( e . module === this . getModuleName ( ) && e . enable ) { this . createReorderElement ( ) ; } } private createReorderElement ( ) { let header = ( this . parent . element . querySelector ( '.e-headercontent' ) as Element ) ; this . upArrow = header . appendChild ( this . parent . createElement ( 'div' , { className : 'e-icons e-icon-reorderuparrow e-reorderuparrow' , attrs : { style : 'display:none' } } ) ) ; this . downArrow = header . appendChild ( this . parent . createElement ( 'div' , { className : 'e-icons e-icon-reorderdownarrow e-reorderdownarrow' , attrs : { style : 'display:none' } } ) ) ; } return { void } hidden public onActionComplete ( e ) { this . parent . trigger ( events . actionComplete , extend ( e , { type : events . actionComplete } ) ) ; } return { void } hidden public destroy ( ) { let gridElement = this . parent . element ; if ( this . parent . isDestroyed || ! gridElement || ( ! gridElement . querySelector ( '.e-gridheader' ) && ! gridElement . querySelector ( '.e-gridcontent' ) ) ) { return ; } remove ( this . upArrow ) ; remove ( this . downArrow ) ; this . parent . off ( events . headerDrop , this . headerDrop ) ; this . parent . off ( events . uiUpdate , this . enableAfterRender ) ; this . parent . off ( events . reorderComplete , this . onActionComplete ) ; this . parent . off ( events . columnDrag , this . drag ) ; this . parent . off ( events . columnDragStart , this . dragStart ) ; this . parent . off ( events . columnDragStop , this . dragStop ) ; this . parent . off ( events . headerRefreshed , this . createReorderElement ) ; } private drag ( e ) { let gObj = this . parent ; let target = e . target as Element ; if ( e . column . allowReordering === false ) { return ; } let closest = closestElement ( target , '.e-headercell:not(.e-stackedHeaderCell)' ) ; let cloneElement = gObj . element . querySelector ( '.e-cloneproperties' ) as HTMLElement ; let isLeft = this . x > getPosition ( e . event ) . x + gObj . getContent ( ) . firstElementChild . scrollLeft ; removeClass ( gObj . getHeaderTable ( ) . querySelectorAll ( '.e-reorderindicate' ) , [ 'e-reorderindicate' ] ) ; this . setDisplay ( 'none' ) ; this . stopTimer ( ) ; classList ( cloneElement , [ 'e-defaultcur' ] , [ 'e-notallowedcur' ] ) ; this . updateScrollPostion ( e . event ) ; if ( closest && ! closest . isEqualNode ( this . element ) ) { target = closest ; if ( ! ( ! this . chkDropPosition ( this . element , target ) || ! this . chkDropAllCols ( this . element , target ) ) ) { this . updateArrowPosition ( target , isLeft ) ; classList ( target , [ 'e-allowDrop' , 'e-reorderindicate' ] , [ ] ) ; } else if ( ! ( gObj . allowGrouping && parentsUntil ( e . target as Element , 'e-groupdroparea' ) ) ) { classList ( cloneElement , [ 'e-notallowedcur' ] , [ 'e-defaultcur' ] ) ; } } gObj . trigger ( events . columnDrag , { target : target , draggableType : 'headercell' , column : e . column } ) ; } private updateScrollPostion ( e ) { let frzCols = this . parent . getFrozenColumns ( ) ; let x = getPosition ( e ) . x ; let cliRect = this . parent . element . getBoundingClientRect ( ) ; let cliRectBaseLeft = frzCols ? this . parent . element . querySelector ( '.e-movableheader' ) . getBoundingClientRect ( ) . left : cliRect . left ; let cliRectBaseRight = cliRect . right ; let scrollElem = frzCols ? this . parent . getContent ( ) . querySelector ( '.e-movablecontent' ) : this . parent . getContent ( ) . firstElementChild ; if ( x > cliRectBaseLeft && x < cliRectBaseLeft + 35 ) { this . timer = window . setInterval ( ( ) => { this . setScrollLeft ( scrollElem , true ) ; } , 50 ) ; } else if ( x < cliRectBaseRight && x > cliRectBaseRight - 35 ) { this . timer = window . setInterval ( ( ) => { this . setScrollLeft ( scrollElem , false ) ; } , 50 ) ; } } private setScrollLeft ( scrollElem , isLeft ) { let scrollLeft = scrollElem . scrollLeft ; scrollElem . scrollLeft = scrollElem . scrollLeft + ( isLeft ? - 5 : 5 ) ; if ( scrollLeft !== scrollElem . scrollLeft ) { this . setDisplay ( 'none' ) ; } } private stopTimer ( ) { window . clearInterval ( this . timer ) ; } private updateArrowPosition ( target , isLeft ) { let cliRect = target . getBoundingClientRect ( ) ; let cliRectBase = this . parent . element . getBoundingClientRect ( ) ; if ( ( isLeft && cliRect . left < cliRectBase . left ) || ( ! isLeft && cliRect . right > cliRectBase . right ) ) { return ; } this . upArrow . style . top = cliRect . top + cliRect . height - cliRectBase . top + 'px' ; this . downArrow . style . top = cliRect . top - cliRectBase . top - 4 + 'px' ; this . upArrow . style . left = this . downArrow . style . left = ( isLeft ? cliRect . left : cliRect . right ) - cliRectBase . left - 4 + 'px' ; this . setDisplay ( '' ) ; } private dragStart ( e ) { let gObj = this . parent ; let target = e . target as Element ; this . element = target . classList . contains ( 'e-headercell' ) ? target as HTMLElement : parentsUntil ( target , 'e-headercell' ) as HTMLElement ; if ( e . column . allowReordering === false ) { return ; } this . x = getPosition ( e . event ) . x + gObj . getContent ( ) . firstElementChild . scrollLeft ; gObj . trigger ( events . columnDragStart , { target : target as Element , draggableType : 'headercell' , column : e . column } ) ; } private dragStop ( e ) { let gObj = this . parent ; this . setDisplay ( 'none' ) ; this . stopTimer ( ) ; if ( ! e . cancel ) { gObj . trigger ( events . columnDrop , { target : e . target , draggableType : 'headercell' , column : e . column } ) ; } removeClass ( gObj . getHeaderTable ( ) . querySelectorAll ( '.e-reorderindicate' ) , [ 'e-reorderindicate' ] ) ; } private setDisplay ( display ) { this . upArrow . style . display = display ; this . downArrow . style . display = display ; } private protected getModuleName ( ) { return 'reorder' ; } }