import { Browser , KeyboardEventArgs } from '@syncfusion/ej2-base' ; import { extend , isNullOrUndefined } from '@syncfusion/ej2-base' ; import { closest , classList } from '@syncfusion/ej2-base' ; import { SortSettings } from '../base/grid' ; import { Column } from '../models/column' ; import { IGrid , IAction , NotifyArgs } from '../base/interface' ; import { SortDirection } from '../base/enum' ; import { setCssInGridPopUp , getActualPropFromColl , isActionPrevent , iterateExtend } from '../base/util' ; import * as events from '../base/constant' ; import { SortDescriptorModel } from '../base/grid-model' ; import { AriaService } from '../services/aria-service' ; import { ServiceLocator } from '../services/service-locator' ; import { FocusStrategy } from '../services/focus-strategy' ; /**
 *
 * The `Sort` module is used to handle sorting action.
 */ export class Sort implements IAction { private columnName : string ; private direction : SortDirection ; private isMultiSort : boolean ; private lastSortedCol : string ; private sortSettings : SortSettings ; private enableSortMultiTouch : boolean ; private contentRefresh : boolean = true ; private isRemove : boolean ; private sortedColumns : string [ ] ; private isModelChanged : boolean = true ; private aria : AriaService = new AriaService ( ) ; private focus : FocusStrategy ; private lastSortedCols : SortDescriptorModel [ ] ; private lastCols : string [ ] ; private parent : IGrid ; hidden constructor ( parent ? , sortSettings ? , sortedColumns ? , locator ? ) { this . parent = parent ; this . sortSettings = sortSettings ; this . sortedColumns = sortedColumns ; this . focus = locator . getService < FocusStrategy > ( 'focus' ) ; this . addEventListener ( ) ; } return { void } hidden public updateModel ( ) { let sortedColumn = { field : this . columnName , direction : this . direction } ; let index ; let gCols = this . parent . groupSettings . columns ; let flag = false ; if ( ! this . isMultiSort ) { if ( ! gCols . length ) { this . sortSettings . columns = [ sortedColumn ] ; } else { let sortedCols = [ ] ; for ( let i = 0 , len = gCols . length ; i < len ; i ++ ) { index = this . getSortedColsIndexByField ( gCols [ i ] , sortedCols ) ; if ( this . columnName === gCols [ i ] ) { flag = true ; sortedCols . push ( sortedColumn ) ; } else { let sCol = this . getSortColumnFromField ( gCols [ i ] ) ; sortedCols . push ( { field : sCol . field , direction : sCol . direction } ) ; } } if ( ! flag ) { sortedCols . push ( sortedColumn ) ; } this . sortSettings . columns = sortedCols ; } } else { index = this . getSortedColsIndexByField ( this . columnName ) ; if ( index > - 1 ) { this . sortSettings . columns . splice ( index , 1 ) ; } this . sortSettings . columns . push ( sortedColumn ) ; this . sortSettings . columns = this . sortSettings . columns ; } this . parent . dataBind ( ) ; this . lastSortedCol = this . columnName ; } return { void } hidden public onActionComplete ( e ) { let args = ! this . isRemove ? { columnName : this . columnName , direction : this . direction , requestType : 'sorting' , type : events . actionComplete } : { requestType : 'sorting' , type : events . actionComplete } ; this . isRemove = false ; this . parent . trigger ( events . actionComplete , extend ( e , args ) ) ; } param { string } columnName param { SortDirection } direction param { boolean } isMultiSort return { void } public sortColumn ( columnName , direction , isMultiSort ? ) { let gObj = this . parent ; if ( this . parent . getColumnByField ( columnName ) . allowSorting === false || this . parent . isContextMenuOpen ( ) ) { return ; } if ( ! gObj . allowMultiSorting ) { isMultiSort = gObj . allowMultiSorting ; } if ( this . isActionPrevent ( ) ) { gObj . notify ( events . preventBatch , { instance : this , handler : this . sortColumn , arg1 : columnName , arg2 : direction , arg3 : isMultiSort } ) ; return ; } this . backupSettings ( ) ; this . columnName = columnName ; this . direction = direction ; this . isMultiSort = isMultiSort ; this . removeSortIcons ( ) ; let column = gObj . getColumnHeaderByField ( columnName ) ; this . updateSortedCols ( columnName , isMultiSort ) ; this . updateModel ( ) ; } private backupSettings ( ) { this . lastSortedCols = iterateExtend ( this . sortSettings . columns ) ; this . lastCols = this . sortedColumns ; } private restoreSettings ( ) { this . isModelChanged = false ; this . isMultiSort = true ; this . parent . setProperties ( { sortSettings : { columns : this . lastSortedCols } } , true ) ; this . sortedColumns = this . lastCols ; this . isModelChanged = true ; } private updateSortedCols ( columnName , isMultiSort ) { if ( ! isMultiSort ) { if ( this . parent . allowGrouping ) { for ( let i = 0 , len = this . sortedColumns . length ; i < len ; i ++ ) { if ( this . parent . groupSettings . columns . indexOf ( this . sortedColumns [ i ] ) < 0 ) { this . sortedColumns . splice ( i , 1 ) ; len -- ; i -- ; } } } else { this . sortedColumns . splice ( 0 , this . sortedColumns . length ) ; } } if ( this . sortedColumns . indexOf ( columnName ) < 0 ) { this . sortedColumns . push ( columnName ) ; } } hidden public onPropertyChanged ( e ) { if ( e . module !== this . getModuleName ( ) ) { return ; } if ( this . contentRefresh ) { let args = this . sortSettings . columns . length ? { columnName : this . columnName , direction : this . direction , requestType : 'sorting' , type : events . actionBegin } : { requestType : 'sorting' , type : events . actionBegin , cancel : false } ; this . parent . notify ( events . modelChanged , args ) ; } this . refreshSortSettings ( ) ; this . removeSortIcons ( ) ; this . addSortIcons ( ) ; } private refreshSortSettings ( ) { this . sortedColumns = [ ] ; this . sortSettings . columns . forEach ( ( e ) => { this . sortedColumns . push ( e . field ) ; } ) ; } return { void } public clearSorting ( ) { let cols = getActualPropFromColl ( this . sortSettings . columns ) ; if ( this . isActionPrevent ( ) ) { this . parent . notify ( events . preventBatch , { instance : this , handler : this . clearSorting } ) ; return ; } for ( let i = 0 , len = cols . length ; i < len ; i ++ ) { this . removeSortColumn ( cols [ i ] . field ) ; } } private isActionPrevent ( ) { return isActionPrevent ( this . parent ) ; } param { string } field return { void } hidden public removeSortColumn ( field ) { let gObj = this . parent ; let cols = this . sortSettings . columns ; if ( this . sortedColumns . indexOf ( field ) < 0 ) { return ; } if ( this . isActionPrevent ( ) ) { this . parent . notify ( events . preventBatch , { instance : this , handler : this . removeSortColumn , arg1 : field } ) ; return ; } this . backupSettings ( ) ; this . removeSortIcons ( ) ; for ( let i = 0 , len = cols . length ; i < len ; i ++ ) { if ( cols [ i ] . field === field ) { if ( gObj . allowGrouping && gObj . groupSettings . columns . indexOf ( cols [ i ] . field ) > - 1 ) { continue ; } this . sortedColumns . splice ( this . sortedColumns . indexOf ( cols [ i ] . field ) , 1 ) ; cols . splice ( i , 1 ) ; this . isRemove = true ; if ( this . isModelChanged ) { this . parent . notify ( events . modelChanged , { requestType : 'sorting' , type : events . actionBegin } ) ; } break ; } } this . addSortIcons ( ) ; } private getSortedColsIndexByField ( field , sortedColumns ? ) { let cols = sortedColumns ? sortedColumns : this . sortSettings . columns ; for ( let i = 0 , len = cols . length ; i < len ; i ++ ) { if ( cols [ i ] . field === field ) { return i ; } } return - 1 ; } private protected getModuleName ( ) { return 'sort' ; } private initialEnd ( ) { this . parent . off ( events . contentReady , this . initialEnd ) ; if ( this . parent . getColumns ( ) . length && this . sortSettings . columns . length ) { let gObj = this . parent ; this . contentRefresh = false ; this . isMultiSort = this . sortSettings . columns . length > 1 ; for ( let col of gObj . sortSettings . columns . slice ( ) ) { if ( this . sortedColumns . indexOf ( col . field ) > - 1 ) { this . sortColumn ( col . field , col . direction , true ) ; } } this . isMultiSort = false ; this . contentRefresh = true ; } } hidden public addEventListener ( ) { if ( this . parent . isDestroyed ) { return ; } this . parent . on ( events . contentReady , this . initialEnd , this ) ; this . parent . on ( events . sortComplete , this . onActionComplete , this ) ; this . parent . on ( events . inBoundModelChanged , this . onPropertyChanged , this ) ; this . parent . on ( events . click , this . clickHandler , this ) ; this . parent . on ( events . headerRefreshed , this . refreshSortIcons , this ) ; this . parent . on ( events . keyPressed , this . keyPressed , this ) ; this . parent . on ( events . cancelBegin , this . cancelBeginEvent , this ) ; } hidden public removeEventListener ( ) { if ( this . parent . isDestroyed ) { return ; } this . parent . off ( events . sortComplete , this . onActionComplete ) ; this . parent . off ( events . inBoundModelChanged , this . onPropertyChanged ) ; this . parent . off ( events . click , this . clickHandler ) ; this . parent . off ( events . headerRefreshed , this . refreshSortIcons ) ; this . parent . off ( events . keyPressed , this . keyPressed ) ; this . parent . off ( events . cancelBegin , this . cancelBeginEvent ) ; } return { void } hidden public destroy ( ) { this . isModelChanged = false ; let gridElement = this . parent . element ; if ( ! gridElement || ( ! gridElement . querySelector ( '.e-gridheader' ) && ! gridElement . querySelector ( '.e-gridcontent' ) ) ) { return ; } if ( this . parent . element . querySelector ( '.e-gridpopup' ) . querySelectorAll ( '.e-sortdirect' ) . length ) { ( this . parent . element . querySelector ( '.e-gridpopup' ) as HTMLElement ) . style . display = 'none' ; } this . clearSorting ( ) ; this . isModelChanged = true ; this . removeEventListener ( ) ; } private cancelBeginEvent ( e ) { if ( e . requestType === 'sorting' ) { this . restoreSettings ( ) ; this . refreshSortIcons ( ) ; this . isMultiSort = true ; } } private clickHandler ( e ) { this . popUpClickHandler ( e ) ; let target = closest ( e . target as Element , '.e-headercell' ) ; if ( target && ! ( e . target as Element ) . classList . contains ( 'e-grptogglebtn' ) && ! ( e . target as Element ) . classList . contains ( 'e-stackedheadercell' ) && ! ( e . target as Element ) . classList . contains ( 'e-stackedheadercelldiv' ) && ! ( target . classList . contains ( 'e-resized' ) ) && ! ( e . target as Element ) . classList . contains ( 'e-rhandler' ) && ! ( e . target as Element ) . classList . contains ( 'e-columnmenu' ) && ! ( e . target as Element ) . classList . contains ( 'e-filtermenudiv' ) ) { let gObj = this . parent ; let colObj = gObj . getColumnByUid ( target . querySelector ( '.e-headercelldiv' ) . getAttribute ( 'e-mappinguid' ) ) as Column ; let direction = ! target . querySelectorAll ( '.e-ascending' ) . length ? 'Ascending' : 'Descending' ; if ( colObj . type !== 'checkbox' ) { this . initiateSort ( target , e , colObj ) ; if ( Browser . isDevice ) { this . showPopUp ( e ) ; } } } if ( target ) { target . classList . remove ( 'e-resized' ) ; } } private keyPressed ( e ) { let ele = e . target as Element ; if ( ! this . parent . isEdit && ( e . action === 'enter' || e . action === 'ctrlEnter' || e . action === 'shiftEnter' ) && closest ( ele as Element , '.e-headercell' ) ) { let target = this . focus . getFocusedElement ( ) ; if ( isNullOrUndefined ( target ) || ! target . classList . contains ( 'e-headercell' ) || ! target . querySelector ( '.e-headercelldiv' ) ) { return ; } let col = this . parent . getColumnByUid ( target . querySelector ( '.e-headercelldiv' ) . getAttribute ( 'e-mappinguid' ) ) as Column ; this . initiateSort ( target , e , col ) ; } } private initiateSort ( target , e , column ) { let gObj = this . parent ; let field = column . field ; let direction = ! target . querySelectorAll ( '.e-ascending' ) . length ? 'Ascending' : 'Descending' ; if ( e . shiftKey || ( this . sortSettings . allowUnsort && target . querySelectorAll ( '.e-descending' ) . length ) && ! ( gObj . groupSettings . columns . indexOf ( field ) > - 1 ) ) { this . removeSortColumn ( field ) ; } else { this . sortColumn ( field , direction , e . ctrlKey || this . enableSortMultiTouch ) ; } } private showPopUp ( e ) { let target = closest ( e . target as Element , '.e-headercell' ) as HTMLElement ; if ( ! isNullOrUndefined ( target ) || this . parent . isContextMenuOpen ( ) ) { setCssInGridPopUp ( < HTMLElement > this . parent . element . querySelector ( '.e-gridpopup' ) , e , 'e-sortdirect e-icons e-icon-sortdirect' + ( this . sortedColumns . length > 1 ? ' e-spanclicked' : '' ) ) ; } } private popUpClickHandler ( e ) { let target = e . target as Element ; if ( closest ( target , '.e-headercell' ) || ( e . target as HTMLElement ) . classList . contains ( 'e-rowcell' ) || closest ( target , '.e-gridpopup' ) ) { if ( target . classList . contains ( 'e-sortdirect' ) ) { if ( ! target . classList . contains ( 'e-spanclicked' ) ) { target . classList . add ( 'e-spanclicked' ) ; this . enableSortMultiTouch = true ; } else { target . classList . remove ( 'e-spanclicked' ) ; this . enableSortMultiTouch = false ; ( this . parent . element . querySelector ( '.e-gridpopup' ) as HTMLElement ) . style . display = 'none' ; } } } else { ( this . parent . element . querySelector ( '.e-gridpopup' ) as HTMLElement ) . style . display = 'none' ; } } private addSortIcons ( ) { let gObj = this . parent ; let header ; let filterElement ; let cols = this . sortSettings . columns ; let fieldNames = this . parent . getColumns ( ) . map ( ( c ) => c . field ) ; for ( let i = 0 , len = cols . length ; i < len ; i ++ ) { if ( fieldNames . indexOf ( cols [ i ] . field ) === - 1 ) { continue ; } header = gObj . getColumnHeaderByField ( cols [ i ] . field ) ; this . aria . setSort ( < HTMLElement > header , cols [ i ] . direction ) ; if ( this . isMultiSort && cols . length > 1 ) { header . querySelector ( '.e-headercelldiv' ) . insertBefore ( this . parent . createElement ( 'span' , { className : 'e-sortnumber' , innerHTML : ( i + 1 ) . toString ( ) } ) , header . querySelector ( '.e-headertext' ) ) ; } filterElement = header . querySelector ( '.e-sortfilterdiv' ) ; if ( cols [ i ] . direction === 'Ascending' ) { classList ( filterElement , [ 'e-ascending' , 'e-icon-ascending' ] , [ ] ) ; } else { classList ( filterElement , [ 'e-descending' , 'e-icon-descending' ] , [ ] ) ; } } } private removeSortIcons ( position ? ) { let gObj = this . parent ; let header ; let cols = this . sortSettings . columns ; let fieldNames = this . parent . getColumns ( ) . map ( ( c ) => c . field ) ; for ( let i = position ? position : 0 , len = ! isNullOrUndefined ( position ) ? position + 1 : cols . length ; i < len ; i ++ ) { header = gObj . getColumnHeaderByField ( cols [ i ] . field ) ; if ( gObj . allowGrouping && gObj . groupSettings . columns . indexOf ( cols [ i ] . field ) > - 1 && ( isNullOrUndefined ( header ) || ! header . querySelector ( '.e-sortfilterdiv' ) ) ) { continue ; } if ( fieldNames . indexOf ( cols [ i ] . field ) === - 1 ) { continue ; } this . aria . setSort ( < HTMLElement > header , 'none' ) ; classList ( header . querySelector ( '.e-sortfilterdiv' ) , [ ] , [ 'e-descending' , 'e-icon-descending' , 'e-ascending' , 'e-icon-ascending' ] ) ; if ( header . querySelector ( '.e-sortnumber' ) ) { header . querySelector ( '.e-headercelldiv' ) . removeChild ( header . querySelector ( '.e-sortnumber' ) ) ; } } } private getSortColumnFromField ( field ) { for ( let i = 0 , len = this . sortSettings . columns . length ; i < len ; i ++ ) { if ( this . sortSettings . columns [ i ] . field === field ) { return this . sortSettings . columns [ i ] ; } } return false as SortDescriptorModel ; } private updateAriaAttr ( ) { let fieldNames = this . parent . getColumns ( ) . map ( ( c ) => c . field ) ; for ( let col of this . sortedColumns ) { if ( fieldNames . indexOf ( col ) === - 1 ) { continue ; } let header = this . parent . getColumnHeaderByField ( col ) ; this . aria . setSort ( < HTMLElement > header , this . getSortColumnFromField ( col ) . direction ) ; } } private refreshSortIcons ( params = { args : { isFrozen : false } } ) { if ( ! params . args . isFrozen ) { this . removeSortIcons ( ) ; this . isMultiSort = true ; this . removeSortIcons ( ) ; this . addSortIcons ( ) ; this . isMultiSort = false ; this . updateAriaAttr ( ) ; } } }