import { Component , EventEmitter , Input , OnChanges , OnDestroy , OnInit , Output , Renderer2 , SimpleChanges , Type } from '@angular/core' ; import { GtConfig , GtConfigField , GtConfigSetting , GtEvent , GtExpandedRow , GtInformation , GtOptions , GtRenderField , GtRow , GtRowMeta , GtTexts } from '..' ; import { GtMetaPipe } from '../pipes/gt-meta.pipe' ; @ Component ( { selector : 'generic-table' , templateUrl : './generic-table.component.html' } ) export class GenericTableComponent < R extends GtRow , C extends GtExpandedRow < R > > implements OnInit , OnChanges , OnDestroy { get gtRowComponent ( ) : Type < C > { return this . _gtRowComponent ; } get hasEdits ( ) : boolean { return Object . keys ( this . editedRows ) . length > 0 ; } get gtOptions ( ) : GtOptions { return this . _gtOptions ; } get gtTotals ( ) : any { return this . _gtTotals ; } get gtFields ( ) : GtConfigField < R , any > [ ] { return this . _gtFields ; } get gtSettings ( ) : GtConfigSetting [ ] { return this . _gtSettings ; } get gtData ( ) : Array < any > { return this . _gtData ; } @ Input ( ) set gtOptions ( value ) { this . _gtOptions = value ; if ( this . gtOptions . numberOfRows && this . gtInfo . recordLength !== this . gtOptions . numberOfRows ) { this . gtInfo . recordLength = this . gtOptions . numberOfRows ; this . redraw ( ) ; } this . _gtOptions = < GtOptions > this . extend ( this . gtDefaultOptions , this . _gtOptions ) ; } @ Input ( ) set gtTotals ( value ) { this . _gtTotals = value ; } @ Input ( ) set gtFields ( value ) { this . _gtFields = value ; const COLUMNS_WITH_CLASS_NAMES = this . _gtFields . map ( column => column ) . filter ( column => column . classNames ) ; if ( COLUMNS_WITH_CLASS_NAMES . length > 0 ) { console . warn ( 'Field setting "classNames" have been deprecated in favor for "columnClass" and will be removed in the future, please update field settings for column with object key: ' + COLUMNS_WITH_CLASS_NAMES [ 0 ] . objectKey ) ; } } @ Input ( ) set gtSettings ( value ) { this . _gtSettings = value ; for ( let i = 0 ; i < this . _gtSettings . length ; i ++ ) { this . _gtSettings [ i ] . sortEnabled = this . _gtSettings [ i ] . sortEnabled !== false ? ( this . _gtSettings [ i ] . sortEnabled = ! ( this . _gtSettings [ i ] . sort && this . _gtSettings [ i ] . sort . indexOf ( 'disable' ) !== - 1 ) ) : false ; if ( typeof this . _gtSettings [ i ] . sort === 'undefined' ) { this . _gtSettings [ i ] . sort = 'enable' ; } if ( typeof this . _gtSettings [ i ] . columnOrder === 'undefined' && this . _gtSettings [ i ] . enabled !== false ) { this . _gtSettings [ i ] . columnOrder = this . _gtSettings [ i - 1 ] ? this . _gtSettings [ i - 1 ] . columnOrder + 1 : 0 ; } if ( typeof this . _gtSettings [ i ] . lockSettings === 'undefined' ) { this . _gtSettings [ i ] . lockSettings = this . _gtSettings [ i ] . enabled === false || false ; } } this . restructureSorting ( ) ; } @ Input ( ) set gtData ( initialData ) { const data = this . _gtOptions . mutateData ? [ ... initialData ] : this . cloneDeep ( initialData ) ; if ( this . gtOptions . lazyLoad && this . gtInfo ) { this . gtMetaPipe . transform ( data , this . gtOptions . rowIndex , this . gtInfo . pageCurrent - 1 , this . gtInfo . recordLength ) ; if ( this . lazyAllSelected ) { const UNIQUE_ROWS = this . selectedRows . map ( row => row . $$gtRowId ) ; data . map ( row => { if ( UNIQUE_ROWS . indexOf ( row . $$gtRowId ) === - 1 ) { this . selectedRows . push ( row ) ; } } ) ; this . _updateMetaInfo ( this . selectedRows , 'isSelected' , true ) ; } } else { this . gtMetaPipe . transform ( data , this . gtOptions . rowIndex ) ; } if ( this . gtOptions . rowSelectionInitialState ) { data . map ( row => { const selected = typeof this . gtOptions . rowSelectionInitialState === 'function' ? this . gtOptions . rowSelectionInitialState ( row ) : this . gtOptions . rowSelectionInitialState ; if ( selected ) { if ( typeof this . metaInfo [ row . $$gtRowId ] === 'undefined' ) { this . metaInfo [ row . $$gtRowId ] = { isSelected : true } ; } else { this . metaInfo [ row . $$gtRowId ] . isSelected = true ; } this . selectedRows . push ( row ) ; } } ) ; } if ( this . gtOptions . rowExpandInitialState && this . gtOptions . rowExpandInitialComponent ) { data . map ( row => { const expanded = typeof this . gtOptions . rowExpandInitialState === 'function' ? this . gtOptions . rowExpandInitialState ( row ) : this . gtOptions . rowExpandInitialState ; this . expandedRow = this . gtOptions . rowExpandInitialComponent ; if ( expanded ) { if ( typeof this . metaInfo [ row . $$gtRowId ] === 'undefined' ) { this . metaInfo [ row . $$gtRowId ] = { isOpen : true } ; } else { this . metaInfo [ row . $$gtRowId ] . isOpen = true ; } } } ) ; } this . _gtData = data ; } @ Input ( ) set gtRowComponent ( value ) { console . warn ( 'GtRowComponent has been deprecated and support will be removed in a future release, see https://github.com/hjalmers/angular-generic-table/issues/34' ) ; this . _gtRowComponent = value ; } public columnWidth : Object = { } ; public configObject : GtConfig < R > ; public sortOrder : Array < any > = [ ] ; public metaInfo : { [ gtRowId ] : GtRowMeta ; } = { } ; public selectedRows : Array < GtRow > = [ ] ; public openRows : Array < GtRow > = [ ] ; private _gtSettings : GtConfigSetting [ ] = [ ] ; private _gtFields : GtConfigField < R , any > [ ] = [ ] ; private _gtData : Array < any > ; private _gtTotals : any ; private _gtRowComponent : Type < C > ; public expandedRow : { component : Type < C > ; data ? : Array < any > ; } ; public gtDefaultTexts : GtTexts = { loading : 'Loading...' , noData : 'No data' , noMatchingData : 'No data matching results found' , noVisibleColumnsHeading : 'No visible columns' , noVisibleColumns : 'Please select at least one column to be visible.' , tableInfo : 'Showing #recordFrom to #recordTo of #recordsAfterSearch entries.' , tableInfoAfterSearch : 'Showing  #recordFrom to #recordTo of #recordsAfterSearch entries (filtered from a total of #recordsAll entries).' , csvDownload : 'download' , sortLabel : 'Sort:' , paginateNext : 'Next page' , paginatePrevious : 'Previous page' , inlineEditEdited : 'Press enter to save' } ; @ Input ( ) gtTexts : GtTexts = this . gtDefaultTexts ; @ Input ( ) gtClasses : string ; @ Output ( ) gtEvent : EventEmitter < GtEvent > = new EventEmitter ( ) ; public gtDefaultOptions : GtOptions = { csvDelimiter : ';' , stack : false , lazyLoad : false , cache : false , debounceTime : 200 , highlightSearch : false , rowSelection : false , rowSelectionAllowMultiple : true , rowExpandAllowMultiple : true , numberOfRows : 10 , reportColumnWidth : false , allowUnsorted : true , mutateData : true } ; private _gtOptions : GtOptions = this . gtDefaultOptions ; public store : Array < any > = [ ] ; public loading = true ; private debounceTimer : void = null ; public loadingProperty : string ; public lazyAllSelected = false ; @ Input ( ) gtInfo : GtInformation = { pageCurrent : 1 , pageTotal : 0 , recordFrom : 0 , recordTo : 0 , recordLength : this . gtOptions . numberOfRows , recordsAll : 0 , recordsAfterFilter : 0 , recordsAfterSearch : 0 } ; public refreshPipe = false ; public refreshTotals = false ; public refreshSorting = false ; public refreshFilter = false ; public refreshPageArray = false ; private globalInlineEditListener : Function ; public editedRows : { [ key ] : { changes : { [ key ] : GtRenderField < GtRow , any > ; } ; row : GtRow ; } ; } = { } ; public data : { exportData : Array < any > ; } = { exportData : [ ] } ; constructor ( private renderer , private gtMetaPipe ) { this . gtEvent . subscribe ( ( $event ) => { if ( $event . name === 'gt-info' ) { this . updateRecordRange ( ) ; } if ( $event . name === 'gt-row-updated' ) { this . updateTotals ( ) ; } } ) ; } param objectKey param event public gtSort = function ( objectKey , event ) { this . inlineEditCancel ( ) ; for ( let i = 0 ; i < this . _gtSettings . length ; i ++ ) { if ( this . _gtSettings [ i ] . objectKey === objectKey ) { if ( this . _gtSettings [ i ] . sort && this . _gtSettings [ i ] . sort . indexOf ( 'disable' ) !== - 1 ) { return ; } else if ( typeof this . _gtSettings [ i ] . sort === 'undefined' ) { this . _gtSettings [ i ] . sort = 'enable' ; } } } const ctrlKey = event . metaKey || event . ctrlKey ; const sort = this . sortOrder . slice ( 0 ) ; let match = - 1 ; let matchDesc = - 1 ; let pos = - 1 ; for ( let i = 0 ; i < sort . length ; i ++ ) { const hit = sort [ i ] . indexOf ( objectKey ) ; if ( hit !== - 1 ) { match = this . sortOrder . indexOf ( objectKey ) ; matchDesc = match === - 1 ? this . sortOrder . indexOf ( '-' + objectKey ) : match ; pos = Math . max ( match , matchDesc ) ; } } if ( ctrlKey ) { if ( this . sortOrder [ this . sortOrder . length - 1 ] === '$$gtInitialRowIndex' ) { this . sortOrder . pop ( ) ; } switch ( pos ) { case - 1 : this . sortOrder . push ( objectKey ) ; break ; default : if ( match !== - 1 ) { this . sortOrder [ pos ] = '-' + objectKey ; } else if ( this . sortOrder . length > 1 ) { if ( ctrlKey ) { this . sortOrder [ pos ] = objectKey ; } else { this . sortOrder . splice ( pos , 1 ) ; } } else if ( this . sortOrder . length === 1 ) { this . sortOrder [ pos ] = objectKey ; } break ; } } else { switch ( pos ) { case - 1 : this . sortOrder = [ objectKey ] ; break ; default : this . sortOrder = match !== - 1 ? [ '-' + objectKey ] : ctrlKey || ! this . gtOptions . allowUnsorted ? [ objectKey ] : [ ] ; break ; } } for ( let i = 0 ; i < this . _gtSettings . length ; i ++ ) { if ( this . _gtSettings [ i ] . objectKey === objectKey ) { switch ( this . _gtSettings [ i ] . sort ) { case 'asc' : this . _gtSettings [ i ] . sort = 'desc' ; break ; case 'desc' : this . _gtSettings [ i ] . sort = ( this . sortOrder . length === 1 && sort . length < 2 ) || ctrlKey || ! this . gtOptions . allowUnsorted ? 'asc' : 'enable' ; break ; case 'enable' : this . _gtSettings [ i ] . sort = 'asc' ; break ; } this . _gtSettings [ i ] . sortOrder = this . _gtSettings [ i ] . sort === 'enable' ? this . _gtSettings . length - 1 : this . sortOrder . indexOf ( objectKey ) === - 1 ? this . sortOrder . indexOf ( '-' + objectKey ) : this . sortOrder . indexOf ( objectKey ) ; } else if ( this . _gtSettings [ i ] . sort && this . _gtSettings [ i ] . sort . indexOf ( 'disable' ) === - 1 && this . sortOrder . indexOf ( this . _gtSettings [ i ] . objectKey ) === - 1 && this . sortOrder . indexOf ( '-' + this . _gtSettings [ i ] . objectKey ) === - 1 ) { this . _gtSettings [ i ] . sort = 'enable' ; this . _gtSettings [ i ] . sortOrder = this . _gtSettings . length - 1 ; } } this . refreshSorting = ! this . refreshSorting ; this . refreshPageArray = ! this . refreshPageArray ; this . sortOrder . push ( '$$gtInitialRowIndex' ) ; this . gtEvent . emit ( { name : 'gt-sorting-applied' , value : this . sortOrder } ) ; } ; param rowLength param reset public changeRowLength = function ( rowLength , reset ? ) { let lengthValue = isNaN ( parseInt ( rowLength , 10 ) ) ? 0 : parseInt ( rowLength , 10 ) ; let newPosition = 1 ; if ( ! lengthValue && this . gtData ) { lengthValue = this . gtData . length ; } if ( reset !== true && this . _gtOptions . lazyLoad !== true ) { const currentRecord = this . gtInfo . recordLength * ( this . gtInfo . pageCurrent - 1 ) ; const currentPosition = this . _gtData . indexOf ( this . _gtData [ currentRecord ] ) + 1 ; newPosition = Math . ceil ( currentPosition / lengthValue ) ; } this . gtInfo . recordLength = lengthValue ; this . gtInfo . pageCurrent = newPosition ; if ( this . _gtOptions . lazyLoad ) { this . _gtData [ 0 ] = this . loadingContent ( lengthValue ) ; this . store = [ ] ; } this . gtEvent . emit ( { name : 'gt-row-length-changed' , value : lengthValue } ) ; } ; /**
     * Force a redraw of table rows.
     * As the table uses pure pipes, we need to force a redraw if an object in the array is changed to see the changes.
     */ public redraw = function ( $event ? ) { this . refreshSorting = ! this . refreshSorting ; this . refreshPageArray = ! this . refreshPageArray ; this . refreshPipe = ! this . refreshPipe ; } ; /** Update record range. */ private updateRecordRange ( ) { this . gtInfo . recordFrom = this . gtInfo . recordsAfterSearch === 0 ? 0 : ( this . gtInfo . pageCurrent - 1 ) * this . gtInfo . recordLength + 1 ; this . gtInfo . recordTo = this . gtInfo . recordsAfterSearch < this . gtInfo . pageCurrent * this . gtInfo . recordLength ? this . gtInfo . recordsAfterSearch : this . gtInfo . pageCurrent * this . gtInfo . recordLength ; } /** Update totals. */ private updateTotals ( ) { this . refreshTotals = ! this . refreshTotals ; } /** Go to next page. */ public nextPage = function ( ) { const page = this . gtInfo . pageCurrent === this . gtInfo . pageTotal ? this . gtInfo . pageTotal : this . gtInfo . pageCurrent + 1 ; this . goToPage ( page ) ; } ; /** Go to previous page. */ public previousPage = function ( ) { const page = this . gtInfo . pageCurrent === 1 ? 1 : this . gtInfo . pageCurrent - 1 ; this . goToPage ( page ) ; } ; /** Request more data (used when lazy loading) */ private getData = function ( ) { this . gtEvent . emit ( { name : 'gt-page-changed-lazy' , value : { pageCurrent : this . gtInfo . pageCurrent , recordLength : this . gtInfo . recordLength } } ) ; } ; param page public goToPage = function ( page ) { const previousPage = this . gtInfo . pageCurrent ; this . gtInfo . pageCurrent = page ; this . inlineEditCancel ( ) ; if ( this . _gtOptions . lazyLoad ) { if ( this . _gtOptions . cache === false || this . _gtData [ this . gtInfo . pageCurrent - 1 ] . length === 0 ) { this . _gtData [ this . gtInfo . pageCurrent - 1 ] = this . loadingContent ( this . gtInfo . recordLength ) ; this . loading = true ; } if ( this . _gtData [ this . gtInfo . pageCurrent - 1 ] [ 0 ] [ this . loadingProperty ] === this . gtTexts . loading ) { clearTimeout ( this . debounceTimer ) ; this . debounceTimer = setTimeout ( ( ) => { this . getData ( ) ; } , this . _gtOptions . debounceTime ) ; } } if ( previousPage !== page ) { this . gtEvent . emit ( { name : 'gt-page-changed' , value : { pageCurrent : this . gtInfo . pageCurrent , pagePrevious : previousPage , recordLength : this . gtInfo . recordLength } } ) ; } } ; /**
     * Get meta data for row.
     */ public getRowState ( row ) { return typeof this . metaInfo [ row . $$gtRowId ] === 'undefined' ? null : this . metaInfo [ row . $$gtRowId ] ; } param expandedRow public expandAllRows ( expandedRow ) { this . expandedRow = expandedRow ; this . _toggleAllRowProperty ( 'isOpen' , true ) ; } /**
     * Collapse all rows.
     */ public collapseAllRows ( ) { this . _toggleAllRowProperty ( 'isOpen' , false ) ; } /**
     * Select all rows.
     */ public selectAllRows ( ) { this . _toggleAllRowProperty ( 'isSelected' , true ) ; } /**
     * Deselect all rows.
     */ public deselectAllRows ( ) { this . _toggleAllRowProperty ( 'isSelected' , false ) ; } /**
     * Toggle all rows.
     */ public toggleAllRows ( ) { if ( this . _gtOptions . lazyLoad ) { if ( ! this . lazyAllSelected || this . selectedRows . length === 0 ) { this . selectAllRows ( ) ; this . lazyAllSelected = true ; } else { this . deselectAllRows ( ) ; this . lazyAllSelected = false ; } } else { if ( this . selectedRows . length !== this . gtData . length ) { this . selectAllRows ( ) ; } else { this . deselectAllRows ( ) ; } } } param row param expandedRow public toggleCollapse ( row , expandedRow ? ) { if ( expandedRow ) { this . expandedRow = expandedRow ; } this . _toggleRowProperty ( row , 'isOpen' ) ; } param row public toggleSelect ( row ) { this . _toggleRowProperty ( row , 'isSelected' ) ; } public rowClick ( row , $event ) { this . gtEvent . emit ( { name : 'gt-row-clicked' , value : { row : row , event : $event } } ) ; } param row param oldValue public updateRow ( row , oldValue ) { this . _toggleRowProperty ( row , 'isUpdated' , oldValue ) ; } param row public removeRow ( row ) { if ( this . isRowSelected ( row ) ) { this . toggleSelect ( row ) ; } const index = this . _gtData . indexOf ( row ) ; this . _gtData . splice ( index , 1 ) ; } param row public isRowSelected ( row ) { return ( this . metaInfo [ row . $$gtRowId ] && this . metaInfo [ row . $$gtRowId ] . isSelected ) ; } param array param property param active param exception private _updateMetaInfo ( array , property , active , exception ? ) { for ( let i = 0 ; i < array . length ; i ++ ) { if ( ! this . metaInfo [ array [ i ] . $$gtRowId ] ) { this . metaInfo [ array [ i ] . $$gtRowId ] = { } ; } if ( exception && array [ i ] . $$gtRowId === exception . $$gtRowId ) { } else { this . metaInfo [ array [ i ] . $$gtRowId ] [ property ] = active ; } } } param target param source returns private _pushLazyRows ( target , source ) { const UNIQUE_ROWS = target . map ( row => row . $$gtRowId ) ; for ( let i = 0 ; i < source . length ; i ++ ) { if ( UNIQUE_ROWS . indexOf ( source [ i ] . $$gtRowId ) === - 1 ) { target . push ( source [ i ] ) ; } } return target ; } param property param active private _toggleAllRowProperty ( property , active ) { let eventName ; let eventValue ; switch ( property ) { case 'isOpen' : if ( this . _gtOptions . rowExpandAllowMultiple === false ) { console . log ( 'feature disabled: enable by setting "rowExpandAllowMultiple = true"' ) ; return ; } if ( active ) { eventName = 'expand-all' ; this . openRows = this . _gtOptions . lazyLoad ? this . _pushLazyRows ( this . openRows , this . _gtData [ this . gtInfo . pageCurrent - 1 ] . slice ( ) ) : this . _gtData . slice ( ) ; this . _updateMetaInfo ( this . openRows , property , active ) ; } else { eventName = 'collapse-all' ; this . _updateMetaInfo ( this . openRows , property , active ) ; this . openRows = [ ] ; } eventValue = { expandedRows : this . openRows , changedRow : 'all' } ; break ; case 'isSelected' : if ( this . _gtOptions . rowSelectionAllowMultiple === false ) { console . log ( 'feature disabled: enable by setting "rowSelectionAllowMultiple = true"' ) ; return ; } if ( active ) { eventName = 'select-all' ; this . selectedRows = this . _gtOptions . lazyLoad ? this . _pushLazyRows ( this . selectedRows , this . _gtData [ this . gtInfo . pageCurrent - 1 ] . slice ( ) ) : this . _gtData . slice ( ) ; this . _updateMetaInfo ( this . selectedRows , property , active ) ; } else { eventName = 'deselect-all' ; this . _updateMetaInfo ( this . selectedRows , property , active ) ; this . selectedRows = [ ] ; } eventValue = { selectedRows : this . selectedRows , changedRow : 'all' } ; break ; } this . gtEvent . emit ( { name : 'gt-row-' + eventName , value : eventValue } ) ; } param row param property param propertyValues private _toggleRowProperty ( row , property , propertyValues ? ) { let eventName ; let eventValue ; if ( typeof row . $$gtRowId !== 'undefined' ) { if ( ! this . metaInfo [ row . $$gtRowId ] ) { this . metaInfo [ row . $$gtRowId ] = { } ; } switch ( property ) { case 'isOpen' : const opened = this . metaInfo [ row . $$gtRowId ] [ property ] ; if ( this . _gtOptions . rowExpandAllowMultiple === false ) { this . _updateMetaInfo ( this . openRows , property , false , row ) ; this . openRows = [ ] ; } if ( ! opened ) { eventName = 'expand' ; this . openRows . push ( row ) ; } else { eventName = 'collapse' ; for ( let i = 0 ; i < this . openRows . length ; i ++ ) { if ( this . openRows [ i ] . $$gtRowId === row . $$gtRowId ) { this . openRows . splice ( i , 1 ) ; break ; } } } eventValue = { expandedRows : this . openRows , changedRow : row } ; break ; case 'isSelected' : const selected = this . metaInfo [ row . $$gtRowId ] [ property ] ; if ( this . _gtOptions . rowSelectionAllowMultiple === false ) { this . _updateMetaInfo ( this . selectedRows , property , false , row ) ; this . selectedRows = [ ] ; } if ( ! selected ) { eventName = 'select' ; this . selectedRows . push ( row ) ; } else { if ( this . gtOptions . lazyLoad && this . lazyAllSelected ) { this . lazyAllSelected = false ; } eventName = 'deselect' ; for ( let i = 0 ; i < this . selectedRows . length ; i ++ ) { if ( this . selectedRows [ i ] . $$gtRowId === row . $$gtRowId ) { this . selectedRows . splice ( i , 1 ) ; break ; } } } eventValue = { selectedRows : this . selectedRows , changedRow : row } ; break ; case 'isUpdated' : eventName = 'updated' ; const oldValue = propertyValues ; if ( typeof this . metaInfo [ row . $$gtRowId ] [ property ] === 'undefined' ) { this . metaInfo [ row . $$gtRowId ] [ property ] = { originalValue : oldValue , oldValue : oldValue , newValue : row } ; } else { this . metaInfo [ row . $$gtRowId ] [ property ] . oldValue = oldValue ; this . metaInfo [ row . $$gtRowId ] [ property ] . newValue = row ; } eventValue = this . metaInfo [ row . $$gtRowId ] [ property ] ; this . redraw ( ) ; this . inlineEditCancel ( row ) ; break ; } this . gtEvent . emit ( { name : 'gt-row-' + eventName , value : eventValue } ) ; if ( property !== 'isUpdated' ) { this . metaInfo [ row . $$gtRowId ] [ property ] = ! this . metaInfo [ row . $$gtRowId ] [ property ] ; } } } param $event param row param column public gtUpdateColumn ( $event , row , column ) { this . _editRow ( row , column ) ; } param row param column public gtDropdownSelect ( row , column ) { const oldValue = { ... row } ; row [ column . objectKey ] = column . renderValue ; this . updateRow ( row , oldValue ) ; } private _editRow ( row , column ) { const OBJECT_KEY = column . objectKey ; column . edited = row [ column . objectKey ] !== column . renderValue ; if ( ! this . editedRows [ row . $$gtRowId ] ) { this . editedRows [ row . $$gtRowId ] = { changes : { } , row : row } ; } if ( column . edited ) { this . editedRows [ row . $$gtRowId ] . changes [ OBJECT_KEY ] = column ; } else { delete this . editedRows [ row . $$gtRowId ] . changes [ OBJECT_KEY ] ; const CHANGED_COLUMNS = Object . keys ( this . editedRows [ row . $$gtRowId ] . changes ) . length ; if ( CHANGED_COLUMNS === 0 ) { delete this . editedRows [ row . $$gtRowId ] ; } } if ( ! this . globalInlineEditListener ) { this . _listenForKeydownEvent ( ) ; } } /**
     * Listen for key down event - listen for key down event during inline edit.
     */ private _listenForKeydownEvent ( ) { this . globalInlineEditListener = this . renderer . listen ( 'document' , 'keydown' , $event => { switch ( $event . key ) { case 'Enter' : this . inlineEditUpdate ( ) ; break ; case 'Escape' : this . inlineEditCancel ( ) ; break ; } } ) ; } /**
     * Inline edit update - accept changes and update row values.
     */ public inlineEditUpdate ( ) { Object . keys ( this . editedRows ) . map ( key => { const ROW = this . editedRows [ key ] . row ; const CHANGES = this . editedRows [ key ] . changes ; Object . keys ( CHANGES ) . map ( objectKey => { const oldValue = { ... ROW } ; ROW [ objectKey ] = CHANGES [ objectKey ] . renderValue ; this . updateRow ( ROW , oldValue ) ; CHANGES [ objectKey ] . edited = false ; } ) ; } ) ; this . editedRows = { } ; this . _stopListeningForKeydownEvent ( ) ; } /**
     * Inline edit cancel - cancel and reset inline edits.
     */ public inlineEditCancel ( row ? ) { if ( row ) { delete this . editedRows [ row . $$gtRowId ] ; this . _stopListeningForKeydownEvent ( ) ; return ; } Object . keys ( this . editedRows ) . map ( key => { const ROW = this . editedRows [ key ] . row ; const CHANGES = this . editedRows [ key ] . changes ; Object . keys ( CHANGES ) . map ( objectKey => { CHANGES [ objectKey ] . renderValue = ROW [ objectKey ] ; CHANGES [ objectKey ] . edited = false ; } ) ; } ) ; this . editedRows = { } ; this . _stopListeningForKeydownEvent ( ) ; } /**
     * Stop listening for key down event - stop listening for key down events passed during inline edit.
     */ private _stopListeningForKeydownEvent ( ) { if ( this . globalInlineEditListener ) { this . globalInlineEditListener ( ) ; this . globalInlineEditListener = null ; } } param filter public gtApplyFilter ( filter ) { this . gtInfo . filter = filter ; this . goToPage ( 1 ) ; this . updateTotals ( ) ; } /** Clear/remove applied filter(s). */ public gtClearFilter ( ) { this . gtInfo . filter = false ; this . updateTotals ( ) ; } param value public gtSearch ( value ) { this . gtInfo . searchTerms = value ; this . goToPage ( 1 ) ; this . updateTotals ( ) ; } param rows returns public gtAdd ( rows ) { this . gtData = [ ... this . gtData , ... rows ] ; return [ ... this . gtData ] ; } param objectKey param value param match returns public gtDelete ( objectKey , value , match = 'first' ) { if ( match === 'first' ) { for ( let i = 0 ; i < this . gtData . length ; i ++ ) { if ( this . gtData [ i ] [ objectKey ] === value ) { if ( this . isRowSelected ( this . gtData [ i ] ) ) { this . toggleSelect ( this . gtData [ i ] ) ; } this . gtData . splice ( i , 1 ) ; this . gtData = [ ... this . gtData ] ; if ( match === 'first' ) { break ; } } } } else { for ( let i = this . gtData . length ; i > 0 ; i -- ) { if ( this . gtData [ i - 1 ] [ objectKey ] === value ) { if ( this . isRowSelected ( this . gtData [ i - 1 ] ) ) { this . toggleSelect ( this . gtData [ i - 1 ] ) ; } this . gtData . splice ( i - 1 , 1 ) ; this . gtData = [ ... this . gtData ] ; } } } return [ ... this . gtData ] ; } param records param perPage returns private createStore ( records , perPage ) { const stores = Math . ceil ( records / perPage ) ; const store = [ ] ; for ( let i = 0 ; i < stores ; i ++ ) { store [ i ] = [ ] ; } return store ; } param perPage returns private loadingContent ( perPage ) { const rowObject = { $$loading : true } ; let order = 0 ; this . _gtSettings . sort ( this . getColumnOrder ) ; for ( let i = 0 ; i < this . _gtSettings . length ; i ++ ) { const setting = this . _gtSettings [ i ] ; if ( setting . visible !== false && setting . enabled !== false ) { if ( order === 0 ) { rowObject [ setting . objectKey ] = this . gtTexts . loading ; this . loadingProperty = setting . objectKey ; } else { rowObject [ setting . objectKey ] = '' ; } order ++ ; } else { rowObject [ setting . objectKey ] = '' ; } } const contentPlaceholder = [ ] ; for ( let i = 0 ; i < perPage ; i ++ ) { contentPlaceholder . push ( rowObject ) ; } return contentPlaceholder ; } /** Sort by sort order */ private getSortOrder = function ( a , b ) { if ( a . sortOrder < b . sortOrder ) { return - 1 ; } if ( a . sortOrder > b . sortOrder || typeof a . sortOrder === 'undefined' ) { return 1 ; } return 0 ; } ; /** Sort by column order */ private getColumnOrder = function ( a , b ) { if ( a . columnOrder === undefined ) { return - 1 ; } if ( a . columnOrder < b . columnOrder ) { return - 1 ; } if ( a . columnOrder > b . columnOrder ) { return 1 ; } return 0 ; } ; /** Create a deep copy of data */ private cloneDeep = function ( o ) { return JSON . parse ( JSON . stringify ( o ) ) ; } ; param fileName param useBOM public exportCSV ( fileName ? , useBOM = false ) { const data = this . data . exportData ; let csv = '' ; const BOM = '\uFEFF' ; for ( let i = 0 ; i < this . _gtSettings . length ; i ++ ) { if ( this . _gtSettings [ i ] . export !== false ) { const fieldSetting = this . getProperty ( this . _gtFields , this . _gtSettings [ i ] . objectKey ) ; const exportValue = fieldSetting . exportHeader ? fieldSetting . exportHeader : fieldSetting . name ; csv += this . escapeCSVDelimiter ( exportValue ) ; csv += this . getProperty ( this . _gtFields , this . _gtSettings [ i ] . objectKey ) . name ; if ( i < this . _gtSettings . length - 1 ) { csv += this . _gtOptions . csvDelimiter ; } } } data . forEach ( row => { csv += '\n' ; for ( let i = 0 ; i < this . _gtSettings . length ; i ++ ) { if ( this . _gtSettings [ i ] . export !== false ) { const fieldSetting = this . getProperty ( this . _gtFields , this . _gtSettings [ i ] . objectKey ) ; const exportValue = fieldSetting . export && typeof fieldSetting . export === 'function' ? fieldSetting . export ( row ) : fieldSetting . value && typeof fieldSetting . value === 'function' ? fieldSetting . value ( row ) : row [ this . _gtSettings [ i ] . objectKey ] ; csv += this . escapeCSVDelimiter ( exportValue ) ; if ( i < this . _gtSettings . length - 1 ) { csv += this . _gtOptions . csvDelimiter ; } } } } ) ; const blob = new Blob ( [ ( useBOM ? BOM : '' ) + csv ] , { type : 'text/csv;charset=utf-8' } ) ; if ( window . navigator . msSaveOrOpenBlob ) { navigator . msSaveOrOpenBlob ( blob , fileName ? fileName + '.csv' : this . gtTexts . csvDownload + '.csv' ) ; } else { const link = document . createElement ( 'a' ) ; link . style . display = 'none' ; document . body . appendChild ( link ) ; if ( link . download !== undefined ) { link . setAttribute ( 'href' , 'data:text/csv;charset=utf-8,' + encodeURIComponent ( ( useBOM ? BOM : '' ) + csv ) ) ; link . setAttribute ( 'download' , fileName ? fileName + '.csv' : this . gtTexts . csvDownload + '.csv' ) ; document . body . appendChild ( link ) ; link . click ( ) ; } else { csv = 'data:text/csv;charset=utf-8,' + ( useBOM ? BOM : '' ) + csv ; window . open ( encodeURIComponent ( csv ) ) ; } document . body . removeChild ( link ) ; } this . gtEvent . emit ( { name : 'gt-exported-csv' , value : fileName ? fileName : this . gtTexts . csvDownload + '.csv' } ) ; } /** Return property */ private getProperty = function ( array , key ) { for ( let i = 0 ; i < array . length ; i ++ ) { if ( array [ i ] . objectKey === key ) { return array [ i ] ; } } } ; private restructureSorting = function ( ) { /** Check and store sort order upon initialization.
         *  This is done by checking sort properties in the settings array of the table, if no sorting is defined
         *  we'll sort the data by the first visible and enabled column in the table(ascending). Please note that actually
         *  sorting have to be done server side when lazy loading data for obvious reasons.  */ const sorting = [ ] ; if ( this . _gtSettings ) { this . _gtSettings . sort ( this . getSortOrder ) ; for ( let i = 0 ; i < this . _gtSettings . length ; i ++ ) { const setting = this . _gtSettings [ i ] ; if ( setting . sort === 'asc' ) { sorting . push ( setting . objectKey ) ; } else if ( setting . sort === 'desc' ) { sorting . push ( '-' + setting . objectKey ) ; } } if ( sorting . length === 0 ) { sorting . push ( '$$gtRowId' ) ; } } if ( this . sortOrder . length === 0 ) { this . sortOrder = sorting ; } } ; param value private escapeCSVDelimiter ( value ) { return typeof value === 'string' && value . indexOf ( this . _gtOptions . csvDelimiter ) !== - 1 ? '"' + value + '"' : value ; } ngOnInit ( ) { if ( ! this . gtOptions . numberOfRows ) { this . changeRowLength ( this . gtOptions . numberOfRows ) ; } this . restructureSorting ( ) ; } /**
     *  Extend object function.
     */ private extend = function ( a , b ) { for ( const key in b ) { if ( b . hasOwnProperty ( key ) ) { a [ key ] = b [ key ] ; } } return a ; } ; ngOnChanges ( changes ) { if ( changes [ 'gtTexts' ] ) { this . gtTexts = < GtTexts > this . extend ( this . gtDefaultTexts , this . gtTexts ) ; } if ( this . gtOptions . lazyLoad && this . gtInfo ) { this . gtInfo . pageTotal = Math . ceil ( this . gtInfo . recordsAfterSearch / this . gtInfo . recordLength ) ; const storePosition = this . gtInfo . pageCurrent - 1 ; if ( this . store . length === 0 || this . store [ 0 ] . length !== this . gtInfo . recordLength ) { this . store = this . createStore ( this . gtInfo . recordsAfterSearch , this . gtInfo . recordLength ) ; } this . store [ storePosition ] = this . gtData ; this . gtInfo . visibleRecords = [ ... this . gtData ] ; this . _gtData = this . store ; this . loading = false ; this . updateRecordRange ( ) ; this . gtEvent . emit ( { name : 'gt-info' , value : this . gtInfo } ) ; } else if ( this . _gtData && this . _gtData . length >= 0 && changes [ 'gtData' ] && changes [ 'gtData' ] . previousValue ) { this . loading = false ; } else if ( changes [ 'gtData' ] && changes [ 'gtData' ] . firstChange && this . _gtData && this . _gtData . length > 0 ) { this . loading = false ; } } trackByFn ( index , item ) { return item . $$gtRowId ; } trackByColumnFn ( index , item ) { return item . objectKey ; } ngOnDestroy ( ) { this . _stopListeningForKeydownEvent ( ) ; } }