import { L10n , closest , isNullOrUndefined , KeyboardEventArgs , EventHandler } from '@syncfusion/ej2-base' ; import { remove } from '@syncfusion/ej2-base' ; import { ContextMenu as Menu , MenuItemModel } from '@syncfusion/ej2-navigations' ; import { OpenCloseMenuEventArgs } from '@syncfusion/ej2-navigations' ; import { IGrid , ContextMenuItemModel , IAction , NotifyArgs , ContextMenuOpenEventArgs , ContextMenuClickEventArgs } from '../base/interface' ; import { Column } from '../models/column' ; import { ServiceLocator } from '../services/service-locator' ; import * as events from '../base/constant' ; import { SortDescriptorModel } from '../base/grid-model' ; import { Resize } from '../actions/resize' ; import { Page } from '../actions/page' ; import { parentsUntil } from '../base/util' ; import { Group } from '../actions/group' ; import { Sort } from '../actions/sort' ; import { PdfExport } from '../actions/pdf-export' ; import { ExcelExport } from '../actions/excel-export' ; export const menuClass = { header : '.e-gridheader' , content : '.e-gridcontent' , edit : '.e-inline-edit' , batchEdit : '.e-editedbatchcell' , editIcon : 'e-edit' , pager : '.e-gridpager' , delete : 'e-delete' , save : 'e-save' , cancel : 'e-cancel' , copy : 'e-copy' , pdf : 'e-pdfexport' , group : 'e-icon-group' , ungroup : 'e-icon-ungroup' , csv : 'e-csvexport' , excel : 'e-excelexport' , fPage : 'e-icon-first' , nPage : 'e-icon-next' , lPage : 'e-icon-last' , pPage : 'e-icon-prev' , ascending : 'e-icon-ascending' , descending : 'e-icon-descending' , groupHeader : 'e-groupdroparea' , touchPop : 'e-gridpopup' } ; export interface CMenuClassList { header : string ; content : string ; edit : string ; batchEdit : string ; editIcon : string ; pager : string ; cancel : string ; save : string ; delete : string ; copy : string ; pdf : string ; group : string ; ungroup : string ; csv : string ; excel : string ; fPage : string ; lPage : string ; nPage : string ; pPage : string ; ascending : string ; descending : string ; groupHeader : string ; touchPop : string ; } /**
 * The `ContextMenu` module is used to handle context menu actions.
 */ export class ContextMenu implements IAction { private element : HTMLUListElement ; public contextMenu : Menu ; private defaultItems : { [ key ] : ContextMenuItemModel ; } = { } ; private disableItems : string [ ] = [ ] ; private hiddenItems : string [ ] = [ ] ; private gridID : string ; private parent : IGrid ; private serviceLocator : ServiceLocator ; private l10n : L10n ; private localeText : { [ key ] : string ; } = this . setLocaleKey ( ) ; private targetColumn : Column ; private eventArgs : Event ; public isOpen : boolean ; public row : HTMLTableRowElement ; public cell : HTMLTableCellElement ; private keyPressHandlerFunction : Function ; constructor ( parent ? , serviceLocator ? ) { this . parent = parent ; this . gridID = parent . element . id ; this . serviceLocator = serviceLocator ; this . addEventListener ( ) ; } hidden public addEventListener ( ) { if ( this . parent . isDestroyed ) { return ; } this . parent . on ( events . uiUpdate , this . enableAfterRenderMenu , this ) ; this . parent . on ( events . initialLoad , this . render , this ) ; } hidden public removeEventListener ( ) { if ( this . parent . isDestroyed ) { return ; } this . parent . off ( events . initialLoad , this . render ) ; this . parent . off ( events . uiUpdate , this . enableAfterRenderMenu ) ; EventHandler . remove ( this . element , 'keydown' , this . keyDownHandler . bind ( this ) ) ; } private keyDownHandler ( e ) { if ( e . code === 'Tab' || e . which === 9 ) { this . contextMenu . close ( ) ; } } private render ( ) { this . parent . element . classList . add ( 'e-noselect' ) ; this . l10n = this . serviceLocator . getService < L10n > ( 'localization' ) ; this . element = this . parent . createElement ( 'ul' , { id : this . gridID + '_cmenu' } ) as HTMLUListElement ; EventHandler . add ( this . element , 'keydown' , this . keyDownHandler . bind ( this ) ) ; this . parent . element . appendChild ( this . element ) ; let target = '#' + this . gridID ; this . contextMenu = new Menu ( { items : this . getMenuItems ( ) , enableRtl : this . parent . enableRtl , enablePersistence : this . parent . enablePersistence , locale : this . parent . locale , target : target , select : this . contextMenuItemClick . bind ( this ) , beforeOpen : this . contextMenuBeforeOpen . bind ( this ) , onOpen : this . contextMenuOpen . bind ( this ) , onClose : this . contextMenuOnClose . bind ( this ) , cssClass : 'e-grid-menu' } ) ; this . contextMenu . appendTo ( this . element ) ; } private enableAfterRenderMenu ( e ) { if ( e . module === this . getModuleName ( ) && e . enable ) { if ( this . contextMenu ) { this . contextMenu . destroy ( ) ; remove ( this . element ) ; this . parent . element . classList . remove ( 'e-noselect' ) ; } this . render ( ) ; } } private getMenuItems ( ) { let menuItems = [ ] ; let exportItems = [ ] ; for ( let item of this . parent . contextMenuItems ) { if ( typeof item === 'string' && this . getDefaultItems ( ) . indexOf ( item ) !== - 1 ) { if ( item . toLocaleLowerCase ( ) . indexOf ( 'export' ) !== - 1 ) { exportItems . push ( this . buildDefaultItems ( item ) ) ; } else { menuItems . push ( this . buildDefaultItems ( item ) ) ; } } else if ( typeof item !== 'string' ) { menuItems . push ( item ) ; } } if ( exportItems . length > 0 ) { let exportGroup = this . buildDefaultItems ( 'export' ) ; exportGroup . items = exportItems ; menuItems . push ( exportGroup ) ; } return menuItems ; } private getLastPage ( ) { let totalpage = Math . floor ( this . parent . pageSettings . totalRecordsCount / this . parent . pageSettings . pageSize ) ; if ( this . parent . pageSettings . totalRecordsCount % this . parent . pageSettings . pageSize ) { totalpage += 1 ; } return totalpage ; } private contextMenuOpen ( ) { this . isOpen = true ; } private contextMenuItemClick ( args ) { let item = this . getKeyFromId ( args . item . id ) ; switch ( item ) { case 'AutoFitAll' : this . parent . autoFitColumns ( [ ] ) ; break ; case 'AutoFit' : this . parent . autoFitColumns ( this . targetColumn . field ) ; break ; case 'Group' : this . parent . groupColumn ( this . targetColumn . field ) ; break ; case 'Ungroup' : this . parent . ungroupColumn ( this . targetColumn . field ) ; break ; case 'Edit' : if ( this . parent . editSettings . mode === 'Batch' ) { if ( this . row && this . cell && ! isNaN ( parseInt ( this . cell . getAttribute ( 'aria-colindex' ) , 10 ) ) ) { this . parent . editModule . editCell ( parseInt ( this . row . getAttribute ( 'aria-rowindex' ) , 10 ) , ( this . parent . getColumns ( ) [ parseInt ( this . cell . getAttribute ( 'aria-colindex' ) , 10 ) ] as Column ) . field ) ; } } else { this . parent . editModule . endEdit ( ) ; this . parent . editModule . startEdit ( this . row ) ; } break ; case 'Delete' : if ( this . parent . editSettings . mode !== 'Batch' ) { this . parent . editModule . endEdit ( ) ; } if ( this . parent . getSelectedRecords ( ) . length === 1 ) { this . parent . editModule . deleteRow ( this . row ) ; } else { this . parent . deleteRecord ( ) ; } break ; case 'Save' : this . parent . editModule . endEdit ( ) ; break ; case 'Cancel' : this . parent . editModule . closeEdit ( ) ; break ; case 'Copy' : this . parent . copy ( ) ; break ; case 'PdfExport' : this . parent . pdfExport ( ) ; break ; case 'ExcelExport' : this . parent . excelExport ( ) ; break ; case 'CsvExport' : this . parent . csvExport ( ) ; break ; case 'SortAscending' : this . isOpen = false ; this . parent . sortColumn ( this . targetColumn . field , 'Ascending' ) ; break ; case 'SortDescending' : this . isOpen = false ; this . parent . sortColumn ( this . targetColumn . field , 'Descending' ) ; break ; case 'FirstPage' : this . parent . goToPage ( 1 ) ; break ; case 'PrevPage' : this . parent . goToPage ( this . parent . pageSettings . currentPage - 1 ) ; break ; case 'LastPage' : this . parent . goToPage ( this . getLastPage ( ) ) ; break ; case 'NextPage' : this . parent . goToPage ( this . parent . pageSettings . currentPage + 1 ) ; break ; } args . column = this . targetColumn ; this . parent . trigger ( events . contextMenuClick , args ) ; } private contextMenuOnClose ( args ) { let parent = 'parentObj' ; if ( args . items . length > 0 && args . items [ 0 ] [ parent ] instanceof Menu ) { this . updateItemStatus ( ) ; } } private getLocaleText ( item ) { return this . l10n . getConstant ( this . localeText [ item ] ) ; } private updateItemStatus ( ) { this . contextMenu . showItems ( this . hiddenItems ) ; this . contextMenu . enableItems ( this . disableItems ) ; this . hiddenItems = [ ] ; this . disableItems = [ ] ; this . isOpen = false ; } private contextMenuBeforeOpen ( args ) { let changedRecords = 'changedRecords' ; let addedRecords = 'addedRecords' ; let deletedRecords = 'deletedRecords' ; let closestGrid = closest ( args . event . target as Element , '.e-grid' ) ; if ( args . event && closestGrid && closestGrid !== this . parent . element ) { args . cancel = true ; } else if ( args . event && ( closest ( args . event . target as Element , '.' + menuClass . groupHeader ) || closest ( args . event . target as Element , '.' + menuClass . touchPop ) || closest ( args . event . target as Element , '.e-summarycell' ) || closest ( args . event . target as Element , '.e-groupcaption' ) || closest ( args . event . target as Element , '.e-filterbarcell' ) ) ) { args . cancel = true ; } else { this . targetColumn = this . getColumn ( args . event ) ; this . selectRow ( args . event , this . parent . selectionSettings . type !== 'Multiple' ) ; let hiddenSepItems = [ ] ; let showSepItems = [ ] ; for ( let item of args . items ) { let key = this . getKeyFromId ( item . id ) ; let dItem = this . defaultItems [ key ] ; if ( this . getDefaultItems ( ) . indexOf ( key ) !== - 1 ) { if ( this . ensureDisabledStatus ( key ) ) { this . disableItems . push ( item . text ) ; } if ( args . event && ( this . ensureTarget ( args . event . target as HTMLElement , menuClass . edit ) || this . ensureTarget ( args . event . target as HTMLElement , menuClass . batchEdit ) ) ) { if ( key !== 'Save' && key !== 'Cancel' ) { this . hiddenItems . push ( item . text ) ; } } else if ( this . parent . editSettings . mode === 'Batch' && ( ( closest ( args . event . target as Element , '.e-gridform' ) ) || this . parent . editModule . getBatchChanges ( ) [ changedRecords ] . length || this . parent . editModule . getBatchChanges ( ) [ addedRecords ] . length || this . parent . editModule . getBatchChanges ( ) [ deletedRecords ] . length ) && ( key === 'Save' || key === 'Cancel' ) ) { continue ; } else if ( isNullOrUndefined ( args . parentItem ) && args . event && ! this . ensureTarget ( args . event . target as HTMLElement , dItem . target ) ) { this . hiddenItems . push ( item . text ) ; } } else if ( ( item as ContextMenuItemModel ) . target && args . event && ! this . ensureTarget ( args . event . target as HTMLElement , ( item as ContextMenuItemModel ) . target ) ) { if ( item . separator ) { hiddenSepItems . push ( item . id ) ; } else { this . hiddenItems . push ( item . text ) ; } } else if ( this . ensureTarget ( args . event . target as HTMLElement , ( item as ContextMenuItemModel ) . target ) && item . separator ) { showSepItems . push ( item . id ) ; } } if ( showSepItems . length > 0 ) { this . contextMenu . showItems ( showSepItems , true ) ; } this . contextMenu . enableItems ( this . disableItems , false ) ; this . contextMenu . hideItems ( this . hiddenItems ) ; if ( hiddenSepItems . length > 0 ) { this . contextMenu . hideItems ( hiddenSepItems , true ) ; } this . eventArgs = args . event ; args . column = this . targetColumn ; this . parent . trigger ( events . contextMenuOpen , args ) ; if ( this . hiddenItems . length === args . items . length ) { this . updateItemStatus ( ) ; args . cancel = true ; } } } private ensureTarget ( targetElement , selector ) { let target = targetElement ; if ( this . ensureFrozenHeader ( targetElement ) && ( selector === menuClass . header || selector === menuClass . content ) ) { target = closest ( targetElement as Element , selector === menuClass . header ? 'thead' : 'tbody' ) ; } else if ( selector === menuClass . content || selector === menuClass . header ) { target = parentsUntil ( closest ( targetElement as Element , '.e-table' ) , selector . substr ( 1 , selector . length ) ) ; } else { target = closest ( targetElement as Element , selector ) ; } return target && parentsUntil ( target , 'e-grid' ) === this . parent . element ; } private ensureFrozenHeader ( targetElement ) { return ( this . parent . getFrozenColumns ( ) || this . parent . frozenRows ) && closest ( targetElement , menuClass . header ) ? true : false ; } private ensureDisabledStatus ( item ) { let status = false ; switch ( item ) { case 'AutoFitAll' : case 'AutoFit' : status = ! ( this . parent . ensureModuleInjected ( Resize ) && ! this . parent . isEdit ) ; break ; case 'Group' : if ( ! this . parent . allowGrouping || ( this . parent . ensureModuleInjected ( Group ) && this . targetColumn && this . parent . groupSettings . columns . indexOf ( this . targetColumn . field ) >= 0 ) ) { status = true ; } break ; case 'Ungroup' : if ( ! this . parent . allowGrouping || ! this . parent . ensureModuleInjected ( Group ) || ( this . parent . ensureModuleInjected ( Group ) && this . targetColumn && this . parent . groupSettings . columns . indexOf ( this . targetColumn . field ) < 0 ) ) { status = true ; } break ; case 'Edit' : case 'Delete' : case 'Save' : case 'Cancel' : if ( ! this . parent . editModule || ( this . parent . getDataRows ( ) . length === 0 ) ) { status = true ; } break ; case 'Copy' : if ( this . parent . getSelectedRowIndexes ( ) . length === 0 || this . parent . getCurrentViewRecords ( ) . length === 0 ) { status = true ; } break ; case 'export' : if ( ( ! this . parent . allowExcelExport || ! this . parent . excelExport ) || ! this . parent . ensureModuleInjected ( PdfExport ) && ! this . parent . ensureModuleInjected ( ExcelExport ) ) { status = true ; } break ; case 'PdfExport' : if ( ! ( this . parent . allowPdfExport ) || ! this . parent . ensureModuleInjected ( PdfExport ) ) { status = true ; } break ; case 'ExcelExport' : case 'CsvExport' : if ( ! ( this . parent . allowExcelExport ) || ! this . parent . ensureModuleInjected ( ExcelExport ) ) { status = true ; } break ; case 'SortAscending' : case 'SortDescending' : if ( ( ! this . parent . allowSorting ) || ! this . parent . ensureModuleInjected ( Sort ) ) { status = true ; } else if ( this . parent . ensureModuleInjected ( Sort ) && this . parent . sortSettings . columns . length > 0 && this . targetColumn ) { this . parent . sortSettings . columns . forEach ( ( element ) => { if ( element . field === this . targetColumn . field && element . direction . toLowerCase ( ) === item . toLowerCase ( ) . replace ( 'sort' , '' ) . toLocaleLowerCase ( ) ) { status = true ; } } ) ; } break ; case 'FirstPage' : case 'PrevPage' : if ( ! this . parent . allowPaging || ! this . parent . ensureModuleInjected ( Page ) || this . parent . getCurrentViewRecords ( ) . length === 0 || ( this . parent . ensureModuleInjected ( Page ) && this . parent . pageSettings . currentPage === 1 ) ) { status = true ; } break ; case 'LastPage' : case 'NextPage' : if ( ! this . parent . allowPaging || ! this . parent . ensureModuleInjected ( Page ) || this . parent . getCurrentViewRecords ( ) . length === 0 || ( this . parent . ensureModuleInjected ( Page ) && this . parent . pageSettings . currentPage === this . getLastPage ( ) ) ) { status = true ; } break ; } return status ; } return { Element } public getContextMenu ( ) { return this . element ; } method return { void } hidden public destroy ( ) { let gridElement = this . parent . element ; if ( ! gridElement || ( ! gridElement . querySelector ( '.e-gridheader' ) && ! gridElement . querySelector ( '.e-gridcontent' ) ) ) { return ; } this . contextMenu . destroy ( ) ; remove ( this . element ) ; this . removeEventListener ( ) ; this . parent . element . classList . remove ( 'e-noselect' ) ; } private getModuleName ( ) { return 'contextMenu' ; } private generateID ( item ) { return this . gridID + '_cmenu_' + item ; } private getKeyFromId ( id ) { return id . replace ( this . gridID + '_cmenu_' , '' ) ; } private buildDefaultItems ( item ) { let menuItem ; switch ( item ) { case 'AutoFitAll' : case 'AutoFit' : menuItem = { target : menuClass . header } ; break ; case 'Group' : menuItem = { target : menuClass . header , iconCss : menuClass . group } ; break ; case 'Ungroup' : menuItem = { target : menuClass . header , iconCss : menuClass . ungroup } ; break ; case 'Edit' : menuItem = { target : menuClass . content , iconCss : menuClass . editIcon } ; break ; case 'Delete' : menuItem = { target : menuClass . content , iconCss : menuClass . delete } ; break ; case 'Save' : menuItem = { target : menuClass . edit , iconCss : menuClass . save } ; break ; case 'Cancel' : menuItem = { target : menuClass . edit , iconCss : menuClass . cancel } ; break ; case 'Copy' : menuItem = { target : menuClass . content , iconCss : menuClass . copy } ; break ; case 'export' : menuItem = { target : menuClass . content } ; break ; case 'PdfExport' : menuItem = { target : menuClass . content , iconCss : menuClass . pdf } ; break ; case 'ExcelExport' : menuItem = { target : menuClass . content , iconCss : menuClass . excel } ; break ; case 'CsvExport' : menuItem = { target : menuClass . content , iconCss : menuClass . csv } ; break ; case 'SortAscending' : menuItem = { target : menuClass . header , iconCss : menuClass . ascending } ; break ; case 'SortDescending' : menuItem = { target : menuClass . header , iconCss : menuClass . descending } ; break ; case 'FirstPage' : menuItem = { target : menuClass . pager , iconCss : menuClass . fPage } ; break ; case 'PrevPage' : menuItem = { target : menuClass . pager , iconCss : menuClass . pPage } ; break ; case 'LastPage' : menuItem = { target : menuClass . pager , iconCss : menuClass . lPage } ; break ; case 'NextPage' : menuItem = { target : menuClass . pager , iconCss : menuClass . nPage } ; break ; } this . defaultItems [ item ] = { text : this . getLocaleText ( item ) , id : this . generateID ( item ) , target : menuItem . target , iconCss : menuItem . iconCss ? 'e-icons ' + menuItem . iconCss : '' } ; return this . defaultItems [ item ] ; } private getDefaultItems ( ) { return [ 'AutoFitAll' , 'AutoFit' , 'Group' , 'Ungroup' , 'Edit' , 'Delete' , 'Save' , 'Cancel' , 'Copy' , 'export' , 'PdfExport' , 'ExcelExport' , 'CsvExport' , 'SortAscending' , 'SortDescending' , 'FirstPage' , 'PrevPage' , 'LastPage' , 'NextPage' ] ; } private setLocaleKey ( ) { return { 'AutoFitAll' : 'autoFitAll' , 'AutoFit' : 'autoFit' , 'Copy' : 'Copy' , 'Group' : 'Group' , 'Ungroup' : 'Ungroup' , 'Edit' : 'EditRecord' , 'Delete' : 'DeleteRecord' , 'Save' : 'Save' , 'Cancel' : 'CancelButton' , 'PdfExport' : 'Pdfexport' , 'ExcelExport' : 'Excelexport' , 'CsvExport' : 'Csvexport' , 'export' : 'Export' , 'SortAscending' : 'SortAscending' , 'SortDescending' : 'SortDescending' , 'FirstPage' : 'FirstPage' , 'LastPage' : 'LastPage' , 'PrevPage' : 'PreviousPage' , 'NextPage' : 'NextPage' } ; } private getColumn ( e ) { let cell = < HTMLElement > closest ( < HTMLElement > e . target , 'th.e-headercell' ) ; if ( cell ) { let uid = cell . querySelector ( '.e-headercelldiv' ) . getAttribute ( 'e-mappinguid' ) ; return this . parent . getColumnByUid ( uid ) ; } return null ; } private selectRow ( e , isSelectable ) { this . cell = ( < HTMLElement > e . target ) as HTMLTableCellElement ; this . row = < HTMLElement > closest ( < HTMLElement > e . target , 'tr.e-row' ) as HTMLTableRowElement || this . row ; if ( this . row && isSelectable && ! parentsUntil ( < HTMLElement > e . target , 'e-gridpager' ) ) { this . parent . selectRow ( parseInt ( this . row . getAttribute ( 'aria-rowindex' ) , 10 ) ) ; } } }