license import { MDCComponent } from '@material/base/component' ; import { SpecificEventListener } from '@material/base/types' ; import { MDCCheckbox , MDCCheckboxFactory } from '@material/checkbox/component' ; import { closest } from '@material/dom/ponyfill' ; import { MDCLinearProgress } from '@material/linear-progress/component' ; import { MDCDataTableAdapter } from './adapter' ; import { cssClasses , dataAttributes , events , messages , selectors , SortValue } from './constants' ; import { MDCDataTableFoundation } from './foundation' ; import { MDCDataTableRowSelectionChangedEventDetail , RowClickEventData } from './types' ; /**
 * Implementation of `MDCDataTableFoundation`
 */ export class MDCDataTable extends MDCComponent < MDCDataTableFoundation > { static override attachTo ( root ) { return new MDCDataTable ( root ) ; } private headerRowCheckbox ! : MDCCheckbox ; private rowCheckboxList ! : MDCCheckbox [ ] ; private checkboxFactory ! : MDCCheckboxFactory ; private linearProgress ! : MDCLinearProgress ; private headerRow ! : HTMLElement ; private content ! : HTMLElement ; private handleHeaderRowCheckboxChange ! : SpecificEventListener < 'change' > ; private handleRowCheckboxChange ! : SpecificEventListener < 'change' > ; private headerRowClickListener ! : SpecificEventListener < 'click' > ; private handleContentClick ! : SpecificEventListener < 'click' > ; override initialize ( checkboxFactory = ( el ) => new MDCCheckbox ( el ) ) { this . checkboxFactory = checkboxFactory ; } override initialSyncWithDOM ( ) { this . headerRow = this . root . querySelector ( `.${ cssClasses . HEADER_ROW }` ) as HTMLElement ; this . handleHeaderRowCheckboxChange = ( ) => { this . foundation . handleHeaderRowCheckboxChange ( ) ; } ; this . headerRow . addEventListener ( 'change' , this . handleHeaderRowCheckboxChange ) ; this . headerRowClickListener = ( event ) => { this . handleHeaderRowClick ( event ) ; } ; this . headerRow . addEventListener ( 'click' , this . headerRowClickListener ) ; this . content = this . root . querySelector ( `.${ cssClasses . CONTENT }` ) as HTMLElement ; this . handleContentClick = ( event ) => { const dataRowEl = closest ( event . target as Element , selectors . ROW ) as HTMLElement ; if ( ! dataRowEl ) return ; this . foundation . handleRowClick ( { rowId : this . getRowIdByRowElement ( dataRowEl ) , row : dataRowEl , } ) ; } ; this . content . addEventListener ( 'click' , this . handleContentClick ) ; this . handleRowCheckboxChange = ( event ) => { this . foundation . handleRowCheckboxChange ( event ) ; } ; this . content . addEventListener ( 'change' , this . handleRowCheckboxChange ) ; this . layout ( ) ; } /**
     * Re-initializes header row checkbox and row checkboxes when selectable rows
     * are added or removed from table.
     */ layout ( ) { this . foundation . layout ( ) ; } return getHeaderCells ( ) { return [ ] . slice . call ( this . root . querySelectorAll ( selectors . HEADER_CELL ) ) ; } return getRows ( ) { return this . foundation . getRows ( ) ; } return getSelectedRowIds ( ) { return this . foundation . getSelectedRowIds ( ) ; } param rowIds setSelectedRowIds ( rowIds ) { this . foundation . setSelectedRowIds ( rowIds ) ; } /**
     * Shows progress indicator when data table is in loading state.
     */ showProgress ( ) { this . getLinearProgress ( ) . open ( ) ; this . foundation . showProgress ( ) ; } /**
     * Hides progress indicator after data table is finished loading.
     */ hideProgress ( ) { this . foundation . hideProgress ( ) ; this . getLinearProgress ( ) . close ( ) ; } override destroy ( ) { if ( this . handleHeaderRowCheckboxChange ) { this . headerRow . removeEventListener ( 'change' , this . handleHeaderRowCheckboxChange ) ; } if ( this . headerRowClickListener ) { this . headerRow . removeEventListener ( 'click' , this . headerRowClickListener ) ; } if ( this . handleRowCheckboxChange ) { this . content . removeEventListener ( 'change' , this . handleRowCheckboxChange ) ; } if ( this . headerRowCheckbox ) { this . headerRowCheckbox . destroy ( ) ; } if ( this . rowCheckboxList ) { for ( const checkbox of this . rowCheckboxList ) { checkbox . destroy ( ) ; } } if ( this . handleContentClick ) { this . content . removeEventListener ( 'click' , this . handleContentClick ) ; } } override getDefaultFoundation ( ) { const adapter = { addClass : ( className ) => { this . root . classList . add ( className ) ; } , removeClass : ( className ) => { this . root . classList . remove ( className ) ; } , getHeaderCellElements : ( ) => this . getHeaderCells ( ) , getHeaderCellCount : ( ) => this . getHeaderCells ( ) . length , getAttributeByHeaderCellIndex : ( index , attribute ) => { return this . getHeaderCells ( ) [ index ] . getAttribute ( attribute ) ; } , setAttributeByHeaderCellIndex : ( index , attribute , value ) => { this . getHeaderCells ( ) [ index ] . setAttribute ( attribute , value ) ; } , setClassNameByHeaderCellIndex : ( index , className ) => { this . getHeaderCells ( ) [ index ] . classList . add ( className ) ; } , removeClassNameByHeaderCellIndex : ( index , className ) => { this . getHeaderCells ( ) [ index ] . classList . remove ( className ) ; } , notifySortAction : ( data ) => { this . emit ( events . SORTED , data , true ) ; } , getTableContainerHeight : ( ) => { const tableContainer = this . root . querySelector < HTMLElement > ( `.${ cssClasses . TABLE_CONTAINER }` ) ; if ( ! tableContainer ) { throw new Error ( 'MDCDataTable: Table container element not found.' ) ; } return tableContainer . getBoundingClientRect ( ) . height ; } , getTableHeaderHeight : ( ) => { const tableHeader = this . root . querySelector < HTMLElement > ( selectors . HEADER_ROW ) ; if ( ! tableHeader ) { throw new Error ( 'MDCDataTable: Table header element not found.' ) ; } return tableHeader . getBoundingClientRect ( ) . height ; } , setProgressIndicatorStyles : ( styles ) => { const progressIndicator = this . root . querySelector < HTMLElement > ( selectors . PROGRESS_INDICATOR ) ; if ( ! progressIndicator ) { throw new Error ( 'MDCDataTable: Progress indicator element not found.' ) ; } progressIndicator . style . setProperty ( 'height' , styles . height ) ; progressIndicator . style . setProperty ( 'top' , styles . top ) ; } , addClassAtRowIndex : ( rowIndex , className ) => { this . getRows ( ) [ rowIndex ] . classList . add ( className ) ; } , getRowCount : ( ) => this . getRows ( ) . length , getRowElements : ( ) => [ ] . slice . call ( this . root . querySelectorAll ( selectors . ROW ) ) , getRowIdAtIndex : ( rowIndex ) => this . getRows ( ) [ rowIndex ] . getAttribute ( dataAttributes . ROW_ID ) , getRowIndexByChildElement : ( el ) => { return this . getRows ( ) . indexOf ( ( closest ( el , selectors . ROW ) as HTMLElement ) ) ; } , getSelectedRowCount : ( ) => this . root . querySelectorAll ( selectors . ROW_SELECTED ) . length , isCheckboxAtRowIndexChecked : ( rowIndex ) => this . rowCheckboxList [ rowIndex ] . checked , isHeaderRowCheckboxChecked : ( ) => this . headerRowCheckbox . checked , isRowsSelectable : ( ) => ! ! this . root . querySelector ( selectors . ROW_CHECKBOX ) || ! ! this . root . querySelector ( selectors . HEADER_ROW_CHECKBOX ) , notifyRowSelectionChanged : ( data ) => { this . emit ( events . ROW_SELECTION_CHANGED , { row : this . getRowByIndex ( data . rowIndex ) , rowId : this . getRowIdByIndex ( data . rowIndex ) , rowIndex : data . rowIndex , selected : data . selected , } , true ) ; } , notifySelectedAll : ( ) => { this . emit ( events . SELECTED_ALL , { } , true ) ; } , notifyUnselectedAll : ( ) => { this . emit ( events . UNSELECTED_ALL , { } , true ) ; } , notifyRowClick : ( data ) => { this . emit ( events . ROW_CLICK , data , true ) ; } , registerHeaderRowCheckbox : ( ) => { if ( this . headerRowCheckbox ) { this . headerRowCheckbox . destroy ( ) ; } const checkboxEl = ( this . root . querySelector ( selectors . HEADER_ROW_CHECKBOX ) as HTMLElement ) ; this . headerRowCheckbox = this . checkboxFactory ( checkboxEl ) ; } , registerRowCheckboxes : ( ) => { if ( this . rowCheckboxList ) { this . rowCheckboxList . forEach ( ( checkbox ) => { checkbox . destroy ( ) ; } ) ; } this . rowCheckboxList = [ ] ; this . getRows ( ) . forEach ( ( rowEl ) => { const checkbox = this . checkboxFactory ( ( rowEl . querySelector ( selectors . ROW_CHECKBOX ) as HTMLElement ) ) ; this . rowCheckboxList . push ( checkbox ) ; } ) ; } , removeClassAtRowIndex : ( rowIndex , className ) => { this . getRows ( ) [ rowIndex ] . classList . remove ( className ) ; } , setAttributeAtRowIndex : ( rowIndex , attr , value ) => { this . getRows ( ) [ rowIndex ] . setAttribute ( attr , value ) ; } , setHeaderRowCheckboxChecked : ( checked ) => { this . headerRowCheckbox . checked = checked ; } , setHeaderRowCheckboxIndeterminate : ( indeterminate ) => { this . headerRowCheckbox . indeterminate = indeterminate ; } , setRowCheckboxCheckedAtIndex : ( rowIndex , checked ) => { this . rowCheckboxList [ rowIndex ] . checked = checked ; } , setSortStatusLabelByHeaderCellIndex : ( columnIndex , sortValue ) => { const headerCell = this . getHeaderCells ( ) [ columnIndex ] ; const sortStatusLabel = headerCell . querySelector < HTMLElement > ( selectors . SORT_STATUS_LABEL ) ; if ( ! sortStatusLabel ) return ; sortStatusLabel . textContent = this . getSortStatusMessageBySortValue ( sortValue ) ; } , } ; return new MDCDataTableFoundation ( adapter ) ; } private getRowByIndex ( index ) { return this . getRows ( ) [ index ] ; } private getRowIdByIndex ( index ) { return this . getRowByIndex ( index ) . getAttribute ( dataAttributes . ROW_ID ) ; } private handleHeaderRowClick ( event ) { const headerCell = closest ( event . target as Element , selectors . HEADER_CELL_WITH_SORT ) as HTMLElement ; if ( ! headerCell ) { return ; } const columnId = headerCell . getAttribute ( dataAttributes . COLUMN_ID ) ; const columnIndex = this . getHeaderCells ( ) . indexOf ( headerCell ) ; if ( columnIndex === - 1 ) { return ; } this . foundation . handleSortAction ( { columnId , columnIndex , headerCell } ) ; } private getSortStatusMessageBySortValue ( sortValue ) { switch ( sortValue ) { case SortValue . ASCENDING : return messages . SORTED_IN_ASCENDING ; case SortValue . DESCENDING : return messages . SORTED_IN_DESCENDING ; default : return '' ; } } private getLinearProgressElement ( ) { const el = this . root . querySelector < HTMLElement > ( `.${ cssClasses . LINEAR_PROGRESS }` ) ; if ( ! el ) { throw new Error ( 'MDCDataTable: linear progress element is not found.' ) ; } return el ; } private getLinearProgress ( ) { if ( ! this . linearProgress ) { const el = this . getLinearProgressElement ( ) ; this . linearProgress = new MDCLinearProgress ( el ) ; } return this . linearProgress ; } private getRowIdByRowElement ( rowElement ) { return rowElement . getAttribute ( dataAttributes . ROW_ID ) ; } }